drm: vc4: Add support for multiple displays to fkms
authorDave Stevenson <dave.stevenson@raspberrypi.org>
Wed, 3 Apr 2019 16:15:45 +0000 (17:15 +0100)
committerpopcornmix <popcornmix@gmail.com>
Wed, 1 Jul 2020 15:32:58 +0000 (16:32 +0100)
There is a slightly nasty hack in that all crtcs share the
same SMI interrupt from the firmware. This seems to currently
work well enough, but ought to be fixed at a later date.

Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.org>
drivers/gpu/drm/vc4/vc4_firmware_kms.c

index 54bb885..d8e278a 100644 (file)
@@ -31,6 +31,8 @@
 #include "vc_image_types.h"
 #include <soc/bcm2835/raspberrypi-firmware.h>
 
+#define PLANES_PER_CRTC                3
+
 struct set_plane {
        u8 display;
        u8 plane_id;
@@ -177,6 +179,7 @@ struct vc4_crtc {
        struct drm_pending_vblank_event *event;
        u32 overscan[4];
        bool vblank_enabled;
+       u32 display_number;
 };
 
 static inline struct vc4_crtc *to_vc4_crtc(struct drm_crtc *crtc)
@@ -481,6 +484,7 @@ static const struct drm_plane_helper_funcs vc4_plane_helper_funcs = {
 
 static struct drm_plane *vc4_fkms_plane_init(struct drm_device *dev,
                                             enum drm_plane_type type,
+                                            u8 display_num,
                                             u8 plane_id)
 {
        struct drm_plane *plane = NULL;
@@ -544,7 +548,7 @@ static struct drm_plane *vc4_fkms_plane_init(struct drm_device *dev,
        vc4_plane->mb.tag.tag = RPI_FIRMWARE_SET_PLANE;
        vc4_plane->mb.tag.buf_size = sizeof(struct set_plane);
        vc4_plane->mb.tag.req_resp_size = 0;
-       vc4_plane->mb.plane.display = 0;
+       vc4_plane->mb.plane.display = display_num;
        vc4_plane->mb.plane.plane_id = plane_id;
        vc4_plane->mb.plane.layer = default_zpos ? default_zpos : -127;
 
@@ -631,16 +635,20 @@ static void vc4_crtc_handle_page_flip(struct vc4_crtc *vc4_crtc)
 
 static irqreturn_t vc4_crtc_irq_handler(int irq, void *data)
 {
-       struct vc4_crtc *vc4_crtc = data;
-       u32 stat = readl(vc4_crtc->regs + SMICS);
+       struct vc4_crtc **crtc_list = data;
+       int i;
+       u32 stat = readl(crtc_list[0]->regs + SMICS);
        irqreturn_t ret = IRQ_NONE;
 
        if (stat & SMICS_INTERRUPTS) {
-               writel(0, vc4_crtc->regs + SMICS);
-               if (vc4_crtc->vblank_enabled)
-                       drm_crtc_handle_vblank(&vc4_crtc->base);
-               vc4_crtc_handle_page_flip(vc4_crtc);
-               ret = IRQ_HANDLED;
+               writel(0, crtc_list[0]->regs + SMICS);
+
+               for (i = 0; crtc_list[i]; i++) {
+                       if (crtc_list[i]->vblank_enabled)
+                               drm_crtc_handle_vblank(&crtc_list[i]->base);
+                       vc4_crtc_handle_page_flip(crtc_list[i]);
+                       ret = IRQ_HANDLED;
+               }
        }
 
        return ret;
@@ -837,66 +845,55 @@ static const struct drm_encoder_helper_funcs vc4_fkms_encoder_helper_funcs = {
        .disable = vc4_fkms_encoder_disable,
 };
 
-static int vc4_fkms_bind(struct device *dev, struct device *master, void *data)
+static int vc4_fkms_create_screen(struct device *dev, struct drm_device *drm,
+                                 int display_idx, int display_ref,
+                                 struct vc4_crtc **ret_crtc)
 {
-       struct platform_device *pdev = to_platform_device(dev);
-       struct drm_device *drm = dev_get_drvdata(master);
        struct vc4_dev *vc4 = to_vc4_dev(drm);
        struct vc4_crtc *vc4_crtc;
        struct vc4_fkms_encoder *vc4_encoder;
        struct drm_crtc *crtc;
        struct drm_plane *primary_plane, *overlay_plane, *cursor_plane;
        struct drm_plane *destroy_plane, *temp;
-       struct device_node *firmware_node;
        u32 blank = 1;
        int ret;
 
-       vc4->firmware_kms = true;
-
-       /* firmware kms doesn't have precise a scanoutpos implementation, so
-        * we can't do the precise vblank timestamp mode.
-        */
-       drm->driver->get_scanout_position = NULL;
-       drm->driver->get_vblank_timestamp = NULL;
-
        vc4_crtc = devm_kzalloc(dev, sizeof(*vc4_crtc), GFP_KERNEL);
        if (!vc4_crtc)
                return -ENOMEM;
        crtc = &vc4_crtc->base;
 
-       firmware_node = of_parse_phandle(dev->of_node, "brcm,firmware", 0);
-       vc4->firmware = rpi_firmware_get(firmware_node);
-       if (!vc4->firmware) {
-               DRM_DEBUG("Failed to get Raspberry Pi firmware reference.\n");
-               return -EPROBE_DEFER;
-       }
-       of_node_put(firmware_node);
-
-       /* Map the SMI interrupt reg */
-       vc4_crtc->regs = vc4_ioremap_regs(pdev, 0);
-       if (IS_ERR(vc4_crtc->regs))
-               return PTR_ERR(vc4_crtc->regs);
+       vc4_crtc->display_number = display_ref;
 
        /* Blank the firmware provided framebuffer */
        rpi_firmware_property(vc4->firmware,
                              RPI_FIRMWARE_FRAMEBUFFER_BLANK,
                              &blank, sizeof(blank));
 
-       primary_plane = vc4_fkms_plane_init(drm, DRM_PLANE_TYPE_PRIMARY, 0);
+       primary_plane = vc4_fkms_plane_init(drm, DRM_PLANE_TYPE_PRIMARY,
+                                           display_ref,
+                                           0 + (display_idx * PLANES_PER_CRTC)
+                                          );
        if (IS_ERR(primary_plane)) {
                dev_err(dev, "failed to construct primary plane\n");
                ret = PTR_ERR(primary_plane);
                goto err;
        }
 
-       overlay_plane = vc4_fkms_plane_init(drm, DRM_PLANE_TYPE_OVERLAY, 1);
+       overlay_plane = vc4_fkms_plane_init(drm, DRM_PLANE_TYPE_OVERLAY,
+                                           display_ref,
+                                           1 + (display_idx * PLANES_PER_CRTC)
+                                          );
        if (IS_ERR(overlay_plane)) {
                dev_err(dev, "failed to construct overlay plane\n");
                ret = PTR_ERR(overlay_plane);
                goto err;
        }
 
-       cursor_plane = vc4_fkms_plane_init(drm, DRM_PLANE_TYPE_CURSOR, 2);
+       cursor_plane = vc4_fkms_plane_init(drm, DRM_PLANE_TYPE_CURSOR,
+                                          display_ref,
+                                          2 + (display_idx * PLANES_PER_CRTC)
+                                         );
        if (IS_ERR(cursor_plane)) {
                dev_err(dev, "failed to construct cursor plane\n");
                ret = PTR_ERR(cursor_plane);
@@ -923,13 +920,6 @@ static int vc4_fkms_bind(struct device *dev, struct device *master, void *data)
                goto err_destroy_encoder;
        }
 
-       writel(0, vc4_crtc->regs + SMICS);
-       ret = devm_request_irq(dev, platform_get_irq(pdev, 0),
-                              vc4_crtc_irq_handler, 0, "vc4 firmware kms",
-                              vc4_crtc);
-       if (ret)
-               goto err_destroy_connector;
-
        ret = rpi_firmware_property(vc4->firmware,
                                    RPI_FIRMWARE_FRAMEBUFFER_GET_OVERSCAN,
                                    &vc4_crtc->overscan,
@@ -939,7 +929,7 @@ static int vc4_fkms_bind(struct device *dev, struct device *master, void *data)
                memset(&vc4_crtc->overscan, 0, sizeof(vc4_crtc->overscan));
        }
 
-       platform_set_drvdata(pdev, vc4_crtc);
+       *ret_crtc = vc4_crtc;
 
        return 0;
 
@@ -956,17 +946,91 @@ err:
        return ret;
 }
 
+static int vc4_fkms_bind(struct device *dev, struct device *master, void *data)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+       struct drm_device *drm = dev_get_drvdata(master);
+       struct vc4_dev *vc4 = to_vc4_dev(drm);
+       struct device_node *firmware_node;
+       struct vc4_crtc **crtc_list;
+       u32 num_displays, display_num;
+       int ret;
+       const u32 display_num_lookup[] = {2, 7, 1};
+
+       vc4->firmware_kms = true;
+
+       /* firmware kms doesn't have precise a scanoutpos implementation, so
+        * we can't do the precise vblank timestamp mode.
+        */
+       drm->driver->get_scanout_position = NULL;
+       drm->driver->get_vblank_timestamp = NULL;
+
+       firmware_node = of_parse_phandle(dev->of_node, "brcm,firmware", 0);
+       vc4->firmware = rpi_firmware_get(firmware_node);
+       if (!vc4->firmware) {
+               DRM_DEBUG("Failed to get Raspberry Pi firmware reference.\n");
+               return -EPROBE_DEFER;
+       }
+       of_node_put(firmware_node);
+
+       ret = rpi_firmware_property(vc4->firmware,
+                                   RPI_FIRMWARE_FRAMEBUFFER_GET_NUM_DISPLAYS,
+                                   &num_displays, sizeof(u32));
+
+       /* If we fail to get the number of displays, or it returns 0, then
+        * assume old firmware that doesn't have the mailbox call, so just
+        * set one display
+        */
+       if (ret || num_displays == 0) {
+               num_displays = 1;
+               DRM_WARN("Unable to determine number of displays's. Assuming 1\n");
+               ret = 0;
+       }
+
+       /* Allocate a list, with space for a NULL on the end */
+       crtc_list = devm_kzalloc(dev, sizeof(crtc_list) * (num_displays + 1),
+                                GFP_KERNEL);
+       if (!crtc_list)
+               return -ENOMEM;
+
+       for (display_num = 0; display_num < num_displays; display_num++) {
+               ret = vc4_fkms_create_screen(dev, drm, display_num,
+                                            display_num_lookup[display_num],
+                                            &crtc_list[display_num]);
+               if (ret)
+                       DRM_ERROR("Oh dear, failed to create display %u\n",
+                                 display_num);
+       }
+
+       /* Map the SMI interrupt reg */
+       crtc_list[0]->regs = vc4_ioremap_regs(pdev, 0);
+       if (IS_ERR(crtc_list[0]->regs))
+               DRM_ERROR("Oh dear, failed to map registers\n");
+
+       writel(0, crtc_list[0]->regs + SMICS);
+       ret = devm_request_irq(dev, platform_get_irq(pdev, 0),
+                              vc4_crtc_irq_handler, 0, "vc4 firmware kms",
+                              crtc_list);
+       if (ret)
+               DRM_ERROR("Oh dear, failed to register IRQ\n");
+
+       platform_set_drvdata(pdev, crtc_list);
+
+       return 0;
+}
+
 static void vc4_fkms_unbind(struct device *dev, struct device *master,
                            void *data)
 {
-       struct drm_device *drm = dev_get_drvdata(master);
        struct platform_device *pdev = to_platform_device(dev);
-       struct vc4_crtc *vc4_crtc = dev_get_drvdata(dev);
+       struct vc4_crtc **crtc_list = dev_get_drvdata(dev);
+       int i;
 
-       vc4_fkms_connector_destroy(vc4_crtc->connector);
-       vc4_fkms_encoder_destroy(vc4_crtc->encoder);
-       drm_atomic_helper_shutdown(drm);
-       drm_crtc_cleanup(&vc4_crtc->base);
+       for (i = 0; crtc_list[i]; i++) {
+               vc4_fkms_connector_destroy(crtc_list[i]->connector);
+               vc4_fkms_encoder_destroy(crtc_list[i]->encoder);
+               drm_crtc_cleanup(&crtc_list[i]->base);
+       }
 
        platform_set_drvdata(pdev, NULL);
 }