mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
585 lines
13 KiB
585 lines
13 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* |
|
* linux/sound/soc/pxa/mmp-sspa.c |
|
* Base on pxa2xx-ssp.c |
|
* |
|
* Copyright (C) 2011 Marvell International Ltd. |
|
*/ |
|
#include <linux/init.h> |
|
#include <linux/module.h> |
|
#include <linux/platform_device.h> |
|
#include <linux/delay.h> |
|
#include <linux/clk.h> |
|
#include <linux/slab.h> |
|
#include <linux/io.h> |
|
#include <linux/dmaengine.h> |
|
#include <linux/pm_runtime.h> |
|
|
|
#include <sound/core.h> |
|
#include <sound/pcm.h> |
|
#include <sound/initval.h> |
|
#include <sound/pcm_params.h> |
|
#include <sound/soc.h> |
|
#include <sound/pxa2xx-lib.h> |
|
#include <sound/dmaengine_pcm.h> |
|
#include "mmp-sspa.h" |
|
|
|
/* |
|
* SSPA audio private data |
|
*/ |
|
struct sspa_priv { |
|
void __iomem *tx_base; |
|
void __iomem *rx_base; |
|
|
|
struct snd_dmaengine_dai_dma_data playback_dma_data; |
|
struct snd_dmaengine_dai_dma_data capture_dma_data; |
|
struct clk *clk; |
|
struct clk *audio_clk; |
|
struct clk *sysclk; |
|
|
|
int running_cnt; |
|
u32 sp; |
|
u32 ctrl; |
|
}; |
|
|
|
static void mmp_sspa_tx_enable(struct sspa_priv *sspa) |
|
{ |
|
unsigned int sspa_sp = sspa->sp; |
|
|
|
sspa_sp &= ~SSPA_SP_MSL; |
|
sspa_sp |= SSPA_SP_S_EN; |
|
sspa_sp |= SSPA_SP_WEN; |
|
__raw_writel(sspa_sp, sspa->tx_base + SSPA_SP); |
|
} |
|
|
|
static void mmp_sspa_tx_disable(struct sspa_priv *sspa) |
|
{ |
|
unsigned int sspa_sp = sspa->sp; |
|
|
|
sspa_sp &= ~SSPA_SP_MSL; |
|
sspa_sp &= ~SSPA_SP_S_EN; |
|
sspa_sp |= SSPA_SP_WEN; |
|
__raw_writel(sspa_sp, sspa->tx_base + SSPA_SP); |
|
} |
|
|
|
static void mmp_sspa_rx_enable(struct sspa_priv *sspa) |
|
{ |
|
unsigned int sspa_sp = sspa->sp; |
|
|
|
sspa_sp |= SSPA_SP_S_EN; |
|
sspa_sp |= SSPA_SP_WEN; |
|
__raw_writel(sspa_sp, sspa->rx_base + SSPA_SP); |
|
} |
|
|
|
static void mmp_sspa_rx_disable(struct sspa_priv *sspa) |
|
{ |
|
unsigned int sspa_sp = sspa->sp; |
|
|
|
sspa_sp &= ~SSPA_SP_S_EN; |
|
sspa_sp |= SSPA_SP_WEN; |
|
__raw_writel(sspa_sp, sspa->rx_base + SSPA_SP); |
|
} |
|
|
|
static int mmp_sspa_startup(struct snd_pcm_substream *substream, |
|
struct snd_soc_dai *dai) |
|
{ |
|
struct sspa_priv *sspa = snd_soc_dai_get_drvdata(dai); |
|
|
|
clk_prepare_enable(sspa->sysclk); |
|
clk_prepare_enable(sspa->clk); |
|
|
|
return 0; |
|
} |
|
|
|
static void mmp_sspa_shutdown(struct snd_pcm_substream *substream, |
|
struct snd_soc_dai *dai) |
|
{ |
|
struct sspa_priv *sspa = snd_soc_dai_get_drvdata(dai); |
|
|
|
clk_disable_unprepare(sspa->clk); |
|
clk_disable_unprepare(sspa->sysclk); |
|
} |
|
|
|
/* |
|
* Set the SSP ports SYSCLK. |
|
*/ |
|
static int mmp_sspa_set_dai_sysclk(struct snd_soc_dai *cpu_dai, |
|
int clk_id, unsigned int freq, int dir) |
|
{ |
|
struct sspa_priv *sspa = snd_soc_dai_get_drvdata(cpu_dai); |
|
struct device *dev = cpu_dai->component->dev; |
|
int ret = 0; |
|
|
|
if (dev->of_node) |
|
return -ENOTSUPP; |
|
|
|
switch (clk_id) { |
|
case MMP_SSPA_CLK_AUDIO: |
|
ret = clk_set_rate(sspa->audio_clk, freq); |
|
if (ret) |
|
return ret; |
|
break; |
|
case MMP_SSPA_CLK_PLL: |
|
case MMP_SSPA_CLK_VCXO: |
|
/* not support yet */ |
|
return -EINVAL; |
|
default: |
|
return -EINVAL; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int mmp_sspa_set_dai_pll(struct snd_soc_dai *cpu_dai, int pll_id, |
|
int source, unsigned int freq_in, |
|
unsigned int freq_out) |
|
{ |
|
struct sspa_priv *sspa = snd_soc_dai_get_drvdata(cpu_dai); |
|
struct device *dev = cpu_dai->component->dev; |
|
int ret = 0; |
|
|
|
if (dev->of_node) |
|
return -ENOTSUPP; |
|
|
|
switch (pll_id) { |
|
case MMP_SYSCLK: |
|
ret = clk_set_rate(sspa->sysclk, freq_out); |
|
if (ret) |
|
return ret; |
|
break; |
|
case MMP_SSPA_CLK: |
|
ret = clk_set_rate(sspa->clk, freq_out); |
|
if (ret) |
|
return ret; |
|
break; |
|
default: |
|
return -ENODEV; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
/* |
|
* Set up the sspa dai format. |
|
*/ |
|
static int mmp_sspa_set_dai_fmt(struct snd_soc_dai *cpu_dai, |
|
unsigned int fmt) |
|
{ |
|
struct sspa_priv *sspa = snd_soc_dai_get_drvdata(cpu_dai); |
|
|
|
/* reset port settings */ |
|
sspa->sp = SSPA_SP_WEN | SSPA_SP_S_RST | SSPA_SP_FFLUSH; |
|
sspa->ctrl = 0; |
|
|
|
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { |
|
case SND_SOC_DAIFMT_CBS_CFS: |
|
sspa->sp |= SSPA_SP_MSL; |
|
break; |
|
case SND_SOC_DAIFMT_CBM_CFM: |
|
break; |
|
default: |
|
return -EINVAL; |
|
} |
|
|
|
switch (fmt & SND_SOC_DAIFMT_INV_MASK) { |
|
case SND_SOC_DAIFMT_NB_NF: |
|
sspa->sp |= SSPA_SP_FSP; |
|
break; |
|
default: |
|
return -EINVAL; |
|
} |
|
|
|
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { |
|
case SND_SOC_DAIFMT_I2S: |
|
sspa->ctrl |= SSPA_CTL_XDATDLY(1); |
|
break; |
|
default: |
|
return -EINVAL; |
|
} |
|
|
|
/* Since we are configuring the timings for the format by hand |
|
* we have to defer some things until hw_params() where we |
|
* know parameters like the sample size. |
|
*/ |
|
return 0; |
|
} |
|
|
|
/* |
|
* Set the SSPA audio DMA parameters and sample size. |
|
* Can be called multiple times by oss emulation. |
|
*/ |
|
static int mmp_sspa_hw_params(struct snd_pcm_substream *substream, |
|
struct snd_pcm_hw_params *params, |
|
struct snd_soc_dai *dai) |
|
{ |
|
struct sspa_priv *sspa = snd_soc_dai_get_drvdata(dai); |
|
struct device *dev = dai->component->dev; |
|
u32 sspa_ctrl = sspa->ctrl; |
|
int bits; |
|
int bitval; |
|
|
|
switch (params_format(params)) { |
|
case SNDRV_PCM_FORMAT_S8: |
|
bits = 8; |
|
bitval = SSPA_CTL_8_BITS; |
|
break; |
|
case SNDRV_PCM_FORMAT_S16_LE: |
|
bits = 16; |
|
bitval = SSPA_CTL_16_BITS; |
|
break; |
|
case SNDRV_PCM_FORMAT_S24_3LE: |
|
bits = 24; |
|
bitval = SSPA_CTL_24_BITS; |
|
break; |
|
case SNDRV_PCM_FORMAT_S32_LE: |
|
bits = 32; |
|
bitval = SSPA_CTL_32_BITS; |
|
break; |
|
default: |
|
return -EINVAL; |
|
} |
|
|
|
sspa_ctrl &= ~SSPA_CTL_XPH; |
|
if (dev->of_node || params_channels(params) == 2) |
|
sspa_ctrl |= SSPA_CTL_XPH; |
|
|
|
sspa_ctrl &= ~SSPA_CTL_XWDLEN1_MASK; |
|
sspa_ctrl |= SSPA_CTL_XWDLEN1(bitval); |
|
|
|
sspa_ctrl &= ~SSPA_CTL_XWDLEN2_MASK; |
|
sspa_ctrl |= SSPA_CTL_XWDLEN2(bitval); |
|
|
|
sspa_ctrl &= ~SSPA_CTL_XSSZ1_MASK; |
|
sspa_ctrl |= SSPA_CTL_XSSZ1(bitval); |
|
|
|
sspa_ctrl &= ~SSPA_CTL_XSSZ2_MASK; |
|
sspa_ctrl |= SSPA_CTL_XSSZ2(bitval); |
|
|
|
sspa->sp &= ~SSPA_SP_FWID_MASK; |
|
sspa->sp |= SSPA_SP_FWID(bits - 1); |
|
|
|
sspa->sp &= ~SSPA_TXSP_FPER_MASK; |
|
sspa->sp |= SSPA_TXSP_FPER(bits * 2 - 1); |
|
|
|
if (dev->of_node) { |
|
clk_set_rate(sspa->clk, params_rate(params) * |
|
params_channels(params) * bits); |
|
} |
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { |
|
__raw_writel(sspa_ctrl, sspa->tx_base + SSPA_CTL); |
|
__raw_writel(0x1, sspa->tx_base + SSPA_FIFO_UL); |
|
} else { |
|
__raw_writel(sspa_ctrl, sspa->rx_base + SSPA_CTL); |
|
__raw_writel(0x0, sspa->rx_base + SSPA_FIFO_UL); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int mmp_sspa_trigger(struct snd_pcm_substream *substream, int cmd, |
|
struct snd_soc_dai *dai) |
|
{ |
|
struct sspa_priv *sspa = snd_soc_dai_get_drvdata(dai); |
|
int ret = 0; |
|
|
|
switch (cmd) { |
|
case SNDRV_PCM_TRIGGER_START: |
|
case SNDRV_PCM_TRIGGER_RESUME: |
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
|
/* |
|
* whatever playback or capture, must enable rx. |
|
* this is a hw issue, so need check if rx has been |
|
* enabled or not; if has been enabled by another |
|
* stream, do not enable again. |
|
*/ |
|
if (!sspa->running_cnt) |
|
mmp_sspa_rx_enable(sspa); |
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
|
mmp_sspa_tx_enable(sspa); |
|
|
|
sspa->running_cnt++; |
|
break; |
|
|
|
case SNDRV_PCM_TRIGGER_STOP: |
|
case SNDRV_PCM_TRIGGER_SUSPEND: |
|
case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
|
sspa->running_cnt--; |
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
|
mmp_sspa_tx_disable(sspa); |
|
|
|
/* have no capture stream, disable rx port */ |
|
if (!sspa->running_cnt) |
|
mmp_sspa_rx_disable(sspa); |
|
break; |
|
|
|
default: |
|
ret = -EINVAL; |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static int mmp_sspa_probe(struct snd_soc_dai *dai) |
|
{ |
|
struct sspa_priv *sspa = dev_get_drvdata(dai->dev); |
|
|
|
snd_soc_dai_init_dma_data(dai, |
|
&sspa->playback_dma_data, |
|
&sspa->capture_dma_data); |
|
|
|
return 0; |
|
} |
|
|
|
#define MMP_SSPA_RATES SNDRV_PCM_RATE_8000_192000 |
|
#define MMP_SSPA_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ |
|
SNDRV_PCM_FMTBIT_S16_LE | \ |
|
SNDRV_PCM_FMTBIT_S24_3LE | \ |
|
SNDRV_PCM_FMTBIT_S32_LE) |
|
|
|
static const struct snd_soc_dai_ops mmp_sspa_dai_ops = { |
|
.startup = mmp_sspa_startup, |
|
.shutdown = mmp_sspa_shutdown, |
|
.trigger = mmp_sspa_trigger, |
|
.hw_params = mmp_sspa_hw_params, |
|
.set_sysclk = mmp_sspa_set_dai_sysclk, |
|
.set_pll = mmp_sspa_set_dai_pll, |
|
.set_fmt = mmp_sspa_set_dai_fmt, |
|
}; |
|
|
|
static struct snd_soc_dai_driver mmp_sspa_dai = { |
|
.probe = mmp_sspa_probe, |
|
.playback = { |
|
.channels_min = 1, |
|
.channels_max = 128, |
|
.rates = MMP_SSPA_RATES, |
|
.formats = MMP_SSPA_FORMATS, |
|
}, |
|
.capture = { |
|
.channels_min = 1, |
|
.channels_max = 2, |
|
.rates = MMP_SSPA_RATES, |
|
.formats = MMP_SSPA_FORMATS, |
|
}, |
|
.ops = &mmp_sspa_dai_ops, |
|
}; |
|
|
|
#define MMP_PCM_INFO (SNDRV_PCM_INFO_MMAP | \ |
|
SNDRV_PCM_INFO_MMAP_VALID | \ |
|
SNDRV_PCM_INFO_INTERLEAVED | \ |
|
SNDRV_PCM_INFO_PAUSE | \ |
|
SNDRV_PCM_INFO_RESUME | \ |
|
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP) |
|
|
|
static const struct snd_pcm_hardware mmp_pcm_hardware[] = { |
|
{ |
|
.info = MMP_PCM_INFO, |
|
.period_bytes_min = 1024, |
|
.period_bytes_max = 2048, |
|
.periods_min = 2, |
|
.periods_max = 32, |
|
.buffer_bytes_max = 4096, |
|
.fifo_size = 32, |
|
}, |
|
{ |
|
.info = MMP_PCM_INFO, |
|
.period_bytes_min = 1024, |
|
.period_bytes_max = 2048, |
|
.periods_min = 2, |
|
.periods_max = 32, |
|
.buffer_bytes_max = 4096, |
|
.fifo_size = 32, |
|
}, |
|
}; |
|
|
|
static const struct snd_dmaengine_pcm_config mmp_pcm_config = { |
|
.prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, |
|
.pcm_hardware = mmp_pcm_hardware, |
|
.prealloc_buffer_size = 4096, |
|
}; |
|
|
|
static int mmp_pcm_mmap(struct snd_soc_component *component, |
|
struct snd_pcm_substream *substream, |
|
struct vm_area_struct *vma) |
|
{ |
|
vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP; |
|
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); |
|
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 mmp_sspa_open(struct snd_soc_component *component, |
|
struct snd_pcm_substream *substream) |
|
{ |
|
struct sspa_priv *sspa = snd_soc_component_get_drvdata(component); |
|
|
|
pm_runtime_get_sync(component->dev); |
|
|
|
/* we can only change the settings if the port is not in use */ |
|
if ((__raw_readl(sspa->tx_base + SSPA_SP) & SSPA_SP_S_EN) || |
|
(__raw_readl(sspa->rx_base + SSPA_SP) & SSPA_SP_S_EN)) { |
|
dev_err(component->dev, |
|
"can't change hardware dai format: stream is in use\n"); |
|
return -EBUSY; |
|
} |
|
|
|
__raw_writel(sspa->sp, sspa->tx_base + SSPA_SP); |
|
__raw_writel(sspa->sp, sspa->rx_base + SSPA_SP); |
|
|
|
sspa->sp &= ~(SSPA_SP_S_RST | SSPA_SP_FFLUSH); |
|
__raw_writel(sspa->sp, sspa->tx_base + SSPA_SP); |
|
__raw_writel(sspa->sp, sspa->rx_base + SSPA_SP); |
|
|
|
/* |
|
* FIXME: hw issue, for the tx serial port, |
|
* can not config the master/slave mode; |
|
* so must clean this bit. |
|
* The master/slave mode has been set in the |
|
* rx port. |
|
*/ |
|
__raw_writel(sspa->sp & ~SSPA_SP_MSL, sspa->tx_base + SSPA_SP); |
|
|
|
__raw_writel(sspa->ctrl, sspa->tx_base + SSPA_CTL); |
|
__raw_writel(sspa->ctrl, sspa->rx_base + SSPA_CTL); |
|
|
|
return 0; |
|
} |
|
|
|
static int mmp_sspa_close(struct snd_soc_component *component, |
|
struct snd_pcm_substream *substream) |
|
{ |
|
pm_runtime_put_sync(component->dev); |
|
return 0; |
|
} |
|
|
|
static const struct snd_soc_component_driver mmp_sspa_component = { |
|
.name = "mmp-sspa", |
|
.mmap = mmp_pcm_mmap, |
|
.open = mmp_sspa_open, |
|
.close = mmp_sspa_close, |
|
}; |
|
|
|
static int asoc_mmp_sspa_probe(struct platform_device *pdev) |
|
{ |
|
struct sspa_priv *sspa; |
|
int ret; |
|
|
|
sspa = devm_kzalloc(&pdev->dev, |
|
sizeof(struct sspa_priv), GFP_KERNEL); |
|
if (!sspa) |
|
return -ENOMEM; |
|
|
|
if (pdev->dev.of_node) { |
|
sspa->rx_base = devm_platform_ioremap_resource(pdev, 0); |
|
if (IS_ERR(sspa->rx_base)) |
|
return PTR_ERR(sspa->rx_base); |
|
|
|
sspa->tx_base = devm_platform_ioremap_resource(pdev, 1); |
|
if (IS_ERR(sspa->tx_base)) |
|
return PTR_ERR(sspa->tx_base); |
|
|
|
sspa->clk = devm_clk_get(&pdev->dev, "bitclk"); |
|
if (IS_ERR(sspa->clk)) |
|
return PTR_ERR(sspa->clk); |
|
|
|
sspa->audio_clk = devm_clk_get(&pdev->dev, "audio"); |
|
if (IS_ERR(sspa->audio_clk)) |
|
return PTR_ERR(sspa->audio_clk); |
|
} else { |
|
struct resource *res; |
|
|
|
res = platform_get_resource(pdev, IORESOURCE_IO, 0); |
|
if (res == NULL) |
|
return -ENODEV; |
|
|
|
sspa->rx_base = devm_ioremap(&pdev->dev, res->start, 0x30); |
|
if (!sspa->rx_base) |
|
return -ENOMEM; |
|
|
|
sspa->tx_base = devm_ioremap(&pdev->dev, |
|
res->start + 0x80, 0x30); |
|
if (!sspa->tx_base) |
|
return -ENOMEM; |
|
|
|
sspa->clk = devm_clk_get(&pdev->dev, NULL); |
|
if (IS_ERR(sspa->clk)) |
|
return PTR_ERR(sspa->clk); |
|
|
|
sspa->audio_clk = clk_get(NULL, "mmp-audio"); |
|
if (IS_ERR(sspa->audio_clk)) |
|
return PTR_ERR(sspa->audio_clk); |
|
|
|
sspa->sysclk = clk_get(NULL, "mmp-sysclk"); |
|
if (IS_ERR(sspa->sysclk)) { |
|
clk_put(sspa->audio_clk); |
|
return PTR_ERR(sspa->sysclk); |
|
} |
|
} |
|
platform_set_drvdata(pdev, sspa); |
|
|
|
sspa->playback_dma_data.maxburst = 4; |
|
sspa->capture_dma_data.maxburst = 4; |
|
/* You know, these addresses are actually ignored. */ |
|
sspa->capture_dma_data.addr = SSPA_D; |
|
sspa->playback_dma_data.addr = 0x80 + SSPA_D; |
|
|
|
if (pdev->dev.of_node) { |
|
ret = devm_snd_dmaengine_pcm_register(&pdev->dev, |
|
&mmp_pcm_config, 0); |
|
if (ret) |
|
return ret; |
|
} |
|
|
|
ret = devm_snd_soc_register_component(&pdev->dev, &mmp_sspa_component, |
|
&mmp_sspa_dai, 1); |
|
if (ret) |
|
return ret; |
|
|
|
pm_runtime_enable(&pdev->dev); |
|
clk_prepare_enable(sspa->audio_clk); |
|
|
|
return 0; |
|
} |
|
|
|
static int asoc_mmp_sspa_remove(struct platform_device *pdev) |
|
{ |
|
struct sspa_priv *sspa = platform_get_drvdata(pdev); |
|
|
|
clk_disable_unprepare(sspa->audio_clk); |
|
pm_runtime_disable(&pdev->dev); |
|
|
|
if (pdev->dev.of_node) |
|
return 0; |
|
|
|
clk_put(sspa->audio_clk); |
|
clk_put(sspa->sysclk); |
|
return 0; |
|
} |
|
|
|
#ifdef CONFIG_OF |
|
static const struct of_device_id mmp_sspa_of_match[] = { |
|
{ .compatible = "marvell,mmp-sspa" }, |
|
{}, |
|
}; |
|
|
|
MODULE_DEVICE_TABLE(of, mmp_sspa_of_match); |
|
#endif |
|
|
|
static struct platform_driver asoc_mmp_sspa_driver = { |
|
.driver = { |
|
.name = "mmp-sspa-dai", |
|
.of_match_table = of_match_ptr(mmp_sspa_of_match), |
|
}, |
|
.probe = asoc_mmp_sspa_probe, |
|
.remove = asoc_mmp_sspa_remove, |
|
}; |
|
|
|
module_platform_driver(asoc_mmp_sspa_driver); |
|
|
|
MODULE_AUTHOR("Leo Yan <[email protected]>"); |
|
MODULE_DESCRIPTION("MMP SSPA SoC Interface"); |
|
MODULE_LICENSE("GPL"); |
|
MODULE_ALIAS("platform:mmp-sspa-dai");
|
|
|