| @@ -0,0 +1,176 @@ |
| | 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 | |
| | 22 | static struct platform_device *ipc_led_platform_device; |
| | 23 | static struct platform_device *ipc_wdt_platform_device; |
| | 24 | |
| | 25 | static const struct dmi_system_id simatic_ipc_whitelist[] = { |
| | 26 | { |
| | 27 | .matches = { |
| | 28 | DMI_MATCH(DMI_SYS_VENDOR, "SIEMENS AG"), |
| | 29 | }, |
| | 30 | }, |
| | 31 | {} |
| | 32 | }; |
| | 33 | |
| | 34 | static struct simatic_ipc_platform platform_data; |
| | 35 | |
| | 36 | static 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 | |
| | 50 | static 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 | */ |
| | 117 | u32 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 | } |
| | 140 | EXPORT_SYMBOL(simatic_ipc_get_membase0); |
| | 141 | |
| | 142 | static 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 | |
| | 162 | static 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 | |
| | 171 | module_init(simatic_ipc_init_module); |
| | 172 | module_exit(simatic_ipc_exit_module); |
| | 173 | |
| | 174 | MODULE_LICENSE("GPL v2"); |
| | 175 | MODULE_AUTHOR("Gerd Haeussler <gerd.haeussler.ext@siemens.com>"); |
| | 176 | MODULE_ALIAS("dmi:*:svnSIEMENSAG:*"); |