162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * System Specific setup for PCEngines ALIX.
462306a36Sopenharmony_ci * At the moment this means setup of GPIO control of LEDs
562306a36Sopenharmony_ci * on Alix.2/3/6 boards.
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Copyright (C) 2008 Constantin Baranov <const@mimas.ru>
862306a36Sopenharmony_ci * Copyright (C) 2011 Ed Wildgoose <kernel@wildgooses.com>
962306a36Sopenharmony_ci *                and Philip Prindeville <philipp@redfish-solutions.com>
1062306a36Sopenharmony_ci *
1162306a36Sopenharmony_ci * TODO: There are large similarities with leds-net5501.c
1262306a36Sopenharmony_ci * by Alessandro Zummo <a.zummo@towertech.it>
1362306a36Sopenharmony_ci * In the future leds-net5501.c should be migrated over to platform
1462306a36Sopenharmony_ci */
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include <linux/kernel.h>
1762306a36Sopenharmony_ci#include <linux/init.h>
1862306a36Sopenharmony_ci#include <linux/io.h>
1962306a36Sopenharmony_ci#include <linux/string.h>
2062306a36Sopenharmony_ci#include <linux/moduleparam.h>
2162306a36Sopenharmony_ci#include <linux/leds.h>
2262306a36Sopenharmony_ci#include <linux/platform_device.h>
2362306a36Sopenharmony_ci#include <linux/input.h>
2462306a36Sopenharmony_ci#include <linux/gpio_keys.h>
2562306a36Sopenharmony_ci#include <linux/gpio/machine.h>
2662306a36Sopenharmony_ci#include <linux/dmi.h>
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci#include <asm/geode.h>
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci#define BIOS_SIGNATURE_TINYBIOS		0xf0000
3162306a36Sopenharmony_ci#define BIOS_SIGNATURE_COREBOOT		0x500
3262306a36Sopenharmony_ci#define BIOS_REGION_SIZE		0x10000
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci/*
3562306a36Sopenharmony_ci * This driver is not modular, but to keep back compatibility
3662306a36Sopenharmony_ci * with existing use cases, continuing with module_param is
3762306a36Sopenharmony_ci * the easiest way forward.
3862306a36Sopenharmony_ci */
3962306a36Sopenharmony_cistatic bool force = 0;
4062306a36Sopenharmony_cimodule_param(force, bool, 0444);
4162306a36Sopenharmony_ci/* FIXME: Award bios is not automatically detected as Alix platform */
4262306a36Sopenharmony_ciMODULE_PARM_DESC(force, "Force detection as ALIX.2/ALIX.3 platform");
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cistatic struct gpio_keys_button alix_gpio_buttons[] = {
4562306a36Sopenharmony_ci	{
4662306a36Sopenharmony_ci		.code			= KEY_RESTART,
4762306a36Sopenharmony_ci		.gpio			= 24,
4862306a36Sopenharmony_ci		.active_low		= 1,
4962306a36Sopenharmony_ci		.desc			= "Reset button",
5062306a36Sopenharmony_ci		.type			= EV_KEY,
5162306a36Sopenharmony_ci		.wakeup			= 0,
5262306a36Sopenharmony_ci		.debounce_interval	= 100,
5362306a36Sopenharmony_ci		.can_disable		= 0,
5462306a36Sopenharmony_ci	}
5562306a36Sopenharmony_ci};
5662306a36Sopenharmony_cistatic struct gpio_keys_platform_data alix_buttons_data = {
5762306a36Sopenharmony_ci	.buttons			= alix_gpio_buttons,
5862306a36Sopenharmony_ci	.nbuttons			= ARRAY_SIZE(alix_gpio_buttons),
5962306a36Sopenharmony_ci	.poll_interval			= 20,
6062306a36Sopenharmony_ci};
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_cistatic struct platform_device alix_buttons_dev = {
6362306a36Sopenharmony_ci	.name				= "gpio-keys-polled",
6462306a36Sopenharmony_ci	.id				= 1,
6562306a36Sopenharmony_ci	.dev = {
6662306a36Sopenharmony_ci		.platform_data		= &alix_buttons_data,
6762306a36Sopenharmony_ci	}
6862306a36Sopenharmony_ci};
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_cistatic struct gpio_led alix_leds[] = {
7162306a36Sopenharmony_ci	{
7262306a36Sopenharmony_ci		.name = "alix:1",
7362306a36Sopenharmony_ci		.default_trigger = "default-on",
7462306a36Sopenharmony_ci	},
7562306a36Sopenharmony_ci	{
7662306a36Sopenharmony_ci		.name = "alix:2",
7762306a36Sopenharmony_ci		.default_trigger = "default-off",
7862306a36Sopenharmony_ci	},
7962306a36Sopenharmony_ci	{
8062306a36Sopenharmony_ci		.name = "alix:3",
8162306a36Sopenharmony_ci		.default_trigger = "default-off",
8262306a36Sopenharmony_ci	},
8362306a36Sopenharmony_ci};
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_cistatic struct gpio_led_platform_data alix_leds_data = {
8662306a36Sopenharmony_ci	.num_leds = ARRAY_SIZE(alix_leds),
8762306a36Sopenharmony_ci	.leds = alix_leds,
8862306a36Sopenharmony_ci};
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_cistatic struct gpiod_lookup_table alix_leds_gpio_table = {
9162306a36Sopenharmony_ci	.dev_id = "leds-gpio",
9262306a36Sopenharmony_ci	.table = {
9362306a36Sopenharmony_ci		/* The Geode GPIOs should be on the CS5535 companion chip */
9462306a36Sopenharmony_ci		GPIO_LOOKUP_IDX("cs5535-gpio", 6, NULL, 0, GPIO_ACTIVE_LOW),
9562306a36Sopenharmony_ci		GPIO_LOOKUP_IDX("cs5535-gpio", 25, NULL, 1, GPIO_ACTIVE_LOW),
9662306a36Sopenharmony_ci		GPIO_LOOKUP_IDX("cs5535-gpio", 27, NULL, 2, GPIO_ACTIVE_LOW),
9762306a36Sopenharmony_ci		{ }
9862306a36Sopenharmony_ci	},
9962306a36Sopenharmony_ci};
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_cistatic struct platform_device alix_leds_dev = {
10262306a36Sopenharmony_ci	.name = "leds-gpio",
10362306a36Sopenharmony_ci	.id = -1,
10462306a36Sopenharmony_ci	.dev.platform_data = &alix_leds_data,
10562306a36Sopenharmony_ci};
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_cistatic struct platform_device *alix_devs[] __initdata = {
10862306a36Sopenharmony_ci	&alix_buttons_dev,
10962306a36Sopenharmony_ci	&alix_leds_dev,
11062306a36Sopenharmony_ci};
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_cistatic void __init register_alix(void)
11362306a36Sopenharmony_ci{
11462306a36Sopenharmony_ci	/* Setup LED control through leds-gpio driver */
11562306a36Sopenharmony_ci	gpiod_add_lookup_table(&alix_leds_gpio_table);
11662306a36Sopenharmony_ci	platform_add_devices(alix_devs, ARRAY_SIZE(alix_devs));
11762306a36Sopenharmony_ci}
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_cistatic bool __init alix_present(unsigned long bios_phys,
12062306a36Sopenharmony_ci				const char *alix_sig,
12162306a36Sopenharmony_ci				size_t alix_sig_len)
12262306a36Sopenharmony_ci{
12362306a36Sopenharmony_ci	const size_t bios_len = BIOS_REGION_SIZE;
12462306a36Sopenharmony_ci	const char *bios_virt;
12562306a36Sopenharmony_ci	const char *scan_end;
12662306a36Sopenharmony_ci	const char *p;
12762306a36Sopenharmony_ci	char name[64];
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	if (force) {
13062306a36Sopenharmony_ci		printk(KERN_NOTICE "%s: forced to skip BIOS test, "
13162306a36Sopenharmony_ci		       "assume system is ALIX.2/ALIX.3\n",
13262306a36Sopenharmony_ci		       KBUILD_MODNAME);
13362306a36Sopenharmony_ci		return true;
13462306a36Sopenharmony_ci	}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	bios_virt = phys_to_virt(bios_phys);
13762306a36Sopenharmony_ci	scan_end = bios_virt + bios_len - (alix_sig_len + 2);
13862306a36Sopenharmony_ci	for (p = bios_virt; p < scan_end; p++) {
13962306a36Sopenharmony_ci		const char *tail;
14062306a36Sopenharmony_ci		char *a;
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci		if (memcmp(p, alix_sig, alix_sig_len) != 0)
14362306a36Sopenharmony_ci			continue;
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci		memcpy(name, p, sizeof(name));
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci		/* remove the first \0 character from string */
14862306a36Sopenharmony_ci		a = strchr(name, '\0');
14962306a36Sopenharmony_ci		if (a)
15062306a36Sopenharmony_ci			*a = ' ';
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci		/* cut the string at a newline */
15362306a36Sopenharmony_ci		a = strchr(name, '\r');
15462306a36Sopenharmony_ci		if (a)
15562306a36Sopenharmony_ci			*a = '\0';
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci		tail = p + alix_sig_len;
15862306a36Sopenharmony_ci		if ((tail[0] == '2' || tail[0] == '3' || tail[0] == '6')) {
15962306a36Sopenharmony_ci			printk(KERN_INFO
16062306a36Sopenharmony_ci			       "%s: system is recognized as \"%s\"\n",
16162306a36Sopenharmony_ci			       KBUILD_MODNAME, name);
16262306a36Sopenharmony_ci			return true;
16362306a36Sopenharmony_ci		}
16462306a36Sopenharmony_ci	}
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	return false;
16762306a36Sopenharmony_ci}
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_cistatic bool __init alix_present_dmi(void)
17062306a36Sopenharmony_ci{
17162306a36Sopenharmony_ci	const char *vendor, *product;
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	vendor = dmi_get_system_info(DMI_SYS_VENDOR);
17462306a36Sopenharmony_ci	if (!vendor || strcmp(vendor, "PC Engines"))
17562306a36Sopenharmony_ci		return false;
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	product = dmi_get_system_info(DMI_PRODUCT_NAME);
17862306a36Sopenharmony_ci	if (!product || (strcmp(product, "ALIX.2D") && strcmp(product, "ALIX.6")))
17962306a36Sopenharmony_ci		return false;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	printk(KERN_INFO "%s: system is recognized as \"%s %s\"\n",
18262306a36Sopenharmony_ci	       KBUILD_MODNAME, vendor, product);
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	return true;
18562306a36Sopenharmony_ci}
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_cistatic int __init alix_init(void)
18862306a36Sopenharmony_ci{
18962306a36Sopenharmony_ci	const char tinybios_sig[] = "PC Engines ALIX.";
19062306a36Sopenharmony_ci	const char coreboot_sig[] = "PC Engines\0ALIX.";
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	if (!is_geode())
19362306a36Sopenharmony_ci		return 0;
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	if (alix_present(BIOS_SIGNATURE_TINYBIOS, tinybios_sig, sizeof(tinybios_sig) - 1) ||
19662306a36Sopenharmony_ci	    alix_present(BIOS_SIGNATURE_COREBOOT, coreboot_sig, sizeof(coreboot_sig) - 1) ||
19762306a36Sopenharmony_ci	    alix_present_dmi())
19862306a36Sopenharmony_ci		register_alix();
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	return 0;
20162306a36Sopenharmony_ci}
20262306a36Sopenharmony_cidevice_initcall(alix_init);
203