root/drivers/platform/x86/siemens/simatic-ipc.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:*");