From patchwork Tue Mar 2 16:33:06 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Henning Schild X-Patchwork-Id: 393087 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.8 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 28D79C433E0 for ; Wed, 3 Mar 2021 02:49:28 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id D15FD64EA4 for ; Wed, 3 Mar 2021 02:49:27 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232444AbhCCCj4 (ORCPT ); Tue, 2 Mar 2021 21:39:56 -0500 Received: from lizzard.sbs.de ([194.138.37.39]:46166 "EHLO lizzard.sbs.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1579464AbhCBQ6e (ORCPT ); Tue, 2 Mar 2021 11:58:34 -0500 Received: from mail1.sbs.de (mail1.sbs.de [192.129.41.35]) by lizzard.sbs.de (8.15.2/8.15.2) with ESMTPS id 122GXDXW028682 (version=TLSv1.2 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Tue, 2 Mar 2021 17:33:13 +0100 Received: from md1za8fc.ad001.siemens.net ([167.87.44.113]) by mail1.sbs.de (8.15.2/8.15.2) with ESMTP id 122GXBHO025192; Tue, 2 Mar 2021 17:33:12 +0100 From: Henning Schild To: linux-kernel@vger.kernel.org, linux-leds@vger.kernel.org, platform-driver-x86@vger.kernel.org, linux-watchdog@vger.kernel.org Cc: Srikanth Krishnakar , Jan Kiszka , Henning Schild , Gerd Haeussler , Guenter Roeck , Wim Van Sebroeck , Mark Gross , Hans de Goede , Pavel Machek Subject: [PATCH 1/4] platform/x86: simatic-ipc: add main driver for Siemens devices Date: Tue, 2 Mar 2021 17:33:06 +0100 Message-Id: <20210302163309.25528-2-henning.schild@siemens.com> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20210302163309.25528-1-henning.schild@siemens.com> References: <20210302163309.25528-1-henning.schild@siemens.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-leds@vger.kernel.org From: Henning Schild 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: Gerd Haeussler Signed-off-by: Henning Schild --- drivers/platform/x86/Kconfig | 9 + drivers/platform/x86/Makefile | 3 + drivers/platform/x86/simatic-ipc.c | 166 ++++++++++++++++++ .../platform_data/x86/simatic-ipc-base.h | 33 ++++ include/linux/platform_data/x86/simatic-ipc.h | 68 +++++++ 5 files changed, 279 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 ad4e630e73e2..bb9e282d89cf 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1284,6 +1284,15 @@ 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 watchdogs + 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..6adcdac1a0d7 --- /dev/null +++ b/drivers/platform/x86/simatic-ipc.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Siemens SIMATIC IPC driver for LEDs + * + * Copyright (c) Siemens AG, 2018-2021 + * + * Authors: + * Henning Schild + * Jan Kiszka + * Gerd Haeussler + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include + +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) +{ + int i; + u8 ledmode = SIMATIC_IPC_DEVICE_NONE; + u8 wdtmode = SIMATIC_IPC_DEVICE_NONE; + + 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(KBUILD_MODNAME ": 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(KBUILD_MODNAME ": device=%s created\n", + ipc_wdt_platform_device->name); + } + + if (ledmode == SIMATIC_IPC_DEVICE_NONE && + wdtmode == SIMATIC_IPC_DEVICE_NONE) { + pr_warn(KBUILD_MODNAME + ": unsupported IPC detected, station id=%08x\n", + station_id); + return -EINVAL; + } else { + return 0; + } +} + +/* + * Get membase address from PCI, used in leds and wdt modul. Here we read + * the bar0. The final address calculation is done in the appropriate modules + */ + +u32 simatic_ipc_get_membase0(unsigned int p2sb) +{ + u32 bar0 = 0; +#ifdef CONFIG_PCI + struct pci_bus *bus; + /* + * The GPIO memory is 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(); +#endif /* CONFIG_PCI */ + 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(KBUILD_MODNAME ": DMI entry %d not found\n", + 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"); +MODULE_AUTHOR("Gerd Haeussler "); +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..d8e59eb8fc96 --- /dev/null +++ b/include/linux/platform_data/x86/simatic-ipc-base.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Siemens SIMATIC IPC drivers + * + * Copyright (c) Siemens AG, 2018-2021 + * + * Authors: + * Henning Schild + * Gerd Haeussler + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __PLATFORM_DATA_X86_SIMATIC_IPC_BASE_H +#define __PLATFORM_DATA_X86_SIMATIC_IPC_BASE_H + +#include + +#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..90bb0d7cf09a --- /dev/null +++ b/include/linux/platform_data/x86/simatic-ipc.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Siemens SIMATIC IPC drivers + * + * Copyright (c) Siemens AG, 2018-2021 + * + * Authors: + * Henning Schild + * Gerd Haeussler + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __PLATFORM_DATA_X86_SIMATIC_IPC_H +#define __PLATFORM_DATA_X86_SIMATIC_IPC_H + +#include +#include + +#define DMI_ENTRY_OEM 129 + +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) +{ + u32 station_id = SIMATIC_IPC_INVALID_STATION_ID; + int i; + struct { + u8 type; /* type (0xff = binary) */ + u8 len; /* len of data entry */ + u8 reserved[3]; + u32 station_id; /* station id (LE) */ + } __packed * data_entry = (void *)data; + + /* find 4th entry in OEM data */ + for (i = 0; i < 3; i++) + data_entry = (void *)((u8 *)(data_entry) + data_entry->len); + + /* decode station id */ + if (data_entry && data_entry->type == 0xff && data_entry->len == 9) + station_id = le32_to_cpu(data_entry->station_id); + + return station_id; +} + +static inline void +simatic_ipc_find_dmi_entry_helper(const struct dmi_header *dh, void *_data) +{ + u32 *id = _data; + + if (dh->type != DMI_ENTRY_OEM) + return; + + *id = simatic_ipc_get_station_id((u8 *)dh + sizeof(struct dmi_header)); +} + +#endif /* __PLATFORM_DATA_X86_SIMATIC_IPC_H */ From patchwork Tue Mar 2 16:33:07 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Henning Schild X-Patchwork-Id: 392405 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, URIBL_BLOCKED, USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id CF397C433E0 for ; Wed, 3 Mar 2021 02:49:36 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 9F41364E89 for ; Wed, 3 Mar 2021 02:49:36 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233890AbhCCCkO (ORCPT ); Tue, 2 Mar 2021 21:40:14 -0500 Received: from gecko.sbs.de ([194.138.37.40]:57470 "EHLO gecko.sbs.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1579466AbhCBQ6j (ORCPT ); Tue, 2 Mar 2021 11:58:39 -0500 Received: from mail1.sbs.de (mail1.sbs.de [192.129.41.35]) by gecko.sbs.de (8.15.2/8.15.2) with ESMTPS id 122GmE6S021402 (version=TLSv1.2 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Tue, 2 Mar 2021 17:48:14 +0100 Received: from md1za8fc.ad001.siemens.net ([167.87.44.113]) by mail1.sbs.de (8.15.2/8.15.2) with ESMTP id 122GXBHP025192; Tue, 2 Mar 2021 17:33:13 +0100 From: Henning Schild To: linux-kernel@vger.kernel.org, linux-leds@vger.kernel.org, platform-driver-x86@vger.kernel.org, linux-watchdog@vger.kernel.org Cc: Srikanth Krishnakar , Jan Kiszka , Henning Schild , Gerd Haeussler , Guenter Roeck , Wim Van Sebroeck , Mark Gross , Hans de Goede , Pavel Machek Subject: [PATCH 2/4] leds: simatic-ipc-leds: add new driver for Siemens Industial PCs Date: Tue, 2 Mar 2021 17:33:07 +0100 Message-Id: <20210302163309.25528-3-henning.schild@siemens.com> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20210302163309.25528-1-henning.schild@siemens.com> References: <20210302163309.25528-1-henning.schild@siemens.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-leds@vger.kernel.org From: Henning Schild This driver adds initial support for several devices from Siemens. It is based on a platform driver introduced in an earlier commit. Signed-off-by: Gerd Haeussler Signed-off-by: Henning Schild --- drivers/leds/Kconfig | 11 ++ drivers/leds/Makefile | 1 + drivers/leds/simatic-ipc-leds.c | 224 ++++++++++++++++++++++++++++++++ 3 files changed, 236 insertions(+) create mode 100644 drivers/leds/simatic-ipc-leds.c diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index b6742b4231bf..3ee6fc613a0a 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -928,6 +928,17 @@ config LEDS_ACER_A500 This option enables support for the Power Button LED of Acer Iconia Tab A500. +config LEDS_SIEMENS_SIMATIC_IPC + tristate "LED driver for Siemens Simatic IPCs" + depends on LEDS_CLASS + depends on SIEMENS_SIMATIC_IPC + help + This option enables support for the LEDs of several Industrial PCs + from Siemens. + + To compile this driver as a module, choose M here: the module + will be called simatic-ipc-leds. + comment "Flash and Torch LED drivers" source "drivers/leds/flash/Kconfig" diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 2a698df9da57..c15e1e3c5958 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -93,6 +93,7 @@ obj-$(CONFIG_LEDS_TURRIS_OMNIA) += leds-turris-omnia.o obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o obj-$(CONFIG_LEDS_WRAP) += leds-wrap.o +obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC) += simatic-ipc-leds.o # LED SPI Drivers obj-$(CONFIG_LEDS_CR0014114) += leds-cr0014114.o diff --git a/drivers/leds/simatic-ipc-leds.c b/drivers/leds/simatic-ipc-leds.c new file mode 100644 index 000000000000..760aef1d4ae1 --- /dev/null +++ b/drivers/leds/simatic-ipc-leds.c @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Siemens SIMATIC IPC driver for LEDs + * + * Copyright (c) Siemens AG, 2018-2021 + * + * Authors: + * Henning Schild + * Jan Kiszka + * Gerd Haeussler + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define SIMATIC_IPC_LED_PORT_BASE 0x404E + +struct simatic_ipc_led { + unsigned int value; /* mask for io and offset for mem */ + char name[32]; + struct led_classdev cdev; +}; + +static struct simatic_ipc_led simatic_ipc_leds_io[] = { + {1 << 15, "simatic-ipc:green:run-stop"}, + {1 << 7, "simatic-ipc:yellow:run-stop"}, + {1 << 14, "simatic-ipc:red:error"}, + {1 << 6, "simatic-ipc:yellow:error"}, + {1 << 13, "simatic-ipc:red:maint"}, + {1 << 5, "simatic-ipc:yellow:maint"}, + {0, ""}, +}; + +/* the actual start will be discovered with pci, 0 is a placeholder */ +struct resource simatic_ipc_led_mem_res = + DEFINE_RES_MEM_NAMED(0, SZ_4K, KBUILD_MODNAME); + +static void *simatic_ipc_led_memory; + +static struct simatic_ipc_led simatic_ipc_leds_mem[] = { + {0x500 + 0x1A0, "simatic-ipc:red:run-stop"}, + {0x500 + 0x1A8, "simatic-ipc:green:run-stop"}, + {0x500 + 0x1C8, "simatic-ipc:red:error"}, + {0x500 + 0x1D0, "simatic-ipc:green:error"}, + {0x500 + 0x1E0, "simatic-ipc:red:maint"}, + {0x500 + 0x198, "simatic-ipc:green:maint"}, + {0, ""}, +}; + +static struct resource simatic_ipc_led_io_res = + DEFINE_RES_IO_NAMED(SIMATIC_IPC_LED_PORT_BASE, SZ_1, KBUILD_MODNAME); + +static DEFINE_SPINLOCK(reg_lock); + +static void simatic_ipc_led_set_io(struct led_classdev *led_cd, + enum led_brightness brightness) +{ + struct simatic_ipc_led *led = + container_of(led_cd, struct simatic_ipc_led, cdev); + unsigned long flags; + unsigned int val; + + spin_lock_irqsave(®_lock, flags); + + val = inw(SIMATIC_IPC_LED_PORT_BASE); + if (brightness == LED_OFF) + outw(val | led->value, SIMATIC_IPC_LED_PORT_BASE); + else + outw(val & ~led->value, SIMATIC_IPC_LED_PORT_BASE); + + spin_unlock_irqrestore(®_lock, flags); +} + +static enum led_brightness simatic_ipc_led_get_io(struct led_classdev *led_cd) +{ + struct simatic_ipc_led *led = + container_of(led_cd, struct simatic_ipc_led, cdev); + + return inw(SIMATIC_IPC_LED_PORT_BASE) & led->value ? + LED_OFF : led_cd->max_brightness; +} + +static void simatic_ipc_led_set_mem(struct led_classdev *led_cd, + enum led_brightness brightness) +{ + struct simatic_ipc_led *led = + container_of(led_cd, struct simatic_ipc_led, cdev); + + u32 *p; + + p = simatic_ipc_led_memory + led->value; + *p = (*p & ~1) | (brightness == LED_OFF); +} + +static enum led_brightness simatic_ipc_led_get_mem(struct led_classdev *led_cd) +{ + struct simatic_ipc_led *led = + container_of(led_cd, struct simatic_ipc_led, cdev); + + u32 *p; + + p = simatic_ipc_led_memory + led->value; + return (*p & 1) ? LED_OFF : led_cd->max_brightness; +} + +static int simatic_ipc_leds_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct led_classdev *cdev; + struct resource *res; + int err, type; + struct simatic_ipc_led *ipcled; + struct simatic_ipc_platform *plat; + u32 *p; + + plat = pdev->dev.platform_data; + + switch (plat->devmode) { + case SIMATIC_IPC_DEVICE_227D: + case SIMATIC_IPC_DEVICE_427E: + res = &simatic_ipc_led_io_res; + ipcled = simatic_ipc_leds_io; + /* the 227D is high on while 427E is low on, invert the struct + * we have + */ + if (plat->devmode == SIMATIC_IPC_DEVICE_227D) { + while (ipcled->value) { + ipcled->value = swab16(ipcled->value); + ipcled++; + } + ipcled = simatic_ipc_leds_io; + } + type = IORESOURCE_IO; + if (!devm_request_region(dev, res->start, + resource_size(res), + KBUILD_MODNAME)) { + dev_err(dev, + "Unable to register IO resource at %pR\n", + res); + return -EBUSY; + } + break; + case SIMATIC_IPC_DEVICE_127E: + res = &simatic_ipc_led_mem_res; + ipcled = simatic_ipc_leds_mem; + type = IORESOURCE_MEM; + + /* get GPIO base from PCI */ + res->start = simatic_ipc_get_membase0(PCI_DEVFN(13, 0)); + if (res->start == 0) + return -ENODEV; + + /* do the final address calculation */ + res->start = res->start + (0xC5 << 16); + res->end += res->start; + + simatic_ipc_led_memory = devm_ioremap_resource(dev, res); + if (!simatic_ipc_led_memory) + return -ENOMEM; + + /* initialize power/watchdog LED */ + p = simatic_ipc_led_memory + 0x500 + 0x1D8; // PM_WDT_OUT + *p = (*p & ~1); + p = simatic_ipc_led_memory + 0x500 + 0x1C0; // PM_BIOS_BOOT_N + *p = (*p | 1); + + break; + default: + return -ENODEV; + } + + while (ipcled->value) { + cdev = &ipcled->cdev; + cdev->brightness_set = simatic_ipc_led_set_io; + cdev->brightness_get = simatic_ipc_led_get_io; + if (type == IORESOURCE_MEM) { + cdev->brightness_set = simatic_ipc_led_set_mem; + cdev->brightness_get = simatic_ipc_led_get_mem; + } + cdev->max_brightness = LED_ON; + cdev->name = ipcled->name; + + err = devm_led_classdev_register(dev, cdev); + if (err < 0) + return err; + ipcled++; + } + + return 0; +} + +static struct platform_driver led_driver = { + .probe = simatic_ipc_leds_probe, + .driver = { + .name = KBUILD_MODNAME, + }, +}; + +static int __init simatic_ipc_led_init_module(void) +{ + return platform_driver_register(&led_driver); +} + +static void simatic_ipc_led_exit_module(void) +{ + platform_driver_unregister(&led_driver); +} + +module_init(simatic_ipc_led_init_module); +module_exit(simatic_ipc_led_exit_module); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" KBUILD_MODNAME); +MODULE_AUTHOR("Henning Schild "); From patchwork Tue Mar 2 16:33:08 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Henning Schild X-Patchwork-Id: 393086 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, URIBL_BLOCKED, USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 88451C433DB for ; Wed, 3 Mar 2021 02:49:44 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 3B99F64E4E for ; Wed, 3 Mar 2021 02:49:44 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232938AbhCCCkl (ORCPT ); Tue, 2 Mar 2021 21:40:41 -0500 Received: from gecko.sbs.de ([194.138.37.40]:59937 "EHLO gecko.sbs.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S238754AbhCBRNj (ORCPT ); Tue, 2 Mar 2021 12:13:39 -0500 Received: from mail1.sbs.de (mail1.sbs.de [192.129.41.35]) by gecko.sbs.de (8.15.2/8.15.2) with ESMTPS id 122GhEw0015430 (version=TLSv1.2 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Tue, 2 Mar 2021 17:43:14 +0100 Received: from md1za8fc.ad001.siemens.net ([167.87.44.113]) by mail1.sbs.de (8.15.2/8.15.2) with ESMTP id 122GXBHQ025192; Tue, 2 Mar 2021 17:33:13 +0100 From: Henning Schild To: linux-kernel@vger.kernel.org, linux-leds@vger.kernel.org, platform-driver-x86@vger.kernel.org, linux-watchdog@vger.kernel.org Cc: Srikanth Krishnakar , Jan Kiszka , Henning Schild , Gerd Haeussler , Guenter Roeck , Wim Van Sebroeck , Mark Gross , Hans de Goede , Pavel Machek Subject: [PATCH 3/4] watchdog: simatic-ipc-wdt: add new driver for Siemens Industrial PCs Date: Tue, 2 Mar 2021 17:33:08 +0100 Message-Id: <20210302163309.25528-4-henning.schild@siemens.com> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20210302163309.25528-1-henning.schild@siemens.com> References: <20210302163309.25528-1-henning.schild@siemens.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-leds@vger.kernel.org From: Henning Schild This driver adds initial support for several devices from Siemens. It is based on a platform driver introduced in an earlier commit. Signed-off-by: Gerd Haeussler Signed-off-by: Henning Schild --- drivers/watchdog/Kconfig | 11 ++ drivers/watchdog/Makefile | 1 + drivers/watchdog/simatic-ipc-wdt.c | 305 +++++++++++++++++++++++++++++ 3 files changed, 317 insertions(+) create mode 100644 drivers/watchdog/simatic-ipc-wdt.c diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 1fe0042a48d2..948497eb4bef 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -1575,6 +1575,17 @@ config NIC7018_WDT To compile this driver as a module, choose M here: the module will be called nic7018_wdt. +config SIEMENS_SIMATIC_IPC_WDT + tristate "Siemens Simatic IPC Watchdog" + depends on SIEMENS_SIMATIC_IPC + select WATCHDOG_CORE + help + This driver adds support for several watchdogs found in Industrial + PCs from Siemens. + + To compile this driver as a module, choose M here: the module will be + called simatic-ipc-wdt. + # M68K Architecture config M54xx_WATCHDOG diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index f3a6540e725e..7f5c73ec058c 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -142,6 +142,7 @@ obj-$(CONFIG_NI903X_WDT) += ni903x_wdt.o obj-$(CONFIG_NIC7018_WDT) += nic7018_wdt.o obj-$(CONFIG_MLX_WDT) += mlx_wdt.o obj-$(CONFIG_KEEMBAY_WATCHDOG) += keembay_wdt.o +obj-$(CONFIG_SIEMENS_SIMATIC_IPC_WDT) += simatic-ipc-wdt.o # M68K Architecture obj-$(CONFIG_M54xx_WATCHDOG) += m54xx_wdt.o diff --git a/drivers/watchdog/simatic-ipc-wdt.c b/drivers/watchdog/simatic-ipc-wdt.c new file mode 100644 index 000000000000..b5c8b7ceb404 --- /dev/null +++ b/drivers/watchdog/simatic-ipc-wdt.c @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Siemens SIMATIC IPC driver for Watchdogs + * + * Copyright (c) Siemens AG, 2020-2021 + * + * Authors: + * Gerd Haeussler + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define WD_ENABLE_IOADR 0x62 +#define WD_TRIGGER_IOADR 0x66 +#define GPIO_COMMUNITY0_PORT_ID 0xaf +#define PAD_CFG_DW0_GPP_A_23 0x4b8 +#define SAFE_EN_N_427E 0x01 +#define SAFE_EN_N_227E 0x04 +#define WD_ENABLED 0x01 + +#define TIMEOUT_MIN 2 +#define TIMEOUT_DEF 64 +#define TIMEOUT_MAX 64 + +#define GP_STATUS_REG_227E 0x404D /* IO PORT for SAFE_EN_N on 227E */ + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0000); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +static DEFINE_SPINLOCK(io_lock); /* the lock for io operations */ +static struct watchdog_device wdd; + +static struct resource gp_status_reg_227e_res = + DEFINE_RES_IO_NAMED(GP_STATUS_REG_227E, SZ_1, KBUILD_MODNAME); + +static struct resource io_resource = + DEFINE_RES_IO_NAMED(WD_ENABLE_IOADR, SZ_1, + KBUILD_MODNAME " WD_ENABLE_IOADR"); + +/* the actual start will be discovered with pci, 0 is a placeholder */ +static struct resource mem_resource = + DEFINE_RES_MEM_NAMED(0, SZ_4, "WD_RESET_BASE_ADR"); + +static u32 wd_timeout_table[] = {2, 4, 6, 8, 16, 32, 48, 64 }; +static void __iomem *wd_reset_base_addr; + +static int get_timeout_idx(u32 timeout) +{ + int i; + + i = ARRAY_SIZE(wd_timeout_table) - 1; + for (; i >= 0; i--) { + if (timeout >= wd_timeout_table[i]) + break; + } + + return i; +} + +static int wd_start(struct watchdog_device *wdd) +{ + u8 regval; + + spin_lock(&io_lock); + + regval = inb(WD_ENABLE_IOADR); + regval |= WD_ENABLED; + outb(regval, WD_ENABLE_IOADR); + + spin_unlock(&io_lock); + + return 0; +} + +static int wd_stop(struct watchdog_device *wdd) +{ + u8 regval; + + spin_lock(&io_lock); + + regval = inb(WD_ENABLE_IOADR); + regval &= ~WD_ENABLED; + outb(regval, WD_ENABLE_IOADR); + + spin_unlock(&io_lock); + + return 0; +} + +static int wd_ping(struct watchdog_device *wdd) +{ + inb(WD_TRIGGER_IOADR); + return 0; +} + +static int wd_set_timeout(struct watchdog_device *wdd, unsigned int t) +{ + u8 regval; + int timeout_idx = get_timeout_idx(t); + + spin_lock(&io_lock); + + regval = inb(WD_ENABLE_IOADR) & 0xc7; + regval |= timeout_idx << 3; + outb(regval, WD_ENABLE_IOADR); + + spin_unlock(&io_lock); + wdd->timeout = wd_timeout_table[timeout_idx]; + + return 0; +} + +static const struct watchdog_info wdt_ident = { + .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING | + WDIOF_SETTIMEOUT, + .identity = KBUILD_MODNAME, +}; + +static const struct watchdog_ops wdt_ops = { + .owner = THIS_MODULE, + .start = wd_start, + .stop = wd_stop, + .ping = wd_ping, + .set_timeout = wd_set_timeout, +}; + +static void wd_set_safe_en_n(u32 wdtmode, bool safe_en_n) +{ + u16 resetbit; + + if (wdtmode == SIMATIC_IPC_DEVICE_227E) { + /* enable SAFE_EN_N on GP_STATUS_REG_227E */ + resetbit = inw(GP_STATUS_REG_227E); + if (safe_en_n) + resetbit &= ~SAFE_EN_N_227E; + else + resetbit |= SAFE_EN_N_227E; + + outw(resetbit, GP_STATUS_REG_227E); + } else { + /* enable SAFE_EN_N on PCH D1600 */ + resetbit = ioread16(wd_reset_base_addr); + + if (safe_en_n) + resetbit &= ~SAFE_EN_N_427E; + else + resetbit |= SAFE_EN_N_427E; + + iowrite16(resetbit, wd_reset_base_addr); + } +} + +static int wd_setup(u32 wdtmode, bool safe_en_n) +{ + u8 regval; + int timeout_idx = 0; + bool alarm_active; + + timeout_idx = get_timeout_idx(TIMEOUT_DEF); + + wd_set_safe_en_n(wdtmode, safe_en_n); + + /* read wd register to determine alarm state */ + regval = inb(WD_ENABLE_IOADR); + if (regval & 0x80) { + pr_warn("Watchdog alarm active.\n"); + regval = 0x82; /* use only macro mode, reset alarm bit */ + alarm_active = true; + } else { + regval = 0x02; /* use only macro mode */ + alarm_active = false; + } + + regval |= timeout_idx << 3; + if (nowayout) + regval |= WD_ENABLED; + + outb(regval, WD_ENABLE_IOADR); + + return alarm_active; +} + +static int simatic_ipc_wdt_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int rc = 0; + struct simatic_ipc_platform *plat = pdev->dev.platform_data; + struct resource *res; + + pr_debug(KBUILD_MODNAME ":%s(#%d) WDT mode: %d\n", + __func__, __LINE__, plat->devmode); + + switch (plat->devmode) { + case SIMATIC_IPC_DEVICE_227E: + if (!devm_request_region(dev, gp_status_reg_227e_res.start, + resource_size(&gp_status_reg_227e_res), + KBUILD_MODNAME)) { + dev_err(dev, + "Unable to register IO resource at %pR\n", + &gp_status_reg_227e_res); + return -EBUSY; + } + fallthrough; + case SIMATIC_IPC_DEVICE_427E: + wdd.info = &wdt_ident; + wdd.ops = &wdt_ops; + wdd.min_timeout = TIMEOUT_MIN; + wdd.max_timeout = TIMEOUT_MAX; + wdd.parent = NULL; + break; + default: + return -EINVAL; + } + + if (!devm_request_region(dev, io_resource.start, + resource_size(&io_resource), + io_resource.name)) { + dev_err(dev, + "Unable to register IO resource at %#x\n", + WD_ENABLE_IOADR); + return -EBUSY; + } + + if (plat->devmode == SIMATIC_IPC_DEVICE_427E) { + res = &mem_resource; + + /* get GPIO base from PCI */ + res->start = simatic_ipc_get_membase0(PCI_DEVFN(0x1f, 1)); + if (res->start == 0) + return -ENODEV; + + /* do the final address calculation */ + res->start = res->start + (GPIO_COMMUNITY0_PORT_ID << 16) + + PAD_CFG_DW0_GPP_A_23; + res->end += res->start; + + wd_reset_base_addr = devm_ioremap_resource(dev, res); + if (IS_ERR(wd_reset_base_addr)) + return -ENOMEM; + } + + wdd.bootstatus = wd_setup(plat->devmode, true); + if (wdd.bootstatus) + pr_warn(KBUILD_MODNAME ": last reboot caused by watchdog reset\n"); + + watchdog_set_nowayout(&wdd, nowayout); + watchdog_stop_on_reboot(&wdd); + + rc = devm_watchdog_register_device(dev, &wdd); + + if (rc == 0) + pr_debug("initialized. nowayout=%d\n", + nowayout); + + return rc; +} + +static int simatic_ipc_wdt_remove(struct platform_device *pdev) +{ + struct simatic_ipc_platform *plat = pdev->dev.platform_data; + + wd_setup(plat->devmode, false); + return 0; +} + +static struct platform_driver wdt_driver = { + .probe = simatic_ipc_wdt_probe, + .remove = simatic_ipc_wdt_remove, + .driver = { + .name = KBUILD_MODNAME, + }, +}; + +static int __init simatic_ipc_wdt_init(void) +{ + return platform_driver_register(&wdt_driver); +} + +static void __exit simatic_ipc_wdt_exit(void) +{ + platform_driver_unregister(&wdt_driver); +} + +module_init(simatic_ipc_wdt_init); +module_exit(simatic_ipc_wdt_exit); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" KBUILD_MODNAME); +MODULE_AUTHOR("Gerd Haeussler "); From patchwork Tue Mar 2 16:33:09 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Henning Schild X-Patchwork-Id: 393085 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.8 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 93E6BC433DB for ; Wed, 3 Mar 2021 02:50:10 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 4CD2F64E4E for ; Wed, 3 Mar 2021 02:50:10 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S241031AbhCCClq (ORCPT ); Tue, 2 Mar 2021 21:41:46 -0500 Received: from gecko.sbs.de ([194.138.37.40]:45318 "EHLO gecko.sbs.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1352057AbhCBS3g (ORCPT ); Tue, 2 Mar 2021 13:29:36 -0500 Received: from mail1.sbs.de (mail1.sbs.de [192.129.41.35]) by gecko.sbs.de (8.15.2/8.15.2) with ESMTPS id 122GXF5L002099 (version=TLSv1.2 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Tue, 2 Mar 2021 17:33:15 +0100 Received: from md1za8fc.ad001.siemens.net ([167.87.44.113]) by mail1.sbs.de (8.15.2/8.15.2) with ESMTP id 122GXBHR025192; Tue, 2 Mar 2021 17:33:14 +0100 From: Henning Schild To: linux-kernel@vger.kernel.org, linux-leds@vger.kernel.org, platform-driver-x86@vger.kernel.org, linux-watchdog@vger.kernel.org Cc: Srikanth Krishnakar , Jan Kiszka , Henning Schild , Gerd Haeussler , Guenter Roeck , Wim Van Sebroeck , Mark Gross , Hans de Goede , Pavel Machek , Michael Haener Subject: [PATCH 4/4] platform/x86: pmc_atom: improve critclk_systems matching for Siemens PCs Date: Tue, 2 Mar 2021 17:33:09 +0100 Message-Id: <20210302163309.25528-5-henning.schild@siemens.com> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20210302163309.25528-1-henning.schild@siemens.com> References: <20210302163309.25528-1-henning.schild@siemens.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-leds@vger.kernel.org From: Henning Schild Siemens industrial PCs unfortunately can not always be properly identified the way we used to. An earlier commit introduced code that allows proper identification without looking at DMI strings that could differ based on product branding. Switch over to that proper way and revert commits that used to collect the machines based on unstable strings. Fixes: 648e921888ad ("clk: x86: Stop marking clocks as CLK_IS_CRITICAL") Fixes: e8796c6c69d1 ("platform/x86: pmc_atom: Add Siemens CONNECT ...") Fixes: f110d252ae79 ("platform/x86: pmc_atom: Add Siemens SIMATIC ...") Fixes: ad0d315b4d4e ("platform/x86: pmc_atom: Add Siemens SIMATIC ...") Tested-by: Michael Haener Signed-off-by: Henning Schild --- drivers/platform/x86/pmc_atom.c | 39 ++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/drivers/platform/x86/pmc_atom.c b/drivers/platform/x86/pmc_atom.c index ca684ed760d1..03344f5502ad 100644 --- a/drivers/platform/x86/pmc_atom.c +++ b/drivers/platform/x86/pmc_atom.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -424,30 +425,31 @@ static const struct dmi_system_id critclk_systems[] = { }, }, { - .ident = "SIMATIC IPC227E", + .ident = "SIEMENS AG", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "SIEMENS AG"), - DMI_MATCH(DMI_PRODUCT_VERSION, "6ES7647-8B"), - }, - }, - { - .ident = "SIMATIC IPC277E", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "SIEMENS AG"), - DMI_MATCH(DMI_PRODUCT_VERSION, "6AV7882-0"), - }, - }, - { - .ident = "CONNECT X300", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "SIEMENS AG"), - DMI_MATCH(DMI_PRODUCT_VERSION, "A5E45074588"), }, }, { /*sentinel*/ } }; +static int pmc_clk_is_critical(const struct dmi_system_id *d) +{ + int ret = true; + u32 station_id; + + if (!strcmp(d->ident, "SIEMENS AG")) { + if (dmi_walk(simatic_ipc_find_dmi_entry_helper, &station_id)) + ret = false; + else + ret = (station_id == SIMATIC_IPC_IPC227E || + station_id == SIMATIC_IPC_IPC277E); + } + + return ret; +} + static int pmc_setup_clks(struct pci_dev *pdev, void __iomem *pmc_regmap, const struct pmc_data *pmc_data) { @@ -462,8 +464,9 @@ static int pmc_setup_clks(struct pci_dev *pdev, void __iomem *pmc_regmap, clk_data->base = pmc_regmap; /* offset is added by client */ clk_data->clks = pmc_data->clks; if (d) { - clk_data->critical = true; - pr_info("%s critclks quirk enabled\n", d->ident); + clk_data->critical = pmc_clk_is_critical(d); + if (clk_data->critical) + pr_info("%s critclks quirk enabled\n", d->ident); } clkdev = platform_device_register_data(&pdev->dev, "clk-pmc-atom",