2015-02-20 18:25:55 +08:00
|
|
|
/*
|
|
|
|
* Renesas R-Car Audio DMAC support
|
|
|
|
*
|
|
|
|
* Copyright (C) 2015 Renesas Electronics Corp.
|
|
|
|
* Copyright (c) 2015 Kuninori Morimoto <kuninori.morimoto.gx@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 "rsnd.h"
|
|
|
|
|
2015-02-20 18:27:12 +08:00
|
|
|
static void rsnd_dmaen_complete(void *data)
|
2015-02-20 18:25:55 +08:00
|
|
|
{
|
|
|
|
struct rsnd_dma *dma = (struct rsnd_dma *)data;
|
|
|
|
struct rsnd_mod *mod = rsnd_dma_to_mod(dma);
|
|
|
|
struct rsnd_dai_stream *io = rsnd_mod_to_io(mod);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Renesas sound Gen1 needs 1 DMAC,
|
|
|
|
* Gen2 needs 2 DMAC.
|
|
|
|
* In Gen2 case, it are Audio-DMAC, and Audio-DMAC-peri-peri.
|
|
|
|
* But, Audio-DMAC-peri-peri doesn't have interrupt,
|
|
|
|
* and this driver is assuming that here.
|
|
|
|
*
|
|
|
|
* If Audio-DMAC-peri-peri has interrpt,
|
|
|
|
* rsnd_dai_pointer_update() will be called twice,
|
|
|
|
* ant it will breaks io->byte_pos
|
|
|
|
*/
|
|
|
|
|
|
|
|
rsnd_dai_pointer_update(io, io->byte_per_period);
|
|
|
|
}
|
|
|
|
|
|
|
|
#define DMA_NAME_SIZE 16
|
2015-02-20 18:27:12 +08:00
|
|
|
static int _rsnd_dmaen_of_name(char *dma_name, struct rsnd_mod *mod)
|
2015-02-20 18:25:55 +08:00
|
|
|
{
|
|
|
|
if (mod)
|
|
|
|
return snprintf(dma_name, DMA_NAME_SIZE / 2, "%s%d",
|
|
|
|
rsnd_mod_dma_name(mod), rsnd_mod_id(mod));
|
|
|
|
else
|
|
|
|
return snprintf(dma_name, DMA_NAME_SIZE / 2, "mem");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2015-02-20 18:27:12 +08:00
|
|
|
static void rsnd_dmaen_of_name(struct rsnd_mod *mod_from,
|
2015-02-20 18:25:55 +08:00
|
|
|
struct rsnd_mod *mod_to,
|
|
|
|
char *dma_name)
|
|
|
|
{
|
|
|
|
int index = 0;
|
|
|
|
|
2015-02-20 18:27:12 +08:00
|
|
|
index = _rsnd_dmaen_of_name(dma_name + index, mod_from);
|
2015-02-20 18:25:55 +08:00
|
|
|
*(dma_name + index++) = '_';
|
2015-02-20 18:27:12 +08:00
|
|
|
index = _rsnd_dmaen_of_name(dma_name + index, mod_to);
|
2015-02-20 18:25:55 +08:00
|
|
|
}
|
|
|
|
|
2015-02-20 18:27:12 +08:00
|
|
|
static void rsnd_dmaen_stop(struct rsnd_dma *dma)
|
2015-02-20 18:25:55 +08:00
|
|
|
{
|
|
|
|
dmaengine_terminate_all(dma->chan);
|
|
|
|
}
|
|
|
|
|
2015-02-20 18:27:12 +08:00
|
|
|
static void rsnd_dmaen_start(struct rsnd_dma *dma)
|
2015-02-20 18:25:55 +08:00
|
|
|
{
|
|
|
|
struct rsnd_mod *mod = rsnd_dma_to_mod(dma);
|
|
|
|
struct rsnd_priv *priv = rsnd_mod_to_priv(mod);
|
|
|
|
struct rsnd_dai_stream *io = rsnd_mod_to_io(mod);
|
|
|
|
struct snd_pcm_substream *substream = io->substream;
|
|
|
|
struct device *dev = rsnd_priv_to_dev(priv);
|
|
|
|
struct dma_async_tx_descriptor *desc;
|
|
|
|
|
|
|
|
desc = dmaengine_prep_dma_cyclic(dma->chan,
|
|
|
|
(dma->addr) ? dma->addr :
|
|
|
|
substream->runtime->dma_addr,
|
|
|
|
snd_pcm_lib_buffer_bytes(substream),
|
|
|
|
snd_pcm_lib_period_bytes(substream),
|
|
|
|
dma->dir,
|
|
|
|
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
|
|
|
|
|
|
|
|
if (!desc) {
|
|
|
|
dev_err(dev, "dmaengine_prep_slave_sg() fail\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-02-20 18:27:12 +08:00
|
|
|
desc->callback = rsnd_dmaen_complete;
|
2015-02-20 18:25:55 +08:00
|
|
|
desc->callback_param = dma;
|
|
|
|
|
|
|
|
if (dmaengine_submit(desc) < 0) {
|
|
|
|
dev_err(dev, "dmaengine_submit() fail\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
dma_async_issue_pending(dma->chan);
|
|
|
|
}
|
|
|
|
|
2015-02-20 18:27:12 +08:00
|
|
|
static int rsnd_dmaen_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id,
|
|
|
|
struct rsnd_mod *mod_from, struct rsnd_mod *mod_to)
|
2015-02-20 18:25:55 +08:00
|
|
|
{
|
|
|
|
struct device *dev = rsnd_priv_to_dev(priv);
|
|
|
|
struct dma_slave_config cfg = {};
|
|
|
|
struct rsnd_mod *mod = rsnd_dma_to_mod(dma);
|
|
|
|
struct rsnd_dai_stream *io = rsnd_mod_to_io(mod);
|
|
|
|
int is_play = rsnd_io_is_play(io);
|
|
|
|
char dma_name[DMA_NAME_SIZE];
|
|
|
|
dma_cap_mask_t mask;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (dma->chan) {
|
|
|
|
dev_err(dev, "it already has dma channel\n");
|
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
dma_cap_zero(mask);
|
|
|
|
dma_cap_set(DMA_SLAVE, mask);
|
|
|
|
|
2015-02-20 18:27:12 +08:00
|
|
|
rsnd_dmaen_of_name(mod_from, mod_to, dma_name);
|
2015-02-20 18:25:55 +08:00
|
|
|
|
|
|
|
cfg.direction = is_play ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM;
|
2015-02-20 18:27:12 +08:00
|
|
|
cfg.src_addr = dma->src_addr;
|
|
|
|
cfg.dst_addr = dma->dst_addr;
|
2015-02-20 18:25:55 +08:00
|
|
|
cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
|
|
|
cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
|
|
|
|
|
|
|
dev_dbg(dev, "dma : %s %pad -> %pad\n",
|
|
|
|
dma_name, &cfg.src_addr, &cfg.dst_addr);
|
|
|
|
|
|
|
|
dma->chan = dma_request_slave_channel_compat(mask, shdma_chan_filter,
|
|
|
|
(void *)id, dev,
|
|
|
|
dma_name);
|
|
|
|
if (!dma->chan) {
|
|
|
|
dev_err(dev, "can't get dma channel\n");
|
|
|
|
goto rsnd_dma_channel_err;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = dmaengine_slave_config(dma->chan, &cfg);
|
|
|
|
if (ret < 0)
|
|
|
|
goto rsnd_dma_init_err;
|
|
|
|
|
|
|
|
dma->addr = is_play ? cfg.src_addr : cfg.dst_addr;
|
|
|
|
dma->dir = is_play ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
rsnd_dma_init_err:
|
|
|
|
rsnd_dma_quit(dma);
|
|
|
|
rsnd_dma_channel_err:
|
|
|
|
|
|
|
|
/*
|
|
|
|
* DMA failed. try to PIO mode
|
|
|
|
* see
|
|
|
|
* rsnd_ssi_fallback()
|
|
|
|
* rsnd_rdai_continuance_probe()
|
|
|
|
*/
|
|
|
|
return -EAGAIN;
|
|
|
|
}
|
|
|
|
|
2015-02-20 18:27:12 +08:00
|
|
|
static void rsnd_dmaen_quit(struct rsnd_dma *dma)
|
2015-02-20 18:25:55 +08:00
|
|
|
{
|
|
|
|
if (dma->chan)
|
|
|
|
dma_release_channel(dma->chan);
|
|
|
|
|
|
|
|
dma->chan = NULL;
|
|
|
|
}
|
|
|
|
|
2015-02-20 18:27:12 +08:00
|
|
|
static struct rsnd_dma_ops rsnd_dmaen_ops = {
|
|
|
|
.start = rsnd_dmaen_start,
|
|
|
|
.stop = rsnd_dmaen_stop,
|
|
|
|
.init = rsnd_dmaen_init,
|
|
|
|
.quit = rsnd_dmaen_quit,
|
|
|
|
};
|
|
|
|
|
2015-02-20 18:26:29 +08:00
|
|
|
/*
|
|
|
|
* DMA read/write register offset
|
|
|
|
*
|
|
|
|
* RSND_xxx_I_N for Audio DMAC input
|
|
|
|
* RSND_xxx_O_N for Audio DMAC output
|
|
|
|
* RSND_xxx_I_P for Audio DMAC peri peri input
|
|
|
|
* RSND_xxx_O_P for Audio DMAC peri peri output
|
|
|
|
*
|
|
|
|
* ex) R-Car H2 case
|
|
|
|
* mod / DMAC in / DMAC out / DMAC PP in / DMAC pp out
|
|
|
|
* SSI : 0xec541000 / 0xec241008 / 0xec24100c
|
|
|
|
* SSIU: 0xec541000 / 0xec100000 / 0xec100000 / 0xec400000 / 0xec400000
|
|
|
|
* SCU : 0xec500000 / 0xec000000 / 0xec004000 / 0xec300000 / 0xec304000
|
|
|
|
* CMD : 0xec500000 / / 0xec008000 0xec308000
|
|
|
|
*/
|
|
|
|
#define RDMA_SSI_I_N(addr, i) (addr ##_reg - 0x00300000 + (0x40 * i) + 0x8)
|
|
|
|
#define RDMA_SSI_O_N(addr, i) (addr ##_reg - 0x00300000 + (0x40 * i) + 0xc)
|
|
|
|
|
|
|
|
#define RDMA_SSIU_I_N(addr, i) (addr ##_reg - 0x00441000 + (0x1000 * i))
|
|
|
|
#define RDMA_SSIU_O_N(addr, i) (addr ##_reg - 0x00441000 + (0x1000 * i))
|
|
|
|
|
|
|
|
#define RDMA_SSIU_I_P(addr, i) (addr ##_reg - 0x00141000 + (0x1000 * i))
|
|
|
|
#define RDMA_SSIU_O_P(addr, i) (addr ##_reg - 0x00141000 + (0x1000 * i))
|
|
|
|
|
|
|
|
#define RDMA_SRC_I_N(addr, i) (addr ##_reg - 0x00500000 + (0x400 * i))
|
|
|
|
#define RDMA_SRC_O_N(addr, i) (addr ##_reg - 0x004fc000 + (0x400 * i))
|
|
|
|
|
|
|
|
#define RDMA_SRC_I_P(addr, i) (addr ##_reg - 0x00200000 + (0x400 * i))
|
|
|
|
#define RDMA_SRC_O_P(addr, i) (addr ##_reg - 0x001fc000 + (0x400 * i))
|
|
|
|
|
|
|
|
#define RDMA_CMD_O_N(addr, i) (addr ##_reg - 0x004f8000 + (0x400 * i))
|
|
|
|
#define RDMA_CMD_O_P(addr, i) (addr ##_reg - 0x001f8000 + (0x400 * i))
|
|
|
|
|
|
|
|
static dma_addr_t
|
|
|
|
rsnd_gen2_dma_addr(struct rsnd_priv *priv,
|
|
|
|
struct rsnd_mod *mod,
|
|
|
|
int is_play, int is_from)
|
|
|
|
{
|
|
|
|
struct device *dev = rsnd_priv_to_dev(priv);
|
|
|
|
struct rsnd_dai_stream *io = rsnd_mod_to_io(mod);
|
|
|
|
phys_addr_t ssi_reg = rsnd_gen_get_phy_addr(priv, RSND_GEN2_SSI);
|
|
|
|
phys_addr_t src_reg = rsnd_gen_get_phy_addr(priv, RSND_GEN2_SCU);
|
|
|
|
int is_ssi = !!(rsnd_io_to_mod_ssi(io) == mod);
|
|
|
|
int use_src = !!rsnd_io_to_mod_src(io);
|
|
|
|
int use_dvc = !!rsnd_io_to_mod_dvc(io);
|
|
|
|
int id = rsnd_mod_id(mod);
|
|
|
|
struct dma_addr {
|
|
|
|
dma_addr_t out_addr;
|
|
|
|
dma_addr_t in_addr;
|
|
|
|
} dma_addrs[3][2][3] = {
|
|
|
|
/* SRC */
|
|
|
|
{{{ 0, 0 },
|
|
|
|
/* Capture */
|
|
|
|
{ RDMA_SRC_O_N(src, id), RDMA_SRC_I_P(src, id) },
|
|
|
|
{ RDMA_CMD_O_N(src, id), RDMA_SRC_I_P(src, id) } },
|
|
|
|
/* Playback */
|
|
|
|
{{ 0, 0, },
|
|
|
|
{ RDMA_SRC_O_P(src, id), RDMA_SRC_I_N(src, id) },
|
|
|
|
{ RDMA_CMD_O_P(src, id), RDMA_SRC_I_N(src, id) } }
|
|
|
|
},
|
|
|
|
/* SSI */
|
|
|
|
/* Capture */
|
|
|
|
{{{ RDMA_SSI_O_N(ssi, id), 0 },
|
|
|
|
{ RDMA_SSIU_O_P(ssi, id), 0 },
|
|
|
|
{ RDMA_SSIU_O_P(ssi, id), 0 } },
|
|
|
|
/* Playback */
|
|
|
|
{{ 0, RDMA_SSI_I_N(ssi, id) },
|
|
|
|
{ 0, RDMA_SSIU_I_P(ssi, id) },
|
|
|
|
{ 0, RDMA_SSIU_I_P(ssi, id) } }
|
|
|
|
},
|
|
|
|
/* SSIU */
|
|
|
|
/* Capture */
|
|
|
|
{{{ RDMA_SSIU_O_N(ssi, id), 0 },
|
|
|
|
{ RDMA_SSIU_O_P(ssi, id), 0 },
|
|
|
|
{ RDMA_SSIU_O_P(ssi, id), 0 } },
|
|
|
|
/* Playback */
|
|
|
|
{{ 0, RDMA_SSIU_I_N(ssi, id) },
|
|
|
|
{ 0, RDMA_SSIU_I_P(ssi, id) },
|
|
|
|
{ 0, RDMA_SSIU_I_P(ssi, id) } } },
|
|
|
|
};
|
|
|
|
|
|
|
|
/* it shouldn't happen */
|
|
|
|
if (use_dvc && !use_src)
|
|
|
|
dev_err(dev, "DVC is selected without SRC\n");
|
|
|
|
|
|
|
|
/* use SSIU or SSI ? */
|
|
|
|
if (is_ssi && (0 == strcmp(rsnd_mod_dma_name(mod), "ssiu")))
|
|
|
|
is_ssi++;
|
|
|
|
|
|
|
|
return (is_from) ?
|
|
|
|
dma_addrs[is_ssi][is_play][use_src + use_dvc].out_addr :
|
|
|
|
dma_addrs[is_ssi][is_play][use_src + use_dvc].in_addr;
|
|
|
|
}
|
|
|
|
|
|
|
|
static dma_addr_t rsnd_dma_addr(struct rsnd_priv *priv,
|
|
|
|
struct rsnd_mod *mod,
|
|
|
|
int is_play, int is_from)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* gen1 uses default DMA addr
|
|
|
|
*/
|
|
|
|
if (rsnd_is_gen1(priv))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (!mod)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return rsnd_gen2_dma_addr(priv, mod, is_play, is_from);
|
|
|
|
}
|
|
|
|
|
|
|
|
#define MOD_MAX 4 /* MEM/SSI/SRC/DVC */
|
2015-02-20 18:25:55 +08:00
|
|
|
static void rsnd_dma_of_path(struct rsnd_dma *dma,
|
|
|
|
int is_play,
|
|
|
|
struct rsnd_mod **mod_from,
|
|
|
|
struct rsnd_mod **mod_to)
|
|
|
|
{
|
|
|
|
struct rsnd_mod *this = rsnd_dma_to_mod(dma);
|
|
|
|
struct rsnd_dai_stream *io = rsnd_mod_to_io(this);
|
|
|
|
struct rsnd_mod *ssi = rsnd_io_to_mod_ssi(io);
|
|
|
|
struct rsnd_mod *src = rsnd_io_to_mod_src(io);
|
|
|
|
struct rsnd_mod *dvc = rsnd_io_to_mod_dvc(io);
|
|
|
|
struct rsnd_mod *mod[MOD_MAX];
|
|
|
|
int i, index;
|
|
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < MOD_MAX; i++)
|
|
|
|
mod[i] = NULL;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* in play case...
|
|
|
|
*
|
|
|
|
* src -> dst
|
|
|
|
*
|
|
|
|
* mem -> SSI
|
|
|
|
* mem -> SRC -> SSI
|
|
|
|
* mem -> SRC -> DVC -> SSI
|
|
|
|
*/
|
|
|
|
mod[0] = NULL; /* for "mem" */
|
|
|
|
index = 1;
|
|
|
|
for (i = 1; i < MOD_MAX; i++) {
|
|
|
|
if (!src) {
|
|
|
|
mod[i] = ssi;
|
|
|
|
} else if (!dvc) {
|
|
|
|
mod[i] = src;
|
|
|
|
src = NULL;
|
|
|
|
} else {
|
|
|
|
if ((!is_play) && (this == src))
|
|
|
|
this = dvc;
|
|
|
|
|
|
|
|
mod[i] = (is_play) ? src : dvc;
|
|
|
|
i++;
|
|
|
|
mod[i] = (is_play) ? dvc : src;
|
|
|
|
src = NULL;
|
|
|
|
dvc = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mod[i] == this)
|
|
|
|
index = i;
|
|
|
|
|
|
|
|
if (mod[i] == ssi)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is_play) {
|
|
|
|
*mod_from = mod[index - 1];
|
|
|
|
*mod_to = mod[index];
|
|
|
|
} else {
|
|
|
|
*mod_from = mod[index];
|
|
|
|
*mod_to = mod[index - 1];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-20 18:27:12 +08:00
|
|
|
void rsnd_dma_stop(struct rsnd_dma *dma)
|
|
|
|
{
|
|
|
|
dma->ops->stop(dma);
|
|
|
|
}
|
|
|
|
|
|
|
|
void rsnd_dma_start(struct rsnd_dma *dma)
|
|
|
|
{
|
|
|
|
dma->ops->start(dma);
|
|
|
|
}
|
|
|
|
|
|
|
|
void rsnd_dma_quit(struct rsnd_dma *dma)
|
|
|
|
{
|
|
|
|
dma->ops->quit(dma);
|
|
|
|
}
|
|
|
|
|
|
|
|
int rsnd_dma_init(struct rsnd_priv *priv, struct rsnd_dma *dma, int id)
|
|
|
|
{
|
|
|
|
struct rsnd_mod *mod = rsnd_dma_to_mod(dma);
|
|
|
|
struct rsnd_mod *mod_from;
|
|
|
|
struct rsnd_mod *mod_to;
|
|
|
|
struct rsnd_dai_stream *io = rsnd_mod_to_io(mod);
|
|
|
|
int is_play = rsnd_io_is_play(io);
|
|
|
|
|
|
|
|
rsnd_dma_of_path(dma, is_play, &mod_from, &mod_to);
|
|
|
|
|
|
|
|
dma->src_addr = rsnd_dma_addr(priv, mod_from, is_play, 1);
|
|
|
|
dma->dst_addr = rsnd_dma_addr(priv, mod_to, is_play, 0);
|
|
|
|
|
|
|
|
dma->ops = &rsnd_dmaen_ops;
|
|
|
|
|
|
|
|
return dma->ops->init(priv, dma, id, mod_from, mod_to);
|
|
|
|
}
|