|  | // 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 = &screen_info; | 
|  | 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 = &screen_info; | 
|  |  | 
|  | 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 screen_info *si = &screen_info; | 
|  | 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(); | 
|  |  | 
|  | 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(pd); | 
|  |  | 
|  | ret = platform_device_add_data(pd, si, sizeof(*si)); | 
|  | 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); |