blob: 1eb86173b34661480649d255a7014932e6b4890d [file] [log] [blame]
/* sound/soc/samsung/seiren/seiren-dma.c
*
* Exynos Seiren DMA driver for Exynos5430
*
* Copyright (c) 2014 Samsung Electronics
* http://www.samsungsemi.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 <linux/slab.h>
#include <linux/dma-mapping.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/mutex.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#include <linux/pm_runtime.h>
#include <linux/iommu.h>
#include <linux/dma/dma-pl330.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include <sound/exynos.h>
#include <asm/dma.h>
#include "seiren.h"
#include "seiren-dma.h"
#include "../dma.h"
/* DMAC source/destination addr */
#define _SA 0x400
#define SA(n) (_SA + (n)*0x20)
#define _DA 0x404
#define DA(n) (_DA + (n)*0x20)
static struct seiren_dma_info {
struct platform_device *pdev;
spinlock_t lock;
void __iomem *regs;
#ifdef CONFIG_SND_SAMSUNG_IOMMU
struct iommu_domain *domain;
#endif
struct runtime *rtd[DMA_CH_MAX];
} sdi;
struct runtime {
int ch;
u32 peri;
void __iomem *regs;
void __iomem *reg_ack;
void __iomem *reg_sa;
void __iomem *reg_da;
};
static int of_dma_match_channel(struct device_node *np, const char *name,
int index, struct of_phandle_args *dma_spec)
{
const char *s;
if (of_property_read_string_index(np, "dma-names", index, &s))
return -ENODEV;
if (strcmp(name, s))
return -ENODEV;
if (of_parse_phandle_with_args(np, "dmas", "#dma-cells", index,
dma_spec))
return -ENODEV;
return 0;
}
static unsigned long esa_dma_request(enum dma_ch dma_ch,
struct samsung_dma_req *param,
struct device *dev, char *ch_name)
{
struct device_node *np;
struct of_phandle_args dma_spec;
struct runtime *rtd = NULL;
int ch, count, n;
ch = esa_dma_open();
if (ch < 0) {
esa_err("%s: dma ch alloc fails!!!\n", __func__);
return -EIO;
}
rtd = kzalloc(sizeof(struct runtime), GFP_KERNEL);
if (!rtd) {
esa_err("%s: runtime data alloc fails!!!\n", __func__);
esa_dma_close(ch);
return -EIO;
}
pm_runtime_get_sync(&sdi.pdev->dev);
sdi.rtd[ch] = rtd;
rtd->ch = ch;
rtd->regs = esa_dma_get_mem() + (ch * DMA_PARAM_SIZE);
rtd->reg_ack = rtd->regs + DMA_ACK;
rtd->reg_sa = sdi.regs + SA(ch);
rtd->reg_da = sdi.regs + DA(ch);
memset(rtd->regs, 0, DMA_PARAM_SIZE);
np = dev->of_node;
if (!np) {
esa_err("%s: of_node not found!!!\n", __func__);
goto err;
}
count = of_property_count_strings(np, "dma-names");
if (count < 0) {
esa_err("%s: dma-names property missing\n", __func__);
goto err;
}
for (n = 0; n < count; n++) {
if (!of_dma_match_channel(np, ch_name, n, &dma_spec)) {
of_property_read_u32_index(np, "dmas",
n * 2 + 1, &rtd->peri);
esa_debug("%s: ch %d (peri %d)\n",
__func__, rtd->ch, rtd->peri);
writel(rtd->peri, rtd->regs + DMA_PERI);
return (unsigned long)ch;
}
}
esa_err("%s: dmas property of %s missing\n", __func__, ch_name);
err:
esa_dma_close(ch);
kfree(rtd);
sdi.rtd[ch] = NULL;
pm_runtime_put_sync(&sdi.pdev->dev);
return -EIO;
}
static int esa_dma_release(unsigned long ch, void *param)
{
struct runtime *rtd = sdi.rtd[ch];
esa_dma_close(rtd->ch);
kfree(rtd);
sdi.rtd[ch] = NULL;
pm_runtime_put_sync(&sdi.pdev->dev);
return 0;
}
static int esa_dma_config(unsigned long ch, struct samsung_dma_config *param)
{
struct runtime *rtd = sdi.rtd[ch];
void __iomem *regs = rtd->regs;
u32 mode_1 = DMA_MODE_NOP;
u32 src_pa = 0;
u32 dst_pa = 0;
if (param->direction == DMA_MEM_TO_DEV) {
mode_1 = DMA_MODE_MEM2DEV;
dst_pa = param->fifo;
} else if (param->direction == DMA_DEV_TO_MEM) {
mode_1 = DMA_MODE_DEV2MEM;
src_pa = param->fifo;
}
writel(mode_1, regs + DMA_MODE_1);
writel(src_pa, regs + DMA_SRC_PA);
writel(dst_pa, regs + DMA_DST_PA);
return 0;
}
static int esa_dma_prepare(unsigned long ch, struct samsung_dma_prep *param)
{
struct runtime *rtd = sdi.rtd[ch];
void __iomem *regs = rtd->regs;
u32 mode_2;
mode_2 = (param->infiniteloop) ? DMA_MODE_LOOP : DMA_MODE_ONCE;
writel(mode_2, regs + DMA_MODE_2);
if (param->direction == DMA_MEM_TO_DEV)
writel(param->buf, regs + DMA_SRC_PA);
else if (param->direction == DMA_DEV_TO_MEM)
writel(param->buf, regs + DMA_DST_PA);
writel(param->period, regs + DMA_PERIOD);
writel(param->len / param->period, regs + DMA_PERIOD_CNT);
esa_dma_set_callback(param->fp, param->fp_param, rtd->ch);
esa_dma_send_cmd(CMD_DMA_PREPARE, rtd->ch, rtd->reg_ack);
return 0;
}
static int esa_dma_trigger(unsigned long ch)
{
struct runtime *rtd = sdi.rtd[ch];
esa_dma_send_cmd(CMD_DMA_START, rtd->ch, rtd->reg_ack);
return 0;
}
static int esa_dma_getposition(unsigned long ch, dma_addr_t *src, dma_addr_t *dst)
{
struct runtime *rtd = sdi.rtd[ch];
*src = readl(rtd->reg_sa);
*dst = readl(rtd->reg_da);
return 0;
}
static int esa_dma_flush(unsigned long ch)
{
struct runtime *rtd = sdi.rtd[ch];
esa_dma_send_cmd(CMD_DMA_STOP, rtd->ch, rtd->reg_ack);
return 0;
}
static struct samsung_dma_ops esa_dma_ops = {
.request = esa_dma_request,
.release = esa_dma_release,
.config = esa_dma_config,
.prepare = esa_dma_prepare,
.trigger = esa_dma_trigger,
.started = NULL,
.getposition = esa_dma_getposition,
.flush = esa_dma_flush,
.stop = esa_dma_flush,
};
void *samsung_esa_dma_get_ops(void)
{
return &esa_dma_ops;
}
EXPORT_SYMBOL_GPL(samsung_esa_dma_get_ops);
static const char banner[] =
KERN_INFO "Exynos Seiren ADMA driver, (c)2014 Samsung Electronics\n";
static int esa_dma_probe(struct platform_device *pdev)
{
struct resource *res;
struct device *dev = &pdev->dev;
struct device_node *np = pdev->dev.of_node;
printk(banner);
spin_lock_init(&sdi.lock);
sdi.pdev = pdev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(dev, "Unable to get LPASS SFRs\n");
return -ENXIO;
}
sdi.regs = devm_ioremap_resource(&pdev->dev, res);
if (!sdi.regs) {
dev_err(dev, "SFR ioremap failed\n");
return -ENOMEM;
}
esa_debug("regs_base = %08X (%08X bytes)\n",
(unsigned int)res->start, (unsigned int)resource_size(res));
if (np) {
if (of_find_property(np, "samsung,lpass-subip", NULL))
lpass_register_subip(dev, "dmac");
}
#ifdef CONFIG_SND_SAMSUNG_IOMMU
sdi.domain = lpass_get_iommu_domain();
if (!sdi.domain) {
dev_err(dev, "iommu not available\n");
goto err;
}
#else
dev_err(dev, "iommu not available\n");
goto err;
#endif
#ifdef CONFIG_PM_RUNTIME
pm_runtime_enable(dev);
pm_runtime_get_sync(dev);
pm_runtime_put_sync(dev);
#else
esa_dma_do_resume(dev);
#endif
return 0;
err:
return -ENODEV;
}
static int esa_dma_remove(struct platform_device *pdev)
{
int ret = 0;
#ifndef CONFIG_PM_RUNTIME
lpass_put_sync(&pdev->dev);
#endif
return ret;
}
static int esa_dma_do_suspend(struct device *dev)
{
lpass_put_sync(dev);
return 0;
}
static int esa_dma_do_resume(struct device *dev)
{
lpass_get_sync(dev);
return 0;
}
#if !defined(CONFIG_PM_RUNTIME) && defined(CONFIG_PM_SLEEP)
static int esa_dma_suspend(struct device *dev)
{
esa_debug("%s entered\n", __func__);
return esa_dma_do_suspend(dev);
}
static int esa_dma_resume(struct device *dev)
{
esa_debug("%s entered\n", __func__);
return esa_dma_do_resume(dev);
}
#else
#define esa_dma_suspend NULL
#define esa_dma_resume NULL
#endif
#ifdef CONFIG_PM_RUNTIME
static int esa_dma_runtime_suspend(struct device *dev)
{
esa_debug("%s entered\n", __func__);
return esa_dma_do_suspend(dev);
}
static int esa_dma_runtime_resume(struct device *dev)
{
esa_debug("%s entered\n", __func__);
return esa_dma_do_resume(dev);
}
#endif
static struct platform_device_id esa_dma_driver_ids[] = {
{
.name = "samsung-seiren-dma",
},
{},
};
MODULE_DEVICE_TABLE(platform, esa_dma_driver_ids);
#ifdef CONFIG_OF
static const struct of_device_id exynos_esa_dma_match[] = {
{
.compatible = "samsung,exynos5430-seiren-dma",
},
{},
};
MODULE_DEVICE_TABLE(of, exynos_esa_dma_match);
#endif
static const struct dev_pm_ops esa_dma_pmops = {
SET_SYSTEM_SLEEP_PM_OPS(
esa_dma_suspend,
esa_dma_resume
)
SET_RUNTIME_PM_OPS(
esa_dma_runtime_suspend,
esa_dma_runtime_resume,
NULL
)
};
static struct platform_driver esa_dma_driver = {
.probe = esa_dma_probe,
.remove = esa_dma_remove,
.id_table = esa_dma_driver_ids,
.driver = {
.name = "samsung-seiren-dma",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(exynos_esa_dma_match),
.pm = &esa_dma_pmops,
},
};
module_platform_driver(esa_dma_driver);
MODULE_AUTHOR("Yeongman Seo <yman.seo@samsung.com>");
MODULE_DESCRIPTION("Exynos Seiren DMA Driver");
MODULE_LICENSE("GPL");