diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index 141aefbe37ec..6f4ec1a589d1 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -285,6 +285,15 @@ config K3_DMA Support the DMA engine for Hisilicon K3 platform devices. +config HISI_ASP_DMA + tristate "Hisilicon Kirin ASP DMA support" + depends on ARCH_HISI + select DMA_ENGINE + select DMA_VIRTUAL_CHANNELS + help + Support the DMA engine for Hisilicon Kirin platform + devices. + config LPC18XX_DMAMUX bool "NXP LPC18xx/43xx DMA MUX for PL080" depends on ARCH_LPC18XX || COMPILE_TEST diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index e4dc9cac7ee8..f6b7636ba5d7 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_TI_DMA_CROSSBAR) += ti-dma-crossbar.o obj-$(CONFIG_TI_EDMA) += edma.o obj-$(CONFIG_XGENE_DMA) += xgene-dma.o obj-$(CONFIG_ZX_DMA) += zx296702_dma.o +obj-$(CONFIG_HISI_ASP_DMA) += hisi_asp_dma.o obj-y += qcom/ obj-y += xilinx/ diff --git a/drivers/dma/hisi_asp_dma.c b/drivers/dma/hisi_asp_dma.c new file mode 100644 index 000000000000..08ecaa298d8b --- /dev/null +++ b/drivers/dma/hisi_asp_dma.c @@ -0,0 +1,1025 @@ +/* + * Copyright (c) 2013 - 2015 Linaro Ltd. + * Copyright (c) 2013 Hisilicon Limited. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "virt-dma.h" + +#define DRIVER_NAME "hisi-asp-dma" +#define DMA_ALIGN 3 +#define DMA_MAX_SIZE 0x1ffc +#define DMA_CYCLIC_MAX_PERIOD 0x1000 +#define LLI_BLOCK_SIZE (4 * PAGE_SIZE) + +#define INT_STAT 0x00 +#define INT_TC1 0x04 +#define INT_TC2 0x08 +#define INT_ERR1 0x0c +#define INT_ERR2 0x10 +#define INT_TC1_MASK 0x18 +#define INT_TC2_MASK 0x1c +#define INT_ERR1_MASK 0x20 +#define INT_ERR2_MASK 0x24 +#define INT_TC1_RAW 0x600 +#define INT_TC2_RAW 0x608 +#define INT_ERR1_RAW 0x610 +#define INT_ERR2_RAW 0x618 +#define CH_PRI 0x688 +#define CH_STAT 0x690 +#define CX_CUR_CNT 0x704 +#define CX_LLI 0x800 +#define CX_CNT1 0x80c +#define CX_CNT0 0x810 +#define CX_SRC 0x814 +#define CX_DST 0x818 +#define CX_CFG 0x81c +#define AXI_CFG 0x820 +#define AXI_CFG_DEFAULT 0x201201 + +#define CX_LLI_CHAIN_EN 0x2 +#define CX_CFG_EN 0x1 +#define CX_CFG_NODEIRQ BIT(1) +#define CX_CFG_MEM2PER (0x1 << 2) +#define CX_CFG_PER2MEM (0x2 << 2) +#define CX_CFG_SRCINCR (0x1 << 31) +#define CX_CFG_DSTINCR (0x1 << 30) + +struct hisi_asp_desc_hw { + u32 lli; + u32 reserved[3]; + u32 count; + u32 saddr; + u32 daddr; + u32 config; +} __aligned(32); + +struct hisi_asp_dma_desc_sw { + struct virt_dma_desc vd; + dma_addr_t desc_hw_lli; + size_t desc_num; + size_t size; + struct hisi_asp_desc_hw *desc_hw; +}; + +struct hisi_asp_dma_phy; + +struct hisi_asp_dma_chan { + u32 ccfg; + struct virt_dma_chan vc; + struct hisi_asp_dma_phy *phy; + struct list_head node; + enum dma_transfer_direction dir; + dma_addr_t dev_addr; + enum dma_status status; + bool cyclic; +}; + +struct hisi_asp_dma_phy { + u32 idx; + void __iomem *base; + struct hisi_asp_dma_chan *vchan; + struct hisi_asp_dma_desc_sw *ds_run; + struct hisi_asp_dma_desc_sw *ds_done; +}; + +struct hisi_asp_dma_dev { + struct dma_device slave; + void __iomem *base; + struct tasklet_struct task; + spinlock_t lock; + struct list_head chan_pending; + struct hisi_asp_dma_phy *phy; + struct hisi_asp_dma_chan *chans; + struct clk *clk; + struct dma_pool *pool; + u32 dma_channels; + u32 dma_requests; + struct regulator *asp_ip; +}; + +#define to_hisi_asp_dma(dmadev) container_of(dmadev, struct hisi_asp_dma_dev, slave) + +static struct hisi_asp_dma_chan *to_hisi_asp_chan(struct dma_chan *chan) +{ + return container_of(chan, struct hisi_asp_dma_chan, vc.chan); +} + +static void hisi_asp_dma_pause_dma(struct hisi_asp_dma_phy *phy, bool on) +{ + u32 val = 0; + + if (on) { + val = readl_relaxed(phy->base + CX_CFG); + val |= CX_CFG_EN; + writel_relaxed(val, phy->base + CX_CFG); + } else { + val = readl_relaxed(phy->base + CX_CFG); + val &= ~CX_CFG_EN; + writel_relaxed(val, phy->base + CX_CFG); + } +} + +static void hisi_asp_dma_terminate_chan(struct hisi_asp_dma_phy *phy, struct hisi_asp_dma_dev *d) +{ + u32 val = 0; + + hisi_asp_dma_pause_dma(phy, false); + + val = 0x1 << phy->idx; + writel_relaxed(val, d->base + INT_TC1_RAW); + writel_relaxed(val, d->base + INT_TC2_RAW); + writel_relaxed(val, d->base + INT_ERR1_RAW); + writel_relaxed(val, d->base + INT_ERR2_RAW); +} + +static void hisi_asp_dma_set_desc(struct hisi_asp_dma_phy *phy, struct hisi_asp_desc_hw *hw) +{ + writel_relaxed(hw->lli, phy->base + CX_LLI); + writel_relaxed(hw->count, phy->base + CX_CNT0); + writel_relaxed(hw->saddr, phy->base + CX_SRC); + writel_relaxed(hw->daddr, phy->base + CX_DST); + writel_relaxed(AXI_CFG_DEFAULT, phy->base + AXI_CFG); + writel_relaxed(hw->config, phy->base + CX_CFG); + wmb(); + pr_debug("%s: desc %p: ch idx = %d, lli: 0x%x, count: 0x%x, saddr: 0x%x, daddr 0x%x, cfg: 0x%x\n", __func__, (void *)hw, + phy->idx, hw->lli, hw->count, hw->saddr, hw->daddr, hw->config); +} + +static u32 hisi_asp_dma_get_curr_cnt(struct hisi_asp_dma_dev *d, struct hisi_asp_dma_phy *phy) +{ + u32 cnt = 0; + + cnt = readl_relaxed(d->base + CX_CUR_CNT + phy->idx * 0x10); + cnt &= 0xffff; + return cnt; +} + +static u32 hisi_asp_dma_get_curr_lli(struct hisi_asp_dma_phy *phy) +{ + return readl_relaxed(phy->base + CX_LLI); +} + +static u32 hisi_asp_dma_get_chan_stat(struct hisi_asp_dma_dev *d) +{ + return readl_relaxed(d->base + CH_STAT); +} + +static void hisi_asp_dma_enable_dma(struct hisi_asp_dma_dev *d, bool on) +{ + if (on) { + /* set same priority */ + writel_relaxed(0x0, d->base + CH_PRI); + + /* unmask irq */ + writel_relaxed(0xffff, d->base + INT_TC1_MASK); + writel_relaxed(0xffff, d->base + INT_TC2_MASK); + writel_relaxed(0xffff, d->base + INT_ERR1_MASK); + writel_relaxed(0xffff, d->base + INT_ERR2_MASK); + } else { + /* mask irq */ + writel_relaxed(0x0, d->base + INT_TC1_MASK); + writel_relaxed(0x0, d->base + INT_TC2_MASK); + writel_relaxed(0x0, d->base + INT_ERR1_MASK); + writel_relaxed(0x0, d->base + INT_ERR2_MASK); + } +} + +static irqreturn_t hisi_asp_dma_int_handler(int irq, void *dev_id) +{ + struct hisi_asp_dma_dev *d = (struct hisi_asp_dma_dev *)dev_id; + struct hisi_asp_dma_phy *p; + struct hisi_asp_dma_chan *c; + u32 stat = readl_relaxed(d->base + INT_STAT); + u32 tc1 = readl_relaxed(d->base + INT_TC1); + u32 tc2 = readl_relaxed(d->base + INT_TC2); + u32 err1 = readl_relaxed(d->base + INT_ERR1); + u32 err2 = readl_relaxed(d->base + INT_ERR2); + u32 i, irq_chan = 0; + + while (stat) { + i = __ffs(stat); + stat &= ~BIT(i); + if (likely(tc1 & BIT(i)) || (tc2 & BIT(i))) { + unsigned long flags; + + p = &d->phy[i]; + c = p->vchan; + if (c && (tc1 & BIT(i))) { + spin_lock_irqsave(&c->vc.lock, flags); + vchan_cookie_complete(&p->ds_run->vd); + p->ds_done = p->ds_run; + p->ds_run = NULL; + spin_unlock_irqrestore(&c->vc.lock, flags); + } + if (c && (tc2 & BIT(i))) { + spin_lock_irqsave(&c->vc.lock, flags); + if (p->ds_run != NULL) + vchan_cyclic_callback(&p->ds_run->vd); + spin_unlock_irqrestore(&c->vc.lock, flags); + } + irq_chan |= BIT(i); + } + if (unlikely((err1 & BIT(i)) || (err2 & BIT(i)))) + dev_warn(d->slave.dev, "DMA ERR\n"); + } + + writel_relaxed(irq_chan, d->base + INT_TC1_RAW); + writel_relaxed(irq_chan, d->base + INT_TC2_RAW); + writel_relaxed(err1, d->base + INT_ERR1_RAW); + writel_relaxed(err2, d->base + INT_ERR2_RAW); + + if (irq_chan) + tasklet_schedule(&d->task); + + if (irq_chan || err1 || err2) + return IRQ_HANDLED; + + return IRQ_NONE; +} + +static int hisi_asp_dma_start_txd(struct hisi_asp_dma_chan *c) +{ + struct hisi_asp_dma_dev *d = to_hisi_asp_dma(c->vc.chan.device); + struct virt_dma_desc *vd = vchan_next_desc(&c->vc); + + if (!c->phy) + return -EAGAIN; + + if (BIT(c->phy->idx) & hisi_asp_dma_get_chan_stat(d)) + return -EAGAIN; + + if (vd) { + struct hisi_asp_dma_desc_sw *ds = + container_of(vd, struct hisi_asp_dma_desc_sw, vd); + /* + * fetch and remove request from vc->desc_issued + * so vc->desc_issued only contains desc pending + */ + list_del(&ds->vd.node); + + WARN_ON_ONCE(c->phy->ds_run); + WARN_ON_ONCE(c->phy->ds_done); + c->phy->ds_run = ds; + /* start dma */ + hisi_asp_dma_set_desc(c->phy, &ds->desc_hw[0]); + return 0; + } + return -EAGAIN; +} + +/* + * XXX This function doesn't seem to actually do much, as the behavior + * is the same with or without it. The 1 >> c->phy->idx bit doesn't make + * total sense, but for now I'm leaving it as-is until I can better + * understand the intent. -jstultz + */ +static void +hisi_asp_dma_set_cyclic(struct hisi_asp_dma_chan *c, struct hisi_asp_dma_dev *d, int cyclic) +{ + int mask = 1 << c->phy->idx; + + writel_relaxed(1 >> c->phy->idx, d->base + INT_TC2_RAW); + if (cyclic) + writel_relaxed(readl(d->base + INT_TC2_MASK) |mask, + d->base + INT_TC2_MASK); + else + writel_relaxed(readl(d->base + INT_TC2_MASK) & ~mask, + d->base + INT_TC2_MASK); +} + +static void hisi_asp_dma_tasklet(unsigned long arg) +{ + struct hisi_asp_dma_dev *d = (struct hisi_asp_dma_dev *)arg; + struct hisi_asp_dma_phy *p; + struct hisi_asp_dma_chan *c, *cn; + unsigned pch, pch_alloc = 0; + + /* check new dma request of running channel in vc->desc_issued */ + list_for_each_entry_safe(c, cn, &d->slave.channels, vc.chan.device_node) { + spin_lock_irq(&c->vc.lock); + p = c->phy; + if (p && p->ds_done) { + if (hisi_asp_dma_start_txd(c)) { + /* No current txd associated with this channel */ + dev_dbg(d->slave.dev, "pchan %u: free\n", p->idx); + hisi_asp_dma_set_cyclic(c, d, 0); + /* Mark this channel free */ + c->phy = NULL; + p->vchan = NULL; + } + } + spin_unlock_irq(&c->vc.lock); + } + + /* check new channel request in d->chan_pending */ + spin_lock_irq(&d->lock); + for (pch = 0; pch < d->dma_channels; pch++) { + p = &d->phy[pch]; + + if (p->vchan == NULL && !list_empty(&d->chan_pending)) { + c = list_first_entry(&d->chan_pending, + struct hisi_asp_dma_chan, node); + /* remove from d->chan_pending */ + list_del_init(&c->node); + pch_alloc |= 1 << pch; + /* Mark this channel allocated */ + p->vchan = c; + c->phy = p; + hisi_asp_dma_set_cyclic(c, d, c->cyclic); + dev_dbg(d->slave.dev, "pchan %u: alloc vchan %p\n", pch, &c->vc); + } + } + spin_unlock_irq(&d->lock); + + for (pch = 0; pch < d->dma_channels; pch++) { + if (pch_alloc & (1 << pch)) { + p = &d->phy[pch]; + c = p->vchan; + if (c) { + spin_lock_irq(&c->vc.lock); + hisi_asp_dma_start_txd(c); + spin_unlock_irq(&c->vc.lock); + } + } + } +} + +static void hisi_asp_dma_free_chan_resources(struct dma_chan *chan) +{ + struct hisi_asp_dma_chan *c = to_hisi_asp_chan(chan); + struct hisi_asp_dma_dev *d = to_hisi_asp_dma(chan->device); + unsigned long flags; + + spin_lock_irqsave(&d->lock, flags); + list_del_init(&c->node); + spin_unlock_irqrestore(&d->lock, flags); + + vchan_free_chan_resources(&c->vc); + c->ccfg = 0; +} + +static enum dma_status hisi_asp_dma_tx_status(struct dma_chan *chan, + dma_cookie_t cookie, struct dma_tx_state *state) +{ + struct hisi_asp_dma_chan *c = to_hisi_asp_chan(chan); + struct hisi_asp_dma_dev *d = to_hisi_asp_dma(chan->device); + struct hisi_asp_dma_phy *p; + struct virt_dma_desc *vd; + unsigned long flags; + enum dma_status ret; + size_t bytes = 0; + + ret = dma_cookie_status(&c->vc.chan, cookie, state); + if (ret == DMA_COMPLETE) + return ret; + + spin_lock_irqsave(&c->vc.lock, flags); + p = c->phy; + ret = c->status; + + /* + * If the cookie is on our issue queue, then the residue is + * its total size. + */ + vd = vchan_find_desc(&c->vc, cookie); + if (vd && !c->cyclic) { + bytes = container_of(vd, struct hisi_asp_dma_desc_sw, vd)->size; + } else if ((!p) || (!p->ds_run)) { + bytes = 0; + } else { + struct hisi_asp_dma_desc_sw *ds = p->ds_run; + u32 clli = 0, index = 0; + + bytes = hisi_asp_dma_get_curr_cnt(d, p); + clli = hisi_asp_dma_get_curr_lli(p); + index = ((clli - ds->desc_hw_lli) / sizeof(struct hisi_asp_desc_hw)) + 1; + for (; index < ds->desc_num; index++) { + bytes += ds->desc_hw[index].count; + /* end of lli */ + if (!ds->desc_hw[index].lli) + break; + } + } + spin_unlock_irqrestore(&c->vc.lock, flags); + dma_set_residue(state, bytes); + return ret; +} + +static void hisi_asp_dma_issue_pending(struct dma_chan *chan) +{ + struct hisi_asp_dma_chan *c = to_hisi_asp_chan(chan); + struct hisi_asp_dma_dev *d = to_hisi_asp_dma(chan->device); + unsigned long flags; + + spin_lock_irqsave(&c->vc.lock, flags); + /* add request to vc->desc_issued */ + if (vchan_issue_pending(&c->vc)) { + spin_lock(&d->lock); + if (!c->phy) { + if (list_empty(&c->node)) { + /* if new channel, add chan_pending */ + list_add_tail(&c->node, &d->chan_pending); + /* check in tasklet */ + tasklet_schedule(&d->task); + dev_dbg(d->slave.dev, "vchan %p: issued\n", &c->vc); + } + } + spin_unlock(&d->lock); + } else + dev_dbg(d->slave.dev, "vchan %p: nothing to issue\n", &c->vc); + spin_unlock_irqrestore(&c->vc.lock, flags); +} + +static void hisi_asp_dma_fill_desc(struct hisi_asp_dma_desc_sw *ds, dma_addr_t dst, + dma_addr_t src, size_t len, u32 num, u32 ccfg) +{ + if (num != ds->desc_num - 1) + ds->desc_hw[num].lli = ds->desc_hw_lli + (num + 1) * + sizeof(struct hisi_asp_desc_hw); + + ds->desc_hw[num].lli |= CX_LLI_CHAIN_EN; + ds->desc_hw[num].count = len; + ds->desc_hw[num].saddr = src; + ds->desc_hw[num].daddr = dst; + ds->desc_hw[num].config = ccfg; + + pr_debug("%s: hisi_asp_dma_desc_sw = %p, desc_hw = %p (num = %d) lli: 0x%x, count: 0x%x, saddr: 0x%x, daddr 0x%x, cfg: 0x%x\n", __func__, + (void *)ds, &ds->desc_hw[num], num, + ds->desc_hw[num].lli, ds->desc_hw[num].count, ds->desc_hw[num].saddr, ds->desc_hw[num].daddr, ds->desc_hw[num].config); + +} + +static struct hisi_asp_dma_desc_sw *hisi_asp_dma_alloc_desc_resource(int num, + struct dma_chan *chan) +{ + struct hisi_asp_dma_chan *c = to_hisi_asp_chan(chan); + struct hisi_asp_dma_desc_sw *ds; + struct hisi_asp_dma_dev *d = to_hisi_asp_dma(chan->device); + int lli_limit = LLI_BLOCK_SIZE / sizeof(struct hisi_asp_desc_hw); + + if (num > lli_limit) { + dev_dbg(chan->device->dev, "vch %p: sg num %d exceed max %d\n", + &c->vc, num, lli_limit); + return NULL; + } + + ds = kzalloc(sizeof(*ds), GFP_ATOMIC); + if (!ds) + return NULL; + + ds->desc_hw = dma_pool_alloc(d->pool, GFP_NOWAIT, &ds->desc_hw_lli); + if (!ds->desc_hw) { + dev_dbg(chan->device->dev, "vch %p: dma alloc fail\n", &c->vc); + kfree(ds); + return NULL; + } + memset(ds->desc_hw, 0, sizeof(struct hisi_asp_desc_hw) * num); + ds->desc_num = num; + return ds; +} + +static struct dma_async_tx_descriptor *hisi_asp_dma_prep_memcpy( + struct dma_chan *chan, dma_addr_t dst, dma_addr_t src, + size_t len, unsigned long flags) +{ + struct hisi_asp_dma_chan *c = to_hisi_asp_chan(chan); + struct hisi_asp_dma_desc_sw *ds; + size_t copy = 0; + int num = 0; + + if (!len) + return NULL; + + num = DIV_ROUND_UP(len, DMA_MAX_SIZE); + + ds = hisi_asp_dma_alloc_desc_resource(num, chan); + if (!ds) { + dev_dbg(chan->device->dev, "vchan %p: kzalloc fail\n", &c->vc); + return NULL; + } + c->cyclic = 0; + ds->size = len; + num = 0; + + if (!c->ccfg) { + /* default is memtomem, without calling device_config */ + c->ccfg = CX_CFG_SRCINCR | CX_CFG_DSTINCR | CX_CFG_EN; + c->ccfg |= (0xf << 20) | (0xf << 24); /* burst = 16 */ + c->ccfg |= (0x3 << 12) | (0x3 << 16); /* width = 64 bit */ + } + + do { + copy = min_t(size_t, len, DMA_MAX_SIZE); + hisi_asp_dma_fill_desc(ds, dst, src, copy, num++, c->ccfg); + + if (c->dir == DMA_MEM_TO_DEV) { + src += copy; + } else if (c->dir == DMA_DEV_TO_MEM) { + dst += copy; + } else { + src += copy; + dst += copy; + } + len -= copy; + } while (len); + + ds->desc_hw[num-1].lli = 0; /* end of link */ + return vchan_tx_prep(&c->vc, &ds->vd, flags); +} + +static struct dma_async_tx_descriptor *hisi_asp_dma_prep_slave_sg( + struct dma_chan *chan, struct scatterlist *sgl, unsigned int sglen, + enum dma_transfer_direction dir, unsigned long flags, void *context) +{ + struct hisi_asp_dma_chan *c = to_hisi_asp_chan(chan); + struct hisi_asp_dma_desc_sw *ds; + size_t len, avail, total = 0; + struct scatterlist *sg; + dma_addr_t addr, src = 0, dst = 0; + int num = sglen, i; + + if (sgl == NULL) + return NULL; + + c->cyclic = 0; + + for_each_sg(sgl, sg, sglen, i) { + avail = sg_dma_len(sg); + pr_err(" avail=0x%x\n", (int)avail); + if (avail > DMA_MAX_SIZE) + num += DIV_ROUND_UP(avail, DMA_MAX_SIZE) - 1; + } + + ds = hisi_asp_dma_alloc_desc_resource(num, chan); + if (!ds) { + dev_err(chan->device->dev, "vchan %p: kzalloc fail\n", &c->vc); + return NULL; + } + num = 0; + + for_each_sg(sgl, sg, sglen, i) { + addr = sg_dma_address(sg); + avail = sg_dma_len(sg); + total += avail; + + do { + len = min_t(size_t, avail, DMA_MAX_SIZE); + + if (dir == DMA_MEM_TO_DEV) { + src = addr; + dst = c->dev_addr; + } else if (dir == DMA_DEV_TO_MEM) { + src = c->dev_addr; + dst = addr; + } + + hisi_asp_dma_fill_desc(ds, dst, src, len, num++, c->ccfg); + + addr += len; + avail -= len; + } while (avail); + } + + ds->desc_hw[num-1].lli = 0; /* end of link */ + ds->size = total; + return vchan_tx_prep(&c->vc, &ds->vd, flags); +} + +static struct dma_async_tx_descriptor * +hisi_asp_dma_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t buf_addr, + size_t buf_len, size_t period_len, + enum dma_transfer_direction dir, + unsigned long flags) +{ + struct hisi_asp_dma_chan *c = to_hisi_asp_chan(chan); + struct hisi_asp_dma_desc_sw *ds; + size_t len, avail, total = 0; + dma_addr_t addr, src = 0, dst = 0; + int num = 1, since = 0; + size_t modulo = DMA_CYCLIC_MAX_PERIOD; + u32 en_tc2 = 0; + + pr_debug("%s: buf %p, dst %p, buf len %d, period_len = %d, dir %d\n", + __func__, (void *)buf_addr, (void *)to_hisi_asp_chan(chan)->dev_addr, + (int)buf_len, (int)period_len, (int)dir); + + avail = buf_len; + if (avail > modulo) + num += DIV_ROUND_UP(avail, modulo) - 1; + + ds = hisi_asp_dma_alloc_desc_resource(num, chan); + if (!ds) { + dev_err(chan->device->dev, "vchan %p: kzalloc fail\n", &c->vc); + return NULL; + } + + c->cyclic = 1; + addr = buf_addr; + avail = buf_len; + total = avail; + num = 0; + + if (period_len < modulo) + modulo = period_len; + + do { + len = min_t(size_t, avail, modulo); + + if (dir == DMA_MEM_TO_DEV) { + src = addr; + dst = c->dev_addr; + } else if (dir == DMA_DEV_TO_MEM) { + src = c->dev_addr; + dst = addr; + } + since += len; + if (since >= period_len) { + /* descriptor asks for TC2 interrupt on completion */ + en_tc2 = CX_CFG_NODEIRQ; + since -= period_len; + } else + en_tc2 = 0; + + hisi_asp_dma_fill_desc(ds, dst, src, len, num++, c->ccfg | en_tc2); + + addr += len; + avail -= len; + } while (avail); + + /* "Cyclic" == end of link points back to start of link */ + ds->desc_hw[num - 1].lli |= ds->desc_hw_lli; + + ds->size = total; + + return vchan_tx_prep(&c->vc, &ds->vd, flags); +} + + +static int hisi_asp_dma_config(struct dma_chan *chan, + struct dma_slave_config *cfg) +{ + struct hisi_asp_dma_chan *c = to_hisi_asp_chan(chan); + u32 maxburst = 0, val = 0; + enum dma_slave_buswidth width = DMA_SLAVE_BUSWIDTH_UNDEFINED; + + if (cfg == NULL) + return -EINVAL; + c->dir = cfg->direction; + if (c->dir == DMA_DEV_TO_MEM) { + c->ccfg = CX_CFG_DSTINCR; + c->dev_addr = cfg->src_addr; + maxburst = cfg->src_maxburst; + width = cfg->src_addr_width; + } else if (c->dir == DMA_MEM_TO_DEV) { + c->ccfg = CX_CFG_SRCINCR; + c->dev_addr = cfg->dst_addr; + maxburst = cfg->dst_maxburst; + width = cfg->dst_addr_width; + } + switch (width) { + case DMA_SLAVE_BUSWIDTH_1_BYTE: + case DMA_SLAVE_BUSWIDTH_2_BYTES: + case DMA_SLAVE_BUSWIDTH_4_BYTES: + case DMA_SLAVE_BUSWIDTH_8_BYTES: + val = __ffs(width); + break; + default: + val = 3; + break; + } + c->ccfg |= (val << 12) | (val << 16); + + if ((maxburst == 0) || (maxburst > 16)) + val = 15; + else + val = maxburst - 1; + c->ccfg |= (val << 20) | (val << 24); + c->ccfg |= CX_CFG_MEM2PER | CX_CFG_EN; + + /* specific request line */ + c->ccfg |= c->vc.chan.chan_id << 4; + + return 0; +} + +static void hisi_asp_dma_free_desc(struct virt_dma_desc *vd) +{ + struct hisi_asp_dma_desc_sw *ds = + container_of(vd, struct hisi_asp_dma_desc_sw, vd); + struct hisi_asp_dma_dev *d = to_hisi_asp_dma(vd->tx.chan->device); + + dma_pool_free(d->pool, ds->desc_hw, ds->desc_hw_lli); + kfree(ds); +} + + +static int hisi_asp_dma_terminate_all(struct dma_chan *chan) +{ + struct hisi_asp_dma_chan *c = to_hisi_asp_chan(chan); + struct hisi_asp_dma_dev *d = to_hisi_asp_dma(chan->device); + struct hisi_asp_dma_phy *p = c->phy; + unsigned long flags; + LIST_HEAD(head); + + dev_dbg(d->slave.dev, "vchan %p: terminate all\n", &c->vc); + + /* Prevent this channel being scheduled */ + spin_lock(&d->lock); + list_del_init(&c->node); + spin_unlock(&d->lock); + + /* Clear the tx descriptor lists */ + spin_lock_irqsave(&c->vc.lock, flags); + vchan_get_all_descriptors(&c->vc, &head); + if (p) { + /* vchan is assigned to a pchan - stop the channel */ + hisi_asp_dma_terminate_chan(p, d); + c->phy = NULL; + p->vchan = NULL; + if (p->ds_run) { + hisi_asp_dma_free_desc(&p->ds_run->vd); + p->ds_run = NULL; + } + if (p->ds_done) { + hisi_asp_dma_free_desc(&p->ds_done->vd); + p->ds_done = NULL; + } + + } + spin_unlock_irqrestore(&c->vc.lock, flags); + vchan_dma_desc_free_list(&c->vc, &head); + + return 0; +} + +static int hisi_asp_dma_transfer_pause(struct dma_chan *chan) +{ + struct hisi_asp_dma_chan *c = to_hisi_asp_chan(chan); + struct hisi_asp_dma_dev *d = to_hisi_asp_dma(chan->device); + struct hisi_asp_dma_phy *p = c->phy; + + dev_dbg(d->slave.dev, "vchan %p: pause\n", &c->vc); + if (c->status == DMA_IN_PROGRESS) { + c->status = DMA_PAUSED; + if (p) { + hisi_asp_dma_pause_dma(p, false); + } else { + spin_lock(&d->lock); + list_del_init(&c->node); + spin_unlock(&d->lock); + } + } + + return 0; +} + +static int hisi_asp_dma_transfer_resume(struct dma_chan *chan) +{ + struct hisi_asp_dma_chan *c = to_hisi_asp_chan(chan); + struct hisi_asp_dma_dev *d = to_hisi_asp_dma(chan->device); + struct hisi_asp_dma_phy *p = c->phy; + unsigned long flags; + + dev_dbg(d->slave.dev, "vchan %p: resume\n", &c->vc); + spin_lock_irqsave(&c->vc.lock, flags); + if (c->status == DMA_PAUSED) { + c->status = DMA_IN_PROGRESS; + if (p) { + hisi_asp_dma_pause_dma(p, true); + } else if (!list_empty(&c->vc.desc_issued)) { + spin_lock(&d->lock); + list_add_tail(&c->node, &d->chan_pending); + spin_unlock(&d->lock); + } + } + spin_unlock_irqrestore(&c->vc.lock, flags); + + return 0; +} + +static const struct of_device_id hisi_asp_pdma_dt_ids[] = { + { .compatible = "hisilicon,hisi-pcm-asp-dma", }, + {} +}; +MODULE_DEVICE_TABLE(of, hisi_asp_pdma_dt_ids); + +static struct dma_chan *hisi_asp_of_dma_simple_xlate(struct of_phandle_args *dma_spec, + struct of_dma *ofdma) +{ + struct hisi_asp_dma_dev *d = ofdma->of_dma_data; + unsigned int request = dma_spec->args[0]; + + if (request > d->dma_requests) + return NULL; + + return dma_get_slave_channel(&(d->chans[request].vc.chan)); +} + +static int hisi_asp_dma_probe(struct platform_device *op) +{ + struct hisi_asp_dma_dev *d; + const struct of_device_id *of_id; + struct resource *iores; + int i, ret, irq = 0; + + iores = platform_get_resource(op, IORESOURCE_MEM, 0); + if (!iores) + return -EINVAL; + + d = devm_kzalloc(&op->dev, sizeof(*d), GFP_KERNEL); + if (!d) + return -ENOMEM; + + d->base = devm_ioremap_resource(&op->dev, iores); + if (IS_ERR(d->base)) + return PTR_ERR(d->base); + + of_id = of_match_device(hisi_asp_pdma_dt_ids, &op->dev); + if (of_id) { + of_property_read_u32((&op->dev)->of_node, + "dma-channels", &d->dma_channels); + of_property_read_u32((&op->dev)->of_node, + "dma-requests", &d->dma_requests); + } + + irq = platform_get_irq(op, 0); + ret = devm_request_irq(&op->dev, irq, + hisi_asp_dma_int_handler, 0, DRIVER_NAME, d); + if (ret) + return ret; + + /* A DMA memory pool for LLIs, align on 32-byte boundary */ + d->pool = dmam_pool_create(DRIVER_NAME, &op->dev, + LLI_BLOCK_SIZE, 32, 0); + if (!d->pool) + return -ENOMEM; + + /* init phy channel */ + d->phy = devm_kzalloc(&op->dev, + d->dma_channels * sizeof(struct hisi_asp_dma_phy), GFP_KERNEL); + if (d->phy == NULL) + return -ENOMEM; + + for (i = 0; i < d->dma_channels; i++) { + struct hisi_asp_dma_phy *p = &d->phy[i]; + + p->idx = i; + p->base = d->base + i * 0x40; + } + + INIT_LIST_HEAD(&d->slave.channels); + dma_cap_set(DMA_SLAVE, d->slave.cap_mask); + dma_cap_set(DMA_MEMCPY, d->slave.cap_mask); + dma_cap_set(DMA_CYCLIC, d->slave.cap_mask); + d->slave.dev = &op->dev; + d->slave.device_free_chan_resources = hisi_asp_dma_free_chan_resources; + d->slave.device_tx_status = hisi_asp_dma_tx_status; + d->slave.device_prep_dma_memcpy = hisi_asp_dma_prep_memcpy; + d->slave.device_prep_slave_sg = hisi_asp_dma_prep_slave_sg; + d->slave.device_prep_dma_cyclic = hisi_asp_dma_prep_dma_cyclic; + d->slave.device_issue_pending = hisi_asp_dma_issue_pending; + d->slave.device_config = hisi_asp_dma_config; + d->slave.device_pause = hisi_asp_dma_transfer_pause; + d->slave.device_resume = hisi_asp_dma_transfer_resume; + d->slave.device_terminate_all = hisi_asp_dma_terminate_all; + d->slave.copy_align = DMA_ALIGN; + + /* init virtual channel */ + d->chans = devm_kzalloc(&op->dev, + d->dma_requests * sizeof(struct hisi_asp_dma_chan), GFP_KERNEL); + if (d->chans == NULL) + return -ENOMEM; + + for (i = 0; i < d->dma_requests; i++) { + struct hisi_asp_dma_chan *c = &d->chans[i]; + + c->status = DMA_IN_PROGRESS; + INIT_LIST_HEAD(&c->node); + c->vc.desc_free = hisi_asp_dma_free_desc; + vchan_init(&c->vc, &d->slave); + } + + /* Enable clock before accessing registers */ + ret = clk_prepare_enable(d->clk); + if (ret < 0) { + dev_err(&op->dev, "clk_prepare_enable failed: %d\n", ret); + return ret; + } + + hisi_asp_dma_enable_dma(d, true); + + ret = dma_async_device_register(&d->slave); + if (ret){ + dev_err(&op->dev, "failed to register dma device\n"); + goto err_disable_clk; + } + + ret = of_dma_controller_register((&op->dev)->of_node, + hisi_asp_of_dma_simple_xlate, d); + if (ret){ + dev_err(&op->dev, "failed to register dma controller\n"); + goto of_dma_register_fail; + } + + spin_lock_init(&d->lock); + INIT_LIST_HEAD(&d->chan_pending); + tasklet_init(&d->task, hisi_asp_dma_tasklet, (unsigned long)d); + platform_set_drvdata(op, d); + dev_info(&op->dev, "initialized\n"); + + return 0; + +of_dma_register_fail: + dma_async_device_unregister(&d->slave); +err_disable_clk: + clk_disable_unprepare(d->clk); + return ret; +} + +static int hisi_asp_dma_remove(struct platform_device *op) +{ + struct hisi_asp_dma_chan *c, *cn; + struct hisi_asp_dma_dev *d = platform_get_drvdata(op); + + dma_async_device_unregister(&d->slave); + of_dma_controller_free((&op->dev)->of_node); + + list_for_each_entry_safe(c, cn, &d->slave.channels, vc.chan.device_node) { + list_del(&c->vc.chan.device_node); + tasklet_kill(&c->vc.task); + } + tasklet_kill(&d->task); + clk_disable_unprepare(d->clk); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int hisi_asp_dma_suspend_dev(struct device *dev) +{ + struct hisi_asp_dma_dev *d = dev_get_drvdata(dev); + u32 stat = 0; + + stat = hisi_asp_dma_get_chan_stat(d); + if (stat) { + dev_warn(d->slave.dev, + "chan %d is running fail to suspend\n", stat); + return -1; + } + hisi_asp_dma_enable_dma(d, false); + clk_disable_unprepare(d->clk); + return 0; +} + +static int hisi_asp_dma_resume_dev(struct device *dev) +{ + struct hisi_asp_dma_dev *d = dev_get_drvdata(dev); + int ret = 0; + + ret = clk_prepare_enable(d->clk); + if (ret < 0) { + dev_err(d->slave.dev, "clk_prepare_enable failed: %d\n", ret); + return ret; + } + hisi_asp_dma_enable_dma(d, true); + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(hisi_asp_dma_pmops, hisi_asp_dma_suspend_dev, hisi_asp_dma_resume_dev); + +static struct platform_driver hisi_asp_pdma_driver = { + .driver = { + .name = DRIVER_NAME, + .pm = &hisi_asp_dma_pmops, + .of_match_table = hisi_asp_pdma_dt_ids, + }, + .probe = hisi_asp_dma_probe, + .remove = hisi_asp_dma_remove, +}; + +module_platform_driver(hisi_asp_pdma_driver); + +MODULE_DESCRIPTION("Hisilicon hisi asp DMA Driver"); +MODULE_ALIAS("platform:hisiaspdma"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/hisilicon/Kconfig b/sound/soc/hisilicon/Kconfig index 4356d5a1d338..e5547797ee35 100644 --- a/sound/soc/hisilicon/Kconfig +++ b/sound/soc/hisilicon/Kconfig @@ -1,5 +1,11 @@ config SND_I2S_HI6210_I2S - tristate "Hisilicon I2S controller" + tristate "Hisilicon Hi6210 I2S controller" + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Hisilicon I2S + +config SND_I2S_HISI_I2S + tristate "Hisilicon 960 I2S controller" select SND_SOC_GENERIC_DMAENGINE_PCM help Hisilicon I2S diff --git a/sound/soc/hisilicon/Makefile b/sound/soc/hisilicon/Makefile index e8095e2af91a..30100de7491b 100644 --- a/sound/soc/hisilicon/Makefile +++ b/sound/soc/hisilicon/Makefile @@ -1 +1,2 @@ obj-$(CONFIG_SND_I2S_HI6210_I2S) += hi6210-i2s.o +obj-$(CONFIG_SND_I2S_HISI_I2S) += hisi-i2s.o diff --git a/sound/soc/hisilicon/hisi-i2s.c b/sound/soc/hisilicon/hisi-i2s.c new file mode 100644 index 000000000000..8d3cd87f725c --- /dev/null +++ b/sound/soc/hisilicon/hisi-i2s.c @@ -0,0 +1,498 @@ +/* + * linux/sound/soc/m8m/hisi_i2s.c - I2S IP driver + * + * Copyright (C) 2015 Linaro, Ltd + * Author: Andy Green + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This driver only deals with S2 interface (BT) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hisi-i2s.h" + +struct hisi_i2s { + struct device *dev; + struct reset_control *rc; + int clocks; + struct regulator *regu_asp; + struct pinctrl *pctrl; + struct pinctrl_state *pin_default; + struct pinctrl_state *pin_idle; + struct clk *asp_subsys_clk; + struct snd_soc_dai_driver dai; + void __iomem *base; + void __iomem *base_syscon; + phys_addr_t base_phys; + struct snd_dmaengine_dai_dma_data dma_data[2]; + spinlock_t lock; + int rate; + int format; + int bits; + int channels; + u32 master; + u32 status; +}; + +static void hisi_bits(struct hisi_i2s *i2s, u32 ofs, u32 reset, u32 set) +{ + u32 val = readl(i2s->base + ofs) & ~reset; + + writel(val | set, i2s->base + ofs); +} + +static void hisi_syscon_bits(struct hisi_i2s *i2s, u32 ofs, u32 reset, u32 set) +{ + u32 val = readl(i2s->base_syscon + ofs) & ~reset; + + writel(val | set, i2s->base_syscon + ofs); +} + +static int _hisi_i2s_set_fmt(struct hisi_i2s *i2s, + struct snd_pcm_substream *substream) +{ + switch (i2s->format & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + i2s->master = false; + hisi_syscon_bits(i2s, HI_ASP_CFG_R_CLK_SEL_REG, 0, HI_ASP_CFG_R_CLK_SEL_EN); + break; + case SND_SOC_DAIFMT_CBS_CFS: + i2s->master = true; + hisi_syscon_bits(i2s, HI_ASP_CFG_R_CLK_SEL_REG, HI_ASP_CFG_R_CLK_SEL_EN,0); + break; + default: + return -EINVAL; + } + + return 0; +} + +int hisi_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct hisi_i2s *i2s = dev_get_drvdata(cpu_dai->dev); + u32 val; + int ret = 0; + +// ret = regulator_enable(i2s->regu_asp); + if (ret) { + dev_err(i2s->dev, "couldn't enable regulators %d\n", ret); + ret = -ENOENT; + return ret; + } + +// ret = clk_prepare_enable(i2s->asp_subsys_clk); + if (ret < 0) { + dev_err(i2s->dev, "couldn't enable clk %d\n", ret); + regulator_disable(i2s->regu_asp); + return ret; + } + + /* deassert reset on sio_bt*/ + hisi_syscon_bits(i2s, HI_ASP_CFG_R_RST_CTRLDIS_REG, 0,BIT(2)|BIT(6)|BIT(8)|BIT(16)); + hisi_syscon_bits(i2s, HI_ASP_CFG_R_RST_CTRLDIS_REG, 0,0xffffffff); + + /* enable clk before frequency division */ + hisi_syscon_bits(i2s, HI_ASP_CFG_R_GATE_EN_REG, 0,BIT(5)|BIT(6)); + hisi_syscon_bits(i2s, HI_ASP_CFG_R_GATE_EN_REG, 0,0xffffffff); +// hisi_syscon_bits(i2s, HI_ASP_CFG_R_SEC_REG, 0,0xffffffff); +// hisi_syscon_bits(i2s, 0x5c, 0,0xffffffff); + + /* enable frequency division */ + hisi_syscon_bits(i2s, HI_ASP_CFG_R_GATE_CLKDIV_EN_REG, 0,BIT(2)|BIT(5)); + + + /* select clk */ + hisi_syscon_bits(i2s, HI_ASP_CFG_R_CLK_SEL_REG, HI_ASP_MASK,HI_ASP_CFG_R_CLK_SEL); + hisi_syscon_bits(i2s, HI_ASP_CFG_R_CLK_SEL_REG, HI_ASP_MASK,0x150010); + + + /* select clk_div */ + hisi_syscon_bits(i2s, HI_ASP_CFG_R_CLK1_DIV_REG, HI_ASP_MASK,HI_ASP_CFG_R_CLK1_DIV_SEL); + hisi_syscon_bits(i2s, HI_ASP_CFG_R_CLK4_DIV_REG, HI_ASP_MASK,HI_ASP_CFG_R_CLK4_DIV_SEL); + hisi_syscon_bits(i2s, HI_ASP_CFG_R_CLK6_DIV_REG, HI_ASP_MASK,HI_ASP_CFG_R_CLK6_DIV_SEL); + + val = readl(i2s->base_syscon + HI_ASP_CFG_R_SEC_REG); + pr_info("****** %s val 0x%x \n", __func__,val); + +#if 1 + /* sio config */ + hisi_bits(i2s, HI_ASP_SIO_MODE_REG, HI_ASP_MASK, 0x0); + hisi_bits(i2s, HI_ASP_SIO_DATA_WIDTH_SET_REG, HI_ASP_MASK, 0x09); + hisi_bits(i2s, HI_ASP_SIO_I2S_POS_MERGE_EN_REG, HI_ASP_MASK, 0x1); + hisi_bits(i2s, HI_ASP_SIO_I2S_START_POS_REG, HI_ASP_MASK, 0x0); +#endif + pr_info("****** %s return \n", __func__); + return 0; +} +void hisi_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct hisi_i2s *i2s = dev_get_drvdata(cpu_dai->dev); + int ret = 0; + +// ret = regulator_disable(i2s->regu_asp); + if (ret) { + dev_err(i2s->dev, "regulator disable failed!, ret:%d\n", ret); + } + + if (!IS_ERR_OR_NULL(i2s->asp_subsys_clk)) { + clk_disable_unprepare(i2s->asp_subsys_clk); + } + +} + +static void hisi_i2s_txctrl(struct snd_soc_dai *cpu_dai, int on) +{ + struct hisi_i2s *i2s = dev_get_drvdata(cpu_dai->dev); + + spin_lock(&i2s->lock); + + if (on) { + /* enable SIO TX */ + hisi_bits(i2s, HI_ASP_SIO_CT_SET_REG, 0, + HI_ASP_SIO_TX_ENABLE | HI_ASP_SIO_TX_DATA_MERGE | HI_ASP_SIO_TX_FIFO_THRESHOLD | + HI_ASP_SIO_RX_ENABLE |HI_ASP_SIO_RX_DATA_MERGE | HI_ASP_SIO_RX_FIFO_THRESHOLD); + } else + /* disable SIO TX */ + hisi_bits(i2s, HI_ASP_SIO_CT_CLR_REG, 0, HI_ASP_SIO_TX_ENABLE |HI_ASP_SIO_RX_ENABLE); + spin_unlock(&i2s->lock); +} + +static void hisi_i2s_rxctrl(struct snd_soc_dai *cpu_dai, int on) +{ + struct hisi_i2s *i2s = dev_get_drvdata(cpu_dai->dev); + + spin_lock(&i2s->lock); + if (on) + /* enable SIO RX */ + hisi_bits(i2s, HI_ASP_SIO_CT_SET_REG, 0, + HI_ASP_SIO_TX_ENABLE | HI_ASP_SIO_TX_DATA_MERGE | HI_ASP_SIO_TX_FIFO_THRESHOLD | + HI_ASP_SIO_RX_ENABLE |HI_ASP_SIO_RX_DATA_MERGE | HI_ASP_SIO_RX_FIFO_THRESHOLD); + else + /* disable SIO RX */ + hisi_bits(i2s, HI_ASP_SIO_CT_CLR_REG,0, HI_ASP_SIO_TX_ENABLE |HI_ASP_SIO_RX_ENABLE); + spin_unlock(&i2s->lock); +} + +static int hisi_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + return 0; +} + +static int hisi_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct hisi_i2s *i2s = dev_get_drvdata(cpu_dai->dev); + + i2s->format = fmt; + i2s->master = (i2s->format & SND_SOC_DAIFMT_MASTER_MASK) == + SND_SOC_DAIFMT_CBS_CFS; + + return 0; +} + +static int hisi_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct hisi_i2s *i2s = dev_get_drvdata(cpu_dai->dev); + struct snd_dmaengine_dai_dma_data *dma_data; + + dma_data = snd_soc_dai_get_dma_data(cpu_dai, substream); + + _hisi_i2s_set_fmt(i2s, substream); + + dma_data->maxburst = 4; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data->addr = i2s->base_phys + HI_ASP_SIO_I2S_DUAL_TX_CHN_REG; + else + dma_data->addr = i2s->base_phys + HI_ASP_SIO_I2S_DUAL_RX_CHN_REG; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_U16_LE: + case SNDRV_PCM_FORMAT_S16_LE: + i2s->bits = 16; + dma_data->addr_width = 4; + break; + + case SNDRV_PCM_FORMAT_U24_LE: + case SNDRV_PCM_FORMAT_S24_LE: + i2s->bits = 32; + dma_data->addr_width = 4; + break; + default: + dev_err(cpu_dai->dev, "Bad format\n"); + return -EINVAL; + } + + return 0; +} + +static int hisi_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *cpu_dai) +{ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + hisi_i2s_rxctrl(cpu_dai, 1); + else + hisi_i2s_txctrl(cpu_dai, 1); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + hisi_i2s_rxctrl(cpu_dai, 0); + else + hisi_i2s_txctrl(cpu_dai, 0); + break; + default: + dev_err(cpu_dai->dev, "uknown cmd\n"); + return -EINVAL; + } + + return 0; +} + +static int hisi_i2s_dai_probe(struct snd_soc_dai *dai) +{ + struct hisi_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, + &i2s->dma_data[SNDRV_PCM_STREAM_PLAYBACK], + &i2s->dma_data[SNDRV_PCM_STREAM_CAPTURE]); + + return 0; +} + + +static struct snd_soc_dai_ops hisi_i2s_dai_ops = { + .trigger = hisi_i2s_trigger, + .hw_params = hisi_i2s_hw_params, + .set_fmt = hisi_i2s_set_fmt, + .set_sysclk = hisi_i2s_set_sysclk, + .startup = hisi_i2s_startup, + .shutdown = hisi_i2s_shutdown, +}; + +struct snd_soc_dai_driver hisi_i2s_dai_init = { + .name = "hisi_i2s", + .probe = hisi_i2s_dai_probe, + .playback = { + .channels_min = 2, + .channels_max = 2, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_U16_LE, + .rates = SNDRV_PCM_RATE_48000, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_U16_LE, + .rates = SNDRV_PCM_RATE_48000, + }, + .ops = &hisi_i2s_dai_ops, +}; + +static const struct snd_soc_component_driver hisi_i2s_i2s_comp = { + .name = "hisi_i2s-i2s", +}; + +#include + +static const struct snd_pcm_hardware snd_hisi_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_HALF_DUPLEX, + .period_bytes_min = 4096, + .period_bytes_max = 4096, + .periods_min = 4, + .periods_max = UINT_MAX, + .buffer_bytes_max = SIZE_MAX, +}; + +static const struct snd_dmaengine_pcm_config hisi_dmaengine_pcm_config = { + .pcm_hardware = &snd_hisi_hardware, + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, + .prealloc_buffer_size = 64 * 1024, +}; + +static int hisi_i2s_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct hisi_i2s *i2s; + struct resource *res; + int ret; + + i2s = devm_kzalloc(dev,sizeof(*i2s), GFP_KERNEL); + if (!i2s) + return -ENOMEM; + + i2s->dev = dev; + spin_lock_init(&i2s->lock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENODEV; + return ret; + } + i2s->base_phys = (phys_addr_t)res->start; + + i2s->dai = hisi_i2s_dai_init; + dev_set_drvdata(&pdev->dev, i2s); + + i2s->base = devm_ioremap_resource(dev, res); + if (IS_ERR(i2s->base)) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = PTR_ERR(i2s->base); + return ret; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) { + ret = -ENODEV; + return ret; + } + i2s->base_syscon = devm_ioremap(dev, res->start, resource_size(res)); + if (IS_ERR(i2s->base_syscon)) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = PTR_ERR(i2s->base_syscon); + return ret; + } + + /* asp power on */ +// i2s->regu_asp = devm_regulator_get(dev, "sio-bt"); + if (IS_ERR(i2s->regu_asp)) { + dev_err(dev, "couldn't get regulators !\n"); + ret = -ENOENT; +// return ret; + } + +// i2s->asp_subsys_clk = devm_clk_get(dev, "clk_asp_subsys"); + if (IS_ERR_OR_NULL(i2s->asp_subsys_clk)) { + dev_err(dev, "devm_clk_get: clk_asp_subsys not found!\n"); +// return -EFAULT; + } + + /* i2s iomux config */ + i2s->pctrl = devm_pinctrl_get(dev); + if (IS_ERR(i2s->pctrl)) { + dev_err(dev, "could not get pinctrl\n"); + ret = -EIO; + return ret; + } + + i2s->pin_default = pinctrl_lookup_state(i2s->pctrl, PINCTRL_STATE_DEFAULT); + if (IS_ERR(i2s->pin_default)) { + dev_err(dev, "could not get default state (%li)\n" , PTR_ERR(i2s->pin_default)); + ret = -EIO; + return ret; + } + +/* i2s->pin_idle = pinctrl_lookup_state(i2s->pctrl, PINCTRL_STATE_IDLE); + if (IS_ERR(i2s->pin_idle)) { + dev_err(dev, "could not get idle state (%li)\n", PTR_ERR(i2s->pin_idle)); + ret = -EIO; + return ret; + }*/ + + if (pinctrl_select_state(i2s->pctrl, i2s->pin_default)) { + dev_err(dev, "could not set pins to default state\n"); + ret = -EIO; + return ret; + } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, + &hisi_dmaengine_pcm_config, + 0); + if (ret) + return ret; + + ret = snd_soc_register_component(&pdev->dev, &hisi_i2s_i2s_comp, + &i2s->dai, 1); + if (ret) { + dev_err(&pdev->dev, "Failed to register dai\n"); + return ret;; + } + dev_info(&pdev->dev, "Registered as %s\n", i2s->dai.name); + + return 0; +} + +static int hisi_i2s_remove(struct platform_device *pdev) +{ + int ret = 0; + struct hisi_i2s *i2s = dev_get_drvdata(&pdev->dev); + + snd_soc_unregister_component(&pdev->dev); + dev_set_drvdata(&pdev->dev, NULL); + + pinctrl_put(i2s->pctrl); +// ret = regulator_disable(i2s->regu_asp); + if (ret) { + dev_err(&pdev->dev, "regulator disable failed!, ret:%d\n", ret); + } + +// clk_disable_unprepare(i2s->asp_subsys_clk); +// clk_put(i2s->asp_subsys_clk); + + return 0; +} + +static const struct of_device_id hisi_i2s_dt_ids[] = { + { .compatible = "hisilicon,hisi-i2s" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, hisi_i2s_dt_ids); + +static struct platform_driver hisi_i2s_driver = { + .probe = hisi_i2s_probe, + .remove = hisi_i2s_remove, + .driver = { + .name = "hisi_i2s", + .owner = THIS_MODULE, + .of_match_table = hisi_i2s_dt_ids, + }, +}; + +module_platform_driver(hisi_i2s_driver); + +MODULE_DESCRIPTION("Hisilicon I2S driver"); +MODULE_AUTHOR("Andy Green "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/hisilicon/hisi-i2s.h b/sound/soc/hisilicon/hisi-i2s.h new file mode 100644 index 000000000000..7dc080113f11 --- /dev/null +++ b/sound/soc/hisilicon/hisi-i2s.h @@ -0,0 +1,109 @@ +/* + * linux/sound/soc/hisilicon/hisi-i2s.h + * + * Copyright (C) 2015 Linaro, Ltd + * Author: Andy Green + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef _HISI_I2S_H +#define _HISI_I2S_H + + enum hisi_bits { + HII2S_BITS_16, + HII2S_BITS_18, + HII2S_BITS_20, + HII2S_BITS_24, +}; + +enum hisi_i2s_rates { + HII2S_FS_RATE_8KHZ = 0, + HII2S_FS_RATE_16KHZ = 1, + HII2S_FS_RATE_32KHZ = 2, + HII2S_FS_RATE_48KHZ = 4, + HII2S_FS_RATE_96KHZ = 5, + HII2S_FS_RATE_192KHZ = 6, +}; + +#define HI_ASP_CFG_R_RST_CTRLEN_REG 0x0 +#define HI_ASP_CFG_R_RST_CTRLDIS_REG 0x4 +#define HI_ASP_CFG_R_GATE_EN_REG 0xC +#define HI_ASP_CFG_R_GATE_DIS_REG 0x10 +#define HI_ASP_CFG_R_GATE_CLKEN_REG 0x14 +#define HI_ASP_CFG_R_GATE_CLKSTAT_REG 0x18 +#define HI_ASP_CFG_R_GATE_CLKDIV_EN_REG 0x1C +#define HI_ASP_CFG_R_CLK1_DIV_REG 0x20 +#define HI_ASP_CFG_R_CLK2_DIV_REG 0x24 +#define HI_ASP_CFG_R_CLK3_DIV_REG 0x28 +#define HI_ASP_CFG_R_CLK4_DIV_REG 0x2C +#define HI_ASP_CFG_R_CLK5_DIV_REG 0x30 +#define HI_ASP_CFG_R_CLK6_DIV_REG 0x34 +#define HI_ASP_CFG_R_CLK_SEL_REG 0x38 +#define HI_ASP_CFG_R_SEC_REG 0x100 + + +#define HI_ASP_SIO_VERSION_REG (0x3C) +#define HI_ASP_SIO_MODE_REG (0x40) +#define HI_ASP_SIO_INTSTATUS_REG (0x44) +#define HI_ASP_SIO_INTCLR_REG (0x48) +#define HI_ASP_SIO_I2S_LEFT_XD_REG (0x4C) +#define HI_ASP_SIO_I2S_RIGHT_XD_REG (0x50) +#define HI_ASP_SIO_I2S_LEFT_RD_REG (0x54) +#define HI_ASP_SIO_I2S_RIGHT_RD_REG (0x58) +#define HI_ASP_SIO_CT_SET_REG (0x5C) +#define HI_ASP_SIO_CT_CLR_REG (0x60) +#define HI_ASP_SIO_RX_STA_REG (0x68) +#define HI_ASP_SIO_TX_STA_REG (0x6C) +#define HI_ASP_SIO_DATA_WIDTH_SET_REG (0x78) +#define HI_ASP_SIO_I2S_START_POS_REG (0x7C) +#define HI_ASP_SIO_I2S_POS_FLAG_REG (0x80) +#define HI_ASP_SIO_SIGNED_EXT_REG (0x84) +#define HI_ASP_SIO_I2S_POS_MERGE_EN_REG (0x88) +#define HI_ASP_SIO_INTMASK_REG (0x8C) +#define HI_ASP_SIO_I2S_DUAL_RX_CHN_REG (0xA0) +#define HI_ASP_SIO_I2S_DUAL_TX_CHN_REG (0xC0) + + +#define HI_ASP_CFG_R_CLK_SEL_EN BIT(2) +#define HI_ASP_CFG_R_CLK_SEL 0x140010 +#define HI_ASP_CFG_R_CLK1_DIV_SEL 0xbcdc9a +#define HI_ASP_CFG_R_CLK4_DIV_SEL 0x00ff000f +#define HI_ASP_CFG_R_CLK6_DIV_SEL 0x00ff003f +#define HI_ASP_CFG_SIO_MODE 0 +#define HI_ASP_SIO_MODE_SEL_EN BIT(0) +#define HI_ASP_MASK 0xffffffff + +#define HI_ASP_SIO_RX_ENABLE BIT(13) +#define HI_ASP_SIO_TX_ENABLE BIT(12) +#define HI_ASP_SIO_RX_FIFO_DISABLE BIT(11) +#define HI_ASP_SIO_TX_FIFO_DISABLE BIT(10) +#define HI_ASP_SIO_RX_DATA_MERGE BIT(9) +#define HI_ASP_SIO_TX_DATA_MERGE BIT(8) +#define HI_ASP_SIO_RX_FIFO_THRESHOLD (0x5 << 4) +#define HI_ASP_SIO_TX_FIFO_THRESHOLD (0xB << 0) +#define HI_ASP_SIO_RX_FIFO_THRESHOLD_CLR (0xF << 4) +#define HI_ASP_SIO_TX_FIFO_THRESHOLD_CLR (0xF << 0) +#define HI_ASP_SIO_BURST (0x4) + + +enum hisi_i2s_formats { + HII2S_FORMAT_I2S, + HII2S_FORMAT_PCM_STD, + HII2S_FORMAT_PCM_USER, + HII2S_FORMAT_LEFT_JUST, + HII2S_FORMAT_RIGHT_JUST, +}; + +#endif/* _HISI_I2S_H */ diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 4e3de566809c..cbb5bbbdf72a 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -2832,7 +2832,7 @@ static int snd_soc_register_dais(struct snd_soc_component *component, unsigned int i; int ret; - dev_dbg(dev, "ASoC: dai register %s #%Zu\n", dev_name(dev), count); + dev_info(dev, "ASoC: dai register %s #%Zu\n", dev_name(dev), count); component->dai_drv = dai_drv; @@ -3161,7 +3161,7 @@ int snd_soc_register_platform(struct device *dev, struct snd_soc_platform *platform; int ret; - dev_dbg(dev, "ASoC: platform register %s\n", dev_name(dev)); + dev_info(dev, "ASoC: platform register %s\n", dev_name(dev)); platform = kzalloc(sizeof(struct snd_soc_platform), GFP_KERNEL); if (platform == NULL) @@ -3320,7 +3320,7 @@ int snd_soc_register_codec(struct device *dev, struct snd_soc_dai *dai; int ret, i; - dev_dbg(dev, "codec register %s\n", dev_name(dev)); + dev_info(dev, "codec register %s\n", dev_name(dev)); codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); if (codec == NULL)