1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * System Specific setup for PCEngines ALIX. 4 * At the moment this means setup of GPIO control of LEDs 5 * on Alix.2/3/6 boards. 6 * 7 * Copyright (C) 2008 Constantin Baranov <const@mimas.ru> 8 * Copyright (C) 2011 Ed Wildgoose <kernel@wildgooses.com> 9 * and Philip Prindeville <philipp@redfish-solutions.com> 10 * 11 * TODO: There are large similarities with leds-net5501.c 12 * by Alessandro Zummo <a.zummo@towertech.it> 13 * In the future leds-net5501.c should be migrated over to platform 14 */ 15 16 #include <linux/kernel.h> 17 #include <linux/init.h> 18 #include <linux/io.h> 19 #include <linux/string.h> 20 #include <linux/moduleparam.h> 21 #include <linux/leds.h> 22 #include <linux/platform_device.h> 23 #include <linux/input.h> 24 #include <linux/gpio_keys.h> 25 #include <linux/dmi.h> 26 27 #include <asm/geode.h> 28 29 #define BIOS_SIGNATURE_TINYBIOS 0xf0000 30 #define BIOS_SIGNATURE_COREBOOT 0x500 31 #define BIOS_REGION_SIZE 0x10000 32 33 /* 34 * This driver is not modular, but to keep back compatibility 35 * with existing use cases, continuing with module_param is 36 * the easiest way forward. 37 */ 38 static bool force = 0; 39 module_param(force, bool, 0444); 40 /* FIXME: Award bios is not automatically detected as Alix platform */ 41 MODULE_PARM_DESC(force, "Force detection as ALIX.2/ALIX.3 platform"); 42 43 static struct gpio_keys_button alix_gpio_buttons[] = { 44 { 45 .code = KEY_RESTART, 46 .gpio = 24, 47 .active_low = 1, 48 .desc = "Reset button", 49 .type = EV_KEY, 50 .wakeup = 0, 51 .debounce_interval = 100, 52 .can_disable = 0, 53 } 54 }; 55 static struct gpio_keys_platform_data alix_buttons_data = { 56 .buttons = alix_gpio_buttons, 57 .nbuttons = ARRAY_SIZE(alix_gpio_buttons), 58 .poll_interval = 20, 59 }; 60 61 static struct platform_device alix_buttons_dev = { 62 .name = "gpio-keys-polled", 63 .id = 1, 64 .dev = { 65 .platform_data = &alix_buttons_data, 66 } 67 }; 68 69 static struct gpio_led alix_leds[] = { 70 { 71 .name = "alix:1", 72 .gpio = 6, 73 .default_trigger = "default-on", 74 .active_low = 1, 75 }, 76 { 77 .name = "alix:2", 78 .gpio = 25, 79 .default_trigger = "default-off", 80 .active_low = 1, 81 }, 82 { 83 .name = "alix:3", 84 .gpio = 27, 85 .default_trigger = "default-off", 86 .active_low = 1, 87 }, 88 }; 89 90 static struct gpio_led_platform_data alix_leds_data = { 91 .num_leds = ARRAY_SIZE(alix_leds), 92 .leds = alix_leds, 93 }; 94 95 static struct platform_device alix_leds_dev = { 96 .name = "leds-gpio", 97 .id = -1, 98 .dev.platform_data = &alix_leds_data, 99 }; 100 101 static struct platform_device *alix_devs[] __initdata = { 102 &alix_buttons_dev, 103 &alix_leds_dev, 104 }; 105 106 static void __init register_alix(void) 107 { 108 /* Setup LED control through leds-gpio driver */ 109 platform_add_devices(alix_devs, ARRAY_SIZE(alix_devs)); 110 } 111 112 static bool __init alix_present(unsigned long bios_phys, 113 const char *alix_sig, 114 size_t alix_sig_len) 115 { 116 const size_t bios_len = BIOS_REGION_SIZE; 117 const char *bios_virt; 118 const char *scan_end; 119 const char *p; 120 char name[64]; 121 122 if (force) { 123 printk(KERN_NOTICE "%s: forced to skip BIOS test, " 124 "assume system is ALIX.2/ALIX.3\n", 125 KBUILD_MODNAME); 126 return true; 127 } 128 129 bios_virt = phys_to_virt(bios_phys); 130 scan_end = bios_virt + bios_len - (alix_sig_len + 2); 131 for (p = bios_virt; p < scan_end; p++) { 132 const char *tail; 133 char *a; 134 135 if (memcmp(p, alix_sig, alix_sig_len) != 0) 136 continue; 137 138 memcpy(name, p, sizeof(name)); 139 140 /* remove the first \0 character from string */ 141 a = strchr(name, '\0'); 142 if (a) 143 *a = ' '; 144 145 /* cut the string at a newline */ 146 a = strchr(name, '\r'); 147 if (a) 148 *a = '\0'; 149 150 tail = p + alix_sig_len; 151 if ((tail[0] == '2' || tail[0] == '3' || tail[0] == '6')) { 152 printk(KERN_INFO 153 "%s: system is recognized as \"%s\"\n", 154 KBUILD_MODNAME, name); 155 return true; 156 } 157 } 158 159 return false; 160 } 161 162 static bool __init alix_present_dmi(void) 163 { 164 const char *vendor, *product; 165 166 vendor = dmi_get_system_info(DMI_SYS_VENDOR); 167 if (!vendor || strcmp(vendor, "PC Engines")) 168 return false; 169 170 product = dmi_get_system_info(DMI_PRODUCT_NAME); 171 if (!product || (strcmp(product, "ALIX.2D") && strcmp(product, "ALIX.6"))) 172 return false; 173 174 printk(KERN_INFO "%s: system is recognized as \"%s %s\"\n", 175 KBUILD_MODNAME, vendor, product); 176 177 return true; 178 } 179 180 static int __init alix_init(void) 181 { 182 const char tinybios_sig[] = "PC Engines ALIX."; 183 const char coreboot_sig[] = "PC Engines\0ALIX."; 184 185 if (!is_geode()) 186 return 0; 187 188 if (alix_present(BIOS_SIGNATURE_TINYBIOS, tinybios_sig, sizeof(tinybios_sig) - 1) || 189 alix_present(BIOS_SIGNATURE_COREBOOT, coreboot_sig, sizeof(coreboot_sig) - 1) || 190 alix_present_dmi()) 191 register_alix(); 192 193 return 0; 194 } 195 device_initcall(alix_init); 196