mirror of
https://gitee.com/open-loongarch/linux-6.12.git
synced 2026-10-25 12:37:11 +08:00
Update to 14fc3074cfe039
1. 添加带irq处理的gpio驱动 2. 龙芯2k系列添加mmc驱动支持 3. 添加看门狗驱动 4. dtb支持从command line中的board_name字段判断 5. 支持内存分布描述数据从固件传入的数据获取 6. 支持从bootcmdline获取ramdisk启动的地址和所用大小 7. 添加2k300的i2c总线驱动 8. 2k300使用新的spi 和 spi-io 驱动 9. CPU芯片温度传感器支持2k300的计算方式 10. 添加2k300的adc驱动 11. pwm: update default pwm freq to 50Mhz 12. loongson pwm 驱动支持dts使用clock-frequency形式声明频率 13. loongson pwm 确保disable时输出的电平为空闲电平 14. 2k300编译配置使能pwm leds功能 15. 2k300的gmac的phy-mode默认使用rgmii-id 16. 2k300先锋派设置YT8531的内部延迟 17. YT8531 phy 支持设置led的工作模式 18. LCD屏幕 支持cmdline的panel属性选择某一款板卡的dts配置,实现屏幕切换 19. 添加脚本用于快速选择板卡配置文件 20. 添加CAN驱动 21. 添加音频驱动 22. 优化OTG兼容性 Signed-off-by: maoxiaochuan <maoxiaochuan@loongson.cn>
This commit is contained in:
+1
-1
@@ -98,7 +98,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/loongson-gd/Kconfig"
|
||||
source "sound/soc/img/Kconfig"
|
||||
source "sound/soc/intel/Kconfig"
|
||||
source "sound/soc/mediatek/Kconfig"
|
||||
|
||||
+1
-1
@@ -50,7 +50,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) += loongson-gd/
|
||||
obj-$(CONFIG_SND_SOC) += img/
|
||||
obj-$(CONFIG_SND_SOC) += intel/
|
||||
obj-$(CONFIG_SND_SOC) += mediatek/
|
||||
|
||||
@@ -547,6 +547,10 @@ static int es8328_hw_params(struct snd_pcm_substream *substream,
|
||||
ES8328_ADCCONTROL4_ADCWL_MASK,
|
||||
wl << ES8328_ADCCONTROL4_ADCWL_SHIFT);
|
||||
|
||||
#ifdef CONFIG_LOONGARCH
|
||||
// alsamixer 中暂时没找到调整该配置选项,临时处理
|
||||
snd_soc_component_write(component, ES8328_ADCPOWER, 0x00);
|
||||
#endif
|
||||
return snd_soc_component_update_bits(component, reg, ES8328_RATEMASK, ratio);
|
||||
}
|
||||
|
||||
@@ -803,7 +807,20 @@ static int es8328_component_probe(struct snd_soc_component *component)
|
||||
dev_err(component->dev, "unable to prepare codec clk\n");
|
||||
goto clk_fail;
|
||||
}
|
||||
#ifdef CONFIG_LOONGARCH
|
||||
// RESET CODEC
|
||||
snd_soc_component_write(component, ES8328_CHIPPOWER,
|
||||
ES8328_CHIPPOWER_ADCDIG_OFF |
|
||||
ES8328_CHIPPOWER_DACDIG_OFF |
|
||||
ES8328_CHIPPOWER_ADCSTM_RESET |
|
||||
ES8328_CHIPPOWER_DACSTM_RESET);
|
||||
|
||||
snd_soc_component_write(component, ES8328_DACCONTROL21,
|
||||
ES8328_DACCONTROL21_LD2MO);
|
||||
|
||||
// PAI: waiting for codec ready in slave mode
|
||||
mdelay(1000);
|
||||
#endif
|
||||
return 0;
|
||||
|
||||
clk_fail:
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
menuconfig SND_SOC_LOONGSON
|
||||
tristate "ASoC support for loongson"
|
||||
|
||||
if SND_SOC_LOONGSON
|
||||
|
||||
config SND_LS
|
||||
tristate "SoC Audio for ls-i2s"
|
||||
help
|
||||
Say Y or M if you want to add support for codecs attached to
|
||||
the I2S interface. You will also need to select the Audio
|
||||
interfaces to support below.
|
||||
|
||||
config SND_LS_PCM
|
||||
tristate "SoC Audio for ls-pcm used descriptor"
|
||||
help
|
||||
Say Y or M if you want to add support for codecs attached to
|
||||
the PCM interface. Select this by configuring the dma of the descriptor.
|
||||
|
||||
config SND_LS_PCM_GENERIC_DMA
|
||||
tristate "SoC Audio for ls-pcm used generic dma"
|
||||
help
|
||||
Say Y or M if you want to add support for codecs attached to
|
||||
the PCM interface. Select this by configuring the generic dma.
|
||||
|
||||
config SND_LS_DAILINK
|
||||
tristate "SoC Audio for Sound-Card/Dai-Link"
|
||||
depends on SND_LS
|
||||
default y
|
||||
|
||||
endif #SND_SOC_LOONGSON
|
||||
@@ -0,0 +1,4 @@
|
||||
obj-$(CONFIG_SND_LS) += ls-i2s.o
|
||||
obj-$(CONFIG_SND_LS_PCM) += ls-pcm.o
|
||||
obj-$(CONFIG_SND_LS_PCM_GENERIC_DMA) += ls-pcm-generic.o
|
||||
obj-$(CONFIG_SND_LS_DAILINK) += ls-sound.o
|
||||
@@ -0,0 +1,382 @@
|
||||
/*
|
||||
* Freescale MPC5200 PSC in I2S mode
|
||||
* ALSA SoC Digital Audio Interface (DAI) driver
|
||||
*
|
||||
* Copyright (C) 2008 Secret Lab Technologies Ltd.
|
||||
* Copyright (C) 2009 Jon Smirl, Digispeaker
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/io.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include "ls-lib.h"
|
||||
#include <uapi/linux/pci.h>
|
||||
|
||||
#include <linux/pci_ids.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/acpi.h>
|
||||
|
||||
#include "ls-i2s.h"
|
||||
#include "ls-pcm.h"
|
||||
|
||||
#define PCM_STEREO_OUT_NAME "I2S PCM Stereo out"
|
||||
#define PCM_STEREO_IN_NAME "I2S PCM Stereo in"
|
||||
|
||||
#define IISRXADDR 0x0c
|
||||
#define IISTXADDR 0x10
|
||||
|
||||
static struct ls_pcm_dma_params ls_i2s_pcm_stereo_out;
|
||||
static struct ls_pcm_dma_params ls_i2s_pcm_stereo_in;
|
||||
static unsigned int revision_id;
|
||||
|
||||
struct ls_i2s {
|
||||
bool slave_mode;
|
||||
bool mclk_external;
|
||||
bool mclk_fixed;
|
||||
unsigned int refclk_freq;
|
||||
unsigned int mclk_freq;
|
||||
unsigned int xfs;
|
||||
};
|
||||
|
||||
static struct ls_i2s g_ls_i2s;
|
||||
|
||||
bool ls_i2s_is_slave_mode(void)
|
||||
{
|
||||
return g_ls_i2s.slave_mode;
|
||||
}
|
||||
|
||||
static int ls_i2s_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ls_i2s_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct ls_pcm_dma_params *dma_data;
|
||||
void *i2sbase;
|
||||
unsigned int mclk_ratio;
|
||||
unsigned int mclk_ratio_frac;
|
||||
unsigned int bclk_ratio;
|
||||
unsigned int wlen = params_width(params);
|
||||
unsigned int depth = params_width(params);
|
||||
unsigned int fs = params->rate_num;
|
||||
unsigned int xfs = g_ls_i2s.xfs;
|
||||
unsigned int mclk_freq = g_ls_i2s.mclk_freq;
|
||||
unsigned int bclk_freq = 0;
|
||||
unsigned int refclk_freq = g_ls_i2s.refclk_freq;
|
||||
unsigned int val = (wlen<<24) | (depth<<16);
|
||||
|
||||
i2sbase = dai->dev->platform_data;
|
||||
|
||||
if (!g_ls_i2s.mclk_external) {
|
||||
if (!g_ls_i2s.mclk_fixed) {
|
||||
mclk_freq = xfs * fs;
|
||||
}
|
||||
|
||||
if (revision_id == 0)
|
||||
{
|
||||
mclk_ratio = DIV_ROUND_CLOSEST(refclk_freq, mclk_freq * 2) - 1;
|
||||
val |= (mclk_ratio<<0);
|
||||
}
|
||||
else
|
||||
{
|
||||
mclk_ratio = refclk_freq / mclk_freq;
|
||||
mclk_ratio_frac = (u64)(refclk_freq - mclk_ratio * mclk_freq)
|
||||
* 65536 / mclk_freq;
|
||||
}
|
||||
}
|
||||
|
||||
if (!g_ls_i2s.slave_mode) {
|
||||
bclk_freq = depth * 2 * fs;
|
||||
if (revision_id == 0)
|
||||
bclk_ratio = DIV_ROUND_CLOSEST(refclk_freq, bclk_freq * 2) - 1;
|
||||
else
|
||||
bclk_ratio = DIV_ROUND_CLOSEST(mclk_freq, bclk_freq * 2) - 1;
|
||||
|
||||
val |= (bclk_ratio<<8);
|
||||
}
|
||||
|
||||
if (revision_id == 1)
|
||||
val |= depth;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
dma_data = &ls_i2s_pcm_stereo_out;
|
||||
else
|
||||
dma_data = &ls_i2s_pcm_stereo_in;
|
||||
|
||||
snd_soc_dai_set_dma_data(dai, substream, dma_data);
|
||||
|
||||
writel(val, i2sbase + 0x4);
|
||||
if (revision_id == 1)
|
||||
writel((mclk_ratio_frac << 16) | mclk_ratio, i2sbase + 0x14);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ls_i2s_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
|
||||
int clk_id, unsigned int freq, int dir)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ls_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int format)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ls_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct ls_runtime_data *prtd = substream->runtime->private_data;
|
||||
uint32_t data;
|
||||
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) {
|
||||
data = readl((void*)prtd->i2s_ctl_base);
|
||||
data &= ~(0x1 << 12);
|
||||
writel(data, (void*)prtd->i2s_ctl_base);
|
||||
data = readl((void*)prtd->i2s_ctl_base);
|
||||
if (ls_i2s_is_slave_mode())
|
||||
data |= 0x4010 | (1 << 7) | (1 << 12);
|
||||
else
|
||||
data |= 0xc010 | (1 << 7) | (1 << 12);
|
||||
writel(data, (void*)prtd->i2s_ctl_base);
|
||||
} else {
|
||||
data = readl((void*)prtd->i2s_ctl_base);
|
||||
if (ls_i2s_is_slave_mode())
|
||||
data |= 0x4010 | (1 << 11) | (1 << 13);
|
||||
else
|
||||
data |= 0xc010 | (1 << 11) | (1 << 13);
|
||||
writel(data, (void*)prtd->i2s_ctl_base);
|
||||
}
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
data = readl((void*)prtd->i2s_ctl_base);
|
||||
data &= ~((1 << 7) | (1 << 12));
|
||||
writel(data, (void*)prtd->i2s_ctl_base);
|
||||
} else {
|
||||
data = readl((void*)prtd->i2s_ctl_base);
|
||||
data &= ~((1 << 13) | (1 << 11));
|
||||
writel(data, (void*)prtd->i2s_ctl_base);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ls_i2s_shutdown(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
static int ls_i2s_probe(struct snd_soc_dai *dai)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ls_i2s_remove(struct snd_soc_dai *dai)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct snd_soc_dai_ops psc_i2s_dai_ops = {
|
||||
.probe = ls_i2s_probe,
|
||||
.remove = ls_i2s_remove,
|
||||
.startup = ls_i2s_startup,
|
||||
.shutdown = ls_i2s_shutdown,
|
||||
.trigger = ls_i2s_trigger,
|
||||
.hw_params = ls_i2s_hw_params,
|
||||
.set_fmt = ls_i2s_set_dai_fmt,
|
||||
.set_sysclk = ls_i2s_set_dai_sysclk,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_driver psc_i2s_dai[] = {{
|
||||
.name = "loongson-i2s-dai",
|
||||
.playback = {
|
||||
.stream_name = "I2S Playback",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = PSC_I2S_RATES,
|
||||
.formats = PSC_I2S_FORMATS,
|
||||
}, .capture = {
|
||||
.stream_name = "I2S Capture",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = PSC_I2S_RATES,
|
||||
.formats = PSC_I2S_FORMATS,
|
||||
},
|
||||
.ops = &psc_i2s_dai_ops,
|
||||
.symmetric_rate = 1,
|
||||
} };
|
||||
|
||||
static const struct snd_soc_component_driver psc_i2s_component = {
|
||||
.name = "loongson-i2s-dai",
|
||||
};
|
||||
|
||||
static int ls_i2s_drv_probe(struct platform_device *pdev)
|
||||
{
|
||||
uint64_t IISRxData, IISTxData;
|
||||
struct clk* refclk;
|
||||
|
||||
#if 0 // 4.19 与 5.10 不兼容代码
|
||||
if (ACPI_COMPANION(&pdev->dev)) {
|
||||
resource_size_t mmio_base, mmio_size;
|
||||
struct pci_dev *ppdev;
|
||||
static void __iomem *pci_i2s_reg;
|
||||
int ret;
|
||||
|
||||
ppdev = pci_get_device(PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_I2S, NULL);
|
||||
|
||||
if (ppdev) {
|
||||
ret = pci_enable_device(ppdev);
|
||||
|
||||
mmio_base = pci_resource_start(ppdev, 0);
|
||||
mmio_size = pci_resource_len(ppdev, 0);
|
||||
pci_i2s_reg = ioremap(mmio_base, mmio_size);
|
||||
revision_id = ppdev->revision;
|
||||
}
|
||||
|
||||
pdev->dev.platform_data = pci_i2s_reg;
|
||||
IISRxData = (uint64_t)pci_i2s_reg | IISRXADDR;
|
||||
IISTxData = (uint64_t)pci_i2s_reg | IISTXADDR;
|
||||
} else {
|
||||
#endif
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct resource *r;
|
||||
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
|
||||
if (r == NULL) {
|
||||
dev_err(&pdev->dev, "no IO memory resource defined\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
pdev->dev.platform_data = ioremap(r->start, r->end - r->start + 1);;
|
||||
IISRxData = r->start | IISRXADDR;
|
||||
IISTxData = r->start | IISTXADDR;
|
||||
|
||||
if (of_device_is_compatible(np, "loongson,ls2k0300-i2s"))
|
||||
revision_id = 1;
|
||||
#if 0
|
||||
}
|
||||
#endif
|
||||
|
||||
refclk = devm_clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(refclk))
|
||||
{
|
||||
if (device_property_read_u32(&pdev->dev, "clock-frequency",
|
||||
&g_ls_i2s.refclk_freq))
|
||||
{
|
||||
dev_err(&pdev->dev, "Clock Get Fail!\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
g_ls_i2s.refclk_freq = clk_get_rate(refclk);
|
||||
}
|
||||
|
||||
pdev->dev.kobj.name = "loongson-i2s-dai";
|
||||
|
||||
if (device_property_present(&pdev->dev, "slave-mode")) {
|
||||
g_ls_i2s.slave_mode = true;
|
||||
dev_info(&pdev->dev, "Choose SLAVE-MODE...\n");
|
||||
}
|
||||
else {
|
||||
g_ls_i2s.slave_mode = false;
|
||||
}
|
||||
|
||||
if (device_property_present(&pdev->dev, "mclk-external")) {
|
||||
g_ls_i2s.mclk_external = true;
|
||||
dev_info(&pdev->dev, "Use EXTERNAL-MCLK...\n");
|
||||
}
|
||||
else {
|
||||
g_ls_i2s.mclk_external = false;
|
||||
}
|
||||
|
||||
if (device_property_present(&pdev->dev, "mclk-fixed")) {
|
||||
g_ls_i2s.mclk_fixed = true;
|
||||
device_property_read_u32(&pdev->dev, "mclk-fixed",
|
||||
&g_ls_i2s.mclk_freq);
|
||||
dev_info(&pdev->dev, "Set MCLK-FIXED <%d> ...\n",
|
||||
g_ls_i2s.mclk_freq);
|
||||
}
|
||||
else {
|
||||
g_ls_i2s.mclk_fixed = false;
|
||||
}
|
||||
|
||||
if(device_property_read_u32(&pdev->dev, "xfs", &g_ls_i2s.xfs))
|
||||
g_ls_i2s.xfs = 128;
|
||||
dev_info(&pdev->dev, "Set xfs <%d> ...\n", g_ls_i2s.xfs);
|
||||
|
||||
ls_i2s_pcm_stereo_out.name = PCM_STEREO_OUT_NAME;
|
||||
ls_i2s_pcm_stereo_out.dev_addr = IISTxData;
|
||||
|
||||
ls_i2s_pcm_stereo_in.name = PCM_STEREO_IN_NAME;
|
||||
ls_i2s_pcm_stereo_in.dev_addr = IISRxData;
|
||||
|
||||
return snd_soc_register_component(&pdev->dev, &psc_i2s_component,
|
||||
psc_i2s_dai, 1);
|
||||
}
|
||||
|
||||
static void ls_i2s_drv_remove(struct platform_device *pdev)
|
||||
{
|
||||
snd_soc_unregister_component(&pdev->dev);
|
||||
}
|
||||
|
||||
static const struct of_device_id snd_ls_i2s_dt_match[] = {
|
||||
{ .compatible = "loongson,ls-i2s", },
|
||||
{ .compatible = "loongson,ls2k0300-i2s", },
|
||||
{ .compatible = "loongson,loongson2-i2s", },
|
||||
{ .compatible = "loongson,ls7a-i2s", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, snd_ls_i2s_dt_match);
|
||||
|
||||
static struct platform_driver ls_i2s_driver = {
|
||||
.probe = ls_i2s_drv_probe,
|
||||
.remove = ls_i2s_drv_remove,
|
||||
.driver = {
|
||||
.name = "loongson-i2sp",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(snd_ls_i2s_dt_match),
|
||||
},
|
||||
};
|
||||
|
||||
static int __init loongson_i2s_init(void)
|
||||
{
|
||||
return platform_driver_register(&ls_i2s_driver);
|
||||
}
|
||||
|
||||
static void __exit loongson_i2s_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&ls_i2s_driver);
|
||||
}
|
||||
|
||||
module_init(loongson_i2s_init)
|
||||
module_exit(loongson_i2s_exit)
|
||||
|
||||
MODULE_AUTHOR("loongson");
|
||||
MODULE_DESCRIPTION("Loongson I2S mode ASoC Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
#ifndef __LS_I2S_H__
|
||||
#define __LS_I2S_H__
|
||||
|
||||
#define PSC_I2S_RATES SNDRV_PCM_RATE_8000_96000
|
||||
|
||||
#define PSC_I2S_FORMATS ( SNDRV_PCM_FMTBIT_S8 | \
|
||||
SNDRV_PCM_FMTBIT_S16_LE | \
|
||||
SNDRV_PCM_FMTBIT_S20_3LE | \
|
||||
SNDRV_PCM_FMTBIT_S24_3LE | \
|
||||
SNDRV_PCM_FMTBIT_S32_LE)
|
||||
|
||||
bool ls_i2s_is_slave_mode(void);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifndef LS_LIB_H
|
||||
#define LS_LIB_H
|
||||
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/irqreturn.h>
|
||||
|
||||
/* PCM */
|
||||
|
||||
struct ls_pcm_dma_params {
|
||||
char *name; /* stream identifier */
|
||||
u32 dcmd; /* DMA descriptor dcmd field */
|
||||
volatile u32 *drcmr; /* the DMA request channel to use */
|
||||
u64 dev_addr; /* device physical address for DMA */
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,592 @@
|
||||
/*
|
||||
* sound/soc/loongson/ls-pcm-generic.c -- ALSA PCM interface for the Loongson chip with generic dma.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/of_dma.h>
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/soc.h>
|
||||
#include "ls-lib.h"
|
||||
|
||||
#include <linux/interrupt.h>
|
||||
#include "ls-pcm.h"
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
|
||||
#include <linux/interrupt.h>
|
||||
#include <uapi/linux/pci.h>
|
||||
|
||||
#include <linux/pci_ids.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/acpi.h>
|
||||
|
||||
#include "ls-i2s.h"
|
||||
|
||||
static struct ls_runtime_data generic_prtd;
|
||||
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_S16_LE |
|
||||
SNDRV_PCM_FMTBIT_S20_3LE |
|
||||
SNDRV_PCM_FMTBIT_S24_LE |
|
||||
SNDRV_PCM_FMTBIT_S32_LE),
|
||||
.rates = SNDRV_PCM_RATE_8000_96000,
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.period_bytes_min = 128,
|
||||
.period_bytes_max = 128*1024,
|
||||
.periods_min = 1,
|
||||
.periods_max = PAGE_SIZE/sizeof(ls_dma_desc),
|
||||
.buffer_bytes_max = 1024 * 1024,
|
||||
};
|
||||
static void ls_pcm_generic_dma_irq(void *dev_id);
|
||||
|
||||
static int __ls_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct ls_runtime_data *rtd = runtime->private_data;
|
||||
size_t totsize = params_buffer_bytes(params);
|
||||
size_t period = params_period_bytes(params);
|
||||
dma_addr_t dma_buff_phys;
|
||||
struct dma_async_tx_descriptor *desc = NULL;
|
||||
dma_cookie_t cookie;
|
||||
|
||||
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
|
||||
|
||||
rtd->totsize = totsize;
|
||||
runtime->dma_bytes = totsize;
|
||||
dma_buff_phys = runtime->dma_addr;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
rtd->tx_dma_buf = dma_buff_phys;
|
||||
desc = dmaengine_prep_dma_cyclic(rtd->tx_ch,
|
||||
rtd->tx_dma_buf,
|
||||
totsize, period, DMA_MEM_TO_DEV,
|
||||
DMA_PREP_INTERRUPT);
|
||||
} else {
|
||||
rtd->rx_dma_buf = dma_buff_phys;
|
||||
desc = dmaengine_prep_dma_cyclic(rtd->rx_ch,
|
||||
rtd->rx_dma_buf,
|
||||
totsize, period, DMA_DEV_TO_MEM,
|
||||
DMA_PREP_INTERRUPT);
|
||||
}
|
||||
|
||||
if (!desc) {
|
||||
printk("dma prep cyclic failed.\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
desc->callback = ls_pcm_generic_dma_irq;
|
||||
desc->callback_param = substream;
|
||||
|
||||
cookie = dmaengine_submit(desc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __ls_pcm_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
snd_pcm_set_runtime_buffer(substream, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ls_pcm_trigger(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct ls_runtime_data *prtd = substream->runtime->private_data;
|
||||
struct dma_chan *chan;
|
||||
int ret = 0;
|
||||
// uint32_t data;
|
||||
|
||||
chan = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? prtd->tx_ch : prtd->rx_ch;
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
dma_async_issue_pending(chan);
|
||||
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
dmaengine_terminate_all(chan);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
dmaengine_pause(chan);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
dmaengine_resume(chan);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t ls_pcm_pointer(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct ls_runtime_data *prtd = runtime->private_data;
|
||||
|
||||
snd_pcm_uframes_t x;
|
||||
u64 addr;
|
||||
enum dma_status status;
|
||||
struct dma_tx_state state;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
status = dmaengine_tx_status(prtd->tx_ch, prtd->tx_ch->cookie, &state);
|
||||
else
|
||||
status = dmaengine_tx_status(prtd->rx_ch, prtd->rx_ch->cookie, &state);
|
||||
|
||||
addr = prtd->totsize - state.residue;
|
||||
|
||||
x = bytes_to_frames(runtime, addr);
|
||||
|
||||
if (x == runtime->buffer_size)
|
||||
x = 0;
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
static int ls_pcm_prepare(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct ls_runtime_data *prtd = substream->runtime->private_data;
|
||||
|
||||
if (!prtd || !prtd->params)
|
||||
return 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ls_pcm_generic_dma_irq(void *dev_id)
|
||||
{
|
||||
struct snd_pcm_substream *substream = dev_id;
|
||||
|
||||
snd_pcm_period_elapsed(substream);
|
||||
}
|
||||
|
||||
static int ls_pcm_open(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct ls_runtime_data *prtd;
|
||||
int ret;
|
||||
|
||||
runtime->hw = ls_pcm_hardware;
|
||||
|
||||
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.
|
||||
*/
|
||||
ret = snd_pcm_hw_constraint_step(runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 128);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = snd_pcm_hw_constraint_step(runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 128);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = snd_pcm_hw_constraint_integer(runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIODS);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = -ENOMEM;
|
||||
prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
|
||||
if (!prtd)
|
||||
goto out;
|
||||
|
||||
runtime->private_data = prtd;
|
||||
|
||||
prtd->base_phys = generic_prtd.base_phys;
|
||||
prtd->base = generic_prtd.base;
|
||||
|
||||
prtd->tx_ch = generic_prtd.tx_ch;
|
||||
prtd->tx_dma_buf = generic_prtd.tx_dma_buf;
|
||||
prtd->tx_buf = generic_prtd.tx_buf;
|
||||
|
||||
prtd->rx_ch = generic_prtd.rx_ch;
|
||||
prtd->rx_dma_buf = generic_prtd.rx_dma_buf;
|
||||
prtd->rx_buf = generic_prtd.rx_buf;
|
||||
|
||||
prtd->i2s_ctl_base = generic_prtd.i2s_ctl_base;
|
||||
|
||||
return 0;
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ls_pcm_close(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct ls_runtime_data *prtd = runtime->private_data;
|
||||
uint32_t data;
|
||||
|
||||
data = readl((void*)prtd->i2s_ctl_base);
|
||||
data &= ~(BIT(12) | BIT(13));
|
||||
writel(data, (void*)prtd->i2s_ctl_base);
|
||||
|
||||
kfree(prtd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ls_pcm_ioctl(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream,
|
||||
unsigned int cmd, void *arg)
|
||||
{
|
||||
return snd_pcm_lib_ioctl(substream, cmd, arg);
|
||||
}
|
||||
|
||||
static int ls_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 ls_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
|
||||
{
|
||||
struct snd_pcm_substream *substream = pcm->streams[stream].substream;
|
||||
struct snd_dma_buffer *buf = &substream->dma_buffer;
|
||||
size_t size = ls_pcm_hardware.buffer_bytes_max;
|
||||
|
||||
buf->dev.type = SNDRV_DMA_TYPE_DEV;
|
||||
buf->dev.dev = pcm->card->dev;
|
||||
buf->private_data = NULL;
|
||||
|
||||
buf->area = dma_alloc_coherent(pcm->card->dev, size,
|
||||
&buf->addr, GFP_KERNEL);
|
||||
if (!buf->area)
|
||||
return -ENOMEM;
|
||||
buf->bytes = size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ls_pcm_free_dma_buffers(struct snd_soc_component *component,
|
||||
struct snd_pcm *pcm)
|
||||
{
|
||||
struct snd_pcm_substream *substream;
|
||||
struct snd_dma_buffer *buf;
|
||||
int stream;
|
||||
|
||||
for (stream = 0; stream < 2; stream++) {
|
||||
substream = pcm->streams[stream].substream;
|
||||
if (!substream)
|
||||
continue;
|
||||
buf = &substream->dma_buffer;
|
||||
if (!buf->area)
|
||||
continue;
|
||||
dma_free_coherent(pcm->card->dev, buf->bytes,
|
||||
buf->area, buf->addr);
|
||||
buf->area = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int ls_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 ls_runtime_data *prtd = runtime->private_data;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct ls_pcm_dma_params *dma = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0),
|
||||
substream);
|
||||
|
||||
if (!dma)
|
||||
return 0;
|
||||
|
||||
if (prtd->params != dma || prtd->params == NULL) {
|
||||
prtd->params = dma;
|
||||
}
|
||||
|
||||
return __ls_pcm_hw_params(substream, params);
|
||||
}
|
||||
|
||||
static int ls_pcm_hw_free(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
__ls_pcm_hw_free(substream);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ls_soc_pcm_new(struct snd_soc_component *component,
|
||||
struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_card *card = rtd->card->snd_card;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
int ret = 0;
|
||||
|
||||
card->dev->coherent_dma_mask = DMA_MASK;
|
||||
|
||||
if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
|
||||
ret = ls_pcm_preallocate_dma_buffer(pcm,
|
||||
SNDRV_PCM_STREAM_PLAYBACK);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
|
||||
ret = ls_pcm_preallocate_dma_buffer(pcm,
|
||||
SNDRV_PCM_STREAM_CAPTURE);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct snd_soc_component_driver ls_soc_platform_generic = {
|
||||
.open = ls_pcm_open,
|
||||
.close = ls_pcm_close,
|
||||
.ioctl = ls_pcm_ioctl,
|
||||
.hw_params = ls_pcm_hw_params,
|
||||
.hw_free = ls_pcm_hw_free,
|
||||
.prepare = ls_pcm_prepare,
|
||||
.trigger = ls_pcm_trigger,
|
||||
.pointer = ls_pcm_pointer,
|
||||
.mmap = ls_pcm_mmap,
|
||||
.pcm_construct = ls_soc_pcm_new,
|
||||
.pcm_destruct = ls_pcm_free_dma_buffers,
|
||||
};
|
||||
|
||||
|
||||
static int ls_snd_soc_register_component(struct platform_device *pdev)
|
||||
{
|
||||
pdev->dev.coherent_dma_mask = DMA_MASK;
|
||||
pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask;
|
||||
|
||||
pdev->dev.kobj.name = "loongson-i2s";
|
||||
|
||||
return snd_soc_register_component(&pdev->dev, &ls_soc_platform_generic, NULL, 0);
|
||||
}
|
||||
|
||||
|
||||
static int ls_of_dma_rx_probe(struct ls_runtime_data *ls_data, struct platform_device *pdev)
|
||||
{
|
||||
struct dma_slave_config config;
|
||||
struct device *dev = &pdev->dev;
|
||||
int ret;
|
||||
|
||||
ls_data->rx_ch = dma_request_slave_channel(dev, "i2s_record");
|
||||
if (!ls_data->rx_ch) {
|
||||
dev_err(dev, "rx dma alloc failed\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ls_data->rx_buf = dma_alloc_coherent(dev, ls_pcm_hardware.buffer_bytes_max,
|
||||
&ls_data->rx_dma_buf,
|
||||
GFP_KERNEL);
|
||||
if (!ls_data->rx_buf) {
|
||||
ret = -ENOMEM;
|
||||
goto alloc_err;
|
||||
}
|
||||
|
||||
memset(&config, 0, sizeof(config));
|
||||
|
||||
config.src_addr = ls_data->base_phys + 0xc;
|
||||
config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
||||
|
||||
ret = dmaengine_slave_config(ls_data->rx_ch, &config);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "rx dma channel config failed\n");
|
||||
ret = -ENODEV;
|
||||
goto config_err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
config_err:
|
||||
dma_free_coherent(dev,
|
||||
ls_pcm_hardware.buffer_bytes_max, ls_data->rx_buf,
|
||||
ls_data->rx_dma_buf);
|
||||
|
||||
alloc_err:
|
||||
dma_release_channel(ls_data->rx_ch);
|
||||
ls_data->rx_ch = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ls_of_dma_tx_probe(struct ls_runtime_data *ls_data, struct platform_device *pdev)
|
||||
{
|
||||
struct dma_slave_config config;
|
||||
struct device *dev = &pdev->dev;
|
||||
int ret;
|
||||
|
||||
ls_data->tx_ch = dma_request_slave_channel(dev, "i2s_play");
|
||||
if (!ls_data->tx_ch) {
|
||||
dev_err(dev, "tx dma alloc failed\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ls_data->tx_buf = dma_alloc_coherent(dev, ls_pcm_hardware.buffer_bytes_max,
|
||||
&ls_data->tx_dma_buf,
|
||||
GFP_KERNEL);
|
||||
if (!ls_data->tx_buf) {
|
||||
ret = -ENOMEM;
|
||||
goto alloc_err;
|
||||
}
|
||||
|
||||
memset(&config, 0, sizeof(config));
|
||||
|
||||
config.dst_addr = ls_data->base_phys + 0x10;
|
||||
config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
||||
|
||||
ret = dmaengine_slave_config(ls_data->tx_ch, &config);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "tx dma channel config failed\n");
|
||||
ret = -ENODEV;
|
||||
goto config_err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
config_err:
|
||||
dma_free_coherent(dev,
|
||||
ls_pcm_hardware.buffer_bytes_max, ls_data->tx_buf,
|
||||
ls_data->tx_dma_buf);
|
||||
|
||||
alloc_err:
|
||||
dma_release_channel(ls_data->tx_ch);
|
||||
ls_data->tx_ch = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ls_soc_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
int timeout = 20000;
|
||||
|
||||
struct resource *r;
|
||||
|
||||
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (r == NULL) {
|
||||
dev_err(&pdev->dev, "no IO memory resource defined\n");
|
||||
ret = -ENODEV;
|
||||
}
|
||||
|
||||
generic_prtd.base_phys = r->start;
|
||||
generic_prtd.base = (uint64_t *)ioremap(r->start, 0x14);
|
||||
generic_prtd.i2s_ctl_base = (uint64_t)generic_prtd.base + 0x8;
|
||||
|
||||
ret = ls_of_dma_rx_probe(&generic_prtd, pdev);
|
||||
if (ret)
|
||||
dev_info(&pdev->dev, "interrupt mode used for rx (no dma)\n");
|
||||
|
||||
ret = ls_of_dma_tx_probe(&generic_prtd, pdev);
|
||||
if (ret)
|
||||
dev_info(&pdev->dev, "interrupt mode used for tx (no dma)\n");
|
||||
|
||||
writel(0x8, (void*)generic_prtd.i2s_ctl_base);
|
||||
while ((!(readl((void*)generic_prtd.i2s_ctl_base) & 0x10000)) && timeout--)
|
||||
udelay(5);
|
||||
if (!ls_i2s_is_slave_mode()) {
|
||||
writel(0x8008, (void*)generic_prtd.i2s_ctl_base);
|
||||
while ((!(readl((void*)generic_prtd.i2s_ctl_base) & 0x100)) && timeout--)
|
||||
udelay(5);
|
||||
}
|
||||
return ls_snd_soc_register_component(pdev);
|
||||
}
|
||||
|
||||
static void ls_soc_remove(struct platform_device *pdev)
|
||||
{
|
||||
|
||||
if (generic_prtd.rx_ch)
|
||||
dma_release_channel(generic_prtd.rx_ch);
|
||||
|
||||
if (generic_prtd.rx_dma_buf)
|
||||
dma_free_coherent(&pdev->dev,
|
||||
ls_pcm_hardware.buffer_bytes_max, generic_prtd.rx_buf,
|
||||
generic_prtd.rx_dma_buf);
|
||||
|
||||
if (generic_prtd.tx_ch)
|
||||
dma_release_channel(generic_prtd.tx_ch);
|
||||
|
||||
if (generic_prtd.tx_dma_buf)
|
||||
dma_free_coherent(&pdev->dev,
|
||||
ls_pcm_hardware.buffer_bytes_max, generic_prtd.tx_buf,
|
||||
generic_prtd.tx_dma_buf);
|
||||
|
||||
snd_soc_unregister_component(&pdev->dev);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id snd_ls_dt_match[] = {
|
||||
{ .compatible = "loongson,ls-pcm-generic-audio", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, snd_ls_dt_match);
|
||||
#endif
|
||||
|
||||
static struct platform_driver ls_generic_pcm_driver = {
|
||||
.driver = {
|
||||
.name = "ls-generic-pcm-audio",
|
||||
.owner = THIS_MODULE,
|
||||
#ifdef CONFIG_OF
|
||||
.of_match_table = of_match_ptr(snd_ls_dt_match),
|
||||
#endif
|
||||
},
|
||||
.probe = ls_soc_probe,
|
||||
.remove = ls_soc_remove,
|
||||
};
|
||||
|
||||
static int __init ls_generic_pcm_init(void)
|
||||
{
|
||||
return platform_driver_register(&ls_generic_pcm_driver);
|
||||
}
|
||||
|
||||
static void __exit ls_generic_pcm_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&ls_generic_pcm_driver);
|
||||
}
|
||||
|
||||
module_init(ls_generic_pcm_init)
|
||||
module_exit(ls_generic_pcm_exit)
|
||||
|
||||
MODULE_AUTHOR("loongson");
|
||||
MODULE_DESCRIPTION("Loongson generic pcm driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
@@ -0,0 +1,653 @@
|
||||
/*
|
||||
* linux/sound/mips/ls-pcm.c -- ALSA PCM interface for the Loongson chip
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/of_dma.h>
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/soc.h>
|
||||
#include "ls-lib.h"
|
||||
|
||||
#include <linux/interrupt.h>
|
||||
#include "ls-pcm.h"
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
|
||||
#include <linux/interrupt.h>
|
||||
#include <uapi/linux/pci.h>
|
||||
|
||||
#include <linux/pci_ids.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/acpi.h>
|
||||
|
||||
#include "ls-i2s.h"
|
||||
|
||||
#define DMA1ADDR 0x100
|
||||
#define DMA2ADDR 0x110
|
||||
|
||||
|
||||
unsigned long long i2s_ctl;
|
||||
|
||||
static struct ls_runtime_data g_prtd;
|
||||
static struct device *dev;
|
||||
static struct platform_device *plat_dev;
|
||||
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 = PSC_I2S_FORMATS,
|
||||
.rates = PSC_I2S_RATES,
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.period_bytes_min = 128,
|
||||
.period_bytes_max = 128*1024,
|
||||
.periods_min = 1,
|
||||
.periods_max = PAGE_SIZE/sizeof(ls_dma_desc),
|
||||
.buffer_bytes_max = 1024 * 1024,
|
||||
};
|
||||
|
||||
|
||||
static int __ls_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct ls_runtime_data *rtd = runtime->private_data;
|
||||
size_t totsize = params_buffer_bytes(params);
|
||||
size_t period = params_period_bytes(params);
|
||||
ls_dma_desc *dma_desc;
|
||||
dma_addr_t dma_buff_phys, next_desc_phys;
|
||||
|
||||
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
|
||||
runtime->dma_bytes = totsize;
|
||||
|
||||
dma_desc = rtd->dma_desc_array;
|
||||
next_desc_phys = rtd->dma_desc_array_phys;
|
||||
dma_buff_phys = runtime->dma_addr;
|
||||
do {
|
||||
next_desc_phys += sizeof(ls_dma_desc);
|
||||
dma_desc->ordered = (next_desc_phys | 0x1);
|
||||
dma_desc->ordered_hi = (next_desc_phys >> 32);
|
||||
|
||||
dma_desc->saddr = dma_buff_phys;
|
||||
dma_desc->saddr_hi = (dma_buff_phys >> 32);
|
||||
dma_desc->daddr = rtd->params->dev_addr;
|
||||
dma_desc->cmd = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
|
||||
0x00001001 : 0x00000001;
|
||||
|
||||
if (period > totsize)
|
||||
period = totsize;
|
||||
dma_desc->length = 8;
|
||||
dma_desc->step_length = 0;
|
||||
dma_desc->step_times = period >> 5;
|
||||
|
||||
dma_desc++;
|
||||
dma_buff_phys += period;
|
||||
|
||||
} while (totsize -= period);
|
||||
dma_desc[-1].ordered = (rtd->dma_desc_array_phys | 0x1);
|
||||
dma_desc[-1].ordered_hi = ((rtd->dma_desc_array_phys) >> 32);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __ls_pcm_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
snd_pcm_set_runtime_buffer(substream, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ls_pcm_trigger(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct ls_runtime_data *prtd = substream->runtime->private_data;
|
||||
struct device *dev = substream->pcm->card->dev;
|
||||
int ret = 0;
|
||||
u64 val, dma_order;
|
||||
int timeout = 20000;
|
||||
void *order_addr;
|
||||
uint32_t data;
|
||||
|
||||
order_addr = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? prtd->order_addr1 : prtd->order_addr2;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
if (ls_i2s_is_slave_mode()) {
|
||||
writel(0x4200, (void*)i2s_ctl);
|
||||
}
|
||||
else {
|
||||
writel(0xc200, (void*)i2s_ctl);
|
||||
}
|
||||
//writel(0x4200, i2s_ctl);
|
||||
udelay(500);
|
||||
|
||||
val = (prtd->dma_desc_array_phys & ~0x1fUL) | 0x8UL;
|
||||
|
||||
if (dev->coherent_dma_mask == DMA_BIT_MASK(64))
|
||||
val |= 0x1UL;
|
||||
else
|
||||
val &= ~0x1UL;
|
||||
|
||||
dma_order = (readq(order_addr) & 0xfUL) | val;
|
||||
writeq(dma_order, order_addr);
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
data = readl((void*)i2s_ctl);
|
||||
data &= ~((1<<13) | (1<<11) | (1<<1));
|
||||
data |= 0x5081;
|
||||
//data |= 0xd081;
|
||||
writel(data, (void*)i2s_ctl);
|
||||
} else {
|
||||
data = readl((void*)i2s_ctl);
|
||||
data &= ~((1<<12) | (1<<7) | (1<<0));
|
||||
data |= 0x6802;
|
||||
//data |= 0xe802;
|
||||
writel(data, (void*)i2s_ctl);
|
||||
}
|
||||
|
||||
while ((readl(order_addr) & 8) && timeout--)
|
||||
udelay(5);
|
||||
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
writeq(0x10UL | (readq(order_addr) & 0x1fUL), order_addr);
|
||||
udelay(1000);
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
val = (prtd->dma_desc_ready_phys & ~0x1fUL) | 0x4UL;
|
||||
|
||||
if (dev->coherent_dma_mask == DMA_BIT_MASK(64))
|
||||
val |= 0x1UL;
|
||||
else
|
||||
val &= ~0x1UL;
|
||||
|
||||
dma_order = (readq(order_addr) & 0x1fUL) | val;
|
||||
writeq(dma_order, order_addr);
|
||||
while (readl(order_addr)&4)
|
||||
;
|
||||
writel(0x10UL | (readq(order_addr) & 0x1fUL), order_addr);
|
||||
udelay(1000);
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
val = (prtd->dma_desc_ready_phys & ~0x1fUL) | 0x8UL;
|
||||
|
||||
if (dev->coherent_dma_mask == DMA_BIT_MASK(64))
|
||||
val |= 0x1UL;
|
||||
else
|
||||
val &= ~0x1UL;
|
||||
|
||||
while ((readl(order_addr) & 8) && timeout--)
|
||||
udelay(5);
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t ls_pcm_pointer(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct ls_runtime_data *prtd = runtime->private_data;
|
||||
struct device *dev = substream->pcm->card->dev;
|
||||
|
||||
snd_pcm_uframes_t x;
|
||||
u64 dma_order, val;
|
||||
u64 addr;
|
||||
void *order_addr;
|
||||
|
||||
order_addr = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? prtd->order_addr1 : prtd->order_addr2;
|
||||
val = (prtd->dma_position_desc_phys & ~0x1fUL) | 0x4UL;
|
||||
|
||||
if (dev->coherent_dma_mask == DMA_BIT_MASK(64))
|
||||
val |= 0x1UL;
|
||||
else
|
||||
val &= ~0x1UL;
|
||||
|
||||
dma_order = (readq(order_addr) & 0x1fUL) | val;
|
||||
writeq(dma_order, order_addr);
|
||||
while (readl(order_addr) & 4)
|
||||
;
|
||||
|
||||
addr = prtd->dma_position_desc->saddr_hi;
|
||||
addr = ((addr << 32) | (prtd->dma_position_desc->saddr & 0xffffffff));
|
||||
|
||||
x = bytes_to_frames(runtime, addr - runtime->dma_addr);
|
||||
|
||||
if (x == runtime->buffer_size)
|
||||
x = 0;
|
||||
return x;
|
||||
}
|
||||
|
||||
static int ls_pcm_prepare(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct ls_runtime_data *prtd = substream->runtime->private_data;
|
||||
|
||||
if (!prtd || !prtd->params)
|
||||
return 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t ls_pcm_dma_irq(int irq, void *dev_id)
|
||||
{
|
||||
struct snd_pcm_substream *substream = dev_id;
|
||||
snd_pcm_period_elapsed(substream);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int ls_pcm_open(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct ls_runtime_data *prtd;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
int ret;
|
||||
int irq;
|
||||
|
||||
// unsigned long int val;
|
||||
|
||||
runtime->hw = ls_pcm_hardware;
|
||||
|
||||
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.
|
||||
*/
|
||||
ret = snd_pcm_hw_constraint_step(runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 128);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = snd_pcm_hw_constraint_step(runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 128);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = snd_pcm_hw_constraint_integer(runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIODS);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = -ENOMEM;
|
||||
prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
|
||||
if (!prtd)
|
||||
goto out;
|
||||
prtd->dma_desc_array =
|
||||
dma_alloc_coherent(substream->pcm->card->dev, PAGE_SIZE,
|
||||
&prtd->dma_desc_array_phys, GFP_KERNEL);
|
||||
if (!prtd->dma_desc_array)
|
||||
goto err1;
|
||||
prtd->dma_desc_ready =
|
||||
dma_alloc_coherent(substream->pcm->card->dev, sizeof(ls_dma_desc),
|
||||
&prtd->dma_desc_ready_phys, GFP_KERNEL);
|
||||
if (!prtd->dma_desc_ready)
|
||||
goto err1;
|
||||
prtd->dma_position_desc =
|
||||
dma_alloc_coherent(substream->pcm->card->dev, sizeof(ls_dma_desc),
|
||||
&prtd->dma_position_desc_phys, GFP_KERNEL);
|
||||
if (!prtd->dma_position_desc)
|
||||
goto err1;
|
||||
|
||||
runtime->private_data = prtd;
|
||||
|
||||
prtd->base_phys = g_prtd.base_phys;
|
||||
prtd->base = g_prtd.base;
|
||||
|
||||
prtd->order_addr1_phys = g_prtd.order_addr1_phys;
|
||||
prtd->order_addr1 = g_prtd.order_addr1;
|
||||
|
||||
prtd->order_addr2_phys = g_prtd.order_addr2_phys;
|
||||
prtd->order_addr2 = g_prtd.order_addr2;
|
||||
|
||||
irq = platform_get_irq(plat_dev, 1);
|
||||
ret = request_irq(irq, ls_pcm_dma_irq, IRQF_TRIGGER_RISING,
|
||||
"dma-read", substream);
|
||||
if (ret < 0) {
|
||||
printk("%s request_irq failed. ret=0x%x\r\n", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
irq = platform_get_irq(plat_dev, 0);
|
||||
ret = request_irq(irq, ls_pcm_dma_irq, IRQF_TRIGGER_RISING,
|
||||
"dma-write", substream);
|
||||
if (ret < 0) {
|
||||
printk("%s request_irq failed. ret=0x%x\r\n", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err1:
|
||||
kfree(rtd);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ls_pcm_close(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct ls_runtime_data *prtd = runtime->private_data;
|
||||
int irq;
|
||||
|
||||
irq = platform_get_irq(plat_dev, 0);
|
||||
free_irq(irq, substream);
|
||||
|
||||
irq = platform_get_irq(plat_dev, 1);
|
||||
free_irq(irq, substream);
|
||||
|
||||
dma_free_coherent(substream->pcm->card->dev, PAGE_SIZE,
|
||||
prtd->dma_desc_array, prtd->dma_desc_array_phys);
|
||||
kfree(prtd);
|
||||
writel(0x00, (void*)i2s_ctl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// static int ls_pcm_ioctl(struct snd_soc_component *component,
|
||||
// struct snd_pcm_substream *substream,
|
||||
// unsigned int cmd, void *arg)
|
||||
// {
|
||||
// return snd_pcm_lib_ioctl(substream, cmd, arg);
|
||||
// }
|
||||
|
||||
static int ls_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 ls_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
|
||||
{
|
||||
struct snd_pcm_substream *substream = pcm->streams[stream].substream;
|
||||
struct snd_dma_buffer *buf = &substream->dma_buffer;
|
||||
size_t size = ls_pcm_hardware.buffer_bytes_max;
|
||||
buf->dev.type = SNDRV_DMA_TYPE_DEV;
|
||||
buf->dev.dev = pcm->card->dev;
|
||||
buf->private_data = NULL;
|
||||
|
||||
buf->area = dma_alloc_coherent(pcm->card->dev, size,
|
||||
&buf->addr, GFP_KERNEL);
|
||||
if (!buf->area)
|
||||
return -ENOMEM;
|
||||
buf->bytes = size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ls_soc_pcm_new(struct snd_soc_component *component,
|
||||
struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_card *card = rtd->card->snd_card;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
int ret = 0;
|
||||
|
||||
if (!card->dev->dma_mask)
|
||||
card->dev->dma_mask = (uint64_t *)dev->coherent_dma_mask;
|
||||
card->dev->coherent_dma_mask = dev->coherent_dma_mask;
|
||||
|
||||
if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
|
||||
ret = ls_pcm_preallocate_dma_buffer(pcm,
|
||||
SNDRV_PCM_STREAM_PLAYBACK);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
|
||||
ret = ls_pcm_preallocate_dma_buffer(pcm,
|
||||
SNDRV_PCM_STREAM_CAPTURE);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ls_pcm_free_dma_buffers(struct snd_soc_component *component,
|
||||
struct snd_pcm *pcm)
|
||||
{
|
||||
struct snd_pcm_substream *substream;
|
||||
struct snd_dma_buffer *buf;
|
||||
int stream;
|
||||
|
||||
for (stream = 0; stream < 2; stream++) {
|
||||
substream = pcm->streams[stream].substream;
|
||||
if (!substream)
|
||||
continue;
|
||||
buf = &substream->dma_buffer;
|
||||
if (!buf->area)
|
||||
continue;
|
||||
dma_free_coherent(pcm->card->dev, buf->bytes,
|
||||
buf->area, buf->addr);
|
||||
buf->area = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int ls_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 ls_runtime_data *prtd = runtime->private_data;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct ls_pcm_dma_params *dma = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0),
|
||||
substream);
|
||||
|
||||
if (!dma)
|
||||
return 0;
|
||||
|
||||
if (prtd->params != dma || prtd->params == NULL) {
|
||||
prtd->params = dma;
|
||||
}
|
||||
|
||||
return __ls_pcm_hw_params(substream, params);
|
||||
}
|
||||
|
||||
static int ls_pcm_hw_free(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
__ls_pcm_hw_free(substream);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_component_driver ls_soc_platform = {
|
||||
.open = ls_pcm_open,
|
||||
.close = ls_pcm_close,
|
||||
//.ioctl = ls_pcm_ioctl,
|
||||
.hw_params = ls_pcm_hw_params,
|
||||
.hw_free = ls_pcm_hw_free,
|
||||
.prepare = ls_pcm_prepare,
|
||||
.trigger = ls_pcm_trigger,
|
||||
.pointer = ls_pcm_pointer,
|
||||
.mmap = ls_pcm_mmap,
|
||||
.pcm_construct = ls_soc_pcm_new,
|
||||
.pcm_destruct = ls_pcm_free_dma_buffers,
|
||||
};
|
||||
|
||||
static int ls_snd_soc_register_component(struct platform_device *pdev)
|
||||
{
|
||||
uint32_t val;
|
||||
|
||||
dev = &pdev->dev;
|
||||
plat_dev = pdev;
|
||||
|
||||
device_property_read_u32(dev, "dma-mask", &val);
|
||||
pdev->dev.coherent_dma_mask = val;
|
||||
|
||||
if (pdev->dev.dma_mask)
|
||||
*(pdev->dev.dma_mask) = pdev->dev.coherent_dma_mask;
|
||||
else
|
||||
pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask;
|
||||
|
||||
pdev->dev.kobj.name = "loongson-i2s";
|
||||
|
||||
return snd_soc_register_component(&pdev->dev, &ls_soc_platform, NULL, 0);
|
||||
}
|
||||
|
||||
static int ls_soc_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
resource_size_t dma_1;
|
||||
resource_size_t dma_2;
|
||||
|
||||
#if 0 // 4.19 与 5.10 不兼容代码
|
||||
if (ACPI_COMPANION(&pdev->dev)) {
|
||||
resource_size_t mmio_base, mmio_size;
|
||||
struct pci_dev *ppdev;
|
||||
static void __iomem *pci_i2s_reg;
|
||||
|
||||
ppdev = pci_get_device(PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_I2S, NULL);
|
||||
|
||||
if (ppdev) {
|
||||
ret = pci_enable_device(ppdev);
|
||||
|
||||
mmio_base = pci_resource_start(ppdev, 0);
|
||||
mmio_size = pci_resource_len(ppdev, 0);
|
||||
pci_i2s_reg = ioremap(mmio_base, mmio_size);
|
||||
}
|
||||
dma_1 = ((uint64_t)pci_i2s_reg | DMA1ADDR);
|
||||
dma_2 = ((uint64_t)pci_i2s_reg | DMA2ADDR);
|
||||
|
||||
i2s_ctl = pci_i2s_reg + 0x8;
|
||||
|
||||
g_prtd.base_phys = (dma_addr_t)pci_i2s_reg;
|
||||
g_prtd.base = pci_i2s_reg;
|
||||
|
||||
g_prtd.order_addr1_phys = (dma_addr_t)dma_1;
|
||||
g_prtd.order_addr1 = (uint64_t *)dma_1;
|
||||
|
||||
g_prtd.order_addr2_phys = (dma_addr_t)dma_2;
|
||||
g_prtd.order_addr2 = (uint64_t *)dma_2;
|
||||
} else {
|
||||
#endif
|
||||
struct dma_chan *chan;
|
||||
__be32 *of_property = NULL;
|
||||
struct resource *r;
|
||||
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (r == NULL) {
|
||||
dev_err(&pdev->dev, "no IO memory resource defined\n");
|
||||
ret = -ENODEV;
|
||||
}
|
||||
|
||||
chan = of_dma_request_slave_channel(pdev->dev.of_node, "i2s_play");
|
||||
if (chan == NULL) {
|
||||
dev_err(&pdev->dev, "no APBDMA-2 resource defined\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
of_property = (__be32 *)of_get_property(chan->device->dev->of_node, "reg", NULL);
|
||||
if (of_property != 0)
|
||||
dma_1 = of_read_number(of_property, 2);
|
||||
|
||||
chan = of_dma_request_slave_channel(pdev->dev.of_node, "i2s_record");
|
||||
if (chan == NULL) {
|
||||
dev_err(&pdev->dev, "no APBDMA-3 resource defined\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
of_property = (__be32 *)of_get_property(chan->device->dev->of_node, "reg", NULL);
|
||||
if (of_property != 0)
|
||||
dma_2 = of_read_number(of_property, 2);
|
||||
|
||||
g_prtd.base_phys = r->start;
|
||||
g_prtd.base = (uint64_t *)ioremap(r->start, 0x10);
|
||||
i2s_ctl = (unsigned long long)(g_prtd.base) + 0x8;
|
||||
|
||||
g_prtd.order_addr1_phys = dma_1;
|
||||
g_prtd.order_addr1 = (uint64_t *)ioremap(dma_1, 8);
|
||||
|
||||
g_prtd.order_addr2_phys = dma_2;
|
||||
g_prtd.order_addr2 = (uint64_t *)ioremap(dma_2, 8);
|
||||
#if 0
|
||||
}
|
||||
#endif
|
||||
return ls_snd_soc_register_component(pdev);
|
||||
}
|
||||
|
||||
static void ls_soc_remove(struct platform_device *pdev)
|
||||
{
|
||||
snd_soc_unregister_component(&pdev->dev);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id snd_ls_dt_match[] = {
|
||||
{ .compatible = "loongson,ls-pcm-audio", },
|
||||
{ .compatible = "loongson,loongson2-pcm-audio", },
|
||||
{ .compatible = "loongson,ls7a-pcm-audio", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, snd_ls_dt_match);
|
||||
#endif
|
||||
|
||||
static struct platform_driver loongson_pcm_driver = {
|
||||
.driver = {
|
||||
.name = "loongson-pcm-audio",
|
||||
.owner = THIS_MODULE,
|
||||
#ifdef CONFIG_OF
|
||||
.of_match_table = of_match_ptr(snd_ls_dt_match),
|
||||
#endif
|
||||
},
|
||||
.probe = ls_soc_probe,
|
||||
.remove = ls_soc_remove,
|
||||
};
|
||||
|
||||
static int __init loongson_pcm_init(void)
|
||||
{
|
||||
return platform_driver_register(&loongson_pcm_driver);
|
||||
}
|
||||
|
||||
static void __exit loongson_pcm_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&loongson_pcm_driver);
|
||||
}
|
||||
|
||||
module_init(loongson_pcm_init)
|
||||
module_exit(loongson_pcm_exit)
|
||||
|
||||
MODULE_AUTHOR("loongson");
|
||||
MODULE_DESCRIPTION("Loongson sound driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* linux/sound/mips/ls-pcm.h -- ALSA PCM interface for the Loongson chip
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#ifndef _LS_PCM_H
|
||||
#define _LS_PCM_H
|
||||
#include <asm/delay.h>
|
||||
|
||||
#define DMA_MASK 0xffffffffffffffff
|
||||
|
||||
typedef struct ls_dma_desc {
|
||||
u32 ordered;
|
||||
u32 saddr;
|
||||
u32 daddr;
|
||||
u32 length;
|
||||
u32 step_length;
|
||||
u32 step_times;
|
||||
u32 cmd;
|
||||
u32 stats;
|
||||
u32 ordered_hi;
|
||||
u32 saddr_hi;
|
||||
u32 dummy[6];
|
||||
} ls_dma_desc;
|
||||
|
||||
struct ls_runtime_data {
|
||||
int dma_ch;
|
||||
u64 totsize;
|
||||
u64 i2s_ctl_base;
|
||||
struct ls_pcm_dma_params *params;
|
||||
ls_dma_desc *dma_desc_array;
|
||||
dma_addr_t dma_desc_array_phys;
|
||||
|
||||
ls_dma_desc *dma_desc_ready;
|
||||
dma_addr_t dma_desc_ready_phys;
|
||||
|
||||
ls_dma_desc *dma_position_desc;
|
||||
dma_addr_t dma_position_desc_phys;
|
||||
|
||||
dma_addr_t base_phys;
|
||||
void *base;
|
||||
dma_addr_t order_addr1_phys;
|
||||
void *order_addr1;
|
||||
dma_addr_t order_addr2_phys;
|
||||
void *order_addr2;
|
||||
struct dma_chan *chan;
|
||||
|
||||
struct dma_chan *tx_ch; /* dma rx channel */
|
||||
dma_addr_t tx_dma_buf; /* dma rx buffer bus address */
|
||||
unsigned int *tx_buf; /* dma rx buffer cpu address */
|
||||
struct dma_chan *rx_ch; /* dma rx channel */
|
||||
dma_addr_t rx_dma_buf; /* dma rx buffer bus address */
|
||||
unsigned int *rx_buf; /* dma rx buffer cpu address */
|
||||
};
|
||||
|
||||
struct ls_pcm_client {
|
||||
struct ls_pcm_dma_params *playback_params;
|
||||
struct ls_pcm_dma_params *capture_params;
|
||||
int (*startup)(struct snd_pcm_substream *);
|
||||
void (*shutdown)(struct snd_pcm_substream *);
|
||||
int (*prepare)(struct snd_pcm_substream *);
|
||||
};
|
||||
|
||||
extern int ls_pcm_new(struct snd_card *, struct ls_pcm_client *, struct snd_pcm **);
|
||||
|
||||
static inline u32 read_reg(volatile u32 *reg)
|
||||
{
|
||||
return *reg;
|
||||
}
|
||||
static inline void write_reg(volatile u32 *reg, u32 val)
|
||||
{
|
||||
*(reg) = (val);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,202 @@
|
||||
#include <linux/module.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/pci_ids.h>
|
||||
#include <linux/pci.h>
|
||||
|
||||
static int loongson_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
static struct snd_soc_ops loongson_dai_link_ops = {
|
||||
.hw_params = loongson_hw_params,
|
||||
};
|
||||
|
||||
enum {
|
||||
PRI_PLAYBACK = 0,
|
||||
PRI_CAPTURE,
|
||||
};
|
||||
#define LOONGSON_DAI_FMT (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | \
|
||||
SND_SOC_DAIFMT_CBS_CFS)
|
||||
// SND_SOC_DAIFMT_CBM_CFM)
|
||||
|
||||
SND_SOC_DAILINK_DEF(ls_dai_cpus, COMP_CPU("loongson-i2s-dai"));
|
||||
SND_SOC_DAILINK_DEF(ls_dai_codecs, COMP_CODEC("", ""));
|
||||
SND_SOC_DAILINK_DEF(ls_dai_platforms, COMP_PLATFORM("loongson-i2s"));
|
||||
|
||||
static struct snd_soc_dai_link loongson_dai_link[] = {
|
||||
[PRI_PLAYBACK] = {
|
||||
/* Primary Playback i/f */
|
||||
.name = "Loongson Dai link RX",
|
||||
.stream_name = "Playback",
|
||||
.dai_fmt = LOONGSON_DAI_FMT,
|
||||
.ops = &loongson_dai_link_ops,
|
||||
SND_SOC_DAILINK_REG(ls_dai),
|
||||
},
|
||||
[PRI_CAPTURE] = {
|
||||
/* Primary Capture i/f */
|
||||
.name = "Loongson Dai link TX",
|
||||
.stream_name = "Capture",
|
||||
.dai_fmt = LOONGSON_DAI_FMT,
|
||||
.ops = &loongson_dai_link_ops,
|
||||
SND_SOC_DAILINK_REG(ls_dai),
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_soc_card ls_sound_card = {
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = loongson_dai_link,
|
||||
.num_links = ARRAY_SIZE(loongson_dai_link),
|
||||
|
||||
};
|
||||
|
||||
static void put_of_nodes(struct snd_soc_card *card)
|
||||
{
|
||||
struct snd_soc_dai_link *dai_link;
|
||||
int i;
|
||||
|
||||
for_each_card_prelinks(card, i, dai_link) {
|
||||
of_node_put(dai_link->cpus->of_node);
|
||||
of_node_put(dai_link->codecs->of_node);
|
||||
}
|
||||
}
|
||||
|
||||
static int ls_sound_init_codec_name(struct platform_device* pdev,
|
||||
struct i2c_client *codec_i2c)
|
||||
{
|
||||
char codec_name[32];
|
||||
const char* codec_dai_name;
|
||||
struct snd_soc_component *codec_component;
|
||||
struct snd_soc_dai *codec_dai;
|
||||
|
||||
codec_component = snd_soc_lookup_component(&codec_i2c->dev, NULL);
|
||||
if (!codec_component)
|
||||
{
|
||||
dev_err(&pdev->dev,
|
||||
"I2C codec Not Found\n");
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
// codec-name = <codec-i2c-driver-name>.<i2cx>-<i2c-addr>
|
||||
sprintf(codec_name, "%s.%d-%04x",
|
||||
codec_i2c->dev.driver->name,
|
||||
codec_i2c->adapter->nr, codec_i2c->addr);
|
||||
|
||||
// codec-dai-name = depends on codec driver
|
||||
for_each_component_dais(codec_component, codec_dai){
|
||||
codec_dai_name = codec_dai->name;
|
||||
break;
|
||||
}
|
||||
|
||||
ls_dai_codecs[0].name = codec_name;
|
||||
ls_dai_codecs[0].dai_name = codec_dai_name;
|
||||
|
||||
dev_info(&pdev->dev, "name,dai-name: %s,%s\n", codec_name, codec_dai_name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ls_sound_drv_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct snd_soc_card *card;
|
||||
struct snd_soc_dai_link *dai_link;
|
||||
int ret;
|
||||
struct device_node *codec_np;
|
||||
struct i2c_client *codec_i2c;
|
||||
const char* card_name;
|
||||
|
||||
card = (struct snd_soc_card *)of_device_get_match_data(&pdev->dev);
|
||||
card->dev = &pdev->dev;
|
||||
dai_link = card->dai_link;
|
||||
|
||||
dai_link->cpus->of_node = of_parse_phandle(np, "loongson,i2s-controller", 0);
|
||||
if (!dai_link->cpus->of_node) {
|
||||
dev_err(&pdev->dev,
|
||||
"Property 'loongson,i2s-controller' missing or invalid\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!dai_link->platforms->name)
|
||||
dai_link->platforms->of_node = dai_link->cpus->of_node;
|
||||
|
||||
codec_np = of_parse_phandle(np, "loongson,audio-codec", 0);
|
||||
if (!codec_np) {
|
||||
dev_err(&pdev->dev,
|
||||
"Property 'loongson,audio-codec' missing or invalid\n");
|
||||
ret = -EINVAL;
|
||||
goto err_put_of_nodes;
|
||||
}
|
||||
|
||||
codec_i2c = of_find_i2c_device_by_node(codec_np);
|
||||
if (!codec_i2c)
|
||||
{
|
||||
dev_err(&pdev->dev,
|
||||
"Property 'loongson,audio-codec' is not a i2c device\n");
|
||||
ret = -EINVAL;
|
||||
goto err_put_of_nodes;
|
||||
}
|
||||
|
||||
of_property_read_string(np, "loongson,sound-card-name", &card_name);
|
||||
|
||||
card->name = card_name;
|
||||
|
||||
ls_sound_init_codec_name(pdev, codec_i2c);
|
||||
|
||||
ret = devm_snd_soc_register_card(card->dev, card);
|
||||
if (ret) {
|
||||
if (ret != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev,
|
||||
"snd_soc_register_card() failed: %d\n", ret);
|
||||
goto err_put_of_nodes;
|
||||
}
|
||||
return 0;
|
||||
|
||||
err_put_of_nodes:
|
||||
put_of_nodes(card);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ls_sound_drv_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = platform_get_drvdata(pdev);
|
||||
|
||||
put_of_nodes(card);
|
||||
}
|
||||
|
||||
static const struct of_device_id snd_ls_sound_dt_match[] = {
|
||||
{ .compatible = "loongson,ls-sound", .data = &ls_sound_card},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, snd_ls_sound_dt_match);
|
||||
|
||||
static struct platform_driver ls_sound_driver = {
|
||||
.probe = ls_sound_drv_probe,
|
||||
.remove = ls_sound_drv_remove,
|
||||
.driver = {
|
||||
.name = "ls-sound",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(snd_ls_sound_dt_match),
|
||||
},
|
||||
};
|
||||
|
||||
static int __init loongson_audio_init(void)
|
||||
{
|
||||
return platform_driver_register(&ls_sound_driver);
|
||||
}
|
||||
module_init(loongson_audio_init);
|
||||
|
||||
static void __exit loongson_audio_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&ls_sound_driver);
|
||||
}
|
||||
module_exit(loongson_audio_exit);
|
||||
|
||||
MODULE_AUTHOR("loongson");
|
||||
MODULE_DESCRIPTION("ALSA SoC loongson");
|
||||
MODULE_LICENSE("GPL");
|
||||
Reference in New Issue
Block a user