Am Mon, 29 Mar 2021 19:49:25 +0200 schrieb Henning Schild <henning.schild@xxxxxxxxxxx>: > This mainly implements detection of these devices and will allow > secondary drivers to work on such machines. > > The identification is DMI-based with a vendor specific way to tell > them apart in a reliable way. > > Drivers for LEDs and Watchdogs will follow to make use of that > platform detection. > > Signed-off-by: Henning Schild <henning.schild@xxxxxxxxxxx> > --- > drivers/platform/x86/Kconfig | 12 ++ > drivers/platform/x86/Makefile | 3 + > drivers/platform/x86/simatic-ipc.c | 169 > ++++++++++++++++++ .../platform_data/x86/simatic-ipc-base.h | > 29 +++ include/linux/platform_data/x86/simatic-ipc.h | 72 ++++++++ > 5 files changed, 285 insertions(+) > create mode 100644 drivers/platform/x86/simatic-ipc.c > create mode 100644 include/linux/platform_data/x86/simatic-ipc-base.h > create mode 100644 include/linux/platform_data/x86/simatic-ipc.h > > diff --git a/drivers/platform/x86/Kconfig > b/drivers/platform/x86/Kconfig index 461ec61530eb..1eaa03d0d183 100644 > --- a/drivers/platform/x86/Kconfig > +++ b/drivers/platform/x86/Kconfig > @@ -1289,6 +1289,18 @@ config INTEL_TELEMETRY > directly via debugfs files. Various tools may use > this interface for SoC state monitoring. > > +config SIEMENS_SIMATIC_IPC > + tristate "Siemens Simatic IPC Class driver" > + depends on PCI > + help > + This Simatic IPC class driver is the central of several > drivers. It > + is mainly used for system identification, after which > drivers in other > + classes will take care of driving specifics of those > machines. > + i.e. LEDs and watchdog. > + > + To compile this driver as a module, choose M here: the > module > + will be called simatic-ipc. > + > endif # X86_PLATFORM_DEVICES > > config PMC_ATOM > diff --git a/drivers/platform/x86/Makefile > b/drivers/platform/x86/Makefile index 60d554073749..26cdebf2e701 > 100644 --- a/drivers/platform/x86/Makefile > +++ b/drivers/platform/x86/Makefile > @@ -138,3 +138,6 @@ obj-$(CONFIG_INTEL_TELEMETRY) += > intel_telemetry_core.o \ intel_telemetry_pltdrv.o \ > intel_telemetry_debugfs.o > obj-$(CONFIG_PMC_ATOM) += pmc_atom.o > + > +# Siemens Simatic Industrial PCs > +obj-$(CONFIG_SIEMENS_SIMATIC_IPC) += simatic-ipc.o > diff --git a/drivers/platform/x86/simatic-ipc.c > b/drivers/platform/x86/simatic-ipc.c new file mode 100644 > index 000000000000..52e8596bc63d > --- /dev/null > +++ b/drivers/platform/x86/simatic-ipc.c > @@ -0,0 +1,169 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Siemens SIMATIC IPC platform driver > + * > + * Copyright (c) Siemens AG, 2018-2021 > + * > + * Authors: > + * Henning Schild <henning.schild@xxxxxxxxxxx> > + * Jan Kiszka <jan.kiszka@xxxxxxxxxxx> > + * Gerd Haeussler <gerd.haeussler.ext@xxxxxxxxxxx> > + */ > + > +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt > + > +#include <linux/dmi.h> > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/pci.h> > +#include <linux/platform_data/x86/simatic-ipc.h> > +#include <linux/platform_device.h> > + > +static struct platform_device *ipc_led_platform_device; > +static struct platform_device *ipc_wdt_platform_device; > + > +static const struct dmi_system_id simatic_ipc_whitelist[] = { > + { > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "SIEMENS AG"), > + }, > + }, > + {} > +}; > + > +static struct simatic_ipc_platform platform_data; > + > +static struct { > + u32 station_id; > + u8 led_mode; > + u8 wdt_mode; > +} device_modes[] = { > + {SIMATIC_IPC_IPC127E, SIMATIC_IPC_DEVICE_127E, > SIMATIC_IPC_DEVICE_NONE}, > + {SIMATIC_IPC_IPC227D, SIMATIC_IPC_DEVICE_227D, > SIMATIC_IPC_DEVICE_NONE}, > + {SIMATIC_IPC_IPC227E, SIMATIC_IPC_DEVICE_427E, > SIMATIC_IPC_DEVICE_227E}, > + {SIMATIC_IPC_IPC277E, SIMATIC_IPC_DEVICE_NONE, > SIMATIC_IPC_DEVICE_227E}, > + {SIMATIC_IPC_IPC427D, SIMATIC_IPC_DEVICE_427E, > SIMATIC_IPC_DEVICE_NONE}, > + {SIMATIC_IPC_IPC427E, SIMATIC_IPC_DEVICE_427E, > SIMATIC_IPC_DEVICE_427E}, > + {SIMATIC_IPC_IPC477E, SIMATIC_IPC_DEVICE_NONE, > SIMATIC_IPC_DEVICE_427E}, +}; > + > +static int register_platform_devices(u32 station_id) > +{ > + u8 ledmode = SIMATIC_IPC_DEVICE_NONE; > + u8 wdtmode = SIMATIC_IPC_DEVICE_NONE; > + int i; > + > + platform_data.devmode = SIMATIC_IPC_DEVICE_NONE; > + > + for (i = 0; i < ARRAY_SIZE(device_modes); i++) { > + if (device_modes[i].station_id == station_id) { > + ledmode = device_modes[i].led_mode; > + wdtmode = device_modes[i].wdt_mode; > + break; > + } > + } > + > + if (ledmode != SIMATIC_IPC_DEVICE_NONE) { > + platform_data.devmode = ledmode; > + ipc_led_platform_device = > + platform_device_register_data(NULL, > + KBUILD_MODNAME "_leds", > PLATFORM_DEVID_NONE, > + &platform_data, > + sizeof(struct simatic_ipc_platform)); > + if (IS_ERR(ipc_led_platform_device)) > + return PTR_ERR(ipc_led_platform_device); > + > + pr_debug("device=%s created\n", > + ipc_led_platform_device->name); > + } > + > + if (wdtmode != SIMATIC_IPC_DEVICE_NONE) { > + platform_data.devmode = wdtmode; > + ipc_wdt_platform_device = > + platform_device_register_data(NULL, > + KBUILD_MODNAME "_wdt", > PLATFORM_DEVID_NONE, > + &platform_data, > + sizeof(struct simatic_ipc_platform)); > + if (IS_ERR(ipc_wdt_platform_device)) > + return PTR_ERR(ipc_wdt_platform_device); > + > + pr_debug("device=%s created\n", > + ipc_wdt_platform_device->name); > + } > + > + if (ledmode == SIMATIC_IPC_DEVICE_NONE && > + wdtmode == SIMATIC_IPC_DEVICE_NONE) { > + pr_warn("unsupported IPC detected, station > id=%08x\n", > + station_id); > + return -EINVAL; > + } > + > + return 0; > +} > + > + extra newline, should be removed > +/* FIXME: this should eventually be done with generic P2SB discovery > code */ +/* > + * Get membase address from PCI, used in leds and wdt modul. Here we > read /modul/module/ was pointed out already but forgotten about Henning > + * the bar0. The final address calculation is done in the > appropriate modules > + */ > +u32 simatic_ipc_get_membase0(unsigned int p2sb) > +{ > + struct pci_bus *bus; > + u32 bar0 = 0; > + /* > + * The GPIO memory is in bar0 of the hidden P2SB device. > + * Unhide the device to have a quick look at it, before we > hide it > + * again. > + * Also grab the pci rescan lock so that device does not get > discovered > + * and remapped while it is visible. > + * This code is inspired by drivers/mfd/lpc_ich.c > + */ > + bus = pci_find_bus(0, 0); > + pci_lock_rescan_remove(); > + pci_bus_write_config_byte(bus, p2sb, 0xE1, 0x0); > + pci_bus_read_config_dword(bus, p2sb, PCI_BASE_ADDRESS_0, > &bar0); + > + bar0 &= ~0xf; > + pci_bus_write_config_byte(bus, p2sb, 0xE1, 0x1); > + pci_unlock_rescan_remove(); > + > + return bar0; > +} > +EXPORT_SYMBOL(simatic_ipc_get_membase0); > + > +static int __init simatic_ipc_init_module(void) > +{ > + const struct dmi_system_id *match; > + u32 station_id; > + int err; > + > + match = dmi_first_match(simatic_ipc_whitelist); > + if (!match) > + return 0; > + > + err = dmi_walk(simatic_ipc_find_dmi_entry_helper, > &station_id); + > + if (err || station_id == SIMATIC_IPC_INVALID_STATION_ID) { > + pr_warn("DMI entry %d not found\n", > SIMATIC_IPC_DMI_ENTRY_OEM); > + return 0; > + } > + > + return register_platform_devices(station_id); > +} > + > +static void __exit simatic_ipc_exit_module(void) > +{ > + platform_device_unregister(ipc_led_platform_device); > + ipc_led_platform_device = NULL; > + > + platform_device_unregister(ipc_wdt_platform_device); > + ipc_wdt_platform_device = NULL; > +} > + > +module_init(simatic_ipc_init_module); > +module_exit(simatic_ipc_exit_module); > + > +MODULE_LICENSE("GPL v2"); > +MODULE_AUTHOR("Gerd Haeussler <gerd.haeussler.ext@xxxxxxxxxxx>"); > +MODULE_ALIAS("dmi:*:svnSIEMENSAG:*"); > diff --git a/include/linux/platform_data/x86/simatic-ipc-base.h > b/include/linux/platform_data/x86/simatic-ipc-base.h new file mode > 100644 index 000000000000..62d2bc774067 > --- /dev/null > +++ b/include/linux/platform_data/x86/simatic-ipc-base.h > @@ -0,0 +1,29 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* > + * Siemens SIMATIC IPC drivers > + * > + * Copyright (c) Siemens AG, 2018-2021 > + * > + * Authors: > + * Henning Schild <henning.schild@xxxxxxxxxxx> > + * Gerd Haeussler <gerd.haeussler.ext@xxxxxxxxxxx> > + */ > + > +#ifndef __PLATFORM_DATA_X86_SIMATIC_IPC_BASE_H > +#define __PLATFORM_DATA_X86_SIMATIC_IPC_BASE_H > + > +#include <linux/types.h> > + > +#define SIMATIC_IPC_DEVICE_NONE 0 > +#define SIMATIC_IPC_DEVICE_227D 1 > +#define SIMATIC_IPC_DEVICE_427E 2 > +#define SIMATIC_IPC_DEVICE_127E 3 > +#define SIMATIC_IPC_DEVICE_227E 4 > + > +struct simatic_ipc_platform { > + u8 devmode; > +}; > + > +u32 simatic_ipc_get_membase0(unsigned int p2sb); > + > +#endif /* __PLATFORM_DATA_X86_SIMATIC_IPC_BASE_H */ > diff --git a/include/linux/platform_data/x86/simatic-ipc.h > b/include/linux/platform_data/x86/simatic-ipc.h new file mode 100644 > index 000000000000..f3b76b39776b > --- /dev/null > +++ b/include/linux/platform_data/x86/simatic-ipc.h > @@ -0,0 +1,72 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* > + * Siemens SIMATIC IPC drivers > + * > + * Copyright (c) Siemens AG, 2018-2021 > + * > + * Authors: > + * Henning Schild <henning.schild@xxxxxxxxxxx> > + * Gerd Haeussler <gerd.haeussler.ext@xxxxxxxxxxx> > + */ > + > +#ifndef __PLATFORM_DATA_X86_SIMATIC_IPC_H > +#define __PLATFORM_DATA_X86_SIMATIC_IPC_H > + > +#include <linux/dmi.h> > +#include <linux/platform_data/x86/simatic-ipc-base.h> > + > +#define SIMATIC_IPC_DMI_ENTRY_OEM 129 > +/* binary type */ > +#define SIMATIC_IPC_DMI_TYPE 0xff > +#define SIMATIC_IPC_DMI_GROUP 0x05 > +#define SIMATIC_IPC_DMI_ENTRY 0x02 > +#define SIMATIC_IPC_DMI_TID 0x02 > + > +enum simatic_ipc_station_ids { > + SIMATIC_IPC_INVALID_STATION_ID = 0, > + SIMATIC_IPC_IPC227D = 0x00000501, > + SIMATIC_IPC_IPC427D = 0x00000701, > + SIMATIC_IPC_IPC227E = 0x00000901, > + SIMATIC_IPC_IPC277E = 0x00000902, > + SIMATIC_IPC_IPC427E = 0x00000A01, > + SIMATIC_IPC_IPC477E = 0x00000A02, > + SIMATIC_IPC_IPC127E = 0x00000D01, > +}; > + > +static inline u32 simatic_ipc_get_station_id(u8 *data, int max_len) > +{ > + struct { > + u8 type; /* type (0xff = > binary) */ > + u8 len; /* len of data entry */ > + u8 group; > + u8 entry; > + u8 tid; > + __le32 station_id; /* station id (LE) > */ > + } __packed * data_entry = (void *)data + sizeof(struct > dmi_header); + > + while ((u8 *)data_entry < data + max_len) { > + if (data_entry->type == SIMATIC_IPC_DMI_TYPE && > + data_entry->len == sizeof(*data_entry) && > + data_entry->group == SIMATIC_IPC_DMI_GROUP && > + data_entry->entry == SIMATIC_IPC_DMI_ENTRY && > + data_entry->tid == SIMATIC_IPC_DMI_TID) { > + return le32_to_cpu(data_entry->station_id); > + } > + data_entry = (void *)((u8 *)(data_entry) + > data_entry->len); > + } > + > + return SIMATIC_IPC_INVALID_STATION_ID; > +} > + > +static inline void > +simatic_ipc_find_dmi_entry_helper(const struct dmi_header *dh, void > *_data) +{ > + u32 *id = _data; > + > + if (dh->type != SIMATIC_IPC_DMI_ENTRY_OEM) > + return; > + > + *id = simatic_ipc_get_station_id((u8 *)dh, dh->length); > +} > + > +#endif /* __PLATFORM_DATA_X86_SIMATIC_IPC_H */