mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-16 15:34:48 +08:00
eae6fff4f1
Current ADG clock calculation needs ADG and SSI settings. Thus, SSI side clock request function depends on ADG settings. After reconsideration, we can close this method inside ADG. This function uses new method. And it becomes preparation for AUDIO_CLKOUT support. Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> Signed-off-by: Mark Brown <broonie@kernel.org>
821 lines
18 KiB
C
821 lines
18 KiB
C
/*
|
|
* Renesas R-Car SSIU/SSI support
|
|
*
|
|
* Copyright (C) 2013 Renesas Solutions Corp.
|
|
* Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
|
|
*
|
|
* Based on fsi.c
|
|
* Kuninori Morimoto <morimoto.kuninori@renesas.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
#include <linux/delay.h>
|
|
#include "rsnd.h"
|
|
#define RSND_SSI_NAME_SIZE 16
|
|
|
|
/*
|
|
* SSICR
|
|
*/
|
|
#define FORCE (1 << 31) /* Fixed */
|
|
#define DMEN (1 << 28) /* DMA Enable */
|
|
#define UIEN (1 << 27) /* Underflow Interrupt Enable */
|
|
#define OIEN (1 << 26) /* Overflow Interrupt Enable */
|
|
#define IIEN (1 << 25) /* Idle Mode Interrupt Enable */
|
|
#define DIEN (1 << 24) /* Data Interrupt Enable */
|
|
|
|
#define DWL_8 (0 << 19) /* Data Word Length */
|
|
#define DWL_16 (1 << 19) /* Data Word Length */
|
|
#define DWL_18 (2 << 19) /* Data Word Length */
|
|
#define DWL_20 (3 << 19) /* Data Word Length */
|
|
#define DWL_22 (4 << 19) /* Data Word Length */
|
|
#define DWL_24 (5 << 19) /* Data Word Length */
|
|
#define DWL_32 (6 << 19) /* Data Word Length */
|
|
|
|
#define SWL_32 (3 << 16) /* R/W System Word Length */
|
|
#define SCKD (1 << 15) /* Serial Bit Clock Direction */
|
|
#define SWSD (1 << 14) /* Serial WS Direction */
|
|
#define SCKP (1 << 13) /* Serial Bit Clock Polarity */
|
|
#define SWSP (1 << 12) /* Serial WS Polarity */
|
|
#define SDTA (1 << 10) /* Serial Data Alignment */
|
|
#define DEL (1 << 8) /* Serial Data Delay */
|
|
#define CKDV(v) (v << 4) /* Serial Clock Division Ratio */
|
|
#define TRMD (1 << 1) /* Transmit/Receive Mode Select */
|
|
#define EN (1 << 0) /* SSI Module Enable */
|
|
|
|
/*
|
|
* SSISR
|
|
*/
|
|
#define UIRQ (1 << 27) /* Underflow Error Interrupt Status */
|
|
#define OIRQ (1 << 26) /* Overflow Error Interrupt Status */
|
|
#define IIRQ (1 << 25) /* Idle Mode Interrupt Status */
|
|
#define DIRQ (1 << 24) /* Data Interrupt Status Flag */
|
|
|
|
/*
|
|
* SSIWSR
|
|
*/
|
|
#define CONT (1 << 8) /* WS Continue Function */
|
|
|
|
#define SSI_NAME "ssi"
|
|
|
|
struct rsnd_ssi {
|
|
struct rsnd_ssi_platform_info *info; /* rcar_snd.h */
|
|
struct rsnd_ssi *parent;
|
|
struct rsnd_mod mod;
|
|
|
|
u32 cr_own;
|
|
u32 cr_clk;
|
|
int chan;
|
|
int err;
|
|
unsigned int usrcnt;
|
|
};
|
|
|
|
#define for_each_rsnd_ssi(pos, priv, i) \
|
|
for (i = 0; \
|
|
(i < rsnd_ssi_nr(priv)) && \
|
|
((pos) = ((struct rsnd_ssi *)(priv)->ssi + i)); \
|
|
i++)
|
|
|
|
#define rsnd_ssi_nr(priv) ((priv)->ssi_nr)
|
|
#define rsnd_mod_to_ssi(_mod) container_of((_mod), struct rsnd_ssi, mod)
|
|
#define rsnd_dma_to_ssi(dma) rsnd_mod_to_ssi(rsnd_dma_to_mod(dma))
|
|
#define rsnd_ssi_pio_available(ssi) ((ssi)->info->irq > 0)
|
|
#define rsnd_ssi_parent(ssi) ((ssi)->parent)
|
|
#define rsnd_ssi_mode_flags(p) ((p)->info->flags)
|
|
#define rsnd_ssi_dai_id(ssi) ((ssi)->info->dai_id)
|
|
#define rsnd_ssi_of_node(priv) \
|
|
of_get_child_by_name(rsnd_priv_to_dev(priv)->of_node, "rcar_sound,ssi")
|
|
|
|
int rsnd_ssi_use_busif(struct rsnd_dai_stream *io, struct rsnd_mod *mod)
|
|
{
|
|
struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
|
|
int use_busif = 0;
|
|
|
|
if (!rsnd_ssi_is_dma_mode(mod))
|
|
return 0;
|
|
|
|
if (!(rsnd_ssi_mode_flags(ssi) & RSND_SSI_NO_BUSIF))
|
|
use_busif = 1;
|
|
if (rsnd_io_to_mod_src(io))
|
|
use_busif = 1;
|
|
|
|
return use_busif;
|
|
}
|
|
|
|
static void rsnd_ssi_status_check(struct rsnd_mod *mod,
|
|
u32 bit)
|
|
{
|
|
struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
|
|
struct device *dev = rsnd_priv_to_dev(priv);
|
|
u32 status;
|
|
int i;
|
|
|
|
for (i = 0; i < 1024; i++) {
|
|
status = rsnd_mod_read(mod, SSISR);
|
|
if (status & bit)
|
|
return;
|
|
|
|
udelay(50);
|
|
}
|
|
|
|
dev_warn(dev, "status check failed\n");
|
|
}
|
|
|
|
static int rsnd_ssi_master_clk_start(struct rsnd_ssi *ssi,
|
|
struct rsnd_dai_stream *io)
|
|
{
|
|
struct rsnd_priv *priv = rsnd_io_to_priv(io);
|
|
struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io);
|
|
struct device *dev = rsnd_priv_to_dev(priv);
|
|
struct rsnd_mod *mod = rsnd_mod_get(ssi);
|
|
int j, ret;
|
|
int ssi_clk_mul_table[] = {
|
|
1, 2, 4, 8, 16, 6, 12,
|
|
};
|
|
unsigned int main_rate;
|
|
unsigned int rate = rsnd_src_get_ssi_rate(priv, io, runtime);
|
|
|
|
/*
|
|
* Find best clock, and try to start ADG
|
|
*/
|
|
for (j = 0; j < ARRAY_SIZE(ssi_clk_mul_table); j++) {
|
|
|
|
/*
|
|
* this driver is assuming that
|
|
* system word is 64fs (= 2 x 32bit)
|
|
* see rsnd_ssi_init()
|
|
*/
|
|
main_rate = rate * 32 * 2 * ssi_clk_mul_table[j];
|
|
|
|
ret = rsnd_adg_ssi_clk_try_start(mod, main_rate);
|
|
if (0 == ret) {
|
|
ssi->cr_clk = FORCE | SWL_32 |
|
|
SCKD | SWSD | CKDV(j);
|
|
|
|
dev_dbg(dev, "%s[%d] outputs %u Hz\n",
|
|
rsnd_mod_name(mod),
|
|
rsnd_mod_id(mod), rate);
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
dev_err(dev, "unsupported clock rate\n");
|
|
return -EIO;
|
|
}
|
|
|
|
static void rsnd_ssi_master_clk_stop(struct rsnd_ssi *ssi)
|
|
{
|
|
struct rsnd_mod *mod = rsnd_mod_get(ssi);
|
|
|
|
ssi->cr_clk = 0;
|
|
rsnd_adg_ssi_clk_stop(mod);
|
|
}
|
|
|
|
static void rsnd_ssi_hw_start(struct rsnd_ssi *ssi,
|
|
struct rsnd_dai_stream *io)
|
|
{
|
|
struct rsnd_priv *priv = rsnd_io_to_priv(io);
|
|
struct rsnd_dai *rdai = rsnd_io_to_rdai(io);
|
|
struct device *dev = rsnd_priv_to_dev(priv);
|
|
struct rsnd_mod *mod = rsnd_mod_get(ssi);
|
|
u32 cr_mode;
|
|
u32 cr;
|
|
|
|
if (0 == ssi->usrcnt) {
|
|
rsnd_mod_hw_start(mod);
|
|
|
|
if (rsnd_rdai_is_clk_master(rdai)) {
|
|
struct rsnd_ssi *ssi_parent = rsnd_ssi_parent(ssi);
|
|
|
|
if (ssi_parent)
|
|
rsnd_ssi_hw_start(ssi_parent, io);
|
|
else
|
|
rsnd_ssi_master_clk_start(ssi, io);
|
|
}
|
|
}
|
|
|
|
if (rsnd_ssi_is_dma_mode(mod)) {
|
|
cr_mode = UIEN | OIEN | /* over/under run */
|
|
DMEN; /* DMA : enable DMA */
|
|
} else {
|
|
cr_mode = DIEN; /* PIO : enable Data interrupt */
|
|
}
|
|
|
|
cr = ssi->cr_own |
|
|
ssi->cr_clk |
|
|
cr_mode |
|
|
EN;
|
|
|
|
rsnd_mod_write(mod, SSICR, cr);
|
|
|
|
/* enable WS continue */
|
|
if (rsnd_rdai_is_clk_master(rdai))
|
|
rsnd_mod_write(mod, SSIWSR, CONT);
|
|
|
|
/* clear error status */
|
|
rsnd_mod_write(mod, SSISR, 0);
|
|
|
|
ssi->usrcnt++;
|
|
|
|
dev_dbg(dev, "%s[%d] hw started\n",
|
|
rsnd_mod_name(mod), rsnd_mod_id(mod));
|
|
}
|
|
|
|
static void rsnd_ssi_hw_stop(struct rsnd_dai_stream *io, struct rsnd_ssi *ssi)
|
|
{
|
|
struct rsnd_mod *mod = rsnd_mod_get(ssi);
|
|
struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
|
|
struct rsnd_dai *rdai = rsnd_io_to_rdai(io);
|
|
struct device *dev = rsnd_priv_to_dev(priv);
|
|
u32 cr;
|
|
|
|
if (0 == ssi->usrcnt) {
|
|
dev_err(dev, "%s called without starting\n", __func__);
|
|
return;
|
|
}
|
|
|
|
ssi->usrcnt--;
|
|
|
|
if (0 == ssi->usrcnt) {
|
|
/*
|
|
* disable all IRQ,
|
|
* and, wait all data was sent
|
|
*/
|
|
cr = ssi->cr_own |
|
|
ssi->cr_clk;
|
|
|
|
rsnd_mod_write(mod, SSICR, cr | EN);
|
|
rsnd_ssi_status_check(mod, DIRQ);
|
|
|
|
/*
|
|
* disable SSI,
|
|
* and, wait idle state
|
|
*/
|
|
rsnd_mod_write(mod, SSICR, cr); /* disabled all */
|
|
rsnd_ssi_status_check(mod, IIRQ);
|
|
|
|
if (rsnd_rdai_is_clk_master(rdai)) {
|
|
struct rsnd_ssi *ssi_parent = rsnd_ssi_parent(ssi);
|
|
|
|
if (ssi_parent)
|
|
rsnd_ssi_hw_stop(io, ssi_parent);
|
|
else
|
|
rsnd_ssi_master_clk_stop(ssi);
|
|
}
|
|
|
|
rsnd_mod_hw_stop(mod);
|
|
|
|
ssi->chan = 0;
|
|
}
|
|
|
|
dev_dbg(dev, "%s[%d] hw stopped\n",
|
|
rsnd_mod_name(mod), rsnd_mod_id(mod));
|
|
}
|
|
|
|
/*
|
|
* SSI mod common functions
|
|
*/
|
|
static int rsnd_ssi_init(struct rsnd_mod *mod,
|
|
struct rsnd_dai_stream *io,
|
|
struct rsnd_priv *priv)
|
|
{
|
|
struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
|
|
struct rsnd_dai *rdai = rsnd_io_to_rdai(io);
|
|
struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io);
|
|
u32 cr;
|
|
|
|
cr = FORCE;
|
|
|
|
/*
|
|
* always use 32bit system word for easy clock calculation.
|
|
* see also rsnd_ssi_master_clk_enable()
|
|
*/
|
|
cr |= SWL_32;
|
|
|
|
/*
|
|
* init clock settings for SSICR
|
|
*/
|
|
switch (runtime->sample_bits) {
|
|
case 16:
|
|
cr |= DWL_16;
|
|
break;
|
|
case 32:
|
|
cr |= DWL_24;
|
|
break;
|
|
default:
|
|
return -EIO;
|
|
}
|
|
|
|
if (rdai->bit_clk_inv)
|
|
cr |= SCKP;
|
|
if (rdai->frm_clk_inv)
|
|
cr |= SWSP;
|
|
if (rdai->data_alignment)
|
|
cr |= SDTA;
|
|
if (rdai->sys_delay)
|
|
cr |= DEL;
|
|
if (rsnd_io_is_play(io))
|
|
cr |= TRMD;
|
|
|
|
/*
|
|
* set ssi parameter
|
|
*/
|
|
ssi->cr_own = cr;
|
|
ssi->err = -1; /* ignore 1st error */
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rsnd_ssi_quit(struct rsnd_mod *mod,
|
|
struct rsnd_dai_stream *io,
|
|
struct rsnd_priv *priv)
|
|
{
|
|
struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
|
|
struct device *dev = rsnd_priv_to_dev(priv);
|
|
|
|
if (ssi->err > 0)
|
|
dev_warn(dev, "%s[%d] under/over flow err = %d\n",
|
|
rsnd_mod_name(mod), rsnd_mod_id(mod), ssi->err);
|
|
|
|
ssi->cr_own = 0;
|
|
ssi->err = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rsnd_ssi_hw_params(struct rsnd_mod *mod,
|
|
struct rsnd_dai_stream *io,
|
|
struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params)
|
|
{
|
|
struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
|
|
struct rsnd_ssi *ssi_parent = rsnd_ssi_parent(ssi);
|
|
int chan = params_channels(params);
|
|
|
|
/*
|
|
* Already working.
|
|
* It will happen if SSI has parent/child connection.
|
|
*/
|
|
if (ssi->usrcnt) {
|
|
/*
|
|
* it is error if child <-> parent SSI uses
|
|
* different channels.
|
|
*/
|
|
if (ssi->chan != chan)
|
|
return -EIO;
|
|
}
|
|
|
|
/* It will be removed on rsnd_ssi_hw_stop */
|
|
ssi->chan = chan;
|
|
if (ssi_parent)
|
|
return rsnd_ssi_hw_params(rsnd_mod_get(ssi_parent), io,
|
|
substream, params);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void rsnd_ssi_record_error(struct rsnd_ssi *ssi, u32 status)
|
|
{
|
|
struct rsnd_mod *mod = rsnd_mod_get(ssi);
|
|
|
|
/* under/over flow error */
|
|
if (status & (UIRQ | OIRQ)) {
|
|
ssi->err++;
|
|
|
|
/* clear error status */
|
|
rsnd_mod_write(mod, SSISR, 0);
|
|
}
|
|
}
|
|
|
|
static int rsnd_ssi_start(struct rsnd_mod *mod,
|
|
struct rsnd_dai_stream *io,
|
|
struct rsnd_priv *priv)
|
|
{
|
|
struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
|
|
|
|
rsnd_src_ssiu_start(mod, io, rsnd_ssi_use_busif(io, mod));
|
|
|
|
rsnd_ssi_hw_start(ssi, io);
|
|
|
|
rsnd_src_ssi_irq_enable(mod);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rsnd_ssi_stop(struct rsnd_mod *mod,
|
|
struct rsnd_dai_stream *io,
|
|
struct rsnd_priv *priv)
|
|
{
|
|
struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
|
|
|
|
rsnd_src_ssi_irq_disable(mod);
|
|
|
|
rsnd_ssi_record_error(ssi, rsnd_mod_read(mod, SSISR));
|
|
|
|
rsnd_ssi_hw_stop(io, ssi);
|
|
|
|
rsnd_src_ssiu_stop(mod, io);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __rsnd_ssi_interrupt(struct rsnd_mod *mod,
|
|
struct rsnd_dai_stream *io)
|
|
{
|
|
struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
|
|
struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
|
|
int is_dma = rsnd_ssi_is_dma_mode(mod);
|
|
u32 status;
|
|
bool elapsed = false;
|
|
|
|
spin_lock(&priv->lock);
|
|
|
|
/* ignore all cases if not working */
|
|
if (!rsnd_io_is_working(io))
|
|
goto rsnd_ssi_interrupt_out;
|
|
|
|
status = rsnd_mod_read(mod, SSISR);
|
|
|
|
/* PIO only */
|
|
if (!is_dma && (status & DIRQ)) {
|
|
struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io);
|
|
u32 *buf = (u32 *)(runtime->dma_area +
|
|
rsnd_dai_pointer_offset(io, 0));
|
|
|
|
/*
|
|
* 8/16/32 data can be assesse to TDR/RDR register
|
|
* directly as 32bit data
|
|
* see rsnd_ssi_init()
|
|
*/
|
|
if (rsnd_io_is_play(io))
|
|
rsnd_mod_write(mod, SSITDR, *buf);
|
|
else
|
|
*buf = rsnd_mod_read(mod, SSIRDR);
|
|
|
|
elapsed = rsnd_dai_pointer_update(io, sizeof(*buf));
|
|
}
|
|
|
|
/* DMA only */
|
|
if (is_dma && (status & (UIRQ | OIRQ))) {
|
|
struct device *dev = rsnd_priv_to_dev(priv);
|
|
|
|
/*
|
|
* restart SSI
|
|
*/
|
|
dev_dbg(dev, "%s[%d] restart\n",
|
|
rsnd_mod_name(mod), rsnd_mod_id(mod));
|
|
|
|
rsnd_ssi_stop(mod, io, priv);
|
|
if (ssi->err < 1024)
|
|
rsnd_ssi_start(mod, io, priv);
|
|
else
|
|
dev_warn(dev, "no more SSI restart\n");
|
|
}
|
|
|
|
rsnd_ssi_record_error(ssi, status);
|
|
|
|
rsnd_ssi_interrupt_out:
|
|
spin_unlock(&priv->lock);
|
|
|
|
if (elapsed)
|
|
rsnd_dai_period_elapsed(io);
|
|
}
|
|
|
|
static irqreturn_t rsnd_ssi_interrupt(int irq, void *data)
|
|
{
|
|
struct rsnd_mod *mod = data;
|
|
|
|
rsnd_mod_interrupt(mod, __rsnd_ssi_interrupt);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/*
|
|
* SSI PIO
|
|
*/
|
|
static int rsnd_ssi_pio_probe(struct rsnd_mod *mod,
|
|
struct rsnd_dai_stream *io,
|
|
struct rsnd_priv *priv)
|
|
{
|
|
struct device *dev = rsnd_priv_to_dev(priv);
|
|
struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
|
|
int ret;
|
|
|
|
ret = devm_request_irq(dev, ssi->info->irq,
|
|
rsnd_ssi_interrupt,
|
|
IRQF_SHARED,
|
|
dev_name(dev), mod);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct rsnd_mod_ops rsnd_ssi_pio_ops = {
|
|
.name = SSI_NAME,
|
|
.probe = rsnd_ssi_pio_probe,
|
|
.init = rsnd_ssi_init,
|
|
.quit = rsnd_ssi_quit,
|
|
.start = rsnd_ssi_start,
|
|
.stop = rsnd_ssi_stop,
|
|
.hw_params = rsnd_ssi_hw_params,
|
|
};
|
|
|
|
static int rsnd_ssi_dma_probe(struct rsnd_mod *mod,
|
|
struct rsnd_dai_stream *io,
|
|
struct rsnd_priv *priv)
|
|
{
|
|
struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
|
|
struct device *dev = rsnd_priv_to_dev(priv);
|
|
int dma_id = ssi->info->dma_id;
|
|
int ret;
|
|
|
|
ret = devm_request_irq(dev, ssi->info->irq,
|
|
rsnd_ssi_interrupt,
|
|
IRQF_SHARED,
|
|
dev_name(dev), mod);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = rsnd_dma_init(
|
|
io, rsnd_mod_to_dma(mod),
|
|
dma_id);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int rsnd_ssi_dma_remove(struct rsnd_mod *mod,
|
|
struct rsnd_dai_stream *io,
|
|
struct rsnd_priv *priv)
|
|
{
|
|
struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
|
|
struct device *dev = rsnd_priv_to_dev(priv);
|
|
int irq = ssi->info->irq;
|
|
|
|
rsnd_dma_quit(io, rsnd_mod_to_dma(mod));
|
|
|
|
/* PIO will request IRQ again */
|
|
devm_free_irq(dev, irq, ssi);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rsnd_ssi_fallback(struct rsnd_mod *mod,
|
|
struct rsnd_dai_stream *io,
|
|
struct rsnd_priv *priv)
|
|
{
|
|
struct device *dev = rsnd_priv_to_dev(priv);
|
|
|
|
/*
|
|
* fallback to PIO
|
|
*
|
|
* SSI .probe might be called again.
|
|
* see
|
|
* rsnd_rdai_continuance_probe()
|
|
*/
|
|
mod->ops = &rsnd_ssi_pio_ops;
|
|
|
|
dev_info(dev, "%s[%d] fallback to PIO mode\n",
|
|
rsnd_mod_name(mod), rsnd_mod_id(mod));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rsnd_ssi_dma_start(struct rsnd_mod *mod,
|
|
struct rsnd_dai_stream *io,
|
|
struct rsnd_priv *priv)
|
|
{
|
|
struct rsnd_dma *dma = rsnd_mod_to_dma(mod);
|
|
|
|
rsnd_dma_start(io, dma);
|
|
|
|
rsnd_ssi_start(mod, io, priv);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rsnd_ssi_dma_stop(struct rsnd_mod *mod,
|
|
struct rsnd_dai_stream *io,
|
|
struct rsnd_priv *priv)
|
|
{
|
|
struct rsnd_dma *dma = rsnd_mod_to_dma(mod);
|
|
|
|
rsnd_ssi_stop(mod, io, priv);
|
|
|
|
rsnd_dma_stop(io, dma);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct dma_chan *rsnd_ssi_dma_req(struct rsnd_dai_stream *io,
|
|
struct rsnd_mod *mod)
|
|
{
|
|
struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
|
|
int is_play = rsnd_io_is_play(io);
|
|
char *name;
|
|
|
|
if (rsnd_ssi_use_busif(io, mod))
|
|
name = is_play ? "rxu" : "txu";
|
|
else
|
|
name = is_play ? "rx" : "tx";
|
|
|
|
return rsnd_dma_request_channel(rsnd_ssi_of_node(priv),
|
|
mod, name);
|
|
}
|
|
|
|
static struct rsnd_mod_ops rsnd_ssi_dma_ops = {
|
|
.name = SSI_NAME,
|
|
.dma_req = rsnd_ssi_dma_req,
|
|
.probe = rsnd_ssi_dma_probe,
|
|
.remove = rsnd_ssi_dma_remove,
|
|
.init = rsnd_ssi_init,
|
|
.quit = rsnd_ssi_quit,
|
|
.start = rsnd_ssi_dma_start,
|
|
.stop = rsnd_ssi_dma_stop,
|
|
.fallback = rsnd_ssi_fallback,
|
|
.hw_params = rsnd_ssi_hw_params,
|
|
};
|
|
|
|
int rsnd_ssi_is_dma_mode(struct rsnd_mod *mod)
|
|
{
|
|
return mod->ops == &rsnd_ssi_dma_ops;
|
|
}
|
|
|
|
|
|
/*
|
|
* Non SSI
|
|
*/
|
|
static struct rsnd_mod_ops rsnd_ssi_non_ops = {
|
|
.name = SSI_NAME,
|
|
};
|
|
|
|
/*
|
|
* ssi mod function
|
|
*/
|
|
struct rsnd_mod *rsnd_ssi_mod_get(struct rsnd_priv *priv, int id)
|
|
{
|
|
if (WARN_ON(id < 0 || id >= rsnd_ssi_nr(priv)))
|
|
id = 0;
|
|
|
|
return rsnd_mod_get((struct rsnd_ssi *)(priv->ssi) + id);
|
|
}
|
|
|
|
int rsnd_ssi_is_pin_sharing(struct rsnd_mod *mod)
|
|
{
|
|
struct rsnd_ssi *ssi = rsnd_mod_to_ssi(mod);
|
|
|
|
return !!(rsnd_ssi_mode_flags(ssi) & RSND_SSI_CLK_PIN_SHARE);
|
|
}
|
|
|
|
static void rsnd_ssi_parent_setup(struct rsnd_priv *priv, struct rsnd_ssi *ssi)
|
|
{
|
|
struct rsnd_mod *mod = rsnd_mod_get(ssi);
|
|
|
|
if (!rsnd_ssi_is_pin_sharing(mod))
|
|
return;
|
|
|
|
switch (rsnd_mod_id(mod)) {
|
|
case 1:
|
|
case 2:
|
|
ssi->parent = rsnd_mod_to_ssi(rsnd_ssi_mod_get(priv, 0));
|
|
break;
|
|
case 4:
|
|
ssi->parent = rsnd_mod_to_ssi(rsnd_ssi_mod_get(priv, 3));
|
|
break;
|
|
case 8:
|
|
ssi->parent = rsnd_mod_to_ssi(rsnd_ssi_mod_get(priv, 7));
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void rsnd_of_parse_ssi(struct platform_device *pdev,
|
|
const struct rsnd_of_data *of_data,
|
|
struct rsnd_priv *priv)
|
|
{
|
|
struct device_node *node;
|
|
struct device_node *np;
|
|
struct rsnd_ssi_platform_info *ssi_info;
|
|
struct rcar_snd_info *info = rsnd_priv_to_info(priv);
|
|
struct device *dev = &pdev->dev;
|
|
int nr, i;
|
|
|
|
if (!of_data)
|
|
return;
|
|
|
|
node = rsnd_ssi_of_node(priv);
|
|
if (!node)
|
|
return;
|
|
|
|
nr = of_get_child_count(node);
|
|
if (!nr)
|
|
goto rsnd_of_parse_ssi_end;
|
|
|
|
ssi_info = devm_kzalloc(dev,
|
|
sizeof(struct rsnd_ssi_platform_info) * nr,
|
|
GFP_KERNEL);
|
|
if (!ssi_info) {
|
|
dev_err(dev, "ssi info allocation error\n");
|
|
goto rsnd_of_parse_ssi_end;
|
|
}
|
|
|
|
info->ssi_info = ssi_info;
|
|
info->ssi_info_nr = nr;
|
|
|
|
i = -1;
|
|
for_each_child_of_node(node, np) {
|
|
i++;
|
|
|
|
ssi_info = info->ssi_info + i;
|
|
|
|
/*
|
|
* pin settings
|
|
*/
|
|
if (of_get_property(np, "shared-pin", NULL))
|
|
ssi_info->flags |= RSND_SSI_CLK_PIN_SHARE;
|
|
|
|
/*
|
|
* irq
|
|
*/
|
|
ssi_info->irq = irq_of_parse_and_map(np, 0);
|
|
|
|
/*
|
|
* DMA
|
|
*/
|
|
ssi_info->dma_id = of_get_property(np, "pio-transfer", NULL) ?
|
|
0 : 1;
|
|
|
|
if (of_get_property(np, "no-busif", NULL))
|
|
ssi_info->flags |= RSND_SSI_NO_BUSIF;
|
|
}
|
|
|
|
rsnd_of_parse_ssi_end:
|
|
of_node_put(node);
|
|
}
|
|
|
|
int rsnd_ssi_probe(struct platform_device *pdev,
|
|
const struct rsnd_of_data *of_data,
|
|
struct rsnd_priv *priv)
|
|
{
|
|
struct rcar_snd_info *info = rsnd_priv_to_info(priv);
|
|
struct rsnd_ssi_platform_info *pinfo;
|
|
struct device *dev = rsnd_priv_to_dev(priv);
|
|
struct rsnd_mod_ops *ops;
|
|
struct clk *clk;
|
|
struct rsnd_ssi *ssi;
|
|
char name[RSND_SSI_NAME_SIZE];
|
|
int i, nr, ret;
|
|
|
|
rsnd_of_parse_ssi(pdev, of_data, priv);
|
|
|
|
/*
|
|
* init SSI
|
|
*/
|
|
nr = info->ssi_info_nr;
|
|
ssi = devm_kzalloc(dev, sizeof(*ssi) * nr, GFP_KERNEL);
|
|
if (!ssi)
|
|
return -ENOMEM;
|
|
|
|
priv->ssi = ssi;
|
|
priv->ssi_nr = nr;
|
|
|
|
for_each_rsnd_ssi(ssi, priv, i) {
|
|
pinfo = &info->ssi_info[i];
|
|
|
|
snprintf(name, RSND_SSI_NAME_SIZE, "%s.%d",
|
|
SSI_NAME, i);
|
|
|
|
clk = devm_clk_get(dev, name);
|
|
if (IS_ERR(clk))
|
|
return PTR_ERR(clk);
|
|
|
|
ssi->info = pinfo;
|
|
|
|
ops = &rsnd_ssi_non_ops;
|
|
if (pinfo->dma_id > 0)
|
|
ops = &rsnd_ssi_dma_ops;
|
|
else if (rsnd_ssi_pio_available(ssi))
|
|
ops = &rsnd_ssi_pio_ops;
|
|
|
|
ret = rsnd_mod_init(priv, rsnd_mod_get(ssi), ops, clk,
|
|
RSND_MOD_SSI, i);
|
|
if (ret)
|
|
return ret;
|
|
|
|
rsnd_ssi_parent_setup(priv, ssi);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void rsnd_ssi_remove(struct platform_device *pdev,
|
|
struct rsnd_priv *priv)
|
|
{
|
|
struct rsnd_ssi *ssi;
|
|
int i;
|
|
|
|
for_each_rsnd_ssi(ssi, priv, i) {
|
|
rsnd_mod_quit(rsnd_mod_get(ssi));
|
|
}
|
|
}
|