]> git.proxmox.com Git - mirror_ubuntu-zesty-kernel.git/blame - drivers/gpu/drm/vc4/vc4_firmware_kms.c
drm/vc4: Name the primary and cursor planes in fkms.
[mirror_ubuntu-zesty-kernel.git] / drivers / gpu / drm / vc4 / vc4_firmware_kms.c
CommitLineData
30c897dd
EA
1/*
2 * Copyright (C) 2016 Broadcom
3 *
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.
7 */
8
9/**
10 * DOC: VC4 firmware KMS module.
11 *
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.
15 */
16
17#include "drm_atomic.h"
18#include "drm_atomic_helper.h"
19#include "drm_plane_helper.h"
20#include "drm_crtc_helper.h"
21#include "linux/clk.h"
22#include "linux/debugfs.h"
23#include "drm_fb_cma_helper.h"
24#include "linux/component.h"
25#include "linux/of_device.h"
26#include "vc4_drv.h"
27#include "vc4_regs.h"
28#include <soc/bcm2835/raspberrypi-firmware.h>
29
30/* The firmware delivers a vblank interrupt to us through the SMI
31 * hardware, which has only this one register.
32 */
33#define SMICS 0x0
34#define SMICS_INTERRUPTS (BIT(9) | BIT(10) | BIT(11))
35
36struct vc4_crtc {
37 struct drm_crtc base;
38 struct drm_encoder *encoder;
39 struct drm_connector *connector;
40 void __iomem *regs;
41
42 struct drm_pending_vblank_event *event;
43};
44
45static inline struct vc4_crtc *to_vc4_crtc(struct drm_crtc *crtc)
46{
47 return container_of(crtc, struct vc4_crtc, base);
48}
49
50struct vc4_fkms_encoder {
51 struct drm_encoder base;
52};
53
54static inline struct vc4_fkms_encoder *
55to_vc4_fkms_encoder(struct drm_encoder *encoder)
56{
57 return container_of(encoder, struct vc4_fkms_encoder, base);
58}
59
60/* VC4 FKMS connector KMS struct */
61struct vc4_fkms_connector {
62 struct drm_connector base;
63
64 /* Since the connector is attached to just the one encoder,
65 * this is the reference to it so we can do the best_encoder()
66 * hook.
67 */
68 struct drm_encoder *encoder;
69};
70
71static inline struct vc4_fkms_connector *
72to_vc4_fkms_connector(struct drm_connector *connector)
73{
74 return container_of(connector, struct vc4_fkms_connector, base);
75}
76
77/* Firmware's structure for making an FB mbox call. */
78struct fbinfo_s {
79 u32 xres, yres, xres_virtual, yres_virtual;
80 u32 pitch, bpp;
81 u32 xoffset, yoffset;
82 u32 base;
83 u32 screen_size;
84 u16 cmap[256];
85};
86
87struct vc4_fkms_plane {
88 struct drm_plane base;
89 struct fbinfo_s *fbinfo;
90 dma_addr_t fbinfo_bus_addr;
91 u32 pitch;
92};
93
94static inline struct vc4_fkms_plane *to_vc4_fkms_plane(struct drm_plane *plane)
95{
96 return (struct vc4_fkms_plane *)plane;
97}
98
99/* Turns the display on/off. */
100static int vc4_plane_set_primary_blank(struct drm_plane *plane, bool blank)
101{
102 struct vc4_dev *vc4 = to_vc4_dev(plane->dev);
103
104 u32 packet = blank;
105 return rpi_firmware_property(vc4->firmware,
106 RPI_FIRMWARE_FRAMEBUFFER_BLANK,
107 &packet, sizeof(packet));
108}
109
110static void vc4_primary_plane_atomic_update(struct drm_plane *plane,
111 struct drm_plane_state *old_state)
112{
113 struct vc4_dev *vc4 = to_vc4_dev(plane->dev);
114 struct vc4_fkms_plane *vc4_plane = to_vc4_fkms_plane(plane);
115 struct drm_plane_state *state = plane->state;
116 struct drm_framebuffer *fb = state->fb;
117 struct drm_gem_cma_object *bo = drm_fb_cma_get_gem_obj(fb, 0);
118 volatile struct fbinfo_s *fbinfo = vc4_plane->fbinfo;
119 u32 bpp = 32;
120 int ret;
121
122 vc4_plane_set_primary_blank(plane, false);
123
124 fbinfo->xres = state->crtc_w;
125 fbinfo->yres = state->crtc_h;
126 fbinfo->xres_virtual = state->crtc_w;
127 fbinfo->yres_virtual = state->crtc_h;
128 fbinfo->bpp = bpp;
129 fbinfo->xoffset = state->crtc_x;
130 fbinfo->yoffset = state->crtc_y;
131 fbinfo->base = bo->paddr + fb->offsets[0];
132 fbinfo->pitch = fb->pitches[0];
133 /* A bug in the firmware makes it so that if the fb->base is
134 * set to nonzero, the configured pitch gets overwritten with
135 * the previous pitch. So, to get the configured pitch
136 * recomputed, we have to make it allocate itself a new buffer
137 * in VC memory, first.
138 */
139 if (vc4_plane->pitch != fb->pitches[0]) {
140 u32 saved_base = fbinfo->base;
141 fbinfo->base = 0;
142
143 ret = rpi_firmware_transaction(vc4->firmware,
144 RPI_FIRMWARE_CHAN_FB,
145 vc4_plane->fbinfo_bus_addr);
146 fbinfo->base = saved_base;
147
148 vc4_plane->pitch = fbinfo->pitch;
149 WARN_ON_ONCE(vc4_plane->pitch != fb->pitches[0]);
150 }
151
152 ret = rpi_firmware_transaction(vc4->firmware,
153 RPI_FIRMWARE_CHAN_FB,
154 vc4_plane->fbinfo_bus_addr);
155 WARN_ON_ONCE(fbinfo->pitch != fb->pitches[0]);
156 WARN_ON_ONCE(fbinfo->base != bo->paddr + fb->offsets[0]);
157}
158
159static void vc4_primary_plane_atomic_disable(struct drm_plane *plane,
160 struct drm_plane_state *old_state)
161{
162 vc4_plane_set_primary_blank(plane, true);
163}
164
165static void vc4_cursor_plane_atomic_update(struct drm_plane *plane,
166 struct drm_plane_state *old_state)
167{
168 struct vc4_dev *vc4 = to_vc4_dev(plane->dev);
169 struct drm_plane_state *state = plane->state;
170 struct drm_framebuffer *fb = state->fb;
171 struct drm_gem_cma_object *bo = drm_fb_cma_get_gem_obj(fb, 0);
172 int ret;
173 u32 packet_state[] = { true, state->crtc_x, state->crtc_y, 0 };
174 u32 packet_info[] = { state->crtc_w, state->crtc_h,
175 0, /* unused */
176 bo->paddr + fb->offsets[0],
177 0, 0, /* hotx, hoty */};
178 WARN_ON_ONCE(fb->pitches[0] != state->crtc_w * 4);
179 WARN_ON_ONCE(fb->bits_per_pixel != 32);
180
181 ret = rpi_firmware_property(vc4->firmware,
182 RPI_FIRMWARE_SET_CURSOR_STATE,
183 &packet_state,
184 sizeof(packet_state));
185 if (ret || packet_state[0] != 0)
186 DRM_ERROR("Failed to set cursor state: 0x%08x\n", packet_state[0]);
187
188 ret = rpi_firmware_property(vc4->firmware,
189 RPI_FIRMWARE_SET_CURSOR_INFO,
190 &packet_info,
191 sizeof(packet_info));
192 if (ret || packet_info[0] != 0)
193 DRM_ERROR("Failed to set cursor info: 0x%08x\n", packet_info[0]);
194}
195
196static void vc4_cursor_plane_atomic_disable(struct drm_plane *plane,
197 struct drm_plane_state *old_state)
198{
199 struct vc4_dev *vc4 = to_vc4_dev(plane->dev);
200 u32 packet_state[] = { false, 0, 0, 0 };
201 int ret;
202
203 ret = rpi_firmware_property(vc4->firmware,
204 RPI_FIRMWARE_SET_CURSOR_STATE,
205 &packet_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]);
209}
210
211static int vc4_plane_atomic_check(struct drm_plane *plane,
212 struct drm_plane_state *state)
213{
214 return 0;
215}
216
217static void vc4_plane_destroy(struct drm_plane *plane)
218{
219 drm_plane_helper_disable(plane);
220 drm_plane_cleanup(plane);
221}
222
223static const struct drm_plane_funcs vc4_plane_funcs = {
224 .update_plane = drm_atomic_helper_update_plane,
225 .disable_plane = drm_atomic_helper_disable_plane,
226 .destroy = vc4_plane_destroy,
227 .set_property = NULL,
228 .reset = drm_atomic_helper_plane_reset,
229 .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
230 .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
231};
232
233static const struct drm_plane_helper_funcs vc4_primary_plane_helper_funcs = {
234 .prepare_fb = NULL,
235 .cleanup_fb = NULL,
236 .atomic_check = vc4_plane_atomic_check,
237 .atomic_update = vc4_primary_plane_atomic_update,
238 .atomic_disable = vc4_primary_plane_atomic_disable,
239};
240
241static const struct drm_plane_helper_funcs vc4_cursor_plane_helper_funcs = {
242 .prepare_fb = NULL,
243 .cleanup_fb = NULL,
244 .atomic_check = vc4_plane_atomic_check,
245 .atomic_update = vc4_cursor_plane_atomic_update,
246 .atomic_disable = vc4_cursor_plane_atomic_disable,
247};
248
249static struct drm_plane *vc4_fkms_plane_init(struct drm_device *dev,
250 enum drm_plane_type type)
251{
252 struct drm_plane *plane = NULL;
253 struct vc4_fkms_plane *vc4_plane;
254 u32 xrgb8888 = DRM_FORMAT_XRGB8888;
255 u32 argb8888 = DRM_FORMAT_ARGB8888;
256 int ret = 0;
257 bool primary = (type == DRM_PLANE_TYPE_PRIMARY);
258
259 vc4_plane = devm_kzalloc(dev->dev, sizeof(*vc4_plane),
260 GFP_KERNEL);
261 if (!vc4_plane) {
262 ret = -ENOMEM;
263 goto fail;
264 }
265
266 plane = &vc4_plane->base;
267 ret = drm_universal_plane_init(dev, plane, 0xff,
268 &vc4_plane_funcs,
269 primary ? &xrgb8888 : &argb8888, 1,
f4aaa81b 270 type, primary ? "primary" : "cursor");
30c897dd
EA
271
272 if (type == DRM_PLANE_TYPE_PRIMARY) {
273 vc4_plane->fbinfo =
274 dma_alloc_coherent(dev->dev,
275 sizeof(*vc4_plane->fbinfo),
276 &vc4_plane->fbinfo_bus_addr,
277 GFP_KERNEL);
278 memset(vc4_plane->fbinfo, 0, sizeof(*vc4_plane->fbinfo));
279
280 drm_plane_helper_add(plane, &vc4_primary_plane_helper_funcs);
281 } else {
282 drm_plane_helper_add(plane, &vc4_cursor_plane_helper_funcs);
283 }
284
285 return plane;
286fail:
287 if (plane)
288 vc4_plane_destroy(plane);
289
290 return ERR_PTR(ret);
291}
292
293static void vc4_crtc_mode_set_nofb(struct drm_crtc *crtc)
294{
295 /* Everyting is handled in the planes. */
296}
297
298static void vc4_crtc_disable(struct drm_crtc *crtc)
299{
300}
301
302static void vc4_crtc_enable(struct drm_crtc *crtc)
303{
304}
305
306static int vc4_crtc_atomic_check(struct drm_crtc *crtc,
307 struct drm_crtc_state *state)
308{
309 return 0;
310}
311
312static void vc4_crtc_atomic_flush(struct drm_crtc *crtc,
313 struct drm_crtc_state *old_state)
314{
315}
316
317static void vc4_crtc_handle_page_flip(struct vc4_crtc *vc4_crtc)
318{
319 struct drm_crtc *crtc = &vc4_crtc->base;
320 struct drm_device *dev = crtc->dev;
321 unsigned long flags;
322
323 spin_lock_irqsave(&dev->event_lock, flags);
324 if (vc4_crtc->event) {
325 drm_crtc_send_vblank_event(crtc, vc4_crtc->event);
326 vc4_crtc->event = NULL;
327 drm_crtc_vblank_put(crtc);
328 }
329 spin_unlock_irqrestore(&dev->event_lock, flags);
330}
331
332static irqreturn_t vc4_crtc_irq_handler(int irq, void *data)
333{
334 struct vc4_crtc *vc4_crtc = data;
335 u32 stat = readl(vc4_crtc->regs + SMICS);
336 irqreturn_t ret = IRQ_NONE;
337
338 if (stat & SMICS_INTERRUPTS) {
339 writel(0, vc4_crtc->regs + SMICS);
340 drm_crtc_handle_vblank(&vc4_crtc->base);
341 vc4_crtc_handle_page_flip(vc4_crtc);
342 ret = IRQ_HANDLED;
343 }
344
345 return ret;
346}
347
348static int vc4_page_flip(struct drm_crtc *crtc,
349 struct drm_framebuffer *fb,
350 struct drm_pending_vblank_event *event,
351 uint32_t flags)
352{
353 if (flags & DRM_MODE_PAGE_FLIP_ASYNC) {
354 DRM_ERROR("Async flips aren't allowed\n");
355 return -EINVAL;
356 }
357
358 return drm_atomic_helper_page_flip(crtc, fb, event, flags);
359}
360
361static const struct drm_crtc_funcs vc4_crtc_funcs = {
362 .set_config = drm_atomic_helper_set_config,
363 .destroy = drm_crtc_cleanup,
364 .page_flip = vc4_page_flip,
365 .set_property = NULL,
366 .cursor_set = NULL, /* handled by drm_mode_cursor_universal */
367 .cursor_move = NULL, /* handled by drm_mode_cursor_universal */
368 .reset = drm_atomic_helper_crtc_reset,
369 .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
370 .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
371};
372
373static const struct drm_crtc_helper_funcs vc4_crtc_helper_funcs = {
374 .mode_set_nofb = vc4_crtc_mode_set_nofb,
375 .disable = vc4_crtc_disable,
376 .enable = vc4_crtc_enable,
377 .atomic_check = vc4_crtc_atomic_check,
378 .atomic_flush = vc4_crtc_atomic_flush,
379};
380
381/* Frees the page flip event when the DRM device is closed with the
382 * event still outstanding.
383 */
384void vc4_fkms_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file)
385{
386 struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
387 struct drm_device *dev = crtc->dev;
388 unsigned long flags;
389
390 spin_lock_irqsave(&dev->event_lock, flags);
391
392 if (vc4_crtc->event && vc4_crtc->event->base.file_priv == file) {
393 kfree(&vc4_crtc->event->base);
394 drm_crtc_vblank_put(crtc);
395 vc4_crtc->event = NULL;
396 }
397
398 spin_unlock_irqrestore(&dev->event_lock, flags);
399}
400
401static const struct of_device_id vc4_firmware_kms_dt_match[] = {
402 { .compatible = "raspberrypi,rpi-firmware-kms" },
403 {}
404};
405
406static enum drm_connector_status
407vc4_fkms_connector_detect(struct drm_connector *connector, bool force)
408{
409 return connector_status_connected;
410}
411
412static int vc4_fkms_connector_get_modes(struct drm_connector *connector)
413{
414 struct drm_device *dev = connector->dev;
415 struct vc4_dev *vc4 = to_vc4_dev(dev);
416 u32 wh[2] = {0, 0};
417 int ret;
418 struct drm_display_mode *mode;
419
420 ret = rpi_firmware_property(vc4->firmware,
421 RPI_FIRMWARE_FRAMEBUFFER_GET_PHYSICAL_WIDTH_HEIGHT,
422 &wh, sizeof(wh));
423 if (ret) {
424 DRM_ERROR("Failed to get screen size: %d (0x%08x 0x%08x)\n",
425 ret, wh[0], wh[1]);
426 return 0;
427 }
428
429 mode = drm_cvt_mode(dev, wh[0], wh[1], 60 /* vrefresh */,
430 0, 0, false);
431 drm_mode_probed_add(connector, mode);
432
433 return 1;
434}
435
436static struct drm_encoder *
437vc4_fkms_connector_best_encoder(struct drm_connector *connector)
438{
439 struct vc4_fkms_connector *fkms_connector =
440 to_vc4_fkms_connector(connector);
441 return fkms_connector->encoder;
442}
443
444static void vc4_fkms_connector_destroy(struct drm_connector *connector)
445{
446 drm_connector_unregister(connector);
447 drm_connector_cleanup(connector);
448}
449
450static const struct drm_connector_funcs vc4_fkms_connector_funcs = {
451 .dpms = drm_atomic_helper_connector_dpms,
452 .detect = vc4_fkms_connector_detect,
453 .fill_modes = drm_helper_probe_single_connector_modes,
454 .destroy = vc4_fkms_connector_destroy,
455 .reset = drm_atomic_helper_connector_reset,
456 .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
457 .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
458};
459
460static const struct drm_connector_helper_funcs vc4_fkms_connector_helper_funcs = {
461 .get_modes = vc4_fkms_connector_get_modes,
462 .best_encoder = vc4_fkms_connector_best_encoder,
463};
464
465static struct drm_connector *vc4_fkms_connector_init(struct drm_device *dev,
466 struct drm_encoder *encoder)
467{
468 struct drm_connector *connector = NULL;
469 struct vc4_fkms_connector *fkms_connector;
470 int ret = 0;
471
472 fkms_connector = devm_kzalloc(dev->dev, sizeof(*fkms_connector),
473 GFP_KERNEL);
474 if (!fkms_connector) {
475 ret = -ENOMEM;
476 goto fail;
477 }
478 connector = &fkms_connector->base;
479
480 fkms_connector->encoder = encoder;
481
482 drm_connector_init(dev, connector, &vc4_fkms_connector_funcs,
483 DRM_MODE_CONNECTOR_HDMIA);
484 drm_connector_helper_add(connector, &vc4_fkms_connector_helper_funcs);
485
486 connector->polled = (DRM_CONNECTOR_POLL_CONNECT |
487 DRM_CONNECTOR_POLL_DISCONNECT);
488
489 connector->interlace_allowed = 0;
490 connector->doublescan_allowed = 0;
491
492 drm_mode_connector_attach_encoder(connector, encoder);
493
494 return connector;
495
496 fail:
497 if (connector)
498 vc4_fkms_connector_destroy(connector);
499
500 return ERR_PTR(ret);
501}
502
503static void vc4_fkms_encoder_destroy(struct drm_encoder *encoder)
504{
505 drm_encoder_cleanup(encoder);
506}
507
508static const struct drm_encoder_funcs vc4_fkms_encoder_funcs = {
509 .destroy = vc4_fkms_encoder_destroy,
510};
511
512static void vc4_fkms_encoder_enable(struct drm_encoder *encoder)
513{
514}
515
516static void vc4_fkms_encoder_disable(struct drm_encoder *encoder)
517{
518}
519
520static const struct drm_encoder_helper_funcs vc4_fkms_encoder_helper_funcs = {
521 .enable = vc4_fkms_encoder_enable,
522 .disable = vc4_fkms_encoder_disable,
523};
524
525static int vc4_fkms_bind(struct device *dev, struct device *master, void *data)
526{
527 struct platform_device *pdev = to_platform_device(dev);
528 struct drm_device *drm = dev_get_drvdata(master);
529 struct vc4_dev *vc4 = to_vc4_dev(drm);
530 struct vc4_crtc *vc4_crtc;
531 struct vc4_fkms_encoder *vc4_encoder;
532 struct drm_crtc *crtc;
533 struct drm_plane *primary_plane, *cursor_plane, *destroy_plane, *temp;
534 struct device_node *firmware_node;
535 int ret;
536
537 vc4->firmware_kms = true;
538
539 vc4_crtc = devm_kzalloc(dev, sizeof(*vc4_crtc), GFP_KERNEL);
540 if (!vc4_crtc)
541 return -ENOMEM;
542 crtc = &vc4_crtc->base;
543
544 firmware_node = of_parse_phandle(dev->of_node, "brcm,firmware", 0);
545 vc4->firmware = rpi_firmware_get(firmware_node);
546 if (!vc4->firmware) {
547 DRM_DEBUG("Failed to get Raspberry Pi firmware reference.\n");
548 return -EPROBE_DEFER;
549 }
550 of_node_put(firmware_node);
551
552 /* Map the SMI interrupt reg */
553 vc4_crtc->regs = vc4_ioremap_regs(pdev, 0);
554 if (IS_ERR(vc4_crtc->regs))
555 return PTR_ERR(vc4_crtc->regs);
556
557 /* For now, we create just the primary and the legacy cursor
558 * planes. We should be able to stack more planes on easily,
559 * but to do that we would need to compute the bandwidth
560 * requirement of the plane configuration, and reject ones
561 * that will take too much.
562 */
563 primary_plane = vc4_fkms_plane_init(drm, DRM_PLANE_TYPE_PRIMARY);
564 if (IS_ERR(primary_plane)) {
565 dev_err(dev, "failed to construct primary plane\n");
566 ret = PTR_ERR(primary_plane);
567 goto err;
568 }
569
570 cursor_plane = vc4_fkms_plane_init(drm, DRM_PLANE_TYPE_CURSOR);
571 if (IS_ERR(cursor_plane)) {
572 dev_err(dev, "failed to construct cursor plane\n");
573 ret = PTR_ERR(cursor_plane);
574 goto err;
575 }
576
577 drm_crtc_init_with_planes(drm, crtc, primary_plane, cursor_plane,
578 &vc4_crtc_funcs, NULL);
579 drm_crtc_helper_add(crtc, &vc4_crtc_helper_funcs);
580 primary_plane->crtc = crtc;
581 cursor_plane->crtc = crtc;
582 vc4->crtc[drm_crtc_index(crtc)] = vc4_crtc;
583
584 vc4_encoder = devm_kzalloc(dev, sizeof(*vc4_encoder), GFP_KERNEL);
585 if (!vc4_encoder)
586 return -ENOMEM;
587 vc4_crtc->encoder = &vc4_encoder->base;
588 vc4_encoder->base.possible_crtcs |= drm_crtc_mask(crtc) ;
589 drm_encoder_init(drm, &vc4_encoder->base, &vc4_fkms_encoder_funcs,
590 DRM_MODE_ENCODER_TMDS, NULL);
591 drm_encoder_helper_add(&vc4_encoder->base,
592 &vc4_fkms_encoder_helper_funcs);
593
594 vc4_crtc->connector = vc4_fkms_connector_init(drm, &vc4_encoder->base);
595 if (IS_ERR(vc4_crtc->connector)) {
596 ret = PTR_ERR(vc4_crtc->connector);
597 goto err_destroy_encoder;
598 }
599
600 writel(0, vc4_crtc->regs + SMICS);
601 ret = devm_request_irq(dev, platform_get_irq(pdev, 0),
602 vc4_crtc_irq_handler, 0, "vc4 firmware kms",
603 vc4_crtc);
604 if (ret)
605 goto err_destroy_connector;
606
607 platform_set_drvdata(pdev, vc4_crtc);
608
609 return 0;
610
611err_destroy_connector:
612 vc4_fkms_connector_destroy(vc4_crtc->connector);
613err_destroy_encoder:
614 vc4_fkms_encoder_destroy(vc4_crtc->encoder);
615 list_for_each_entry_safe(destroy_plane, temp,
616 &drm->mode_config.plane_list, head) {
617 if (destroy_plane->possible_crtcs == 1 << drm_crtc_index(crtc))
618 destroy_plane->funcs->destroy(destroy_plane);
619 }
620err:
621 return ret;
622}
623
624static void vc4_fkms_unbind(struct device *dev, struct device *master,
625 void *data)
626{
627 struct platform_device *pdev = to_platform_device(dev);
628 struct vc4_crtc *vc4_crtc = dev_get_drvdata(dev);
629
630 vc4_fkms_connector_destroy(vc4_crtc->connector);
631 vc4_fkms_encoder_destroy(vc4_crtc->encoder);
632 drm_crtc_cleanup(&vc4_crtc->base);
633
634 platform_set_drvdata(pdev, NULL);
635}
636
637static const struct component_ops vc4_fkms_ops = {
638 .bind = vc4_fkms_bind,
639 .unbind = vc4_fkms_unbind,
640};
641
642static int vc4_fkms_probe(struct platform_device *pdev)
643{
644 return component_add(&pdev->dev, &vc4_fkms_ops);
645}
646
647static int vc4_fkms_remove(struct platform_device *pdev)
648{
649 component_del(&pdev->dev, &vc4_fkms_ops);
650 return 0;
651}
652
653struct platform_driver vc4_firmware_kms_driver = {
654 .probe = vc4_fkms_probe,
655 .remove = vc4_fkms_remove,
656 .driver = {
657 .name = "vc4_firmware_kms",
658 .of_match_table = vc4_firmware_kms_dt_match,
659 },
660};