/* * Intel LPSS DMA Core Framework * * Copyright (C) 2014, Intel Corporation * Authors: Huiquan Zhong * * 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 "lpss_dma.h" static struct lpss_dma_chan *dev_to_dma_chan(struct device *dev) { struct lpss_dma_chan_dev *chan_dev; chan_dev = container_of(dev, typeof(*chan_dev), device); return chan_dev->chan; } static ssize_t show_dma_name(struct device *dev, struct device_attribute *attr, char *buf) { struct lpss_dma_chan *chan = dev_to_dma_chan(dev); struct lpss_dma *dma = chan_to_lpss_dma(chan); int ret; if (chan->dir == LPSS_DMA_TX) ret = sprintf(buf, "%s-tx\n", dma->dma_name); else ret = sprintf(buf, "%s-rx\n", dma->dma_name); return ret; } static ssize_t show_busy(struct device *dev, struct device_attribute *attr, char *buf) { struct lpss_dma_chan *chan; int err; chan = dev_to_dma_chan(dev); if (chan) err = sprintf(buf, "%d\n", chan->busy); else err = -ENODEV; return err; } static ssize_t show_dma_cnt(struct device *dev, struct device_attribute *attr, char *buf) { struct lpss_dma_chan *chan; int err; chan = dev_to_dma_chan(dev); if (chan) err = sprintf(buf, "%d\n", chan->dma_cnt); else err = -ENODEV; return err; } static struct device_attribute lpss_dma_attrs[] = { __ATTR(name, S_IRUSR, show_dma_name, NULL), __ATTR(busy, S_IRUSR, show_busy, NULL), __ATTR(dma_cnt, S_IRUSR, show_dma_cnt, NULL), __ATTR_NULL }; static void chan_dev_release(struct device *dev) { struct lpss_dma_chan_dev *chan_dev; chan_dev = container_of(dev, typeof(*chan_dev), device); kfree(chan_dev); } static struct class lpss_dma_class = { .name = "lpss_dma", .dev_attrs = lpss_dma_attrs, .dev_release = chan_dev_release, }; static void lpss_dma_lock(struct lpss_dma_chan *chan, unsigned long *flags) { struct lpss_dma *dma = chan_to_lpss_dma(chan); disable_irq(dma->irq); spin_lock_irqsave(&chan->lock, *flags); } static void lpss_dma_unlock(struct lpss_dma_chan *chan, unsigned long *flags) { struct lpss_dma *dma = chan_to_lpss_dma(chan); spin_unlock_irqrestore(&chan->lock, *flags); enable_irq(dma->irq); } void lpss_dma_complete(struct lpss_dma_chan *chan, bool call) { struct lpss_dma *dma = chan_to_lpss_dma(chan); lpss_dma_tx_callback callback_txd = NULL; void *param_txd = NULL; if (chan->dir == LPSS_DMA_TX) dma_unmap_single(dma->dev, chan->src_addr, chan->len, DMA_TO_DEVICE); else { dma_sync_single_for_cpu(dma->dev, chan->dst_addr, chan->len, DMA_FROM_DEVICE); dma_unmap_single(dma->dev, chan->dst_addr, chan->len, DMA_FROM_DEVICE); } if (call) { chan->busy = false; chan->complete_len = chan->len; } if (!chan->complete_len) return; if (chan->callback) { callback_txd = chan->callback; param_txd = chan->callback_param; callback_txd(param_txd); } if (chan->chan_comp) { complete(chan->chan_comp); } } static void _lpss_dma_stop_chan(struct lpss_dma_chan *chan) { lpss_dma_hw_stop(chan); chan->busy = false; if (chan->dir == LPSS_DMA_TX) chan->complete_len = lpss_dma_get_src_addr(chan) - chan->src_addr; else chan->complete_len = lpss_dma_get_dst_addr(chan) - chan->dst_addr; lpss_dma_complete(chan, false); } int lpss_dma_start_tx(void *lpss_dma, void *src, size_t len) { struct lpss_dma *dma = lpss_dma; struct lpss_dma_chan *txc = &dma->tx_chan; unsigned long flags; int ret; BUG_ON(!dma); BUG_ON(!len); if (test_bit(flag_suspend, &dma->flags)) return -EINVAL; lpss_dma_lock(txc, &flags); if (unlikely(txc->busy)) { lpss_dma_unlock(txc, &flags); dev_err(dma->dev, "%s: %s tx channel is busy\n", __func__, dma->dma_name); return -EBUSY; } txc->busy = true; txc->complete_len = 0; txc->src_addr = dma_map_single(dma->dev, src, len, DMA_TO_DEVICE); txc->len = len; txc->dma_cnt ++; ret = lpss_dma_hw_start(txc); lpss_dma_unlock(txc, &flags); return ret; } int lpss_dma_stop_tx(void *lpss_dma) { struct lpss_dma *dma = lpss_dma; struct lpss_dma_chan *txc = &dma->tx_chan; unsigned long flags; BUG_ON(!dma); if (test_bit(flag_suspend, &dma->flags)) return -EINVAL; lpss_dma_lock(txc, &flags); if (!txc->busy) { lpss_dma_unlock(txc, &flags); return -EPERM; } _lpss_dma_stop_chan(txc); lpss_dma_unlock(txc, &flags); return 0; } int lpss_dma_start_rx(void *lpss_dma, void *dst, size_t len) { struct lpss_dma *dma = lpss_dma; struct lpss_dma_chan *rxc = &dma->rx_chan; unsigned long flags; int ret; BUG_ON(!dma); BUG_ON(!len); if (test_bit(flag_suspend, &dma->flags)) return -EINVAL; lpss_dma_lock(rxc, &flags); if (unlikely(rxc->busy)) { lpss_dma_unlock(rxc, &flags); dev_err(dma->dev, "%s: %s rx channel is busy\n", __func__, dma->dma_name); return -EBUSY; } rxc->busy = true; rxc->complete_len = 0; rxc->dst_addr = dma_map_single(dma->dev, dst, len, DMA_FROM_DEVICE); rxc->len = len; rxc->dma_cnt ++; ret = lpss_dma_hw_start(rxc); lpss_dma_unlock(rxc, &flags); return ret; } int lpss_dma_stop_rx(void *lpss_dma) { struct lpss_dma *dma = lpss_dma; struct lpss_dma_chan *rxc = &dma->rx_chan; unsigned long flags; BUG_ON(!dma); if (test_bit(flag_suspend, &dma->flags)) return -EINVAL; lpss_dma_lock(rxc, &flags); if (!rxc->busy) { lpss_dma_unlock(rxc, &flags); return -EPERM; } _lpss_dma_stop_chan(rxc); lpss_dma_unlock(rxc, &flags); return 0; } size_t lpss_dma_get_tx_count(void *lpss_dma) { struct lpss_dma *dma = lpss_dma; struct lpss_dma_chan *txc = &dma->tx_chan; BUG_ON(!dma); return txc->complete_len; } size_t lpss_dma_get_rx_count(void *lpss_dma) { struct lpss_dma *dma = lpss_dma; struct lpss_dma_chan *rxc = &dma->rx_chan; BUG_ON(!dma); return rxc->complete_len; } int lpss_dma_set_buswidth(void *lpss_dma, enum lpss_dma_buswidth buswidth) { struct lpss_dma *dma = lpss_dma; struct lpss_dma_chan *txc = &dma->tx_chan; struct lpss_dma_chan *rxc = &dma->rx_chan; BUG_ON(!dma); txc->dma_buswidth = rxc->dma_buswidth = buswidth; lpss_chan_prepare(txc); lpss_chan_prepare(rxc); return 0; } int lpss_dma_set_burstsize(void *lpss_dma, enum lpss_dma_msize burstsize) { struct lpss_dma *dma = lpss_dma; struct lpss_dma_chan *txc = &dma->tx_chan; struct lpss_dma_chan *rxc = &dma->rx_chan; BUG_ON(!dma); txc->dma_burstsize = rxc->dma_burstsize = burstsize; lpss_chan_prepare(txc); lpss_chan_prepare(rxc); return 0; } void lpss_dma_resume(void *lpss_dma) { struct lpss_dma *dma = lpss_dma; BUG_ON(!dma); dev_dbg(dma->dev, "lpss: %s\n", __func__); pm_runtime_get_sync(dma->dev); enable_lpss_dma(dma); clear_bit(flag_suspend, &dma->flags); return; } void lpss_dma_suspend(void *lpss_dma) { struct lpss_dma *dma = lpss_dma; struct lpss_dma_chan *txc = &dma->tx_chan; struct lpss_dma_chan *rxc = &dma->rx_chan; unsigned long flags; BUG_ON(!dma); dev_dbg(dma->dev, "lpss: %s\n", __func__); set_bit(flag_suspend, &dma->flags); /* If TX, RX channel is busying, should stop first */ lpss_dma_lock(txc, &flags); if (txc->busy) _lpss_dma_stop_chan(txc); lpss_dma_unlock(txc, &flags); lpss_dma_lock(rxc, &flags); if (rxc->busy) _lpss_dma_stop_chan(rxc); lpss_dma_unlock(rxc, &flags); pm_runtime_put(dma->dev); return; } int lpss_dma_sysfs_init(struct lpss_dma *dma) { struct lpss_dma_chan *txc, *rxc; static int dev_id; int ret; /* 1. tx channel sysfs init */ txc = &dma->tx_chan; txc->dev = kzalloc(sizeof(*txc->dev), GFP_KERNEL); if (txc->dev == NULL) { dev_err(dma->dev, "%s: kzalloc failed\n", __func__); return -ENOMEM; } txc->dev->device.class = &lpss_dma_class; txc->dev->device.parent = dma->dev; txc->dev->chan = txc; txc->dev->dev_id = dev_id; dev_set_name(&txc->dev->device, "dma%dchan%d", dev_id, txc->ch_id - dma->ch_base); ret = device_register(&txc->dev->device); if (ret < 0) { dev_err(dma->dev, "%s: device_register failed\n", __func__); kfree(txc->dev); return ret; } /* 2, rx channel sysfs init */ rxc = &dma->rx_chan; rxc->dev = kzalloc(sizeof(*rxc->dev), GFP_KERNEL); if (rxc->dev == NULL) { dev_err(dma->dev, "%s: kzalloc failed\n", __func__); return -ENOMEM; } rxc->dev->device.class = &lpss_dma_class; rxc->dev->device.parent = dma->dev; rxc->dev->chan = rxc; rxc->dev->dev_id = dev_id; dev_set_name(&rxc->dev->device, "dma%dchan%d", dev_id, rxc->ch_id - dma->ch_base); ret = device_register(&rxc->dev->device); if (ret < 0) { dev_err(dma->dev, "%s: device_register failed\n", __func__); kfree(rxc->dev); return ret; } dev_id ++; return ret; } void *lpss_dma_register(struct lpss_dma_info *info) { struct lpss_dma *dma; struct lpss_dma_chan *txc, *rxc; int err; BUG_ON(!info); dma = devm_kzalloc(info->pdev, sizeof(struct lpss_dma), GFP_KERNEL); if (!dma) { dev_err(info->pdev, "%s: kzalloc failed probe\n", __func__); return NULL; } dma->dev = info->pdev; dma->dma_base = info->membase; dma->irq = info->irq; dma->ch_base = info->ch_base; dma->dma_version = info->dma_version; strcpy(dma->dma_name, info->name); /* 1. tx channel setup */ txc = &dma->tx_chan; txc->ch_id = dma->ch_base; txc->ch_regs = dma->dma_base + (LPSS_DMA_CH_SIZE * txc->ch_id); txc->dir = LPSS_DMA_TX; txc->dma_buswidth = info->dma_buswidth; txc->dma_burstsize = info->dma_burstsize; txc->dst_addr = info->per_tx_addr; if (info->tx_callback) { txc->callback = info->tx_callback; txc->callback_param = info->callback_param; } if (info->tx_comp) { txc->chan_comp = info->tx_comp; } spin_lock_init(&txc->lock); /* 2. rx channel setup */ rxc = &dma->rx_chan; rxc->ch_id = dma->ch_base + 1; rxc->ch_regs = dma->dma_base + (LPSS_DMA_CH_SIZE * rxc->ch_id); rxc->dir = LPSS_DMA_RX; rxc->dma_buswidth = info->dma_buswidth; rxc->dma_burstsize = info->dma_burstsize; rxc->src_addr = info->per_rx_addr; if (info->rx_callback) { rxc->callback = info->rx_callback; rxc->callback_param = info->callback_param; } if (info->rx_comp) { rxc->chan_comp = info->rx_comp; } spin_lock_init(&rxc->lock); lpss_dma_sysfs_init(dma); err = lpss_dma_setup(dma); if (err < 0) return NULL; return dma; } void lpss_dma_unregister(void *lpss_dma) { struct lpss_dma *dma = lpss_dma; struct lpss_dma_chan *txc, *rxc; BUG_ON(!dma); txc = &dma->tx_chan; txc->dev->chan = NULL; device_unregister(&txc->dev->device); rxc = &dma->rx_chan; rxc->dev->chan = NULL; device_unregister(&rxc->dev->device); devm_kfree(dma->dev, dma); } void *lpss_dma_alloc(size_t len) { if (len <= 0) return NULL; return kzalloc(len, GFP_KERNEL); } void lpss_dma_free(void *addr) { kfree(addr); } static int __init lpss_dma_init(void) { return class_register(&lpss_dma_class); } arch_initcall(lpss_dma_init);