xref: /linux/arch/x86/platform/geode/alix.c (revision 0d3b051adbb72ed81956447d0d1e54d5943ee6f5)
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