Merge drm/drm-next into drm-misc-next
authorThomas Zimmermann <tzimmermann@suse.de>
Mon, 20 Jun 2022 16:21:25 +0000 (18:21 +0200)
committerThomas Zimmermann <tzimmermann@suse.de>
Mon, 20 Jun 2022 16:21:25 +0000 (18:21 +0200)
Backmerging to get new regmap APIs of v5.19-rc1.

Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de>
76 files changed:
Documentation/devicetree/bindings/display/panel/novatek,nt36672a.yaml
Documentation/devicetree/bindings/gpu/brcm,bcm-v3d.yaml
Documentation/driver-api/firmware/other_interfaces.rst
Documentation/gpu/vkms.rst
MAINTAINERS
drivers/dma-buf/dma-buf.c
drivers/dma-buf/udmabuf.c
drivers/firmware/sysfb.c
drivers/firmware/sysfb_simplefb.c
drivers/gpu/drm/Kconfig
drivers/gpu/drm/Makefile
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
drivers/gpu/drm/bridge/Kconfig
drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
drivers/gpu/drm/bridge/analogix/anx7625.c
drivers/gpu/drm/bridge/chipone-icn6211.c
drivers/gpu/drm/bridge/lontium-lt8912b.c
drivers/gpu/drm/bridge/lontium-lt9211.c
drivers/gpu/drm/bridge/tc358767.c
drivers/gpu/drm/bridge/tc358775.c
drivers/gpu/drm/bridge/ti-sn65dsi83.c
drivers/gpu/drm/bridge/ti-sn65dsi86.c
drivers/gpu/drm/display/drm_dp_mst_topology.c
drivers/gpu/drm/drm_edid.c
drivers/gpu/drm/drm_gem_cma_helper.c
drivers/gpu/drm/drm_of.c
drivers/gpu/drm/drm_probe_helper.c
drivers/gpu/drm/drm_syncobj.c
drivers/gpu/drm/logicvc/Kconfig [new file with mode: 0644]
drivers/gpu/drm/logicvc/Makefile [new file with mode: 0644]
drivers/gpu/drm/logicvc/logicvc_crtc.c [new file with mode: 0644]
drivers/gpu/drm/logicvc/logicvc_crtc.h [new file with mode: 0644]
drivers/gpu/drm/logicvc/logicvc_drm.c [new file with mode: 0644]
drivers/gpu/drm/logicvc/logicvc_drm.h [new file with mode: 0644]
drivers/gpu/drm/logicvc/logicvc_interface.c [new file with mode: 0644]
drivers/gpu/drm/logicvc/logicvc_interface.h [new file with mode: 0644]
drivers/gpu/drm/logicvc/logicvc_layer.c [new file with mode: 0644]
drivers/gpu/drm/logicvc/logicvc_layer.h [new file with mode: 0644]
drivers/gpu/drm/logicvc/logicvc_mode.c [new file with mode: 0644]
drivers/gpu/drm/logicvc/logicvc_mode.h [new file with mode: 0644]
drivers/gpu/drm/logicvc/logicvc_of.c [new file with mode: 0644]
drivers/gpu/drm/logicvc/logicvc_of.h [new file with mode: 0644]
drivers/gpu/drm/logicvc/logicvc_regs.h [new file with mode: 0644]
drivers/gpu/drm/mgag200/mgag200_drv.c
drivers/gpu/drm/mgag200/mgag200_g200.c
drivers/gpu/drm/msm/dp/dp_parser.c
drivers/gpu/drm/msm/dsi/dsi_host.c
drivers/gpu/drm/nouveau/include/nvhw/drf.h
drivers/gpu/drm/nouveau/nvkm/subdev/bios/priv.h
drivers/gpu/drm/nouveau/nvkm/subdev/bios/shadow.c
drivers/gpu/drm/nouveau/nvkm/subdev/bios/shadowrom.c
drivers/gpu/drm/panel/panel-novatek-nt36672a.c
drivers/gpu/drm/qxl/qxl_kms.c
drivers/gpu/drm/rcar-du/rcar_mipi_dsi.c
drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
drivers/gpu/drm/tiny/bochs.c
drivers/gpu/drm/v3d/Kconfig
drivers/gpu/drm/v3d/v3d_debugfs.c
drivers/gpu/drm/v3d/v3d_drv.c
drivers/gpu/drm/v3d/v3d_gem.c
drivers/gpu/drm/virtio/virtgpu_ioctl.c
drivers/gpu/drm/virtio/virtgpu_object.c
drivers/gpu/drm/virtio/virtgpu_prime.c
drivers/gpu/drm/vkms/vkms_composer.c
drivers/video/fbdev/Kconfig
drivers/video/fbdev/core/fbcon.c
drivers/video/fbdev/core/fbmem.c
drivers/video/fbdev/efifb.c
drivers/video/fbdev/simplefb.c
include/drm/display/drm_dp_helper.h
include/drm/drm_edid.h
include/drm/drm_of.h
include/linux/iosys-map.h
include/linux/sysfb.h
include/uapi/linux/dma-buf.h

index 563766d..41ee315 100644 (file)
@@ -46,6 +46,7 @@ properties:
 
   reg: true
   port: true
+  backlight: true
 
 required:
   - compatible
@@ -73,6 +74,7 @@ examples:
             vddpos-supply = <&lab>;
             vddneg-supply = <&ibb>;
 
+            backlight = <&pmi8998_wled>;
             reset-gpios = <&tlmm 6 GPIO_ACTIVE_HIGH>;
 
             port {
index e6485f7..217c428 100644 (file)
@@ -16,6 +16,7 @@ properties:
 
   compatible:
     enum:
+      - brcm,2711-v3d
       - brcm,7268-v3d
       - brcm,7278-v3d
 
index b81794e..06ac89a 100644 (file)
@@ -13,6 +13,12 @@ EDD Interfaces
 .. kernel-doc:: drivers/firmware/edd.c
    :internal:
 
+Generic System Framebuffers Interface
+-------------------------------------
+
+.. kernel-doc:: drivers/firmware/sysfb.c
+   :export:
+
 Intel Stratix10 SoC Service Layer
 ---------------------------------
 Some features of the Intel Stratix10 SoC require a level of privilege
index 9c873c3..973e2d4 100644 (file)
@@ -102,12 +102,6 @@ Debugging:
 
 - kms_plane: some test cases are failing due to timeout on capturing CRC;
 
-- kms_flip: when running test cases in sequence, some successful individual
-  test cases are failing randomly; when individually, some successful test
-  cases display in the log the following error::
-
-  [drm:vkms_prepare_fb [vkms]] ERROR vmap failed: -4
-
 Virtual hardware (vblank-less) mode:
 
 - VKMS already has support for vblanks simulated via hrtimers, which can be
index 39fcbdd..3412310 100644 (file)
@@ -6284,6 +6284,12 @@ S:       Orphan / Obsolete
 F:     drivers/gpu/drm/i810/
 F:     include/uapi/drm/i810_drm.h
 
+DRM DRIVER FOR LOGICVC DISPLAY CONTROLLER
+M:     Paul Kocialkowski <paul.kocialkowski@bootlin.com>
+S:     Supported
+T:     git git://anongit.freedesktop.org/drm/drm-misc
+F:     drivers/gpu/drm/logicvc/
+
 DRM DRIVER FOR LVDS PANELS
 M:     Laurent Pinchart <laurent.pinchart@ideasonboard.com>
 L:     dri-devel@lists.freedesktop.org
index 727f5bb..6301332 100644 (file)
@@ -20,6 +20,7 @@
 #include <linux/debugfs.h>
 #include <linux/module.h>
 #include <linux/seq_file.h>
+#include <linux/sync_file.h>
 #include <linux/poll.h>
 #include <linux/dma-resv.h>
 #include <linux/mm.h>
@@ -192,6 +193,9 @@ static loff_t dma_buf_llseek(struct file *file, loff_t offset, int whence)
  * Note that this only signals the completion of the respective fences, i.e. the
  * DMA transfers are complete. Cache flushing and any other necessary
  * preparations before CPU access can begin still need to happen.
+ *
+ * As an alternative to poll(), the set of fences on DMA buffer can be
+ * exported as a &sync_file using &dma_buf_sync_file_export.
  */
 
 static void dma_buf_poll_cb(struct dma_fence *fence, struct dma_fence_cb *cb)
@@ -326,6 +330,101 @@ static long dma_buf_set_name(struct dma_buf *dmabuf, const char __user *buf)
        return 0;
 }
 
+#if IS_ENABLED(CONFIG_SYNC_FILE)
+static long dma_buf_export_sync_file(struct dma_buf *dmabuf,
+                                    void __user *user_data)
+{
+       struct dma_buf_export_sync_file arg;
+       enum dma_resv_usage usage;
+       struct dma_fence *fence = NULL;
+       struct sync_file *sync_file;
+       int fd, ret;
+
+       if (copy_from_user(&arg, user_data, sizeof(arg)))
+               return -EFAULT;
+
+       if (arg.flags & ~DMA_BUF_SYNC_RW)
+               return -EINVAL;
+
+       if ((arg.flags & DMA_BUF_SYNC_RW) == 0)
+               return -EINVAL;
+
+       fd = get_unused_fd_flags(O_CLOEXEC);
+       if (fd < 0)
+               return fd;
+
+       usage = dma_resv_usage_rw(arg.flags & DMA_BUF_SYNC_WRITE);
+       ret = dma_resv_get_singleton(dmabuf->resv, usage, &fence);
+       if (ret)
+               goto err_put_fd;
+
+       if (!fence)
+               fence = dma_fence_get_stub();
+
+       sync_file = sync_file_create(fence);
+
+       dma_fence_put(fence);
+
+       if (!sync_file) {
+               ret = -ENOMEM;
+               goto err_put_fd;
+       }
+
+       arg.fd = fd;
+       if (copy_to_user(user_data, &arg, sizeof(arg))) {
+               ret = -EFAULT;
+               goto err_put_file;
+       }
+
+       fd_install(fd, sync_file->file);
+
+       return 0;
+
+err_put_file:
+       fput(sync_file->file);
+err_put_fd:
+       put_unused_fd(fd);
+       return ret;
+}
+
+static long dma_buf_import_sync_file(struct dma_buf *dmabuf,
+                                    const void __user *user_data)
+{
+       struct dma_buf_import_sync_file arg;
+       struct dma_fence *fence;
+       enum dma_resv_usage usage;
+       int ret = 0;
+
+       if (copy_from_user(&arg, user_data, sizeof(arg)))
+               return -EFAULT;
+
+       if (arg.flags & ~DMA_BUF_SYNC_RW)
+               return -EINVAL;
+
+       if ((arg.flags & DMA_BUF_SYNC_RW) == 0)
+               return -EINVAL;
+
+       fence = sync_file_get_fence(arg.fd);
+       if (!fence)
+               return -EINVAL;
+
+       usage = (arg.flags & DMA_BUF_SYNC_WRITE) ? DMA_RESV_USAGE_WRITE :
+                                                  DMA_RESV_USAGE_READ;
+
+       dma_resv_lock(dmabuf->resv, NULL);
+
+       ret = dma_resv_reserve_fences(dmabuf->resv, 1);
+       if (!ret)
+               dma_resv_add_fence(dmabuf->resv, fence, usage);
+
+       dma_resv_unlock(dmabuf->resv);
+
+       dma_fence_put(fence);
+
+       return ret;
+}
+#endif
+
 static long dma_buf_ioctl(struct file *file,
                          unsigned int cmd, unsigned long arg)
 {
@@ -369,6 +468,13 @@ static long dma_buf_ioctl(struct file *file,
        case DMA_BUF_SET_NAME_B:
                return dma_buf_set_name(dmabuf, (const char __user *)arg);
 
+#if IS_ENABLED(CONFIG_SYNC_FILE)
+       case DMA_BUF_IOCTL_EXPORT_SYNC_FILE:
+               return dma_buf_export_sync_file(dmabuf, (void __user *)arg);
+       case DMA_BUF_IOCTL_IMPORT_SYNC_FILE:
+               return dma_buf_import_sync_file(dmabuf, (const void __user *)arg);
+#endif
+
        default:
                return -ENOTTY;
        }
index e733068..384cb3d 100644 (file)
@@ -365,7 +365,23 @@ static struct miscdevice udmabuf_misc = {
 
 static int __init udmabuf_dev_init(void)
 {
-       return misc_register(&udmabuf_misc);
+       int ret;
+
+       ret = misc_register(&udmabuf_misc);
+       if (ret < 0) {
+               pr_err("Could not initialize udmabuf device\n");
+               return ret;
+       }
+
+       ret = dma_coerce_mask_and_coherent(udmabuf_misc.this_device,
+                                          DMA_BIT_MASK(64));
+       if (ret < 0) {
+               pr_err("Could not setup DMA mask for udmabuf device\n");
+               misc_deregister(&udmabuf_misc);
+               return ret;
+       }
+
+       return 0;
 }
 
 static void __exit udmabuf_dev_exit(void)
index 2bfbb05..1f276f1 100644 (file)
 #include <linux/screen_info.h>
 #include <linux/sysfb.h>
 
+static struct platform_device *pd;
+static DEFINE_MUTEX(disable_lock);
+static bool disabled;
+
+static bool sysfb_unregister(void)
+{
+       if (IS_ERR_OR_NULL(pd))
+               return false;
+
+       platform_device_unregister(pd);
+       pd = NULL;
+
+       return true;
+}
+
+/**
+ * sysfb_disable() - disable the Generic System Framebuffers support
+ *
+ * This disables the registration of system framebuffer devices that match the
+ * generic drivers that make use of the system framebuffer set up by firmware.
+ *
+ * It also unregisters a device if this was already registered by sysfb_init().
+ *
+ * Context: The function can sleep. A @disable_lock mutex is acquired to serialize
+ *          against sysfb_init(), that registers a system framebuffer device.
+ */
+void sysfb_disable(void)
+{
+       mutex_lock(&disable_lock);
+       sysfb_unregister();
+       disabled = true;
+       mutex_unlock(&disable_lock);
+}
+EXPORT_SYMBOL_GPL(sysfb_disable);
+
 static __init int sysfb_init(void)
 {
        struct screen_info *si = &screen_info;
        struct simplefb_platform_data mode;
-       struct platform_device *pd;
        const char *name;
        bool compatible;
-       int ret;
+       int ret = 0;
+
+       mutex_lock(&disable_lock);
+       if (disabled)
+               goto unlock_mutex;
 
        /* try to create a simple-framebuffer device */
        compatible = sysfb_parse_mode(si, &mode);
        if (compatible) {
-               ret = sysfb_create_simplefb(si, &mode);
-               if (!ret)
-                       return 0;
+               pd = sysfb_create_simplefb(si, &mode);
+               if (!IS_ERR(pd))
+                       goto unlock_mutex;
        }
 
        /* if the FB is incompatible, create a legacy framebuffer device */
@@ -60,8 +98,10 @@ static __init int sysfb_init(void)
                name = "platform-framebuffer";
 
        pd = platform_device_alloc(name, 0);
-       if (!pd)
-               return -ENOMEM;
+       if (!pd) {
+               ret = -ENOMEM;
+               goto unlock_mutex;
+       }
 
        sysfb_apply_efi_quirks(pd);
 
@@ -73,9 +113,11 @@ static __init int sysfb_init(void)
        if (ret)
                goto err;
 
-       return 0;
+       goto unlock_mutex;
 err:
        platform_device_put(pd);
+unlock_mutex:
+       mutex_unlock(&disable_lock);
        return ret;
 }
 
index bda8712..a353e27 100644 (file)
@@ -57,8 +57,8 @@ __init bool sysfb_parse_mode(const struct screen_info *si,
        return false;
 }
 
-__init int sysfb_create_simplefb(const struct screen_info *si,
-                                const struct simplefb_platform_data *mode)
+__init struct platform_device *sysfb_create_simplefb(const struct screen_info *si,
+                                                    const struct simplefb_platform_data *mode)
 {
        struct platform_device *pd;
        struct resource res;
@@ -76,7 +76,7 @@ __init int sysfb_create_simplefb(const struct screen_info *si,
                base |= (u64)si->ext_lfb_base << 32;
        if (!base || (u64)(resource_size_t)base != base) {
                printk(KERN_DEBUG "sysfb: inaccessible VRAM base\n");
-               return -EINVAL;
+               return ERR_PTR(-EINVAL);
        }
 
        /*
@@ -93,7 +93,7 @@ __init int sysfb_create_simplefb(const struct screen_info *si,
        length = mode->height * mode->stride;
        if (length > size) {
                printk(KERN_WARNING "sysfb: VRAM smaller than advertised\n");
-               return -EINVAL;
+               return ERR_PTR(-EINVAL);
        }
        length = PAGE_ALIGN(length);
 
@@ -104,11 +104,11 @@ __init int sysfb_create_simplefb(const struct screen_info *si,
        res.start = base;
        res.end = res.start + length - 1;
        if (res.end <= res.start)
-               return -EINVAL;
+               return ERR_PTR(-EINVAL);
 
        pd = platform_device_alloc("simple-framebuffer", 0);
        if (!pd)
-               return -ENOMEM;
+               return ERR_PTR(-ENOMEM);
 
        sysfb_apply_efi_quirks(pd);
 
@@ -124,10 +124,10 @@ __init int sysfb_create_simplefb(const struct screen_info *si,
        if (ret)
                goto err_put_device;
 
-       return 0;
+       return pd;
 
 err_put_device:
        platform_device_put(pd);
 
-       return ret;
+       return ERR_PTR(ret);
 }
index e88c497..22e7fa4 100644 (file)
@@ -351,6 +351,8 @@ source "drivers/gpu/drm/etnaviv/Kconfig"
 
 source "drivers/gpu/drm/hisilicon/Kconfig"
 
+source "drivers/gpu/drm/logicvc/Kconfig"
+
 source "drivers/gpu/drm/mediatek/Kconfig"
 
 source "drivers/gpu/drm/mxsfb/Kconfig"
index 15fe316..13ef240 100644 (file)
@@ -121,6 +121,7 @@ obj-$(CONFIG_DRM_STM) += stm/
 obj-$(CONFIG_DRM_STI) += sti/
 obj-y                  += imx/
 obj-$(CONFIG_DRM_INGENIC) += ingenic/
+obj-$(CONFIG_DRM_LOGICVC) += logicvc/
 obj-$(CONFIG_DRM_MEDIATEK) += mediatek/
 obj-$(CONFIG_DRM_MESON)        += meson/
 obj-y                  += i2c/
index ad45711..eb6c55e 100644 (file)
@@ -6592,12 +6592,14 @@ dm_crtc_duplicate_state(struct drm_crtc *crtc)
        return &state->base;
 }
 
+#ifdef CONFIG_DEBUG_FS
 static int amdgpu_dm_crtc_late_register(struct drm_crtc *crtc)
 {
        crtc_debugfs_init(crtc);
 
        return 0;
 }
+#endif
 
 static inline int dm_set_vupdate_irq(struct drm_crtc *crtc, bool enable)
 {
@@ -6691,7 +6693,9 @@ static const struct drm_crtc_funcs amdgpu_dm_crtc_funcs = {
        .enable_vblank = dm_enable_vblank,
        .disable_vblank = dm_disable_vblank,
        .get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp,
+#if defined(CONFIG_DEBUG_FS)
        .late_register = amdgpu_dm_crtc_late_register,
+#endif
 };
 
 static enum drm_connector_status
index 8ffd601..1afe99d 100644 (file)
@@ -94,6 +94,8 @@ config DRM_ITE_IT6505
         select DRM_KMS_HELPER
         select DRM_DP_HELPER
         select EXTCON
+        select CRYPTO
+        select CRYPTO_HASH
         help
           ITE IT6505 DisplayPort bridge chip driver.
 
index 5bb9300..074c2e6 100644 (file)
@@ -1065,6 +1065,10 @@ static int adv7511_init_cec_regmap(struct adv7511 *adv)
                                                ADV7511_CEC_I2C_ADDR_DEFAULT);
        if (IS_ERR(adv->i2c_cec))
                return PTR_ERR(adv->i2c_cec);
+
+       regmap_write(adv->regmap, ADV7511_REG_CEC_I2C_ADDR,
+                    adv->i2c_cec->addr << 1);
+
        i2c_set_clientdata(adv->i2c_cec, adv);
 
        adv->regmap_cec = devm_regmap_init_i2c(adv->i2c_cec,
@@ -1271,9 +1275,6 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
        if (ret)
                goto err_i2c_unregister_packet;
 
-       regmap_write(adv7511->regmap, ADV7511_REG_CEC_I2C_ADDR,
-                    adv7511->i2c_cec->addr << 1);
-
        INIT_WORK(&adv7511->hpd_work, adv7511_hpd_work);
 
        if (i2c->irq) {
index e92eb4a..f08f330 100644 (file)
@@ -1637,16 +1637,16 @@ static int anx7625_parse_dt(struct device *dev,
                if (of_property_read_u32(ep0, "bus-type", &bus_type))
                        bus_type = 0;
 
-               mipi_lanes = of_property_count_u32_elems(ep0, "data-lanes");
+               mipi_lanes = drm_of_get_data_lanes_count(ep0, 1, MAX_LANES_SUPPORT);
                of_node_put(ep0);
        }
 
        if (bus_type == V4L2_FWNODE_BUS_TYPE_PARALLEL) /* bus type is Parallel(DSI) */
                pdata->is_dpi = 0;
 
-       pdata->mipi_lanes = mipi_lanes;
-       if (pdata->mipi_lanes > MAX_LANES_SUPPORT || pdata->mipi_lanes <= 0)
-               pdata->mipi_lanes = MAX_LANES_SUPPORT;
+       pdata->mipi_lanes = MAX_LANES_SUPPORT;
+       if (mipi_lanes > 0)
+               pdata->mipi_lanes = mipi_lanes;
 
        if (pdata->is_dpi)
                DRM_DEV_DEBUG_DRIVER(dev, "found MIPI DPI host node.\n");
@@ -1658,8 +1658,10 @@ static int anx7625_parse_dt(struct device *dev,
 
        pdata->panel_bridge = devm_drm_of_get_bridge(dev, np, 1, 0);
        if (IS_ERR(pdata->panel_bridge)) {
-               if (PTR_ERR(pdata->panel_bridge) == -ENODEV)
+               if (PTR_ERR(pdata->panel_bridge) == -ENODEV) {
+                       pdata->panel_bridge = NULL;
                        return 0;
+               }
 
                return PTR_ERR(pdata->panel_bridge);
        }
index 2b8cf64..d25bc62 100644 (file)
@@ -498,21 +498,18 @@ static int chipone_dsi_attach(struct chipone *icn)
 {
        struct mipi_dsi_device *dsi = icn->dsi;
        struct device *dev = icn->dev;
-       struct device_node *endpoint;
        int dsi_lanes, ret;
 
-       endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, 0);
-       dsi_lanes = of_property_count_u32_elems(endpoint, "data-lanes");
-       of_node_put(endpoint);
+       dsi_lanes = drm_of_get_data_lanes_count_ep(dev->of_node, 0, 0, 1, 4);
 
        /*
         * If the 'data-lanes' property does not exist in DT or is invalid,
         * default to previously hard-coded behavior, which was 4 data lanes.
         */
-       if (dsi_lanes >= 1 && dsi_lanes <= 4)
-               icn->dsi->lanes = dsi_lanes;
-       else
+       if (dsi_lanes < 0)
                icn->dsi->lanes = 4;
+       else
+               icn->dsi->lanes = dsi_lanes;
 
        dsi->format = MIPI_DSI_FMT_RGB888;
        dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
index c642d1e..c925158 100644 (file)
@@ -607,7 +607,6 @@ static int lt8912_parse_dt(struct lt8912 *lt)
        int ret;
        int data_lanes;
        struct device_node *port_node;
-       struct device_node *endpoint;
 
        gp_reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
        if (IS_ERR(gp_reset)) {
@@ -618,16 +617,12 @@ static int lt8912_parse_dt(struct lt8912 *lt)
        }
        lt->gp_reset = gp_reset;
 
-       endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1);
-       if (!endpoint)
-               return -ENODEV;
-
-       data_lanes = of_property_count_u32_elems(endpoint, "data-lanes");
-       of_node_put(endpoint);
+       data_lanes = drm_of_get_data_lanes_count_ep(dev->of_node, 0, -1, 1, 4);
        if (data_lanes < 0) {
                dev_err(lt->dev, "%s: Bad data-lanes property\n", __func__);
                return data_lanes;
        }
+
        lt->data_lanes = data_lanes;
 
        lt->host_node = of_graph_get_remote_node(dev->of_node, 0, -1);
index e92821f..84d764b 100644 (file)
@@ -686,7 +686,7 @@ static int lt9211_host_attach(struct lt9211 *ctx)
        int ret;
 
        endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1);
-       dsi_lanes = of_property_count_u32_elems(endpoint, "data-lanes");
+       dsi_lanes = drm_of_get_data_lanes_count(endpoint, 1, 4);
        host_node = of_graph_get_remote_port_parent(endpoint);
        host = of_find_mipi_dsi_host_by_node(host_node);
        of_node_put(host_node);
@@ -695,8 +695,8 @@ static int lt9211_host_attach(struct lt9211 *ctx)
        if (!host)
                return -EPROBE_DEFER;
 
-       if (dsi_lanes < 0 || dsi_lanes > 4)
-               return -EINVAL;
+       if (dsi_lanes < 0)
+               return dsi_lanes;
 
        dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
        if (IS_ERR(dsi))
index 6c61770..e4dd4f0 100644 (file)
@@ -1890,18 +1890,18 @@ static int tc_mipi_dsi_host_attach(struct tc_data *tc)
        int dsi_lanes, ret;
 
        endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1);
-       dsi_lanes = of_property_count_u32_elems(endpoint, "data-lanes");
+       dsi_lanes = drm_of_get_data_lanes_count(endpoint, 1, 4);
        host_node = of_graph_get_remote_port_parent(endpoint);
        host = of_find_mipi_dsi_host_by_node(host_node);
        of_node_put(host_node);
        of_node_put(endpoint);
 
-       if (dsi_lanes <= 0 || dsi_lanes > 4)
-               return -EINVAL;
-
        if (!host)
                return -EPROBE_DEFER;
 
+       if (dsi_lanes < 0)
+               return dsi_lanes;
+
        dsi = mipi_dsi_device_register_full(host, &info);
        if (IS_ERR(dsi))
                return dev_err_probe(dev, PTR_ERR(dsi),
index 62a7ef3..e5d00a6 100644 (file)
@@ -529,8 +529,7 @@ static int tc358775_parse_dt(struct device_node *np, struct tc_data *tc)
        struct device_node *endpoint;
        struct device_node *parent;
        struct device_node *remote;
-       struct property *prop;
-       int len = 0;
+       int dsi_lanes = -1;
 
        /*
         * To get the data-lanes of dsi, we need to access the dsi0_out of port1
@@ -544,25 +543,15 @@ static int tc358775_parse_dt(struct device_node *np, struct tc_data *tc)
                of_node_put(endpoint);
                if (parent) {
                        /* dsi0 port 1 */
-                       endpoint = of_graph_get_endpoint_by_regs(parent, 1, -1);
+                       dsi_lanes = drm_of_get_data_lanes_count_ep(parent, 1, -1, 1, 4);
                        of_node_put(parent);
-                       if (endpoint) {
-                               prop = of_find_property(endpoint, "data-lanes",
-                                                       &len);
-                               of_node_put(endpoint);
-                               if (!prop) {
-                                       dev_err(tc->dev,
-                                               "failed to find data lane\n");
-                                       return -EPROBE_DEFER;
-                               }
-                       }
                }
        }
 
-       tc->num_dsi_lanes = len / sizeof(u32);
+       if (dsi_lanes < 0)
+               return dsi_lanes;
 
-       if (tc->num_dsi_lanes < 1 || tc->num_dsi_lanes > 4)
-               return -EINVAL;
+       tc->num_dsi_lanes = dsi_lanes;
 
        tc->host_node = of_graph_get_remote_node(np, 0, 0);
        if (!tc->host_node)
index ac66f40..b27c0d7 100644 (file)
@@ -140,12 +140,10 @@ struct sn65dsi83 {
        struct drm_bridge               bridge;
        struct device                   *dev;
        struct regmap                   *regmap;
-       struct device_node              *host_node;
        struct mipi_dsi_device          *dsi;
        struct drm_bridge               *panel_bridge;
        struct gpio_desc                *enable_gpio;
        struct regulator                *vcc;
-       int                             dsi_lanes;
        bool                            lvds_dual_link;
        bool                            lvds_dual_link_even_odd_swap;
 };
@@ -306,7 +304,7 @@ static u8 sn65dsi83_get_dsi_range(struct sn65dsi83 *ctx,
         */
        return DIV_ROUND_UP(clamp((unsigned int)mode->clock *
                            mipi_dsi_pixel_format_to_bpp(ctx->dsi->format) /
-                           ctx->dsi_lanes / 2, 40000U, 500000U), 5000U);
+                           ctx->dsi->lanes / 2, 40000U, 500000U), 5000U);
 }
 
 static u8 sn65dsi83_get_dsi_div(struct sn65dsi83 *ctx)
@@ -314,7 +312,7 @@ static u8 sn65dsi83_get_dsi_div(struct sn65dsi83 *ctx)
        /* The divider is (DSI_CLK / LVDS_CLK) - 1, which really is: */
        unsigned int dsi_div = mipi_dsi_pixel_format_to_bpp(ctx->dsi->format);
 
-       dsi_div /= ctx->dsi_lanes;
+       dsi_div /= ctx->dsi->lanes;
 
        if (!ctx->lvds_dual_link)
                dsi_div /= 2;
@@ -405,7 +403,7 @@ static void sn65dsi83_atomic_enable(struct drm_bridge *bridge,
        /* Set number of DSI lanes and LVDS link config. */
        regmap_write(ctx->regmap, REG_DSI_LANE,
                     REG_DSI_LANE_DSI_CHANNEL_MODE_SINGLE |
-                    REG_DSI_LANE_CHA_DSI_LANES(~(ctx->dsi_lanes - 1)) |
+                    REG_DSI_LANE_CHA_DSI_LANES(~(ctx->dsi->lanes - 1)) |
                     /* CHB is DSI85-only, set to default on DSI83/DSI84 */
                     REG_DSI_LANE_CHB_DSI_LANES(3));
        /* No equalization. */
@@ -569,22 +567,6 @@ static int sn65dsi83_parse_dt(struct sn65dsi83 *ctx, enum sn65dsi83_model model)
 {
        struct drm_bridge *panel_bridge;
        struct device *dev = ctx->dev;
-       struct device_node *endpoint;
-       int ret;
-
-       endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, 0);
-       ctx->dsi_lanes = of_property_count_u32_elems(endpoint, "data-lanes");
-       ctx->host_node = of_graph_get_remote_port_parent(endpoint);
-       of_node_put(endpoint);
-
-       if (ctx->dsi_lanes <= 0 || ctx->dsi_lanes > 4) {
-               ret = -EINVAL;
-               goto err_put_node;
-       }
-       if (!ctx->host_node) {
-               ret = -ENODEV;
-               goto err_put_node;
-       }
 
        ctx->lvds_dual_link = false;
        ctx->lvds_dual_link_even_odd_swap = false;
@@ -610,10 +592,8 @@ static int sn65dsi83_parse_dt(struct sn65dsi83 *ctx, enum sn65dsi83_model model)
        }
 
        panel_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 2, 0);
-       if (IS_ERR(panel_bridge)) {
-               ret = PTR_ERR(panel_bridge);
-               goto err_put_node;
-       }
+       if (IS_ERR(panel_bridge))
+               return PTR_ERR(panel_bridge);
 
        ctx->panel_bridge = panel_bridge;
 
@@ -623,15 +603,13 @@ static int sn65dsi83_parse_dt(struct sn65dsi83 *ctx, enum sn65dsi83_model model)
                                     "Failed to get supply 'vcc'\n");
 
        return 0;
-
-err_put_node:
-       of_node_put(ctx->host_node);
-       return ret;
 }
 
 static int sn65dsi83_host_attach(struct sn65dsi83 *ctx)
 {
        struct device *dev = ctx->dev;
+       struct device_node *host_node;
+       struct device_node *endpoint;
        struct mipi_dsi_device *dsi;
        struct mipi_dsi_host *host;
        const struct mipi_dsi_device_info info = {
@@ -639,13 +617,20 @@ static int sn65dsi83_host_attach(struct sn65dsi83 *ctx)
                .channel = 0,
                .node = NULL,
        };
-       int ret;
+       int dsi_lanes, ret;
+
+       endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1);
+       dsi_lanes = drm_of_get_data_lanes_count(endpoint, 1, 4);
+       host_node = of_graph_get_remote_port_parent(endpoint);
+       host = of_find_mipi_dsi_host_by_node(host_node);
+       of_node_put(host_node);
+       of_node_put(endpoint);
 
-       host = of_find_mipi_dsi_host_by_node(ctx->host_node);
-       if (!host) {
-               dev_err(dev, "failed to find dsi host\n");
+       if (!host)
                return -EPROBE_DEFER;
-       }
+
+       if (dsi_lanes < 0)
+               return dsi_lanes;
 
        dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
        if (IS_ERR(dsi))
@@ -654,7 +639,7 @@ static int sn65dsi83_host_attach(struct sn65dsi83 *ctx)
 
        ctx->dsi = dsi;
 
-       dsi->lanes = ctx->dsi_lanes;
+       dsi->lanes = dsi_lanes;
        dsi->format = MIPI_DSI_FMT_RGB888;
        dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST;
 
@@ -701,10 +686,8 @@ static int sn65dsi83_probe(struct i2c_client *client,
                return ret;
 
        ctx->regmap = devm_regmap_init_i2c(client, &sn65dsi83_regmap_config);
-       if (IS_ERR(ctx->regmap)) {
-               ret = PTR_ERR(ctx->regmap);
-               goto err_put_node;
-       }
+       if (IS_ERR(ctx->regmap))
+               return PTR_ERR(ctx->regmap);
 
        dev_set_drvdata(dev, ctx);
        i2c_set_clientdata(client, ctx);
@@ -721,8 +704,6 @@ static int sn65dsi83_probe(struct i2c_client *client,
 
 err_remove_bridge:
        drm_bridge_remove(&ctx->bridge);
-err_put_node:
-       of_node_put(ctx->host_node);
        return ret;
 }
 
@@ -731,7 +712,6 @@ static int sn65dsi83_remove(struct i2c_client *client)
        struct sn65dsi83 *ctx = i2c_get_clientdata(client);
 
        drm_bridge_remove(&ctx->bridge);
-       of_node_put(ctx->host_node);
 
        return 0;
 }
index 8cad662..c2b9227 100644 (file)
@@ -1142,8 +1142,8 @@ static void ti_sn_bridge_parse_lanes(struct ti_sn65dsi86 *pdata,
         * mappings that the hardware supports.
         */
        endpoint = of_graph_get_endpoint_by_regs(np, 1, -1);
-       dp_lanes = of_property_count_u32_elems(endpoint, "data-lanes");
-       if (dp_lanes > 0 && dp_lanes <= SN_MAX_DP_LANES) {
+       dp_lanes = drm_of_get_data_lanes_count(endpoint, 1, SN_MAX_DP_LANES);
+       if (dp_lanes > 0) {
                of_property_read_u32_array(endpoint, "data-lanes",
                                           lane_assignments, dp_lanes);
                of_property_read_u32_array(endpoint, "lane-polarities",
index 67b3b96..18f2b60 100644 (file)
@@ -3860,9 +3860,7 @@ int drm_dp_mst_topology_mgr_resume(struct drm_dp_mst_topology_mgr *mgr,
        if (!mgr->mst_primary)
                goto out_fail;
 
-       ret = drm_dp_dpcd_read(mgr->aux, DP_DPCD_REV, mgr->dpcd,
-                              DP_RECEIVER_CAP_SIZE);
-       if (ret != DP_RECEIVER_CAP_SIZE) {
+       if (drm_dp_read_dpcd_caps(mgr->aux, mgr->dpcd) < 0) {
                drm_dbg_kms(mgr->dev, "dpcd read failed - undocked during suspend?\n");
                goto out_fail;
        }
@@ -4911,8 +4909,7 @@ void drm_dp_mst_dump_topology(struct seq_file *m,
                u8 buf[DP_PAYLOAD_TABLE_SIZE];
                int ret;
 
-               ret = drm_dp_dpcd_read(mgr->aux, DP_DPCD_REV, buf, DP_RECEIVER_CAP_SIZE);
-               if (ret) {
+               if (drm_dp_read_dpcd_caps(mgr->aux, buf) < 0) {
                        seq_printf(m, "dpcd read failed\n");
                        goto out;
                }
index 929fc0e..2bdaf1e 100644 (file)
@@ -1613,6 +1613,35 @@ static const void *edid_extension_block_data(const struct edid *edid, int index)
        return edid_block_data(edid, index + 1);
 }
 
+static int drm_edid_block_count(const struct drm_edid *drm_edid)
+{
+       int num_blocks;
+
+       /* Starting point */
+       num_blocks = edid_block_count(drm_edid->edid);
+
+       /* Limit by allocated size */
+       num_blocks = min(num_blocks, (int)drm_edid->size / EDID_LENGTH);
+
+       return num_blocks;
+}
+
+static int drm_edid_extension_block_count(const struct drm_edid *drm_edid)
+{
+       return drm_edid_block_count(drm_edid) - 1;
+}
+
+static const void *drm_edid_block_data(const struct drm_edid *drm_edid, int index)
+{
+       return edid_block_data(drm_edid->edid, index);
+}
+
+static const void *drm_edid_extension_block_data(const struct drm_edid *drm_edid,
+                                                int index)
+{
+       return edid_extension_block_data(drm_edid->edid, index);
+}
+
 /*
  * Initializer helper for legacy interfaces, where we have no choice but to
  * trust edid size. Not for general purpose use.
@@ -1665,8 +1694,8 @@ static const void *__drm_edid_iter_next(struct drm_edid_iter *iter)
        if (!iter->drm_edid)
                return NULL;
 
-       if (iter->index < edid_block_count(iter->drm_edid->edid))
-               block = edid_block_data(iter->drm_edid->edid, iter->index++);
+       if (iter->index < drm_edid_block_count(iter->drm_edid))
+               block = drm_edid_block_data(iter->drm_edid, iter->index++);
 
        return block;
 }
@@ -1992,13 +2021,16 @@ bool drm_edid_is_valid(struct edid *edid)
 EXPORT_SYMBOL(drm_edid_is_valid);
 
 static struct edid *edid_filter_invalid_blocks(const struct edid *edid,
-                                              int invalid_blocks)
+                                              int invalid_blocks,
+                                              size_t *alloc_size)
 {
        struct edid *new, *dest_block;
        int valid_extensions = edid->extensions - invalid_blocks;
        int i;
 
-       new = kmalloc(edid_size_by_blocks(valid_extensions + 1), GFP_KERNEL);
+       *alloc_size = edid_size_by_blocks(valid_extensions + 1);
+
+       new = kmalloc(*alloc_size, GFP_KERNEL);
        if (!new)
                goto out;
 
@@ -2111,7 +2143,8 @@ static void connector_bad_edid(struct drm_connector *connector,
 }
 
 /* Get override or firmware EDID */
-static struct edid *drm_get_override_edid(struct drm_connector *connector)
+static struct edid *drm_get_override_edid(struct drm_connector *connector,
+                                         size_t *alloc_size)
 {
        struct edid *override = NULL;
 
@@ -2121,6 +2154,10 @@ static struct edid *drm_get_override_edid(struct drm_connector *connector)
        if (!override)
                override = drm_load_edid_firmware(connector);
 
+       /* FIXME: Get alloc size from deeper down the stack */
+       if (!IS_ERR_OR_NULL(override) && alloc_size)
+               *alloc_size = edid_size(override);
+
        return IS_ERR(override) ? NULL : override;
 }
 
@@ -2140,7 +2177,7 @@ int drm_add_override_edid_modes(struct drm_connector *connector)
        struct edid *override;
        int num_modes = 0;
 
-       override = drm_get_override_edid(connector);
+       override = drm_get_override_edid(connector, NULL);
        if (override) {
                drm_connector_update_edid_property(connector, override);
                num_modes = drm_add_edid_modes(connector, override);
@@ -2189,39 +2226,20 @@ static enum edid_block_status edid_block_read(void *block, unsigned int block_nu
        return status;
 }
 
-/**
- * drm_do_get_edid - get EDID data using a custom EDID block read function
- * @connector: connector we're probing
- * @read_block: EDID block read function
- * @context: private data passed to the block read function
- *
- * When the I2C adapter connected to the DDC bus is hidden behind a device that
- * exposes a different interface to read EDID blocks this function can be used
- * to get EDID data using a custom block read function.
- *
- * As in the general case the DDC bus is accessible by the kernel at the I2C
- * level, drivers must make all reasonable efforts to expose it as an I2C
- * adapter and use drm_get_edid() instead of abusing this function.
- *
- * The EDID may be overridden using debugfs override_edid or firmware EDID
- * (drm_load_edid_firmware() and drm.edid_firmware parameter), in this priority
- * order. Having either of them bypasses actual EDID reads.
- *
- * Return: Pointer to valid EDID or NULL if we couldn't find any.
- */
-struct edid *drm_do_get_edid(struct drm_connector *connector,
-                            read_block_fn read_block,
-                            void *context)
+static struct edid *_drm_do_get_edid(struct drm_connector *connector,
+                                    read_block_fn read_block, void *context,
+                                    size_t *size)
 {
        enum edid_block_status status;
        int i, invalid_blocks = 0;
        struct edid *edid, *new;
+       size_t alloc_size = EDID_LENGTH;
 
-       edid = drm_get_override_edid(connector);
+       edid = drm_get_override_edid(connector, &alloc_size);
        if (edid)
                goto ok;
 
-       edid = kmalloc(EDID_LENGTH, GFP_KERNEL);
+       edid = kmalloc(alloc_size, GFP_KERNEL);
        if (!edid)
                return NULL;
 
@@ -2249,7 +2267,8 @@ struct edid *drm_do_get_edid(struct drm_connector *connector,
        if (!edid_extension_block_count(edid))
                goto ok;
 
-       new = krealloc(edid, edid_size(edid), GFP_KERNEL);
+       alloc_size = edid_size(edid);
+       new = krealloc(edid, alloc_size, GFP_KERNEL);
        if (!new)
                goto fail;
        edid = new;
@@ -2271,18 +2290,130 @@ struct edid *drm_do_get_edid(struct drm_connector *connector,
        if (invalid_blocks) {
                connector_bad_edid(connector, edid, edid_block_count(edid));
 
-               edid = edid_filter_invalid_blocks(edid, invalid_blocks);
+               edid = edid_filter_invalid_blocks(edid, invalid_blocks,
+                                                 &alloc_size);
        }
 
 ok:
+       if (size)
+               *size = alloc_size;
+
        return edid;
 
 fail:
        kfree(edid);
        return NULL;
 }
+
+/**
+ * drm_do_get_edid - get EDID data using a custom EDID block read function
+ * @connector: connector we're probing
+ * @read_block: EDID block read function
+ * @context: private data passed to the block read function
+ *
+ * When the I2C adapter connected to the DDC bus is hidden behind a device that
+ * exposes a different interface to read EDID blocks this function can be used
+ * to get EDID data using a custom block read function.
+ *
+ * As in the general case the DDC bus is accessible by the kernel at the I2C
+ * level, drivers must make all reasonable efforts to expose it as an I2C
+ * adapter and use drm_get_edid() instead of abusing this function.
+ *
+ * The EDID may be overridden using debugfs override_edid or firmware EDID
+ * (drm_load_edid_firmware() and drm.edid_firmware parameter), in this priority
+ * order. Having either of them bypasses actual EDID reads.
+ *
+ * Return: Pointer to valid EDID or NULL if we couldn't find any.
+ */
+struct edid *drm_do_get_edid(struct drm_connector *connector,
+                            read_block_fn read_block,
+                            void *context)
+{
+       return _drm_do_get_edid(connector, read_block, context, NULL);
+}
 EXPORT_SYMBOL_GPL(drm_do_get_edid);
 
+/* Allocate struct drm_edid container *without* duplicating the edid data */
+static const struct drm_edid *_drm_edid_alloc(const void *edid, size_t size)
+{
+       struct drm_edid *drm_edid;
+
+       if (!edid || !size || size < EDID_LENGTH)
+               return NULL;
+
+       drm_edid = kzalloc(sizeof(*drm_edid), GFP_KERNEL);
+       if (drm_edid) {
+               drm_edid->edid = edid;
+               drm_edid->size = size;
+       }
+
+       return drm_edid;
+}
+
+/**
+ * drm_edid_alloc - Allocate a new drm_edid container
+ * @edid: Pointer to raw EDID data
+ * @size: Size of memory allocated for EDID
+ *
+ * Allocate a new drm_edid container. Do not calculate edid size from edid, pass
+ * the actual size that has been allocated for the data. There is no validation
+ * of the raw EDID data against the size, but at least the EDID base block must
+ * fit in the buffer.
+ *
+ * The returned pointer must be freed using drm_edid_free().
+ *
+ * Return: drm_edid container, or NULL on errors
+ */
+const struct drm_edid *drm_edid_alloc(const void *edid, size_t size)
+{
+       const struct drm_edid *drm_edid;
+
+       if (!edid || !size || size < EDID_LENGTH)
+               return NULL;
+
+       edid = kmemdup(edid, size, GFP_KERNEL);
+       if (!edid)
+               return NULL;
+
+       drm_edid = _drm_edid_alloc(edid, size);
+       if (!drm_edid)
+               kfree(edid);
+
+       return drm_edid;
+}
+EXPORT_SYMBOL(drm_edid_alloc);
+
+/**
+ * drm_edid_dup - Duplicate a drm_edid container
+ * @drm_edid: EDID to duplicate
+ *
+ * The returned pointer must be freed using drm_edid_free().
+ *
+ * Returns: drm_edid container copy, or NULL on errors
+ */
+const struct drm_edid *drm_edid_dup(const struct drm_edid *drm_edid)
+{
+       if (!drm_edid)
+               return NULL;
+
+       return drm_edid_alloc(drm_edid->edid, drm_edid->size);
+}
+EXPORT_SYMBOL(drm_edid_dup);
+
+/**
+ * drm_edid_free - Free the drm_edid container
+ * @drm_edid: EDID to free
+ */
+void drm_edid_free(const struct drm_edid *drm_edid)
+{
+       if (!drm_edid)
+               return;
+
+       kfree(drm_edid->edid);
+       kfree(drm_edid);
+}
+EXPORT_SYMBOL(drm_edid_free);
+
 /**
  * drm_probe_ddc() - probe DDC presence
  * @adapter: I2C adapter to probe
@@ -2319,12 +2450,118 @@ struct edid *drm_get_edid(struct drm_connector *connector,
        if (connector->force == DRM_FORCE_UNSPECIFIED && !drm_probe_ddc(adapter))
                return NULL;
 
-       edid = drm_do_get_edid(connector, drm_do_probe_ddc_edid, adapter);
+       edid = _drm_do_get_edid(connector, drm_do_probe_ddc_edid, adapter, NULL);
        drm_connector_update_edid_property(connector, edid);
        return edid;
 }
 EXPORT_SYMBOL(drm_get_edid);
 
+/**
+ * drm_edid_read_custom - Read EDID data using given EDID block read function
+ * @connector: Connector to use
+ * @read_block: EDID block read function
+ * @context: Private data passed to the block read function
+ *
+ * When the I2C adapter connected to the DDC bus is hidden behind a device that
+ * exposes a different interface to read EDID blocks this function can be used
+ * to get EDID data using a custom block read function.
+ *
+ * As in the general case the DDC bus is accessible by the kernel at the I2C
+ * level, drivers must make all reasonable efforts to expose it as an I2C
+ * adapter and use drm_edid_read() or drm_edid_read_ddc() instead of abusing
+ * this function.
+ *
+ * The EDID may be overridden using debugfs override_edid or firmware EDID
+ * (drm_load_edid_firmware() and drm.edid_firmware parameter), in this priority
+ * order. Having either of them bypasses actual EDID reads.
+ *
+ * The returned pointer must be freed using drm_edid_free().
+ *
+ * Return: Pointer to EDID, or NULL if probe/read failed.
+ */
+const struct drm_edid *drm_edid_read_custom(struct drm_connector *connector,
+                                           read_block_fn read_block,
+                                           void *context)
+{
+       const struct drm_edid *drm_edid;
+       struct edid *edid;
+       size_t size = 0;
+
+       edid = _drm_do_get_edid(connector, read_block, context, &size);
+       if (!edid)
+               return NULL;
+
+       /* Sanity check for now */
+       drm_WARN_ON(connector->dev, !size);
+
+       drm_edid = _drm_edid_alloc(edid, size);
+       if (!drm_edid)
+               kfree(edid);
+
+       return drm_edid;
+}
+EXPORT_SYMBOL(drm_edid_read_custom);
+
+/**
+ * drm_edid_read_ddc - Read EDID data using given I2C adapter
+ * @connector: Connector to use
+ * @adapter: I2C adapter to use for DDC
+ *
+ * Read EDID using the given I2C adapter.
+ *
+ * The EDID may be overridden using debugfs override_edid or firmware EDID
+ * (drm_load_edid_firmware() and drm.edid_firmware parameter), in this priority
+ * order. Having either of them bypasses actual EDID reads.
+ *
+ * Prefer initializing connector->ddc with drm_connector_init_with_ddc() and
+ * using drm_edid_read() instead of this function.
+ *
+ * The returned pointer must be freed using drm_edid_free().
+ *
+ * Return: Pointer to EDID, or NULL if probe/read failed.
+ */
+const struct drm_edid *drm_edid_read_ddc(struct drm_connector *connector,
+                                        struct i2c_adapter *adapter)
+{
+       const struct drm_edid *drm_edid;
+
+       if (connector->force == DRM_FORCE_OFF)
+               return NULL;
+
+       if (connector->force == DRM_FORCE_UNSPECIFIED && !drm_probe_ddc(adapter))
+               return NULL;
+
+       drm_edid = drm_edid_read_custom(connector, drm_do_probe_ddc_edid, adapter);
+
+       /* Note: Do *not* call connector updates here. */
+
+       return drm_edid;
+}
+EXPORT_SYMBOL(drm_edid_read_ddc);
+
+/**
+ * drm_edid_read - Read EDID data using connector's I2C adapter
+ * @connector: Connector to use
+ *
+ * Read EDID using the connector's I2C adapter.
+ *
+ * The EDID may be overridden using debugfs override_edid or firmware EDID
+ * (drm_load_edid_firmware() and drm.edid_firmware parameter), in this priority
+ * order. Having either of them bypasses actual EDID reads.
+ *
+ * The returned pointer must be freed using drm_edid_free().
+ *
+ * Return: Pointer to EDID, or NULL if probe/read failed.
+ */
+const struct drm_edid *drm_edid_read(struct drm_connector *connector)
+{
+       if (drm_WARN_ON(connector->dev, !connector->ddc))
+               return NULL;
+
+       return drm_edid_read_ddc(connector, connector->ddc);
+}
+EXPORT_SYMBOL(drm_edid_read);
+
 static u32 edid_extract_panel_id(const struct edid *edid)
 {
        /*
@@ -3574,22 +3811,21 @@ static int add_detailed_modes(struct drm_connector *connector,
 const u8 *drm_find_edid_extension(const struct drm_edid *drm_edid,
                                  int ext_id, int *ext_index)
 {
-       const struct edid *edid = drm_edid ? drm_edid->edid : NULL;
        const u8 *edid_ext = NULL;
        int i;
 
        /* No EDID or EDID extensions */
-       if (!edid || !edid_extension_block_count(edid))
+       if (!drm_edid || !drm_edid_extension_block_count(drm_edid))
                return NULL;
 
        /* Find CEA extension */
-       for (i = *ext_index; i < edid_extension_block_count(edid); i++) {
-               edid_ext = edid_extension_block_data(edid, i);
+       for (i = *ext_index; i < drm_edid_extension_block_count(drm_edid); i++) {
+               edid_ext = drm_edid_extension_block_data(drm_edid, i);
                if (edid_block_tag(edid_ext) == ext_id)
                        break;
        }
 
-       if (i >= edid_extension_block_count(edid))
+       if (i >= drm_edid_extension_block_count(drm_edid))
                return NULL;
 
        *ext_index = i + 1;
@@ -4482,6 +4718,20 @@ __cea_db_iter_current_block(const struct cea_db_iter *iter)
        return NULL;
 }
 
+/*
+ * References:
+ * - CTA-861-H section 7.3.3 CTA Extension Version 3
+ */
+static int cea_db_collection_size(const u8 *cta)
+{
+       u8 d = cta[2];
+
+       if (d < 4 || d > 127)
+               return 0;
+
+       return d - 4;
+}
+
 /*
  * References:
  * - VESA E-EDID v1.4
@@ -4492,17 +4742,19 @@ static const void *__cea_db_iter_edid_next(struct cea_db_iter *iter)
        const u8 *ext;
 
        drm_edid_iter_for_each(ext, &iter->edid_iter) {
+               int size;
+
                /* Only support CTA Extension revision 3+ */
                if (ext[0] != CEA_EXT || cea_revision(ext) < 3)
                        continue;
 
-               iter->index = 4;
-               iter->end = ext[2];
-               if (iter->end == 0)
-                       iter->end = 127;
-               if (iter->end < 4 || iter->end > 127)
+               size = cea_db_collection_size(ext);
+               if (!size)
                        continue;
 
+               iter->index = 4;
+               iter->end = iter->index + size;
+
                return ext;
        }
 
index f36734c..42abee9 100644 (file)
 /**
  * DOC: cma helpers
  *
- * The Contiguous Memory Allocator reserves a pool of memory at early boot
- * that is used to service requests for large blocks of contiguous memory.
+ * The DRM GEM/CMA helpers are a means to provide buffer objects that are
+ * presented to the device as a contiguous chunk of memory. This is useful
+ * for devices that do not support scatter-gather DMA (either directly or
+ * by using an intimately attached IOMMU).
  *
- * The DRM GEM/CMA helpers use this allocator as a means to provide buffer
- * objects that are physically contiguous in memory. This is useful for
- * display drivers that are unable to map scattered buffers via an IOMMU.
+ * Despite the name, the DRM GEM/CMA helpers are not hardwired to use the
+ * Contiguous Memory Allocator (CMA).
+ *
+ * For devices that access the memory bus through an (external) IOMMU then
+ * the buffer objects are allocated using a traditional page-based
+ * allocator and may be scattered through physical memory. However they
+ * are contiguous in the IOVA space so appear contiguous to devices using
+ * them.
+ *
+ * For other devices then the helpers rely on CMA to provide buffer
+ * objects that are physically contiguous in memory.
  *
  * For GEM callback helpers in struct &drm_gem_object functions, see likewise
  * named functions with an _object_ infix (e.g., drm_gem_cma_object_vmap() wraps
@@ -111,8 +121,14 @@ error:
  * @drm: DRM device
  * @size: size of the object to allocate
  *
- * This function creates a CMA GEM object and allocates a contiguous chunk of
- * memory as backing store.
+ * This function creates a CMA GEM object and allocates memory as backing store.
+ * The allocated memory will occupy a contiguous chunk of bus address space.
+ *
+ * For devices that are directly connected to the memory bus then the allocated
+ * memory will be physically contiguous. For devices that access through an
+ * IOMMU, then the allocated memory is not expected to be physically contiguous
+ * because having contiguous IOVAs is sufficient to meet a devices DMA
+ * requirements.
  *
  * Returns:
  * A struct drm_gem_cma_object * on success or an ERR_PTR()-encoded negative
@@ -162,9 +178,12 @@ EXPORT_SYMBOL_GPL(drm_gem_cma_create);
  * @size: size of the object to allocate
  * @handle: return location for the GEM handle
  *
- * This function creates a CMA GEM object, allocating a physically contiguous
- * chunk of memory as backing store. The GEM object is then added to the list
- * of object associated with the given file and a handle to it is returned.
+ * This function creates a CMA GEM object, allocating a chunk of memory as
+ * backing store. The GEM object is then added to the list of object associated
+ * with the given file and a handle to it is returned.
+ *
+ * The allocated memory will occupy a contiguous chunk of bus address space.
+ * See drm_gem_cma_create() for more details.
  *
  * Returns:
  * A struct drm_gem_cma_object * on success or an ERR_PTR()-encoded negative
index 9a2cfab..2c1ee60 100644 (file)
@@ -430,3 +430,64 @@ int drm_of_lvds_get_data_mapping(const struct device_node *port)
        return -EINVAL;
 }
 EXPORT_SYMBOL_GPL(drm_of_lvds_get_data_mapping);
+
+/**
+ * drm_of_get_data_lanes_count - Get DSI/(e)DP data lane count
+ * @endpoint: DT endpoint node of the DSI/(e)DP source or sink
+ * @min: minimum supported number of data lanes
+ * @max: maximum supported number of data lanes
+ *
+ * Count DT "data-lanes" property elements and check for validity.
+ *
+ * Return:
+ * * min..max - positive integer count of "data-lanes" elements
+ * * -ve - the "data-lanes" property is missing or invalid
+ * * -EINVAL - the "data-lanes" property is unsupported
+ */
+int drm_of_get_data_lanes_count(const struct device_node *endpoint,
+                               const unsigned int min, const unsigned int max)
+{
+       int ret;
+
+       ret = of_property_count_u32_elems(endpoint, "data-lanes");
+       if (ret < 0)
+               return ret;
+
+       if (ret < min || ret > max)
+               return -EINVAL;
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(drm_of_get_data_lanes_count);
+
+/**
+ * drm_of_get_data_lanes_count_ep - Get DSI/(e)DP data lane count by endpoint
+ * @port: DT port node of the DSI/(e)DP source or sink
+ * @port_reg: identifier (value of reg property) of the parent port node
+ * @reg: identifier (value of reg property) of the endpoint node
+ * @min: minimum supported number of data lanes
+ * @max: maximum supported number of data lanes
+ *
+ * Count DT "data-lanes" property elements and check for validity.
+ * This variant uses endpoint specifier.
+ *
+ * Return:
+ * * min..max - positive integer count of "data-lanes" elements
+ * * -EINVAL - the "data-mapping" property is unsupported
+ * * -ENODEV - the "data-mapping" property is missing
+ */
+int drm_of_get_data_lanes_count_ep(const struct device_node *port,
+                                  int port_reg, int reg,
+                                  const unsigned int min,
+                                  const unsigned int max)
+{
+       struct device_node *endpoint;
+       int ret;
+
+       endpoint = of_graph_get_endpoint_by_regs(port, port_reg, reg);
+       ret = drm_of_get_data_lanes_count(endpoint, min, max);
+       of_node_put(endpoint);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(drm_of_get_data_lanes_count_ep);
index 75a7164..a8d26b2 100644 (file)
@@ -354,6 +354,24 @@ drm_helper_probe_detect(struct drm_connector *connector,
 }
 EXPORT_SYMBOL(drm_helper_probe_detect);
 
+static int drm_helper_probe_get_modes(struct drm_connector *connector)
+{
+       const struct drm_connector_helper_funcs *connector_funcs =
+               connector->helper_private;
+       int count;
+
+       count = connector_funcs->get_modes(connector);
+
+       /*
+        * Fallback for when DDC probe failed in drm_get_edid() and thus skipped
+        * override/firmware EDID.
+        */
+       if (count == 0 && connector->status == connector_status_connected)
+               count = drm_add_override_edid_modes(connector);
+
+       return count;
+}
+
 static int __drm_helper_update_and_validate(struct drm_connector *connector,
                                            uint32_t maxX, uint32_t maxY,
                                            struct drm_modeset_acquire_ctx *ctx)
@@ -473,8 +491,6 @@ int drm_helper_probe_single_connector_modes(struct drm_connector *connector,
 {
        struct drm_device *dev = connector->dev;
        struct drm_display_mode *mode;
-       const struct drm_connector_helper_funcs *connector_funcs =
-               connector->helper_private;
        int count = 0, ret;
        enum drm_connector_status old_status;
        struct drm_modeset_acquire_ctx ctx;
@@ -559,14 +575,7 @@ retry:
                goto exit;
        }
 
-       count = (*connector_funcs->get_modes)(connector);
-
-       /*
-        * Fallback for when DDC probe failed in drm_get_edid() and thus skipped
-        * override/firmware EDID.
-        */
-       if (count == 0 && connector->status == connector_status_connected)
-               count = drm_add_override_edid_modes(connector);
+       count = drm_helper_probe_get_modes(connector);
 
        if (count == 0 && (connector->status == connector_status_connected ||
                           connector->status == connector_status_unknown)) {
index bbad9e4..0c2be83 100644 (file)
@@ -874,8 +874,10 @@ static int drm_syncobj_transfer_to_timeline(struct drm_file *file_private,
 
        fence = dma_fence_unwrap_merge(tmp);
        dma_fence_put(tmp);
-       if (!fence)
+       if (!fence) {
+               ret = -ENOMEM;
                goto err_put_timeline;
+       }
 
        chain = dma_fence_chain_alloc();
        if (!chain) {
diff --git a/drivers/gpu/drm/logicvc/Kconfig b/drivers/gpu/drm/logicvc/Kconfig
new file mode 100644 (file)
index 0000000..300b2be
--- /dev/null
@@ -0,0 +1,9 @@
+config DRM_LOGICVC
+       tristate "LogiCVC DRM"
+       depends on DRM
+       depends on OF || COMPILE_TEST
+       select DRM_KMS_HELPER
+       select DRM_KMS_CMA_HELPER
+       select DRM_GEM_CMA_HELPER
+       help
+         DRM display driver for the logiCVC programmable logic block from Xylon
diff --git a/drivers/gpu/drm/logicvc/Makefile b/drivers/gpu/drm/logicvc/Makefile
new file mode 100644 (file)
index 0000000..6e4b019
--- /dev/null
@@ -0,0 +1,9 @@
+logicvc-drm-y += \
+       logicvc_crtc.o \
+       logicvc_drm.o \
+       logicvc_interface.o \
+       logicvc_layer.o \
+       logicvc_mode.o \
+       logicvc_of.o
+
+obj-$(CONFIG_DRM_LOGICVC) += logicvc-drm.o
diff --git a/drivers/gpu/drm/logicvc/logicvc_crtc.c b/drivers/gpu/drm/logicvc/logicvc_crtc.c
new file mode 100644 (file)
index 0000000..c94bb9b
--- /dev/null
@@ -0,0 +1,280 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2019-2022 Bootlin
+ * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
+ */
+
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_print.h>
+#include <drm/drm_vblank.h>
+
+#include "logicvc_crtc.h"
+#include "logicvc_drm.h"
+#include "logicvc_interface.h"
+#include "logicvc_layer.h"
+#include "logicvc_regs.h"
+
+#define logicvc_crtc(c) \
+       container_of(c, struct logicvc_crtc, drm_crtc)
+
+static enum drm_mode_status
+logicvc_crtc_mode_valid(struct drm_crtc *drm_crtc,
+                       const struct drm_display_mode *mode)
+{
+       if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+               return -EINVAL;
+
+       return 0;
+}
+
+static void logicvc_crtc_atomic_begin(struct drm_crtc *drm_crtc,
+                                     struct drm_atomic_state *state)
+{
+       struct logicvc_crtc *crtc = logicvc_crtc(drm_crtc);
+       struct drm_crtc_state *old_state =
+               drm_atomic_get_old_crtc_state(state, drm_crtc);
+       struct drm_device *drm_dev = drm_crtc->dev;
+       unsigned long flags;
+
+       /*
+        * We need to grab the pending event here if vblank was already enabled
+        * since we won't get a call to atomic_enable to grab it.
+        */
+       if (drm_crtc->state->event && old_state->active) {
+               spin_lock_irqsave(&drm_dev->event_lock, flags);
+               WARN_ON(drm_crtc_vblank_get(drm_crtc) != 0);
+
+               crtc->event = drm_crtc->state->event;
+               drm_crtc->state->event = NULL;
+
+               spin_unlock_irqrestore(&drm_dev->event_lock, flags);
+       }
+}
+
+static void logicvc_crtc_atomic_enable(struct drm_crtc *drm_crtc,
+                                      struct drm_atomic_state *state)
+{
+       struct logicvc_crtc *crtc = logicvc_crtc(drm_crtc);
+       struct logicvc_drm *logicvc = logicvc_drm(drm_crtc->dev);
+       struct drm_crtc_state *old_state =
+               drm_atomic_get_old_crtc_state(state, drm_crtc);
+       struct drm_crtc_state *new_state =
+               drm_atomic_get_new_crtc_state(state, drm_crtc);
+       struct drm_display_mode *mode = &new_state->adjusted_mode;
+
+       struct drm_device *drm_dev = drm_crtc->dev;
+       unsigned int hact, hfp, hsl, hbp;
+       unsigned int vact, vfp, vsl, vbp;
+       unsigned long flags;
+       u32 ctrl;
+
+       /* Timings */
+
+       hact = mode->hdisplay;
+       hfp = mode->hsync_start - mode->hdisplay;
+       hsl = mode->hsync_end - mode->hsync_start;
+       hbp = mode->htotal - mode->hsync_end;
+
+       vact = mode->vdisplay;
+       vfp = mode->vsync_start - mode->vdisplay;
+       vsl = mode->vsync_end - mode->vsync_start;
+       vbp = mode->vtotal - mode->vsync_end;
+
+       regmap_write(logicvc->regmap, LOGICVC_HSYNC_FRONT_PORCH_REG, hfp - 1);
+       regmap_write(logicvc->regmap, LOGICVC_HSYNC_REG, hsl - 1);
+       regmap_write(logicvc->regmap, LOGICVC_HSYNC_BACK_PORCH_REG, hbp - 1);
+       regmap_write(logicvc->regmap, LOGICVC_HRES_REG, hact - 1);
+
+       regmap_write(logicvc->regmap, LOGICVC_VSYNC_FRONT_PORCH_REG, vfp - 1);
+       regmap_write(logicvc->regmap, LOGICVC_VSYNC_REG, vsl - 1);
+       regmap_write(logicvc->regmap, LOGICVC_VSYNC_BACK_PORCH_REG, vbp - 1);
+       regmap_write(logicvc->regmap, LOGICVC_VRES_REG, vact - 1);
+
+       /* Signals */
+
+       ctrl = LOGICVC_CTRL_HSYNC_ENABLE | LOGICVC_CTRL_VSYNC_ENABLE |
+              LOGICVC_CTRL_DE_ENABLE;
+
+       if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+               ctrl |= LOGICVC_CTRL_HSYNC_INVERT;
+
+       if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+               ctrl |= LOGICVC_CTRL_VSYNC_INVERT;
+
+       if (logicvc->interface) {
+               struct drm_connector *connector =
+                       &logicvc->interface->drm_connector;
+               struct drm_display_info *display_info =
+                       &connector->display_info;
+
+               if (display_info->bus_flags & DRM_BUS_FLAG_DE_LOW)
+                       ctrl |= LOGICVC_CTRL_DE_INVERT;
+
+               if (display_info->bus_flags &
+                   DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE)
+                       ctrl |= LOGICVC_CTRL_CLOCK_INVERT;
+       }
+
+       regmap_update_bits(logicvc->regmap, LOGICVC_CTRL_REG,
+                          LOGICVC_CTRL_HSYNC_ENABLE |
+                          LOGICVC_CTRL_HSYNC_INVERT |
+                          LOGICVC_CTRL_VSYNC_ENABLE |
+                          LOGICVC_CTRL_VSYNC_INVERT |
+                          LOGICVC_CTRL_DE_ENABLE |
+                          LOGICVC_CTRL_DE_INVERT |
+                          LOGICVC_CTRL_PIXEL_INVERT |
+                          LOGICVC_CTRL_CLOCK_INVERT, ctrl);
+
+       /* Generate internal state reset. */
+       regmap_write(logicvc->regmap, LOGICVC_DTYPE_REG, 0);
+
+       drm_crtc_vblank_on(drm_crtc);
+
+       /* Register our event after vblank is enabled. */
+       if (drm_crtc->state->event && !old_state->active) {
+               spin_lock_irqsave(&drm_dev->event_lock, flags);
+               WARN_ON(drm_crtc_vblank_get(drm_crtc) != 0);
+
+               crtc->event = drm_crtc->state->event;
+               drm_crtc->state->event = NULL;
+               spin_unlock_irqrestore(&drm_dev->event_lock, flags);
+       }
+}
+
+static void logicvc_crtc_atomic_disable(struct drm_crtc *drm_crtc,
+                                       struct drm_atomic_state *state)
+{
+       struct logicvc_drm *logicvc = logicvc_drm(drm_crtc->dev);
+       struct drm_device *drm_dev = drm_crtc->dev;
+
+       drm_crtc_vblank_off(drm_crtc);
+
+       /* Disable and clear CRTC bits. */
+       regmap_update_bits(logicvc->regmap, LOGICVC_CTRL_REG,
+                          LOGICVC_CTRL_HSYNC_ENABLE |
+                          LOGICVC_CTRL_HSYNC_INVERT |
+                          LOGICVC_CTRL_VSYNC_ENABLE |
+                          LOGICVC_CTRL_VSYNC_INVERT |
+                          LOGICVC_CTRL_DE_ENABLE |
+                          LOGICVC_CTRL_DE_INVERT |
+                          LOGICVC_CTRL_PIXEL_INVERT |
+                          LOGICVC_CTRL_CLOCK_INVERT, 0);
+
+       /* Generate internal state reset. */
+       regmap_write(logicvc->regmap, LOGICVC_DTYPE_REG, 0);
+
+       /* Consume any leftover event since vblank is now disabled. */
+       if (drm_crtc->state->event && !drm_crtc->state->active) {
+               spin_lock_irq(&drm_dev->event_lock);
+
+               drm_crtc_send_vblank_event(drm_crtc, drm_crtc->state->event);
+               drm_crtc->state->event = NULL;
+               spin_unlock_irq(&drm_dev->event_lock);
+       }
+}
+
+static const struct drm_crtc_helper_funcs logicvc_crtc_helper_funcs = {
+       .mode_valid             = logicvc_crtc_mode_valid,
+       .atomic_begin           = logicvc_crtc_atomic_begin,
+       .atomic_enable          = logicvc_crtc_atomic_enable,
+       .atomic_disable         = logicvc_crtc_atomic_disable,
+};
+
+static int logicvc_crtc_enable_vblank(struct drm_crtc *drm_crtc)
+{
+       struct logicvc_drm *logicvc = logicvc_drm(drm_crtc->dev);
+
+       /* Clear any pending V_SYNC interrupt. */
+       regmap_write_bits(logicvc->regmap, LOGICVC_INT_STAT_REG,
+                         LOGICVC_INT_STAT_V_SYNC, LOGICVC_INT_STAT_V_SYNC);
+
+       /* Unmask V_SYNC interrupt. */
+       regmap_write_bits(logicvc->regmap, LOGICVC_INT_MASK_REG,
+                         LOGICVC_INT_MASK_V_SYNC, 0);
+
+       return 0;
+}
+
+static void logicvc_crtc_disable_vblank(struct drm_crtc *drm_crtc)
+{
+       struct logicvc_drm *logicvc = logicvc_drm(drm_crtc->dev);
+
+       /* Mask V_SYNC interrupt. */
+       regmap_write_bits(logicvc->regmap, LOGICVC_INT_MASK_REG,
+                         LOGICVC_INT_MASK_V_SYNC, LOGICVC_INT_MASK_V_SYNC);
+}
+
+static const struct drm_crtc_funcs logicvc_crtc_funcs = {
+       .reset                  = drm_atomic_helper_crtc_reset,
+       .destroy                = drm_crtc_cleanup,
+       .set_config             = drm_atomic_helper_set_config,
+       .page_flip              = drm_atomic_helper_page_flip,
+       .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+       .atomic_destroy_state   = drm_atomic_helper_crtc_destroy_state,
+       .enable_vblank          = logicvc_crtc_enable_vblank,
+       .disable_vblank         = logicvc_crtc_disable_vblank,
+};
+
+void logicvc_crtc_vblank_handler(struct logicvc_drm *logicvc)
+{
+       struct drm_device *drm_dev = &logicvc->drm_dev;
+       struct logicvc_crtc *crtc = logicvc->crtc;
+       unsigned long flags;
+
+       if (!crtc)
+               return;
+
+       drm_crtc_handle_vblank(&crtc->drm_crtc);
+
+       if (crtc->event) {
+               spin_lock_irqsave(&drm_dev->event_lock, flags);
+               drm_crtc_send_vblank_event(&crtc->drm_crtc, crtc->event);
+               drm_crtc_vblank_put(&crtc->drm_crtc);
+               crtc->event = NULL;
+               spin_unlock_irqrestore(&drm_dev->event_lock, flags);
+       }
+}
+
+int logicvc_crtc_init(struct logicvc_drm *logicvc)
+{
+       struct drm_device *drm_dev = &logicvc->drm_dev;
+       struct device *dev = drm_dev->dev;
+       struct device_node *of_node = dev->of_node;
+       struct logicvc_crtc *crtc;
+       struct logicvc_layer *layer_primary;
+       int ret;
+
+       crtc = devm_kzalloc(dev, sizeof(*crtc), GFP_KERNEL);
+       if (!crtc)
+               return -ENOMEM;
+
+       layer_primary = logicvc_layer_get_primary(logicvc);
+       if (!layer_primary) {
+               drm_err(drm_dev, "Failed to get primary layer\n");
+               return -EINVAL;
+       }
+
+       ret = drm_crtc_init_with_planes(drm_dev, &crtc->drm_crtc,
+                                       &layer_primary->drm_plane, NULL,
+                                       &logicvc_crtc_funcs, NULL);
+       if (ret) {
+               drm_err(drm_dev, "Failed to initialize CRTC\n");
+               return ret;
+       }
+
+       drm_crtc_helper_add(&crtc->drm_crtc, &logicvc_crtc_helper_funcs);
+
+       crtc->drm_crtc.port = of_graph_get_port_by_id(of_node, 1);
+
+       logicvc->crtc = crtc;
+
+       return 0;
+}
diff --git a/drivers/gpu/drm/logicvc/logicvc_crtc.h b/drivers/gpu/drm/logicvc/logicvc_crtc.h
new file mode 100644 (file)
index 0000000..b122901
--- /dev/null
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2019-2022 Bootlin
+ * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
+ */
+
+#ifndef _LOGICVC_CRTC_H_
+#define _LOGICVC_CRTC_H_
+
+struct drm_pending_vblank_event;
+struct logicvc_drm;
+
+struct logicvc_crtc {
+       struct drm_crtc drm_crtc;
+       struct drm_pending_vblank_event *event;
+};
+
+void logicvc_crtc_vblank_handler(struct logicvc_drm *logicvc);
+int logicvc_crtc_init(struct logicvc_drm *logicvc);
+
+#endif
diff --git a/drivers/gpu/drm/logicvc/logicvc_drm.c b/drivers/gpu/drm/logicvc/logicvc_drm.c
new file mode 100644 (file)
index 0000000..df1805c
--- /dev/null
@@ -0,0 +1,497 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2019-2022 Bootlin
+ * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_print.h>
+
+#include "logicvc_crtc.h"
+#include "logicvc_drm.h"
+#include "logicvc_interface.h"
+#include "logicvc_mode.h"
+#include "logicvc_layer.h"
+#include "logicvc_of.h"
+#include "logicvc_regs.h"
+
+DEFINE_DRM_GEM_CMA_FOPS(logicvc_drm_fops);
+
+static int logicvc_drm_gem_cma_dumb_create(struct drm_file *file_priv,
+                                          struct drm_device *drm_dev,
+                                          struct drm_mode_create_dumb *args)
+{
+       struct logicvc_drm *logicvc = logicvc_drm(drm_dev);
+
+       /* Stride is always fixed to its configuration value. */
+       args->pitch = logicvc->config.row_stride * DIV_ROUND_UP(args->bpp, 8);
+
+       return drm_gem_cma_dumb_create_internal(file_priv, drm_dev, args);
+}
+
+static struct drm_driver logicvc_drm_driver = {
+       .driver_features                = DRIVER_GEM | DRIVER_MODESET |
+                                         DRIVER_ATOMIC,
+
+       .fops                           = &logicvc_drm_fops,
+       .name                           = "logicvc-drm",
+       .desc                           = "Xylon LogiCVC DRM driver",
+       .date                           = "20200403",
+       .major                          = 1,
+       .minor                          = 0,
+
+       DRM_GEM_CMA_DRIVER_OPS_VMAP_WITH_DUMB_CREATE(logicvc_drm_gem_cma_dumb_create),
+};
+
+static struct regmap_config logicvc_drm_regmap_config = {
+       .reg_bits       = 32,
+       .val_bits       = 32,
+       .reg_stride     = 4,
+       .name           = "logicvc-drm",
+};
+
+static irqreturn_t logicvc_drm_irq_handler(int irq, void *data)
+{
+       struct logicvc_drm *logicvc = data;
+       irqreturn_t ret = IRQ_NONE;
+       u32 stat = 0;
+
+       /* Get pending interrupt sources. */
+       regmap_read(logicvc->regmap, LOGICVC_INT_STAT_REG, &stat);
+
+       /* Clear all pending interrupt sources. */
+       regmap_write(logicvc->regmap, LOGICVC_INT_STAT_REG, stat);
+
+       if (stat & LOGICVC_INT_STAT_V_SYNC) {
+               logicvc_crtc_vblank_handler(logicvc);
+               ret = IRQ_HANDLED;
+       }
+
+       return ret;
+}
+
+static int logicvc_drm_config_parse(struct logicvc_drm *logicvc)
+{
+       struct drm_device *drm_dev = &logicvc->drm_dev;
+       struct device *dev = drm_dev->dev;
+       struct device_node *of_node = dev->of_node;
+       struct logicvc_drm_config *config = &logicvc->config;
+       struct device_node *layers_node;
+       int ret;
+
+       logicvc_of_property_parse_bool(of_node, LOGICVC_OF_PROPERTY_DITHERING,
+                                      &config->dithering);
+       logicvc_of_property_parse_bool(of_node,
+                                      LOGICVC_OF_PROPERTY_BACKGROUND_LAYER,
+                                      &config->background_layer);
+       logicvc_of_property_parse_bool(of_node,
+                                      LOGICVC_OF_PROPERTY_LAYERS_CONFIGURABLE,
+                                      &config->layers_configurable);
+
+       ret = logicvc_of_property_parse_u32(of_node,
+                                           LOGICVC_OF_PROPERTY_DISPLAY_INTERFACE,
+                                           &config->display_interface);
+       if (ret)
+               return ret;
+
+       ret = logicvc_of_property_parse_u32(of_node,
+                                           LOGICVC_OF_PROPERTY_DISPLAY_COLORSPACE,
+                                           &config->display_colorspace);
+       if (ret)
+               return ret;
+
+       ret = logicvc_of_property_parse_u32(of_node,
+                                           LOGICVC_OF_PROPERTY_DISPLAY_DEPTH,
+                                           &config->display_depth);
+       if (ret)
+               return ret;
+
+       ret = logicvc_of_property_parse_u32(of_node,
+                                           LOGICVC_OF_PROPERTY_ROW_STRIDE,
+                                           &config->row_stride);
+       if (ret)
+               return ret;
+
+       layers_node = of_get_child_by_name(of_node, "layers");
+       if (!layers_node) {
+               drm_err(drm_dev, "Missing non-optional layers node\n");
+               return -EINVAL;
+       }
+
+       config->layers_count = of_get_child_count(layers_node);
+       if (!config->layers_count) {
+               drm_err(drm_dev,
+                       "Missing a non-optional layers children node\n");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int logicvc_clocks_prepare(struct logicvc_drm *logicvc)
+{
+       struct drm_device *drm_dev = &logicvc->drm_dev;
+       struct device *dev = drm_dev->dev;
+
+       struct {
+               struct clk **clk;
+               char *name;
+               bool optional;
+       } clocks_map[] = {
+               {
+                       .clk = &logicvc->vclk,
+                       .name = "vclk",
+                       .optional = false,
+               },
+               {
+                       .clk = &logicvc->vclk2,
+                       .name = "vclk2",
+                       .optional = true,
+               },
+               {
+                       .clk = &logicvc->lvdsclk,
+                       .name = "lvdsclk",
+                       .optional = true,
+               },
+               {
+                       .clk = &logicvc->lvdsclkn,
+                       .name = "lvdsclkn",
+                       .optional = true,
+               },
+       };
+       unsigned int i;
+       int ret;
+
+       for (i = 0; i < ARRAY_SIZE(clocks_map); i++) {
+               struct clk *clk;
+
+               clk = devm_clk_get(dev, clocks_map[i].name);
+               if (IS_ERR(clk)) {
+                       if (PTR_ERR(clk) == -ENOENT && clocks_map[i].optional)
+                               continue;
+
+                       drm_err(drm_dev, "Missing non-optional clock %s\n",
+                               clocks_map[i].name);
+
+                       ret = PTR_ERR(clk);
+                       goto error;
+               }
+
+               ret = clk_prepare_enable(clk);
+               if (ret) {
+                       drm_err(drm_dev,
+                               "Failed to prepare and enable clock %s\n",
+                               clocks_map[i].name);
+                       goto error;
+               }
+
+               *clocks_map[i].clk = clk;
+       }
+
+       return 0;
+
+error:
+       for (i = 0; i < ARRAY_SIZE(clocks_map); i++) {
+               if (!*clocks_map[i].clk)
+                       continue;
+
+               clk_disable_unprepare(*clocks_map[i].clk);
+               *clocks_map[i].clk = NULL;
+       }
+
+       return ret;
+}
+
+static int logicvc_clocks_unprepare(struct logicvc_drm *logicvc)
+{
+       struct clk **clocks[] = {
+               &logicvc->vclk,
+               &logicvc->vclk2,
+               &logicvc->lvdsclk,
+               &logicvc->lvdsclkn,
+       };
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(clocks); i++) {
+               if (!*clocks[i])
+                       continue;
+
+               clk_disable_unprepare(*clocks[i]);
+               *clocks[i] = NULL;
+       }
+
+       return 0;
+}
+
+static const struct logicvc_drm_caps logicvc_drm_caps[] = {
+       {
+               .major          = 3,
+               .layer_address  = false,
+       },
+       {
+               .major          = 4,
+               .layer_address  = true,
+       },
+       {
+               .major          = 5,
+               .layer_address  = true,
+       },
+};
+
+static const struct logicvc_drm_caps *
+logicvc_drm_caps_match(struct logicvc_drm *logicvc)
+{
+       struct drm_device *drm_dev = &logicvc->drm_dev;
+       const struct logicvc_drm_caps *caps = NULL;
+       unsigned int major, minor;
+       char level;
+       unsigned int i;
+       u32 version;
+
+       regmap_read(logicvc->regmap, LOGICVC_IP_VERSION_REG, &version);
+
+       major = FIELD_GET(LOGICVC_IP_VERSION_MAJOR_MASK, version);
+       minor = FIELD_GET(LOGICVC_IP_VERSION_MINOR_MASK, version);
+       level = FIELD_GET(LOGICVC_IP_VERSION_LEVEL_MASK, version) + 'a';
+
+       for (i = 0; i < ARRAY_SIZE(logicvc_drm_caps); i++) {
+               if (logicvc_drm_caps[i].major &&
+                   logicvc_drm_caps[i].major != major)
+                       continue;
+
+               if (logicvc_drm_caps[i].minor &&
+                   logicvc_drm_caps[i].minor != minor)
+                       continue;
+
+               if (logicvc_drm_caps[i].level &&
+                   logicvc_drm_caps[i].level != level)
+                       continue;
+
+               caps = &logicvc_drm_caps[i];
+       }
+
+       drm_info(drm_dev, "LogiCVC version %d.%02d.%c\n", major, minor, level);
+
+       return caps;
+}
+
+static int logicvc_drm_probe(struct platform_device *pdev)
+{
+       struct device_node *of_node = pdev->dev.of_node;
+       struct device_node *reserved_mem_node;
+       struct reserved_mem *reserved_mem = NULL;
+       const struct logicvc_drm_caps *caps;
+       struct logicvc_drm *logicvc;
+       struct device *dev = &pdev->dev;
+       struct drm_device *drm_dev;
+       struct regmap *regmap;
+       struct resource res;
+       void __iomem *base;
+       int irq;
+       int ret;
+
+       ret = of_reserved_mem_device_init(dev);
+       if (ret && ret != -ENODEV) {
+               dev_err(dev, "Failed to init memory region\n");
+               goto error_early;
+       }
+
+       reserved_mem_node = of_parse_phandle(of_node, "memory-region", 0);
+       if (reserved_mem_node) {
+               reserved_mem = of_reserved_mem_lookup(reserved_mem_node);
+               of_node_put(reserved_mem_node);
+       }
+
+       /* Get regmap from parent if available. */
+       if (of_node->parent)
+               regmap = syscon_node_to_regmap(of_node->parent);
+
+       /* Register our own regmap otherwise. */
+       if (IS_ERR_OR_NULL(regmap)) {
+               ret = of_address_to_resource(of_node, 0, &res);
+               if (ret) {
+                       dev_err(dev, "Failed to get resource from address\n");
+                       goto error_reserved_mem;
+               }
+
+               base = devm_ioremap_resource(dev, &res);
+               if (IS_ERR(base)) {
+                       dev_err(dev, "Failed to map I/O base\n");
+                       ret = PTR_ERR(base);
+                       goto error_reserved_mem;
+               }
+
+               logicvc_drm_regmap_config.max_register = resource_size(&res) -
+                                                        4;
+
+               regmap = devm_regmap_init_mmio(dev, base,
+                                              &logicvc_drm_regmap_config);
+               if (IS_ERR(regmap)) {
+                       dev_err(dev, "Failed to create regmap for I/O\n");
+                       ret = PTR_ERR(regmap);
+                       goto error_reserved_mem;
+               }
+       }
+
+       irq = platform_get_irq(pdev, 0);
+       if (irq < 0) {
+               dev_err(dev, "Failed to get IRQ\n");
+               ret = -ENODEV;
+               goto error_reserved_mem;
+       }
+
+       logicvc = devm_drm_dev_alloc(dev, &logicvc_drm_driver,
+                                    struct logicvc_drm, drm_dev);
+       if (IS_ERR(logicvc)) {
+               ret = PTR_ERR(logicvc);
+               goto error_reserved_mem;
+       }
+
+       platform_set_drvdata(pdev, logicvc);
+       drm_dev = &logicvc->drm_dev;
+
+       logicvc->regmap = regmap;
+       INIT_LIST_HEAD(&logicvc->layers_list);
+
+       caps = logicvc_drm_caps_match(logicvc);
+       if (!caps) {
+               ret = -EINVAL;
+               goto error_reserved_mem;
+       }
+
+       logicvc->caps = caps;
+
+       if (reserved_mem)
+               logicvc->reserved_mem_base = reserved_mem->base;
+
+       ret = logicvc_clocks_prepare(logicvc);
+       if (ret) {
+               drm_err(drm_dev, "Failed to prepare clocks\n");
+               goto error_reserved_mem;
+       }
+
+       ret = devm_request_irq(dev, irq, logicvc_drm_irq_handler, 0,
+                              dev_name(dev), logicvc);
+       if (ret) {
+               drm_err(drm_dev, "Failed to request IRQ\n");
+               goto error_clocks;
+       }
+
+       ret = logicvc_drm_config_parse(logicvc);
+       if (ret && ret != -ENODEV) {
+               drm_err(drm_dev, "Failed to parse config\n");
+               goto error_clocks;
+       }
+
+       ret = drmm_mode_config_init(drm_dev);
+       if (ret) {
+               drm_err(drm_dev, "Failed to init mode config\n");
+               goto error_clocks;
+       }
+
+       ret = logicvc_layers_init(logicvc);
+       if (ret) {
+               drm_err(drm_dev, "Failed to initialize layers\n");
+               goto error_clocks;
+       }
+
+       ret = logicvc_crtc_init(logicvc);
+       if (ret) {
+               drm_err(drm_dev, "Failed to initialize CRTC\n");
+               goto error_clocks;
+       }
+
+       logicvc_layers_attach_crtc(logicvc);
+
+       ret = logicvc_interface_init(logicvc);
+       if (ret) {
+               if (ret != -EPROBE_DEFER)
+                       drm_err(drm_dev, "Failed to initialize interface\n");
+
+               goto error_clocks;
+       }
+
+       logicvc_interface_attach_crtc(logicvc);
+
+       ret = logicvc_mode_init(logicvc);
+       if (ret) {
+               drm_err(drm_dev, "Failed to initialize KMS\n");
+               goto error_clocks;
+       }
+
+       ret = drm_dev_register(drm_dev, 0);
+       if (ret) {
+               drm_err(drm_dev, "Failed to register DRM device\n");
+               goto error_mode;
+       }
+
+       drm_fbdev_generic_setup(drm_dev, drm_dev->mode_config.preferred_depth);
+
+       return 0;
+
+error_mode:
+       logicvc_mode_fini(logicvc);
+
+error_clocks:
+       logicvc_clocks_unprepare(logicvc);
+
+error_reserved_mem:
+       of_reserved_mem_device_release(dev);
+
+error_early:
+       return ret;
+}
+
+static int logicvc_drm_remove(struct platform_device *pdev)
+{
+       struct logicvc_drm *logicvc = platform_get_drvdata(pdev);
+       struct device *dev = &pdev->dev;
+       struct drm_device *drm_dev = &logicvc->drm_dev;
+
+       drm_dev_unregister(drm_dev);
+       drm_atomic_helper_shutdown(drm_dev);
+
+       logicvc_mode_fini(logicvc);
+
+       logicvc_clocks_unprepare(logicvc);
+
+       of_reserved_mem_device_release(dev);
+
+       return 0;
+}
+
+static const struct of_device_id logicvc_drm_of_table[] = {
+       { .compatible = "xylon,logicvc-3.02.a-display" },
+       { .compatible = "xylon,logicvc-4.01.a-display" },
+       {},
+};
+MODULE_DEVICE_TABLE(of, logicvc_drm_of_table);
+
+static struct platform_driver logicvc_drm_platform_driver = {
+       .probe          = logicvc_drm_probe,
+       .remove         = logicvc_drm_remove,
+       .driver         = {
+               .name           = "logicvc-drm",
+               .of_match_table = logicvc_drm_of_table,
+       },
+};
+
+module_platform_driver(logicvc_drm_platform_driver);
+
+MODULE_AUTHOR("Paul Kocialkowski <paul.kocialkowski@bootlin.com>");
+MODULE_DESCRIPTION("Xylon LogiCVC DRM driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/logicvc/logicvc_drm.h b/drivers/gpu/drm/logicvc/logicvc_drm.h
new file mode 100644 (file)
index 0000000..e0f4787
--- /dev/null
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2019-2022 Bootlin
+ * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
+ */
+
+#ifndef _LOGICVC_DRM_H_
+#define _LOGICVC_DRM_H_
+
+#include <linux/regmap.h>
+#include <linux/types.h>
+#include <drm/drm_device.h>
+
+#define LOGICVC_DISPLAY_INTERFACE_RGB                  0
+#define LOGICVC_DISPLAY_INTERFACE_ITU656               1
+#define LOGICVC_DISPLAY_INTERFACE_LVDS_4BITS           2
+#define LOGICVC_DISPLAY_INTERFACE_LVDS_4BITS_CAMERA    3
+#define LOGICVC_DISPLAY_INTERFACE_LVDS_3BITS           4
+#define LOGICVC_DISPLAY_INTERFACE_DVI                  5
+
+#define LOGICVC_DISPLAY_COLORSPACE_RGB         0
+#define LOGICVC_DISPLAY_COLORSPACE_YUV422      1
+#define LOGICVC_DISPLAY_COLORSPACE_YUV444      2
+
+#define logicvc_drm(d) \
+       container_of(d, struct logicvc_drm, drm_dev)
+
+struct logicvc_crtc;
+struct logicvc_interface;
+
+struct logicvc_drm_config {
+       u32 display_interface;
+       u32 display_colorspace;
+       u32 display_depth;
+       u32 row_stride;
+       bool dithering;
+       bool background_layer;
+       bool layers_configurable;
+       u32 layers_count;
+};
+
+struct logicvc_drm_caps {
+       unsigned int major;
+       unsigned int minor;
+       char level;
+       bool layer_address;
+};
+
+struct logicvc_drm {
+       const struct logicvc_drm_caps *caps;
+       struct logicvc_drm_config config;
+
+       struct drm_device drm_dev;
+       phys_addr_t reserved_mem_base;
+       struct regmap *regmap;
+
+       struct clk *vclk;
+       struct clk *vclk2;
+       struct clk *lvdsclk;
+       struct clk *lvdsclkn;
+
+       struct list_head layers_list;
+       struct logicvc_crtc *crtc;
+       struct logicvc_interface *interface;
+};
+
+#endif
diff --git a/drivers/gpu/drm/logicvc/logicvc_interface.c b/drivers/gpu/drm/logicvc/logicvc_interface.c
new file mode 100644 (file)
index 0000000..c73592f
--- /dev/null
@@ -0,0 +1,214 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2019-2022 Bootlin
+ * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
+ */
+
+#include <linux/types.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+
+#include "logicvc_crtc.h"
+#include "logicvc_drm.h"
+#include "logicvc_interface.h"
+#include "logicvc_regs.h"
+
+#define logicvc_interface_from_drm_encoder(c) \
+       container_of(c, struct logicvc_interface, drm_encoder)
+#define logicvc_interface_from_drm_connector(c) \
+       container_of(c, struct logicvc_interface, drm_connector)
+
+static void logicvc_encoder_enable(struct drm_encoder *drm_encoder)
+{
+       struct logicvc_drm *logicvc = logicvc_drm(drm_encoder->dev);
+       struct logicvc_interface *interface =
+               logicvc_interface_from_drm_encoder(drm_encoder);
+
+       regmap_update_bits(logicvc->regmap, LOGICVC_POWER_CTRL_REG,
+                          LOGICVC_POWER_CTRL_VIDEO_ENABLE,
+                          LOGICVC_POWER_CTRL_VIDEO_ENABLE);
+
+       if (interface->drm_panel) {
+               drm_panel_prepare(interface->drm_panel);
+               drm_panel_enable(interface->drm_panel);
+       }
+}
+
+static void logicvc_encoder_disable(struct drm_encoder *drm_encoder)
+{
+       struct logicvc_interface *interface =
+               logicvc_interface_from_drm_encoder(drm_encoder);
+
+       if (interface->drm_panel) {
+               drm_panel_disable(interface->drm_panel);
+               drm_panel_unprepare(interface->drm_panel);
+       }
+}
+
+static const struct drm_encoder_helper_funcs logicvc_encoder_helper_funcs = {
+       .enable                 = logicvc_encoder_enable,
+       .disable                = logicvc_encoder_disable,
+};
+
+static const struct drm_encoder_funcs logicvc_encoder_funcs = {
+       .destroy                = drm_encoder_cleanup,
+};
+
+static int logicvc_connector_get_modes(struct drm_connector *drm_connector)
+{
+       struct logicvc_interface *interface =
+               logicvc_interface_from_drm_connector(drm_connector);
+
+       if (interface->drm_panel)
+               return drm_panel_get_modes(interface->drm_panel, drm_connector);
+
+       WARN_ONCE(1, "Retrieving modes from a native connector is not implemented.");
+
+       return 0;
+}
+
+static const struct drm_connector_helper_funcs logicvc_connector_helper_funcs = {
+       .get_modes              = logicvc_connector_get_modes,
+};
+
+static const struct drm_connector_funcs logicvc_connector_funcs = {
+       .reset                  = drm_atomic_helper_connector_reset,
+       .fill_modes             = drm_helper_probe_single_connector_modes,
+       .destroy                = drm_connector_cleanup,
+       .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+       .atomic_destroy_state   = drm_atomic_helper_connector_destroy_state,
+};
+
+static int logicvc_interface_encoder_type(struct logicvc_drm *logicvc)
+{
+       switch (logicvc->config.display_interface) {
+       case LOGICVC_DISPLAY_INTERFACE_LVDS_4BITS:
+       case LOGICVC_DISPLAY_INTERFACE_LVDS_4BITS_CAMERA:
+       case LOGICVC_DISPLAY_INTERFACE_LVDS_3BITS:
+               return DRM_MODE_ENCODER_LVDS;
+       case LOGICVC_DISPLAY_INTERFACE_DVI:
+               return DRM_MODE_ENCODER_TMDS;
+       case LOGICVC_DISPLAY_INTERFACE_RGB:
+               return DRM_MODE_ENCODER_DPI;
+       default:
+               return DRM_MODE_ENCODER_NONE;
+       }
+}
+
+static int logicvc_interface_connector_type(struct logicvc_drm *logicvc)
+{
+       switch (logicvc->config.display_interface) {
+       case LOGICVC_DISPLAY_INTERFACE_LVDS_4BITS:
+       case LOGICVC_DISPLAY_INTERFACE_LVDS_4BITS_CAMERA:
+       case LOGICVC_DISPLAY_INTERFACE_LVDS_3BITS:
+               return DRM_MODE_CONNECTOR_LVDS;
+       case LOGICVC_DISPLAY_INTERFACE_DVI:
+               return DRM_MODE_CONNECTOR_DVID;
+       case LOGICVC_DISPLAY_INTERFACE_RGB:
+               return DRM_MODE_CONNECTOR_DPI;
+       default:
+               return DRM_MODE_CONNECTOR_Unknown;
+       }
+}
+
+static bool logicvc_interface_native_connector(struct logicvc_drm *logicvc)
+{
+       switch (logicvc->config.display_interface) {
+       case LOGICVC_DISPLAY_INTERFACE_DVI:
+               return true;
+       default:
+               return false;
+       }
+}
+
+void logicvc_interface_attach_crtc(struct logicvc_drm *logicvc)
+{
+       uint32_t possible_crtcs = drm_crtc_mask(&logicvc->crtc->drm_crtc);
+
+       logicvc->interface->drm_encoder.possible_crtcs = possible_crtcs;
+}
+
+int logicvc_interface_init(struct logicvc_drm *logicvc)
+{
+       struct logicvc_interface *interface;
+       struct drm_device *drm_dev = &logicvc->drm_dev;
+       struct device *dev = drm_dev->dev;
+       struct device_node *of_node = dev->of_node;
+       int encoder_type = logicvc_interface_encoder_type(logicvc);
+       int connector_type = logicvc_interface_connector_type(logicvc);
+       bool native_connector = logicvc_interface_native_connector(logicvc);
+       int ret;
+
+       interface = devm_kzalloc(dev, sizeof(*interface), GFP_KERNEL);
+       if (!interface) {
+               ret = -ENOMEM;
+               goto error_early;
+       }
+
+       ret = drm_of_find_panel_or_bridge(of_node, 0, 0, &interface->drm_panel,
+                                         &interface->drm_bridge);
+       if (ret == -EPROBE_DEFER)
+               goto error_early;
+
+       ret = drm_encoder_init(drm_dev, &interface->drm_encoder,
+                              &logicvc_encoder_funcs, encoder_type, NULL);
+       if (ret) {
+               drm_err(drm_dev, "Failed to initialize encoder\n");
+               goto error_early;
+       }
+
+       drm_encoder_helper_add(&interface->drm_encoder,
+                              &logicvc_encoder_helper_funcs);
+
+       if (native_connector || interface->drm_panel) {
+               ret = drm_connector_init(drm_dev, &interface->drm_connector,
+                                        &logicvc_connector_funcs,
+                                        connector_type);
+               if (ret) {
+                       drm_err(drm_dev, "Failed to initialize connector\n");
+                       goto error_encoder;
+               }
+
+               drm_connector_helper_add(&interface->drm_connector,
+                                        &logicvc_connector_helper_funcs);
+
+               ret = drm_connector_attach_encoder(&interface->drm_connector,
+                                                  &interface->drm_encoder);
+               if (ret) {
+                       drm_err(drm_dev,
+                               "Failed to attach connector to encoder\n");
+                       goto error_encoder;
+               }
+       }
+
+       if (interface->drm_bridge) {
+               ret = drm_bridge_attach(&interface->drm_encoder,
+                                       interface->drm_bridge, NULL, 0);
+               if (ret) {
+                       drm_err(drm_dev,
+                               "Failed to attach bridge to encoder\n");
+                       goto error_encoder;
+               }
+       }
+
+       logicvc->interface = interface;
+
+       return 0;
+
+error_encoder:
+       drm_encoder_cleanup(&interface->drm_encoder);
+
+error_early:
+       return ret;
+}
diff --git a/drivers/gpu/drm/logicvc/logicvc_interface.h b/drivers/gpu/drm/logicvc/logicvc_interface.h
new file mode 100644 (file)
index 0000000..fd709fa
--- /dev/null
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2019-2022 Bootlin
+ * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
+ */
+
+#ifndef _LOGICVC_INTERFACE_H_
+#define _LOGICVC_INTERFACE_H_
+
+#include <drm/drm_bridge.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_panel.h>
+
+struct logicvc_drm;
+
+struct logicvc_interface {
+       struct drm_encoder drm_encoder;
+       struct drm_connector drm_connector;
+
+       struct drm_panel *drm_panel;
+       struct drm_bridge *drm_bridge;
+};
+
+void logicvc_interface_attach_crtc(struct logicvc_drm *logicvc);
+int logicvc_interface_init(struct logicvc_drm *logicvc);
+
+#endif
diff --git a/drivers/gpu/drm/logicvc/logicvc_layer.c b/drivers/gpu/drm/logicvc/logicvc_layer.c
new file mode 100644 (file)
index 0000000..bae1c7f
--- /dev/null
@@ -0,0 +1,628 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2019-2022 Bootlin
+ * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
+ */
+
+#include <linux/of.h>
+#include <linux/types.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_plane_helper.h>
+#include <drm/drm_print.h>
+
+#include "logicvc_crtc.h"
+#include "logicvc_drm.h"
+#include "logicvc_layer.h"
+#include "logicvc_of.h"
+#include "logicvc_regs.h"
+
+#define logicvc_layer(p) \
+       container_of(p, struct logicvc_layer, drm_plane)
+
+static uint32_t logicvc_layer_formats_rgb16[] = {
+       DRM_FORMAT_RGB565,
+       DRM_FORMAT_BGR565,
+       DRM_FORMAT_INVALID,
+};
+
+static uint32_t logicvc_layer_formats_rgb24[] = {
+       DRM_FORMAT_XRGB8888,
+       DRM_FORMAT_XBGR8888,
+       DRM_FORMAT_INVALID,
+};
+
+/*
+ * What we call depth in this driver only counts color components, not alpha.
+ * This allows us to stay compatible with the LogiCVC bistream definitions.
+ */
+static uint32_t logicvc_layer_formats_rgb24_alpha[] = {
+       DRM_FORMAT_ARGB8888,
+       DRM_FORMAT_ABGR8888,
+       DRM_FORMAT_INVALID,
+};
+
+static struct logicvc_layer_formats logicvc_layer_formats[] = {
+       {
+               .colorspace     = LOGICVC_LAYER_COLORSPACE_RGB,
+               .depth          = 16,
+               .formats        = logicvc_layer_formats_rgb16,
+       },
+       {
+               .colorspace     = LOGICVC_LAYER_COLORSPACE_RGB,
+               .depth          = 24,
+               .formats        = logicvc_layer_formats_rgb24,
+       },
+       {
+               .colorspace     = LOGICVC_LAYER_COLORSPACE_RGB,
+               .depth          = 24,
+               .alpha          = true,
+               .formats        = logicvc_layer_formats_rgb24_alpha,
+       },
+       { }
+};
+
+static bool logicvc_layer_format_inverted(uint32_t format)
+{
+       switch (format) {
+       case DRM_FORMAT_BGR565:
+       case DRM_FORMAT_BGR888:
+       case DRM_FORMAT_XBGR8888:
+       case DRM_FORMAT_ABGR8888:
+               return true;
+       default:
+               return false;
+       }
+}
+
+static int logicvc_plane_atomic_check(struct drm_plane *drm_plane,
+                                     struct drm_atomic_state *state)
+{
+       struct drm_device *drm_dev = drm_plane->dev;
+       struct logicvc_layer *layer = logicvc_layer(drm_plane);
+       struct logicvc_drm *logicvc = logicvc_drm(drm_dev);
+       struct drm_plane_state *new_state =
+               drm_atomic_get_new_plane_state(state, drm_plane);
+       struct drm_crtc_state *crtc_state;
+       int min_scale, max_scale;
+       bool can_position;
+       int ret;
+
+       if (!new_state->crtc)
+               return 0;
+
+       crtc_state = drm_atomic_get_existing_crtc_state(new_state->state,
+                                                       new_state->crtc);
+       if (WARN_ON(!crtc_state))
+               return -EINVAL;
+
+       if (new_state->crtc_x < 0 || new_state->crtc_y < 0) {
+               drm_err(drm_dev,
+                       "Negative on-CRTC positions are not supported.\n");
+               return -EINVAL;
+       }
+
+       if (!logicvc->caps->layer_address) {
+               ret = logicvc_layer_buffer_find_setup(logicvc, layer, new_state,
+                                                     NULL);
+               if (ret) {
+                       drm_err(drm_dev, "No viable setup for buffer found.\n");
+                       return ret;
+               }
+       }
+
+       min_scale = DRM_PLANE_HELPER_NO_SCALING;
+       max_scale = DRM_PLANE_HELPER_NO_SCALING;
+
+       can_position = (drm_plane->type == DRM_PLANE_TYPE_OVERLAY &&
+                       layer->index != (logicvc->config.layers_count - 1) &&
+                       logicvc->config.layers_configurable);
+
+       ret = drm_atomic_helper_check_plane_state(new_state, crtc_state,
+                                                 min_scale, max_scale,
+                                                 can_position, true);
+       if (ret) {
+               drm_err(drm_dev, "Invalid plane state\n\n");
+               return ret;
+       }
+
+       return 0;
+}
+
+static void logicvc_plane_atomic_update(struct drm_plane *drm_plane,
+                                       struct drm_atomic_state *state)
+{
+       struct logicvc_layer *layer = logicvc_layer(drm_plane);
+       struct logicvc_drm *logicvc = logicvc_drm(drm_plane->dev);
+       struct drm_device *drm_dev = &logicvc->drm_dev;
+       struct drm_plane_state *new_state =
+               drm_atomic_get_new_plane_state(state, drm_plane);
+       struct drm_crtc *drm_crtc = &logicvc->crtc->drm_crtc;
+       struct drm_display_mode *mode = &drm_crtc->state->adjusted_mode;
+       struct drm_framebuffer *fb = new_state->fb;
+       struct logicvc_layer_buffer_setup setup = {};
+       u32 index = layer->index;
+       u32 reg;
+
+       /* Layer dimensions */
+
+       regmap_write(logicvc->regmap, LOGICVC_LAYER_WIDTH_REG(index),
+                    new_state->crtc_w - 1);
+       regmap_write(logicvc->regmap, LOGICVC_LAYER_HEIGHT_REG(index),
+                    new_state->crtc_h - 1);
+
+       if (logicvc->caps->layer_address) {
+               phys_addr_t fb_addr = drm_fb_cma_get_gem_addr(fb, new_state, 0);
+
+               regmap_write(logicvc->regmap, LOGICVC_LAYER_ADDRESS_REG(index),
+                            fb_addr);
+       } else {
+               /* Rely on offsets to configure the address. */
+
+               logicvc_layer_buffer_find_setup(logicvc, layer, new_state,
+                                               &setup);
+
+               /* Layer memory offsets */
+
+               regmap_write(logicvc->regmap, LOGICVC_BUFFER_SEL_REG,
+                            LOGICVC_BUFFER_SEL_VALUE(index, setup.buffer_sel));
+               regmap_write(logicvc->regmap, LOGICVC_LAYER_HOFFSET_REG(index),
+                            setup.hoffset);
+               regmap_write(logicvc->regmap, LOGICVC_LAYER_VOFFSET_REG(index),
+                            setup.voffset);
+       }
+
+       /* Layer position */
+
+       regmap_write(logicvc->regmap, LOGICVC_LAYER_HPOSITION_REG(index),
+                    mode->hdisplay - 1 - new_state->crtc_x);
+
+       /* Vertical position must be set last to sync layer register changes. */
+       regmap_write(logicvc->regmap, LOGICVC_LAYER_VPOSITION_REG(index),
+                    mode->vdisplay - 1 - new_state->crtc_y);
+
+       /* Layer alpha */
+
+       if (layer->config.alpha_mode == LOGICVC_LAYER_ALPHA_LAYER) {
+               u32 alpha_bits;
+               u32 alpha_max;
+               u32 alpha;
+
+               switch (layer->config.depth) {
+               case 8:
+                       alpha_bits = 3;
+                       break;
+               case 16:
+                       if (layer->config.colorspace ==
+                           LOGICVC_LAYER_COLORSPACE_YUV)
+                               alpha_bits = 8;
+                       else
+                               alpha_bits = 6;
+                       break;
+               default:
+                       alpha_bits = 8;
+                       break;
+               }
+
+               alpha_max = BIT(alpha_bits) - 1;
+               alpha = new_state->alpha * alpha_max / DRM_BLEND_ALPHA_OPAQUE;
+
+               drm_dbg_kms(drm_dev, "Setting layer %d alpha to %d/%d\n", index,
+                           alpha, alpha_max);
+
+               regmap_write(logicvc->regmap, LOGICVC_LAYER_ALPHA_REG(index),
+                            alpha);
+       }
+
+       /* Layer control */
+
+       reg = LOGICVC_LAYER_CTRL_ENABLE;
+
+       if (logicvc_layer_format_inverted(fb->format->format))
+               reg |= LOGICVC_LAYER_CTRL_PIXEL_FORMAT_INVERT;
+
+       reg |= LOGICVC_LAYER_CTRL_COLOR_KEY_DISABLE;
+
+       regmap_write(logicvc->regmap, LOGICVC_LAYER_CTRL_REG(index), reg);
+}
+
+static void logicvc_plane_atomic_disable(struct drm_plane *drm_plane,
+                                        struct drm_atomic_state *state)
+{
+       struct logicvc_layer *layer = logicvc_layer(drm_plane);
+       struct logicvc_drm *logicvc = logicvc_drm(drm_plane->dev);
+       u32 index = layer->index;
+
+       regmap_write(logicvc->regmap, LOGICVC_LAYER_CTRL_REG(index), 0);
+}
+
+static struct drm_plane_helper_funcs logicvc_plane_helper_funcs = {
+       .atomic_check           = logicvc_plane_atomic_check,
+       .atomic_update          = logicvc_plane_atomic_update,
+       .atomic_disable         = logicvc_plane_atomic_disable,
+};
+
+static const struct drm_plane_funcs logicvc_plane_funcs = {
+       .update_plane           = drm_atomic_helper_update_plane,
+       .disable_plane          = drm_atomic_helper_disable_plane,
+       .destroy                = drm_plane_cleanup,
+       .reset                  = drm_atomic_helper_plane_reset,
+       .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+       .atomic_destroy_state   = drm_atomic_helper_plane_destroy_state,
+};
+
+int logicvc_layer_buffer_find_setup(struct logicvc_drm *logicvc,
+                                   struct logicvc_layer *layer,
+                                   struct drm_plane_state *state,
+                                   struct logicvc_layer_buffer_setup *setup)
+{
+       struct drm_device *drm_dev = &logicvc->drm_dev;
+       struct drm_framebuffer *fb = state->fb;
+       /* All the supported formats have a single data plane. */
+       u32 layer_bytespp = fb->format->cpp[0];
+       u32 layer_stride = layer_bytespp * logicvc->config.row_stride;
+       u32 base_offset = layer->config.base_offset * layer_stride;
+       u32 buffer_offset = layer->config.buffer_offset * layer_stride;
+       u8 buffer_sel = 0;
+       u16 voffset = 0;
+       u16 hoffset = 0;
+       phys_addr_t fb_addr;
+       u32 fb_offset;
+       u32 gap;
+
+       if (!logicvc->reserved_mem_base) {
+               drm_err(drm_dev, "No reserved memory base was registered!\n");
+               return -ENOMEM;
+       }
+
+       fb_addr = drm_fb_cma_get_gem_addr(fb, state, 0);
+       if (fb_addr < logicvc->reserved_mem_base) {
+               drm_err(drm_dev,
+                       "Framebuffer memory below reserved memory base!\n");
+               return -EINVAL;
+       }
+
+       fb_offset = (u32) (fb_addr - logicvc->reserved_mem_base);
+
+       if (fb_offset < base_offset) {
+               drm_err(drm_dev,
+                       "Framebuffer offset below layer base offset!\n");
+               return -EINVAL;
+       }
+
+       gap = fb_offset - base_offset;
+
+       /* Use the possible video buffers selection. */
+       if (gap && buffer_offset) {
+               buffer_sel = gap / buffer_offset;
+               if (buffer_sel > LOGICVC_BUFFER_SEL_MAX)
+                       buffer_sel = LOGICVC_BUFFER_SEL_MAX;
+
+               gap -= buffer_sel * buffer_offset;
+       }
+
+       /* Use the vertical offset. */
+       if (gap && layer_stride && logicvc->config.layers_configurable) {
+               voffset = gap / layer_stride;
+               if (voffset > LOGICVC_LAYER_VOFFSET_MAX)
+                       voffset = LOGICVC_LAYER_VOFFSET_MAX;
+
+               gap -= voffset * layer_stride;
+       }
+
+       /* Use the horizontal offset. */
+       if (gap && layer_bytespp && logicvc->config.layers_configurable) {
+               hoffset = gap / layer_bytespp;
+               if (hoffset > LOGICVC_DIMENSIONS_MAX)
+                       hoffset = LOGICVC_DIMENSIONS_MAX;
+
+               gap -= hoffset * layer_bytespp;
+       }
+
+       if (gap) {
+               drm_err(drm_dev,
+                       "Unable to find layer %d buffer setup for 0x%x byte gap\n",
+                       layer->index, fb_offset - base_offset);
+               return -EINVAL;
+       }
+
+       drm_dbg_kms(drm_dev, "Found layer %d buffer setup for 0x%x byte gap:\n",
+                   layer->index, fb_offset - base_offset);
+
+       drm_dbg_kms(drm_dev, "- buffer_sel = 0x%x chunks of 0x%x bytes\n",
+                   buffer_sel, buffer_offset);
+       drm_dbg_kms(drm_dev, "- voffset = 0x%x chunks of 0x%x bytes\n", voffset,
+                   layer_stride);
+       drm_dbg_kms(drm_dev, "- hoffset = 0x%x chunks of 0x%x bytes\n", hoffset,
+                   layer_bytespp);
+
+       if (setup) {
+               setup->buffer_sel = buffer_sel;
+               setup->voffset = voffset;
+               setup->hoffset = hoffset;
+       }
+
+       return 0;
+}
+
+static struct logicvc_layer_formats *logicvc_layer_formats_lookup(struct logicvc_layer *layer)
+{
+       bool alpha;
+       unsigned int i = 0;
+
+       alpha = (layer->config.alpha_mode == LOGICVC_LAYER_ALPHA_PIXEL);
+
+       while (logicvc_layer_formats[i].formats) {
+               if (logicvc_layer_formats[i].colorspace == layer->config.colorspace &&
+                   logicvc_layer_formats[i].depth == layer->config.depth &&
+                   logicvc_layer_formats[i].alpha == alpha)
+                       return &logicvc_layer_formats[i];
+
+               i++;
+       }
+
+       return NULL;
+}
+
+static unsigned int logicvc_layer_formats_count(struct logicvc_layer_formats *formats)
+{
+       unsigned int count = 0;
+
+       while (formats->formats[count] != DRM_FORMAT_INVALID)
+               count++;
+
+       return count;
+}
+
+static int logicvc_layer_config_parse(struct logicvc_drm *logicvc,
+                                     struct logicvc_layer *layer)
+{
+       struct device_node *of_node = layer->of_node;
+       struct logicvc_layer_config *config = &layer->config;
+       int ret;
+
+       logicvc_of_property_parse_bool(of_node,
+                                      LOGICVC_OF_PROPERTY_LAYER_PRIMARY,
+                                      &config->primary);
+
+       ret = logicvc_of_property_parse_u32(of_node,
+                                           LOGICVC_OF_PROPERTY_LAYER_COLORSPACE,
+                                           &config->colorspace);
+       if (ret)
+               return ret;
+
+       ret = logicvc_of_property_parse_u32(of_node,
+                                           LOGICVC_OF_PROPERTY_LAYER_DEPTH,
+                                           &config->depth);
+       if (ret)
+               return ret;
+
+       ret = logicvc_of_property_parse_u32(of_node,
+                                           LOGICVC_OF_PROPERTY_LAYER_ALPHA_MODE,
+                                           &config->alpha_mode);
+       if (ret)
+               return ret;
+
+       /*
+        * Memory offset is only relevant without layer address configuration.
+        */
+       if (logicvc->caps->layer_address)
+               return 0;
+
+       ret = logicvc_of_property_parse_u32(of_node,
+                                           LOGICVC_OF_PROPERTY_LAYER_BASE_OFFSET,
+                                           &config->base_offset);
+       if (ret)
+               return ret;
+
+       ret = logicvc_of_property_parse_u32(of_node,
+                                           LOGICVC_OF_PROPERTY_LAYER_BUFFER_OFFSET,
+                                           &config->buffer_offset);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+struct logicvc_layer *logicvc_layer_get_from_index(struct logicvc_drm *logicvc,
+                                                  u32 index)
+{
+       struct logicvc_layer *layer;
+
+       list_for_each_entry(layer, &logicvc->layers_list, list)
+               if (layer->index == index)
+                       return layer;
+
+       return NULL;
+}
+
+struct logicvc_layer *logicvc_layer_get_from_type(struct logicvc_drm *logicvc,
+                                                 enum drm_plane_type type)
+{
+       struct logicvc_layer *layer;
+
+       list_for_each_entry(layer, &logicvc->layers_list, list)
+               if (layer->drm_plane.type == type)
+                       return layer;
+
+       return NULL;
+}
+
+struct logicvc_layer *logicvc_layer_get_primary(struct logicvc_drm *logicvc)
+{
+       return logicvc_layer_get_from_type(logicvc, DRM_PLANE_TYPE_PRIMARY);
+}
+
+static int logicvc_layer_init(struct logicvc_drm *logicvc,
+                             struct device_node *of_node, u32 index)
+{
+       struct drm_device *drm_dev = &logicvc->drm_dev;
+       struct device *dev = drm_dev->dev;
+       struct logicvc_layer *layer = NULL;
+       struct logicvc_layer_formats *formats;
+       unsigned int formats_count;
+       enum drm_plane_type type;
+       unsigned int zpos;
+       int ret;
+
+       layer = devm_kzalloc(dev, sizeof(*layer), GFP_KERNEL);
+       if (!layer) {
+               ret = -ENOMEM;
+               goto error;
+       }
+
+       layer->of_node = of_node;
+       layer->index = index;
+
+       ret = logicvc_layer_config_parse(logicvc, layer);
+       if (ret) {
+               drm_err(drm_dev, "Failed to parse config for layer #%d\n",
+                       index);
+               goto error;
+       }
+
+       formats = logicvc_layer_formats_lookup(layer);
+       if (!formats) {
+               drm_err(drm_dev, "Failed to lookup formats for layer #%d\n",
+                       index);
+               goto error;
+       }
+
+       formats_count = logicvc_layer_formats_count(formats);
+
+       /* The final layer can be configured as a background layer. */
+       if (logicvc->config.background_layer &&
+           index == (logicvc->config.layers_count - 1)) {
+               /*
+                * A zero value for black is only valid for RGB, not for YUV,
+                * so this will need to take the format in account for YUV.
+                */
+               u32 background = 0;
+
+               drm_dbg_kms(drm_dev, "Using layer #%d as background layer\n",
+                           index);
+
+               regmap_write(logicvc->regmap, LOGICVC_BACKGROUND_COLOR_REG,
+                            background);
+
+               devm_kfree(dev, layer);
+
+               return 0;
+       }
+
+       if (layer->config.primary)
+               type = DRM_PLANE_TYPE_PRIMARY;
+       else
+               type = DRM_PLANE_TYPE_OVERLAY;
+
+       ret = drm_universal_plane_init(drm_dev, &layer->drm_plane, 0,
+                                      &logicvc_plane_funcs, formats->formats,
+                                      formats_count, NULL, type, NULL);
+       if (ret) {
+               drm_err(drm_dev, "Failed to initialize layer plane\n");
+               return ret;
+       }
+
+       drm_plane_helper_add(&layer->drm_plane, &logicvc_plane_helper_funcs);
+
+       zpos = logicvc->config.layers_count - index - 1;
+       drm_dbg_kms(drm_dev, "Giving layer #%d zpos %d\n", index, zpos);
+
+       if (layer->config.alpha_mode == LOGICVC_LAYER_ALPHA_LAYER)
+               drm_plane_create_alpha_property(&layer->drm_plane);
+
+       drm_plane_create_zpos_immutable_property(&layer->drm_plane, zpos);
+
+       drm_dbg_kms(drm_dev, "Registering layer #%d\n", index);
+
+       layer->formats = formats;
+
+       list_add_tail(&layer->list, &logicvc->layers_list);
+
+       return 0;
+
+error:
+       if (layer)
+               devm_kfree(dev, layer);
+
+       return ret;
+}
+
+static void logicvc_layer_fini(struct logicvc_drm *logicvc,
+                              struct logicvc_layer *layer)
+{
+       struct device *dev = logicvc->drm_dev.dev;
+
+       list_del(&layer->list);
+       devm_kfree(dev, layer);
+}
+
+void logicvc_layers_attach_crtc(struct logicvc_drm *logicvc)
+{
+       uint32_t possible_crtcs = drm_crtc_mask(&logicvc->crtc->drm_crtc);
+       struct logicvc_layer *layer;
+
+       list_for_each_entry(layer, &logicvc->layers_list, list) {
+               if (layer->drm_plane.type != DRM_PLANE_TYPE_OVERLAY)
+                       continue;
+
+               layer->drm_plane.possible_crtcs = possible_crtcs;
+       }
+}
+
+int logicvc_layers_init(struct logicvc_drm *logicvc)
+{
+       struct drm_device *drm_dev = &logicvc->drm_dev;
+       struct device *dev = drm_dev->dev;
+       struct device_node *of_node = dev->of_node;
+       struct device_node *layer_node = NULL;
+       struct device_node *layers_node;
+       struct logicvc_layer *layer;
+       struct logicvc_layer *next;
+       int ret = 0;
+
+       layers_node = of_get_child_by_name(of_node, "layers");
+       if (!layers_node) {
+               drm_err(drm_dev, "No layers node found in the description\n");
+               ret = -ENODEV;
+               goto error;
+       }
+
+       for_each_child_of_node(layers_node, layer_node) {
+               u32 index = 0;
+
+               if (!logicvc_of_node_is_layer(layer_node))
+                       continue;
+
+               ret = of_property_read_u32(layer_node, "reg", &index);
+               if (ret)
+                       continue;
+
+               layer = logicvc_layer_get_from_index(logicvc, index);
+               if (layer) {
+                       drm_err(drm_dev, "Duplicated entry for layer #%d\n",
+                               index);
+                       continue;
+               }
+
+               ret = logicvc_layer_init(logicvc, layer_node, index);
+               if (ret)
+                       goto error;
+
+               of_node_put(layer_node);
+       }
+
+       of_node_put(layers_node);
+
+       return 0;
+
+error:
+       list_for_each_entry_safe(layer, next, &logicvc->layers_list, list)
+               logicvc_layer_fini(logicvc, layer);
+
+       return ret;
+}
diff --git a/drivers/gpu/drm/logicvc/logicvc_layer.h b/drivers/gpu/drm/logicvc/logicvc_layer.h
new file mode 100644 (file)
index 0000000..4a4b02e
--- /dev/null
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2019-2022 Bootlin
+ * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
+ */
+
+#ifndef _LOGICVC_LAYER_H_
+#define _LOGICVC_LAYER_H_
+
+#include <linux/of.h>
+#include <linux/types.h>
+#include <drm/drm_plane.h>
+
+#define LOGICVC_LAYER_COLORSPACE_RGB           0
+#define LOGICVC_LAYER_COLORSPACE_YUV           1
+
+#define LOGICVC_LAYER_ALPHA_LAYER              0
+#define LOGICVC_LAYER_ALPHA_PIXEL              1
+
+struct logicvc_layer_buffer_setup {
+       u8 buffer_sel;
+       u16 voffset;
+       u16 hoffset;
+};
+
+struct logicvc_layer_config {
+       u32 colorspace;
+       u32 depth;
+       u32 alpha_mode;
+       u32 base_offset;
+       u32 buffer_offset;
+       bool primary;
+};
+
+struct logicvc_layer_formats {
+       u32 colorspace;
+       u32 depth;
+       bool alpha;
+       uint32_t *formats;
+};
+
+struct logicvc_layer {
+       struct logicvc_layer_config config;
+       struct logicvc_layer_formats *formats;
+       struct device_node *of_node;
+
+       struct drm_plane drm_plane;
+       struct list_head list;
+       u32 index;
+};
+
+int logicvc_layer_buffer_find_setup(struct logicvc_drm *logicvc,
+                                   struct logicvc_layer *layer,
+                                   struct drm_plane_state *state,
+                                   struct logicvc_layer_buffer_setup *setup);
+struct logicvc_layer *logicvc_layer_get_from_index(struct logicvc_drm *logicvc,
+                                                  u32 index);
+struct logicvc_layer *logicvc_layer_get_from_type(struct logicvc_drm *logicvc,
+                                                 enum drm_plane_type type);
+struct logicvc_layer *logicvc_layer_get_primary(struct logicvc_drm *logicvc);
+void logicvc_layers_attach_crtc(struct logicvc_drm *logicvc);
+int logicvc_layers_init(struct logicvc_drm *logicvc);
+
+#endif
diff --git a/drivers/gpu/drm/logicvc/logicvc_mode.c b/drivers/gpu/drm/logicvc/logicvc_mode.c
new file mode 100644 (file)
index 0000000..1194070
--- /dev/null
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2019-2022 Bootlin
+ * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
+ */
+
+#include <linux/types.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_mode_config.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
+
+#include "logicvc_drm.h"
+#include "logicvc_interface.h"
+#include "logicvc_layer.h"
+#include "logicvc_mode.h"
+
+static const struct drm_mode_config_funcs logicvc_mode_config_funcs = {
+       .fb_create              = drm_gem_fb_create,
+       .output_poll_changed    = drm_fb_helper_output_poll_changed,
+       .atomic_check           = drm_atomic_helper_check,
+       .atomic_commit          = drm_atomic_helper_commit,
+};
+
+int logicvc_mode_init(struct logicvc_drm *logicvc)
+{
+       struct drm_device *drm_dev = &logicvc->drm_dev;
+       struct drm_mode_config *mode_config = &drm_dev->mode_config;
+       struct logicvc_layer *layer_primary;
+       uint32_t preferred_depth;
+       int ret;
+
+       ret = drm_vblank_init(drm_dev, mode_config->num_crtc);
+       if (ret) {
+               drm_err(drm_dev, "Failed to initialize vblank\n");
+               return ret;
+       }
+
+       layer_primary = logicvc_layer_get_primary(logicvc);
+       if (!layer_primary) {
+               drm_err(drm_dev, "Failed to get primary layer\n");
+               return -EINVAL;
+       }
+
+       preferred_depth = layer_primary->formats->depth;
+
+       /* DRM counts alpha in depth, our driver doesn't. */
+       if (layer_primary->formats->alpha)
+               preferred_depth += 8;
+
+       mode_config->min_width = 64;
+       mode_config->max_width = 2048;
+       mode_config->min_height = 1;
+       mode_config->max_height = 2048;
+       mode_config->preferred_depth = preferred_depth;
+       mode_config->funcs = &logicvc_mode_config_funcs;
+
+       drm_mode_config_reset(drm_dev);
+
+       drm_kms_helper_poll_init(drm_dev);
+
+       return 0;
+}
+
+void logicvc_mode_fini(struct logicvc_drm *logicvc)
+{
+       struct drm_device *drm_dev = &logicvc->drm_dev;
+
+       drm_kms_helper_poll_fini(drm_dev);
+}
diff --git a/drivers/gpu/drm/logicvc/logicvc_mode.h b/drivers/gpu/drm/logicvc/logicvc_mode.h
new file mode 100644 (file)
index 0000000..fee538a
--- /dev/null
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2019-2022 Bootlin
+ * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
+ */
+
+#ifndef _LOGICVC_MODE_H_
+#define _LOGICVC_MODE_H_
+
+struct logicvc_drm;
+
+int logicvc_mode_init(struct logicvc_drm *logicvc);
+void logicvc_mode_fini(struct logicvc_drm *logicvc);
+
+#endif
diff --git a/drivers/gpu/drm/logicvc/logicvc_of.c b/drivers/gpu/drm/logicvc/logicvc_of.c
new file mode 100644 (file)
index 0000000..e068773
--- /dev/null
@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2019-2022 Bootlin
+ * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
+ */
+
+#include <drm/drm_print.h>
+
+#include "logicvc_drm.h"
+#include "logicvc_layer.h"
+#include "logicvc_of.h"
+
+static struct logicvc_of_property_sv logicvc_of_display_interface_sv[] = {
+       { "lvds-4bits", LOGICVC_DISPLAY_INTERFACE_LVDS_4BITS },
+       { "lvds-3bits", LOGICVC_DISPLAY_INTERFACE_LVDS_3BITS },
+       { },
+};
+
+static struct logicvc_of_property_sv logicvc_of_display_colorspace_sv[] = {
+       { "rgb",        LOGICVC_DISPLAY_COLORSPACE_RGB },
+       { "yuv422",     LOGICVC_DISPLAY_COLORSPACE_YUV422 },
+       { "yuv444",     LOGICVC_DISPLAY_COLORSPACE_YUV444 },
+       { },
+};
+
+static struct logicvc_of_property_sv logicvc_of_layer_colorspace_sv[] = {
+       { "rgb",        LOGICVC_LAYER_COLORSPACE_RGB },
+       { "yuv",        LOGICVC_LAYER_COLORSPACE_YUV },
+       { },
+};
+
+static struct logicvc_of_property_sv logicvc_of_layer_alpha_mode_sv[] = {
+       { "layer",      LOGICVC_LAYER_ALPHA_LAYER },
+       { "pixel",      LOGICVC_LAYER_ALPHA_PIXEL },
+       { },
+};
+
+static struct logicvc_of_property logicvc_of_properties[] = {
+       [LOGICVC_OF_PROPERTY_DISPLAY_INTERFACE] = {
+               .name           = "xylon,display-interface",
+               .sv             = logicvc_of_display_interface_sv,
+               .range          = {
+                       LOGICVC_DISPLAY_INTERFACE_LVDS_4BITS,
+                       LOGICVC_DISPLAY_INTERFACE_LVDS_3BITS,
+               },
+       },
+       [LOGICVC_OF_PROPERTY_DISPLAY_COLORSPACE] = {
+               .name           = "xylon,display-colorspace",
+               .sv             = logicvc_of_display_colorspace_sv,
+               .range          = {
+                       LOGICVC_DISPLAY_COLORSPACE_RGB,
+                       LOGICVC_DISPLAY_COLORSPACE_YUV444,
+               },
+       },
+       [LOGICVC_OF_PROPERTY_DISPLAY_DEPTH] = {
+               .name           = "xylon,display-depth",
+               .range          = { 8, 24 },
+       },
+       [LOGICVC_OF_PROPERTY_ROW_STRIDE] = {
+               .name           = "xylon,row-stride",
+       },
+       [LOGICVC_OF_PROPERTY_DITHERING] = {
+               .name           = "xylon,dithering",
+               .optional       = true,
+       },
+       [LOGICVC_OF_PROPERTY_BACKGROUND_LAYER] = {
+               .name           = "xylon,background-layer",
+               .optional       = true,
+       },
+       [LOGICVC_OF_PROPERTY_LAYERS_CONFIGURABLE] = {
+               .name           = "xylon,layers-configurable",
+               .optional       = true,
+       },
+       [LOGICVC_OF_PROPERTY_LAYERS_COUNT] = {
+               .name           = "xylon,layers-count",
+       },
+       [LOGICVC_OF_PROPERTY_LAYER_DEPTH] = {
+               .name           = "xylon,layer-depth",
+               .range          = { 8, 24 },
+       },
+       [LOGICVC_OF_PROPERTY_LAYER_COLORSPACE] = {
+               .name           = "xylon,layer-colorspace",
+               .sv             = logicvc_of_layer_colorspace_sv,
+               .range          = {
+                       LOGICVC_LAYER_COLORSPACE_RGB,
+                       LOGICVC_LAYER_COLORSPACE_RGB,
+               },
+       },
+       [LOGICVC_OF_PROPERTY_LAYER_ALPHA_MODE] = {
+               .name           = "xylon,layer-alpha-mode",
+               .sv             = logicvc_of_layer_alpha_mode_sv,
+               .range          = {
+                       LOGICVC_LAYER_ALPHA_LAYER,
+                       LOGICVC_LAYER_ALPHA_PIXEL,
+               },
+       },
+       [LOGICVC_OF_PROPERTY_LAYER_BASE_OFFSET] = {
+               .name           = "xylon,layer-base-offset",
+       },
+       [LOGICVC_OF_PROPERTY_LAYER_BUFFER_OFFSET] = {
+               .name           = "xylon,layer-buffer-offset",
+       },
+       [LOGICVC_OF_PROPERTY_LAYER_PRIMARY] = {
+               .name           = "xylon,layer-primary",
+               .optional       = true,
+       },
+};
+
+static int logicvc_of_property_sv_value(struct logicvc_of_property_sv *sv,
+                                       const char *string, u32 *value)
+{
+       unsigned int i = 0;
+
+       while (sv[i].string) {
+               if (!strcmp(sv[i].string, string)) {
+                       *value = sv[i].value;
+                       return 0;
+               }
+
+               i++;
+       }
+
+       return -EINVAL;
+}
+
+int logicvc_of_property_parse_u32(struct device_node *of_node,
+                                 unsigned int index, u32 *target)
+{
+       struct logicvc_of_property *property;
+       const char *string;
+       u32 value;
+       int ret;
+
+       if (index >= LOGICVC_OF_PROPERTY_MAXIMUM)
+               return -EINVAL;
+
+       property = &logicvc_of_properties[index];
+
+       if (!property->optional &&
+           !of_property_read_bool(of_node, property->name))
+               return -ENODEV;
+
+       if (property->sv) {
+               ret = of_property_read_string(of_node, property->name, &string);
+               if (ret)
+                       return ret;
+
+               ret = logicvc_of_property_sv_value(property->sv, string,
+                                                  &value);
+               if (ret)
+                       return ret;
+       } else {
+               ret = of_property_read_u32(of_node, property->name, &value);
+               if (ret)
+                       return ret;
+       }
+
+       if (property->range[0] || property->range[1])
+               if (value < property->range[0] || value > property->range[1])
+                       return -ERANGE;
+
+       *target = value;
+
+       return 0;
+}
+
+void logicvc_of_property_parse_bool(struct device_node *of_node,
+                                   unsigned int index, bool *target)
+{
+       struct logicvc_of_property *property;
+
+       if (index >= LOGICVC_OF_PROPERTY_MAXIMUM) {
+               /* Fallback. */
+               *target = false;
+               return;
+       }
+
+       property = &logicvc_of_properties[index];
+       *target = of_property_read_bool(of_node, property->name);
+}
+
+bool logicvc_of_node_is_layer(struct device_node *of_node)
+{
+       return !of_node_cmp(of_node->name, "layer");
+}
diff --git a/drivers/gpu/drm/logicvc/logicvc_of.h b/drivers/gpu/drm/logicvc/logicvc_of.h
new file mode 100644 (file)
index 0000000..46036e4
--- /dev/null
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2019-2022 Bootlin
+ * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
+ */
+
+#ifndef _LOGICVC_OF_H_
+#define _LOGICVC_OF_H_
+
+enum logicvc_of_property_index {
+       LOGICVC_OF_PROPERTY_DISPLAY_INTERFACE = 0,
+       LOGICVC_OF_PROPERTY_DISPLAY_COLORSPACE,
+       LOGICVC_OF_PROPERTY_DISPLAY_DEPTH,
+       LOGICVC_OF_PROPERTY_ROW_STRIDE,
+       LOGICVC_OF_PROPERTY_DITHERING,
+       LOGICVC_OF_PROPERTY_BACKGROUND_LAYER,
+       LOGICVC_OF_PROPERTY_LAYERS_CONFIGURABLE,
+       LOGICVC_OF_PROPERTY_LAYERS_COUNT,
+       LOGICVC_OF_PROPERTY_LAYER_DEPTH,
+       LOGICVC_OF_PROPERTY_LAYER_COLORSPACE,
+       LOGICVC_OF_PROPERTY_LAYER_ALPHA_MODE,
+       LOGICVC_OF_PROPERTY_LAYER_BASE_OFFSET,
+       LOGICVC_OF_PROPERTY_LAYER_BUFFER_OFFSET,
+       LOGICVC_OF_PROPERTY_LAYER_PRIMARY,
+       LOGICVC_OF_PROPERTY_MAXIMUM,
+};
+
+struct logicvc_of_property_sv {
+       const char *string;
+       u32 value;
+};
+
+struct logicvc_of_property {
+       char *name;
+       bool optional;
+       struct logicvc_of_property_sv *sv;
+       u32 range[2];
+};
+
+int logicvc_of_property_parse_u32(struct device_node *of_node,
+                                 unsigned int index, u32 *target);
+void logicvc_of_property_parse_bool(struct device_node *of_node,
+                                   unsigned int index, bool *target);
+bool logicvc_of_node_is_layer(struct device_node *of_node);
+
+#endif
diff --git a/drivers/gpu/drm/logicvc/logicvc_regs.h b/drivers/gpu/drm/logicvc/logicvc_regs.h
new file mode 100644 (file)
index 0000000..4aae27e
--- /dev/null
@@ -0,0 +1,80 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2019-2022 Bootlin
+ * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
+ *
+ * Copyright (C) 2014 Xylon d.o.o.
+ * Author: Davor Joja <davor.joja@logicbricks.com>
+ */
+
+#ifndef _LOGICVC_REGS_H_
+#define _LOGICVC_REGS_H_
+
+#define LOGICVC_DIMENSIONS_MAX         (BIT(16) - 1)
+
+#define LOGICVC_HSYNC_FRONT_PORCH_REG  0x00
+#define LOGICVC_HSYNC_REG              0x08
+#define LOGICVC_HSYNC_BACK_PORCH_REG   0x10
+#define LOGICVC_HRES_REG               0x18
+#define LOGICVC_VSYNC_FRONT_PORCH_REG  0x20
+#define LOGICVC_VSYNC_REG              0x28
+#define LOGICVC_VSYNC_BACK_PORCH_REG   0x30
+#define LOGICVC_VRES_REG               0x38
+
+#define LOGICVC_CTRL_REG               0x40
+#define LOGICVC_CTRL_CLOCK_INVERT      BIT(8)
+#define LOGICVC_CTRL_PIXEL_INVERT      BIT(7)
+#define LOGICVC_CTRL_DE_INVERT         BIT(5)
+#define LOGICVC_CTRL_DE_ENABLE         BIT(4)
+#define LOGICVC_CTRL_VSYNC_INVERT      BIT(3)
+#define LOGICVC_CTRL_VSYNC_ENABLE      BIT(2)
+#define LOGICVC_CTRL_HSYNC_INVERT      BIT(1)
+#define LOGICVC_CTRL_HSYNC_ENABLE      BIT(0)
+
+#define LOGICVC_DTYPE_REG              0x48
+#define LOGICVC_BACKGROUND_COLOR_REG   0x50
+
+#define LOGICVC_BUFFER_SEL_REG         0x58
+#define LOGICVC_BUFFER_SEL_VALUE(i, v) \
+       (BIT(10 + (i)) | ((v) << (2 * (i))))
+#define LOGICVC_BUFFER_SEL_MAX         2
+
+#define LOGICVC_DOUBLE_CLUT_REG                0x60
+
+#define LOGICVC_INT_STAT_REG           0x68
+#define LOGICVC_INT_STAT_V_SYNC                BIT(5)
+
+#define LOGICVC_INT_MASK_REG           0x70
+#define LOGICVC_INT_MASK_V_SYNC                BIT(5)
+
+#define LOGICVC_POWER_CTRL_REG         0x78
+#define LOGICVC_POWER_CTRL_BACKLIGHT_ENABLE    BIT(0)
+#define LOGICVC_POWER_CTRL_VDD_ENABLE          BIT(1)
+#define LOGICVC_POWER_CTRL_VEE_ENABLE          BIT(2)
+#define LOGICVC_POWER_CTRL_VIDEO_ENABLE                BIT(3)
+
+#define LOGICVC_IP_VERSION_REG         0xf8
+#define LOGICVC_IP_VERSION_MAJOR_MASK  GENMASK(16, 11)
+#define LOGICVC_IP_VERSION_MINOR_MASK  GENMASK(10, 5)
+#define LOGICVC_IP_VERSION_LEVEL_MASK  GENMASK(4, 0)
+
+#define LOGICVC_LAYER_ADDRESS_REG(i)   (0x100 + (i) * 0x80)
+#define LOGICVC_LAYER_HOFFSET_REG(i)   (0x100 + (i) * 0x80)
+
+#define LOGICVC_LAYER_VOFFSET_REG(i)   (0x108 + (i) * 0x80)
+#define LOGICVC_LAYER_VOFFSET_MAX      4095
+
+#define LOGICVC_LAYER_HPOSITION_REG(i) (0x110 + (i) * 0x80)
+#define LOGICVC_LAYER_VPOSITION_REG(i) (0x118 + (i) * 0x80)
+#define LOGICVC_LAYER_WIDTH_REG(i)     (0x120 + (i) * 0x80)
+#define LOGICVC_LAYER_HEIGHT_REG(i)    (0x128 + (i) * 0x80)
+#define LOGICVC_LAYER_ALPHA_REG(i)     (0x130 + (i) * 0x80)
+
+#define LOGICVC_LAYER_CTRL_REG(i)      (0x138 + (i) * 0x80)
+#define LOGICVC_LAYER_CTRL_ENABLE      BIT(0)
+#define LOGICVC_LAYER_CTRL_COLOR_KEY_DISABLE   BIT(1)
+#define LOGICVC_LAYER_CTRL_PIXEL_FORMAT_INVERT BIT(4)
+
+#define LOGICVC_LAYER_COLOR_KEY_REG(i) (0x140 + (i) * 0x80)
+
+#endif
index 361eb7d..73e8e4e 100644 (file)
@@ -8,7 +8,6 @@
 
 #include <linux/module.h>
 #include <linux/pci.h>
-#include <linux/vmalloc.h>
 
 #include <drm/drm_aperture.h>
 #include <drm/drm_drv.h>
index 616e113..6743859 100644 (file)
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0-only
 
 #include <linux/pci.h>
+#include <linux/vmalloc.h>
 
 #include <drm/drm_drv.h>
 
index 8f9fed9..57ae14a 100644 (file)
@@ -102,14 +102,12 @@ static int dp_parser_ctrl_res(struct dp_parser *parser)
 static int dp_parser_misc(struct dp_parser *parser)
 {
        struct device_node *of_node = parser->pdev->dev.of_node;
-       int len = 0;
-       const char *data_lane_property = "data-lanes";
+       int len;
 
-       len = of_property_count_elems_of_size(of_node,
-                        data_lane_property, sizeof(u32));
+       len = drm_of_get_data_lanes_count(of_node, 1, DP_MAX_NUM_DP_LANES);
        if (len < 0) {
-               DRM_WARN("Invalid property %s, default max DP lanes = %d\n",
-                               data_lane_property, DP_MAX_NUM_DP_LANES);
+               DRM_WARN("Invalid property \"data-lanes\", default max DP lanes = %d\n",
+                        DP_MAX_NUM_DP_LANES);
                len = DP_MAX_NUM_DP_LANES;
        }
 
index a95d5df..208e9ed 100644 (file)
@@ -21,6 +21,8 @@
 
 #include <video/mipi_display.h>
 
+#include <drm/drm_of.h>
+
 #include "dsi.h"
 #include "dsi.xml.h"
 #include "sfpb.xml.h"
@@ -1779,11 +1781,10 @@ static int dsi_host_parse_lane_data(struct msm_dsi_host *msm_host,
                return 0;
        }
 
-       num_lanes = len / sizeof(u32);
-
-       if (num_lanes < 1 || num_lanes > 4) {
+       num_lanes = drm_of_get_data_lanes_count(ep, 1, 4);
+       if (num_lanes < 0) {
                DRM_DEV_ERROR(dev, "bad number of data lanes\n");
-               return -EINVAL;
+               return num_lanes;
        }
 
        msm_host->num_data_lanes = num_lanes;
index bd0fc41..d6969c0 100644 (file)
 #define DRF_MD_(X,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,IMPL,...) IMPL
 #define DRF_MD(A...) DRF_MD_(X, ##A, DRF_MD_I, DRF_MD_N)(X, ##A)
 
-/* Helper for testing against field value in aribtrary object */
+/* Helper for testing against field value in arbitrary object */
 #define DRF_TV_N(X,e,p,o,d,r,  f,cmp,v)                                          \
        NVVAL_TEST_X(DRF_RD_X(e, (p), (o), d##_##r   ), d##_##r##_##f, cmp, (v))
 #define DRF_TV_I(X,e,p,o,d,r,i,f,cmp,v)                                          \
 #define DRF_TV_(X,_1,_2,_3,_4,_5,_6,_7,_8,_9,IMPL,...) IMPL
 #define DRF_TV(A...) DRF_TV_(X, ##A, DRF_TV_I, DRF_TV_N)(X, ##A)
 
-/* Helper for testing against field definition in aribtrary object */
+/* Helper for testing against field definition in arbitrary object */
 #define DRF_TD_N(X,e,p,o,d,r,  f,cmp,v)                                                          \
        NVVAL_TEST_X(DRF_RD_X(e, (p), (o), d##_##r   ), d##_##r##_##f, cmp, d##_##r##_##f##_##v)
 #define DRF_TD_I(X,e,p,o,d,r,i,f,cmp,v)                                                          \
index fac1bff..cfa8a0c 100644 (file)
@@ -19,7 +19,7 @@ struct nvbios_source {
 int nvbios_extend(struct nvkm_bios *, u32 length);
 int nvbios_shadow(struct nvkm_bios *);
 
-extern const struct nvbios_source nvbios_rom;
+extern const struct nvbios_source nvbios_prom;
 extern const struct nvbios_source nvbios_ramin;
 extern const struct nvbios_source nvbios_acpi_fast;
 extern const struct nvbios_source nvbios_acpi_slow;
index 4b571cc..1918868 100644 (file)
@@ -171,7 +171,7 @@ nvbios_shadow(struct nvkm_bios *bios)
        struct shadow mthds[] = {
                { 0, &nvbios_of },
                { 0, &nvbios_ramin },
-               { 0, &nvbios_rom },
+               { 0, &nvbios_prom },
                { 0, &nvbios_acpi_fast },
                { 4, &nvbios_acpi_slow },
                { 1, &nvbios_pcirom },
index ffa4b39..39144ce 100644 (file)
@@ -25,7 +25,7 @@
 #include <subdev/pci.h>
 
 static u32
-prom_read(void *data, u32 offset, u32 length, struct nvkm_bios *bios)
+nvbios_prom_read(void *data, u32 offset, u32 length, struct nvkm_bios *bios)
 {
        struct nvkm_device *device = data;
        u32 i;
@@ -38,14 +38,14 @@ prom_read(void *data, u32 offset, u32 length, struct nvkm_bios *bios)
 }
 
 static void
-prom_fini(void *data)
+nvbios_prom_fini(void *data)
 {
        struct nvkm_device *device = data;
        nvkm_pci_rom_shadow(device->pci, true);
 }
 
 static void *
-prom_init(struct nvkm_bios *bios, const char *name)
+nvbios_prom_init(struct nvkm_bios *bios, const char *name)
 {
        struct nvkm_device *device = bios->subdev.device;
        if (device->card_type == NV_40 && device->chipset >= 0x4c)
@@ -55,10 +55,10 @@ prom_init(struct nvkm_bios *bios, const char *name)
 }
 
 const struct nvbios_source
-nvbios_rom = {
+nvbios_prom = {
        .name = "PROM",
-       .init = prom_init,
-       .fini = prom_fini,
-       .read = prom_read,
+       .init = nvbios_prom_init,
+       .fini = nvbios_prom_fini,
+       .read = nvbios_prom_read,
        .rw = false,
 };
index 231f371..6d6ce42 100644 (file)
@@ -628,6 +628,10 @@ static int nt36672a_panel_add(struct nt36672a_panel *pinfo)
 
        drm_panel_init(&pinfo->base, dev, &panel_funcs, DRM_MODE_CONNECTOR_DSI);
 
+       ret = drm_panel_of_backlight(&pinfo->base);
+       if (ret)
+               return dev_err_probe(dev, ret, "Failed to get backlight\n");
+
        drm_panel_add(&pinfo->base);
 
        return 0;
index a054e4a..9bf6d4c 100644 (file)
@@ -33,8 +33,6 @@
 #include "qxl_drv.h"
 #include "qxl_object.h"
 
-int qxl_log_level;
-
 static bool qxl_check_device(struct qxl_device *qdev)
 {
        struct qxl_rom *rom = qdev->rom;
index 891bb95..31ed285 100644 (file)
@@ -679,23 +679,12 @@ static const struct mipi_dsi_host_ops rcar_mipi_dsi_host_ops = {
 
 static int rcar_mipi_dsi_parse_dt(struct rcar_mipi_dsi *dsi)
 {
-       struct device_node *ep;
-       u32 data_lanes[4];
        int ret;
 
-       ep = of_graph_get_endpoint_by_regs(dsi->dev->of_node, 1, 0);
-       if (!ep) {
-               dev_dbg(dsi->dev, "unconnected port@1\n");
-               return -ENODEV;
-       }
-
-       ret = of_property_read_variable_u32_array(ep, "data-lanes", data_lanes,
-                                                 1, 4);
-       of_node_put(ep);
-
+       ret = drm_of_get_data_lanes_count_ep(dsi->dev->of_node, 1, 0, 1, 4);
        if (ret < 0) {
                dev_err(dsi->dev, "missing or invalid data-lanes property\n");
-               return -ENODEV;
+               return ret;
        }
 
        dsi->num_data_lanes = ret;
index bffe1b9..f082b8e 100644 (file)
@@ -151,16 +151,11 @@ struct sun8i_hdmi_phy;
 struct sun8i_hdmi_phy_variant {
        bool has_phy_clk;
        bool has_second_pll;
-       unsigned int is_custom_phy : 1;
        const struct dw_hdmi_curr_ctrl *cur_ctr;
        const struct dw_hdmi_mpll_config *mpll_cfg;
        const struct dw_hdmi_phy_config *phy_cfg;
+       const struct dw_hdmi_phy_ops *phy_ops;
        void (*phy_init)(struct sun8i_hdmi_phy *phy);
-       void (*phy_disable)(struct dw_hdmi *hdmi,
-                           struct sun8i_hdmi_phy *phy);
-       int  (*phy_config)(struct dw_hdmi *hdmi,
-                          struct sun8i_hdmi_phy *phy,
-                          unsigned int clk_rate);
 };
 
 struct sun8i_hdmi_phy {
@@ -173,7 +168,7 @@ struct sun8i_hdmi_phy {
        unsigned int                    rcal;
        struct regmap                   *regs;
        struct reset_control            *rst_phy;
-       struct sun8i_hdmi_phy_variant   *variant;
+       const struct sun8i_hdmi_phy_variant *variant;
 };
 
 struct sun8i_dw_hdmi_quirks {
index 2860e6b..ca53b5e 100644 (file)
@@ -123,10 +123,30 @@ static const struct dw_hdmi_phy_config sun50i_h6_phy_config[] = {
        { ~0UL,      0x0000, 0x0000, 0x0000}
 };
 
-static int sun8i_hdmi_phy_config_a83t(struct dw_hdmi *hdmi,
-                                     struct sun8i_hdmi_phy *phy,
-                                     unsigned int clk_rate)
+static void sun8i_hdmi_phy_set_polarity(struct sun8i_hdmi_phy *phy,
+                                       const struct drm_display_mode *mode)
 {
+       u32 val = 0;
+
+       if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+               val |= SUN8I_HDMI_PHY_DBG_CTRL_POL_NHSYNC;
+
+       if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+               val |= SUN8I_HDMI_PHY_DBG_CTRL_POL_NVSYNC;
+
+       regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_DBG_CTRL_REG,
+                          SUN8I_HDMI_PHY_DBG_CTRL_POL_MASK, val);
+};
+
+static int sun8i_a83t_hdmi_phy_config(struct dw_hdmi *hdmi, void *data,
+                                     const struct drm_display_info *display,
+                                     const struct drm_display_mode *mode)
+{
+       unsigned int clk_rate = mode->crtc_clock * 1000;
+       struct sun8i_hdmi_phy *phy = data;
+
+       sun8i_hdmi_phy_set_polarity(phy, mode);
+
        regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_REXT_CTRL_REG,
                           SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN,
                           SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN);
@@ -185,10 +205,31 @@ static int sun8i_hdmi_phy_config_a83t(struct dw_hdmi *hdmi,
        return 0;
 }
 
-static int sun8i_hdmi_phy_config_h3(struct dw_hdmi *hdmi,
-                                   struct sun8i_hdmi_phy *phy,
-                                   unsigned int clk_rate)
+static void sun8i_a83t_hdmi_phy_disable(struct dw_hdmi *hdmi, void *data)
 {
+       struct sun8i_hdmi_phy *phy = data;
+
+       dw_hdmi_phy_gen2_txpwron(hdmi, 0);
+       dw_hdmi_phy_gen2_pddq(hdmi, 1);
+
+       regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_REXT_CTRL_REG,
+                          SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN, 0);
+}
+
+static const struct dw_hdmi_phy_ops sun8i_a83t_hdmi_phy_ops = {
+       .init           = sun8i_a83t_hdmi_phy_config,
+       .disable        = sun8i_a83t_hdmi_phy_disable,
+       .read_hpd       = dw_hdmi_phy_read_hpd,
+       .update_hpd     = dw_hdmi_phy_update_hpd,
+       .setup_hpd      = dw_hdmi_phy_setup_hpd,
+};
+
+static int sun8i_h3_hdmi_phy_config(struct dw_hdmi *hdmi, void *data,
+                                   const struct drm_display_info *display,
+                                   const struct drm_display_mode *mode)
+{
+       unsigned int clk_rate = mode->crtc_clock * 1000;
+       struct sun8i_hdmi_phy *phy = data;
        u32 pll_cfg1_init;
        u32 pll_cfg2_init;
        u32 ana_cfg1_end;
@@ -197,6 +238,11 @@ static int sun8i_hdmi_phy_config_h3(struct dw_hdmi *hdmi,
        u32 b_offset = 0;
        u32 val;
 
+       if (phy->variant->has_phy_clk)
+               clk_set_rate(phy->clk_phy, clk_rate);
+
+       sun8i_hdmi_phy_set_polarity(phy, mode);
+
        /* bandwidth / frequency independent settings */
 
        pll_cfg1_init = SUN8I_HDMI_PHY_PLL_CFG1_LDO2_EN |
@@ -333,41 +379,10 @@ static int sun8i_hdmi_phy_config_h3(struct dw_hdmi *hdmi,
        return 0;
 }
 
-static int sun8i_hdmi_phy_config(struct dw_hdmi *hdmi, void *data,
-                                const struct drm_display_info *display,
-                                const struct drm_display_mode *mode)
-{
-       struct sun8i_hdmi_phy *phy = (struct sun8i_hdmi_phy *)data;
-       u32 val = 0;
-
-       if (mode->flags & DRM_MODE_FLAG_NHSYNC)
-               val |= SUN8I_HDMI_PHY_DBG_CTRL_POL_NHSYNC;
-
-       if (mode->flags & DRM_MODE_FLAG_NVSYNC)
-               val |= SUN8I_HDMI_PHY_DBG_CTRL_POL_NVSYNC;
-
-       regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_DBG_CTRL_REG,
-                          SUN8I_HDMI_PHY_DBG_CTRL_POL_MASK, val);
-
-       if (phy->variant->has_phy_clk)
-               clk_set_rate(phy->clk_phy, mode->crtc_clock * 1000);
-
-       return phy->variant->phy_config(hdmi, phy, mode->crtc_clock * 1000);
-};
-
-static void sun8i_hdmi_phy_disable_a83t(struct dw_hdmi *hdmi,
-                                       struct sun8i_hdmi_phy *phy)
+static void sun8i_h3_hdmi_phy_disable(struct dw_hdmi *hdmi, void *data)
 {
-       dw_hdmi_phy_gen2_txpwron(hdmi, 0);
-       dw_hdmi_phy_gen2_pddq(hdmi, 1);
-
-       regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_REXT_CTRL_REG,
-                          SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN, 0);
-}
+       struct sun8i_hdmi_phy *phy = data;
 
-static void sun8i_hdmi_phy_disable_h3(struct dw_hdmi *hdmi,
-                                     struct sun8i_hdmi_phy *phy)
-{
        regmap_write(phy->regs, SUN8I_HDMI_PHY_ANA_CFG1_REG,
                     SUN8I_HDMI_PHY_ANA_CFG1_LDOEN |
                     SUN8I_HDMI_PHY_ANA_CFG1_ENVBS |
@@ -375,19 +390,12 @@ static void sun8i_hdmi_phy_disable_h3(struct dw_hdmi *hdmi,
        regmap_write(phy->regs, SUN8I_HDMI_PHY_PLL_CFG1_REG, 0);
 }
 
-static void sun8i_hdmi_phy_disable(struct dw_hdmi *hdmi, void *data)
-{
-       struct sun8i_hdmi_phy *phy = (struct sun8i_hdmi_phy *)data;
-
-       phy->variant->phy_disable(hdmi, phy);
-}
-
-static const struct dw_hdmi_phy_ops sun8i_hdmi_phy_ops = {
-       .init = &sun8i_hdmi_phy_config,
-       .disable = &sun8i_hdmi_phy_disable,
-       .read_hpd = &dw_hdmi_phy_read_hpd,
-       .update_hpd = &dw_hdmi_phy_update_hpd,
-       .setup_hpd = &dw_hdmi_phy_setup_hpd,
+static const struct dw_hdmi_phy_ops sun8i_h3_hdmi_phy_ops = {
+       .init           = sun8i_h3_hdmi_phy_config,
+       .disable        = sun8i_h3_hdmi_phy_disable,
+       .read_hpd       = dw_hdmi_phy_read_hpd,
+       .update_hpd     = dw_hdmi_phy_update_hpd,
+       .setup_hpd      = dw_hdmi_phy_setup_hpd,
 };
 
 static void sun8i_hdmi_phy_unlock(struct sun8i_hdmi_phy *phy)
@@ -565,10 +573,10 @@ void sun8i_hdmi_phy_deinit(struct sun8i_hdmi_phy *phy)
 void sun8i_hdmi_phy_set_ops(struct sun8i_hdmi_phy *phy,
                            struct dw_hdmi_plat_data *plat_data)
 {
-       struct sun8i_hdmi_phy_variant *variant = phy->variant;
+       const struct sun8i_hdmi_phy_variant *variant = phy->variant;
 
-       if (variant->is_custom_phy) {
-               plat_data->phy_ops = &sun8i_hdmi_phy_ops;
+       if (variant->phy_ops) {
+               plat_data->phy_ops = variant->phy_ops;
                plat_data->phy_name = "sun8i_dw_hdmi_phy";
                plat_data->phy_data = phy;
        } else {
@@ -587,35 +595,27 @@ static const struct regmap_config sun8i_hdmi_phy_regmap_config = {
 };
 
 static const struct sun8i_hdmi_phy_variant sun8i_a83t_hdmi_phy = {
-       .is_custom_phy = true,
+       .phy_ops = &sun8i_a83t_hdmi_phy_ops,
        .phy_init = &sun8i_hdmi_phy_init_a83t,
-       .phy_disable = &sun8i_hdmi_phy_disable_a83t,
-       .phy_config = &sun8i_hdmi_phy_config_a83t,
 };
 
 static const struct sun8i_hdmi_phy_variant sun8i_h3_hdmi_phy = {
        .has_phy_clk = true,
-       .is_custom_phy = true,
+       .phy_ops = &sun8i_h3_hdmi_phy_ops,
        .phy_init = &sun8i_hdmi_phy_init_h3,
-       .phy_disable = &sun8i_hdmi_phy_disable_h3,
-       .phy_config = &sun8i_hdmi_phy_config_h3,
 };
 
 static const struct sun8i_hdmi_phy_variant sun8i_r40_hdmi_phy = {
        .has_phy_clk = true,
        .has_second_pll = true,
-       .is_custom_phy = true,
+       .phy_ops = &sun8i_h3_hdmi_phy_ops,
        .phy_init = &sun8i_hdmi_phy_init_h3,
-       .phy_disable = &sun8i_hdmi_phy_disable_h3,
-       .phy_config = &sun8i_hdmi_phy_config_h3,
 };
 
 static const struct sun8i_hdmi_phy_variant sun50i_a64_hdmi_phy = {
        .has_phy_clk = true,
-       .is_custom_phy = true,
+       .phy_ops = &sun8i_h3_hdmi_phy_ops,
        .phy_init = &sun8i_hdmi_phy_init_h3,
-       .phy_disable = &sun8i_hdmi_phy_disable_h3,
-       .phy_config = &sun8i_hdmi_phy_config_h3,
 };
 
 static const struct sun8i_hdmi_phy_variant sun50i_h6_hdmi_phy = {
@@ -672,116 +672,64 @@ int sun8i_hdmi_phy_get(struct sun8i_dw_hdmi *hdmi, struct device_node *node)
 
 static int sun8i_hdmi_phy_probe(struct platform_device *pdev)
 {
-       const struct of_device_id *match;
        struct device *dev = &pdev->dev;
-       struct device_node *node = dev->of_node;
        struct sun8i_hdmi_phy *phy;
-       struct resource res;
        void __iomem *regs;
-       int ret;
-
-       match = of_match_node(sun8i_hdmi_phy_of_table, node);
-       if (!match) {
-               dev_err(dev, "Incompatible HDMI PHY\n");
-               return -EINVAL;
-       }
 
        phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
        if (!phy)
                return -ENOMEM;
 
-       phy->variant = (struct sun8i_hdmi_phy_variant *)match->data;
+       phy->variant = of_device_get_match_data(dev);
        phy->dev = dev;
 
-       ret = of_address_to_resource(node, 0, &res);
-       if (ret) {
-               dev_err(dev, "phy: Couldn't get our resources\n");
-               return ret;
-       }
-
-       regs = devm_ioremap_resource(dev, &res);
-       if (IS_ERR(regs)) {
-               dev_err(dev, "Couldn't map the HDMI PHY registers\n");
-               return PTR_ERR(regs);
-       }
+       regs = devm_platform_ioremap_resource(pdev, 0);
+       if (IS_ERR(regs))
+               return dev_err_probe(dev, PTR_ERR(regs),
+                                    "Couldn't map the HDMI PHY registers\n");
 
        phy->regs = devm_regmap_init_mmio(dev, regs,
                                          &sun8i_hdmi_phy_regmap_config);
-       if (IS_ERR(phy->regs)) {
-               dev_err(dev, "Couldn't create the HDMI PHY regmap\n");
-               return PTR_ERR(phy->regs);
-       }
+       if (IS_ERR(phy->regs))
+               return dev_err_probe(dev, PTR_ERR(phy->regs),
+                                    "Couldn't create the HDMI PHY regmap\n");
 
-       phy->clk_bus = of_clk_get_by_name(node, "bus");
-       if (IS_ERR(phy->clk_bus)) {
-               dev_err(dev, "Could not get bus clock\n");
-               return PTR_ERR(phy->clk_bus);
-       }
+       phy->clk_bus = devm_clk_get(dev, "bus");
+       if (IS_ERR(phy->clk_bus))
+               return dev_err_probe(dev, PTR_ERR(phy->clk_bus),
+                                    "Could not get bus clock\n");
 
-       phy->clk_mod = of_clk_get_by_name(node, "mod");
-       if (IS_ERR(phy->clk_mod)) {
-               dev_err(dev, "Could not get mod clock\n");
-               ret = PTR_ERR(phy->clk_mod);
-               goto err_put_clk_bus;
-       }
+       phy->clk_mod = devm_clk_get(dev, "mod");
+       if (IS_ERR(phy->clk_mod))
+               return dev_err_probe(dev, PTR_ERR(phy->clk_mod),
+                                    "Could not get mod clock\n");
 
        if (phy->variant->has_phy_clk) {
-               phy->clk_pll0 = of_clk_get_by_name(node, "pll-0");
-               if (IS_ERR(phy->clk_pll0)) {
-                       dev_err(dev, "Could not get pll-0 clock\n");
-                       ret = PTR_ERR(phy->clk_pll0);
-                       goto err_put_clk_mod;
-               }
+               phy->clk_pll0 = devm_clk_get(dev, "pll-0");
+               if (IS_ERR(phy->clk_pll0))
+                       return dev_err_probe(dev, PTR_ERR(phy->clk_pll0),
+                                            "Could not get pll-0 clock\n");
 
                if (phy->variant->has_second_pll) {
-                       phy->clk_pll1 = of_clk_get_by_name(node, "pll-1");
-                       if (IS_ERR(phy->clk_pll1)) {
-                               dev_err(dev, "Could not get pll-1 clock\n");
-                               ret = PTR_ERR(phy->clk_pll1);
-                               goto err_put_clk_pll0;
-                       }
+                       phy->clk_pll1 = devm_clk_get(dev, "pll-1");
+                       if (IS_ERR(phy->clk_pll1))
+                               return dev_err_probe(dev, PTR_ERR(phy->clk_pll1),
+                                                    "Could not get pll-1 clock\n");
                }
        }
 
-       phy->rst_phy = of_reset_control_get_shared(node, "phy");
-       if (IS_ERR(phy->rst_phy)) {
-               dev_err(dev, "Could not get phy reset control\n");
-               ret = PTR_ERR(phy->rst_phy);
-               goto err_put_clk_pll1;
-       }
+       phy->rst_phy = devm_reset_control_get_shared(dev, "phy");
+       if (IS_ERR(phy->rst_phy))
+               return dev_err_probe(dev, PTR_ERR(phy->rst_phy),
+                                    "Could not get phy reset control\n");
 
        platform_set_drvdata(pdev, phy);
 
        return 0;
-
-err_put_clk_pll1:
-       clk_put(phy->clk_pll1);
-err_put_clk_pll0:
-       clk_put(phy->clk_pll0);
-err_put_clk_mod:
-       clk_put(phy->clk_mod);
-err_put_clk_bus:
-       clk_put(phy->clk_bus);
-
-       return ret;
-}
-
-static int sun8i_hdmi_phy_remove(struct platform_device *pdev)
-{
-       struct sun8i_hdmi_phy *phy = platform_get_drvdata(pdev);
-
-       reset_control_put(phy->rst_phy);
-
-       clk_put(phy->clk_pll0);
-       clk_put(phy->clk_pll1);
-       clk_put(phy->clk_mod);
-       clk_put(phy->clk_bus);
-       return 0;
 }
 
 struct platform_driver sun8i_hdmi_phy_driver = {
        .probe  = sun8i_hdmi_phy_probe,
-       .remove = sun8i_hdmi_phy_remove,
        .driver = {
                .name = "sun8i-hdmi-phy",
                .of_match_table = sun8i_hdmi_phy_of_table,
index ed971c8..4f8bf86 100644 (file)
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 
+#include <linux/module.h>
 #include <linux/pci.h>
 
 #include <drm/drm_aperture.h>
index e973ec4..ce62c59 100644 (file)
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 config DRM_V3D
        tristate "Broadcom V3D 3.x and newer"
-       depends on ARCH_BCM || ARCH_BRCMSTB || COMPILE_TEST
+       depends on ARCH_BCM || ARCH_BRCMSTB || ARCH_BCM2835 || COMPILE_TEST
        depends on DRM
        depends on COMMON_CLK
        depends on MMU
@@ -9,4 +9,5 @@ config DRM_V3D
        select DRM_GEM_SHMEM_HELPER
        help
          Choose this option if you have a system that has a Broadcom
-         V3D 3.x or newer GPU, such as BCM7268.
+         V3D 3.x or newer GPUs. SoCs supported include the BCM2711,
+         BCM7268 and BCM7278.
index 29fd131..efbde12 100644 (file)
@@ -4,7 +4,6 @@
 #include <linux/circ_buf.h>
 #include <linux/ctype.h>
 #include <linux/debugfs.h>
-#include <linux/pm_runtime.h>
 #include <linux/seq_file.h>
 #include <linux/string_helpers.h>
 
@@ -131,11 +130,7 @@ static int v3d_v3d_debugfs_ident(struct seq_file *m, void *unused)
        struct drm_device *dev = node->minor->dev;
        struct v3d_dev *v3d = to_v3d_dev(dev);
        u32 ident0, ident1, ident2, ident3, cores;
-       int ret, core;
-
-       ret = pm_runtime_get_sync(v3d->drm.dev);
-       if (ret < 0)
-               return ret;
+       int core;
 
        ident0 = V3D_READ(V3D_HUB_IDENT0);
        ident1 = V3D_READ(V3D_HUB_IDENT1);
@@ -188,9 +183,6 @@ static int v3d_v3d_debugfs_ident(struct seq_file *m, void *unused)
                           (misccfg & V3D_MISCCFG_OVRTMUOUT) != 0);
        }
 
-       pm_runtime_mark_last_busy(v3d->drm.dev);
-       pm_runtime_put_autosuspend(v3d->drm.dev);
-
        return 0;
 }
 
@@ -218,11 +210,6 @@ static int v3d_measure_clock(struct seq_file *m, void *unused)
        uint32_t cycles;
        int core = 0;
        int measure_ms = 1000;
-       int ret;
-
-       ret = pm_runtime_get_sync(v3d->drm.dev);
-       if (ret < 0)
-               return ret;
 
        if (v3d->ver >= 40) {
                V3D_CORE_WRITE(core, V3D_V4_PCTR_0_SRC_0_3,
@@ -246,9 +233,6 @@ static int v3d_measure_clock(struct seq_file *m, void *unused)
                   cycles / (measure_ms * 1000),
                   (cycles / (measure_ms * 100)) % 10);
 
-       pm_runtime_mark_last_busy(v3d->drm.dev);
-       pm_runtime_put_autosuspend(v3d->drm.dev);
-
        return 0;
 }
 
index 1afcd54..8c7f910 100644 (file)
@@ -19,7 +19,6 @@
 #include <linux/module.h>
 #include <linux/of_platform.h>
 #include <linux/platform_device.h>
-#include <linux/pm_runtime.h>
 #include <linux/reset.h>
 
 #include <drm/drm_drv.h>
@@ -43,7 +42,6 @@ static int v3d_get_param_ioctl(struct drm_device *dev, void *data,
 {
        struct v3d_dev *v3d = to_v3d_dev(dev);
        struct drm_v3d_get_param *args = data;
-       int ret;
        static const u32 reg_map[] = {
                [DRM_V3D_PARAM_V3D_UIFCFG] = V3D_HUB_UIFCFG,
                [DRM_V3D_PARAM_V3D_HUB_IDENT1] = V3D_HUB_IDENT1,
@@ -69,17 +67,12 @@ static int v3d_get_param_ioctl(struct drm_device *dev, void *data,
                if (args->value != 0)
                        return -EINVAL;
 
-               ret = pm_runtime_get_sync(v3d->drm.dev);
-               if (ret < 0)
-                       return ret;
                if (args->param >= DRM_V3D_PARAM_V3D_CORE0_IDENT0 &&
                    args->param <= DRM_V3D_PARAM_V3D_CORE0_IDENT2) {
                        args->value = V3D_CORE_READ(0, offset);
                } else {
                        args->value = V3D_READ(offset);
                }
-               pm_runtime_mark_last_busy(v3d->drm.dev);
-               pm_runtime_put_autosuspend(v3d->drm.dev);
                return 0;
        }
 
@@ -198,6 +191,7 @@ static const struct drm_driver v3d_drm_driver = {
 };
 
 static const struct of_device_id v3d_of_match[] = {
+       { .compatible = "brcm,2711-v3d" },
        { .compatible = "brcm,7268-v3d" },
        { .compatible = "brcm,7278-v3d" },
        {},
@@ -280,10 +274,6 @@ static int v3d_platform_drm_probe(struct platform_device *pdev)
                return -ENOMEM;
        }
 
-       pm_runtime_use_autosuspend(dev);
-       pm_runtime_set_autosuspend_delay(dev, 50);
-       pm_runtime_enable(dev);
-
        ret = v3d_gem_init(drm);
        if (ret)
                goto dma_free;
index 2352e96..725a252 100644 (file)
@@ -6,7 +6,6 @@
 #include <linux/io.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
-#include <linux/pm_runtime.h>
 #include <linux/reset.h>
 #include <linux/sched/signal.h>
 #include <linux/uaccess.h>
@@ -372,9 +371,6 @@ v3d_job_free(struct kref *ref)
        dma_fence_put(job->irq_fence);
        dma_fence_put(job->done_fence);
 
-       pm_runtime_mark_last_busy(job->v3d->drm.dev);
-       pm_runtime_put_autosuspend(job->v3d->drm.dev);
-
        if (job->perfmon)
                v3d_perfmon_put(job->perfmon);
 
@@ -476,14 +472,10 @@ v3d_job_init(struct v3d_dev *v3d, struct drm_file *file_priv,
        job->v3d = v3d;
        job->free = free;
 
-       ret = pm_runtime_get_sync(v3d->drm.dev);
-       if (ret < 0)
-               goto fail;
-
        ret = drm_sched_job_init(&job->base, &v3d_priv->sched_entity[queue],
                                 v3d_priv);
        if (ret)
-               goto fail_job;
+               goto fail;
 
        if (has_multisync) {
                if (se->in_sync_count && se->wait_stage == queue) {
@@ -514,8 +506,6 @@ v3d_job_init(struct v3d_dev *v3d, struct drm_file *file_priv,
 
 fail_deps:
        drm_sched_job_cleanup(&job->base);
-fail_job:
-       pm_runtime_put_autosuspend(v3d->drm.dev);
 fail:
        kfree(*container);
        *container = NULL;
index f8d8335..9b27021 100644 (file)
@@ -580,8 +580,10 @@ static int virtio_gpu_get_caps_ioctl(struct drm_device *dev,
        spin_unlock(&vgdev->display_info_lock);
 
        /* not in cache - need to talk to hw */
-       virtio_gpu_cmd_get_capset(vgdev, found_valid, args->cap_set_ver,
-                                 &cache_ent);
+       ret = virtio_gpu_cmd_get_capset(vgdev, found_valid, args->cap_set_ver,
+                                       &cache_ent);
+       if (ret)
+               return ret;
        virtio_gpu_notify(vgdev);
 
 copy_exit:
index f293e6a..1cc8f3f 100644 (file)
@@ -168,9 +168,9 @@ static int virtio_gpu_object_shmem_init(struct virtio_gpu_device *vgdev,
         * since virtio_gpu doesn't support dma-buf import from other devices.
         */
        shmem->pages = drm_gem_shmem_get_sg_table(&bo->base);
-       if (!shmem->pages) {
+       if (IS_ERR(shmem->pages)) {
                drm_gem_shmem_unpin(&bo->base);
-               return -EINVAL;
+               return PTR_ERR(shmem->pages);
        }
 
        if (use_dma_api) {
index 55d80b7..44425f2 100644 (file)
@@ -90,7 +90,6 @@ static const struct virtio_dma_buf_ops virtgpu_dmabuf_ops =  {
 int virtio_gpu_resource_assign_uuid(struct virtio_gpu_device *vgdev,
                                    struct virtio_gpu_object *bo)
 {
-       int ret;
        struct virtio_gpu_object_array *objs;
 
        objs = virtio_gpu_array_alloc(1);
@@ -98,11 +97,8 @@ int virtio_gpu_resource_assign_uuid(struct virtio_gpu_device *vgdev,
                return -ENOMEM;
 
        virtio_gpu_array_add_obj(objs, &bo->base.base);
-       ret = virtio_gpu_cmd_resource_assign_uuid(vgdev, objs);
-       if (ret)
-               return ret;
 
-       return 0;
+       return virtio_gpu_cmd_resource_assign_uuid(vgdev, objs);
 }
 
 struct dma_buf *virtgpu_gem_prime_export(struct drm_gem_object *obj,
index 914c0ac..775b977 100644 (file)
@@ -157,7 +157,7 @@ static void compose_plane(struct vkms_composer *primary_composer,
        void *vaddr;
        void (*pixel_blend)(const u8 *p_src, u8 *p_dst);
 
-       if (WARN_ON(iosys_map_is_null(&primary_composer->map[0])))
+       if (WARN_ON(iosys_map_is_null(&plane_composer->map[0])))
                return;
 
        vaddr = plane_composer->map[0].vaddr;
@@ -180,7 +180,7 @@ static int compose_active_planes(void **vaddr_out,
        int i;
 
        if (!*vaddr_out) {
-               *vaddr_out = kzalloc(gem_obj->size, GFP_KERNEL);
+               *vaddr_out = kvzalloc(gem_obj->size, GFP_KERNEL);
                if (!*vaddr_out) {
                        DRM_ERROR("Cannot allocate memory for output frame.");
                        return -ENOMEM;
@@ -263,7 +263,7 @@ void vkms_composer_worker(struct work_struct *work)
                                    crtc_state);
        if (ret) {
                if (ret == -EINVAL && !wb_pending)
-                       kfree(vaddr_out);
+                       kvfree(vaddr_out);
                return;
        }
 
@@ -275,7 +275,7 @@ void vkms_composer_worker(struct work_struct *work)
                crtc_state->wb_pending = false;
                spin_unlock_irq(&out->composer_lock);
        } else {
-               kfree(vaddr_out);
+               kvfree(vaddr_out);
        }
 
        /*
index bd84901..f2a6b81 100644 (file)
@@ -616,7 +616,7 @@ config FB_UVESA
 
 config FB_VESA
        bool "VESA VGA graphics support"
-       depends on (FB = y) && (X86 || COMPILE_TEST)
+       depends on (FB = y) && X86
        select FB_CFB_FILLRECT
        select FB_CFB_COPYAREA
        select FB_CFB_IMAGEBLIT
index c4e9171..1be8aa9 100644 (file)
@@ -125,8 +125,8 @@ static int logo_lines;
    enums.  */
 static int logo_shown = FBCON_LOGO_CANSHOW;
 /* console mappings */
-static int first_fb_vc;
-static int last_fb_vc = MAX_NR_CONSOLES - 1;
+static unsigned int first_fb_vc;
+static unsigned int last_fb_vc = MAX_NR_CONSOLES - 1;
 static int fbcon_is_default = 1; 
 static int primary_device = -1;
 static int fbcon_has_console_bind;
@@ -440,10 +440,12 @@ static int __init fb_console_setup(char *this_opt)
                        options += 3;
                        if (*options)
                                first_fb_vc = simple_strtoul(options, &options, 10) - 1;
-                       if (first_fb_vc < 0)
+                       if (first_fb_vc >= MAX_NR_CONSOLES)
                                first_fb_vc = 0;
                        if (*options++ == '-')
                                last_fb_vc = simple_strtoul(options, &options, 10) - 1;
+                       if (last_fb_vc < first_fb_vc || last_fb_vc >= MAX_NR_CONSOLES)
+                               last_fb_vc = MAX_NR_CONSOLES - 1;
                        fbcon_is_default = 0; 
                        continue;
                }
@@ -1758,8 +1760,6 @@ static bool fbcon_scroll(struct vc_data *vc, unsigned int t, unsigned int b,
        case SM_UP:
                if (count > vc->vc_rows)        /* Maximum realistic size */
                        count = vc->vc_rows;
-               if (logo_shown >= 0)
-                       goto redraw_up;
                switch (fb_scrollmode(p)) {
                case SCROLL_MOVE:
                        fbcon_redraw_blit(vc, info, p, t, b - t - count,
@@ -1848,8 +1848,6 @@ static bool fbcon_scroll(struct vc_data *vc, unsigned int t, unsigned int b,
        case SM_DOWN:
                if (count > vc->vc_rows)        /* Maximum realistic size */
                        count = vc->vc_rows;
-               if (logo_shown >= 0)
-                       goto redraw_down;
                switch (fb_scrollmode(p)) {
                case SCROLL_MOVE:
                        fbcon_redraw_blit(vc, info, p, b - 1, b - t - count,
@@ -2182,7 +2180,6 @@ static int fbcon_switch(struct vc_data *vc)
        if (logo_shown == FBCON_LOGO_DRAW) {
 
                logo_shown = fg_console;
-               /* This is protected above by initmem_freed */
                fb_show_logo(info, ops->rotate);
                update_region(vc,
                              vc->vc_origin + vc->vc_size_row * vc->vc_top,
index 2a1a425..c4a1832 100644 (file)
@@ -19,6 +19,7 @@
 #include <linux/kernel.h>
 #include <linux/major.h>
 #include <linux/slab.h>
+#include <linux/sysfb.h>
 #include <linux/mm.h>
 #include <linux/mman.h>
 #include <linux/vt.h>
@@ -1764,6 +1765,17 @@ int remove_conflicting_framebuffers(struct apertures_struct *a,
                do_free = true;
        }
 
+       /*
+        * If a driver asked to unregister a platform device registered by
+        * sysfb, then can be assumed that this is a driver for a display
+        * that is set up by the system firmware and has a generic driver.
+        *
+        * Drivers for devices that don't have a generic driver will never
+        * ask for this, so let's assume that a real driver for the display
+        * was already probed and prevent sysfb to register devices later.
+        */
+       sysfb_disable();
+
        mutex_lock(&registration_lock);
        do_remove_conflicting_framebuffers(a, name, primary);
        mutex_unlock(&registration_lock);
index b3d5f88..16c1aaa 100644 (file)
@@ -358,17 +358,6 @@ static int efifb_probe(struct platform_device *dev)
        char *option = NULL;
        efi_memory_desc_t md;
 
-       /*
-        * Generic drivers must not be registered if a framebuffer exists.
-        * If a native driver was probed, the display hardware was already
-        * taken and attempting to use the system framebuffer is dangerous.
-        */
-       if (num_registered_fb > 0) {
-               dev_err(&dev->dev,
-                       "efifb: a framebuffer is already registered\n");
-               return -EINVAL;
-       }
-
        if (screen_info.orig_video_isVGA != VIDEO_TYPE_EFI || pci_dev_disabled)
                return -ENODEV;
 
index 2c19856..991a318 100644 (file)
@@ -419,17 +419,6 @@ static int simplefb_probe(struct platform_device *pdev)
        struct simplefb_par *par;
        struct resource *res, *mem;
 
-       /*
-        * Generic drivers must not be registered if a framebuffer exists.
-        * If a native driver was probed, the display hardware was already
-        * taken and attempting to use the system framebuffer is dangerous.
-        */
-       if (num_registered_fb > 0) {
-               dev_err(&pdev->dev,
-                       "simplefb: a framebuffer is already registered\n");
-               return -EINVAL;
-       }
-
        if (fb_get_options("simplefb", NULL))
                return -ENODEV;
 
index dc3c022..c5f8f45 100644 (file)
@@ -372,8 +372,10 @@ struct drm_dp_aux {
         * Also note that this callback can be called no matter the
         * state @dev is in and also no matter what state the panel is
         * in. It's expected:
+        *
         * - If the @dev providing the AUX bus is currently unpowered then
         *   it will power itself up for the transfer.
+        *
         * - If we're on eDP (using a drm_panel) and the panel is not in a
         *   state where it can respond (it's not powered or it's in a
         *   low power state) then this function may return an error, but
index 95ac09e..9d2d781 100644 (file)
@@ -594,6 +594,15 @@ drm_display_mode_from_cea_vic(struct drm_device *dev,
                              u8 video_code);
 
 /* Interface based on struct drm_edid */
+const struct drm_edid *drm_edid_alloc(const void *edid, size_t size);
+const struct drm_edid *drm_edid_dup(const struct drm_edid *drm_edid);
+void drm_edid_free(const struct drm_edid *drm_edid);
+const struct drm_edid *drm_edid_read(struct drm_connector *connector);
+const struct drm_edid *drm_edid_read_ddc(struct drm_connector *connector,
+                                        struct i2c_adapter *adapter);
+const struct drm_edid *drm_edid_read_custom(struct drm_connector *connector,
+                                           int (*read_block)(void *context, u8 *buf, unsigned int block, size_t len),
+                                           void *context);
 const u8 *drm_find_edid_extension(const struct drm_edid *drm_edid,
                                  int ext_id, int *ext_index);
 
index 99f79ac..10ab58c 100644 (file)
@@ -50,6 +50,12 @@ int drm_of_find_panel_or_bridge(const struct device_node *np,
 int drm_of_lvds_get_dual_link_pixel_order(const struct device_node *port1,
                                          const struct device_node *port2);
 int drm_of_lvds_get_data_mapping(const struct device_node *port);
+int drm_of_get_data_lanes_count(const struct device_node *endpoint,
+                               const unsigned int min, const unsigned int max);
+int drm_of_get_data_lanes_count_ep(const struct device_node *port,
+                                  int port_reg, int reg,
+                                  const unsigned int min,
+                                  const unsigned int max);
 #else
 static inline uint32_t drm_of_crtc_port_mask(struct drm_device *dev,
                                          struct device_node *port)
@@ -105,6 +111,22 @@ drm_of_lvds_get_data_mapping(const struct device_node *port)
 {
        return -EINVAL;
 }
+
+static inline int
+drm_of_get_data_lanes_count(const struct device_node *endpoint,
+                           const unsigned int min, const unsigned int max)
+{
+       return -EINVAL;
+}
+
+static inline int
+drm_of_get_data_lanes_count_ep(const struct device_node *port,
+                              int port_reg, int reg,
+                              const unsigned int min,
+                              const unsigned int max)
+{
+       return -EINVAL;
+}
 #endif
 
 /*
index e69a002..4b8406e 100644 (file)
@@ -23,7 +23,7 @@
  *     memcpy(vaddr, src, len);
  *
  *     void *vaddr_iomem = ...; // pointer to I/O memory
- *     memcpy_toio(vaddr_iomem, src, len);
+ *     memcpy_toio(vaddr_iomem, src, len);
  *
  * The user of such pointer may not have information about the mapping of that
  * region or may want to have a single code path to handle operations on that
index b0dcfa2..8ba8b5b 100644 (file)
@@ -55,6 +55,18 @@ struct efifb_dmi_info {
        int flags;
 };
 
+#ifdef CONFIG_SYSFB
+
+void sysfb_disable(void);
+
+#else /* CONFIG_SYSFB */
+
+static inline void sysfb_disable(void)
+{
+}
+
+#endif /* CONFIG_SYSFB */
+
 #ifdef CONFIG_EFI
 
 extern struct efifb_dmi_info efifb_dmi_list[];
@@ -72,8 +84,8 @@ static inline void sysfb_apply_efi_quirks(struct platform_device *pd)
 
 bool sysfb_parse_mode(const struct screen_info *si,
                      struct simplefb_platform_data *mode);
-int sysfb_create_simplefb(const struct screen_info *si,
-                         const struct simplefb_platform_data *mode);
+struct platform_device *sysfb_create_simplefb(const struct screen_info *si,
+                                             const struct simplefb_platform_data *mode);
 
 #else /* CONFIG_SYSFB_SIMPLE */
 
@@ -83,10 +95,10 @@ static inline bool sysfb_parse_mode(const struct screen_info *si,
        return false;
 }
 
-static inline int sysfb_create_simplefb(const struct screen_info *si,
-                                        const struct simplefb_platform_data *mode)
+static inline struct platform_device *sysfb_create_simplefb(const struct screen_info *si,
+                                                           const struct simplefb_platform_data *mode)
 {
-       return -EINVAL;
+       return ERR_PTR(-EINVAL);
 }
 
 #endif /* CONFIG_SYSFB_SIMPLE */
index b1523cb..5a6fda6 100644 (file)
@@ -85,6 +85,88 @@ struct dma_buf_sync {
 
 #define DMA_BUF_NAME_LEN       32
 
+/**
+ * struct dma_buf_export_sync_file - Get a sync_file from a dma-buf
+ *
+ * Userspace can perform a DMA_BUF_IOCTL_EXPORT_SYNC_FILE to retrieve the
+ * current set of fences on a dma-buf file descriptor as a sync_file.  CPU
+ * waits via poll() or other driver-specific mechanisms typically wait on
+ * whatever fences are on the dma-buf at the time the wait begins.  This
+ * is similar except that it takes a snapshot of the current fences on the
+ * dma-buf for waiting later instead of waiting immediately.  This is
+ * useful for modern graphics APIs such as Vulkan which assume an explicit
+ * synchronization model but still need to inter-operate with dma-buf.
+ *
+ * The intended usage pattern is the following:
+ *
+ *  1. Export a sync_file with flags corresponding to the expected GPU usage
+ *     via DMA_BUF_IOCTL_EXPORT_SYNC_FILE.
+ *
+ *  2. Submit rendering work which uses the dma-buf.  The work should wait on
+ *     the exported sync file before rendering and produce another sync_file
+ *     when complete.
+ *
+ *  3. Import the rendering-complete sync_file into the dma-buf with flags
+ *     corresponding to the GPU usage via DMA_BUF_IOCTL_IMPORT_SYNC_FILE.
+ *
+ * Unlike doing implicit synchronization via a GPU kernel driver's exec ioctl,
+ * the above is not a single atomic operation.  If userspace wants to ensure
+ * ordering via these fences, it is the respnosibility of userspace to use
+ * locks or other mechanisms to ensure that no other context adds fences or
+ * submits work between steps 1 and 3 above.
+ */
+struct dma_buf_export_sync_file {
+       /**
+        * @flags: Read/write flags
+        *
+        * Must be DMA_BUF_SYNC_READ, DMA_BUF_SYNC_WRITE, or both.
+        *
+        * If DMA_BUF_SYNC_READ is set and DMA_BUF_SYNC_WRITE is not set,
+        * the returned sync file waits on any writers of the dma-buf to
+        * complete.  Waiting on the returned sync file is equivalent to
+        * poll() with POLLIN.
+        *
+        * If DMA_BUF_SYNC_WRITE is set, the returned sync file waits on
+        * any users of the dma-buf (read or write) to complete.  Waiting
+        * on the returned sync file is equivalent to poll() with POLLOUT.
+        * If both DMA_BUF_SYNC_WRITE and DMA_BUF_SYNC_READ are set, this
+        * is equivalent to just DMA_BUF_SYNC_WRITE.
+        */
+       __u32 flags;
+       /** @fd: Returned sync file descriptor */
+       __s32 fd;
+};
+
+/**
+ * struct dma_buf_import_sync_file - Insert a sync_file into a dma-buf
+ *
+ * Userspace can perform a DMA_BUF_IOCTL_IMPORT_SYNC_FILE to insert a
+ * sync_file into a dma-buf for the purposes of implicit synchronization
+ * with other dma-buf consumers.  This allows clients using explicitly
+ * synchronized APIs such as Vulkan to inter-op with dma-buf consumers
+ * which expect implicit synchronization such as OpenGL or most media
+ * drivers/video.
+ */
+struct dma_buf_import_sync_file {
+       /**
+        * @flags: Read/write flags
+        *
+        * Must be DMA_BUF_SYNC_READ, DMA_BUF_SYNC_WRITE, or both.
+        *
+        * If DMA_BUF_SYNC_READ is set and DMA_BUF_SYNC_WRITE is not set,
+        * this inserts the sync_file as a read-only fence.  Any subsequent
+        * implicitly synchronized writes to this dma-buf will wait on this
+        * fence but reads will not.
+        *
+        * If DMA_BUF_SYNC_WRITE is set, this inserts the sync_file as a
+        * write fence.  All subsequent implicitly synchronized access to
+        * this dma-buf will wait on this fence.
+        */
+       __u32 flags;
+       /** @fd: Sync file descriptor */
+       __s32 fd;
+};
+
 #define DMA_BUF_BASE           'b'
 #define DMA_BUF_IOCTL_SYNC     _IOW(DMA_BUF_BASE, 0, struct dma_buf_sync)
 
@@ -94,5 +176,7 @@ struct dma_buf_sync {
 #define DMA_BUF_SET_NAME       _IOW(DMA_BUF_BASE, 1, const char *)
 #define DMA_BUF_SET_NAME_A     _IOW(DMA_BUF_BASE, 1, __u32)
 #define DMA_BUF_SET_NAME_B     _IOW(DMA_BUF_BASE, 1, __u64)
+#define DMA_BUF_IOCTL_EXPORT_SYNC_FILE _IOWR(DMA_BUF_BASE, 2, struct dma_buf_export_sync_file)
+#define DMA_BUF_IOCTL_IMPORT_SYNC_FILE _IOW(DMA_BUF_BASE, 3, struct dma_buf_import_sync_file)
 
 #endif