/* * Renesas R-Car Audio DMAC support * * Copyright (C) 2015 Renesas Electronics Corp. * Copyright (c) 2015 Kuninori Morimoto * * 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" static void rsnd_dmaen_complete(void *data) { 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 static int _rsnd_dmaen_of_name(char *dma_name, struct rsnd_mod *mod) { 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"); } static void rsnd_dmaen_of_name(struct rsnd_mod *mod_from, struct rsnd_mod *mod_to, char *dma_name) { int index = 0; index = _rsnd_dmaen_of_name(dma_name + index, mod_from); *(dma_name + index++) = '_'; index = _rsnd_dmaen_of_name(dma_name + index, mod_to); } static void rsnd_dmaen_stop(struct rsnd_dma *dma) { dmaengine_terminate_all(dma->chan); } static void rsnd_dmaen_start(struct rsnd_dma *dma) { 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; } desc->callback = rsnd_dmaen_complete; desc->callback_param = dma; if (dmaengine_submit(desc) < 0) { dev_err(dev, "dmaengine_submit() fail\n"); return; } dma_async_issue_pending(dma->chan); } 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) { 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); rsnd_dmaen_of_name(mod_from, mod_to, dma_name); 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 = 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; } static void rsnd_dmaen_quit(struct rsnd_dma *dma) { if (dma->chan) dma_release_channel(dma->chan); dma->chan = NULL; } static struct rsnd_dma_ops rsnd_dmaen_ops = { .start = rsnd_dmaen_start, .stop = rsnd_dmaen_stop, .init = rsnd_dmaen_init, .quit = rsnd_dmaen_quit, }; /* * 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 */ 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]; } } 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); }