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