diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
commit | ace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch) | |
tree | b2d64bc10158fdd5497876388cd68142ca374ed3 /sound/soc/sh/rcar/dma.c | |
parent | Initial commit. (diff) | |
download | linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip |
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sound/soc/sh/rcar/dma.c')
-rw-r--r-- | sound/soc/sh/rcar/dma.c | 946 |
1 files changed, 946 insertions, 0 deletions
diff --git a/sound/soc/sh/rcar/dma.c b/sound/soc/sh/rcar/dma.c new file mode 100644 index 0000000000..1c494e5214 --- /dev/null +++ b/sound/soc/sh/rcar/dma.c @@ -0,0 +1,946 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Renesas R-Car Audio DMAC support +// +// Copyright (C) 2015 Renesas Electronics Corp. +// Copyright (c) 2015 Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> + +#include <linux/delay.h> +#include <linux/of_dma.h> +#include "rsnd.h" + +/* + * Audio DMAC peri peri register + */ +#define PDMASAR 0x00 +#define PDMADAR 0x04 +#define PDMACHCR 0x0c + +/* PDMACHCR */ +#define PDMACHCR_DE (1 << 0) + + +struct rsnd_dmaen { + struct dma_chan *chan; + dma_cookie_t cookie; + unsigned int dma_len; +}; + +struct rsnd_dmapp { + int dmapp_id; + u32 chcr; +}; + +struct rsnd_dma { + struct rsnd_mod mod; + struct rsnd_mod *mod_from; + struct rsnd_mod *mod_to; + dma_addr_t src_addr; + dma_addr_t dst_addr; + union { + struct rsnd_dmaen en; + struct rsnd_dmapp pp; + } dma; +}; + +struct rsnd_dma_ctrl { + void __iomem *ppbase; + phys_addr_t ppres; + int dmaen_num; + int dmapp_num; +}; + +#define rsnd_priv_to_dmac(p) ((struct rsnd_dma_ctrl *)(p)->dma) +#define rsnd_mod_to_dma(_mod) container_of((_mod), struct rsnd_dma, mod) +#define rsnd_dma_to_dmaen(dma) (&(dma)->dma.en) +#define rsnd_dma_to_dmapp(dma) (&(dma)->dma.pp) + +/* for DEBUG */ +static struct rsnd_mod_ops mem_ops = { + .name = "mem", +}; + +static struct rsnd_mod mem = { +}; + +/* + * Audio DMAC + */ +static void __rsnd_dmaen_complete(struct rsnd_mod *mod, + struct rsnd_dai_stream *io) +{ + if (rsnd_io_is_working(io)) + rsnd_dai_period_elapsed(io); +} + +static void rsnd_dmaen_complete(void *data) +{ + struct rsnd_mod *mod = data; + + rsnd_mod_interrupt(mod, __rsnd_dmaen_complete); +} + +static struct dma_chan *rsnd_dmaen_request_channel(struct rsnd_dai_stream *io, + struct rsnd_mod *mod_from, + struct rsnd_mod *mod_to) +{ + if ((!mod_from && !mod_to) || + (mod_from && mod_to)) + return NULL; + + if (mod_from) + return rsnd_mod_dma_req(io, mod_from); + else + return rsnd_mod_dma_req(io, mod_to); +} + +static int rsnd_dmaen_stop(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + struct rsnd_dma *dma = rsnd_mod_to_dma(mod); + struct rsnd_dmaen *dmaen = rsnd_dma_to_dmaen(dma); + + if (dmaen->chan) + dmaengine_terminate_async(dmaen->chan); + + return 0; +} + +static int rsnd_dmaen_cleanup(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + struct rsnd_dma *dma = rsnd_mod_to_dma(mod); + struct rsnd_dmaen *dmaen = rsnd_dma_to_dmaen(dma); + + /* + * DMAEngine release uses mutex lock. + * Thus, it shouldn't be called under spinlock. + * Let's call it under prepare + */ + if (dmaen->chan) + dma_release_channel(dmaen->chan); + + dmaen->chan = NULL; + + return 0; +} + +static int rsnd_dmaen_prepare(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + struct rsnd_dma *dma = rsnd_mod_to_dma(mod); + struct rsnd_dmaen *dmaen = rsnd_dma_to_dmaen(dma); + struct device *dev = rsnd_priv_to_dev(priv); + + /* maybe suspended */ + if (dmaen->chan) + return 0; + + /* + * DMAEngine request uses mutex lock. + * Thus, it shouldn't be called under spinlock. + * Let's call it under prepare + */ + dmaen->chan = rsnd_dmaen_request_channel(io, + dma->mod_from, + dma->mod_to); + if (IS_ERR_OR_NULL(dmaen->chan)) { + dmaen->chan = NULL; + dev_err(dev, "can't get dma channel\n"); + return -EIO; + } + + return 0; +} + +static int rsnd_dmaen_start(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + struct rsnd_dma *dma = rsnd_mod_to_dma(mod); + struct rsnd_dmaen *dmaen = rsnd_dma_to_dmaen(dma); + struct snd_pcm_substream *substream = io->substream; + struct device *dev = rsnd_priv_to_dev(priv); + struct dma_async_tx_descriptor *desc; + struct dma_slave_config cfg = {}; + enum dma_slave_buswidth buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES; + int is_play = rsnd_io_is_play(io); + int ret; + + /* + * in case of monaural data writing or reading through Audio-DMAC + * data is always in Left Justified format, so both src and dst + * DMA Bus width need to be set equal to physical data width. + */ + if (rsnd_runtime_channel_original(io) == 1) { + struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io); + int bits = snd_pcm_format_physical_width(runtime->format); + + switch (bits) { + case 8: + buswidth = DMA_SLAVE_BUSWIDTH_1_BYTE; + break; + case 16: + buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; + break; + case 32: + buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES; + break; + default: + dev_err(dev, "invalid format width %d\n", bits); + return -EINVAL; + } + } + + cfg.direction = is_play ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM; + cfg.src_addr = dma->src_addr; + cfg.dst_addr = dma->dst_addr; + cfg.src_addr_width = buswidth; + cfg.dst_addr_width = buswidth; + + dev_dbg(dev, "%s %pad -> %pad\n", + rsnd_mod_name(mod), + &cfg.src_addr, &cfg.dst_addr); + + ret = dmaengine_slave_config(dmaen->chan, &cfg); + if (ret < 0) + return ret; + + desc = dmaengine_prep_dma_cyclic(dmaen->chan, + substream->runtime->dma_addr, + snd_pcm_lib_buffer_bytes(substream), + snd_pcm_lib_period_bytes(substream), + is_play ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + + if (!desc) { + dev_err(dev, "dmaengine_prep_slave_sg() fail\n"); + return -EIO; + } + + desc->callback = rsnd_dmaen_complete; + desc->callback_param = rsnd_mod_get(dma); + + dmaen->dma_len = snd_pcm_lib_buffer_bytes(substream); + + dmaen->cookie = dmaengine_submit(desc); + if (dmaen->cookie < 0) { + dev_err(dev, "dmaengine_submit() fail\n"); + return -EIO; + } + + dma_async_issue_pending(dmaen->chan); + + return 0; +} + +struct dma_chan *rsnd_dma_request_channel(struct device_node *of_node, char *name, + struct rsnd_mod *mod, char *x) +{ + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct device *dev = rsnd_priv_to_dev(priv); + struct dma_chan *chan = NULL; + struct device_node *np; + int i = 0; + + for_each_child_of_node(of_node, np) { + i = rsnd_node_fixed_index(dev, np, name, i); + if (i < 0) { + chan = NULL; + of_node_put(np); + break; + } + + if (i == rsnd_mod_id_raw(mod) && (!chan)) + chan = of_dma_request_slave_channel(np, x); + i++; + } + + /* It should call of_node_put(), since, it is rsnd_xxx_of_node() */ + of_node_put(of_node); + + return chan; +} + +static int rsnd_dmaen_attach(struct rsnd_dai_stream *io, + struct rsnd_dma *dma, + struct rsnd_mod *mod_from, struct rsnd_mod *mod_to) +{ + struct rsnd_priv *priv = rsnd_io_to_priv(io); + struct rsnd_dma_ctrl *dmac = rsnd_priv_to_dmac(priv); + struct dma_chan *chan; + + /* try to get DMAEngine channel */ + chan = rsnd_dmaen_request_channel(io, mod_from, mod_to); + if (IS_ERR_OR_NULL(chan)) { + /* Let's follow when -EPROBE_DEFER case */ + if (PTR_ERR(chan) == -EPROBE_DEFER) + return PTR_ERR(chan); + + /* + * DMA failed. try to PIO mode + * see + * rsnd_ssi_fallback() + * rsnd_rdai_continuance_probe() + */ + return -EAGAIN; + } + + /* + * use it for IPMMU if needed + * see + * rsnd_preallocate_pages() + */ + io->dmac_dev = chan->device->dev; + + dma_release_channel(chan); + + dmac->dmaen_num++; + + return 0; +} + +static int rsnd_dmaen_pointer(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + snd_pcm_uframes_t *pointer) +{ + struct snd_pcm_runtime *runtime = rsnd_io_to_runtime(io); + struct rsnd_dma *dma = rsnd_mod_to_dma(mod); + struct rsnd_dmaen *dmaen = rsnd_dma_to_dmaen(dma); + struct dma_tx_state state; + enum dma_status status; + unsigned int pos = 0; + + status = dmaengine_tx_status(dmaen->chan, dmaen->cookie, &state); + if (status == DMA_IN_PROGRESS || status == DMA_PAUSED) { + if (state.residue > 0 && state.residue <= dmaen->dma_len) + pos = dmaen->dma_len - state.residue; + } + *pointer = bytes_to_frames(runtime, pos); + + return 0; +} + +static struct rsnd_mod_ops rsnd_dmaen_ops = { + .name = "audmac", + .prepare = rsnd_dmaen_prepare, + .cleanup = rsnd_dmaen_cleanup, + .start = rsnd_dmaen_start, + .stop = rsnd_dmaen_stop, + .pointer = rsnd_dmaen_pointer, + .get_status = rsnd_mod_get_status, +}; + +/* + * Audio DMAC peri peri + */ +static const u8 gen2_id_table_ssiu[] = { + /* SSI00 ~ SSI07 */ + 0x00, 0x01, 0x02, 0x03, 0x39, 0x3a, 0x3b, 0x3c, + /* SSI10 ~ SSI17 */ + 0x04, 0x05, 0x06, 0x07, 0x3d, 0x3e, 0x3f, 0x40, + /* SSI20 ~ SSI27 */ + 0x08, 0x09, 0x0a, 0x0b, 0x41, 0x42, 0x43, 0x44, + /* SSI30 ~ SSI37 */ + 0x0c, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, + /* SSI40 ~ SSI47 */ + 0x0d, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, + /* SSI5 */ + 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* SSI6 */ + 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* SSI7 */ + 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* SSI8 */ + 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* SSI90 ~ SSI97 */ + 0x12, 0x13, 0x14, 0x15, 0x53, 0x54, 0x55, 0x56, +}; +static const u8 gen2_id_table_scu[] = { + 0x2d, /* SCU_SRCI0 */ + 0x2e, /* SCU_SRCI1 */ + 0x2f, /* SCU_SRCI2 */ + 0x30, /* SCU_SRCI3 */ + 0x31, /* SCU_SRCI4 */ + 0x32, /* SCU_SRCI5 */ + 0x33, /* SCU_SRCI6 */ + 0x34, /* SCU_SRCI7 */ + 0x35, /* SCU_SRCI8 */ + 0x36, /* SCU_SRCI9 */ +}; +static const u8 gen2_id_table_cmd[] = { + 0x37, /* SCU_CMD0 */ + 0x38, /* SCU_CMD1 */ +}; + +static u32 rsnd_dmapp_get_id(struct rsnd_dai_stream *io, + struct rsnd_mod *mod) +{ + struct rsnd_mod *ssi = rsnd_io_to_mod_ssi(io); + struct rsnd_mod *ssiu = rsnd_io_to_mod_ssiu(io); + struct rsnd_mod *src = rsnd_io_to_mod_src(io); + struct rsnd_mod *dvc = rsnd_io_to_mod_dvc(io); + const u8 *entry = NULL; + int id = 255; + int size = 0; + + if ((mod == ssi) || + (mod == ssiu)) { + int busif = rsnd_mod_id_sub(ssiu); + + entry = gen2_id_table_ssiu; + size = ARRAY_SIZE(gen2_id_table_ssiu); + id = (rsnd_mod_id(mod) * 8) + busif; + } else if (mod == src) { + entry = gen2_id_table_scu; + size = ARRAY_SIZE(gen2_id_table_scu); + id = rsnd_mod_id(mod); + } else if (mod == dvc) { + entry = gen2_id_table_cmd; + size = ARRAY_SIZE(gen2_id_table_cmd); + id = rsnd_mod_id(mod); + } + + if ((!entry) || (size <= id)) { + struct device *dev = rsnd_priv_to_dev(rsnd_io_to_priv(io)); + + dev_err(dev, "unknown connection (%s)\n", rsnd_mod_name(mod)); + + /* use non-prohibited SRS number as error */ + return 0x00; /* SSI00 */ + } + + return entry[id]; +} + +static u32 rsnd_dmapp_get_chcr(struct rsnd_dai_stream *io, + struct rsnd_mod *mod_from, + struct rsnd_mod *mod_to) +{ + return (rsnd_dmapp_get_id(io, mod_from) << 24) + + (rsnd_dmapp_get_id(io, mod_to) << 16); +} + +#define rsnd_dmapp_addr(dmac, dma, reg) \ + (dmac->ppbase + 0x20 + reg + \ + (0x10 * rsnd_dma_to_dmapp(dma)->dmapp_id)) +static void rsnd_dmapp_write(struct rsnd_dma *dma, u32 data, u32 reg) +{ + struct rsnd_mod *mod = rsnd_mod_get(dma); + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct rsnd_dma_ctrl *dmac = rsnd_priv_to_dmac(priv); + struct device *dev = rsnd_priv_to_dev(priv); + + dev_dbg(dev, "w 0x%px : %08x\n", rsnd_dmapp_addr(dmac, dma, reg), data); + + iowrite32(data, rsnd_dmapp_addr(dmac, dma, reg)); +} + +static u32 rsnd_dmapp_read(struct rsnd_dma *dma, u32 reg) +{ + struct rsnd_mod *mod = rsnd_mod_get(dma); + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct rsnd_dma_ctrl *dmac = rsnd_priv_to_dmac(priv); + + return ioread32(rsnd_dmapp_addr(dmac, dma, reg)); +} + +static void rsnd_dmapp_bset(struct rsnd_dma *dma, u32 data, u32 mask, u32 reg) +{ + struct rsnd_mod *mod = rsnd_mod_get(dma); + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct rsnd_dma_ctrl *dmac = rsnd_priv_to_dmac(priv); + void __iomem *addr = rsnd_dmapp_addr(dmac, dma, reg); + u32 val = ioread32(addr); + + val &= ~mask; + val |= (data & mask); + + iowrite32(val, addr); +} + +static int rsnd_dmapp_stop(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + struct rsnd_dma *dma = rsnd_mod_to_dma(mod); + int i; + + rsnd_dmapp_bset(dma, 0, PDMACHCR_DE, PDMACHCR); + + for (i = 0; i < 1024; i++) { + if (0 == (rsnd_dmapp_read(dma, PDMACHCR) & PDMACHCR_DE)) + return 0; + udelay(1); + } + + return -EIO; +} + +static int rsnd_dmapp_start(struct rsnd_mod *mod, + struct rsnd_dai_stream *io, + struct rsnd_priv *priv) +{ + struct rsnd_dma *dma = rsnd_mod_to_dma(mod); + struct rsnd_dmapp *dmapp = rsnd_dma_to_dmapp(dma); + + rsnd_dmapp_write(dma, dma->src_addr, PDMASAR); + rsnd_dmapp_write(dma, dma->dst_addr, PDMADAR); + rsnd_dmapp_write(dma, dmapp->chcr, PDMACHCR); + + return 0; +} + +static int rsnd_dmapp_attach(struct rsnd_dai_stream *io, + struct rsnd_dma *dma, + struct rsnd_mod *mod_from, struct rsnd_mod *mod_to) +{ + struct rsnd_dmapp *dmapp = rsnd_dma_to_dmapp(dma); + struct rsnd_priv *priv = rsnd_io_to_priv(io); + struct rsnd_dma_ctrl *dmac = rsnd_priv_to_dmac(priv); + struct device *dev = rsnd_priv_to_dev(priv); + + dmapp->dmapp_id = dmac->dmapp_num; + dmapp->chcr = rsnd_dmapp_get_chcr(io, mod_from, mod_to) | PDMACHCR_DE; + + dmac->dmapp_num++; + + dev_dbg(dev, "id/src/dst/chcr = %d/%pad/%pad/%08x\n", + dmapp->dmapp_id, &dma->src_addr, &dma->dst_addr, dmapp->chcr); + + return 0; +} + +#ifdef CONFIG_DEBUG_FS +static void rsnd_dmapp_debug_info(struct seq_file *m, + struct rsnd_dai_stream *io, + struct rsnd_mod *mod) +{ + struct rsnd_priv *priv = rsnd_mod_to_priv(mod); + struct rsnd_dma_ctrl *dmac = rsnd_priv_to_dmac(priv); + struct rsnd_dma *dma = rsnd_mod_to_dma(mod); + struct rsnd_dmapp *dmapp = rsnd_dma_to_dmapp(dma); + + rsnd_debugfs_reg_show(m, dmac->ppres, dmac->ppbase, + 0x20 + 0x10 * dmapp->dmapp_id, 0x10); +} +#define DEBUG_INFO .debug_info = rsnd_dmapp_debug_info +#else +#define DEBUG_INFO +#endif + +static struct rsnd_mod_ops rsnd_dmapp_ops = { + .name = "audmac-pp", + .start = rsnd_dmapp_start, + .stop = rsnd_dmapp_stop, + .quit = rsnd_dmapp_stop, + .get_status = rsnd_mod_get_status, + DEBUG_INFO +}; + +/* + * Common DMAC Interface + */ + +/* + * 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, j) (addr ##_reg - 0x00441000 + (0x1000 * (i)) + (((j) / 4) * 0xA000) + (((j) % 4) * 0x400) - (0x4000 * ((i) / 9) * ((j) / 4))) +#define RDMA_SSIU_O_N(addr, i, j) RDMA_SSIU_I_N(addr, i, j) + +#define RDMA_SSIU_I_P(addr, i, j) (addr ##_reg - 0x00141000 + (0x1000 * (i)) + (((j) / 4) * 0xA000) + (((j) % 4) * 0x400) - (0x4000 * ((i) / 9) * ((j) / 4))) +#define RDMA_SSIU_O_P(addr, i, j) RDMA_SSIU_I_P(addr, i, j) + +#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_dai_stream *io, + struct rsnd_mod *mod, + int is_play, int is_from) +{ + struct rsnd_priv *priv = rsnd_io_to_priv(io); + struct device *dev = rsnd_priv_to_dev(priv); + 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) || + !!(rsnd_io_to_mod_ssiu(io) == mod); + int use_src = !!rsnd_io_to_mod_src(io); + int use_cmd = !!rsnd_io_to_mod_dvc(io) || + !!rsnd_io_to_mod_mix(io) || + !!rsnd_io_to_mod_ctu(io); + int id = rsnd_mod_id(mod); + int busif = rsnd_mod_id_sub(rsnd_io_to_mod_ssiu(io)); + struct dma_addr { + dma_addr_t out_addr; + dma_addr_t in_addr; + } dma_addrs[3][2][3] = { + /* SRC */ + /* Capture */ + {{{ 0, 0 }, + { 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, busif), 0 }, + { RDMA_SSIU_O_P(ssi, id, busif), 0 } }, + /* Playback */ + {{ 0, RDMA_SSI_I_N(ssi, id) }, + { 0, RDMA_SSIU_I_P(ssi, id, busif) }, + { 0, RDMA_SSIU_I_P(ssi, id, busif) } } + }, + /* SSIU */ + /* Capture */ + {{{ RDMA_SSIU_O_N(ssi, id, busif), 0 }, + { RDMA_SSIU_O_P(ssi, id, busif), 0 }, + { RDMA_SSIU_O_P(ssi, id, busif), 0 } }, + /* Playback */ + {{ 0, RDMA_SSIU_I_N(ssi, id, busif) }, + { 0, RDMA_SSIU_I_P(ssi, id, busif) }, + { 0, RDMA_SSIU_I_P(ssi, id, busif) } } }, + }; + + /* + * FIXME + * + * We can't support SSI9-4/5/6/7, because its address is + * out of calculation rule + */ + if ((id == 9) && (busif >= 4)) + dev_err(dev, "This driver doesn't support SSI%d-%d, so far", + id, busif); + + /* it shouldn't happen */ + if (use_cmd && !use_src) + dev_err(dev, "DVC is selected without SRC\n"); + + /* use SSIU or SSI ? */ + if (is_ssi && rsnd_ssi_use_busif(io)) + is_ssi++; + + return (is_from) ? + dma_addrs[is_ssi][is_play][use_src + use_cmd].out_addr : + dma_addrs[is_ssi][is_play][use_src + use_cmd].in_addr; +} + +/* + * Gen4 DMA read/write register offset + * + * ex) R-Car V4H case + * mod / SYS-DMAC in / SYS-DMAC out + * SSI_SDMC: 0xec400000 / 0xec400000 / 0xec400000 + */ +#define RDMA_SSI_SDMC(addr, i) (addr + (0x8000 * i)) +static dma_addr_t +rsnd_gen4_dma_addr(struct rsnd_dai_stream *io, struct rsnd_mod *mod, + int is_play, int is_from) +{ + struct rsnd_priv *priv = rsnd_io_to_priv(io); + phys_addr_t addr = rsnd_gen_get_phy_addr(priv, RSND_GEN4_SDMC); + int id = rsnd_mod_id(mod); + int busif = rsnd_mod_id_sub(mod); + + /* + * SSI0 only is supported + */ + if (id != 0) { + struct device *dev = rsnd_priv_to_dev(priv); + + dev_err(dev, "This driver doesn't support non SSI0"); + return -EINVAL; + } + + return RDMA_SSI_SDMC(addr, busif); +} + +static dma_addr_t rsnd_dma_addr(struct rsnd_dai_stream *io, + struct rsnd_mod *mod, + int is_play, int is_from) +{ + struct rsnd_priv *priv = rsnd_io_to_priv(io); + + if (!mod) + return 0; + + /* + * gen1 uses default DMA addr + */ + if (rsnd_is_gen1(priv)) + return 0; + else if (rsnd_is_gen4(priv)) + return rsnd_gen4_dma_addr(io, mod, is_play, is_from); + else + return rsnd_gen2_dma_addr(io, mod, is_play, is_from); +} + +#define MOD_MAX (RSND_MOD_MAX + 1) /* +Memory */ +static void rsnd_dma_of_path(struct rsnd_mod *this, + struct rsnd_dai_stream *io, + int is_play, + struct rsnd_mod **mod_from, + struct rsnd_mod **mod_to) +{ + struct rsnd_mod *ssi; + struct rsnd_mod *src = rsnd_io_to_mod_src(io); + struct rsnd_mod *ctu = rsnd_io_to_mod_ctu(io); + struct rsnd_mod *mix = rsnd_io_to_mod_mix(io); + struct rsnd_mod *dvc = rsnd_io_to_mod_dvc(io); + struct rsnd_mod *mod[MOD_MAX]; + struct rsnd_mod *mod_start, *mod_end; + struct rsnd_priv *priv = rsnd_mod_to_priv(this); + struct device *dev = rsnd_priv_to_dev(priv); + int nr, i, idx; + + /* + * It should use "rcar_sound,ssiu" on DT. + * But, we need to keep compatibility for old version. + * + * If it has "rcar_sound.ssiu", it will be used. + * If not, "rcar_sound.ssi" will be used. + * see + * rsnd_ssiu_dma_req() + * rsnd_ssi_dma_req() + */ + if (rsnd_ssiu_of_node(priv)) { + struct rsnd_mod *ssiu = rsnd_io_to_mod_ssiu(io); + + /* use SSIU */ + ssi = ssiu; + if (this == rsnd_io_to_mod_ssi(io)) + this = ssiu; + } else { + /* keep compatible, use SSI */ + ssi = rsnd_io_to_mod_ssi(io); + } + + if (!ssi) + return; + + nr = 0; + for (i = 0; i < MOD_MAX; i++) { + mod[i] = NULL; + nr += !!rsnd_io_to_mod(io, i); + } + + /* + * [S] -*-> [E] + * [S] -*-> SRC -o-> [E] + * [S] -*-> SRC -> DVC -o-> [E] + * [S] -*-> SRC -> CTU -> MIX -> DVC -o-> [E] + * + * playback [S] = mem + * [E] = SSI + * + * capture [S] = SSI + * [E] = mem + * + * -*-> Audio DMAC + * -o-> Audio DMAC peri peri + */ + mod_start = (is_play) ? NULL : ssi; + mod_end = (is_play) ? ssi : NULL; + + idx = 0; + mod[idx++] = mod_start; + for (i = 1; i < nr; i++) { + if (src) { + mod[idx++] = src; + src = NULL; + } else if (ctu) { + mod[idx++] = ctu; + ctu = NULL; + } else if (mix) { + mod[idx++] = mix; + mix = NULL; + } else if (dvc) { + mod[idx++] = dvc; + dvc = NULL; + } + } + mod[idx] = mod_end; + + /* + * | SSI | SRC | + * -------------+-----+-----+ + * is_play | o | * | + * !is_play | * | o | + */ + if ((this == ssi) == (is_play)) { + *mod_from = mod[idx - 1]; + *mod_to = mod[idx]; + } else { + *mod_from = mod[0]; + *mod_to = mod[1]; + } + + dev_dbg(dev, "module connection (this is %s)\n", rsnd_mod_name(this)); + for (i = 0; i <= idx; i++) { + dev_dbg(dev, " %s%s\n", + rsnd_mod_name(mod[i] ? mod[i] : &mem), + (mod[i] == *mod_from) ? " from" : + (mod[i] == *mod_to) ? " to" : ""); + } +} + +static int rsnd_dma_alloc(struct rsnd_dai_stream *io, struct rsnd_mod *mod, + struct rsnd_mod **dma_mod) +{ + struct rsnd_mod *mod_from = NULL; + struct rsnd_mod *mod_to = NULL; + struct rsnd_priv *priv = rsnd_io_to_priv(io); + struct rsnd_dma_ctrl *dmac = rsnd_priv_to_dmac(priv); + struct device *dev = rsnd_priv_to_dev(priv); + struct rsnd_dma *dma; + struct rsnd_mod_ops *ops; + enum rsnd_mod_type type; + int (*attach)(struct rsnd_dai_stream *io, struct rsnd_dma *dma, + struct rsnd_mod *mod_from, struct rsnd_mod *mod_to); + int is_play = rsnd_io_is_play(io); + int ret, dma_id; + + /* + * DMA failed. try to PIO mode + * see + * rsnd_ssi_fallback() + * rsnd_rdai_continuance_probe() + */ + if (!dmac) + return -EAGAIN; + + rsnd_dma_of_path(mod, io, is_play, &mod_from, &mod_to); + + /* for Gen2 or later */ + if (mod_from && mod_to) { + ops = &rsnd_dmapp_ops; + attach = rsnd_dmapp_attach; + dma_id = dmac->dmapp_num; + type = RSND_MOD_AUDMAPP; + } else { + ops = &rsnd_dmaen_ops; + attach = rsnd_dmaen_attach; + dma_id = dmac->dmaen_num; + type = RSND_MOD_AUDMA; + } + + /* for Gen1, overwrite */ + if (rsnd_is_gen1(priv)) { + ops = &rsnd_dmaen_ops; + attach = rsnd_dmaen_attach; + dma_id = dmac->dmaen_num; + type = RSND_MOD_AUDMA; + } + + dma = devm_kzalloc(dev, sizeof(*dma), GFP_KERNEL); + if (!dma) + return -ENOMEM; + + *dma_mod = rsnd_mod_get(dma); + + ret = rsnd_mod_init(priv, *dma_mod, ops, NULL, + type, dma_id); + if (ret < 0) + return ret; + + dev_dbg(dev, "%s %s -> %s\n", + rsnd_mod_name(*dma_mod), + rsnd_mod_name(mod_from ? mod_from : &mem), + rsnd_mod_name(mod_to ? mod_to : &mem)); + + ret = attach(io, dma, mod_from, mod_to); + if (ret < 0) + return ret; + + dma->src_addr = rsnd_dma_addr(io, mod_from, is_play, 1); + dma->dst_addr = rsnd_dma_addr(io, mod_to, is_play, 0); + dma->mod_from = mod_from; + dma->mod_to = mod_to; + + return 0; +} + +int rsnd_dma_attach(struct rsnd_dai_stream *io, struct rsnd_mod *mod, + struct rsnd_mod **dma_mod) +{ + if (!(*dma_mod)) { + int ret = rsnd_dma_alloc(io, mod, dma_mod); + + if (ret < 0) + return ret; + } + + return rsnd_dai_connect(*dma_mod, io, (*dma_mod)->type); +} + +int rsnd_dma_probe(struct rsnd_priv *priv) +{ + struct platform_device *pdev = rsnd_priv_to_pdev(priv); + struct device *dev = rsnd_priv_to_dev(priv); + struct rsnd_dma_ctrl *dmac; + struct resource *res; + + /* + * for Gen1 + */ + if (rsnd_is_gen1(priv)) + return 0; + + /* + * for Gen2 or later + */ + dmac = devm_kzalloc(dev, sizeof(*dmac), GFP_KERNEL); + if (!dmac) { + dev_err(dev, "dma allocate failed\n"); + return 0; /* it will be PIO mode */ + } + + /* for Gen4 doesn't have DMA-pp */ + if (rsnd_is_gen4(priv)) + goto audmapp_end; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "audmapp"); + if (!res) { + dev_err(dev, "lack of audmapp in DT\n"); + return 0; /* it will be PIO mode */ + } + + dmac->dmapp_num = 0; + dmac->ppres = res->start; + dmac->ppbase = devm_ioremap_resource(dev, res); + if (IS_ERR(dmac->ppbase)) + return PTR_ERR(dmac->ppbase); +audmapp_end: + priv->dma = dmac; + + /* dummy mem mod for debug */ + return rsnd_mod_init(NULL, &mem, &mem_ops, NULL, 0, 0); +} |