Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | // SPDX-License-Identifier: GPL-2.0-only /* * DMA Router driver for LPC18xx/43xx DMA MUX * * Copyright (C) 2015 Joachim Eastwood <manabian@gmail.com> * * Based on TI DMA Crossbar driver by: * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com * Author: Peter Ujfalusi <peter.ujfalusi@ti.com> */ #include <linux/err.h> #include <linux/init.h> #include <linux/mfd/syscon.h> #include <linux/of_device.h> #include <linux/of_dma.h> #include <linux/regmap.h> #include <linux/spinlock.h> /* CREG register offset and macros for mux manipulation */ #define LPC18XX_CREG_DMAMUX 0x11c #define LPC18XX_DMAMUX_VAL(v, n) ((v) << (n * 2)) #define LPC18XX_DMAMUX_MASK(n) (0x3 << (n * 2)) #define LPC18XX_DMAMUX_MAX_VAL 0x3 struct lpc18xx_dmamux { u32 value; bool busy; }; struct lpc18xx_dmamux_data { struct dma_router dmarouter; struct lpc18xx_dmamux *muxes; u32 dma_master_requests; u32 dma_mux_requests; struct regmap *reg; spinlock_t lock; }; static void lpc18xx_dmamux_free(struct device *dev, void *route_data) { struct lpc18xx_dmamux_data *dmamux = dev_get_drvdata(dev); struct lpc18xx_dmamux *mux = route_data; unsigned long flags; spin_lock_irqsave(&dmamux->lock, flags); mux->busy = false; spin_unlock_irqrestore(&dmamux->lock, flags); } static void *lpc18xx_dmamux_reserve(struct of_phandle_args *dma_spec, struct of_dma *ofdma) { struct platform_device *pdev = of_find_device_by_node(ofdma->of_node); struct lpc18xx_dmamux_data *dmamux = platform_get_drvdata(pdev); unsigned long flags; unsigned mux; if (dma_spec->args_count != 3) { dev_err(&pdev->dev, "invalid number of dma mux args\n"); return ERR_PTR(-EINVAL); } mux = dma_spec->args[0]; if (mux >= dmamux->dma_master_requests) { dev_err(&pdev->dev, "invalid mux number: %d\n", dma_spec->args[0]); return ERR_PTR(-EINVAL); } if (dma_spec->args[1] > LPC18XX_DMAMUX_MAX_VAL) { dev_err(&pdev->dev, "invalid dma mux value: %d\n", dma_spec->args[1]); return ERR_PTR(-EINVAL); } /* The of_node_put() will be done in the core for the node */ dma_spec->np = of_parse_phandle(ofdma->of_node, "dma-masters", 0); if (!dma_spec->np) { dev_err(&pdev->dev, "can't get dma master\n"); return ERR_PTR(-EINVAL); } spin_lock_irqsave(&dmamux->lock, flags); if (dmamux->muxes[mux].busy) { spin_unlock_irqrestore(&dmamux->lock, flags); dev_err(&pdev->dev, "dma request %u busy with %u.%u\n", mux, mux, dmamux->muxes[mux].value); of_node_put(dma_spec->np); return ERR_PTR(-EBUSY); } dmamux->muxes[mux].busy = true; dmamux->muxes[mux].value = dma_spec->args[1]; regmap_update_bits(dmamux->reg, LPC18XX_CREG_DMAMUX, LPC18XX_DMAMUX_MASK(mux), LPC18XX_DMAMUX_VAL(dmamux->muxes[mux].value, mux)); spin_unlock_irqrestore(&dmamux->lock, flags); dma_spec->args[1] = dma_spec->args[2]; dma_spec->args_count = 2; dev_dbg(&pdev->dev, "mapping dmamux %u.%u to dma request %u\n", mux, dmamux->muxes[mux].value, mux); return &dmamux->muxes[mux]; } static int lpc18xx_dmamux_probe(struct platform_device *pdev) { struct device_node *dma_np, *np = pdev->dev.of_node; struct lpc18xx_dmamux_data *dmamux; int ret; dmamux = devm_kzalloc(&pdev->dev, sizeof(*dmamux), GFP_KERNEL); if (!dmamux) return -ENOMEM; dmamux->reg = syscon_regmap_lookup_by_compatible("nxp,lpc1850-creg"); if (IS_ERR(dmamux->reg)) { dev_err(&pdev->dev, "syscon lookup failed\n"); return PTR_ERR(dmamux->reg); } ret = of_property_read_u32(np, "dma-requests", &dmamux->dma_mux_requests); if (ret) { dev_err(&pdev->dev, "missing dma-requests property\n"); return ret; } dma_np = of_parse_phandle(np, "dma-masters", 0); if (!dma_np) { dev_err(&pdev->dev, "can't get dma master\n"); return -ENODEV; } ret = of_property_read_u32(dma_np, "dma-requests", &dmamux->dma_master_requests); of_node_put(dma_np); if (ret) { dev_err(&pdev->dev, "missing master dma-requests property\n"); return ret; } dmamux->muxes = devm_kcalloc(&pdev->dev, dmamux->dma_master_requests, sizeof(struct lpc18xx_dmamux), GFP_KERNEL); if (!dmamux->muxes) return -ENOMEM; spin_lock_init(&dmamux->lock); platform_set_drvdata(pdev, dmamux); dmamux->dmarouter.dev = &pdev->dev; dmamux->dmarouter.route_free = lpc18xx_dmamux_free; return of_dma_router_register(np, lpc18xx_dmamux_reserve, &dmamux->dmarouter); } static const struct of_device_id lpc18xx_dmamux_match[] = { { .compatible = "nxp,lpc1850-dmamux" }, {}, }; static struct platform_driver lpc18xx_dmamux_driver = { .probe = lpc18xx_dmamux_probe, .driver = { .name = "lpc18xx-dmamux", .of_match_table = lpc18xx_dmamux_match, }, }; static int __init lpc18xx_dmamux_init(void) { return platform_driver_register(&lpc18xx_dmamux_driver); } arch_initcall(lpc18xx_dmamux_init); |