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