// SPDX-License-Identifier: GPL-2.0
//
// Renesas RZ/G2L ASoC Serial Sound Interface (SSIF-2) Driver
//
// Copyright (C) 2021 Renesas Electronics Corp.
// Copyright (C) 2019 Chris Brandt.
//
#include <linux/clk.h>
#include <linux/dmaengine.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <sound/soc.h>
/* REGISTER OFFSET */
#define SSICR 0x000
#define SSISR 0x004
#define SSIFCR 0x010
#define SSIFSR 0x014
#define SSIFTDR 0x018
#define SSIFRDR 0x01c
#define SSIOFR 0x020
#define SSISCR 0x024
/* SSI REGISTER BITS */
#define SSICR_DWL(x) (((x) & 0x7) << 19)
#define SSICR_SWL(x) (((x) & 0x7) << 16)
#define SSICR_CKS BIT(30)
#define SSICR_TUIEN BIT(29)
#define SSICR_TOIEN BIT(28)
#define SSICR_RUIEN BIT(27)
#define SSICR_ROIEN BIT(26)
#define SSICR_MST BIT(14)
#define SSICR_BCKP BIT(13)
#define SSICR_LRCKP BIT(12)
#define SSICR_CKDV(x) (((x) & 0xf) << 4)
#define SSICR_TEN BIT(1)
#define SSICR_REN BIT(0)
#define SSISR_TUIRQ BIT(29)
#define SSISR_TOIRQ BIT(28)
#define SSISR_RUIRQ BIT(27)
#define SSISR_ROIRQ BIT(26)
#define SSISR_IIRQ BIT(25)
#define SSIFCR_AUCKE BIT(31)
#define SSIFCR_SSIRST BIT(16)
#define SSIFCR_TIE BIT(3)
#define SSIFCR_RIE BIT(2)
#define SSIFCR_TFRST BIT(1)
#define SSIFCR_RFRST BIT(0)
#define SSIFCR_FIFO_RST (SSIFCR_TFRST | SSIFCR_RFRST)
#define SSIFSR_TDC_MASK 0x3f
#define SSIFSR_TDC_SHIFT 24
#define SSIFSR_RDC_MASK 0x3f
#define SSIFSR_RDC_SHIFT 8
#define SSIFSR_TDE BIT(16)
#define SSIFSR_RDF BIT(0)
#define SSIOFR_LRCONT BIT(8)
#define SSISCR_TDES(x) (((x) & 0x1f) << 8)
#define SSISCR_RDFS(x) (((x) & 0x1f) << 0)
/* Pre allocated buffers sizes */
#define PREALLOC_BUFFER (SZ_32K)
#define PREALLOC_BUFFER_MAX (SZ_32K)
#define SSI_RATES SNDRV_PCM_RATE_8000_48000 /* 8k-44.1kHz */
#define SSI_FMTS SNDRV_PCM_FMTBIT_S16_LE
#define SSI_CHAN_MIN 2
#define SSI_CHAN_MAX 2
#define SSI_FIFO_DEPTH 32
struct rz_ssi_priv;
struct rz_ssi_stream {
struct rz_ssi_priv *priv;
struct snd_pcm_substream *substream;
int fifo_sample_size; /* sample capacity of SSI FIFO */
int dma_buffer_pos; /* The address for the next DMA descriptor */
int period_counter; /* for keeping track of periods transferred */
int sample_width;
int buffer_pos; /* current frame position in the buffer */
int running; /* 0=stopped, 1=running */
int uerr_num;
int oerr_num;
struct dma_chan *dma_ch;
int (*transfer)(struct rz_ssi_priv *ssi, struct rz_ssi_stream *strm);
};
struct rz_ssi_priv {
void __iomem *base;
struct platform_device *pdev;
struct reset_control *rstc;
struct device *dev;
struct clk *sfr_clk;
struct clk *clk;
phys_addr_t phys;
int irq_int;
int irq_tx;
int irq_rx;
int irq_rt;
spinlock_t lock;
/*
* The SSI supports full-duplex transmission and reception.
* However, if an error occurs, channel reset (both transmission
* and reception reset) is required.
* So it is better to use as half-duplex (playing and recording
* should be done on separate channels).
*/
struct rz_ssi_stream playback;
struct rz_ssi_stream capture;
/* clock */
unsigned long audio_mck;
unsigned long audio_clk_1;
unsigned long audio_clk_2;
bool lrckp_fsync_fall; /* LR clock polarity (SSICR.LRCKP) */
bool bckp_rise; /* Bit clock polarity (SSICR.BCKP) */
bool dma_rt;
/* Full duplex communication support */
struct {
unsigned int rate;
unsigned int channels;
unsigned int sample_width;
unsigned int sample_bits;
} hw_params_cache;
};
static void rz_ssi_dma_complete(void *data);
static void rz_ssi_reg_writel(struct rz_ssi_priv *priv, uint reg, u32 data)
{
writel(data, (priv->base + reg));
}
static u32 rz_ssi_reg_readl(struct rz_ssi_priv *priv, uint reg)
{
return readl(priv->base + reg);
}
static void rz_ssi_reg_mask_setl(struct rz_ssi_priv *priv, uint reg,
u32 bclr, u32 bset)
{
u32 val;
val = readl(priv->base + reg);
val = (val & ~bclr) | bset;
writel(val, (priv->base + reg));
}
static inline struct snd_soc_dai *
rz_ssi_get_dai(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
return snd_soc_rtd_to_cpu(rtd, 0);
}
static inline bool rz_ssi_stream_is_play(struct rz_ssi_priv *ssi,
struct snd_pcm_substream *substream)
{
return substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
}
static inline struct rz_ssi_stream *
rz_ssi_stream_get(struct rz_ssi_priv *ssi, struct snd_pcm_substream *substream)
{
struct rz_ssi_stream *stream = &ssi->