Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+92
View File
@@ -0,0 +1,92 @@
# 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,
UEFI, VESA, etc.
On x86 BIOS or UEFI systems, you should also select SYSFB_SIMPLEFB
to use UEFI and VESA framebuffers.
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
@@ -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
@@ -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");
@@ -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");
@@ -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
@@ -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);
@@ -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);
+434
View File
@@ -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");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,906 @@
// 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/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_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 int
simplefb_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
simplefb_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 *
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 simplefb_get_validated_int0(dev, "width", pd->width);
}
static int
simplefb_get_height_pd(struct drm_device *dev,
const struct simplefb_platform_data *pd)
{
return simplefb_get_validated_int0(dev, "height", pd->height);
}
static int
simplefb_get_stride_pd(struct drm_device *dev,
const struct simplefb_platform_data *pd)
{
return simplefb_get_validated_int(dev, "stride", pd->stride);
}
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 simplefb_get_validated_int0(dev, "width", width);
}
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 simplefb_get_validated_int0(dev, "height", height);
}
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 simplefb_get_validated_int(dev, "stride", stride);
}
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;
}
/*
* 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;
int width_mm = 0, height_mm = 0;
struct device_node *panel_node;
const struct drm_format_info *format;
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) {
simplefb_read_u32_of(dev, panel_node, "width-mm", &width_mm);
simplefb_read_u32_of(dev, panel_node, "height-mm", &height_mm);
of_node_put(panel_node);
}
} else {
drm_err(dev, "no simplefb configuration found\n");
return ERR_PTR(-ENODEV);
}
if (!stride) {
stride = drm_format_info_min_pitch(format, 0, width);
if (drm_WARN_ON(dev, !stride))
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);
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;
}
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,
DRM_MODE_PANEL_ORIENTATION_UNKNOWN,
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");
+679
View File
@@ -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");