1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2012 Oleksandr Tymoshenko <gonzo@freebsd.org>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29 #include <sys/param.h>
30 #include <sys/systm.h>
31 #include <sys/bus.h>
32 #include <sys/kernel.h>
33 #include <sys/lock.h>
34 #include <sys/module.h>
35 #include <sys/mutex.h>
36 #include <sys/sx.h>
37 #include <sys/rman.h>
38 #include <machine/bus.h>
39
40 #include <dev/ofw/ofw_bus.h>
41 #include <dev/ofw/ofw_bus_subr.h>
42
43 #include <arm/broadcom/bcm2835/bcm2835_firmware.h>
44 #include <arm/broadcom/bcm2835/bcm2835_mbox.h>
45 #include <arm/broadcom/bcm2835/bcm2835_mbox_prop.h>
46 #include <arm/broadcom/bcm2835/bcm2835_vcbus.h>
47
48 #include "mbox_if.h"
49
50 #define REG_READ 0x00
51 #define REG_POL 0x10
52 #define REG_SENDER 0x14
53 #define REG_STATUS 0x18
54 #define STATUS_FULL 0x80000000
55 #define STATUS_EMPTY 0x40000000
56 #define REG_CONFIG 0x1C
57 #define CONFIG_DATA_IRQ 0x00000001
58 #define REG_WRITE 0x20 /* This is Mailbox 1 address */
59
60 #define MBOX_MSG(chan, data) (((data) & ~0xf) | ((chan) & 0xf))
61 #define MBOX_CHAN(msg) ((msg) & 0xf)
62 #define MBOX_DATA(msg) ((msg) & ~0xf)
63
64 #define MBOX_LOCK(sc) do { \
65 mtx_lock(&(sc)->lock); \
66 } while(0)
67
68 #define MBOX_UNLOCK(sc) do { \
69 mtx_unlock(&(sc)->lock); \
70 } while(0)
71
72 #ifdef DEBUG
73 #define dprintf(fmt, args...) printf(fmt, ##args)
74 #else
75 #define dprintf(fmt, args...)
76 #endif
77
78 struct bcm_mbox_softc {
79 struct mtx lock;
80 struct resource * mem_res;
81 struct resource * irq_res;
82 void* intr_hl;
83 bus_space_tag_t bst;
84 bus_space_handle_t bsh;
85 int msg[BCM2835_MBOX_CHANS];
86 int have_message[BCM2835_MBOX_CHANS];
87 struct sx property_chan_lock;
88 };
89
90 #define mbox_read_4(sc, reg) \
91 bus_space_read_4((sc)->bst, (sc)->bsh, reg)
92 #define mbox_write_4(sc, reg, val) \
93 bus_space_write_4((sc)->bst, (sc)->bsh, reg, val)
94
95 static struct ofw_compat_data compat_data[] = {
96 {"broadcom,bcm2835-mbox", 1},
97 {"brcm,bcm2835-mbox", 1},
98 {NULL, 0}
99 };
100
101 static int
bcm_mbox_read_msg(struct bcm_mbox_softc * sc,int * ochan)102 bcm_mbox_read_msg(struct bcm_mbox_softc *sc, int *ochan)
103 {
104 #ifdef DEBUG
105 uint32_t data;
106 #endif
107 uint32_t msg;
108 int chan;
109
110 msg = mbox_read_4(sc, REG_READ);
111 dprintf("bcm_mbox_intr: raw data %08x\n", msg);
112 chan = MBOX_CHAN(msg);
113 #ifdef DEBUG
114 data = MBOX_DATA(msg);
115 #endif
116 if (sc->msg[chan]) {
117 printf("bcm_mbox_intr: channel %d oveflow\n", chan);
118 return (1);
119 }
120 dprintf("bcm_mbox_intr: chan %d, data %08x\n", chan, data);
121 sc->msg[chan] = msg;
122
123 if (ochan != NULL)
124 *ochan = chan;
125
126 return (0);
127 }
128
129 static void
bcm_mbox_intr(void * arg)130 bcm_mbox_intr(void *arg)
131 {
132 struct bcm_mbox_softc *sc = arg;
133 int chan;
134
135 MBOX_LOCK(sc);
136 while (!(mbox_read_4(sc, REG_STATUS) & STATUS_EMPTY))
137 if (bcm_mbox_read_msg(sc, &chan) == 0) {
138 sc->have_message[chan] = 1;
139 wakeup(&sc->have_message[chan]);
140 }
141 MBOX_UNLOCK(sc);
142 }
143
144 static int
bcm_mbox_probe(device_t dev)145 bcm_mbox_probe(device_t dev)
146 {
147
148 if (!ofw_bus_status_okay(dev))
149 return (ENXIO);
150
151 if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
152 return (ENXIO);
153
154 device_set_desc(dev, "BCM2835 VideoCore Mailbox");
155
156 return (BUS_PROBE_DEFAULT);
157 }
158
159 static int
bcm_mbox_attach(device_t dev)160 bcm_mbox_attach(device_t dev)
161 {
162 struct bcm_mbox_softc *sc = device_get_softc(dev);
163 int i;
164
165 sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, 0, RF_ACTIVE);
166 if (sc->mem_res == NULL) {
167 device_printf(dev, "could not allocate memory resource\n");
168 return (ENXIO);
169 }
170
171 sc->bst = rman_get_bustag(sc->mem_res);
172 sc->bsh = rman_get_bushandle(sc->mem_res);
173
174 sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, 0, RF_ACTIVE);
175 if (sc->irq_res == NULL) {
176 device_printf(dev, "could not allocate interrupt resource\n");
177 goto fail_mem;
178 }
179
180 /* Setup and enable the timer */
181 if (bus_setup_intr(dev, sc->irq_res, INTR_MPSAFE | INTR_TYPE_MISC,
182 NULL, bcm_mbox_intr, sc, &sc->intr_hl) != 0) {
183 device_printf(dev, "Unable to setup the clock irq handler.\n");
184 goto fail_irq;
185 }
186
187 mtx_init(&sc->lock, "vcio mbox", NULL, MTX_DEF);
188 for (i = 0; i < BCM2835_MBOX_CHANS; i++) {
189 sc->msg[i] = 0;
190 sc->have_message[i] = 0;
191 }
192
193 sx_init(&sc->property_chan_lock, "mboxprop");
194
195 /* Read all pending messages */
196 while ((mbox_read_4(sc, REG_STATUS) & STATUS_EMPTY) == 0)
197 (void)mbox_read_4(sc, REG_READ);
198
199 mbox_write_4(sc, REG_CONFIG, CONFIG_DATA_IRQ);
200
201 return (0);
202
203 fail_irq:
204 bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq_res);
205 sc->irq_res = NULL;
206 fail_mem:
207 bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->mem_res);
208 sc->mem_res = NULL;
209 return (ENXIO);
210 }
211
212 /*
213 * Mailbox API
214 */
215 static int
bcm_mbox_write(device_t dev,int chan,uint32_t data)216 bcm_mbox_write(device_t dev, int chan, uint32_t data)
217 {
218 int limit = 1000;
219 struct bcm_mbox_softc *sc = device_get_softc(dev);
220
221 dprintf("bcm_mbox_write: chan %d, data %08x\n", chan, data);
222 MBOX_LOCK(sc);
223 sc->have_message[chan] = 0;
224 while ((mbox_read_4(sc, REG_STATUS) & STATUS_FULL) && --limit)
225 DELAY(5);
226 if (limit == 0) {
227 printf("bcm_mbox_write: STATUS_FULL stuck");
228 MBOX_UNLOCK(sc);
229 return (EAGAIN);
230 }
231 mbox_write_4(sc, REG_WRITE, MBOX_MSG(chan, data));
232 MBOX_UNLOCK(sc);
233
234 return (0);
235 }
236
237 static int
bcm_mbox_read(device_t dev,int chan,uint32_t * data)238 bcm_mbox_read(device_t dev, int chan, uint32_t *data)
239 {
240 struct bcm_mbox_softc *sc = device_get_softc(dev);
241 int err, read_chan;
242
243 dprintf("bcm_mbox_read: chan %d\n", chan);
244
245 err = 0;
246 MBOX_LOCK(sc);
247 if (!cold) {
248 if (sc->have_message[chan] == 0) {
249 if (mtx_sleep(&sc->have_message[chan], &sc->lock, 0,
250 "mbox", 10*hz) != 0) {
251 device_printf(dev, "timeout waiting for message on chan %d\n", chan);
252 err = ETIMEDOUT;
253 }
254 }
255 } else {
256 do {
257 /* Wait for a message */
258 while ((mbox_read_4(sc, REG_STATUS) & STATUS_EMPTY))
259 ;
260 /* Read the message */
261 if (bcm_mbox_read_msg(sc, &read_chan) != 0) {
262 err = EINVAL;
263 goto out;
264 }
265 } while (read_chan != chan);
266 }
267 /*
268 * get data from intr handler, the same channel is never coming
269 * because of holding sc lock.
270 */
271 *data = MBOX_DATA(sc->msg[chan]);
272 sc->msg[chan] = 0;
273 sc->have_message[chan] = 0;
274 out:
275 MBOX_UNLOCK(sc);
276 dprintf("bcm_mbox_read: chan %d, data %08x\n", chan, *data);
277
278 return (err);
279 }
280
281 static device_method_t bcm_mbox_methods[] = {
282 DEVMETHOD(device_probe, bcm_mbox_probe),
283 DEVMETHOD(device_attach, bcm_mbox_attach),
284
285 DEVMETHOD(mbox_read, bcm_mbox_read),
286 DEVMETHOD(mbox_write, bcm_mbox_write),
287
288 DEVMETHOD_END
289 };
290
291 static driver_t bcm_mbox_driver = {
292 "mbox",
293 bcm_mbox_methods,
294 sizeof(struct bcm_mbox_softc),
295 };
296
297 EARLY_DRIVER_MODULE(mbox, simplebus, bcm_mbox_driver, 0, 0,
298 BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LAST);
299
300 static void
bcm2835_mbox_dma_cb(void * arg,bus_dma_segment_t * segs,int nseg,int err)301 bcm2835_mbox_dma_cb(void *arg, bus_dma_segment_t *segs, int nseg, int err)
302 {
303 bus_addr_t *addr;
304
305 if (err)
306 return;
307 addr = (bus_addr_t *)arg;
308 *addr = ARMC_TO_VCBUS(segs[0].ds_addr);
309 }
310
311 static void *
bcm2835_mbox_init_dma(device_t dev,size_t len,bus_dma_tag_t * tag,bus_dmamap_t * map,bus_addr_t * phys)312 bcm2835_mbox_init_dma(device_t dev, size_t len, bus_dma_tag_t *tag,
313 bus_dmamap_t *map, bus_addr_t *phys)
314 {
315 void *buf;
316 int err;
317
318 err = bus_dma_tag_create(bus_get_dma_tag(dev), 16, 0,
319 bcm283x_dmabus_peripheral_lowaddr(), BUS_SPACE_MAXADDR, NULL, NULL,
320 len, 1, len, 0, NULL, NULL, tag);
321 if (err != 0) {
322 device_printf(dev, "can't create DMA tag\n");
323 return (NULL);
324 }
325
326 err = bus_dmamem_alloc(*tag, &buf, 0, map);
327 if (err != 0) {
328 bus_dma_tag_destroy(*tag);
329 device_printf(dev, "can't allocate dmamem\n");
330 return (NULL);
331 }
332
333 err = bus_dmamap_load(*tag, *map, buf, len, bcm2835_mbox_dma_cb,
334 phys, 0);
335 if (err != 0) {
336 bus_dmamem_free(*tag, buf, *map);
337 bus_dma_tag_destroy(*tag);
338 device_printf(dev, "can't load DMA map\n");
339 return (NULL);
340 }
341
342 return (buf);
343 }
344
345 static int
bcm2835_mbox_err(device_t dev,bus_addr_t msg_phys,uint32_t resp_phys,struct bcm2835_mbox_hdr * msg,size_t len)346 bcm2835_mbox_err(device_t dev, bus_addr_t msg_phys, uint32_t resp_phys,
347 struct bcm2835_mbox_hdr *msg, size_t len)
348 {
349 int idx;
350 struct bcm2835_mbox_tag_hdr *tag;
351 uint8_t *last;
352
353 if ((uint32_t)msg_phys != resp_phys) {
354 device_printf(dev, "response channel mismatch\n");
355 return (EIO);
356 }
357 if (msg->code != BCM2835_MBOX_CODE_RESP_SUCCESS) {
358 device_printf(dev, "mbox response error\n");
359 return (EIO);
360 }
361
362 /* Loop until the end tag. */
363 tag = (struct bcm2835_mbox_tag_hdr *)(msg + 1);
364 last = (uint8_t *)msg + len;
365 for (idx = 0; tag->tag != 0; idx++) {
366 /*
367 * When setting the GPIO config or state the firmware doesn't
368 * set tag->val_len correctly.
369 */
370 if ((tag->tag == BCM2835_FIRMWARE_TAG_SET_GPIO_CONFIG ||
371 tag->tag == BCM2835_FIRMWARE_TAG_SET_GPIO_STATE) &&
372 tag->val_len == 0) {
373 tag->val_len = BCM2835_MBOX_TAG_VAL_LEN_RESPONSE |
374 tag->val_buf_size;
375 }
376 if ((tag->val_len & BCM2835_MBOX_TAG_VAL_LEN_RESPONSE) == 0) {
377 device_printf(dev, "tag %d response error\n", idx);
378 return (EIO);
379 }
380 /* Clear the response bit. */
381 tag->val_len &= ~BCM2835_MBOX_TAG_VAL_LEN_RESPONSE;
382
383 /* Next tag. */
384 tag = (struct bcm2835_mbox_tag_hdr *)((uint8_t *)tag +
385 sizeof(*tag) + tag->val_buf_size);
386
387 if ((uint8_t *)tag > last) {
388 device_printf(dev, "mbox buffer size error\n");
389 return (EIO);
390 }
391 }
392
393 return (0);
394 }
395
396 int
bcm2835_mbox_property(void * msg,size_t msg_size)397 bcm2835_mbox_property(void *msg, size_t msg_size)
398 {
399 struct bcm_mbox_softc *sc;
400 bus_dma_tag_t msg_tag;
401 bus_dmamap_t msg_map;
402 bus_addr_t msg_phys;
403 char *buf;
404 uint32_t reg;
405 device_t mbox;
406 int err;
407
408 /* get mbox device */
409 mbox = devclass_get_device(devclass_find("mbox"), 0);
410 if (mbox == NULL)
411 return (ENXIO);
412
413 sc = device_get_softc(mbox);
414 sx_xlock(&sc->property_chan_lock);
415
416 /* Allocate memory for the message */
417 buf = bcm2835_mbox_init_dma(mbox, msg_size, &msg_tag, &msg_map,
418 &msg_phys);
419 if (buf == NULL) {
420 err = ENOMEM;
421 goto out;
422 }
423
424 memcpy(buf, msg, msg_size);
425
426 bus_dmamap_sync(msg_tag, msg_map,
427 BUS_DMASYNC_PREWRITE);
428
429 MBOX_WRITE(mbox, BCM2835_MBOX_CHAN_PROP, (uint32_t)msg_phys);
430 MBOX_READ(mbox, BCM2835_MBOX_CHAN_PROP, ®);
431
432 bus_dmamap_sync(msg_tag, msg_map,
433 BUS_DMASYNC_PREREAD);
434
435 memcpy(msg, buf, msg_size);
436
437 err = bcm2835_mbox_err(mbox, msg_phys, reg,
438 (struct bcm2835_mbox_hdr *)msg, msg_size);
439
440 bus_dmamap_unload(msg_tag, msg_map);
441 bus_dmamem_free(msg_tag, buf, msg_map);
442 bus_dma_tag_destroy(msg_tag);
443 out:
444 sx_xunlock(&sc->property_chan_lock);
445 return (err);
446 }
447
448 int
bcm2835_mbox_set_power_state(uint32_t device_id,boolean_t on)449 bcm2835_mbox_set_power_state(uint32_t device_id, boolean_t on)
450 {
451 struct msg_set_power_state msg;
452 int err;
453
454 memset(&msg, 0, sizeof(msg));
455 msg.hdr.buf_size = sizeof(msg);
456 msg.hdr.code = BCM2835_MBOX_CODE_REQ;
457 msg.tag_hdr.tag = BCM2835_MBOX_TAG_SET_POWER_STATE;
458 msg.tag_hdr.val_buf_size = sizeof(msg.body);
459 msg.tag_hdr.val_len = sizeof(msg.body.req);
460 msg.body.req.device_id = device_id;
461 msg.body.req.state = (on ? BCM2835_MBOX_POWER_ON : 0) |
462 BCM2835_MBOX_POWER_WAIT;
463 msg.end_tag = 0;
464
465 err = bcm2835_mbox_property(&msg, sizeof(msg));
466
467 return (err);
468 }
469
470 int
bcm2835_mbox_notify_xhci_reset(uint32_t pci_dev_addr)471 bcm2835_mbox_notify_xhci_reset(uint32_t pci_dev_addr)
472 {
473 struct msg_notify_xhci_reset msg;
474 int err;
475
476 memset(&msg, 0, sizeof(msg));
477 msg.hdr.buf_size = sizeof(msg);
478 msg.hdr.code = BCM2835_MBOX_CODE_REQ;
479 msg.tag_hdr.tag = BCM2835_MBOX_TAG_NOTIFY_XHCI_RESET;
480 msg.tag_hdr.val_buf_size = sizeof(msg.body);
481 msg.tag_hdr.val_len = sizeof(msg.body.req);
482 msg.body.req.pci_device_addr = pci_dev_addr;
483 msg.end_tag = 0;
484
485 err = bcm2835_mbox_property(&msg, sizeof(msg));
486
487 return (err);
488 }
489
490 int
bcm2835_mbox_get_clock_rate(uint32_t clock_id,uint32_t * hz)491 bcm2835_mbox_get_clock_rate(uint32_t clock_id, uint32_t *hz)
492 {
493 struct msg_get_clock_rate msg;
494 int err;
495
496 memset(&msg, 0, sizeof(msg));
497 msg.hdr.buf_size = sizeof(msg);
498 msg.hdr.code = BCM2835_MBOX_CODE_REQ;
499 msg.tag_hdr.tag = BCM2835_MBOX_TAG_GET_CLOCK_RATE;
500 msg.tag_hdr.val_buf_size = sizeof(msg.body);
501 msg.tag_hdr.val_len = sizeof(msg.body.req);
502 msg.body.req.clock_id = clock_id;
503 msg.end_tag = 0;
504
505 err = bcm2835_mbox_property(&msg, sizeof(msg));
506 *hz = msg.body.resp.rate_hz;
507
508 return (err);
509 }
510
511 int
bcm2835_mbox_fb_get_w_h(struct bcm2835_fb_config * fb)512 bcm2835_mbox_fb_get_w_h(struct bcm2835_fb_config *fb)
513 {
514 int err;
515 struct msg_fb_get_w_h msg;
516
517 memset(&msg, 0, sizeof(msg));
518 msg.hdr.buf_size = sizeof(msg);
519 msg.hdr.code = BCM2835_MBOX_CODE_REQ;
520 BCM2835_MBOX_INIT_TAG(&msg.physical_w_h, GET_PHYSICAL_W_H);
521 msg.physical_w_h.tag_hdr.val_len = 0;
522 msg.end_tag = 0;
523
524 err = bcm2835_mbox_property(&msg, sizeof(msg));
525 if (err == 0) {
526 fb->xres = msg.physical_w_h.body.resp.width;
527 fb->yres = msg.physical_w_h.body.resp.height;
528 }
529
530 return (err);
531 }
532
533 int
bcm2835_mbox_fb_get_bpp(struct bcm2835_fb_config * fb)534 bcm2835_mbox_fb_get_bpp(struct bcm2835_fb_config *fb)
535 {
536 int err;
537 struct msg_fb_get_bpp msg;
538
539 memset(&msg, 0, sizeof(msg));
540 msg.hdr.buf_size = sizeof(msg);
541 msg.hdr.code = BCM2835_MBOX_CODE_REQ;
542 BCM2835_MBOX_INIT_TAG(&msg.bpp, GET_DEPTH);
543 msg.bpp.tag_hdr.val_len = 0;
544 msg.end_tag = 0;
545
546 err = bcm2835_mbox_property(&msg, sizeof(msg));
547 if (err == 0)
548 fb->bpp = msg.bpp.body.resp.bpp;
549
550 return (err);
551 }
552
553 int
bcm2835_mbox_fb_init(struct bcm2835_fb_config * fb)554 bcm2835_mbox_fb_init(struct bcm2835_fb_config *fb)
555 {
556 int err;
557 struct msg_fb_setup msg;
558
559 memset(&msg, 0, sizeof(msg));
560 msg.hdr.buf_size = sizeof(msg);
561 msg.hdr.code = BCM2835_MBOX_CODE_REQ;
562 BCM2835_MBOX_INIT_TAG(&msg.physical_w_h, SET_PHYSICAL_W_H);
563 msg.physical_w_h.body.req.width = fb->xres;
564 msg.physical_w_h.body.req.height = fb->yres;
565 BCM2835_MBOX_INIT_TAG(&msg.virtual_w_h, SET_VIRTUAL_W_H);
566 msg.virtual_w_h.body.req.width = fb->vxres;
567 msg.virtual_w_h.body.req.height = fb->vyres;
568 BCM2835_MBOX_INIT_TAG(&msg.offset, SET_VIRTUAL_OFFSET);
569 msg.offset.body.req.x = fb->xoffset;
570 msg.offset.body.req.y = fb->yoffset;
571 BCM2835_MBOX_INIT_TAG(&msg.depth, SET_DEPTH);
572 msg.depth.body.req.bpp = fb->bpp;
573 BCM2835_MBOX_INIT_TAG(&msg.alpha, SET_ALPHA_MODE);
574 msg.alpha.body.req.alpha = BCM2835_MBOX_ALPHA_MODE_IGNORED;
575 BCM2835_MBOX_INIT_TAG(&msg.buffer, ALLOCATE_BUFFER);
576 msg.buffer.body.req.alignment = PAGE_SIZE;
577 BCM2835_MBOX_INIT_TAG(&msg.pitch, GET_PITCH);
578 msg.end_tag = 0;
579
580 err = bcm2835_mbox_property(&msg, sizeof(msg));
581 if (err == 0) {
582 fb->xres = msg.physical_w_h.body.resp.width;
583 fb->yres = msg.physical_w_h.body.resp.height;
584 fb->vxres = msg.virtual_w_h.body.resp.width;
585 fb->vyres = msg.virtual_w_h.body.resp.height;
586 fb->xoffset = msg.offset.body.resp.x;
587 fb->yoffset = msg.offset.body.resp.y;
588 fb->pitch = msg.pitch.body.resp.pitch;
589 fb->base = VCBUS_TO_ARMC(msg.buffer.body.resp.fb_address);
590 fb->size = msg.buffer.body.resp.fb_size;
591 }
592
593 return (err);
594 }
595