1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (c) 2018, NVIDIA CORPORATION. |
4 | */ |
5 | |
6 | #include <linux/genalloc.h> |
7 | #include <linux/io.h> |
8 | #include <linux/mailbox_client.h> |
9 | #include <linux/of_address.h> |
10 | #include <linux/platform_device.h> |
11 | |
12 | #include <soc/tegra/bpmp.h> |
13 | #include <soc/tegra/bpmp-abi.h> |
14 | #include <soc/tegra/ivc.h> |
15 | |
16 | #include "bpmp-private.h" |
17 | |
18 | struct tegra186_bpmp { |
19 | struct tegra_bpmp *parent; |
20 | |
21 | struct { |
22 | struct gen_pool *pool; |
23 | union { |
24 | void __iomem *sram; |
25 | void *dram; |
26 | }; |
27 | dma_addr_t phys; |
28 | } tx, rx; |
29 | |
30 | struct { |
31 | struct mbox_client client; |
32 | struct mbox_chan *channel; |
33 | } mbox; |
34 | }; |
35 | |
36 | static inline struct tegra_bpmp * |
37 | mbox_client_to_bpmp(struct mbox_client *client) |
38 | { |
39 | struct tegra186_bpmp *priv; |
40 | |
41 | priv = container_of(client, struct tegra186_bpmp, mbox.client); |
42 | |
43 | return priv->parent; |
44 | } |
45 | |
46 | static bool tegra186_bpmp_is_message_ready(struct tegra_bpmp_channel *channel) |
47 | { |
48 | int err; |
49 | |
50 | err = tegra_ivc_read_get_next_frame(ivc: channel->ivc, map: &channel->ib); |
51 | if (err) { |
52 | iosys_map_clear(map: &channel->ib); |
53 | return false; |
54 | } |
55 | |
56 | return true; |
57 | } |
58 | |
59 | static bool tegra186_bpmp_is_channel_free(struct tegra_bpmp_channel *channel) |
60 | { |
61 | int err; |
62 | |
63 | err = tegra_ivc_write_get_next_frame(ivc: channel->ivc, map: &channel->ob); |
64 | if (err) { |
65 | iosys_map_clear(map: &channel->ob); |
66 | return false; |
67 | } |
68 | |
69 | return true; |
70 | } |
71 | |
72 | static int tegra186_bpmp_ack_message(struct tegra_bpmp_channel *channel) |
73 | { |
74 | return tegra_ivc_read_advance(ivc: channel->ivc); |
75 | } |
76 | |
77 | static int tegra186_bpmp_post_message(struct tegra_bpmp_channel *channel) |
78 | { |
79 | return tegra_ivc_write_advance(ivc: channel->ivc); |
80 | } |
81 | |
82 | static int tegra186_bpmp_ring_doorbell(struct tegra_bpmp *bpmp) |
83 | { |
84 | struct tegra186_bpmp *priv = bpmp->priv; |
85 | int err; |
86 | |
87 | err = mbox_send_message(chan: priv->mbox.channel, NULL); |
88 | if (err < 0) |
89 | return err; |
90 | |
91 | mbox_client_txdone(chan: priv->mbox.channel, r: 0); |
92 | |
93 | return 0; |
94 | } |
95 | |
96 | static void tegra186_bpmp_ivc_notify(struct tegra_ivc *ivc, void *data) |
97 | { |
98 | struct tegra_bpmp *bpmp = data; |
99 | struct tegra186_bpmp *priv = bpmp->priv; |
100 | |
101 | if (WARN_ON(priv->mbox.channel == NULL)) |
102 | return; |
103 | |
104 | tegra186_bpmp_ring_doorbell(bpmp); |
105 | } |
106 | |
107 | static int tegra186_bpmp_channel_init(struct tegra_bpmp_channel *channel, |
108 | struct tegra_bpmp *bpmp, |
109 | unsigned int index) |
110 | { |
111 | struct tegra186_bpmp *priv = bpmp->priv; |
112 | size_t message_size, queue_size; |
113 | struct iosys_map rx, tx; |
114 | unsigned int offset; |
115 | int err; |
116 | |
117 | channel->ivc = devm_kzalloc(dev: bpmp->dev, size: sizeof(*channel->ivc), |
118 | GFP_KERNEL); |
119 | if (!channel->ivc) |
120 | return -ENOMEM; |
121 | |
122 | message_size = tegra_ivc_align(MSG_MIN_SZ); |
123 | queue_size = tegra_ivc_total_queue_size(queue_size: message_size); |
124 | offset = queue_size * index; |
125 | |
126 | if (priv->rx.pool) { |
127 | iosys_map_set_vaddr_iomem(map: &rx, vaddr_iomem: priv->rx.sram + offset); |
128 | iosys_map_set_vaddr_iomem(map: &tx, vaddr_iomem: priv->tx.sram + offset); |
129 | } else { |
130 | iosys_map_set_vaddr(map: &rx, vaddr: priv->rx.dram + offset); |
131 | iosys_map_set_vaddr(map: &tx, vaddr: priv->tx.dram + offset); |
132 | } |
133 | |
134 | err = tegra_ivc_init(ivc: channel->ivc, NULL, rx: &rx, rx_phys: priv->rx.phys + offset, tx: &tx, |
135 | tx_phys: priv->tx.phys + offset, num_frames: 1, frame_size: message_size, notify: tegra186_bpmp_ivc_notify, |
136 | data: bpmp); |
137 | if (err < 0) { |
138 | dev_err(bpmp->dev, "failed to setup IVC for channel %u: %d\n" , |
139 | index, err); |
140 | return err; |
141 | } |
142 | |
143 | init_completion(x: &channel->completion); |
144 | channel->bpmp = bpmp; |
145 | |
146 | return 0; |
147 | } |
148 | |
149 | static void tegra186_bpmp_channel_reset(struct tegra_bpmp_channel *channel) |
150 | { |
151 | /* reset the channel state */ |
152 | tegra_ivc_reset(ivc: channel->ivc); |
153 | |
154 | /* sync the channel state with BPMP */ |
155 | while (tegra_ivc_notified(ivc: channel->ivc)) |
156 | ; |
157 | } |
158 | |
159 | static void tegra186_bpmp_channel_cleanup(struct tegra_bpmp_channel *channel) |
160 | { |
161 | tegra_ivc_cleanup(ivc: channel->ivc); |
162 | } |
163 | |
164 | static void mbox_handle_rx(struct mbox_client *client, void *data) |
165 | { |
166 | struct tegra_bpmp *bpmp = mbox_client_to_bpmp(client); |
167 | |
168 | tegra_bpmp_handle_rx(bpmp); |
169 | } |
170 | |
171 | static void tegra186_bpmp_teardown_channels(struct tegra_bpmp *bpmp) |
172 | { |
173 | struct tegra186_bpmp *priv = bpmp->priv; |
174 | unsigned int i; |
175 | |
176 | for (i = 0; i < bpmp->threaded.count; i++) { |
177 | if (!bpmp->threaded_channels[i].bpmp) |
178 | continue; |
179 | |
180 | tegra186_bpmp_channel_cleanup(channel: &bpmp->threaded_channels[i]); |
181 | } |
182 | |
183 | tegra186_bpmp_channel_cleanup(channel: bpmp->rx_channel); |
184 | tegra186_bpmp_channel_cleanup(channel: bpmp->tx_channel); |
185 | |
186 | if (priv->tx.pool) { |
187 | gen_pool_free(pool: priv->tx.pool, addr: (unsigned long)priv->tx.sram, size: 4096); |
188 | gen_pool_free(pool: priv->rx.pool, addr: (unsigned long)priv->rx.sram, size: 4096); |
189 | } |
190 | } |
191 | |
192 | static int tegra186_bpmp_dram_init(struct tegra_bpmp *bpmp) |
193 | { |
194 | struct tegra186_bpmp *priv = bpmp->priv; |
195 | struct device_node *np; |
196 | struct resource res; |
197 | size_t size; |
198 | int err; |
199 | |
200 | np = of_parse_phandle(np: bpmp->dev->of_node, phandle_name: "memory-region" , index: 0); |
201 | if (!np) |
202 | return -ENODEV; |
203 | |
204 | err = of_address_to_resource(dev: np, index: 0, r: &res); |
205 | if (err < 0) { |
206 | dev_warn(bpmp->dev, "failed to parse memory region: %d\n" , err); |
207 | return err; |
208 | } |
209 | |
210 | size = resource_size(res: &res); |
211 | |
212 | if (size < SZ_8K) { |
213 | dev_warn(bpmp->dev, "DRAM region must be larger than 8 KiB\n" ); |
214 | return -EINVAL; |
215 | } |
216 | |
217 | priv->tx.phys = res.start; |
218 | priv->rx.phys = res.start + SZ_4K; |
219 | |
220 | priv->tx.dram = devm_memremap(dev: bpmp->dev, offset: priv->tx.phys, size, |
221 | flags: MEMREMAP_WC); |
222 | if (IS_ERR(ptr: priv->tx.dram)) { |
223 | err = PTR_ERR(ptr: priv->tx.dram); |
224 | dev_warn(bpmp->dev, "failed to map DRAM region: %d\n" , err); |
225 | return err; |
226 | } |
227 | |
228 | priv->rx.dram = priv->tx.dram + SZ_4K; |
229 | |
230 | return 0; |
231 | } |
232 | |
233 | static int tegra186_bpmp_sram_init(struct tegra_bpmp *bpmp) |
234 | { |
235 | struct tegra186_bpmp *priv = bpmp->priv; |
236 | int err; |
237 | |
238 | priv->tx.pool = of_gen_pool_get(np: bpmp->dev->of_node, propname: "shmem" , index: 0); |
239 | if (!priv->tx.pool) { |
240 | dev_err(bpmp->dev, "TX shmem pool not found\n" ); |
241 | return -EPROBE_DEFER; |
242 | } |
243 | |
244 | priv->tx.sram = (void __iomem *)gen_pool_dma_alloc(pool: priv->tx.pool, size: 4096, |
245 | dma: &priv->tx.phys); |
246 | if (!priv->tx.sram) { |
247 | dev_err(bpmp->dev, "failed to allocate from TX pool\n" ); |
248 | return -ENOMEM; |
249 | } |
250 | |
251 | priv->rx.pool = of_gen_pool_get(np: bpmp->dev->of_node, propname: "shmem" , index: 1); |
252 | if (!priv->rx.pool) { |
253 | dev_err(bpmp->dev, "RX shmem pool not found\n" ); |
254 | err = -EPROBE_DEFER; |
255 | goto free_tx; |
256 | } |
257 | |
258 | priv->rx.sram = (void __iomem *)gen_pool_dma_alloc(pool: priv->rx.pool, size: 4096, |
259 | dma: &priv->rx.phys); |
260 | if (!priv->rx.sram) { |
261 | dev_err(bpmp->dev, "failed to allocate from RX pool\n" ); |
262 | err = -ENOMEM; |
263 | goto free_tx; |
264 | } |
265 | |
266 | return 0; |
267 | |
268 | free_tx: |
269 | gen_pool_free(pool: priv->tx.pool, addr: (unsigned long)priv->tx.sram, size: 4096); |
270 | |
271 | return err; |
272 | } |
273 | |
274 | static int tegra186_bpmp_setup_channels(struct tegra_bpmp *bpmp) |
275 | { |
276 | unsigned int i; |
277 | int err; |
278 | |
279 | err = tegra186_bpmp_dram_init(bpmp); |
280 | if (err == -ENODEV) { |
281 | err = tegra186_bpmp_sram_init(bpmp); |
282 | if (err < 0) |
283 | return err; |
284 | } |
285 | |
286 | err = tegra186_bpmp_channel_init(channel: bpmp->tx_channel, bpmp, |
287 | index: bpmp->soc->channels.cpu_tx.offset); |
288 | if (err < 0) |
289 | return err; |
290 | |
291 | err = tegra186_bpmp_channel_init(channel: bpmp->rx_channel, bpmp, |
292 | index: bpmp->soc->channels.cpu_rx.offset); |
293 | if (err < 0) { |
294 | tegra186_bpmp_channel_cleanup(channel: bpmp->tx_channel); |
295 | return err; |
296 | } |
297 | |
298 | for (i = 0; i < bpmp->threaded.count; i++) { |
299 | unsigned int index = bpmp->soc->channels.thread.offset + i; |
300 | |
301 | err = tegra186_bpmp_channel_init(channel: &bpmp->threaded_channels[i], |
302 | bpmp, index); |
303 | if (err < 0) |
304 | break; |
305 | } |
306 | |
307 | if (err < 0) |
308 | tegra186_bpmp_teardown_channels(bpmp); |
309 | |
310 | return err; |
311 | } |
312 | |
313 | static void tegra186_bpmp_reset_channels(struct tegra_bpmp *bpmp) |
314 | { |
315 | unsigned int i; |
316 | |
317 | /* reset message channels */ |
318 | tegra186_bpmp_channel_reset(channel: bpmp->tx_channel); |
319 | tegra186_bpmp_channel_reset(channel: bpmp->rx_channel); |
320 | |
321 | for (i = 0; i < bpmp->threaded.count; i++) |
322 | tegra186_bpmp_channel_reset(channel: &bpmp->threaded_channels[i]); |
323 | } |
324 | |
325 | static int tegra186_bpmp_init(struct tegra_bpmp *bpmp) |
326 | { |
327 | struct tegra186_bpmp *priv; |
328 | int err; |
329 | |
330 | priv = devm_kzalloc(dev: bpmp->dev, size: sizeof(*priv), GFP_KERNEL); |
331 | if (!priv) |
332 | return -ENOMEM; |
333 | |
334 | priv->parent = bpmp; |
335 | bpmp->priv = priv; |
336 | |
337 | err = tegra186_bpmp_setup_channels(bpmp); |
338 | if (err < 0) |
339 | return err; |
340 | |
341 | /* mbox registration */ |
342 | priv->mbox.client.dev = bpmp->dev; |
343 | priv->mbox.client.rx_callback = mbox_handle_rx; |
344 | priv->mbox.client.tx_block = false; |
345 | priv->mbox.client.knows_txdone = false; |
346 | |
347 | priv->mbox.channel = mbox_request_channel(cl: &priv->mbox.client, index: 0); |
348 | if (IS_ERR(ptr: priv->mbox.channel)) { |
349 | err = PTR_ERR(ptr: priv->mbox.channel); |
350 | dev_err(bpmp->dev, "failed to get HSP mailbox: %d\n" , err); |
351 | tegra186_bpmp_teardown_channels(bpmp); |
352 | return err; |
353 | } |
354 | |
355 | tegra186_bpmp_reset_channels(bpmp); |
356 | |
357 | return 0; |
358 | } |
359 | |
360 | static void tegra186_bpmp_deinit(struct tegra_bpmp *bpmp) |
361 | { |
362 | struct tegra186_bpmp *priv = bpmp->priv; |
363 | |
364 | mbox_free_channel(chan: priv->mbox.channel); |
365 | |
366 | tegra186_bpmp_teardown_channels(bpmp); |
367 | } |
368 | |
369 | static int tegra186_bpmp_resume(struct tegra_bpmp *bpmp) |
370 | { |
371 | tegra186_bpmp_reset_channels(bpmp); |
372 | |
373 | return 0; |
374 | } |
375 | |
376 | const struct tegra_bpmp_ops tegra186_bpmp_ops = { |
377 | .init = tegra186_bpmp_init, |
378 | .deinit = tegra186_bpmp_deinit, |
379 | .is_response_ready = tegra186_bpmp_is_message_ready, |
380 | .is_request_ready = tegra186_bpmp_is_message_ready, |
381 | .ack_response = tegra186_bpmp_ack_message, |
382 | .ack_request = tegra186_bpmp_ack_message, |
383 | .is_response_channel_free = tegra186_bpmp_is_channel_free, |
384 | .is_request_channel_free = tegra186_bpmp_is_channel_free, |
385 | .post_response = tegra186_bpmp_post_message, |
386 | .post_request = tegra186_bpmp_post_message, |
387 | .ring_doorbell = tegra186_bpmp_ring_doorbell, |
388 | .resume = tegra186_bpmp_resume, |
389 | }; |
390 | |