simatic-ipc.c (5081B)
1// SPDX-License-Identifier: GPL-2.0 2/* 3 * Siemens SIMATIC IPC platform driver 4 * 5 * Copyright (c) Siemens AG, 2018-2021 6 * 7 * Authors: 8 * Henning Schild <henning.schild@siemens.com> 9 * Jan Kiszka <jan.kiszka@siemens.com> 10 * Gerd Haeussler <gerd.haeussler.ext@siemens.com> 11 */ 12 13#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 14 15#include <linux/dmi.h> 16#include <linux/kernel.h> 17#include <linux/module.h> 18#include <linux/pci.h> 19#include <linux/platform_data/x86/simatic-ipc.h> 20#include <linux/platform_device.h> 21 22static struct platform_device *ipc_led_platform_device; 23static struct platform_device *ipc_wdt_platform_device; 24 25static const struct dmi_system_id simatic_ipc_whitelist[] = { 26 { 27 .matches = { 28 DMI_MATCH(DMI_SYS_VENDOR, "SIEMENS AG"), 29 }, 30 }, 31 {} 32}; 33 34static struct simatic_ipc_platform platform_data; 35 36static struct { 37 u32 station_id; 38 u8 led_mode; 39 u8 wdt_mode; 40} device_modes[] = { 41 {SIMATIC_IPC_IPC127E, SIMATIC_IPC_DEVICE_127E, SIMATIC_IPC_DEVICE_NONE}, 42 {SIMATIC_IPC_IPC227D, SIMATIC_IPC_DEVICE_227D, SIMATIC_IPC_DEVICE_NONE}, 43 {SIMATIC_IPC_IPC227E, SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_227E}, 44 {SIMATIC_IPC_IPC277E, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_227E}, 45 {SIMATIC_IPC_IPC427D, SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_NONE}, 46 {SIMATIC_IPC_IPC427E, SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_427E}, 47 {SIMATIC_IPC_IPC477E, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_427E}, 48}; 49 50static int register_platform_devices(u32 station_id) 51{ 52 u8 ledmode = SIMATIC_IPC_DEVICE_NONE; 53 u8 wdtmode = SIMATIC_IPC_DEVICE_NONE; 54 int i; 55 56 platform_data.devmode = SIMATIC_IPC_DEVICE_NONE; 57 58 for (i = 0; i < ARRAY_SIZE(device_modes); i++) { 59 if (device_modes[i].station_id == station_id) { 60 ledmode = device_modes[i].led_mode; 61 wdtmode = device_modes[i].wdt_mode; 62 break; 63 } 64 } 65 66 if (ledmode != SIMATIC_IPC_DEVICE_NONE) { 67 platform_data.devmode = ledmode; 68 ipc_led_platform_device = 69 platform_device_register_data(NULL, 70 KBUILD_MODNAME "_leds", PLATFORM_DEVID_NONE, 71 &platform_data, 72 sizeof(struct simatic_ipc_platform)); 73 if (IS_ERR(ipc_led_platform_device)) 74 return PTR_ERR(ipc_led_platform_device); 75 76 pr_debug("device=%s created\n", 77 ipc_led_platform_device->name); 78 } 79 80 if (wdtmode != SIMATIC_IPC_DEVICE_NONE) { 81 platform_data.devmode = wdtmode; 82 ipc_wdt_platform_device = 83 platform_device_register_data(NULL, 84 KBUILD_MODNAME "_wdt", PLATFORM_DEVID_NONE, 85 &platform_data, 86 sizeof(struct simatic_ipc_platform)); 87 if (IS_ERR(ipc_wdt_platform_device)) 88 return PTR_ERR(ipc_wdt_platform_device); 89 90 pr_debug("device=%s created\n", 91 ipc_wdt_platform_device->name); 92 } 93 94 if (ledmode == SIMATIC_IPC_DEVICE_NONE && 95 wdtmode == SIMATIC_IPC_DEVICE_NONE) { 96 pr_warn("unsupported IPC detected, station id=%08x\n", 97 station_id); 98 return -EINVAL; 99 } 100 101 return 0; 102} 103 104/* FIXME: this should eventually be done with generic P2SB discovery code 105 * the individual drivers for watchdogs and LEDs access memory that implements 106 * GPIO, but pinctrl will not come up because of missing ACPI entries 107 * 108 * While there is no conflict a cleaner solution would be to somehow bring up 109 * pinctrl even with these ACPI entries missing, and base the drivers on pinctrl. 110 * After which the following function could be dropped, together with the code 111 * poking the memory. 112 */ 113/* 114 * Get membase address from PCI, used in leds and wdt module. Here we read 115 * the bar0. The final address calculation is done in the appropriate modules 116 */ 117u32 simatic_ipc_get_membase0(unsigned int p2sb) 118{ 119 struct pci_bus *bus; 120 u32 bar0 = 0; 121 /* 122 * The GPIO memory is in bar0 of the hidden P2SB device. 123 * Unhide the device to have a quick look at it, before we hide it 124 * again. 125 * Also grab the pci rescan lock so that device does not get discovered 126 * and remapped while it is visible. 127 * This code is inspired by drivers/mfd/lpc_ich.c 128 */ 129 bus = pci_find_bus(0, 0); 130 pci_lock_rescan_remove(); 131 pci_bus_write_config_byte(bus, p2sb, 0xE1, 0x0); 132 pci_bus_read_config_dword(bus, p2sb, PCI_BASE_ADDRESS_0, &bar0); 133 134 bar0 &= ~0xf; 135 pci_bus_write_config_byte(bus, p2sb, 0xE1, 0x1); 136 pci_unlock_rescan_remove(); 137 138 return bar0; 139} 140EXPORT_SYMBOL(simatic_ipc_get_membase0); 141 142static int __init simatic_ipc_init_module(void) 143{ 144 const struct dmi_system_id *match; 145 u32 station_id; 146 int err; 147 148 match = dmi_first_match(simatic_ipc_whitelist); 149 if (!match) 150 return 0; 151 152 err = dmi_walk(simatic_ipc_find_dmi_entry_helper, &station_id); 153 154 if (err || station_id == SIMATIC_IPC_INVALID_STATION_ID) { 155 pr_warn("DMI entry %d not found\n", SIMATIC_IPC_DMI_ENTRY_OEM); 156 return 0; 157 } 158 159 return register_platform_devices(station_id); 160} 161 162static void __exit simatic_ipc_exit_module(void) 163{ 164 platform_device_unregister(ipc_led_platform_device); 165 ipc_led_platform_device = NULL; 166 167 platform_device_unregister(ipc_wdt_platform_device); 168 ipc_wdt_platform_device = NULL; 169} 170 171module_init(simatic_ipc_init_module); 172module_exit(simatic_ipc_exit_module); 173 174MODULE_LICENSE("GPL v2"); 175MODULE_AUTHOR("Gerd Haeussler <gerd.haeussler.ext@siemens.com>"); 176MODULE_ALIAS("dmi:*:svnSIEMENSAG:*");