Created
December 29, 2020 04:38
-
-
Save baalajimaestro/d9c576507ee52a6d289a29be49a35060 to your computer and use it in GitHub Desktop.
Patch to include bbswitch inline, compatible and tested on 5.10.3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- /dev/null 2020-12-29 08:23:57.202225942 +0530 | |
+++ b/drivers/video/bbswitch/bbswitch.c 2020-12-26 17:53:33.867054206 +0530 | |
@@ -0,0 +1,522 @@ | |
+/** | |
+ * Disable discrete graphics (currently nvidia only) | |
+ * | |
+ * Usage: | |
+ * Disable discrete card | |
+ * # echo OFF > /proc/acpi/bbswitch | |
+ * Enable discrete card | |
+ * # echo ON > /proc/acpi/bbswitch | |
+ * Get status | |
+ * # cat /proc/acpi/bbswitch | |
+ */ | |
+/* | |
+ * Copyright (C) 2011-2013 Bumblebee Project | |
+ * Author: Peter Wu <lekensteyn@gmail.com> | |
+ * | |
+ * This program is free software: you can redistribute it and/or modify | |
+ * it under the terms of the GNU General Public License as published by | |
+ * the Free Software Foundation, either version 2 of the License, or | |
+ * (at your option) any later version. | |
+ * | |
+ * This program is distributed in the hope that it will be useful, | |
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
+ * GNU General Public License for more details. | |
+ * | |
+ * You should have received a copy of the GNU General Public License | |
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
+ */ | |
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
+ | |
+#include <linux/pci.h> | |
+#include <linux/acpi.h> | |
+#include <linux/module.h> | |
+#include <linux/uaccess.h> | |
+#include <linux/suspend.h> | |
+#include <linux/seq_file.h> | |
+#include <linux/pm_runtime.h> | |
+#include <linux/proc_fs.h> | |
+#include <linux/version.h> | |
+ | |
+#define BBSWITCH_VERSION "0.8" | |
+ | |
+MODULE_LICENSE("GPL"); | |
+MODULE_DESCRIPTION("Toggle the discrete graphics card"); | |
+MODULE_AUTHOR("Peter Wu <lekensteyn@gmail.com>"); | |
+MODULE_VERSION(BBSWITCH_VERSION); | |
+ | |
+enum { | |
+ CARD_UNCHANGED = -1, | |
+ CARD_OFF = 0, | |
+ CARD_ON = 1, | |
+}; | |
+ | |
+static int load_state = CARD_UNCHANGED; | |
+MODULE_PARM_DESC(load_state, "Initial card state (0 = off, 1 = on, -1 = unchanged)"); | |
+module_param(load_state, int, 0400); | |
+static int unload_state = CARD_UNCHANGED; | |
+MODULE_PARM_DESC(unload_state, "Card state on unload (0 = off, 1 = on, -1 = unchanged)"); | |
+module_param(unload_state, int, 0600); | |
+static bool skip_optimus_dsm = false; | |
+MODULE_PARM_DESC(skip_optimus_dsm, "Skip probe of Optimus discrete DSM (default = false)"); | |
+module_param(skip_optimus_dsm, bool, 0400); | |
+ | |
+extern struct proc_dir_entry *acpi_root_dir; | |
+ | |
+static const char acpi_optimus_dsm_muid[16] = { | |
+ 0xF8, 0xD8, 0x86, 0xA4, 0xDA, 0x0B, 0x1B, 0x47, | |
+ 0xA7, 0x2B, 0x60, 0x42, 0xA6, 0xB5, 0xBE, 0xE0, | |
+}; | |
+ | |
+static const char acpi_nvidia_dsm_muid[16] = { | |
+ 0xA0, 0xA0, 0x95, 0x9D, 0x60, 0x00, 0x48, 0x4D, | |
+ 0xB3, 0x4D, 0x7E, 0x5F, 0xEA, 0x12, 0x9F, 0xD4 | |
+}; | |
+ | |
+/* | |
+The next UUID has been found as well in | |
+https://bugs.launchpad.net/lpbugreporter/+bug/752542: | |
+0xD3, 0x73, 0xD8, 0x7E, 0xD0, 0xC2, 0x4F, 0x4E, | |
+0xA8, 0x54, 0x0F, 0x13, 0x17, 0xB0, 0x1C, 0x2C | |
+It looks like something for Intel GPU: | |
+http://lxr.linux.no/#linux+v3.1.5/drivers/gpu/drm/i915/intel_acpi.c | |
+ */ | |
+ | |
+#define DSM_TYPE_UNSUPPORTED 0 | |
+#define DSM_TYPE_OPTIMUS 1 | |
+#define DSM_TYPE_NVIDIA 2 | |
+static int dsm_type = DSM_TYPE_UNSUPPORTED; | |
+ | |
+static struct pci_dev *dis_dev; | |
+static acpi_handle dis_handle; | |
+ | |
+/* whether the card was off before suspend or not; on: 0, off: 1 */ | |
+static int dis_before_suspend_disabled; | |
+ | |
+static char *buffer_to_string(const char *buffer, size_t n, char *target) { | |
+ int i; | |
+ for (i=0; i<n; i++) { | |
+ snprintf(target + i * 5, 5 * (n - i), "0x%02X,", buffer ? buffer[i] & 0xFF : 0); | |
+ } | |
+ return target; | |
+} | |
+ | |
+// Returns 0 if the call succeeded and non-zero otherwise. If the call | |
+// succeeded, the result is stored in "result" providing that the result is an | |
+// integer or a buffer containing 4 values | |
+static int acpi_call_dsm(acpi_handle handle, const char muid[16], int revid, | |
+ int func, char args[4], uint32_t *result) { | |
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; | |
+ struct acpi_object_list input; | |
+ union acpi_object params[4]; | |
+ union acpi_object *obj; | |
+ int err; | |
+ | |
+ input.count = 4; | |
+ input.pointer = params; | |
+ params[0].type = ACPI_TYPE_BUFFER; | |
+ params[0].buffer.length = 16; | |
+ params[0].buffer.pointer = (char *)muid; | |
+ params[1].type = ACPI_TYPE_INTEGER; | |
+ params[1].integer.value = revid; | |
+ params[2].type = ACPI_TYPE_INTEGER; | |
+ params[2].integer.value = func; | |
+ /* Although the ACPI spec defines Arg3 as a Package, in practise | |
+ * implementations expect a Buffer (CreateWordField and Index functions are | |
+ * applied to it). */ | |
+ params[3].type = ACPI_TYPE_BUFFER; | |
+ params[3].buffer.length = 4; | |
+ if (args) { | |
+ params[3].buffer.pointer = args; | |
+ } else { | |
+ // Some implementations (Asus U36SD) seem to check the args before the | |
+ // function ID and crash if it is not a buffer. | |
+ params[3].buffer.pointer = (char[4]){0, 0, 0, 0}; | |
+ } | |
+ | |
+ err = acpi_evaluate_object(handle, "_DSM", &input, &output); | |
+ if (err) { | |
+ struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; | |
+ char muid_str[5 * 16]; | |
+ char args_str[5 * 4]; | |
+ | |
+ acpi_get_name(handle, ACPI_FULL_PATHNAME, &buf); | |
+ | |
+ pr_warn("failed to evaluate %s._DSM {%s} 0x%X 0x%X {%s}: %s\n", | |
+ (char *)buf.pointer, | |
+ buffer_to_string(muid, 16, muid_str), revid, func, | |
+ buffer_to_string(args, 4, args_str), acpi_format_exception(err)); | |
+ return err; | |
+ } | |
+ | |
+ obj = (union acpi_object *)output.pointer; | |
+ | |
+ if (obj->type == ACPI_TYPE_INTEGER && result) { | |
+ *result = obj->integer.value; | |
+ } else if (obj->type == ACPI_TYPE_BUFFER) { | |
+ if (obj->buffer.length == 4 && result) { | |
+ *result = 0; | |
+ *result |= obj->buffer.pointer[0]; | |
+ *result |= (obj->buffer.pointer[1] << 8); | |
+ *result |= (obj->buffer.pointer[2] << 16); | |
+ *result |= (obj->buffer.pointer[3] << 24); | |
+ } | |
+ } else { | |
+ pr_warn("_DSM call yields an unsupported result type: %#x\n", | |
+ obj->type); | |
+ } | |
+ | |
+ kfree(output.pointer); | |
+ return 0; | |
+} | |
+ | |
+// Returns 1 if a _DSM function and its function index exists and 0 otherwise | |
+static int has_dsm_func(const char muid[16], int revid, int sfnc) { | |
+ u32 result = 0; | |
+ | |
+ // fail if the _DSM call failed | |
+ if (acpi_call_dsm(dis_handle, muid, revid, 0, 0, &result)) | |
+ return 0; | |
+ | |
+ // ACPI Spec v4 9.14.1: if bit 0 is zero, no function is supported. If | |
+ // the n-th bit is enabled, function n is supported | |
+ return result & 1 && result & (1 << sfnc); | |
+} | |
+ | |
+static int bbswitch_optimus_dsm(void) { | |
+ if (dsm_type == DSM_TYPE_OPTIMUS) { | |
+ char args[] = {1, 0, 0, 3}; | |
+ u32 result = 0; | |
+ | |
+ if (acpi_call_dsm(dis_handle, acpi_optimus_dsm_muid, 0x100, 0x1A, args, | |
+ &result)) { | |
+ // failure | |
+ return 1; | |
+ } | |
+ pr_debug("Result of Optimus _DSM call: %08X\n", result); | |
+ } | |
+ return 0; | |
+} | |
+ | |
+static int bbswitch_acpi_off(void) { | |
+ if (dsm_type == DSM_TYPE_NVIDIA) { | |
+ char args[] = {2, 0, 0, 0}; | |
+ u32 result = 0; | |
+ | |
+ if (acpi_call_dsm(dis_handle, acpi_nvidia_dsm_muid, 0x102, 0x3, args, | |
+ &result)) { | |
+ // failure | |
+ return 1; | |
+ } | |
+ pr_debug("Result of _DSM call for OFF: %08X\n", result); | |
+ } | |
+ return 0; | |
+} | |
+ | |
+static int bbswitch_acpi_on(void) { | |
+ if (dsm_type == DSM_TYPE_NVIDIA) { | |
+ char args[] = {1, 0, 0, 0}; | |
+ u32 result = 0; | |
+ | |
+ if (acpi_call_dsm(dis_handle, acpi_nvidia_dsm_muid, 0x102, 0x3, args, | |
+ &result)) { | |
+ // failure | |
+ return 1; | |
+ } | |
+ pr_debug("Result of _DSM call for ON: %08X\n", result); | |
+ } | |
+ return 0; | |
+} | |
+ | |
+// Returns 1 if the card is disabled, 0 if enabled | |
+static int is_card_disabled(void) { | |
+ u32 cfg_word; | |
+ // read first config word which contains Vendor and Device ID. If all bits | |
+ // are enabled, the device is assumed to be off | |
+ pci_read_config_dword(dis_dev, 0, &cfg_word); | |
+ // if one of the bits is not enabled (the card is enabled), the inverted | |
+ // result will be non-zero and hence logical not will make it 0 ("false") | |
+ return !~cfg_word; | |
+} | |
+ | |
+static void bbswitch_off(void) { | |
+ if (is_card_disabled()) | |
+ return; | |
+ | |
+ // to prevent the system from possibly locking up, don't disable the device | |
+ // if it's still in use by a driver (i.e. nouveau or nvidia) | |
+ if (dis_dev->driver) { | |
+ pr_warn("device %s is in use by driver '%s', refusing OFF\n", | |
+ dev_name(&dis_dev->dev), dis_dev->driver->name); | |
+ return; | |
+ } | |
+ | |
+ pr_info("disabling discrete graphics\n"); | |
+ | |
+ if (bbswitch_optimus_dsm()) { | |
+ pr_warn("Optimus ACPI call failed, the device is not disabled\n"); | |
+ return; | |
+ } | |
+ | |
+ pci_save_state(dis_dev); | |
+ pci_clear_master(dis_dev); | |
+ pci_disable_device(dis_dev); | |
+ do { | |
+ struct acpi_device *ad = NULL; | |
+ int r; | |
+ | |
+ r = acpi_bus_get_device(dis_handle, &ad); | |
+ if (r || !ad) { | |
+ pr_warn("Cannot get ACPI device for PCI device\n"); | |
+ break; | |
+ } | |
+ if (ad->power.state == ACPI_STATE_UNKNOWN) { | |
+ pr_debug("ACPI power state is unknown, forcing D0\n"); | |
+ ad->power.state = ACPI_STATE_D0; | |
+ } | |
+ } while (0); | |
+ pci_set_power_state(dis_dev, PCI_D3cold); | |
+ | |
+ if (bbswitch_acpi_off()) | |
+ pr_warn("The discrete card could not be disabled by a _DSM call\n"); | |
+} | |
+ | |
+static void bbswitch_on(void) { | |
+ if (!is_card_disabled()) | |
+ return; | |
+ | |
+ pr_info("enabling discrete graphics\n"); | |
+ | |
+ if (bbswitch_acpi_on()) | |
+ pr_warn("The discrete card could not be enabled by a _DSM call\n"); | |
+ | |
+ pci_set_power_state(dis_dev, PCI_D0); | |
+ pci_restore_state(dis_dev); | |
+ if (pci_enable_device(dis_dev)) | |
+ pr_warn("failed to enable %s\n", dev_name(&dis_dev->dev)); | |
+ pci_set_master(dis_dev); | |
+} | |
+ | |
+/* power bus so we can read PCI configuration space */ | |
+static void dis_dev_get(void) { | |
+ if (dis_dev->bus && dis_dev->bus->self) | |
+ pm_runtime_get_sync(&dis_dev->bus->self->dev); | |
+} | |
+ | |
+static void dis_dev_put(void) { | |
+ if (dis_dev->bus && dis_dev->bus->self) | |
+ pm_runtime_put_sync(&dis_dev->bus->self->dev); | |
+} | |
+ | |
+static ssize_t bbswitch_proc_write(struct file *fp, const char __user *buff, | |
+ size_t len, loff_t *off) { | |
+ char cmd[8]; | |
+ | |
+ if (len >= sizeof(cmd)) | |
+ len = sizeof(cmd) - 1; | |
+ | |
+ if (copy_from_user(cmd, buff, len)) | |
+ return -EFAULT; | |
+ | |
+ dis_dev_get(); | |
+ | |
+ if (strncmp(cmd, "OFF", 3) == 0) | |
+ bbswitch_off(); | |
+ | |
+ if (strncmp(cmd, "ON", 2) == 0) | |
+ bbswitch_on(); | |
+ | |
+ dis_dev_put(); | |
+ | |
+ return len; | |
+} | |
+ | |
+static int bbswitch_proc_show(struct seq_file *seqfp, void *p) { | |
+ // show the card state. Example output: 0000:01:00:00 ON | |
+ dis_dev_get(); | |
+ seq_printf(seqfp, "%s %s\n", dev_name(&dis_dev->dev), | |
+ is_card_disabled() ? "OFF" : "ON"); | |
+ dis_dev_put(); | |
+ return 0; | |
+} | |
+static int bbswitch_proc_open(struct inode *inode, struct file *file) { | |
+ return single_open(file, bbswitch_proc_show, NULL); | |
+} | |
+ | |
+static int bbswitch_pm_handler(struct notifier_block *nbp, | |
+ unsigned long event_type, void *p) { | |
+ switch (event_type) { | |
+ case PM_HIBERNATION_PREPARE: | |
+ case PM_SUSPEND_PREPARE: | |
+ dis_dev_get(); | |
+ dis_before_suspend_disabled = is_card_disabled(); | |
+ // enable the device before suspend to avoid the PCI config space from | |
+ // being saved incorrectly | |
+ if (dis_before_suspend_disabled) | |
+ bbswitch_on(); | |
+ dis_dev_put(); | |
+ break; | |
+ case PM_POST_HIBERNATION: | |
+ case PM_POST_SUSPEND: | |
+ case PM_POST_RESTORE: | |
+ // after suspend, the card is on, but if it was off before suspend, | |
+ // disable it again | |
+ if (dis_before_suspend_disabled) { | |
+ dis_dev_get(); | |
+ bbswitch_off(); | |
+ dis_dev_put(); | |
+ } | |
+ break; | |
+ case PM_RESTORE_PREPARE: | |
+ // deliberately don't do anything as it does not occur before suspend | |
+ // nor hibernate, but before restoring a saved image. In that case, | |
+ // either PM_POST_HIBERNATION or PM_POST_RESTORE will be called | |
+ break; | |
+ } | |
+ return 0; | |
+} | |
+ | |
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) | |
+static struct proc_ops bbswitch_fops = { | |
+ .proc_open = bbswitch_proc_open, | |
+ .proc_read = seq_read, | |
+ .proc_write = bbswitch_proc_write, | |
+ .proc_lseek = seq_lseek, | |
+ .proc_release= single_release | |
+}; | |
+#else | |
+static struct file_operations bbswitch_fops = { | |
+ .open = bbswitch_proc_open, | |
+ .read = seq_read, | |
+ .write = bbswitch_proc_write, | |
+ .llseek = seq_lseek, | |
+ .release= single_release | |
+}; | |
+#endif | |
+ | |
+static struct notifier_block nb = { | |
+ .notifier_call = &bbswitch_pm_handler | |
+}; | |
+ | |
+static int __init bbswitch_init(void) { | |
+ struct proc_dir_entry *acpi_entry; | |
+ struct pci_dev *pdev = NULL; | |
+ acpi_handle igd_handle = NULL; | |
+ | |
+ pr_info("version %s\n", BBSWITCH_VERSION); | |
+ | |
+ while ((pdev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, pdev)) != NULL) { | |
+ struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; | |
+ acpi_handle handle; | |
+ int pci_class = pdev->class >> 8; | |
+ | |
+ if (pci_class != PCI_CLASS_DISPLAY_VGA && | |
+ pci_class != PCI_CLASS_DISPLAY_3D) | |
+ continue; | |
+ | |
+#ifdef ACPI_HANDLE | |
+ /* since Linux 3.8 */ | |
+ handle = ACPI_HANDLE(&pdev->dev); | |
+#else | |
+ /* removed since Linux 3.13 */ | |
+ handle = DEVICE_ACPI_HANDLE(&pdev->dev); | |
+#endif | |
+ if (!handle) { | |
+ pr_warn("cannot find ACPI handle for VGA device %s\n", | |
+ dev_name(&pdev->dev)); | |
+ continue; | |
+ } | |
+ | |
+ acpi_get_name(handle, ACPI_FULL_PATHNAME, &buf); | |
+ | |
+ if (pdev->vendor == PCI_VENDOR_ID_INTEL) { | |
+ igd_handle = handle; | |
+ pr_info("Found integrated VGA device %s: %s\n", | |
+ dev_name(&pdev->dev), (char *)buf.pointer); | |
+ } else { | |
+ dis_dev = pdev; | |
+ dis_handle = handle; | |
+ pr_info("Found discrete VGA device %s: %s\n", | |
+ dev_name(&pdev->dev), (char *)buf.pointer); | |
+ } | |
+ kfree(buf.pointer); | |
+ } | |
+ | |
+ if (dis_dev == NULL) { | |
+ pr_err("No discrete VGA device found\n"); | |
+ return -ENODEV; | |
+ } | |
+ | |
+ if (!skip_optimus_dsm && | |
+ has_dsm_func(acpi_optimus_dsm_muid, 0x100, 0x1A)) { | |
+ dsm_type = DSM_TYPE_OPTIMUS; | |
+ pr_info("detected an Optimus _DSM function\n"); | |
+ } else if (has_dsm_func(acpi_nvidia_dsm_muid, 0x102, 0x3)) { | |
+ dsm_type = DSM_TYPE_NVIDIA; | |
+ pr_info("detected a nVidia _DSM function\n"); | |
+ } else { | |
+ /* At least two Acer machines are known to use the intel ACPI handle | |
+ * with the legacy nvidia DSM */ | |
+ dis_handle = igd_handle; | |
+ if (dis_handle && has_dsm_func(acpi_nvidia_dsm_muid, 0x102, 0x3)) { | |
+ dsm_type = DSM_TYPE_NVIDIA; | |
+ pr_info("detected a nVidia _DSM function on the" | |
+ " integrated video card\n"); | |
+ } else { | |
+ pr_err("No suitable _DSM call found.\n"); | |
+ return -ENODEV; | |
+ } | |
+ } | |
+ | |
+ acpi_entry = proc_create("bbswitch", 0664, acpi_root_dir, &bbswitch_fops); | |
+ if (acpi_entry == NULL) { | |
+ pr_err("Couldn't create proc entry\n"); | |
+ return -ENOMEM; | |
+ } | |
+ | |
+ dis_dev_get(); | |
+ | |
+ if (!is_card_disabled()) { | |
+ /* We think the card is enabled, so ensure the kernel does as well */ | |
+ if (pci_enable_device(dis_dev)) | |
+ pr_warn("failed to enable %s\n", dev_name(&dis_dev->dev)); | |
+ } | |
+ | |
+ if (load_state == CARD_ON) | |
+ bbswitch_on(); | |
+ else if (load_state == CARD_OFF) | |
+ bbswitch_off(); | |
+ | |
+ pr_info("Succesfully loaded. Discrete card %s is %s\n", | |
+ dev_name(&dis_dev->dev), is_card_disabled() ? "off" : "on"); | |
+ | |
+ dis_dev_put(); | |
+ | |
+ register_pm_notifier(&nb); | |
+ | |
+ return 0; | |
+} | |
+ | |
+static void __exit bbswitch_exit(void) { | |
+ remove_proc_entry("bbswitch", acpi_root_dir); | |
+ | |
+ dis_dev_get(); | |
+ | |
+ if (unload_state == CARD_ON) | |
+ bbswitch_on(); | |
+ else if (unload_state == CARD_OFF) | |
+ bbswitch_off(); | |
+ | |
+ pr_info("Unloaded. Discrete card %s is %s\n", | |
+ dev_name(&dis_dev->dev), is_card_disabled() ? "off" : "on"); | |
+ | |
+ dis_dev_put(); | |
+ | |
+ if (nb.notifier_call) | |
+ unregister_pm_notifier(&nb); | |
+} | |
+ | |
+module_init(bbswitch_init); | |
+module_exit(bbswitch_exit); | |
+ | |
+/* vim: set sw=4 ts=4 et: */ | |
--- /dev/null 2020-12-29 09:26:56.248219448 +0530 | |
+++ b/drivers/video/bbswitch/Kconfig 2020-12-29 09:25:17.522219618 +0530 | |
@@ -0,0 +1,3 @@ | |
+config DISPLAY_BBSWITCH | |
+ tristate "BBSwitch Driver" | |
+ default y | |
--- /dev/null 2020-12-29 08:23:57.202225942 +0530 | |
+++ b/drivers/video/bbswitch/Makefile 2020-12-29 08:29:02.274225417 +0530 | |
@@ -0,0 +1 @@ | |
+obj-$(CONFIG_DISPLAY_BBSWITCH) += bbswitch.o | |
--- a/drivers/video/Kconfig 2020-12-29 08:37:37.255224533 +0530 | |
+++ b/drivers/video/Kconfig 2020-12-29 08:27:55.823225532 +0530 | |
@@ -16,7 +16,7 @@ | |
source "drivers/gpu/host1x/Kconfig" | |
source "drivers/gpu/ipu-v3/Kconfig" | |
- | |
+source "drivers/video/bbswitch/Kconfig" | |
source "drivers/gpu/drm/Kconfig" | |
menu "Frame buffer Devices" | |
--- a/drivers/video/Makefile 2020-12-29 08:50:56.341223160 +0530 | |
+++ b/drivers/video/Makefile 2020-12-29 08:52:46.508222970 +0530 | |
@@ -5,6 +5,7 @@ | |
obj-$(CONFIG_VT) += console/ | |
obj-$(CONFIG_FB_STI) += console/ | |
obj-$(CONFIG_LOGO) += logo/ | |
+obj-$(CONFIG_DISPLAY_BBSWITCH) += bbswitch/ | |
obj-y += backlight/ | |
obj-y += fbdev/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment