2019-06-04 16:11:33 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
2010-06-04 13:11:24 +08:00
|
|
|
/*
|
|
|
|
* linux/sound/soc/ep93xx-i2s.c
|
|
|
|
* EP93xx I2S driver
|
|
|
|
*
|
2011-06-15 12:45:36 +08:00
|
|
|
* Copyright (C) 2010 Ryan Mallon
|
2010-06-04 13:11:24 +08:00
|
|
|
*
|
|
|
|
* Based on the original driver by:
|
|
|
|
* Copyright (C) 2007 Chase Douglas <chasedouglas@gmail>
|
|
|
|
* Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/clk.h>
|
|
|
|
#include <linux/io.h>
|
|
|
|
|
|
|
|
#include <sound/core.h>
|
2013-12-11 03:34:46 +08:00
|
|
|
#include <sound/dmaengine_pcm.h>
|
2010-06-04 13:11:24 +08:00
|
|
|
#include <sound/pcm.h>
|
|
|
|
#include <sound/pcm_params.h>
|
|
|
|
#include <sound/initval.h>
|
|
|
|
#include <sound/soc.h>
|
|
|
|
|
2012-08-24 21:12:11 +08:00
|
|
|
#include <linux/platform_data/dma-ep93xx.h>
|
2019-04-16 04:17:11 +08:00
|
|
|
#include <linux/soc/cirrus/ep93xx.h>
|
2010-06-04 13:11:24 +08:00
|
|
|
|
2013-12-11 03:34:45 +08:00
|
|
|
#include "ep93xx-pcm.h"
|
|
|
|
|
2010-06-04 13:11:24 +08:00
|
|
|
#define EP93XX_I2S_TXCLKCFG 0x00
|
|
|
|
#define EP93XX_I2S_RXCLKCFG 0x04
|
2018-04-29 04:51:42 +08:00
|
|
|
#define EP93XX_I2S_GLSTS 0x08
|
2010-06-04 13:11:24 +08:00
|
|
|
#define EP93XX_I2S_GLCTRL 0x0C
|
|
|
|
|
2018-04-29 04:51:42 +08:00
|
|
|
#define EP93XX_I2S_I2STX0LFT 0x10
|
|
|
|
#define EP93XX_I2S_I2STX0RT 0x14
|
|
|
|
|
2010-06-04 13:11:24 +08:00
|
|
|
#define EP93XX_I2S_TXLINCTRLDATA 0x28
|
|
|
|
#define EP93XX_I2S_TXCTRL 0x2C
|
|
|
|
#define EP93XX_I2S_TXWRDLEN 0x30
|
|
|
|
#define EP93XX_I2S_TX0EN 0x34
|
|
|
|
|
|
|
|
#define EP93XX_I2S_RXLINCTRLDATA 0x58
|
|
|
|
#define EP93XX_I2S_RXCTRL 0x5C
|
|
|
|
#define EP93XX_I2S_RXWRDLEN 0x60
|
|
|
|
#define EP93XX_I2S_RX0EN 0x64
|
|
|
|
|
|
|
|
#define EP93XX_I2S_WRDLEN_16 (0 << 0)
|
|
|
|
#define EP93XX_I2S_WRDLEN_24 (1 << 0)
|
|
|
|
#define EP93XX_I2S_WRDLEN_32 (2 << 0)
|
|
|
|
|
2018-04-29 04:51:39 +08:00
|
|
|
#define EP93XX_I2S_RXLINCTRLDATA_R_JUST BIT(1) /* Right justify */
|
|
|
|
|
|
|
|
#define EP93XX_I2S_TXLINCTRLDATA_R_JUST BIT(2) /* Right justify */
|
2010-06-04 13:11:24 +08:00
|
|
|
|
2018-04-29 04:51:42 +08:00
|
|
|
/*
|
|
|
|
* Transmit empty interrupt level select:
|
|
|
|
* 0 - Generate interrupt when FIFO is half empty
|
|
|
|
* 1 - Generate interrupt when FIFO is empty
|
|
|
|
*/
|
|
|
|
#define EP93XX_I2S_TXCTRL_TXEMPTY_LVL BIT(0)
|
|
|
|
#define EP93XX_I2S_TXCTRL_TXUFIE BIT(1) /* Transmit interrupt enable */
|
|
|
|
|
2010-06-04 13:11:24 +08:00
|
|
|
#define EP93XX_I2S_CLKCFG_LRS (1 << 0) /* lrclk polarity */
|
|
|
|
#define EP93XX_I2S_CLKCFG_CKP (1 << 1) /* Bit clock polarity */
|
|
|
|
#define EP93XX_I2S_CLKCFG_REL (1 << 2) /* First bit transition */
|
|
|
|
#define EP93XX_I2S_CLKCFG_MASTER (1 << 3) /* Master mode */
|
|
|
|
#define EP93XX_I2S_CLKCFG_NBCG (1 << 4) /* Not bit clock gating */
|
|
|
|
|
2018-04-29 04:51:42 +08:00
|
|
|
#define EP93XX_I2S_GLSTS_TX0_FIFO_FULL BIT(12)
|
|
|
|
|
2010-06-04 13:11:24 +08:00
|
|
|
struct ep93xx_i2s_info {
|
|
|
|
struct clk *mclk;
|
|
|
|
struct clk *sclk;
|
|
|
|
struct clk *lrclk;
|
|
|
|
void __iomem *regs;
|
2013-12-11 03:34:46 +08:00
|
|
|
struct snd_dmaengine_dai_dma_data dma_params_rx;
|
|
|
|
struct snd_dmaengine_dai_dma_data dma_params_tx;
|
2010-06-04 13:11:24 +08:00
|
|
|
};
|
|
|
|
|
2013-05-15 04:19:47 +08:00
|
|
|
static struct ep93xx_dma_data ep93xx_i2s_dma_data[] = {
|
2010-06-04 13:11:24 +08:00
|
|
|
[SNDRV_PCM_STREAM_PLAYBACK] = {
|
|
|
|
.name = "i2s-pcm-out",
|
2013-04-03 17:00:00 +08:00
|
|
|
.port = EP93XX_DMA_I2S1,
|
2013-03-22 21:12:10 +08:00
|
|
|
.direction = DMA_MEM_TO_DEV,
|
2010-06-04 13:11:24 +08:00
|
|
|
},
|
|
|
|
[SNDRV_PCM_STREAM_CAPTURE] = {
|
|
|
|
.name = "i2s-pcm-in",
|
2013-04-03 17:00:00 +08:00
|
|
|
.port = EP93XX_DMA_I2S1,
|
2013-03-22 21:12:10 +08:00
|
|
|
.direction = DMA_DEV_TO_MEM,
|
2010-06-04 13:11:24 +08:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
static inline void ep93xx_i2s_write_reg(struct ep93xx_i2s_info *info,
|
|
|
|
unsigned reg, unsigned val)
|
|
|
|
{
|
|
|
|
__raw_writel(val, info->regs + reg);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline unsigned ep93xx_i2s_read_reg(struct ep93xx_i2s_info *info,
|
|
|
|
unsigned reg)
|
|
|
|
{
|
|
|
|
return __raw_readl(info->regs + reg);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ep93xx_i2s_enable(struct ep93xx_i2s_info *info, int stream)
|
|
|
|
{
|
|
|
|
unsigned base_reg;
|
|
|
|
|
|
|
|
if ((ep93xx_i2s_read_reg(info, EP93XX_I2S_TX0EN) & 0x1) == 0 &&
|
|
|
|
(ep93xx_i2s_read_reg(info, EP93XX_I2S_RX0EN) & 0x1) == 0) {
|
|
|
|
/* Enable clocks */
|
|
|
|
clk_enable(info->mclk);
|
|
|
|
clk_enable(info->sclk);
|
|
|
|
clk_enable(info->lrclk);
|
|
|
|
|
|
|
|
/* Enable i2s */
|
|
|
|
ep93xx_i2s_write_reg(info, EP93XX_I2S_GLCTRL, 1);
|
|
|
|
}
|
|
|
|
|
2018-04-29 04:51:40 +08:00
|
|
|
/* Enable fifo */
|
2010-06-04 13:11:24 +08:00
|
|
|
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
|
|
base_reg = EP93XX_I2S_TX0EN;
|
|
|
|
else
|
|
|
|
base_reg = EP93XX_I2S_RX0EN;
|
2018-04-29 04:51:40 +08:00
|
|
|
ep93xx_i2s_write_reg(info, base_reg, 1);
|
2018-04-29 04:51:42 +08:00
|
|
|
|
|
|
|
/* Enable TX IRQs (FIFO empty or underflow) */
|
|
|
|
if (IS_ENABLED(CONFIG_SND_EP93XX_SOC_I2S_WATCHDOG) &&
|
|
|
|
stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
|
|
ep93xx_i2s_write_reg(info, EP93XX_I2S_TXCTRL,
|
|
|
|
EP93XX_I2S_TXCTRL_TXEMPTY_LVL |
|
|
|
|
EP93XX_I2S_TXCTRL_TXUFIE);
|
2010-06-04 13:11:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void ep93xx_i2s_disable(struct ep93xx_i2s_info *info, int stream)
|
|
|
|
{
|
|
|
|
unsigned base_reg;
|
|
|
|
|
2018-04-29 04:51:42 +08:00
|
|
|
/* Disable IRQs */
|
|
|
|
if (IS_ENABLED(CONFIG_SND_EP93XX_SOC_I2S_WATCHDOG) &&
|
|
|
|
stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
|
|
ep93xx_i2s_write_reg(info, EP93XX_I2S_TXCTRL, 0);
|
|
|
|
|
2018-04-29 04:51:40 +08:00
|
|
|
/* Disable fifo */
|
2010-06-04 13:11:24 +08:00
|
|
|
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
|
|
base_reg = EP93XX_I2S_TX0EN;
|
|
|
|
else
|
|
|
|
base_reg = EP93XX_I2S_RX0EN;
|
2018-04-29 04:51:40 +08:00
|
|
|
ep93xx_i2s_write_reg(info, base_reg, 0);
|
2010-06-04 13:11:24 +08:00
|
|
|
|
|
|
|
if ((ep93xx_i2s_read_reg(info, EP93XX_I2S_TX0EN) & 0x1) == 0 &&
|
|
|
|
(ep93xx_i2s_read_reg(info, EP93XX_I2S_RX0EN) & 0x1) == 0) {
|
|
|
|
/* Disable i2s */
|
|
|
|
ep93xx_i2s_write_reg(info, EP93XX_I2S_GLCTRL, 0);
|
|
|
|
|
|
|
|
/* Disable clocks */
|
|
|
|
clk_disable(info->lrclk);
|
|
|
|
clk_disable(info->sclk);
|
|
|
|
clk_disable(info->mclk);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-29 04:51:42 +08:00
|
|
|
/*
|
|
|
|
* According to documentation I2S controller can handle underflow conditions
|
|
|
|
* just fine, but in reality the state machine is sometimes confused so that
|
|
|
|
* the whole stream is shifted by one byte. The watchdog below disables the TX
|
|
|
|
* FIFO, fills the buffer with zeroes and re-enables the FIFO. State machine
|
|
|
|
* is being reset and by filling the buffer we get some time before next
|
|
|
|
* underflow happens.
|
|
|
|
*/
|
|
|
|
static irqreturn_t ep93xx_i2s_interrupt(int irq, void *dev_id)
|
|
|
|
{
|
|
|
|
struct ep93xx_i2s_info *info = dev_id;
|
|
|
|
|
|
|
|
/* Disable FIFO */
|
|
|
|
ep93xx_i2s_write_reg(info, EP93XX_I2S_TX0EN, 0);
|
|
|
|
/*
|
|
|
|
* Fill TX FIFO with zeroes, this way we can defer next IRQs as much as
|
|
|
|
* possible and get more time for DMA to catch up. Actually there are
|
|
|
|
* only 8 samples in this FIFO, so even on 8kHz maximum deferral here is
|
|
|
|
* 1ms.
|
|
|
|
*/
|
|
|
|
while (!(ep93xx_i2s_read_reg(info, EP93XX_I2S_GLSTS) &
|
|
|
|
EP93XX_I2S_GLSTS_TX0_FIFO_FULL)) {
|
|
|
|
ep93xx_i2s_write_reg(info, EP93XX_I2S_I2STX0LFT, 0);
|
|
|
|
ep93xx_i2s_write_reg(info, EP93XX_I2S_I2STX0RT, 0);
|
|
|
|
}
|
|
|
|
/* Re-enable FIFO */
|
|
|
|
ep93xx_i2s_write_reg(info, EP93XX_I2S_TX0EN, 1);
|
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
2013-04-21 01:29:04 +08:00
|
|
|
static int ep93xx_i2s_dai_probe(struct snd_soc_dai *dai)
|
2010-06-04 13:11:24 +08:00
|
|
|
{
|
2013-12-11 03:34:46 +08:00
|
|
|
struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai);
|
|
|
|
|
|
|
|
info->dma_params_tx.filter_data =
|
|
|
|
&ep93xx_i2s_dma_data[SNDRV_PCM_STREAM_PLAYBACK];
|
|
|
|
info->dma_params_rx.filter_data =
|
|
|
|
&ep93xx_i2s_dma_data[SNDRV_PCM_STREAM_CAPTURE];
|
|
|
|
|
|
|
|
dai->playback_dma_data = &info->dma_params_tx;
|
|
|
|
dai->capture_dma_data = &info->dma_params_rx;
|
2010-06-04 13:11:24 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ep93xx_i2s_shutdown(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_soc_dai *dai)
|
|
|
|
{
|
2010-03-18 04:15:21 +08:00
|
|
|
struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai);
|
2010-06-04 13:11:24 +08:00
|
|
|
|
|
|
|
ep93xx_i2s_disable(info, substream->stream);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ep93xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
|
|
|
|
unsigned int fmt)
|
|
|
|
{
|
2010-03-18 04:15:21 +08:00
|
|
|
struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(cpu_dai);
|
2018-04-29 04:51:39 +08:00
|
|
|
unsigned int clk_cfg;
|
|
|
|
unsigned int txlin_ctrl = 0;
|
|
|
|
unsigned int rxlin_ctrl = 0;
|
2010-06-04 13:11:24 +08:00
|
|
|
|
|
|
|
clk_cfg = ep93xx_i2s_read_reg(info, EP93XX_I2S_RXCLKCFG);
|
|
|
|
|
|
|
|
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
|
|
|
case SND_SOC_DAIFMT_I2S:
|
|
|
|
clk_cfg |= EP93XX_I2S_CLKCFG_REL;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SND_SOC_DAIFMT_LEFT_J:
|
|
|
|
clk_cfg &= ~EP93XX_I2S_CLKCFG_REL;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SND_SOC_DAIFMT_RIGHT_J:
|
|
|
|
clk_cfg &= ~EP93XX_I2S_CLKCFG_REL;
|
2018-04-29 04:51:39 +08:00
|
|
|
rxlin_ctrl |= EP93XX_I2S_RXLINCTRLDATA_R_JUST;
|
|
|
|
txlin_ctrl |= EP93XX_I2S_TXLINCTRLDATA_R_JUST;
|
2010-06-04 13:11:24 +08:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
|
|
|
case SND_SOC_DAIFMT_CBS_CFS:
|
|
|
|
/* CPU is master */
|
|
|
|
clk_cfg |= EP93XX_I2S_CLKCFG_MASTER;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SND_SOC_DAIFMT_CBM_CFM:
|
|
|
|
/* Codec is master */
|
|
|
|
clk_cfg &= ~EP93XX_I2S_CLKCFG_MASTER;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
|
|
|
|
case SND_SOC_DAIFMT_NB_NF:
|
|
|
|
/* Negative bit clock, lrclk low on left word */
|
2018-04-29 04:51:38 +08:00
|
|
|
clk_cfg &= ~(EP93XX_I2S_CLKCFG_CKP | EP93XX_I2S_CLKCFG_LRS);
|
2010-06-04 13:11:24 +08:00
|
|
|
break;
|
|
|
|
|
|
|
|
case SND_SOC_DAIFMT_NB_IF:
|
|
|
|
/* Negative bit clock, lrclk low on right word */
|
|
|
|
clk_cfg &= ~EP93XX_I2S_CLKCFG_CKP;
|
2018-04-29 04:51:38 +08:00
|
|
|
clk_cfg |= EP93XX_I2S_CLKCFG_LRS;
|
2010-06-04 13:11:24 +08:00
|
|
|
break;
|
|
|
|
|
|
|
|
case SND_SOC_DAIFMT_IB_NF:
|
|
|
|
/* Positive bit clock, lrclk low on left word */
|
|
|
|
clk_cfg |= EP93XX_I2S_CLKCFG_CKP;
|
2018-04-29 04:51:38 +08:00
|
|
|
clk_cfg &= ~EP93XX_I2S_CLKCFG_LRS;
|
2010-06-04 13:11:24 +08:00
|
|
|
break;
|
|
|
|
|
|
|
|
case SND_SOC_DAIFMT_IB_IF:
|
|
|
|
/* Positive bit clock, lrclk low on right word */
|
2018-04-29 04:51:38 +08:00
|
|
|
clk_cfg |= EP93XX_I2S_CLKCFG_CKP | EP93XX_I2S_CLKCFG_LRS;
|
2010-06-04 13:11:24 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Write new register values */
|
|
|
|
ep93xx_i2s_write_reg(info, EP93XX_I2S_RXCLKCFG, clk_cfg);
|
|
|
|
ep93xx_i2s_write_reg(info, EP93XX_I2S_TXCLKCFG, clk_cfg);
|
2018-04-29 04:51:39 +08:00
|
|
|
ep93xx_i2s_write_reg(info, EP93XX_I2S_RXLINCTRLDATA, rxlin_ctrl);
|
|
|
|
ep93xx_i2s_write_reg(info, EP93XX_I2S_TXLINCTRLDATA, txlin_ctrl);
|
2010-06-04 13:11:24 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ep93xx_i2s_hw_params(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_hw_params *params,
|
|
|
|
struct snd_soc_dai *dai)
|
|
|
|
{
|
2010-03-18 04:15:21 +08:00
|
|
|
struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai);
|
2010-06-04 13:11:24 +08:00
|
|
|
unsigned word_len, div, sdiv, lrdiv;
|
2011-03-08 01:30:36 +08:00
|
|
|
int err;
|
2010-06-04 13:11:24 +08:00
|
|
|
|
|
|
|
switch (params_format(params)) {
|
|
|
|
case SNDRV_PCM_FORMAT_S16_LE:
|
|
|
|
word_len = EP93XX_I2S_WRDLEN_16;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SNDRV_PCM_FORMAT_S24_LE:
|
|
|
|
word_len = EP93XX_I2S_WRDLEN_24;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SNDRV_PCM_FORMAT_S32_LE:
|
|
|
|
word_len = EP93XX_I2S_WRDLEN_32;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
|
|
ep93xx_i2s_write_reg(info, EP93XX_I2S_TXWRDLEN, word_len);
|
|
|
|
else
|
|
|
|
ep93xx_i2s_write_reg(info, EP93XX_I2S_RXWRDLEN, word_len);
|
|
|
|
|
|
|
|
/*
|
ASoC: EP93xx: fixed LRCLK rate and DMA oper. in I2S code
Changelog:
1. I2S module of EP93xx should be feed by 32bit DMA transfers. This is
hardware limitation and that's the way original Cirrus's driver worked.
This will fix distorted sound playback and make capture actually work in
present ep93xx drivers.
I've found, that author of code, on which modern ep93xx-i2s.c and
ep93xx-pcm.c are based, had faced this problem also in 2007:
http://blog.gmane.org/gmane.linux.ports.arm.cirrus/month=20070101/page=3
Now SoC code uses his developments, but not overcomes the hardware
issues. Some details from EP93xx users guide:
Both I2S transmitter and receiver have similar 16x32bit FIFO, where they
store 8 samples for both left and right channels. The FIFO is always
32bit wide and should be properly aligned if you use samples of other
width. Transmitter and receiver have configuration registers for
selection of I2S word length (16, 24, 32). They are I2STXWrdLen and
I2SRXWrdLen.
Yes, EP93xx DMA can do byte, word and quad-word transfers. The width for
transfers to and from peripherals is selected by particular module
configuration. Lucky AC97 module has such configuration: AC97RXCRx
registers, bit CM (Compact mode enable) switches between 16 and 32 bit
samples. AC97TXCRx registers have the same bits for transmitters.
ep93xx-ac97.c enables this compact mode and so has all the rights to use
S16_LE format.
No one has found such a configuration in I2S module until now in any
Cirrus manuals. I2S module always feeds it's 32bit wide FIFO with 32bit
samples consecutively for left and right channels. You cannot use 32-bit
DMA transfers to transfer two 16-bit samples.
So we can use two formats for AC97, but should remove all but S32_LE for
I2S. Always using 32 bit chunks is not a problem for I2S, the codec I
use uses less bits too (24), it's permitted by I2S standard.
In proposed patch formats list shortened to just S32_LE, this makes all
the DMA transactions right, while ALSA will do all sample format
translation for us.
2. Incorrect setting of LRCLK (2 times slower) in original ep93xx-i2s.c
masks the first problem.
DMA takes two 16 bit samples instead of one, overall sound speed seems
to be normal, but you get actually 4000 sampling rate instead of
requested 8000 and therefore some noise... This is also the reason why
the capture function not worked at all in this driver...
If we take a look into I2S specification, we will figure that LRCLK MUST
be equal to sample rate, if we are talking about stereo (in mono too,
but it's not our case at all).
In proposed patch SCLK and LRCLK rates are corrected, assuming we always
send 32 bits * 2 channels to codec.
Signed-off-by: Alexander Sverdlin <subaparts@yandex.ru>
Acked-by: Liam Girdwood <lrg@slimlogic.co.uk>
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
2011-01-16 20:48:05 +08:00
|
|
|
* EP93xx I2S module can be setup so SCLK / LRCLK value can be
|
|
|
|
* 32, 64, 128. MCLK / SCLK value can be 2 and 4.
|
|
|
|
* We set LRCLK equal to `rate' and minimum SCLK / LRCLK
|
|
|
|
* value is 64, because our sample size is 32 bit * 2 channels.
|
|
|
|
* I2S standard permits us to transmit more bits than
|
|
|
|
* the codec uses.
|
2010-06-04 13:11:24 +08:00
|
|
|
*/
|
ASoC: EP93xx: fixed LRCLK rate and DMA oper. in I2S code
Changelog:
1. I2S module of EP93xx should be feed by 32bit DMA transfers. This is
hardware limitation and that's the way original Cirrus's driver worked.
This will fix distorted sound playback and make capture actually work in
present ep93xx drivers.
I've found, that author of code, on which modern ep93xx-i2s.c and
ep93xx-pcm.c are based, had faced this problem also in 2007:
http://blog.gmane.org/gmane.linux.ports.arm.cirrus/month=20070101/page=3
Now SoC code uses his developments, but not overcomes the hardware
issues. Some details from EP93xx users guide:
Both I2S transmitter and receiver have similar 16x32bit FIFO, where they
store 8 samples for both left and right channels. The FIFO is always
32bit wide and should be properly aligned if you use samples of other
width. Transmitter and receiver have configuration registers for
selection of I2S word length (16, 24, 32). They are I2STXWrdLen and
I2SRXWrdLen.
Yes, EP93xx DMA can do byte, word and quad-word transfers. The width for
transfers to and from peripherals is selected by particular module
configuration. Lucky AC97 module has such configuration: AC97RXCRx
registers, bit CM (Compact mode enable) switches between 16 and 32 bit
samples. AC97TXCRx registers have the same bits for transmitters.
ep93xx-ac97.c enables this compact mode and so has all the rights to use
S16_LE format.
No one has found such a configuration in I2S module until now in any
Cirrus manuals. I2S module always feeds it's 32bit wide FIFO with 32bit
samples consecutively for left and right channels. You cannot use 32-bit
DMA transfers to transfer two 16-bit samples.
So we can use two formats for AC97, but should remove all but S32_LE for
I2S. Always using 32 bit chunks is not a problem for I2S, the codec I
use uses less bits too (24), it's permitted by I2S standard.
In proposed patch formats list shortened to just S32_LE, this makes all
the DMA transactions right, while ALSA will do all sample format
translation for us.
2. Incorrect setting of LRCLK (2 times slower) in original ep93xx-i2s.c
masks the first problem.
DMA takes two 16 bit samples instead of one, overall sound speed seems
to be normal, but you get actually 4000 sampling rate instead of
requested 8000 and therefore some noise... This is also the reason why
the capture function not worked at all in this driver...
If we take a look into I2S specification, we will figure that LRCLK MUST
be equal to sample rate, if we are talking about stereo (in mono too,
but it's not our case at all).
In proposed patch SCLK and LRCLK rates are corrected, assuming we always
send 32 bits * 2 channels to codec.
Signed-off-by: Alexander Sverdlin <subaparts@yandex.ru>
Acked-by: Liam Girdwood <lrg@slimlogic.co.uk>
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
2011-01-16 20:48:05 +08:00
|
|
|
div = clk_get_rate(info->mclk) / params_rate(params);
|
2011-03-08 01:30:36 +08:00
|
|
|
sdiv = 4;
|
|
|
|
if (div > (256 + 512) / 2) {
|
|
|
|
lrdiv = 128;
|
|
|
|
} else {
|
|
|
|
lrdiv = 64;
|
|
|
|
if (div < (128 + 256) / 2)
|
|
|
|
sdiv = 2;
|
|
|
|
}
|
2010-06-04 13:11:24 +08:00
|
|
|
|
|
|
|
err = clk_set_rate(info->sclk, clk_get_rate(info->mclk) / sdiv);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
err = clk_set_rate(info->lrclk, clk_get_rate(info->sclk) / lrdiv);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
ep93xx_i2s_enable(info, substream->stream);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ep93xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id,
|
|
|
|
unsigned int freq, int dir)
|
|
|
|
{
|
2010-03-18 04:15:21 +08:00
|
|
|
struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(cpu_dai);
|
2010-06-04 13:11:24 +08:00
|
|
|
|
|
|
|
if (dir == SND_SOC_CLOCK_IN || clk_id != 0)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
return clk_set_rate(info->mclk, freq);
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_PM
|
|
|
|
static int ep93xx_i2s_suspend(struct snd_soc_dai *dai)
|
|
|
|
{
|
2010-03-18 04:15:21 +08:00
|
|
|
struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai);
|
2010-06-04 13:11:24 +08:00
|
|
|
|
|
|
|
if (!dai->active)
|
2011-03-08 01:29:58 +08:00
|
|
|
return 0;
|
2010-06-04 13:11:24 +08:00
|
|
|
|
|
|
|
ep93xx_i2s_disable(info, SNDRV_PCM_STREAM_PLAYBACK);
|
|
|
|
ep93xx_i2s_disable(info, SNDRV_PCM_STREAM_CAPTURE);
|
2011-03-08 01:29:58 +08:00
|
|
|
|
|
|
|
return 0;
|
2010-06-04 13:11:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int ep93xx_i2s_resume(struct snd_soc_dai *dai)
|
|
|
|
{
|
2010-03-18 04:15:21 +08:00
|
|
|
struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai);
|
2010-06-04 13:11:24 +08:00
|
|
|
|
|
|
|
if (!dai->active)
|
2011-03-08 01:29:58 +08:00
|
|
|
return 0;
|
2010-06-04 13:11:24 +08:00
|
|
|
|
|
|
|
ep93xx_i2s_enable(info, SNDRV_PCM_STREAM_PLAYBACK);
|
|
|
|
ep93xx_i2s_enable(info, SNDRV_PCM_STREAM_CAPTURE);
|
2011-03-08 01:29:58 +08:00
|
|
|
|
|
|
|
return 0;
|
2010-06-04 13:11:24 +08:00
|
|
|
}
|
|
|
|
#else
|
|
|
|
#define ep93xx_i2s_suspend NULL
|
|
|
|
#define ep93xx_i2s_resume NULL
|
|
|
|
#endif
|
|
|
|
|
2011-11-23 18:40:40 +08:00
|
|
|
static const struct snd_soc_dai_ops ep93xx_i2s_dai_ops = {
|
2010-06-04 13:11:24 +08:00
|
|
|
.shutdown = ep93xx_i2s_shutdown,
|
|
|
|
.hw_params = ep93xx_i2s_hw_params,
|
|
|
|
.set_sysclk = ep93xx_i2s_set_sysclk,
|
|
|
|
.set_fmt = ep93xx_i2s_set_dai_fmt,
|
|
|
|
};
|
|
|
|
|
ASoC: EP93xx: fixed LRCLK rate and DMA oper. in I2S code
Changelog:
1. I2S module of EP93xx should be feed by 32bit DMA transfers. This is
hardware limitation and that's the way original Cirrus's driver worked.
This will fix distorted sound playback and make capture actually work in
present ep93xx drivers.
I've found, that author of code, on which modern ep93xx-i2s.c and
ep93xx-pcm.c are based, had faced this problem also in 2007:
http://blog.gmane.org/gmane.linux.ports.arm.cirrus/month=20070101/page=3
Now SoC code uses his developments, but not overcomes the hardware
issues. Some details from EP93xx users guide:
Both I2S transmitter and receiver have similar 16x32bit FIFO, where they
store 8 samples for both left and right channels. The FIFO is always
32bit wide and should be properly aligned if you use samples of other
width. Transmitter and receiver have configuration registers for
selection of I2S word length (16, 24, 32). They are I2STXWrdLen and
I2SRXWrdLen.
Yes, EP93xx DMA can do byte, word and quad-word transfers. The width for
transfers to and from peripherals is selected by particular module
configuration. Lucky AC97 module has such configuration: AC97RXCRx
registers, bit CM (Compact mode enable) switches between 16 and 32 bit
samples. AC97TXCRx registers have the same bits for transmitters.
ep93xx-ac97.c enables this compact mode and so has all the rights to use
S16_LE format.
No one has found such a configuration in I2S module until now in any
Cirrus manuals. I2S module always feeds it's 32bit wide FIFO with 32bit
samples consecutively for left and right channels. You cannot use 32-bit
DMA transfers to transfer two 16-bit samples.
So we can use two formats for AC97, but should remove all but S32_LE for
I2S. Always using 32 bit chunks is not a problem for I2S, the codec I
use uses less bits too (24), it's permitted by I2S standard.
In proposed patch formats list shortened to just S32_LE, this makes all
the DMA transactions right, while ALSA will do all sample format
translation for us.
2. Incorrect setting of LRCLK (2 times slower) in original ep93xx-i2s.c
masks the first problem.
DMA takes two 16 bit samples instead of one, overall sound speed seems
to be normal, but you get actually 4000 sampling rate instead of
requested 8000 and therefore some noise... This is also the reason why
the capture function not worked at all in this driver...
If we take a look into I2S specification, we will figure that LRCLK MUST
be equal to sample rate, if we are talking about stereo (in mono too,
but it's not our case at all).
In proposed patch SCLK and LRCLK rates are corrected, assuming we always
send 32 bits * 2 channels to codec.
Signed-off-by: Alexander Sverdlin <subaparts@yandex.ru>
Acked-by: Liam Girdwood <lrg@slimlogic.co.uk>
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
2011-01-16 20:48:05 +08:00
|
|
|
#define EP93XX_I2S_FORMATS (SNDRV_PCM_FMTBIT_S32_LE)
|
2010-06-04 13:11:24 +08:00
|
|
|
|
2010-03-18 04:15:21 +08:00
|
|
|
static struct snd_soc_dai_driver ep93xx_i2s_dai = {
|
2010-06-04 13:11:24 +08:00
|
|
|
.symmetric_rates= 1,
|
2013-04-21 01:29:04 +08:00
|
|
|
.probe = ep93xx_i2s_dai_probe,
|
2010-06-04 13:11:24 +08:00
|
|
|
.suspend = ep93xx_i2s_suspend,
|
|
|
|
.resume = ep93xx_i2s_resume,
|
|
|
|
.playback = {
|
|
|
|
.channels_min = 2,
|
|
|
|
.channels_max = 2,
|
2011-03-08 01:30:12 +08:00
|
|
|
.rates = SNDRV_PCM_RATE_8000_192000,
|
2010-06-04 13:11:24 +08:00
|
|
|
.formats = EP93XX_I2S_FORMATS,
|
|
|
|
},
|
|
|
|
.capture = {
|
|
|
|
.channels_min = 2,
|
|
|
|
.channels_max = 2,
|
2011-03-08 01:30:12 +08:00
|
|
|
.rates = SNDRV_PCM_RATE_8000_192000,
|
2010-06-04 13:11:24 +08:00
|
|
|
.formats = EP93XX_I2S_FORMATS,
|
|
|
|
},
|
|
|
|
.ops = &ep93xx_i2s_dai_ops,
|
|
|
|
};
|
|
|
|
|
2013-03-21 18:30:43 +08:00
|
|
|
static const struct snd_soc_component_driver ep93xx_i2s_component = {
|
|
|
|
.name = "ep93xx-i2s",
|
|
|
|
};
|
|
|
|
|
2010-06-04 13:11:24 +08:00
|
|
|
static int ep93xx_i2s_probe(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
struct ep93xx_i2s_info *info;
|
|
|
|
int err;
|
|
|
|
|
2012-03-27 07:00:17 +08:00
|
|
|
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
|
|
|
|
if (!info)
|
|
|
|
return -ENOMEM;
|
2010-06-04 13:11:24 +08:00
|
|
|
|
2019-07-27 23:07:08 +08:00
|
|
|
info->regs = devm_platform_ioremap_resource(pdev, 0);
|
2013-01-21 18:09:26 +08:00
|
|
|
if (IS_ERR(info->regs))
|
|
|
|
return PTR_ERR(info->regs);
|
2010-06-04 13:11:24 +08:00
|
|
|
|
2018-04-29 04:51:42 +08:00
|
|
|
if (IS_ENABLED(CONFIG_SND_EP93XX_SOC_I2S_WATCHDOG)) {
|
|
|
|
int irq = platform_get_irq(pdev, 0);
|
|
|
|
if (irq <= 0)
|
|
|
|
return irq < 0 ? irq : -ENODEV;
|
|
|
|
|
|
|
|
err = devm_request_irq(&pdev->dev, irq, ep93xx_i2s_interrupt, 0,
|
|
|
|
pdev->name, info);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2010-06-04 13:11:24 +08:00
|
|
|
info->mclk = clk_get(&pdev->dev, "mclk");
|
|
|
|
if (IS_ERR(info->mclk)) {
|
|
|
|
err = PTR_ERR(info->mclk);
|
2012-03-27 07:00:17 +08:00
|
|
|
goto fail;
|
2010-06-04 13:11:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
info->sclk = clk_get(&pdev->dev, "sclk");
|
|
|
|
if (IS_ERR(info->sclk)) {
|
|
|
|
err = PTR_ERR(info->sclk);
|
|
|
|
goto fail_put_mclk;
|
|
|
|
}
|
|
|
|
|
|
|
|
info->lrclk = clk_get(&pdev->dev, "lrclk");
|
|
|
|
if (IS_ERR(info->lrclk)) {
|
|
|
|
err = PTR_ERR(info->lrclk);
|
|
|
|
goto fail_put_sclk;
|
|
|
|
}
|
|
|
|
|
2012-03-27 07:00:17 +08:00
|
|
|
dev_set_drvdata(&pdev->dev, info);
|
|
|
|
|
2019-06-28 12:09:00 +08:00
|
|
|
err = devm_snd_soc_register_component(&pdev->dev, &ep93xx_i2s_component,
|
2013-03-21 18:30:43 +08:00
|
|
|
&ep93xx_i2s_dai, 1);
|
2010-06-04 13:11:24 +08:00
|
|
|
if (err)
|
|
|
|
goto fail_put_lrclk;
|
|
|
|
|
2013-12-11 03:34:45 +08:00
|
|
|
err = devm_ep93xx_pcm_platform_register(&pdev->dev);
|
|
|
|
if (err)
|
2019-06-28 12:09:00 +08:00
|
|
|
goto fail_put_lrclk;
|
2013-12-11 03:34:45 +08:00
|
|
|
|
2010-06-04 13:11:24 +08:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
fail_put_lrclk:
|
|
|
|
clk_put(info->lrclk);
|
|
|
|
fail_put_sclk:
|
|
|
|
clk_put(info->sclk);
|
|
|
|
fail_put_mclk:
|
|
|
|
clk_put(info->mclk);
|
|
|
|
fail:
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2012-12-07 22:26:23 +08:00
|
|
|
static int ep93xx_i2s_remove(struct platform_device *pdev)
|
2010-06-04 13:11:24 +08:00
|
|
|
{
|
2010-03-18 04:15:21 +08:00
|
|
|
struct ep93xx_i2s_info *info = dev_get_drvdata(&pdev->dev);
|
2010-06-04 13:11:24 +08:00
|
|
|
|
|
|
|
clk_put(info->lrclk);
|
|
|
|
clk_put(info->sclk);
|
|
|
|
clk_put(info->mclk);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct platform_driver ep93xx_i2s_driver = {
|
|
|
|
.probe = ep93xx_i2s_probe,
|
2012-12-07 22:26:23 +08:00
|
|
|
.remove = ep93xx_i2s_remove,
|
2010-06-04 13:11:24 +08:00
|
|
|
.driver = {
|
|
|
|
.name = "ep93xx-i2s",
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2011-11-24 12:07:55 +08:00
|
|
|
module_platform_driver(ep93xx_i2s_driver);
|
2010-06-04 13:11:24 +08:00
|
|
|
|
|
|
|
MODULE_ALIAS("platform:ep93xx-i2s");
|
2011-06-15 12:45:36 +08:00
|
|
|
MODULE_AUTHOR("Ryan Mallon");
|
2010-06-04 13:11:24 +08:00
|
|
|
MODULE_DESCRIPTION("EP93XX I2S driver");
|
|
|
|
MODULE_LICENSE("GPL");
|