/*
* pxa-ssp.c -- ALSA Soc Audio Layer
*
* Copyright 2005,2008 Wolfson Microelectronics PLC.
* Author: Liam Girdwood
* Mark Brown <broonie@opensource.wolfsonmicro.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* 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/pxa2xx-lib.h>
#include <sound/dmaengine_pcm.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->pdev->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->pdev->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_enable(struct ssp_device *ssp)
{
uint32_t sscr0;
sscr0 = __raw_readl(ssp->mmio_base + SSCR0) | SSCR0_SSE;
__raw_writel(sscr0, ssp->mmio_base + SSCR0);
}
static void pxa_ssp_disable(struct ssp_device *ssp)
{
uint32_t sscr0;
sscr0 = __raw_readl(ssp->mmio_base + SSCR0) & ~SSCR0_SSE;
__raw_writel(sscr0, ssp->mmio_base + SSCR0);
}
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 (!cpu_dai->active) {
clk_prepare_enable(ssp->clk);
pxa_ssp_disable(ssp);
}
if (priv->extclk)
clk_prepare_enable(priv->extclk);
dma = kzalloc(sizeof(struct snd_dmaengine_dai_dma_data), GFP_KERNEL);
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 (!cpu_dai->active) {
pxa_ssp_disable(ssp);
clk_disable_unprepare(ssp->clk);
}
if (priv->extclk)
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_dai *cpu_dai)
{
struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
struct ssp_device *ssp = priv->ssp;
if (!cpu_dai->active)
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_dai *cpu_dai)
{
struct ssp_priv *priv = snd_soc_dai_get_drvdata(cpu_dai);
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