From nobody Sat Feb 7 11:05:27 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id DBB67C7EE29 for ; Thu, 1 Jun 2023 12:38:46 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232946AbjFAMiq (ORCPT ); Thu, 1 Jun 2023 08:38:46 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:39800 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229589AbjFAMio (ORCPT ); Thu, 1 Jun 2023 08:38:44 -0400 Received: from mail-yw1-x1130.google.com (mail-yw1-x1130.google.com [IPv6:2607:f8b0:4864:20::1130]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 4218A119; Thu, 1 Jun 2023 05:38:41 -0700 (PDT) Received: by mail-yw1-x1130.google.com with SMTP id 00721157ae682-5689335d2b6so7062587b3.3; Thu, 01 Jun 2023 05:38:41 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20221208; t=1685623120; x=1688215120; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=SG3ENXnJkshIwq0YnXkOGqFDWsm7dyrKaP2wFaSOnLY=; b=NrvqRx2nBAz04RIA8gNXo+pTINhhT/f17BSpPnfebonsCZVBp2JKE3jnQU1gQgpYnQ WEYZ9PasMLpBacOeJYuOyUyIRdOMtVqoSJZoVmzzzxqTmRivMpRs+12ypI+kQaWz9Myj GMO7/uIIgaUjuADBhudDlrpp3sPUduHwPhBcGppPpbz1OGS2qUic4kUS0qo6lj/1ojkv UWnH6g4oQfkPNjSuO1fLhh7Dqee5dUL2p/E2xVrleGHtm2lqhITPVlWeC1aDEF88Z0/o IQgOHJNzsYUB9djFxi0nuFo2zwew0jsY6BwVtUMjZDVFBVwveDdpC6dL1y8mWzCRFn+3 PLtA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1685623120; x=1688215120; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=SG3ENXnJkshIwq0YnXkOGqFDWsm7dyrKaP2wFaSOnLY=; b=dLVd5wJI2frM/9K8BAfnYzfIk8hPJj+pOdheh7jMSzb6sM2fMVfrS0QIiHGA2JORjh ZBiJ1nCW7XBq62TwduJmLaWULCMC/h1CBPmDBa8DvqnjFBjdAqA0fz2l88JE0mCTLBCe tqUIj9taZc7YE6hwShE5mjDnZ6QcIF1ItpT5H1ltFPigxV0ASd7hJ81wWo+8L924aaYI gziIs41Xl5tohkFhfK3PPZT3l7/MwbAd4eVRdRFMkQEGrkokKbzxbcrKT8CKEH6e76Qu tOVHlDfm6gsyiGnZORAFRAQkusBXFtgRY/WB1ydXPsGJnIZO752/cmthPV0PKr8JNRUs qMJA== X-Gm-Message-State: AC+VfDxKGxwqnIq4KjOGyI6xBzOFeZTAtS6cOA+cRfzqhHQuc9tt6IyJ sCXJMW7plQqoCOhDJPMhl7fYFd0Ilrnbmw== X-Google-Smtp-Source: ACHHUZ6aK3J4uCK13HjCrGlzXYC4HgaV3l6ulBncj2Hgyk0Nq/kQoo0r7ZxYoyGMXasfU1pJO6JQHA== X-Received: by 2002:a0d:d70d:0:b0:561:5d6a:9150 with SMTP id z13-20020a0dd70d000000b005615d6a9150mr8113272ywd.50.1685623118793; Thu, 01 Jun 2023 05:38:38 -0700 (PDT) Received: from z-Lenovo-Product.lan ([2605:59c8:60b6:b110::4f6]) by smtp.gmail.com with ESMTPSA id t66-20020a818345000000b00568938ca41bsm3198993ywf.53.2023.06.01.05.38.37 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 01 Jun 2023 05:38:38 -0700 (PDT) From: David Ober To: linux-hwmon@vger.kernel.org Cc: linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, jdelvare@suse.com, linux@roeck-us.net, corbet@lwn.net, dober@lenovo.com, David Ober Subject: [PATCH] New module to add NCT6692D watchdog funtionality Date: Thu, 1 Jun 2023 08:38:30 -0400 Message-Id: <20230601123830.82476-1-dober6023@gmail.com> X-Mailer: git-send-email 2.34.1 MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" The new module adds in the basic functionality of the NCT6692D watchdog driver. This functionality is added to support the=20 Lenovo SE30 device Signed-off-by: David Ober --- Documentation/hwmon/nct6692.rst | 30 ++ drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/nct6692.c | 691 ++++++++++++++++++++++++++++++++ 4 files changed, 732 insertions(+) create mode 100644 Documentation/hwmon/nct6692.rst create mode 100644 drivers/hwmon/nct6692.c diff --git a/Documentation/hwmon/nct6692.rst b/Documentation/hwmon/nct6692.= rst new file mode 100644 index 000000000000..3bb1a4cccc62 --- /dev/null +++ b/Documentation/hwmon/nct6692.rst @@ -0,0 +1,30 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver nct6692 +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Supported chips: + + * Nuvoton NCT6692D + + Prefix: 'nct6692' + + Addresses scanned: ISA address retrieved from Super I/O registers + + Datasheet: Available from Nuvoton upon request + +Authors: + + David Ober + +Description +----------- + +This driver implements support for the Nuvoton NCT6692D eSIO chip. + + +Usage Note +---------- + +Implement the watchdog functionality of the NCT6692D eSIO chip + diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 0d3fd47db918..54907f5dbbce 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1528,6 +1528,16 @@ config SENSORS_NCT6683 This driver can also be built as a module. If so, the module will be called nct6683. =20 +config SENSORS_NCT6692 + tristate "Nuvoton NCT6692D" + depends on !PPC + help + If you say yes here you get support for the hardware monitoring + functionality of the Nuvoton NCT6692D eSIO chip. + + This driver can also be built as a module. If so, the module + will be called nct6692. + config SENSORS_NCT6775_CORE tristate select REGMAP diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 708e612e4ead..d9cd0735dd26 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -159,6 +159,7 @@ obj-$(CONFIG_SENSORS_MLXREG_FAN) +=3D mlxreg-fan.o obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) +=3D menf21bmc_hwmon.o obj-$(CONFIG_SENSORS_MR75203) +=3D mr75203.o obj-$(CONFIG_SENSORS_NCT6683) +=3D nct6683.o +obj-$(CONFIG_SENSORS_NCT6692) +=3D nct6692.o obj-$(CONFIG_SENSORS_NCT6775_CORE) +=3D nct6775-core.o nct6775-objs :=3D nct6775-platform.o obj-$(CONFIG_SENSORS_NCT6775) +=3D nct6775.o diff --git a/drivers/hwmon/nct6692.c b/drivers/hwmon/nct6692.c new file mode 100644 index 000000000000..2fd5d51d888c --- /dev/null +++ b/drivers/hwmon/nct6692.c @@ -0,0 +1,691 @@ The new module adds in the basic functionality of the NCT6692D watchdog driver. This functionality is added to support the=20 Lenovo SE30 device +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ThinkEdge Watchdog Driver + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* + * ISA constants + */ + +#define IOREGION_ALIGNMENT (~7) +#define IOREGION_OFFSET 4 /* Use EC port 1 */ +#define IOREGION_LENGTH 4 + +#define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */ +#define WATCHDOG_WDT_SEL 1 + +/*The timeout range is 1-255 minutes*/ +#define MIN_TIMEOUT 1 +#define MAX_TIMEOUT 255 + +#define THINKEDGE_MAX_PARAM_STRING_LEN 16 + +static int timeout; /* in seconds */ +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. 1 <=3D timeout <=3D 255, default=3D" + __MODULE_STRING(WATCHDOG_TIMEOUT) "."); + +static bool nowayout =3D WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=3D" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +static int early_disable; +module_param(early_disable, int, 0); +MODULE_PARM_DESC(early_disable, "Disable watchdog at boot time (default=3D= 0)"); + +#define DRVNAME "nct6692" + +#define NCT6692 "nct6692" +#define NCT6692_ID 0x0110 + +#define WDT_EFER(X) (X + 0) /* Extended Function Enable Registers */ +#define WDT_EFIR(X) (X + 0) /* Extended Function Index Register(same as E= FER) */ +#define WDT_EFDR(X) (WDT_EFIR(X) + 1) /* Extended Function Data Register */ +#define CHIPID_MASK 0xFFF0 + +#define NCT6692_MAX_IO_RETRY_NUM 0x300 + +#define NCT6692_EC_NAME "nct6692_ec" +#define NCT6692_EC_REPORT_NAME "nct6692_ec_rep" +#define NCT6692_HWM_CFG 0x180 + +#define NCT6692_SIO_UNLOCK_KEY 0x87 +#define NCT6692_SIO_LOCK_KEY 0xAA + +#define NCT6692_LD_NUM_SHM 0x0F +#define NCT6692_LD_WIN2_BASE_ADDR 0xF8 +#define NCT6692_LD_REPORT_BASE_ADDR 0xE4 + +// Host Interface WIN2 offset definition +#define SHM_WIN_MOD_OFFSET 0x01 +#define SHM_WIN_CMD_OFFSET 0x02 +#define SHM_WIN_SEL_OFFSET 0x03 +#define SHM_WIN_CTL_OFFSET 0x04 +#define VAL_SHM_WIN_CTRL_WR 0x40 +#define VAL_SHM_WIN_CTRL_RD 0x80 +#define SHM_WIN_ID_OFFSET 0x08 +#define SHM_WIN_DAT_OFFSET 0x10 + +struct nct6692_shm_t { + u_char *base_addr; + u_long base_phys; + u_char offset_mod; + u_char offset_cmd; + u_char offset_sel; + u_char offset_ctl; + u_char offset_id; + u_char offset_dat; + u_char *report_addr; + u_long report_phys; +}; + +/* REGs definitions */ +enum nct6692_channel_type { + NCT6692_CHANNEL_DEFAULT, + NCT6692_CHANNEL_REPORT, /* Not used in this driver. */ +}; + +struct nct6692_sio_data { + u_long base_phys; + u_long report_phys; + int sioreg; +}; + +struct nct6692_reg_t { + u_char channel; /* nct6692_channel_type */ + u_char mod; + u_char cmd; + u_char sel; + u_int idx; +}; + +struct nct6692_data_t { + struct nct6692_shm_t shm; + struct nct6692_reg_t cfg; + struct nct6692_reg_t cnt; + u_char timeout; + struct watchdog_device wdt; +}; + +static inline void superio_outb(int ioreg, int reg, int val) +{ + outb(reg, WDT_EFER(ioreg)); + outb(val, WDT_EFDR(ioreg)); +} + +static inline int superio_inb(int ioreg, int reg) +{ + outb(reg, WDT_EFER(ioreg)); + return inb(WDT_EFDR(ioreg)); +} + +static inline int superio_enter(int key, int addr, const char *name) +{ + if (!request_muxed_region(addr, 2, name)) { + pr_err("I/O address 0x%04x already in use\n", addr); + return -EBUSY; + } + outb_p(key, WDT_EFER(addr)); /* Enter extended function mode */ + outb_p(key, WDT_EFER(addr)); /* Again according to manual */ + + return 0; +} + +static inline void superio_select(int ioreg, int ld) +{ + superio_outb(ioreg, 0x07, ld); +} + +static inline void superio_exit(int key, int addr) +{ + outb_p(key, WDT_EFER(addr)); /* Leave extended function mode */ + release_region(addr, 2); +} + +/* + * The following several functions are used to access host interface accor= ding + * to the definition of memory region, reg (as a base addr) and an index o= ffset + * It uses (shm.base_addr + shm.offset) format to locate the data area of = the + * host interface channel. Then access the address "reg.idx + idx_offset" = that + * are suitable for a loop accessing. Where the idx_offset is an extra off= set + * based on the definition of reg for accessing the address based on the r= eg. + * + * Functions with suffix of 'report' are used to access report channel. + */ + +int read_shm_win(const struct nct6692_shm_t *shm, + const struct nct6692_reg_t *reg, + u_char idx_offset) +{ + int retval; + u_char pre_id; + u_char new_id; + u_int count =3D 0; + + if (!request_mem_region(shm->base_phys, 256, NCT6692_EC_NAME)) { + pr_err("nuv:request channel mutex fail (base_addr=3D%lX)\n", + shm->base_phys); + retval =3D -EBUSY; + goto exit; + } + + iowrite8(reg->mod, shm->base_addr + shm->offset_mod); + iowrite8(reg->cmd, shm->base_addr + shm->offset_cmd); + iowrite8(reg->sel, shm->base_addr + shm->offset_sel); + pre_id =3D ioread8(shm->base_addr + shm->offset_id); + iowrite8(VAL_SHM_WIN_CTRL_RD, shm->base_addr + shm->offset_ctl); + do { + new_id =3D ioread8(shm->base_addr + shm->offset_id); + if (count =3D=3D NCT6692_MAX_IO_RETRY_NUM) { + pr_warn("nuv:Wait ID count timeout in %s!\n", __func__); + retval =3D 0; + goto exit_release; + } + count++; + } while (pre_id =3D=3D new_id); + retval =3D ioread8(shm->base_addr + shm->offset_dat + reg->idx + idx_offs= et); + +exit_release: + release_mem_region(shm->base_phys, 256); +exit: + return retval; +} + +int write_shm_win(const struct nct6692_shm_t *shm, + const struct nct6692_reg_t *reg, + u_char idx_offset, + u_char val) +{ + int err =3D 0; + u_char pre_id; + u_char new_id; + u_int count =3D 0; + + if (!request_mem_region(shm->base_phys, 256, NCT6692_EC_NAME)) { + pr_err("nuv:request channel mutex fail (base_addr=3D%lX)\n", + shm->base_phys); + err =3D -EBUSY; + goto err_exit; + } + + iowrite8(reg->mod, shm->base_addr + shm->offset_mod); + iowrite8(reg->cmd, shm->base_addr + shm->offset_cmd); + iowrite8(reg->sel, shm->base_addr + shm->offset_sel); + + pre_id =3D ioread8(shm->base_addr + shm->offset_id); + iowrite8(VAL_SHM_WIN_CTRL_RD, shm->base_addr + shm->offset_ctl); + do { + new_id =3D ioread8(shm->base_addr + shm->offset_id); + if (count =3D=3D NCT6692_MAX_IO_RETRY_NUM) { + pr_warn("nuv:Wait ID count timeout in %s!\n", __func__); + err =3D -EINVAL; + goto err_exit_release; + } + count++; + } while (pre_id =3D=3D new_id); + + iowrite8(val, shm->base_addr + shm->offset_dat + reg->idx + idx_offset); + + pre_id =3D new_id; + + iowrite8(VAL_SHM_WIN_CTRL_WR, shm->base_addr + shm->offset_ctl); + do { + new_id =3D ioread8(shm->base_addr + shm->offset_id); + if (count =3D=3D NCT6692_MAX_IO_RETRY_NUM) { + pr_warn("nuv:Wait ID count timeout in %s!\n", __func__); + err =3D -EINVAL; + goto err_exit_release; + } + count++; + } while (pre_id =3D=3D new_id); + +err_exit_release: + release_mem_region(shm->base_phys, 256); + +err_exit: + return err; +} + +int read_shm_report(const struct nct6692_shm_t *shm, + const struct nct6692_reg_t *reg, + u_char idx_offset) +{ + int val; + + if (!request_mem_region(shm->report_phys, 256, + NCT6692_EC_REPORT_NAME)) { + pr_err("nuv:request channel mutex fail (base_addr=3D%lX)\n", + shm->report_phys); + return -EBUSY; + } + + val =3D ioread8(shm->report_addr + reg->idx + idx_offset); + + release_mem_region(shm->report_phys, 256); + + return val; +} + +int read_shm(const struct nct6692_shm_t *shm, + const struct nct6692_reg_t *reg) +{ + if (reg->channel =3D=3D NCT6692_CHANNEL_DEFAULT) + return read_shm_win(shm, reg, 0); + + return read_shm_report(shm, reg, 0); +} + +int write_shm(const struct nct6692_shm_t *shm, + const struct nct6692_reg_t *reg, + u_char val) +{ + if (reg->channel =3D=3D NCT6692_CHANNEL_DEFAULT) + return write_shm_win(shm, reg, 0, val); + + pr_warn("Report channel CAN NOT be written!\n"); + return -EINVAL; +} + +int read_shm_with_offset(const struct nct6692_shm_t *shm, + const struct nct6692_reg_t *reg, + u_char idx_offset) +{ + if (reg->channel =3D=3D NCT6692_CHANNEL_DEFAULT) + return read_shm_win(shm, reg, idx_offset); + + return read_shm_report(shm, reg, idx_offset); +} + +int write_shm_with_offset(const struct nct6692_shm_t *shm, + const struct nct6692_reg_t *reg, + u_char idx_offset, + u_char val) +{ + if (reg->channel =3D=3D NCT6692_CHANNEL_DEFAULT) + return write_shm_win(shm, reg, idx_offset, val); + + pr_warn("Report channel CAN NOT be written!\n"); + return -EINVAL; +} + +static int nct6692_wdt_init(struct watchdog_device *wdog) +{ + int err =3D 0; + u_char timeout, cfg; + struct nct6692_data_t *data =3D watchdog_get_drvdata(wdog); + + timeout =3D read_shm(&data->shm, &data->cnt); + cfg =3D read_shm(&data->shm, &data->cfg); + + if (timeout < 0) + return timeout; + if (timeout =3D=3D 0) + return 0; + + if (early_disable) { + pr_warn("Stopping previously enabled watchdog until userland kicks in\n"= ); + // Disable WDT and clear timeout count + data->timeout =3D 0; + err =3D write_shm(&data->shm, &data->cnt, data->timeout); + } else { + pr_info("Watchdog already running. Resetting timeout to %d sec\n", + wdog->timeout); + data->timeout =3D wdog->timeout; + err =3D write_shm(&data->shm, &data->cnt, data->timeout); + } + + return err; +} + +static int nct6692_wdt_enable(bool enable, u_int timeout, + struct nct6692_data_t *data) +{ + u_char reg; + + reg =3D read_shm(&data->shm, &data->cfg); + + if (enable) { + write_shm(&data->shm, &data->cfg, 0x02); + /* ^^^^ 0x00 to disable reboot */ + data->timeout =3D timeout; + write_shm(&data->shm, &data->cnt, data->timeout); + reg =3D read_shm(&data->shm, &data->cfg); + } else { + data->timeout =3D 0; + write_shm(&data->shm, &data->cnt, data->timeout); + } + reg =3D read_shm(&data->shm, &data->cfg); + reg =3D read_shm(&data->shm, &data->cnt); + return 0; +} + +static int nct6692_wdt_set_time(struct watchdog_device *wdog, u_int timeou= t) +{ + struct nct6692_data_t *data =3D watchdog_get_drvdata(wdog); + + if (timeout !=3D 0) + nct6692_wdt_enable(true, timeout, data); + else + nct6692_wdt_enable(false, timeout, data); + + return 0; +} + +static int nct6692_wdt_setup(struct watchdog_device *wdt) +{ + int err =3D 0; + + watchdog_stop_on_reboot(wdt); + + err =3D nct6692_wdt_init(wdt); + if (err) { + pr_err("failed to initialize watchdog (err=3D%d)\n", err); + return err; + } + return err; +} + +static int nct6692_wdt_start(struct watchdog_device *wdog) +{ + struct nct6692_data_t *data =3D watchdog_get_drvdata(wdog); + + nct6692_wdt_setup(wdog); + nct6692_wdt_set_time(wdog, data->timeout); + return 0; +} + +static int nct6692_wdt_stop(struct watchdog_device *wdog) +{ + return nct6692_wdt_set_time(wdog, 0); +} + +static int nct6692_wdt_set_timeout(struct watchdog_device *wdog, + u_int timeout) +{ + wdog->timeout =3D timeout; + return 0; +} + +static u_int nct6692_wdt_get_timeleft(struct watchdog_device *wdog) +{ + struct nct6692_data_t *data =3D watchdog_get_drvdata(wdog); + u_int timeleft; + + timeleft =3D read_shm(&data->shm, &data->cnt); + return timeleft; +} + +static int nct6692_wdt_ping(struct watchdog_device *wdt) +{ + struct nct6692_data_t *data =3D watchdog_get_drvdata(wdt); + int timeout; + + /* + * Note: + * NCT6692 does not support refreshing WDT_TIMER_REG register when + * the watchdog is active. Please disable watchdog before feeding + * the watchdog and enable it again. + */ + /* Disable soft watchdog timer */ + timeout =3D 0; + nct6692_wdt_enable(false, timeout, data); + + /* feed watchdog */ + timeout =3D wdt->timeout; + write_shm(&data->shm, &data->cnt, data->timeout); + + /* Enable soft watchdog timer */ + nct6692_wdt_enable(true, timeout, data); + return 0; + +} + +static const struct watchdog_info nct6692_wdt_info =3D { + .options =3D WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, + .identity =3D "nct6692 watchdog", +}; + +static const struct watchdog_ops nct6692_wdt_ops =3D { + .owner =3D THIS_MODULE, + .start =3D nct6692_wdt_start, + .stop =3D nct6692_wdt_stop, + .ping =3D nct6692_wdt_ping, + .set_timeout =3D nct6692_wdt_set_timeout, + .get_timeleft =3D nct6692_wdt_get_timeleft, +}; + +static int nct6692_probe(struct platform_device *pdev) +{ + struct device *dev =3D &pdev->dev; + struct nct6692_data_t *data =3D NULL; + struct nct6692_sio_data *sio_data =3D dev->platform_data; + struct resource *res; + + pr_info("Probe NCT6692 called\n"); + res =3D platform_get_resource(pdev, IORESOURCE_IO, 0); + + data =3D kzalloc(sizeof(struct nct6692_data_t), GFP_KERNEL); + if (!data) + return -ENOMEM; + + // init value + data->shm.base_addr =3D 0; + data->shm.report_addr =3D 0; + + data->shm.base_phys =3D sio_data->base_phys; + data->shm.base_addr =3D (u_char *)ioremap_cache(data->shm.base_phys, 256); + + data->shm.offset_mod =3D SHM_WIN_MOD_OFFSET; + data->shm.offset_cmd =3D SHM_WIN_CMD_OFFSET; + data->shm.offset_sel =3D SHM_WIN_SEL_OFFSET; + data->shm.offset_ctl =3D SHM_WIN_CTL_OFFSET; + data->shm.offset_id =3D SHM_WIN_ID_OFFSET; + data->shm.offset_dat =3D SHM_WIN_DAT_OFFSET; + + // Base for REPORT Channel + data->shm.report_phys =3D sio_data->report_phys; + data->shm.report_addr =3D (u_char *)ioremap_cache(data->shm.report_phys, = 256); + + data->cfg.channel =3D NCT6692_CHANNEL_DEFAULT; + data->cfg.mod =3D 0x10; + data->cfg.cmd =3D 0; + data->cfg.sel =3D 0; + data->cfg.idx =3D 0x15; + data->cnt.channel =3D NCT6692_CHANNEL_DEFAULT; + data->cnt.mod =3D 0x10; + data->cnt.cmd =3D 0; + data->cnt.sel =3D 0; + data->cnt.idx =3D 0x16; + + data->wdt.ops =3D &nct6692_wdt_ops; + data->wdt.info =3D &nct6692_wdt_info; + + data->wdt.timeout =3D WATCHDOG_TIMEOUT; /* Set default timeout */ + data->wdt.min_timeout =3D MIN_TIMEOUT; + data->wdt.max_timeout =3D MAX_TIMEOUT; + data->wdt.parent =3D &pdev->dev; + + watchdog_init_timeout(&data->wdt, timeout, &pdev->dev); + watchdog_set_nowayout(&data->wdt, nowayout); + watchdog_set_drvdata(&data->wdt, data); + + watchdog_stop_on_unregister(&data->wdt); + + return devm_watchdog_register_device(dev, &data->wdt); +} + +static struct platform_driver nct6692_driver =3D { + .driver =3D { + .name =3D DRVNAME, + /*.pm =3D NCT6692_DEV_PM_OPS,*/ + }, + .probe =3D nct6692_probe, +}; + +static int __init nct6692_find(int addr, u_long *base_phys, + u_long *report_phys) +{ + u16 val; + int err =3D 0; + u_long tmp_base_phys; + u_long tmp_report_phys; + + err =3D superio_enter(NCT6692_SIO_UNLOCK_KEY, addr, NCT6692); + if (err) + return err; + + val =3D superio_inb(addr, 0x20); + val =3D ((val << 8) | (superio_inb(addr, 0x21) & 0xFF)) & CHIPID_MASK; + + if (val !=3D NCT6692_ID) { + err =3D -ENODEV; + goto fail; + } + + superio_select(addr, NCT6692_LD_NUM_SHM); + tmp_base_phys =3D ((superio_inb(addr, NCT6692_LD_WIN2_BASE_ADDR) & 0xFF) | + ((superio_inb(addr, NCT6692_LD_WIN2_BASE_ADDR + 1) & 0xFF) << 8) | + ((superio_inb(addr, NCT6692_LD_WIN2_BASE_ADDR + 2) & 0xFF) << 16) | + ((superio_inb(addr, NCT6692_LD_WIN2_BASE_ADDR + 3) & 0xFF) << 24)) & + 0xFFFFFFFF; + + if ((tmp_base_phys =3D=3D 0xFFFFFFFF) || (tmp_base_phys =3D=3D 0)) { + err =3D -ENODEV; + goto fail; + } + + tmp_report_phys =3D ((superio_inb(addr, NCT6692_LD_REPORT_BASE_ADDR) & 0x= FF) | + ((superio_inb(addr, NCT6692_LD_REPORT_BASE_ADDR + 1) & 0xFF) << 8) | + ((superio_inb(addr, NCT6692_LD_REPORT_BASE_ADDR + 2) & 0xFF) << 16) | + ((superio_inb(addr, NCT6692_LD_REPORT_BASE_ADDR + 3) & 0xFF) << 24))= & + 0xFFFFFFFF; + + if ((tmp_report_phys =3D=3D 0xFFFFFFFF) || (tmp_report_phys =3D=3D 0)) { + err =3D -ENODEV; + goto fail; + } + +fail: + superio_exit(NCT6692_SIO_LOCK_KEY, addr); + if (!err) { + if (base_phys !=3D NULL) + *base_phys =3D tmp_base_phys; + if (report_phys !=3D NULL) + *report_phys =3D tmp_report_phys; + } + return err; +} + +static struct platform_device *pdev; + +static int __init nct6692_init(void) +{ + struct nct6692_sio_data sio_data; + int sioaddr[2] =3D { 0x2e, 0x4e }; + struct resource res; + int err; + int address; + bool found =3D false; + u_long base_phys =3D 0; + u_long report_phys =3D 0; + + platform_driver_register(&nct6692_driver); + + /* + * initialize sio_data->kind and sio_data->sioreg. + * + * when Super-I/O functions move to a separate file, the Super-I/O + * driver will probe 0x2e and 0x4e and auto-detect the presence of a + * nct6692 hardware monitor, and call probe() + */ + err =3D nct6692_find(sioaddr[0], &base_phys, &report_phys); + if (err) { + err =3D nct6692_find(sioaddr[1], &base_phys, &report_phys); + if (err) + return -ENODEV; + } + found =3D true; + sio_data.base_phys =3D base_phys; + sio_data.report_phys =3D report_phys; + + pr_info("NCT6692 device found\n"); + pdev =3D platform_device_alloc(DRVNAME, base_phys); + if (!pdev) { + err =3D -ENOMEM; + goto exit_device_unregister; + } + err =3D platform_device_add_data(pdev, &sio_data, + sizeof(struct nct6692_sio_data)); + if (err) + goto exit_device_put; + + memset(&res, 0, sizeof(res)); + res.name =3D DRVNAME; + res.start =3D address + IOREGION_OFFSET; + res.end =3D address + IOREGION_OFFSET + IOREGION_LENGTH - 1; + res.flags =3D IORESOURCE_IO; + + err =3D acpi_check_resource_conflict(&res); + if (err) { + platform_device_put(pdev); + pdev =3D NULL; + } + + err =3D platform_device_add_resources(pdev, &res, 1); + if (err) + goto exit_device_put; + + /* platform_device_add calls probe() */ + err =3D platform_device_add(pdev); + if (err) + goto exit_device_put; + + if (!found) { + err =3D -ENODEV; + goto exit_unregister; + } + + return 0; +exit_device_put: + platform_device_put(pdev); +exit_device_unregister: + if (pdev) + platform_device_unregister(pdev); +exit_unregister: + platform_driver_unregister(&nct6692_driver); + return err; +} + +static void __exit nct6692_exit(void) +{ + platform_device_unregister(pdev); + platform_driver_unregister(&nct6692_driver); +} + +MODULE_AUTHOR("David Ober "); +MODULE_DESCRIPTION("NCT6692D driver"); +MODULE_LICENSE("GPL"); + +module_init(nct6692_init); +module_exit(nct6692_exit); + --=20 2.34.1