diff options
75 files changed, 3245 insertions, 565 deletions
diff --git a/Documentation/devicetree/bindings/display/fsl,lcdif.yaml b/Documentation/devicetree/bindings/display/fsl,lcdif.yaml index 900a56cae80e..876015a44a1e 100644 --- a/Documentation/devicetree/bindings/display/fsl,lcdif.yaml +++ b/Documentation/devicetree/bindings/display/fsl,lcdif.yaml @@ -20,6 +20,7 @@ properties: - fsl,imx23-lcdif - fsl,imx28-lcdif - fsl,imx6sx-lcdif + - fsl,imx8mp-lcdif - items: - enum: - fsl,imx6sl-lcdif diff --git a/Documentation/devicetree/bindings/display/panel/ebbg,ft8719.yaml b/Documentation/devicetree/bindings/display/panel/ebbg,ft8719.yaml new file mode 100644 index 000000000000..80deedc01c7c --- /dev/null +++ b/Documentation/devicetree/bindings/display/panel/ebbg,ft8719.yaml @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/panel/ebbg,ft8719.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: EBBG FT8719 MIPI-DSI LCD panel + +maintainers: + - Joel Selvaraj <jo@jsfamily.in> + +description: | + The FT8719 panel from EBBG is a FHD+ LCD display panel with a resolution + of 1080x2246. It is a video mode DSI panel. The backlight is managed + through the QCOM WLED driver. + +allOf: + - $ref: panel-common.yaml# + +properties: + compatible: + const: ebbg,ft8719 + + reg: + maxItems: 1 + description: DSI virtual channel of the peripheral + + vddio-supply: + description: power IC supply regulator + + vddpos-supply: + description: positive boost supply regulator + + vddneg-supply: + description: negative boost supply regulator + +required: + - compatible + - reg + - vddio-supply + - vddpos-supply + - vddneg-supply + - reset-gpios + - port + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + + dsi { + #address-cells = <1>; + #size-cells = <0>; + + panel@0 { + compatible = "ebbg,ft8719"; + reg = <0>; + + vddio-supply = <&vreg_l14a_1p88>; + vddpos-supply = <&lab>; + vddneg-supply = <&ibb>; + + reset-gpios = <&tlmm 6 GPIO_ACTIVE_LOW>; + + backlight = <&pmi8998_wled>; + + port { + ebbg_ft8719_in_0: endpoint { + remote-endpoint = <&dsi0_out>; + }; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/display/panel/panel-simple.yaml b/Documentation/devicetree/bindings/display/panel/panel-simple.yaml index 21ba90c9fe33..a5568d1dc272 100644 --- a/Documentation/devicetree/bindings/display/panel/panel-simple.yaml +++ b/Documentation/devicetree/bindings/display/panel/panel-simple.yaml @@ -35,6 +35,8 @@ properties: - ampire,am-480272h3tmqw-t01h # Ampire AM-800480R3TMQW-A1H 7.0" WVGA TFT LCD panel - ampire,am800480r3tmqwa1h + # Ampire AM-800600P5TMQW-TB8H 8.0" SVGA TFT LCD panel + - ampire,am800600p5tmqw-tb8h # AU Optronics Corporation 10.1" WSVGA TFT LCD panel - auo,b101aw03 # AU Optronics Corporation 10.1" WSVGA TFT LCD panel @@ -107,6 +109,8 @@ properties: - chunghwa,claa101wb03 # DataImage, Inc. 4.3" WQVGA (480x272) TFT LCD panel with 24-bit parallel interface. - dataimage,fg040346dsswbg04 + # DataImage, Inc. 10.1" WXGA (1280×800) TFT LCD panel + - dataimage,fg1001l0dsswmg01 # DataImage, Inc. 7" WVGA (800x480) TFT LCD panel with 24-bit parallel interface. - dataimage,scf0700c48ggu18 # DLC Display Co. DLC1010GIG 10.1" WXGA TFT LCD Panel diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index 0496773a3c4d..7a326168d1d2 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -350,6 +350,8 @@ patternProperties: description: Embedded Artists AB "^ebang,.*": description: Zhejiang Ebang Communication Co., Ltd + "^ebbg,.*": + description: EBBG "^ebs-systart,.*": description: EBS-SYSTART GmbH "^ebv,.*": diff --git a/Documentation/driver-api/aperture.rst b/Documentation/driver-api/aperture.rst new file mode 100644 index 000000000000..d173f4e7a7d9 --- /dev/null +++ b/Documentation/driver-api/aperture.rst @@ -0,0 +1,13 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Managing Ownership of the Framebuffer Aperture +============================================== + +.. kernel-doc:: drivers/video/aperture.c + :doc: overview + +.. kernel-doc:: include/linux/aperture.h + :internal: + +.. kernel-doc:: drivers/video/aperture.c + :export: diff --git a/Documentation/driver-api/index.rst b/Documentation/driver-api/index.rst index a6d525cd9fc4..d3a58f77328e 100644 --- a/Documentation/driver-api/index.rst +++ b/Documentation/driver-api/index.rst @@ -27,6 +27,7 @@ available subsections can be seen below. component message-based infiniband + aperture frame-buffer regulator reset diff --git a/Documentation/gpu/drm-internals.rst b/Documentation/gpu/drm-internals.rst index 38afed24a75c..5fd20a306718 100644 --- a/Documentation/gpu/drm-internals.rst +++ b/Documentation/gpu/drm-internals.rst @@ -207,6 +207,38 @@ Utilities :internal: +Unit testing +============ + +KUnit +----- + +KUnit (Kernel unit testing framework) provides a common framework for unit tests +within the Linux kernel. + +This section covers the specifics for the DRM subsystem. For general information +about KUnit, please refer to Documentation/dev-tools/kunit/start.rst. + +How to run the tests? +~~~~~~~~~~~~~~~~~~~~~ + +In order to facilitate running the test suite, a configuration file is present +in ``drivers/gpu/drm/tests/.kunitconfig``. It can be used by ``kunit.py`` as +follows: + +.. code-block:: bash + + $ ./tools/testing/kunit/kunit.py run --kunitconfig=drivers/gpu/drm/tests \ + --kconfig_add CONFIG_VIRTIO_UML=y \ + --kconfig_add CONFIG_UML_PCI_OVER_VIRTIO=y + +.. note:: + The configuration included in ``.kunitconfig`` should be as generic as + possible. + ``CONFIG_VIRTIO_UML`` and ``CONFIG_UML_PCI_OVER_VIRTIO`` are not + included in it because they are only required for User Mode Linux. + + Legacy Support Code =================== diff --git a/MAINTAINERS b/MAINTAINERS index 4036b64ed7b0..6a66d91e02a2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6226,6 +6226,13 @@ S: Maintained F: Documentation/devicetree/bindings/display/bridge/chipone,icn6211.yaml F: drivers/gpu/drm/bridge/chipone-icn6211.c +DRM DRIVER FOR EBBG FT8719 PANEL +M: Joel Selvaraj <jo@jsfamily.in> +S: Maintained +T: git git://anongit.freedesktop.org/drm/drm-misc +F: Documentation/devicetree/bindings/display/panel/ebbg,ft8719.yaml +F: drivers/gpu/drm/panel/panel-ebbg-ft8719.c + DRM DRIVER FOR FARADAY TVE200 TV ENCODER M: Linus Walleij <linus.walleij@linaro.org> S: Maintained @@ -6466,13 +6473,17 @@ S: Orphan / Obsolete F: drivers/gpu/drm/savage/ F: include/uapi/drm/savage_drm.h -DRM DRIVER FOR SIMPLE FRAMEBUFFERS +DRM DRIVER FOR FIRMWARE FRAMEBUFFERS M: Thomas Zimmermann <tzimmermann@suse.de> M: Javier Martinez Canillas <javierm@redhat.com> L: dri-devel@lists.freedesktop.org S: Maintained T: git git://anongit.freedesktop.org/drm/drm-misc +F: drivers/gpu/drm/drm_aperture.c F: drivers/gpu/drm/tiny/simpledrm.c +F: drivers/video/aperture.c +F: include/drm/drm_aperture.h +F: include/linux/aperture.h DRM DRIVER FOR SIS VIDEO CARDS S: Orphan / Obsolete diff --git a/drivers/dma-buf/Kconfig b/drivers/dma-buf/Kconfig index 541efe01abc7..e4dc53a36428 100644 --- a/drivers/dma-buf/Kconfig +++ b/drivers/dma-buf/Kconfig @@ -75,7 +75,7 @@ menuconfig DMABUF_HEAPS between drivers. menuconfig DMABUF_SYSFS_STATS - bool "DMA-BUF sysfs statistics" + bool "DMA-BUF sysfs statistics (DEPRECATED)" depends on DMA_SHARED_BUFFER help Choose this option to enable DMA-BUF sysfs statistics @@ -85,6 +85,10 @@ menuconfig DMABUF_SYSFS_STATS statistics for the DMA-BUF with the unique inode number <inode_number>. + This option is deprecated and should sooner or later be removed. + Android is the only user of this and it turned out that this resulted + in quite some performance problems. + source "drivers/dma-buf/heaps/Kconfig" endmenu diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 22e7fa48d693..6c2256e8474b 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -70,6 +70,22 @@ config DRM_DEBUG_SELFTEST If in doubt, say "N". +config DRM_KUNIT_TEST + tristate "KUnit tests for DRM" if !KUNIT_ALL_TESTS + depends on DRM && KUNIT=y + select DRM_KMS_HELPER + default KUNIT_ALL_TESTS + help + This builds unit tests for DRM. This option is not useful for + distributions or general kernels, but only for kernel + developers working on DRM and associated drivers. + + For more information on KUnit and unit tests in general, + please refer to the KUnit documentation in + Documentation/dev-tools/kunit/. + + If in doubt, say "N". + config DRM_KMS_HELPER tristate depends on DRM diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 13ef240b3d2b..e7af358e6dda 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -76,6 +76,7 @@ obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o # obj-$(CONFIG_DRM_DEBUG_SELFTEST) += selftests/ +obj-$(CONFIG_DRM_KUNIT_TEST) += tests/ obj-$(CONFIG_DRM_MIPI_DBI) += drm_mipi_dbi.o obj-$(CONFIG_DRM_MIPI_DSI) += drm_mipi_dsi.o @@ -130,7 +131,7 @@ obj-y += bridge/ obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu/ obj-$(CONFIG_DRM_ETNAVIV) += etnaviv/ obj-y += hisilicon/ -obj-$(CONFIG_DRM_MXSFB) += mxsfb/ +obj-y += mxsfb/ obj-y += tiny/ obj-$(CONFIG_DRM_PL111) += pl111/ obj-$(CONFIG_DRM_TVE200) += tve200/ diff --git a/drivers/gpu/drm/ast/ast_dp.c b/drivers/gpu/drm/ast/ast_dp.c index f573d582407e..56483860306b 100644 --- a/drivers/gpu/drm/ast/ast_dp.c +++ b/drivers/gpu/drm/ast/ast_dp.c @@ -34,7 +34,7 @@ int ast_astdp_read_edid(struct drm_device *dev, u8 *ediddata) * CRE4[7:0]: Read-Pointer for EDID (Unit: 4bytes); valid range: 0~64 */ ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE4, - (u8) ~ASTDP_EDID_READ_POINTER_MASK, (u8) i); + ASTDP_AND_CLEAR_MASK, (u8)i); j = 0; /* @@ -274,8 +274,8 @@ void ast_dp_set_mode(struct drm_crtc *crtc, struct ast_vbios_mode_info *vbios_mo * CRE1[7:0]: MISC1 (default: 0x00) * CRE2[7:0]: video format index (0x00 ~ 0x20 or 0x40 ~ 0x50) */ - ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE0, (u8) ~ASTDP_CLEAR_MASK, - ASTDP_MISC0_24bpp); - ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE1, (u8) ~ASTDP_CLEAR_MASK, ASTDP_MISC1); - ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE2, (u8) ~ASTDP_CLEAR_MASK, ModeIdx); + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE0, ASTDP_AND_CLEAR_MASK, + ASTDP_MISC0_24bpp); + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE1, ASTDP_AND_CLEAR_MASK, ASTDP_MISC1); + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE2, ASTDP_AND_CLEAR_MASK, ModeIdx); } diff --git a/drivers/gpu/drm/ast/ast_drv.h b/drivers/gpu/drm/ast/ast_drv.h index a34db4380f68..2e44b971c3a6 100644 --- a/drivers/gpu/drm/ast/ast_drv.h +++ b/drivers/gpu/drm/ast/ast_drv.h @@ -433,7 +433,7 @@ int ast_mode_config_init(struct ast_private *ast); */ #define ASTDP_MISC0_24bpp BIT(5) #define ASTDP_MISC1 0 -#define ASTDP_CLEAR_MASK GENMASK(7, 0) +#define ASTDP_AND_CLEAR_MASK 0x00 /* * ASTDP resoultion table: diff --git a/drivers/gpu/drm/ast/ast_mode.c b/drivers/gpu/drm/ast/ast_mode.c index 3eb9afecd9d4..214b10178454 100644 --- a/drivers/gpu/drm/ast/ast_mode.c +++ b/drivers/gpu/drm/ast/ast_mode.c @@ -990,6 +990,9 @@ static void ast_crtc_dpms(struct drm_crtc *crtc, int mode) { struct ast_private *ast = to_ast_private(crtc->dev); u8 ch = AST_DPMS_VSYNC_OFF | AST_DPMS_HSYNC_OFF; + struct ast_crtc_state *ast_state; + const struct drm_format_info *format; + struct ast_vbios_mode_info *vbios_mode_info; /* TODO: Maybe control display signal generation with * Sync Enable (bit CR17.7). @@ -1007,6 +1010,16 @@ static void ast_crtc_dpms(struct drm_crtc *crtc, int mode) ast_dp_set_on_off(crtc->dev, 1); } + ast_state = to_ast_crtc_state(crtc->state); + format = ast_state->format; + + if (format) { + vbios_mode_info = &ast_state->vbios_mode_info; + + ast_set_color_reg(ast, format); + ast_set_vbios_color_reg(ast, format, vbios_mode_info); + } + ast_crtc_load_lut(ast, crtc); break; case DRM_MODE_DPMS_STANDBY: @@ -1095,15 +1108,19 @@ ast_crtc_helper_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode static int ast_crtc_helper_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state) { - struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, - crtc); + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); struct drm_device *dev = crtc->dev; struct ast_crtc_state *ast_state; const struct drm_format_info *format; bool succ; + int ret; + + ret = drm_atomic_helper_check_crtc_state(crtc_state, false); + if (ret) + return ret; if (!crtc_state->enable) - return 0; /* no mode checks if CRTC is being disabled */ + goto out; ast_state = to_ast_crtc_state(crtc_state); @@ -1117,7 +1134,8 @@ static int ast_crtc_helper_atomic_check(struct drm_crtc *crtc, if (!succ) return -EINVAL; - return 0; +out: + return drm_atomic_add_affected_planes(state, crtc); } static void ast_crtc_helper_atomic_begin(struct drm_crtc *crtc, struct drm_atomic_state *state) diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c index 074c2e650cae..38bf28720f3a 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c @@ -1393,10 +1393,21 @@ static struct i2c_driver adv7511_driver = { static int __init adv7511_init(void) { - if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) - mipi_dsi_driver_register(&adv7533_dsi_driver); + int ret; + + if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) { + ret = mipi_dsi_driver_register(&adv7533_dsi_driver); + if (ret) + return ret; + } - return i2c_add_driver(&adv7511_driver); + ret = i2c_add_driver(&adv7511_driver); + if (ret) { + if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) + mipi_dsi_driver_unregister(&adv7533_dsi_driver); + } + + return ret; } module_init(adv7511_init); diff --git a/drivers/gpu/drm/bridge/parade-ps8622.c b/drivers/gpu/drm/bridge/parade-ps8622.c index 37b308850b4e..b5750e5f71d7 100644 --- a/drivers/gpu/drm/bridge/parade-ps8622.c +++ b/drivers/gpu/drm/bridge/parade-ps8622.c @@ -324,11 +324,7 @@ error: static int ps8622_backlight_update(struct backlight_device *bl) { struct ps8622_bridge *ps8622 = dev_get_drvdata(&bl->dev); - int ret, brightness = bl->props.brightness; - - if (bl->props.power != FB_BLANK_UNBLANK || - bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK)) - brightness = 0; + int ret, brightness = backlight_get_brightness(bl); if (!ps8622->enabled) return -EINVAL; diff --git a/drivers/gpu/drm/bridge/tc358767.c b/drivers/gpu/drm/bridge/tc358767.c index e4dd4f05f94b..44f32bf483c5 100644 --- a/drivers/gpu/drm/bridge/tc358767.c +++ b/drivers/gpu/drm/bridge/tc358767.c @@ -288,7 +288,6 @@ struct tc_data { struct drm_connector connector; struct mipi_dsi_device *dsi; - u8 dsi_lanes; /* link settings */ struct tc_edp_link link; @@ -1261,7 +1260,7 @@ static int tc_dsi_rx_enable(struct tc_data *tc) regmap_write(tc->regmap, PPI_TX_RX_TA, TTA_GET | TTA_SURE); regmap_write(tc->regmap, PPI_LPTXTIMECNT, LPX_PERIOD); - value = ((LANEENABLE_L0EN << tc->dsi_lanes) - LANEENABLE_L0EN) | + value = ((LANEENABLE_L0EN << tc->dsi->lanes) - LANEENABLE_L0EN) | LANEENABLE_CLEN; regmap_write(tc->regmap, PPI_LANEENABLE, value); regmap_write(tc->regmap, DSI_LANEENABLE, value); @@ -1909,8 +1908,7 @@ static int tc_mipi_dsi_host_attach(struct tc_data *tc) tc->dsi = dsi; - tc->dsi_lanes = dsi_lanes; - dsi->lanes = tc->dsi_lanes; + dsi->lanes = dsi_lanes; dsi->format = MIPI_DSI_FMT_RGB888; dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE; diff --git a/drivers/gpu/drm/display/Kconfig b/drivers/gpu/drm/display/Kconfig index 1b6e6af37546..09712b88a5b8 100644 --- a/drivers/gpu/drm/display/Kconfig +++ b/drivers/gpu/drm/display/Kconfig @@ -3,7 +3,7 @@ config DRM_DP_AUX_BUS tristate depends on DRM - depends on OF + depends on OF || COMPILE_TEST config DRM_DISPLAY_HELPER tristate diff --git a/drivers/gpu/drm/drm_aperture.c b/drivers/gpu/drm/drm_aperture.c index 059fd71424f6..fdb7d5c17ba1 100644 --- a/drivers/gpu/drm/drm_aperture.c +++ b/drivers/gpu/drm/drm_aperture.c @@ -1,14 +1,7 @@ // SPDX-License-Identifier: MIT -#include <linux/device.h> -#include <linux/fb.h> -#include <linux/list.h> -#include <linux/mutex.h> -#include <linux/pci.h> -#include <linux/platform_device.h> /* for firmware helpers */ -#include <linux/slab.h> -#include <linux/types.h> -#include <linux/vgaarb.h> +#include <linux/aperture.h> +#include <linux/platform_device.h> #include <drm/drm_aperture.h> #include <drm/drm_drv.h> @@ -126,92 +119,6 @@ * afterwards. */ -struct drm_aperture { - struct drm_device *dev; - resource_size_t base; - resource_size_t size; - struct list_head lh; - void (*detach)(struct drm_device *dev); -}; - -static LIST_HEAD(drm_apertures); -static DEFINE_MUTEX(drm_apertures_lock); - -static bool overlap(resource_size_t base1, resource_size_t end1, - resource_size_t base2, resource_size_t end2) -{ - return (base1 < end2) && (end1 > base2); -} - -static void devm_aperture_acquire_release(void *data) -{ - struct drm_aperture *ap = data; - bool detached = !ap->dev; - - if (detached) - return; - - mutex_lock(&drm_apertures_lock); - list_del(&ap->lh); - mutex_unlock(&drm_apertures_lock); -} - -static int devm_aperture_acquire(struct drm_device *dev, - resource_size_t base, resource_size_t size, - void (*detach)(struct drm_device *)) -{ - size_t end = base + size; - struct list_head *pos; - struct drm_aperture *ap; - - mutex_lock(&drm_apertures_lock); - - list_for_each(pos, &drm_apertures) { - ap = container_of(pos, struct drm_aperture, lh); - if (overlap(base, end, ap->base, ap->base + ap->size)) { - mutex_unlock(&drm_apertures_lock); - return -EBUSY; - } - } - - ap = devm_kzalloc(dev->dev, sizeof(*ap), GFP_KERNEL); - if (!ap) { - mutex_unlock(&drm_apertures_lock); - return -ENOMEM; - } - - ap->dev = dev; - ap->base = base; - ap->size = size; - ap->detach = detach; - INIT_LIST_HEAD(&ap->lh); - - list_add(&ap->lh, &drm_apertures); - - mutex_unlock(&drm_apertures_lock); - - return devm_add_action_or_reset(dev->dev, devm_aperture_acquire_release, ap); -} - -static void drm_aperture_detach_firmware(struct drm_device *dev) -{ - struct platform_device *pdev = to_platform_device(dev->dev); - - /* - * Remove the device from the device hierarchy. This is the right thing - * to do for firmware-based DRM drivers, such as EFI, VESA or VGA. After - * the new driver takes over the hardware, the firmware device's state - * will be lost. - * - * For non-platform devices, a new callback would be required. - * - * If the aperture helpers ever need to handle native drivers, this call - * would only have to unplug the DRM device, so that the hardware device - * stays around after detachment. - */ - platform_device_unregister(pdev); -} - /** * devm_aperture_acquire_from_firmware - Acquires ownership of a firmware framebuffer * on behalf of a DRM driver. @@ -239,39 +146,16 @@ static void drm_aperture_detach_firmware(struct drm_device *dev) int devm_aperture_acquire_from_firmware(struct drm_device *dev, resource_size_t base, resource_size_t size) { + struct platform_device *pdev; + if (drm_WARN_ON(dev, !dev_is_platform(dev->dev))) return -EINVAL; - return devm_aperture_acquire(dev, base, size, drm_aperture_detach_firmware); -} -EXPORT_SYMBOL(devm_aperture_acquire_from_firmware); - -static void drm_aperture_detach_drivers(resource_size_t base, resource_size_t size) -{ - resource_size_t end = base + size; - struct list_head *pos, *n; - - mutex_lock(&drm_apertures_lock); - - list_for_each_safe(pos, n, &drm_apertures) { - struct drm_aperture *ap = - container_of(pos, struct drm_aperture, lh); - struct drm_device *dev = ap->dev; - - if (WARN_ON_ONCE(!dev)) - continue; - - if (!overlap(base, end, ap->base, ap->base + ap->size)) - continue; - - ap->dev = NULL; /* detach from device */ - list_del(&ap->lh); + pdev = to_platform_device(dev->dev); - ap->detach(dev); - } - - mutex_unlock(&drm_apertures_lock); + return devm_aperture_acquire_for_platform_device(pdev, base, size); } +EXPORT_SYMBOL(devm_aperture_acquire_from_firmware); /** * drm_aperture_remove_conflicting_framebuffers - remove existing framebuffers in the given range @@ -289,27 +173,7 @@ static void drm_aperture_detach_drivers(resource_size_t base, resource_size_t si int drm_aperture_remove_conflicting_framebuffers(resource_size_t base, resource_size_t size, bool primary, const struct drm_driver *req_driver) { -#if IS_REACHABLE(CONFIG_FB) - struct apertures_struct *a; - int ret; - - a = alloc_apertures(1); - if (!a) - return -ENOMEM; - - a->ranges[0].base = base; - a->ranges[0].size = size; - - ret = remove_conflicting_framebuffers(a, req_driver->name, primary); - kfree(a); - - if (ret) - return ret; -#endif - - drm_aperture_detach_drivers(base, size); - - return 0; + return aperture_remove_conflicting_devices(base, size, primary, req_driver->name); } EXPORT_SYMBOL(drm_aperture_remove_conflicting_framebuffers); @@ -328,30 +192,6 @@ EXPORT_SYMBOL(drm_aperture_remove_conflicting_framebuffers); int drm_aperture_remove_conflicting_pci_framebuffers(struct pci_dev *pdev, const struct drm_driver *req_driver) { - resource_size_t base, size; - int bar, ret; - - /* - * WARNING: Apparently we must kick fbdev drivers before vgacon, - * otherwise the vga fbdev driver falls over. - */ -#if IS_REACHABLE(CONFIG_FB) - ret = remove_conflicting_pci_framebuffers(pdev, req_driver->name); - if (ret) - return ret; -#endif - ret = vga_remove_vgacon(pdev); - if (ret) - return ret; - - for (bar = 0; bar < PCI_STD_NUM_BARS; ++bar) { - if (!(pci_resource_flags(pdev, bar) & IORESOURCE_MEM)) - continue; - base = pci_resource_start(pdev, bar); - size = pci_resource_len(pdev, bar); - drm_aperture_detach_drivers(base, size); - } - - return 0; + return aperture_remove_conflicting_pci_devices(pdev, req_driver->name); } EXPORT_SYMBOL(drm_aperture_remove_conflicting_pci_framebuffers); diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c index 0f685f4b2911..8bf41aa24068 100644 --- a/drivers/gpu/drm/drm_atomic_helper.c +++ b/drivers/gpu/drm/drm_atomic_helper.c @@ -878,6 +878,61 @@ int drm_atomic_helper_check_plane_state(struct drm_plane_state *plane_state, EXPORT_SYMBOL(drm_atomic_helper_check_plane_state); /** + * drm_atomic_helper_check_crtc_state() - Check CRTC state for validity + * @crtc_state: CRTC state to check + * @can_disable_primary_planes: can the CRTC be enabled without a primary plane? + * + * Checks that a desired CRTC update is valid. Drivers that provide + * their own CRTC handling rather than helper-provided implementations may + * still wish to call this function to avoid duplication of error checking + * code. + * + * Note that @can_disable_primary_planes only tests if the CRTC can be + * enabled without a primary plane. To test if a primary plane can be updated + * without a CRTC, use drm_atomic_helper_check_plane_state() in the plane's + * atomic check. + * + * RETURNS: + * Zero if update appears valid, error code on failure + */ +int drm_atomic_helper_check_crtc_state(struct drm_crtc_state *crtc_state, + bool can_disable_primary_planes) +{ + struct drm_device *dev = crtc_state->crtc->dev; + struct drm_atomic_state *state = crtc_state->state; + + if (!crtc_state->enable) + return 0; + + /* needs at least one primary plane to be enabled */ + if (!can_disable_primary_planes) { + bool has_primary_plane = false; + struct drm_plane *plane; + + drm_for_each_plane_mask(plane, dev, crtc_state->plane_mask) { + struct drm_plane_state *plane_state; + + if (plane->type != DRM_PLANE_TYPE_PRIMARY) + continue; + plane_state = drm_atomic_get_plane_state(state, plane); + if (IS_ERR(plane_state)) + return PTR_ERR(plane_state); + if (plane_state->fb && plane_state->crtc) { + has_primary_plane = true; + break; + } + } + if (!has_primary_plane) { + drm_dbg_kms(dev, "Cannot enable CRTC without a primary plane.\n"); + return -EINVAL; + } + } + + return 0; +} +EXPORT_SYMBOL(drm_atomic_helper_check_crtc_state); + +/** * drm_atomic_helper_check_planes - validate state object for planes changes * @dev: DRM device * @state: the driver state object diff --git a/drivers/gpu/drm/drm_blend.c b/drivers/gpu/drm/drm_blend.c index 46ee5d5df6b4..b4c8cab7158c 100644 --- a/drivers/gpu/drm/drm_blend.c +++ b/drivers/gpu/drm/drm_blend.c @@ -80,7 +80,7 @@ * * Note that the source rectangle must fully lie within the bounds of the * &drm_framebuffer. The destination rectangle can lie outside of the visible - * area of the current mode of the CRTC. It must be apprpriately clipped by the + * area of the current mode of the CRTC. It must be appropriately clipped by the * driver, which can be done by calling drm_plane_helper_check_update(). Drivers * are also allowed to round the subpixel sampling positions appropriately, but * only to the next full pixel. No pixel outside of the source rectangle may diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 28ea0f8196b9..c7ca435ceb95 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -251,7 +251,7 @@ int drm_connector_init(struct drm_device *dev, connector->funcs = funcs; /* connector index is used with 32bit bitmasks */ - ret = ida_simple_get(&config->connector_ida, 0, 32, GFP_KERNEL); + ret = ida_alloc_max(&config->connector_ida, 31, GFP_KERNEL); if (ret < 0) { DRM_DEBUG_KMS("Failed to allocate %s connector index: %d\n", drm_connector_enum_list[connector_type].name, @@ -263,7 +263,7 @@ int drm_connector_init(struct drm_device *dev, connector->connector_type = connector_type; connector->connector_type_id = - ida_simple_get(connector_ida, 1, 0, GFP_KERNEL); + ida_alloc_min(connector_ida, 1, GFP_KERNEL); if (connector->connector_type_id < 0) { ret = connector->connector_type_id; goto out_put_id; @@ -323,10 +323,10 @@ int drm_connector_init(struct drm_device *dev, connector->debugfs_entry = NULL; out_put_type_id: if (ret) - ida_simple_remove(connector_ida, connector->connector_type_id); + ida_free(connector_ida, connector->connector_type_id); out_put_id: if (ret) - ida_simple_remove(&config->connector_ida, connector->index); + ida_free(&config->connector_ida, connector->index); out_put: if (ret) drm_mode_object_unregister(dev, &connector->base); @@ -480,11 +480,10 @@ void drm_connector_cleanup(struct drm_connector *connector) list_for_each_entry_safe(mode, t, &connector->modes, head) drm_mode_remove(connector, mode); - ida_simple_remove(&drm_connector_enum_list[connector->connector_type].ida, + ida_free(&drm_connector_enum_list[connector->connector_type].ida, connector->connector_type_id); - ida_simple_remove(&dev->mode_config.connector_ida, - connector->index); + ida_free(&dev->mode_config.connector_ida, connector->index); kfree(connector->display_info.bus_formats); drm_mode_object_unregister(dev, &connector->base); diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index 5e9c373e6b88..2d4cee6a10ff 100644 --- a/drivers/gpu/drm/drm_fb_helper.c +++ b/drivers/gpu/drm/drm_fb_helper.c @@ -681,7 +681,11 @@ static void drm_fb_helper_damage(struct fb_info *info, u32 x, u32 y, schedule_work(&helper->damage_work); } -/* Convert memory region into area of scanlines and pixels per scanline */ +/* + * Convert memory region into area of scanlines and pixels per + * scanline. The parameters off and len must not reach beyond + * the end of the framebuffer. + */ static void drm_fb_helper_memory_range_to_clip(struct fb_info *info, off_t off, size_t len, struct drm_rect *clip) { @@ -716,22 +720,29 @@ static void drm_fb_helper_memory_range_to_clip(struct fb_info *info, off_t off, */ void drm_fb_helper_deferred_io(struct fb_info *info, struct list_head *pagereflist) { - unsigned long start, end, min, max; + unsigned long start, end, min_off, max_off; struct fb_deferred_io_pageref *pageref; struct drm_rect damage_area; - min = ULONG_MAX; - max = 0; + min_off = ULONG_MAX; + max_off = 0; list_for_each_entry(pageref, pagereflist, list) { start = pageref->offset; end = start + PAGE_SIZE; - min = min(min, start); - max = max(max, end); + min_off = min(min_off, start); + max_off = max(max_off, end); } - if (min >= max) + if (min_off >= max_off) return; - drm_fb_helper_memory_range_to_clip(info, min, max - min, &damage_area); + /* + * As we can only track pages, we might reach beyond the end + * of the screen and account for non-existing scanlines. Hence, + * keep the covered memory area within the screen buffer. + */ + max_off = min(max_off, info->screen_size); + + drm_fb_helper_memory_range_to_clip(info, min_off, max_off - min_off, &damage_area); drm_fb_helper_damage(info, damage_area.x1, damage_area.y1, drm_rect_width(&damage_area), drm_rect_height(&damage_area)); diff --git a/drivers/gpu/drm/drm_simple_kms_helper.c b/drivers/gpu/drm/drm_simple_kms_helper.c index 72989ed1baba..36633590ebf3 100644 --- a/drivers/gpu/drm/drm_simple_kms_helper.c +++ b/drivers/gpu/drm/drm_simple_kms_helper.c @@ -100,14 +100,12 @@ drm_simple_kms_crtc_mode_valid(struct drm_crtc *crtc, static int drm_simple_kms_crtc_check(struct drm_crtc *crtc, struct drm_atomic_state *state) { - struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, - crtc); - bool has_primary = crtc_state->plane_mask & - drm_plane_mask(crtc->primary); + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + int ret; - /* We always want to have an active plane with an active CRTC */ - if (has_primary != crtc_state->enable) - return -EINVAL; + ret = drm_atomic_helper_check_crtc_state(crtc_state, false); + if (ret) + return ret; return drm_atomic_add_affected_planes(state, crtc); } @@ -227,7 +225,7 @@ static int drm_simple_kms_plane_atomic_check(struct drm_plane *plane, ret = drm_atomic_helper_check_plane_state(plane_state, crtc_state, DRM_PLANE_HELPER_NO_SCALING, DRM_PLANE_HELPER_NO_SCALING, - false, true); + false, false); if (ret) return ret; diff --git a/drivers/gpu/drm/logicvc/logicvc_drm.c b/drivers/gpu/drm/logicvc/logicvc_drm.c index df1805cf0f95..65a050176c33 100644 --- a/drivers/gpu/drm/logicvc/logicvc_drm.c +++ b/drivers/gpu/drm/logicvc/logicvc_drm.c @@ -298,7 +298,7 @@ static int logicvc_drm_probe(struct platform_device *pdev) struct logicvc_drm *logicvc; struct device *dev = &pdev->dev; struct drm_device *drm_dev; - struct regmap *regmap; + struct regmap *regmap = NULL; struct resource res; void __iomem *base; int irq; @@ -349,7 +349,6 @@ static int logicvc_drm_probe(struct platform_device *pdev) irq = platform_get_irq(pdev, 0); if (irq < 0) { - dev_err(dev, "Failed to get IRQ\n"); ret = -ENODEV; goto error_reserved_mem; } diff --git a/drivers/gpu/drm/logicvc/logicvc_layer.c b/drivers/gpu/drm/logicvc/logicvc_layer.c index fbebe966f93a..441e3cfce4cf 100644 --- a/drivers/gpu/drm/logicvc/logicvc_layer.c +++ b/drivers/gpu/drm/logicvc/logicvc_layer.c @@ -491,6 +491,7 @@ static int logicvc_layer_init(struct logicvc_drm *logicvc, if (!formats) { drm_err(drm_dev, "Failed to lookup formats for layer #%d\n", index); + ret = -EINVAL; goto error; } @@ -612,10 +613,10 @@ int logicvc_layers_init(struct logicvc_drm *logicvc) } ret = logicvc_layer_init(logicvc, layer_node, index); - if (ret) + if (ret) { + of_node_put(layers_node); goto error; - - of_node_put(layer_node); + } } of_node_put(layers_node); diff --git a/drivers/gpu/drm/mcde/mcde_dsi.c b/drivers/gpu/drm/mcde/mcde_dsi.c index 5651734ce977..9f9ac8699310 100644 --- a/drivers/gpu/drm/mcde/mcde_dsi.c +++ b/drivers/gpu/drm/mcde/mcde_dsi.c @@ -1111,6 +1111,7 @@ static int mcde_dsi_bind(struct device *dev, struct device *master, bridge = of_drm_find_bridge(child); if (!bridge) { dev_err(dev, "failed to find bridge\n"); + of_node_put(child); return -EINVAL; } } diff --git a/drivers/gpu/drm/mxsfb/Kconfig b/drivers/gpu/drm/mxsfb/Kconfig index 987170e16ebd..873551b4552f 100644 --- a/drivers/gpu/drm/mxsfb/Kconfig +++ b/drivers/gpu/drm/mxsfb/Kconfig @@ -19,3 +19,19 @@ config DRM_MXSFB i.MX28, i.MX6SX, i.MX7 and i.MX8M). If M is selected the module will be called mxsfb. + +config DRM_IMX_LCDIF + tristate "i.MX LCDIFv3 LCD controller" + depends on DRM && OF + depends on COMMON_CLK + select DRM_MXS + select DRM_KMS_HELPER + select DRM_GEM_CMA_HELPER + select DRM_PANEL + select DRM_PANEL_BRIDGE + help + Choose this option if you have an LCDIFv3 LCD controller. + Those devices are found in various i.MX SoC (i.MX8MP, + i.MXRT). + + If M is selected the module will be called imx-lcdif. diff --git a/drivers/gpu/drm/mxsfb/Makefile b/drivers/gpu/drm/mxsfb/Makefile index 26d153896d72..3fa44059b9d8 100644 --- a/drivers/gpu/drm/mxsfb/Makefile +++ b/drivers/gpu/drm/mxsfb/Makefile @@ -1,3 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only mxsfb-y := mxsfb_drv.o mxsfb_kms.o obj-$(CONFIG_DRM_MXSFB) += mxsfb.o +imx-lcdif-y := lcdif_drv.o lcdif_kms.o +obj-$(CONFIG_DRM_IMX_LCDIF) += imx-lcdif.o diff --git a/drivers/gpu/drm/mxsfb/lcdif_drv.c b/drivers/gpu/drm/mxsfb/lcdif_drv.c new file mode 100644 index 000000000000..befad33dcb95 --- /dev/null +++ b/drivers/gpu/drm/mxsfb/lcdif_drv.c @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2022 Marek Vasut <marex@denx.de> + * + * This code is based on drivers/gpu/drm/mxsfb/mxsfb* + */ + +#include <linux/clk.h> +#include <linux/dma-mapping.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_connector.h> +#include <drm/drm_drv.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_mode_config.h> +#include <drm/drm_module.h> +#include <drm/drm_of.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_vblank.h> + +#include "lcdif_drv.h" +#include "lcdif_regs.h" + +static const struct drm_mode_config_funcs lcdif_mode_config_funcs = { + .fb_create = drm_gem_fb_create, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static const struct drm_mode_config_helper_funcs lcdif_mode_config_helpers = { + .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm, +}; + +static int lcdif_attach_bridge(struct lcdif_drm_private *lcdif) +{ + struct drm_device *drm = lcdif->drm; + struct drm_bridge *bridge; + struct drm_panel *panel; + int ret; + + ret = drm_of_find_panel_or_bridge(drm->dev->of_node, 0, 0, &panel, + &bridge); + if (ret) + return ret; + + if (panel) { + bridge = devm_drm_panel_bridge_add_typed(drm->dev, panel, + DRM_MODE_CONNECTOR_DPI); + if (IS_ERR(bridge)) + return PTR_ERR(bridge); + } + + if (!bridge) + return -ENODEV; + + ret = drm_bridge_attach(&lcdif->encoder, bridge, NULL, 0); + if (ret) + return dev_err_probe(drm->dev, ret, "Failed to attach bridge\n"); + + lcdif->bridge = bridge; + + return 0; +} + +static irqreturn_t lcdif_irq_handler(int irq, void *data) +{ + struct drm_device *drm = data; + struct lcdif_drm_private *lcdif = drm->dev_private; + u32 reg, stat; + + stat = readl(lcdif->base + LCDC_V8_INT_STATUS_D0); + if (!stat) + return IRQ_NONE; + + if (stat & INT_STATUS_D0_VS_BLANK) { + reg = readl(lcdif->base + LCDC_V8_CTRLDESCL0_5); + if (!(reg & CTRLDESCL0_5_SHADOW_LOAD_EN)) + drm_crtc_handle_vblank(&lcdif->crtc); + } + + writel(stat, lcdif->base + LCDC_V8_INT_STATUS_D0); + + return IRQ_HANDLED; +} + +static int lcdif_load(struct drm_device *drm) +{ + struct platform_device *pdev = to_platform_device(drm->dev); + struct lcdif_drm_private *lcdif; + struct resource *res; + int ret; + + lcdif = devm_kzalloc(&pdev->dev, sizeof(*lcdif), GFP_KERNEL); + if (!lcdif) + return -ENOMEM; + + lcdif->drm = drm; + drm->dev_private = lcdif; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + lcdif->base = devm_ioremap_resource(drm->dev, res); + if (IS_ERR(lcdif->base)) + return PTR_ERR(lcdif->base); + + lcdif->clk = devm_clk_get(drm->dev, "pix"); + if (IS_ERR(lcdif->clk)) + return PTR_ERR(lcdif->clk); + + lcdif->clk_axi = devm_clk_get(drm->dev, "axi"); + if (IS_ERR(lcdif->clk_axi)) + return PTR_ERR(lcdif->clk_axi); + + lcdif->clk_disp_axi = devm_clk_get(drm->dev, "disp_axi"); + if (IS_ERR(lcdif->clk_disp_axi)) + return PTR_ERR(lcdif->clk_disp_axi); + + platform_set_drvdata(pdev, drm); + + ret = dma_set_mask_and_coherent(drm->dev, DMA_BIT_MASK(36)); + if (ret) + return ret; + + /* Modeset init */ + drm_mode_config_init(drm); + + ret = lcdif_kms_init(lcdif); + if (ret < 0) { + dev_err(drm->dev, "Failed to initialize KMS pipeline\n"); + return ret; + } + + ret = drm_vblank_init(drm, drm->mode_config.num_crtc); + if (ret < 0) { + dev_err(drm->dev, "Failed to initialise vblank\n"); + return ret; + } + + /* Start with vertical blanking interrupt reporting disabled. */ + drm_crtc_vblank_off(&lcdif->crtc); + + ret = lcdif_attach_bridge(lcdif); + if (ret) + return dev_err_probe(drm->dev, ret, "Cannot connect bridge\n"); + + drm->mode_config.min_width = LCDIF_MIN_XRES; + drm->mode_config.min_height = LCDIF_MIN_YRES; + drm->mode_config.max_width = LCDIF_MAX_XRES; + drm->mode_config.max_height = LCDIF_MAX_YRES; + drm->mode_config.funcs = &lcdif_mode_config_funcs; + drm->mode_config.helper_private = &lcdif_mode_config_helpers; + + drm_mode_config_reset(drm); + + ret = platform_get_irq(pdev, 0); + if (ret < 0) + return ret; + lcdif->irq = ret; + + ret = devm_request_irq(drm->dev, lcdif->irq, lcdif_irq_handler, 0, + drm->driver->name, drm); + if (ret < 0) { + dev_err(drm->dev, "Failed to install IRQ handler\n"); + return ret; + } + + drm_kms_helper_poll_init(drm); + + drm_helper_hpd_irq_event(drm); + + pm_runtime_enable(drm->dev); + + return 0; +} + +static void lcdif_unload(struct drm_device *drm) +{ + struct lcdif_drm_private *lcdif = drm->dev_private; + + pm_runtime_get_sync(drm->dev); + + drm_crtc_vblank_off(&lcdif->crtc); + + drm_kms_helper_poll_fini(drm); + drm_mode_config_cleanup(drm); + + pm_runtime_put_sync(drm->dev); + pm_runtime_disable(drm->dev); + + drm->dev_private = NULL; +} + +DEFINE_DRM_GEM_CMA_FOPS(fops); + +static const struct drm_driver lcdif_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, + DRM_GEM_CMA_DRIVER_OPS, + .fops = &fops, + .name = "imx-lcdif", + .desc = "i.MX LCDIF Controller DRM", + .date = "20220417", + .major = 1, + .minor = 0, +}; + +static const struct of_device_id lcdif_dt_ids[] = { + { .compatible = "fsl,imx8mp-lcdif" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, lcdif_dt_ids); + +static int lcdif_probe(struct platform_device *pdev) +{ + struct drm_device *drm; + int ret; + + drm = drm_dev_alloc(&lcdif_driver, &pdev->dev); + if (IS_ERR(drm)) + return PTR_ERR(drm); + + ret = lcdif_load(drm); + if (ret) + goto err_free; + + ret = drm_dev_register(drm, 0); + if (ret) + goto err_unload; + + drm_fbdev_generic_setup(drm, 32); + + return 0; + +err_unload: + lcdif_unload(drm); +err_free: + drm_dev_put(drm); + + return ret; +} + +static int lcdif_remove(struct platform_device *pdev) +{ + struct drm_device *drm = platform_get_drvdata(pdev); + + drm_dev_unregister(drm); + drm_atomic_helper_shutdown(drm); + lcdif_unload(drm); + drm_dev_put(drm); + + return 0; +} + +static void lcdif_shutdown(struct platform_device *pdev) +{ + struct drm_device *drm = platform_get_drvdata(pdev); + + drm_atomic_helper_shutdown(drm); +} + +static int __maybe_unused lcdif_rpm_suspend(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + struct lcdif_drm_private *lcdif = drm->dev_private; + + /* These clock supply the DISPLAY CLOCK Domain */ + clk_disable_unprepare(lcdif->clk); + /* These clock supply the System Bus, AXI, Write Path, LFIFO */ + clk_disable_unprepare(lcdif->clk_disp_axi); + /* These clock supply the Control Bus, APB, APBH Ctrl Registers */ + clk_disable_unprepare(lcdif->clk_axi); + + return 0; +} + +static int __maybe_unused lcdif_rpm_resume(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + struct lcdif_drm_private *lcdif = drm->dev_private; + + /* These clock supply the Control Bus, APB, APBH Ctrl Registers */ + clk_prepare_enable(lcdif->clk_axi); + /* These clock supply the System Bus, AXI, Write Path, LFIFO */ + clk_prepare_enable(lcdif->clk_disp_axi); + /* These clock supply the DISPLAY CLOCK Domain */ + clk_prepare_enable(lcdif->clk); + + return 0; +} + +static int __maybe_unused lcdif_suspend(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + int ret; + + ret = drm_mode_config_helper_suspend(drm); + if (ret) + return ret; + + return lcdif_rpm_suspend(dev); +} + +static int __maybe_unused lcdif_resume(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + + lcdif_rpm_resume(dev); + + return drm_mode_config_helper_resume(drm); +} + +static const struct dev_pm_ops lcdif_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(lcdif_suspend, lcdif_resume) + SET_RUNTIME_PM_OPS(lcdif_rpm_suspend, lcdif_rpm_resume, NULL) +}; + +static struct platform_driver lcdif_platform_driver = { + .probe = lcdif_probe, + .remove = lcdif_remove, + .shutdown = lcdif_shutdown, + .driver = { + .name = "imx-lcdif", + .of_match_table = lcdif_dt_ids, + .pm = &lcdif_pm_ops, + }, +}; + +drm_module_platform_driver(lcdif_platform_driver); + +MODULE_AUTHOR("Marek Vasut <marex@denx.de>"); +MODULE_DESCRIPTION("Freescale LCDIF DRM/KMS driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/mxsfb/lcdif_drv.h b/drivers/gpu/drm/mxsfb/lcdif_drv.h new file mode 100644 index 000000000000..cb916341e845 --- /dev/null +++ b/drivers/gpu/drm/mxsfb/lcdif_drv.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2022 Marek Vasut <marex@denx.de> + * + * i.MX8MP/i.MXRT LCDIFv3 LCD controller driver. + */ + +#ifndef __LCDIF_DRV_H__ +#define __LCDIF_DRV_H__ + +#include <drm/drm_crtc.h> +#include <drm/drm_device.h> +#include <drm/drm_encoder.h> +#include <drm/drm_plane.h> + +struct clk; + +struct lcdif_drm_private { + void __iomem *base; /* registers */ + struct clk *clk; + struct clk *clk_axi; + struct clk *clk_disp_axi; + + unsigned int irq; + + struct drm_device *drm; + struct { + struct drm_plane primary; + /* i.MXRT does support overlay planes, add them here. */ + } planes; + struct drm_crtc crtc; + struct drm_encoder encoder; + struct drm_bridge *bridge; +}; + +static inline struct lcdif_drm_private * +to_lcdif_drm_private(struct drm_device *drm) +{ + return drm->dev_private; +} + +int lcdif_kms_init(struct lcdif_drm_private *lcdif); + +#endif /* __LCDIF_DRV_H__ */ diff --git a/drivers/gpu/drm/mxsfb/lcdif_kms.c b/drivers/gpu/drm/mxsfb/lcdif_kms.c new file mode 100644 index 000000000000..4005660b0ced --- /dev/null +++ b/drivers/gpu/drm/mxsfb/lcdif_kms.c @@ -0,0 +1,484 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2022 Marek Vasut <marex@denx.de> + * + * This code is based on drivers/gpu/drm/mxsfb/mxsfb* + */ + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/pm_runtime.h> +#include <linux/spinlock.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_crtc.h> +#include <drm/drm_encoder.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_gem_atomic_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_plane.h> +#include <drm/drm_plane_helper.h> +#include <drm/drm_vblank.h> + +#include "lcdif_drv.h" +#include "lcdif_regs.h" + +/* ----------------------------------------------------------------------------- + * CRTC + */ +static void lcdif_set_formats(struct lcdif_drm_private *lcdif, + const u32 bus_format) +{ + struct drm_device *drm = lcdif->drm; + const u32 format = lcdif->crtc.primary->state->fb->format->format; + + writel(CSC0_CTRL_BYPASS, lcdif->base + LCDC_V8_CSC0_CTRL); + + switch (bus_format) { + case MEDIA_BUS_FMT_RGB565_1X16: + writel(DISP_PARA_LINE_PATTERN_RGB565, + lcdif->base + LCDC_V8_DISP_PARA); + break; + case MEDIA_BUS_FMT_RGB888_1X24: + writel(DISP_PARA_LINE_PATTERN_RGB888, + lcdif->base + LCDC_V8_DISP_PARA); + break; + case MEDIA_BUS_FMT_UYVY8_1X16: + writel(DISP_PARA_LINE_PATTERN_UYVY_H, + lcdif->base + LCDC_V8_DISP_PARA); + + /* CSC: BT.601 Full Range RGB to YCbCr coefficients. */ + writel(CSC0_COEF0_A2(0x096) | CSC0_COEF0_A1(0x04c), + lcdif->base + LCDC_V8_CSC0_COEF0); + writel(CSC0_COEF1_B1(0x7d5) | CSC0_COEF1_A3(0x01d), + lcdif->base + LCDC_V8_CSC0_COEF1); + writel(CSC0_COEF2_B3(0x080) | CSC0_COEF2_B2(0x7ac), + lcdif->base + LCDC_V8_CSC0_COEF2); + writel(CSC0_COEF3_C2(0x795) | CSC0_COEF3_C1(0x080), + lcdif->base + LCDC_V8_CSC0_COEF3); + writel(CSC0_COEF4_D1(0x000) | CSC0_COEF4_C3(0x7ec), + lcdif->base + LCDC_V8_CSC0_COEF4); + writel(CSC0_COEF5_D3(0x080) | CSC0_COEF5_D2(0x080), + lcdif->base + LCDC_V8_CSC0_COEF5); + + writel(CSC0_CTRL_CSC_MODE_RGB2YCbCr, + lcdif->base + LCDC_V8_CSC0_CTRL); + + break; + default: + dev_err(drm->dev, "Unknown media bus format 0x%x\n", bus_format); + break; + } + + switch (format) { + case DRM_FORMAT_RGB565: + writel(CTRLDESCL0_5_BPP_16_RGB565, + lcdif->base + LCDC_V8_CTRLDESCL0_5); + break; + case DRM_FORMAT_RGB888: + writel(CTRLDESCL0_5_BPP_24_RGB888, + lcdif->base + LCDC_V8_CTRLDESCL0_5); + break; + case DRM_FORMAT_XRGB1555: + writel(CTRLDESCL0_5_BPP_16_ARGB1555, + lcdif->base + LCDC_V8_CTRLDESCL0_5); + break; + case DRM_FORMAT_XRGB4444: + writel(CTRLDESCL0_5_BPP_16_ARGB4444, + lcdif->base + LCDC_V8_CTRLDESCL0_5); + break; + case DRM_FORMAT_XBGR8888: + writel(CTRLDESCL0_5_BPP_32_ABGR8888, + lcdif->base + LCDC_V8_CTRLDESCL0_5); + break; + case DRM_FORMAT_XRGB8888: + writel(CTRLDESCL0_5_BPP_32_ARGB8888, + lcdif->base + LCDC_V8_CTRLDESCL0_5); + break; + default: + dev_err(drm->dev, "Unknown pixel format 0x%x\n", format); + break; + } +} + +static void lcdif_set_mode(struct lcdif_drm_private *lcdif, u32 bus_flags) +{ + struct drm_display_mode *m = &lcdif->crtc.state->adjusted_mode; + u32 ctrl = 0; + + if (m->flags & DRM_MODE_FLAG_NHSYNC) + ctrl |= CTRL_INV_HS; + if (m->flags & DRM_MODE_FLAG_NVSYNC) + ctrl |= CTRL_INV_VS; + if (bus_flags & DRM_BUS_FLAG_DE_LOW) + ctrl |= CTRL_INV_DE; + if (bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE) + ctrl |= CTRL_INV_PXCK; + + writel(ctrl, lcdif->base + LCDC_V8_CTRL); + + writel(DISP_SIZE_DELTA_Y(m->crtc_vdisplay) | + DISP_SIZE_DELTA_X(m->crtc_hdisplay), + lcdif->base + LCDC_V8_DISP_SIZE); + + writel(HSYN_PARA_BP_H(m->htotal - m->hsync_end) | + HSYN_PARA_FP_H(m->hsync_start - m->hdisplay), + lcdif->base + LCDC_V8_HSYN_PARA); + + writel(VSYN_PARA_BP_V(m->vtotal - m->vsync_end) | + VSYN_PARA_FP_V(m->vsync_start - m->vdisplay), + lcdif->base + LCDC_V8_VSYN_PARA); + + writel(VSYN_HSYN_WIDTH_PW_V(m->vsync_end - m->vsync_start) | + VSYN_HSYN_WIDTH_PW_H(m->hsync_end - m->hsync_start), + lcdif->base + LCDC_V8_VSYN_HSYN_WIDTH); + + writel(CTRLDESCL0_1_HEIGHT(m->crtc_vdisplay) | + CTRLDESCL0_1_WIDTH(m->crtc_hdisplay), + lcdif->base + LCDC_V8_CTRLDESCL0_1); + + writel(CTRLDESCL0_3_PITCH(lcdif->crtc.primary->state->fb->pitches[0]), + lcdif->base + LCDC_V8_CTRLDESCL0_3); +} + +static void lcdif_enable_controller(struct lcdif_drm_private *lcdif) +{ + u32 reg; + + reg = readl(lcdif->base + LCDC_V8_DISP_PARA); + reg |= DISP_PARA_DISP_ON; + writel(reg, lcdif->base + LCDC_V8_DISP_PARA); + + reg = readl(lcdif->base + LCDC_V8_CTRLDESCL0_5); + reg |= CTRLDESCL0_5_EN; + writel(reg, lcdif->base + LCDC_V8_CTRLDESCL0_5); +} + +static void lcdif_disable_controller(struct lcdif_drm_private *lcdif) +{ + u32 reg; + int ret; + + reg = readl(lcdif->base + LCDC_V8_CTRLDESCL0_5); + reg &= ~CTRLDESCL0_5_EN; + writel(reg, lcdif->base + LCDC_V8_CTRLDESCL0_5); + + ret = readl_poll_timeout(lcdif->base + LCDC_V8_CTRLDESCL0_5, + reg, !(reg & CTRLDESCL0_5_EN), + 0, 36000); /* Wait ~2 frame times max */ + if (ret) + drm_err(lcdif->drm, "Failed to disable controller!\n"); + + reg = readl(lcdif->base + LCDC_V8_DISP_PARA); + reg &= ~DISP_PARA_DISP_ON; + writel(reg, lcdif->base + LCDC_V8_DISP_PARA); +} + +static void lcdif_reset_block(struct lcdif_drm_private *lcdif) +{ + writel(CTRL_SW_RESET, lcdif->base + LCDC_V8_CTRL + REG_SET); + readl(lcdif->base + LCDC_V8_CTRL); + writel(CTRL_SW_RESET, lcdif->base + LCDC_V8_CTRL + REG_CLR); + readl(lcdif->base + LCDC_V8_CTRL); +} + +static void lcdif_crtc_mode_set_nofb(struct lcdif_drm_private *lcdif, + struct drm_bridge_state *bridge_state, + const u32 bus_format) +{ + struct drm_device *drm = lcdif->crtc.dev; + struct drm_display_mode *m = &lcdif->crtc.state->adjusted_mode; + u32 bus_flags = 0; + + if (lcdif->bridge && lcdif->bridge->timings) + bus_flags = lcdif->bridge->timings->input_bus_flags; + else if (bridge_state) + bus_flags = bridge_state->input_bus_cfg.flags; + + DRM_DEV_DEBUG_DRIVER(drm->dev, "Pixel clock: %dkHz (actual: %dkHz)\n", + m->crtc_clock, + (int)(clk_get_rate(lcdif->clk) / 1000)); + DRM_DEV_DEBUG_DRIVER(drm->dev, "Connector bus_flags: 0x%08X\n", + bus_flags); + DRM_DEV_DEBUG_DRIVER(drm->dev, "Mode flags: 0x%08X\n", m->flags); + + /* Mandatory eLCDIF reset as per the Reference Manual */ + lcdif_reset_block(lcdif); + + lcdif_set_formats(lcdif, bus_format); + + lcdif_set_mode(lcdif, bus_flags); +} + +static int lcdif_crtc_atomic_check(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, + crtc); + bool has_primary = crtc_state->plane_mask & + drm_plane_mask(crtc->primary); + + /* The primary plane has to be enabled when the CRTC is active. */ + if (crtc_state->active && !has_primary) + return -EINVAL; + + return drm_atomic_add_affected_planes(state, crtc); +} + +static void lcdif_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct lcdif_drm_private *lcdif = to_lcdif_drm_private(crtc->dev); + struct drm_pending_vblank_event *event; + u32 reg; + + reg = readl(lcdif->base + LCDC_V8_CTRLDESCL0_5); + reg |= CTRLDESCL0_5_SHADOW_LOAD_EN; + writel(reg, lcdif->base + LCDC_V8_CTRLDESCL0_5); + + event = crtc->state->event; + crtc->state->event = NULL; + + if (!event) + return; + + spin_lock_irq(&crtc->dev->event_lock); + if (drm_crtc_vblank_get(crtc) == 0) + drm_crtc_arm_vblank_event(crtc, event); + else + drm_crtc_send_vblank_event(crtc, event); + spin_unlock_irq(&crtc->dev->event_lock); +} + +static void lcdif_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct lcdif_drm_private *lcdif = to_lcdif_drm_private(crtc->dev); + struct drm_plane_state *new_pstate = drm_atomic_get_new_plane_state(state, + crtc->primary); + struct drm_display_mode *m = &lcdif->crtc.state->adjusted_mode; + struct drm_bridge_state *bridge_state = NULL; + struct drm_device *drm = lcdif->drm; + u32 bus_format = 0; + dma_addr_t paddr; + + /* If there is a bridge attached to the LCDIF, use its bus format */ + if (lcdif->bridge) { + bridge_state = + drm_atomic_get_new_bridge_state(state, + lcdif->bridge); + if (!bridge_state) + bus_format = MEDIA_BUS_FMT_FIXED; + else + bus_format = bridge_state->input_bus_cfg.format; + + if (bus_format == MEDIA_BUS_FMT_FIXED) { + dev_warn_once(drm->dev, + "Bridge does not provide bus format, assuming MEDIA_BUS_FMT_RGB888_1X24.\n" + "Please fix bridge driver by handling atomic_get_input_bus_fmts.\n"); + bus_format = MEDIA_BUS_FMT_RGB888_1X24; + } + } + + /* If all else fails, default to RGB888_1X24 */ + if (!bus_format) + bus_format = MEDIA_BUS_FMT_RGB888_1X24; + + clk_set_rate(lcdif->clk, m->crtc_clock * 1000); + + pm_runtime_get_sync(drm->dev); + + lcdif_crtc_mode_set_nofb(lcdif, bridge_state, bus_format); + + /* Write cur_buf as well to avoid an initial corrupt frame */ + paddr = drm_fb_cma_get_gem_addr(new_pstate->fb, new_pstate, 0); + if (paddr) { + writel(lower_32_bits(paddr), + lcdif->base + LCDC_V8_CTRLDESCL_LOW0_4); + writel(CTRLDESCL_HIGH0_4_ADDR_HIGH(upper_32_bits(paddr)), + lcdif->base + LCDC_V8_CTRLDESCL_HIGH0_4); + } + lcdif_enable_controller(lcdif); + + drm_crtc_vblank_on(crtc); +} + +static void lcdif_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct lcdif_drm_private *lcdif = to_lcdif_drm_private(crtc->dev); + struct drm_device *drm = lcdif->drm; + struct drm_pending_vblank_event *event; + + drm_crtc_vblank_off(crtc); + + lcdif_disable_controller(lcdif); + + spin_lock_irq(&drm->event_lock); + event = crtc->state->event; + if (event) { + crtc->state->event = NULL; + drm_crtc_send_vblank_event(crtc, event); + } + spin_unlock_irq(&drm->event_lock); + + pm_runtime_put_sync(drm->dev); +} + +static int lcdif_crtc_enable_vblank(struct drm_crtc *crtc) +{ + struct lcdif_drm_private *lcdif = to_lcdif_drm_private(crtc->dev); + + /* Clear and enable VBLANK IRQ */ + writel(INT_STATUS_D0_VS_BLANK, lcdif->base + LCDC_V8_INT_STATUS_D0); + writel(INT_ENABLE_D0_VS_BLANK_EN, lcdif->base + LCDC_V8_INT_ENABLE_D0); + + return 0; +} + +static void lcdif_crtc_disable_vblank(struct drm_crtc *crtc) +{ + struct lcdif_drm_private *lcdif = to_lcdif_drm_private(crtc->dev); + + /* Disable and clear VBLANK IRQ */ + writel(0, lcdif->base + LCDC_V8_INT_ENABLE_D0); + writel(INT_STATUS_D0_VS_BLANK, lcdif->base + LCDC_V8_INT_STATUS_D0); +} + +static const struct drm_crtc_helper_funcs lcdif_crtc_helper_funcs = { + .atomic_check = lcdif_crtc_atomic_check, + .atomic_flush = lcdif_crtc_atomic_flush, + .atomic_enable = lcdif_crtc_atomic_enable, + .atomic_disable = lcdif_crtc_atomic_disable, +}; + +static const struct drm_crtc_funcs lcdif_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 = lcdif_crtc_enable_vblank, + .disable_vblank = lcdif_crtc_disable_vblank, +}; + +/* ----------------------------------------------------------------------------- + * Encoder + */ + +static const struct drm_encoder_funcs lcdif_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +/* ----------------------------------------------------------------------------- + * Planes + */ + +static int lcdif_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, + plane); + struct lcdif_drm_private *lcdif = to_lcdif_drm_private(plane->dev); + struct drm_crtc_state *crtc_state; + + crtc_state = drm_atomic_get_new_crtc_state(state, + &lcdif->crtc); + + return drm_atomic_helper_check_plane_state(plane_state, crtc_state, + DRM_PLANE_HELPER_NO_SCALING, + DRM_PLANE_HELPER_NO_SCALING, + false, true); +} + +static void lcdif_plane_primary_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct lcdif_drm_private *lcdif = to_lcdif_drm_private(plane->dev); + struct drm_plane_state *new_pstate = drm_atomic_get_new_plane_state(state, + plane); + dma_addr_t paddr; + + paddr = drm_fb_cma_get_gem_addr(new_pstate->fb, new_pstate, 0); + if (paddr) { + writel(lower_32_bits(paddr), + lcdif->base + LCDC_V8_CTRLDESCL_LOW0_4); + writel(CTRLDESCL_HIGH0_4_ADDR_HIGH(upper_32_bits(paddr)), + lcdif->base + LCDC_V8_CTRLDESCL_HIGH0_4); + } +} + +static bool lcdif_format_mod_supported(struct drm_plane *plane, + uint32_t format, + uint64_t modifier) +{ + return modifier == DRM_FORMAT_MOD_LINEAR; +} + +static const struct drm_plane_helper_funcs lcdif_plane_primary_helper_funcs = { + .atomic_check = lcdif_plane_atomic_check, + .atomic_update = lcdif_plane_primary_atomic_update, +}; + +static const struct drm_plane_funcs lcdif_plane_funcs = { + .format_mod_supported = lcdif_format_mod_supported, + .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, +}; + +static const u32 lcdif_primary_plane_formats[] = { + DRM_FORMAT_RGB565, + DRM_FORMAT_RGB888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_XRGB4444, + DRM_FORMAT_XRGB8888, +}; + +static const u64 lcdif_modifiers[] = { + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID +}; + +/* ----------------------------------------------------------------------------- + * Initialization + */ + +int lcdif_kms_init(struct lcdif_drm_private *lcdif) +{ + struct drm_encoder *encoder = &lcdif->encoder; + struct drm_crtc *crtc = &lcdif->crtc; + int ret; + + drm_plane_helper_add(&lcdif->planes.primary, + &lcdif_plane_primary_helper_funcs); + ret = drm_universal_plane_init(lcdif->drm, &lcdif->planes.primary, 1, + &lcdif_plane_funcs, + lcdif_primary_plane_formats, + ARRAY_SIZE(lcdif_primary_plane_formats), + lcdif_modifiers, DRM_PLANE_TYPE_PRIMARY, + NULL); + if (ret) + return ret; + + drm_crtc_helper_add(crtc, &lcdif_crtc_helper_funcs); + ret = drm_crtc_init_with_planes(lcdif->drm, crtc, + &lcdif->planes.primary, NULL, + &lcdif_crtc_funcs, NULL); + if (ret) + return ret; + + encoder->possible_crtcs = drm_crtc_mask(crtc); + return drm_encoder_init(lcdif->drm, encoder, &lcdif_encoder_funcs, + DRM_MODE_ENCODER_NONE, NULL); +} diff --git a/drivers/gpu/drm/mxsfb/lcdif_regs.h b/drivers/gpu/drm/mxsfb/lcdif_regs.h new file mode 100644 index 000000000000..c70220651e3a --- /dev/null +++ b/drivers/gpu/drm/mxsfb/lcdif_regs.h @@ -0,0 +1,257 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2022 Marek Vasut <marex@denx.de> + * + * i.MX8MP/i.MXRT LCDIF LCD controller driver. + */ + +#ifndef __LCDIF_REGS_H__ +#define __LCDIF_REGS_H__ + +#define REG_SET 4 +#define REG_CLR 8 + +/* V8 register set */ +#define LCDC_V8_CTRL 0x00 +#define LCDC_V8_DISP_PARA 0x10 +#define LCDC_V8_DISP_SIZE 0x14 +#define LCDC_V8_HSYN_PARA 0x18 +#define LCDC_V8_VSYN_PARA 0x1c +#define LCDC_V8_VSYN_HSYN_WIDTH 0x20 +#define LCDC_V8_INT_STATUS_D0 0x24 +#define LCDC_V8_INT_ENABLE_D0 0x28 +#define LCDC_V8_INT_STATUS_D1 0x30 +#define LCDC_V8_INT_ENABLE_D1 0x34 +#define LCDC_V8_CTRLDESCL0_1 0x200 +#define LCDC_V8_CTRLDESCL0_3 0x208 +#define LCDC_V8_CTRLDESCL_LOW0_4 0x20c +#define LCDC_V8_CTRLDESCL_HIGH0_4 0x210 +#define LCDC_V8_CTRLDESCL0_5 0x214 +#define LCDC_V8_CSC0_CTRL 0x21c +#define LCDC_V8_CSC0_COEF0 0x220 +#define LCDC_V8_CSC0_COEF1 0x224 +#define LCDC_V8_CSC0_COEF2 0x228 +#define LCDC_V8_CSC0_COEF3 0x22c +#define LCDC_V8_CSC0_COEF4 0x230 +#define LCDC_V8_CSC0_COEF5 0x234 +#define LCDC_V8_PANIC0_THRES 0x238 + +#define CTRL_SFTRST BIT(31) +#define CTRL_CLKGATE BIT(30) +#define CTRL_BYPASS_COUNT BIT(19) +#define CTRL_VSYNC_MODE BIT(18) +#define CTRL_DOTCLK_MODE BIT(17) +#define CTRL_DATA_SELECT BIT(16) +#define CTRL_BUS_WIDTH_16 (0 << 10) +#define CTRL_BUS_WIDTH_8 (1 << 10) +#define CTRL_BUS_WIDTH_18 (2 << 10) +#define CTRL_BUS_WIDTH_24 (3 << 10) +#define CTRL_BUS_WIDTH_MASK (0x3 << 10) +#define CTRL_WORD_LENGTH_16 (0 << 8) +#define CTRL_WORD_LENGTH_8 (1 << 8) +#define CTRL_WORD_LENGTH_18 (2 << 8) +#define CTRL_WORD_LENGTH_24 (3 << 8) +#define CTRL_MASTER BIT(5) +#define CTRL_DF16 BIT(3) +#define CTRL_DF18 BIT(2) +#define CTRL_DF24 BIT(1) +#define CTRL_RUN BIT(0) + +#define CTRL1_RECOVER_ON_UNDERFLOW BIT(24) +#define CTRL1_FIFO_CLEAR BIT(21) +#define CTRL1_SET_BYTE_PACKAGING(x) (((x) & 0xf) << 16) +#define CTRL1_GET_BYTE_PACKAGING(x) (((x) >> 16) & 0xf) +#define CTRL1_CUR_FRAME_DONE_IRQ_EN BIT(13) +#define CTRL1_CUR_FRAME_DONE_IRQ BIT(9) + +#define CTRL2_SET_OUTSTANDING_REQS_1 0 +#define CTRL2_SET_OUTSTANDING_REQS_2 (0x1 << 21) +#define CTRL2_SET_OUTSTANDING_REQS_4 (0x2 << 21) +#define CTRL2_SET_OUTSTANDING_REQS_8 (0x3 << 21) +#define CTRL2_SET_OUTSTANDING_REQS_16 (0x4 << 21) +#define CTRL2_SET_OUTSTANDING_REQS_MASK (0x7 << 21) + +#define TRANSFER_COUNT_SET_VCOUNT(x) (((x) & 0xffff) << 16) +#define TRANSFER_COUNT_GET_VCOUNT(x) (((x) >> 16) & 0xffff) +#define TRANSFER_COUNT_SET_HCOUNT(x) ((x) & 0xffff) +#define TRANSFER_COUNT_GET_HCOUNT(x) ((x) & 0xffff) + +#define VDCTRL0_ENABLE_PRESENT BIT(28) +#define VDCTRL0_VSYNC_ACT_HIGH BIT(27) +#define VDCTRL0_HSYNC_ACT_HIGH BIT(26) +#define VDCTRL0_DOTCLK_ACT_FALLING BIT(25) +#define VDCTRL0_ENABLE_ACT_HIGH BIT(24) +#define VDCTRL0_VSYNC_PERIOD_UNIT BIT(21) +#define VDCTRL0_VSYNC_PULSE_WIDTH_UNIT BIT(20) +#define VDCTRL0_HALF_LINE BIT(19) +#define VDCTRL0_HALF_LINE_MODE BIT(18) +#define VDCTRL0_SET_VSYNC_PULSE_WIDTH(x) ((x) & 0x3ffff) +#define VDCTRL0_GET_VSYNC_PULSE_WIDTH(x) ((x) & 0x3ffff) + +#define VDCTRL2_SET_HSYNC_PERIOD(x) ((x) & 0x3ffff) +#define VDCTRL2_GET_HSYNC_PERIOD(x) ((x) & 0x3ffff) + +#define VDCTRL3_MUX_SYNC_SIGNALS BIT(29) +#define VDCTRL3_VSYNC_ONLY BIT(28) +#define SET_HOR_WAIT_CNT(x) (((x) & 0xfff) << 16) +#define GET_HOR_WAIT_CNT(x) (((x) >> 16) & 0xfff) +#define SET_VERT_WAIT_CNT(x) ((x) & 0xffff) +#define GET_VERT_WAIT_CNT(x) ((x) & 0xffff) + +#define VDCTRL4_SET_DOTCLK_DLY(x) (((x) & 0x7) << 29) /* v4 only */ +#define VDCTRL4_GET_DOTCLK_DLY(x) (((x) >> 29) & 0x7) /* v4 only */ +#define VDCTRL4_SYNC_SIGNALS_ON BIT(18) +#define SET_DOTCLK_H_VALID_DATA_CNT(x) ((x) & 0x3ffff) + +#define DEBUG0_HSYNC BIT(26) +#define DEBUG0_VSYNC BIT(25) + +#define AS_CTRL_PS_DISABLE BIT(23) +#define AS_CTRL_ALPHA_INVERT BIT(20) +#define AS_CTRL_ALPHA(a) (((a) & 0xff) << 8) +#define AS_CTRL_FORMAT_RGB565 (0xe << 4) +#define AS_CTRL_FORMAT_RGB444 (0xd << 4) +#define AS_CTRL_FORMAT_RGB555 (0xc << 4) +#define AS_CTRL_FORMAT_ARGB4444 (0x9 << 4) +#define AS_CTRL_FORMAT_ARGB1555 (0x8 << 4) +#define AS_CTRL_FORMAT_RGB888 (0x4 << 4) +#define AS_CTRL_FORMAT_ARGB8888 (0x0 << 4) +#define AS_CTRL_ENABLE_COLORKEY BIT(3) +#define AS_CTRL_ALPHA_CTRL_ROP (3 << 1) +#define AS_CTRL_ALPHA_CTRL_MULTIPLY (2 << 1) +#define AS_CTRL_ALPHA_CTRL_OVERRIDE (1 << 1) +#define AS_CTRL_ALPHA_CTRL_EMBEDDED (0 << 1) +#define AS_CTRL_AS_ENABLE BIT(0) + +/* V8 register set */ +#define CTRL_SW_RESET BIT(31) +#define CTRL_FETCH_START_OPTION_FPV 0 +#define CTRL_FETCH_START_OPTION_PWV BIT(8) +#define CTRL_FETCH_START_OPTION_BPV BIT(9) +#define CTRL_FETCH_START_OPTION_RESV GENMASK(9, 8) +#define CTRL_FETCH_START_OPTION_MASK GENMASK(9, 8) +#define CTRL_NEG BIT(4) +#define CTRL_INV_PXCK BIT(3) +#define CTRL_INV_DE BIT(2) +#define CTRL_INV_VS BIT(1) +#define CTRL_INV_HS BIT(0) + +#define DISP_PARA_DISP_ON BIT(31) +#define DISP_PARA_SWAP_EN BIT(30) +#define DISP_PARA_LINE_PATTERN_UYVY_H (GENMASK(29, 28) | BIT(26)) +#define DISP_PARA_LINE_PATTERN_RGB565 GENMASK(28, 26) +#define DISP_PARA_LINE_PATTERN_RGB888 0 +#define DISP_PARA_LINE_PATTERN_MASK GENMASK(29, 26) +#define DISP_PARA_DISP_MODE_MASK GENMASK(25, 24) +#define DISP_PARA_BGND_R_MASK GENMASK(23, 16) +#define DISP_PARA_BGND_G_MASK GENMASK(15, 8) +#define DISP_PARA_BGND_B_MASK GENMASK(7, 0) + +#define DISP_SIZE_DELTA_Y(n) (((n) & 0xffff) << 16) +#define DISP_SIZE_DELTA_Y_MASK GENMASK(31, 16) +#define DISP_SIZE_DELTA_X(n) ((n) & 0xffff) +#define DISP_SIZE_DELTA_X_MASK GENMASK(15, 0) + +#define HSYN_PARA_BP_H(n) (((n) & 0xffff) << 16) +#define HSYN_PARA_BP_H_MASK GENMASK(31, 16) +#define HSYN_PARA_FP_H(n) ((n) & 0xffff) +#define HSYN_PARA_FP_H_MASK GENMASK(15, 0) + +#define VSYN_PARA_BP_V(n) (((n) & 0xffff) << 16) +#define VSYN_PARA_BP_V_MASK GENMASK(31, 16) +#define VSYN_PARA_FP_V(n) ((n) & 0xffff) +#define VSYN_PARA_FP_V_MASK GENMASK(15, 0) + +#define VSYN_HSYN_WIDTH_PW_V(n) (((n) & 0xffff) << 16) +#define VSYN_HSYN_WIDTH_PW_V_MASK GENMASK(31, 16) +#define VSYN_HSYN_WIDTH_PW_H(n) ((n) & 0xffff) +#define VSYN_HSYN_WIDTH_PW_H_MASK GENMASK(15, 0) + +#define INT_STATUS_D0_FIFO_EMPTY BIT(24) +#define INT_STATUS_D0_DMA_DONE BIT(16) +#define INT_STATUS_D0_DMA_ERR BIT(8) +#define INT_STATUS_D0_VS_BLANK BIT(2) +#define INT_STATUS_D0_UNDERRUN BIT(1) +#define INT_STATUS_D0_VSYNC BIT(0) + +#define INT_ENABLE_D0_FIFO_EMPTY_EN BIT(24) +#define INT_ENABLE_D0_DMA_DONE_EN BIT(16) +#define INT_ENABLE_D0_DMA_ERR_EN BIT(8) +#define INT_ENABLE_D0_VS_BLANK_EN BIT(2) +#define INT_ENABLE_D0_UNDERRUN_EN BIT(1) +#define INT_ENABLE_D0_VSYNC_EN BIT(0) + +#define INT_STATUS_D1_PLANE_PANIC BIT(0) + +#define INT_ENABLE_D1_PLANE_PANIC_EN BIT(0) + +#define CTRLDESCL0_1_HEIGHT(n) (((n) & 0xffff) << 16) +#define CTRLDESCL0_1_HEIGHT_MASK GENMASK(31, 16) +#define CTRLDESCL0_1_WIDTH(n) ((n) & 0xffff) +#define CTRLDESCL0_1_WIDTH_MASK GENMASK(15, 0) + +#define CTRLDESCL0_3_PITCH(n) ((n) & 0xffff) +#define CTRLDESCL0_3_PITCH_MASK GENMASK(15, 0) + +#define CTRLDESCL_HIGH0_4_ADDR_HIGH(n) ((n) & 0xf) +#define CTRLDESCL_HIGH0_4_ADDR_HIGH_MASK GENMASK(3, 0) + +#define CTRLDESCL0_5_EN BIT(31) +#define CTRLDESCL0_5_SHADOW_LOAD_EN BIT(30) +#define CTRLDESCL0_5_BPP_16_RGB565 BIT(26) +#define CTRLDESCL0_5_BPP_16_ARGB1555 (BIT(26) | BIT(24)) +#define CTRLDESCL0_5_BPP_16_ARGB4444 (BIT(26) | BIT(25)) +#define CTRLDESCL0_5_BPP_YCbCr422 (BIT(26) | BIT(25) | BIT(24)) +#define CTRLDESCL0_5_BPP_24_RGB888 BIT(27) +#define CTRLDESCL0_5_BPP_32_ARGB8888 (BIT(27) | BIT(24)) +#define CTRLDESCL0_5_BPP_32_ABGR8888 (BIT(27) | BIT(25)) +#define CTRLDESCL0_5_BPP_MASK GENMASK(27, 24) +#define CTRLDESCL0_5_YUV_FORMAT_Y2VY1U 0 +#define CTRLDESCL0_5_YUV_FORMAT_Y2UY1V BIT(14) +#define CTRLDESCL0_5_YUV_FORMAT_VY2UY1 BIT(15) +#define CTRLDESCL0_5_YUV_FORMAT_UY2VY1 (BIT(15) | BIT(14)) +#define CTRLDESCL0_5_YUV_FORMAT_MASK GENMASK(15, 14) + +#define CSC0_CTRL_CSC_MODE_RGB2YCbCr GENMASK(2, 1) +#define CSC0_CTRL_CSC_MODE_MASK GENMASK(2, 1) +#define CSC0_CTRL_BYPASS BIT(0) + +#define CSC0_COEF0_A2(n) (((n) << 16) & CSC0_COEF0_A2_MASK) +#define CSC0_COEF0_A2_MASK GENMASK(26, 16) +#define CSC0_COEF0_A1(n) ((n) & CSC0_COEF0_A1_MASK) +#define CSC0_COEF0_A1_MASK GENMASK(10, 0) + +#define CSC0_COEF1_B1(n) (((n) << 16) & CSC0_COEF1_B1_MASK) +#define CSC0_COEF1_B1_MASK GENMASK(26, 16) +#define CSC0_COEF1_A3(n) ((n) & CSC0_COEF1_A3_MASK) +#define CSC0_COEF1_A3_MASK GENMASK(10, 0) + +#define CSC0_COEF2_B3(n) (((n) << 16) & CSC0_COEF2_B3_MASK) +#define CSC0_COEF2_B3_MASK GENMASK(26, 16) +#define CSC0_COEF2_B2(n) ((n) & CSC0_COEF2_B2_MASK) +#define CSC0_COEF2_B2_MASK GENMASK(10, 0) + +#define CSC0_COEF3_C2(n) (((n) << 16) & CSC0_COEF3_C2_MASK) +#define CSC0_COEF3_C2_MASK GENMASK(26, 16) +#define CSC0_COEF3_C1(n) ((n) & CSC0_COEF3_C1_MASK) +#define CSC0_COEF3_C1_MASK GENMASK(10, 0) + +#define CSC0_COEF4_D1(n) (((n) << 16) & CSC0_COEF4_D1_MASK) +#define CSC0_COEF4_D1_MASK GENMASK(24, 16) +#define CSC0_COEF4_C3(n) ((n) & CSC0_COEF4_C3_MASK) +#define CSC0_COEF4_C3_MASK GENMASK(10, 0) + +#define CSC0_COEF5_D3(n) (((n) << 16) & CSC0_COEF5_D3_MASK) +#define CSC0_COEF5_D3_MASK GENMASK(24, 16) +#define CSC0_COEF5_D2(n) ((n) & CSC0_COEF5_D2_MASK) +#define CSC0_COEF5_D2_MASK GENMASK(8, 0) + +#define PANIC0_THRES_LOW_MASK GENMASK(24, 16) +#define PANIC0_THRES_HIGH_MASK GENMASK(8, 0) + +#define LCDIF_MIN_XRES 120 +#define LCDIF_MIN_YRES 120 +#define LCDIF_MAX_XRES 0xffff +#define LCDIF_MAX_YRES 0xffff + +#endif /* __LCDIF_REGS_H__ */ diff --git a/drivers/gpu/drm/nouveau/Kconfig b/drivers/gpu/drm/nouveau/Kconfig index 34760164c271..03d12caf9e26 100644 --- a/drivers/gpu/drm/nouveau/Kconfig +++ b/drivers/gpu/drm/nouveau/Kconfig @@ -11,7 +11,6 @@ config DRM_NOUVEAU select DRM_TTM select DRM_TTM_HELPER select BACKLIGHT_CLASS_DEVICE if DRM_NOUVEAU_BACKLIGHT - select ACPI_VIDEO if ACPI && X86 && BACKLIGHT_CLASS_DEVICE && INPUT select X86_PLATFORM_DEVICES if ACPI && X86 select ACPI_WMI if ACPI && X86 select MXM_WMI if ACPI && X86 diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index 4f1f004b3c54..a9043eacce97 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -115,6 +115,17 @@ config DRM_PANEL_EDP that it can be automatically turned off when the panel goes into a low power state. +config DRM_PANEL_EBBG_FT8719 + tristate "EBBG FT8719 panel driver" + depends on OF + depends on DRM_MIPI_DSI + depends on BACKLIGHT_CLASS_DEVICE + help + Say Y here if you want to enable support for the EBBG FT8719 + video mode panel. Mainly found on Xiaomi Poco F1 mobile phone. + The panel has a resolution of 1080x2246. It provides a MIPI DSI + interface to the host. + config DRM_PANEL_ELIDA_KD35T133 tristate "Elida KD35T133 panel driver" depends on OF diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile index 42a7ab54234b..34e717382dbb 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_DRM_PANEL_DSI_CM) += panel-dsi-cm.o obj-$(CONFIG_DRM_PANEL_LVDS) += panel-lvds.o obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.o obj-$(CONFIG_DRM_PANEL_EDP) += panel-edp.o +obj-$(CONFIG_DRM_PANEL_EBBG_FT8719) += panel-ebbg-ft8719.o obj-$(CONFIG_DRM_PANEL_ELIDA_KD35T133) += panel-elida-kd35t133.o obj-$(CONFIG_DRM_PANEL_FEIXIN_K101_IM2BA02) += panel-feixin-k101-im2ba02.o obj-$(CONFIG_DRM_PANEL_FEIYANG_FY07024DI26A30D) += panel-feiyang-fy07024di26a30d.o diff --git a/drivers/gpu/drm/panel/panel-asus-z00t-tm5p5-n35596.c b/drivers/gpu/drm/panel/panel-asus-z00t-tm5p5-n35596.c index 44674ebedf59..174ff434bd71 100644 --- a/drivers/gpu/drm/panel/panel-asus-z00t-tm5p5-n35596.c +++ b/drivers/gpu/drm/panel/panel-asus-z00t-tm5p5-n35596.c @@ -215,14 +215,9 @@ static const struct drm_panel_funcs tm5p5_nt35596_panel_funcs = { static int tm5p5_nt35596_bl_update_status(struct backlight_device *bl) { struct mipi_dsi_device *dsi = bl_get_data(bl); - u16 brightness = bl->props.brightness; + u16 brightness = backlight_get_brightness(bl); int ret; - if (bl->props.power != FB_BLANK_UNBLANK || - bl->props.fb_blank != FB_BLANK_UNBLANK || - bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK)) - brightness = 0; - dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; ret = mipi_dsi_dcs_set_display_brightness(dsi, brightness); diff --git a/drivers/gpu/drm/panel/panel-dsi-cm.c b/drivers/gpu/drm/panel/panel-dsi-cm.c index b58cb064975f..b0213a518f9d 100644 --- a/drivers/gpu/drm/panel/panel-dsi-cm.c +++ b/drivers/gpu/drm/panel/panel-dsi-cm.c @@ -85,17 +85,10 @@ static void dsicm_bl_power(struct panel_drv_data *ddata, bool enable) else return; - if (enable) { - backlight->props.fb_blank = FB_BLANK_UNBLANK; - backlight->props.state = ~(BL_CORE_FBBLANK | BL_CORE_SUSPENDED); - backlight->props.power = FB_BLANK_UNBLANK; - } else { - backlight->props.fb_blank = FB_BLANK_NORMAL; - backlight->props.power = FB_BLANK_POWERDOWN; - backlight->props.state |= BL_CORE_FBBLANK | BL_CORE_SUSPENDED; - } - - backlight_update_status(backlight); + if (enable) + backlight_enable(backlight); + else + backlight_disable(backlight); } static void hw_guard_start(struct panel_drv_data *ddata, int guard_msec) @@ -196,13 +189,7 @@ static int dsicm_bl_update_status(struct backlight_device *dev) { struct panel_drv_data *ddata = dev_get_drvdata(&dev->dev); int r = 0; - int level; - - if (dev->props.fb_blank == FB_BLANK_UNBLANK && - dev->props.power == FB_BLANK_UNBLANK) - level = dev->props.brightness; - else - level = 0; + int level = backlight_get_brightness(dev); dev_dbg(&ddata->dsi->dev, "update brightness to %d\n", level); @@ -219,11 +206,7 @@ static int dsicm_bl_update_status(struct backlight_device *dev) static int dsicm_bl_get_intensity(struct backlight_device *dev) { - if (dev->props.fb_blank == FB_BLANK_UNBLANK && - dev->props.power == FB_BLANK_UNBLANK) - return dev->props.brightness; - - return 0; + return backlight_get_brightness(dev); } static const struct backlight_ops dsicm_bl_ops = { diff --git a/drivers/gpu/drm/panel/panel-ebbg-ft8719.c b/drivers/gpu/drm/panel/panel-ebbg-ft8719.c new file mode 100644 index 000000000000..386f8321b930 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-ebbg-ft8719.c @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2022 Joel Selvaraj <jo@jsfamily.in> + * Generated with linux-mdss-dsi-panel-driver-generator from vendor device tree: + * Copyright (c) 2013, The Linux Foundation. All rights reserved. + */ + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/regulator/consumer.h> +#include <linux/module.h> +#include <linux/of.h> + +#include <video/mipi_display.h> + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +static const char * const regulator_names[] = { + "vddio", + "vddpos", + "vddneg", +}; + +static const unsigned long regulator_enable_loads[] = { + 62000, + 100000, + 100000 +}; + +struct ebbg_ft8719 { + struct drm_panel panel; + struct mipi_dsi_device *dsi; + + struct regulator_bulk_data supplies[ARRAY_SIZE(regulator_names)]; + + struct gpio_desc *reset_gpio; +}; + +static inline +struct ebbg_ft8719 *to_ebbg_ft8719(struct drm_panel *panel) +{ + return container_of(panel, struct ebbg_ft8719, panel); +} + +static void ebbg_ft8719_reset(struct ebbg_ft8719 *ctx) +{ + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(4000, 5000); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + usleep_range(1000, 2000); + gpiod_set_value_cansleep(ctx->reset_gpio, 0); + usleep_range(15000, 16000); +} + +static int ebbg_ft8719_on(struct ebbg_ft8719 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct device *dev = &dsi->dev; + int ret; + + dsi->mode_flags |= MIPI_DSI_MODE_LPM; + + ret = mipi_dsi_dcs_set_display_brightness(dsi, 0x00ff); + if (ret < 0) { + dev_err(dev, "Failed to set display brightness: %d\n", ret); + return ret; + } + + mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x24); + mipi_dsi_dcs_write_seq(dsi, MIPI_DCS_WRITE_POWER_SAVE, 0x00); + + ret = mipi_dsi_dcs_exit_sleep_mode(dsi); + if (ret < 0) { + dev_err(dev, "Failed to exit sleep mode: %d\n", ret); + return ret; + } + msleep(90); + + ret = mipi_dsi_dcs_set_display_on(dsi); + if (ret < 0) { + dev_err(dev, "Failed to set display on: %d\n", ret); + return ret; + } + + return 0; +} + +static int ebbg_ft8719_off(struct ebbg_ft8719 *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct device *dev = &dsi->dev; + int ret; + + dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; + + ret = mipi_dsi_dcs_set_display_off(dsi); + if (ret < 0) { + dev_err(dev, "Failed to set display off: %d\n", ret); + return ret; + } + usleep_range(10000, 11000); + + ret = mipi_dsi_dcs_enter_sleep_mode(dsi); + if (ret < 0) { + dev_err(dev, "Failed to enter sleep mode: %d\n", ret); + return ret; + } + msleep(90); + + return 0; +} + +static int ebbg_ft8719_prepare(struct drm_panel *panel) +{ + struct ebbg_ft8719 *ctx = to_ebbg_ft8719(panel); + struct device *dev = &ctx->dsi->dev; + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + if (ret < 0) + return ret; + + ebbg_ft8719_reset(ctx); + + ret = ebbg_ft8719_on(ctx); + if (ret < 0) { + dev_err(dev, "Failed to initialize panel: %d\n", ret); + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + return ret; + } + + return 0; +} + +static int ebbg_ft8719_unprepare(struct drm_panel *panel) +{ + struct ebbg_ft8719 *ctx = to_ebbg_ft8719(panel); + struct device *dev = &ctx->dsi->dev; + int ret; + + ret = ebbg_ft8719_off(ctx); + if (ret < 0) + dev_err(dev, "Failed to un-initialize panel: %d\n", ret); + + gpiod_set_value_cansleep(ctx->reset_gpio, 1); + + ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies); + if (ret) + dev_err(panel->dev, "Failed to disable regulators: %d\n", ret); + + return 0; +} + +static const struct drm_display_mode ebbg_ft8719_mode = { + .clock = (1080 + 28 + 4 + 16) * (2246 + 120 + 4 + 12) * 60 / 1000, + .hdisplay = 1080, + .hsync_start = 1080 + 28, + .hsync_end = 1080 + 28 + 4, + .htotal = 1080 + 28 + 4 + 16, + .vdisplay = 2246, + .vsync_start = 2246 + 120, + .vsync_end = 2246 + 120 + 4, + .vtotal = 2246 + 120 + 4 + 12, + .width_mm = 68, + .height_mm = 141, +}; + +static int ebbg_ft8719_get_modes(struct drm_panel *panel, + struct drm_connector *connector) +{ + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, &ebbg_ft8719_mode); + if (!mode) + return -ENOMEM; + + drm_mode_set_name(mode); + + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + drm_mode_probed_add(connector, mode); + + return 1; +} + +static const struct drm_panel_funcs ebbg_ft8719_panel_funcs = { + .prepare = ebbg_ft8719_prepare, + .unprepare = ebbg_ft8719_unprepare, + .get_modes = ebbg_ft8719_get_modes, +}; + +static int ebbg_ft8719_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct ebbg_ft8719 *ctx; + int i, ret; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(ctx->supplies); i++) + ctx->supplies[i].supply = regulator_names[i]; + + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ctx->supplies), + ctx->supplies); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + + for (i = 0; i < ARRAY_SIZE(ctx->supplies); i++) { + ret = regulator_set_load(ctx->supplies[i].consumer, + regulator_enable_loads[i]); + if (ret) + return dev_err_probe(dev, ret, + "Failed to set regulator load\n"); + } + + ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(ctx->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio), + "Failed to get reset-gpios\n"); + + ctx->dsi = dsi; + mipi_dsi_set_drvdata(dsi, ctx); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST | + MIPI_DSI_CLOCK_NON_CONTINUOUS; + + drm_panel_init(&ctx->panel, dev, &ebbg_ft8719_panel_funcs, + DRM_MODE_CONNECTOR_DSI); + + ret = drm_panel_of_backlight(&ctx->panel); + if (ret) + return dev_err_probe(dev, ret, "Failed to get backlight\n"); + + drm_panel_add(&ctx->panel); + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(dev, "Failed to attach to DSI host: %d\n", ret); + drm_panel_remove(&ctx->panel); + return ret; + } + + return 0; +} + +static int ebbg_ft8719_remove(struct mipi_dsi_device *dsi) +{ + struct ebbg_ft8719 *ctx = mipi_dsi_get_drvdata(dsi); + int ret; + + ret = mipi_dsi_detach(dsi); + if (ret < 0) + dev_err(&dsi->dev, "Failed to detach from DSI host: %d\n", ret); + + drm_panel_remove(&ctx->panel); + + return 0; +} + +static const struct of_device_id ebbg_ft8719_of_match[] = { + { .compatible = "ebbg,ft8719" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ebbg_ft8719_of_match); + +static struct mipi_dsi_driver ebbg_ft8719_driver = { + .probe = ebbg_ft8719_probe, + .remove = ebbg_ft8719_remove, + .driver = { + .name = "panel-ebbg-ft8719", + .of_match_table = ebbg_ft8719_of_match, + }, +}; +module_mipi_dsi_driver(ebbg_ft8719_driver); + +MODULE_AUTHOR("Joel Selvaraj <jo@jsfamily.in>"); +MODULE_DESCRIPTION("DRM driver for EBBG FT8719 video dsi panel"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/panel/panel-edp.c b/drivers/gpu/drm/panel/panel-edp.c index 16bdcd83d550..3626469c4cc2 100644 --- a/drivers/gpu/drm/panel/panel-edp.c +++ b/drivers/gpu/drm/panel/panel-edp.c @@ -1885,6 +1885,7 @@ static const struct edp_panel_entry edp_panels[] = { EDP_PANEL_ENTRY('K', 'D', 'B', 0x0624, &kingdisplay_kd116n21_30nv_a010.delay, "116N21-30NV-A010"), EDP_PANEL_ENTRY('K', 'D', 'B', 0x1120, &delay_200_500_e80_d50, "116N29-30NK-C007"), + EDP_PANEL_ENTRY('S', 'H', 'P', 0x1511, &delay_200_500_e50, "LQ140M1JW48"), EDP_PANEL_ENTRY('S', 'H', 'P', 0x1523, &sharp_lq140m1jw46.delay, "LQ140M1JW46"), EDP_PANEL_ENTRY('S', 'H', 'P', 0x154c, &delay_200_500_p2e100, "LQ116M1JW10"), diff --git a/drivers/gpu/drm/panel/panel-novatek-nt35510.c b/drivers/gpu/drm/panel/panel-novatek-nt35510.c index 873cbd38e6d3..40ea41b0a5dd 100644 --- a/drivers/gpu/drm/panel/panel-novatek-nt35510.c +++ b/drivers/gpu/drm/panel/panel-novatek-nt35510.c @@ -190,7 +190,7 @@ struct nt35510_config { * 6 = Hsync x 2 * 7 = Hsync x 4 * bits 4..6 in the upper nibble controls BTP, the boosting - * amplification for the the step-up circuit: + * amplification for the step-up circuit: * 0 = Disable * 1 = 1.5 x VDDB * 2 = 1.66 x VDDB @@ -211,7 +211,7 @@ struct nt35510_config { * bits 0..2 in the lower nibble controls NCK, the booster clock * frequency, the values are the same as for PCK in @bt1ctr. * bits 4..5 in the upper nibble controls BTN, the boosting - * amplification for the the step-up circuit. + * amplification for the step-up circuit. * 0 = Disable * 1 = -1.5 x VDDB * 2 = -2 x VDDB @@ -250,7 +250,7 @@ struct nt35510_config { * bits 0..2 in the lower nibble controls LCK, the booster clock * frequency, the values are the same as for PCK in @bt1ctr. * bits 4..5 in the upper nibble controls BTL, the boosting - * amplification for the the step-up circuit. + * amplification for the step-up circuit. * 0 = AVEE + VCL * 1 = AVEE - AVDD * 2 = AVEE + VCL - AVDD diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index a1c12bde686f..b1e211a4b615 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -675,8 +675,10 @@ static int panel_simple_probe(struct device *dev, const struct panel_desc *desc) drm_panel_init(&panel->base, dev, &panel_simple_funcs, connector_type); err = drm_panel_of_backlight(&panel->base); - if (err) + if (err) { + dev_err_probe(dev, err, "Could not find backlight\n"); goto disable_pm_runtime; + } drm_panel_add(&panel->base); @@ -790,6 +792,36 @@ static const struct panel_desc ampire_am800480r3tmqwa1h = { .bus_format = MEDIA_BUS_FMT_RGB666_1X18, }; +static const struct display_timing ampire_am800600p5tmqw_tb8h_timing = { + .pixelclock = { 34500000, 39600000, 50400000 }, + .hactive = { 800, 800, 800 }, + .hfront_porch = { 12, 112, 312 }, + .hback_porch = { 87, 87, 48 }, + .hsync_len = { 1, 1, 40 }, + .vactive = { 600, 600, 600 }, + .vfront_porch = { 1, 21, 61 }, + .vback_porch = { 38, 38, 19 }, + .vsync_len = { 1, 1, 20 }, + .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW | + DISPLAY_FLAGS_DE_HIGH | DISPLAY_FLAGS_PIXDATA_POSEDGE | + DISPLAY_FLAGS_SYNC_POSEDGE, +}; + +static const struct panel_desc ampire_am800600p5tmqwtb8h = { + .timings = &ire_am800600p5tmqw_tb8h_timing, + .num_timings = 1, + .bpc = 6, + .size = { + .width = 162, + .height = 122, + }, + .bus_format = MEDIA_BUS_FMT_RGB666_1X18, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | + DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE | + DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE, + .connector_type = DRM_MODE_CONNECTOR_DPI, +}; + static const struct display_timing santek_st0700i5y_rbslw_f_timing = { .pixelclock = { 26400000, 33300000, 46800000 }, .hactive = { 800, 800, 800 }, @@ -1439,6 +1471,30 @@ static const struct panel_desc dataimage_fg040346dsswbg04 = { .connector_type = DRM_MODE_CONNECTOR_DPI, }; +static const struct display_timing dataimage_fg1001l0dsswmg01_timing = { + .pixelclock = { 68900000, 71110000, 73400000 }, + .hactive = { 1280, 1280, 1280 }, + .vactive = { 800, 800, 800 }, + .hback_porch = { 100, 100, 100 }, + .hfront_porch = { 100, 100, 100 }, + .vback_porch = { 5, 5, 5 }, + .vfront_porch = { 5, 5, 5 }, + .hsync_len = { 24, 24, 24 }, + .vsync_len = { 3, 3, 3 }, + .flags = DISPLAY_FLAGS_DE_HIGH | DISPLAY_FLAGS_PIXDATA_POSEDGE | + DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW, +}; + +static const struct panel_desc dataimage_fg1001l0dsswmg01 = { + .timings = &dataimage_fg1001l0dsswmg01_timing, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 217, + .height = 136, + }, +}; + static const struct drm_display_mode dataimage_scf0700c48ggu18_mode = { .clock = 33260, .hdisplay = 800, @@ -3767,6 +3823,9 @@ static const struct of_device_id platform_of_match[] = { .compatible = "ampire,am800480r3tmqwa1h", .data = &ire_am800480r3tmqwa1h, }, { + .compatible = "ampire,am800600p5tmqw-tb8h", + .data = &ire_am800600p5tmqwtb8h, + }, { .compatible = "arm,rtsm-display", .data = &arm_rtsm, }, { @@ -3845,6 +3904,9 @@ static const struct of_device_id platform_of_match[] = { .compatible = "dataimage,fg040346dsswbg04", .data = &dataimage_fg040346dsswbg04, }, { + .compatible = "dataimage,fg1001l0dsswmg01", + .data = &dataimage_fg1001l0dsswmg01, + }, { .compatible = "dataimage,scf0700c48ggu18", .data = &dataimage_scf0700c48ggu18, }, { diff --git a/drivers/gpu/drm/panel/panel-sony-acx565akm.c b/drivers/gpu/drm/panel/panel-sony-acx565akm.c index 0d7541a33f87..3d6a286056a0 100644 --- a/drivers/gpu/drm/panel/panel-sony-acx565akm.c +++ b/drivers/gpu/drm/panel/panel-sony-acx565akm.c @@ -298,13 +298,7 @@ static void acx565akm_set_brightness(struct acx565akm_panel *lcd, int level) static int acx565akm_bl_update_status_locked(struct backlight_device *dev) { struct acx565akm_panel *lcd = dev_get_drvdata(&dev->dev); - int level; - - if (dev->props.fb_blank == FB_BLANK_UNBLANK && - dev->props.power == FB_BLANK_UNBLANK) - level = dev->props.brightness; - else - level = 0; + int level = backlight_get_brightness(dev); acx565akm_set_brightness(lcd, level); @@ -330,8 +324,7 @@ static int acx565akm_bl_get_intensity(struct backlight_device *dev) mutex_lock(&lcd->mutex); - if (dev->props.fb_blank == FB_BLANK_UNBLANK && - dev->props.power == FB_BLANK_UNBLANK) + if (!backlight_is_blank(dev)) intensity = acx565akm_get_actual_brightness(lcd); else intensity = 0; @@ -349,7 +342,6 @@ static const struct backlight_ops acx565akm_bl_ops = { static int acx565akm_backlight_init(struct acx565akm_panel *lcd) { struct backlight_properties props = { - .fb_blank = FB_BLANK_UNBLANK, .power = FB_BLANK_UNBLANK, .type = BACKLIGHT_RAW, }; diff --git a/drivers/gpu/drm/pl111/pl111_display.c b/drivers/gpu/drm/pl111/pl111_display.c index ccf5f02b2962..56275f06a8f3 100644 --- a/drivers/gpu/drm/pl111/pl111_display.c +++ b/drivers/gpu/drm/pl111/pl111_display.c @@ -249,7 +249,7 @@ static void pl111_display_enable(struct drm_simple_display_pipe *pipe, cntl |= CNTL_ST_CDWID_24; /* - * Note that the the ARM hardware's format reader takes 'r' from + * Note that the ARM hardware's format reader takes 'r' from * the low bit, while DRM formats list channels from high bit * to low bit as you read left to right. The ST Micro version of * the PL110 (LCDC) however uses the standard DRM format. diff --git a/drivers/gpu/drm/selftests/test-drm_dp_mst_helper.c b/drivers/gpu/drm/selftests/test-drm_dp_mst_helper.c index 967c52150b67..4caa9be900ac 100644 --- a/drivers/gpu/drm/selftests/test-drm_dp_mst_helper.c +++ b/drivers/gpu/drm/selftests/test-drm_dp_mst_helper.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Test cases for for the DRM DP MST helpers + * Test cases for the DRM DP MST helpers */ #define PREFIX_STR "[drm_dp_mst_helper]" diff --git a/drivers/gpu/drm/shmobile/shmob_drm_backlight.c b/drivers/gpu/drm/shmobile/shmob_drm_backlight.c index f6628a5ee95f..794573badfe8 100644 --- a/drivers/gpu/drm/shmobile/shmob_drm_backlight.c +++ b/drivers/gpu/drm/shmobile/shmob_drm_backlight.c @@ -18,11 +18,7 @@ static int shmob_drm_backlight_update(struct backlight_device *bdev) struct shmob_drm_connector *scon = bl_get_data(bdev); struct shmob_drm_device *sdev = scon->connector.dev->dev_private; const struct shmob_drm_backlight_data *bdata = &sdev->pdata->backlight; - int brightness = bdev->props.brightness; - - if (bdev->props.power != FB_BLANK_UNBLANK || - bdev->props.state & BL_CORE_SUSPENDED) - brightness = 0; + int brightness = backlight_get_brightness(bdev); return bdata->set_brightness(brightness); } diff --git a/drivers/gpu/drm/stm/drv.c b/drivers/gpu/drm/stm/drv.c index 0da7cce2a1a2..c63945dc2260 100644 --- a/drivers/gpu/drm/stm/drv.c +++ b/drivers/gpu/drm/stm/drv.c @@ -95,6 +95,7 @@ static int drv_load(struct drm_device *ddev) ddev->mode_config.max_width = STM_MAX_FB_WIDTH; ddev->mode_config.max_height = STM_MAX_FB_HEIGHT; ddev->mode_config.funcs = &drv_mode_config_funcs; + ddev->mode_config.normalize_zpos = true; ret = ltdc_load(ddev); if (ret) diff --git a/drivers/gpu/drm/stm/ltdc.c b/drivers/gpu/drm/stm/ltdc.c index 76230f775081..cc6547de682f 100644 --- a/drivers/gpu/drm/stm/ltdc.c +++ b/drivers/gpu/drm/stm/ltdc.c @@ -165,16 +165,20 @@ #define BCCR_BCWHITE GENMASK(23, 0) /* Background Color WHITE */ #define IER_LIE BIT(0) /* Line Interrupt Enable */ -#define IER_FUIE BIT(1) /* Fifo Underrun Interrupt Enable */ +#define IER_FUWIE BIT(1) /* Fifo Underrun Warning Interrupt Enable */ #define IER_TERRIE BIT(2) /* Transfer ERRor Interrupt Enable */ -#define IER_RRIE BIT(3) /* Register Reload Interrupt enable */ +#define IER_RRIE BIT(3) /* Register Reload Interrupt Enable */ +#define IER_FUEIE BIT(6) /* Fifo Underrun Error Interrupt Enable */ +#define IER_CRCIE BIT(7) /* CRC Error Interrupt Enable */ #define CPSR_CYPOS GENMASK(15, 0) /* Current Y position */ #define ISR_LIF BIT(0) /* Line Interrupt Flag */ -#define ISR_FUIF BIT(1) /* Fifo Underrun Interrupt Flag */ +#define ISR_FUWIF BIT(1) /* Fifo Underrun Warning Interrupt Flag */ #define ISR_TERRIF BIT(2) /* Transfer ERRor Interrupt Flag */ #define ISR_RRIF BIT(3) /* Register Reload Interrupt Flag */ +#define ISR_FUEIF BIT(6) /* Fifo Underrun Error Interrupt Flag */ +#define ISR_CRCIF BIT(7) /* CRC Error Interrupt Flag */ #define EDCR_OCYEN BIT(25) /* Output Conversion to YCbCr 422: ENable */ #define EDCR_OCYSEL BIT(26) /* Output Conversion to YCbCr 422: SELection of the CCIR */ @@ -183,6 +187,7 @@ #define LXCR_LEN BIT(0) /* Layer ENable */ #define LXCR_COLKEN BIT(1) /* Color Keying Enable */ #define LXCR_CLUTEN BIT(4) /* Color Look-Up Table ENable */ +#define LXCR_HMEN BIT(8) /* Horizontal Mirroring ENable */ #define LXWHPCR_WHSTPOS GENMASK(11, 0) /* Window Horizontal StarT POSition */ #define LXWHPCR_WHSPPOS GENMASK(27, 16) /* Window Horizontal StoP POSition */ @@ -197,9 +202,10 @@ #define LXBFCR_BF2 GENMASK(2, 0) /* Blending Factor 2 */ #define LXBFCR_BF1 GENMASK(10, 8) /* Blending Factor 1 */ +#define LXBFCR_BOR GENMASK(18, 16) /* Blending ORder */ #define LXCFBLR_CFBLL GENMASK(12, 0) /* Color Frame Buffer Line Length */ -#define LXCFBLR_CFBP GENMASK(28, 16) /* Color Frame Buffer Pitch in bytes */ +#define LXCFBLR_CFBP GENMASK(31, 16) /* Color Frame Buffer Pitch in bytes */ #define LXCFBLNR_CFBLN GENMASK(10, 0) /* Color Frame Buffer Line Number */ @@ -232,6 +238,8 @@ #define NB_PF 8 /* Max nb of HW pixel format */ +#define FUT_DFT 128 /* Default value of fifo underrun threshold */ + /* * Skip the first value and the second in case CRC was enabled during * the thread irq. This is to be sure CRC value is relevant for the @@ -712,12 +720,13 @@ static irqreturn_t ltdc_irq_thread(int irq, void *arg) ltdc_irq_crc_handle(ldev, crtc); } - /* Save FIFO Underrun & Transfer Error status */ mutex_lock(&ldev->err_lock); - if (ldev->irq_status & ISR_FUIF) - ldev->error_status |= ISR_FUIF; if (ldev->irq_status & ISR_TERRIF) - ldev->error_status |= ISR_TERRIF; + ldev->transfer_err++; + if (ldev->irq_status & ISR_FUEIF) + ldev->fifo_err++; + if (ldev->irq_status & ISR_FUWIF) + ldev->fifo_warn++; mutex_unlock(&ldev->err_lock); return IRQ_HANDLED; @@ -776,7 +785,7 @@ static void ltdc_crtc_atomic_enable(struct drm_crtc *crtc, regmap_write(ldev->regmap, LTDC_BCCR, BCCR_BCBLACK); /* Enable IRQ */ - regmap_set_bits(ldev->regmap, LTDC_IER, IER_RRIE | IER_FUIE | IER_TERRIE); + regmap_set_bits(ldev->regmap, LTDC_IER, IER_FUWIE | IER_FUEIE | IER_RRIE | IER_TERRIE); /* Commit shadow registers = update planes at next vblank */ if (!ldev->caps.plane_reg_shadow) @@ -790,19 +799,32 @@ static void ltdc_crtc_atomic_disable(struct drm_crtc *crtc, { struct ltdc_device *ldev = crtc_to_ltdc(crtc); struct drm_device *ddev = crtc->dev; + int layer_index = 0; DRM_DEBUG_DRIVER("\n"); drm_crtc_vblank_off(crtc); + /* Disable all layers */ + for (layer_index = 0; layer_index < ldev->caps.nb_layers; layer_index++) + regmap_write_bits(ldev->regmap, LTDC_L1CR + layer_index * LAY_OFS, + LXCR_CLUTEN | LXCR_LEN, 0); + /* disable IRQ */ - regmap_clear_bits(ldev->regmap, LTDC_IER, IER_RRIE | IER_FUIE | IER_TERRIE); + regmap_clear_bits(ldev->regmap, LTDC_IER, IER_FUWIE | IER_FUEIE | IER_RRIE | IER_TERRIE); /* immediately commit disable of layers before switching off LTDC */ if (!ldev->caps.plane_reg_shadow) regmap_set_bits(ldev->regmap, LTDC_SRCR, SRCR_IMR); pm_runtime_put_sync(ddev->dev); + + /* clear interrupt error counters */ + mutex_lock(&ldev->err_lock); + ldev->transfer_err = 0; + ldev->fifo_err = 0; + ldev->fifo_warn = 0; + mutex_unlock(&ldev->err_lock); } #define CLK_TOLERANCE_HZ 50 @@ -905,9 +927,9 @@ static void ltdc_crtc_mode_set_nofb(struct drm_crtc *crtc) drm_connector_list_iter_end(&iter); } - if (bridge && bridge->timings) + if (bridge && bridge->timings) { bus_flags = bridge->timings->input_bus_flags; - else if (connector) { + } else if (connector) { bus_flags = connector->display_info.bus_flags; if (connector->display_info.num_bus_formats) bus_formats = connector->display_info.bus_formats[0]; @@ -1163,6 +1185,18 @@ static int ltdc_crtc_verify_crc_source(struct drm_crtc *crtc, return 0; } +static void ltdc_crtc_atomic_print_state(struct drm_printer *p, + const struct drm_crtc_state *state) +{ + struct drm_crtc *crtc = state->crtc; + struct ltdc_device *ldev = crtc_to_ltdc(crtc); + + drm_printf(p, "\ttransfer_error=%d\n", ldev->transfer_err); + drm_printf(p, "\tfifo_underrun_error=%d\n", ldev->fifo_err); + drm_printf(p, "\tfifo_underrun_warning=%d\n", ldev->fifo_warn); + drm_printf(p, "\tfifo_underrun_threshold=%d\n", ldev->fifo_threshold); +} + static const struct drm_crtc_funcs ltdc_crtc_funcs = { .destroy = drm_crtc_cleanup, .set_config = drm_atomic_helper_set_config, @@ -1173,6 +1207,7 @@ static const struct drm_crtc_funcs ltdc_crtc_funcs = { .enable_vblank = ltdc_crtc_enable_vblank, .disable_vblank = ltdc_crtc_disable_vblank, .get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp, + .atomic_print_state = ltdc_crtc_atomic_print_state, }; static const struct drm_crtc_funcs ltdc_crtc_with_crc_support_funcs = { @@ -1187,6 +1222,7 @@ static const struct drm_crtc_funcs ltdc_crtc_with_crc_support_funcs = { .get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp, .set_crc_source = ltdc_crtc_set_crc_source, .verify_crc_source = ltdc_crtc_verify_crc_source, + .atomic_print_state = ltdc_crtc_atomic_print_state, }; /* @@ -1212,7 +1248,8 @@ static int ltdc_plane_atomic_check(struct drm_plane *plane, /* Reject scaling */ if (src_w != new_plane_state->crtc_w || src_h != new_plane_state->crtc_h) { - DRM_ERROR("Scaling is not supported"); + DRM_DEBUG_DRIVER("Scaling is not supported"); + return -EINVAL; } @@ -1232,7 +1269,8 @@ static void ltdc_plane_atomic_update(struct drm_plane *plane, u32 y0 = newstate->crtc_y; u32 y1 = newstate->crtc_y + newstate->crtc_h - 1; u32 src_x, src_y, src_w, src_h; - u32 val, pitch_in_bytes, line_length, line_number, paddr, ahbp, avbp, bpcr; + u32 val, pitch_in_bytes, line_length, line_number, ahbp, avbp, bpcr; + u32 paddr, paddr1, paddr2; enum ltdc_pix_fmt pf; if (!newstate->crtc || !fb) { @@ -1284,13 +1322,6 @@ static void ltdc_plane_atomic_update(struct drm_plane *plane, } regmap_write_bits(ldev->regmap, LTDC_L1PFCR + lofs, LXPFCR_PF, val); - /* Configures the color frame buffer pitch in bytes & line length */ - pitch_in_bytes = fb->pitches[0]; - line_length = fb->format->cpp[0] * - (x1 - x0 + 1) + (ldev->caps.bus_width >> 3) - 1; - val = ((pitch_in_bytes << 16) | line_length); - regmap_write_bits(ldev->regmap, LTDC_L1CFBLR + lofs, LXCFBLR_CFBLL | LXCFBLR_CFBP, val); - /* Specifies the constant alpha value */ val = newstate->alpha >> 8; regmap_write_bits(ldev->regmap, LTDC_L1CACR + lofs, LXCACR_CONSTA, val); @@ -1305,78 +1336,124 @@ static void ltdc_plane_atomic_update(struct drm_plane *plane, plane->type != DRM_PLANE_TYPE_PRIMARY) val = BF1_PAXCA | BF2_1PAXCA; - regmap_write_bits(ldev->regmap, LTDC_L1BFCR + lofs, LXBFCR_BF2 | LXBFCR_BF1, val); - - /* Configures the frame buffer line number */ - line_number = y1 - y0 + 1; - regmap_write_bits(ldev->regmap, LTDC_L1CFBLNR + lofs, LXCFBLNR_CFBLN, line_number); + if (ldev->caps.dynamic_zorder) { + val |= (newstate->normalized_zpos << 16); + regmap_write_bits(ldev->regmap, LTDC_L1BFCR + lofs, + LXBFCR_BF2 | LXBFCR_BF1 | LXBFCR_BOR, val); + } else { + regmap_write_bits(ldev->regmap, LTDC_L1BFCR + lofs, + LXBFCR_BF2 | LXBFCR_BF1, val); + } /* Sets the FB address */ paddr = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 0); + if (newstate->rotation & DRM_MODE_REFLECT_X) + paddr += (fb->format->cpp[0] * (x1 - x0 + 1)) - 1; + + if (newstate->rotation & DRM_MODE_REFLECT_Y) + paddr += (fb->pitches[0] * (y1 - y0)); + DRM_DEBUG_DRIVER("fb: phys 0x%08x", paddr); regmap_write(ldev->regmap, LTDC_L1CFBAR + lofs, paddr); + /* Configures the color frame buffer pitch in bytes & line length */ + line_length = fb->format->cpp[0] * + (x1 - x0 + 1) + (ldev->caps.bus_width >> 3) - 1; + + if (newstate->rotation & DRM_MODE_REFLECT_Y) + /* Compute negative value (signed on 16 bits) for the picth */ + pitch_in_bytes = 0x10000 - fb->pitches[0]; + else + pitch_in_bytes = fb->pitches[0]; + + val = (pitch_in_bytes << 16) | line_length; + regmap_write_bits(ldev->regmap, LTDC_L1CFBLR + lofs, LXCFBLR_CFBLL | LXCFBLR_CFBP, val); + + /* Configures the frame buffer line number */ + line_number = y1 - y0 + 1; + regmap_write_bits(ldev->regmap, LTDC_L1CFBLNR + lofs, LXCFBLNR_CFBLN, line_number); + if (ldev->caps.ycbcr_input) { if (fb->format->is_yuv) { switch (fb->format->format) { case DRM_FORMAT_NV12: case DRM_FORMAT_NV21: - /* Configure the auxiliary frame buffer address 0 & 1 */ - paddr = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 1); - regmap_write(ldev->regmap, LTDC_L1AFBA0R + lofs, paddr); - regmap_write(ldev->regmap, LTDC_L1AFBA1R + lofs, paddr + 1); + /* Configure the auxiliary frame buffer address 0 */ + paddr1 = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 1); - /* Configure the buffer length */ - val = ((pitch_in_bytes << 16) | line_length); - regmap_write(ldev->regmap, LTDC_L1AFBLR + lofs, val); + if (newstate->rotation & DRM_MODE_REFLECT_X) + paddr1 += ((fb->format->cpp[1] * (x1 - x0 + 1)) >> 1) - 1; - /* Configure the frame buffer line number */ - val = (line_number >> 1); - regmap_write(ldev->regmap, LTDC_L1AFBLNR + lofs, val); + if (newstate->rotation & DRM_MODE_REFLECT_Y) + paddr1 += (fb->pitches[1] * (y1 - y0 - 1)) >> 1; + + regmap_write(ldev->regmap, LTDC_L1AFBA0R + lofs, paddr1); break; case DRM_FORMAT_YUV420: - /* Configure the auxiliary frame buffer address 0 */ - paddr = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 1); - regmap_write(ldev->regmap, LTDC_L1AFBA0R + lofs, paddr); - - /* Configure the auxiliary frame buffer address 1 */ - paddr = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 2); - regmap_write(ldev->regmap, LTDC_L1AFBA1R + lofs, paddr); + /* Configure the auxiliary frame buffer address 0 & 1 */ + paddr1 = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 1); + paddr2 = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 2); - line_length = ((fb->format->cpp[0] * (x1 - x0 + 1)) >> 1) + - (ldev->caps.bus_width >> 3) - 1; + if (newstate->rotation & DRM_MODE_REFLECT_X) { + paddr1 += ((fb->format->cpp[1] * (x1 - x0 + 1)) >> 1) - 1; + paddr2 += ((fb->format->cpp[2] * (x1 - x0 + 1)) >> 1) - 1; + } - /* Configure the buffer length */ - val = (((pitch_in_bytes >> 1) << 16) | line_length); - regmap_write(ldev->regmap, LTDC_L1AFBLR + lofs, val); + if (newstate->rotation & DRM_MODE_REFLECT_Y) { + paddr1 += (fb->pitches[1] * (y1 - y0 - 1)) >> 1; + paddr2 += (fb->pitches[2] * (y1 - y0 - 1)) >> 1; + } - /* Configure the frame buffer line number */ - val = (line_number >> 1); - regmap_write(ldev->regmap, LTDC_L1AFBLNR + lofs, val); + regmap_write(ldev->regmap, LTDC_L1AFBA0R + lofs, paddr1); + regmap_write(ldev->regmap, LTDC_L1AFBA1R + lofs, paddr2); break; case DRM_FORMAT_YVU420: - /* Configure the auxiliary frame buffer address 0 */ - paddr = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 2); - regmap_write(ldev->regmap, LTDC_L1AFBA0R + lofs, paddr); - - /* Configure the auxiliary frame buffer address 1 */ - paddr = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 1); - regmap_write(ldev->regmap, LTDC_L1AFBA1R + lofs, paddr); + /* Configure the auxiliary frame buffer address 0 & 1 */ + paddr1 = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 2); + paddr2 = (u32)drm_fb_cma_get_gem_addr(fb, newstate, 1); - line_length = ((fb->format->cpp[0] * (x1 - x0 + 1)) >> 1) + - (ldev->caps.bus_width >> 3) - 1; + if (newstate->rotation & DRM_MODE_REFLECT_X) { + paddr1 += ((fb->format->cpp[1] * (x1 - x0 + 1)) >> 1) - 1; + paddr2 += ((fb->format->cpp[2] * (x1 - x0 + 1)) >> 1) - 1; + } - /* Configure the buffer length */ - val = (((pitch_in_bytes >> 1) << 16) | line_length); - regmap_write(ldev->regmap, LTDC_L1AFBLR + lofs, val); + if (newstate->rotation & DRM_MODE_REFLECT_Y) { + paddr1 += (fb->pitches[1] * (y1 - y0 - 1)) >> 1; + paddr2 += (fb->pitches[2] * (y1 - y0 - 1)) >> 1; + } - /* Configure the frame buffer line number */ - val = (line_number >> 1); - regmap_write(ldev->regmap, LTDC_L1AFBLNR + lofs, val); + regmap_write(ldev->regmap, LTDC_L1AFBA0R + lofs, paddr1); + regmap_write(ldev->regmap, LTDC_L1AFBA1R + lofs, paddr2); break; } + /* + * Set the length and the number of lines of the auxiliary + * buffers if the framebuffer contains more than one plane. + */ + if (fb->format->num_planes > 1) { + if (newstate->rotation & DRM_MODE_REFLECT_Y) + /* + * Compute negative value (signed on 16 bits) + * for the picth + */ + pitch_in_bytes = 0x10000 - fb->pitches[1]; + else + pitch_in_bytes = fb->pitches[1]; + + line_length = ((fb->format->cpp[1] * (x1 - x0 + 1)) >> 1) + + (ldev->caps.bus_width >> 3) - 1; + + /* Configure the auxiliary buffer length */ + val = (pitch_in_bytes << 16) | line_length; + regmap_write(ldev->regmap, LTDC_L1AFBLR + lofs, val); + + /* Configure the auxiliary frame buffer line number */ + val = line_number >> 1; + regmap_write(ldev->regmap, LTDC_L1AFBLNR + lofs, val); + } + /* Configure YCbC conversion coefficient */ ltdc_set_ycbcr_coeffs(plane); @@ -1391,7 +1468,12 @@ static void ltdc_plane_atomic_update(struct drm_plane *plane, /* Enable layer and CLUT if needed */ val = fb->format->format == DRM_FORMAT_C8 ? LXCR_CLUTEN : 0; val |= LXCR_LEN; - regmap_write_bits(ldev->regmap, LTDC_L1CR + lofs, LXCR_LEN | LXCR_CLUTEN, val); + + /* Enable horizontal mirroring if requested */ + if (newstate->rotation & DRM_MODE_REFLECT_X) + val |= LXCR_HMEN; + + regmap_write_bits(ldev->regmap, LTDC_L1CR + lofs, LXCR_LEN | LXCR_CLUTEN | LXCR_HMEN, val); /* Commit shadow registers = update plane at next vblank */ if (ldev->caps.plane_reg_shadow) @@ -1401,13 +1483,21 @@ static void ltdc_plane_atomic_update(struct drm_plane *plane, ldev->plane_fpsi[plane->index].counter++; mutex_lock(&ldev->err_lock); - if (ldev->error_status & ISR_FUIF) { - DRM_WARN("ltdc fifo underrun: please verify display mode\n"); - ldev->error_status &= ~ISR_FUIF; + if (ldev->transfer_err) { + DRM_WARN("ltdc transfer error: %d\n", ldev->transfer_err); + ldev->transfer_err = 0; } - if (ldev->error_status & ISR_TERRIF) { - DRM_WARN("ltdc transfer error\n"); - ldev->error_status &= ~ISR_TERRIF; + + if (ldev->caps.fifo_threshold) { + if (ldev->fifo_err) { + DRM_WARN("ltdc fifo underrun: please verify display mode\n"); + ldev->fifo_err = 0; + } + } else { + if (ldev->fifo_warn >= ldev->fifo_threshold) { + DRM_WARN("ltdc fifo underrun: please verify display mode\n"); + ldev->fifo_warn = 0; + } } mutex_unlock(&ldev->err_lock); } @@ -1420,8 +1510,8 @@ static void ltdc_plane_atomic_disable(struct drm_plane *plane, struct ltdc_device *ldev = plane_to_ltdc(plane); u32 lofs = plane->index * LAY_OFS; - /* disable layer */ - regmap_write_bits(ldev->regmap, LTDC_L1CR + lofs, LXCR_LEN, 0); + /* Disable layer */ + regmap_write_bits(ldev->regmap, LTDC_L1CR + lofs, LXCR_LEN | LXCR_CLUTEN | LXCR_HMEN, 0); /* Commit shadow registers = update plane at next vblank */ if (ldev->caps.plane_reg_shadow) @@ -1565,6 +1655,7 @@ static int ltdc_crtc_init(struct drm_device *ddev, struct drm_crtc *crtc) { struct ltdc_device *ldev = ddev->dev_private; struct drm_plane *primary, *overlay; + int supported_rotations = DRM_MODE_ROTATE_0 | DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y; unsigned int i; int ret; @@ -1574,7 +1665,14 @@ static int ltdc_crtc_init(struct drm_device *ddev, struct drm_crtc *crtc) return -EINVAL; } - drm_plane_create_zpos_immutable_property(primary, 0); + if (ldev->caps.dynamic_zorder) + drm_plane_create_zpos_property(primary, 0, 0, ldev->caps.nb_layers - 1); + else + drm_plane_create_zpos_immutable_property(primary, 0); + + if (ldev->caps.plane_rotation) + drm_plane_create_rotation_property(primary, DRM_MODE_ROTATE_0, + supported_rotations); /* Init CRTC according to its hardware features */ if (ldev->caps.crc) @@ -1603,7 +1701,14 @@ static int ltdc_crtc_init(struct drm_device *ddev, struct drm_crtc *crtc) DRM_ERROR("Can not create overlay plane %d\n", i); goto cleanup; } - drm_plane_create_zpos_immutable_property(overlay, i); + if (ldev->caps.dynamic_zorder) + drm_plane_create_zpos_property(overlay, i, 0, ldev->caps.nb_layers - 1); + else + drm_plane_create_zpos_immutable_property(overlay, i); + + if (ldev->caps.plane_rotation) + drm_plane_create_rotation_property(overlay, DRM_MODE_ROTATE_0, + supported_rotations); } return 0; @@ -1634,6 +1739,10 @@ static void ltdc_encoder_enable(struct drm_encoder *encoder) DRM_DEBUG_DRIVER("\n"); + /* set fifo underrun threshold register */ + if (ldev->caps.fifo_threshold) + regmap_write(ldev->regmap, LTDC_FUT, ldev->fifo_threshold); + /* Enable LTDC */ regmap_set_bits(ldev->regmap, LTDC_GCR, GCR_LTDCEN); } @@ -1733,6 +1842,9 @@ static int ltdc_get_caps(struct drm_device *ddev) ldev->caps.ycbcr_output = false; ldev->caps.plane_reg_shadow = false; ldev->caps.crc = false; + ldev->caps.dynamic_zorder = false; + ldev->caps.plane_rotation = false; + ldev->caps.fifo_threshold = false; break; case HWVER_20101: ldev->caps.layer_ofs = LAY_OFS_0; @@ -1748,6 +1860,9 @@ static int ltdc_get_caps(struct drm_device *ddev) ldev->caps.ycbcr_output = false; ldev->caps.plane_reg_shadow = false; ldev->caps.crc = false; + ldev->caps.dynamic_zorder = false; + ldev->caps.plane_rotation = false; + ldev->caps.fifo_threshold = false; break; case HWVER_40100: ldev->caps.layer_ofs = LAY_OFS_1; @@ -1763,6 +1878,9 @@ static int ltdc_get_caps(struct drm_device *ddev) ldev->caps.ycbcr_output = true; ldev->caps.plane_reg_shadow = true; ldev->caps.crc = true; + ldev->caps.dynamic_zorder = true; + ldev->caps.plane_rotation = true; + ldev->caps.fifo_threshold = true; break; default: return -ENODEV; @@ -1887,9 +2005,6 @@ int ltdc_load(struct drm_device *ddev) goto err; } - /* Disable interrupts */ - regmap_clear_bits(ldev->regmap, LTDC_IER, IER_LIE | IER_RRIE | IER_FUIE | IER_TERRIE); - ret = ltdc_get_caps(ddev); if (ret) { DRM_ERROR("hardware identifier (0x%08x) not supported!\n", @@ -1897,8 +2012,22 @@ int ltdc_load(struct drm_device *ddev) goto err; } + /* Disable interrupts */ + if (ldev->caps.fifo_threshold) + regmap_clear_bits(ldev->regmap, LTDC_IER, IER_LIE | IER_RRIE | IER_FUWIE | + IER_TERRIE); + else + regmap_clear_bits(ldev->regmap, LTDC_IER, IER_LIE | IER_RRIE | IER_FUWIE | + IER_TERRIE | IER_FUEIE); + DRM_DEBUG_DRIVER("ltdc hw version 0x%08x\n", ldev->caps.hw_version); + /* initialize default value for fifo underrun threshold & clear interrupt error counters */ + ldev->transfer_err = 0; + ldev->fifo_err = 0; + ldev->fifo_warn = 0; + ldev->fifo_threshold = FUT_DFT; + for (i = 0; i < ldev->caps.nb_irq; i++) { irq = platform_get_irq(pdev, i); if (irq < 0) { @@ -1913,7 +2042,6 @@ int ltdc_load(struct drm_device *ddev) DRM_ERROR("Failed to register LTDC interrupt\n"); goto err; } - } crtc = devm_kzalloc(dev, sizeof(*crtc), GFP_KERNEL); diff --git a/drivers/gpu/drm/stm/ltdc.h b/drivers/gpu/drm/stm/ltdc.h index 59fc5d1bbbab..9d488043ffdb 100644 --- a/drivers/gpu/drm/stm/ltdc.h +++ b/drivers/gpu/drm/stm/ltdc.h @@ -28,6 +28,9 @@ struct ltdc_caps { bool ycbcr_output; /* ycbcr output converter supported */ bool plane_reg_shadow; /* plane shadow registers ability */ bool crc; /* cyclic redundancy check supported */ + bool dynamic_zorder; /* dynamic z-order */ + bool plane_rotation; /* plane rotation */ + bool fifo_threshold; /* fifo underrun threshold supported */ }; #define LTDC_MAX_LAYER 4 @@ -43,8 +46,11 @@ struct ltdc_device { struct clk *pixel_clk; /* lcd pixel clock */ struct mutex err_lock; /* protecting error_status */ struct ltdc_caps caps; - u32 error_status; u32 irq_status; + u32 fifo_err; /* fifo underrun error counter */ + u32 fifo_warn; /* fifo underrun warning counter */ + u32 fifo_threshold; /* fifo underrun threshold */ + u32 transfer_err; /* transfer error counter */ struct fps_info plane_fpsi[LTDC_MAX_LAYER]; struct drm_atomic_state *suspend_state; int crc_skip_count; diff --git a/drivers/gpu/drm/tests/.kunitconfig b/drivers/gpu/drm/tests/.kunitconfig new file mode 100644 index 000000000000..6ec04b4c979d --- /dev/null +++ b/drivers/gpu/drm/tests/.kunitconfig @@ -0,0 +1,3 @@ +CONFIG_KUNIT=y +CONFIG_DRM=y +CONFIG_DRM_KUNIT_TEST=y diff --git a/drivers/gpu/drm/tests/Makefile b/drivers/gpu/drm/tests/Makefile new file mode 100644 index 000000000000..2c8273796d9d --- /dev/null +++ b/drivers/gpu/drm/tests/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_DRM_KUNIT_TEST) += drm_format_helper_test.o diff --git a/drivers/gpu/drm/tests/drm_format_helper_test.c b/drivers/gpu/drm/tests/drm_format_helper_test.c new file mode 100644 index 000000000000..98583bf56044 --- /dev/null +++ b/drivers/gpu/drm/tests/drm_format_helper_test.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <kunit/test.h> + +#include <drm/drm_device.h> +#include <drm/drm_file.h> +#include <drm/drm_format_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_mode.h> +#include <drm/drm_print.h> +#include <drm/drm_rect.h> + +#include "../drm_crtc_internal.h" + +#define TEST_BUF_SIZE 50 + +struct xrgb8888_to_rgb332_case { + const char *name; + unsigned int pitch; + unsigned int dst_pitch; + struct drm_rect clip; + const u32 xrgb8888[TEST_BUF_SIZE]; + const u8 expected[4 * TEST_BUF_SIZE]; +}; + +static struct xrgb8888_to_rgb332_case xrgb8888_to_rgb332_cases[] = { + { + .name = "single_pixel_source_buffer", + .pitch = 1 * 4, + .dst_pitch = 0, + .clip = DRM_RECT_INIT(0, 0, 1, 1), + .xrgb8888 = { 0x01FF0000 }, + .expected = { 0xE0 }, + }, + { + .name = "single_pixel_clip_rectangle", + .pitch = 2 * 4, + .dst_pitch = 0, + .clip = DRM_RECT_INIT(1, 1, 1, 1), + .xrgb8888 = { + 0x00000000, 0x00000000, + 0x00000000, 0x10FF0000, + }, + .expected = { 0xE0 }, + }, + { + /* Well known colors: White, black, red, green, blue, magenta, + * yellow and cyan. Different values for the X in XRGB8888 to + * make sure it is ignored. Partial clip area. + */ + .name = "well_known_colors", + .pitch = 4 * 4, + .dst_pitch = 0, + .clip = DRM_RECT_INIT(1, 1, 2, 4), + .xrgb8888 = { + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x11FFFFFF, 0x22000000, 0x00000000, + 0x00000000, 0x33FF0000, 0x4400FF00, 0x00000000, + 0x00000000, 0x550000FF, 0x66FF00FF, 0x00000000, + 0x00000000, 0x77FFFF00, 0x8800FFFF, 0x00000000, + }, + .expected = { + 0xFF, 0x00, + 0xE0, 0x1C, + 0x03, 0xE3, + 0xFC, 0x1F, + }, + }, + { + /* Randomly picked colors. Full buffer within the clip area. */ + .name = "destination_pitch", + .pitch = 3 * 4, + .dst_pitch = 5, + .clip = DRM_RECT_INIT(0, 0, 3, 3), + .xrgb8888 = { + 0xA10E449C, 0xB1114D05, 0xC1A80303, + 0xD16C7073, 0xA20E449C, 0xB2114D05, + 0xC2A80303, 0xD26C7073, 0xA30E449C, + }, + .expected = { + 0x0A, 0x08, 0xA0, 0x00, 0x00, + 0x6D, 0x0A, 0x08, 0x00, 0x00, + 0xA0, 0x6D, 0x0A, 0x00, 0x00, + }, + }, +}; + +/* + * conversion_buf_size - Return the destination buffer size required to convert + * between formats. + * @dst_format: destination buffer pixel format (DRM_FORMAT_*) + * @dst_pitch: Number of bytes between two consecutive scanlines within dst + * @clip: Clip rectangle area to convert + * + * Returns: + * The size of the destination buffer or negative value on error. + */ +static size_t conversion_buf_size(u32 dst_format, unsigned int dst_pitch, + const struct drm_rect *clip) +{ + const struct drm_format_info *dst_fi = drm_format_info(dst_format); + + if (!dst_fi) + return -EINVAL; + + if (!dst_pitch) + dst_pitch = drm_rect_width(clip) * dst_fi->cpp[0]; + + return dst_pitch * drm_rect_height(clip); +} + +static void xrgb8888_to_rgb332_case_desc(struct xrgb8888_to_rgb332_case *t, + char *desc) +{ + strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE); +} + +KUNIT_ARRAY_PARAM(xrgb8888_to_rgb332, xrgb8888_to_rgb332_cases, + xrgb8888_to_rgb332_case_desc); + +static void xrgb8888_to_rgb332_test(struct kunit *test) +{ + const struct xrgb8888_to_rgb332_case *params = test->param_value; + size_t dst_size; + __u8 *dst = NULL; + + struct drm_framebuffer fb = { + .format = drm_format_info(DRM_FORMAT_XRGB8888), + .pitches = { params->pitch, 0, 0 }, + }; + + dst_size = conversion_buf_size(DRM_FORMAT_RGB332, params->dst_pitch, + ¶ms->clip); + KUNIT_ASSERT_GT(test, dst_size, 0); + + dst = kunit_kzalloc(test, dst_size, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dst); + + drm_fb_xrgb8888_to_rgb332(dst, params->dst_pitch, params->xrgb8888, + &fb, ¶ms->clip); + KUNIT_EXPECT_EQ(test, memcmp(dst, params->expected, dst_size), 0); +} + +static struct kunit_case drm_format_helper_test_cases[] = { + KUNIT_CASE_PARAM(xrgb8888_to_rgb332_test, + xrgb8888_to_rgb332_gen_params), + {} +}; + +static struct kunit_suite drm_format_helper_test_suite = { + .name = "drm_format_helper_test", + .test_cases = drm_format_helper_test_cases, +}; + +kunit_test_suite(drm_format_helper_test_suite); + +MODULE_DESCRIPTION("KUnit tests for the drm_format_helper APIs"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("José Expósito <jose.exposito89@gmail.com>"); diff --git a/drivers/gpu/drm/tiny/Kconfig b/drivers/gpu/drm/tiny/Kconfig index 627d637a1e7e..027cd87c3d0d 100644 --- a/drivers/gpu/drm/tiny/Kconfig +++ b/drivers/gpu/drm/tiny/Kconfig @@ -69,6 +69,7 @@ config DRM_PANEL_MIPI_DBI config DRM_SIMPLEDRM tristate "Simple framebuffer driver" depends on DRM && MMU + select APERTURE_HELPERS select DRM_GEM_SHMEM_HELPER select DRM_KMS_HELPER help diff --git a/drivers/gpu/drm/vc4/vc4_crtc.c b/drivers/gpu/drm/vc4/vc4_crtc.c index 0b80d84a92bd..fb95ed1e3515 100644 --- a/drivers/gpu/drm/vc4/vc4_crtc.c +++ b/drivers/gpu/drm/vc4/vc4_crtc.c @@ -317,10 +317,13 @@ static void vc4_crtc_config_pv(struct drm_crtc *crtc, struct drm_encoder *encode struct drm_crtc_state *crtc_state = crtc->state; struct drm_display_mode *mode = &crtc_state->adjusted_mode; bool interlace = mode->flags & DRM_MODE_FLAG_INTERLACE; - u32 pixel_rep = (mode->flags & DRM_MODE_FLAG_DBLCLK) ? 2 : 1; + bool is_hdmi = vc4_encoder->type == VC4_ENCODER_TYPE_HDMI0 || + vc4_encoder->type == VC4_ENCODER_TYPE_HDMI1; + u32 pixel_rep = ((mode->flags & DRM_MODE_FLAG_DBLCLK) && !is_hdmi) ? 2 : 1; bool is_dsi = (vc4_encoder->type == VC4_ENCODER_TYPE_DSI0 || vc4_encoder->type == VC4_ENCODER_TYPE_DSI1); - u32 format = is_dsi ? PV_CONTROL_FORMAT_DSIV_24 : PV_CONTROL_FORMAT_24; + bool is_dsi1 = vc4_encoder->type == VC4_ENCODER_TYPE_DSI1; + u32 format = is_dsi1 ? PV_CONTROL_FORMAT_DSIV_24 : PV_CONTROL_FORMAT_24; u8 ppc = pv_data->pixels_per_clock; bool debug_dump_regs = false; @@ -346,7 +349,8 @@ static void vc4_crtc_config_pv(struct drm_crtc *crtc, struct drm_encoder *encode PV_HORZB_HACTIVE)); CRTC_WRITE(PV_VERTA, - VC4_SET_FIELD(mode->crtc_vtotal - mode->crtc_vsync_end, + VC4_SET_FIELD(mode->crtc_vtotal - mode->crtc_vsync_end + + interlace, PV_VERTA_VBP) | VC4_SET_FIELD(mode->crtc_vsync_end - mode->crtc_vsync_start, PV_VERTA_VSYNC)); @@ -358,7 +362,7 @@ static void vc4_crtc_config_pv(struct drm_crtc *crtc, struct drm_encoder *encode if (interlace) { CRTC_WRITE(PV_VERTA_EVEN, VC4_SET_FIELD(mode->crtc_vtotal - - mode->crtc_vsync_end - 1, + mode->crtc_vsync_end, PV_VERTA_VBP) | VC4_SET_FIELD(mode->crtc_vsync_end - mode->crtc_vsync_start, @@ -378,7 +382,7 @@ static void vc4_crtc_config_pv(struct drm_crtc *crtc, struct drm_encoder *encode PV_VCONTROL_CONTINUOUS | (is_dsi ? PV_VCONTROL_DSI : 0) | PV_VCONTROL_INTERLACE | - VC4_SET_FIELD(mode->htotal * pixel_rep / 2, + VC4_SET_FIELD(mode->htotal * pixel_rep / (2 * ppc), PV_VCONTROL_ODD_DELAY)); CRTC_WRITE(PV_VSYNCD_EVEN, 0); } else { diff --git a/drivers/gpu/drm/vc4/vc4_dpi.c b/drivers/gpu/drm/vc4/vc4_dpi.c index c180eb60bee8..44355b347ff2 100644 --- a/drivers/gpu/drm/vc4/vc4_dpi.c +++ b/drivers/gpu/drm/vc4/vc4_dpi.c @@ -131,7 +131,7 @@ static void vc4_dpi_encoder_enable(struct drm_encoder *encoder) struct vc4_dpi *dpi = vc4_encoder->dpi; struct drm_connector_list_iter conn_iter; struct drm_connector *connector = NULL, *connector_scan; - u32 dpi_c = DPI_ENABLE | DPI_OUTPUT_ENABLE_MODE; + u32 dpi_c = DPI_ENABLE; int ret; /* Look up the connector attached to DPI so we can get the @@ -148,49 +148,68 @@ static void vc4_dpi_encoder_enable(struct drm_encoder *encoder) } drm_connector_list_iter_end(&conn_iter); - if (connector && connector->display_info.num_bus_formats) { - u32 bus_format = connector->display_info.bus_formats[0]; - - switch (bus_format) { - case MEDIA_BUS_FMT_RGB888_1X24: - dpi_c |= VC4_SET_FIELD(DPI_FORMAT_24BIT_888_RGB, - DPI_FORMAT); - break; - case MEDIA_BUS_FMT_BGR888_1X24: - dpi_c |= VC4_SET_FIELD(DPI_FORMAT_24BIT_888_RGB, - DPI_FORMAT); - dpi_c |= VC4_SET_FIELD(DPI_ORDER_BGR, DPI_ORDER); - break; - case MEDIA_BUS_FMT_RGB666_1X24_CPADHI: - dpi_c |= VC4_SET_FIELD(DPI_FORMAT_18BIT_666_RGB_2, - DPI_FORMAT); - break; - case MEDIA_BUS_FMT_RGB666_1X18: - dpi_c |= VC4_SET_FIELD(DPI_FORMAT_18BIT_666_RGB_1, - DPI_FORMAT); - break; - case MEDIA_BUS_FMT_RGB565_1X16: - dpi_c |= VC4_SET_FIELD(DPI_FORMAT_16BIT_565_RGB_3, - DPI_FORMAT); - break; - default: - DRM_ERROR("Unknown media bus format %d\n", bus_format); - break; + /* Default to 24bit if no connector or format found. */ + dpi_c |= VC4_SET_FIELD(DPI_FORMAT_24BIT_888_RGB, DPI_FORMAT); + + if (connector) { + if (connector->display_info.num_bus_formats) { + u32 bus_format = connector->display_info.bus_formats[0]; + + dpi_c &= ~DPI_FORMAT_MASK; + + switch (bus_format) { + case MEDIA_BUS_FMT_RGB888_1X24: + dpi_c |= VC4_SET_FIELD(DPI_FORMAT_24BIT_888_RGB, + DPI_FORMAT); + break; + case MEDIA_BUS_FMT_BGR888_1X24: + dpi_c |= VC4_SET_FIELD(DPI_FORMAT_24BIT_888_RGB, + DPI_FORMAT); + dpi_c |= VC4_SET_FIELD(DPI_ORDER_BGR, + DPI_ORDER); + break; + case MEDIA_BUS_FMT_RGB666_1X24_CPADHI: + dpi_c |= VC4_SET_FIELD(DPI_FORMAT_18BIT_666_RGB_2, + DPI_FORMAT); + break; + case MEDIA_BUS_FMT_RGB666_1X18: + dpi_c |= VC4_SET_FIELD(DPI_FORMAT_18BIT_666_RGB_1, + DPI_FORMAT); + break; + case MEDIA_BUS_FMT_RGB565_1X16: + dpi_c |= VC4_SET_FIELD(DPI_FORMAT_16BIT_565_RGB_3, + DPI_FORMAT); + break; + default: + DRM_ERROR("Unknown media bus format %d\n", + bus_format); + break; + } } - } else { - /* Default to 24bit if no connector found. */ - dpi_c |= VC4_SET_FIELD(DPI_FORMAT_24BIT_888_RGB, DPI_FORMAT); + + if (connector->display_info.bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE) + dpi_c |= DPI_PIXEL_CLK_INVERT; + + if (connector->display_info.bus_flags & DRM_BUS_FLAG_DE_LOW) + dpi_c |= DPI_OUTPUT_ENABLE_INVERT; } - if (mode->flags & DRM_MODE_FLAG_NHSYNC) - dpi_c |= DPI_HSYNC_INVERT; - else if (!(mode->flags & DRM_MODE_FLAG_PHSYNC)) - dpi_c |= DPI_HSYNC_DISABLE; + if (mode->flags & DRM_MODE_FLAG_CSYNC) { + if (mode->flags & DRM_MODE_FLAG_NCSYNC) + dpi_c |= DPI_OUTPUT_ENABLE_INVERT; + } else { + dpi_c |= DPI_OUTPUT_ENABLE_MODE; + + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + dpi_c |= DPI_HSYNC_INVERT; + else if (!(mode->flags & DRM_MODE_FLAG_PHSYNC)) + dpi_c |= DPI_HSYNC_DISABLE; - if (mode->flags & DRM_MODE_FLAG_NVSYNC) - dpi_c |= DPI_VSYNC_INVERT; - else if (!(mode->flags & DRM_MODE_FLAG_PVSYNC)) - dpi_c |= DPI_VSYNC_DISABLE; + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + dpi_c |= DPI_VSYNC_INVERT; + else if (!(mode->flags & DRM_MODE_FLAG_PVSYNC)) + dpi_c |= DPI_VSYNC_DISABLE; + } DPI_WRITE(DPI_C, dpi_c); diff --git a/drivers/gpu/drm/vc4/vc4_drv.c b/drivers/gpu/drm/vc4/vc4_drv.c index 162bc18e7497..14a7d529144d 100644 --- a/drivers/gpu/drm/vc4/vc4_drv.c +++ b/drivers/gpu/drm/vc4/vc4_drv.c @@ -209,6 +209,15 @@ static void vc4_match_add_drivers(struct device *dev, } } +const struct of_device_id vc4_dma_range_matches[] = { + { .compatible = "brcm,bcm2711-hvs" }, + { .compatible = "brcm,bcm2835-hvs" }, + { .compatible = "brcm,bcm2835-v3d" }, + { .compatible = "brcm,cygnus-v3d" }, + { .compatible = "brcm,vc4-v3d" }, + {} +}; + static int vc4_drm_bind(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); @@ -227,6 +236,16 @@ static int vc4_drm_bind(struct device *dev) vc4_drm_driver.driver_features &= ~DRIVER_RENDER; of_node_put(node); + node = of_find_matching_node_and_match(NULL, vc4_dma_range_matches, + NULL); + if (node) { + ret = of_dma_configure(dev, node, true); + of_node_put(node); + + if (ret) + return ret; + } + vc4 = devm_drm_dev_alloc(dev, &vc4_drm_driver, struct vc4_dev, base); if (IS_ERR(vc4)) return PTR_ERR(vc4); diff --git a/drivers/gpu/drm/vc4/vc4_dsi.c b/drivers/gpu/drm/vc4/vc4_dsi.c index 98308a17e4ed..b7b2c76770dc 100644 --- a/drivers/gpu/drm/vc4/vc4_dsi.c +++ b/drivers/gpu/drm/vc4/vc4_dsi.c @@ -181,8 +181,50 @@ #define DSI0_TXPKT_PIX_FIFO 0x20 /* AKA PIX_FIFO */ -#define DSI0_INT_STAT 0x24 -#define DSI0_INT_EN 0x28 +#define DSI0_INT_STAT 0x24 +#define DSI0_INT_EN 0x28 +# define DSI0_INT_FIFO_ERR BIT(25) +# define DSI0_INT_CMDC_DONE_MASK VC4_MASK(24, 23) +# define DSI0_INT_CMDC_DONE_SHIFT 23 +# define DSI0_INT_CMDC_DONE_NO_REPEAT 1 +# define DSI0_INT_CMDC_DONE_REPEAT 3 +# define DSI0_INT_PHY_DIR_RTF BIT(22) +# define DSI0_INT_PHY_D1_ULPS BIT(21) +# define DSI0_INT_PHY_D1_STOP BIT(20) +# define DSI0_INT_PHY_RXLPDT BIT(19) +# define DSI0_INT_PHY_RXTRIG BIT(18) +# define DSI0_INT_PHY_D0_ULPS BIT(17) +# define DSI0_INT_PHY_D0_LPDT BIT(16) +# define DSI0_INT_PHY_D0_FTR BIT(15) +# define DSI0_INT_PHY_D0_STOP BIT(14) +/* Signaled when the clock lane enters the given state. */ +# define DSI0_INT_PHY_CLK_ULPS BIT(13) +# define DSI0_INT_PHY_CLK_HS BIT(12) +# define DSI0_INT_PHY_CLK_FTR BIT(11) +/* Signaled on timeouts */ +# define DSI0_INT_PR_TO BIT(10) +# define DSI0_INT_TA_TO BIT(9) +# define DSI0_INT_LPRX_TO BIT(8) +# define DSI0_INT_HSTX_TO BIT(7) +/* Contention on a line when trying to drive the line low */ +# define DSI0_INT_ERR_CONT_LP1 BIT(6) +# define DSI0_INT_ERR_CONT_LP0 BIT(5) +/* Control error: incorrect line state sequence on data lane 0. */ +# define DSI0_INT_ERR_CONTROL BIT(4) +# define DSI0_INT_ERR_SYNC_ESC BIT(3) +# define DSI0_INT_RX2_PKT BIT(2) +# define DSI0_INT_RX1_PKT BIT(1) +# define DSI0_INT_CMD_PKT BIT(0) + +#define DSI0_INTERRUPTS_ALWAYS_ENABLED (DSI0_INT_ERR_SYNC_ESC | \ + DSI0_INT_ERR_CONTROL | \ + DSI0_INT_ERR_CONT_LP0 | \ + DSI0_INT_ERR_CONT_LP1 | \ + DSI0_INT_HSTX_TO | \ + DSI0_INT_LPRX_TO | \ + DSI0_INT_TA_TO | \ + DSI0_INT_PR_TO) + # define DSI1_INT_PHY_D3_ULPS BIT(30) # define DSI1_INT_PHY_D3_STOP BIT(29) # define DSI1_INT_PHY_D2_ULPS BIT(28) @@ -761,6 +803,9 @@ static void vc4_dsi_encoder_disable(struct drm_encoder *encoder) list_for_each_entry_reverse(iter, &dsi->bridge_chain, chain_node) { if (iter->funcs->disable) iter->funcs->disable(iter); + + if (iter == dsi->bridge) + break; } vc4_dsi_ulps(dsi, true); @@ -805,11 +850,9 @@ static bool vc4_dsi_encoder_mode_fixup(struct drm_encoder *encoder, /* Find what divider gets us a faster clock than the requested * pixel clock. */ - for (divider = 1; divider < 8; divider++) { - if (parent_rate / divider < pll_clock) { - divider--; + for (divider = 1; divider < 255; divider++) { + if (parent_rate / (divider + 1) < pll_clock) break; - } } /* Now that we've picked a PLL divider, calculate back to its @@ -894,6 +937,9 @@ static void vc4_dsi_encoder_enable(struct drm_encoder *encoder) DSI_PORT_WRITE(PHY_AFEC0, afec0); + /* AFEC reset hold time */ + mdelay(1); + DSI_PORT_WRITE(PHY_AFEC1, VC4_SET_FIELD(6, DSI0_PHY_AFEC1_IDR_DLANE1) | VC4_SET_FIELD(6, DSI0_PHY_AFEC1_IDR_DLANE0) | @@ -1060,12 +1106,9 @@ static void vc4_dsi_encoder_enable(struct drm_encoder *encoder) DSI_PORT_WRITE(CTRL, DSI_PORT_READ(CTRL) | DSI1_CTRL_EN); /* Bring AFE out of reset. */ - if (dsi->variant->port == 0) { - } else { - DSI_PORT_WRITE(PHY_AFEC0, - DSI_PORT_READ(PHY_AFEC0) & - ~DSI1_PHY_AFEC0_RESET); - } + DSI_PORT_WRITE(PHY_AFEC0, + DSI_PORT_READ(PHY_AFEC0) & + ~DSI_PORT_BIT(PHY_AFEC0_RESET)); vc4_dsi_ulps(dsi, false); @@ -1184,13 +1227,28 @@ static ssize_t vc4_dsi_host_transfer(struct mipi_dsi_host *host, /* Enable the appropriate interrupt for the transfer completion. */ dsi->xfer_result = 0; reinit_completion(&dsi->xfer_completion); - DSI_PORT_WRITE(INT_STAT, DSI1_INT_TXPKT1_DONE | DSI1_INT_PHY_DIR_RTF); - if (msg->rx_len) { - DSI_PORT_WRITE(INT_EN, (DSI1_INTERRUPTS_ALWAYS_ENABLED | - DSI1_INT_PHY_DIR_RTF)); + if (dsi->variant->port == 0) { + DSI_PORT_WRITE(INT_STAT, + DSI0_INT_CMDC_DONE_MASK | DSI1_INT_PHY_DIR_RTF); + if (msg->rx_len) { + DSI_PORT_WRITE(INT_EN, (DSI0_INTERRUPTS_ALWAYS_ENABLED | + DSI0_INT_PHY_DIR_RTF)); + } else { + DSI_PORT_WRITE(INT_EN, + (DSI0_INTERRUPTS_ALWAYS_ENABLED | + VC4_SET_FIELD(DSI0_INT_CMDC_DONE_NO_REPEAT, + DSI0_INT_CMDC_DONE))); + } } else { - DSI_PORT_WRITE(INT_EN, (DSI1_INTERRUPTS_ALWAYS_ENABLED | - DSI1_INT_TXPKT1_DONE)); + DSI_PORT_WRITE(INT_STAT, + DSI1_INT_TXPKT1_DONE | DSI1_INT_PHY_DIR_RTF); + if (msg->rx_len) { + DSI_PORT_WRITE(INT_EN, (DSI1_INTERRUPTS_ALWAYS_ENABLED | + DSI1_INT_PHY_DIR_RTF)); + } else { + DSI_PORT_WRITE(INT_EN, (DSI1_INTERRUPTS_ALWAYS_ENABLED | + DSI1_INT_TXPKT1_DONE)); + } } /* Send the packet. */ @@ -1207,7 +1265,7 @@ static ssize_t vc4_dsi_host_transfer(struct mipi_dsi_host *host, ret = dsi->xfer_result; } - DSI_PORT_WRITE(INT_EN, DSI1_INTERRUPTS_ALWAYS_ENABLED); + DSI_PORT_WRITE(INT_EN, DSI_PORT_BIT(INTERRUPTS_ALWAYS_ENABLED)); if (ret) goto reset_fifo_and_return; @@ -1253,7 +1311,7 @@ reset_fifo_and_return: DSI_PORT_BIT(CTRL_RESET_FIFOS)); DSI_PORT_WRITE(TXPKT1C, 0); - DSI_PORT_WRITE(INT_EN, DSI1_INTERRUPTS_ALWAYS_ENABLED); + DSI_PORT_WRITE(INT_EN, DSI_PORT_BIT(INTERRUPTS_ALWAYS_ENABLED)); return ret; } @@ -1390,26 +1448,28 @@ static irqreturn_t vc4_dsi_irq_handler(int irq, void *data) DSI_PORT_WRITE(INT_STAT, stat); dsi_handle_error(dsi, &ret, stat, - DSI1_INT_ERR_SYNC_ESC, "LPDT sync"); + DSI_PORT_BIT(INT_ERR_SYNC_ESC), "LPDT sync"); dsi_handle_error(dsi, &ret, stat, - DSI1_INT_ERR_CONTROL, "data lane 0 sequence"); + DSI_PORT_BIT(INT_ERR_CONTROL), "data lane 0 sequence"); dsi_handle_error(dsi, &ret, stat, - DSI1_INT_ERR_CONT_LP0, "LP0 contention"); + DSI_PORT_BIT(INT_ERR_CONT_LP0), "LP0 contention"); dsi_handle_error(dsi, &ret, stat, - DSI1_INT_ERR_CONT_LP1, "LP1 contention"); + DSI_PORT_BIT(INT_ERR_CONT_LP1), "LP1 contention"); dsi_handle_error(dsi, &ret, stat, - DSI1_INT_HSTX_TO, "HSTX timeout"); + DSI_PORT_BIT(INT_HSTX_TO), "HSTX timeout"); dsi_handle_error(dsi, &ret, stat, - DSI1_INT_LPRX_TO, "LPRX timeout"); + DSI_PORT_BIT(INT_LPRX_TO), "LPRX timeout"); dsi_handle_error(dsi, &ret, stat, - DSI1_INT_TA_TO, "turnaround timeout"); + DSI_PORT_BIT(INT_TA_TO), "turnaround timeout"); dsi_handle_error(dsi, &ret, stat, - DSI1_INT_PR_TO, "peripheral reset timeout"); + DSI_PORT_BIT(INT_PR_TO), "peripheral reset timeout"); - if (stat & (DSI1_INT_TXPKT1_DONE | DSI1_INT_PHY_DIR_RTF)) { + if (stat & ((dsi->variant->port ? DSI1_INT_TXPKT1_DONE : + DSI0_INT_CMDC_DONE_MASK) | + DSI_PORT_BIT(INT_PHY_DIR_RTF))) { complete(&dsi->xfer_completion); ret = IRQ_HANDLED; - } else if (stat & DSI1_INT_HSTX_TO) { + } else if (stat & DSI_PORT_BIT(INT_HSTX_TO)) { complete(&dsi->xfer_completion); dsi->xfer_result = -ETIMEDOUT; ret = IRQ_HANDLED; @@ -1487,13 +1547,29 @@ vc4_dsi_init_phy_clocks(struct vc4_dsi *dsi) dsi->clk_onecell); } +static void vc4_dsi_dma_mem_release(void *ptr) +{ + struct vc4_dsi *dsi = ptr; + struct device *dev = &dsi->pdev->dev; + + dma_free_coherent(dev, 4, dsi->reg_dma_mem, dsi->reg_dma_paddr); + dsi->reg_dma_mem = NULL; +} + +static void vc4_dsi_dma_chan_release(void *ptr) +{ + struct vc4_dsi *dsi = ptr; + + dma_release_channel(dsi->reg_dma_chan); + dsi->reg_dma_chan = NULL; +} + static int vc4_dsi_bind(struct device *dev, struct device *master, void *data) { struct platform_device *pdev = to_platform_device(dev); struct drm_device *drm = dev_get_drvdata(master); struct vc4_dsi *dsi = dev_get_drvdata(dev); struct vc4_dsi_encoder *vc4_dsi_encoder; - dma_cap_mask_t dma_mask; int ret; dsi->variant = of_device_get_match_data(dev); @@ -1504,7 +1580,8 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data) return -ENOMEM; INIT_LIST_HEAD(&dsi->bridge_chain); - vc4_dsi_encoder->base.type = VC4_ENCODER_TYPE_DSI1; + vc4_dsi_encoder->base.type = dsi->variant->port ? + VC4_ENCODER_TYPE_DSI1 : VC4_ENCODER_TYPE_DSI0; vc4_dsi_encoder->dsi = dsi; dsi->encoder = &vc4_dsi_encoder->base.base; @@ -1527,6 +1604,8 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data) * so set up a channel for talking to it. */ if (dsi->variant->broken_axi_workaround) { + dma_cap_mask_t dma_mask; + dsi->reg_dma_mem = dma_alloc_coherent(dev, 4, &dsi->reg_dma_paddr, GFP_KERNEL); @@ -1535,8 +1614,13 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data) return -ENOMEM; } + ret = devm_add_action_or_reset(dev, vc4_dsi_dma_mem_release, dsi); + if (ret) + return ret; + dma_cap_zero(dma_mask); dma_cap_set(DMA_MEMCPY, dma_mask); + dsi->reg_dma_chan = dma_request_chan_by_mask(&dma_mask); if (IS_ERR(dsi->reg_dma_chan)) { ret = PTR_ERR(dsi->reg_dma_chan); @@ -1546,6 +1630,10 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data) return ret; } + ret = devm_add_action_or_reset(dev, vc4_dsi_dma_chan_release, dsi); + if (ret) + return ret; + /* Get the physical address of the device's registers. The * struct resource for the regs gives us the bus address * instead. diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c index 823d812f4982..e078f4ce0b4b 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.c +++ b/drivers/gpu/drm/vc4/vc4_hdmi.c @@ -79,6 +79,11 @@ #define VC5_HDMI_VERTB_VSPO_SHIFT 16 #define VC5_HDMI_VERTB_VSPO_MASK VC4_MASK(29, 16) +#define VC4_HDMI_MISC_CONTROL_PIXEL_REP_SHIFT 0 +#define VC4_HDMI_MISC_CONTROL_PIXEL_REP_MASK VC4_MASK(3, 0) +#define VC5_HDMI_MISC_CONTROL_PIXEL_REP_SHIFT 0 +#define VC5_HDMI_MISC_CONTROL_PIXEL_REP_MASK VC4_MASK(3, 0) + #define VC5_HDMI_SCRAMBLER_CTL_ENABLE BIT(0) #define VC5_HDMI_DEEP_COLOR_CONFIG_1_INIT_PACK_PHASE_SHIFT 8 @@ -145,6 +150,12 @@ static int vc4_hdmi_debugfs_regs(struct seq_file *m, void *unused) drm_print_regset32(&p, &vc4_hdmi->hdmi_regset); drm_print_regset32(&p, &vc4_hdmi->hd_regset); + drm_print_regset32(&p, &vc4_hdmi->cec_regset); + drm_print_regset32(&p, &vc4_hdmi->csc_regset); + drm_print_regset32(&p, &vc4_hdmi->dvp_regset); + drm_print_regset32(&p, &vc4_hdmi->phy_regset); + drm_print_regset32(&p, &vc4_hdmi->ram_regset); + drm_print_regset32(&p, &vc4_hdmi->rm_regset); return 0; } @@ -418,6 +429,7 @@ static int vc4_hdmi_connector_init(struct drm_device *dev, connector->interlace_allowed = 1; connector->doublescan_allowed = 0; + connector->stereo_allowed = 1; if (vc4_hdmi->variant->supports_hdr) drm_connector_attach_hdr_output_metadata_property(connector); @@ -455,9 +467,11 @@ static void vc4_hdmi_write_infoframe(struct drm_encoder *encoder, const struct vc4_hdmi_register *ram_packet_start = &vc4_hdmi->variant->registers[HDMI_RAM_PACKET_START]; u32 packet_reg = ram_packet_start->offset + VC4_HDMI_PACKET_STRIDE * packet_id; + u32 packet_reg_next = ram_packet_start->offset + + VC4_HDMI_PACKET_STRIDE * (packet_id + 1); void __iomem *base = __vc4_hdmi_get_field_base(vc4_hdmi, ram_packet_start->reg); - uint8_t buffer[VC4_HDMI_PACKET_STRIDE]; + uint8_t buffer[VC4_HDMI_PACKET_STRIDE] = {}; unsigned long flags; ssize_t len, i; int ret; @@ -493,6 +507,13 @@ static void vc4_hdmi_write_infoframe(struct drm_encoder *encoder, packet_reg += 4; } + /* + * clear remainder of packet ram as it's included in the + * infoframe and triggers a checksum error on hdmi analyser + */ + for (; packet_reg < packet_reg_next; packet_reg += 4) + writel(0, base + packet_reg); + HDMI_WRITE(HDMI_RAM_PACKET_CONFIG, HDMI_READ(HDMI_RAM_PACKET_CONFIG) | BIT(packet_id)); @@ -584,7 +605,9 @@ static void vc4_hdmi_set_audio_infoframe(struct drm_encoder *encoder) union hdmi_infoframe frame; memcpy(&frame.audio, audio, sizeof(*audio)); - vc4_hdmi_write_infoframe(encoder, &frame); + + if (vc4_hdmi->packet_ram_enabled) + vc4_hdmi_write_infoframe(encoder, &frame); } static void vc4_hdmi_set_hdr_infoframe(struct drm_encoder *encoder) @@ -724,6 +747,8 @@ static void vc4_hdmi_encoder_post_crtc_disable(struct drm_encoder *encoder, mutex_lock(&vc4_hdmi->mutex); + vc4_hdmi->packet_ram_enabled = false; + spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); HDMI_WRITE(HDMI_RAM_PACKET_CONFIG, 0); @@ -771,15 +796,6 @@ static void vc4_hdmi_encoder_post_crtc_powerdown(struct drm_encoder *encoder, mutex_unlock(&vc4_hdmi->mutex); } -static void vc4_hdmi_encoder_disable(struct drm_encoder *encoder) -{ - struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); - - mutex_lock(&vc4_hdmi->mutex); - vc4_hdmi->output_enabled = false; - mutex_unlock(&vc4_hdmi->mutex); -} - static void vc4_hdmi_csc_setup(struct vc4_hdmi *vc4_hdmi, struct drm_connector_state *state, const struct drm_display_mode *mode) @@ -970,14 +986,15 @@ static void vc4_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi, VC4_HDMI_VERTA_VFP) | VC4_SET_FIELD(mode->crtc_vdisplay, VC4_HDMI_VERTA_VAL)); u32 vertb = (VC4_SET_FIELD(0, VC4_HDMI_VERTB_VSPO) | - VC4_SET_FIELD(mode->crtc_vtotal - mode->crtc_vsync_end, + VC4_SET_FIELD(mode->crtc_vtotal - mode->crtc_vsync_end + + interlaced, VC4_HDMI_VERTB_VBP)); u32 vertb_even = (VC4_SET_FIELD(0, VC4_HDMI_VERTB_VSPO) | VC4_SET_FIELD(mode->crtc_vtotal - - mode->crtc_vsync_end - - interlaced, + mode->crtc_vsync_end, VC4_HDMI_VERTB_VBP)); unsigned long flags; + u32 reg; spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); @@ -1004,6 +1021,11 @@ static void vc4_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi, HDMI_WRITE(HDMI_VERTB0, vertb_even); HDMI_WRITE(HDMI_VERTB1, vertb); + reg = HDMI_READ(HDMI_MISC_CONTROL); + reg &= ~VC4_HDMI_MISC_CONTROL_PIXEL_REP_MASK; + reg |= VC4_SET_FIELD(pixel_rep - 1, VC4_HDMI_MISC_CONTROL_PIXEL_REP); + HDMI_WRITE(HDMI_MISC_CONTROL, reg); + spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); } @@ -1022,13 +1044,13 @@ static void vc5_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi, VC4_SET_FIELD(mode->crtc_vsync_start - mode->crtc_vdisplay, VC5_HDMI_VERTA_VFP) | VC4_SET_FIELD(mode->crtc_vdisplay, VC5_HDMI_VERTA_VAL)); - u32 vertb = (VC4_SET_FIELD(0, VC5_HDMI_VERTB_VSPO) | + u32 vertb = (VC4_SET_FIELD(mode->htotal >> (2 - pixel_rep), + VC5_HDMI_VERTB_VSPO) | VC4_SET_FIELD(mode->crtc_vtotal - mode->crtc_vsync_end, VC4_HDMI_VERTB_VBP)); u32 vertb_even = (VC4_SET_FIELD(0, VC5_HDMI_VERTB_VSPO) | VC4_SET_FIELD(mode->crtc_vtotal - - mode->crtc_vsync_end - - interlaced, + mode->crtc_vsync_end - interlaced, VC4_HDMI_VERTB_VBP)); unsigned long flags; unsigned char gcp; @@ -1102,6 +1124,11 @@ static void vc5_hdmi_set_timings(struct vc4_hdmi *vc4_hdmi, reg |= gcp_en ? VC5_HDMI_GCP_CONFIG_GCP_ENABLE : 0; HDMI_WRITE(HDMI_GCP_CONFIG, reg); + reg = HDMI_READ(HDMI_MISC_CONTROL); + reg &= ~VC5_HDMI_MISC_CONTROL_PIXEL_REP_MASK; + reg |= VC4_SET_FIELD(pixel_rep - 1, VC5_HDMI_MISC_CONTROL_PIXEL_REP); + HDMI_WRITE(HDMI_MISC_CONTROL, reg); + HDMI_WRITE(HDMI_CLOCK_STOP, 0); spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); @@ -1330,14 +1357,12 @@ static void vc4_hdmi_encoder_post_crtc_enable(struct drm_encoder *encoder, WARN_ON(!(HDMI_READ(HDMI_SCHEDULER_CONTROL) & VC4_HDMI_SCHEDULER_CONTROL_HDMI_ACTIVE)); - HDMI_WRITE(HDMI_SCHEDULER_CONTROL, - HDMI_READ(HDMI_SCHEDULER_CONTROL) | - VC4_HDMI_SCHEDULER_CONTROL_VERT_ALWAYS_KEEPOUT); HDMI_WRITE(HDMI_RAM_PACKET_CONFIG, VC4_HDMI_RAM_PACKET_ENABLE); spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); + vc4_hdmi->packet_ram_enabled = true; vc4_hdmi_set_infoframes(encoder); } @@ -1348,15 +1373,6 @@ static void vc4_hdmi_encoder_post_crtc_enable(struct drm_encoder *encoder, mutex_unlock(&vc4_hdmi->mutex); } -static void vc4_hdmi_encoder_enable(struct drm_encoder *encoder) -{ - struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); - - mutex_lock(&vc4_hdmi->mutex); - vc4_hdmi->output_enabled = true; - mutex_unlock(&vc4_hdmi->mutex); -} - static void vc4_hdmi_encoder_atomic_mode_set(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state) @@ -1597,18 +1613,37 @@ static int vc4_hdmi_encoder_atomic_check(struct drm_encoder *encoder, struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state) { + struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); + struct drm_connector *connector = &vc4_hdmi->connector; + struct drm_connector_state *old_conn_state = + drm_atomic_get_old_connector_state(conn_state->state, connector); + struct vc4_hdmi_connector_state *old_vc4_state = + conn_state_to_vc4_hdmi_conn_state(old_conn_state); struct vc4_hdmi_connector_state *vc4_state = conn_state_to_vc4_hdmi_conn_state(conn_state); struct drm_display_mode *mode = &crtc_state->adjusted_mode; - struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder); unsigned long long tmds_char_rate = mode->clock * 1000; unsigned long long tmds_bit_rate; int ret; - if (vc4_hdmi->variant->unsupported_odd_h_timings && - !(mode->flags & DRM_MODE_FLAG_DBLCLK) && - ((mode->hdisplay % 2) || (mode->hsync_start % 2) || - (mode->hsync_end % 2) || (mode->htotal % 2))) - return -EINVAL; + if (vc4_hdmi->variant->unsupported_odd_h_timings) { + if (mode->flags & DRM_MODE_FLAG_DBLCLK) { + /* Only try to fixup DBLCLK modes to get 480i and 576i + * working. + * A generic solution for all modes with odd horizontal + * timing values seems impossible based on trying to + * solve it for 1366x768 monitors. + */ + if ((mode->hsync_start - mode->hdisplay) & 1) + mode->hsync_start--; + if ((mode->hsync_end - mode->hsync_start) & 1) + mode->hsync_end--; + } + + /* Now check whether we still have odd values remaining */ + if ((mode->hdisplay % 2) || (mode->hsync_start % 2) || + (mode->hsync_end % 2) || (mode->htotal % 2)) + return -EINVAL; + } /* * The 1440p@60 pixel rate is in the same range than the first @@ -1628,6 +1663,11 @@ static int vc4_hdmi_encoder_atomic_check(struct drm_encoder *encoder, if (ret) return ret; + /* vc4_hdmi_encoder_compute_config may have changed output_bpc and/or output_format */ + if (vc4_state->output_bpc != old_vc4_state->output_bpc || + vc4_state->output_format != old_vc4_state->output_format) + crtc_state->mode_changed = true; + return 0; } @@ -1650,8 +1690,6 @@ static const struct drm_encoder_helper_funcs vc4_hdmi_encoder_helper_funcs = { .atomic_check = vc4_hdmi_encoder_atomic_check, .atomic_mode_set = vc4_hdmi_encoder_atomic_mode_set, .mode_valid = vc4_hdmi_encoder_mode_valid, - .disable = vc4_hdmi_encoder_disable, - .enable = vc4_hdmi_encoder_enable, }; static u32 vc4_hdmi_channel_map(struct vc4_hdmi *vc4_hdmi, u32 channel_mask) @@ -1748,19 +1786,15 @@ static inline struct vc4_hdmi *dai_to_hdmi(struct snd_soc_dai *dai) static bool vc4_hdmi_audio_can_stream(struct vc4_hdmi *vc4_hdmi) { - lockdep_assert_held(&vc4_hdmi->mutex); + struct drm_display_info *display = &vc4_hdmi->connector.display_info; - /* - * If the controller is disabled, prevent any ALSA output. - */ - if (!vc4_hdmi->output_enabled) - return false; + lockdep_assert_held(&vc4_hdmi->mutex); /* * If the encoder is currently in DVI mode, treat the codec DAI * as missing. */ - if (!(HDMI_READ(HDMI_RAM_PACKET_CONFIG) & VC4_HDMI_RAM_PACKET_ENABLE)) + if (!display->is_hdmi) return false; return true; @@ -1941,10 +1975,10 @@ static int vc4_hdmi_audio_prepare(struct device *dev, void *data, /* Set the MAI threshold */ HDMI_WRITE(HDMI_MAI_THR, - VC4_SET_FIELD(0x10, VC4_HD_MAI_THR_PANICHIGH) | - VC4_SET_FIELD(0x10, VC4_HD_MAI_THR_PANICLOW) | - VC4_SET_FIELD(0x10, VC4_HD_MAI_THR_DREQHIGH) | - VC4_SET_FIELD(0x10, VC4_HD_MAI_THR_DREQLOW)); + VC4_SET_FIELD(0x08, VC4_HD_MAI_THR_PANICHIGH) | + VC4_SET_FIELD(0x08, VC4_HD_MAI_THR_PANICLOW) | + VC4_SET_FIELD(0x06, VC4_HD_MAI_THR_DREQHIGH) | + VC4_SET_FIELD(0x08, VC4_HD_MAI_THR_DREQLOW)); HDMI_WRITE(HDMI_MAI_CONFIG, VC4_HDMI_MAI_CONFIG_BIT_REVERSE | @@ -2035,12 +2069,12 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi) struct device *dev = &vc4_hdmi->pdev->dev; struct platform_device *codec_pdev; const __be32 *addr; - int index; + int index, len; int ret; - if (!of_find_property(dev->of_node, "dmas", NULL)) { + if (!of_find_property(dev->of_node, "dmas", &len) || !len) { dev_warn(dev, - "'dmas' DT property is missing, no HDMI audio\n"); + "'dmas' DT property is missing or empty, no HDMI audio\n"); return 0; } @@ -2521,8 +2555,6 @@ static int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi) struct cec_connector_info conn_info; struct platform_device *pdev = vc4_hdmi->pdev; struct device *dev = &pdev->dev; - unsigned long flags; - u32 value; int ret; if (!of_find_property(dev->of_node, "interrupts", NULL)) { @@ -2541,15 +2573,6 @@ static int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi) cec_fill_conn_info_from_drm(&conn_info, &vc4_hdmi->connector); cec_s_conn_info(vc4_hdmi->cec_adap, &conn_info); - spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); - value = HDMI_READ(HDMI_CEC_CNTRL_1); - /* Set the logical address to Unregistered */ - value |= VC4_HDMI_CEC_ADDR_MASK; - HDMI_WRITE(HDMI_CEC_CNTRL_1, value); - spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); - - vc4_hdmi_cec_update_clk_div(vc4_hdmi); - if (vc4_hdmi->variant->external_irq_controller) { ret = request_threaded_irq(platform_get_irq_byname(pdev, "cec-rx"), vc4_cec_irq_handler_rx_bare, @@ -2565,10 +2588,6 @@ static int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi) if (ret) goto err_remove_cec_rx_handler; } else { - spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); - HDMI_WRITE(HDMI_CEC_CPU_MASK_SET, 0xffffffff); - spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); - ret = request_threaded_irq(platform_get_irq(pdev, 0), vc4_cec_irq_handler, vc4_cec_irq_handler_thread, 0, @@ -2619,7 +2638,6 @@ static int vc4_hdmi_cec_init(struct vc4_hdmi *vc4_hdmi) } static void vc4_hdmi_cec_exit(struct vc4_hdmi *vc4_hdmi) {}; - #endif static int vc4_hdmi_build_regset(struct vc4_hdmi *vc4_hdmi, @@ -2704,6 +2722,7 @@ static int vc5_hdmi_init_resources(struct vc4_hdmi *vc4_hdmi) struct platform_device *pdev = vc4_hdmi->pdev; struct device *dev = &pdev->dev; struct resource *res; + int ret; res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hdmi"); if (!res) @@ -2800,6 +2819,38 @@ static int vc5_hdmi_init_resources(struct vc4_hdmi *vc4_hdmi) return PTR_ERR(vc4_hdmi->reset); } + ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->hdmi_regset, VC4_HDMI); + if (ret) + return ret; + + ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->hd_regset, VC4_HD); + if (ret) + return ret; + + ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->cec_regset, VC5_CEC); + if (ret) + return ret; + + ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->csc_regset, VC5_CSC); + if (ret) + return ret; + + ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->dvp_regset, VC5_DVP); + if (ret) + return ret; + + ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->phy_regset, VC5_PHY); + if (ret) + return ret; + + ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->ram_regset, VC5_RAM); + if (ret) + return ret; + + ret = vc4_hdmi_build_regset(vc4_hdmi, &vc4_hdmi->rm_regset, VC5_RM); + if (ret) + return ret; + return 0; } @@ -2815,12 +2866,34 @@ static int __maybe_unused vc4_hdmi_runtime_suspend(struct device *dev) static int vc4_hdmi_runtime_resume(struct device *dev) { struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev); + unsigned long __maybe_unused flags; + u32 __maybe_unused value; int ret; ret = clk_prepare_enable(vc4_hdmi->hsm_clock); if (ret) return ret; + if (vc4_hdmi->variant->reset) + vc4_hdmi->variant->reset(vc4_hdmi); + +#ifdef CONFIG_DRM_VC4_HDMI_CEC + spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); + value = HDMI_READ(HDMI_CEC_CNTRL_1); + /* Set the logical address to Unregistered */ + value |= VC4_HDMI_CEC_ADDR_MASK; + HDMI_WRITE(HDMI_CEC_CNTRL_1, value); + spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); + + vc4_hdmi_cec_update_clk_div(vc4_hdmi); + + if (!vc4_hdmi->variant->external_irq_controller) { + spin_lock_irqsave(&vc4_hdmi->hw_lock, flags); + HDMI_WRITE(HDMI_CEC_CPU_MASK_SET, 0xffffffff); + spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags); + } +#endif + return 0; } @@ -2910,9 +2983,6 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data) pm_runtime_set_active(dev); pm_runtime_enable(dev); - if (vc4_hdmi->variant->reset) - vc4_hdmi->variant->reset(vc4_hdmi); - if ((of_device_is_compatible(dev->of_node, "brcm,bcm2711-hdmi0") || of_device_is_compatible(dev->of_node, "brcm,bcm2711-hdmi1")) && HDMI_READ(HDMI_VID_CTL) & VC4_HD_VID_CTL_ENABLE) { diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.h b/drivers/gpu/drm/vc4/vc4_hdmi.h index 51b27dcdcd9b..c3ed2b07df23 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi.h +++ b/drivers/gpu/drm/vc4/vc4_hdmi.h @@ -179,6 +179,14 @@ struct vc4_hdmi { struct debugfs_regset32 hdmi_regset; struct debugfs_regset32 hd_regset; + /* VC5 only */ + struct debugfs_regset32 cec_regset; + struct debugfs_regset32 csc_regset; + struct debugfs_regset32 dvp_regset; + struct debugfs_regset32 phy_regset; + struct debugfs_regset32 ram_regset; + struct debugfs_regset32 rm_regset; + /** * @hw_lock: Spinlock protecting device register access. */ @@ -205,10 +213,10 @@ struct vc4_hdmi { struct drm_display_mode saved_adjusted_mode; /** - * @output_enabled: Is the HDMI controller currently active? - * Protected by @mutex. + * @packet_ram_enabled: Is the HDMI controller packet RAM currently + * on? Protected by @mutex. */ - bool output_enabled; + bool packet_ram_enabled; /** * @scdc_enabled: Is the HDMI controller currently running with diff --git a/drivers/gpu/drm/vc4/vc4_hdmi_regs.h b/drivers/gpu/drm/vc4/vc4_hdmi_regs.h index a040356b6bdc..48db438550b1 100644 --- a/drivers/gpu/drm/vc4/vc4_hdmi_regs.h +++ b/drivers/gpu/drm/vc4/vc4_hdmi_regs.h @@ -127,6 +127,17 @@ enum vc4_hdmi_field { HDMI_VERTB0, HDMI_VERTB1, HDMI_VID_CTL, + HDMI_MISC_CONTROL, + HDMI_FORMAT_DET_1, + HDMI_FORMAT_DET_2, + HDMI_FORMAT_DET_3, + HDMI_FORMAT_DET_4, + HDMI_FORMAT_DET_5, + HDMI_FORMAT_DET_6, + HDMI_FORMAT_DET_7, + HDMI_FORMAT_DET_8, + HDMI_FORMAT_DET_9, + HDMI_FORMAT_DET_10, }; struct vc4_hdmi_register { @@ -189,6 +200,7 @@ static const struct vc4_hdmi_register __maybe_unused vc4_hdmi_fields[] = { VC4_HDMI_REG(HDMI_VERTB0, 0x00d0), VC4_HDMI_REG(HDMI_VERTA1, 0x00d4), VC4_HDMI_REG(HDMI_VERTB1, 0x00d8), + VC4_HDMI_REG(HDMI_MISC_CONTROL, 0x00e4), VC4_HDMI_REG(HDMI_CEC_CNTRL_1, 0x00e8), VC4_HDMI_REG(HDMI_CEC_CNTRL_2, 0x00ec), VC4_HDMI_REG(HDMI_CEC_CNTRL_3, 0x00f0), @@ -237,8 +249,19 @@ static const struct vc4_hdmi_register __maybe_unused vc5_hdmi_hdmi0_fields[] = { VC4_HDMI_REG(HDMI_VERTB0, 0x0f0), VC4_HDMI_REG(HDMI_VERTA1, 0x0f4), VC4_HDMI_REG(HDMI_VERTB1, 0x0f8), + VC4_HDMI_REG(HDMI_MISC_CONTROL, 0x100), VC4_HDMI_REG(HDMI_MAI_CHANNEL_MAP, 0x09c), VC4_HDMI_REG(HDMI_MAI_CONFIG, 0x0a0), + VC4_HDMI_REG(HDMI_FORMAT_DET_1, 0x134), + VC4_HDMI_REG(HDMI_FORMAT_DET_2, 0x138), + VC4_HDMI_REG(HDMI_FORMAT_DET_3, 0x13c), + VC4_HDMI_REG(HDMI_FORMAT_DET_4, 0x140), + VC4_HDMI_REG(HDMI_FORMAT_DET_5, 0x144), + VC4_HDMI_REG(HDMI_FORMAT_DET_6, 0x148), + VC4_HDMI_REG(HDMI_FORMAT_DET_7, 0x14c), + VC4_HDMI_REG(HDMI_FORMAT_DET_8, 0x150), + VC4_HDMI_REG(HDMI_FORMAT_DET_9, 0x154), + VC4_HDMI_REG(HDMI_FORMAT_DET_10, 0x158), VC4_HDMI_REG(HDMI_DEEP_COLOR_CONFIG_1, 0x170), VC4_HDMI_REG(HDMI_GCP_CONFIG, 0x178), VC4_HDMI_REG(HDMI_GCP_WORD_1, 0x17c), @@ -319,8 +342,19 @@ static const struct vc4_hdmi_register __maybe_unused vc5_hdmi_hdmi1_fields[] = { VC4_HDMI_REG(HDMI_VERTB0, 0x0f0), VC4_HDMI_REG(HDMI_VERTA1, 0x0f4), VC4_HDMI_REG(HDMI_VERTB1, 0x0f8), + VC4_HDMI_REG(HDMI_MISC_CONTROL, 0x100), VC4_HDMI_REG(HDMI_MAI_CHANNEL_MAP, 0x09c), VC4_HDMI_REG(HDMI_MAI_CONFIG, 0x0a0), + VC4_HDMI_REG(HDMI_FORMAT_DET_1, 0x134), + VC4_HDMI_REG(HDMI_FORMAT_DET_2, 0x138), + VC4_HDMI_REG(HDMI_FORMAT_DET_3, 0x13c), + VC4_HDMI_REG(HDMI_FORMAT_DET_4, 0x140), + VC4_HDMI_REG(HDMI_FORMAT_DET_5, 0x144), + VC4_HDMI_REG(HDMI_FORMAT_DET_6, 0x148), + VC4_HDMI_REG(HDMI_FORMAT_DET_7, 0x14c), + VC4_HDMI_REG(HDMI_FORMAT_DET_8, 0x150), + VC4_HDMI_REG(HDMI_FORMAT_DET_9, 0x154), + VC4_HDMI_REG(HDMI_FORMAT_DET_10, 0x158), VC4_HDMI_REG(HDMI_DEEP_COLOR_CONFIG_1, 0x170), VC4_HDMI_REG(HDMI_GCP_CONFIG, 0x178), VC4_HDMI_REG(HDMI_GCP_WORD_1, 0x17c), @@ -420,7 +454,7 @@ static inline u32 vc4_hdmi_read(struct vc4_hdmi *hdmi, const struct vc4_hdmi_variant *variant = hdmi->variant; void __iomem *base; - WARN_ON(!pm_runtime_active(&hdmi->pdev->dev)); + WARN_ON(pm_runtime_status_suspended(&hdmi->pdev->dev)); if (reg >= variant->num_registers) { dev_warn(&hdmi->pdev->dev, @@ -450,7 +484,7 @@ static inline void vc4_hdmi_write(struct vc4_hdmi *hdmi, lockdep_assert_held(&hdmi->hw_lock); - WARN_ON(!pm_runtime_active(&hdmi->pdev->dev)); + WARN_ON(pm_runtime_status_suspended(&hdmi->pdev->dev)); if (reg >= variant->num_registers) { dev_warn(&hdmi->pdev->dev, diff --git a/drivers/gpu/drm/vc4/vc4_hvs.c b/drivers/gpu/drm/vc4/vc4_hvs.c index 2a58fc421cf6..5fdfc01fbc5a 100644 --- a/drivers/gpu/drm/vc4/vc4_hvs.c +++ b/drivers/gpu/drm/vc4/vc4_hvs.c @@ -94,6 +94,46 @@ static int vc4_hvs_debugfs_underrun(struct seq_file *m, void *data) return 0; } +static int vc4_hvs_debugfs_dlist(struct seq_file *m, void *data) +{ + struct drm_info_node *node = m->private; + struct drm_device *dev = node->minor->dev; + struct vc4_dev *vc4 = to_vc4_dev(dev); + struct vc4_hvs *hvs = vc4->hvs; + struct drm_printer p = drm_seq_file_printer(m); + unsigned int next_entry_start = 0; + unsigned int i, j; + u32 dlist_word, dispstat; + + for (i = 0; i < SCALER_CHANNELS_COUNT; i++) { + dispstat = VC4_GET_FIELD(HVS_READ(SCALER_DISPSTATX(i)), + SCALER_DISPSTATX_MODE); + if (dispstat == SCALER_DISPSTATX_MODE_DISABLED || + dispstat == SCALER_DISPSTATX_MODE_EOF) { + drm_printf(&p, "HVS chan %u disabled\n", i); + continue; + } + + drm_printf(&p, "HVS chan %u:\n", i); + + for (j = HVS_READ(SCALER_DISPLISTX(i)); j < 256; j++) { + dlist_word = readl((u32 __iomem *)vc4->hvs->dlist + j); + drm_printf(&p, "dlist: %02d: 0x%08x\n", j, + dlist_word); + if (!next_entry_start || + next_entry_start == j) { + if (dlist_word & SCALER_CTL0_END) + break; + next_entry_start = j + + VC4_GET_FIELD(dlist_word, + SCALER_CTL0_SIZE); + } + } + } + + return 0; +} + /* The filter kernel is composed of dwords each containing 3 9-bit * signed integers packed next to each other. */ @@ -734,6 +774,8 @@ static int vc4_hvs_bind(struct device *dev, struct device *master, void *data) vc4_debugfs_add_regset32(drm, "hvs_regs", &hvs->regset); vc4_debugfs_add_file(drm, "hvs_underrun", vc4_hvs_debugfs_underrun, NULL); + vc4_debugfs_add_file(drm, "hvs_dlists", vc4_hvs_debugfs_dlist, + NULL); return 0; } diff --git a/drivers/gpu/drm/vc4/vc4_kms.c b/drivers/gpu/drm/vc4/vc4_kms.c index a1819df6c190..6f5ccefc92ec 100644 --- a/drivers/gpu/drm/vc4/vc4_kms.c +++ b/drivers/gpu/drm/vc4/vc4_kms.c @@ -406,7 +406,7 @@ static void vc4_atomic_commit_tail(struct drm_atomic_state *state) * Do a temporary request on the core clock during the * modeset. */ - clk_set_min_rate(hvs->core_clk, core_rate); + WARN_ON(clk_set_min_rate(hvs->core_clk, core_rate)); } drm_atomic_helper_commit_modeset_disables(dev, state); @@ -439,7 +439,7 @@ static void vc4_atomic_commit_tail(struct drm_atomic_state *state) * Request a clock rate based on the current HVS * requirements. */ - clk_set_min_rate(hvs->core_clk, new_hvs_state->core_clock_rate); + WARN_ON(clk_set_min_rate(hvs->core_clk, new_hvs_state->core_clock_rate)); drm_dbg(dev, "Core clock actual rate: %lu Hz\n", clk_get_rate(hvs->core_clk)); @@ -947,7 +947,9 @@ vc4_core_clock_atomic_check(struct drm_atomic_state *state) continue; num_outputs++; - cob_rate += hvs_new_state->fifo_state[i].fifo_load; + cob_rate = max_t(unsigned long, + hvs_new_state->fifo_state[i].fifo_load, + cob_rate); } pixel_rate = load_state->hvs_load; diff --git a/drivers/gpu/drm/vc4/vc4_plane.c b/drivers/gpu/drm/vc4/vc4_plane.c index 311077647394..5507f2cd7a27 100644 --- a/drivers/gpu/drm/vc4/vc4_plane.c +++ b/drivers/gpu/drm/vc4/vc4_plane.c @@ -312,16 +312,16 @@ static int vc4_plane_margins_adj(struct drm_plane_state *pstate) adjhdisplay, crtc_state->mode.hdisplay); vc4_pstate->crtc_x += left; - if (vc4_pstate->crtc_x > crtc_state->mode.hdisplay - left) - vc4_pstate->crtc_x = crtc_state->mode.hdisplay - left; + if (vc4_pstate->crtc_x > crtc_state->mode.hdisplay - right) + vc4_pstate->crtc_x = crtc_state->mode.hdisplay - right; adjvdisplay = crtc_state->mode.vdisplay - (top + bottom); vc4_pstate->crtc_y = DIV_ROUND_CLOSEST(vc4_pstate->crtc_y * adjvdisplay, crtc_state->mode.vdisplay); vc4_pstate->crtc_y += top; - if (vc4_pstate->crtc_y > crtc_state->mode.vdisplay - top) - vc4_pstate->crtc_y = crtc_state->mode.vdisplay - top; + if (vc4_pstate->crtc_y > crtc_state->mode.vdisplay - bottom) + vc4_pstate->crtc_y = crtc_state->mode.vdisplay - bottom; vc4_pstate->crtc_w = DIV_ROUND_CLOSEST(vc4_pstate->crtc_w * adjhdisplay, @@ -341,7 +341,6 @@ static int vc4_plane_setup_clipping_and_scaling(struct drm_plane_state *state) struct vc4_plane_state *vc4_state = to_vc4_plane_state(state); struct drm_framebuffer *fb = state->fb; struct drm_gem_cma_object *bo = drm_fb_cma_get_gem_obj(fb, 0); - u32 subpixel_src_mask = (1 << 16) - 1; int num_planes = fb->format->num_planes; struct drm_crtc_state *crtc_state; u32 h_subsample = fb->format->hsub; @@ -363,18 +362,15 @@ static int vc4_plane_setup_clipping_and_scaling(struct drm_plane_state *state) for (i = 0; i < num_planes; i++) vc4_state->offsets[i] = bo->paddr + fb->offsets[i]; - /* We don't support subpixel source positioning for scaling. */ - if ((state->src.x1 & subpixel_src_mask) || - (state->src.x2 & subpixel_src_mask) || - (state->src.y1 & subpixel_src_mask) || - (state->src.y2 & subpixel_src_mask)) { - return -EINVAL; - } - - vc4_state->src_x = state->src.x1 >> 16; - vc4_state->src_y = state->src.y1 >> 16; - vc4_state->src_w[0] = (state->src.x2 - state->src.x1) >> 16; - vc4_state->src_h[0] = (state->src.y2 - state->src.y1) >> 16; + /* + * We don't support subpixel source positioning for scaling, + * but fractional coordinates can be generated by clipping + * so just round for now + */ + vc4_state->src_x = DIV_ROUND_CLOSEST(state->src.x1, 1 << 16); + vc4_state->src_y = DIV_ROUND_CLOSEST(state->src.y1, 1 << 16); + vc4_state->src_w[0] = DIV_ROUND_CLOSEST(state->src.x2, 1 << 16) - vc4_state->src_x; + vc4_state->src_h[0] = DIV_ROUND_CLOSEST(state->src.y2, 1 << 16) - vc4_state->src_y; vc4_state->crtc_x = state->dst.x1; vc4_state->crtc_y = state->dst.y1; @@ -670,6 +666,48 @@ static const u32 colorspace_coeffs[2][DRM_COLOR_ENCODING_MAX][3] = { } }; +static u32 vc4_hvs4_get_alpha_blend_mode(struct drm_plane_state *state) +{ + if (!state->fb->format->has_alpha) + return VC4_SET_FIELD(SCALER_POS2_ALPHA_MODE_FIXED, + SCALER_POS2_ALPHA_MODE); + + switch (state->pixel_blend_mode) { + case DRM_MODE_BLEND_PIXEL_NONE: + return VC4_SET_FIELD(SCALER_POS2_ALPHA_MODE_FIXED, + SCALER_POS2_ALPHA_MODE); + default: + case DRM_MODE_BLEND_PREMULTI: + return VC4_SET_FIELD(SCALER_POS2_ALPHA_MODE_PIPELINE, + SCALER_POS2_ALPHA_MODE) | + SCALER_POS2_ALPHA_PREMULT; + case DRM_MODE_BLEND_COVERAGE: + return VC4_SET_FIELD(SCALER_POS2_ALPHA_MODE_PIPELINE, + SCALER_POS2_ALPHA_MODE); + } +} + +static u32 vc4_hvs5_get_alpha_blend_mode(struct drm_plane_state *state) +{ + if (!state->fb->format->has_alpha) + return VC4_SET_FIELD(SCALER5_CTL2_ALPHA_MODE_FIXED, + SCALER5_CTL2_ALPHA_MODE); + + switch (state->pixel_blend_mode) { + case DRM_MODE_BLEND_PIXEL_NONE: + return VC4_SET_FIELD(SCALER5_CTL2_ALPHA_MODE_FIXED, + SCALER5_CTL2_ALPHA_MODE); + default: + case DRM_MODE_BLEND_PREMULTI: + return VC4_SET_FIELD(SCALER5_CTL2_ALPHA_MODE_PIPELINE, + SCALER5_CTL2_ALPHA_MODE) | + SCALER5_CTL2_ALPHA_PREMULT; + case DRM_MODE_BLEND_COVERAGE: + return VC4_SET_FIELD(SCALER5_CTL2_ALPHA_MODE_PIPELINE, + SCALER5_CTL2_ALPHA_MODE); + } +} + /* Writes out a full display list for an active plane to the plane's * private dlist state. */ @@ -952,13 +990,8 @@ static int vc4_plane_mode_set(struct drm_plane *plane, /* Position Word 2: Source Image Size, Alpha */ vc4_state->pos2_offset = vc4_state->dlist_count; vc4_dlist_write(vc4_state, - VC4_SET_FIELD(fb->format->has_alpha ? - SCALER_POS2_ALPHA_MODE_PIPELINE : - SCALER_POS2_ALPHA_MODE_FIXED, - SCALER_POS2_ALPHA_MODE) | (mix_plane_alpha ? SCALER_POS2_ALPHA_MIX : 0) | - (fb->format->has_alpha ? - SCALER_POS2_ALPHA_PREMULT : 0) | + vc4_hvs4_get_alpha_blend_mode(state) | VC4_SET_FIELD(vc4_state->src_w[0], SCALER_POS2_WIDTH) | VC4_SET_FIELD(vc4_state->src_h[0], @@ -1003,14 +1036,9 @@ static int vc4_plane_mode_set(struct drm_plane *plane, vc4_dlist_write(vc4_state, VC4_SET_FIELD(state->alpha >> 4, SCALER5_CTL2_ALPHA) | - (fb->format->has_alpha ? - SCALER5_CTL2_ALPHA_PREMULT : 0) | + vc4_hvs5_get_alpha_blend_mode(state) | (mix_plane_alpha ? - SCALER5_CTL2_ALPHA_MIX : 0) | - VC4_SET_FIELD(fb->format->has_alpha ? - SCALER5_CTL2_ALPHA_MODE_PIPELINE : - SCALER5_CTL2_ALPHA_MODE_FIXED, - SCALER5_CTL2_ALPHA_MODE) + SCALER5_CTL2_ALPHA_MIX : 0) ); /* Position Word 1: Scaled Image Dimensions. */ @@ -1495,6 +1523,10 @@ struct drm_plane *vc4_plane_init(struct drm_device *dev, drm_plane_helper_add(plane, &vc4_plane_helper_funcs); drm_plane_create_alpha_property(plane); + drm_plane_create_blend_mode_property(plane, + BIT(DRM_MODE_BLEND_PIXEL_NONE) | + BIT(DRM_MODE_BLEND_PREMULTI) | + BIT(DRM_MODE_BLEND_COVERAGE)); drm_plane_create_rotation_property(plane, DRM_MODE_ROTATE_0, DRM_MODE_ROTATE_0 | DRM_MODE_ROTATE_180 | diff --git a/drivers/vfio/pci/vfio_pci_core.c b/drivers/vfio/pci/vfio_pci_core.c index a0d69ddaf90d..756d049bd9cf 100644 --- a/drivers/vfio/pci/vfio_pci_core.c +++ b/drivers/vfio/pci/vfio_pci_core.c @@ -10,6 +10,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include <linux/aperture.h> #include <linux/device.h> #include <linux/eventfd.h> #include <linux/file.h> @@ -1793,6 +1794,10 @@ static int vfio_pci_vga_init(struct vfio_pci_core_device *vdev) if (!vfio_pci_is_vga(pdev)) return 0; + ret = aperture_remove_conflicting_pci_devices(pdev, vdev->vdev.ops->name); + if (ret) + return ret; + ret = vga_client_register(pdev, vfio_pci_set_decode); if (ret) return ret; diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 427a993c7f57..0587e21abad9 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -5,6 +5,12 @@ menu "Graphics support" +config APERTURE_HELPERS + bool + help + Support tracking and hand-over of aperture ownership. Required + by graphics drivers for firmware-provided framebuffers. + if HAS_IOMEM config HAVE_FB_ATMEL diff --git a/drivers/video/Makefile b/drivers/video/Makefile index df7650adede9..5bb6b452cc83 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -1,4 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_APERTURE_HELPERS) += aperture.o obj-$(CONFIG_VGASTATE) += vgastate.o obj-$(CONFIG_HDMI) += hdmi.o diff --git a/drivers/video/aperture.c b/drivers/video/aperture.c new file mode 100644 index 000000000000..538f2d40acda --- /dev/null +++ b/drivers/video/aperture.c @@ -0,0 +1,351 @@ +// SPDX-License-Identifier: MIT + +#include <linux/aperture.h> +#include <linux/device.h> +#include <linux/fb.h> /* for old fbdev helpers */ +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/vgaarb.h> + +/** + * DOC: overview + * + * A graphics device might be supported by different drivers, but only one + * driver can be active at any given time. Many systems load a generic + * graphics drivers, such as EFI-GOP or VESA, early during the boot process. + * During later boot stages, they replace the generic driver with a dedicated, + * hardware-specific driver. To take over the device the dedicated driver + * first has to remove the generic driver. Aperture functions manage + * ownership of framebuffer memory and hand-over between drivers. + * + * Graphics drivers should call aperture_remove_conflicting_devices() + * at the top of their probe function. The function removes any generic + * driver that is currently associated with the given framebuffer memory. + * An example for a graphics device on the platform bus is shown below. + * + * .. code-block:: c + * + * static int example_probe(struct platform_device *pdev) + * { + * struct resource *mem; + * resource_size_t base, size; + * int ret; + * + * mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + * if (!mem) + * return -ENODEV; + * base = mem->start; + * size = resource_size(mem); + * + * ret = aperture_remove_conflicting_devices(base, size, false, "example"); + * if (ret) + * return ret; + * + * // Initialize the hardware + * ... + * + * return 0; + * } + * + * static const struct platform_driver example_driver = { + * .probe = example_probe, + * ... + * }; + * + * The given example reads the platform device's I/O-memory range from the + * device instance. An active framebuffer will be located within this range. + * The call to aperture_remove_conflicting_devices() releases drivers that + * have previously claimed ownership of the range and are currently driving + * output on the framebuffer. If successful, the new driver can take over + * the device. + * + * While the given example uses a platform device, the aperture helpers work + * with every bus that has an addressable framebuffer. In the case of PCI, + * device drivers can also call aperture_remove_conflicting_pci_devices() and + * let the function detect the apertures automatically. Device drivers without + * knowledge of the framebuffer's location can call + * aperture_remove_all_conflicting_devices(), which removes all known devices. + * + * Drivers that are susceptible to being removed by other drivers, such as + * generic EFI or VESA drivers, have to register themselves as owners of their + * framebuffer apertures. Ownership of the framebuffer memory is achieved + * by calling devm_aperture_acquire_for_platform_device(). If successful, the + * driveris the owner of the framebuffer range. The function fails if the + * framebuffer is already owned by another driver. See below for an example. + * + * .. code-block:: c + * + * static int generic_probe(struct platform_device *pdev) + * { + * struct resource *mem; + * resource_size_t base, size; + * + * mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + * if (!mem) + * return -ENODEV; + * base = mem->start; + * size = resource_size(mem); + * + * ret = devm_aperture_acquire_for_platform_device(pdev, base, size); + * if (ret) + * return ret; + * + * // Initialize the hardware + * ... + * + * return 0; + * } + * + * static int generic_remove(struct platform_device *) + * { + * // Hot-unplug the device + * ... + * + * return 0; + * } + * + * static const struct platform_driver generic_driver = { + * .probe = generic_probe, + * .remove = generic_remove, + * ... + * }; + * + * The similar to the previous example, the generic driver claims ownership + * of the framebuffer memory from its probe function. This will fail if the + * memory range, or parts of it, is already owned by another driver. + * + * If successful, the generic driver is now subject to forced removal by + * another driver. This only works for platform drivers that support hot + * unplugging. When a driver calls aperture_remove_conflicting_devices() + * et al for the registered framebuffer range, the aperture helpers call + * platform_device_unregister() and the generic driver unloads itself. The + * generic driver also has to provide a remove function to make this work. + * Once hot unplugged fro mhardware, it may not access the device's + * registers, framebuffer memory, ROM, etc afterwards. + */ + +struct aperture_range { + struct device *dev; + resource_size_t base; + resource_size_t size; + struct list_head lh; + void (*detach)(struct device *dev); +}; + +static LIST_HEAD(apertures); +static DEFINE_MUTEX(apertures_lock); + +static bool overlap(resource_size_t base1, resource_size_t end1, + resource_size_t base2, resource_size_t end2) +{ + return (base1 < end2) && (end1 > base2); +} + +static void devm_aperture_acquire_release(void *data) +{ + struct aperture_range *ap = data; + bool detached = !ap->dev; + + if (detached) + return; + + mutex_lock(&apertures_lock); + list_del(&ap->lh); + mutex_unlock(&apertures_lock); +} + +static int devm_aperture_acquire(struct device *dev, + resource_size_t base, resource_size_t size, + void (*detach)(struct device *)) +{ + size_t end = base + size; + struct list_head *pos; + struct aperture_range *ap; + + mutex_lock(&apertures_lock); + + list_for_each(pos, &apertures) { + ap = container_of(pos, struct aperture_range, lh); + if (overlap(base, end, ap->base, ap->base + ap->size)) { + mutex_unlock(&apertures_lock); + return -EBUSY; + } + } + + ap = devm_kzalloc(dev, sizeof(*ap), GFP_KERNEL); + if (!ap) { + mutex_unlock(&apertures_lock); + return -ENOMEM; + } + + ap->dev = dev; + ap->base = base; + ap->size = size; + ap->detach = detach; + INIT_LIST_HEAD(&ap->lh); + + list_add(&ap->lh, &apertures); + + mutex_unlock(&apertures_lock); + + return devm_add_action_or_reset(dev, devm_aperture_acquire_release, ap); +} + +static void aperture_detach_platform_device(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + + /* + * Remove the device from the device hierarchy. This is the right thing + * to do for firmware-based DRM drivers, such as EFI, VESA or VGA. After + * the new driver takes over the hardware, the firmware device's state + * will be lost. + * + * For non-platform devices, a new callback would be required. + * + * If the aperture helpers ever need to handle native drivers, this call + * would only have to unplug the DRM device, so that the hardware device + * stays around after detachment. + */ + platform_device_unregister(pdev); +} + +/** + * devm_aperture_acquire_for_platform_device - Acquires ownership of an aperture + * on behalf of a platform device. + * @pdev: the platform device to own the aperture + * @base: the aperture's byte offset in physical memory + * @size: the aperture size in bytes + * + * Installs the given device as the new owner of the aperture. The function + * expects the aperture to be provided by a platform device. If another + * driver takes over ownership of the aperture, aperture helpers will then + * unregister the platform device automatically. All acquired apertures are + * released automatically when the underlying device goes away. + * + * The function fails if the aperture, or parts of it, is currently + * owned by another device. To evict current owners, callers should use + * remove_conflicting_devices() et al. before calling this function. + * + * Returns: + * 0 on success, or a negative errno value otherwise. + */ +int devm_aperture_acquire_for_platform_device(struct platform_device *pdev, + resource_size_t base, + resource_size_t size) +{ + return devm_aperture_acquire(&pdev->dev, base, size, aperture_detach_platform_device); +} +EXPORT_SYMBOL(devm_aperture_acquire_for_platform_device); + +static void aperture_detach_devices(resource_size_t base, resource_size_t size) +{ + resource_size_t end = base + size; + struct list_head *pos, *n; + + mutex_lock(&apertures_lock); + + list_for_each_safe(pos, n, &apertures) { + struct aperture_range *ap = container_of(pos, struct aperture_range, lh); + struct device *dev = ap->dev; + + if (WARN_ON_ONCE(!dev)) + continue; + + if (!overlap(base, end, ap->base, ap->base + ap->size)) + continue; + + ap->dev = NULL; /* detach from device */ + list_del(&ap->lh); + + ap->detach(dev); + } + + mutex_unlock(&apertures_lock); +} + +/** + * aperture_remove_conflicting_devices - remove devices in the given range + * @base: the aperture's base address in physical memory + * @size: aperture size in bytes + * @primary: also kick vga16fb if present; only relevant for VGA devices + * @name: a descriptive name of the requesting driver + * + * This function removes devices that own apertures within @base and @size. + * + * Returns: + * 0 on success, or a negative errno code otherwise + */ +int aperture_remove_conflicting_devices(resource_size_t base, resource_size_t size, + bool primary, const char *name) +{ +#if IS_REACHABLE(CONFIG_FB) + struct apertures_struct *a; + int ret; + + a = alloc_apertures(1); + if (!a) + return -ENOMEM; + + a->ranges[0].base = base; + a->ranges[0].size = size; + + ret = remove_conflicting_framebuffers(a, name, primary); + kfree(a); + + if (ret) + return ret; +#endif + + aperture_detach_devices(base, size); + + return 0; +} +EXPORT_SYMBOL(aperture_remove_conflicting_devices); + +/** + * aperture_remove_conflicting_pci_devices - remove existing framebuffers for PCI devices + * @pdev: PCI device + * @name: a descriptive name of the requesting driver + * + * This function removes devices that own apertures within any of @pdev's + * memory bars. The function assumes that PCI device with shadowed ROM + * drives a primary display and therefore kicks out vga16fb as well. + * + * Returns: + * 0 on success, or a negative errno code otherwise + */ +int aperture_remove_conflicting_pci_devices(struct pci_dev *pdev, const char *name) +{ + resource_size_t base, size; + int bar, ret; + + /* + * WARNING: Apparently we must kick fbdev drivers before vgacon, + * otherwise the vga fbdev driver falls over. + */ +#if IS_REACHABLE(CONFIG_FB) + ret = remove_conflicting_pci_framebuffers(pdev, name); + if (ret) + return ret; +#endif + ret = vga_remove_vgacon(pdev); + if (ret) + return ret; + + for (bar = 0; bar < PCI_STD_NUM_BARS; ++bar) { + if (!(pci_resource_flags(pdev, bar) & IORESOURCE_MEM)) + continue; + base = pci_resource_start(pdev, bar); + size = pci_resource_len(pdev, bar); + aperture_detach_devices(base, size); + } + + return 0; + +} +EXPORT_SYMBOL(aperture_remove_conflicting_pci_devices); diff --git a/drivers/video/console/Kconfig b/drivers/video/console/Kconfig index 40c50fa2dd70..22cea5082ac4 100644 --- a/drivers/video/console/Kconfig +++ b/drivers/video/console/Kconfig @@ -10,6 +10,7 @@ config VGA_CONSOLE depends on !4xx && !PPC_8xx && !SPARC && !M68K && !PARISC && !SUPERH && \ (!ARM || ARCH_FOOTBRIDGE || ARCH_INTEGRATOR || ARCH_NETWINDER) && \ !ARM64 && !ARC && !MICROBLAZE && !OPENRISC && !S390 && !UML + select APERTURE_HELPERS if (DRM || FB || VFIO_PCI_CORE) default y help Saying Y here will allow you to use Linux in text mode through a diff --git a/drivers/video/fbdev/Kconfig b/drivers/video/fbdev/Kconfig index f2a6b81e45c4..cfc55273dc5d 100644 --- a/drivers/video/fbdev/Kconfig +++ b/drivers/video/fbdev/Kconfig @@ -455,6 +455,7 @@ config FB_ATARI config FB_OF bool "Open Firmware frame buffer device support" depends on (FB = y) && PPC && (!PPC_PSERIES || PCI) + select APERTURE_HELPERS select FB_CFB_FILLRECT select FB_CFB_COPYAREA select FB_CFB_IMAGEBLIT @@ -527,6 +528,7 @@ config FB_IMSTT config FB_VGA16 tristate "VGA 16-color graphics support" depends on FB && (X86 || PPC) + select APERTURE_HELPERS select FB_CFB_FILLRECT select FB_CFB_COPYAREA select FB_CFB_IMAGEBLIT @@ -551,7 +553,7 @@ config FB_STI BIOS routines contained in a ROM chip in HP PA-RISC based machines. Enabling this option will implement the linux framebuffer device using calls to the STI BIOS routines for initialisation. - + If you enable this option, you will get a planar framebuffer device /dev/fb which will work on the most common HP graphic cards of the NGLE family, including the artist chips (in the 7xx and Bxxx series), @@ -617,6 +619,7 @@ config FB_UVESA config FB_VESA bool "VESA VGA graphics support" depends on (FB = y) && X86 + select APERTURE_HELPERS select FB_CFB_FILLRECT select FB_CFB_COPYAREA select FB_CFB_IMAGEBLIT @@ -630,6 +633,7 @@ config FB_VESA config FB_EFI bool "EFI-based Framebuffer Support" depends on (FB = y) && !IA64 && EFI + select APERTURE_HELPERS select DRM_PANEL_ORIENTATION_QUIRKS select FB_CFB_FILLRECT select FB_CFB_COPYAREA @@ -2190,6 +2194,7 @@ config FB_SIMPLE tristate "Simple framebuffer support" depends on FB depends on !DRM_SIMPLEDRM + select APERTURE_HELPERS select FB_CFB_FILLRECT select FB_CFB_COPYAREA select FB_CFB_IMAGEBLIT diff --git a/include/drm/drm_atomic_helper.h b/include/drm/drm_atomic_helper.h index 4045e2507e11..2a0b17842402 100644 --- a/include/drm/drm_atomic_helper.h +++ b/include/drm/drm_atomic_helper.h @@ -46,6 +46,8 @@ int drm_atomic_helper_check_plane_state(struct drm_plane_state *plane_state, int max_scale, bool can_position, bool can_update_disabled); +int drm_atomic_helper_check_crtc_state(struct drm_crtc_state *crtc_state, + bool can_disable_primary_plane); int drm_atomic_helper_check_planes(struct drm_device *dev, struct drm_atomic_state *state); int drm_atomic_helper_check(struct drm_device *dev, diff --git a/include/drm/drm_mipi_dsi.h b/include/drm/drm_mipi_dsi.h index 51e09a1a106a..91a164bdd8f3 100644 --- a/include/drm/drm_mipi_dsi.h +++ b/include/drm/drm_mipi_dsi.h @@ -296,6 +296,23 @@ int mipi_dsi_dcs_get_display_brightness(struct mipi_dsi_device *dsi, u16 *brightness); /** + * mipi_dsi_dcs_write_seq - transmit a DCS command with payload + * @dsi: DSI peripheral device + * @cmd: Command + * @seq: buffer containing data to be transmitted + */ +#define mipi_dsi_dcs_write_seq(dsi, cmd, seq...) do { \ + static const u8 d[] = { cmd, seq }; \ + struct device *dev = &dsi->dev; \ + int ret; \ + ret = mipi_dsi_dcs_write_buffer(dsi, d, ARRAY_SIZE(d)); \ + if (ret < 0) { \ + dev_err_ratelimited(dev, "sending command %#02x failed: %d\n", cmd, ret); \ + return ret; \ + } \ + } while (0) + +/** * struct mipi_dsi_driver - DSI driver * @driver: device driver model driver * @probe: callback for device binding diff --git a/include/drm/drm_rect.h b/include/drm/drm_rect.h index 6f6e19bd4dac..e8d94fca2703 100644 --- a/include/drm/drm_rect.h +++ b/include/drm/drm_rect.h @@ -48,6 +48,22 @@ struct drm_rect { }; /** + * DRM_RECT_INIT - initialize a rectangle from x/y/w/h + * @x: x coordinate + * @y: y coordinate + * @w: width + * @h: height + * + * RETURNS: + * A new rectangle of the specified size. + */ +#define DRM_RECT_INIT(x, y, w, h) ((struct drm_rect){ \ + .x1 = (x), \ + .y1 = (y), \ + .x2 = (x) + (w), \ + .y2 = (y) + (h) }) + +/** * DRM_RECT_FMT - printf string for &struct drm_rect */ #define DRM_RECT_FMT "%dx%d%+d%+d" diff --git a/include/linux/aperture.h b/include/linux/aperture.h new file mode 100644 index 000000000000..442f15a57cad --- /dev/null +++ b/include/linux/aperture.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: MIT */ + +#ifndef _LINUX_APERTURE_H_ +#define _LINUX_APERTURE_H_ + +#include <linux/types.h> + +struct pci_dev; +struct platform_device; + +#if defined(CONFIG_APERTURE_HELPERS) +int devm_aperture_acquire_for_platform_device(struct platform_device *pdev, + resource_size_t base, + resource_size_t size); + +int aperture_remove_conflicting_devices(resource_size_t base, resource_size_t size, + bool primary, const char *name); + +int aperture_remove_conflicting_pci_devices(struct pci_dev *pdev, const char *name); +#else +static inline int devm_aperture_acquire_for_platform_device(struct platform_device *pdev, + resource_size_t base, + resource_size_t size) +{ + return 0; +} + +static inline int aperture_remove_conflicting_devices(resource_size_t base, resource_size_t size, + bool primary, const char *name) +{ + return 0; +} + +static inline int aperture_remove_conflicting_pci_devices(struct pci_dev *pdev, const char *name) +{ + return 0; +} +#endif + +/** + * aperture_remove_all_conflicting_devices - remove all existing framebuffers + * @primary: also kick vga16fb if present; only relevant for VGA devices + * @name: a descriptive name of the requesting driver + * + * This function removes all graphics device drivers. Use this function on systems + * that can have their framebuffer located anywhere in memory. + * + * Returns: + * 0 on success, or a negative errno code otherwise + */ +static inline int aperture_remove_all_conflicting_devices(bool primary, const char *name) +{ + return aperture_remove_conflicting_devices(0, (resource_size_t)-1, primary, name); +} + +#endif diff --git a/include/linux/iosys-map.h b/include/linux/iosys-map.h index 4b8406ee8bc4..a533cae189d7 100644 --- a/include/linux/iosys-map.h +++ b/include/linux/iosys-map.h @@ -6,6 +6,7 @@ #ifndef __IOSYS_MAP_H__ #define __IOSYS_MAP_H__ +#include <linux/compiler_types.h> #include <linux/io.h> #include <linux/string.h> @@ -333,6 +334,36 @@ static inline void iosys_map_memset(struct iosys_map *dst, size_t offset, memset(dst->vaddr + offset, value, len); } +#ifdef CONFIG_64BIT +#define __iosys_map_rd_io_u64_case(val_, vaddr_iomem_) \ + u64: val_ = readq(vaddr_iomem_) +#define __iosys_map_wr_io_u64_case(val_, vaddr_iomem_) \ + u64: writeq(val_, vaddr_iomem_) +#else +#define __iosys_map_rd_io_u64_case(val_, vaddr_iomem_) \ + u64: memcpy_fromio(&(val_), vaddr_iomem_, sizeof(u64)) +#define __iosys_map_wr_io_u64_case(val_, vaddr_iomem_) \ + u64: memcpy_toio(vaddr_iomem_, &(val_), sizeof(u64)) +#endif + +#define __iosys_map_rd_io(val__, vaddr_iomem__, type__) _Generic(val__, \ + u8: val__ = readb(vaddr_iomem__), \ + u16: val__ = readw(vaddr_iomem__), \ + u32: val__ = readl(vaddr_iomem__), \ + __iosys_map_rd_io_u64_case(val__, vaddr_iomem__)) + +#define __iosys_map_rd_sys(val__, vaddr__, type__) \ + val__ = READ_ONCE(*(type__ *)(vaddr__)) + +#define __iosys_map_wr_io(val__, vaddr_iomem__, type__) _Generic(val__, \ + u8: writeb(val__, vaddr_iomem__), \ + u16: writew(val__, vaddr_iomem__), \ + u32: writel(val__, vaddr_iomem__), \ + __iosys_map_wr_io_u64_case(val__, vaddr_iomem__)) + +#define __iosys_map_wr_sys(val__, vaddr__, type__) \ + WRITE_ONCE(*(type__ *)(vaddr__), val__) + /** * iosys_map_rd - Read a C-type value from the iosys_map * @@ -340,16 +371,21 @@ static inline void iosys_map_memset(struct iosys_map *dst, size_t offset, * @offset__: The offset from which to read * @type__: Type of the value being read * - * Read a C type value from iosys_map, handling possible un-aligned accesses to - * the mapping. + * Read a C type value (u8, u16, u32 and u64) from iosys_map. For other types or + * if pointer may be unaligned (and problematic for the architecture supported), + * use iosys_map_memcpy_from(). * * Returns: * The value read from the mapping. */ -#define iosys_map_rd(map__, offset__, type__) ({ \ - type__ val; \ - iosys_map_memcpy_from(&val, map__, offset__, sizeof(val)); \ - val; \ +#define iosys_map_rd(map__, offset__, type__) ({ \ + type__ val; \ + if ((map__)->is_iomem) { \ + __iosys_map_rd_io(val, (map__)->vaddr_iomem + (offset__), type__);\ + } else { \ + __iosys_map_rd_sys(val, (map__)->vaddr + (offset__), type__); \ + } \ + val; \ }) /** @@ -360,12 +396,17 @@ static inline void iosys_map_memset(struct iosys_map *dst, size_t offset, * @type__: Type of the value being written * @val__: Value to write * - * Write a C-type value to the iosys_map, handling possible un-aligned accesses - * to the mapping. + * Write a C type value (u8, u16, u32 and u64) to the iosys_map. For other types + * or if pointer may be unaligned (and problematic for the architecture + * supported), use iosys_map_memcpy_to() */ -#define iosys_map_wr(map__, offset__, type__, val__) ({ \ - type__ val = (val__); \ - iosys_map_memcpy_to(map__, offset__, &val, sizeof(val)); \ +#define iosys_map_wr(map__, offset__, type__, val__) ({ \ + type__ val = (val__); \ + if ((map__)->is_iomem) { \ + __iosys_map_wr_io(val, (map__)->vaddr_iomem + (offset__), type__);\ + } else { \ + __iosys_map_wr_sys(val, (map__)->vaddr + (offset__), type__); \ + } \ }) /** @@ -379,9 +420,10 @@ static inline void iosys_map_memset(struct iosys_map *dst, size_t offset, * * Read a value from iosys_map considering its layout is described by a C struct * starting at @struct_offset__. The field offset and size is calculated and its - * value read handling possible un-aligned memory accesses. For example: suppose - * there is a @struct foo defined as below and the value ``foo.field2.inner2`` - * needs to be read from the iosys_map: + * value read. If the field access would incur in un-aligned access, then either + * iosys_map_memcpy_from() needs to be used or the architecture must support it. + * For example: suppose there is a @struct foo defined as below and the value + * ``foo.field2.inner2`` needs to be read from the iosys_map: * * .. code-block:: c * @@ -445,10 +487,12 @@ static inline void iosys_map_memset(struct iosys_map *dst, size_t offset, * @field__: Member of the struct to read * @val__: Value to write * - * Write a value to the iosys_map considering its layout is described by a C struct - * starting at @struct_offset__. The field offset and size is calculated and the - * @val__ is written handling possible un-aligned memory accesses. Refer to - * iosys_map_rd_field() for expected usage and memory layout. + * Write a value to the iosys_map considering its layout is described by a C + * struct starting at @struct_offset__. The field offset and size is calculated + * and the @val__ is written. If the field access would incur in un-aligned + * access, then either iosys_map_memcpy_to() needs to be used or the + * architecture must support it. Refer to iosys_map_rd_field() for expected + * usage and memory layout. */ #define iosys_map_wr_field(map__, struct_offset__, struct_type__, field__, val__) ({ \ struct_type__ *s; \ diff --git a/include/uapi/drm/drm_fourcc.h b/include/uapi/drm/drm_fourcc.h index f1972154a594..c1b4cfda7507 100644 --- a/include/uapi/drm/drm_fourcc.h +++ b/include/uapi/drm/drm_fourcc.h @@ -559,7 +559,7 @@ extern "C" { * * The main surface is Y-tiled and is at plane index 0 whereas CCS is linear * and at index 1. The clear color is stored at index 2, and the pitch should - * be ignored. The clear color structure is 256 bits. The first 128 bits + * be 64 bytes aligned. The clear color structure is 256 bits. The first 128 bits * represents Raw Clear Color Red, Green, Blue and Alpha color each represented * by 32 bits. The raw clear color is consumed by the 3d engine and generates * the converted clear color of size 64 bits. The first 32 bits store the Lower @@ -612,9 +612,9 @@ extern "C" { * outside of the GEM object in a reserved memory area dedicated for the * storage of the CCS data for all RC/RC_CC/MC compressible GEM objects. The * main surface pitch is required to be a multiple of four Tile 4 widths. The - * clear color is stored at plane index 1 and the pitch should be ignored. The - * format of the 256 bits of clear color data matches the one used for the - * I915_FORMAT_MOD_Y_TILED_GEN12_RC_CCS_CC modifier, see its description + * clear color is stored at plane index 1 and the pitch should be 64 bytes + * aligned. The format of the 256 bits of clear color data matches the one used + * for the I915_FORMAT_MOD_Y_TILED_GEN12_RC_CCS_CC modifier, see its description * for details. */ #define I915_FORMAT_MOD_4_TILED_DG2_RC_CCS_CC fourcc_mod_code(INTEL, 12) |