2 * Copyright (C) 2016 Broadcom
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
10 * DOC: VC4 firmware KMS module.
12 * As a hack to get us from the current closed source driver world
13 * toward a totally open stack, implement KMS on top of the Raspberry
14 * Pi's firmware display stack.
17 #include "drm/drm_atomic_helper.h"
18 #include "drm/drm_plane_helper.h"
19 #include "drm/drm_crtc_helper.h"
20 #include "linux/clk.h"
21 #include "linux/debugfs.h"
22 #include "drm/drm_fb_cma_helper.h"
23 #include "linux/component.h"
24 #include "linux/of_device.h"
27 #include <soc/bcm2835/raspberrypi-firmware.h>
29 /* The firmware delivers a vblank interrupt to us through the SMI
30 * hardware, which has only this one register.
33 #define SMICS_INTERRUPTS (BIT(9) | BIT(10) | BIT(11))
37 struct drm_encoder
*encoder
;
38 struct drm_connector
*connector
;
41 struct drm_pending_vblank_event
*event
;
44 static inline struct vc4_crtc
*to_vc4_crtc(struct drm_crtc
*crtc
)
46 return container_of(crtc
, struct vc4_crtc
, base
);
49 struct vc4_fkms_encoder
{
50 struct drm_encoder base
;
53 static inline struct vc4_fkms_encoder
*
54 to_vc4_fkms_encoder(struct drm_encoder
*encoder
)
56 return container_of(encoder
, struct vc4_fkms_encoder
, base
);
59 /* VC4 FKMS connector KMS struct */
60 struct vc4_fkms_connector
{
61 struct drm_connector base
;
63 /* Since the connector is attached to just the one encoder,
64 * this is the reference to it so we can do the best_encoder()
67 struct drm_encoder
*encoder
;
70 static inline struct vc4_fkms_connector
*
71 to_vc4_fkms_connector(struct drm_connector
*connector
)
73 return container_of(connector
, struct vc4_fkms_connector
, base
);
76 /* Firmware's structure for making an FB mbox call. */
78 u32 xres
, yres
, xres_virtual
, yres_virtual
;
86 struct vc4_fkms_plane
{
87 struct drm_plane base
;
88 struct fbinfo_s
*fbinfo
;
89 dma_addr_t fbinfo_bus_addr
;
93 static inline struct vc4_fkms_plane
*to_vc4_fkms_plane(struct drm_plane
*plane
)
95 return (struct vc4_fkms_plane
*)plane
;
98 /* Turns the display on/off. */
99 static int vc4_plane_set_primary_blank(struct drm_plane
*plane
, bool blank
)
101 struct vc4_dev
*vc4
= to_vc4_dev(plane
->dev
);
105 DRM_DEBUG_ATOMIC("[PLANE:%d:%s] primary plane %s",
106 plane
->base
.id
, plane
->name
,
107 blank
? "blank" : "unblank");
109 return rpi_firmware_property(vc4
->firmware
,
110 RPI_FIRMWARE_FRAMEBUFFER_BLANK
,
111 &packet
, sizeof(packet
));
114 static void vc4_primary_plane_atomic_update(struct drm_plane
*plane
,
115 struct drm_plane_state
*old_state
)
117 struct vc4_dev
*vc4
= to_vc4_dev(plane
->dev
);
118 struct vc4_fkms_plane
*vc4_plane
= to_vc4_fkms_plane(plane
);
119 struct drm_plane_state
*state
= plane
->state
;
120 struct drm_framebuffer
*fb
= state
->fb
;
121 struct drm_gem_cma_object
*bo
= drm_fb_cma_get_gem_obj(fb
, 0);
122 volatile struct fbinfo_s
*fbinfo
= vc4_plane
->fbinfo
;
126 vc4_plane_set_primary_blank(plane
, false);
128 fbinfo
->xres
= state
->crtc_w
;
129 fbinfo
->yres
= state
->crtc_h
;
130 fbinfo
->xres_virtual
= state
->crtc_w
;
131 fbinfo
->yres_virtual
= state
->crtc_h
;
133 fbinfo
->xoffset
= state
->crtc_x
;
134 fbinfo
->yoffset
= state
->crtc_y
;
135 fbinfo
->base
= bo
->paddr
+ fb
->offsets
[0];
136 fbinfo
->pitch
= fb
->pitches
[0];
137 /* A bug in the firmware makes it so that if the fb->base is
138 * set to nonzero, the configured pitch gets overwritten with
139 * the previous pitch. So, to get the configured pitch
140 * recomputed, we have to make it allocate itself a new buffer
141 * in VC memory, first.
143 if (vc4_plane
->pitch
!= fb
->pitches
[0]) {
144 u32 saved_base
= fbinfo
->base
;
147 ret
= rpi_firmware_transaction(vc4
->firmware
,
148 RPI_FIRMWARE_CHAN_FB
,
149 vc4_plane
->fbinfo_bus_addr
);
150 fbinfo
->base
= saved_base
;
152 vc4_plane
->pitch
= fbinfo
->pitch
;
153 WARN_ON_ONCE(vc4_plane
->pitch
!= fb
->pitches
[0]);
156 DRM_DEBUG_ATOMIC("[PLANE:%d:%s] primary update %dx%d@%d +%d,%d 0x%08x/%d\n",
157 plane
->base
.id
, plane
->name
,
163 bo
->paddr
+ fb
->offsets
[0],
166 ret
= rpi_firmware_transaction(vc4
->firmware
,
167 RPI_FIRMWARE_CHAN_FB
,
168 vc4_plane
->fbinfo_bus_addr
);
169 WARN_ON_ONCE(fbinfo
->pitch
!= fb
->pitches
[0]);
170 WARN_ON_ONCE(fbinfo
->base
!= bo
->paddr
+ fb
->offsets
[0]);
173 static void vc4_primary_plane_atomic_disable(struct drm_plane
*plane
,
174 struct drm_plane_state
*old_state
)
176 vc4_plane_set_primary_blank(plane
, true);
179 static void vc4_cursor_plane_atomic_update(struct drm_plane
*plane
,
180 struct drm_plane_state
*old_state
)
182 struct vc4_dev
*vc4
= to_vc4_dev(plane
->dev
);
183 struct drm_plane_state
*state
= plane
->state
;
184 struct drm_framebuffer
*fb
= state
->fb
;
185 struct drm_gem_cma_object
*bo
= drm_fb_cma_get_gem_obj(fb
, 0);
187 u32 packet_state
[] = { true, state
->crtc_x
, state
->crtc_y
, 0 };
188 u32 packet_info
[] = { state
->crtc_w
, state
->crtc_h
,
190 bo
->paddr
+ fb
->offsets
[0],
191 0, 0, /* hotx, hoty */};
192 WARN_ON_ONCE(fb
->pitches
[0] != state
->crtc_w
* 4);
194 DRM_DEBUG_ATOMIC("[PLANE:%d:%s] update %dx%d cursor at %d,%d (0x%08x/%d)",
195 plane
->base
.id
, plane
->name
,
200 bo
->paddr
+ fb
->offsets
[0],
203 ret
= rpi_firmware_property(vc4
->firmware
,
204 RPI_FIRMWARE_SET_CURSOR_STATE
,
206 sizeof(packet_state
));
207 if (ret
|| packet_state
[0] != 0)
208 DRM_ERROR("Failed to set cursor state: 0x%08x\n", packet_state
[0]);
210 ret
= rpi_firmware_property(vc4
->firmware
,
211 RPI_FIRMWARE_SET_CURSOR_INFO
,
213 sizeof(packet_info
));
214 if (ret
|| packet_info
[0] != 0)
215 DRM_ERROR("Failed to set cursor info: 0x%08x\n", packet_info
[0]);
218 static void vc4_cursor_plane_atomic_disable(struct drm_plane
*plane
,
219 struct drm_plane_state
*old_state
)
221 struct vc4_dev
*vc4
= to_vc4_dev(plane
->dev
);
222 u32 packet_state
[] = { false, 0, 0, 0 };
225 DRM_DEBUG_ATOMIC("[PLANE:%d:%s] disabling cursor", plane
->base
.id
, plane
->name
);
227 ret
= rpi_firmware_property(vc4
->firmware
,
228 RPI_FIRMWARE_SET_CURSOR_STATE
,
230 sizeof(packet_state
));
231 if (ret
|| packet_state
[0] != 0)
232 DRM_ERROR("Failed to set cursor state: 0x%08x\n", packet_state
[0]);
235 static int vc4_plane_atomic_check(struct drm_plane
*plane
,
236 struct drm_plane_state
*state
)
241 static void vc4_plane_destroy(struct drm_plane
*plane
)
243 drm_plane_helper_disable(plane
);
244 drm_plane_cleanup(plane
);
247 static const struct drm_plane_funcs vc4_plane_funcs
= {
248 .update_plane
= drm_atomic_helper_update_plane
,
249 .disable_plane
= drm_atomic_helper_disable_plane
,
250 .destroy
= vc4_plane_destroy
,
251 .set_property
= NULL
,
252 .reset
= drm_atomic_helper_plane_reset
,
253 .atomic_duplicate_state
= drm_atomic_helper_plane_duplicate_state
,
254 .atomic_destroy_state
= drm_atomic_helper_plane_destroy_state
,
257 static const struct drm_plane_helper_funcs vc4_primary_plane_helper_funcs
= {
260 .atomic_check
= vc4_plane_atomic_check
,
261 .atomic_update
= vc4_primary_plane_atomic_update
,
262 .atomic_disable
= vc4_primary_plane_atomic_disable
,
265 static const struct drm_plane_helper_funcs vc4_cursor_plane_helper_funcs
= {
268 .atomic_check
= vc4_plane_atomic_check
,
269 .atomic_update
= vc4_cursor_plane_atomic_update
,
270 .atomic_disable
= vc4_cursor_plane_atomic_disable
,
273 static struct drm_plane
*vc4_fkms_plane_init(struct drm_device
*dev
,
274 enum drm_plane_type type
)
276 struct drm_plane
*plane
= NULL
;
277 struct vc4_fkms_plane
*vc4_plane
;
278 u32 xrgb8888
= DRM_FORMAT_XRGB8888
;
279 u32 argb8888
= DRM_FORMAT_ARGB8888
;
281 bool primary
= (type
== DRM_PLANE_TYPE_PRIMARY
);
283 vc4_plane
= devm_kzalloc(dev
->dev
, sizeof(*vc4_plane
),
290 plane
= &vc4_plane
->base
;
291 ret
= drm_universal_plane_init(dev
, plane
, 0xff,
293 primary
? &xrgb8888
: &argb8888
, 1,
294 type
, primary
? "primary" : "cursor");
296 if (type
== DRM_PLANE_TYPE_PRIMARY
) {
298 dma_alloc_coherent(dev
->dev
,
299 sizeof(*vc4_plane
->fbinfo
),
300 &vc4_plane
->fbinfo_bus_addr
,
302 memset(vc4_plane
->fbinfo
, 0, sizeof(*vc4_plane
->fbinfo
));
304 drm_plane_helper_add(plane
, &vc4_primary_plane_helper_funcs
);
306 drm_plane_helper_add(plane
, &vc4_cursor_plane_helper_funcs
);
312 vc4_plane_destroy(plane
);
317 static void vc4_crtc_mode_set_nofb(struct drm_crtc
*crtc
)
319 /* Everyting is handled in the planes. */
322 static void vc4_crtc_disable(struct drm_crtc
*crtc
)
326 static void vc4_crtc_enable(struct drm_crtc
*crtc
)
330 static int vc4_crtc_atomic_check(struct drm_crtc
*crtc
,
331 struct drm_crtc_state
*state
)
336 static void vc4_crtc_atomic_flush(struct drm_crtc
*crtc
,
337 struct drm_crtc_state
*old_state
)
339 struct vc4_crtc
*vc4_crtc
= to_vc4_crtc(crtc
);
340 struct drm_device
*dev
= crtc
->dev
;
342 if (crtc
->state
->event
) {
345 crtc
->state
->event
->pipe
= drm_crtc_index(crtc
);
347 WARN_ON(drm_crtc_vblank_get(crtc
) != 0);
349 spin_lock_irqsave(&dev
->event_lock
, flags
);
350 vc4_crtc
->event
= crtc
->state
->event
;
351 crtc
->state
->event
= NULL
;
352 spin_unlock_irqrestore(&dev
->event_lock
, flags
);
356 static void vc4_crtc_handle_page_flip(struct vc4_crtc
*vc4_crtc
)
358 struct drm_crtc
*crtc
= &vc4_crtc
->base
;
359 struct drm_device
*dev
= crtc
->dev
;
362 spin_lock_irqsave(&dev
->event_lock
, flags
);
363 if (vc4_crtc
->event
) {
364 drm_crtc_send_vblank_event(crtc
, vc4_crtc
->event
);
365 vc4_crtc
->event
= NULL
;
366 drm_crtc_vblank_put(crtc
);
368 spin_unlock_irqrestore(&dev
->event_lock
, flags
);
371 static irqreturn_t
vc4_crtc_irq_handler(int irq
, void *data
)
373 struct vc4_crtc
*vc4_crtc
= data
;
374 u32 stat
= readl(vc4_crtc
->regs
+ SMICS
);
375 irqreturn_t ret
= IRQ_NONE
;
377 if (stat
& SMICS_INTERRUPTS
) {
378 writel(0, vc4_crtc
->regs
+ SMICS
);
379 drm_crtc_handle_vblank(&vc4_crtc
->base
);
380 vc4_crtc_handle_page_flip(vc4_crtc
);
387 static int vc4_page_flip(struct drm_crtc
*crtc
,
388 struct drm_framebuffer
*fb
,
389 struct drm_pending_vblank_event
*event
,
390 uint32_t flags
, struct drm_modeset_acquire_ctx
*ctx
)
392 if (flags
& DRM_MODE_PAGE_FLIP_ASYNC
) {
393 DRM_ERROR("Async flips aren't allowed\n");
397 return drm_atomic_helper_page_flip(crtc
, fb
, event
, flags
, ctx
);
400 static const struct drm_crtc_funcs vc4_crtc_funcs
= {
401 .set_config
= drm_atomic_helper_set_config
,
402 .destroy
= drm_crtc_cleanup
,
403 .page_flip
= vc4_page_flip
,
404 .set_property
= NULL
,
405 .cursor_set
= NULL
, /* handled by drm_mode_cursor_universal */
406 .cursor_move
= NULL
, /* handled by drm_mode_cursor_universal */
407 .reset
= drm_atomic_helper_crtc_reset
,
408 .atomic_duplicate_state
= drm_atomic_helper_crtc_duplicate_state
,
409 .atomic_destroy_state
= drm_atomic_helper_crtc_destroy_state
,
412 static const struct drm_crtc_helper_funcs vc4_crtc_helper_funcs
= {
413 .mode_set_nofb
= vc4_crtc_mode_set_nofb
,
414 .disable
= vc4_crtc_disable
,
415 .enable
= vc4_crtc_enable
,
416 .atomic_check
= vc4_crtc_atomic_check
,
417 .atomic_flush
= vc4_crtc_atomic_flush
,
420 /* Frees the page flip event when the DRM device is closed with the
421 * event still outstanding.
423 void vc4_fkms_cancel_page_flip(struct drm_crtc
*crtc
, struct drm_file
*file
)
425 struct vc4_crtc
*vc4_crtc
= to_vc4_crtc(crtc
);
426 struct drm_device
*dev
= crtc
->dev
;
429 spin_lock_irqsave(&dev
->event_lock
, flags
);
431 if (vc4_crtc
->event
&& vc4_crtc
->event
->base
.file_priv
== file
) {
432 kfree(&vc4_crtc
->event
->base
);
433 drm_crtc_vblank_put(crtc
);
434 vc4_crtc
->event
= NULL
;
437 spin_unlock_irqrestore(&dev
->event_lock
, flags
);
440 static const struct of_device_id vc4_firmware_kms_dt_match
[] = {
441 { .compatible
= "raspberrypi,rpi-firmware-kms" },
445 static enum drm_connector_status
446 vc4_fkms_connector_detect(struct drm_connector
*connector
, bool force
)
448 return connector_status_connected
;
451 static int vc4_fkms_connector_get_modes(struct drm_connector
*connector
)
453 struct drm_device
*dev
= connector
->dev
;
454 struct vc4_dev
*vc4
= to_vc4_dev(dev
);
457 struct drm_display_mode
*mode
;
459 ret
= rpi_firmware_property(vc4
->firmware
,
460 RPI_FIRMWARE_FRAMEBUFFER_GET_PHYSICAL_WIDTH_HEIGHT
,
463 DRM_ERROR("Failed to get screen size: %d (0x%08x 0x%08x)\n",
468 mode
= drm_cvt_mode(dev
, wh
[0], wh
[1], 60 /* vrefresh */,
470 drm_mode_probed_add(connector
, mode
);
475 static struct drm_encoder
*
476 vc4_fkms_connector_best_encoder(struct drm_connector
*connector
)
478 struct vc4_fkms_connector
*fkms_connector
=
479 to_vc4_fkms_connector(connector
);
480 return fkms_connector
->encoder
;
483 static void vc4_fkms_connector_destroy(struct drm_connector
*connector
)
485 drm_connector_unregister(connector
);
486 drm_connector_cleanup(connector
);
489 static const struct drm_connector_funcs vc4_fkms_connector_funcs
= {
490 .dpms
= drm_atomic_helper_connector_dpms
,
491 .detect
= vc4_fkms_connector_detect
,
492 .fill_modes
= drm_helper_probe_single_connector_modes
,
493 .destroy
= vc4_fkms_connector_destroy
,
494 .reset
= drm_atomic_helper_connector_reset
,
495 .atomic_duplicate_state
= drm_atomic_helper_connector_duplicate_state
,
496 .atomic_destroy_state
= drm_atomic_helper_connector_destroy_state
,
499 static const struct drm_connector_helper_funcs vc4_fkms_connector_helper_funcs
= {
500 .get_modes
= vc4_fkms_connector_get_modes
,
501 .best_encoder
= vc4_fkms_connector_best_encoder
,
504 static struct drm_connector
*vc4_fkms_connector_init(struct drm_device
*dev
,
505 struct drm_encoder
*encoder
)
507 struct drm_connector
*connector
= NULL
;
508 struct vc4_fkms_connector
*fkms_connector
;
511 fkms_connector
= devm_kzalloc(dev
->dev
, sizeof(*fkms_connector
),
513 if (!fkms_connector
) {
517 connector
= &fkms_connector
->base
;
519 fkms_connector
->encoder
= encoder
;
521 drm_connector_init(dev
, connector
, &vc4_fkms_connector_funcs
,
522 DRM_MODE_CONNECTOR_HDMIA
);
523 drm_connector_helper_add(connector
, &vc4_fkms_connector_helper_funcs
);
525 connector
->polled
= (DRM_CONNECTOR_POLL_CONNECT
|
526 DRM_CONNECTOR_POLL_DISCONNECT
);
528 connector
->interlace_allowed
= 0;
529 connector
->doublescan_allowed
= 0;
531 drm_mode_connector_attach_encoder(connector
, encoder
);
537 vc4_fkms_connector_destroy(connector
);
542 static void vc4_fkms_encoder_destroy(struct drm_encoder
*encoder
)
544 drm_encoder_cleanup(encoder
);
547 static const struct drm_encoder_funcs vc4_fkms_encoder_funcs
= {
548 .destroy
= vc4_fkms_encoder_destroy
,
551 static void vc4_fkms_encoder_enable(struct drm_encoder
*encoder
)
555 static void vc4_fkms_encoder_disable(struct drm_encoder
*encoder
)
559 static const struct drm_encoder_helper_funcs vc4_fkms_encoder_helper_funcs
= {
560 .enable
= vc4_fkms_encoder_enable
,
561 .disable
= vc4_fkms_encoder_disable
,
564 static int vc4_fkms_bind(struct device
*dev
, struct device
*master
, void *data
)
566 struct platform_device
*pdev
= to_platform_device(dev
);
567 struct drm_device
*drm
= dev_get_drvdata(master
);
568 struct vc4_dev
*vc4
= to_vc4_dev(drm
);
569 struct vc4_crtc
*vc4_crtc
;
570 struct vc4_fkms_encoder
*vc4_encoder
;
571 struct drm_crtc
*crtc
;
572 struct drm_plane
*primary_plane
, *cursor_plane
, *destroy_plane
, *temp
;
573 struct device_node
*firmware_node
;
576 vc4
->firmware_kms
= true;
578 vc4_crtc
= devm_kzalloc(dev
, sizeof(*vc4_crtc
), GFP_KERNEL
);
581 crtc
= &vc4_crtc
->base
;
583 firmware_node
= of_parse_phandle(dev
->of_node
, "brcm,firmware", 0);
584 vc4
->firmware
= rpi_firmware_get(firmware_node
);
585 if (!vc4
->firmware
) {
586 DRM_DEBUG("Failed to get Raspberry Pi firmware reference.\n");
587 return -EPROBE_DEFER
;
589 of_node_put(firmware_node
);
591 /* Map the SMI interrupt reg */
592 vc4_crtc
->regs
= vc4_ioremap_regs(pdev
, 0);
593 if (IS_ERR(vc4_crtc
->regs
))
594 return PTR_ERR(vc4_crtc
->regs
);
596 /* For now, we create just the primary and the legacy cursor
597 * planes. We should be able to stack more planes on easily,
598 * but to do that we would need to compute the bandwidth
599 * requirement of the plane configuration, and reject ones
600 * that will take too much.
602 primary_plane
= vc4_fkms_plane_init(drm
, DRM_PLANE_TYPE_PRIMARY
);
603 if (IS_ERR(primary_plane
)) {
604 dev_err(dev
, "failed to construct primary plane\n");
605 ret
= PTR_ERR(primary_plane
);
609 cursor_plane
= vc4_fkms_plane_init(drm
, DRM_PLANE_TYPE_CURSOR
);
610 if (IS_ERR(cursor_plane
)) {
611 dev_err(dev
, "failed to construct cursor plane\n");
612 ret
= PTR_ERR(cursor_plane
);
616 drm_crtc_init_with_planes(drm
, crtc
, primary_plane
, cursor_plane
,
617 &vc4_crtc_funcs
, NULL
);
618 drm_crtc_helper_add(crtc
, &vc4_crtc_helper_funcs
);
619 primary_plane
->crtc
= crtc
;
620 cursor_plane
->crtc
= crtc
;
622 vc4_encoder
= devm_kzalloc(dev
, sizeof(*vc4_encoder
), GFP_KERNEL
);
625 vc4_crtc
->encoder
= &vc4_encoder
->base
;
626 vc4_encoder
->base
.possible_crtcs
|= drm_crtc_mask(crtc
) ;
627 drm_encoder_init(drm
, &vc4_encoder
->base
, &vc4_fkms_encoder_funcs
,
628 DRM_MODE_ENCODER_TMDS
, NULL
);
629 drm_encoder_helper_add(&vc4_encoder
->base
,
630 &vc4_fkms_encoder_helper_funcs
);
632 vc4_crtc
->connector
= vc4_fkms_connector_init(drm
, &vc4_encoder
->base
);
633 if (IS_ERR(vc4_crtc
->connector
)) {
634 ret
= PTR_ERR(vc4_crtc
->connector
);
635 goto err_destroy_encoder
;
638 writel(0, vc4_crtc
->regs
+ SMICS
);
639 ret
= devm_request_irq(dev
, platform_get_irq(pdev
, 0),
640 vc4_crtc_irq_handler
, 0, "vc4 firmware kms",
643 goto err_destroy_connector
;
645 platform_set_drvdata(pdev
, vc4_crtc
);
649 err_destroy_connector
:
650 vc4_fkms_connector_destroy(vc4_crtc
->connector
);
652 vc4_fkms_encoder_destroy(vc4_crtc
->encoder
);
653 list_for_each_entry_safe(destroy_plane
, temp
,
654 &drm
->mode_config
.plane_list
, head
) {
655 if (destroy_plane
->possible_crtcs
== 1 << drm_crtc_index(crtc
))
656 destroy_plane
->funcs
->destroy(destroy_plane
);
662 static void vc4_fkms_unbind(struct device
*dev
, struct device
*master
,
665 struct platform_device
*pdev
= to_platform_device(dev
);
666 struct vc4_crtc
*vc4_crtc
= dev_get_drvdata(dev
);
668 vc4_fkms_connector_destroy(vc4_crtc
->connector
);
669 vc4_fkms_encoder_destroy(vc4_crtc
->encoder
);
670 drm_crtc_cleanup(&vc4_crtc
->base
);
672 platform_set_drvdata(pdev
, NULL
);
675 static const struct component_ops vc4_fkms_ops
= {
676 .bind
= vc4_fkms_bind
,
677 .unbind
= vc4_fkms_unbind
,
680 static int vc4_fkms_probe(struct platform_device
*pdev
)
682 return component_add(&pdev
->dev
, &vc4_fkms_ops
);
685 static int vc4_fkms_remove(struct platform_device
*pdev
)
687 component_del(&pdev
->dev
, &vc4_fkms_ops
);
691 struct platform_driver vc4_firmware_kms_driver
= {
692 .probe
= vc4_fkms_probe
,
693 .remove
= vc4_fkms_remove
,
695 .name
= "vc4_firmware_kms",
696 .of_match_table
= vc4_firmware_kms_dt_match
,