162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2022 Schneider-Electric 462306a36Sopenharmony_ci * Author: Miquel Raynal <miquel.raynal@bootlin.com 562306a36Sopenharmony_ci * Based on TI crossbar driver written by Peter Ujfalusi <peter.ujfalusi@ti.com> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci#include <linux/bitops.h> 862306a36Sopenharmony_ci#include <linux/of.h> 962306a36Sopenharmony_ci#include <linux/of_dma.h> 1062306a36Sopenharmony_ci#include <linux/of_platform.h> 1162306a36Sopenharmony_ci#include <linux/platform_device.h> 1262306a36Sopenharmony_ci#include <linux/slab.h> 1362306a36Sopenharmony_ci#include <linux/soc/renesas/r9a06g032-sysctrl.h> 1462306a36Sopenharmony_ci#include <linux/types.h> 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#define RNZ1_DMAMUX_NCELLS 6 1762306a36Sopenharmony_ci#define RZN1_DMAMUX_MAX_LINES 64 1862306a36Sopenharmony_ci#define RZN1_DMAMUX_LINES_PER_CTLR 16 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_cistruct rzn1_dmamux_data { 2162306a36Sopenharmony_ci struct dma_router dmarouter; 2262306a36Sopenharmony_ci DECLARE_BITMAP(used_chans, 2 * RZN1_DMAMUX_LINES_PER_CTLR); 2362306a36Sopenharmony_ci}; 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_cistruct rzn1_dmamux_map { 2662306a36Sopenharmony_ci unsigned int req_idx; 2762306a36Sopenharmony_ci}; 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_cistatic void rzn1_dmamux_free(struct device *dev, void *route_data) 3062306a36Sopenharmony_ci{ 3162306a36Sopenharmony_ci struct rzn1_dmamux_data *dmamux = dev_get_drvdata(dev); 3262306a36Sopenharmony_ci struct rzn1_dmamux_map *map = route_data; 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci dev_dbg(dev, "Unmapping DMAMUX request %u\n", map->req_idx); 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci clear_bit(map->req_idx, dmamux->used_chans); 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci kfree(map); 3962306a36Sopenharmony_ci} 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_cistatic void *rzn1_dmamux_route_allocate(struct of_phandle_args *dma_spec, 4262306a36Sopenharmony_ci struct of_dma *ofdma) 4362306a36Sopenharmony_ci{ 4462306a36Sopenharmony_ci struct platform_device *pdev = of_find_device_by_node(ofdma->of_node); 4562306a36Sopenharmony_ci struct rzn1_dmamux_data *dmamux = platform_get_drvdata(pdev); 4662306a36Sopenharmony_ci struct rzn1_dmamux_map *map; 4762306a36Sopenharmony_ci unsigned int dmac_idx, chan, val; 4862306a36Sopenharmony_ci u32 mask; 4962306a36Sopenharmony_ci int ret; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci if (dma_spec->args_count != RNZ1_DMAMUX_NCELLS) 5262306a36Sopenharmony_ci return ERR_PTR(-EINVAL); 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci map = kzalloc(sizeof(*map), GFP_KERNEL); 5562306a36Sopenharmony_ci if (!map) 5662306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci chan = dma_spec->args[0]; 5962306a36Sopenharmony_ci map->req_idx = dma_spec->args[4]; 6062306a36Sopenharmony_ci val = dma_spec->args[5]; 6162306a36Sopenharmony_ci dma_spec->args_count -= 2; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci if (chan >= RZN1_DMAMUX_LINES_PER_CTLR) { 6462306a36Sopenharmony_ci dev_err(&pdev->dev, "Invalid DMA request line: %u\n", chan); 6562306a36Sopenharmony_ci ret = -EINVAL; 6662306a36Sopenharmony_ci goto free_map; 6762306a36Sopenharmony_ci } 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci if (map->req_idx >= RZN1_DMAMUX_MAX_LINES || 7062306a36Sopenharmony_ci (map->req_idx % RZN1_DMAMUX_LINES_PER_CTLR) != chan) { 7162306a36Sopenharmony_ci dev_err(&pdev->dev, "Invalid MUX request line: %u\n", map->req_idx); 7262306a36Sopenharmony_ci ret = -EINVAL; 7362306a36Sopenharmony_ci goto free_map; 7462306a36Sopenharmony_ci } 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci dmac_idx = map->req_idx >= RZN1_DMAMUX_LINES_PER_CTLR ? 1 : 0; 7762306a36Sopenharmony_ci dma_spec->np = of_parse_phandle(ofdma->of_node, "dma-masters", dmac_idx); 7862306a36Sopenharmony_ci if (!dma_spec->np) { 7962306a36Sopenharmony_ci dev_err(&pdev->dev, "Can't get DMA master\n"); 8062306a36Sopenharmony_ci ret = -EINVAL; 8162306a36Sopenharmony_ci goto free_map; 8262306a36Sopenharmony_ci } 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci dev_dbg(&pdev->dev, "Mapping DMAMUX request %u to DMAC%u request %u\n", 8562306a36Sopenharmony_ci map->req_idx, dmac_idx, chan); 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci if (test_and_set_bit(map->req_idx, dmamux->used_chans)) { 8862306a36Sopenharmony_ci ret = -EBUSY; 8962306a36Sopenharmony_ci goto free_map; 9062306a36Sopenharmony_ci } 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci mask = BIT(map->req_idx); 9362306a36Sopenharmony_ci ret = r9a06g032_sysctrl_set_dmamux(mask, val ? mask : 0); 9462306a36Sopenharmony_ci if (ret) 9562306a36Sopenharmony_ci goto clear_bitmap; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci return map; 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ciclear_bitmap: 10062306a36Sopenharmony_ci clear_bit(map->req_idx, dmamux->used_chans); 10162306a36Sopenharmony_cifree_map: 10262306a36Sopenharmony_ci kfree(map); 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci return ERR_PTR(ret); 10562306a36Sopenharmony_ci} 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci#ifdef CONFIG_OF 10862306a36Sopenharmony_cistatic const struct of_device_id rzn1_dmac_match[] = { 10962306a36Sopenharmony_ci { .compatible = "renesas,rzn1-dma" }, 11062306a36Sopenharmony_ci {} 11162306a36Sopenharmony_ci}; 11262306a36Sopenharmony_ci#endif 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_cistatic int rzn1_dmamux_probe(struct platform_device *pdev) 11562306a36Sopenharmony_ci{ 11662306a36Sopenharmony_ci struct device_node *mux_node = pdev->dev.of_node; 11762306a36Sopenharmony_ci const struct of_device_id *match; 11862306a36Sopenharmony_ci struct device_node *dmac_node; 11962306a36Sopenharmony_ci struct rzn1_dmamux_data *dmamux; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci dmamux = devm_kzalloc(&pdev->dev, sizeof(*dmamux), GFP_KERNEL); 12262306a36Sopenharmony_ci if (!dmamux) 12362306a36Sopenharmony_ci return -ENOMEM; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci dmac_node = of_parse_phandle(mux_node, "dma-masters", 0); 12662306a36Sopenharmony_ci if (!dmac_node) 12762306a36Sopenharmony_ci return dev_err_probe(&pdev->dev, -ENODEV, "Can't get DMA master node\n"); 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci match = of_match_node(rzn1_dmac_match, dmac_node); 13062306a36Sopenharmony_ci of_node_put(dmac_node); 13162306a36Sopenharmony_ci if (!match) 13262306a36Sopenharmony_ci return dev_err_probe(&pdev->dev, -EINVAL, "DMA master is not supported\n"); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci dmamux->dmarouter.dev = &pdev->dev; 13562306a36Sopenharmony_ci dmamux->dmarouter.route_free = rzn1_dmamux_free; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci platform_set_drvdata(pdev, dmamux); 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci return of_dma_router_register(mux_node, rzn1_dmamux_route_allocate, 14062306a36Sopenharmony_ci &dmamux->dmarouter); 14162306a36Sopenharmony_ci} 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_cistatic const struct of_device_id rzn1_dmamux_match[] = { 14462306a36Sopenharmony_ci { .compatible = "renesas,rzn1-dmamux" }, 14562306a36Sopenharmony_ci {} 14662306a36Sopenharmony_ci}; 14762306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, rzn1_dmamux_match); 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_cistatic struct platform_driver rzn1_dmamux_driver = { 15062306a36Sopenharmony_ci .driver = { 15162306a36Sopenharmony_ci .name = "renesas,rzn1-dmamux", 15262306a36Sopenharmony_ci .of_match_table = rzn1_dmamux_match, 15362306a36Sopenharmony_ci }, 15462306a36Sopenharmony_ci .probe = rzn1_dmamux_probe, 15562306a36Sopenharmony_ci}; 15662306a36Sopenharmony_cimodule_platform_driver(rzn1_dmamux_driver); 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 15962306a36Sopenharmony_ciMODULE_AUTHOR("Miquel Raynal <miquel.raynal@bootlin.com"); 16062306a36Sopenharmony_ciMODULE_DESCRIPTION("Renesas RZ/N1 DMAMUX driver"); 161