Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+63
View File
@@ -0,0 +1,63 @@
# SPDX-License-Identifier: GPL-2.0-only
menu "PXA"
config SND_PXA2XX_SOC
tristate "SoC Audio for the Intel PXA2xx chip"
depends on ARCH_PXA || (COMPILE_TEST && GPIOLIB_LEGACY)
select SND_PXA2XX_LIB
help
Say Y or M if you want to add support for codecs attached to
the PXA2xx AC97, I2S or SSP interface. You will also need
to select the audio interfaces to support below.
config SND_PXA2XX_SOC_AC97
tristate "SoC AC97 support for PXA2xx"
depends on SND_PXA2XX_SOC
depends on AC97_BUS=n
default y
select AC97_BUS_NEW
select SND_PXA2XX_LIB
select SND_PXA2XX_LIB_AC97
config SND_PXA2XX_SOC_I2S
select SND_PXA2XX_LIB
tristate
config SND_PXA_SOC_SSP
tristate "Soc Audio via PXA2xx/PXA3xx SSP ports"
depends on ARCH_PXA
select PXA_SSP
select SND_PXA2XX_LIB
config SND_MMP_SOC_SSPA
tristate "SoC Audio via MMP SSPA ports"
depends on ARCH_MMP
select SND_SOC_GENERIC_DMAENGINE_PCM
help
Say Y if you want to add support for codecs attached to
the MMP SSPA interface.
config SND_PXA2XX_SOC_SPITZ
tristate "SoC Audio support for Sharp Zaurus SL-Cxx00"
depends on SND_PXA2XX_SOC && PXA_SHARP_Cxx00 && I2C
select SND_PXA2XX_SOC_I2S
select SND_SOC_WM8750
help
Say Y if you want to add support for SoC audio on Sharp
Zaurus SL-Cxx00 models (Spitz, Borzoi and Akita).
config SND_PXA910_SOC
tristate "SoC Audio for Marvell PXA910 chip"
depends on ARCH_MMP && SND
select SND_PCM
help
Say Y if you want to add support for SoC audio on the
Marvell PXA910 reference platform.
config SND_PXA2XX_LIB
tristate
select SND_DMAENGINE_PCM
config SND_PXA2XX_LIB_AC97
bool
endmenu
+19
View File
@@ -0,0 +1,19 @@
# SPDX-License-Identifier: GPL-2.0
# PXA Platform Support
snd-soc-pxa2xx-y := pxa2xx-pcm.o
snd-soc-pxa2xx-ac97-y := pxa2xx-ac97.o pxa2xx-ac97-lib.o
snd-soc-pxa2xx-i2s-y := pxa2xx-i2s.o
snd-soc-pxa2xx-lib-y := pxa2xx-pcm-lib.o
snd-soc-pxa-ssp-y := pxa-ssp.o
snd-soc-mmp-sspa-y := mmp-sspa.o
obj-$(CONFIG_SND_PXA2XX_SOC) += snd-soc-pxa2xx.o
obj-$(CONFIG_SND_PXA2XX_SOC_AC97) += snd-soc-pxa2xx-ac97.o
obj-$(CONFIG_SND_PXA2XX_SOC_I2S) += snd-soc-pxa2xx-i2s.o
obj-$(CONFIG_SND_PXA_SOC_SSP) += snd-soc-pxa-ssp.o
obj-$(CONFIG_SND_MMP_SOC_SSPA) += snd-soc-mmp-sspa.o
obj-$(CONFIG_SND_PXA2XX_LIB) += snd-soc-pxa2xx-lib.o
# PXA Machine Support
snd-soc-spitz-y := spitz.o
obj-$(CONFIG_SND_PXA2XX_SOC_SPITZ) += snd-soc-spitz.o
+585
View File
@@ -0,0 +1,585 @@
// 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/dmaengine_pcm.h>
#include "pxa2xx-lib.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_CLOCK_PROVIDER_MASK) {
case SND_SOC_DAIFMT_BP_FP:
sspa->sp |= SSPA_SP_MSL;
break;
case SND_SOC_DAIFMT_BC_FC:
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 = {
.probe = mmp_sspa_probe,
.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 = {
.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)
{
vm_flags_set(vma, 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,
.legacy_dai_naming = 1,
};
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 void 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;
clk_put(sspa->audio_clk);
clk_put(sspa->sysclk);
}
#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 <leoy@marvell.com>");
MODULE_DESCRIPTION("MMP SSPA SoC Interface");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:mmp-sspa-dai");
+70
View File
@@ -0,0 +1,70 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* linux/sound/soc/pxa/mmp-sspa.h
*
* Copyright (C) 2011 Marvell International Ltd.
*/
#ifndef _MMP_SSPA_H
#define _MMP_SSPA_H
/*
* SSPA Registers
*/
#define SSPA_D (0x00)
#define SSPA_ID (0x04)
#define SSPA_CTL (0x08)
#define SSPA_SP (0x0c)
#define SSPA_FIFO_UL (0x10)
#define SSPA_INT_MASK (0x14)
#define SSPA_C (0x18)
#define SSPA_FIFO_NOFS (0x1c)
#define SSPA_FIFO_SIZE (0x20)
/* SSPA Control Register */
#define SSPA_CTL_XPH (1 << 31) /* Read Phase */
#define SSPA_CTL_XFIG (1 << 15) /* Transmit Zeros when FIFO Empty */
#define SSPA_CTL_JST (1 << 3) /* Audio Sample Justification */
#define SSPA_CTL_XFRLEN2_MASK (7 << 24)
#define SSPA_CTL_XFRLEN2(x) ((x) << 24) /* Transmit Frame Length in Phase 2 */
#define SSPA_CTL_XWDLEN2_MASK (7 << 21)
#define SSPA_CTL_XWDLEN2(x) ((x) << 21) /* Transmit Word Length in Phase 2 */
#define SSPA_CTL_XDATDLY(x) ((x) << 19) /* Transmit Data Delay */
#define SSPA_CTL_XSSZ2_MASK (7 << 16)
#define SSPA_CTL_XSSZ2(x) ((x) << 16) /* Transmit Sample Audio Size */
#define SSPA_CTL_XFRLEN1_MASK (7 << 8)
#define SSPA_CTL_XFRLEN1(x) ((x) << 8) /* Transmit Frame Length in Phase 1 */
#define SSPA_CTL_XWDLEN1_MASK (7 << 5)
#define SSPA_CTL_XWDLEN1(x) ((x) << 5) /* Transmit Word Length in Phase 1 */
#define SSPA_CTL_XSSZ1_MASK (7 << 0)
#define SSPA_CTL_XSSZ1(x) ((x) << 0) /* XSSZ1 */
#define SSPA_CTL_8_BITS (0x0) /* Sample Size */
#define SSPA_CTL_12_BITS (0x1)
#define SSPA_CTL_16_BITS (0x2)
#define SSPA_CTL_20_BITS (0x3)
#define SSPA_CTL_24_BITS (0x4)
#define SSPA_CTL_32_BITS (0x5)
/* SSPA Serial Port Register */
#define SSPA_SP_WEN (1 << 31) /* Write Configuration Enable */
#define SSPA_SP_MSL (1 << 18) /* Master Slave Configuration */
#define SSPA_SP_CLKP (1 << 17) /* CLKP Polarity Clock Edge Select */
#define SSPA_SP_FSP (1 << 16) /* FSP Polarity Clock Edge Select */
#define SSPA_SP_FFLUSH (1 << 2) /* FIFO Flush */
#define SSPA_SP_S_RST (1 << 1) /* Active High Reset Signal */
#define SSPA_SP_S_EN (1 << 0) /* Serial Clock Domain Enable */
#define SSPA_SP_FWID_MASK (0x3f << 20)
#define SSPA_SP_FWID(x) ((x) << 20) /* Frame-Sync Width */
#define SSPA_TXSP_FPER_MASK (0x3f << 4)
#define SSPA_TXSP_FPER(x) ((x) << 4) /* Frame-Sync Active */
/* sspa clock sources */
#define MMP_SSPA_CLK_PLL 0
#define MMP_SSPA_CLK_VCXO 1
#define MMP_SSPA_CLK_AUDIO 3
/* sspa pll id */
#define MMP_SYSCLK 0
#define MMP_SSPA_CLK 1
#endif /* _MMP_SSPA_H */
+887
View File
@@ -0,0 +1,887 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* pxa-ssp.c -- ALSA Soc Audio Layer
*
* Copyright 2005,2008 Wolfson Microelectronics PLC.
* Author: Liam Girdwood
* Mark Brown <broonie@opensource.wolfsonmicro.com>
*
* TODO:
* o Test network mode for > 16bit sample size
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/pxa2xx_ssp.h>
#include <linux/of.h>
#include <linux/dmaengine.h>
#include <asm/irq.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/dmaengine_pcm.h>
#include "pxa2xx-lib.h"
#include "pxa-ssp.h"
/*
* SSP audio private data
*/
struct ssp_priv {
struct ssp_device *ssp;
struct clk *extclk;
unsigned long ssp_clk;
unsigned int sysclk;
unsigned int dai_fmt;
unsigned int configured_dai_fmt;
#ifdef CONFIG_PM
uint32_t cr0;
uint32_t cr1;
uint32_t to;
uint32_t psp;
#endif
};
static void dump_registers(struct ssp_device *ssp)
{
dev_dbg(ssp->dev, "SSCR0 0x%08x SSCR1 0x%08x SSTO 0x%08x\n",
pxa_ssp_read_reg(ssp, SSCR0), pxa_ssp_read_reg(ssp, SSCR1),
pxa_ssp_read_reg(ssp, SSTO));
dev_dbg(ssp->dev, "SSPSP 0x%08x SSSR 0x%08x SSACD 0x%08x\n",
pxa_ssp_read_reg(ssp, SSPSP), pxa_ssp_read_reg(ssp, SSSR),
pxa_ssp_read_reg(ssp, SSACD));
}
static void pxa_ssp_set_dma_params(struct ssp_device *ssp, int width4,
int out, struct snd_dmaengine_dai_dma_data *dma)
{
dma->addr_width = width4 ? DMA_SLAVE_BUSWIDTH_4_BYTES :
DMA_SLAVE_BUSWIDTH_2_BYTES;
dma->maxburst = 16;
dma->addr = ssp->phys_base + SSDR;
}
static int pxa_ssp_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai)
{
struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
struct ssp_device *ssp = priv->ssp;
struct snd_dmaengine_dai_dma_data *dma;
int ret = 0;
if (!snd_soc_dai_active(cpu_dai)) {
clk_prepare_enable(ssp->clk);
pxa_ssp_disable(ssp);
}
clk_prepare_enable(priv->extclk);
dma = kzalloc_obj(struct snd_dmaengine_dai_dma_data);
if (!dma)
return -ENOMEM;
dma->chan_name = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
"tx" : "rx";
snd_soc_dai_set_dma_data(cpu_dai, substream, dma);
return ret;
}
static void pxa_ssp_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai)
{
struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
struct ssp_device *ssp = priv->ssp;
if (!snd_soc_dai_active(cpu_dai)) {
pxa_ssp_disable(ssp);
clk_disable_unprepare(ssp->clk);
}
clk_disable_unprepare(priv->extclk);
kfree(snd_soc_dai_get_dma_data(cpu_dai, substream));
snd_soc_dai_set_dma_data(cpu_dai, substream, NULL);
}
#ifdef CONFIG_PM
static int pxa_ssp_suspend(struct snd_soc_component *component)
{
struct ssp_priv *priv = snd_soc_component_get_drvdata(component);
struct ssp_device *ssp = priv->ssp;
if (!snd_soc_component_active(component))
clk_prepare_enable(ssp->clk);
priv->cr0 = __raw_readl(ssp->mmio_base + SSCR0);
priv->cr1 = __raw_readl(ssp->mmio_base + SSCR1);
priv->to = __raw_readl(ssp->mmio_base + SSTO);
priv->psp = __raw_readl(ssp->mmio_base + SSPSP);
pxa_ssp_disable(ssp);
clk_disable_unprepare(ssp->clk);
return 0;
}
static int pxa_ssp_resume(struct snd_soc_component *component)
{
struct ssp_priv *priv = snd_soc_component_get_drvdata(component);
struct ssp_device *ssp = priv->ssp;
uint32_t sssr = SSSR_ROR | SSSR_TUR | SSSR_BCE;
clk_prepare_enable(ssp->clk);
__raw_writel(sssr, ssp->mmio_base + SSSR);
__raw_writel(priv->cr0 & ~SSCR0_SSE, ssp->mmio_base + SSCR0);
__raw_writel(priv->cr1, ssp->mmio_base + SSCR1);
__raw_writel(priv->to, ssp->mmio_base + SSTO);
__raw_writel(priv->psp, ssp->mmio_base + SSPSP);
if (snd_soc_component_active(component))
pxa_ssp_enable(ssp);
else
clk_disable_unprepare(ssp->clk);
return 0;
}
#else
#define pxa_ssp_suspend NULL
#define pxa_ssp_resume NULL
#endif
/*
* ssp_set_clkdiv - set SSP clock divider
* @div: serial clock rate divider
*/
static void pxa_ssp_set_scr(struct ssp_device *ssp, u32 div)
{
u32 sscr0 = pxa_ssp_read_reg(ssp, SSCR0);
if (ssp->type == PXA25x_SSP) {
sscr0 &= ~0x0000ff00;
sscr0 |= ((div - 2)/2) << 8; /* 2..512 */
} else {
sscr0 &= ~0x000fff00;
sscr0 |= (div - 1) << 8; /* 1..4096 */
}
pxa_ssp_write_reg(ssp, SSCR0, sscr0);
}
/*
* Set the SSP ports SYSCLK.
*/
static int pxa_ssp_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
int clk_id, unsigned int freq, int dir)
{
struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
struct ssp_device *ssp = priv->ssp;
u32 sscr0 = pxa_ssp_read_reg(ssp, SSCR0) &
~(SSCR0_ECS | SSCR0_NCS | SSCR0_MOD | SSCR0_ACS);
if (priv->extclk) {
int ret;
/*
* For DT based boards, if an extclk is given, use it
* here and configure PXA_SSP_CLK_EXT.
*/
ret = clk_set_rate(priv->extclk, freq);
if (ret < 0)
return ret;
clk_id = PXA_SSP_CLK_EXT;
}
dev_dbg(ssp->dev,
"pxa_ssp_set_dai_sysclk id: %d, clk_id %d, freq %u\n",
cpu_dai->id, clk_id, freq);
switch (clk_id) {
case PXA_SSP_CLK_NET_PLL:
sscr0 |= SSCR0_MOD;
break;
case PXA_SSP_CLK_PLL:
/* Internal PLL is fixed */
if (ssp->type == PXA25x_SSP)
priv->sysclk = 1843200;
else
priv->sysclk = 13000000;
break;
case PXA_SSP_CLK_EXT:
priv->sysclk = freq;
sscr0 |= SSCR0_ECS;
break;
case PXA_SSP_CLK_NET:
priv->sysclk = freq;
sscr0 |= SSCR0_NCS | SSCR0_MOD;
break;
case PXA_SSP_CLK_AUDIO:
priv->sysclk = 0;
pxa_ssp_set_scr(ssp, 1);
sscr0 |= SSCR0_ACS;
break;
default:
return -ENODEV;
}
/* The SSP clock must be disabled when changing SSP clock mode
* on PXA2xx. On PXA3xx it must be enabled when doing so. */
if (ssp->type != PXA3xx_SSP)
clk_disable_unprepare(ssp->clk);
pxa_ssp_write_reg(ssp, SSCR0, sscr0);
if (ssp->type != PXA3xx_SSP)
clk_prepare_enable(ssp->clk);
return 0;
}
/*
* Configure the PLL frequency pxa27x and (afaik - pxa320 only)
*/
static int pxa_ssp_set_pll(struct ssp_priv *priv, unsigned int freq)
{
struct ssp_device *ssp = priv->ssp;
u32 ssacd = pxa_ssp_read_reg(ssp, SSACD) & ~0x70;
if (ssp->type == PXA3xx_SSP)
pxa_ssp_write_reg(ssp, SSACDD, 0);
switch (freq) {
case 5622000:
break;
case 11345000:
ssacd |= (0x1 << 4);
break;
case 12235000:
ssacd |= (0x2 << 4);
break;
case 14857000:
ssacd |= (0x3 << 4);
break;
case 32842000:
ssacd |= (0x4 << 4);
break;
case 48000000:
ssacd |= (0x5 << 4);
break;
case 0:
/* Disable */
break;
default:
/* PXA3xx has a clock ditherer which can be used to generate
* a wider range of frequencies - calculate a value for it.
*/
if (ssp->type == PXA3xx_SSP) {
u32 val;
u64 tmp = 19968;
tmp *= 1000000;
do_div(tmp, freq);
val = tmp;
val = (val << 16) | 64;
pxa_ssp_write_reg(ssp, SSACDD, val);
ssacd |= (0x6 << 4);
dev_dbg(ssp->dev,
"Using SSACDD %x to supply %uHz\n",
val, freq);
break;
}
return -EINVAL;
}
pxa_ssp_write_reg(ssp, SSACD, ssacd);
return 0;
}
/*
* Set the active slots in TDM/Network mode
*/
static int pxa_ssp_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai,
unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width)
{
struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
struct ssp_device *ssp = priv->ssp;
u32 sscr0;
sscr0 = pxa_ssp_read_reg(ssp, SSCR0);
sscr0 &= ~(SSCR0_MOD | SSCR0_SlotsPerFrm(8) | SSCR0_EDSS | SSCR0_DSS);
/* set slot width */
if (slot_width > 16)
sscr0 |= SSCR0_EDSS | SSCR0_DataSize(slot_width - 16);
else
sscr0 |= SSCR0_DataSize(slot_width);
if (slots > 1) {
/* enable network mode */
sscr0 |= SSCR0_MOD;
/* set number of active slots */
sscr0 |= SSCR0_SlotsPerFrm(slots);
/* set active slot mask */
pxa_ssp_write_reg(ssp, SSTSA, tx_mask);
pxa_ssp_write_reg(ssp, SSRSA, rx_mask);
}
pxa_ssp_write_reg(ssp, SSCR0, sscr0);
return 0;
}
/*
* Tristate the SSP DAI lines
*/
static int pxa_ssp_set_dai_tristate(struct snd_soc_dai *cpu_dai,
int tristate)
{
struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
struct ssp_device *ssp = priv->ssp;
u32 sscr1;
sscr1 = pxa_ssp_read_reg(ssp, SSCR1);
if (tristate)
sscr1 &= ~SSCR1_TTE;
else
sscr1 |= SSCR1_TTE;
pxa_ssp_write_reg(ssp, SSCR1, sscr1);
return 0;
}
static int pxa_ssp_set_dai_fmt(struct snd_soc_dai *cpu_dai,
unsigned int fmt)
{
struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
case SND_SOC_DAIFMT_BC_FC:
case SND_SOC_DAIFMT_BC_FP:
case SND_SOC_DAIFMT_BP_FP:
break;
default:
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
case SND_SOC_DAIFMT_NB_IF:
case SND_SOC_DAIFMT_IB_IF:
case SND_SOC_DAIFMT_IB_NF:
break;
default:
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
case SND_SOC_DAIFMT_DSP_A:
case SND_SOC_DAIFMT_DSP_B:
break;
default:
return -EINVAL;
}
/* Settings will be applied in hw_params() */
priv->dai_fmt = fmt;
return 0;
}
/*
* Set up the SSP DAI format.
* The SSP Port must be inactive before calling this function as the
* physical interface format is changed.
*/
static int pxa_ssp_configure_dai_fmt(struct ssp_priv *priv)
{
struct ssp_device *ssp = priv->ssp;
u32 sscr0, sscr1, sspsp, scfr;
/* check if we need to change anything at all */
if (priv->configured_dai_fmt == priv->dai_fmt)
return 0;
/* reset port settings */
sscr0 = pxa_ssp_read_reg(ssp, SSCR0) &
~(SSCR0_PSP | SSCR0_MOD);
sscr1 = pxa_ssp_read_reg(ssp, SSCR1) &
~(SSCR1_SCLKDIR | SSCR1_SFRMDIR | SSCR1_SCFR |
SSCR1_RWOT | SSCR1_TRAIL | SSCR1_TFT | SSCR1_RFT);
sspsp = pxa_ssp_read_reg(ssp, SSPSP) &
~(SSPSP_SFRMP | SSPSP_SCMODE(3));
sscr1 |= SSCR1_RxTresh(8) | SSCR1_TxTresh(7);
switch (priv->dai_fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
case SND_SOC_DAIFMT_BC_FC:
sscr1 |= SSCR1_SCLKDIR | SSCR1_SFRMDIR | SSCR1_SCFR;
break;
case SND_SOC_DAIFMT_BC_FP:
sscr1 |= SSCR1_SCLKDIR | SSCR1_SCFR;
break;
case SND_SOC_DAIFMT_BP_FP:
break;
default:
return -EINVAL;
}
switch (priv->dai_fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
sspsp |= SSPSP_SFRMP;
break;
case SND_SOC_DAIFMT_NB_IF:
break;
case SND_SOC_DAIFMT_IB_IF:
sspsp |= SSPSP_SCMODE(2);
break;
case SND_SOC_DAIFMT_IB_NF:
sspsp |= SSPSP_SCMODE(2) | SSPSP_SFRMP;
break;
default:
return -EINVAL;
}
switch (priv->dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
sscr0 |= SSCR0_PSP;
sscr1 |= SSCR1_RWOT | SSCR1_TRAIL;
/* See hw_params() */
break;
case SND_SOC_DAIFMT_DSP_A:
sspsp |= SSPSP_FSRT;
fallthrough;
case SND_SOC_DAIFMT_DSP_B:
sscr0 |= SSCR0_MOD | SSCR0_PSP;
sscr1 |= SSCR1_TRAIL | SSCR1_RWOT;
break;
default:
return -EINVAL;
}
pxa_ssp_write_reg(ssp, SSCR0, sscr0);
pxa_ssp_write_reg(ssp, SSCR1, sscr1);
pxa_ssp_write_reg(ssp, SSPSP, sspsp);
switch (priv->dai_fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
case SND_SOC_DAIFMT_BC_FC:
case SND_SOC_DAIFMT_BC_FP:
scfr = pxa_ssp_read_reg(ssp, SSCR1) | SSCR1_SCFR;
pxa_ssp_write_reg(ssp, SSCR1, scfr);
while (pxa_ssp_read_reg(ssp, SSSR) & SSSR_BSY)
cpu_relax();
break;
}
dump_registers(ssp);
/* 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.
*/
priv->configured_dai_fmt = priv->dai_fmt;
return 0;
}
struct pxa_ssp_clock_mode {
int rate;
int pll;
u8 acds;
u8 scdb;
};
static const struct pxa_ssp_clock_mode pxa_ssp_clock_modes[] = {
{ .rate = 8000, .pll = 32842000, .acds = SSACD_ACDS_32, .scdb = SSACD_SCDB_4X },
{ .rate = 11025, .pll = 5622000, .acds = SSACD_ACDS_4, .scdb = SSACD_SCDB_4X },
{ .rate = 16000, .pll = 32842000, .acds = SSACD_ACDS_16, .scdb = SSACD_SCDB_4X },
{ .rate = 22050, .pll = 5622000, .acds = SSACD_ACDS_2, .scdb = SSACD_SCDB_4X },
{ .rate = 44100, .pll = 11345000, .acds = SSACD_ACDS_2, .scdb = SSACD_SCDB_4X },
{ .rate = 48000, .pll = 12235000, .acds = SSACD_ACDS_2, .scdb = SSACD_SCDB_4X },
{ .rate = 96000, .pll = 12235000, .acds = SSACD_ACDS_4, .scdb = SSACD_SCDB_1X },
{}
};
/*
* Set the SSP audio DMA parameters and sample size.
* Can be called multiple times by oss emulation.
*/
static int pxa_ssp_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *cpu_dai)
{
struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
struct ssp_device *ssp = priv->ssp;
int chn = params_channels(params);
u32 sscr0, sspsp;
int width = snd_pcm_format_physical_width(params_format(params));
int ttsa = pxa_ssp_read_reg(ssp, SSTSA) & 0xf;
struct snd_dmaengine_dai_dma_data *dma_data;
int rate = params_rate(params);
int bclk = rate * chn * (width / 8);
int ret;
dma_data = snd_soc_dai_get_dma_data(cpu_dai, substream);
/* Network mode with one active slot (ttsa == 1) can be used
* to force 16-bit frame width on the wire (for S16_LE), even
* with two channels. Use 16-bit DMA transfers for this case.
*/
pxa_ssp_set_dma_params(ssp,
((chn == 2) && (ttsa != 1)) || (width == 32),
substream->stream == SNDRV_PCM_STREAM_PLAYBACK, dma_data);
/* we can only change the settings if the port is not in use */
if (pxa_ssp_read_reg(ssp, SSCR0) & SSCR0_SSE)
return 0;
ret = pxa_ssp_configure_dai_fmt(priv);
if (ret < 0)
return ret;
/* clear selected SSP bits */
sscr0 = pxa_ssp_read_reg(ssp, SSCR0) & ~(SSCR0_DSS | SSCR0_EDSS);
/* bit size */
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
if (ssp->type == PXA3xx_SSP)
sscr0 |= SSCR0_FPCKE;
sscr0 |= SSCR0_DataSize(16);
break;
case SNDRV_PCM_FORMAT_S24_LE:
sscr0 |= (SSCR0_EDSS | SSCR0_DataSize(8));
break;
case SNDRV_PCM_FORMAT_S32_LE:
sscr0 |= (SSCR0_EDSS | SSCR0_DataSize(16));
break;
}
pxa_ssp_write_reg(ssp, SSCR0, sscr0);
if (sscr0 & SSCR0_ACS) {
ret = pxa_ssp_set_pll(priv, bclk);
/*
* If we were able to generate the bclk directly,
* all is fine. Otherwise, look up the closest rate
* from the table and also set the dividers.
*/
if (ret < 0) {
const struct pxa_ssp_clock_mode *m;
int ssacd;
for (m = pxa_ssp_clock_modes; m->rate; m++) {
if (m->rate == rate)
break;
}
if (!m->rate)
return -EINVAL;
ret = pxa_ssp_set_pll(priv, bclk);
if (ret < 0)
return ret;
ssacd = pxa_ssp_read_reg(ssp, SSACD);
ssacd &= ~(SSACD_ACDS(7) | SSACD_SCDB_1X);
ssacd |= SSACD_ACDS(m->acds);
ssacd |= m->scdb;
pxa_ssp_write_reg(ssp, SSACD, ssacd);
}
} else if (sscr0 & SSCR0_ECS) {
/*
* For setups with external clocking, the PLL and its diviers
* are not active. Instead, the SCR bits in SSCR0 can be used
* to divide the clock.
*/
pxa_ssp_set_scr(ssp, bclk / rate);
}
switch (priv->dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
sspsp = pxa_ssp_read_reg(ssp, SSPSP);
if (((priv->sysclk / bclk) == 64) && (width == 16)) {
/* This is a special case where the bitclk is 64fs
* and we're not dealing with 2*32 bits of audio
* samples.
*
* The SSP values used for that are all found out by
* trying and failing a lot; some of the registers
* needed for that mode are only available on PXA3xx.
*/
if (ssp->type != PXA3xx_SSP)
return -EINVAL;
sspsp |= SSPSP_SFRMWDTH(width * 2);
sspsp |= SSPSP_SFRMDLY(width * 4);
sspsp |= SSPSP_EDMYSTOP(3);
sspsp |= SSPSP_DMYSTOP(3);
sspsp |= SSPSP_DMYSTRT(1);
} else {
/* The frame width is the width the LRCLK is
* asserted for; the delay is expressed in
* half cycle units. We need the extra cycle
* because the data starts clocking out one BCLK
* after LRCLK changes polarity.
*/
sspsp |= SSPSP_SFRMWDTH(width + 1);
sspsp |= SSPSP_SFRMDLY((width + 1) * 2);
sspsp |= SSPSP_DMYSTRT(1);
}
pxa_ssp_write_reg(ssp, SSPSP, sspsp);
break;
default:
break;
}
/* When we use a network mode, we always require TDM slots
* - complain loudly and fail if they've not been set up yet.
*/
if ((sscr0 & SSCR0_MOD) && !ttsa) {
dev_err(ssp->dev, "No TDM timeslot configured\n");
return -EINVAL;
}
dump_registers(ssp);
return 0;
}
static void pxa_ssp_set_running_bit(struct snd_pcm_substream *substream,
struct ssp_device *ssp, int value)
{
uint32_t sscr0 = pxa_ssp_read_reg(ssp, SSCR0);
uint32_t sscr1 = pxa_ssp_read_reg(ssp, SSCR1);
uint32_t sspsp = pxa_ssp_read_reg(ssp, SSPSP);
uint32_t sssr = pxa_ssp_read_reg(ssp, SSSR);
if (value && (sscr0 & SSCR0_SSE))
pxa_ssp_write_reg(ssp, SSCR0, sscr0 & ~SSCR0_SSE);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
if (value)
sscr1 |= SSCR1_TSRE;
else
sscr1 &= ~SSCR1_TSRE;
} else {
if (value)
sscr1 |= SSCR1_RSRE;
else
sscr1 &= ~SSCR1_RSRE;
}
pxa_ssp_write_reg(ssp, SSCR1, sscr1);
if (value) {
pxa_ssp_write_reg(ssp, SSSR, sssr);
pxa_ssp_write_reg(ssp, SSPSP, sspsp);
pxa_ssp_write_reg(ssp, SSCR0, sscr0 | SSCR0_SSE);
}
}
static int pxa_ssp_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *cpu_dai)
{
int ret = 0;
struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
struct ssp_device *ssp = priv->ssp;
int val;
switch (cmd) {
case SNDRV_PCM_TRIGGER_RESUME:
pxa_ssp_enable(ssp);
break;
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
pxa_ssp_set_running_bit(substream, ssp, 1);
val = pxa_ssp_read_reg(ssp, SSSR);
pxa_ssp_write_reg(ssp, SSSR, val);
break;
case SNDRV_PCM_TRIGGER_START:
pxa_ssp_set_running_bit(substream, ssp, 1);
break;
case SNDRV_PCM_TRIGGER_STOP:
pxa_ssp_set_running_bit(substream, ssp, 0);
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
pxa_ssp_disable(ssp);
break;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
pxa_ssp_set_running_bit(substream, ssp, 0);
break;
default:
ret = -EINVAL;
}
dump_registers(ssp);
return ret;
}
static int pxa_ssp_probe(struct snd_soc_dai *dai)
{
struct device *dev = dai->dev;
struct ssp_priv *priv;
int ret;
priv = kzalloc_obj(struct ssp_priv);
if (!priv)
return -ENOMEM;
if (dev->of_node) {
struct device_node *ssp_handle;
ssp_handle = of_parse_phandle(dev->of_node, "port", 0);
if (!ssp_handle) {
dev_err(dev, "unable to get 'port' phandle\n");
ret = -ENODEV;
goto err_priv;
}
priv->ssp = pxa_ssp_request_of(ssp_handle, "SoC audio");
if (priv->ssp == NULL) {
ret = -ENODEV;
goto err_priv;
}
priv->extclk = devm_clk_get(dev, "extclk");
if (IS_ERR(priv->extclk)) {
ret = PTR_ERR(priv->extclk);
if (ret == -EPROBE_DEFER)
goto err_priv;
priv->extclk = NULL;
}
} else {
priv->ssp = pxa_ssp_request(dai->id + 1, "SoC audio");
if (priv->ssp == NULL) {
ret = -ENODEV;
goto err_priv;
}
}
priv->dai_fmt = (unsigned int) -1;
snd_soc_dai_set_drvdata(dai, priv);
return 0;
err_priv:
kfree(priv);
return ret;
}
static int pxa_ssp_remove(struct snd_soc_dai *dai)
{
struct ssp_priv *priv = snd_soc_dai_get_drvdata(dai);
pxa_ssp_free(priv->ssp);
kfree(priv);
return 0;
}
#define PXA_SSP_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 | \
SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
#define PXA_SSP_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE)
static const struct snd_soc_dai_ops pxa_ssp_dai_ops = {
.probe = pxa_ssp_probe,
.remove = pxa_ssp_remove,
.startup = pxa_ssp_startup,
.shutdown = pxa_ssp_shutdown,
.trigger = pxa_ssp_trigger,
.hw_params = pxa_ssp_hw_params,
.set_sysclk = pxa_ssp_set_dai_sysclk,
.set_fmt = pxa_ssp_set_dai_fmt,
.set_tdm_slot = pxa_ssp_set_dai_tdm_slot,
.set_tristate = pxa_ssp_set_dai_tristate,
};
static struct snd_soc_dai_driver pxa_ssp_dai = {
.playback = {
.channels_min = 1,
.channels_max = 8,
.rates = PXA_SSP_RATES,
.formats = PXA_SSP_FORMATS,
},
.capture = {
.channels_min = 1,
.channels_max = 8,
.rates = PXA_SSP_RATES,
.formats = PXA_SSP_FORMATS,
},
.ops = &pxa_ssp_dai_ops,
};
static const struct snd_soc_component_driver pxa_ssp_component = {
.name = "pxa-ssp",
.pcm_new = pxa2xx_soc_pcm_new,
.open = pxa2xx_soc_pcm_open,
.close = pxa2xx_soc_pcm_close,
.hw_params = pxa2xx_soc_pcm_hw_params,
.prepare = pxa2xx_soc_pcm_prepare,
.trigger = pxa2xx_soc_pcm_trigger,
.pointer = pxa2xx_soc_pcm_pointer,
.suspend = pxa_ssp_suspend,
.resume = pxa_ssp_resume,
.legacy_dai_naming = 1,
};
#ifdef CONFIG_OF
static const struct of_device_id pxa_ssp_of_ids[] = {
{ .compatible = "mrvl,pxa-ssp-dai" },
{}
};
MODULE_DEVICE_TABLE(of, pxa_ssp_of_ids);
#endif
static int asoc_ssp_probe(struct platform_device *pdev)
{
return devm_snd_soc_register_component(&pdev->dev, &pxa_ssp_component,
&pxa_ssp_dai, 1);
}
static struct platform_driver asoc_ssp_driver = {
.driver = {
.name = "pxa-ssp-dai",
.of_match_table = of_match_ptr(pxa_ssp_of_ids),
},
.probe = asoc_ssp_probe,
};
module_platform_driver(asoc_ssp_driver);
/* Module information */
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
MODULE_DESCRIPTION("PXA SSP/PCM SoC Interface");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:pxa-ssp-dai");
+36
View File
@@ -0,0 +1,36 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* ASoC PXA SSP port support
*/
#ifndef _PXA_SSP_H
#define _PXA_SSP_H
/* SSP clock sources */
#define PXA_SSP_CLK_PLL 0
#define PXA_SSP_CLK_EXT 1
#define PXA_SSP_CLK_NET 2
#define PXA_SSP_CLK_AUDIO 3
#define PXA_SSP_CLK_NET_PLL 4
/* SSP audio dividers */
#define PXA_SSP_AUDIO_DIV_ACDS 0
#define PXA_SSP_AUDIO_DIV_SCDB 1
#define PXA_SSP_DIV_SCR 2
/* SSP ACDS audio dividers values */
#define PXA_SSP_CLK_AUDIO_DIV_1 0
#define PXA_SSP_CLK_AUDIO_DIV_2 1
#define PXA_SSP_CLK_AUDIO_DIV_4 2
#define PXA_SSP_CLK_AUDIO_DIV_8 3
#define PXA_SSP_CLK_AUDIO_DIV_16 4
#define PXA_SSP_CLK_AUDIO_DIV_32 5
/* SSP divider bypass */
#define PXA_SSP_CLK_SCDB_4 0
#define PXA_SSP_CLK_SCDB_1 1
#define PXA_SSP_CLK_SCDB_8 2
#define PXA_SSP_PLL_OUT 0
#endif
+420
View File
@@ -0,0 +1,420 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Based on sound/arm/pxa2xx-ac97.c and sound/soc/pxa/pxa2xx-ac97.c
* which contain:
*
* Author: Nicolas Pitre
* Created: Dec 02, 2004
* Copyright: MontaVista Software Inc.
*/
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/soc/pxa/cpu.h>
#include <sound/pxa2xx-lib.h>
#include <linux/platform_data/asoc-pxa.h>
#include "pxa2xx-ac97-regs.h"
#include "pxa2xx-lib.h"
static DEFINE_MUTEX(car_mutex);
static DECLARE_WAIT_QUEUE_HEAD(gsr_wq);
static volatile long gsr_bits;
static struct clk *ac97_clk;
static struct clk *ac97conf_clk;
struct gpio_desc *rst_gpio;
static void __iomem *ac97_reg_base;
/*
* Beware PXA27x bugs:
*
* o Slot 12 read from modem space will hang controller.
* o CDONE, SDONE interrupt fails after any slot 12 IO.
*
* We therefore have an hybrid approach for waiting on SDONE (interrupt or
* 1 jiffy timeout if interrupt never comes).
*/
int pxa2xx_ac97_read(int slot, unsigned short reg)
{
int val = -ENODEV;
u32 __iomem *reg_addr;
if (slot > 0)
return -ENODEV;
guard(mutex)(&car_mutex);
/* set up primary or secondary codec space */
if (cpu_is_pxa25x() && reg == AC97_GPIO_STATUS)
reg_addr = ac97_reg_base +
(slot ? SMC_REG_BASE : PMC_REG_BASE);
else
reg_addr = ac97_reg_base +
(slot ? SAC_REG_BASE : PAC_REG_BASE);
reg_addr += (reg >> 1);
/* start read access across the ac97 link */
writel(GSR_CDONE | GSR_SDONE, ac97_reg_base + GSR);
gsr_bits = 0;
val = (readl(reg_addr) & 0xffff);
if (reg == AC97_GPIO_STATUS)
return val;
if (wait_event_timeout(gsr_wq, (readl(ac97_reg_base + GSR) | gsr_bits) & GSR_SDONE, 1) <= 0 &&
!((readl(ac97_reg_base + GSR) | gsr_bits) & GSR_SDONE)) {
printk(KERN_ERR "%s: read error (ac97_reg=%d GSR=%#lx)\n",
__func__, reg, readl(ac97_reg_base + GSR) | gsr_bits);
return -ETIMEDOUT;
}
/* valid data now */
writel(GSR_CDONE | GSR_SDONE, ac97_reg_base + GSR);
gsr_bits = 0;
val = (readl(reg_addr) & 0xffff);
/* but we've just started another cycle... */
wait_event_timeout(gsr_wq, (readl(ac97_reg_base + GSR) | gsr_bits) & GSR_SDONE, 1);
return val;
}
int pxa2xx_ac97_write(int slot, unsigned short reg, unsigned short val)
{
u32 __iomem *reg_addr;
int ret = 0;
guard(mutex)(&car_mutex);
/* set up primary or secondary codec space */
if (cpu_is_pxa25x() && reg == AC97_GPIO_STATUS)
reg_addr = ac97_reg_base +
(slot ? SMC_REG_BASE : PMC_REG_BASE);
else
reg_addr = ac97_reg_base +
(slot ? SAC_REG_BASE : PAC_REG_BASE);
reg_addr += (reg >> 1);
writel(GSR_CDONE | GSR_SDONE, ac97_reg_base + GSR);
gsr_bits = 0;
writel(val, reg_addr);
if (wait_event_timeout(gsr_wq, (readl(ac97_reg_base + GSR) | gsr_bits) & GSR_CDONE, 1) <= 0 &&
!((readl(ac97_reg_base + GSR) | gsr_bits) & GSR_CDONE)) {
printk(KERN_ERR "%s: write error (ac97_reg=%d GSR=%#lx)\n",
__func__, reg, readl(ac97_reg_base + GSR) | gsr_bits);
ret = -EIO;
}
return ret;
}
#ifdef CONFIG_PXA25x
static inline void pxa_ac97_warm_pxa25x(void)
{
gsr_bits = 0;
writel(readl(ac97_reg_base + GCR) | (GCR_WARM_RST), ac97_reg_base + GCR);
}
static inline void pxa_ac97_cold_pxa25x(void)
{
writel(readl(ac97_reg_base + GCR) & ( GCR_COLD_RST), ac97_reg_base + GCR); /* clear everything but nCRST */
writel(readl(ac97_reg_base + GCR) & (~GCR_COLD_RST), ac97_reg_base + GCR); /* then assert nCRST */
gsr_bits = 0;
writel(GCR_COLD_RST, ac97_reg_base + GCR);
}
#endif
#ifdef CONFIG_PXA27x
static inline void pxa_ac97_warm_pxa27x(void)
{
gsr_bits = 0;
/* warm reset broken on Bulverde, so manually keep AC97 reset high */
pxa27x_configure_ac97reset(rst_gpio, true);
udelay(10);
writel(readl(ac97_reg_base + GCR) | (GCR_WARM_RST), ac97_reg_base + GCR);
pxa27x_configure_ac97reset(rst_gpio, false);
udelay(500);
}
static inline void pxa_ac97_cold_pxa27x(void)
{
writel(readl(ac97_reg_base + GCR) & ( GCR_COLD_RST), ac97_reg_base + GCR); /* clear everything but nCRST */
writel(readl(ac97_reg_base + GCR) & (~GCR_COLD_RST), ac97_reg_base + GCR); /* then assert nCRST */
gsr_bits = 0;
/* PXA27x Developers Manual section 13.5.2.2.1 */
clk_prepare_enable(ac97conf_clk);
udelay(5);
clk_disable_unprepare(ac97conf_clk);
writel(GCR_COLD_RST | GCR_WARM_RST, ac97_reg_base + GCR);
}
#endif
#ifdef CONFIG_PXA3xx
static inline void pxa_ac97_warm_pxa3xx(void)
{
gsr_bits = 0;
/* Can't use interrupts */
writel(readl(ac97_reg_base + GCR) | (GCR_WARM_RST), ac97_reg_base + GCR);
}
static inline void pxa_ac97_cold_pxa3xx(void)
{
/* Hold CLKBPB for 100us */
writel(0, ac97_reg_base + GCR);
writel(GCR_CLKBPB, ac97_reg_base + GCR);
udelay(100);
writel(0, ac97_reg_base + GCR);
writel(readl(ac97_reg_base + GCR) & ( GCR_COLD_RST), ac97_reg_base + GCR); /* clear everything but nCRST */
writel(readl(ac97_reg_base + GCR) & (~GCR_COLD_RST), ac97_reg_base + GCR); /* then assert nCRST */
gsr_bits = 0;
/* Can't use interrupts on PXA3xx */
writel(readl(ac97_reg_base + GCR) & (~(GCR_PRIRDY_IEN|GCR_SECRDY_IEN)), ac97_reg_base + GCR);
writel(GCR_WARM_RST | GCR_COLD_RST, ac97_reg_base + GCR);
}
#endif
bool pxa2xx_ac97_try_warm_reset(void)
{
unsigned long gsr;
unsigned int timeout = 100;
#ifdef CONFIG_PXA25x
if (cpu_is_pxa25x())
pxa_ac97_warm_pxa25x();
else
#endif
#ifdef CONFIG_PXA27x
if (cpu_is_pxa27x())
pxa_ac97_warm_pxa27x();
else
#endif
#ifdef CONFIG_PXA3xx
if (cpu_is_pxa3xx())
pxa_ac97_warm_pxa3xx();
else
#endif
snd_BUG();
while (!((readl(ac97_reg_base + GSR) | gsr_bits) & (GSR_PCR | GSR_SCR)) && timeout--)
mdelay(1);
gsr = readl(ac97_reg_base + GSR) | gsr_bits;
if (!(gsr & (GSR_PCR | GSR_SCR))) {
printk(KERN_INFO "%s: warm reset timeout (GSR=%#lx)\n",
__func__, gsr);
return false;
}
return true;
}
bool pxa2xx_ac97_try_cold_reset(void)
{
unsigned long gsr;
unsigned int timeout = 1000;
#ifdef CONFIG_PXA25x
if (cpu_is_pxa25x())
pxa_ac97_cold_pxa25x();
else
#endif
#ifdef CONFIG_PXA27x
if (cpu_is_pxa27x())
pxa_ac97_cold_pxa27x();
else
#endif
#ifdef CONFIG_PXA3xx
if (cpu_is_pxa3xx())
pxa_ac97_cold_pxa3xx();
else
#endif
snd_BUG();
while (!((readl(ac97_reg_base + GSR) | gsr_bits) & (GSR_PCR | GSR_SCR)) && timeout--)
mdelay(1);
gsr = readl(ac97_reg_base + GSR) | gsr_bits;
if (!(gsr & (GSR_PCR | GSR_SCR))) {
printk(KERN_INFO "%s: cold reset timeout (GSR=%#lx)\n",
__func__, gsr);
return false;
}
return true;
}
void pxa2xx_ac97_finish_reset(void)
{
u32 gcr = readl(ac97_reg_base + GCR);
gcr &= ~(GCR_PRIRDY_IEN|GCR_SECRDY_IEN);
gcr |= GCR_SDONE_IE|GCR_CDONE_IE;
writel(gcr, ac97_reg_base + GCR);
}
static irqreturn_t pxa2xx_ac97_irq(int irq, void *dev_id)
{
long status;
status = readl(ac97_reg_base + GSR);
if (status) {
writel(status, ac97_reg_base + GSR);
gsr_bits |= status;
wake_up(&gsr_wq);
/* Although we don't use those we still need to clear them
since they tend to spuriously trigger when MMC is used
(hardware bug? go figure)... */
if (cpu_is_pxa27x()) {
writel(MISR_EOC, ac97_reg_base + MISR);
writel(PISR_EOC, ac97_reg_base + PISR);
writel(MCSR_EOC, ac97_reg_base + MCSR);
}
return IRQ_HANDLED;
}
return IRQ_NONE;
}
#ifdef CONFIG_PM
int pxa2xx_ac97_hw_suspend(void)
{
writel(readl(ac97_reg_base + GCR) | (GCR_ACLINK_OFF), ac97_reg_base + GCR);
clk_disable_unprepare(ac97_clk);
return 0;
}
int pxa2xx_ac97_hw_resume(void)
{
clk_prepare_enable(ac97_clk);
return 0;
}
#endif
int pxa2xx_ac97_hw_probe(struct platform_device *dev)
{
int ret;
int irq;
ac97_reg_base = devm_platform_ioremap_resource(dev, 0);
if (IS_ERR(ac97_reg_base)) {
dev_err(&dev->dev, "Missing MMIO resource\n");
return PTR_ERR(ac97_reg_base);
}
if (cpu_is_pxa27x()) {
/* Assert reset using GPIOD_OUT_HIGH, because reset is GPIO_ACTIVE_LOW */
rst_gpio = devm_gpiod_get_optional(&dev->dev, "reset",
GPIOD_OUT_HIGH);
if (IS_ERR(rst_gpio))
return dev_err_probe(&dev->dev, PTR_ERR(rst_gpio),
"reset gpio failed\n");
/*
* This gpio is needed for a work-around to a bug in the ac97
* controller during warm reset. The direction and level is set
* here so that it is an output driven high when switching from
* AC97_nRESET alt function to generic gpio.
*/
gpiod_set_consumer_name(rst_gpio, "pxa27x ac97 reset");
pxa27x_configure_ac97reset(rst_gpio, false);
ac97conf_clk = clk_get(&dev->dev, "AC97CONFCLK");
if (IS_ERR(ac97conf_clk)) {
ret = PTR_ERR(ac97conf_clk);
ac97conf_clk = NULL;
goto err_conf;
}
}
ac97_clk = clk_get(&dev->dev, "AC97CLK");
if (IS_ERR(ac97_clk)) {
ret = PTR_ERR(ac97_clk);
ac97_clk = NULL;
goto err_clk;
}
ret = clk_prepare_enable(ac97_clk);
if (ret)
goto err_clk2;
irq = platform_get_irq(dev, 0);
if (irq < 0) {
ret = irq;
goto err_irq;
}
ret = request_irq(irq, pxa2xx_ac97_irq, 0, "AC97", NULL);
if (ret < 0)
goto err_irq;
return 0;
err_irq:
writel(readl(ac97_reg_base + GCR) | (GCR_ACLINK_OFF), ac97_reg_base + GCR);
err_clk2:
clk_put(ac97_clk);
ac97_clk = NULL;
err_clk:
if (ac97conf_clk) {
clk_put(ac97conf_clk);
ac97conf_clk = NULL;
}
err_conf:
return ret;
}
void pxa2xx_ac97_hw_remove(struct platform_device *dev)
{
writel(readl(ac97_reg_base + GCR) | (GCR_ACLINK_OFF), ac97_reg_base + GCR);
free_irq(platform_get_irq(dev, 0), NULL);
if (ac97conf_clk) {
clk_put(ac97conf_clk);
ac97conf_clk = NULL;
}
clk_disable_unprepare(ac97_clk);
clk_put(ac97_clk);
ac97_clk = NULL;
}
u32 pxa2xx_ac97_read_modr(void)
{
if (!ac97_reg_base)
return 0;
return readl(ac97_reg_base + MODR);
}
EXPORT_SYMBOL_GPL(pxa2xx_ac97_read_modr);
u32 pxa2xx_ac97_read_misr(void)
{
if (!ac97_reg_base)
return 0;
return readl(ac97_reg_base + MISR);
}
EXPORT_SYMBOL_GPL(pxa2xx_ac97_read_misr);
MODULE_AUTHOR("Nicolas Pitre");
MODULE_DESCRIPTION("Intel/Marvell PXA sound library");
MODULE_LICENSE("GPL");
@@ -0,0 +1,100 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __ASM_ARCH_REGS_AC97_H
#define __ASM_ARCH_REGS_AC97_H
/*
* AC97 Controller registers
*/
#define POCR (0x0000) /* PCM Out Control Register */
#define POCR_FEIE (1 << 3) /* FIFO Error Interrupt Enable */
#define POCR_FSRIE (1 << 1) /* FIFO Service Request Interrupt Enable */
#define PICR (0x0004) /* PCM In Control Register */
#define PICR_FEIE (1 << 3) /* FIFO Error Interrupt Enable */
#define PICR_FSRIE (1 << 1) /* FIFO Service Request Interrupt Enable */
#define MCCR (0x0008) /* Mic In Control Register */
#define MCCR_FEIE (1 << 3) /* FIFO Error Interrupt Enable */
#define MCCR_FSRIE (1 << 1) /* FIFO Service Request Interrupt Enable */
#define GCR (0x000C) /* Global Control Register */
#ifdef CONFIG_PXA3xx
#define GCR_CLKBPB (1 << 31) /* Internal clock enable */
#endif
#define GCR_nDMAEN (1 << 24) /* non DMA Enable */
#define GCR_CDONE_IE (1 << 19) /* Command Done Interrupt Enable */
#define GCR_SDONE_IE (1 << 18) /* Status Done Interrupt Enable */
#define GCR_SECRDY_IEN (1 << 9) /* Secondary Ready Interrupt Enable */
#define GCR_PRIRDY_IEN (1 << 8) /* Primary Ready Interrupt Enable */
#define GCR_SECRES_IEN (1 << 5) /* Secondary Resume Interrupt Enable */
#define GCR_PRIRES_IEN (1 << 4) /* Primary Resume Interrupt Enable */
#define GCR_ACLINK_OFF (1 << 3) /* AC-link Shut Off */
#define GCR_WARM_RST (1 << 2) /* AC97 Warm Reset */
#define GCR_COLD_RST (1 << 1) /* AC'97 Cold Reset (0 = active) */
#define GCR_GIE (1 << 0) /* Codec GPI Interrupt Enable */
#define POSR (0x0010) /* PCM Out Status Register */
#define POSR_FIFOE (1 << 4) /* FIFO error */
#define POSR_FSR (1 << 2) /* FIFO Service Request */
#define PISR (0x0014) /* PCM In Status Register */
#define PISR_FIFOE (1 << 4) /* FIFO error */
#define PISR_EOC (1 << 3) /* DMA End-of-Chain (exclusive clear) */
#define PISR_FSR (1 << 2) /* FIFO Service Request */
#define MCSR (0x0018) /* Mic In Status Register */
#define MCSR_FIFOE (1 << 4) /* FIFO error */
#define MCSR_EOC (1 << 3) /* DMA End-of-Chain (exclusive clear) */
#define MCSR_FSR (1 << 2) /* FIFO Service Request */
#define GSR (0x001C) /* Global Status Register */
#define GSR_CDONE (1 << 19) /* Command Done */
#define GSR_SDONE (1 << 18) /* Status Done */
#define GSR_RDCS (1 << 15) /* Read Completion Status */
#define GSR_BIT3SLT12 (1 << 14) /* Bit 3 of slot 12 */
#define GSR_BIT2SLT12 (1 << 13) /* Bit 2 of slot 12 */
#define GSR_BIT1SLT12 (1 << 12) /* Bit 1 of slot 12 */
#define GSR_SECRES (1 << 11) /* Secondary Resume Interrupt */
#define GSR_PRIRES (1 << 10) /* Primary Resume Interrupt */
#define GSR_SCR (1 << 9) /* Secondary Codec Ready */
#define GSR_PCR (1 << 8) /* Primary Codec Ready */
#define GSR_MCINT (1 << 7) /* Mic In Interrupt */
#define GSR_POINT (1 << 6) /* PCM Out Interrupt */
#define GSR_PIINT (1 << 5) /* PCM In Interrupt */
#define GSR_ACOFFD (1 << 3) /* AC-link Shut Off Done */
#define GSR_MOINT (1 << 2) /* Modem Out Interrupt */
#define GSR_MIINT (1 << 1) /* Modem In Interrupt */
#define GSR_GSCI (1 << 0) /* Codec GPI Status Change Interrupt */
#define CAR (0x0020) /* CODEC Access Register */
#define CAR_CAIP (1 << 0) /* Codec Access In Progress */
#define PCDR (0x0040) /* PCM FIFO Data Register */
#define MCDR (0x0060) /* Mic-in FIFO Data Register */
#define MOCR (0x0100) /* Modem Out Control Register */
#define MOCR_FEIE (1 << 3) /* FIFO Error */
#define MOCR_FSRIE (1 << 1) /* FIFO Service Request Interrupt Enable */
#define MICR (0x0108) /* Modem In Control Register */
#define MICR_FEIE (1 << 3) /* FIFO Error */
#define MICR_FSRIE (1 << 1) /* FIFO Service Request Interrupt Enable */
#define MOSR (0x0110) /* Modem Out Status Register */
#define MOSR_FIFOE (1 << 4) /* FIFO error */
#define MOSR_FSR (1 << 2) /* FIFO Service Request */
#define MISR (0x0118) /* Modem In Status Register */
#define MISR_FIFOE (1 << 4) /* FIFO error */
#define MISR_EOC (1 << 3) /* DMA End-of-Chain (exclusive clear) */
#define MISR_FSR (1 << 2) /* FIFO Service Request */
#define MODR (0x0140) /* Modem FIFO Data Register */
#define PAC_REG_BASE (0x0200) /* Primary Audio Codec */
#define SAC_REG_BASE (0x0300) /* Secondary Audio Codec */
#define PMC_REG_BASE (0x0400) /* Primary Modem Codec */
#define SMC_REG_BASE (0x0500) /* Secondary Modem Codec */
#endif /* __ASM_ARCH_REGS_AC97_H */
+299
View File
@@ -0,0 +1,299 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/sound/pxa2xx-ac97.c -- AC97 support for the Intel PXA2xx chip.
*
* Author: Nicolas Pitre
* Created: Dec 02, 2004
* Copyright: MontaVista Software Inc.
*/
#include <linux/init.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/dmaengine.h>
#include <linux/dma/pxa-dma.h>
#include <sound/ac97/controller.h>
#include <sound/core.h>
#include <sound/ac97_codec.h>
#include <sound/soc.h>
#include <sound/dmaengine_pcm.h>
#include <linux/platform_data/asoc-pxa.h>
#include "pxa2xx-lib.h"
#define PCDR 0x0040 /* PCM FIFO Data Register */
#define MODR 0x0140 /* Modem FIFO Data Register */
#define MCDR 0x0060 /* Mic-in FIFO Data Register */
static void pxa2xx_ac97_warm_reset(struct ac97_controller *adrv)
{
pxa2xx_ac97_try_warm_reset();
pxa2xx_ac97_finish_reset();
}
static void pxa2xx_ac97_cold_reset(struct ac97_controller *adrv)
{
pxa2xx_ac97_try_cold_reset();
pxa2xx_ac97_finish_reset();
}
static int pxa2xx_ac97_read_actrl(struct ac97_controller *adrv, int slot,
unsigned short reg)
{
return pxa2xx_ac97_read(slot, reg);
}
static int pxa2xx_ac97_write_actrl(struct ac97_controller *adrv, int slot,
unsigned short reg, unsigned short val)
{
return pxa2xx_ac97_write(slot, reg, val);
}
static struct ac97_controller_ops pxa2xx_ac97_ops = {
.read = pxa2xx_ac97_read_actrl,
.write = pxa2xx_ac97_write_actrl,
.warm_reset = pxa2xx_ac97_warm_reset,
.reset = pxa2xx_ac97_cold_reset,
};
static struct snd_dmaengine_dai_dma_data pxa2xx_ac97_pcm_stereo_in = {
.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
.chan_name = "pcm_pcm_stereo_in",
.maxburst = 32,
};
static struct snd_dmaengine_dai_dma_data pxa2xx_ac97_pcm_stereo_out = {
.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
.chan_name = "pcm_pcm_stereo_out",
.maxburst = 32,
};
static struct snd_dmaengine_dai_dma_data pxa2xx_ac97_pcm_aux_mono_out = {
.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES,
.chan_name = "pcm_aux_mono_out",
.maxburst = 16,
};
static struct snd_dmaengine_dai_dma_data pxa2xx_ac97_pcm_aux_mono_in = {
.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES,
.chan_name = "pcm_aux_mono_in",
.maxburst = 16,
};
static struct snd_dmaengine_dai_dma_data pxa2xx_ac97_pcm_mic_mono_in = {
.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES,
.chan_name = "pcm_aux_mic_mono",
.maxburst = 16,
};
static int pxa2xx_ac97_hifi_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai)
{
struct snd_dmaengine_dai_dma_data *dma_data;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
dma_data = &pxa2xx_ac97_pcm_stereo_out;
else
dma_data = &pxa2xx_ac97_pcm_stereo_in;
snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data);
return 0;
}
static int pxa2xx_ac97_aux_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai)
{
struct snd_dmaengine_dai_dma_data *dma_data;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
dma_data = &pxa2xx_ac97_pcm_aux_mono_out;
else
dma_data = &pxa2xx_ac97_pcm_aux_mono_in;
snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data);
return 0;
}
static int pxa2xx_ac97_mic_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai)
{
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
return -ENODEV;
snd_soc_dai_set_dma_data(cpu_dai, substream,
&pxa2xx_ac97_pcm_mic_mono_in);
return 0;
}
#define PXA2XX_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \
SNDRV_PCM_RATE_48000)
static const struct snd_soc_dai_ops pxa_ac97_hifi_dai_ops = {
.startup = pxa2xx_ac97_hifi_startup,
};
static const struct snd_soc_dai_ops pxa_ac97_aux_dai_ops = {
.startup = pxa2xx_ac97_aux_startup,
};
static const struct snd_soc_dai_ops pxa_ac97_mic_dai_ops = {
.startup = pxa2xx_ac97_mic_startup,
};
/*
* There is only 1 physical AC97 interface for pxa2xx, but it
* has extra fifo's that can be used for aux DACs and ADCs.
*/
static struct snd_soc_dai_driver pxa_ac97_dai_driver[] = {
{
.name = "pxa2xx-ac97",
.playback = {
.stream_name = "AC97 Playback",
.channels_min = 2,
.channels_max = 2,
.rates = PXA2XX_AC97_RATES,
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
.capture = {
.stream_name = "AC97 Capture",
.channels_min = 2,
.channels_max = 2,
.rates = PXA2XX_AC97_RATES,
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
.ops = &pxa_ac97_hifi_dai_ops,
},
{
.name = "pxa2xx-ac97-aux",
.playback = {
.stream_name = "AC97 Aux Playback",
.channels_min = 1,
.channels_max = 1,
.rates = PXA2XX_AC97_RATES,
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
.capture = {
.stream_name = "AC97 Aux Capture",
.channels_min = 1,
.channels_max = 1,
.rates = PXA2XX_AC97_RATES,
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
.ops = &pxa_ac97_aux_dai_ops,
},
{
.name = "pxa2xx-ac97-mic",
.capture = {
.stream_name = "AC97 Mic Capture",
.channels_min = 1,
.channels_max = 1,
.rates = PXA2XX_AC97_RATES,
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
.ops = &pxa_ac97_mic_dai_ops,
},
};
static const struct snd_soc_component_driver pxa_ac97_component = {
.name = "pxa-ac97",
.pcm_new = pxa2xx_soc_pcm_new,
.open = pxa2xx_soc_pcm_open,
.close = pxa2xx_soc_pcm_close,
.hw_params = pxa2xx_soc_pcm_hw_params,
.prepare = pxa2xx_soc_pcm_prepare,
.trigger = pxa2xx_soc_pcm_trigger,
.pointer = pxa2xx_soc_pcm_pointer,
};
#ifdef CONFIG_OF
static const struct of_device_id pxa2xx_ac97_dt_ids[] = {
{ .compatible = "marvell,pxa250-ac97", },
{ .compatible = "marvell,pxa270-ac97", },
{ .compatible = "marvell,pxa300-ac97", },
{ }
};
MODULE_DEVICE_TABLE(of, pxa2xx_ac97_dt_ids);
#endif
static int pxa2xx_ac97_dev_probe(struct platform_device *pdev)
{
int ret;
struct ac97_controller *ctrl;
struct resource *regs;
if (pdev->id != -1) {
dev_err(&pdev->dev, "PXA2xx has only one AC97 port.\n");
return -ENXIO;
}
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!regs)
return -ENXIO;
pxa2xx_ac97_pcm_stereo_in.addr = regs->start + PCDR;
pxa2xx_ac97_pcm_stereo_out.addr = regs->start + PCDR;
pxa2xx_ac97_pcm_aux_mono_out.addr = regs->start + MODR;
pxa2xx_ac97_pcm_aux_mono_in.addr = regs->start + MODR;
pxa2xx_ac97_pcm_mic_mono_in.addr = regs->start + MCDR;
ret = pxa2xx_ac97_hw_probe(pdev);
if (ret) {
dev_err(&pdev->dev, "PXA2xx AC97 hw probe error (%d)\n", ret);
return ret;
}
ctrl = snd_ac97_controller_register(&pxa2xx_ac97_ops, &pdev->dev,
AC97_SLOTS_AVAILABLE_ALL);
if (IS_ERR(ctrl))
return PTR_ERR(ctrl);
platform_set_drvdata(pdev, ctrl);
/* Punt most of the init to the SoC probe; we may need the machine
* driver to do interesting things with the clocking to get us up
* and running.
*/
return devm_snd_soc_register_component(&pdev->dev, &pxa_ac97_component,
pxa_ac97_dai_driver, ARRAY_SIZE(pxa_ac97_dai_driver));
}
static void pxa2xx_ac97_dev_remove(struct platform_device *pdev)
{
struct ac97_controller *ctrl = platform_get_drvdata(pdev);
snd_ac97_controller_unregister(ctrl);
pxa2xx_ac97_hw_remove(pdev);
}
static int pxa2xx_ac97_dev_suspend(struct device *dev)
{
return pxa2xx_ac97_hw_suspend();
}
static int pxa2xx_ac97_dev_resume(struct device *dev)
{
return pxa2xx_ac97_hw_resume();
}
static DEFINE_SIMPLE_DEV_PM_OPS(pxa2xx_ac97_pm_ops,
pxa2xx_ac97_dev_suspend, pxa2xx_ac97_dev_resume);
static struct platform_driver pxa2xx_ac97_driver = {
.probe = pxa2xx_ac97_dev_probe,
.remove = pxa2xx_ac97_dev_remove,
.driver = {
.name = "pxa2xx-ac97",
.pm = &pxa2xx_ac97_pm_ops,
.of_match_table = of_match_ptr(pxa2xx_ac97_dt_ids),
},
};
module_platform_driver(pxa2xx_ac97_driver);
MODULE_AUTHOR("Nicolas Pitre");
MODULE_DESCRIPTION("AC97 driver for the Intel PXA2xx chip");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:pxa2xx-ac97");
+412
View File
@@ -0,0 +1,412 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* pxa2xx-i2s.c -- ALSA Soc Audio Layer
*
* Copyright 2005 Wolfson Microelectronics PLC.
* Author: Liam Girdwood
* lrg@slimlogic.co.uk
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include <sound/dmaengine_pcm.h>
#include <linux/platform_data/asoc-pxa.h>
#include "pxa2xx-lib.h"
#include "pxa2xx-i2s.h"
/*
* I2S Controller Register and Bit Definitions
*/
#define SACR0 (0x0000) /* Global Control Register */
#define SACR1 (0x0004) /* Serial Audio I 2 S/MSB-Justified Control Register */
#define SASR0 (0x000C) /* Serial Audio I 2 S/MSB-Justified Interface and FIFO Status Register */
#define SAIMR (0x0014) /* Serial Audio Interrupt Mask Register */
#define SAICR (0x0018) /* Serial Audio Interrupt Clear Register */
#define SADIV (0x0060) /* Audio Clock Divider Register. */
#define SADR (0x0080) /* Serial Audio Data Register (TX and RX FIFO access Register). */
#define SACR0_RFTH(x) ((x) << 12) /* Rx FIFO Interrupt or DMA Trigger Threshold */
#define SACR0_TFTH(x) ((x) << 8) /* Tx FIFO Interrupt or DMA Trigger Threshold */
#define SACR0_STRF (1 << 5) /* FIFO Select for EFWR Special Function */
#define SACR0_EFWR (1 << 4) /* Enable EFWR Function */
#define SACR0_RST (1 << 3) /* FIFO, i2s Register Reset */
#define SACR0_BCKD (1 << 2) /* Bit Clock Direction */
#define SACR0_ENB (1 << 0) /* Enable I2S Link */
#define SACR1_ENLBF (1 << 5) /* Enable Loopback */
#define SACR1_DRPL (1 << 4) /* Disable Replaying Function */
#define SACR1_DREC (1 << 3) /* Disable Recording Function */
#define SACR1_AMSL (1 << 0) /* Specify Alternate Mode */
#define SASR0_I2SOFF (1 << 7) /* Controller Status */
#define SASR0_ROR (1 << 6) /* Rx FIFO Overrun */
#define SASR0_TUR (1 << 5) /* Tx FIFO Underrun */
#define SASR0_RFS (1 << 4) /* Rx FIFO Service Request */
#define SASR0_TFS (1 << 3) /* Tx FIFO Service Request */
#define SASR0_BSY (1 << 2) /* I2S Busy */
#define SASR0_RNE (1 << 1) /* Rx FIFO Not Empty */
#define SASR0_TNF (1 << 0) /* Tx FIFO Not Empty */
#define SAICR_ROR (1 << 6) /* Clear Rx FIFO Overrun Interrupt */
#define SAICR_TUR (1 << 5) /* Clear Tx FIFO Underrun Interrupt */
#define SAIMR_ROR (1 << 6) /* Enable Rx FIFO Overrun Condition Interrupt */
#define SAIMR_TUR (1 << 5) /* Enable Tx FIFO Underrun Condition Interrupt */
#define SAIMR_RFS (1 << 4) /* Enable Rx FIFO Service Interrupt */
#define SAIMR_TFS (1 << 3) /* Enable Tx FIFO Service Interrupt */
struct pxa_i2s_port {
u32 sadiv;
u32 sacr0;
u32 sacr1;
u32 saimr;
int master;
u32 fmt;
};
static struct pxa_i2s_port pxa_i2s;
static struct clk *clk_i2s;
static int clk_ena = 0;
static void __iomem *i2s_reg_base;
static struct snd_dmaengine_dai_dma_data pxa2xx_i2s_pcm_stereo_out = {
.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
.chan_name = "tx",
.maxburst = 32,
};
static struct snd_dmaengine_dai_dma_data pxa2xx_i2s_pcm_stereo_in = {
.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
.chan_name = "rx",
.maxburst = 32,
};
static int pxa2xx_i2s_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
if (IS_ERR(clk_i2s))
return PTR_ERR(clk_i2s);
if (!snd_soc_dai_active(cpu_dai))
writel(0, i2s_reg_base + SACR0);
return 0;
}
/* wait for I2S controller to be ready */
static int pxa_i2s_wait(void)
{
int i;
/* flush the Rx FIFO */
for (i = 0; i < 16; i++)
readl(i2s_reg_base + SADR);
return 0;
}
static int pxa2xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
unsigned int fmt)
{
/* interface format */
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
pxa_i2s.fmt = 0;
break;
case SND_SOC_DAIFMT_LEFT_J:
pxa_i2s.fmt = SACR1_AMSL;
break;
}
switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
case SND_SOC_DAIFMT_BP_FP:
pxa_i2s.master = 1;
break;
case SND_SOC_DAIFMT_BC_FP:
pxa_i2s.master = 0;
break;
default:
break;
}
return 0;
}
static int pxa2xx_i2s_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
int clk_id, unsigned int freq, int dir)
{
if (clk_id != PXA2XX_I2S_SYSCLK)
return -ENODEV;
return 0;
}
static int pxa2xx_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct snd_dmaengine_dai_dma_data *dma_data;
if (WARN_ON(IS_ERR(clk_i2s)))
return -EINVAL;
clk_prepare_enable(clk_i2s);
clk_ena = 1;
pxa_i2s_wait();
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
dma_data = &pxa2xx_i2s_pcm_stereo_out;
else
dma_data = &pxa2xx_i2s_pcm_stereo_in;
snd_soc_dai_set_dma_data(dai, substream, dma_data);
/* is port used by another stream */
if (!(SACR0 & SACR0_ENB)) {
writel(0, i2s_reg_base + SACR0);
if (pxa_i2s.master)
writel(readl(i2s_reg_base + SACR0) | (SACR0_BCKD), i2s_reg_base + SACR0);
writel(readl(i2s_reg_base + SACR0) | (SACR0_RFTH(14) | SACR0_TFTH(1)), i2s_reg_base + SACR0);
writel(readl(i2s_reg_base + SACR1) | (pxa_i2s.fmt), i2s_reg_base + SACR1);
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
writel(readl(i2s_reg_base + SAIMR) | (SAIMR_TFS), i2s_reg_base + SAIMR);
else
writel(readl(i2s_reg_base + SAIMR) | (SAIMR_RFS), i2s_reg_base + SAIMR);
switch (params_rate(params)) {
case 8000:
writel(0x48, i2s_reg_base + SADIV);
break;
case 11025:
writel(0x34, i2s_reg_base + SADIV);
break;
case 16000:
writel(0x24, i2s_reg_base + SADIV);
break;
case 22050:
writel(0x1a, i2s_reg_base + SADIV);
break;
case 44100:
writel(0xd, i2s_reg_base + SADIV);
break;
case 48000:
writel(0xc, i2s_reg_base + SADIV);
break;
case 96000: /* not in manual and possibly slightly inaccurate */
writel(0x6, i2s_reg_base + SADIV);
break;
}
return 0;
}
static int pxa2xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
int ret = 0;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
writel(readl(i2s_reg_base + SACR1) & (~SACR1_DRPL), i2s_reg_base + SACR1);
else
writel(readl(i2s_reg_base + SACR1) & (~SACR1_DREC), i2s_reg_base + SACR1);
writel(readl(i2s_reg_base + SACR0) | (SACR0_ENB), i2s_reg_base + SACR0);
break;
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
break;
default:
ret = -EINVAL;
}
return ret;
}
static void pxa2xx_i2s_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
writel(readl(i2s_reg_base + SACR1) | (SACR1_DRPL), i2s_reg_base + SACR1);
writel(readl(i2s_reg_base + SAIMR) & (~SAIMR_TFS), i2s_reg_base + SAIMR);
} else {
writel(readl(i2s_reg_base + SACR1) | (SACR1_DREC), i2s_reg_base + SACR1);
writel(readl(i2s_reg_base + SAIMR) & (~SAIMR_RFS), i2s_reg_base + SAIMR);
}
if ((readl(i2s_reg_base + SACR1) & (SACR1_DREC | SACR1_DRPL)) == (SACR1_DREC | SACR1_DRPL)) {
writel(readl(i2s_reg_base + SACR0) & (~SACR0_ENB), i2s_reg_base + SACR0);
pxa_i2s_wait();
if (clk_ena) {
clk_disable_unprepare(clk_i2s);
clk_ena = 0;
}
}
}
#ifdef CONFIG_PM
static int pxa2xx_soc_pcm_suspend(struct snd_soc_component *component)
{
/* store registers */
pxa_i2s.sacr0 = readl(i2s_reg_base + SACR0);
pxa_i2s.sacr1 = readl(i2s_reg_base + SACR1);
pxa_i2s.saimr = readl(i2s_reg_base + SAIMR);
pxa_i2s.sadiv = readl(i2s_reg_base + SADIV);
/* deactivate link */
writel(readl(i2s_reg_base + SACR0) & (~SACR0_ENB), i2s_reg_base + SACR0);
pxa_i2s_wait();
return 0;
}
static int pxa2xx_soc_pcm_resume(struct snd_soc_component *component)
{
pxa_i2s_wait();
writel(pxa_i2s.sacr0 & ~SACR0_ENB, i2s_reg_base + SACR0);
writel(pxa_i2s.sacr1, i2s_reg_base + SACR1);
writel(pxa_i2s.saimr, i2s_reg_base + SAIMR);
writel(pxa_i2s.sadiv, i2s_reg_base + SADIV);
writel(pxa_i2s.sacr0, i2s_reg_base + SACR0);
return 0;
}
#else
#define pxa2xx_soc_pcm_suspend NULL
#define pxa2xx_soc_pcm_resume NULL
#endif
static int pxa2xx_i2s_probe(struct snd_soc_dai *dai)
{
clk_i2s = clk_get(dai->dev, "I2SCLK");
if (IS_ERR(clk_i2s))
return PTR_ERR(clk_i2s);
/*
* PXA Developer's Manual:
* If SACR0[ENB] is toggled in the middle of a normal operation,
* the SACR0[RST] bit must also be set and cleared to reset all
* I2S controller registers.
*/
writel(SACR0_RST, i2s_reg_base + SACR0);
writel(0, i2s_reg_base + SACR0);
/* Make sure RPL and REC are disabled */
writel(SACR1_DRPL | SACR1_DREC, i2s_reg_base + SACR1);
/* Along with FIFO servicing */
writel(readl(i2s_reg_base + SAIMR) & (~(SAIMR_RFS | SAIMR_TFS)), i2s_reg_base + SAIMR);
snd_soc_dai_init_dma_data(dai, &pxa2xx_i2s_pcm_stereo_out,
&pxa2xx_i2s_pcm_stereo_in);
return 0;
}
static int pxa2xx_i2s_remove(struct snd_soc_dai *dai)
{
clk_put(clk_i2s);
clk_i2s = ERR_PTR(-ENOENT);
return 0;
}
#define PXA2XX_I2S_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \
SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000)
static const struct snd_soc_dai_ops pxa_i2s_dai_ops = {
.probe = pxa2xx_i2s_probe,
.remove = pxa2xx_i2s_remove,
.startup = pxa2xx_i2s_startup,
.shutdown = pxa2xx_i2s_shutdown,
.trigger = pxa2xx_i2s_trigger,
.hw_params = pxa2xx_i2s_hw_params,
.set_fmt = pxa2xx_i2s_set_dai_fmt,
.set_sysclk = pxa2xx_i2s_set_dai_sysclk,
};
static struct snd_soc_dai_driver pxa_i2s_dai = {
.playback = {
.channels_min = 2,
.channels_max = 2,
.rates = PXA2XX_I2S_RATES,
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
.capture = {
.channels_min = 2,
.channels_max = 2,
.rates = PXA2XX_I2S_RATES,
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
.ops = &pxa_i2s_dai_ops,
.symmetric_rate = 1,
};
static const struct snd_soc_component_driver pxa_i2s_component = {
.name = "pxa-i2s",
.pcm_new = pxa2xx_soc_pcm_new,
.open = pxa2xx_soc_pcm_open,
.close = pxa2xx_soc_pcm_close,
.hw_params = pxa2xx_soc_pcm_hw_params,
.prepare = pxa2xx_soc_pcm_prepare,
.trigger = pxa2xx_soc_pcm_trigger,
.pointer = pxa2xx_soc_pcm_pointer,
.suspend = pxa2xx_soc_pcm_suspend,
.resume = pxa2xx_soc_pcm_resume,
.legacy_dai_naming = 1,
};
static int pxa2xx_i2s_drv_probe(struct platform_device *pdev)
{
struct resource *res;
i2s_reg_base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
if (IS_ERR(i2s_reg_base))
return PTR_ERR(i2s_reg_base);
pxa2xx_i2s_pcm_stereo_out.addr = res->start + SADR;
pxa2xx_i2s_pcm_stereo_in.addr = res->start + SADR;
return devm_snd_soc_register_component(&pdev->dev, &pxa_i2s_component,
&pxa_i2s_dai, 1);
}
static struct platform_driver pxa2xx_i2s_driver = {
.probe = pxa2xx_i2s_drv_probe,
.driver = {
.name = "pxa2xx-i2s",
},
};
static int __init pxa2xx_i2s_init(void)
{
clk_i2s = ERR_PTR(-ENOENT);
return platform_driver_register(&pxa2xx_i2s_driver);
}
static void __exit pxa2xx_i2s_exit(void)
{
platform_driver_unregister(&pxa2xx_i2s_driver);
}
module_init(pxa2xx_i2s_init);
module_exit(pxa2xx_i2s_exit);
/* Module information */
MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk");
MODULE_DESCRIPTION("pxa2xx I2S SoC Interface");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:pxa2xx-i2s");
+12
View File
@@ -0,0 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* linux/sound/soc/pxa/pxa2xx-i2s.h
*/
#ifndef _PXA2XX_I2S_H
#define _PXA2XX_I2S_H
/* I2S clock */
#define PXA2XX_I2S_SYSCLK 0
#endif
+45
View File
@@ -0,0 +1,45 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __PXA2XX_LIB_H
#define __PXA2XX_LIB_H
#include <uapi/sound/asound.h>
#include <linux/platform_device.h>
/* PCM */
struct snd_pcm_substream;
struct snd_pcm_hw_params;
struct snd_soc_pcm_runtime;
struct snd_pcm;
struct snd_soc_component;
int pxa2xx_soc_pcm_new(struct snd_soc_component *component,
struct snd_soc_pcm_runtime *rtd);
int pxa2xx_soc_pcm_open(struct snd_soc_component *component,
struct snd_pcm_substream *substream);
int pxa2xx_soc_pcm_close(struct snd_soc_component *component,
struct snd_pcm_substream *substream);
int pxa2xx_soc_pcm_hw_params(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params);
int pxa2xx_soc_pcm_prepare(struct snd_soc_component *component,
struct snd_pcm_substream *substream);
int pxa2xx_soc_pcm_trigger(struct snd_soc_component *component,
struct snd_pcm_substream *substream, int cmd);
snd_pcm_uframes_t pxa2xx_soc_pcm_pointer(struct snd_soc_component *component,
struct snd_pcm_substream *substream);
/* AC97 */
int pxa2xx_ac97_read(int slot, unsigned short reg);
int pxa2xx_ac97_write(int slot, unsigned short reg, unsigned short val);
bool pxa2xx_ac97_try_warm_reset(void);
bool pxa2xx_ac97_try_cold_reset(void);
void pxa2xx_ac97_finish_reset(void);
int pxa2xx_ac97_hw_suspend(void);
int pxa2xx_ac97_hw_resume(void);
int pxa2xx_ac97_hw_probe(struct platform_device *dev);
void pxa2xx_ac97_hw_remove(struct platform_device *dev);
#endif
+189
View File
@@ -0,0 +1,189 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/dma/pxa-dma.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/dmaengine_pcm.h>
#include "pxa2xx-lib.h"
static const struct snd_pcm_hardware pxa2xx_pcm_hardware = {
.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S32_LE,
.period_bytes_min = 32,
.period_bytes_max = 8192 - 32,
.periods_min = 1,
.periods_max = 256,
.buffer_bytes_max = 128 * 1024,
.fifo_size = 32,
};
static int pxa2xx_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct dma_chan *chan = snd_dmaengine_pcm_get_chan(substream);
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_dmaengine_dai_dma_data *dma_params;
struct dma_slave_config config;
int ret;
dma_params = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream);
if (!dma_params)
return 0;
ret = snd_hwparams_to_dma_slave_config(substream, params, &config);
if (ret)
return ret;
snd_dmaengine_pcm_set_config_from_dai_data(substream,
snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream),
&config);
ret = dmaengine_slave_config(chan, &config);
if (ret)
return ret;
return 0;
}
static int pxa2xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
return snd_dmaengine_pcm_trigger(substream, cmd);
}
static snd_pcm_uframes_t
pxa2xx_pcm_pointer(struct snd_pcm_substream *substream)
{
return snd_dmaengine_pcm_pointer(substream);
}
static int pxa2xx_pcm_prepare(struct snd_pcm_substream *substream)
{
return 0;
}
static int pxa2xx_pcm_open(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_dmaengine_dai_dma_data *dma_params;
int ret;
runtime->hw = pxa2xx_pcm_hardware;
dma_params = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream);
if (!dma_params)
return 0;
/*
* 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, 32);
if (ret)
return ret;
ret = snd_pcm_hw_constraint_step(runtime, 0,
SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32);
if (ret)
return ret;
ret = snd_pcm_hw_constraint_integer(runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
if (ret < 0)
return ret;
return snd_dmaengine_pcm_open(
substream, dma_request_slave_channel(snd_soc_rtd_to_cpu(rtd, 0)->dev,
dma_params->chan_name));
}
static int pxa2xx_pcm_close(struct snd_pcm_substream *substream)
{
return snd_dmaengine_pcm_close_release_chan(substream);
}
static int pxa2xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm)
{
size_t size = pxa2xx_pcm_hardware.buffer_bytes_max;
return snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV_WC,
pcm->card->dev, size);
}
int pxa2xx_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;
ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
if (ret)
return ret;
return pxa2xx_pcm_preallocate_dma_buffer(pcm);
}
EXPORT_SYMBOL(pxa2xx_soc_pcm_new);
int pxa2xx_soc_pcm_open(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
return pxa2xx_pcm_open(substream);
}
EXPORT_SYMBOL(pxa2xx_soc_pcm_open);
int pxa2xx_soc_pcm_close(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
return pxa2xx_pcm_close(substream);
}
EXPORT_SYMBOL(pxa2xx_soc_pcm_close);
int pxa2xx_soc_pcm_hw_params(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
return pxa2xx_pcm_hw_params(substream, params);
}
EXPORT_SYMBOL(pxa2xx_soc_pcm_hw_params);
int pxa2xx_soc_pcm_prepare(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
return pxa2xx_pcm_prepare(substream);
}
EXPORT_SYMBOL(pxa2xx_soc_pcm_prepare);
int pxa2xx_soc_pcm_trigger(struct snd_soc_component *component,
struct snd_pcm_substream *substream, int cmd)
{
return pxa2xx_pcm_trigger(substream, cmd);
}
EXPORT_SYMBOL(pxa2xx_soc_pcm_trigger);
snd_pcm_uframes_t
pxa2xx_soc_pcm_pointer(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
return pxa2xx_pcm_pointer(substream);
}
EXPORT_SYMBOL(pxa2xx_soc_pcm_pointer);
MODULE_AUTHOR("Nicolas Pitre");
MODULE_DESCRIPTION("Intel PXA2xx sound library");
MODULE_LICENSE("GPL");
+50
View File
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/sound/arm/pxa2xx-pcm.c -- ALSA PCM interface for the Intel PXA2xx chip
*
* Author: Nicolas Pitre
* Created: Nov 30, 2004
* Copyright: (C) 2004 MontaVista Software, Inc.
*/
#include <linux/dma-mapping.h>
#include <linux/module.h>
#include <linux/dmaengine.h>
#include <linux/of.h>
#include <sound/core.h>
#include <sound/soc.h>
#include <sound/dmaengine_pcm.h>
#include "pxa2xx-lib.h"
static const struct snd_soc_component_driver pxa2xx_soc_platform = {
.pcm_new = pxa2xx_soc_pcm_new,
.open = pxa2xx_soc_pcm_open,
.close = pxa2xx_soc_pcm_close,
.hw_params = pxa2xx_soc_pcm_hw_params,
.prepare = pxa2xx_soc_pcm_prepare,
.trigger = pxa2xx_soc_pcm_trigger,
.pointer = pxa2xx_soc_pcm_pointer,
};
static int pxa2xx_soc_platform_probe(struct platform_device *pdev)
{
return devm_snd_soc_register_component(&pdev->dev, &pxa2xx_soc_platform,
NULL, 0);
}
static struct platform_driver pxa_pcm_driver = {
.driver = {
.name = "pxa-pcm-audio",
},
.probe = pxa2xx_soc_platform_probe,
};
module_platform_driver(pxa_pcm_driver);
MODULE_AUTHOR("Nicolas Pitre");
MODULE_DESCRIPTION("Intel PXA2xx PCM DMA module");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:pxa-pcm-audio");
+325
View File
@@ -0,0 +1,325 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* spitz.c -- SoC audio for Sharp SL-Cxx00 models Spitz, Borzoi and Akita
*
* Copyright 2005 Wolfson Microelectronics PLC.
* Copyright 2005 Openedhand Ltd.
*
* Authors: Liam Girdwood <lrg@slimlogic.co.uk>
* Richard Purdie <richard@openedhand.com>
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <asm/mach-types.h>
#include "../codecs/wm8750.h"
#include "pxa2xx-i2s.h"
#define SPITZ_HP 0
#define SPITZ_MIC 1
#define SPITZ_LINE 2
#define SPITZ_HEADSET 3
#define SPITZ_HP_OFF 4
#define SPITZ_SPK_ON 0
#define SPITZ_SPK_OFF 1
/* audio clock in Hz - rounded from 12.235MHz */
#define SPITZ_AUDIO_CLOCK 12288000
static int spitz_jack_func;
static int spitz_spk_func;
static struct gpio_desc *gpiod_mic, *gpiod_mute_l, *gpiod_mute_r;
static void spitz_ext_control(struct snd_soc_dapm_context *dapm)
{
snd_soc_dapm_mutex_lock(dapm);
if (spitz_spk_func == SPITZ_SPK_ON)
snd_soc_dapm_enable_pin_unlocked(dapm, "Ext Spk");
else
snd_soc_dapm_disable_pin_unlocked(dapm, "Ext Spk");
/* set up jack connection */
switch (spitz_jack_func) {
case SPITZ_HP:
/* enable and unmute hp jack, disable mic bias */
snd_soc_dapm_disable_pin_unlocked(dapm, "Headset Jack");
snd_soc_dapm_disable_pin_unlocked(dapm, "Mic Jack");
snd_soc_dapm_disable_pin_unlocked(dapm, "Line Jack");
snd_soc_dapm_enable_pin_unlocked(dapm, "Headphone Jack");
gpiod_set_value(gpiod_mute_l, 1);
gpiod_set_value(gpiod_mute_r, 1);
break;
case SPITZ_MIC:
/* enable mic jack and bias, mute hp */
snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone Jack");
snd_soc_dapm_disable_pin_unlocked(dapm, "Headset Jack");
snd_soc_dapm_disable_pin_unlocked(dapm, "Line Jack");
snd_soc_dapm_enable_pin_unlocked(dapm, "Mic Jack");
gpiod_set_value(gpiod_mute_l, 0);
gpiod_set_value(gpiod_mute_r, 0);
break;
case SPITZ_LINE:
/* enable line jack, disable mic bias and mute hp */
snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone Jack");
snd_soc_dapm_disable_pin_unlocked(dapm, "Headset Jack");
snd_soc_dapm_disable_pin_unlocked(dapm, "Mic Jack");
snd_soc_dapm_enable_pin_unlocked(dapm, "Line Jack");
gpiod_set_value(gpiod_mute_l, 0);
gpiod_set_value(gpiod_mute_r, 0);
break;
case SPITZ_HEADSET:
/* enable and unmute headset jack enable mic bias, mute L hp */
snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone Jack");
snd_soc_dapm_enable_pin_unlocked(dapm, "Mic Jack");
snd_soc_dapm_disable_pin_unlocked(dapm, "Line Jack");
snd_soc_dapm_enable_pin_unlocked(dapm, "Headset Jack");
gpiod_set_value(gpiod_mute_l, 0);
gpiod_set_value(gpiod_mute_r, 1);
break;
case SPITZ_HP_OFF:
/* jack removed, everything off */
snd_soc_dapm_disable_pin_unlocked(dapm, "Headphone Jack");
snd_soc_dapm_disable_pin_unlocked(dapm, "Headset Jack");
snd_soc_dapm_disable_pin_unlocked(dapm, "Mic Jack");
snd_soc_dapm_disable_pin_unlocked(dapm, "Line Jack");
gpiod_set_value(gpiod_mute_l, 0);
gpiod_set_value(gpiod_mute_r, 0);
break;
}
snd_soc_dapm_sync_unlocked(dapm);
snd_soc_dapm_mutex_unlock(dapm);
}
static int spitz_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(rtd->card);
/* check the jack status at stream startup */
spitz_ext_control(dapm);
return 0;
}
static int spitz_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
unsigned int clk = 0;
int ret = 0;
switch (params_rate(params)) {
case 8000:
case 16000:
case 48000:
case 96000:
clk = 12288000;
break;
case 11025:
case 22050:
case 44100:
clk = 11289600;
break;
}
/* set the codec system clock for DAC and ADC */
ret = snd_soc_dai_set_sysclk(codec_dai, WM8750_SYSCLK, clk,
SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
/* set the I2S system clock as input (unused) */
ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0,
SND_SOC_CLOCK_IN);
if (ret < 0)
return ret;
return 0;
}
static const struct snd_soc_ops spitz_ops = {
.startup = spitz_startup,
.hw_params = spitz_hw_params,
};
static int spitz_get_jack(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.enumerated.item[0] = spitz_jack_func;
return 0;
}
static int spitz_set_jack(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card);
if (spitz_jack_func == ucontrol->value.enumerated.item[0])
return 0;
spitz_jack_func = ucontrol->value.enumerated.item[0];
spitz_ext_control(dapm);
return 1;
}
static int spitz_get_spk(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.enumerated.item[0] = spitz_spk_func;
return 0;
}
static int spitz_set_spk(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card);
if (spitz_spk_func == ucontrol->value.enumerated.item[0])
return 0;
spitz_spk_func = ucontrol->value.enumerated.item[0];
spitz_ext_control(dapm);
return 1;
}
static int spitz_mic_bias(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *k, int event)
{
gpiod_set_value_cansleep(gpiod_mic, SND_SOC_DAPM_EVENT_ON(event));
return 0;
}
/* spitz machine dapm widgets */
static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = {
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_MIC("Mic Jack", spitz_mic_bias),
SND_SOC_DAPM_SPK("Ext Spk", NULL),
SND_SOC_DAPM_LINE("Line Jack", NULL),
/* headset is a mic and mono headphone */
SND_SOC_DAPM_HP("Headset Jack", NULL),
};
/* Spitz machine audio_map */
static const struct snd_soc_dapm_route spitz_audio_map[] = {
/* headphone connected to LOUT1, ROUT1 */
{"Headphone Jack", NULL, "LOUT1"},
{"Headphone Jack", NULL, "ROUT1"},
/* headset connected to ROUT1 and LINPUT1 with bias (def below) */
{"Headset Jack", NULL, "ROUT1"},
/* ext speaker connected to LOUT2, ROUT2 */
{"Ext Spk", NULL, "ROUT2"},
{"Ext Spk", NULL, "LOUT2"},
/* mic is connected to input 1 - with bias */
{"LINPUT1", NULL, "Mic Bias"},
{"Mic Bias", NULL, "Mic Jack"},
/* line is connected to input 1 - no bias */
{"LINPUT1", NULL, "Line Jack"},
};
static const char * const jack_function[] = {"Headphone", "Mic", "Line",
"Headset", "Off"};
static const char * const spk_function[] = {"On", "Off"};
static const struct soc_enum spitz_enum[] = {
SOC_ENUM_SINGLE_EXT(5, jack_function),
SOC_ENUM_SINGLE_EXT(2, spk_function),
};
static const struct snd_kcontrol_new wm8750_spitz_controls[] = {
SOC_ENUM_EXT("Jack Function", spitz_enum[0], spitz_get_jack,
spitz_set_jack),
SOC_ENUM_EXT("Speaker Function", spitz_enum[1], spitz_get_spk,
spitz_set_spk),
};
/* spitz digital audio interface glue - connects codec <--> CPU */
SND_SOC_DAILINK_DEFS(wm8750,
DAILINK_COMP_ARRAY(COMP_CPU("pxa2xx-i2s")),
DAILINK_COMP_ARRAY(COMP_CODEC("wm8750.0-001b", "wm8750-hifi")),
DAILINK_COMP_ARRAY(COMP_PLATFORM("pxa-pcm-audio")));
static struct snd_soc_dai_link spitz_dai = {
.name = "wm8750",
.stream_name = "WM8750",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBC_CFC,
.ops = &spitz_ops,
SND_SOC_DAILINK_REG(wm8750),
};
/* spitz audio machine driver */
static struct snd_soc_card snd_soc_spitz = {
.name = "Spitz",
.owner = THIS_MODULE,
.dai_link = &spitz_dai,
.num_links = 1,
.controls = wm8750_spitz_controls,
.num_controls = ARRAY_SIZE(wm8750_spitz_controls),
.dapm_widgets = wm8750_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(wm8750_dapm_widgets),
.dapm_routes = spitz_audio_map,
.num_dapm_routes = ARRAY_SIZE(spitz_audio_map),
.fully_routed = true,
};
static int spitz_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &snd_soc_spitz;
int ret;
gpiod_mic = devm_gpiod_get(&pdev->dev, "mic", GPIOD_OUT_LOW);
if (IS_ERR(gpiod_mic))
return PTR_ERR(gpiod_mic);
gpiod_mute_l = devm_gpiod_get(&pdev->dev, "mute-l", GPIOD_OUT_LOW);
if (IS_ERR(gpiod_mute_l))
return PTR_ERR(gpiod_mute_l);
gpiod_mute_r = devm_gpiod_get(&pdev->dev, "mute-r", GPIOD_OUT_LOW);
if (IS_ERR(gpiod_mute_r))
return PTR_ERR(gpiod_mute_r);
card->dev = &pdev->dev;
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret)
dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
ret);
return ret;
}
static struct platform_driver spitz_driver = {
.driver = {
.name = "spitz-audio",
.pm = &snd_soc_pm_ops,
},
.probe = spitz_probe,
};
module_platform_driver(spitz_driver);
MODULE_AUTHOR("Richard Purdie");
MODULE_DESCRIPTION("ALSA SoC Spitz");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:spitz-audio");