1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * framebuffer-coreboot.c 4 * 5 * Memory based framebuffer accessed through coreboot table. 6 * 7 * Copyright 2012-2013 David Herrmann <dh.herrmann@gmail.com> 8 * Copyright 2017 Google Inc. 9 * Copyright 2017 Samuel Holland <samuel@sholland.org> 10 */ 11 12 #include <linux/device.h> 13 #include <linux/kernel.h> 14 #include <linux/mm.h> 15 #include <linux/mod_devicetable.h> 16 #include <linux/module.h> 17 #include <linux/pci.h> 18 #include <linux/platform_data/simplefb.h> 19 #include <linux/platform_device.h> 20 #include <linux/sysfb.h> 21 22 #include "coreboot_table.h" 23 24 #if defined(CONFIG_PCI) 25 static bool framebuffer_pci_dev_is_enabled(struct pci_dev *pdev) 26 { 27 /* 28 * TODO: Try to integrate this code into the PCI subsystem 29 */ 30 int ret; 31 u16 command; 32 33 ret = pci_read_config_word(pdev, PCI_COMMAND, &command); 34 if (ret != PCIBIOS_SUCCESSFUL) 35 return false; 36 if (!(command & PCI_COMMAND_MEMORY)) 37 return false; 38 return true; 39 } 40 41 static struct pci_dev *framebuffer_parent_pci_dev(struct resource *res) 42 { 43 struct pci_dev *pdev = NULL; 44 const struct resource *r = NULL; 45 46 while (!r && (pdev = pci_get_base_class(PCI_BASE_CLASS_DISPLAY, pdev))) 47 r = pci_find_resource(pdev, res); 48 49 if (!r || !pdev) 50 return NULL; /* not found; not an error */ 51 52 if (!framebuffer_pci_dev_is_enabled(pdev)) { 53 pci_dev_put(pdev); 54 return ERR_PTR(-ENODEV); 55 } 56 57 return pdev; 58 } 59 #else 60 static struct pci_dev *framebuffer_parent_pci_dev(struct resource *res) 61 { 62 return NULL; 63 } 64 #endif 65 66 static struct device *framebuffer_parent_dev(struct resource *res) 67 { 68 struct pci_dev *pdev; 69 70 pdev = framebuffer_parent_pci_dev(res); 71 if (IS_ERR(pdev)) 72 return ERR_CAST(pdev); 73 else if (pdev) 74 return &pdev->dev; 75 76 return NULL; 77 } 78 79 static int framebuffer_probe(struct coreboot_device *dev) 80 { 81 struct lb_framebuffer *fb = &dev->framebuffer; 82 struct device *parent; 83 struct platform_device *pdev; 84 struct resource res; 85 int ret; 86 #if !IS_ENABLED(CONFIG_DRM_COREBOOTDRM) 87 struct simplefb_platform_data pdata = { 88 .width = fb->x_resolution, 89 .height = fb->y_resolution, 90 .stride = fb->bytes_per_line, 91 .format = NULL, 92 }; 93 int i; 94 static const struct simplefb_format formats[] = SIMPLEFB_FORMATS; 95 #endif 96 97 /* 98 * On coreboot systems, the advertised LB_TAG_FRAMEBUFFER entry 99 * in the coreboot table should only be used if the payload did 100 * not pass a framebuffer information to the Linux kernel. 101 * 102 * If the global screen_info data has been filled, the Generic 103 * System Framebuffers (sysfb) will already register a platform 104 * device and pass that screen_info as platform_data to a driver 105 * that can scan-out using the system provided framebuffer. 106 */ 107 if (sysfb_handles_screen_info()) 108 return -ENODEV; 109 110 if (!fb->physical_address) 111 return -ENODEV; 112 113 res = DEFINE_RES_MEM(fb->physical_address, 114 PAGE_ALIGN(fb->y_resolution * fb->bytes_per_line)); 115 if (res.end <= res.start) 116 return -EINVAL; 117 118 parent = framebuffer_parent_dev(&res); 119 if (IS_ERR(parent)) 120 return PTR_ERR(parent); 121 122 #if IS_ENABLED(CONFIG_DRM_COREBOOTDRM) 123 pdev = platform_device_register_resndata(parent, "coreboot-framebuffer", 0, 124 &res, 1, fb, fb->size); 125 if (IS_ERR(pdev)) { 126 pr_warn("coreboot: could not register framebuffer\n"); 127 ret = PTR_ERR(pdev); 128 goto out_put_device_parent; 129 } 130 #else 131 /* 132 * FIXME: Coreboot systems should use a driver that binds to 133 * coreboot-framebuffer devices. Remove support for 134 * simple-framebuffer at some point. 135 */ 136 for (i = 0; i < ARRAY_SIZE(formats); ++i) { 137 if (fb->bits_per_pixel == formats[i].bits_per_pixel && 138 fb->red_mask_pos == formats[i].red.offset && 139 fb->red_mask_size == formats[i].red.length && 140 fb->green_mask_pos == formats[i].green.offset && 141 fb->green_mask_size == formats[i].green.length && 142 fb->blue_mask_pos == formats[i].blue.offset && 143 fb->blue_mask_size == formats[i].blue.length) 144 pdata.format = formats[i].name; 145 } 146 if (!pdata.format) { 147 ret = -ENODEV; 148 goto out_put_device_parent; 149 } 150 151 pdev = platform_device_register_resndata(parent, 152 "simple-framebuffer", 0, 153 &res, 1, &pdata, 154 sizeof(pdata)); 155 if (IS_ERR(pdev)) { 156 ret = PTR_ERR(pdev); 157 pr_warn("coreboot: could not register framebuffer\n"); 158 goto out_put_device_parent; 159 } 160 #endif 161 162 ret = 0; 163 164 out_put_device_parent: 165 if (parent) 166 put_device(parent); 167 return ret; 168 } 169 170 static const struct coreboot_device_id framebuffer_ids[] = { 171 { .tag = CB_TAG_FRAMEBUFFER }, 172 { /* sentinel */ } 173 }; 174 MODULE_DEVICE_TABLE(coreboot, framebuffer_ids); 175 176 static struct coreboot_driver framebuffer_driver = { 177 .probe = framebuffer_probe, 178 .drv = { 179 .name = "framebuffer", 180 }, 181 .id_table = framebuffer_ids, 182 }; 183 module_coreboot_driver(framebuffer_driver); 184 185 MODULE_AUTHOR("Samuel Holland <samuel@sholland.org>"); 186 MODULE_DESCRIPTION("Memory based framebuffer accessed through coreboot table"); 187 MODULE_LICENSE("GPL"); 188