root/drivers/firmware/sysfb.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Generic System Framebuffers
 * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@gmail.com>
 */

/*
 * Simple-Framebuffer support
 * Create a platform-device for any available boot framebuffer. The
 * simple-framebuffer platform device is already available on DT systems, so
 * this module parses the global "screen_info" object and creates a suitable
 * platform device compatible with the "simple-framebuffer" DT object. If
 * the framebuffer is incompatible, we instead create a legacy
 * "vesa-framebuffer", "efi-framebuffer" or "platform-framebuffer" device and
 * pass the screen_info as platform_data. This allows legacy drivers
 * to pick these devices up without messing with simple-framebuffer drivers.
 * The global "screen_info" is still valid at all times.
 *
 * If CONFIG_SYSFB_SIMPLEFB is not selected, never register "simple-framebuffer"
 * platform devices, but only use legacy framebuffer devices for
 * backwards compatibility.
 *
 * TODO: We set the dev_id field of all platform-devices to 0. This allows
 * other OF/DT parsers to create such devices, too. However, they must
 * start at offset 1 for this to work.
 */

#include <linux/err.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/pci.h>
#include <linux/platform_data/simplefb.h>
#include <linux/platform_device.h>
#include <linux/screen_info.h>
#include <linux/sysfb.h>

static struct platform_device *pd;
static DEFINE_MUTEX(disable_lock);
static bool disabled;

static struct device *sysfb_parent_dev(const struct screen_info *si);

static bool sysfb_unregister(void)
{
        if (IS_ERR_OR_NULL(pd))
                return false;

        platform_device_unregister(pd);
        pd = NULL;

        return true;
}

/**
 * sysfb_disable() - disable the Generic System Framebuffers support
 * @dev:        the device to check if non-NULL
 *
 * This disables the registration of system framebuffer devices that match the
 * generic drivers that make use of the system framebuffer set up by firmware.
 *
 * It also unregisters a device if this was already registered by sysfb_init().
 *
 * Context: The function can sleep. A @disable_lock mutex is acquired to serialize
 *          against sysfb_init(), that registers a system framebuffer device.
 */
void sysfb_disable(struct device *dev)
{
        struct screen_info *si = &sysfb_primary_display.screen;
        struct device *parent;

        mutex_lock(&disable_lock);
        parent = sysfb_parent_dev(si);
        if (!dev || !parent || dev == parent) {
                sysfb_unregister();
                disabled = true;
        }
        mutex_unlock(&disable_lock);
}
EXPORT_SYMBOL_GPL(sysfb_disable);

/**
 * sysfb_handles_screen_info() - reports if sysfb handles the global screen_info
 *
 * Callers can use sysfb_handles_screen_info() to determine whether the Generic
 * System Framebuffers (sysfb) can handle the global screen_info data structure
 * or not. Drivers might need this information to know if they have to setup the
 * system framebuffer, or if they have to delegate this action to sysfb instead.
 *
 * Returns:
 * True if sysfb handles the global screen_info data structure.
 */
bool sysfb_handles_screen_info(void)
{
        const struct screen_info *si = &sysfb_primary_display.screen;

        return !!screen_info_video_type(si);
}
EXPORT_SYMBOL_GPL(sysfb_handles_screen_info);

#if defined(CONFIG_PCI)
static bool sysfb_pci_dev_is_enabled(struct pci_dev *pdev)
{
        /*
         * TODO: Try to integrate this code into the PCI subsystem
         */
        int ret;
        u16 command;

        ret = pci_read_config_word(pdev, PCI_COMMAND, &command);
        if (ret != PCIBIOS_SUCCESSFUL)
                return false;
        if (!(command & PCI_COMMAND_MEMORY))
                return false;
        return true;
}
#else
static bool sysfb_pci_dev_is_enabled(struct pci_dev *pdev)
{
        return false;
}
#endif

static struct device *sysfb_parent_dev(const struct screen_info *si)
{
        struct pci_dev *pdev;

        pdev = screen_info_pci_dev(si);
        if (IS_ERR(pdev)) {
                return ERR_CAST(pdev);
        } else if (pdev) {
                if (!sysfb_pci_dev_is_enabled(pdev)) {
                        pci_dev_put(pdev);
                        return ERR_PTR(-ENODEV);
                }
                return &pdev->dev;
        }

        return NULL;
}

static __init int sysfb_init(void)
{
        struct sysfb_display_info *dpy = &sysfb_primary_display;
        struct screen_info *si = &dpy->screen;
        struct device *parent;
        unsigned int type;
        struct simplefb_platform_data mode;
        const char *name;
        bool compatible;
        int ret = 0;

        screen_info_apply_fixups();

        mutex_lock(&disable_lock);
        if (disabled)
                goto unlock_mutex;

        sysfb_apply_efi_quirks(si);

        parent = sysfb_parent_dev(si);
        if (IS_ERR(parent)) {
                ret = PTR_ERR(parent);
                goto unlock_mutex;
        }

        /* try to create a simple-framebuffer device */
        compatible = sysfb_parse_mode(si, &mode);
        if (compatible) {
                pd = sysfb_create_simplefb(si, &mode, parent);
                if (!IS_ERR(pd))
                        goto put_device;
        }

        type = screen_info_video_type(si);

        /* if the FB is incompatible, create a legacy framebuffer device */
        switch (type) {
        case VIDEO_TYPE_EGAC:
                name = "ega-framebuffer";
                break;
        case VIDEO_TYPE_VGAC:
                name = "vga-framebuffer";
                break;
        case VIDEO_TYPE_VLFB:
                name = "vesa-framebuffer";
                break;
        case VIDEO_TYPE_EFI:
                name = "efi-framebuffer";
                break;
        default:
                name = "platform-framebuffer";
                break;
        }

        pd = platform_device_alloc(name, 0);
        if (!pd) {
                ret = -ENOMEM;
                goto put_device;
        }

        pd->dev.parent = parent;

        sysfb_set_efifb_fwnode(si, pd);

        ret = platform_device_add_data(pd, dpy, sizeof(*dpy));
        if (ret)
                goto err;

        ret = platform_device_add(pd);
        if (ret)
                goto err;

        goto put_device;
err:
        platform_device_put(pd);
put_device:
        put_device(parent);
unlock_mutex:
        mutex_unlock(&disable_lock);
        return ret;
}

/* must execute after PCI subsystem for EFI quirks */
device_initcall(sysfb_init);