238 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			238 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| /*
 | |
|  * Siemens SIMATIC IPC platform driver
 | |
|  *
 | |
|  * Copyright (c) Siemens AG, 2018-2023
 | |
|  *
 | |
|  * Authors:
 | |
|  *  Henning Schild <henning.schild@siemens.com>
 | |
|  *  Jan Kiszka <jan.kiszka@siemens.com>
 | |
|  *  Gerd Haeussler <gerd.haeussler.ext@siemens.com>
 | |
|  */
 | |
| 
 | |
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 | |
| 
 | |
| #include <linux/dmi.h>
 | |
| #include <linux/kernel.h>
 | |
| #include <linux/module.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 struct platform_device *ipc_batt_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;
 | |
| 
 | |
| #define SIMATIC_IPC_MAX_EXTRA_MODULES 2
 | |
| 
 | |
| static struct {
 | |
| 	u32 station_id;
 | |
| 	u8 led_mode;
 | |
| 	u8 wdt_mode;
 | |
| 	u8 batt_mode;
 | |
| 	char *extra_modules[SIMATIC_IPC_MAX_EXTRA_MODULES];
 | |
| } device_modes[] = {
 | |
| 	{SIMATIC_IPC_IPC127E,
 | |
| 		SIMATIC_IPC_DEVICE_127E, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_127E,
 | |
| 		{ "emc1403", NULL }},
 | |
| 	{SIMATIC_IPC_IPC227D,
 | |
| 		SIMATIC_IPC_DEVICE_227D, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_NONE,
 | |
| 		{ "emc1403", NULL }},
 | |
| 	{SIMATIC_IPC_IPC227E,
 | |
| 		SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_227E, SIMATIC_IPC_DEVICE_227E,
 | |
| 		{ "emc1403", NULL }},
 | |
| 	{SIMATIC_IPC_IPC227G,
 | |
| 		SIMATIC_IPC_DEVICE_227G, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_227G,
 | |
| 		{ "nct6775", "w83627hf_wdt" }},
 | |
| 	{SIMATIC_IPC_IPC277G,
 | |
| 		SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_227G,
 | |
| 		{ "nct6775", "w83627hf_wdt" }},
 | |
| 	{SIMATIC_IPC_IPC277E,
 | |
| 		SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_227E, SIMATIC_IPC_DEVICE_227E,
 | |
| 		{ "emc1403", NULL }},
 | |
| 	{SIMATIC_IPC_IPC427D,
 | |
| 		SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_NONE,
 | |
| 		{ "emc1403", NULL }},
 | |
| 	{SIMATIC_IPC_IPC427E,
 | |
| 		SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_NONE,
 | |
| 		{ "emc1403", NULL }},
 | |
| 	{SIMATIC_IPC_IPC477E,
 | |
| 		SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_427E, SIMATIC_IPC_DEVICE_NONE,
 | |
| 		{ "emc1403", NULL }},
 | |
| 	{SIMATIC_IPC_IPCBX_39A,
 | |
| 		SIMATIC_IPC_DEVICE_227G, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_BX_39A,
 | |
| 		{ "nct6775", "w83627hf_wdt" }},
 | |
| 	{SIMATIC_IPC_IPCPX_39A,
 | |
| 		SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_BX_39A,
 | |
| 		{ "nct6775", "w83627hf_wdt" }},
 | |
| 	{SIMATIC_IPC_IPCBX_21A,
 | |
| 		SIMATIC_IPC_DEVICE_BX_21A, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_BX_21A,
 | |
| 		{ "emc1403", NULL }},
 | |
| 	{SIMATIC_IPC_IPCBX_56A,
 | |
| 		SIMATIC_IPC_DEVICE_BX_59A, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_BX_59A,
 | |
| 		{ "emc1403", "w83627hf_wdt" }},
 | |
| 	{SIMATIC_IPC_IPCBX_59A,
 | |
| 		SIMATIC_IPC_DEVICE_BX_59A, SIMATIC_IPC_DEVICE_NONE, SIMATIC_IPC_DEVICE_BX_59A,
 | |
| 		{ "emc1403", "w83627hf_wdt" }},
 | |
| };
 | |
| 
 | |
| static int register_platform_devices(u32 station_id)
 | |
| {
 | |
| 	u8 ledmode = SIMATIC_IPC_DEVICE_NONE;
 | |
| 	u8 wdtmode = SIMATIC_IPC_DEVICE_NONE;
 | |
| 	u8 battmode = SIMATIC_IPC_DEVICE_NONE;
 | |
| 	char *pdevname;
 | |
| 	int i;
 | |
| 
 | |
| 	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;
 | |
| 			battmode = device_modes[i].batt_mode;
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (battmode != SIMATIC_IPC_DEVICE_NONE) {
 | |
| 		pdevname = KBUILD_MODNAME "_batt";
 | |
| 		if (battmode == SIMATIC_IPC_DEVICE_127E)
 | |
| 			pdevname = KBUILD_MODNAME "_batt_apollolake";
 | |
| 		if (battmode == SIMATIC_IPC_DEVICE_BX_21A)
 | |
| 			pdevname = KBUILD_MODNAME "_batt_elkhartlake";
 | |
| 		if (battmode == SIMATIC_IPC_DEVICE_227G ||
 | |
| 		    battmode == SIMATIC_IPC_DEVICE_BX_39A ||
 | |
| 		    battmode == SIMATIC_IPC_DEVICE_BX_59A)
 | |
| 			pdevname = KBUILD_MODNAME "_batt_f7188x";
 | |
| 		platform_data.devmode = battmode;
 | |
| 		ipc_batt_platform_device =
 | |
| 			platform_device_register_data(NULL, pdevname,
 | |
| 				PLATFORM_DEVID_NONE, &platform_data,
 | |
| 				sizeof(struct simatic_ipc_platform));
 | |
| 		if (IS_ERR(ipc_batt_platform_device))
 | |
| 			return PTR_ERR(ipc_batt_platform_device);
 | |
| 
 | |
| 		pr_debug("device=%s created\n",
 | |
| 			 ipc_batt_platform_device->name);
 | |
| 	}
 | |
| 
 | |
| 	if (ledmode != SIMATIC_IPC_DEVICE_NONE) {
 | |
| 		pdevname = KBUILD_MODNAME "_leds";
 | |
| 		if (ledmode == SIMATIC_IPC_DEVICE_127E)
 | |
| 			pdevname = KBUILD_MODNAME "_leds_gpio_apollolake";
 | |
| 		if (ledmode == SIMATIC_IPC_DEVICE_227G || ledmode == SIMATIC_IPC_DEVICE_BX_59A)
 | |
| 			pdevname = KBUILD_MODNAME "_leds_gpio_f7188x";
 | |
| 		if (ledmode == SIMATIC_IPC_DEVICE_BX_21A)
 | |
| 			pdevname = KBUILD_MODNAME "_leds_gpio_elkhartlake";
 | |
| 		platform_data.devmode = ledmode;
 | |
| 		ipc_led_platform_device =
 | |
| 			platform_device_register_data(NULL,
 | |
| 				pdevname, 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 &&
 | |
| 	    battmode == SIMATIC_IPC_DEVICE_NONE) {
 | |
| 		pr_warn("unsupported IPC detected, station id=%08x\n",
 | |
| 			station_id);
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void request_additional_modules(u32 station_id)
 | |
| {
 | |
| 	char **extra_modules = NULL;
 | |
| 	int i;
 | |
| 
 | |
| 	for (i = 0; i < ARRAY_SIZE(device_modes); i++) {
 | |
| 		if (device_modes[i].station_id == station_id) {
 | |
| 			extra_modules = device_modes[i].extra_modules;
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (!extra_modules)
 | |
| 		return;
 | |
| 
 | |
| 	for (i = 0; i < SIMATIC_IPC_MAX_EXTRA_MODULES; i++) {
 | |
| 		if (extra_modules[i])
 | |
| 			request_module(extra_modules[i]);
 | |
| 		else
 | |
| 			break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 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;
 | |
| 	}
 | |
| 
 | |
| 	request_additional_modules(station_id);
 | |
| 
 | |
| 	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;
 | |
| 
 | |
| 	platform_device_unregister(ipc_batt_platform_device);
 | |
| 	ipc_batt_platform_device = NULL;
 | |
| }
 | |
| 
 | |
| module_init(simatic_ipc_init_module);
 | |
| module_exit(simatic_ipc_exit_module);
 | |
| 
 | |
| MODULE_DESCRIPTION("Siemens SIMATIC IPC platform driver");
 | |
| MODULE_LICENSE("GPL v2");
 | |
| MODULE_AUTHOR("Gerd Haeussler <gerd.haeussler.ext@siemens.com>");
 | |
| MODULE_ALIAS("dmi:*:svnSIEMENSAG:*");
 |