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
+32
View File
@@ -0,0 +1,32 @@
# SPDX-License-Identifier: GPL-2.0-only
menu "Broadcom"
config SND_BCM2835_SOC_I2S
tristate "SoC Audio support for the Broadcom BCM2835 I2S module"
depends on ARCH_BCM2835 || COMPILE_TEST
select SND_SOC_GENERIC_DMAENGINE_PCM
select REGMAP_MMIO
help
Say Y or M if you want to add support for codecs attached to
the BCM2835 I2S interface. You will also need
to select the audio interfaces to support below.
config SND_SOC_CYGNUS
tristate "SoC platform audio for Broadcom Cygnus chips"
depends on ARCH_BCM_CYGNUS || COMPILE_TEST
help
Say Y if you want to add support for ASoC audio on Broadcom
Cygnus chips (bcm958300, bcm958305, bcm911360)
If you don't know what to do here, say N.
config SND_BCM63XX_I2S_WHISTLER
tristate "SoC Audio support for the Broadcom BCM63XX I2S module"
select REGMAP_MMIO
help
Say Y if you want to add support for ASoC audio on Broadcom
DSL/PON chips (bcm63158, bcm63178)
If you don't know what to do here, say N
endmenu
+15
View File
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-2.0-only
# BCM2835 Platform Support
snd-soc-bcm2835-i2s-y := bcm2835-i2s.o
obj-$(CONFIG_SND_BCM2835_SOC_I2S) += snd-soc-bcm2835-i2s.o
# CYGNUS Platform Support
snd-soc-cygnus-y := cygnus-pcm.o cygnus-ssp.o
obj-$(CONFIG_SND_SOC_CYGNUS) += snd-soc-cygnus.o
# BCM63XX Platform Support
snd-soc-63xx-y := bcm63xx-i2s-whistler.o bcm63xx-pcm-whistler.o
obj-$(CONFIG_SND_BCM63XX_I2S_WHISTLER) += snd-soc-63xx.o
+931
View File
@@ -0,0 +1,931 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* ALSA SoC I2S Audio Layer for Broadcom BCM2835 SoC
*
* Author: Florian Meier <florian.meier@koalo.de>
* Copyright 2013
*
* Based on
* Raspberry Pi PCM I2S ALSA Driver
* Copyright (c) by Phil Poole 2013
*
* ALSA SoC I2S (McBSP) Audio Layer for TI DAVINCI processor
* Vladimir Barinov, <vbarinov@embeddedalley.com>
* Copyright (C) 2007 MontaVista Software, Inc., <source@mvista.com>
*
* OMAP ALSA SoC DAI driver using McBSP port
* Copyright (C) 2008 Nokia Corporation
* Contact: Jarkko Nikula <jarkko.nikula@bitmer.com>
* Peter Ujfalusi <peter.ujfalusi@ti.com>
*
* Freescale SSI ALSA SoC Digital Audio Interface (DAI) driver
* Author: Timur Tabi <timur@freescale.com>
* Copyright 2007-2010 Freescale Semiconductor, Inc.
*/
#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/dmaengine_pcm.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
/* I2S registers */
#define BCM2835_I2S_CS_A_REG 0x00
#define BCM2835_I2S_FIFO_A_REG 0x04
#define BCM2835_I2S_MODE_A_REG 0x08
#define BCM2835_I2S_RXC_A_REG 0x0c
#define BCM2835_I2S_TXC_A_REG 0x10
#define BCM2835_I2S_DREQ_A_REG 0x14
#define BCM2835_I2S_INTEN_A_REG 0x18
#define BCM2835_I2S_INTSTC_A_REG 0x1c
#define BCM2835_I2S_GRAY_REG 0x20
/* I2S register settings */
#define BCM2835_I2S_STBY BIT(25)
#define BCM2835_I2S_SYNC BIT(24)
#define BCM2835_I2S_RXSEX BIT(23)
#define BCM2835_I2S_RXF BIT(22)
#define BCM2835_I2S_TXE BIT(21)
#define BCM2835_I2S_RXD BIT(20)
#define BCM2835_I2S_TXD BIT(19)
#define BCM2835_I2S_RXR BIT(18)
#define BCM2835_I2S_TXW BIT(17)
#define BCM2835_I2S_CS_RXERR BIT(16)
#define BCM2835_I2S_CS_TXERR BIT(15)
#define BCM2835_I2S_RXSYNC BIT(14)
#define BCM2835_I2S_TXSYNC BIT(13)
#define BCM2835_I2S_DMAEN BIT(9)
#define BCM2835_I2S_RXTHR(v) ((v) << 7)
#define BCM2835_I2S_TXTHR(v) ((v) << 5)
#define BCM2835_I2S_RXCLR BIT(4)
#define BCM2835_I2S_TXCLR BIT(3)
#define BCM2835_I2S_TXON BIT(2)
#define BCM2835_I2S_RXON BIT(1)
#define BCM2835_I2S_EN (1)
#define BCM2835_I2S_CLKDIS BIT(28)
#define BCM2835_I2S_PDMN BIT(27)
#define BCM2835_I2S_PDME BIT(26)
#define BCM2835_I2S_FRXP BIT(25)
#define BCM2835_I2S_FTXP BIT(24)
#define BCM2835_I2S_CLKM BIT(23)
#define BCM2835_I2S_CLKI BIT(22)
#define BCM2835_I2S_FSM BIT(21)
#define BCM2835_I2S_FSI BIT(20)
#define BCM2835_I2S_FLEN(v) ((v) << 10)
#define BCM2835_I2S_FSLEN(v) (v)
#define BCM2835_I2S_CHWEX BIT(15)
#define BCM2835_I2S_CHEN BIT(14)
#define BCM2835_I2S_CHPOS(v) ((v) << 4)
#define BCM2835_I2S_CHWID(v) (v)
#define BCM2835_I2S_CH1(v) ((v) << 16)
#define BCM2835_I2S_CH2(v) (v)
#define BCM2835_I2S_CH1_POS(v) BCM2835_I2S_CH1(BCM2835_I2S_CHPOS(v))
#define BCM2835_I2S_CH2_POS(v) BCM2835_I2S_CH2(BCM2835_I2S_CHPOS(v))
#define BCM2835_I2S_TX_PANIC(v) ((v) << 24)
#define BCM2835_I2S_RX_PANIC(v) ((v) << 16)
#define BCM2835_I2S_TX(v) ((v) << 8)
#define BCM2835_I2S_RX(v) (v)
#define BCM2835_I2S_INT_RXERR BIT(3)
#define BCM2835_I2S_INT_TXERR BIT(2)
#define BCM2835_I2S_INT_RXR BIT(1)
#define BCM2835_I2S_INT_TXW BIT(0)
/* Frame length register is 10 bit, maximum length 1024 */
#define BCM2835_I2S_MAX_FRAME_LENGTH 1024
/* General device struct */
struct bcm2835_i2s_dev {
struct device *dev;
struct snd_dmaengine_dai_dma_data dma_data[2];
unsigned int fmt;
unsigned int tdm_slots;
unsigned int rx_mask;
unsigned int tx_mask;
unsigned int slot_width;
unsigned int frame_length;
struct regmap *i2s_regmap;
struct clk *clk;
bool clk_prepared;
int clk_rate;
};
static void bcm2835_i2s_start_clock(struct bcm2835_i2s_dev *dev)
{
unsigned int provider = dev->fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK;
if (dev->clk_prepared)
return;
switch (provider) {
case SND_SOC_DAIFMT_BP_FP:
case SND_SOC_DAIFMT_BP_FC:
clk_prepare_enable(dev->clk);
dev->clk_prepared = true;
break;
default:
break;
}
}
static void bcm2835_i2s_stop_clock(struct bcm2835_i2s_dev *dev)
{
if (dev->clk_prepared)
clk_disable_unprepare(dev->clk);
dev->clk_prepared = false;
}
static void bcm2835_i2s_clear_fifos(struct bcm2835_i2s_dev *dev,
bool tx, bool rx)
{
int timeout = 1000;
uint32_t syncval;
uint32_t csreg;
uint32_t i2s_active_state;
bool clk_was_prepared;
uint32_t off;
uint32_t clr;
off = tx ? BCM2835_I2S_TXON : 0;
off |= rx ? BCM2835_I2S_RXON : 0;
clr = tx ? BCM2835_I2S_TXCLR : 0;
clr |= rx ? BCM2835_I2S_RXCLR : 0;
/* Backup the current state */
regmap_read(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, &csreg);
i2s_active_state = csreg & (BCM2835_I2S_RXON | BCM2835_I2S_TXON);
/* Start clock if not running */
clk_was_prepared = dev->clk_prepared;
if (!clk_was_prepared)
bcm2835_i2s_start_clock(dev);
/* Stop I2S module */
regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, off, 0);
/*
* Clear the FIFOs
* Requires at least 2 PCM clock cycles to take effect
*/
regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, clr, clr);
/* Wait for 2 PCM clock cycles */
/*
* Toggle the SYNC flag. After 2 PCM clock cycles it can be read back
* FIXME: This does not seem to work for slave mode!
*/
regmap_read(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, &syncval);
syncval &= BCM2835_I2S_SYNC;
regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
BCM2835_I2S_SYNC, ~syncval);
/* Wait for the SYNC flag changing it's state */
while (--timeout) {
regmap_read(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, &csreg);
if ((csreg & BCM2835_I2S_SYNC) != syncval)
break;
}
if (!timeout)
dev_err(dev->dev, "I2S SYNC error!\n");
/* Stop clock if it was not running before */
if (!clk_was_prepared)
bcm2835_i2s_stop_clock(dev);
/* Restore I2S state */
regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
BCM2835_I2S_RXON | BCM2835_I2S_TXON, i2s_active_state);
}
static int bcm2835_i2s_set_dai_fmt(struct snd_soc_dai *dai,
unsigned int fmt)
{
struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
dev->fmt = fmt;
return 0;
}
static int bcm2835_i2s_set_dai_bclk_ratio(struct snd_soc_dai *dai,
unsigned int ratio)
{
struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
if (!ratio) {
dev->tdm_slots = 0;
return 0;
}
if (ratio > BCM2835_I2S_MAX_FRAME_LENGTH)
return -EINVAL;
dev->tdm_slots = 2;
dev->rx_mask = 0x03;
dev->tx_mask = 0x03;
dev->slot_width = ratio / 2;
dev->frame_length = ratio;
return 0;
}
static int bcm2835_i2s_set_dai_tdm_slot(struct snd_soc_dai *dai,
unsigned int tx_mask, unsigned int rx_mask,
int slots, int width)
{
struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
if (slots) {
if (slots < 0 || width < 0)
return -EINVAL;
/* Limit masks to available slots */
rx_mask &= GENMASK(slots - 1, 0);
tx_mask &= GENMASK(slots - 1, 0);
/*
* The driver is limited to 2-channel setups.
* Check that exactly 2 bits are set in the masks.
*/
if (hweight_long((unsigned long) rx_mask) != 2
|| hweight_long((unsigned long) tx_mask) != 2)
return -EINVAL;
if (slots * width > BCM2835_I2S_MAX_FRAME_LENGTH)
return -EINVAL;
}
dev->tdm_slots = slots;
dev->rx_mask = rx_mask;
dev->tx_mask = tx_mask;
dev->slot_width = width;
dev->frame_length = slots * width;
return 0;
}
/*
* Convert logical slot number into physical slot number.
*
* If odd_offset is 0 sequential number is identical to logical number.
* This is used for DSP modes with slot numbering 0 1 2 3 ...
*
* Otherwise odd_offset defines the physical offset for odd numbered
* slots. This is used for I2S and left/right justified modes to
* translate from logical slot numbers 0 1 2 3 ... into physical slot
* numbers 0 2 ... 3 4 ...
*/
static int bcm2835_i2s_convert_slot(unsigned int slot, unsigned int odd_offset)
{
if (!odd_offset)
return slot;
if (slot & 1)
return (slot >> 1) + odd_offset;
return slot >> 1;
}
/*
* Calculate channel position from mask and slot width.
*
* Mask must contain exactly 2 set bits.
* Lowest set bit is channel 1 position, highest set bit channel 2.
* The constant offset is added to both channel positions.
*
* If odd_offset is > 0 slot positions are translated to
* I2S-style TDM slot numbering ( 0 2 ... 3 4 ...) with odd
* logical slot numbers starting at physical slot odd_offset.
*/
static void bcm2835_i2s_calc_channel_pos(
unsigned int *ch1_pos, unsigned int *ch2_pos,
unsigned int mask, unsigned int width,
unsigned int bit_offset, unsigned int odd_offset)
{
*ch1_pos = bcm2835_i2s_convert_slot((ffs(mask) - 1), odd_offset)
* width + bit_offset;
*ch2_pos = bcm2835_i2s_convert_slot((fls(mask) - 1), odd_offset)
* width + bit_offset;
}
static int bcm2835_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
unsigned int data_length, data_delay, framesync_length;
unsigned int slots, slot_width, odd_slot_offset;
int frame_length, bclk_rate;
unsigned int rx_mask, tx_mask;
unsigned int rx_ch1_pos, rx_ch2_pos, tx_ch1_pos, tx_ch2_pos;
unsigned int mode, format;
bool bit_clock_provider = false;
bool frame_sync_provider = false;
bool frame_start_falling_edge = false;
uint32_t csreg;
int ret = 0;
/*
* If a stream is already enabled,
* the registers are already set properly.
*/
regmap_read(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, &csreg);
if (csreg & (BCM2835_I2S_TXON | BCM2835_I2S_RXON))
return 0;
data_length = params_width(params);
data_delay = 0;
odd_slot_offset = 0;
mode = 0;
if (dev->tdm_slots) {
slots = dev->tdm_slots;
slot_width = dev->slot_width;
frame_length = dev->frame_length;
rx_mask = dev->rx_mask;
tx_mask = dev->tx_mask;
bclk_rate = dev->frame_length * params_rate(params);
} else {
slots = 2;
slot_width = params_width(params);
rx_mask = 0x03;
tx_mask = 0x03;
frame_length = snd_soc_params_to_frame_size(params);
if (frame_length < 0)
return frame_length;
bclk_rate = snd_soc_params_to_bclk(params);
if (bclk_rate < 0)
return bclk_rate;
}
/* Check if data fits into slots */
if (data_length > slot_width)
return -EINVAL;
/* Check if CPU is bit clock provider */
switch (dev->fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
case SND_SOC_DAIFMT_BP_FP:
case SND_SOC_DAIFMT_BP_FC:
bit_clock_provider = true;
break;
case SND_SOC_DAIFMT_BC_FP:
case SND_SOC_DAIFMT_BC_FC:
bit_clock_provider = false;
break;
default:
return -EINVAL;
}
/* Check if CPU is frame sync provider */
switch (dev->fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
case SND_SOC_DAIFMT_BP_FP:
case SND_SOC_DAIFMT_BC_FP:
frame_sync_provider = true;
break;
case SND_SOC_DAIFMT_BP_FC:
case SND_SOC_DAIFMT_BC_FC:
frame_sync_provider = false;
break;
default:
return -EINVAL;
}
/* Clock should only be set up here if CPU is clock master */
if (bit_clock_provider &&
(!dev->clk_prepared || dev->clk_rate != bclk_rate)) {
if (dev->clk_prepared)
bcm2835_i2s_stop_clock(dev);
if (dev->clk_rate != bclk_rate) {
ret = clk_set_rate(dev->clk, bclk_rate);
if (ret)
return ret;
dev->clk_rate = bclk_rate;
}
bcm2835_i2s_start_clock(dev);
}
/* Setup the frame format */
format = BCM2835_I2S_CHEN;
if (data_length >= 24)
format |= BCM2835_I2S_CHWEX;
format |= BCM2835_I2S_CHWID((data_length-8)&0xf);
/* CH2 format is the same as for CH1 */
format = BCM2835_I2S_CH1(format) | BCM2835_I2S_CH2(format);
switch (dev->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
/* I2S mode needs an even number of slots */
if (slots & 1)
return -EINVAL;
/*
* Use I2S-style logical slot numbering: even slots
* are in first half of frame, odd slots in second half.
*/
odd_slot_offset = slots >> 1;
/* MSB starts one cycle after frame start */
data_delay = 1;
/* Setup frame sync signal for 50% duty cycle */
framesync_length = frame_length / 2;
frame_start_falling_edge = true;
break;
case SND_SOC_DAIFMT_LEFT_J:
if (slots & 1)
return -EINVAL;
odd_slot_offset = slots >> 1;
data_delay = 0;
framesync_length = frame_length / 2;
frame_start_falling_edge = false;
break;
case SND_SOC_DAIFMT_RIGHT_J:
if (slots & 1)
return -EINVAL;
/* Odd frame lengths aren't supported */
if (frame_length & 1)
return -EINVAL;
odd_slot_offset = slots >> 1;
data_delay = slot_width - data_length;
framesync_length = frame_length / 2;
frame_start_falling_edge = false;
break;
case SND_SOC_DAIFMT_DSP_A:
data_delay = 1;
framesync_length = 1;
frame_start_falling_edge = false;
break;
case SND_SOC_DAIFMT_DSP_B:
data_delay = 0;
framesync_length = 1;
frame_start_falling_edge = false;
break;
default:
return -EINVAL;
}
bcm2835_i2s_calc_channel_pos(&rx_ch1_pos, &rx_ch2_pos,
rx_mask, slot_width, data_delay, odd_slot_offset);
bcm2835_i2s_calc_channel_pos(&tx_ch1_pos, &tx_ch2_pos,
tx_mask, slot_width, data_delay, odd_slot_offset);
/*
* Transmitting data immediately after frame start, eg
* in left-justified or DSP mode A, only works stable
* if bcm2835 is the frame clock provider.
*/
if ((!rx_ch1_pos || !tx_ch1_pos) && !frame_sync_provider)
dev_warn(dev->dev,
"Unstable consumer config detected, L/R may be swapped");
/*
* Set format for both streams.
* We cannot set another frame length
* (and therefore word length) anyway,
* so the format will be the same.
*/
regmap_write(dev->i2s_regmap, BCM2835_I2S_RXC_A_REG,
format
| BCM2835_I2S_CH1_POS(rx_ch1_pos)
| BCM2835_I2S_CH2_POS(rx_ch2_pos));
regmap_write(dev->i2s_regmap, BCM2835_I2S_TXC_A_REG,
format
| BCM2835_I2S_CH1_POS(tx_ch1_pos)
| BCM2835_I2S_CH2_POS(tx_ch2_pos));
/* Setup the I2S mode */
if (data_length <= 16) {
/*
* Use frame packed mode (2 channels per 32 bit word)
* We cannot set another frame length in the second stream
* (and therefore word length) anyway,
* so the format will be the same.
*/
mode |= BCM2835_I2S_FTXP | BCM2835_I2S_FRXP;
}
mode |= BCM2835_I2S_FLEN(frame_length - 1);
mode |= BCM2835_I2S_FSLEN(framesync_length);
/* CLKM selects bcm2835 clock slave mode */
if (!bit_clock_provider)
mode |= BCM2835_I2S_CLKM;
/* FSM selects bcm2835 frame sync slave mode */
if (!frame_sync_provider)
mode |= BCM2835_I2S_FSM;
/* CLKI selects normal clocking mode, sampling on rising edge */
switch (dev->fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
case SND_SOC_DAIFMT_NB_IF:
mode |= BCM2835_I2S_CLKI;
break;
case SND_SOC_DAIFMT_IB_NF:
case SND_SOC_DAIFMT_IB_IF:
break;
default:
return -EINVAL;
}
/* FSI selects frame start on falling edge */
switch (dev->fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
case SND_SOC_DAIFMT_IB_NF:
if (frame_start_falling_edge)
mode |= BCM2835_I2S_FSI;
break;
case SND_SOC_DAIFMT_NB_IF:
case SND_SOC_DAIFMT_IB_IF:
if (!frame_start_falling_edge)
mode |= BCM2835_I2S_FSI;
break;
default:
return -EINVAL;
}
regmap_write(dev->i2s_regmap, BCM2835_I2S_MODE_A_REG, mode);
/* Setup the DMA parameters */
regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
BCM2835_I2S_RXTHR(1)
| BCM2835_I2S_TXTHR(1)
| BCM2835_I2S_DMAEN, 0xffffffff);
regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_DREQ_A_REG,
BCM2835_I2S_TX_PANIC(0x10)
| BCM2835_I2S_RX_PANIC(0x30)
| BCM2835_I2S_TX(0x30)
| BCM2835_I2S_RX(0x20), 0xffffffff);
/* Clear FIFOs */
bcm2835_i2s_clear_fifos(dev, true, true);
dev_dbg(dev->dev,
"slots: %d width: %d rx mask: 0x%02x tx_mask: 0x%02x\n",
slots, slot_width, rx_mask, tx_mask);
dev_dbg(dev->dev, "frame len: %d sync len: %d data len: %d\n",
frame_length, framesync_length, data_length);
dev_dbg(dev->dev, "rx pos: %d,%d tx pos: %d,%d\n",
rx_ch1_pos, rx_ch2_pos, tx_ch1_pos, tx_ch2_pos);
dev_dbg(dev->dev, "sampling rate: %d bclk rate: %d\n",
params_rate(params), bclk_rate);
dev_dbg(dev->dev, "CLKM: %d CLKI: %d FSM: %d FSI: %d frame start: %s edge\n",
!!(mode & BCM2835_I2S_CLKM),
!!(mode & BCM2835_I2S_CLKI),
!!(mode & BCM2835_I2S_FSM),
!!(mode & BCM2835_I2S_FSI),
(mode & BCM2835_I2S_FSI) ? "falling" : "rising");
return ret;
}
static int bcm2835_i2s_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
uint32_t cs_reg;
/*
* Clear both FIFOs if the one that should be started
* is not empty at the moment. This should only happen
* after overrun. Otherwise, hw_params would have cleared
* the FIFO.
*/
regmap_read(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, &cs_reg);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK
&& !(cs_reg & BCM2835_I2S_TXE))
bcm2835_i2s_clear_fifos(dev, true, false);
else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE
&& (cs_reg & BCM2835_I2S_RXD))
bcm2835_i2s_clear_fifos(dev, false, true);
return 0;
}
static void bcm2835_i2s_stop(struct bcm2835_i2s_dev *dev,
struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
uint32_t mask;
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
mask = BCM2835_I2S_RXON;
else
mask = BCM2835_I2S_TXON;
regmap_update_bits(dev->i2s_regmap,
BCM2835_I2S_CS_A_REG, mask, 0);
/* Stop also the clock when not SND_SOC_DAIFMT_CONT */
if (!snd_soc_dai_active(dai) && !(dev->fmt & SND_SOC_DAIFMT_CONT))
bcm2835_i2s_stop_clock(dev);
}
static int bcm2835_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
uint32_t mask;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
bcm2835_i2s_start_clock(dev);
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
mask = BCM2835_I2S_RXON;
else
mask = BCM2835_I2S_TXON;
regmap_update_bits(dev->i2s_regmap,
BCM2835_I2S_CS_A_REG, mask, mask);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
bcm2835_i2s_stop(dev, substream, dai);
break;
default:
return -EINVAL;
}
return 0;
}
static int bcm2835_i2s_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
if (snd_soc_dai_active(dai))
return 0;
/* Should this still be running stop it */
bcm2835_i2s_stop_clock(dev);
/* Enable PCM block */
regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
BCM2835_I2S_EN, BCM2835_I2S_EN);
/*
* Disable STBY.
* Requires at least 4 PCM clock cycles to take effect.
*/
regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
BCM2835_I2S_STBY, BCM2835_I2S_STBY);
return 0;
}
static void bcm2835_i2s_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
bcm2835_i2s_stop(dev, substream, dai);
/* If both streams are stopped, disable module and clock */
if (snd_soc_dai_active(dai))
return;
/* Disable the module */
regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
BCM2835_I2S_EN, 0);
/*
* Stopping clock is necessary, because stop does
* not stop the clock when SND_SOC_DAIFMT_CONT
*/
bcm2835_i2s_stop_clock(dev);
}
static int bcm2835_i2s_dai_probe(struct snd_soc_dai *dai)
{
struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
snd_soc_dai_init_dma_data(dai,
&dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK],
&dev->dma_data[SNDRV_PCM_STREAM_CAPTURE]);
return 0;
}
static const struct snd_soc_dai_ops bcm2835_i2s_dai_ops = {
.probe = bcm2835_i2s_dai_probe,
.startup = bcm2835_i2s_startup,
.shutdown = bcm2835_i2s_shutdown,
.prepare = bcm2835_i2s_prepare,
.trigger = bcm2835_i2s_trigger,
.hw_params = bcm2835_i2s_hw_params,
.set_fmt = bcm2835_i2s_set_dai_fmt,
.set_bclk_ratio = bcm2835_i2s_set_dai_bclk_ratio,
.set_tdm_slot = bcm2835_i2s_set_dai_tdm_slot,
};
static struct snd_soc_dai_driver bcm2835_i2s_dai = {
.name = "bcm2835-i2s",
.playback = {
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
.rate_min = 8000,
.rate_max = 384000,
.formats = SNDRV_PCM_FMTBIT_S16_LE
| SNDRV_PCM_FMTBIT_S24_LE
| SNDRV_PCM_FMTBIT_S32_LE
},
.capture = {
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
.rate_min = 8000,
.rate_max = 384000,
.formats = SNDRV_PCM_FMTBIT_S16_LE
| SNDRV_PCM_FMTBIT_S24_LE
| SNDRV_PCM_FMTBIT_S32_LE
},
.ops = &bcm2835_i2s_dai_ops,
.symmetric_rate = 1,
.symmetric_sample_bits = 1,
};
static bool bcm2835_i2s_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case BCM2835_I2S_CS_A_REG:
case BCM2835_I2S_FIFO_A_REG:
case BCM2835_I2S_INTSTC_A_REG:
case BCM2835_I2S_GRAY_REG:
return true;
default:
return false;
}
}
static bool bcm2835_i2s_precious_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case BCM2835_I2S_FIFO_A_REG:
return true;
default:
return false;
}
}
static const struct regmap_config bcm2835_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = BCM2835_I2S_GRAY_REG,
.precious_reg = bcm2835_i2s_precious_reg,
.volatile_reg = bcm2835_i2s_volatile_reg,
.cache_type = REGCACHE_MAPLE,
};
static const struct snd_soc_component_driver bcm2835_i2s_component = {
.name = "bcm2835-i2s-comp",
.legacy_dai_naming = 1,
};
static int bcm2835_i2s_probe(struct platform_device *pdev)
{
struct bcm2835_i2s_dev *dev;
int ret;
void __iomem *base;
const __be32 *addr;
dma_addr_t dma_base;
dev = devm_kzalloc(&pdev->dev, sizeof(*dev),
GFP_KERNEL);
if (!dev)
return -ENOMEM;
/* get the clock */
dev->clk_prepared = false;
dev->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(dev->clk))
return dev_err_probe(&pdev->dev, PTR_ERR(dev->clk),
"could not get clk\n");
/* Request ioarea */
base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(base))
return PTR_ERR(base);
dev->i2s_regmap = devm_regmap_init_mmio(&pdev->dev, base,
&bcm2835_regmap_config);
if (IS_ERR(dev->i2s_regmap))
return PTR_ERR(dev->i2s_regmap);
/* Set the DMA address - we have to parse DT ourselves */
addr = of_get_address(pdev->dev.of_node, 0, NULL, NULL);
if (!addr) {
dev_err(&pdev->dev, "could not get DMA-register address\n");
return -EINVAL;
}
dma_base = be32_to_cpup(addr);
dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].addr =
dma_base + BCM2835_I2S_FIFO_A_REG;
dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].addr =
dma_base + BCM2835_I2S_FIFO_A_REG;
/* Set the bus width */
dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].addr_width =
DMA_SLAVE_BUSWIDTH_4_BYTES;
dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].addr_width =
DMA_SLAVE_BUSWIDTH_4_BYTES;
/* Set burst */
dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].maxburst = 2;
dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].maxburst = 2;
/*
* Set the PACK flag to enable S16_LE support (2 S16_LE values
* packed into 32-bit transfers).
*/
dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].flags =
SND_DMAENGINE_PCM_DAI_FLAG_PACK;
dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].flags =
SND_DMAENGINE_PCM_DAI_FLAG_PACK;
/* Store the pdev */
dev->dev = &pdev->dev;
dev_set_drvdata(&pdev->dev, dev);
ret = devm_snd_soc_register_component(&pdev->dev,
&bcm2835_i2s_component, &bcm2835_i2s_dai, 1);
if (ret) {
dev_err(&pdev->dev, "Could not register DAI: %d\n", ret);
return ret;
}
ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
if (ret) {
dev_err(&pdev->dev, "Could not register PCM: %d\n", ret);
return ret;
}
return 0;
}
static const struct of_device_id bcm2835_i2s_of_match[] = {
{ .compatible = "brcm,bcm2835-i2s", },
{},
};
MODULE_DEVICE_TABLE(of, bcm2835_i2s_of_match);
static struct platform_driver bcm2835_i2s_driver = {
.probe = bcm2835_i2s_probe,
.driver = {
.name = "bcm2835-i2s",
.of_match_table = bcm2835_i2s_of_match,
},
};
module_platform_driver(bcm2835_i2s_driver);
MODULE_ALIAS("platform:bcm2835-i2s");
MODULE_DESCRIPTION("BCM2835 I2S interface");
MODULE_AUTHOR("Florian Meier <florian.meier@koalo.de>");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,303 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// linux/sound/bcm/bcm63xx-i2s-whistler.c
// BCM63xx whistler i2s driver
// Copyright (c) 2020 Broadcom Corporation
// Author: Kevin-Ke Li <kevin-ke.li@broadcom.com>
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "bcm63xx-i2s.h"
#define DRV_NAME "brcm-i2s"
static bool brcm_i2s_wr_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case I2S_TX_CFG ... I2S_TX_DESC_IFF_LEN:
case I2S_TX_CFG_2 ... I2S_RX_DESC_IFF_LEN:
case I2S_RX_CFG_2 ... I2S_REG_MAX:
return true;
default:
return false;
}
}
static bool brcm_i2s_rd_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case I2S_TX_CFG ... I2S_REG_MAX:
return true;
default:
return false;
}
}
static bool brcm_i2s_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case I2S_TX_CFG:
case I2S_TX_IRQ_CTL:
case I2S_TX_DESC_IFF_ADDR:
case I2S_TX_DESC_IFF_LEN:
case I2S_TX_DESC_OFF_ADDR:
case I2S_TX_DESC_OFF_LEN:
case I2S_TX_CFG_2:
case I2S_RX_CFG:
case I2S_RX_IRQ_CTL:
case I2S_RX_DESC_OFF_ADDR:
case I2S_RX_DESC_OFF_LEN:
case I2S_RX_DESC_IFF_LEN:
case I2S_RX_DESC_IFF_ADDR:
case I2S_RX_CFG_2:
return true;
default:
return false;
}
}
static const struct regmap_config brcm_i2s_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = I2S_REG_MAX,
.writeable_reg = brcm_i2s_wr_reg,
.readable_reg = brcm_i2s_rd_reg,
.volatile_reg = brcm_i2s_volatile_reg,
.cache_type = REGCACHE_FLAT,
};
static int bcm63xx_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
int ret = 0;
struct bcm_i2s_priv *i2s_priv = snd_soc_dai_get_drvdata(dai);
ret = clk_set_rate(i2s_priv->i2s_clk, params_rate(params));
if (ret < 0)
dev_err(i2s_priv->dev,
"Can't set sample rate, err: %d\n", ret);
return ret;
}
static int bcm63xx_i2s_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
unsigned int slavemode;
struct bcm_i2s_priv *i2s_priv = snd_soc_dai_get_drvdata(dai);
struct regmap *regmap_i2s = i2s_priv->regmap_i2s;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
regmap_update_bits(regmap_i2s, I2S_TX_CFG,
I2S_TX_OUT_R | I2S_TX_DATA_ALIGNMENT |
I2S_TX_DATA_ENABLE | I2S_TX_CLOCK_ENABLE,
I2S_TX_OUT_R | I2S_TX_DATA_ALIGNMENT |
I2S_TX_DATA_ENABLE | I2S_TX_CLOCK_ENABLE);
regmap_write(regmap_i2s, I2S_TX_IRQ_CTL, 0);
regmap_write(regmap_i2s, I2S_TX_IRQ_IFF_THLD, 0);
regmap_write(regmap_i2s, I2S_TX_IRQ_OFF_THLD, 1);
/* TX and RX block each have an independent bit to indicate
* if it is generating the clock for the I2S bus. The bus
* clocks need to be generated from either the TX or RX block,
* but not both
*/
regmap_read(regmap_i2s, I2S_RX_CFG_2, &slavemode);
if (slavemode & I2S_RX_SLAVE_MODE_MASK)
regmap_update_bits(regmap_i2s, I2S_TX_CFG_2,
I2S_TX_SLAVE_MODE_MASK,
I2S_TX_MASTER_MODE);
else
regmap_update_bits(regmap_i2s, I2S_TX_CFG_2,
I2S_TX_SLAVE_MODE_MASK,
I2S_TX_SLAVE_MODE);
} else {
regmap_update_bits(regmap_i2s, I2S_RX_CFG,
I2S_RX_IN_R | I2S_RX_DATA_ALIGNMENT |
I2S_RX_CLOCK_ENABLE,
I2S_RX_IN_R | I2S_RX_DATA_ALIGNMENT |
I2S_RX_CLOCK_ENABLE);
regmap_write(regmap_i2s, I2S_RX_IRQ_CTL, 0);
regmap_write(regmap_i2s, I2S_RX_IRQ_IFF_THLD, 0);
regmap_write(regmap_i2s, I2S_RX_IRQ_OFF_THLD, 1);
regmap_read(regmap_i2s, I2S_TX_CFG_2, &slavemode);
if (slavemode & I2S_TX_SLAVE_MODE_MASK)
regmap_update_bits(regmap_i2s, I2S_RX_CFG_2,
I2S_RX_SLAVE_MODE_MASK, 0);
else
regmap_update_bits(regmap_i2s, I2S_RX_CFG_2,
I2S_RX_SLAVE_MODE_MASK,
I2S_RX_SLAVE_MODE);
}
return 0;
}
static void bcm63xx_i2s_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
unsigned int enabled, slavemode;
struct bcm_i2s_priv *i2s_priv = snd_soc_dai_get_drvdata(dai);
struct regmap *regmap_i2s = i2s_priv->regmap_i2s;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
regmap_update_bits(regmap_i2s, I2S_TX_CFG,
I2S_TX_OUT_R | I2S_TX_DATA_ALIGNMENT |
I2S_TX_DATA_ENABLE | I2S_TX_CLOCK_ENABLE, 0);
regmap_write(regmap_i2s, I2S_TX_IRQ_CTL, 1);
regmap_write(regmap_i2s, I2S_TX_IRQ_IFF_THLD, 4);
regmap_write(regmap_i2s, I2S_TX_IRQ_OFF_THLD, 4);
regmap_read(regmap_i2s, I2S_TX_CFG_2, &slavemode);
slavemode = slavemode & I2S_TX_SLAVE_MODE_MASK;
if (!slavemode) {
regmap_read(regmap_i2s, I2S_RX_CFG, &enabled);
enabled = enabled & I2S_RX_ENABLE_MASK;
if (enabled)
regmap_update_bits(regmap_i2s, I2S_RX_CFG_2,
I2S_RX_SLAVE_MODE_MASK,
I2S_RX_MASTER_MODE);
}
regmap_update_bits(regmap_i2s, I2S_TX_CFG_2,
I2S_TX_SLAVE_MODE_MASK,
I2S_TX_SLAVE_MODE);
} else {
regmap_update_bits(regmap_i2s, I2S_RX_CFG,
I2S_RX_IN_R | I2S_RX_DATA_ALIGNMENT |
I2S_RX_CLOCK_ENABLE, 0);
regmap_write(regmap_i2s, I2S_RX_IRQ_CTL, 1);
regmap_write(regmap_i2s, I2S_RX_IRQ_IFF_THLD, 4);
regmap_write(regmap_i2s, I2S_RX_IRQ_OFF_THLD, 4);
regmap_read(regmap_i2s, I2S_RX_CFG_2, &slavemode);
slavemode = slavemode & I2S_RX_SLAVE_MODE_MASK;
if (!slavemode) {
regmap_read(regmap_i2s, I2S_TX_CFG, &enabled);
enabled = enabled & I2S_TX_ENABLE_MASK;
if (enabled)
regmap_update_bits(regmap_i2s, I2S_TX_CFG_2,
I2S_TX_SLAVE_MODE_MASK,
I2S_TX_MASTER_MODE);
}
regmap_update_bits(regmap_i2s, I2S_RX_CFG_2,
I2S_RX_SLAVE_MODE_MASK, I2S_RX_SLAVE_MODE);
}
}
static const struct snd_soc_dai_ops bcm63xx_i2s_dai_ops = {
.startup = bcm63xx_i2s_startup,
.shutdown = bcm63xx_i2s_shutdown,
.hw_params = bcm63xx_i2s_hw_params,
};
static struct snd_soc_dai_driver bcm63xx_i2s_dai = {
.name = DRV_NAME,
.playback = {
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SNDRV_PCM_FMTBIT_S32_LE,
},
.capture = {
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SNDRV_PCM_FMTBIT_S32_LE,
},
.ops = &bcm63xx_i2s_dai_ops,
.symmetric_rate = 1,
.symmetric_channels = 1,
};
static const struct snd_soc_component_driver bcm63xx_i2s_component = {
.name = "bcm63xx",
.legacy_dai_naming = 1,
};
static int bcm63xx_i2s_dev_probe(struct platform_device *pdev)
{
int ret = 0;
void __iomem *regs;
struct bcm_i2s_priv *i2s_priv;
struct regmap *regmap_i2s;
struct clk *i2s_clk;
i2s_priv = devm_kzalloc(&pdev->dev, sizeof(*i2s_priv), GFP_KERNEL);
if (!i2s_priv)
return -ENOMEM;
i2s_clk = devm_clk_get(&pdev->dev, "i2sclk");
if (IS_ERR(i2s_clk)) {
dev_err(&pdev->dev, "%s: cannot get a brcm clock: %ld\n",
__func__, PTR_ERR(i2s_clk));
return PTR_ERR(i2s_clk);
}
regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(regs)) {
ret = PTR_ERR(regs);
return ret;
}
regmap_i2s = devm_regmap_init_mmio(&pdev->dev,
regs, &brcm_i2s_regmap_config);
if (IS_ERR(regmap_i2s))
return PTR_ERR(regmap_i2s);
regmap_update_bits(regmap_i2s, I2S_MISC_CFG,
I2S_PAD_LVL_LOOP_DIS_MASK,
I2S_PAD_LVL_LOOP_DIS_ENABLE);
ret = devm_snd_soc_register_component(&pdev->dev,
&bcm63xx_i2s_component,
&bcm63xx_i2s_dai, 1);
if (ret) {
dev_err(&pdev->dev, "failed to register the dai\n");
return ret;
}
i2s_priv->dev = &pdev->dev;
i2s_priv->i2s_clk = i2s_clk;
i2s_priv->regmap_i2s = regmap_i2s;
dev_set_drvdata(&pdev->dev, i2s_priv);
ret = bcm63xx_soc_platform_probe(pdev, i2s_priv);
if (ret)
dev_err(&pdev->dev, "failed to register the pcm\n");
return ret;
}
static void bcm63xx_i2s_dev_remove(struct platform_device *pdev)
{
bcm63xx_soc_platform_remove(pdev);
}
#ifdef CONFIG_OF
static const struct of_device_id snd_soc_bcm_audio_match[] = {
{.compatible = "brcm,bcm63xx-i2s"},
{ }
};
#endif
static struct platform_driver bcm63xx_i2s_driver = {
.driver = {
.name = DRV_NAME,
.of_match_table = of_match_ptr(snd_soc_bcm_audio_match),
},
.probe = bcm63xx_i2s_dev_probe,
.remove = bcm63xx_i2s_dev_remove,
};
module_platform_driver(bcm63xx_i2s_driver);
MODULE_AUTHOR("Kevin,Li <kevin-ke.li@broadcom.com>");
MODULE_DESCRIPTION("Broadcom DSL XPON ASOC I2S Interface");
MODULE_LICENSE("GPL v2");
+89
View File
@@ -0,0 +1,89 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// linux/sound/soc/bcm/bcm63xx-i2s.h
// Copyright (c) 2020 Broadcom Corporation
// Author: Kevin-Ke Li <kevin-ke.li@broadcom.com>
#ifndef __BCM63XX_I2S_H
#define __BCM63XX_I2S_H
#define I2S_DESC_FIFO_DEPTH 8
#define I2S_MISC_CFG (0x003C)
#define I2S_PAD_LVL_LOOP_DIS_MASK (1 << 2)
#define I2S_PAD_LVL_LOOP_DIS_ENABLE I2S_PAD_LVL_LOOP_DIS_MASK
#define I2S_TX_ENABLE_MASK (1 << 31)
#define I2S_TX_ENABLE I2S_TX_ENABLE_MASK
#define I2S_TX_OUT_R (1 << 19)
#define I2S_TX_DATA_ALIGNMENT (1 << 2)
#define I2S_TX_DATA_ENABLE (1 << 1)
#define I2S_TX_CLOCK_ENABLE (1 << 0)
#define I2S_TX_DESC_OFF_LEVEL_SHIFT 12
#define I2S_TX_DESC_OFF_LEVEL_MASK (0x0F << I2S_TX_DESC_OFF_LEVEL_SHIFT)
#define I2S_TX_DESC_IFF_LEVEL_SHIFT 8
#define I2S_TX_DESC_IFF_LEVEL_MASK (0x0F << I2S_TX_DESC_IFF_LEVEL_SHIFT)
#define I2S_TX_DESC_OFF_INTR_EN_MSK (1 << 1)
#define I2S_TX_DESC_OFF_INTR_EN I2S_TX_DESC_OFF_INTR_EN_MSK
#define I2S_TX_CFG (0x0000)
#define I2S_TX_IRQ_CTL (0x0004)
#define I2S_TX_IRQ_EN (0x0008)
#define I2S_TX_IRQ_IFF_THLD (0x000c)
#define I2S_TX_IRQ_OFF_THLD (0x0010)
#define I2S_TX_DESC_IFF_ADDR (0x0014)
#define I2S_TX_DESC_IFF_LEN (0x0018)
#define I2S_TX_DESC_OFF_ADDR (0x001C)
#define I2S_TX_DESC_OFF_LEN (0x0020)
#define I2S_TX_CFG_2 (0x0024)
#define I2S_TX_SLAVE_MODE_SHIFT 13
#define I2S_TX_SLAVE_MODE_MASK (1 << I2S_TX_SLAVE_MODE_SHIFT)
#define I2S_TX_SLAVE_MODE I2S_TX_SLAVE_MODE_MASK
#define I2S_TX_MASTER_MODE 0
#define I2S_TX_INTR_MASK 0x0F
#define I2S_RX_ENABLE_MASK (1 << 31)
#define I2S_RX_ENABLE I2S_RX_ENABLE_MASK
#define I2S_RX_IN_R (1 << 19)
#define I2S_RX_DATA_ALIGNMENT (1 << 2)
#define I2S_RX_CLOCK_ENABLE (1 << 0)
#define I2S_RX_DESC_OFF_LEVEL_SHIFT 12
#define I2S_RX_DESC_OFF_LEVEL_MASK (0x0F << I2S_RX_DESC_OFF_LEVEL_SHIFT)
#define I2S_RX_DESC_IFF_LEVEL_SHIFT 8
#define I2S_RX_DESC_IFF_LEVEL_MASK (0x0F << I2S_RX_DESC_IFF_LEVEL_SHIFT)
#define I2S_RX_DESC_OFF_INTR_EN_MSK (1 << 1)
#define I2S_RX_DESC_OFF_INTR_EN I2S_RX_DESC_OFF_INTR_EN_MSK
#define I2S_RX_CFG (0x0040) /* 20c0 */
#define I2S_RX_IRQ_CTL (0x0044)
#define I2S_RX_IRQ_EN (0x0048)
#define I2S_RX_IRQ_IFF_THLD (0x004C)
#define I2S_RX_IRQ_OFF_THLD (0x0050)
#define I2S_RX_DESC_IFF_ADDR (0x0054)
#define I2S_RX_DESC_IFF_LEN (0x0058)
#define I2S_RX_DESC_OFF_ADDR (0x005C)
#define I2S_RX_DESC_OFF_LEN (0x0060)
#define I2S_RX_CFG_2 (0x0064)
#define I2S_RX_SLAVE_MODE_SHIFT 13
#define I2S_RX_SLAVE_MODE_MASK (1 << I2S_RX_SLAVE_MODE_SHIFT)
#define I2S_RX_SLAVE_MODE I2S_RX_SLAVE_MODE_MASK
#define I2S_RX_MASTER_MODE 0
#define I2S_RX_INTR_MASK 0x0F
#define I2S_REG_MAX 0x007C
struct bcm_i2s_priv {
struct device *dev;
struct regmap *regmap_i2s;
struct clk *i2s_clk;
struct snd_pcm_substream *play_substream;
struct snd_pcm_substream *capture_substream;
struct i2s_dma_desc *play_dma_desc;
struct i2s_dma_desc *capture_dma_desc;
};
extern int bcm63xx_soc_platform_probe(struct platform_device *pdev,
struct bcm_i2s_priv *i2s_priv);
extern int bcm63xx_soc_platform_remove(struct platform_device *pdev);
#endif
@@ -0,0 +1,420 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// linux/sound/bcm/bcm63xx-pcm-whistler.c
// BCM63xx whistler pcm interface
// Copyright (c) 2020 Broadcom Corporation
// Author: Kevin-Ke Li <kevin-ke.li@broadcom.com>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/module.h>
#include <sound/pcm_params.h>
#include <linux/regmap.h>
#include <linux/of_device.h>
#include <sound/soc.h>
#include "bcm63xx-i2s.h"
struct i2s_dma_desc {
unsigned char *dma_area;
dma_addr_t dma_addr;
unsigned int dma_len;
};
struct bcm63xx_runtime_data {
int dma_len;
dma_addr_t dma_addr;
dma_addr_t dma_addr_next;
};
static const struct snd_pcm_hardware bcm63xx_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_S32_LE, /* support S32 only */
.period_bytes_max = 8192 - 32,
.periods_min = 1,
.periods_max = PAGE_SIZE/sizeof(struct i2s_dma_desc),
.buffer_bytes_max = 128 * 1024,
.fifo_size = 32,
};
static int bcm63xx_pcm_hw_params(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct i2s_dma_desc *dma_desc;
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
dma_desc = kzalloc_obj(*dma_desc, GFP_NOWAIT);
if (!dma_desc)
return -ENOMEM;
snd_soc_dai_set_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream, dma_desc);
return 0;
}
static int bcm63xx_pcm_hw_free(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct i2s_dma_desc *dma_desc;
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
dma_desc = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream);
kfree(dma_desc);
return 0;
}
static int bcm63xx_pcm_trigger(struct snd_soc_component *component,
struct snd_pcm_substream *substream, int cmd)
{
int ret = 0;
struct snd_soc_pcm_runtime *rtd;
struct bcm_i2s_priv *i2s_priv;
struct regmap *regmap_i2s;
rtd = snd_soc_substream_to_rtd(substream);
i2s_priv = dev_get_drvdata(snd_soc_rtd_to_cpu(rtd, 0)->dev);
regmap_i2s = i2s_priv->regmap_i2s;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
regmap_update_bits(regmap_i2s,
I2S_TX_IRQ_EN,
I2S_TX_DESC_OFF_INTR_EN,
I2S_TX_DESC_OFF_INTR_EN);
regmap_update_bits(regmap_i2s,
I2S_TX_CFG,
I2S_TX_ENABLE_MASK,
I2S_TX_ENABLE);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
regmap_write(regmap_i2s,
I2S_TX_IRQ_EN,
0);
regmap_update_bits(regmap_i2s,
I2S_TX_CFG,
I2S_TX_ENABLE_MASK,
0);
break;
default:
ret = -EINVAL;
}
} else {
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
regmap_update_bits(regmap_i2s,
I2S_RX_IRQ_EN,
I2S_RX_DESC_OFF_INTR_EN_MSK,
I2S_RX_DESC_OFF_INTR_EN);
regmap_update_bits(regmap_i2s,
I2S_RX_CFG,
I2S_RX_ENABLE_MASK,
I2S_RX_ENABLE);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
regmap_update_bits(regmap_i2s,
I2S_RX_IRQ_EN,
I2S_RX_DESC_OFF_INTR_EN_MSK,
0);
regmap_update_bits(regmap_i2s,
I2S_RX_CFG,
I2S_RX_ENABLE_MASK,
0);
break;
default:
ret = -EINVAL;
}
}
return ret;
}
static int bcm63xx_pcm_prepare(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct i2s_dma_desc *dma_desc;
struct regmap *regmap_i2s;
struct bcm_i2s_priv *i2s_priv;
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
uint32_t regaddr_desclen, regaddr_descaddr;
dma_desc = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream);
dma_desc->dma_len = snd_pcm_lib_period_bytes(substream);
dma_desc->dma_addr = runtime->dma_addr;
dma_desc->dma_area = runtime->dma_area;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
regaddr_desclen = I2S_TX_DESC_IFF_LEN;
regaddr_descaddr = I2S_TX_DESC_IFF_ADDR;
} else {
regaddr_desclen = I2S_RX_DESC_IFF_LEN;
regaddr_descaddr = I2S_RX_DESC_IFF_ADDR;
}
i2s_priv = dev_get_drvdata(snd_soc_rtd_to_cpu(rtd, 0)->dev);
regmap_i2s = i2s_priv->regmap_i2s;
regmap_write(regmap_i2s, regaddr_desclen, dma_desc->dma_len);
regmap_write(regmap_i2s, regaddr_descaddr, dma_desc->dma_addr);
return 0;
}
static snd_pcm_uframes_t
bcm63xx_pcm_pointer(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
snd_pcm_uframes_t x;
struct bcm63xx_runtime_data *prtd = substream->runtime->private_data;
if (!prtd->dma_addr_next)
prtd->dma_addr_next = substream->runtime->dma_addr;
x = bytes_to_frames(substream->runtime,
prtd->dma_addr_next - substream->runtime->dma_addr);
return x == substream->runtime->buffer_size ? 0 : x;
}
static int bcm63xx_pcm_open(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
int ret = 0;
struct snd_pcm_runtime *runtime = substream->runtime;
struct bcm63xx_runtime_data *prtd;
runtime->hw = bcm63xx_pcm_hardware;
ret = snd_pcm_hw_constraint_step(runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32);
if (ret)
goto out;
ret = snd_pcm_hw_constraint_step(runtime, 0,
SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32);
if (ret)
goto out;
ret = snd_pcm_hw_constraint_integer(runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
if (ret < 0)
goto out;
ret = -ENOMEM;
prtd = kzalloc_obj(*prtd);
if (!prtd)
goto out;
runtime->private_data = prtd;
return 0;
out:
return ret;
}
static int bcm63xx_pcm_close(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct bcm63xx_runtime_data *prtd = runtime->private_data;
kfree(prtd);
return 0;
}
static irqreturn_t i2s_dma_isr(int irq, void *bcm_i2s_priv)
{
unsigned int availdepth, ifflevel, offlevel, int_status, val_1, val_2;
struct bcm63xx_runtime_data *prtd;
struct snd_pcm_substream *substream;
struct snd_pcm_runtime *runtime;
struct regmap *regmap_i2s;
struct i2s_dma_desc *dma_desc;
struct snd_soc_pcm_runtime *rtd;
struct bcm_i2s_priv *i2s_priv;
i2s_priv = (struct bcm_i2s_priv *)bcm_i2s_priv;
regmap_i2s = i2s_priv->regmap_i2s;
/* rx */
regmap_read(regmap_i2s, I2S_RX_IRQ_CTL, &int_status);
if (int_status & I2S_RX_DESC_OFF_INTR_EN_MSK) {
substream = i2s_priv->capture_substream;
runtime = substream->runtime;
rtd = snd_soc_substream_to_rtd(substream);
prtd = runtime->private_data;
dma_desc = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream);
offlevel = (int_status & I2S_RX_DESC_OFF_LEVEL_MASK) >>
I2S_RX_DESC_OFF_LEVEL_SHIFT;
bool val_read = false;
while (offlevel) {
regmap_read(regmap_i2s, I2S_RX_DESC_OFF_ADDR, &val_1);
regmap_read(regmap_i2s, I2S_RX_DESC_OFF_LEN, &val_2);
val_read = true;
offlevel--;
}
if (val_read)
prtd->dma_addr_next = val_1 + val_2;
ifflevel = (int_status & I2S_RX_DESC_IFF_LEVEL_MASK) >>
I2S_RX_DESC_IFF_LEVEL_SHIFT;
availdepth = I2S_DESC_FIFO_DEPTH - ifflevel;
while (availdepth) {
dma_desc->dma_addr +=
snd_pcm_lib_period_bytes(substream);
dma_desc->dma_area +=
snd_pcm_lib_period_bytes(substream);
if (dma_desc->dma_addr - runtime->dma_addr >=
runtime->dma_bytes) {
dma_desc->dma_addr = runtime->dma_addr;
dma_desc->dma_area = runtime->dma_area;
}
prtd->dma_addr = dma_desc->dma_addr;
regmap_write(regmap_i2s, I2S_RX_DESC_IFF_LEN,
snd_pcm_lib_period_bytes(substream));
regmap_write(regmap_i2s, I2S_RX_DESC_IFF_ADDR,
dma_desc->dma_addr);
availdepth--;
}
snd_pcm_period_elapsed(substream);
/* Clear interrupt by writing 0 */
regmap_update_bits(regmap_i2s, I2S_RX_IRQ_CTL,
I2S_RX_INTR_MASK, 0);
}
/* tx */
regmap_read(regmap_i2s, I2S_TX_IRQ_CTL, &int_status);
if (int_status & I2S_TX_DESC_OFF_INTR_EN_MSK) {
substream = i2s_priv->play_substream;
runtime = substream->runtime;
rtd = snd_soc_substream_to_rtd(substream);
prtd = runtime->private_data;
dma_desc = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream);
offlevel = (int_status & I2S_TX_DESC_OFF_LEVEL_MASK) >>
I2S_TX_DESC_OFF_LEVEL_SHIFT;
while (offlevel) {
regmap_read(regmap_i2s, I2S_TX_DESC_OFF_ADDR, &val_1);
regmap_read(regmap_i2s, I2S_TX_DESC_OFF_LEN, &val_2);
prtd->dma_addr_next = val_1 + val_2;
offlevel--;
}
ifflevel = (int_status & I2S_TX_DESC_IFF_LEVEL_MASK) >>
I2S_TX_DESC_IFF_LEVEL_SHIFT;
availdepth = I2S_DESC_FIFO_DEPTH - ifflevel;
while (availdepth) {
dma_desc->dma_addr +=
snd_pcm_lib_period_bytes(substream);
dma_desc->dma_area +=
snd_pcm_lib_period_bytes(substream);
if (dma_desc->dma_addr - runtime->dma_addr >=
runtime->dma_bytes) {
dma_desc->dma_addr = runtime->dma_addr;
dma_desc->dma_area = runtime->dma_area;
}
prtd->dma_addr = dma_desc->dma_addr;
regmap_write(regmap_i2s, I2S_TX_DESC_IFF_LEN,
snd_pcm_lib_period_bytes(substream));
regmap_write(regmap_i2s, I2S_TX_DESC_IFF_ADDR,
dma_desc->dma_addr);
availdepth--;
}
snd_pcm_period_elapsed(substream);
/* Clear interrupt by writing 0 */
regmap_update_bits(regmap_i2s, I2S_TX_IRQ_CTL,
I2S_TX_INTR_MASK, 0);
}
return IRQ_HANDLED;
}
static int bcm63xx_soc_pcm_new(struct snd_soc_component *component,
struct snd_soc_pcm_runtime *rtd)
{
struct snd_pcm *pcm = rtd->pcm;
struct bcm_i2s_priv *i2s_priv;
int ret;
i2s_priv = dev_get_drvdata(snd_soc_rtd_to_cpu(rtd, 0)->dev);
ret = of_dma_configure(pcm->card->dev, pcm->card->dev->of_node, 1);
if (ret)
return ret;
ret = dma_coerce_mask_and_coherent(pcm->card->dev, DMA_BIT_MASK(32));
if (ret)
return ret;
if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream)
i2s_priv->play_substream =
pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream)
i2s_priv->capture_substream =
pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
return snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV_WC,
pcm->card->dev,
bcm63xx_pcm_hardware.buffer_bytes_max);
}
static const struct snd_soc_component_driver bcm63xx_soc_platform = {
.open = bcm63xx_pcm_open,
.close = bcm63xx_pcm_close,
.hw_params = bcm63xx_pcm_hw_params,
.hw_free = bcm63xx_pcm_hw_free,
.prepare = bcm63xx_pcm_prepare,
.trigger = bcm63xx_pcm_trigger,
.pointer = bcm63xx_pcm_pointer,
.pcm_new = bcm63xx_soc_pcm_new,
};
int bcm63xx_soc_platform_probe(struct platform_device *pdev,
struct bcm_i2s_priv *i2s_priv)
{
int ret;
ret = platform_get_irq(pdev, 0);
if (ret < 0)
return ret;
ret = devm_request_irq(&pdev->dev, ret, i2s_dma_isr,
irq_get_trigger_type(ret), "i2s_dma", (void *)i2s_priv);
if (ret) {
dev_err(&pdev->dev,
"i2s_init: failed to request interrupt.ret=%d\n", ret);
return ret;
}
return devm_snd_soc_register_component(&pdev->dev,
&bcm63xx_soc_platform, NULL, 0);
}
int bcm63xx_soc_platform_remove(struct platform_device *pdev)
{
return 0;
}
MODULE_AUTHOR("Kevin,Li <kevin-ke.li@broadcom.com>");
MODULE_DESCRIPTION("Broadcom DSL XPON ASOC PCM Interface");
MODULE_LICENSE("GPL v2");
+750
View File
@@ -0,0 +1,750 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (C) 2014-2015 Broadcom Corporation
#include <linux/debugfs.h>
#include <linux/dma-mapping.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/timer.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include "cygnus-ssp.h"
/* Register offset needed for ASoC PCM module */
#define INTH_R5F_STATUS_OFFSET 0x040
#define INTH_R5F_CLEAR_OFFSET 0x048
#define INTH_R5F_MASK_SET_OFFSET 0x050
#define INTH_R5F_MASK_CLEAR_OFFSET 0x054
#define BF_REARM_FREE_MARK_OFFSET 0x344
#define BF_REARM_FULL_MARK_OFFSET 0x348
/* Ring Buffer Ctrl Regs --- Start */
/* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_RDADDR_REG_BASE */
#define SRC_RBUF_0_RDADDR_OFFSET 0x500
#define SRC_RBUF_1_RDADDR_OFFSET 0x518
#define SRC_RBUF_2_RDADDR_OFFSET 0x530
#define SRC_RBUF_3_RDADDR_OFFSET 0x548
#define SRC_RBUF_4_RDADDR_OFFSET 0x560
#define SRC_RBUF_5_RDADDR_OFFSET 0x578
#define SRC_RBUF_6_RDADDR_OFFSET 0x590
/* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_WRADDR_REG_BASE */
#define SRC_RBUF_0_WRADDR_OFFSET 0x504
#define SRC_RBUF_1_WRADDR_OFFSET 0x51c
#define SRC_RBUF_2_WRADDR_OFFSET 0x534
#define SRC_RBUF_3_WRADDR_OFFSET 0x54c
#define SRC_RBUF_4_WRADDR_OFFSET 0x564
#define SRC_RBUF_5_WRADDR_OFFSET 0x57c
#define SRC_RBUF_6_WRADDR_OFFSET 0x594
/* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_BASEADDR_REG_BASE */
#define SRC_RBUF_0_BASEADDR_OFFSET 0x508
#define SRC_RBUF_1_BASEADDR_OFFSET 0x520
#define SRC_RBUF_2_BASEADDR_OFFSET 0x538
#define SRC_RBUF_3_BASEADDR_OFFSET 0x550
#define SRC_RBUF_4_BASEADDR_OFFSET 0x568
#define SRC_RBUF_5_BASEADDR_OFFSET 0x580
#define SRC_RBUF_6_BASEADDR_OFFSET 0x598
/* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_ENDADDR_REG_BASE */
#define SRC_RBUF_0_ENDADDR_OFFSET 0x50c
#define SRC_RBUF_1_ENDADDR_OFFSET 0x524
#define SRC_RBUF_2_ENDADDR_OFFSET 0x53c
#define SRC_RBUF_3_ENDADDR_OFFSET 0x554
#define SRC_RBUF_4_ENDADDR_OFFSET 0x56c
#define SRC_RBUF_5_ENDADDR_OFFSET 0x584
#define SRC_RBUF_6_ENDADDR_OFFSET 0x59c
/* AUD_FMM_BF_CTRL_SOURCECH_RINGBUF_X_FREE_MARK_REG_BASE */
#define SRC_RBUF_0_FREE_MARK_OFFSET 0x510
#define SRC_RBUF_1_FREE_MARK_OFFSET 0x528
#define SRC_RBUF_2_FREE_MARK_OFFSET 0x540
#define SRC_RBUF_3_FREE_MARK_OFFSET 0x558
#define SRC_RBUF_4_FREE_MARK_OFFSET 0x570
#define SRC_RBUF_5_FREE_MARK_OFFSET 0x588
#define SRC_RBUF_6_FREE_MARK_OFFSET 0x5a0
/* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_RDADDR_REG_BASE */
#define DST_RBUF_0_RDADDR_OFFSET 0x5c0
#define DST_RBUF_1_RDADDR_OFFSET 0x5d8
#define DST_RBUF_2_RDADDR_OFFSET 0x5f0
#define DST_RBUF_3_RDADDR_OFFSET 0x608
#define DST_RBUF_4_RDADDR_OFFSET 0x620
#define DST_RBUF_5_RDADDR_OFFSET 0x638
/* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_WRADDR_REG_BASE */
#define DST_RBUF_0_WRADDR_OFFSET 0x5c4
#define DST_RBUF_1_WRADDR_OFFSET 0x5dc
#define DST_RBUF_2_WRADDR_OFFSET 0x5f4
#define DST_RBUF_3_WRADDR_OFFSET 0x60c
#define DST_RBUF_4_WRADDR_OFFSET 0x624
#define DST_RBUF_5_WRADDR_OFFSET 0x63c
/* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_BASEADDR_REG_BASE */
#define DST_RBUF_0_BASEADDR_OFFSET 0x5c8
#define DST_RBUF_1_BASEADDR_OFFSET 0x5e0
#define DST_RBUF_2_BASEADDR_OFFSET 0x5f8
#define DST_RBUF_3_BASEADDR_OFFSET 0x610
#define DST_RBUF_4_BASEADDR_OFFSET 0x628
#define DST_RBUF_5_BASEADDR_OFFSET 0x640
/* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_ENDADDR_REG_BASE */
#define DST_RBUF_0_ENDADDR_OFFSET 0x5cc
#define DST_RBUF_1_ENDADDR_OFFSET 0x5e4
#define DST_RBUF_2_ENDADDR_OFFSET 0x5fc
#define DST_RBUF_3_ENDADDR_OFFSET 0x614
#define DST_RBUF_4_ENDADDR_OFFSET 0x62c
#define DST_RBUF_5_ENDADDR_OFFSET 0x644
/* AUD_FMM_BF_CTRL_DESTCH_RINGBUF_X_FULL_MARK_REG_BASE */
#define DST_RBUF_0_FULL_MARK_OFFSET 0x5d0
#define DST_RBUF_1_FULL_MARK_OFFSET 0x5e8
#define DST_RBUF_2_FULL_MARK_OFFSET 0x600
#define DST_RBUF_3_FULL_MARK_OFFSET 0x618
#define DST_RBUF_4_FULL_MARK_OFFSET 0x630
#define DST_RBUF_5_FULL_MARK_OFFSET 0x648
/* Ring Buffer Ctrl Regs --- End */
/* Error Status Regs --- Start */
/* AUD_FMM_BF_ESR_ESRX_STATUS_REG_BASE */
#define ESR0_STATUS_OFFSET 0x900
#define ESR1_STATUS_OFFSET 0x918
#define ESR2_STATUS_OFFSET 0x930
#define ESR3_STATUS_OFFSET 0x948
#define ESR4_STATUS_OFFSET 0x960
/* AUD_FMM_BF_ESR_ESRX_STATUS_CLEAR_REG_BASE */
#define ESR0_STATUS_CLR_OFFSET 0x908
#define ESR1_STATUS_CLR_OFFSET 0x920
#define ESR2_STATUS_CLR_OFFSET 0x938
#define ESR3_STATUS_CLR_OFFSET 0x950
#define ESR4_STATUS_CLR_OFFSET 0x968
/* AUD_FMM_BF_ESR_ESRX_MASK_REG_BASE */
#define ESR0_MASK_STATUS_OFFSET 0x90c
#define ESR1_MASK_STATUS_OFFSET 0x924
#define ESR2_MASK_STATUS_OFFSET 0x93c
#define ESR3_MASK_STATUS_OFFSET 0x954
#define ESR4_MASK_STATUS_OFFSET 0x96c
/* AUD_FMM_BF_ESR_ESRX_MASK_SET_REG_BASE */
#define ESR0_MASK_SET_OFFSET 0x910
#define ESR1_MASK_SET_OFFSET 0x928
#define ESR2_MASK_SET_OFFSET 0x940
#define ESR3_MASK_SET_OFFSET 0x958
#define ESR4_MASK_SET_OFFSET 0x970
/* AUD_FMM_BF_ESR_ESRX_MASK_CLEAR_REG_BASE */
#define ESR0_MASK_CLR_OFFSET 0x914
#define ESR1_MASK_CLR_OFFSET 0x92c
#define ESR2_MASK_CLR_OFFSET 0x944
#define ESR3_MASK_CLR_OFFSET 0x95c
#define ESR4_MASK_CLR_OFFSET 0x974
/* Error Status Regs --- End */
#define R5F_ESR0_SHIFT 0 /* esr0 = fifo underflow */
#define R5F_ESR1_SHIFT 1 /* esr1 = ringbuf underflow */
#define R5F_ESR2_SHIFT 2 /* esr2 = ringbuf overflow */
#define R5F_ESR3_SHIFT 3 /* esr3 = freemark */
#define R5F_ESR4_SHIFT 4 /* esr4 = fullmark */
/* Mask for R5F register. Set all relevant interrupt for playback handler */
#define ANY_PLAYBACK_IRQ (BIT(R5F_ESR0_SHIFT) | \
BIT(R5F_ESR1_SHIFT) | \
BIT(R5F_ESR3_SHIFT))
/* Mask for R5F register. Set all relevant interrupt for capture handler */
#define ANY_CAPTURE_IRQ (BIT(R5F_ESR2_SHIFT) | BIT(R5F_ESR4_SHIFT))
/*
* PERIOD_BYTES_MIN is the number of bytes to at which the interrupt will tick.
* This number should be a multiple of 256. Minimum value is 256
*/
#define PERIOD_BYTES_MIN 0x100
static const struct snd_pcm_hardware cygnus_pcm_hw = {
.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S32_LE,
/* A period is basically an interrupt */
.period_bytes_min = PERIOD_BYTES_MIN,
.period_bytes_max = 0x10000,
/* period_min/max gives range of approx interrupts per buffer */
.periods_min = 2,
.periods_max = 8,
/*
* maximum buffer size in bytes = period_bytes_max * periods_max
* We allocate this amount of data for each enabled channel
*/
.buffer_bytes_max = 4 * 0x8000,
};
static u64 cygnus_dma_dmamask = DMA_BIT_MASK(32);
static struct cygnus_aio_port *cygnus_dai_get_dma_data(
struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream);
return snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(soc_runtime, 0), substream);
}
static void ringbuf_set_initial(void __iomem *audio_io,
struct ringbuf_regs *p_rbuf,
bool is_playback,
u32 start,
u32 periodsize,
u32 bufsize)
{
u32 initial_rd;
u32 initial_wr;
u32 end;
u32 fmark_val; /* free or full mark */
p_rbuf->period_bytes = periodsize;
p_rbuf->buf_size = bufsize;
if (is_playback) {
/* Set the pointers to indicate full (flip uppermost bit) */
initial_rd = start;
initial_wr = initial_rd ^ BIT(31);
} else {
/* Set the pointers to indicate empty */
initial_wr = start;
initial_rd = initial_wr;
}
end = start + bufsize - 1;
/*
* The interrupt will fire when free/full mark is *exceeded*
* The fmark value must be multiple of PERIOD_BYTES_MIN so set fmark
* to be PERIOD_BYTES_MIN less than the period size.
*/
fmark_val = periodsize - PERIOD_BYTES_MIN;
writel(start, audio_io + p_rbuf->baseaddr);
writel(end, audio_io + p_rbuf->endaddr);
writel(fmark_val, audio_io + p_rbuf->fmark);
writel(initial_rd, audio_io + p_rbuf->rdaddr);
writel(initial_wr, audio_io + p_rbuf->wraddr);
}
static int configure_ringbuf_regs(struct snd_pcm_substream *substream)
{
struct cygnus_aio_port *aio;
struct ringbuf_regs *p_rbuf;
int status = 0;
aio = cygnus_dai_get_dma_data(substream);
/* Map the ssp portnum to a set of ring buffers. */
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
p_rbuf = &aio->play_rb_regs;
switch (aio->portnum) {
case 0:
*p_rbuf = RINGBUF_REG_PLAYBACK(0);
break;
case 1:
*p_rbuf = RINGBUF_REG_PLAYBACK(2);
break;
case 2:
*p_rbuf = RINGBUF_REG_PLAYBACK(4);
break;
case 3: /* SPDIF */
*p_rbuf = RINGBUF_REG_PLAYBACK(6);
break;
default:
status = -EINVAL;
}
} else {
p_rbuf = &aio->capture_rb_regs;
switch (aio->portnum) {
case 0:
*p_rbuf = RINGBUF_REG_CAPTURE(0);
break;
case 1:
*p_rbuf = RINGBUF_REG_CAPTURE(2);
break;
case 2:
*p_rbuf = RINGBUF_REG_CAPTURE(4);
break;
default:
status = -EINVAL;
}
}
return status;
}
static struct ringbuf_regs *get_ringbuf(struct snd_pcm_substream *substream)
{
struct cygnus_aio_port *aio;
struct ringbuf_regs *p_rbuf = NULL;
aio = cygnus_dai_get_dma_data(substream);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
p_rbuf = &aio->play_rb_regs;
else
p_rbuf = &aio->capture_rb_regs;
return p_rbuf;
}
static void enable_intr(struct snd_pcm_substream *substream)
{
struct cygnus_aio_port *aio;
u32 clear_mask;
aio = cygnus_dai_get_dma_data(substream);
/* The port number maps to the bit position to be cleared */
clear_mask = BIT(aio->portnum);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
/* Clear interrupt status before enabling them */
writel(clear_mask, aio->cygaud->audio + ESR0_STATUS_CLR_OFFSET);
writel(clear_mask, aio->cygaud->audio + ESR1_STATUS_CLR_OFFSET);
writel(clear_mask, aio->cygaud->audio + ESR3_STATUS_CLR_OFFSET);
/* Unmask the interrupts of the given port*/
writel(clear_mask, aio->cygaud->audio + ESR0_MASK_CLR_OFFSET);
writel(clear_mask, aio->cygaud->audio + ESR1_MASK_CLR_OFFSET);
writel(clear_mask, aio->cygaud->audio + ESR3_MASK_CLR_OFFSET);
writel(ANY_PLAYBACK_IRQ,
aio->cygaud->audio + INTH_R5F_MASK_CLEAR_OFFSET);
} else {
writel(clear_mask, aio->cygaud->audio + ESR2_STATUS_CLR_OFFSET);
writel(clear_mask, aio->cygaud->audio + ESR4_STATUS_CLR_OFFSET);
writel(clear_mask, aio->cygaud->audio + ESR2_MASK_CLR_OFFSET);
writel(clear_mask, aio->cygaud->audio + ESR4_MASK_CLR_OFFSET);
writel(ANY_CAPTURE_IRQ,
aio->cygaud->audio + INTH_R5F_MASK_CLEAR_OFFSET);
}
}
static void disable_intr(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct cygnus_aio_port *aio;
u32 set_mask;
aio = cygnus_dai_get_dma_data(substream);
dev_dbg(snd_soc_rtd_to_cpu(rtd, 0)->dev, "%s on port %d\n", __func__, aio->portnum);
/* The port number maps to the bit position to be set */
set_mask = BIT(aio->portnum);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
/* Mask the interrupts of the given port*/
writel(set_mask, aio->cygaud->audio + ESR0_MASK_SET_OFFSET);
writel(set_mask, aio->cygaud->audio + ESR1_MASK_SET_OFFSET);
writel(set_mask, aio->cygaud->audio + ESR3_MASK_SET_OFFSET);
} else {
writel(set_mask, aio->cygaud->audio + ESR2_MASK_SET_OFFSET);
writel(set_mask, aio->cygaud->audio + ESR4_MASK_SET_OFFSET);
}
}
static int cygnus_pcm_trigger(struct snd_soc_component *component,
struct snd_pcm_substream *substream, int cmd)
{
int ret = 0;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
enable_intr(substream);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
disable_intr(substream);
break;
default:
ret = -EINVAL;
}
return ret;
}
static void cygnus_pcm_period_elapsed(struct snd_pcm_substream *substream)
{
struct cygnus_aio_port *aio;
struct ringbuf_regs *p_rbuf = NULL;
u32 regval;
aio = cygnus_dai_get_dma_data(substream);
p_rbuf = get_ringbuf(substream);
/*
* If free/full mark interrupt occurs, provide timestamp
* to ALSA and update appropriate idx by period_bytes
*/
snd_pcm_period_elapsed(substream);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
/* Set the ring buffer to full */
regval = readl(aio->cygaud->audio + p_rbuf->rdaddr);
regval = regval ^ BIT(31);
writel(regval, aio->cygaud->audio + p_rbuf->wraddr);
} else {
/* Set the ring buffer to empty */
regval = readl(aio->cygaud->audio + p_rbuf->wraddr);
writel(regval, aio->cygaud->audio + p_rbuf->rdaddr);
}
}
/*
* ESR0/1/3 status Description
* 0x1 I2S0_out port caused interrupt
* 0x2 I2S1_out port caused interrupt
* 0x4 I2S2_out port caused interrupt
* 0x8 SPDIF_out port caused interrupt
*/
static void handle_playback_irq(struct cygnus_audio *cygaud)
{
void __iomem *audio_io;
u32 port;
u32 esr_status0, esr_status1, esr_status3;
audio_io = cygaud->audio;
/*
* ESR status gets updates with/without interrupts enabled.
* So, check the ESR mask, which provides interrupt enable/
* disable status and use it to determine which ESR status
* should be serviced.
*/
esr_status0 = readl(audio_io + ESR0_STATUS_OFFSET);
esr_status0 &= ~readl(audio_io + ESR0_MASK_STATUS_OFFSET);
esr_status1 = readl(audio_io + ESR1_STATUS_OFFSET);
esr_status1 &= ~readl(audio_io + ESR1_MASK_STATUS_OFFSET);
esr_status3 = readl(audio_io + ESR3_STATUS_OFFSET);
esr_status3 &= ~readl(audio_io + ESR3_MASK_STATUS_OFFSET);
for (port = 0; port < CYGNUS_MAX_PLAYBACK_PORTS; port++) {
u32 esrmask = BIT(port);
/*
* Ringbuffer or FIFO underflow
* If we get this interrupt then, it is also true that we have
* not yet responded to the freemark interrupt.
* Log a debug message. The freemark handler below will
* handle getting everything going again.
*/
if ((esrmask & esr_status1) || (esrmask & esr_status0)) {
dev_dbg(cygaud->dev,
"Underrun: esr0=0x%x, esr1=0x%x esr3=0x%x\n",
esr_status0, esr_status1, esr_status3);
}
/*
* Freemark is hit. This is the normal interrupt.
* In typical operation the read and write regs will be equal
*/
if (esrmask & esr_status3) {
struct snd_pcm_substream *playstr;
playstr = cygaud->portinfo[port].play_stream;
cygnus_pcm_period_elapsed(playstr);
}
}
/* Clear ESR interrupt */
writel(esr_status0, audio_io + ESR0_STATUS_CLR_OFFSET);
writel(esr_status1, audio_io + ESR1_STATUS_CLR_OFFSET);
writel(esr_status3, audio_io + ESR3_STATUS_CLR_OFFSET);
/* Rearm freemark logic by writing 1 to the correct bit */
writel(esr_status3, audio_io + BF_REARM_FREE_MARK_OFFSET);
}
/*
* ESR2/4 status Description
* 0x1 I2S0_in port caused interrupt
* 0x2 I2S1_in port caused interrupt
* 0x4 I2S2_in port caused interrupt
*/
static void handle_capture_irq(struct cygnus_audio *cygaud)
{
void __iomem *audio_io;
u32 port;
u32 esr_status2, esr_status4;
audio_io = cygaud->audio;
/*
* ESR status gets updates with/without interrupts enabled.
* So, check the ESR mask, which provides interrupt enable/
* disable status and use it to determine which ESR status
* should be serviced.
*/
esr_status2 = readl(audio_io + ESR2_STATUS_OFFSET);
esr_status2 &= ~readl(audio_io + ESR2_MASK_STATUS_OFFSET);
esr_status4 = readl(audio_io + ESR4_STATUS_OFFSET);
esr_status4 &= ~readl(audio_io + ESR4_MASK_STATUS_OFFSET);
for (port = 0; port < CYGNUS_MAX_CAPTURE_PORTS; port++) {
u32 esrmask = BIT(port);
/*
* Ringbuffer or FIFO overflow
* If we get this interrupt then, it is also true that we have
* not yet responded to the fullmark interrupt.
* Log a debug message. The fullmark handler below will
* handle getting everything going again.
*/
if (esrmask & esr_status2)
dev_dbg(cygaud->dev,
"Overflow: esr2=0x%x\n", esr_status2);
if (esrmask & esr_status4) {
struct snd_pcm_substream *capstr;
capstr = cygaud->portinfo[port].capture_stream;
cygnus_pcm_period_elapsed(capstr);
}
}
writel(esr_status2, audio_io + ESR2_STATUS_CLR_OFFSET);
writel(esr_status4, audio_io + ESR4_STATUS_CLR_OFFSET);
/* Rearm fullmark logic by writing 1 to the correct bit */
writel(esr_status4, audio_io + BF_REARM_FULL_MARK_OFFSET);
}
static irqreturn_t cygnus_dma_irq(int irq, void *data)
{
u32 r5_status;
struct cygnus_audio *cygaud = data;
/*
* R5 status bits Description
* 0 ESR0 (playback FIFO interrupt)
* 1 ESR1 (playback rbuf interrupt)
* 2 ESR2 (capture rbuf interrupt)
* 3 ESR3 (Freemark play. interrupt)
* 4 ESR4 (Fullmark capt. interrupt)
*/
r5_status = readl(cygaud->audio + INTH_R5F_STATUS_OFFSET);
if (!(r5_status & (ANY_PLAYBACK_IRQ | ANY_CAPTURE_IRQ)))
return IRQ_NONE;
/* If playback interrupt happened */
if (ANY_PLAYBACK_IRQ & r5_status) {
handle_playback_irq(cygaud);
writel(ANY_PLAYBACK_IRQ & r5_status,
cygaud->audio + INTH_R5F_CLEAR_OFFSET);
}
/* If capture interrupt happened */
if (ANY_CAPTURE_IRQ & r5_status) {
handle_capture_irq(cygaud);
writel(ANY_CAPTURE_IRQ & r5_status,
cygaud->audio + INTH_R5F_CLEAR_OFFSET);
}
return IRQ_HANDLED;
}
static int cygnus_pcm_open(struct snd_soc_component *component,
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 cygnus_aio_port *aio;
int ret;
aio = cygnus_dai_get_dma_data(substream);
if (!aio)
return -ENODEV;
dev_dbg(snd_soc_rtd_to_cpu(rtd, 0)->dev, "%s port %d\n", __func__, aio->portnum);
snd_soc_set_runtime_hwparams(substream, &cygnus_pcm_hw);
ret = snd_pcm_hw_constraint_step(runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_BYTES, PERIOD_BYTES_MIN);
if (ret < 0)
return ret;
ret = snd_pcm_hw_constraint_step(runtime, 0,
SNDRV_PCM_HW_PARAM_BUFFER_BYTES, PERIOD_BYTES_MIN);
if (ret < 0)
return ret;
/*
* Keep track of which substream belongs to which port.
* This info is needed by snd_pcm_period_elapsed() in irq_handler
*/
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
aio->play_stream = substream;
else
aio->capture_stream = substream;
return 0;
}
static int cygnus_pcm_close(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct cygnus_aio_port *aio;
aio = cygnus_dai_get_dma_data(substream);
dev_dbg(snd_soc_rtd_to_cpu(rtd, 0)->dev, "%s port %d\n", __func__, aio->portnum);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
aio->play_stream = NULL;
else
aio->capture_stream = NULL;
if (!aio->play_stream && !aio->capture_stream)
dev_dbg(snd_soc_rtd_to_cpu(rtd, 0)->dev, "freed port %d\n", aio->portnum);
return 0;
}
static int cygnus_pcm_prepare(struct snd_soc_component *component,
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 cygnus_aio_port *aio;
unsigned long bufsize, periodsize;
bool is_play;
u32 start;
struct ringbuf_regs *p_rbuf = NULL;
aio = cygnus_dai_get_dma_data(substream);
dev_dbg(snd_soc_rtd_to_cpu(rtd, 0)->dev, "%s port %d\n", __func__, aio->portnum);
bufsize = snd_pcm_lib_buffer_bytes(substream);
periodsize = snd_pcm_lib_period_bytes(substream);
dev_dbg(snd_soc_rtd_to_cpu(rtd, 0)->dev, "%s (buf_size %lu) (period_size %lu)\n",
__func__, bufsize, periodsize);
configure_ringbuf_regs(substream);
p_rbuf = get_ringbuf(substream);
start = runtime->dma_addr;
is_play = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? 1 : 0;
ringbuf_set_initial(aio->cygaud->audio, p_rbuf, is_play, start,
periodsize, bufsize);
return 0;
}
static snd_pcm_uframes_t cygnus_pcm_pointer(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct cygnus_aio_port *aio;
unsigned int res = 0, cur = 0, base = 0;
struct ringbuf_regs *p_rbuf = NULL;
aio = cygnus_dai_get_dma_data(substream);
/*
* Get the offset of the current read (for playack) or write
* index (for capture). Report this value back to the asoc framework.
*/
p_rbuf = get_ringbuf(substream);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
cur = readl(aio->cygaud->audio + p_rbuf->rdaddr);
else
cur = readl(aio->cygaud->audio + p_rbuf->wraddr);
base = readl(aio->cygaud->audio + p_rbuf->baseaddr);
/*
* Mask off the MSB of the rdaddr,wraddr and baseaddr
* since MSB is not part of the address
*/
res = (cur & 0x7fffffff) - (base & 0x7fffffff);
return bytes_to_frames(substream->runtime, res);
}
static int cygnus_dma_new(struct snd_soc_component *component,
struct snd_soc_pcm_runtime *rtd)
{
size_t size = cygnus_pcm_hw.buffer_bytes_max;
struct snd_card *card = rtd->card->snd_card;
if (!card->dev->dma_mask)
card->dev->dma_mask = &cygnus_dma_dmamask;
if (!card->dev->coherent_dma_mask)
card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV,
card->dev, size, size);
return 0;
}
static const struct snd_soc_component_driver cygnus_soc_platform = {
.open = cygnus_pcm_open,
.close = cygnus_pcm_close,
.prepare = cygnus_pcm_prepare,
.trigger = cygnus_pcm_trigger,
.pointer = cygnus_pcm_pointer,
.pcm_new = cygnus_dma_new,
};
int cygnus_soc_platform_register(struct device *dev,
struct cygnus_audio *cygaud)
{
int rc;
dev_dbg(dev, "%s Enter\n", __func__);
rc = devm_request_irq(dev, cygaud->irq_num, cygnus_dma_irq,
IRQF_SHARED, "cygnus-audio", cygaud);
if (rc) {
dev_err(dev, "%s request_irq error %d\n", __func__, rc);
return rc;
}
rc = devm_snd_soc_register_component(dev, &cygnus_soc_platform,
NULL, 0);
if (rc) {
dev_err(dev, "%s failed\n", __func__);
return rc;
}
return 0;
}
int cygnus_soc_platform_unregister(struct device *dev)
{
return 0;
}
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Broadcom");
MODULE_DESCRIPTION("Cygnus ASoC PCM module");
File diff suppressed because it is too large Load Diff
+127
View File
@@ -0,0 +1,127 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2014-2015 Broadcom Corporation */
#ifndef __CYGNUS_SSP_H__
#define __CYGNUS_SSP_H__
#define CYGNUS_TDM_DAI_MAX_SLOTS 16
#define CYGNUS_MAX_PLAYBACK_PORTS 4
#define CYGNUS_MAX_CAPTURE_PORTS 3
#define CYGNUS_MAX_I2S_PORTS 3
#define CYGNUS_MAX_PORTS CYGNUS_MAX_PLAYBACK_PORTS
#define CYGNUS_AUIDO_MAX_NUM_CLKS 3
#define CYGNUS_SSP_FRAMEBITS_DIV 1
#define CYGNUS_SSPMODE_I2S 0
#define CYGNUS_SSPMODE_TDM 1
#define CYGNUS_SSPMODE_UNKNOWN -1
#define CYGNUS_SSP_CLKSRC_PLL 0
/* Max string length of our dt property names */
#define PROP_LEN_MAX 40
struct ringbuf_regs {
unsigned rdaddr;
unsigned wraddr;
unsigned baseaddr;
unsigned endaddr;
unsigned fmark; /* freemark for play, fullmark for caputure */
unsigned period_bytes;
unsigned buf_size;
};
#define RINGBUF_REG_PLAYBACK(num) ((struct ringbuf_regs) { \
.rdaddr = SRC_RBUF_ ##num## _RDADDR_OFFSET, \
.wraddr = SRC_RBUF_ ##num## _WRADDR_OFFSET, \
.baseaddr = SRC_RBUF_ ##num## _BASEADDR_OFFSET, \
.endaddr = SRC_RBUF_ ##num## _ENDADDR_OFFSET, \
.fmark = SRC_RBUF_ ##num## _FREE_MARK_OFFSET, \
.period_bytes = 0, \
.buf_size = 0, \
})
#define RINGBUF_REG_CAPTURE(num) ((struct ringbuf_regs) { \
.rdaddr = DST_RBUF_ ##num## _RDADDR_OFFSET, \
.wraddr = DST_RBUF_ ##num## _WRADDR_OFFSET, \
.baseaddr = DST_RBUF_ ##num## _BASEADDR_OFFSET, \
.endaddr = DST_RBUF_ ##num## _ENDADDR_OFFSET, \
.fmark = DST_RBUF_ ##num## _FULL_MARK_OFFSET, \
.period_bytes = 0, \
.buf_size = 0, \
})
enum cygnus_audio_port_type {
PORT_TDM,
PORT_SPDIF,
};
struct cygnus_ssp_regs {
u32 i2s_stream_cfg;
u32 i2s_cfg;
u32 i2s_cap_stream_cfg;
u32 i2s_cap_cfg;
u32 i2s_mclk_cfg;
u32 bf_destch_ctrl;
u32 bf_destch_cfg;
u32 bf_sourcech_ctrl;
u32 bf_sourcech_cfg;
u32 bf_sourcech_grp;
};
struct cygnus_track_clk {
bool cap_en;
bool play_en;
bool cap_clk_en;
bool play_clk_en;
};
struct cygnus_aio_port {
int portnum;
int mode;
bool is_slave;
int streams_on; /* will be 0 if both capture and play are off */
int fsync_width;
int port_type;
u32 mclk;
u32 lrclk;
u32 bit_per_frame;
u32 pll_clk_num;
struct cygnus_audio *cygaud;
struct cygnus_ssp_regs regs;
struct ringbuf_regs play_rb_regs;
struct ringbuf_regs capture_rb_regs;
struct snd_pcm_substream *play_stream;
struct snd_pcm_substream *capture_stream;
struct cygnus_track_clk clk_trace;
};
struct cygnus_audio {
struct cygnus_aio_port portinfo[CYGNUS_MAX_PORTS];
int irq_num;
void __iomem *audio;
struct device *dev;
void __iomem *i2s_in;
struct clk *audio_clk[CYGNUS_AUIDO_MAX_NUM_CLKS];
int active_ports;
unsigned long vco_rate;
};
extern int cygnus_ssp_set_custom_fsync_width(struct snd_soc_dai *cpu_dai,
int len);
extern int cygnus_soc_platform_register(struct device *dev,
struct cygnus_audio *cygaud);
extern int cygnus_soc_platform_unregister(struct device *dev);
extern int cygnus_ssp_set_custom_fsync_width(struct snd_soc_dai *cpu_dai,
int len);
#endif