diff options
| author | Niklas Cassel <cassel@kernel.org> | 2026-09-16 15:00:32 +0200 |
|---|---|---|
| committer | Niklas Cassel <cassel@kernel.org> | 2026-09-16 16:18:00 +0200 |
| commit | 8d836581f9b1f57ceaa2b47654754ef1260b410b (patch) | |
| tree | 1dbd4d0dfb99c2e238356cbe947e5b0be3e3a571 /drivers/gpu/drm/sysfb | |
| download | linux-stable-8d836581f9b1f57ceaa2b47654754ef1260b410b.tar.gz linux-stable-8d836581f9b1f57ceaa2b47654754ef1260b410b.zip | |
ata: libata-scsi: fix ata_dsm_trim_pages() kernel-docgrafted
make htmldocs fails with:
Documentation/driver-api/libata:604: ./drivers/ata/libata-scsi.c:2301:
ERROR: Unexpected indentation. [docutils]
The Return: section of ata_dsm_trim_pages() ends its first sentence with
a colon and continues with an indented bullet list. reStructuredText
requires a blank line before an indented block, so docutils chokes on
the list.
Simply adding the missing blank line does not work either: Return: is a
kernel-doc "special section", which is terminated by the first blank
line, so the bullet list would end up in the Description section,
detached from the sentence introducing it.
Spell the two bounds out as prose instead, so that the Return: section
stays self-contained. Documentation-only change.
Fixes: e64e6b5dc867 ("ata: libata-scsi: scale DSM TRIM payload by MAX PAGES PER DSM COMMAND")
Reported-by: Thomas Huth <thuth@redhat.com>
Closes: https://lore.kernel.org/linux-ide/b0a0b8e8-cc4f-4c7b-8bc5-fee0712d405a@redhat.com/
Reviewed-by: Damien Le Moal <dlemoal@kernel.org>
Reviewed-by: Thomas Huth <thuth@redhat.com>
Link: https://lore.kernel.org/r/20260916130031.29990-2-cassel@kernel.org
Signed-off-by: Niklas Cassel <cassel@kernel.org>
Diffstat (limited to 'drivers/gpu/drm/sysfb')
| -rw-r--r-- | drivers/gpu/drm/sysfb/Kconfig | 89 | ||||
| -rw-r--r-- | drivers/gpu/drm/sysfb/Makefile | 13 | ||||
| -rw-r--r-- | drivers/gpu/drm/sysfb/corebootdrm.c | 455 | ||||
| -rw-r--r-- | drivers/gpu/drm/sysfb/drm_sysfb.c | 59 | ||||
| -rw-r--r-- | drivers/gpu/drm/sysfb/drm_sysfb_helper.h | 209 | ||||
| -rw-r--r-- | drivers/gpu/drm/sysfb/drm_sysfb_modeset.c | 608 | ||||
| -rw-r--r-- | drivers/gpu/drm/sysfb/drm_sysfb_screen_info.c | 81 | ||||
| -rw-r--r-- | drivers/gpu/drm/sysfb/efidrm.c | 434 | ||||
| -rw-r--r-- | drivers/gpu/drm/sysfb/ofdrm.c | 1166 | ||||
| -rw-r--r-- | drivers/gpu/drm/sysfb/simpledrm.c | 972 | ||||
| -rw-r--r-- | drivers/gpu/drm/sysfb/vesadrm.c | 679 |
11 files changed, 4765 insertions, 0 deletions
diff --git a/drivers/gpu/drm/sysfb/Kconfig b/drivers/gpu/drm/sysfb/Kconfig new file mode 100644 index 000000000..f7e481788 --- /dev/null +++ b/drivers/gpu/drm/sysfb/Kconfig @@ -0,0 +1,89 @@ +# SPDX-License-Identifier: GPL-2.0-only + +menu "Drivers for system framebuffers" + depends on DRM + +config DRM_SYSFB_HELPER + tristate + depends on DRM + +config DRM_COREBOOTDRM + tristate "Coreboot framebuffer driver" + depends on DRM && MMU + depends on GOOGLE_FRAMEBUFFER_COREBOOT + select APERTURE_HELPERS + select DRM_CLIENT_SELECTION + select DRM_GEM_SHMEM_HELPER + select DRM_KMS_HELPER + select DRM_SYSFB_HELPER + help + DRM driver for coreboot-provided framebuffers. + + This driver assumes that the display hardware has been initialized + by coreboot firmware before the kernel boots. Scanout buffer, size, + and display format must be provided via coreboot framebuffer device. + +config DRM_EFIDRM + tristate "EFI framebuffer driver" + depends on DRM && MMU && EFI && (!SYSFB_SIMPLEFB || COMPILE_TEST) + select APERTURE_HELPERS + select DRM_CLIENT_SELECTION + select DRM_GEM_SHMEM_HELPER + select DRM_KMS_HELPER + select DRM_SYSFB_HELPER + select SYSFB + help + DRM driver for EFI framebuffers. + + This driver assumes that the display hardware has been initialized + by the firmware or bootloader before the kernel boots. Scanout + buffer, size, and display format must be provided via EFI interfaces. + +config DRM_OFDRM + tristate "Open Firmware display driver" + depends on DRM && MMU && OF && (PPC || COMPILE_TEST) + select APERTURE_HELPERS + select DRM_CLIENT_SELECTION + select DRM_GEM_SHMEM_HELPER + select DRM_KMS_HELPER + select DRM_SYSFB_HELPER + help + DRM driver for Open Firmware framebuffers. + + This driver assumes that the display hardware has been initialized + by the Open Firmware before the kernel boots. Scanout buffer, size, + and display format must be provided via device tree. + +config DRM_SIMPLEDRM + tristate "Simple framebuffer driver" + depends on DRM && MMU + select APERTURE_HELPERS + select DRM_CLIENT_SELECTION + select DRM_GEM_SHMEM_HELPER + select DRM_KMS_HELPER + select DRM_SYSFB_HELPER + help + DRM driver for simple platform-provided framebuffers. + + This driver assumes that the display hardware has been initialized + by the firmware or bootloader before the kernel boots. Scanout + buffer, size, and display format must be provided via device tree's + simple-framebuffer node. + +config DRM_VESADRM + tristate "VESA framebuffer driver" + depends on DRM && MMU && X86 && (!SYSFB_SIMPLEFB || COMPILE_TEST) + select APERTURE_HELPERS + select DRM_CLIENT_SELECTION + select DRM_GEM_SHMEM_HELPER + select DRM_KMS_HELPER + select DRM_SYSFB_HELPER + select SYSFB + help + DRM driver for VESA framebuffers. + + This driver assumes that the display hardware has been initialized + by the firmware or bootloader before the kernel boots. Scanout + buffer, size, and display format must be provided via VBE interfaces. + +endmenu diff --git a/drivers/gpu/drm/sysfb/Makefile b/drivers/gpu/drm/sysfb/Makefile new file mode 100644 index 000000000..85c9087ab --- /dev/null +++ b/drivers/gpu/drm/sysfb/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0-only + +drm_sysfb_helper-y := \ + drm_sysfb.o \ + drm_sysfb_modeset.o +drm_sysfb_helper-$(CONFIG_SCREEN_INFO) += drm_sysfb_screen_info.o +obj-$(CONFIG_DRM_SYSFB_HELPER) += drm_sysfb_helper.o + +obj-$(CONFIG_DRM_COREBOOTDRM) += corebootdrm.o +obj-$(CONFIG_DRM_EFIDRM) += efidrm.o +obj-$(CONFIG_DRM_OFDRM) += ofdrm.o +obj-$(CONFIG_DRM_SIMPLEDRM) += simpledrm.o +obj-$(CONFIG_DRM_VESADRM) += vesadrm.o diff --git a/drivers/gpu/drm/sysfb/corebootdrm.c b/drivers/gpu/drm/sysfb/corebootdrm.c new file mode 100644 index 000000000..853da9a60 --- /dev/null +++ b/drivers/gpu/drm/sysfb/corebootdrm.c @@ -0,0 +1,455 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include <linux/aperture.h> +#include <linux/coreboot.h> +#include <linux/minmax.h> +#include <linux/platform_device.h> +#include <linux/pm.h> + +#include <drm/clients/drm_client_setup.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_connector.h> +#include <drm/drm_damage_helper.h> +#include <drm/drm_device.h> +#include <drm/drm_drv.h> +#include <drm/drm_fbdev_shmem.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_atomic_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_gem_shmem_helper.h> +#include <drm/drm_managed.h> +#include <drm/drm_modeset_helper.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +#include "drm_sysfb_helper.h" + +#define DRIVER_NAME "corebootdrm" +#define DRIVER_DESC "DRM driver for Coreboot framebuffers" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 + +static const struct drm_format_info * +corebootdrm_get_format_fb(struct drm_device *dev, const struct lb_framebuffer *fb) +{ + static const struct drm_sysfb_format formats[] = { + { PIXEL_FORMAT_XRGB1555, DRM_FORMAT_XRGB1555, }, + { PIXEL_FORMAT_RGB565, DRM_FORMAT_RGB565, }, + { PIXEL_FORMAT_RGB888, DRM_FORMAT_RGB888, }, + { PIXEL_FORMAT_XRGB8888, DRM_FORMAT_XRGB8888, }, + { PIXEL_FORMAT_XBGR8888, DRM_FORMAT_XBGR8888, }, + { PIXEL_FORMAT_XRGB2101010, DRM_FORMAT_XRGB2101010, }, + }; + const struct pixel_format pixel = { + .bits_per_pixel = fb->bits_per_pixel, + .indexed = false, + .alpha = { + .offset = 0, + .length = 0, + }, + .red = { + .offset = fb->red_mask_pos, + .length = fb->red_mask_size, + }, + .green = { + .offset = fb->green_mask_pos, + .length = fb->green_mask_size, + }, + .blue = { + .offset = fb->blue_mask_pos, + .length = fb->blue_mask_size, + }, + }; + + return drm_sysfb_get_format(dev, formats, ARRAY_SIZE(formats), &pixel); +} + +static int corebootdrm_get_width_fb(struct drm_device *dev, const struct lb_framebuffer *fb) +{ + return drm_sysfb_get_validated_int0(dev, "width", fb->x_resolution, INT_MAX); +} + +static int corebootdrm_get_height_fb(struct drm_device *dev, const struct lb_framebuffer *fb) +{ + return drm_sysfb_get_validated_int0(dev, "height", fb->y_resolution, INT_MAX); +} + +static int corebootdrm_get_pitch_fb(struct drm_device *dev, const struct drm_format_info *format, + unsigned int width, const struct lb_framebuffer *fb) +{ + u64 bytes_per_line = fb->bytes_per_line; + + if (!bytes_per_line) + bytes_per_line = drm_format_info_min_pitch(format, 0, width); + + return drm_sysfb_get_validated_int0(dev, "pitch", bytes_per_line, INT_MAX); +} + +static resource_size_t corebootdrm_get_size_fb(struct drm_device *dev, unsigned int height, + unsigned int pitch, + const struct lb_framebuffer *fb) +{ + resource_size_t size; + + if (check_mul_overflow(height, pitch, &size)) + return 0; + + return size; +} + +static phys_addr_t corebootdrm_get_address_fb(struct drm_device *dev, resource_size_t size, + const struct lb_framebuffer *fb) +{ + if (size > PHYS_ADDR_MAX) + return 0; + if (!fb->physical_address) + return 0; + if (fb->physical_address > (PHYS_ADDR_MAX - size)) + return 0; + + return fb->physical_address; +} + +static enum drm_panel_orientation corebootdrm_get_orientation_fb(struct drm_device *dev, + const struct lb_framebuffer *fb) +{ + if (!LB_FRAMEBUFFER_HAS_ORIENTATION(fb)) + return DRM_MODE_PANEL_ORIENTATION_UNKNOWN; + + switch (fb->orientation) { + case LB_FRAMEBUFFER_ORIENTATION_NORMAL: + return DRM_MODE_PANEL_ORIENTATION_NORMAL; + case LB_FRAMEBUFFER_ORIENTATION_BOTTOM_UP: + return DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP; + case LB_FRAMEBUFFER_ORIENTATION_LEFT_UP: + return DRM_MODE_PANEL_ORIENTATION_LEFT_UP; + case LB_FRAMEBUFFER_ORIENTATION_RIGHT_UP: + return DRM_MODE_PANEL_ORIENTATION_RIGHT_UP; + } + + return DRM_MODE_PANEL_ORIENTATION_UNKNOWN; +} + +/* + * Simple Framebuffer device + */ + +struct corebootdrm_device { + struct drm_sysfb_device sysfb; + + /* modesetting */ + u32 formats[DRM_SYSFB_PLANE_NFORMATS(1)]; + struct drm_plane primary_plane; + struct drm_crtc crtc; + struct drm_encoder encoder; + struct drm_connector connector; +}; + +/* + * Modesetting + */ + +static const u64 corebootdrm_primary_plane_format_modifiers[] = { + DRM_SYSFB_PLANE_FORMAT_MODIFIERS, +}; + +static const struct drm_plane_helper_funcs corebootdrm_primary_plane_helper_funcs = { + DRM_SYSFB_PLANE_HELPER_FUNCS, +}; + +static const struct drm_plane_funcs corebootdrm_primary_plane_funcs = { + DRM_SYSFB_PLANE_FUNCS, + .destroy = drm_plane_cleanup, +}; + +static const struct drm_crtc_helper_funcs corebootdrm_crtc_helper_funcs = { + DRM_SYSFB_CRTC_HELPER_FUNCS, +}; + +static const struct drm_crtc_funcs corebootdrm_crtc_funcs = { + DRM_SYSFB_CRTC_FUNCS, + .destroy = drm_crtc_cleanup, +}; + +static const struct drm_encoder_funcs corebootdrm_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +static const struct drm_connector_helper_funcs corebootdrm_connector_helper_funcs = { + DRM_SYSFB_CONNECTOR_HELPER_FUNCS, +}; + +static const struct drm_connector_funcs corebootdrm_connector_funcs = { + DRM_SYSFB_CONNECTOR_FUNCS, + .destroy = drm_connector_cleanup, +}; + +static const struct drm_mode_config_funcs corebootdrm_mode_config_funcs = { + DRM_SYSFB_MODE_CONFIG_FUNCS, +}; + +static int corebootdrm_mode_config_init(struct corebootdrm_device *cdev, + enum drm_panel_orientation orientation) +{ + struct drm_sysfb_device *sysfb = &cdev->sysfb; + struct drm_device *dev = &sysfb->dev; + const struct drm_format_info *format = sysfb->fb_format; + unsigned int width = sysfb->fb_mode.hdisplay; + unsigned int height = sysfb->fb_mode.vdisplay; + struct drm_plane *primary_plane; + struct drm_crtc *crtc; + struct drm_encoder *encoder; + struct drm_connector *connector; + size_t nformats; + int ret; + + ret = drmm_mode_config_init(dev); + if (ret) + return ret; + + dev->mode_config.min_width = width; + dev->mode_config.max_width = max_t(unsigned int, width, DRM_SHADOW_PLANE_MAX_WIDTH); + dev->mode_config.min_height = height; + dev->mode_config.max_height = max_t(unsigned int, height, DRM_SHADOW_PLANE_MAX_HEIGHT); + dev->mode_config.funcs = &corebootdrm_mode_config_funcs; + dev->mode_config.preferred_depth = format->depth; + + /* Primary plane */ + + nformats = drm_sysfb_build_fourcc_list(dev, &format->format, 1, + cdev->formats, ARRAY_SIZE(cdev->formats)); + + primary_plane = &cdev->primary_plane; + ret = drm_universal_plane_init(dev, primary_plane, 0, &corebootdrm_primary_plane_funcs, + cdev->formats, nformats, + corebootdrm_primary_plane_format_modifiers, + DRM_PLANE_TYPE_PRIMARY, NULL); + if (ret) + return ret; + drm_plane_helper_add(primary_plane, &corebootdrm_primary_plane_helper_funcs); + drm_plane_enable_fb_damage_clips(primary_plane); + + /* CRTC */ + + crtc = &cdev->crtc; + ret = drm_crtc_init_with_planes(dev, crtc, primary_plane, NULL, + &corebootdrm_crtc_funcs, NULL); + if (ret) + return ret; + drm_crtc_helper_add(crtc, &corebootdrm_crtc_helper_funcs); + + /* Encoder */ + + encoder = &cdev->encoder; + ret = drm_encoder_init(dev, encoder, &corebootdrm_encoder_funcs, + DRM_MODE_ENCODER_NONE, NULL); + if (ret) + return ret; + encoder->possible_crtcs = drm_crtc_mask(crtc); + + /* Connector */ + + connector = &cdev->connector; + ret = drm_connector_init(dev, connector, &corebootdrm_connector_funcs, + DRM_MODE_CONNECTOR_Unknown); + if (ret) + return ret; + drm_connector_helper_add(connector, &corebootdrm_connector_helper_funcs); + drm_connector_set_panel_orientation_with_quirk(connector, orientation, + width, height); + + ret = drm_connector_attach_encoder(connector, encoder); + if (ret) + return ret; + + return 0; +} + +/* + * DRM driver + */ + +DEFINE_DRM_GEM_FOPS(corebootdrm_fops); + +static struct drm_driver corebootdrm_drm_driver = { + DRM_GEM_SHMEM_DRIVER_OPS, + DRM_FBDEV_SHMEM_DRIVER_OPS, + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, + .driver_features = DRIVER_ATOMIC | DRIVER_GEM | DRIVER_MODESET, + .fops = &corebootdrm_fops, +}; + +/* + * Coreboot driver + */ + +static int corebootdrm_pm_suspend(struct device *dev) +{ + struct corebootdrm_device *cdev = dev_get_drvdata(dev); + struct drm_device *drm = &cdev->sysfb.dev; + + return drm_mode_config_helper_suspend(drm); +} + +static int corebootdrm_pm_resume(struct device *dev) +{ + struct corebootdrm_device *cdev = dev_get_drvdata(dev); + struct drm_device *drm = &cdev->sysfb.dev; + + return drm_mode_config_helper_resume(drm); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(corebootdrm_pm_ops, corebootdrm_pm_suspend, corebootdrm_pm_resume); + +static int corebootdrm_probe(struct platform_device *pdev) +{ + const struct lb_framebuffer *fb = dev_get_platdata(&pdev->dev); + struct corebootdrm_device *cdev; + struct drm_sysfb_device *sysfb; + struct drm_device *dev; + const struct drm_format_info *format; + int width, height, pitch; + resource_size_t size; + phys_addr_t address; + enum drm_panel_orientation orientation; + struct resource *res, *mem = NULL; + struct resource aperture; + void __iomem *screen_base; + int ret; + + cdev = devm_drm_dev_alloc(&pdev->dev, &corebootdrm_drm_driver, + struct corebootdrm_device, sysfb.dev); + if (IS_ERR(cdev)) + return PTR_ERR(cdev); + platform_set_drvdata(pdev, cdev); + + sysfb = &cdev->sysfb; + dev = &sysfb->dev; + + if (!fb) { + drm_err(dev, "coreboot framebuffer not found\n"); + return -EINVAL; + } else if (!LB_FRAMEBUFFER_HAS_LFB(fb)) { + drm_err(dev, "coreboot framebuffer entry too small\n"); + return -EINVAL; + } + + /* + * Hardware settings + */ + + format = corebootdrm_get_format_fb(dev, fb); + if (!format) + return -EINVAL; + width = corebootdrm_get_width_fb(dev, fb); + if (width < 0) + return width; + height = corebootdrm_get_height_fb(dev, fb); + if (height < 0) + return height; + pitch = corebootdrm_get_pitch_fb(dev, format, width, fb); + if (pitch < 0) + return pitch; + size = corebootdrm_get_size_fb(dev, height, pitch, fb); + if (!size) + return -EINVAL; + address = corebootdrm_get_address_fb(dev, size, fb); + if (!address) + return -EINVAL; + orientation = corebootdrm_get_orientation_fb(dev, fb); + + sysfb->fb_mode = drm_sysfb_mode(width, height, 0, 0); + sysfb->fb_format = format; + sysfb->fb_pitch = pitch; + + drm_dbg(dev, "display mode={" DRM_MODE_FMT "}\n", DRM_MODE_ARG(&sysfb->fb_mode)); + drm_dbg(dev, "framebuffer format=%p4cc, size=%dx%d, pitch=%d byte\n", + &format->format, width, height, pitch); + + /* + * Memory management + */ + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + drm_err(dev, "memory resource not found\n"); + return -EINVAL; + } + + mem = devm_request_mem_region(&pdev->dev, res->start, resource_size(res), + dev->driver->name); + if (!mem) { + drm_warn(dev, "could not acquire memory resource at %pr\n", res); + /* + * We cannot make this fatal. Sometimes this comes from magic + * spaces our resource handlers simply don't know about. Use + * the memory resource as-is and try to map that instead. + */ + mem = res; + } + + drm_dbg(dev, "using memory resource at %pr\n", mem); + + aperture = DEFINE_RES_MEM(address, size); + if (!resource_contains(mem, &aperture)) { + drm_err(dev, "framebuffer aperture at invalid memory range %pr\n", &aperture); + return -EINVAL; + } + + ret = devm_aperture_acquire_for_platform_device(pdev, address, size); + if (ret) { + drm_err(dev, "could not acquire framebuffer aperture: %d\n", ret); + return ret; + } + + screen_base = devm_ioremap_wc(&pdev->dev, address, size); + if (!screen_base) + return -ENOMEM; + + iosys_map_set_vaddr_iomem(&sysfb->fb_addr, screen_base); + + /* + * DRM mode setting and registration + */ + + ret = corebootdrm_mode_config_init(cdev, orientation); + if (ret) + return ret; + + drm_mode_config_reset(dev); + + ret = drm_dev_register(dev, 0); + if (ret) + return ret; + + drm_client_setup(dev, sysfb->fb_format); + + return 0; +} + +static void corebootdrm_remove(struct platform_device *pdev) +{ + struct corebootdrm_device *cdev = platform_get_drvdata(pdev); + struct drm_device *dev = &cdev->sysfb.dev; + + drm_dev_unplug(dev); +} + +static struct platform_driver corebootdrm_platform_driver = { + .driver = { + .name = "coreboot-framebuffer", + .pm = pm_sleep_ptr(&corebootdrm_pm_ops), + }, + .probe = corebootdrm_probe, + .remove = corebootdrm_remove, +}; + +module_platform_driver(corebootdrm_platform_driver); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sysfb/drm_sysfb.c b/drivers/gpu/drm/sysfb/drm_sysfb.c new file mode 100644 index 000000000..fbfb37d0f --- /dev/null +++ b/drivers/gpu/drm/sysfb/drm_sysfb.c @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include <linux/export.h> +#include <linux/limits.h> +#include <linux/minmax.h> +#include <linux/module.h> + +#include <drm/drm_print.h> + +#include "drm_sysfb_helper.h" + +int drm_sysfb_get_validated_int(struct drm_device *dev, const char *name, + u64 value, u32 max) +{ + if (value > min(max, INT_MAX)) { + drm_warn(dev, "%s of %llu exceeds maximum of %u\n", name, value, max); + return -EINVAL; + } + return value; +} +EXPORT_SYMBOL(drm_sysfb_get_validated_int); + +int drm_sysfb_get_validated_int0(struct drm_device *dev, const char *name, + u64 value, u32 max) +{ + if (!value) { + drm_warn(dev, "%s of 0 not allowed\n", name); + return -EINVAL; + } + return drm_sysfb_get_validated_int(dev, name, value, max); +} +EXPORT_SYMBOL(drm_sysfb_get_validated_int0); + +const struct drm_format_info *drm_sysfb_get_format(struct drm_device *dev, + const struct drm_sysfb_format *formats, + size_t nformats, + const struct pixel_format *pixel) +{ + const struct drm_format_info *format = NULL; + size_t i; + + for (i = 0; i < nformats; ++i) { + const struct drm_sysfb_format *f = &formats[i]; + + if (pixel_format_equal(pixel, &f->pixel)) { + format = drm_format_info(f->fourcc); + break; + } + } + + if (!format) + drm_warn(dev, "No compatible color format found\n"); + + return format; +} +EXPORT_SYMBOL(drm_sysfb_get_format); + +MODULE_DESCRIPTION("Helpers for DRM sysfb drivers"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sysfb/drm_sysfb_helper.h b/drivers/gpu/drm/sysfb/drm_sysfb_helper.h new file mode 100644 index 000000000..547f2327a --- /dev/null +++ b/drivers/gpu/drm/sysfb/drm_sysfb_helper.h @@ -0,0 +1,209 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef DRM_SYSFB_HELPER_H +#define DRM_SYSFB_HELPER_H + +#include <linux/container_of.h> +#include <linux/iosys-map.h> + +#include <video/pixel_format.h> + +#include <drm/drm_crtc.h> +#include <drm/drm_device.h> +#include <drm/drm_gem_atomic_helper.h> +#include <drm/drm_modes.h> + +struct drm_format_info; +struct drm_scanout_buffer; +struct screen_info; + +typedef void (*drm_sysfb_blit_func)(struct iosys_map *, const unsigned int *, + const struct iosys_map *, + const struct drm_framebuffer *, + const struct drm_rect *, + struct drm_format_conv_state *); + +/* + * Input parsing + */ + +struct drm_sysfb_format { + struct pixel_format pixel; + u32 fourcc; +}; + +int drm_sysfb_get_validated_int(struct drm_device *dev, const char *name, + u64 value, u32 max); +int drm_sysfb_get_validated_int0(struct drm_device *dev, const char *name, + u64 value, u32 max); +const struct drm_format_info *drm_sysfb_get_format(struct drm_device *dev, + const struct drm_sysfb_format *formats, + size_t nformats, + const struct pixel_format *pixel); + +#if defined(CONFIG_SCREEN_INFO) +int drm_sysfb_get_width_si(struct drm_device *dev, const struct screen_info *si); +int drm_sysfb_get_height_si(struct drm_device *dev, const struct screen_info *si); +struct resource *drm_sysfb_get_memory_si(struct drm_device *dev, + const struct screen_info *si, + struct resource *res); +int drm_sysfb_get_stride_si(struct drm_device *dev, const struct screen_info *si, + const struct drm_format_info *format, + unsigned int width, unsigned int height, u64 size); +s64 drm_sysfb_get_visible_size_si(struct drm_device *dev, const struct screen_info *si, + unsigned int height, unsigned int stride, u64 size); +#endif + +/* + * Display modes + */ + +struct drm_display_mode drm_sysfb_mode(unsigned int width, + unsigned int height, + unsigned int width_mm, + unsigned int height_mm); + +/* + * Device + */ + +struct drm_sysfb_device { + struct drm_device dev; + + const u8 *edid; /* can be NULL */ + + /* hardware settings */ + struct drm_display_mode fb_mode; + const struct drm_format_info *fb_format; + unsigned int fb_pitch; + unsigned int fb_gamma_lut_size; + + /* hardware-framebuffer kernel address */ + struct iosys_map fb_addr; +}; + +static inline struct drm_sysfb_device *to_drm_sysfb_device(struct drm_device *dev) +{ + return container_of(dev, struct drm_sysfb_device, dev); +} + +/* + * Plane + */ + +struct drm_sysfb_plane_state { + struct drm_shadow_plane_state base; + + /* transfers framebuffer data to scanout buffer in CRTC format */ + drm_sysfb_blit_func blit_to_crtc; +}; + +static inline struct drm_sysfb_plane_state * +to_drm_sysfb_plane_state(struct drm_plane_state *base) +{ + return container_of(to_drm_shadow_plane_state(base), struct drm_sysfb_plane_state, base); +} + +size_t drm_sysfb_build_fourcc_list(struct drm_device *dev, + const u32 *native_fourccs, size_t native_nfourccs, + u32 *fourccs_out, size_t nfourccs_out); + +int drm_sysfb_plane_helper_begin_fb_access(struct drm_plane *plane, + struct drm_plane_state *plane_state); +int drm_sysfb_plane_helper_atomic_check(struct drm_plane *plane, + struct drm_atomic_commit *new_state); +void drm_sysfb_plane_helper_atomic_update(struct drm_plane *plane, + struct drm_atomic_commit *state); +void drm_sysfb_plane_helper_atomic_disable(struct drm_plane *plane, + struct drm_atomic_commit *state); +int drm_sysfb_plane_helper_get_scanout_buffer(struct drm_plane *plane, + struct drm_scanout_buffer *sb); + +#define DRM_SYSFB_PLANE_NFORMATS(_num_native) \ + ((_num_native) + 1) + +#define DRM_SYSFB_PLANE_FORMAT_MODIFIERS \ + DRM_FORMAT_MOD_LINEAR, \ + DRM_FORMAT_MOD_INVALID + +#define DRM_SYSFB_PLANE_HELPER_FUNCS \ + .begin_fb_access = drm_sysfb_plane_helper_begin_fb_access, \ + .end_fb_access = drm_gem_end_shadow_fb_access, \ + .atomic_check = drm_sysfb_plane_helper_atomic_check, \ + .atomic_update = drm_sysfb_plane_helper_atomic_update, \ + .atomic_disable = drm_sysfb_plane_helper_atomic_disable, \ + .get_scanout_buffer = drm_sysfb_plane_helper_get_scanout_buffer + +void drm_sysfb_plane_reset(struct drm_plane *plane); +struct drm_plane_state *drm_sysfb_plane_atomic_duplicate_state(struct drm_plane *plane); +void drm_sysfb_plane_atomic_destroy_state(struct drm_plane *plane, + struct drm_plane_state *plane_state); + +#define DRM_SYSFB_PLANE_FUNCS \ + .reset = drm_sysfb_plane_reset, \ + .update_plane = drm_atomic_helper_update_plane, \ + .disable_plane = drm_atomic_helper_disable_plane, \ + .atomic_duplicate_state = drm_sysfb_plane_atomic_duplicate_state, \ + .atomic_destroy_state = drm_sysfb_plane_atomic_destroy_state + +/* + * CRTC + */ + +struct drm_sysfb_crtc_state { + struct drm_crtc_state base; + + /* CRTC input color format; required for color mgmt. */ + const struct drm_format_info *format; +}; + +static inline struct drm_sysfb_crtc_state * +to_drm_sysfb_crtc_state(struct drm_crtc_state *base) +{ + return container_of(base, struct drm_sysfb_crtc_state, base); +} + +enum drm_mode_status drm_sysfb_crtc_helper_mode_valid(struct drm_crtc *crtc, + const struct drm_display_mode *mode); +int drm_sysfb_crtc_helper_atomic_check(struct drm_crtc *crtc, struct drm_atomic_commit *new_state); + +#define DRM_SYSFB_CRTC_HELPER_FUNCS \ + .mode_valid = drm_sysfb_crtc_helper_mode_valid, \ + .atomic_check = drm_sysfb_crtc_helper_atomic_check + +void drm_sysfb_crtc_reset(struct drm_crtc *crtc); +struct drm_crtc_state *drm_sysfb_crtc_atomic_duplicate_state(struct drm_crtc *crtc); +void drm_sysfb_crtc_atomic_destroy_state(struct drm_crtc *crtc, struct drm_crtc_state *crtc_state); + +#define DRM_SYSFB_CRTC_FUNCS \ + .reset = drm_sysfb_crtc_reset, \ + .set_config = drm_atomic_helper_set_config, \ + .page_flip = drm_atomic_helper_page_flip, \ + .atomic_duplicate_state = drm_sysfb_crtc_atomic_duplicate_state, \ + .atomic_destroy_state = drm_sysfb_crtc_atomic_destroy_state + +/* + * Connector + */ + +int drm_sysfb_connector_helper_get_modes(struct drm_connector *connector); + +#define DRM_SYSFB_CONNECTOR_HELPER_FUNCS \ + .get_modes = drm_sysfb_connector_helper_get_modes + +#define DRM_SYSFB_CONNECTOR_FUNCS \ + .reset = drm_atomic_helper_connector_reset, \ + .fill_modes = drm_helper_probe_single_connector_modes, \ + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, \ + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state + +/* + * Mode config + */ + +#define DRM_SYSFB_MODE_CONFIG_FUNCS \ + .fb_create = drm_gem_fb_create_with_dirty, \ + .atomic_check = drm_atomic_helper_check, \ + .atomic_commit = drm_atomic_helper_commit + +#endif diff --git a/drivers/gpu/drm/sysfb/drm_sysfb_modeset.c b/drivers/gpu/drm/sysfb/drm_sysfb_modeset.c new file mode 100644 index 000000000..d2de29caf --- /dev/null +++ b/drivers/gpu/drm/sysfb/drm_sysfb_modeset.c @@ -0,0 +1,608 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include <linux/export.h> +#include <linux/slab.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_damage_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_edid.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_panic.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +#include "drm_sysfb_helper.h" + +struct drm_display_mode drm_sysfb_mode(unsigned int width, + unsigned int height, + unsigned int width_mm, + unsigned int height_mm) +{ + /* + * Assume a monitor resolution of 96 dpi to + * get a somewhat reasonable screen size. + */ + if (!width_mm) + width_mm = DRM_MODE_RES_MM(width, 96ul); + if (!height_mm) + height_mm = DRM_MODE_RES_MM(height, 96ul); + + { + const struct drm_display_mode mode = { + DRM_MODE_INIT(60, width, height, width_mm, height_mm) + }; + + return mode; + } +} +EXPORT_SYMBOL(drm_sysfb_mode); + +/* + * Plane + */ + +static u32 to_nonalpha_fourcc(u32 fourcc) +{ + /* only handle formats with depth != 0 and alpha channel */ + switch (fourcc) { + case DRM_FORMAT_ARGB1555: + return DRM_FORMAT_XRGB1555; + case DRM_FORMAT_ABGR1555: + return DRM_FORMAT_XBGR1555; + case DRM_FORMAT_RGBA5551: + return DRM_FORMAT_RGBX5551; + case DRM_FORMAT_BGRA5551: + return DRM_FORMAT_BGRX5551; + case DRM_FORMAT_ARGB8888: + return DRM_FORMAT_XRGB8888; + case DRM_FORMAT_ABGR8888: + return DRM_FORMAT_XBGR8888; + case DRM_FORMAT_RGBA8888: + return DRM_FORMAT_RGBX8888; + case DRM_FORMAT_BGRA8888: + return DRM_FORMAT_BGRX8888; + case DRM_FORMAT_ARGB2101010: + return DRM_FORMAT_XRGB2101010; + case DRM_FORMAT_ABGR2101010: + return DRM_FORMAT_XBGR2101010; + case DRM_FORMAT_RGBA1010102: + return DRM_FORMAT_RGBX1010102; + case DRM_FORMAT_BGRA1010102: + return DRM_FORMAT_BGRX1010102; + } + + return fourcc; +} + +static bool is_listed_fourcc(const u32 *fourccs, size_t nfourccs, u32 fourcc) +{ + const u32 *fourccs_end = fourccs + nfourccs; + + while (fourccs < fourccs_end) { + if (*fourccs == fourcc) + return true; + ++fourccs; + } + return false; +} + +/** + * drm_sysfb_build_fourcc_list - Filters a list of supported color formats against + * the device's native formats + * @dev: DRM device + * @native_fourccs: 4CC codes of natively supported color formats + * @native_nfourccs: The number of entries in @native_fourccs + * @fourccs_out: Returns 4CC codes of supported color formats + * @nfourccs_out: The number of available entries in @fourccs_out + * + * This function create a list of supported color format from natively + * supported formats and additional emulated formats. + * At a minimum, most userspace programs expect at least support for + * XRGB8888 on the primary plane. Sysfb devices that have to emulate + * the format should use drm_sysfb_build_fourcc_list() to create a list + * of supported color formats. The returned list can be handed over to + * drm_universal_plane_init() et al. Native formats will go before + * emulated formats. Native formats with alpha channel will be replaced + * by equal formats without alpha channel, as primary planes usually + * don't support alpha. Other heuristics might be applied to optimize + * the sorting order. Formats near the beginning of the list are usually + * preferred over formats near the end of the list. + * + * Returns: + * The number of color-formats 4CC codes returned in @fourccs_out. + */ +size_t drm_sysfb_build_fourcc_list(struct drm_device *dev, + const u32 *native_fourccs, size_t native_nfourccs, + u32 *fourccs_out, size_t nfourccs_out) +{ + /* + * XRGB8888 is the default fallback format for most of userspace + * and it's currently the only format that should be emulated for + * the primary plane. Only if there's ever another default fallback, + * it should be added here. + */ + static const u32 extra_fourccs[] = { + DRM_FORMAT_XRGB8888, + }; + static const size_t extra_nfourccs = ARRAY_SIZE(extra_fourccs); + + u32 *fourccs = fourccs_out; + const u32 *fourccs_end = fourccs_out + nfourccs_out; + size_t i; + + /* + * The device's native formats go first. + */ + + for (i = 0; i < native_nfourccs; ++i) { + /* + * Several DTs, boot loaders and firmware report native + * alpha formats that are non-alpha formats instead. So + * replace alpha formats by non-alpha formats. + */ + u32 fourcc = to_nonalpha_fourcc(native_fourccs[i]); + + if (is_listed_fourcc(fourccs_out, fourccs - fourccs_out, fourcc)) { + continue; /* skip duplicate entries */ + } else if (fourccs == fourccs_end) { + drm_warn(dev, "Ignoring native format %p4cc\n", &fourcc); + continue; /* end of available output buffer */ + } + + drm_dbg_kms(dev, "adding native format %p4cc\n", &fourcc); + + *fourccs = fourcc; + ++fourccs; + } + + /* + * The extra formats, emulated by the driver, go second. + */ + + for (i = 0; (i < extra_nfourccs) && (fourccs < fourccs_end); ++i) { + u32 fourcc = extra_fourccs[i]; + + if (is_listed_fourcc(fourccs_out, fourccs - fourccs_out, fourcc)) { + continue; /* skip duplicate and native entries */ + } else if (fourccs == fourccs_end) { + drm_warn(dev, "Ignoring emulated format %p4cc\n", &fourcc); + continue; /* end of available output buffer */ + } + + drm_dbg_kms(dev, "adding emulated format %p4cc\n", &fourcc); + + *fourccs = fourcc; + ++fourccs; + } + + return fourccs - fourccs_out; +} +EXPORT_SYMBOL(drm_sysfb_build_fourcc_list); + +static void drm_sysfb_plane_state_destroy(struct drm_sysfb_plane_state *sysfb_plane_state) +{ + __drm_gem_destroy_shadow_plane_state(&sysfb_plane_state->base); + + kfree(sysfb_plane_state); +} + +static void drm_sysfb_memcpy(struct iosys_map *dst, const unsigned int *dst_pitch, + const struct iosys_map *src, const struct drm_framebuffer *fb, + const struct drm_rect *clip, struct drm_format_conv_state *state) +{ + drm_fb_memcpy(dst, dst_pitch, src, fb, clip); +} + +static drm_sysfb_blit_func drm_sysfb_get_blit_func(u32 dst_format, u32 src_format) +{ + if (src_format == dst_format) { + return drm_sysfb_memcpy; + } else if (src_format == DRM_FORMAT_XRGB8888) { + switch (dst_format) { + case DRM_FORMAT_RGB565: + return drm_fb_xrgb8888_to_rgb565; + case DRM_FORMAT_RGB565 | DRM_FORMAT_BIG_ENDIAN: + return drm_fb_xrgb8888_to_rgb565be; + case DRM_FORMAT_XRGB1555: + return drm_fb_xrgb8888_to_xrgb1555; + case DRM_FORMAT_ARGB1555: + return drm_fb_xrgb8888_to_argb1555; + case DRM_FORMAT_RGBA5551: + return drm_fb_xrgb8888_to_rgba5551; + case DRM_FORMAT_RGB888: + return drm_fb_xrgb8888_to_rgb888; + case DRM_FORMAT_BGR888: + return drm_fb_xrgb8888_to_bgr888; + case DRM_FORMAT_ARGB8888: + return drm_fb_xrgb8888_to_argb8888; + case DRM_FORMAT_XBGR8888: + return drm_fb_xrgb8888_to_xbgr8888; + case DRM_FORMAT_ABGR8888: + return drm_fb_xrgb8888_to_abgr8888; + case DRM_FORMAT_XRGB2101010: + return drm_fb_xrgb8888_to_xrgb2101010; + case DRM_FORMAT_ARGB2101010: + return drm_fb_xrgb8888_to_argb2101010; + case DRM_FORMAT_BGRX8888: + return drm_fb_xrgb8888_to_bgrx8888; + case DRM_FORMAT_RGB332: + return drm_fb_xrgb8888_to_rgb332; + } + } + + return NULL; +} + +int drm_sysfb_plane_helper_begin_fb_access(struct drm_plane *plane, + struct drm_plane_state *plane_state) +{ + struct drm_device *dev = plane->dev; + struct drm_sysfb_plane_state *sysfb_plane_state = to_drm_sysfb_plane_state(plane_state); + struct drm_framebuffer *fb = plane_state->fb; + struct drm_crtc_state *crtc_state; + struct drm_sysfb_crtc_state *sysfb_crtc_state; + drm_sysfb_blit_func blit_to_crtc; + int ret; + + ret = drm_gem_begin_shadow_fb_access(plane, plane_state); + if (ret) + return ret; + + if (!fb) + return 0; + + ret = -EINVAL; + + crtc_state = drm_atomic_get_new_crtc_state(plane_state->state, plane_state->crtc); + if (drm_WARN_ON_ONCE(dev, !crtc_state)) + goto err_drm_gem_end_shadow_fb_access; + sysfb_crtc_state = to_drm_sysfb_crtc_state(crtc_state); + + if (drm_WARN_ON_ONCE(dev, !sysfb_crtc_state->format)) + goto err_drm_gem_end_shadow_fb_access; + blit_to_crtc = drm_sysfb_get_blit_func(sysfb_crtc_state->format->format, + fb->format->format); + if (!blit_to_crtc) { + drm_warn_once(dev, "No blit helper from %p4cc to %p4cc found.\n", + &fb->format->format, &sysfb_crtc_state->format->format); + goto err_drm_gem_end_shadow_fb_access; + } + sysfb_plane_state->blit_to_crtc = blit_to_crtc; + + return 0; + +err_drm_gem_end_shadow_fb_access: + drm_gem_end_shadow_fb_access(plane, plane_state); + return ret; +} +EXPORT_SYMBOL(drm_sysfb_plane_helper_begin_fb_access); + +int drm_sysfb_plane_helper_atomic_check(struct drm_plane *plane, + struct drm_atomic_commit *new_state) +{ + struct drm_sysfb_device *sysfb = to_drm_sysfb_device(plane->dev); + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(new_state, plane); + struct drm_shadow_plane_state *new_shadow_plane_state = + to_drm_shadow_plane_state(new_plane_state); + struct drm_framebuffer *new_fb = new_plane_state->fb; + struct drm_crtc *new_crtc = new_plane_state->crtc; + struct drm_crtc_state *new_crtc_state = NULL; + struct drm_sysfb_crtc_state *new_sysfb_crtc_state; + int ret; + + if (new_crtc) + new_crtc_state = drm_atomic_get_new_crtc_state(new_state, new_plane_state->crtc); + + ret = drm_atomic_helper_check_plane_state(new_plane_state, new_crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + false, false); + if (ret) + return ret; + else if (!new_plane_state->visible) + return 0; + + new_crtc_state = drm_atomic_get_new_crtc_state(new_state, new_plane_state->crtc); + + new_sysfb_crtc_state = to_drm_sysfb_crtc_state(new_crtc_state); + new_sysfb_crtc_state->format = sysfb->fb_format; + + if (new_fb->format != new_sysfb_crtc_state->format) { + void *buf; + + /* format conversion necessary; reserve buffer */ + buf = drm_format_conv_state_reserve(&new_shadow_plane_state->fmtcnv_state, + sysfb->fb_pitch, GFP_KERNEL); + if (!buf) + return -ENOMEM; + } + + return 0; +} +EXPORT_SYMBOL(drm_sysfb_plane_helper_atomic_check); + +void drm_sysfb_plane_helper_atomic_update(struct drm_plane *plane, struct drm_atomic_commit *state) +{ + struct drm_device *dev = plane->dev; + struct drm_sysfb_device *sysfb = to_drm_sysfb_device(dev); + struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane); + struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane); + struct drm_sysfb_plane_state *sysfb_plane_state = to_drm_sysfb_plane_state(plane_state); + struct drm_shadow_plane_state *shadow_plane_state = &sysfb_plane_state->base; + struct drm_framebuffer *fb = plane_state->fb; + unsigned int dst_pitch = sysfb->fb_pitch; + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, plane_state->crtc); + struct drm_sysfb_crtc_state *sysfb_crtc_state = to_drm_sysfb_crtc_state(crtc_state); + const struct drm_format_info *dst_format = sysfb_crtc_state->format; + drm_sysfb_blit_func blit_to_crtc = sysfb_plane_state->blit_to_crtc; + struct drm_atomic_helper_damage_iter iter; + struct drm_rect damage; + int ret, idx; + + ret = drm_gem_fb_begin_cpu_access(fb, DMA_FROM_DEVICE); + if (ret) + return; + + if (!drm_dev_enter(dev, &idx)) + goto out_drm_gem_fb_end_cpu_access; + + drm_atomic_helper_damage_iter_init(&iter, old_plane_state, plane_state); + drm_atomic_for_each_plane_damage(&iter, &damage) { + struct iosys_map dst = sysfb->fb_addr; + struct drm_rect dst_clip = plane_state->dst; + + if (!drm_rect_intersect(&dst_clip, &damage)) + continue; + + iosys_map_incr(&dst, drm_fb_clip_offset(dst_pitch, dst_format, &dst_clip)); + blit_to_crtc(&dst, &dst_pitch, shadow_plane_state->data, fb, &damage, + &shadow_plane_state->fmtcnv_state); + } + + drm_dev_exit(idx); +out_drm_gem_fb_end_cpu_access: + drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE); +} +EXPORT_SYMBOL(drm_sysfb_plane_helper_atomic_update); + +void drm_sysfb_plane_helper_atomic_disable(struct drm_plane *plane, + struct drm_atomic_commit *state) +{ + struct drm_device *dev = plane->dev; + struct drm_sysfb_device *sysfb = to_drm_sysfb_device(dev); + struct iosys_map dst = sysfb->fb_addr; + struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane); + void __iomem *dst_vmap = dst.vaddr_iomem; /* TODO: Use mapping abstraction */ + unsigned int dst_pitch = sysfb->fb_pitch; + const struct drm_format_info *dst_format = sysfb->fb_format; + struct drm_rect dst_clip; + unsigned long lines, linepixels, i; + int idx; + + drm_rect_init(&dst_clip, + plane_state->src_x >> 16, plane_state->src_y >> 16, + plane_state->src_w >> 16, plane_state->src_h >> 16); + + lines = drm_rect_height(&dst_clip); + linepixels = drm_rect_width(&dst_clip); + + if (!drm_dev_enter(dev, &idx)) + return; + + /* Clear buffer to black if disabled */ + dst_vmap += drm_fb_clip_offset(dst_pitch, dst_format, &dst_clip); + for (i = 0; i < lines; ++i) { + memset_io(dst_vmap, 0, linepixels * dst_format->cpp[0]); + dst_vmap += dst_pitch; + } + + drm_dev_exit(idx); +} +EXPORT_SYMBOL(drm_sysfb_plane_helper_atomic_disable); + +int drm_sysfb_plane_helper_get_scanout_buffer(struct drm_plane *plane, + struct drm_scanout_buffer *sb) +{ + struct drm_sysfb_device *sysfb = to_drm_sysfb_device(plane->dev); + + sb->width = sysfb->fb_mode.hdisplay; + sb->height = sysfb->fb_mode.vdisplay; + sb->format = sysfb->fb_format; + sb->pitch[0] = sysfb->fb_pitch; + sb->map[0] = sysfb->fb_addr; + + return 0; +} +EXPORT_SYMBOL(drm_sysfb_plane_helper_get_scanout_buffer); + +void drm_sysfb_plane_reset(struct drm_plane *plane) +{ + struct drm_sysfb_plane_state *sysfb_plane_state; + + if (plane->state) + drm_sysfb_plane_state_destroy(to_drm_sysfb_plane_state(plane->state)); + + sysfb_plane_state = kzalloc_obj(*sysfb_plane_state); + if (sysfb_plane_state) + __drm_gem_reset_shadow_plane(plane, &sysfb_plane_state->base); + else + __drm_gem_reset_shadow_plane(plane, NULL); +} +EXPORT_SYMBOL(drm_sysfb_plane_reset); + +struct drm_plane_state *drm_sysfb_plane_atomic_duplicate_state(struct drm_plane *plane) +{ + struct drm_device *dev = plane->dev; + struct drm_plane_state *plane_state = plane->state; + struct drm_sysfb_plane_state *sysfb_plane_state; + struct drm_sysfb_plane_state *new_sysfb_plane_state; + struct drm_shadow_plane_state *new_shadow_plane_state; + + if (drm_WARN_ON(dev, !plane_state)) + return NULL; + sysfb_plane_state = to_drm_sysfb_plane_state(plane_state); + + new_sysfb_plane_state = kzalloc_obj(*new_sysfb_plane_state); + if (!new_sysfb_plane_state) + return NULL; + new_shadow_plane_state = &new_sysfb_plane_state->base; + + __drm_gem_duplicate_shadow_plane_state(plane, new_shadow_plane_state); + new_sysfb_plane_state->blit_to_crtc = sysfb_plane_state->blit_to_crtc; + + return &new_shadow_plane_state->base; +} +EXPORT_SYMBOL(drm_sysfb_plane_atomic_duplicate_state); + +void drm_sysfb_plane_atomic_destroy_state(struct drm_plane *plane, + struct drm_plane_state *plane_state) +{ + drm_sysfb_plane_state_destroy(to_drm_sysfb_plane_state(plane_state)); +} +EXPORT_SYMBOL(drm_sysfb_plane_atomic_destroy_state); + +/* + * CRTC + */ + +static void drm_sysfb_crtc_state_destroy(struct drm_sysfb_crtc_state *sysfb_crtc_state) +{ + __drm_atomic_helper_crtc_destroy_state(&sysfb_crtc_state->base); + + kfree(sysfb_crtc_state); +} + +enum drm_mode_status drm_sysfb_crtc_helper_mode_valid(struct drm_crtc *crtc, + const struct drm_display_mode *mode) +{ + struct drm_sysfb_device *sysfb = to_drm_sysfb_device(crtc->dev); + + return drm_crtc_helper_mode_valid_fixed(crtc, mode, &sysfb->fb_mode); +} +EXPORT_SYMBOL(drm_sysfb_crtc_helper_mode_valid); + +int drm_sysfb_crtc_helper_atomic_check(struct drm_crtc *crtc, struct drm_atomic_commit *new_state) +{ + struct drm_device *dev = crtc->dev; + struct drm_sysfb_device *sysfb = to_drm_sysfb_device(dev); + struct drm_crtc_state *new_crtc_state = drm_atomic_get_new_crtc_state(new_state, crtc); + int ret; + + if (!new_crtc_state->enable) + return 0; + + ret = drm_atomic_helper_check_crtc_primary_plane(new_crtc_state); + if (ret) + return ret; + + if (new_crtc_state->color_mgmt_changed) { + const size_t gamma_lut_length = + sysfb->fb_gamma_lut_size * sizeof(struct drm_color_lut); + const struct drm_property_blob *gamma_lut = new_crtc_state->gamma_lut; + + if (gamma_lut && (gamma_lut->length != gamma_lut_length)) { + drm_dbg(dev, "Incorrect gamma_lut length %zu\n", gamma_lut->length); + return -EINVAL; + } + } + + return 0; +} +EXPORT_SYMBOL(drm_sysfb_crtc_helper_atomic_check); + +void drm_sysfb_crtc_reset(struct drm_crtc *crtc) +{ + struct drm_sysfb_device *sysfb = to_drm_sysfb_device(crtc->dev); + struct drm_sysfb_crtc_state *sysfb_crtc_state; + + if (crtc->state) + drm_sysfb_crtc_state_destroy(to_drm_sysfb_crtc_state(crtc->state)); + + sysfb_crtc_state = kzalloc_obj(*sysfb_crtc_state); + if (sysfb_crtc_state) { + sysfb_crtc_state->format = sysfb->fb_format; + __drm_atomic_helper_crtc_reset(crtc, &sysfb_crtc_state->base); + } else { + __drm_atomic_helper_crtc_reset(crtc, NULL); + } +} +EXPORT_SYMBOL(drm_sysfb_crtc_reset); + +struct drm_crtc_state *drm_sysfb_crtc_atomic_duplicate_state(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct drm_crtc_state *crtc_state = crtc->state; + struct drm_sysfb_crtc_state *new_sysfb_crtc_state; + struct drm_sysfb_crtc_state *sysfb_crtc_state; + + if (drm_WARN_ON(dev, !crtc_state)) + return NULL; + + new_sysfb_crtc_state = kzalloc_obj(*new_sysfb_crtc_state); + if (!new_sysfb_crtc_state) + return NULL; + + sysfb_crtc_state = to_drm_sysfb_crtc_state(crtc_state); + + __drm_atomic_helper_crtc_duplicate_state(crtc, &new_sysfb_crtc_state->base); + new_sysfb_crtc_state->format = sysfb_crtc_state->format; + + return &new_sysfb_crtc_state->base; +} +EXPORT_SYMBOL(drm_sysfb_crtc_atomic_duplicate_state); + +void drm_sysfb_crtc_atomic_destroy_state(struct drm_crtc *crtc, struct drm_crtc_state *crtc_state) +{ + drm_sysfb_crtc_state_destroy(to_drm_sysfb_crtc_state(crtc_state)); +} +EXPORT_SYMBOL(drm_sysfb_crtc_atomic_destroy_state); + +/* + * Connector + */ + +static int drm_sysfb_get_edid_block(void *data, u8 *buf, unsigned int block, size_t len) +{ + struct drm_sysfb_device *sysfb = data; + const u8 *edid = sysfb->edid; + size_t off = block * EDID_LENGTH; + size_t end = off + len; + + if (!edid) + return -EINVAL; + if (end > EDID_LENGTH) + return -EINVAL; + memcpy(buf, &edid[off], len); + + /* + * We don't have EDID extensions available and reporting them + * will upset DRM helpers. Thus clear the extension field and + * update the checksum. Adding the extension flag to the checksum + * does this. + */ + buf[127] += buf[126]; + buf[126] = 0; + + return 0; +} + +int drm_sysfb_connector_helper_get_modes(struct drm_connector *connector) +{ + struct drm_sysfb_device *sysfb = to_drm_sysfb_device(connector->dev); + const struct drm_edid *drm_edid; + + if (sysfb->edid) { + drm_edid = drm_edid_read_custom(connector, drm_sysfb_get_edid_block, sysfb); + drm_edid_connector_update(connector, drm_edid); + drm_edid_free(drm_edid); + } + + /* Return the fixed mode even with EDID */ + return drm_connector_helper_get_modes_fixed(connector, &sysfb->fb_mode); +} +EXPORT_SYMBOL(drm_sysfb_connector_helper_get_modes); diff --git a/drivers/gpu/drm/sysfb/drm_sysfb_screen_info.c b/drivers/gpu/drm/sysfb/drm_sysfb_screen_info.c new file mode 100644 index 000000000..1c479ce0e --- /dev/null +++ b/drivers/gpu/drm/sysfb/drm_sysfb_screen_info.c @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include <linux/export.h> +#include <linux/limits.h> +#include <linux/math64.h> +#include <linux/minmax.h> +#include <linux/screen_info.h> + +#include <drm/drm_fourcc.h> +#include <drm/drm_print.h> + +#include "drm_sysfb_helper.h" + +static s64 drm_sysfb_get_validated_size0(struct drm_device *dev, const char *name, + u64 value, u64 max) +{ + if (!value) { + drm_warn(dev, "%s of 0 not allowed\n", name); + return -EINVAL; + } else if (value > min(max, S64_MAX)) { + drm_warn(dev, "%s of %llu exceeds maximum of %llu\n", name, value, max); + return -EINVAL; + } + return value; +} + +int drm_sysfb_get_width_si(struct drm_device *dev, const struct screen_info *si) +{ + return drm_sysfb_get_validated_int0(dev, "width", si->lfb_width, U16_MAX); +} +EXPORT_SYMBOL(drm_sysfb_get_width_si); + +int drm_sysfb_get_height_si(struct drm_device *dev, const struct screen_info *si) +{ + return drm_sysfb_get_validated_int0(dev, "height", si->lfb_height, U16_MAX); +} +EXPORT_SYMBOL(drm_sysfb_get_height_si); + +struct resource *drm_sysfb_get_memory_si(struct drm_device *dev, + const struct screen_info *si, + struct resource *res) +{ + ssize_t num; + + num = screen_info_resources(si, res, 1); + if (!num) { + drm_warn(dev, "memory resource not found\n"); + return NULL; + } + + return res; +} +EXPORT_SYMBOL(drm_sysfb_get_memory_si); + +int drm_sysfb_get_stride_si(struct drm_device *dev, const struct screen_info *si, + const struct drm_format_info *format, + unsigned int width, unsigned int height, u64 size) +{ + u64 lfb_linelength = si->lfb_linelength; + s64 stride; + + if (!lfb_linelength) + lfb_linelength = drm_format_info_min_pitch(format, 0, width); + + stride = drm_sysfb_get_validated_size0(dev, "stride", lfb_linelength, + div64_u64(size, height)); + if (stride < INT_MIN || stride > INT_MAX) + return -EINVAL; + + return (int)stride; /* stride or negative errno code */ +} +EXPORT_SYMBOL(drm_sysfb_get_stride_si); + +s64 drm_sysfb_get_visible_size_si(struct drm_device *dev, const struct screen_info *si, + unsigned int height, unsigned int stride, u64 size) +{ + u64 vsize = mul_u32_u32(height, stride); + + return drm_sysfb_get_validated_size0(dev, "visible size", vsize, size); +} +EXPORT_SYMBOL(drm_sysfb_get_visible_size_si); diff --git a/drivers/gpu/drm/sysfb/efidrm.c b/drivers/gpu/drm/sysfb/efidrm.c new file mode 100644 index 000000000..3f9cd5d03 --- /dev/null +++ b/drivers/gpu/drm/sysfb/efidrm.c @@ -0,0 +1,434 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include <linux/aperture.h> +#include <linux/efi.h> +#include <linux/limits.h> +#include <linux/platform_device.h> +#include <linux/sysfb.h> +#include <linux/pm.h> + +#include <drm/clients/drm_client_setup.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_connector.h> +#include <drm/drm_damage_helper.h> +#include <drm/drm_device.h> +#include <drm/drm_drv.h> +#include <drm/drm_edid.h> +#include <drm/drm_fbdev_shmem.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_atomic_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_gem_shmem_helper.h> +#include <drm/drm_managed.h> +#include <drm/drm_modeset_helper.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +#include <video/pixel_format.h> + +#include "drm_sysfb_helper.h" + +#define DRIVER_NAME "efidrm" +#define DRIVER_DESC "DRM driver for EFI platform devices" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 + +static const struct drm_format_info *efidrm_get_format_si(struct drm_device *dev, + const struct screen_info *si) +{ + static const struct drm_sysfb_format formats[] = { + { PIXEL_FORMAT_XRGB1555, DRM_FORMAT_XRGB1555, }, + { PIXEL_FORMAT_RGB565, DRM_FORMAT_RGB565, }, + { PIXEL_FORMAT_RGB888, DRM_FORMAT_RGB888, }, + { PIXEL_FORMAT_XRGB8888, DRM_FORMAT_XRGB8888, }, + { PIXEL_FORMAT_XBGR8888, DRM_FORMAT_XBGR8888, }, + { PIXEL_FORMAT_XRGB2101010, DRM_FORMAT_XRGB2101010, }, + }; + struct pixel_format pixel; + int ret; + + ret = screen_info_pixel_format(si, &pixel); + if (ret) + return NULL; + + return drm_sysfb_get_format(dev, formats, ARRAY_SIZE(formats), &pixel); +} + +static u64 efidrm_get_mem_flags(struct drm_device *dev, resource_size_t start, + resource_size_t len) +{ + u64 attribute = EFI_MEMORY_UC | EFI_MEMORY_WC | + EFI_MEMORY_WT | EFI_MEMORY_WB; + u64 mem_flags = EFI_MEMORY_WC | EFI_MEMORY_UC; + resource_size_t end = start + len; + efi_memory_desc_t md; + u64 md_end; + + if (!efi_enabled(EFI_MEMMAP) || efi_mem_desc_lookup(start, &md)) + goto out; + + md_end = md.phys_addr + (md.num_pages << EFI_PAGE_SHIFT); + if (end > md_end) + goto out; + + attribute &= md.attribute; + if (attribute) { + mem_flags |= EFI_MEMORY_WT | EFI_MEMORY_WB; + mem_flags &= attribute; + } + +out: + return mem_flags; +} + +/* + * EFI device + */ + +struct efidrm_device { + struct drm_sysfb_device sysfb; + + /* modesetting */ + u32 formats[DRM_SYSFB_PLANE_NFORMATS(1)]; + struct drm_plane primary_plane; + struct drm_crtc crtc; + struct drm_encoder encoder; + struct drm_connector connector; +}; + +/* + * Modesetting + */ + +static const u64 efidrm_primary_plane_format_modifiers[] = { + DRM_SYSFB_PLANE_FORMAT_MODIFIERS, +}; + +static const struct drm_plane_helper_funcs efidrm_primary_plane_helper_funcs = { + DRM_SYSFB_PLANE_HELPER_FUNCS, +}; + +static const struct drm_plane_funcs efidrm_primary_plane_funcs = { + DRM_SYSFB_PLANE_FUNCS, + .destroy = drm_plane_cleanup, +}; + +static const struct drm_crtc_helper_funcs efidrm_crtc_helper_funcs = { + DRM_SYSFB_CRTC_HELPER_FUNCS, +}; + +static const struct drm_crtc_funcs efidrm_crtc_funcs = { + DRM_SYSFB_CRTC_FUNCS, + .destroy = drm_crtc_cleanup, +}; + +static const struct drm_encoder_funcs efidrm_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +static const struct drm_connector_helper_funcs efidrm_connector_helper_funcs = { + DRM_SYSFB_CONNECTOR_HELPER_FUNCS, +}; + +static const struct drm_connector_funcs efidrm_connector_funcs = { + DRM_SYSFB_CONNECTOR_FUNCS, + .destroy = drm_connector_cleanup, +}; + +static const struct drm_mode_config_funcs efidrm_mode_config_funcs = { + DRM_SYSFB_MODE_CONFIG_FUNCS, +}; + +/* + * Init / Cleanup + */ + +static struct efidrm_device *efidrm_device_create(struct drm_driver *drv, + struct platform_device *pdev) +{ + const struct sysfb_display_info *dpy; + const struct screen_info *si; + const struct drm_format_info *format; + int width, height, stride; + s64 vsize; + u64 mem_flags; + struct resource resbuf; + struct resource *res; + struct efidrm_device *efi; + struct drm_sysfb_device *sysfb; + struct drm_device *dev; + struct resource *mem = NULL; + struct drm_plane *primary_plane; + struct drm_crtc *crtc; + struct drm_encoder *encoder; + struct drm_connector *connector; + unsigned long max_width, max_height; + size_t nformats; + int ret; + + dpy = dev_get_platdata(&pdev->dev); + if (!dpy) + return ERR_PTR(-ENODEV); + si = &dpy->screen; + + if (screen_info_video_type(si) != VIDEO_TYPE_EFI) + return ERR_PTR(-ENODEV); + + /* + * EFI DRM driver + */ + + efi = devm_drm_dev_alloc(&pdev->dev, drv, struct efidrm_device, sysfb.dev); + if (IS_ERR(efi)) + return ERR_CAST(efi); + sysfb = &efi->sysfb; + dev = &sysfb->dev; + platform_set_drvdata(pdev, dev); + + /* + * Hardware settings + */ + + format = efidrm_get_format_si(dev, si); + if (!format) + return ERR_PTR(-EINVAL); + width = drm_sysfb_get_width_si(dev, si); + if (width < 0) + return ERR_PTR(width); + height = drm_sysfb_get_height_si(dev, si); + if (height < 0) + return ERR_PTR(height); + res = drm_sysfb_get_memory_si(dev, si, &resbuf); + if (!res) + return ERR_PTR(-EINVAL); + stride = drm_sysfb_get_stride_si(dev, si, format, width, height, resource_size(res)); + if (stride < 0) + return ERR_PTR(stride); + vsize = drm_sysfb_get_visible_size_si(dev, si, height, stride, resource_size(res)); + if (vsize < 0) + return ERR_PTR(vsize); + + drm_dbg(dev, "framebuffer format=%p4cc, size=%dx%d, stride=%d bytes\n", + &format->format, width, height, stride); + +#if defined(CONFIG_FIRMWARE_EDID) + if (drm_edid_header_is_valid(dpy->edid.dummy) == 8) + sysfb->edid = dpy->edid.dummy; +#endif + sysfb->fb_mode = drm_sysfb_mode(width, height, 0, 0); + sysfb->fb_format = format; + sysfb->fb_pitch = stride; + + /* + * Memory management + */ + + ret = devm_aperture_acquire_for_platform_device(pdev, res->start, vsize); + if (ret) { + drm_err(dev, "could not acquire memory range %pr: %d\n", res, ret); + return ERR_PTR(ret); + } + + drm_dbg(dev, "using I/O memory framebuffer at %pr\n", res); + + mem = devm_request_mem_region(&pdev->dev, res->start, vsize, drv->name); + if (!mem) { + /* + * We cannot make this fatal. Sometimes this comes from magic + * spaces our resource handlers simply don't know about. Use + * the I/O-memory resource as-is and try to map that instead. + */ + drm_warn(dev, "could not acquire memory region %pr\n", res); + mem = res; + } + + mem_flags = efidrm_get_mem_flags(dev, res->start, vsize); + + if (mem_flags & EFI_MEMORY_WC) { + void __iomem *screen_base = devm_ioremap_wc(&pdev->dev, mem->start, + resource_size(mem)); + + if (!screen_base) + return ERR_PTR(-ENXIO); + iosys_map_set_vaddr_iomem(&sysfb->fb_addr, screen_base); + } else if (mem_flags & EFI_MEMORY_UC) { + void __iomem *screen_base = devm_ioremap(&pdev->dev, mem->start, + resource_size(mem)); + + if (!screen_base) + return ERR_PTR(-ENXIO); + iosys_map_set_vaddr_iomem(&sysfb->fb_addr, screen_base); + } else if (mem_flags & EFI_MEMORY_WT) { + void *screen_base = devm_memremap(&pdev->dev, mem->start, + resource_size(mem), MEMREMAP_WT); + + if (IS_ERR(screen_base)) + return ERR_CAST(screen_base); + iosys_map_set_vaddr(&sysfb->fb_addr, screen_base); + } else if (mem_flags & EFI_MEMORY_WB) { + void *screen_base = devm_memremap(&pdev->dev, mem->start, + resource_size(mem), MEMREMAP_WB); + + if (IS_ERR(screen_base)) + return ERR_CAST(screen_base); + iosys_map_set_vaddr(&sysfb->fb_addr, screen_base); + } else { + drm_err(dev, "invalid mem_flags: 0x%llx\n", mem_flags); + return ERR_PTR(-EINVAL); + } + + /* + * Modesetting + */ + + ret = drmm_mode_config_init(dev); + if (ret) + return ERR_PTR(ret); + + max_width = max_t(unsigned long, width, DRM_SHADOW_PLANE_MAX_WIDTH); + max_height = max_t(unsigned long, height, DRM_SHADOW_PLANE_MAX_HEIGHT); + + dev->mode_config.min_width = width; + dev->mode_config.max_width = max_width; + dev->mode_config.min_height = height; + dev->mode_config.max_height = max_height; + dev->mode_config.preferred_depth = format->depth; + dev->mode_config.funcs = &efidrm_mode_config_funcs; + + /* Primary plane */ + + nformats = drm_sysfb_build_fourcc_list(dev, &format->format, 1, + efi->formats, ARRAY_SIZE(efi->formats)); + + primary_plane = &efi->primary_plane; + ret = drm_universal_plane_init(dev, primary_plane, 0, &efidrm_primary_plane_funcs, + efi->formats, nformats, + efidrm_primary_plane_format_modifiers, + DRM_PLANE_TYPE_PRIMARY, NULL); + if (ret) + return ERR_PTR(ret); + drm_plane_helper_add(primary_plane, &efidrm_primary_plane_helper_funcs); + drm_plane_enable_fb_damage_clips(primary_plane); + + /* CRTC */ + + crtc = &efi->crtc; + ret = drm_crtc_init_with_planes(dev, crtc, primary_plane, NULL, + &efidrm_crtc_funcs, NULL); + if (ret) + return ERR_PTR(ret); + drm_crtc_helper_add(crtc, &efidrm_crtc_helper_funcs); + + /* Encoder */ + + encoder = &efi->encoder; + ret = drm_encoder_init(dev, encoder, &efidrm_encoder_funcs, + DRM_MODE_ENCODER_NONE, NULL); + if (ret) + return ERR_PTR(ret); + encoder->possible_crtcs = drm_crtc_mask(crtc); + + /* Connector */ + + connector = &efi->connector; + ret = drm_connector_init(dev, connector, &efidrm_connector_funcs, + DRM_MODE_CONNECTOR_Unknown); + if (ret) + return ERR_PTR(ret); + drm_connector_helper_add(connector, &efidrm_connector_helper_funcs); + drm_connector_set_panel_orientation_with_quirk(connector, + DRM_MODE_PANEL_ORIENTATION_UNKNOWN, + width, height); + if (sysfb->edid) + drm_connector_attach_edid_property(connector); + + ret = drm_connector_attach_encoder(connector, encoder); + if (ret) + return ERR_PTR(ret); + + drm_mode_config_reset(dev); + + return efi; +} + +/* + * DRM driver + */ + +DEFINE_DRM_GEM_FOPS(efidrm_fops); + +static struct drm_driver efidrm_driver = { + DRM_GEM_SHMEM_DRIVER_OPS, + DRM_FBDEV_SHMEM_DRIVER_OPS, + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, + .driver_features = DRIVER_ATOMIC | DRIVER_GEM | DRIVER_MODESET, + .fops = &efidrm_fops, +}; + +/* + * Platform driver + */ + +static int efidrm_pm_suspend(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + + return drm_mode_config_helper_suspend(drm); +} + +static int efidrm_pm_resume(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + + return drm_mode_config_helper_resume(drm); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(efidrm_pm_ops, efidrm_pm_suspend, efidrm_pm_resume); + +static int efidrm_probe(struct platform_device *pdev) +{ + struct efidrm_device *efi; + struct drm_sysfb_device *sysfb; + struct drm_device *dev; + int ret; + + efi = efidrm_device_create(&efidrm_driver, pdev); + if (IS_ERR(efi)) + return PTR_ERR(efi); + sysfb = &efi->sysfb; + dev = &sysfb->dev; + + ret = drm_dev_register(dev, 0); + if (ret) + return ret; + + drm_client_setup(dev, sysfb->fb_format); + + return 0; +} + +static void efidrm_remove(struct platform_device *pdev) +{ + struct drm_device *dev = platform_get_drvdata(pdev); + + drm_dev_unplug(dev); +} + +static struct platform_driver efidrm_platform_driver = { + .driver = { + .name = "efi-framebuffer", + .pm = pm_sleep_ptr(&efidrm_pm_ops), + }, + .probe = efidrm_probe, + .remove = efidrm_remove, +}; + +module_platform_driver(efidrm_platform_driver); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sysfb/ofdrm.c b/drivers/gpu/drm/sysfb/ofdrm.c new file mode 100644 index 000000000..819aed466 --- /dev/null +++ b/drivers/gpu/drm/sysfb/ofdrm.c @@ -0,0 +1,1166 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include <linux/aperture.h> +#include <linux/of_address.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/pm.h> + +#include <drm/clients/drm_client_setup.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_color_mgmt.h> +#include <drm/drm_connector.h> +#include <drm/drm_damage_helper.h> +#include <drm/drm_device.h> +#include <drm/drm_drv.h> +#include <drm/drm_edid.h> +#include <drm/drm_fbdev_shmem.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_atomic_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_gem_shmem_helper.h> +#include <drm/drm_managed.h> +#include <drm/drm_modeset_helper.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +#include "drm_sysfb_helper.h" + +#define DRIVER_NAME "ofdrm" +#define DRIVER_DESC "DRM driver for OF platform devices" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 + +#define PCI_VENDOR_ID_ATI_R520 0x7100 +#define PCI_VENDOR_ID_ATI_R600 0x9400 + +#define OFDRM_GAMMA_LUT_SIZE 256 + +/* Definitions used by the Avivo palette */ +#define AVIVO_DC_LUT_RW_SELECT 0x6480 +#define AVIVO_DC_LUT_RW_MODE 0x6484 +#define AVIVO_DC_LUT_RW_INDEX 0x6488 +#define AVIVO_DC_LUT_SEQ_COLOR 0x648c +#define AVIVO_DC_LUT_PWL_DATA 0x6490 +#define AVIVO_DC_LUT_30_COLOR 0x6494 +#define AVIVO_DC_LUT_READ_PIPE_SELECT 0x6498 +#define AVIVO_DC_LUT_WRITE_EN_MASK 0x649c +#define AVIVO_DC_LUT_AUTOFILL 0x64a0 +#define AVIVO_DC_LUTA_CONTROL 0x64c0 +#define AVIVO_DC_LUTA_BLACK_OFFSET_BLUE 0x64c4 +#define AVIVO_DC_LUTA_BLACK_OFFSET_GREEN 0x64c8 +#define AVIVO_DC_LUTA_BLACK_OFFSET_RED 0x64cc +#define AVIVO_DC_LUTA_WHITE_OFFSET_BLUE 0x64d0 +#define AVIVO_DC_LUTA_WHITE_OFFSET_GREEN 0x64d4 +#define AVIVO_DC_LUTA_WHITE_OFFSET_RED 0x64d8 +#define AVIVO_DC_LUTB_CONTROL 0x6cc0 +#define AVIVO_DC_LUTB_BLACK_OFFSET_BLUE 0x6cc4 +#define AVIVO_DC_LUTB_BLACK_OFFSET_GREEN 0x6cc8 +#define AVIVO_DC_LUTB_BLACK_OFFSET_RED 0x6ccc +#define AVIVO_DC_LUTB_WHITE_OFFSET_BLUE 0x6cd0 +#define AVIVO_DC_LUTB_WHITE_OFFSET_GREEN 0x6cd4 +#define AVIVO_DC_LUTB_WHITE_OFFSET_RED 0x6cd8 + +enum ofdrm_model { + OFDRM_MODEL_UNKNOWN, + OFDRM_MODEL_MACH64, /* ATI Mach64 */ + OFDRM_MODEL_RAGE128, /* ATI Rage128 */ + OFDRM_MODEL_RAGE_M3A, /* ATI Rage Mobility M3 Head A */ + OFDRM_MODEL_RAGE_M3B, /* ATI Rage Mobility M3 Head B */ + OFDRM_MODEL_RADEON, /* ATI Radeon */ + OFDRM_MODEL_GXT2000, /* IBM GXT2000 */ + OFDRM_MODEL_AVIVO, /* ATI R5xx */ + OFDRM_MODEL_QEMU, /* QEMU VGA */ +}; + +/* + * Helpers for display nodes + */ + +static int display_get_validated_int(struct drm_device *dev, const char *name, uint32_t value) +{ + return drm_sysfb_get_validated_int(dev, name, value, INT_MAX); +} + +static int display_get_validated_int0(struct drm_device *dev, const char *name, uint32_t value) +{ + return drm_sysfb_get_validated_int0(dev, name, value, INT_MAX); +} + +static const struct drm_format_info *display_get_validated_format(struct drm_device *dev, + u32 depth, bool big_endian) +{ + const struct drm_format_info *info; + u32 format; + + switch (depth) { + case 8: + format = drm_mode_legacy_fb_format(8, 8); + break; + case 15: + case 16: + format = drm_mode_legacy_fb_format(16, depth); + break; + case 32: + format = drm_mode_legacy_fb_format(32, 24); + break; + default: + drm_err(dev, "unsupported framebuffer depth %u\n", depth); + return ERR_PTR(-EINVAL); + } + + /* + * DRM formats assume little-endian byte order. Update the format + * if the scanout buffer uses big-endian ordering. + */ + if (big_endian) { + switch (format) { + case DRM_FORMAT_XRGB8888: + format = DRM_FORMAT_BGRX8888; + break; + case DRM_FORMAT_ARGB8888: + format = DRM_FORMAT_BGRA8888; + break; + case DRM_FORMAT_RGB565: + format = DRM_FORMAT_RGB565 | DRM_FORMAT_BIG_ENDIAN; + break; + case DRM_FORMAT_XRGB1555: + format = DRM_FORMAT_XRGB1555 | DRM_FORMAT_BIG_ENDIAN; + break; + default: + break; + } + } + + info = drm_format_info(format); + if (!info) { + drm_err(dev, "cannot find framebuffer format for depth %u\n", depth); + return ERR_PTR(-EINVAL); + } + + return info; +} + +static int display_read_u32_of(struct drm_device *dev, struct device_node *of_node, + const char *name, u32 *value) +{ + int ret = of_property_read_u32(of_node, name, value); + + if (ret) + drm_err(dev, "cannot parse framebuffer %s: error %d\n", name, ret); + return ret; +} + +static bool display_get_big_endian_of(struct drm_device *dev, struct device_node *of_node) +{ + bool big_endian; + +#ifdef __BIG_ENDIAN + big_endian = !of_property_read_bool(of_node, "little-endian"); +#else + big_endian = of_property_read_bool(of_node, "big-endian"); +#endif + + return big_endian; +} + +static int display_get_width_of(struct drm_device *dev, struct device_node *of_node) +{ + u32 width; + int ret = display_read_u32_of(dev, of_node, "width", &width); + + if (ret) + return ret; + return display_get_validated_int0(dev, "width", width); +} + +static int display_get_height_of(struct drm_device *dev, struct device_node *of_node) +{ + u32 height; + int ret = display_read_u32_of(dev, of_node, "height", &height); + + if (ret) + return ret; + return display_get_validated_int0(dev, "height", height); +} + +static int display_get_depth_of(struct drm_device *dev, struct device_node *of_node) +{ + u32 depth; + int ret = display_read_u32_of(dev, of_node, "depth", &depth); + + if (ret) + return ret; + return display_get_validated_int0(dev, "depth", depth); +} + +static int display_get_linebytes_of(struct drm_device *dev, struct device_node *of_node) +{ + u32 linebytes; + int ret = display_read_u32_of(dev, of_node, "linebytes", &linebytes); + + if (ret) + return ret; + return display_get_validated_int(dev, "linebytes", linebytes); +} + +static u64 display_get_address_of(struct drm_device *dev, struct device_node *of_node) +{ + u32 address; + int ret; + + /* + * Not all devices provide an address property, it's not + * a bug if this fails. The driver will try to find the + * framebuffer base address from the device's memory regions. + */ + ret = of_property_read_u32(of_node, "address", &address); + if (ret) + return OF_BAD_ADDR; + + return address; +} + +static const u8 *display_get_edid_of(struct drm_device *dev, struct device_node *of_node, + u8 buf[EDID_LENGTH]) +{ + int ret = of_property_read_u8_array(of_node, "EDID", buf, EDID_LENGTH); + + if (ret) + return NULL; + return buf; +} + +static bool is_avivo(u32 vendor, u32 device) +{ + /* This will match most R5xx */ + return (vendor == PCI_VENDOR_ID_ATI) && + ((device >= PCI_VENDOR_ID_ATI_R520 && device < 0x7800) || + (PCI_VENDOR_ID_ATI_R600 >= 0x9400)); +} + +static enum ofdrm_model display_get_model_of(struct drm_device *dev, struct device_node *of_node) +{ + enum ofdrm_model model = OFDRM_MODEL_UNKNOWN; + + if (of_node_name_prefix(of_node, "ATY,Rage128")) { + model = OFDRM_MODEL_RAGE128; + } else if (of_node_name_prefix(of_node, "ATY,RageM3pA") || + of_node_name_prefix(of_node, "ATY,RageM3p12A")) { + model = OFDRM_MODEL_RAGE_M3A; + } else if (of_node_name_prefix(of_node, "ATY,RageM3pB")) { + model = OFDRM_MODEL_RAGE_M3B; + } else if (of_node_name_prefix(of_node, "ATY,Rage6")) { + model = OFDRM_MODEL_RADEON; + } else if (of_node_name_prefix(of_node, "ATY,")) { + return OFDRM_MODEL_MACH64; + } else if (of_device_is_compatible(of_node, "pci1014,b7") || + of_device_is_compatible(of_node, "pci1014,21c")) { + model = OFDRM_MODEL_GXT2000; + } else if (of_node_name_prefix(of_node, "vga,Display-")) { + struct device_node *of_parent; + const __be32 *vendor_p, *device_p; + + /* Look for AVIVO initialized by SLOF */ + of_parent = of_get_parent(of_node); + vendor_p = of_get_property(of_parent, "vendor-id", NULL); + device_p = of_get_property(of_parent, "device-id", NULL); + if (vendor_p && device_p) { + u32 vendor = be32_to_cpup(vendor_p); + u32 device = be32_to_cpup(device_p); + + if (is_avivo(vendor, device)) + model = OFDRM_MODEL_AVIVO; + } + of_node_put(of_parent); + } else if (of_device_is_compatible(of_node, "qemu,std-vga")) { + model = OFDRM_MODEL_QEMU; + } + + return model; +} + +/* + * Open Firmware display device + */ + +struct ofdrm_device; + +struct ofdrm_device_funcs { + void __iomem *(*cmap_ioremap)(struct ofdrm_device *odev, + struct device_node *of_node, + u64 fb_bas); + void (*cmap_write)(struct ofdrm_device *odev, unsigned char index, + unsigned char r, unsigned char g, unsigned char b); +}; + +struct ofdrm_device { + struct drm_sysfb_device sysfb; + + const struct ofdrm_device_funcs *funcs; + + /* colormap */ + void __iomem *cmap_base; + + u8 edid[EDID_LENGTH]; + + /* modesetting */ + u32 formats[DRM_SYSFB_PLANE_NFORMATS(1)]; + struct drm_plane primary_plane; + struct drm_crtc crtc; + struct drm_encoder encoder; + struct drm_connector connector; +}; + +static struct ofdrm_device *ofdrm_device_of_dev(struct drm_device *dev) +{ + return container_of(to_drm_sysfb_device(dev), struct ofdrm_device, sysfb); +} + +/* + * Hardware + */ + +#if defined(CONFIG_PCI) +static struct pci_dev *display_get_pci_dev_of(struct drm_device *dev, struct device_node *of_node) +{ + const __be32 *vendor_p, *device_p; + u32 vendor, device; + struct pci_dev *pcidev; + + vendor_p = of_get_property(of_node, "vendor-id", NULL); + if (!vendor_p) + return ERR_PTR(-ENODEV); + vendor = be32_to_cpup(vendor_p); + + device_p = of_get_property(of_node, "device-id", NULL); + if (!device_p) + return ERR_PTR(-ENODEV); + device = be32_to_cpup(device_p); + + pcidev = pci_get_device(vendor, device, NULL); + if (!pcidev) + return ERR_PTR(-ENODEV); + + return pcidev; +} + +static void ofdrm_pci_release(void *data) +{ + struct pci_dev *pcidev = data; + + pci_disable_device(pcidev); + pci_dev_put(pcidev); +} + +static int ofdrm_device_init_pci(struct ofdrm_device *odev) +{ + struct drm_device *dev = &odev->sysfb.dev; + struct platform_device *pdev = to_platform_device(dev->dev); + struct device_node *of_node = pdev->dev.of_node; + struct pci_dev *pcidev; + int ret; + + /* + * Never use pcim_ or other managed helpers on the returned PCI + * device. Otherwise, probing the native driver will fail for + * resource conflicts. PCI-device management has to be tied to + * the lifetime of the platform device until the native driver + * takes over. + */ + pcidev = display_get_pci_dev_of(dev, of_node); + if (IS_ERR(pcidev)) + return 0; /* no PCI device found; ignore the error */ + + ret = pci_enable_device(pcidev); + if (ret) { + drm_err(dev, "pci_enable_device(%s) failed: %d\n", + dev_name(&pcidev->dev), ret); + pci_dev_put(pcidev); + return ret; + } + ret = devm_add_action_or_reset(&pdev->dev, ofdrm_pci_release, pcidev); + if (ret) + return ret; + + return 0; +} +#else +static int ofdrm_device_init_pci(struct ofdrm_device *odev) +{ + return 0; +} +#endif + +/* + * OF display settings + */ + +static struct resource *ofdrm_find_fb_resource(struct ofdrm_device *odev, + struct resource *fb_res) +{ + struct platform_device *pdev = to_platform_device(odev->sysfb.dev.dev); + struct resource *res, *max_res = NULL; + u32 i; + + for (i = 0; pdev->num_resources; ++i) { + res = platform_get_resource(pdev, IORESOURCE_MEM, i); + if (!res) + break; /* all resources processed */ + if (resource_size(res) < resource_size(fb_res)) + continue; /* resource too small */ + if (fb_res->start && resource_contains(res, fb_res)) + return res; /* resource contains framebuffer */ + if (!max_res || resource_size(res) > resource_size(max_res)) + max_res = res; /* store largest resource as fallback */ + } + + return max_res; +} + +/* + * Colormap / Palette + */ + +static void __iomem *get_cmap_address_of(struct ofdrm_device *odev, struct device_node *of_node, + int bar_no, unsigned long offset, unsigned long size) +{ + struct drm_device *dev = &odev->sysfb.dev; + const __be32 *addr_p; + u64 max_size, address; + unsigned int flags; + void __iomem *mem; + + addr_p = of_get_pci_address(of_node, bar_no, &max_size, &flags); + if (!addr_p) + addr_p = of_get_address(of_node, bar_no, &max_size, &flags); + if (!addr_p) + return IOMEM_ERR_PTR(-ENODEV); + + if ((flags & (IORESOURCE_IO | IORESOURCE_MEM)) == 0) + return IOMEM_ERR_PTR(-ENODEV); + + if ((offset + size) >= max_size) + return IOMEM_ERR_PTR(-ENODEV); + + address = of_translate_address(of_node, addr_p); + if (address == OF_BAD_ADDR) + return IOMEM_ERR_PTR(-ENODEV); + + mem = devm_ioremap(dev->dev, address + offset, size); + if (!mem) + return IOMEM_ERR_PTR(-ENOMEM); + + return mem; +} + +static void __iomem *ofdrm_mach64_cmap_ioremap(struct ofdrm_device *odev, + struct device_node *of_node, + u64 fb_base) +{ + struct drm_device *dev = &odev->sysfb.dev; + u64 address; + void __iomem *cmap_base; + + address = fb_base & 0xff000000ul; + address += 0x7ff000; + + cmap_base = devm_ioremap(dev->dev, address, 0x1000); + if (!cmap_base) + return IOMEM_ERR_PTR(-ENOMEM); + + return cmap_base; +} + +static void ofdrm_mach64_cmap_write(struct ofdrm_device *odev, unsigned char index, + unsigned char r, unsigned char g, unsigned char b) +{ + void __iomem *addr = odev->cmap_base + 0xcc0; + void __iomem *data = odev->cmap_base + 0xcc0 + 1; + + writeb(index, addr); + writeb(r, data); + writeb(g, data); + writeb(b, data); +} + +static void __iomem *ofdrm_rage128_cmap_ioremap(struct ofdrm_device *odev, + struct device_node *of_node, + u64 fb_base) +{ + return get_cmap_address_of(odev, of_node, 2, 0, 0x1fff); +} + +static void ofdrm_rage128_cmap_write(struct ofdrm_device *odev, unsigned char index, + unsigned char r, unsigned char g, unsigned char b) +{ + void __iomem *addr = odev->cmap_base + 0xb0; + void __iomem *data = odev->cmap_base + 0xb4; + u32 color = (r << 16) | (g << 8) | b; + + writeb(index, addr); + writel(color, data); +} + +static void __iomem *ofdrm_rage_m3a_cmap_ioremap(struct ofdrm_device *odev, + struct device_node *of_node, + u64 fb_base) +{ + return get_cmap_address_of(odev, of_node, 2, 0, 0x1fff); +} + +static void ofdrm_rage_m3a_cmap_write(struct ofdrm_device *odev, unsigned char index, + unsigned char r, unsigned char g, unsigned char b) +{ + void __iomem *dac_ctl = odev->cmap_base + 0x58; + void __iomem *addr = odev->cmap_base + 0xb0; + void __iomem *data = odev->cmap_base + 0xb4; + u32 color = (r << 16) | (g << 8) | b; + u32 val; + + /* Clear PALETTE_ACCESS_CNTL in DAC_CNTL */ + val = readl(dac_ctl); + val &= ~0x20; + writel(val, dac_ctl); + + /* Set color at palette index */ + writeb(index, addr); + writel(color, data); +} + +static void __iomem *ofdrm_rage_m3b_cmap_ioremap(struct ofdrm_device *odev, + struct device_node *of_node, + u64 fb_base) +{ + return get_cmap_address_of(odev, of_node, 2, 0, 0x1fff); +} + +static void ofdrm_rage_m3b_cmap_write(struct ofdrm_device *odev, unsigned char index, + unsigned char r, unsigned char g, unsigned char b) +{ + void __iomem *dac_ctl = odev->cmap_base + 0x58; + void __iomem *addr = odev->cmap_base + 0xb0; + void __iomem *data = odev->cmap_base + 0xb4; + u32 color = (r << 16) | (g << 8) | b; + u32 val; + + /* Set PALETTE_ACCESS_CNTL in DAC_CNTL */ + val = readl(dac_ctl); + val |= 0x20; + writel(val, dac_ctl); + + /* Set color at palette index */ + writeb(index, addr); + writel(color, data); +} + +static void __iomem *ofdrm_radeon_cmap_ioremap(struct ofdrm_device *odev, + struct device_node *of_node, + u64 fb_base) +{ + return get_cmap_address_of(odev, of_node, 1, 0, 0x1fff); +} + +static void __iomem *ofdrm_gxt2000_cmap_ioremap(struct ofdrm_device *odev, + struct device_node *of_node, + u64 fb_base) +{ + return get_cmap_address_of(odev, of_node, 0, 0x6000, 0x1000); +} + +static void ofdrm_gxt2000_cmap_write(struct ofdrm_device *odev, unsigned char index, + unsigned char r, unsigned char g, unsigned char b) +{ + void __iomem *data = ((unsigned int __iomem *)odev->cmap_base) + index; + u32 color = (r << 16) | (g << 8) | b; + + writel(color, data); +} + +static void __iomem *ofdrm_avivo_cmap_ioremap(struct ofdrm_device *odev, + struct device_node *of_node, + u64 fb_base) +{ + struct device_node *of_parent; + void __iomem *cmap_base; + + of_parent = of_get_parent(of_node); + cmap_base = get_cmap_address_of(odev, of_parent, 0, 0, 0x10000); + of_node_put(of_parent); + + return cmap_base; +} + +static void ofdrm_avivo_cmap_write(struct ofdrm_device *odev, unsigned char index, + unsigned char r, unsigned char g, unsigned char b) +{ + void __iomem *lutsel = odev->cmap_base + AVIVO_DC_LUT_RW_SELECT; + void __iomem *addr = odev->cmap_base + AVIVO_DC_LUT_RW_INDEX; + void __iomem *data = odev->cmap_base + AVIVO_DC_LUT_30_COLOR; + u32 color = (r << 22) | (g << 12) | (b << 2); + + /* Write to both LUTs for now */ + + writel(1, lutsel); + writeb(index, addr); + writel(color, data); + + writel(0, lutsel); + writeb(index, addr); + writel(color, data); +} + +static void __iomem *ofdrm_qemu_cmap_ioremap(struct ofdrm_device *odev, + struct device_node *of_node, + u64 fb_base) +{ + static const __be32 io_of_addr[3] = { + cpu_to_be32(0x01000000), + cpu_to_be32(0x00), + cpu_to_be32(0x00), + }; + + struct drm_device *dev = &odev->sysfb.dev; + u64 address; + void __iomem *cmap_base; + + address = of_translate_address(of_node, io_of_addr); + if (address == OF_BAD_ADDR) + return IOMEM_ERR_PTR(-ENODEV); + + cmap_base = devm_ioremap(dev->dev, address + 0x3c8, 2); + if (!cmap_base) + return IOMEM_ERR_PTR(-ENOMEM); + + return cmap_base; +} + +static void ofdrm_qemu_cmap_write(struct ofdrm_device *odev, unsigned char index, + unsigned char r, unsigned char g, unsigned char b) +{ + void __iomem *addr = odev->cmap_base; + void __iomem *data = odev->cmap_base + 1; + + writeb(index, addr); + writeb(r, data); + writeb(g, data); + writeb(b, data); +} + +static void ofdrm_set_gamma_lut(struct drm_crtc *crtc, unsigned int index, + u16 red, u16 green, u16 blue) +{ + struct drm_device *dev = crtc->dev; + struct ofdrm_device *odev = ofdrm_device_of_dev(dev); + u8 i8 = index & 0xff; + u8 r8 = red >> 8; + u8 g8 = green >> 8; + u8 b8 = blue >> 8; + + if (drm_WARN_ON_ONCE(dev, index != i8)) + return; /* driver bug */ + + odev->funcs->cmap_write(odev, i8, r8, g8, b8); +} + +static void ofdrm_device_fill_gamma(struct ofdrm_device *odev, + const struct drm_format_info *format) +{ + struct drm_device *dev = &odev->sysfb.dev; + struct drm_crtc *crtc = &odev->crtc; + + switch (format->format) { + case DRM_FORMAT_RGB565: + case DRM_FORMAT_RGB565 | DRM_FORMAT_BIG_ENDIAN: + drm_crtc_fill_gamma_565(crtc, ofdrm_set_gamma_lut); + break; + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_BGRX8888: + drm_crtc_fill_gamma_888(crtc, ofdrm_set_gamma_lut); + break; + default: + drm_warn_once(dev, "Unsupported format %p4cc for gamma correction\n", + &format->format); + break; + } +} + +static void ofdrm_device_load_gamma(struct ofdrm_device *odev, + const struct drm_format_info *format, + struct drm_color_lut *lut) +{ + struct drm_device *dev = &odev->sysfb.dev; + struct drm_crtc *crtc = &odev->crtc; + + switch (format->format) { + case DRM_FORMAT_RGB565: + case DRM_FORMAT_RGB565 | DRM_FORMAT_BIG_ENDIAN: + drm_crtc_load_gamma_565_from_888(crtc, lut, ofdrm_set_gamma_lut); + break; + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_BGRX8888: + drm_crtc_load_gamma_888(crtc, lut, ofdrm_set_gamma_lut); + break; + default: + drm_warn_once(dev, "Unsupported format %p4cc for gamma correction\n", + &format->format); + break; + } +} + +/* + * Modesetting + */ + +static const u64 ofdrm_primary_plane_format_modifiers[] = { + DRM_SYSFB_PLANE_FORMAT_MODIFIERS, +}; + +static const struct drm_plane_helper_funcs ofdrm_primary_plane_helper_funcs = { + DRM_SYSFB_PLANE_HELPER_FUNCS, +}; + +static const struct drm_plane_funcs ofdrm_primary_plane_funcs = { + DRM_SYSFB_PLANE_FUNCS, + .destroy = drm_plane_cleanup, +}; + +static void ofdrm_crtc_helper_atomic_flush(struct drm_crtc *crtc, struct drm_atomic_commit *state) +{ + struct ofdrm_device *odev = ofdrm_device_of_dev(crtc->dev); + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + struct drm_sysfb_crtc_state *sysfb_crtc_state = to_drm_sysfb_crtc_state(crtc_state); + + if (crtc_state->enable && crtc_state->color_mgmt_changed) { + const struct drm_format_info *format = sysfb_crtc_state->format; + + if (crtc_state->gamma_lut) + ofdrm_device_load_gamma(odev, format, crtc_state->gamma_lut->data); + else + ofdrm_device_fill_gamma(odev, format); + } +} + +static const struct drm_crtc_helper_funcs ofdrm_crtc_helper_funcs = { + DRM_SYSFB_CRTC_HELPER_FUNCS, + .atomic_flush = ofdrm_crtc_helper_atomic_flush, +}; + +static const struct drm_crtc_funcs ofdrm_crtc_funcs = { + DRM_SYSFB_CRTC_FUNCS, + .destroy = drm_crtc_cleanup, +}; + +static const struct drm_encoder_funcs ofdrm_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +static const struct drm_connector_helper_funcs ofdrm_connector_helper_funcs = { + DRM_SYSFB_CONNECTOR_HELPER_FUNCS, +}; + +static const struct drm_connector_funcs ofdrm_connector_funcs = { + DRM_SYSFB_CONNECTOR_FUNCS, + .destroy = drm_connector_cleanup, +}; + +static const struct drm_mode_config_funcs ofdrm_mode_config_funcs = { + DRM_SYSFB_MODE_CONFIG_FUNCS, +}; + +/* + * Init / Cleanup + */ + +static const struct ofdrm_device_funcs ofdrm_unknown_device_funcs = { +}; + +static const struct ofdrm_device_funcs ofdrm_mach64_device_funcs = { + .cmap_ioremap = ofdrm_mach64_cmap_ioremap, + .cmap_write = ofdrm_mach64_cmap_write, +}; + +static const struct ofdrm_device_funcs ofdrm_rage128_device_funcs = { + .cmap_ioremap = ofdrm_rage128_cmap_ioremap, + .cmap_write = ofdrm_rage128_cmap_write, +}; + +static const struct ofdrm_device_funcs ofdrm_rage_m3a_device_funcs = { + .cmap_ioremap = ofdrm_rage_m3a_cmap_ioremap, + .cmap_write = ofdrm_rage_m3a_cmap_write, +}; + +static const struct ofdrm_device_funcs ofdrm_rage_m3b_device_funcs = { + .cmap_ioremap = ofdrm_rage_m3b_cmap_ioremap, + .cmap_write = ofdrm_rage_m3b_cmap_write, +}; + +static const struct ofdrm_device_funcs ofdrm_radeon_device_funcs = { + .cmap_ioremap = ofdrm_radeon_cmap_ioremap, + .cmap_write = ofdrm_rage128_cmap_write, /* same as Rage128 */ +}; + +static const struct ofdrm_device_funcs ofdrm_gxt2000_device_funcs = { + .cmap_ioremap = ofdrm_gxt2000_cmap_ioremap, + .cmap_write = ofdrm_gxt2000_cmap_write, +}; + +static const struct ofdrm_device_funcs ofdrm_avivo_device_funcs = { + .cmap_ioremap = ofdrm_avivo_cmap_ioremap, + .cmap_write = ofdrm_avivo_cmap_write, +}; + +static const struct ofdrm_device_funcs ofdrm_qemu_device_funcs = { + .cmap_ioremap = ofdrm_qemu_cmap_ioremap, + .cmap_write = ofdrm_qemu_cmap_write, +}; + +static struct ofdrm_device *ofdrm_device_create(struct drm_driver *drv, + struct platform_device *pdev) +{ + struct device_node *of_node = pdev->dev.of_node; + struct ofdrm_device *odev; + struct drm_sysfb_device *sysfb; + struct drm_device *dev; + enum ofdrm_model model; + bool big_endian; + int width, height, depth, linebytes; + const struct drm_format_info *format; + u64 address; + const u8 *edid; + resource_size_t fb_size, fb_base, fb_pgbase, fb_pgsize; + struct resource *res, *mem; + void __iomem *screen_base; + struct drm_plane *primary_plane; + struct drm_crtc *crtc; + struct drm_encoder *encoder; + struct drm_connector *connector; + unsigned long max_width, max_height; + size_t nformats; + int ret; + + odev = devm_drm_dev_alloc(&pdev->dev, drv, struct ofdrm_device, sysfb.dev); + if (IS_ERR(odev)) + return ERR_CAST(odev); + sysfb = &odev->sysfb; + dev = &sysfb->dev; + platform_set_drvdata(pdev, dev); + + ret = ofdrm_device_init_pci(odev); + if (ret) + return ERR_PTR(ret); + + /* + * OF display-node settings + */ + + model = display_get_model_of(dev, of_node); + drm_dbg(dev, "detected model %d\n", model); + + switch (model) { + case OFDRM_MODEL_UNKNOWN: + odev->funcs = &ofdrm_unknown_device_funcs; + break; + case OFDRM_MODEL_MACH64: + odev->funcs = &ofdrm_mach64_device_funcs; + break; + case OFDRM_MODEL_RAGE128: + odev->funcs = &ofdrm_rage128_device_funcs; + break; + case OFDRM_MODEL_RAGE_M3A: + odev->funcs = &ofdrm_rage_m3a_device_funcs; + break; + case OFDRM_MODEL_RAGE_M3B: + odev->funcs = &ofdrm_rage_m3b_device_funcs; + break; + case OFDRM_MODEL_RADEON: + odev->funcs = &ofdrm_radeon_device_funcs; + break; + case OFDRM_MODEL_GXT2000: + odev->funcs = &ofdrm_gxt2000_device_funcs; + break; + case OFDRM_MODEL_AVIVO: + odev->funcs = &ofdrm_avivo_device_funcs; + break; + case OFDRM_MODEL_QEMU: + odev->funcs = &ofdrm_qemu_device_funcs; + break; + } + + big_endian = display_get_big_endian_of(dev, of_node); + + width = display_get_width_of(dev, of_node); + if (width < 0) + return ERR_PTR(width); + height = display_get_height_of(dev, of_node); + if (height < 0) + return ERR_PTR(height); + depth = display_get_depth_of(dev, of_node); + if (depth < 0) + return ERR_PTR(depth); + linebytes = display_get_linebytes_of(dev, of_node); + if (linebytes < 0) + return ERR_PTR(linebytes); + + format = display_get_validated_format(dev, depth, big_endian); + if (IS_ERR(format)) + return ERR_CAST(format); + if (!linebytes) { + linebytes = drm_format_info_min_pitch(format, 0, width); + if (drm_WARN_ON(dev, !linebytes)) + return ERR_PTR(-EINVAL); + } + + fb_size = linebytes * height; + + /* + * Try to figure out the address of the framebuffer. Unfortunately, Open + * Firmware doesn't provide a standard way to do so. All we can do is a + * dodgy heuristic that happens to work in practice. + * + * On most machines, the "address" property contains what we need, though + * not on Matrox cards found in IBM machines. What appears to give good + * results is to go through the PCI ranges and pick one that encloses the + * "address" property. If none match, we pick the largest. + */ + address = display_get_address_of(dev, of_node); + if (address != OF_BAD_ADDR) { + struct resource fb_res = DEFINE_RES_MEM(address, fb_size); + + res = ofdrm_find_fb_resource(odev, &fb_res); + if (!res) + return ERR_PTR(-EINVAL); + if (resource_contains(res, &fb_res)) + fb_base = address; + else + fb_base = res->start; + } else { + struct resource fb_res = DEFINE_RES_MEM(0u, fb_size); + + res = ofdrm_find_fb_resource(odev, &fb_res); + if (!res) + return ERR_PTR(-EINVAL); + fb_base = res->start; + } + + /* + * I/O resources + */ + + fb_pgbase = round_down(fb_base, PAGE_SIZE); + fb_pgsize = fb_base - fb_pgbase + round_up(fb_size, PAGE_SIZE); + + ret = devm_aperture_acquire_for_platform_device(pdev, fb_pgbase, fb_pgsize); + if (ret) { + drm_err(dev, "could not acquire memory range %pr: error %d\n", &res, ret); + return ERR_PTR(ret); + } + + mem = devm_request_mem_region(&pdev->dev, fb_pgbase, fb_pgsize, drv->name); + if (!mem) { + drm_warn(dev, "could not acquire memory region %pr\n", &res); + return ERR_PTR(-ENOMEM); + } + + screen_base = devm_ioremap(&pdev->dev, mem->start, resource_size(mem)); + if (!screen_base) + return ERR_PTR(-ENOMEM); + + if (odev->funcs->cmap_ioremap) { + void __iomem *cmap_base = odev->funcs->cmap_ioremap(odev, of_node, fb_base); + + if (IS_ERR(cmap_base)) { + /* Don't fail; continue without colormap */ + drm_warn(dev, "could not find colormap: error %ld\n", PTR_ERR(cmap_base)); + } else { + odev->cmap_base = cmap_base; + } + } + + /* EDID is optional */ + edid = display_get_edid_of(dev, of_node, odev->edid); + + /* + * Firmware framebuffer + */ + + iosys_map_set_vaddr_iomem(&sysfb->fb_addr, screen_base); + sysfb->fb_mode = drm_sysfb_mode(width, height, 0, 0); + sysfb->fb_format = format; + sysfb->fb_pitch = linebytes; + if (odev->cmap_base) + sysfb->fb_gamma_lut_size = OFDRM_GAMMA_LUT_SIZE; + sysfb->edid = edid; + + drm_dbg(dev, "display mode={" DRM_MODE_FMT "}\n", DRM_MODE_ARG(&sysfb->fb_mode)); + drm_dbg(dev, "framebuffer format=%p4cc, size=%dx%d, linebytes=%d byte\n", + &format->format, width, height, linebytes); + + /* + * Mode-setting pipeline + */ + + ret = drmm_mode_config_init(dev); + if (ret) + return ERR_PTR(ret); + + max_width = max_t(unsigned long, width, DRM_SHADOW_PLANE_MAX_WIDTH); + max_height = max_t(unsigned long, height, DRM_SHADOW_PLANE_MAX_HEIGHT); + + dev->mode_config.min_width = width; + dev->mode_config.max_width = max_width; + dev->mode_config.min_height = height; + dev->mode_config.max_height = max_height; + dev->mode_config.funcs = &ofdrm_mode_config_funcs; + dev->mode_config.preferred_depth = format->depth; + dev->mode_config.quirk_addfb_prefer_host_byte_order = true; + + /* Primary plane */ + + nformats = drm_sysfb_build_fourcc_list(dev, &format->format, 1, + odev->formats, ARRAY_SIZE(odev->formats)); + + primary_plane = &odev->primary_plane; + ret = drm_universal_plane_init(dev, primary_plane, 0, &ofdrm_primary_plane_funcs, + odev->formats, nformats, + ofdrm_primary_plane_format_modifiers, + DRM_PLANE_TYPE_PRIMARY, NULL); + if (ret) + return ERR_PTR(ret); + drm_plane_helper_add(primary_plane, &ofdrm_primary_plane_helper_funcs); + drm_plane_enable_fb_damage_clips(primary_plane); + + /* CRTC */ + + crtc = &odev->crtc; + ret = drm_crtc_init_with_planes(dev, crtc, primary_plane, NULL, + &ofdrm_crtc_funcs, NULL); + if (ret) + return ERR_PTR(ret); + drm_crtc_helper_add(crtc, &ofdrm_crtc_helper_funcs); + + if (sysfb->fb_gamma_lut_size) { + ret = drm_mode_crtc_set_gamma_size(crtc, sysfb->fb_gamma_lut_size); + if (!ret) + drm_crtc_enable_color_mgmt(crtc, 0, false, sysfb->fb_gamma_lut_size); + } + + /* Encoder */ + + encoder = &odev->encoder; + ret = drm_encoder_init(dev, encoder, &ofdrm_encoder_funcs, DRM_MODE_ENCODER_NONE, NULL); + if (ret) + return ERR_PTR(ret); + encoder->possible_crtcs = drm_crtc_mask(crtc); + + /* Connector */ + + connector = &odev->connector; + ret = drm_connector_init(dev, connector, &ofdrm_connector_funcs, + DRM_MODE_CONNECTOR_Unknown); + if (ret) + return ERR_PTR(ret); + drm_connector_helper_add(connector, &ofdrm_connector_helper_funcs); + drm_connector_set_panel_orientation_with_quirk(connector, + DRM_MODE_PANEL_ORIENTATION_UNKNOWN, + width, height); + if (edid) + drm_connector_attach_edid_property(connector); + + ret = drm_connector_attach_encoder(connector, encoder); + if (ret) + return ERR_PTR(ret); + + drm_mode_config_reset(dev); + + return odev; +} + +/* + * DRM driver + */ + +DEFINE_DRM_GEM_FOPS(ofdrm_fops); + +static struct drm_driver ofdrm_driver = { + DRM_GEM_SHMEM_DRIVER_OPS, + DRM_FBDEV_SHMEM_DRIVER_OPS, + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, + .driver_features = DRIVER_ATOMIC | DRIVER_GEM | DRIVER_MODESET, + .fops = &ofdrm_fops, +}; + +/* + * Platform driver + */ + +static int ofdrm_pm_suspend(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + + return drm_mode_config_helper_suspend(drm); +} + +static int ofdrm_pm_resume(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + + return drm_mode_config_helper_resume(drm); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(ofdrm_pm_ops, ofdrm_pm_suspend, ofdrm_pm_resume); + +static int ofdrm_probe(struct platform_device *pdev) +{ + struct ofdrm_device *odev; + struct drm_sysfb_device *sysfb; + struct drm_device *dev; + int ret; + + odev = ofdrm_device_create(&ofdrm_driver, pdev); + if (IS_ERR(odev)) + return PTR_ERR(odev); + sysfb = &odev->sysfb; + dev = &sysfb->dev; + + ret = drm_dev_register(dev, 0); + if (ret) + return ret; + + drm_client_setup(dev, sysfb->fb_format); + + return 0; +} + +static void ofdrm_remove(struct platform_device *pdev) +{ + struct drm_device *dev = platform_get_drvdata(pdev); + + drm_dev_unplug(dev); +} + +static const struct of_device_id ofdrm_of_match_display[] = { + { .compatible = "display", }, + { }, +}; +MODULE_DEVICE_TABLE(of, ofdrm_of_match_display); + +static struct platform_driver ofdrm_platform_driver = { + .driver = { + .name = "of-display", + .of_match_table = ofdrm_of_match_display, + .pm = pm_sleep_ptr(&ofdrm_pm_ops), + }, + .probe = ofdrm_probe, + .remove = ofdrm_remove, +}; + +module_platform_driver(ofdrm_platform_driver); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sysfb/simpledrm.c b/drivers/gpu/drm/sysfb/simpledrm.c new file mode 100644 index 000000000..21ddf4502 --- /dev/null +++ b/drivers/gpu/drm/sysfb/simpledrm.c @@ -0,0 +1,972 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include <linux/aperture.h> +#include <linux/clk.h> +#include <linux/minmax.h> +#include <linux/of_address.h> +#include <linux/of_clk.h> +#include <linux/of_reserved_mem.h> +#include <linux/overflow.h> +#include <linux/platform_data/simplefb.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/pm_domain.h> +#include <linux/regulator/consumer.h> + +#include <drm/clients/drm_client_setup.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_connector.h> +#include <drm/drm_damage_helper.h> +#include <drm/drm_device.h> +#include <drm/drm_drv.h> +#include <drm/drm_fbdev_shmem.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_atomic_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_gem_shmem_helper.h> +#include <drm/drm_managed.h> +#include <drm/drm_modeset_helper.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_of.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +#include "drm_sysfb_helper.h" + +#define DRIVER_NAME "simpledrm" +#define DRIVER_DESC "DRM driver for simple-framebuffer platform devices" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 + +/* + * Helpers for simplefb + */ + +static const struct drm_format_info * +simplefb_get_validated_format(struct drm_device *dev, const char *format_name) +{ + static const struct simplefb_format formats[] = SIMPLEFB_FORMATS; + const struct simplefb_format *fmt = formats; + const struct simplefb_format *end = fmt + ARRAY_SIZE(formats); + const struct drm_format_info *info; + + if (!format_name) { + drm_err(dev, "simplefb: missing framebuffer format\n"); + return ERR_PTR(-EINVAL); + } + + while (fmt < end) { + if (!strcmp(format_name, fmt->name)) { + info = drm_format_info(fmt->fourcc); + if (!info) + return ERR_PTR(-EINVAL); + return info; + } + ++fmt; + } + + drm_err(dev, "simplefb: unknown framebuffer format %s\n", + format_name); + + return ERR_PTR(-EINVAL); +} + +static int +simplefb_get_width_pd(struct drm_device *dev, + const struct simplefb_platform_data *pd) +{ + return drm_sysfb_get_validated_int0(dev, "width", pd->width, U16_MAX); +} + +static int +simplefb_get_height_pd(struct drm_device *dev, + const struct simplefb_platform_data *pd) +{ + return drm_sysfb_get_validated_int0(dev, "height", pd->height, U16_MAX); +} + +static int +simplefb_get_stride_pd(struct drm_device *dev, + const struct simplefb_platform_data *pd) +{ + return drm_sysfb_get_validated_int(dev, "stride", pd->stride, INT_MAX); +} + +static const struct drm_format_info * +simplefb_get_format_pd(struct drm_device *dev, + const struct simplefb_platform_data *pd) +{ + return simplefb_get_validated_format(dev, pd->format); +} + +static int +simplefb_read_u32_of(struct drm_device *dev, struct device_node *of_node, + const char *name, u32 *value) +{ + int ret = of_property_read_u32(of_node, name, value); + + if (ret) + drm_err(dev, "simplefb: cannot parse framebuffer %s: error %d\n", + name, ret); + return ret; +} + +static int +simplefb_read_string_of(struct drm_device *dev, struct device_node *of_node, + const char *name, const char **value) +{ + int ret = of_property_read_string(of_node, name, value); + + if (ret) + drm_err(dev, "simplefb: cannot parse framebuffer %s: error %d\n", + name, ret); + return ret; +} + +static int +simplefb_get_width_of(struct drm_device *dev, struct device_node *of_node) +{ + u32 width; + int ret = simplefb_read_u32_of(dev, of_node, "width", &width); + + if (ret) + return ret; + return drm_sysfb_get_validated_int0(dev, "width", width, U16_MAX); +} + +static int +simplefb_get_height_of(struct drm_device *dev, struct device_node *of_node) +{ + u32 height; + int ret = simplefb_read_u32_of(dev, of_node, "height", &height); + + if (ret) + return ret; + return drm_sysfb_get_validated_int0(dev, "height", height, U16_MAX); +} + +static int +simplefb_get_stride_of(struct drm_device *dev, struct device_node *of_node) +{ + u32 stride; + int ret = simplefb_read_u32_of(dev, of_node, "stride", &stride); + + if (ret) + return ret; + return drm_sysfb_get_validated_int(dev, "stride", stride, INT_MAX); +} + +static const struct drm_format_info * +simplefb_get_format_of(struct drm_device *dev, struct device_node *of_node) +{ + const char *format; + int ret = simplefb_read_string_of(dev, of_node, "format", &format); + + if (ret) + return ERR_PTR(ret); + return simplefb_get_validated_format(dev, format); +} + +static struct resource * +simplefb_get_memory_of(struct drm_device *dev, struct device_node *of_node) +{ + struct resource r, *res; + int err; + + err = of_reserved_mem_region_to_resource(of_node, 0, &r); + if (err) + return NULL; + + res = devm_kmemdup(dev->dev, &r, sizeof(r), GFP_KERNEL); + if (!res) + return ERR_PTR(-ENOMEM); + + if (of_property_present(of_node, "reg")) + drm_warn(dev, "preferring \"memory-region\" over \"reg\" property\n"); + + return res; +} + +static int __simplefb_get_panel_size_mm_of(struct drm_device *dev, + struct device_node *of_panel_node, + const char *name) +{ + int ret; + u32 value; + + ret = of_property_read_u32(of_panel_node, name, &value); + if (ret) { + drm_dbg(dev, "simplefb: cannot parse panel %s: error %d\n", + name, ret); + return ret; + } else if (value > U16_MAX) { + drm_dbg(dev, "simplefb: panel %s of %u exceeds maximum value\n", + name, value); + return -EINVAL; + } + + return value; +} + +static int simplefb_get_panel_width_mm_of(struct drm_device *dev, + struct device_node *of_panel_node) +{ + return __simplefb_get_panel_size_mm_of(dev, of_panel_node, "width-mm"); +} + +static int simplefb_get_panel_height_mm_of(struct drm_device *dev, + struct device_node *of_panel_node) +{ + return __simplefb_get_panel_size_mm_of(dev, of_panel_node, "height-mm"); +} + +/* + * Simple Framebuffer device + */ + +struct simpledrm_device { + struct drm_sysfb_device sysfb; + + /* clocks */ +#if defined CONFIG_OF && defined CONFIG_COMMON_CLK + unsigned int clk_count; + struct clk **clks; +#endif + /* regulators */ +#if defined CONFIG_OF && defined CONFIG_REGULATOR + unsigned int regulator_count; + struct regulator **regulators; +#endif + /* power-domains */ +#if defined CONFIG_OF && defined CONFIG_PM_GENERIC_DOMAINS + int pwr_dom_count; + struct device **pwr_dom_devs; + struct device_link **pwr_dom_links; +#endif + + /* modesetting */ + u32 formats[DRM_SYSFB_PLANE_NFORMATS(1)]; + struct drm_plane primary_plane; + struct drm_crtc crtc; + struct drm_encoder encoder; + struct drm_connector connector; +}; + +/* + * Hardware + */ + +#if defined CONFIG_OF && defined CONFIG_COMMON_CLK +/* + * Clock handling code. + * + * Here we handle the clocks property of our "simple-framebuffer" dt node. + * This is necessary so that we can make sure that any clocks needed by + * the display engine that the bootloader set up for us (and for which it + * provided a simplefb dt node), stay up, for the life of the simplefb + * driver. + * + * When the driver unloads, we cleanly disable, and then release the clocks. + * + * We only complain about errors here, no action is taken as the most likely + * error can only happen due to a mismatch between the bootloader which set + * up simplefb, and the clock definitions in the device tree. Chances are + * that there are no adverse effects, and if there are, a clean teardown of + * the fb probe will not help us much either. So just complain and carry on, + * and hope that the user actually gets a working fb at the end of things. + */ + +static void simpledrm_device_release_clocks(void *res) +{ + struct simpledrm_device *sdev = res; + unsigned int i; + + for (i = 0; i < sdev->clk_count; ++i) { + if (sdev->clks[i]) { + clk_disable_unprepare(sdev->clks[i]); + clk_put(sdev->clks[i]); + } + } +} + +static int simpledrm_device_init_clocks(struct simpledrm_device *sdev) +{ + struct drm_device *dev = &sdev->sysfb.dev; + struct platform_device *pdev = to_platform_device(dev->dev); + struct device_node *of_node = pdev->dev.of_node; + struct clk *clock; + unsigned int i; + int ret; + + if (dev_get_platdata(&pdev->dev) || !of_node) + return 0; + + sdev->clk_count = of_clk_get_parent_count(of_node); + if (!sdev->clk_count) + return 0; + + sdev->clks = drmm_kzalloc(dev, sdev->clk_count * sizeof(sdev->clks[0]), + GFP_KERNEL); + if (!sdev->clks) + return -ENOMEM; + + for (i = 0; i < sdev->clk_count; ++i) { + clock = of_clk_get(of_node, i); + if (IS_ERR(clock)) { + ret = PTR_ERR(clock); + if (ret == -EPROBE_DEFER) + goto err; + drm_err(dev, "clock %u not found: %d\n", i, ret); + continue; + } + ret = clk_prepare_enable(clock); + if (ret) { + drm_err(dev, "failed to enable clock %u: %d\n", + i, ret); + clk_put(clock); + continue; + } + sdev->clks[i] = clock; + } + + return devm_add_action_or_reset(&pdev->dev, + simpledrm_device_release_clocks, + sdev); + +err: + while (i) { + --i; + if (sdev->clks[i]) { + clk_disable_unprepare(sdev->clks[i]); + clk_put(sdev->clks[i]); + } + } + return ret; +} +#else +static int simpledrm_device_init_clocks(struct simpledrm_device *sdev) +{ + return 0; +} +#endif + +#if defined CONFIG_OF && defined CONFIG_REGULATOR + +#define SUPPLY_SUFFIX "-supply" + +/* + * Regulator handling code. + * + * Here we handle the num-supplies and vin*-supply properties of our + * "simple-framebuffer" dt node. This is necessary so that we can make sure + * that any regulators needed by the display hardware that the bootloader + * set up for us (and for which it provided a simplefb dt node), stay up, + * for the life of the simplefb driver. + * + * When the driver unloads, we cleanly disable, and then release the + * regulators. + * + * We only complain about errors here, no action is taken as the most likely + * error can only happen due to a mismatch between the bootloader which set + * up simplefb, and the regulator definitions in the device tree. Chances are + * that there are no adverse effects, and if there are, a clean teardown of + * the fb probe will not help us much either. So just complain and carry on, + * and hope that the user actually gets a working fb at the end of things. + */ + +static void simpledrm_device_release_regulators(void *res) +{ + struct simpledrm_device *sdev = res; + unsigned int i; + + for (i = 0; i < sdev->regulator_count; ++i) { + if (sdev->regulators[i]) { + regulator_disable(sdev->regulators[i]); + regulator_put(sdev->regulators[i]); + } + } +} + +static int simpledrm_device_init_regulators(struct simpledrm_device *sdev) +{ + struct drm_device *dev = &sdev->sysfb.dev; + struct platform_device *pdev = to_platform_device(dev->dev); + struct device_node *of_node = pdev->dev.of_node; + struct property *prop; + struct regulator *regulator; + const char *p; + unsigned int count = 0, i = 0; + int ret; + + if (dev_get_platdata(&pdev->dev) || !of_node) + return 0; + + /* Count the number of regulator supplies */ + for_each_property_of_node(of_node, prop) { + p = strstr(prop->name, SUPPLY_SUFFIX); + if (p && p != prop->name) + ++count; + } + + if (!count) + return 0; + + sdev->regulators = drmm_kzalloc(dev, + count * sizeof(sdev->regulators[0]), + GFP_KERNEL); + if (!sdev->regulators) + return -ENOMEM; + + for_each_property_of_node(of_node, prop) { + char name[32]; /* 32 is max size of property name */ + size_t len; + + p = strstr(prop->name, SUPPLY_SUFFIX); + if (!p || p == prop->name) + continue; + len = strlen(prop->name) - strlen(SUPPLY_SUFFIX) + 1; + strscpy(name, prop->name, min(sizeof(name), len)); + + regulator = regulator_get_optional(&pdev->dev, name); + if (IS_ERR(regulator)) { + ret = PTR_ERR(regulator); + if (ret == -EPROBE_DEFER) + goto err; + drm_err(dev, "regulator %s not found: %d\n", + name, ret); + continue; + } + + ret = regulator_enable(regulator); + if (ret) { + drm_err(dev, "failed to enable regulator %u: %d\n", + i, ret); + regulator_put(regulator); + continue; + } + + sdev->regulators[i++] = regulator; + } + sdev->regulator_count = i; + + return devm_add_action_or_reset(&pdev->dev, + simpledrm_device_release_regulators, + sdev); + +err: + while (i) { + --i; + if (sdev->regulators[i]) { + regulator_disable(sdev->regulators[i]); + regulator_put(sdev->regulators[i]); + } + } + return ret; +} +#else +static int simpledrm_device_init_regulators(struct simpledrm_device *sdev) +{ + return 0; +} +#endif + +#if defined CONFIG_OF && defined CONFIG_PM_GENERIC_DOMAINS +/* + * Generic power domain handling code. + * + * Here we handle the power-domains properties of our "simple-framebuffer" + * dt node. This is only necessary if there is more than one power-domain. + * A single power-domains is handled automatically by the driver core. Multiple + * power-domains have to be handled by drivers since the driver core can't know + * the correct power sequencing. Power sequencing is not an issue for simpledrm + * since the bootloader has put the power domains already in the correct state. + * simpledrm has only to ensure they remain active for its lifetime. + * + * When the driver unloads, we detach from the power-domains. + * + * We only complain about errors here, no action is taken as the most likely + * error can only happen due to a mismatch between the bootloader which set + * up the "simple-framebuffer" dt node, and the PM domain providers in the + * device tree. Chances are that there are no adverse effects, and if there are, + * a clean teardown of the fb probe will not help us much either. So just + * complain and carry on, and hope that the user actually gets a working fb at + * the end of things. + */ +static void simpledrm_device_detach_genpd(void *res) +{ + int i; + struct simpledrm_device *sdev = res; + + if (sdev->pwr_dom_count <= 1) + return; + + for (i = sdev->pwr_dom_count - 1; i >= 0; i--) { + if (sdev->pwr_dom_links[i]) + device_link_del(sdev->pwr_dom_links[i]); + if (!IS_ERR_OR_NULL(sdev->pwr_dom_devs[i])) + dev_pm_domain_detach(sdev->pwr_dom_devs[i], true); + } +} + +static int simpledrm_device_attach_genpd(struct simpledrm_device *sdev) +{ + struct device *dev = sdev->sysfb.dev.dev; + int i; + + sdev->pwr_dom_count = of_count_phandle_with_args(dev->of_node, "power-domains", + "#power-domain-cells"); + /* + * Single power-domain devices are handled by driver core nothing to do + * here. The same for device nodes without "power-domains" property. + */ + if (sdev->pwr_dom_count <= 1) + return 0; + + sdev->pwr_dom_devs = devm_kcalloc(dev, sdev->pwr_dom_count, + sizeof(*sdev->pwr_dom_devs), + GFP_KERNEL); + if (!sdev->pwr_dom_devs) + return -ENOMEM; + + sdev->pwr_dom_links = devm_kcalloc(dev, sdev->pwr_dom_count, + sizeof(*sdev->pwr_dom_links), + GFP_KERNEL); + if (!sdev->pwr_dom_links) + return -ENOMEM; + + for (i = 0; i < sdev->pwr_dom_count; i++) { + sdev->pwr_dom_devs[i] = dev_pm_domain_attach_by_id(dev, i); + if (IS_ERR(sdev->pwr_dom_devs[i])) { + int ret = PTR_ERR(sdev->pwr_dom_devs[i]); + if (ret == -EPROBE_DEFER) { + simpledrm_device_detach_genpd(sdev); + return ret; + } + drm_warn(&sdev->sysfb.dev, + "pm_domain_attach_by_id(%u) failed: %d\n", i, ret); + continue; + } + + sdev->pwr_dom_links[i] = device_link_add(dev, + sdev->pwr_dom_devs[i], + DL_FLAG_STATELESS | + DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (!sdev->pwr_dom_links[i]) + drm_warn(&sdev->sysfb.dev, "failed to link power-domain %d\n", i); + } + + return devm_add_action_or_reset(dev, simpledrm_device_detach_genpd, sdev); +} +#else +static int simpledrm_device_attach_genpd(struct simpledrm_device *sdev) +{ + return 0; +} +#endif + +/* + * Modesetting + */ + +static const u64 simpledrm_primary_plane_format_modifiers[] = { + DRM_SYSFB_PLANE_FORMAT_MODIFIERS, +}; + +static const struct drm_plane_helper_funcs simpledrm_primary_plane_helper_funcs = { + DRM_SYSFB_PLANE_HELPER_FUNCS, +}; + +static const struct drm_plane_funcs simpledrm_primary_plane_funcs = { + DRM_SYSFB_PLANE_FUNCS, + .destroy = drm_plane_cleanup, +}; + +static const struct drm_crtc_helper_funcs simpledrm_crtc_helper_funcs = { + DRM_SYSFB_CRTC_HELPER_FUNCS, +}; + +static const struct drm_crtc_funcs simpledrm_crtc_funcs = { + DRM_SYSFB_CRTC_FUNCS, + .destroy = drm_crtc_cleanup, +}; + +static const struct drm_encoder_funcs simpledrm_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +static const struct drm_connector_helper_funcs simpledrm_connector_helper_funcs = { + DRM_SYSFB_CONNECTOR_HELPER_FUNCS, +}; + +static const struct drm_connector_funcs simpledrm_connector_funcs = { + DRM_SYSFB_CONNECTOR_FUNCS, + .destroy = drm_connector_cleanup, +}; + +static const struct drm_mode_config_funcs simpledrm_mode_config_funcs = { + DRM_SYSFB_MODE_CONFIG_FUNCS, +}; + +/* + * Init / Cleanup + */ + +static struct simpledrm_device *simpledrm_device_create(struct drm_driver *drv, + struct platform_device *pdev) +{ + const struct simplefb_platform_data *pd = dev_get_platdata(&pdev->dev); + struct device_node *of_node = pdev->dev.of_node; + struct simpledrm_device *sdev; + struct drm_sysfb_device *sysfb; + struct drm_device *dev; + int width, height, stride; + u16 width_mm = 0, height_mm = 0; + struct device_node *panel_node; + enum drm_panel_orientation orientation = DRM_MODE_PANEL_ORIENTATION_UNKNOWN; + const struct drm_format_info *format; + u64 size; + struct resource *res, *mem = NULL; + struct drm_plane *primary_plane; + struct drm_crtc *crtc; + struct drm_encoder *encoder; + struct drm_connector *connector; + unsigned long max_width, max_height; + size_t nformats; + int ret; + + sdev = devm_drm_dev_alloc(&pdev->dev, drv, struct simpledrm_device, sysfb.dev); + if (IS_ERR(sdev)) + return ERR_CAST(sdev); + sysfb = &sdev->sysfb; + dev = &sysfb->dev; + platform_set_drvdata(pdev, sdev); + + /* + * Hardware settings + */ + + ret = simpledrm_device_init_clocks(sdev); + if (ret) + return ERR_PTR(ret); + ret = simpledrm_device_init_regulators(sdev); + if (ret) + return ERR_PTR(ret); + ret = simpledrm_device_attach_genpd(sdev); + if (ret) + return ERR_PTR(ret); + + if (pd) { + width = simplefb_get_width_pd(dev, pd); + if (width < 0) + return ERR_PTR(width); + height = simplefb_get_height_pd(dev, pd); + if (height < 0) + return ERR_PTR(height); + stride = simplefb_get_stride_pd(dev, pd); + if (stride < 0) + return ERR_PTR(stride); + format = simplefb_get_format_pd(dev, pd); + if (IS_ERR(format)) + return ERR_CAST(format); + } else if (of_node) { + width = simplefb_get_width_of(dev, of_node); + if (width < 0) + return ERR_PTR(width); + height = simplefb_get_height_of(dev, of_node); + if (height < 0) + return ERR_PTR(height); + stride = simplefb_get_stride_of(dev, of_node); + if (stride < 0) + return ERR_PTR(stride); + format = simplefb_get_format_of(dev, of_node); + if (IS_ERR(format)) + return ERR_CAST(format); + mem = simplefb_get_memory_of(dev, of_node); + if (IS_ERR(mem)) + return ERR_CAST(mem); + panel_node = of_parse_phandle(of_node, "panel", 0); + if (panel_node) { + /* + * Ignore errors from parsing the physical panel + * size. Using the pre-initialized sizes of 0 will + * make drm_sysfb_mode() calculate a default physical + * size based on a resolution of 96 dpi. + */ + ret = simplefb_get_panel_width_mm_of(dev, panel_node); + if (ret > 0) + width_mm = ret; + ret = simplefb_get_panel_height_mm_of(dev, panel_node); + if (ret > 0) + height_mm = ret; + /* + * Ignore errors from parsing the panel orientation. With + * the orientation initialized to UNKNOWN, the connector + * helpers will do the right thing. + */ + drm_of_get_panel_orientation(panel_node, &orientation); + of_node_put(panel_node); + } + } else { + drm_err(dev, "no simplefb configuration found\n"); + return ERR_PTR(-ENODEV); + } + if (!stride) { + u64 pitch = drm_format_info_min_pitch(format, 0, width); + + if (drm_WARN_ON(dev, !pitch)) { + return ERR_PTR(-EINVAL); /* driver bug */ + } else if (pitch > INT_MAX) { + drm_warn(dev, "stride of %llu exceeds maximum\n", pitch); + return ERR_PTR(-EINVAL); + } + stride = pitch; + } + if (check_mul_overflow(height, stride, &size)) { + drm_err(dev, "framebuffer size exceeds maximum\n"); + return ERR_PTR(-EINVAL); + } + if (ALIGN(size, PAGE_SIZE) < PAGE_SIZE) { + drm_err(dev, "page-aligned framebuffer exceeds maximum\n"); + return ERR_PTR(-EINVAL); + } + + sysfb->fb_mode = drm_sysfb_mode(width, height, width_mm, height_mm); + sysfb->fb_format = format; + sysfb->fb_pitch = stride; + + drm_dbg(dev, "display mode={" DRM_MODE_FMT "}\n", DRM_MODE_ARG(&sysfb->fb_mode)); + drm_dbg(dev, "framebuffer format=%p4cc, size=%dx%d, stride=%d byte\n", + &format->format, width, height, stride); + + /* + * Memory management + */ + + if (mem) { + void *screen_base; + + ret = devm_aperture_acquire_for_platform_device(pdev, mem->start, + resource_size(mem)); + if (ret) { + drm_err(dev, "could not acquire memory range %pr: %d\n", mem, ret); + return ERR_PTR(ret); + } + + drm_dbg(dev, "using system memory framebuffer at %pr\n", mem); + + if (size > resource_size(mem)) { + drm_err(dev, + "framebuffer size of %llu exceeds memory range %pr\n", + size, mem); + return ERR_PTR(-EINVAL); + } + + screen_base = devm_memremap(dev->dev, mem->start, resource_size(mem), MEMREMAP_WC); + if (IS_ERR(screen_base)) + return screen_base; + + iosys_map_set_vaddr(&sysfb->fb_addr, screen_base); + } else { + void __iomem *screen_base; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return ERR_PTR(-EINVAL); + + ret = devm_aperture_acquire_for_platform_device(pdev, res->start, + resource_size(res)); + if (ret) { + drm_err(dev, "could not acquire memory range %pr: %d\n", res, ret); + return ERR_PTR(ret); + } + + drm_dbg(dev, "using I/O memory framebuffer at %pr\n", res); + + mem = devm_request_mem_region(&pdev->dev, res->start, resource_size(res), + drv->name); + if (!mem) { + /* + * We cannot make this fatal. Sometimes this comes from magic + * spaces our resource handlers simply don't know about. Use + * the I/O-memory resource as-is and try to map that instead. + */ + drm_warn(dev, "could not acquire memory region %pr\n", res); + mem = res; + } + + if (size > resource_size(mem)) { + drm_err(dev, + "framebuffer size of %llu exceeds memory range %pr\n", + size, mem); + return ERR_PTR(-EINVAL); + } + + screen_base = devm_ioremap_wc(&pdev->dev, mem->start, resource_size(mem)); + if (!screen_base) + return ERR_PTR(-ENOMEM); + + iosys_map_set_vaddr_iomem(&sysfb->fb_addr, screen_base); + } + + /* + * Modesetting + */ + + ret = drmm_mode_config_init(dev); + if (ret) + return ERR_PTR(ret); + + max_width = max_t(unsigned long, width, DRM_SHADOW_PLANE_MAX_WIDTH); + max_height = max_t(unsigned long, height, DRM_SHADOW_PLANE_MAX_HEIGHT); + + dev->mode_config.min_width = width; + dev->mode_config.max_width = max_width; + dev->mode_config.min_height = height; + dev->mode_config.max_height = max_height; + dev->mode_config.preferred_depth = format->depth; + dev->mode_config.funcs = &simpledrm_mode_config_funcs; + + /* Primary plane */ + + nformats = drm_sysfb_build_fourcc_list(dev, &format->format, 1, + sdev->formats, ARRAY_SIZE(sdev->formats)); + + primary_plane = &sdev->primary_plane; + ret = drm_universal_plane_init(dev, primary_plane, 0, &simpledrm_primary_plane_funcs, + sdev->formats, nformats, + simpledrm_primary_plane_format_modifiers, + DRM_PLANE_TYPE_PRIMARY, NULL); + if (ret) + return ERR_PTR(ret); + drm_plane_helper_add(primary_plane, &simpledrm_primary_plane_helper_funcs); + drm_plane_enable_fb_damage_clips(primary_plane); + + /* CRTC */ + + crtc = &sdev->crtc; + ret = drm_crtc_init_with_planes(dev, crtc, primary_plane, NULL, + &simpledrm_crtc_funcs, NULL); + if (ret) + return ERR_PTR(ret); + drm_crtc_helper_add(crtc, &simpledrm_crtc_helper_funcs); + + /* Encoder */ + + encoder = &sdev->encoder; + ret = drm_encoder_init(dev, encoder, &simpledrm_encoder_funcs, + DRM_MODE_ENCODER_NONE, NULL); + if (ret) + return ERR_PTR(ret); + encoder->possible_crtcs = drm_crtc_mask(crtc); + + /* Connector */ + + connector = &sdev->connector; + ret = drm_connector_init(dev, connector, &simpledrm_connector_funcs, + DRM_MODE_CONNECTOR_Unknown); + if (ret) + return ERR_PTR(ret); + drm_connector_helper_add(connector, &simpledrm_connector_helper_funcs); + drm_connector_set_panel_orientation_with_quirk(connector, orientation, + width, height); + + ret = drm_connector_attach_encoder(connector, encoder); + if (ret) + return ERR_PTR(ret); + + drm_mode_config_reset(dev); + + return sdev; +} + +/* + * DRM driver + */ + +DEFINE_DRM_GEM_FOPS(simpledrm_fops); + +static struct drm_driver simpledrm_driver = { + DRM_GEM_SHMEM_DRIVER_OPS, + DRM_FBDEV_SHMEM_DRIVER_OPS, + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, + .driver_features = DRIVER_ATOMIC | DRIVER_GEM | DRIVER_MODESET, + .fops = &simpledrm_fops, +}; + +/* + * Platform driver + */ + +static int simpledrm_pm_suspend(struct device *dev) +{ + struct simpledrm_device *sdev = dev_get_drvdata(dev); + struct drm_device *drm = &sdev->sysfb.dev; + + return drm_mode_config_helper_suspend(drm); +} + +static int simpledrm_pm_resume(struct device *dev) +{ + struct simpledrm_device *sdev = dev_get_drvdata(dev); + struct drm_device *drm = &sdev->sysfb.dev; + + return drm_mode_config_helper_resume(drm); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(simpledrm_pm_ops, simpledrm_pm_suspend, simpledrm_pm_resume); + +static int simpledrm_probe(struct platform_device *pdev) +{ + struct simpledrm_device *sdev; + struct drm_sysfb_device *sysfb; + struct drm_device *dev; + int ret; + + sdev = simpledrm_device_create(&simpledrm_driver, pdev); + if (IS_ERR(sdev)) + return PTR_ERR(sdev); + sysfb = &sdev->sysfb; + dev = &sysfb->dev; + + ret = drm_dev_register(dev, 0); + if (ret) + return ret; + + drm_client_setup(dev, sdev->sysfb.fb_format); + + return 0; +} + +static void simpledrm_remove(struct platform_device *pdev) +{ + struct simpledrm_device *sdev = platform_get_drvdata(pdev); + struct drm_device *dev = &sdev->sysfb.dev; + + drm_dev_unplug(dev); +} + +static const struct of_device_id simpledrm_of_match_table[] = { + { .compatible = "simple-framebuffer", }, + { }, +}; +MODULE_DEVICE_TABLE(of, simpledrm_of_match_table); + +static struct platform_driver simpledrm_platform_driver = { + .driver = { + .name = "simple-framebuffer", /* connect to sysfb */ + .of_match_table = simpledrm_of_match_table, + .pm = pm_sleep_ptr(&simpledrm_pm_ops), + }, + .probe = simpledrm_probe, + .remove = simpledrm_remove, +}; + +module_platform_driver(simpledrm_platform_driver); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/sysfb/vesadrm.c b/drivers/gpu/drm/sysfb/vesadrm.c new file mode 100644 index 000000000..6a67b2d2e --- /dev/null +++ b/drivers/gpu/drm/sysfb/vesadrm.c @@ -0,0 +1,679 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include <linux/aperture.h> +#include <linux/ioport.h> +#include <linux/limits.h> +#include <linux/platform_device.h> +#include <linux/sysfb.h> +#include <linux/pm.h> + +#include <drm/clients/drm_client_setup.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_state_helper.h> +#include <drm/drm_color_mgmt.h> +#include <drm/drm_connector.h> +#include <drm/drm_damage_helper.h> +#include <drm/drm_device.h> +#include <drm/drm_drv.h> +#include <drm/drm_edid.h> +#include <drm/drm_fbdev_shmem.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem_atomic_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_gem_shmem_helper.h> +#include <drm/drm_managed.h> +#include <drm/drm_modeset_helper.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +#include <video/pixel_format.h> +#include <video/vga.h> + +#include "drm_sysfb_helper.h" + +#define DRIVER_NAME "vesadrm" +#define DRIVER_DESC "DRM driver for VESA platform devices" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 + +#define VESADRM_GAMMA_LUT_SIZE 256 + +static const struct drm_format_info *vesadrm_get_format_si(struct drm_device *dev, + const struct screen_info *si) +{ + static const struct drm_sysfb_format formats[] = { + { PIXEL_FORMAT_XRGB1555, DRM_FORMAT_XRGB1555, }, + { PIXEL_FORMAT_RGB565, DRM_FORMAT_RGB565, }, + { PIXEL_FORMAT_RGB888, DRM_FORMAT_RGB888, }, + { PIXEL_FORMAT_XRGB8888, DRM_FORMAT_XRGB8888, }, + { PIXEL_FORMAT_XBGR8888, DRM_FORMAT_XBGR8888, }, + { PIXEL_FORMAT_C8, DRM_FORMAT_C8, }, + }; + struct pixel_format pixel; + int ret; + + ret = screen_info_pixel_format(si, &pixel); + if (ret) + return NULL; + + return drm_sysfb_get_format(dev, formats, ARRAY_SIZE(formats), &pixel); +} + +/* + * VESA device + */ + +struct vesadrm_device { + struct drm_sysfb_device sysfb; + +#if defined(CONFIG_X86_32) + /* VESA Protected Mode interface */ + struct { + const u8 *PrimaryPalette; + } pmi; +#endif + + void (*cmap_write)(struct vesadrm_device *vesa, unsigned int index, + u16 red, u16 green, u16 blue); + + /* modesetting */ + u32 formats[DRM_SYSFB_PLANE_NFORMATS(1)]; + struct drm_plane primary_plane; + struct drm_crtc crtc; + struct drm_encoder encoder; + struct drm_connector connector; +}; + +static struct vesadrm_device *to_vesadrm_device(struct drm_device *dev) +{ + return container_of(to_drm_sysfb_device(dev), struct vesadrm_device, sysfb); +} + +/* + * Color LUT + */ + +static void vesadrm_vga_cmap_write(struct vesadrm_device *vesa, unsigned int index, + u16 red, u16 green, u16 blue) +{ + u8 i8 = index; + u8 r8 = red >> 8; + u8 g8 = green >> 8; + u8 b8 = blue >> 8; + + outb_p(i8, VGA_PEL_IW); + outb_p(r8, VGA_PEL_D); + outb_p(g8, VGA_PEL_D); + outb_p(b8, VGA_PEL_D); +} + +#if defined(CONFIG_X86_32) +static void vesadrm_pmi_cmap_write(struct vesadrm_device *vesa, unsigned int index, + u16 red, u16 green, u16 blue) +{ + u32 i32 = index; + struct { + u8 b8; + u8 g8; + u8 r8; + u8 x8; + } PaletteEntry = { + blue >> 8, + green >> 8, + red >> 8, + 0x00, + }; + + __asm__ __volatile__ ( + "call *(%%esi)" + : /* no return value */ + : "a" (0x4f09), + "b" (0), + "c" (1), + "d" (i32), + "D" (&PaletteEntry), + "S" (&vesa->pmi.PrimaryPalette)); +} +#endif + +static void vesadrm_set_color_lut(struct drm_crtc *crtc, unsigned int index, + u16 red, u16 green, u16 blue) +{ + struct drm_device *dev = crtc->dev; + struct vesadrm_device *vesa = to_vesadrm_device(dev); + u8 i8 = index & 0xff; + + if (drm_WARN_ON_ONCE(dev, index != i8)) + return; /* driver bug */ + + vesa->cmap_write(vesa, i8, red, green, blue); +} + +static void vesadrm_fill_gamma_lut(struct vesadrm_device *vesa, + const struct drm_format_info *format) +{ + struct drm_device *dev = &vesa->sysfb.dev; + struct drm_crtc *crtc = &vesa->crtc; + + switch (format->format) { + case DRM_FORMAT_XRGB1555: + drm_crtc_fill_gamma_555(crtc, vesadrm_set_color_lut); + break; + case DRM_FORMAT_RGB565: + drm_crtc_fill_gamma_565(crtc, vesadrm_set_color_lut); + break; + case DRM_FORMAT_RGB888: + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_BGRX8888: + drm_crtc_fill_gamma_888(crtc, vesadrm_set_color_lut); + break; + default: + drm_warn_once(dev, "Unsupported format %p4cc for gamma correction\n", + &format->format); + break; + } +} + +static void vesadrm_load_gamma_lut(struct vesadrm_device *vesa, + const struct drm_format_info *format, + struct drm_color_lut *lut) +{ + struct drm_device *dev = &vesa->sysfb.dev; + struct drm_crtc *crtc = &vesa->crtc; + + switch (format->format) { + case DRM_FORMAT_XRGB1555: + drm_crtc_load_gamma_555_from_888(crtc, lut, vesadrm_set_color_lut); + break; + case DRM_FORMAT_RGB565: + drm_crtc_load_gamma_565_from_888(crtc, lut, vesadrm_set_color_lut); + break; + case DRM_FORMAT_RGB888: + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_BGRX8888: + drm_crtc_load_gamma_888(crtc, lut, vesadrm_set_color_lut); + break; + default: + drm_warn_once(dev, "Unsupported format %p4cc for gamma correction\n", + &format->format); + break; + } +} + +static void vesadrm_fill_palette_lut(struct vesadrm_device *vesa, + const struct drm_format_info *format) +{ + struct drm_device *dev = &vesa->sysfb.dev; + struct drm_crtc *crtc = &vesa->crtc; + + switch (format->format) { + case DRM_FORMAT_C8: + drm_crtc_fill_palette_8(crtc, vesadrm_set_color_lut); + break; + case DRM_FORMAT_RGB332: + drm_crtc_fill_palette_332(crtc, vesadrm_set_color_lut); + break; + default: + drm_warn_once(dev, "Unsupported format %p4cc for palette\n", + &format->format); + break; + } +} + +static void vesadrm_load_palette_lut(struct vesadrm_device *vesa, + const struct drm_format_info *format, + struct drm_color_lut *lut) +{ + struct drm_device *dev = &vesa->sysfb.dev; + struct drm_crtc *crtc = &vesa->crtc; + + switch (format->format) { + case DRM_FORMAT_C8: + drm_crtc_load_palette_8(crtc, lut, vesadrm_set_color_lut); + break; + default: + drm_warn_once(dev, "Unsupported format %p4cc for gamma correction\n", + &format->format); + break; + } +} + +/* + * Modesetting + */ + +static const u64 vesadrm_primary_plane_format_modifiers[] = { + DRM_SYSFB_PLANE_FORMAT_MODIFIERS, +}; + +static int vesadrm_primary_plane_helper_atomic_check(struct drm_plane *plane, + struct drm_atomic_commit *new_state) +{ + struct drm_sysfb_device *sysfb = to_drm_sysfb_device(plane->dev); + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(new_state, plane); + struct drm_framebuffer *new_fb = new_plane_state->fb; + struct drm_crtc_state *new_crtc_state; + struct drm_sysfb_crtc_state *new_sysfb_crtc_state; + int ret; + + ret = drm_sysfb_plane_helper_atomic_check(plane, new_state); + if (ret) + return ret; + else if (!new_plane_state->visible) + return 0; + + /* + * Fix up format conversion for specific cases + */ + + switch (sysfb->fb_format->format) { + case DRM_FORMAT_C8: + new_crtc_state = drm_atomic_get_new_crtc_state(new_state, new_plane_state->crtc); + new_sysfb_crtc_state = to_drm_sysfb_crtc_state(new_crtc_state); + + switch (new_fb->format->format) { + case DRM_FORMAT_XRGB8888: + /* + * Reduce XRGB8888 to RGB332. Each resulting pixel is an index + * into the C8 hardware palette, which stores RGB332 colors. + */ + if (new_sysfb_crtc_state->format->format != DRM_FORMAT_RGB332) { + new_sysfb_crtc_state->format = + drm_format_info(DRM_FORMAT_RGB332); + new_crtc_state->color_mgmt_changed = true; + } + break; + case DRM_FORMAT_C8: + /* + * Restore original output. Emulation of XRGB8888 set RBG332 + * output format and hardware palette. This needs to be undone + * when we switch back to DRM_FORMAT_C8. + */ + if (new_sysfb_crtc_state->format->format == DRM_FORMAT_RGB332) { + new_sysfb_crtc_state->format = sysfb->fb_format; + new_crtc_state->color_mgmt_changed = true; + } + break; + } + break; + } + + return 0; +} + +static const struct drm_plane_helper_funcs vesadrm_primary_plane_helper_funcs = { + .begin_fb_access = drm_sysfb_plane_helper_begin_fb_access, + .end_fb_access = drm_gem_end_shadow_fb_access, + .atomic_check = vesadrm_primary_plane_helper_atomic_check, + .atomic_update = drm_sysfb_plane_helper_atomic_update, + .atomic_disable = drm_sysfb_plane_helper_atomic_disable, + .get_scanout_buffer = drm_sysfb_plane_helper_get_scanout_buffer, +}; + +static const struct drm_plane_funcs vesadrm_primary_plane_funcs = { + DRM_SYSFB_PLANE_FUNCS, + .destroy = drm_plane_cleanup, +}; + +static void vesadrm_crtc_helper_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_commit *state) +{ + struct drm_device *dev = crtc->dev; + struct drm_sysfb_device *sysfb = to_drm_sysfb_device(dev); + struct vesadrm_device *vesa = to_vesadrm_device(dev); + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + struct drm_sysfb_crtc_state *sysfb_crtc_state = to_drm_sysfb_crtc_state(crtc_state); + + /* + * The gamma LUT has to be reloaded after changing the primary + * plane's color format. + */ + if (crtc_state->enable && crtc_state->color_mgmt_changed) { + switch (sysfb->fb_format->format) { + /* + * Index formats + */ + case DRM_FORMAT_C8: + if (sysfb_crtc_state->format->format == DRM_FORMAT_RGB332) { + vesadrm_fill_palette_lut(vesa, sysfb_crtc_state->format); + } else if (crtc->state->gamma_lut) { + vesadrm_load_palette_lut(vesa, + sysfb_crtc_state->format, + crtc_state->gamma_lut->data); + } else { + vesadrm_fill_palette_lut(vesa, sysfb_crtc_state->format); + } + break; + /* + * Component formats + */ + default: + if (sysfb_crtc_state->format == sysfb->fb_format) { + if (crtc_state->gamma_lut) + vesadrm_load_gamma_lut(vesa, + sysfb_crtc_state->format, + crtc_state->gamma_lut->data); + else + vesadrm_fill_gamma_lut(vesa, sysfb_crtc_state->format); + } else { + vesadrm_fill_gamma_lut(vesa, sysfb_crtc_state->format); + } + break; + } + } +} + +static const struct drm_crtc_helper_funcs vesadrm_crtc_helper_funcs = { + DRM_SYSFB_CRTC_HELPER_FUNCS, + .atomic_flush = vesadrm_crtc_helper_atomic_flush, +}; + +static const struct drm_crtc_funcs vesadrm_crtc_funcs = { + DRM_SYSFB_CRTC_FUNCS, + .destroy = drm_crtc_cleanup, +}; + +static const struct drm_encoder_funcs vesadrm_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +static const struct drm_connector_helper_funcs vesadrm_connector_helper_funcs = { + DRM_SYSFB_CONNECTOR_HELPER_FUNCS, +}; + +static const struct drm_connector_funcs vesadrm_connector_funcs = { + DRM_SYSFB_CONNECTOR_FUNCS, + .destroy = drm_connector_cleanup, +}; + +static const struct drm_mode_config_funcs vesadrm_mode_config_funcs = { + DRM_SYSFB_MODE_CONFIG_FUNCS, +}; + +/* + * Init / Cleanup + */ + +static struct vesadrm_device *vesadrm_device_create(struct drm_driver *drv, + struct platform_device *pdev) +{ + const struct sysfb_display_info *dpy; + const struct screen_info *si; + const struct drm_format_info *format; + int width, height, stride; + s64 vsize; + struct resource resbuf; + struct resource *res; + struct vesadrm_device *vesa; + struct drm_sysfb_device *sysfb; + struct drm_device *dev; + struct resource *mem = NULL; + void __iomem *screen_base; + struct drm_plane *primary_plane; + struct drm_crtc *crtc; + struct drm_encoder *encoder; + struct drm_connector *connector; + unsigned long max_width, max_height; + size_t nformats; + int ret; + + dpy = dev_get_platdata(&pdev->dev); + if (!dpy) + return ERR_PTR(-ENODEV); + si = &dpy->screen; + + if (screen_info_video_type(si) != VIDEO_TYPE_VLFB) + return ERR_PTR(-ENODEV); + + /* + * VESA DRM driver + */ + + vesa = devm_drm_dev_alloc(&pdev->dev, drv, struct vesadrm_device, sysfb.dev); + if (IS_ERR(vesa)) + return ERR_CAST(vesa); + sysfb = &vesa->sysfb; + dev = &sysfb->dev; + platform_set_drvdata(pdev, dev); + + /* + * Hardware settings + */ + + format = vesadrm_get_format_si(dev, si); + if (!format) + return ERR_PTR(-EINVAL); + width = drm_sysfb_get_width_si(dev, si); + if (width < 0) + return ERR_PTR(width); + height = drm_sysfb_get_height_si(dev, si); + if (height < 0) + return ERR_PTR(height); + res = drm_sysfb_get_memory_si(dev, si, &resbuf); + if (!res) + return ERR_PTR(-EINVAL); + stride = drm_sysfb_get_stride_si(dev, si, format, width, height, resource_size(res)); + if (stride < 0) + return ERR_PTR(stride); + vsize = drm_sysfb_get_visible_size_si(dev, si, height, stride, resource_size(res)); + if (vsize < 0) + return ERR_PTR(vsize); + + drm_dbg(dev, "framebuffer format=%p4cc, size=%dx%d, stride=%d bytes\n", + &format->format, width, height, stride); + + if (!__screen_info_vbe_mode_nonvga(si)) { + vesa->cmap_write = vesadrm_vga_cmap_write; + } else { +#if defined(CONFIG_X86_32) + phys_addr_t pmi_base = __screen_info_vesapm_info_base(si); + + if (pmi_base) { + const u16 *pmi_addr = phys_to_virt(pmi_base); + + vesa->pmi.PrimaryPalette = (u8 *)pmi_addr + pmi_addr[2]; + vesa->cmap_write = vesadrm_pmi_cmap_write; + } else +#endif + if (format->is_color_indexed) + drm_warn(dev, "hardware palette is unchangeable, colors may be incorrect\n"); + } + +#if defined(CONFIG_FIRMWARE_EDID) + if (drm_edid_header_is_valid(dpy->edid.dummy) == 8) + sysfb->edid = dpy->edid.dummy; +#endif + sysfb->fb_mode = drm_sysfb_mode(width, height, 0, 0); + sysfb->fb_format = format; + sysfb->fb_pitch = stride; + if (vesa->cmap_write) + sysfb->fb_gamma_lut_size = VESADRM_GAMMA_LUT_SIZE; + + /* + * Memory management + */ + + ret = devm_aperture_acquire_for_platform_device(pdev, res->start, vsize); + if (ret) { + drm_err(dev, "could not acquire memory range %pr: %d\n", res, ret); + return ERR_PTR(ret); + } + + drm_dbg(dev, "using I/O memory framebuffer at %pr\n", res); + + mem = devm_request_mem_region(&pdev->dev, res->start, vsize, drv->name); + if (!mem) { + /* + * We cannot make this fatal. Sometimes this comes from magic + * spaces our resource handlers simply don't know about. Use + * the I/O-memory resource as-is and try to map that instead. + */ + drm_warn(dev, "could not acquire memory region %pr\n", res); + mem = res; + } + + screen_base = devm_ioremap_wc(&pdev->dev, mem->start, resource_size(mem)); + if (!screen_base) + return ERR_PTR(-ENOMEM); + iosys_map_set_vaddr_iomem(&sysfb->fb_addr, screen_base); + + /* + * Modesetting + */ + + ret = drmm_mode_config_init(dev); + if (ret) + return ERR_PTR(ret); + + max_width = max_t(unsigned long, width, DRM_SHADOW_PLANE_MAX_WIDTH); + max_height = max_t(unsigned long, height, DRM_SHADOW_PLANE_MAX_HEIGHT); + + dev->mode_config.min_width = width; + dev->mode_config.max_width = max_width; + dev->mode_config.min_height = height; + dev->mode_config.max_height = max_height; + dev->mode_config.preferred_depth = format->depth; + dev->mode_config.funcs = &vesadrm_mode_config_funcs; + + /* Primary plane */ + + nformats = drm_sysfb_build_fourcc_list(dev, &format->format, 1, + vesa->formats, ARRAY_SIZE(vesa->formats)); + + primary_plane = &vesa->primary_plane; + ret = drm_universal_plane_init(dev, primary_plane, 0, &vesadrm_primary_plane_funcs, + vesa->formats, nformats, + vesadrm_primary_plane_format_modifiers, + DRM_PLANE_TYPE_PRIMARY, NULL); + if (ret) + return ERR_PTR(ret); + drm_plane_helper_add(primary_plane, &vesadrm_primary_plane_helper_funcs); + drm_plane_enable_fb_damage_clips(primary_plane); + + /* CRTC */ + + crtc = &vesa->crtc; + ret = drm_crtc_init_with_planes(dev, crtc, primary_plane, NULL, + &vesadrm_crtc_funcs, NULL); + if (ret) + return ERR_PTR(ret); + drm_crtc_helper_add(crtc, &vesadrm_crtc_helper_funcs); + + if (sysfb->fb_gamma_lut_size) { + ret = drm_mode_crtc_set_gamma_size(crtc, sysfb->fb_gamma_lut_size); + if (!ret) + drm_crtc_enable_color_mgmt(crtc, 0, false, sysfb->fb_gamma_lut_size); + } + + /* Encoder */ + + encoder = &vesa->encoder; + ret = drm_encoder_init(dev, encoder, &vesadrm_encoder_funcs, + DRM_MODE_ENCODER_NONE, NULL); + if (ret) + return ERR_PTR(ret); + encoder->possible_crtcs = drm_crtc_mask(crtc); + + /* Connector */ + + connector = &vesa->connector; + ret = drm_connector_init(dev, connector, &vesadrm_connector_funcs, + DRM_MODE_CONNECTOR_Unknown); + if (ret) + return ERR_PTR(ret); + drm_connector_helper_add(connector, &vesadrm_connector_helper_funcs); + drm_connector_set_panel_orientation_with_quirk(connector, + DRM_MODE_PANEL_ORIENTATION_UNKNOWN, + width, height); + if (sysfb->edid) + drm_connector_attach_edid_property(connector); + + ret = drm_connector_attach_encoder(connector, encoder); + if (ret) + return ERR_PTR(ret); + + drm_mode_config_reset(dev); + + return vesa; +} + +/* + * DRM driver + */ + +DEFINE_DRM_GEM_FOPS(vesadrm_fops); + +static struct drm_driver vesadrm_driver = { + DRM_GEM_SHMEM_DRIVER_OPS, + DRM_FBDEV_SHMEM_DRIVER_OPS, + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, + .driver_features = DRIVER_ATOMIC | DRIVER_GEM | DRIVER_MODESET, + .fops = &vesadrm_fops, +}; + +/* + * Platform driver + */ + +static int vesadrm_pm_suspend(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + + return drm_mode_config_helper_suspend(drm); +} + +static int vesadrm_pm_resume(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + + return drm_mode_config_helper_resume(drm); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(vesadrm_pm_ops, vesadrm_pm_suspend, vesadrm_pm_resume); + +static int vesadrm_probe(struct platform_device *pdev) +{ + struct vesadrm_device *vesa; + struct drm_sysfb_device *sysfb; + struct drm_device *dev; + int ret; + + vesa = vesadrm_device_create(&vesadrm_driver, pdev); + if (IS_ERR(vesa)) + return PTR_ERR(vesa); + sysfb = &vesa->sysfb; + dev = &sysfb->dev; + + ret = drm_dev_register(dev, 0); + if (ret) + return ret; + + drm_client_setup(dev, sysfb->fb_format); + + return 0; +} + +static void vesadrm_remove(struct platform_device *pdev) +{ + struct drm_device *dev = platform_get_drvdata(pdev); + + drm_dev_unplug(dev); +} + +static struct platform_driver vesadrm_platform_driver = { + .driver = { + .name = "vesa-framebuffer", + .pm = pm_sleep_ptr(&vesadrm_pm_ops), + }, + .probe = vesadrm_probe, + .remove = vesadrm_remove, +}; + +module_platform_driver(vesadrm_platform_driver); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); |
