[PATCH 1/3] ASoC: Add support for Loongson I2S controller
From: Yingkun Meng mengyingkun@loongson.cn
Loongson I2S controller is found on 7axxx/2kxxx chips from loongson, it is a PCI device with two private DMA controllers, one for playback, the other for capture.
The driver supports the use of DTS or ACPI to describe device resources.
Signed-off-by: Yingkun Meng mengyingkun@loongson.cn --- sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/loongson/Kconfig | 15 + sound/soc/loongson/Makefile | 4 + sound/soc/loongson/loongson_i2s.c | 209 +++++++++++ sound/soc/loongson/loongson_i2s.h | 70 ++++ sound/soc/loongson/loongson_i2s_pci.c | 478 ++++++++++++++++++++++++++ 7 files changed, 778 insertions(+) create mode 100644 sound/soc/loongson/Kconfig create mode 100644 sound/soc/loongson/Makefile create mode 100644 sound/soc/loongson/loongson_i2s.c create mode 100644 sound/soc/loongson/loongson_i2s.h create mode 100644 sound/soc/loongson/loongson_i2s_pci.c
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 4b6e5a802880..bfa9622e1ab1 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -79,6 +79,7 @@ source "sound/soc/google/Kconfig" source "sound/soc/hisilicon/Kconfig" source "sound/soc/jz4740/Kconfig" source "sound/soc/kirkwood/Kconfig" +source "sound/soc/loongson/Kconfig" source "sound/soc/img/Kconfig" source "sound/soc/intel/Kconfig" source "sound/soc/mediatek/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 9d9b228e4508..8376fdb217ed 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -46,6 +46,7 @@ obj-$(CONFIG_SND_SOC) += fsl/ obj-$(CONFIG_SND_SOC) += google/ obj-$(CONFIG_SND_SOC) += hisilicon/ obj-$(CONFIG_SND_SOC) += jz4740/ +obj-$(CONFIG_SND_SOC) += loongson/ obj-$(CONFIG_SND_SOC) += img/ obj-$(CONFIG_SND_SOC) += intel/ obj-$(CONFIG_SND_SOC) += mediatek/ diff --git a/sound/soc/loongson/Kconfig b/sound/soc/loongson/Kconfig new file mode 100644 index 000000000000..fd477e7da6e0 --- /dev/null +++ b/sound/soc/loongson/Kconfig @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0 +menu "SoC Audio for Loongson CPUs" + depends on LOONGARCH || COMPILE_TEST + +config SND_SOC_LOONGSON_I2S_PCI + tristate "Loongson I2S-PCI Device Driver" + select REGMAP_MMIO + help + Say Y or M if you want to add support for I2S driver for + Loongson I2S controller. + + The controller is found in loongson bridge chips or SoCs, + and work as a PCI device. + +endmenu diff --git a/sound/soc/loongson/Makefile b/sound/soc/loongson/Makefile new file mode 100644 index 000000000000..cfd0de1b1b22 --- /dev/null +++ b/sound/soc/loongson/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 +#Platform Support +snd-soc-loongson-i2s-pci-objs := loongson_i2s_pci.o loongson_i2s.o +obj-$(CONFIG_SND_SOC_LOONGSON_I2S_PCI) += snd-soc-loongson-i2s-pci.o diff --git a/sound/soc/loongson/loongson_i2s.c b/sound/soc/loongson/loongson_i2s.c new file mode 100644 index 000000000000..00793964237f --- /dev/null +++ b/sound/soc/loongson/loongson_i2s.c @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Loongson-2K I2S master mode driver + * + * Copyright (C) 2022 Loongson Technology Corporation Limited + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/pm_runtime.h> +#include <linux/dma-mapping.h> +#include <sound/soc.h> +#include <linux/regmap.h> +#include <sound/pcm_params.h> +#include "loongson_i2s.h" + +#define LOONGSON_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +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); + u32 val; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + /* Enable MCLK */ + if (i2s->rev_id == 1) { + regmap_update_bits(i2s->regmap, LS_I2S_CTRL, + I2S_CTRL_MCLK_EN, + I2S_CTRL_MCLK_EN); + regmap_read_poll_timeout_atomic(i2s->regmap, + LS_I2S_CTRL, val, + !(val & I2S_CTRL_MCLK_READY), + 10, 2000); + } + + /* Enable master mode */ + regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MASTER, + I2S_CTRL_MASTER); + if (i2s->rev_id == 1) { + regmap_read_poll_timeout_atomic(i2s->regmap, + LS_I2S_CTRL, val, + !(val & I2S_CTRL_CLK_READY), + 10, 2000); + } + fallthrough; + 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); + 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); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int loongson_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai); + u32 clk_rate = i2s->clk_rate; + u32 sysclk = i2s->sysclk; + u32 bits = params_width(params); + u32 chans = params_channels(params); + u32 fs = params_rate(params); + u32 bclk_ratio, mclk_ratio; + u32 mclk_ratio_frac; + u32 val = 0; + + if (i2s->rev_id == 0) { + bclk_ratio = DIV_ROUND_CLOSEST(clk_rate, + (bits * chans * fs * 2)) - 1; + mclk_ratio = DIV_ROUND_CLOSEST(clk_rate, (sysclk * 2)) - 1; + + /* According to 2k1000LA user manual, set bits == depth */ + val |= (bits << 24); + val |= (bits << 16); + val |= (bclk_ratio << 8); + val |= mclk_ratio; + regmap_write(i2s->regmap, LS_I2S_CFG, val); + } else if (i2s->rev_id == 1) { + bclk_ratio = DIV_ROUND_CLOSEST(sysclk, + (bits * chans * fs * 2)) - 1; + mclk_ratio = clk_rate / sysclk; + mclk_ratio_frac = DIV_ROUND_CLOSEST(((u64)clk_rate << 16), + sysclk) - (mclk_ratio << 16); + + regmap_read(i2s->regmap, LS_I2S_CFG, &val); + val |= (bits << 24); + val |= (bclk_ratio << 8); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + val |= (bits << 16); + else + val |= bits; + regmap_write(i2s->regmap, LS_I2S_CFG, val); + + val = (mclk_ratio_frac << 16) | mclk_ratio; + regmap_write(i2s->regmap, LS_I2S_CFG1, val); + } else + dev_err(i2s->dev, "I2S revision invalid\n"); + + return 0; +} + +static int loongson_i2s_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + i2s->sysclk = freq; + + return 0; +} + +static const struct snd_soc_dai_ops loongson_i2s_dai_ops = { + .trigger = loongson_i2s_trigger, + .hw_params = loongson_i2s_hw_params, + .set_sysclk = loongson_i2s_set_dai_sysclk, +}; + +static int loongson_i2s_dai_probe(struct snd_soc_dai *cpu_dai) +{ + struct loongson_i2s *i2s = dev_get_drvdata(cpu_dai->dev); + + snd_soc_dai_init_dma_data(cpu_dai, &i2s->playback_dma_data, + &i2s->capture_dma_data); + snd_soc_dai_set_drvdata(cpu_dai, i2s); + + return 0; +} + +struct snd_soc_dai_driver loongson_i2s_dai = { + .name = "loongson-i2s", + .probe = loongson_i2s_dai_probe, + .playback = { + .stream_name = "CPU-Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = LOONGSON_I2S_FORMATS, + }, + .capture = { + .stream_name = "CPU-Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = LOONGSON_I2S_FORMATS, + }, + .ops = &loongson_i2s_dai_ops, + .symmetric_rate = 1, +}; + +static int i2s_suspend(struct device *dev) +{ + struct loongson_i2s *i2s = dev_get_drvdata(dev); + + regcache_cache_only(i2s->regmap, true); + + return 0; +} + +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; +} + +const struct dev_pm_ops loongson_i2s_pm = { + SET_SYSTEM_SLEEP_PM_OPS(i2s_suspend, i2s_resume) +}; + +void loongson_i2s_init(struct loongson_i2s *i2s) +{ + if (i2s->rev_id == 1) { + regmap_write(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_RESET); + udelay(200); + } +} diff --git a/sound/soc/loongson/loongson_i2s.h b/sound/soc/loongson/loongson_i2s.h new file mode 100644 index 000000000000..a743c34b8968 --- /dev/null +++ b/sound/soc/loongson/loongson_i2s.h @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * ALSA I2S interface for the Loongson chip + * + */ +#ifndef _LOONGSON_I2S_H +#define _LOONGSON_I2S_H + +#include <linux/regmap.h> +#include <sound/dmaengine_pcm.h> + +/* I2S Common Registers */ +#define LS_I2S_VER 0x00 /* I2S Version */ +#define LS_I2S_CFG 0x04 /* I2S Config */ +#define LS_I2S_CTRL 0x08 /* I2S Control */ +#define LS_I2S_RX_DATA 0x0C /* I2S DMA RX Address */ +#define LS_I2S_TX_DATA 0x10 /* I2S DMA TX Address */ + +/* 2K2000 I2S Specify Registers */ +#define LS_I2S_CFG1 0x14 /* I2S Config1 */ + +/* 7A2000 I2S Specify Registers */ +#define LS_I2S_TX_ORDER 0x100 /* TX DMA Order */ +#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 LS_I2S_DRVNAME "loongson-i2s" + +struct loongson_dma_data { + u64 dev_addr; /* device physical address for DMA */ + void __iomem *order_addr; /* DMA order register */ + u32 irq; /* DMA irq */ +}; + +struct loongson_i2s { + struct device *dev; + union { + struct snd_dmaengine_dai_dma_data playback_dma_data; + struct loongson_dma_data tx_dma_data; + }; + union { + struct snd_dmaengine_dai_dma_data capture_dma_data; + struct loongson_dma_data rx_dma_data; + }; + struct regmap *regmap; + void __iomem *reg_base; + u32 rev_id; + u32 clk_rate; + u32 sysclk; +}; + +extern const struct dev_pm_ops loongson_i2s_pm; +extern struct snd_soc_dai_driver loongson_i2s_dai; + +void loongson_i2s_init(struct loongson_i2s *i2s); + +#endif diff --git a/sound/soc/loongson/loongson_i2s_pci.c b/sound/soc/loongson/loongson_i2s_pci.c new file mode 100644 index 000000000000..e4b8c654b12c --- /dev/null +++ b/sound/soc/loongson/loongson_i2s_pci.c @@ -0,0 +1,478 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Loongson-2K I2S master mode driver + * + * Copyright (C) 2022 Loongson Technology Corporation Limited + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/pm_runtime.h> +#include <linux/dma-mapping.h> +#include <linux/acpi.h> +#include <linux/pci.h> +#include <sound/soc.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#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_ASK_MASK (~0x1fUL) /* Ask addr mask */ +#define DMA_ORDER_CTRL_MASK (0x0fUL) /* Control mask */ + +#define BAR_NUM 0 + +/* + * DMA registers descriptor. + */ +struct loongson_dma_desc { + u32 order; /* Next descriptor address register */ + u32 saddr; /* Source address register */ + u32 daddr; /* Device address register */ + u32 length; /* Total length register */ + u32 step_length; /* Memory stride register */ + u32 step_times; /* Repeat time register */ + u32 cmd; /* Command register */ + u32 stats; /* Status register */ + u32 order_hi; /* Next descriptor high address register */ + u32 saddr_hi; /* High source address register */ + u32 res[6]; /* Reserved */ +}; + +struct loongson_runtime_data { + struct loongson_dma_data *dma_data; + + struct loongson_dma_desc *dma_desc_arr; + dma_addr_t dma_desc_arr_phy; + + struct loongson_dma_desc *dma_pos_desc; + dma_addr_t dma_pos_desc_phy; +}; + +static const struct snd_pcm_hardware ls_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_S8 | + 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 = PAGE_SIZE / sizeof(struct loongson_dma_desc), + .buffer_bytes_max = 1024 * 1024, +}; + +static struct +loongson_dma_desc *dma_desc_save(struct loongson_runtime_data *prtd) +{ + void __iomem *order_reg = prtd->dma_data->order_addr; + u64 val; + + val = (u64)prtd->dma_pos_desc_phy & DMA_ORDER_ASK_MASK; + val |= (readq(order_reg) & DMA_ORDER_CTRL_MASK); + val |= DMA_ORDER_ASK_VALID; + writeq(val, order_reg); + + while (readl(order_reg) & DMA_ORDER_ASK_VALID) + udelay(2); + + return prtd->dma_pos_desc; +} + +static int loongson_pcm_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int cmd) +{ + struct loongson_runtime_data *prtd = substream->runtime->private_data; + 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: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + val = prtd->dma_pos_desc_phy & DMA_ORDER_ASK_MASK; + if (dev->coherent_dma_mask == DMA_BIT_MASK(64)) + val |= DMA_ORDER_ADDR_64; + else + val &= ~DMA_ORDER_ADDR_64; + val |= (readq(order_reg) & DMA_ORDER_CTRL_MASK); + val |= DMA_ORDER_START; + writeq(val, order_reg); + + while ((readl(order_reg) & DMA_ORDER_START)) + udelay(2); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dma_desc_save(prtd); + + /* dma stop */ + val = readq(order_reg) | DMA_ORDER_STOP; + writeq(val, order_reg); + udelay(1000); + + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int loongson_pcm_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct loongson_runtime_data *prtd = runtime->private_data; + size_t buf_len = params_buffer_bytes(params); + size_t period_len = params_period_bytes(params); + dma_addr_t order_addr, mem_addr; + struct loongson_dma_desc *desc; + u32 num_periods; + int i; + + if (buf_len % period_len) { + pr_err("buf len not multiply of period len\n"); + return -EINVAL; + } + + num_periods = buf_len / period_len; + if (!num_periods) { + pr_err("dma data too small\n"); + return -EINVAL; + } + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = buf_len; + + /* initialize dma descriptor array */ + mem_addr = runtime->dma_addr; + order_addr = prtd->dma_desc_arr_phy; + for (i = 0; i < num_periods; i++) { + desc = &prtd->dma_desc_arr[i]; + + /* next descriptor physical address */ + order_addr += sizeof(*desc); + desc->order = order_addr | BIT(0); + desc->order_hi = order_addr >> 32; + + desc->saddr = mem_addr; + desc->saddr_hi = mem_addr >> 32; + desc->daddr = prtd->dma_data->dev_addr; + + desc->cmd = BIT(0); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + desc->cmd |= BIT(12); + + desc->length = period_len >> 2; + desc->step_length = 0; + desc->step_times = 1; + + mem_addr += period_len; + } + if (num_periods > 0) { + desc = &prtd->dma_desc_arr[num_periods - 1]; + desc->order = prtd->dma_desc_arr_phy | BIT(0); + desc->order_hi = prtd->dma_desc_arr_phy >> 32; + } + + /* init position descriptor */ + *prtd->dma_pos_desc = *prtd->dma_desc_arr; + + return 0; +} + +static snd_pcm_uframes_t +loongson_pcm_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct loongson_runtime_data *prtd = runtime->private_data; + struct loongson_dma_desc *desc; + snd_pcm_uframes_t x; + u64 addr; + + desc = dma_desc_save(prtd); + addr = ((u64)desc->saddr_hi << 32) | desc->saddr; + + x = bytes_to_frames(runtime, addr - runtime->dma_addr); + if (x == runtime->buffer_size) + x = 0; + return x; +} + +static irqreturn_t loongson_pcm_dma_irq(int irq, void *devid) +{ + struct snd_pcm_substream *substream = devid; + + snd_pcm_period_elapsed(substream); + return IRQ_HANDLED; +} + +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_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) + * 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); + snd_soc_set_runtime_hwparams(substream, &ls_pcm_hardware); + + prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); + if (!prtd) + return -ENOMEM; + + 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; + goto desc_err; + } + + 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; + goto pos_err; + } + + dma_data = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream); + ret = request_irq(dma_data->irq, loongson_pcm_dma_irq, + IRQF_TRIGGER_RISING, LS_I2S_DRVNAME, substream); + if (ret < 0) + goto irq_err; + + prtd->dma_data = dma_data; + substream->runtime->private_data = prtd; + + return 0; +irq_err: + dma_free_coherent(card->dev, sizeof(*prtd->dma_pos_desc), + prtd->dma_pos_desc, prtd->dma_pos_desc_phy); +pos_err: + dma_free_coherent(card->dev, PAGE_SIZE, prtd->dma_desc_arr, + prtd->dma_desc_arr_phy); +desc_err: + kfree(prtd); + + return ret; +} + +static int loongson_pcm_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_card *card = substream->pcm->card; + struct loongson_runtime_data *prtd = substream->runtime->private_data; + struct loongson_dma_data *dma_data; + + dma_free_coherent(card->dev, PAGE_SIZE, prtd->dma_desc_arr, + prtd->dma_desc_arr_phy); + + dma_free_coherent(card->dev, sizeof(*prtd->dma_pos_desc), + prtd->dma_pos_desc, prtd->dma_pos_desc_phy); + + dma_data = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream); + free_irq(dma_data->irq, substream); + + kfree(prtd); + return 0; +} + +static int loongson_pcm_mmap(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + return remap_pfn_range(vma, vma->vm_start, + substream->dma_buffer.addr >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, vma->vm_page_prot); +} + +static int loongson_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + struct snd_pcm *pcm = rtd->pcm; + + return snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + pcm->card->dev, + ls_pcm_hardware.buffer_bytes_max); +} + +static const struct snd_soc_component_driver loongson_i2s_component = { + .name = LS_I2S_DRVNAME, + .open = loongson_pcm_open, + .close = loongson_pcm_close, + .hw_params = loongson_pcm_hw_params, + .trigger = loongson_pcm_trigger, + .pointer = loongson_pcm_pointer, + .mmap = loongson_pcm_mmap, + .pcm_construct = loongson_pcm_new, +}; + +static const struct regmap_config loongson_i2s_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x110, + .cache_type = REGCACHE_FLAT, +}; + +static int loongson_i2s_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *pid) +{ + const struct fwnode_handle *fwnode = pdev->dev.fwnode; + struct loongson_dma_data *tx_data, *rx_data; + struct loongson_i2s *i2s; + int ret; + + if (pci_enable_device(pdev)) { + dev_err(&pdev->dev, "pci_enable_device failed\n"); + return -ENODEV; + } + + ret = pci_request_region(pdev, BAR_NUM, LS_I2S_DRVNAME); + if (ret) { + dev_err(&pdev->dev, "request regions failed %d\n", ret); + goto err_disable; + } + + i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); + if (!i2s) { + ret = -ENOMEM; + goto err_release; + } + i2s->rev_id = pdev->revision; + i2s->dev = &pdev->dev; + pci_set_drvdata(pdev, i2s); + + i2s->reg_base = pci_iomap(pdev, BAR_NUM, 0); + if (!i2s->reg_base) { + dev_err(&pdev->dev, "pci_iomap_error\n"); + ret = -EIO; + goto err_release; + } + + i2s->regmap = devm_regmap_init_mmio(&pdev->dev, i2s->reg_base, + &loongson_i2s_regmap_config); + if (IS_ERR(i2s->regmap)) { + dev_err(&pdev->dev, "Failed to initialize register map"); + ret = PTR_ERR(i2s->regmap); + goto err_unmap; + } + + tx_data = &i2s->tx_dma_data; + rx_data = &i2s->rx_dma_data; + + tx_data->dev_addr = (u64)i2s->reg_base + LS_I2S_TX_DATA; + tx_data->order_addr = i2s->reg_base + LS_I2S_TX_ORDER; + + rx_data->dev_addr = (u64)i2s->reg_base + LS_I2S_RX_DATA; + 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"); + ret = tx_data->irq; + goto err_unmap; + } + + rx_data->irq = fwnode_irq_get_byname(fwnode, "rx"); + if (rx_data->irq < 0) { + dev_err(&pdev->dev, "dma rx irq invalid\n"); + ret = rx_data->irq; + goto err_unmap; + } + + device_property_read_u32(&pdev->dev, "clock-frequency", &i2s->clk_rate); + if (!i2s->clk_rate) { + dev_err(&pdev->dev, "clock-frequency property invalid\n"); + ret = -EINVAL; + goto err_unmap; + } + + dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + + dev_set_name(&pdev->dev, "%s", loongson_i2s_dai.name); + + loongson_i2s_init(i2s); + + ret = devm_snd_soc_register_component(&pdev->dev, + &loongson_i2s_component, + &loongson_i2s_dai, 1); + if (ret) { + dev_err(&pdev->dev, "register DAI failed %d\n", ret); + goto err_unmap; + } + + return 0; + +err_unmap: + pci_iounmap(pdev, i2s->reg_base); +err_release: + pci_release_region(pdev, BAR_NUM); +err_disable: + pci_disable_device(pdev); + return ret; +} + +static void loongson_i2s_pci_remove(struct pci_dev *pdev) +{ + struct loongson_i2s *i2s = dev_get_drvdata(&pdev->dev); + + pci_iounmap(pdev, i2s->reg_base); + pci_release_region(pdev, BAR_NUM); + pci_disable_device(pdev); +} + +static const struct pci_device_id loongson_i2s_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_LOONGSON, 0x7a27) }, + { }, +}; +MODULE_DEVICE_TABLE(pci, loongson_i2s_ids); + +static struct pci_driver loongson_i2s_driver = { + .name = "loongson-i2s-pci", + .id_table = loongson_i2s_ids, + .probe = loongson_i2s_pci_probe, + .remove = loongson_i2s_pci_remove, + .driver = { + .owner = THIS_MODULE, + .pm = pm_sleep_ptr(&loongson_i2s_pm), + }, +}; +module_pci_driver(loongson_i2s_driver); + +MODULE_DESCRIPTION("Loongson I2S Master Mode ASoC Driver"); +MODULE_AUTHOR("Loongson Technology Corporation Limited"); +MODULE_LICENSE("GPL");
base-commit: 1fbcc5ab1c7a172ef1159b154c296fe1e9ce209b
From: Yingkun Meng mengyingkun@loongson.cn
The Loongson Generic ASoC Sound Card is a general ASoC DAI Link driver that can be used for Loongson CPU DAI drivers and external CODECs.
The driver supports the use of ACPI table to describe device resources. On loongson 7axxx platforms, the audio device is an ACPI device.
Signed-off-by: Yingkun Meng mengyingkun@loongson.cn --- sound/soc/loongson/Kconfig | 10 ++ sound/soc/loongson/Makefile | 4 + sound/soc/loongson/loongson_card.c | 201 +++++++++++++++++++++++++++++ 3 files changed, 215 insertions(+) create mode 100644 sound/soc/loongson/loongson_card.c
diff --git a/sound/soc/loongson/Kconfig b/sound/soc/loongson/Kconfig index fd477e7da6e0..da471ba3b2de 100644 --- a/sound/soc/loongson/Kconfig +++ b/sound/soc/loongson/Kconfig @@ -12,4 +12,14 @@ 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 + 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. + endmenu diff --git a/sound/soc/loongson/Makefile b/sound/soc/loongson/Makefile index cfd0de1b1b22..a173a0fe17fe 100644 --- a/sound/soc/loongson/Makefile +++ b/sound/soc/loongson/Makefile @@ -2,3 +2,7 @@ #Platform Support snd-soc-loongson-i2s-pci-objs := loongson_i2s_pci.o loongson_i2s.o obj-$(CONFIG_SND_SOC_LOONGSON_I2S_PCI) += snd-soc-loongson-i2s-pci.o + +#Machine Support +snd-soc-loongson-card-objs := loongson_card.o +obj-$(CONFIG_SND_SOC_LOONGSON_CARD) += snd-soc-loongson-card.o diff --git a/sound/soc/loongson/loongson_card.c b/sound/soc/loongson/loongson_card.c new file mode 100644 index 000000000000..bedf799bd7f2 --- /dev/null +++ b/sound/soc/loongson/loongson_card.c @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Loongson ASoC Audio driver + * + * Copyright (C) 2022 Loongson Technology Corporation Limited + */ + +#include <linux/module.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <linux/acpi.h> +#include <linux/pci.h> +#include <sound/pcm_params.h> + +struct loongson_card_data { + struct snd_soc_card snd_card; + unsigned int mclk_fs; +}; + +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 = asoc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct loongson_card_data *ls_card = snd_soc_card_get_drvdata(rtd->card); + 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; + } + + 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; +} + +static const struct snd_soc_ops loongson_ops = { + .hw_params = loongson_card_hw_params, +}; + +SND_SOC_DAILINK_DEFS(analog, + DAILINK_COMP_ARRAY(COMP_CPU("loongson-i2s")), + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_PLATFORM("loongson-i2s"))); + +static struct snd_soc_dai_link loongson_dai_links[] = { + { + .name = "HiFi PAIF TX", + .stream_name = "Playback", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_IB_NF + | SND_SOC_DAIFMT_CBC_CFC, + SND_SOC_DAILINK_REG(analog), + .ops = &loongson_ops, + }, + { + .name = "HiFi PAIF RX", + .stream_name = "Capture", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_IB_NF + | SND_SOC_DAIFMT_CBC_CFC, + SND_SOC_DAILINK_REG(analog), + .ops = &loongson_ops, + }, +}; + +static int loongson_card_parse_of(struct loongson_card_data *data) +{ + const char *cpu_dai_name, *codec_dai_name; + struct device_node *cpu, *codec; + struct snd_soc_card *card = &data->snd_card; + struct device *dev = card->dev; + struct of_phandle_args args; + int ret, i; + + cpu = of_get_child_by_name(dev->of_node, "cpu"); + if (!cpu) { + dev_err(dev, "platform property missing or invalid\n"); + return -EINVAL; + } + 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; + } + + ret = of_parse_phandle_with_args(cpu, "sound-dai", + "#sound-dai-cells", 0, &args); + if (ret) { + dev_err(dev, "codec node missing #sound-dai-cells\n"); + goto err; + } + for (i = 0; i < card->num_links; i++) + loongson_dai_links[i].cpus->of_node = args.np; + + ret = of_parse_phandle_with_args(codec, "sound-dai", + "#sound-dai-cells", 0, &args); + if (ret) { + dev_err(dev, "codec node missing #sound-dai-cells\n"); + goto err; + } + for (i = 0; i < card->num_links; i++) + loongson_dai_links[i].codecs->of_node = args.np; + + snd_soc_of_get_dai_name(cpu, &cpu_dai_name); + snd_soc_of_get_dai_name(codec, &codec_dai_name); + for (i = 0; i < card->num_links; i++) { + loongson_dai_links[i].cpus->dai_name = cpu_dai_name; + loongson_dai_links[i].codecs->dai_name = codec_dai_name; + } + of_node_put(cpu); + of_node_put(codec); + + return 0; + +err: + of_node_put(cpu); + of_node_put(codec); + return ret; +} + +static int loongson_asoc_card_probe(struct platform_device *pdev) +{ + struct loongson_card_data *ls_priv; + struct snd_soc_card *card; + const char *codec_name, *codec_dai_name; + int ret, i; + + ls_priv = devm_kzalloc(&pdev->dev, sizeof(*ls_priv), GFP_KERNEL); + if (!ls_priv) + return -ENOMEM; + + card = &ls_priv->snd_card; + + card->dev = &pdev->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; + } + + if (has_acpi_companion(&pdev->dev)) { + device_property_read_string(&pdev->dev, "codec-name", + &codec_name); + for (i = 0; i < card->num_links; i++) + loongson_dai_links[i].codecs->name = codec_name; + + device_property_read_string(&pdev->dev, "codec-dai-name", + &codec_dai_name); + for (i = 0; i < card->num_links; i++) + loongson_dai_links[i].codecs->dai_name = codec_dai_name; + } else { + ret = loongson_card_parse_of(ls_priv); + if (ret) + return ret; + } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + + return ret; +} + +static const struct of_device_id loongson_asoc_dt_ids[] = { + { .compatible = "loongson,ls-audio-card" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, loongson_asoc_dt_ids); + +static struct platform_driver loongson_audio_driver = { + .probe = loongson_asoc_card_probe, + .driver = { + .name = "loongson-asoc-card", + .pm = &snd_soc_pm_ops, + .of_match_table = of_match_ptr(loongson_asoc_dt_ids), + }, +}; +module_platform_driver(loongson_audio_driver); + +MODULE_DESCRIPTION("Loongson ASoc Sound Card driver"); +MODULE_AUTHOR("Loongson Technology Corporation Limited"); +MODULE_LICENSE("GPL");
On Mon, Jun 05, 2023 at 08:09:33PM +0800, YingKun Meng wrote:
The driver supports the use of ACPI table to describe device resources. On loongson 7axxx platforms, the audio device is an ACPI device.
I'm not seeing any ACPI IDs added here, this is all DT other than this:
- if (has_acpi_companion(&pdev->dev)) {
device_property_read_string(&pdev->dev, "codec-name",
&codec_name);
for (i = 0; i < card->num_links; i++)
loongson_dai_links[i].codecs->name = codec_name;
device_property_read_string(&pdev->dev, "codec-dai-name",
&codec_dai_name);
for (i = 0; i < card->num_links; i++)
loongson_dai_links[i].codecs->dai_name = codec_dai_name;
- } else {
which doesn't look *great* from an ACPI point of view TBH. I thought there were now some (semi?) standard ACPI bindings these days to support referencing other devices better?
Hi Brown,
Thanks for your kindly work.
On 2023/6/5 20:47, Mark Brown wrote:
On Mon, Jun 05, 2023 at 08:09:33PM +0800, YingKun Meng wrote:
The driver supports the use of ACPI table to describe device resources. On loongson 7axxx platforms, the audio device is an ACPI device.
I'm not seeing any ACPI IDs added here, this is all DT other than this:
I use the special ACPI ID "PRP0001", it provides a means to use the existing
DT-compatible device identification in ACPI.
As described in the document "ACPI Based Device Enumeration".
- if (has_acpi_companion(&pdev->dev)) {
device_property_read_string(&pdev->dev, "codec-name",
&codec_name);
for (i = 0; i < card->num_links; i++)
loongson_dai_links[i].codecs->name = codec_name;
device_property_read_string(&pdev->dev, "codec-dai-name",
&codec_dai_name);
for (i = 0; i < card->num_links; i++)
loongson_dai_links[i].codecs->dai_name = codec_dai_name;
- } else {
which doesn't look *great* from an ACPI point of view TBH. I thought there were now some (semi?) standard ACPI bindings these days to support referencing other devices better?
Got it! I will rewrite this part.
From: Yingkun Meng mengyingkun@loongson.cn
The audio card uses loongson I2S controller present in 7axxx/2kxxx chips to transfer audio data.
Signed-off-by: Yingkun Meng mengyingkun@loongson.cn --- .../sound/loongson,ls-audio-card.yaml | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml
diff --git a/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml b/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml new file mode 100644 index 000000000000..f1d6ee346bb3 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/loongson-audio-card.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Loongson generic ASoC audio sound card. + +maintainers: + - Yingkun Meng mengyingkun@loongson.cn + +description: + Generic ASoC audio device for loongson platform. + +properties: + compatible: + const: loongson,ls-audio-card + + model: + $ref: /schemas/types.yaml#/definitions/string + description: User specified audio sound card name + + mclk-fs: + $ref: simple-card.yaml#/definitions/mclk-fs + + cpu: + description: Holds subnode which indicates cpu dai. + type: object + additionalProperties: false + properties: + sound-dai: + maxItems: 1 + + codec: + description: Holds subnode which indicates codec dai. + type: object + additionalProperties: false + properties: + sound-dai: + maxItems: 1 + +required: + - compatible + - model + - mclk-fs + - cpu + - codec + +additionalProperties: false + +examples: + - | + sound { + compatible = "loongson,ls-audio-card"; + model = "loongson-audio"; + mclk-fs = <512>; + + cpu { + sound-dai = <&i2s>; + }; + codec { + sound-dai = <&es8323>; + }; + };
On 05/06/2023 14:09, YingKun Meng wrote:
From: Yingkun Meng mengyingkun@loongson.cn
The audio card uses loongson I2S controller present in 7axxx/2kxxx chips to transfer audio data.
Please use scripts/get_maintainers.pl to get a list of necessary people and lists to CC. It might happen, that command when run on an older kernel, gives you outdated entries. Therefore please be sure you base your patches on recent Linux kernel.
You missed at least DT list (maybe more), so this won't be tested. Please resend and include all necessary entries.
Signed-off-by: Yingkun Meng mengyingkun@loongson.cn
.../sound/loongson,ls-audio-card.yaml | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml
diff --git a/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml b/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml new file mode 100644 index 000000000000..f1d6ee346bb3 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/loongson-audio-card.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml#
+title: Loongson generic ASoC audio sound card.
What is a "generic audio card"? Does it even match hardware? Bindings are supposed to describe hardware, which is usually very specific.
Also: Drop full stop. It's a title.
+maintainers:
- Yingkun Meng mengyingkun@loongson.cn
+description:
- Generic ASoC audio device for loongson platform.
+properties:
- compatible:
- const: loongson,ls-audio-card
- model:
- $ref: /schemas/types.yaml#/definitions/string
- description: User specified audio sound card name
- mclk-fs:
- $ref: simple-card.yaml#/definitions/mclk-fs
- cpu:
- description: Holds subnode which indicates cpu dai.
- type: object
- additionalProperties: false
- properties:
sound-dai:
maxItems: 1
In the cpu: required with sound-dai
- codec:
- description: Holds subnode which indicates codec dai.
- type: object
- additionalProperties: false
- properties:
sound-dai:
maxItems: 1
In the codec: required with sound-dai
No multiple dai links? Are you sure this card is so limited?
Best regards, Krzysztof
On Mon, Jun 05, 2023 at 04:45:38PM +0200, Krzysztof Kozlowski wrote:
On 05/06/2023 14:09, YingKun Meng wrote:
+title: Loongson generic ASoC audio sound card.
What is a "generic audio card"? Does it even match hardware? Bindings are supposed to describe hardware, which is usually very specific.
The concept of a generic, reusable sound card seems reasonably clear - there's a bunch of in tree examples already and the idea that we have to pull together multiple bits of hardware to make a useful sound subsystem is a known thing.
Also: Drop full stop. It's a title.
Shouldn't this be checked by the tooling?
On 2023/6/5 23:06, Mark Brown wrote:
On Mon, Jun 05, 2023 at 04:45:38PM +0200, Krzysztof Kozlowski wrote:
On 05/06/2023 14:09, YingKun Meng wrote:
+title: Loongson generic ASoC audio sound card.
What is a "generic audio card"? Does it even match hardware? Bindings are supposed to describe hardware, which is usually very specific.
The concept of a generic, reusable sound card seems reasonably clear - there's a bunch of in tree examples already and the idea that we have to pull together multiple bits of hardware to make a useful sound subsystem is a known thing.
Also: Drop full stop. It's a title.
Shouldn't this be checked by the tooling?
All work will be done in the new version.
Thanks, Yingkun Meng
Hi Krzysztof,
Thanks for your kindly work.
On 2023/6/5 22:45, Krzysztof Kozlowski wrote:
On 05/06/2023 14:09, YingKun Meng wrote:
From: Yingkun Meng mengyingkun@loongson.cn
The audio card uses loongson I2S controller present in 7axxx/2kxxx chips to transfer audio data.
Please use scripts/get_maintainers.pl to get a list of necessary people and lists to CC. It might happen, that command when run on an older kernel, gives you outdated entries. Therefore please be sure you base your patches on recent Linux kernel.
You missed at least DT list (maybe more), so this won't be tested. Please resend and include all necessary entries.
Sorry for my mistake. Fixed in new version.
Signed-off-by: Yingkun Meng mengyingkun@loongson.cn
.../sound/loongson,ls-audio-card.yaml | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml
diff --git a/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml b/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml new file mode 100644 index 000000000000..f1d6ee346bb3 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/loongson,ls-audio-card.yaml @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/loongson-audio-card.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml#
+title: Loongson generic ASoC audio sound card.
What is a "generic audio card"? Does it even match hardware? Bindings are supposed to describe hardware, which is usually very specific.
Also: Drop full stop. It's a title.
On loongson platform, the I2S controllers connect different codecs to form different audio devices that can be driven by the same machine driver. The "generic audio card" refers to these audio devices.
Currently, it can match some specific hardware, such as 2k2000 + es8323: the 2k2000 is a loogson SoC with a I2S controller 7a2000 + es8288: the 7a2000 is a bridge chip with a I2S controller
+maintainers:
- Yingkun Meng mengyingkun@loongson.cn
+description:
- Generic ASoC audio device for loongson platform.
+properties:
- compatible:
- const: loongson,ls-audio-card
- model:
- $ref: /schemas/types.yaml#/definitions/string
- description: User specified audio sound card name
- mclk-fs:
- $ref: simple-card.yaml#/definitions/mclk-fs
- cpu:
- description: Holds subnode which indicates cpu dai.
- type: object
- additionalProperties: false
- properties:
sound-dai:
maxItems: 1
In the cpu: required with sound-dai
OK.
- codec:
- description: Holds subnode which indicates codec dai.
- type: object
- additionalProperties: false
- properties:
sound-dai:
maxItems: 1
In the codec: required with sound-dai
No multiple dai links? Are you sure this card is so limited?
Yes. The audio device has only one channel.
Best regards, Krzysztof
Thanks,
Yingkun Meng
On Mon, Jun 05, 2023 at 08:09:32PM +0800, YingKun Meng wrote:
regmap_read_poll_timeout_atomic(i2s->regmap,
LS_I2S_CTRL, val,
!(val & I2S_CTRL_MCLK_READY),
10, 2000);
The driver is waiting for status bits to change in the regmap but...
pr_err("buf len not multiply of period len\n");
Use dev_ functions to log things please.
+static const struct regmap_config loongson_i2s_regmap_config = {
- .reg_bits = 32,
- .reg_stride = 4,
- .val_bits = 32,
- .max_register = 0x110,
- .cache_type = REGCACHE_FLAT,
+};
...there are no volatile registers in the regmap so we will never read from the hardware. I don't understand how this can work?
- i2s->reg_base = pci_iomap(pdev, BAR_NUM, 0);
- if (!i2s->reg_base) {
dev_err(&pdev->dev, "pci_iomap_error\n");
ret = -EIO;
goto err_release;
- }
pcim_iomap()?
- dev_set_name(&pdev->dev, "%s", loongson_i2s_dai.name);
Don't log static information like this, it just adds noise and makes the boot slower.
- pci_disable_device(pdev);
pcim_enable_device() too.
On 2023/6/5 21:17, Mark Brown wrote:
On Mon, Jun 05, 2023 at 08:09:32PM +0800, YingKun Meng wrote:
regmap_read_poll_timeout_atomic(i2s->regmap,
LS_I2S_CTRL, val,
!(val & I2S_CTRL_MCLK_READY),
10, 2000);
The driver is waiting for status bits to change in the regmap but...
Break condition reversed. Fixed in new version.
pr_err("buf len not multiply of period len\n");
Use dev_ functions to log things please.
OK.
+static const struct regmap_config loongson_i2s_regmap_config = {
- .reg_bits = 32,
- .reg_stride = 4,
- .val_bits = 32,
- .max_register = 0x110,
- .cache_type = REGCACHE_FLAT,
+};
...there are no volatile registers in the regmap so we will never read from the hardware. I don't understand how this can work?
The I2S controller has two private DMA controllers to transfer the audio data. Its register set is divided into two parts: I2S control registers and DMA control registers.
1) The I2S control registers are used to config I2S parameters, accessed by regmap API. So we don't need to read back.
2) The DMA control registers are used to maintain the status of audio data transmission. These registers isn't maintained by regmap. They are accessed using readx()/writex() APIs.
- i2s->reg_base = pci_iomap(pdev, BAR_NUM, 0);
- if (!i2s->reg_base) {
dev_err(&pdev->dev, "pci_iomap_error\n");
ret = -EIO;
goto err_release;
- }
pcim_iomap()?
OK.
- dev_set_name(&pdev->dev, "%s", loongson_i2s_dai.name);
Don't log static information like this, it just adds noise and makes the boot slower.
Removed in new version. Its original purpose is to set a fixed value for platform component name, and match this value in machine driver.
- pci_disable_device(pdev);
pcim_enable_device() too.
OK.
Hi YingKun,
kernel test robot noticed the following build errors:
[auto build test ERROR on 1fbcc5ab1c7a172ef1159b154c296fe1e9ce209b]
url: https://github.com/intel-lab-lkp/linux/commits/YingKun-Meng/ASoC-loongson-Ad... base: 1fbcc5ab1c7a172ef1159b154c296fe1e9ce209b patch link: https://lore.kernel.org/r/20230605120934.2306548-1-mengyingkun%40loongson.cn patch subject: [PATCH 1/3] ASoC: Add support for Loongson I2S controller config: powerpc-allmodconfig (https://download.01.org/0day-ci/archive/20230606/202306060223.9hdivLrx-lkp@i...) compiler: powerpc-linux-gcc (GCC) 12.3.0 reproduce (this is a W=1 build): mkdir -p ~/bin wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross # https://github.com/intel-lab-lkp/linux/commit/cb79a1df70a849f772428740eb3c15... git remote add linux-review https://github.com/intel-lab-lkp/linux git fetch --no-tags linux-review YingKun-Meng/ASoC-loongson-Add-Loongson-Generic-ASoC-Sound-Card-Support/20230605-215940 git checkout cb79a1df70a849f772428740eb3c155da83de25b # save the config file mkdir build_dir && cp config build_dir/.config COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.3.0 ~/bin/make.cross W=1 O=build_dir ARCH=powerpc olddefconfig COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.3.0 ~/bin/make.cross W=1 O=build_dir ARCH=powerpc SHELL=/bin/bash sound/soc/loongson/
If you fix the issue, kindly add following tag where applicable | Reported-by: kernel test robot lkp@intel.com | Closes: https://lore.kernel.org/oe-kbuild-all/202306060223.9hdivLrx-lkp@intel.com/
All error/warnings (new ones prefixed by >>):
sound/soc/loongson/loongson_i2s_pci.c: In function 'dma_desc_save':
sound/soc/loongson/loongson_i2s_pci.c:82:17: error: implicit declaration of function 'readq'; did you mean 'readl'? [-Werror=implicit-function-declaration]
82 | val |= (readq(order_reg) & DMA_ORDER_CTRL_MASK); | ^~~~~ | readl
sound/soc/loongson/loongson_i2s_pci.c:84:9: error: implicit declaration of function 'writeq'; did you mean 'writel'? [-Werror=implicit-function-declaration]
84 | writeq(val, order_reg); | ^~~~~~ | writel sound/soc/loongson/loongson_i2s_pci.c: In function 'loongson_pcm_hw_params':
sound/soc/loongson/loongson_i2s_pci.c:171:45: warning: right shift count >= width of type [-Wshift-count-overflow]
171 | desc->order_hi = order_addr >> 32; | ^~ sound/soc/loongson/loongson_i2s_pci.c:174:43: warning: right shift count >= width of type [-Wshift-count-overflow] 174 | desc->saddr_hi = mem_addr >> 32; | ^~ sound/soc/loongson/loongson_i2s_pci.c:190:57: warning: right shift count >= width of type [-Wshift-count-overflow] 190 | desc->order_hi = prtd->dma_desc_arr_phy >> 32; | ^~ sound/soc/loongson/loongson_i2s_pci.c: In function 'loongson_i2s_pci_probe':
sound/soc/loongson/loongson_i2s_pci.c:397:29: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
397 | tx_data->dev_addr = (u64)i2s->reg_base + LS_I2S_TX_DATA; | ^ sound/soc/loongson/loongson_i2s_pci.c:400:29: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast] 400 | rx_data->dev_addr = (u64)i2s->reg_base + LS_I2S_RX_DATA; | ^ cc1: some warnings being treated as errors
vim +82 sound/soc/loongson/loongson_i2s_pci.c
74 75 static struct 76 loongson_dma_desc *dma_desc_save(struct loongson_runtime_data *prtd) 77 { 78 void __iomem *order_reg = prtd->dma_data->order_addr; 79 u64 val; 80 81 val = (u64)prtd->dma_pos_desc_phy & DMA_ORDER_ASK_MASK;
82 val |= (readq(order_reg) & DMA_ORDER_CTRL_MASK);
83 val |= DMA_ORDER_ASK_VALID;
84 writeq(val, order_reg);
85 86 while (readl(order_reg) & DMA_ORDER_ASK_VALID) 87 udelay(2); 88 89 return prtd->dma_pos_desc; 90 } 91 92 static int loongson_pcm_trigger(struct snd_soc_component *component, 93 struct snd_pcm_substream *substream, int cmd) 94 { 95 struct loongson_runtime_data *prtd = substream->runtime->private_data; 96 struct device *dev = substream->pcm->card->dev; 97 void __iomem *order_reg = prtd->dma_data->order_addr; 98 u64 val; 99 int ret = 0; 100 101 switch (cmd) { 102 case SNDRV_PCM_TRIGGER_START: 103 case SNDRV_PCM_TRIGGER_RESUME: 104 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 105 val = prtd->dma_pos_desc_phy & DMA_ORDER_ASK_MASK; 106 if (dev->coherent_dma_mask == DMA_BIT_MASK(64)) 107 val |= DMA_ORDER_ADDR_64; 108 else 109 val &= ~DMA_ORDER_ADDR_64; 110 val |= (readq(order_reg) & DMA_ORDER_CTRL_MASK); 111 val |= DMA_ORDER_START; 112 writeq(val, order_reg); 113 114 while ((readl(order_reg) & DMA_ORDER_START)) 115 udelay(2); 116 break; 117 case SNDRV_PCM_TRIGGER_STOP: 118 case SNDRV_PCM_TRIGGER_SUSPEND: 119 case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 120 dma_desc_save(prtd); 121 122 /* dma stop */ 123 val = readq(order_reg) | DMA_ORDER_STOP; 124 writeq(val, order_reg); 125 udelay(1000); 126 127 break; 128 default: 129 ret = -EINVAL; 130 } 131 132 return ret; 133 } 134 135 static int loongson_pcm_hw_params(struct snd_soc_component *component, 136 struct snd_pcm_substream *substream, 137 struct snd_pcm_hw_params *params) 138 { 139 struct snd_pcm_runtime *runtime = substream->runtime; 140 struct loongson_runtime_data *prtd = runtime->private_data; 141 size_t buf_len = params_buffer_bytes(params); 142 size_t period_len = params_period_bytes(params); 143 dma_addr_t order_addr, mem_addr; 144 struct loongson_dma_desc *desc; 145 u32 num_periods; 146 int i; 147 148 if (buf_len % period_len) { 149 pr_err("buf len not multiply of period len\n"); 150 return -EINVAL; 151 } 152 153 num_periods = buf_len / period_len; 154 if (!num_periods) { 155 pr_err("dma data too small\n"); 156 return -EINVAL; 157 } 158 159 snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); 160 runtime->dma_bytes = buf_len; 161 162 /* initialize dma descriptor array */ 163 mem_addr = runtime->dma_addr; 164 order_addr = prtd->dma_desc_arr_phy; 165 for (i = 0; i < num_periods; i++) { 166 desc = &prtd->dma_desc_arr[i]; 167 168 /* next descriptor physical address */ 169 order_addr += sizeof(*desc); 170 desc->order = order_addr | BIT(0);
171 desc->order_hi = order_addr >> 32;
172 173 desc->saddr = mem_addr; 174 desc->saddr_hi = mem_addr >> 32; 175 desc->daddr = prtd->dma_data->dev_addr; 176 177 desc->cmd = BIT(0); 178 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 179 desc->cmd |= BIT(12); 180 181 desc->length = period_len >> 2; 182 desc->step_length = 0; 183 desc->step_times = 1; 184 185 mem_addr += period_len; 186 } 187 if (num_periods > 0) { 188 desc = &prtd->dma_desc_arr[num_periods - 1]; 189 desc->order = prtd->dma_desc_arr_phy | BIT(0); 190 desc->order_hi = prtd->dma_desc_arr_phy >> 32; 191 } 192 193 /* init position descriptor */ 194 *prtd->dma_pos_desc = *prtd->dma_desc_arr; 195 196 return 0; 197 } 198
Hi YingKun,
kernel test robot noticed the following build errors:
[auto build test ERROR on 1fbcc5ab1c7a172ef1159b154c296fe1e9ce209b]
url: https://github.com/intel-lab-lkp/linux/commits/YingKun-Meng/ASoC-loongson-Ad... base: 1fbcc5ab1c7a172ef1159b154c296fe1e9ce209b patch link: https://lore.kernel.org/r/20230605120934.2306548-1-mengyingkun%40loongson.cn patch subject: [PATCH 1/3] ASoC: Add support for Loongson I2S controller config: sh-allmodconfig (https://download.01.org/0day-ci/archive/20230606/202306060320.Sphw0ihy-lkp@i...) compiler: sh4-linux-gcc (GCC) 12.3.0 reproduce (this is a W=1 build): mkdir -p ~/bin wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross # https://github.com/intel-lab-lkp/linux/commit/cb79a1df70a849f772428740eb3c15... git remote add linux-review https://github.com/intel-lab-lkp/linux git fetch --no-tags linux-review YingKun-Meng/ASoC-loongson-Add-Loongson-Generic-ASoC-Sound-Card-Support/20230605-215940 git checkout cb79a1df70a849f772428740eb3c155da83de25b # save the config file mkdir build_dir && cp config build_dir/.config COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.3.0 ~/bin/make.cross W=1 O=build_dir ARCH=sh olddefconfig COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.3.0 ~/bin/make.cross W=1 O=build_dir ARCH=sh SHELL=/bin/bash sound/soc/loongson/
If you fix the issue, kindly add following tag where applicable | Reported-by: kernel test robot lkp@intel.com | Closes: https://lore.kernel.org/oe-kbuild-all/202306060320.Sphw0ihy-lkp@intel.com/
All error/warnings (new ones prefixed by >>):
sound/soc/loongson/loongson_i2s_pci.c: In function 'loongson_pcm_hw_params': sound/soc/loongson/loongson_i2s_pci.c:171:45: warning: right shift count >= width of type [-Wshift-count-overflow] 171 | desc->order_hi = order_addr >> 32; | ^~ sound/soc/loongson/loongson_i2s_pci.c:174:43: warning: right shift count >= width of type [-Wshift-count-overflow] 174 | desc->saddr_hi = mem_addr >> 32; | ^~ sound/soc/loongson/loongson_i2s_pci.c:190:57: warning: right shift count >= width of type [-Wshift-count-overflow] 190 | desc->order_hi = prtd->dma_desc_arr_phy >> 32; | ^~ sound/soc/loongson/loongson_i2s_pci.c: In function 'loongson_i2s_pci_probe':
sound/soc/loongson/loongson_i2s_pci.c:364:15: error: implicit declaration of function 'pci_request_region'; did you mean 'pci_request_regions'? [-Werror=implicit-function-declaration]
364 | ret = pci_request_region(pdev, BAR_NUM, LS_I2S_DRVNAME); | ^~~~~~~~~~~~~~~~~~ | pci_request_regions
sound/soc/loongson/loongson_i2s_pci.c:379:25: error: implicit declaration of function 'pci_iomap'; did you mean 'pcim_iomap'? [-Werror=implicit-function-declaration]
379 | i2s->reg_base = pci_iomap(pdev, BAR_NUM, 0); | ^~~~~~~~~ | pcim_iomap
sound/soc/loongson/loongson_i2s_pci.c:379:23: warning: assignment to 'void *' from 'int' makes pointer from integer without a cast [-Wint-conversion]
379 | i2s->reg_base = pci_iomap(pdev, BAR_NUM, 0); | ^ sound/soc/loongson/loongson_i2s_pci.c:397:29: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast] 397 | tx_data->dev_addr = (u64)i2s->reg_base + LS_I2S_TX_DATA; | ^ sound/soc/loongson/loongson_i2s_pci.c:400:29: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast] 400 | rx_data->dev_addr = (u64)i2s->reg_base + LS_I2S_RX_DATA; | ^
sound/soc/loongson/loongson_i2s_pci.c:441:9: error: implicit declaration of function 'pci_iounmap'; did you mean 'pcim_iounmap'? [-Werror=implicit-function-declaration]
441 | pci_iounmap(pdev, i2s->reg_base); | ^~~~~~~~~~~ | pcim_iounmap
sound/soc/loongson/loongson_i2s_pci.c:443:9: error: implicit declaration of function 'pci_release_region'; did you mean 'pci_release_regions'? [-Werror=implicit-function-declaration]
443 | pci_release_region(pdev, BAR_NUM); | ^~~~~~~~~~~~~~~~~~ | pci_release_regions sound/soc/loongson/loongson_i2s_pci.c: At top level:
sound/soc/loongson/loongson_i2s_pci.c:474:1: warning: data definition has no type or storage class
474 | module_pci_driver(loongson_i2s_driver); | ^~~~~~~~~~~~~~~~~
sound/soc/loongson/loongson_i2s_pci.c:474:1: error: type defaults to 'int' in declaration of 'module_pci_driver' [-Werror=implicit-int] sound/soc/loongson/loongson_i2s_pci.c:474:1: warning: parameter names (without types) in function declaration
sound/soc/loongson/loongson_i2s_pci.c:464:26: warning: 'loongson_i2s_driver' defined but not used [-Wunused-variable] 464 | static struct pci_driver loongson_i2s_driver = { | ^~~~~~~~~~~~~~~~~~~ cc1: some warnings being treated as errors
vim +364 sound/soc/loongson/loongson_i2s_pci.c
350 351 static int loongson_i2s_pci_probe(struct pci_dev *pdev, 352 const struct pci_device_id *pid) 353 { 354 const struct fwnode_handle *fwnode = pdev->dev.fwnode; 355 struct loongson_dma_data *tx_data, *rx_data; 356 struct loongson_i2s *i2s; 357 int ret; 358 359 if (pci_enable_device(pdev)) { 360 dev_err(&pdev->dev, "pci_enable_device failed\n"); 361 return -ENODEV; 362 } 363
364 ret = pci_request_region(pdev, BAR_NUM, LS_I2S_DRVNAME);
365 if (ret) { 366 dev_err(&pdev->dev, "request regions failed %d\n", ret); 367 goto err_disable; 368 } 369 370 i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); 371 if (!i2s) { 372 ret = -ENOMEM; 373 goto err_release; 374 } 375 i2s->rev_id = pdev->revision; 376 i2s->dev = &pdev->dev; 377 pci_set_drvdata(pdev, i2s); 378
379 i2s->reg_base = pci_iomap(pdev, BAR_NUM, 0);
380 if (!i2s->reg_base) { 381 dev_err(&pdev->dev, "pci_iomap_error\n"); 382 ret = -EIO; 383 goto err_release; 384 } 385 386 i2s->regmap = devm_regmap_init_mmio(&pdev->dev, i2s->reg_base, 387 &loongson_i2s_regmap_config); 388 if (IS_ERR(i2s->regmap)) { 389 dev_err(&pdev->dev, "Failed to initialize register map"); 390 ret = PTR_ERR(i2s->regmap); 391 goto err_unmap; 392 } 393 394 tx_data = &i2s->tx_dma_data; 395 rx_data = &i2s->rx_dma_data; 396 397 tx_data->dev_addr = (u64)i2s->reg_base + LS_I2S_TX_DATA; 398 tx_data->order_addr = i2s->reg_base + LS_I2S_TX_ORDER; 399 400 rx_data->dev_addr = (u64)i2s->reg_base + LS_I2S_RX_DATA; 401 rx_data->order_addr = i2s->reg_base + LS_I2S_RX_ORDER; 402 403 tx_data->irq = fwnode_irq_get_byname(fwnode, "tx"); 404 if (tx_data->irq < 0) { 405 dev_err(&pdev->dev, "dma tx irq invalid\n"); 406 ret = tx_data->irq; 407 goto err_unmap; 408 } 409 410 rx_data->irq = fwnode_irq_get_byname(fwnode, "rx"); 411 if (rx_data->irq < 0) { 412 dev_err(&pdev->dev, "dma rx irq invalid\n"); 413 ret = rx_data->irq; 414 goto err_unmap; 415 } 416 417 device_property_read_u32(&pdev->dev, "clock-frequency", &i2s->clk_rate); 418 if (!i2s->clk_rate) { 419 dev_err(&pdev->dev, "clock-frequency property invalid\n"); 420 ret = -EINVAL; 421 goto err_unmap; 422 } 423 424 dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); 425 426 dev_set_name(&pdev->dev, "%s", loongson_i2s_dai.name); 427 428 loongson_i2s_init(i2s); 429 430 ret = devm_snd_soc_register_component(&pdev->dev, 431 &loongson_i2s_component, 432 &loongson_i2s_dai, 1); 433 if (ret) { 434 dev_err(&pdev->dev, "register DAI failed %d\n", ret); 435 goto err_unmap; 436 } 437 438 return 0; 439 440 err_unmap:
441 pci_iounmap(pdev, i2s->reg_base);
442 err_release:
443 pci_release_region(pdev, BAR_NUM);
444 err_disable: 445 pci_disable_device(pdev); 446 return ret; 447 } 448 449 static void loongson_i2s_pci_remove(struct pci_dev *pdev) 450 { 451 struct loongson_i2s *i2s = dev_get_drvdata(&pdev->dev); 452 453 pci_iounmap(pdev, i2s->reg_base); 454 pci_release_region(pdev, BAR_NUM); 455 pci_disable_device(pdev); 456 } 457 458 static const struct pci_device_id loongson_i2s_ids[] = { 459 { PCI_DEVICE(PCI_VENDOR_ID_LOONGSON, 0x7a27) }, 460 { }, 461 }; 462 MODULE_DEVICE_TABLE(pci, loongson_i2s_ids); 463 464 static struct pci_driver loongson_i2s_driver = { 465 .name = "loongson-i2s-pci", 466 .id_table = loongson_i2s_ids, 467 .probe = loongson_i2s_pci_probe, 468 .remove = loongson_i2s_pci_remove, 469 .driver = { 470 .owner = THIS_MODULE, 471 .pm = pm_sleep_ptr(&loongson_i2s_pm), 472 }, 473 };
474 module_pci_driver(loongson_i2s_driver);
475
participants (4)
-
kernel test robot
-
Krzysztof Kozlowski
-
Mark Brown
-
YingKun Meng