mirror of
https://gitee.com/open-loongarch/linux-5.10.git
synced 2026-10-25 20:57:11 +08:00
398 lines
9.3 KiB
C
398 lines
9.3 KiB
C
/*
|
|
* Driver for the Loongson APB DMA Controller (APB DMAC on 2K or 7A systems)
|
|
*
|
|
* Copyright (C) 2017 Loongson Corporation
|
|
*
|
|
* 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; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/dmaengine.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/dmapool.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_dma.h>
|
|
|
|
#include "ls_apbdmac.h"
|
|
#include "dmaengine.h"
|
|
|
|
/**
|
|
* apbdma_tx_status - poll for transaction completion
|
|
* @chan: DMA channel
|
|
* @cookie: transaction identifier to check status of
|
|
* @txstate: if not %NULL updated with transaction state
|
|
*
|
|
*/
|
|
static enum dma_status
|
|
apbdma_tx_status(struct dma_chan *chan,
|
|
dma_cookie_t cookie,
|
|
struct dma_tx_state *txstate)
|
|
{
|
|
return DMA_COMPLETE;
|
|
}
|
|
|
|
/**
|
|
* apbdma_issue_pending - try to finish work
|
|
* @chan: target DMA channel
|
|
*/
|
|
static void apbdma_issue_pending(struct dma_chan *chan)
|
|
{
|
|
}
|
|
|
|
/**
|
|
* apbdma_alloc_chan_resources - allocate resources for DMA channel
|
|
* @chan: allocate descriptor resources for this channel
|
|
*
|
|
* return - the number of allocated descriptors
|
|
*/
|
|
static int apbdma_alloc_chan_resources(struct dma_chan *chan)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* apbdma_free_chan_resources - free all channel resources
|
|
* @chan: DMA channel
|
|
*/
|
|
static void apbdma_free_chan_resources(struct dma_chan *chan)
|
|
{
|
|
}
|
|
|
|
#ifdef CONFIG_OF
|
|
static bool apb_dma_filter(struct dma_chan *chan, void *slave)
|
|
{
|
|
struct device *dev = slave;
|
|
if (dev == chan->device->dev) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static struct dma_chan *apb_dma_xlate(struct of_phandle_args *dma_spec,
|
|
struct of_dma *of_dma)
|
|
{
|
|
struct dma_chan *chan;
|
|
dma_cap_mask_t mask;
|
|
struct platform_device *dmac_pdev;
|
|
|
|
dmac_pdev = of_find_device_by_node(dma_spec->np);
|
|
|
|
dma_cap_zero(mask);
|
|
dma_cap_set(DMA_SLAVE, mask);
|
|
|
|
/*
|
|
* We can fill both SRC_PER and DST_PER, one of these fields will be
|
|
* ignored depending on DMA transfer direction.
|
|
*/
|
|
chan = dma_request_channel(mask, apb_dma_filter, &(dmac_pdev->dev));
|
|
if (!chan)
|
|
return NULL;
|
|
|
|
return chan;
|
|
}
|
|
#else
|
|
static struct dma_chan *apb_dma_xlate(struct of_phandle_args *dma_spec,
|
|
struct of_dma *of_dma)
|
|
{
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
#if defined(CONFIG_OF)
|
|
static const struct of_device_id ls_apbdma_dt_ids[] = {
|
|
{
|
|
.compatible = "loongson,ls-apbdma-0",
|
|
}, {
|
|
.compatible = "loongson,ls-apbdma-1",
|
|
}, {
|
|
.compatible = "loongson,ls-apbdma-2",
|
|
}, {
|
|
.compatible = "loongson,ls-apbdma-3",
|
|
}, {
|
|
.compatible = "loongson,ls-apbdma-4",
|
|
}, {
|
|
/* sentinel */
|
|
}
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, ls_apbdma_dt_ids);
|
|
#endif
|
|
|
|
static const struct platform_device_id ls_apbdma_devtypes[] = {
|
|
{
|
|
.name = "ls-apbdma-0",
|
|
}, {
|
|
.name = "ls-apbdma-1",
|
|
}, {
|
|
.name = "ls-apbdma-2",
|
|
}, {
|
|
.name = "ls-apbdma-3",
|
|
}, {
|
|
.name = "ls-apbdma-4",
|
|
}, {
|
|
/* sentinel */
|
|
}
|
|
};
|
|
|
|
/**
|
|
* set_apbdma_config - set up apbdma configuration register
|
|
* @pdata: Controller configuration parameters
|
|
*
|
|
*apbdma sel mapping table:
|
|
*dma_sel0 ------ NAND dma_sel1 ------ AES read
|
|
*dma_sel2 ------ AES write dma_sel3 ------ DES read
|
|
*dma_sel4 ------ DES write dma_sel5 ------ SDIO
|
|
*dma_sel6 ------ I2S send dma_sel7 ------ I2S receive
|
|
*dma_sel8 ------ AC97 oc dma_sel9 ------ AC97 ic
|
|
*/
|
|
static int set_apbdma_config(struct ls_apbdma_platform_data *pdata)
|
|
{
|
|
size_t size;
|
|
void __iomem *regs;
|
|
struct resource *io;
|
|
unsigned int sel, dma_nr, val;
|
|
int err = 0;
|
|
|
|
if (!pdata)
|
|
return -EINVAL;
|
|
|
|
// no need config.
|
|
if (!pdata->common)
|
|
return 0;
|
|
|
|
io = platform_get_resource(pdata->common, IORESOURCE_MEM, 0);
|
|
if (!io)
|
|
return -EINVAL;
|
|
|
|
size = resource_size(io);
|
|
if (!request_mem_region(io->start, size, pdata->common->dev.of_node->name)) {
|
|
return -EBUSY;
|
|
}
|
|
regs = ioremap(io->start, size);
|
|
if (!regs) {
|
|
err = -ENOMEM;
|
|
goto err_release_r;
|
|
}
|
|
/*set up APB dma sel bits*/
|
|
sel = pdata->dma_sel;
|
|
dma_nr = pdata->apb_dma_nr;
|
|
|
|
if (io->start == LS2K0500_APB_DMA_CONF) {
|
|
val = readl(regs) & ~sel;
|
|
val |= dma_nr;
|
|
} else { /* default is LS2K1000 */
|
|
val = readl(regs) & SEL_MASK_SHIFT(sel);
|
|
val |= SEL_VAL(dma_nr, sel);
|
|
}
|
|
writel(val, regs);
|
|
err_release_r:
|
|
release_mem_region(io->start, size);
|
|
|
|
return err;
|
|
}
|
|
|
|
#ifdef CONFIG_OF
|
|
static struct ls_apbdma_platform_data *
|
|
ls_apbdma_parse_dt(struct platform_device *pdev)
|
|
{
|
|
struct device_node *np = pdev->dev.of_node;
|
|
struct ls_apbdma_platform_data *pdata;
|
|
struct of_phandle_args dma_spec;
|
|
|
|
if (!np) {
|
|
dev_err(&pdev->dev, "Missing DT data\n");
|
|
return NULL;
|
|
}
|
|
|
|
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
|
|
if (!pdata)
|
|
return NULL;
|
|
|
|
if (of_property_read_u32(np, "dma-channels", &pdata->nr_channels))
|
|
goto err_parse;
|
|
|
|
if (of_parse_phandle_with_args(np, "apbdma-sel", "#config-nr", 0,
|
|
&dma_spec)) {
|
|
// for ls2k500, it isn`t need to config sel for nand,sdio,ac97.
|
|
pdata->common = NULL;
|
|
pdata->dma_sel = (unsigned int)(-1);
|
|
pdata->apb_dma_nr = (unsigned int)(-1);
|
|
return pdata;
|
|
}
|
|
|
|
pdata->common = of_find_device_by_node(dma_spec.np);
|
|
if (!pdata->common)
|
|
goto err_parse;
|
|
|
|
pdata->dma_sel = dma_spec.args[0];
|
|
pdata->apb_dma_nr = dma_spec.args[1];
|
|
|
|
return pdata;
|
|
err_parse:
|
|
devm_kfree(&pdev->dev, pdata);
|
|
return NULL;
|
|
}
|
|
#else
|
|
static inline struct ls_apbdma_platform_data *
|
|
ls_apbdma_parse_dt(struct platform_device *pdev)
|
|
{
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
static int ls_apbdma_probe(struct platform_device *pdev)
|
|
{
|
|
struct ls_apbdma_platform_data *pdata;
|
|
struct ls_apbdma *apbdma = NULL;
|
|
void __iomem *regs;
|
|
struct resource *r;
|
|
size_t size;
|
|
int err = 0;
|
|
int i;
|
|
dma_cap_mask_t mask;
|
|
|
|
/* get DMA parameters from controller type */
|
|
pdata = dev_get_platdata(&pdev->dev);
|
|
if (!pdata)
|
|
pdata = ls_apbdma_parse_dt(pdev);
|
|
if (!pdata)
|
|
return -ENOMEM;
|
|
|
|
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
if (!r)
|
|
return -EINVAL;
|
|
if (!request_mem_region(r->start, r->end - r->start + 1, pdev->name))
|
|
return -EBUSY;
|
|
|
|
regs = ioremap(r->start, r->end - r->start + 1);
|
|
if (!regs) {
|
|
err = -ENOMEM;
|
|
goto err_release_r;
|
|
}
|
|
|
|
#ifndef CONFIG_LOONGARCH
|
|
if (hw_coherentio == 1)
|
|
writel(readl(regs) & (~0x2), regs);
|
|
else
|
|
writel(readl(regs) | 0x2, regs);
|
|
#endif
|
|
|
|
err = set_apbdma_config(pdata);
|
|
if (err)
|
|
goto err_kfree;
|
|
size = sizeof(struct ls_apbdma);
|
|
size += pdata->nr_channels * sizeof(struct ls_dma_chan);
|
|
apbdma = kzalloc(size, GFP_KERNEL);
|
|
if (!apbdma)
|
|
return -ENOMEM;
|
|
|
|
platform_set_drvdata(pdev, apbdma);
|
|
/* initialize channels related values */
|
|
INIT_LIST_HEAD(&apbdma->dma_common.channels);
|
|
for (i = 0; i < pdata->nr_channels; i++) {
|
|
struct ls_dma_chan *lschan = &apbdma->chan[i];
|
|
lschan->chan_common.device = &apbdma->dma_common;
|
|
list_add_tail(&lschan->chan_common.device_node,
|
|
&apbdma->dma_common.channels);
|
|
lschan->mask = 1 << i;
|
|
}
|
|
|
|
dma_cap_zero(mask);
|
|
dma_cap_set(DMA_SLAVE, mask);
|
|
/* set base routines */
|
|
apbdma->dma_common.cap_mask = mask;
|
|
apbdma->dma_common.device_alloc_chan_resources = apbdma_alloc_chan_resources;
|
|
apbdma->dma_common.device_free_chan_resources = apbdma_free_chan_resources;
|
|
apbdma->dma_common.device_tx_status = apbdma_tx_status;
|
|
apbdma->dma_common.device_issue_pending = apbdma_issue_pending;
|
|
apbdma->dma_common.dev = &pdev->dev;
|
|
dev_info(&pdev->dev, "Loongson APB DMA Controller ( %s%s), %d channels\n",
|
|
dma_has_cap(DMA_MEMCPY, apbdma->dma_common.cap_mask) ? "cpy " : "",
|
|
dma_has_cap(DMA_SLAVE, apbdma->dma_common.cap_mask) ? "slave " : "",
|
|
pdata->nr_channels);
|
|
|
|
dma_async_device_register(&apbdma->dma_common);
|
|
|
|
/*
|
|
* Do not return an error if the dmac node is not present in order to
|
|
* not break the existing way of requesting channel with
|
|
* dma_request_channel().
|
|
*/
|
|
if (pdev->dev.of_node) {
|
|
err = of_dma_controller_register(pdev->dev.of_node,
|
|
apb_dma_xlate, apbdma);
|
|
if (err) {
|
|
dev_err(&pdev->dev, "could not register of_dma_controller\n");
|
|
goto err_of_dma_controller_register;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_of_dma_controller_register:
|
|
dma_async_device_unregister(&apbdma->dma_common);
|
|
err_kfree:
|
|
devm_kfree(&pdev->dev, pdata);
|
|
kfree(apbdma);
|
|
err_release_r:
|
|
iounmap(regs);
|
|
release_mem_region(r->start, r->end - r->start + 1);
|
|
return err;
|
|
}
|
|
|
|
static int ls_apbdma_remove(struct platform_device *pdev)
|
|
{
|
|
struct ls_apbdma *apbdma = platform_get_drvdata(pdev);
|
|
struct dma_chan *chan, *_chan;
|
|
|
|
dma_async_device_unregister(&apbdma->dma_common);
|
|
|
|
list_for_each_entry_safe(chan, _chan, &apbdma->dma_common.channels,
|
|
device_node) {
|
|
list_del(&chan->device_node);
|
|
}
|
|
|
|
if (pdev->dev.of_node)
|
|
of_dma_controller_free(pdev->dev.of_node);
|
|
|
|
kfree(apbdma);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver ls_apbdma_driver = {
|
|
.probe = ls_apbdma_probe,
|
|
.remove = ls_apbdma_remove,
|
|
.id_table = ls_apbdma_devtypes,
|
|
.driver = {
|
|
.name = "ls_apbdma",
|
|
.of_match_table = of_match_ptr(ls_apbdma_dt_ids),
|
|
},
|
|
};
|
|
|
|
static int __init ls_apbdma_init(void)
|
|
{
|
|
return platform_driver_register(&ls_apbdma_driver);
|
|
}
|
|
subsys_initcall(ls_apbdma_init);
|
|
|
|
static void __exit ls_apbdma_exit(void)
|
|
{
|
|
platform_driver_unregister(&ls_apbdma_driver);
|
|
}
|
|
module_exit(ls_apbdma_exit);
|
|
|
|
MODULE_DESCRIPTION("Loongson APB DMA Controller driver");
|
|
MODULE_AUTHOR("Xuefeng Li <lixuefeng@loongson.cn>");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_ALIAS("platform:ls_apbdmac");
|