diff options
Diffstat (limited to 'drivers/dma/stm32/stm32-dmamux.c')
| -rw-r--r-- | drivers/dma/stm32/stm32-dmamux.c | 402 | 
1 files changed, 402 insertions, 0 deletions
| diff --git a/drivers/dma/stm32/stm32-dmamux.c b/drivers/dma/stm32/stm32-dmamux.c new file mode 100644 index 000000000000..8d77e2a7939a --- /dev/null +++ b/drivers/dma/stm32/stm32-dmamux.c @@ -0,0 +1,402 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Copyright (C) STMicroelectronics SA 2017 + * Author(s): M'boumba Cedric Madianga <[email protected]> + *            Pierre-Yves Mordret <[email protected]> + * + * DMA Router driver for STM32 DMA MUX + * + * Based on TI DMA Crossbar driver + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_dma.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/reset.h> +#include <linux/slab.h> +#include <linux/spinlock.h> + +#define STM32_DMAMUX_CCR(x)		(0x4 * (x)) +#define STM32_DMAMUX_MAX_DMA_REQUESTS	32 +#define STM32_DMAMUX_MAX_REQUESTS	255 + +struct stm32_dmamux { +	u32 master; +	u32 request; +	u32 chan_id; +}; + +struct stm32_dmamux_data { +	struct dma_router dmarouter; +	struct clk *clk; +	void __iomem *iomem; +	u32 dma_requests; /* Number of DMA requests connected to DMAMUX */ +	u32 dmamux_requests; /* Number of DMA requests routed toward DMAs */ +	spinlock_t lock; /* Protects register access */ +	DECLARE_BITMAP(dma_inuse, STM32_DMAMUX_MAX_DMA_REQUESTS); /* Used DMA channel */ +	u32 ccr[STM32_DMAMUX_MAX_DMA_REQUESTS]; /* Used to backup CCR register +						 * in suspend +						 */ +	u32 dma_reqs[]; /* Number of DMA Request per DMA masters. +			 *  [0] holds number of DMA Masters. +			 *  To be kept at very end of this structure +			 */ +}; + +static inline u32 stm32_dmamux_read(void __iomem *iomem, u32 reg) +{ +	return readl_relaxed(iomem + reg); +} + +static inline void stm32_dmamux_write(void __iomem *iomem, u32 reg, u32 val) +{ +	writel_relaxed(val, iomem + reg); +} + +static void stm32_dmamux_free(struct device *dev, void *route_data) +{ +	struct stm32_dmamux_data *dmamux = dev_get_drvdata(dev); +	struct stm32_dmamux *mux = route_data; +	unsigned long flags; + +	/* Clear dma request */ +	spin_lock_irqsave(&dmamux->lock, flags); + +	stm32_dmamux_write(dmamux->iomem, STM32_DMAMUX_CCR(mux->chan_id), 0); +	clear_bit(mux->chan_id, dmamux->dma_inuse); + +	pm_runtime_put_sync(dev); + +	spin_unlock_irqrestore(&dmamux->lock, flags); + +	dev_dbg(dev, "Unmapping DMAMUX(%u) to DMA%u(%u)\n", +		mux->request, mux->master, mux->chan_id); + +	kfree(mux); +} + +static void *stm32_dmamux_route_allocate(struct of_phandle_args *dma_spec, +					 struct of_dma *ofdma) +{ +	struct platform_device *pdev = of_find_device_by_node(ofdma->of_node); +	struct stm32_dmamux_data *dmamux = platform_get_drvdata(pdev); +	struct stm32_dmamux *mux; +	u32 i, min, max; +	int ret; +	unsigned long flags; + +	if (dma_spec->args_count != 3) { +		dev_err(&pdev->dev, "invalid number of dma mux args\n"); +		return ERR_PTR(-EINVAL); +	} + +	if (dma_spec->args[0] > dmamux->dmamux_requests) { +		dev_err(&pdev->dev, "invalid mux request number: %d\n", +			dma_spec->args[0]); +		return ERR_PTR(-EINVAL); +	} + +	mux = kzalloc(sizeof(*mux), GFP_KERNEL); +	if (!mux) +		return ERR_PTR(-ENOMEM); + +	spin_lock_irqsave(&dmamux->lock, flags); +	mux->chan_id = find_first_zero_bit(dmamux->dma_inuse, +					   dmamux->dma_requests); + +	if (mux->chan_id == dmamux->dma_requests) { +		spin_unlock_irqrestore(&dmamux->lock, flags); +		dev_err(&pdev->dev, "Run out of free DMA requests\n"); +		ret = -ENOMEM; +		goto error_chan_id; +	} +	set_bit(mux->chan_id, dmamux->dma_inuse); +	spin_unlock_irqrestore(&dmamux->lock, flags); + +	/* Look for DMA Master */ +	for (i = 1, min = 0, max = dmamux->dma_reqs[i]; +	     i <= dmamux->dma_reqs[0]; +	     min += dmamux->dma_reqs[i], max += dmamux->dma_reqs[++i]) +		if (mux->chan_id < max) +			break; +	mux->master = i - 1; + +	/* The of_node_put() will be done in of_dma_router_xlate function */ +	dma_spec->np = of_parse_phandle(ofdma->of_node, "dma-masters", i - 1); +	if (!dma_spec->np) { +		dev_err(&pdev->dev, "can't get dma master\n"); +		ret = -EINVAL; +		goto error; +	} + +	/* Set dma request */ +	spin_lock_irqsave(&dmamux->lock, flags); +	ret = pm_runtime_resume_and_get(&pdev->dev); +	if (ret < 0) { +		spin_unlock_irqrestore(&dmamux->lock, flags); +		goto error; +	} +	spin_unlock_irqrestore(&dmamux->lock, flags); + +	mux->request = dma_spec->args[0]; + +	/*  craft DMA spec */ +	dma_spec->args[3] = dma_spec->args[2] | mux->chan_id << 16; +	dma_spec->args[2] = dma_spec->args[1]; +	dma_spec->args[1] = 0; +	dma_spec->args[0] = mux->chan_id - min; +	dma_spec->args_count = 4; + +	stm32_dmamux_write(dmamux->iomem, STM32_DMAMUX_CCR(mux->chan_id), +			   mux->request); +	dev_dbg(&pdev->dev, "Mapping DMAMUX(%u) to DMA%u(%u)\n", +		mux->request, mux->master, mux->chan_id); + +	return mux; + +error: +	clear_bit(mux->chan_id, dmamux->dma_inuse); + +error_chan_id: +	kfree(mux); +	return ERR_PTR(ret); +} + +static const struct of_device_id stm32_stm32dma_master_match[] __maybe_unused = { +	{ .compatible = "st,stm32-dma", }, +	{}, +}; + +static int stm32_dmamux_probe(struct platform_device *pdev) +{ +	struct device_node *node = pdev->dev.of_node; +	const struct of_device_id *match; +	struct device_node *dma_node; +	struct stm32_dmamux_data *stm32_dmamux; +	void __iomem *iomem; +	struct reset_control *rst; +	int i, count, ret; +	u32 dma_req; + +	if (!node) +		return -ENODEV; + +	count = device_property_count_u32(&pdev->dev, "dma-masters"); +	if (count < 0) { +		dev_err(&pdev->dev, "Can't get DMA master(s) node\n"); +		return -ENODEV; +	} + +	stm32_dmamux = devm_kzalloc(&pdev->dev, sizeof(*stm32_dmamux) + +				    sizeof(u32) * (count + 1), GFP_KERNEL); +	if (!stm32_dmamux) +		return -ENOMEM; + +	dma_req = 0; +	for (i = 1; i <= count; i++) { +		dma_node = of_parse_phandle(node, "dma-masters", i - 1); + +		match = of_match_node(stm32_stm32dma_master_match, dma_node); +		if (!match) { +			dev_err(&pdev->dev, "DMA master is not supported\n"); +			of_node_put(dma_node); +			return -EINVAL; +		} + +		if (of_property_read_u32(dma_node, "dma-requests", +					 &stm32_dmamux->dma_reqs[i])) { +			dev_info(&pdev->dev, +				 "Missing MUX output information, using %u.\n", +				 STM32_DMAMUX_MAX_DMA_REQUESTS); +			stm32_dmamux->dma_reqs[i] = +				STM32_DMAMUX_MAX_DMA_REQUESTS; +		} +		dma_req += stm32_dmamux->dma_reqs[i]; +		of_node_put(dma_node); +	} + +	if (dma_req > STM32_DMAMUX_MAX_DMA_REQUESTS) { +		dev_err(&pdev->dev, "Too many DMA Master Requests to manage\n"); +		return -ENODEV; +	} + +	stm32_dmamux->dma_requests = dma_req; +	stm32_dmamux->dma_reqs[0] = count; + +	if (device_property_read_u32(&pdev->dev, "dma-requests", +				     &stm32_dmamux->dmamux_requests)) { +		stm32_dmamux->dmamux_requests = STM32_DMAMUX_MAX_REQUESTS; +		dev_warn(&pdev->dev, "DMAMUX defaulting on %u requests\n", +			 stm32_dmamux->dmamux_requests); +	} +	pm_runtime_get_noresume(&pdev->dev); + +	iomem = devm_platform_ioremap_resource(pdev, 0); +	if (IS_ERR(iomem)) +		return PTR_ERR(iomem); + +	spin_lock_init(&stm32_dmamux->lock); + +	stm32_dmamux->clk = devm_clk_get(&pdev->dev, NULL); +	if (IS_ERR(stm32_dmamux->clk)) +		return dev_err_probe(&pdev->dev, PTR_ERR(stm32_dmamux->clk), +				     "Missing clock controller\n"); + +	ret = clk_prepare_enable(stm32_dmamux->clk); +	if (ret < 0) { +		dev_err(&pdev->dev, "clk_prep_enable error: %d\n", ret); +		return ret; +	} + +	rst = devm_reset_control_get(&pdev->dev, NULL); +	if (IS_ERR(rst)) { +		ret = PTR_ERR(rst); +		if (ret == -EPROBE_DEFER) +			goto err_clk; +	} else if (count > 1) { /* Don't reset if there is only one dma-master */ +		reset_control_assert(rst); +		udelay(2); +		reset_control_deassert(rst); +	} + +	stm32_dmamux->iomem = iomem; +	stm32_dmamux->dmarouter.dev = &pdev->dev; +	stm32_dmamux->dmarouter.route_free = stm32_dmamux_free; + +	platform_set_drvdata(pdev, stm32_dmamux); +	pm_runtime_set_active(&pdev->dev); +	pm_runtime_enable(&pdev->dev); + +	pm_runtime_get_noresume(&pdev->dev); + +	/* Reset the dmamux */ +	for (i = 0; i < stm32_dmamux->dma_requests; i++) +		stm32_dmamux_write(stm32_dmamux->iomem, STM32_DMAMUX_CCR(i), 0); + +	pm_runtime_put(&pdev->dev); + +	ret = of_dma_router_register(node, stm32_dmamux_route_allocate, +				     &stm32_dmamux->dmarouter); +	if (ret) +		goto pm_disable; + +	return 0; + +pm_disable: +	pm_runtime_disable(&pdev->dev); +err_clk: +	clk_disable_unprepare(stm32_dmamux->clk); + +	return ret; +} + +#ifdef CONFIG_PM +static int stm32_dmamux_runtime_suspend(struct device *dev) +{ +	struct platform_device *pdev = to_platform_device(dev); +	struct stm32_dmamux_data *stm32_dmamux = platform_get_drvdata(pdev); + +	clk_disable_unprepare(stm32_dmamux->clk); + +	return 0; +} + +static int stm32_dmamux_runtime_resume(struct device *dev) +{ +	struct platform_device *pdev = to_platform_device(dev); +	struct stm32_dmamux_data *stm32_dmamux = platform_get_drvdata(pdev); +	int ret; + +	ret = clk_prepare_enable(stm32_dmamux->clk); +	if (ret) { +		dev_err(&pdev->dev, "failed to prepare_enable clock\n"); +		return ret; +	} + +	return 0; +} +#endif + +#ifdef CONFIG_PM_SLEEP +static int stm32_dmamux_suspend(struct device *dev) +{ +	struct platform_device *pdev = to_platform_device(dev); +	struct stm32_dmamux_data *stm32_dmamux = platform_get_drvdata(pdev); +	int i, ret; + +	ret = pm_runtime_resume_and_get(dev); +	if (ret < 0) +		return ret; + +	for (i = 0; i < stm32_dmamux->dma_requests; i++) +		stm32_dmamux->ccr[i] = stm32_dmamux_read(stm32_dmamux->iomem, +							 STM32_DMAMUX_CCR(i)); + +	pm_runtime_put_sync(dev); + +	pm_runtime_force_suspend(dev); + +	return 0; +} + +static int stm32_dmamux_resume(struct device *dev) +{ +	struct platform_device *pdev = to_platform_device(dev); +	struct stm32_dmamux_data *stm32_dmamux = platform_get_drvdata(pdev); +	int i, ret; + +	ret = pm_runtime_force_resume(dev); +	if (ret < 0) +		return ret; + +	ret = pm_runtime_resume_and_get(dev); +	if (ret < 0) +		return ret; + +	for (i = 0; i < stm32_dmamux->dma_requests; i++) +		stm32_dmamux_write(stm32_dmamux->iomem, STM32_DMAMUX_CCR(i), +				   stm32_dmamux->ccr[i]); + +	pm_runtime_put_sync(dev); + +	return 0; +} +#endif + +static const struct dev_pm_ops stm32_dmamux_pm_ops = { +	SET_SYSTEM_SLEEP_PM_OPS(stm32_dmamux_suspend, stm32_dmamux_resume) +	SET_RUNTIME_PM_OPS(stm32_dmamux_runtime_suspend, +			   stm32_dmamux_runtime_resume, NULL) +}; + +static const struct of_device_id stm32_dmamux_match[] = { +	{ .compatible = "st,stm32h7-dmamux" }, +	{}, +}; + +static struct platform_driver stm32_dmamux_driver = { +	.probe	= stm32_dmamux_probe, +	.driver = { +		.name = "stm32-dmamux", +		.of_match_table = stm32_dmamux_match, +		.pm = &stm32_dmamux_pm_ops, +	}, +}; + +static int __init stm32_dmamux_init(void) +{ +	return platform_driver_register(&stm32_dmamux_driver); +} +arch_initcall(stm32_dmamux_init); + +MODULE_DESCRIPTION("DMA Router driver for STM32 DMA MUX"); +MODULE_AUTHOR("M'boumba Cedric Madianga <[email protected]>"); +MODULE_AUTHOR("Pierre-Yves Mordret <[email protected]>"); |