/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 "); MODULE_DESCRIPTION("Exynos Seiren DMA Driver"); MODULE_LICENSE("GPL");