alix.c (4973B)
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/gpio/machine.h> 26#include <linux/dmi.h> 27 28#include <asm/geode.h> 29 30#define BIOS_SIGNATURE_TINYBIOS 0xf0000 31#define BIOS_SIGNATURE_COREBOOT 0x500 32#define BIOS_REGION_SIZE 0x10000 33 34/* 35 * This driver is not modular, but to keep back compatibility 36 * with existing use cases, continuing with module_param is 37 * the easiest way forward. 38 */ 39static bool force = 0; 40module_param(force, bool, 0444); 41/* FIXME: Award bios is not automatically detected as Alix platform */ 42MODULE_PARM_DESC(force, "Force detection as ALIX.2/ALIX.3 platform"); 43 44static struct gpio_keys_button alix_gpio_buttons[] = { 45 { 46 .code = KEY_RESTART, 47 .gpio = 24, 48 .active_low = 1, 49 .desc = "Reset button", 50 .type = EV_KEY, 51 .wakeup = 0, 52 .debounce_interval = 100, 53 .can_disable = 0, 54 } 55}; 56static struct gpio_keys_platform_data alix_buttons_data = { 57 .buttons = alix_gpio_buttons, 58 .nbuttons = ARRAY_SIZE(alix_gpio_buttons), 59 .poll_interval = 20, 60}; 61 62static struct platform_device alix_buttons_dev = { 63 .name = "gpio-keys-polled", 64 .id = 1, 65 .dev = { 66 .platform_data = &alix_buttons_data, 67 } 68}; 69 70static struct gpio_led alix_leds[] = { 71 { 72 .name = "alix:1", 73 .default_trigger = "default-on", 74 }, 75 { 76 .name = "alix:2", 77 .default_trigger = "default-off", 78 }, 79 { 80 .name = "alix:3", 81 .default_trigger = "default-off", 82 }, 83}; 84 85static struct gpio_led_platform_data alix_leds_data = { 86 .num_leds = ARRAY_SIZE(alix_leds), 87 .leds = alix_leds, 88}; 89 90static struct gpiod_lookup_table alix_leds_gpio_table = { 91 .dev_id = "leds-gpio", 92 .table = { 93 /* The Geode GPIOs should be on the CS5535 companion chip */ 94 GPIO_LOOKUP_IDX("cs5535-gpio", 6, NULL, 0, GPIO_ACTIVE_LOW), 95 GPIO_LOOKUP_IDX("cs5535-gpio", 25, NULL, 1, GPIO_ACTIVE_LOW), 96 GPIO_LOOKUP_IDX("cs5535-gpio", 27, NULL, 2, GPIO_ACTIVE_LOW), 97 { } 98 }, 99}; 100 101static struct platform_device alix_leds_dev = { 102 .name = "leds-gpio", 103 .id = -1, 104 .dev.platform_data = &alix_leds_data, 105}; 106 107static struct platform_device *alix_devs[] __initdata = { 108 &alix_buttons_dev, 109 &alix_leds_dev, 110}; 111 112static void __init register_alix(void) 113{ 114 /* Setup LED control through leds-gpio driver */ 115 gpiod_add_lookup_table(&alix_leds_gpio_table); 116 platform_add_devices(alix_devs, ARRAY_SIZE(alix_devs)); 117} 118 119static bool __init alix_present(unsigned long bios_phys, 120 const char *alix_sig, 121 size_t alix_sig_len) 122{ 123 const size_t bios_len = BIOS_REGION_SIZE; 124 const char *bios_virt; 125 const char *scan_end; 126 const char *p; 127 char name[64]; 128 129 if (force) { 130 printk(KERN_NOTICE "%s: forced to skip BIOS test, " 131 "assume system is ALIX.2/ALIX.3\n", 132 KBUILD_MODNAME); 133 return true; 134 } 135 136 bios_virt = phys_to_virt(bios_phys); 137 scan_end = bios_virt + bios_len - (alix_sig_len + 2); 138 for (p = bios_virt; p < scan_end; p++) { 139 const char *tail; 140 char *a; 141 142 if (memcmp(p, alix_sig, alix_sig_len) != 0) 143 continue; 144 145 memcpy(name, p, sizeof(name)); 146 147 /* remove the first \0 character from string */ 148 a = strchr(name, '\0'); 149 if (a) 150 *a = ' '; 151 152 /* cut the string at a newline */ 153 a = strchr(name, '\r'); 154 if (a) 155 *a = '\0'; 156 157 tail = p + alix_sig_len; 158 if ((tail[0] == '2' || tail[0] == '3' || tail[0] == '6')) { 159 printk(KERN_INFO 160 "%s: system is recognized as \"%s\"\n", 161 KBUILD_MODNAME, name); 162 return true; 163 } 164 } 165 166 return false; 167} 168 169static bool __init alix_present_dmi(void) 170{ 171 const char *vendor, *product; 172 173 vendor = dmi_get_system_info(DMI_SYS_VENDOR); 174 if (!vendor || strcmp(vendor, "PC Engines")) 175 return false; 176 177 product = dmi_get_system_info(DMI_PRODUCT_NAME); 178 if (!product || (strcmp(product, "ALIX.2D") && strcmp(product, "ALIX.6"))) 179 return false; 180 181 printk(KERN_INFO "%s: system is recognized as \"%s %s\"\n", 182 KBUILD_MODNAME, vendor, product); 183 184 return true; 185} 186 187static int __init alix_init(void) 188{ 189 const char tinybios_sig[] = "PC Engines ALIX."; 190 const char coreboot_sig[] = "PC Engines\0ALIX."; 191 192 if (!is_geode()) 193 return 0; 194 195 if (alix_present(BIOS_SIGNATURE_TINYBIOS, tinybios_sig, sizeof(tinybios_sig) - 1) || 196 alix_present(BIOS_SIGNATURE_COREBOOT, coreboot_sig, sizeof(coreboot_sig) - 1) || 197 alix_present_dmi()) 198 register_alix(); 199 200 return 0; 201} 202device_initcall(alix_init);