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:
maoxiaochuan
2025-02-13 16:03:32 +08:00
parent f70d65c829
commit 7ffd18ab9d
107 changed files with 17571 additions and 43 deletions
+1 -1
View File
@@ -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
View File
@@ -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/
+17
View File
@@ -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:
+30
View File
@@ -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
+4
View File
@@ -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
+382
View File
@@ -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");
+15
View File
@@ -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
+16
View File
@@ -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
+592
View File
@@ -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");
+653
View File
@@ -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");
+77
View File
@@ -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
+202
View File
@@ -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");