18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
28c2ecf20Sopenharmony_ci
38c2ecf20Sopenharmony_ci/*
48c2ecf20Sopenharmony_ci * PC-Engines APUv2/APUv3 board platform driver
58c2ecf20Sopenharmony_ci * for GPIO buttons and LEDs
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Copyright (C) 2018 metux IT consult
88c2ecf20Sopenharmony_ci * Author: Enrico Weigelt <info@metux.net>
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#define pr_fmt(fmt)	KBUILD_MODNAME ": " fmt
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#include <linux/dmi.h>
148c2ecf20Sopenharmony_ci#include <linux/err.h>
158c2ecf20Sopenharmony_ci#include <linux/kernel.h>
168c2ecf20Sopenharmony_ci#include <linux/leds.h>
178c2ecf20Sopenharmony_ci#include <linux/module.h>
188c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
198c2ecf20Sopenharmony_ci#include <linux/gpio_keys.h>
208c2ecf20Sopenharmony_ci#include <linux/gpio/machine.h>
218c2ecf20Sopenharmony_ci#include <linux/input.h>
228c2ecf20Sopenharmony_ci#include <linux/platform_data/gpio/gpio-amd-fch.h>
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci/*
258c2ecf20Sopenharmony_ci * NOTE: this driver only supports APUv2/3 - not APUv1, as this one
268c2ecf20Sopenharmony_ci * has completely different register layouts.
278c2ecf20Sopenharmony_ci */
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci/* Register mappings */
308c2ecf20Sopenharmony_ci#define APU2_GPIO_REG_LED1		AMD_FCH_GPIO_REG_GPIO57
318c2ecf20Sopenharmony_ci#define APU2_GPIO_REG_LED2		AMD_FCH_GPIO_REG_GPIO58
328c2ecf20Sopenharmony_ci#define APU2_GPIO_REG_LED3		AMD_FCH_GPIO_REG_GPIO59_DEVSLP1
338c2ecf20Sopenharmony_ci#define APU2_GPIO_REG_MODESW		AMD_FCH_GPIO_REG_GPIO32_GE1
348c2ecf20Sopenharmony_ci#define APU2_GPIO_REG_SIMSWAP		AMD_FCH_GPIO_REG_GPIO33_GE2
358c2ecf20Sopenharmony_ci#define APU2_GPIO_REG_MPCIE2		AMD_FCH_GPIO_REG_GPIO55_DEVSLP0
368c2ecf20Sopenharmony_ci#define APU2_GPIO_REG_MPCIE3		AMD_FCH_GPIO_REG_GPIO51
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci/* Order in which the GPIO lines are defined in the register list */
398c2ecf20Sopenharmony_ci#define APU2_GPIO_LINE_LED1		0
408c2ecf20Sopenharmony_ci#define APU2_GPIO_LINE_LED2		1
418c2ecf20Sopenharmony_ci#define APU2_GPIO_LINE_LED3		2
428c2ecf20Sopenharmony_ci#define APU2_GPIO_LINE_MODESW		3
438c2ecf20Sopenharmony_ci#define APU2_GPIO_LINE_SIMSWAP		4
448c2ecf20Sopenharmony_ci#define APU2_GPIO_LINE_MPCIE2		5
458c2ecf20Sopenharmony_ci#define APU2_GPIO_LINE_MPCIE3		6
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci/* GPIO device */
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_cistatic int apu2_gpio_regs[] = {
508c2ecf20Sopenharmony_ci	[APU2_GPIO_LINE_LED1]		= APU2_GPIO_REG_LED1,
518c2ecf20Sopenharmony_ci	[APU2_GPIO_LINE_LED2]		= APU2_GPIO_REG_LED2,
528c2ecf20Sopenharmony_ci	[APU2_GPIO_LINE_LED3]		= APU2_GPIO_REG_LED3,
538c2ecf20Sopenharmony_ci	[APU2_GPIO_LINE_MODESW]		= APU2_GPIO_REG_MODESW,
548c2ecf20Sopenharmony_ci	[APU2_GPIO_LINE_SIMSWAP]	= APU2_GPIO_REG_SIMSWAP,
558c2ecf20Sopenharmony_ci	[APU2_GPIO_LINE_MPCIE2]		= APU2_GPIO_REG_MPCIE2,
568c2ecf20Sopenharmony_ci	[APU2_GPIO_LINE_MPCIE3]		= APU2_GPIO_REG_MPCIE3,
578c2ecf20Sopenharmony_ci};
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_cistatic const char * const apu2_gpio_names[] = {
608c2ecf20Sopenharmony_ci	[APU2_GPIO_LINE_LED1]		= "front-led1",
618c2ecf20Sopenharmony_ci	[APU2_GPIO_LINE_LED2]		= "front-led2",
628c2ecf20Sopenharmony_ci	[APU2_GPIO_LINE_LED3]		= "front-led3",
638c2ecf20Sopenharmony_ci	[APU2_GPIO_LINE_MODESW]		= "front-button",
648c2ecf20Sopenharmony_ci	[APU2_GPIO_LINE_SIMSWAP]	= "simswap",
658c2ecf20Sopenharmony_ci	[APU2_GPIO_LINE_MPCIE2]		= "mpcie2_reset",
668c2ecf20Sopenharmony_ci	[APU2_GPIO_LINE_MPCIE3]		= "mpcie3_reset",
678c2ecf20Sopenharmony_ci};
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_cistatic const struct amd_fch_gpio_pdata board_apu2 = {
708c2ecf20Sopenharmony_ci	.gpio_num	= ARRAY_SIZE(apu2_gpio_regs),
718c2ecf20Sopenharmony_ci	.gpio_reg	= apu2_gpio_regs,
728c2ecf20Sopenharmony_ci	.gpio_names	= apu2_gpio_names,
738c2ecf20Sopenharmony_ci};
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci/* GPIO LEDs device */
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_cistatic const struct gpio_led apu2_leds[] = {
788c2ecf20Sopenharmony_ci	{ .name = "apu:green:1" },
798c2ecf20Sopenharmony_ci	{ .name = "apu:green:2" },
808c2ecf20Sopenharmony_ci	{ .name = "apu:green:3" },
818c2ecf20Sopenharmony_ci};
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_cistatic const struct gpio_led_platform_data apu2_leds_pdata = {
848c2ecf20Sopenharmony_ci	.num_leds	= ARRAY_SIZE(apu2_leds),
858c2ecf20Sopenharmony_ci	.leds		= apu2_leds,
868c2ecf20Sopenharmony_ci};
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_cistatic struct gpiod_lookup_table gpios_led_table = {
898c2ecf20Sopenharmony_ci	.dev_id = "leds-gpio",
908c2ecf20Sopenharmony_ci	.table = {
918c2ecf20Sopenharmony_ci		GPIO_LOOKUP_IDX(AMD_FCH_GPIO_DRIVER_NAME, APU2_GPIO_LINE_LED1,
928c2ecf20Sopenharmony_ci				NULL, 0, GPIO_ACTIVE_LOW),
938c2ecf20Sopenharmony_ci		GPIO_LOOKUP_IDX(AMD_FCH_GPIO_DRIVER_NAME, APU2_GPIO_LINE_LED2,
948c2ecf20Sopenharmony_ci				NULL, 1, GPIO_ACTIVE_LOW),
958c2ecf20Sopenharmony_ci		GPIO_LOOKUP_IDX(AMD_FCH_GPIO_DRIVER_NAME, APU2_GPIO_LINE_LED3,
968c2ecf20Sopenharmony_ci				NULL, 2, GPIO_ACTIVE_LOW),
978c2ecf20Sopenharmony_ci		{} /* Terminating entry */
988c2ecf20Sopenharmony_ci	}
998c2ecf20Sopenharmony_ci};
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci/* GPIO keyboard device */
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_cistatic struct gpio_keys_button apu2_keys_buttons[] = {
1048c2ecf20Sopenharmony_ci	{
1058c2ecf20Sopenharmony_ci		.code			= KEY_RESTART,
1068c2ecf20Sopenharmony_ci		.active_low		= 1,
1078c2ecf20Sopenharmony_ci		.desc			= "front button",
1088c2ecf20Sopenharmony_ci		.type			= EV_KEY,
1098c2ecf20Sopenharmony_ci		.debounce_interval	= 10,
1108c2ecf20Sopenharmony_ci		.value			= 1,
1118c2ecf20Sopenharmony_ci	},
1128c2ecf20Sopenharmony_ci};
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_cistatic const struct gpio_keys_platform_data apu2_keys_pdata = {
1158c2ecf20Sopenharmony_ci	.buttons	= apu2_keys_buttons,
1168c2ecf20Sopenharmony_ci	.nbuttons	= ARRAY_SIZE(apu2_keys_buttons),
1178c2ecf20Sopenharmony_ci	.poll_interval	= 100,
1188c2ecf20Sopenharmony_ci	.rep		= 0,
1198c2ecf20Sopenharmony_ci	.name		= "apu2-keys",
1208c2ecf20Sopenharmony_ci};
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_cistatic struct gpiod_lookup_table gpios_key_table = {
1238c2ecf20Sopenharmony_ci	.dev_id = "gpio-keys-polled",
1248c2ecf20Sopenharmony_ci	.table = {
1258c2ecf20Sopenharmony_ci		GPIO_LOOKUP_IDX(AMD_FCH_GPIO_DRIVER_NAME, APU2_GPIO_LINE_MODESW,
1268c2ecf20Sopenharmony_ci				NULL, 0, GPIO_ACTIVE_LOW),
1278c2ecf20Sopenharmony_ci		{} /* Terminating entry */
1288c2ecf20Sopenharmony_ci	}
1298c2ecf20Sopenharmony_ci};
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci/* Board setup */
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci/* Note: matching works on string prefix, so "apu2" must come before "apu" */
1348c2ecf20Sopenharmony_cistatic const struct dmi_system_id apu_gpio_dmi_table[] __initconst = {
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci	/* APU2 w/ legacy BIOS < 4.0.8 */
1378c2ecf20Sopenharmony_ci	{
1388c2ecf20Sopenharmony_ci		.ident		= "apu2",
1398c2ecf20Sopenharmony_ci		.matches	= {
1408c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "PC Engines"),
1418c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_BOARD_NAME, "APU2")
1428c2ecf20Sopenharmony_ci		},
1438c2ecf20Sopenharmony_ci		.driver_data	= (void *)&board_apu2,
1448c2ecf20Sopenharmony_ci	},
1458c2ecf20Sopenharmony_ci	/* APU2 w/ legacy BIOS >= 4.0.8 */
1468c2ecf20Sopenharmony_ci	{
1478c2ecf20Sopenharmony_ci		.ident		= "apu2",
1488c2ecf20Sopenharmony_ci		.matches	= {
1498c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "PC Engines"),
1508c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_BOARD_NAME, "apu2")
1518c2ecf20Sopenharmony_ci		},
1528c2ecf20Sopenharmony_ci		.driver_data	= (void *)&board_apu2,
1538c2ecf20Sopenharmony_ci	},
1548c2ecf20Sopenharmony_ci	/* APU2 w/ mainline BIOS */
1558c2ecf20Sopenharmony_ci	{
1568c2ecf20Sopenharmony_ci		.ident		= "apu2",
1578c2ecf20Sopenharmony_ci		.matches	= {
1588c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "PC Engines"),
1598c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_BOARD_NAME, "PC Engines apu2")
1608c2ecf20Sopenharmony_ci		},
1618c2ecf20Sopenharmony_ci		.driver_data	= (void *)&board_apu2,
1628c2ecf20Sopenharmony_ci	},
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci	/* APU3 w/ legacy BIOS < 4.0.8 */
1658c2ecf20Sopenharmony_ci	{
1668c2ecf20Sopenharmony_ci		.ident		= "apu3",
1678c2ecf20Sopenharmony_ci		.matches	= {
1688c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "PC Engines"),
1698c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_BOARD_NAME, "APU3")
1708c2ecf20Sopenharmony_ci		},
1718c2ecf20Sopenharmony_ci		.driver_data = (void *)&board_apu2,
1728c2ecf20Sopenharmony_ci	},
1738c2ecf20Sopenharmony_ci	/* APU3 w/ legacy BIOS >= 4.0.8 */
1748c2ecf20Sopenharmony_ci	{
1758c2ecf20Sopenharmony_ci		.ident       = "apu3",
1768c2ecf20Sopenharmony_ci		.matches     = {
1778c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "PC Engines"),
1788c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_BOARD_NAME, "apu3")
1798c2ecf20Sopenharmony_ci		},
1808c2ecf20Sopenharmony_ci		.driver_data = (void *)&board_apu2,
1818c2ecf20Sopenharmony_ci	},
1828c2ecf20Sopenharmony_ci	/* APU3 w/ mainline BIOS */
1838c2ecf20Sopenharmony_ci	{
1848c2ecf20Sopenharmony_ci		.ident       = "apu3",
1858c2ecf20Sopenharmony_ci		.matches     = {
1868c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "PC Engines"),
1878c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_BOARD_NAME, "PC Engines apu3")
1888c2ecf20Sopenharmony_ci		},
1898c2ecf20Sopenharmony_ci		.driver_data = (void *)&board_apu2,
1908c2ecf20Sopenharmony_ci	},
1918c2ecf20Sopenharmony_ci	/* APU4 w/ legacy BIOS < 4.0.8 */
1928c2ecf20Sopenharmony_ci	{
1938c2ecf20Sopenharmony_ci		.ident        = "apu4",
1948c2ecf20Sopenharmony_ci		.matches    = {
1958c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "PC Engines"),
1968c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_BOARD_NAME, "APU4")
1978c2ecf20Sopenharmony_ci		},
1988c2ecf20Sopenharmony_ci		.driver_data = (void *)&board_apu2,
1998c2ecf20Sopenharmony_ci	},
2008c2ecf20Sopenharmony_ci	/* APU4 w/ legacy BIOS >= 4.0.8 */
2018c2ecf20Sopenharmony_ci	{
2028c2ecf20Sopenharmony_ci		.ident       = "apu4",
2038c2ecf20Sopenharmony_ci		.matches     = {
2048c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "PC Engines"),
2058c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_BOARD_NAME, "apu4")
2068c2ecf20Sopenharmony_ci		},
2078c2ecf20Sopenharmony_ci		.driver_data = (void *)&board_apu2,
2088c2ecf20Sopenharmony_ci	},
2098c2ecf20Sopenharmony_ci	/* APU4 w/ mainline BIOS */
2108c2ecf20Sopenharmony_ci	{
2118c2ecf20Sopenharmony_ci		.ident       = "apu4",
2128c2ecf20Sopenharmony_ci		.matches     = {
2138c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "PC Engines"),
2148c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_BOARD_NAME, "PC Engines apu4")
2158c2ecf20Sopenharmony_ci		},
2168c2ecf20Sopenharmony_ci		.driver_data = (void *)&board_apu2,
2178c2ecf20Sopenharmony_ci	},
2188c2ecf20Sopenharmony_ci	{}
2198c2ecf20Sopenharmony_ci};
2208c2ecf20Sopenharmony_ci
2218c2ecf20Sopenharmony_cistatic struct platform_device *apu_gpio_pdev;
2228c2ecf20Sopenharmony_cistatic struct platform_device *apu_leds_pdev;
2238c2ecf20Sopenharmony_cistatic struct platform_device *apu_keys_pdev;
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_cistatic struct platform_device * __init apu_create_pdev(
2268c2ecf20Sopenharmony_ci	const char *name,
2278c2ecf20Sopenharmony_ci	const void *pdata,
2288c2ecf20Sopenharmony_ci	size_t sz)
2298c2ecf20Sopenharmony_ci{
2308c2ecf20Sopenharmony_ci	struct platform_device *pdev;
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ci	pdev = platform_device_register_resndata(NULL,
2338c2ecf20Sopenharmony_ci		name,
2348c2ecf20Sopenharmony_ci		PLATFORM_DEVID_NONE,
2358c2ecf20Sopenharmony_ci		NULL,
2368c2ecf20Sopenharmony_ci		0,
2378c2ecf20Sopenharmony_ci		pdata,
2388c2ecf20Sopenharmony_ci		sz);
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_ci	if (IS_ERR(pdev))
2418c2ecf20Sopenharmony_ci		pr_err("failed registering %s: %ld\n", name, PTR_ERR(pdev));
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci	return pdev;
2448c2ecf20Sopenharmony_ci}
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_cistatic int __init apu_board_init(void)
2478c2ecf20Sopenharmony_ci{
2488c2ecf20Sopenharmony_ci	const struct dmi_system_id *id;
2498c2ecf20Sopenharmony_ci
2508c2ecf20Sopenharmony_ci	id = dmi_first_match(apu_gpio_dmi_table);
2518c2ecf20Sopenharmony_ci	if (!id) {
2528c2ecf20Sopenharmony_ci		pr_err("failed to detect APU board via DMI\n");
2538c2ecf20Sopenharmony_ci		return -ENODEV;
2548c2ecf20Sopenharmony_ci	}
2558c2ecf20Sopenharmony_ci
2568c2ecf20Sopenharmony_ci	gpiod_add_lookup_table(&gpios_led_table);
2578c2ecf20Sopenharmony_ci	gpiod_add_lookup_table(&gpios_key_table);
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci	apu_gpio_pdev = apu_create_pdev(
2608c2ecf20Sopenharmony_ci		AMD_FCH_GPIO_DRIVER_NAME,
2618c2ecf20Sopenharmony_ci		id->driver_data,
2628c2ecf20Sopenharmony_ci		sizeof(struct amd_fch_gpio_pdata));
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_ci	apu_leds_pdev = apu_create_pdev(
2658c2ecf20Sopenharmony_ci		"leds-gpio",
2668c2ecf20Sopenharmony_ci		&apu2_leds_pdata,
2678c2ecf20Sopenharmony_ci		sizeof(apu2_leds_pdata));
2688c2ecf20Sopenharmony_ci
2698c2ecf20Sopenharmony_ci	apu_keys_pdev = apu_create_pdev(
2708c2ecf20Sopenharmony_ci		"gpio-keys-polled",
2718c2ecf20Sopenharmony_ci		&apu2_keys_pdata,
2728c2ecf20Sopenharmony_ci		sizeof(apu2_keys_pdata));
2738c2ecf20Sopenharmony_ci
2748c2ecf20Sopenharmony_ci	return 0;
2758c2ecf20Sopenharmony_ci}
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_cistatic void __exit apu_board_exit(void)
2788c2ecf20Sopenharmony_ci{
2798c2ecf20Sopenharmony_ci	gpiod_remove_lookup_table(&gpios_led_table);
2808c2ecf20Sopenharmony_ci	gpiod_remove_lookup_table(&gpios_key_table);
2818c2ecf20Sopenharmony_ci
2828c2ecf20Sopenharmony_ci	platform_device_unregister(apu_keys_pdev);
2838c2ecf20Sopenharmony_ci	platform_device_unregister(apu_leds_pdev);
2848c2ecf20Sopenharmony_ci	platform_device_unregister(apu_gpio_pdev);
2858c2ecf20Sopenharmony_ci}
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_cimodule_init(apu_board_init);
2888c2ecf20Sopenharmony_cimodule_exit(apu_board_exit);
2898c2ecf20Sopenharmony_ci
2908c2ecf20Sopenharmony_ciMODULE_AUTHOR("Enrico Weigelt, metux IT consult <info@metux.net>");
2918c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("PC Engines APUv2/APUv3 board GPIO/LEDs/keys driver");
2928c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
2938c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(dmi, apu_gpio_dmi_table);
2948c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:pcengines-apuv2");
2958c2ecf20Sopenharmony_ciMODULE_SOFTDEP("pre: platform:" AMD_FCH_GPIO_DRIVER_NAME " platform:leds-gpio platform:gpio_keys_polled");
296