Received: by 2002:a05:6a10:6d10:0:0:0:0 with SMTP id gq16csp2531956pxb; Sun, 24 Apr 2022 18:02:36 -0700 (PDT) X-Google-Smtp-Source: ABdhPJzC5nficSZGfT4lYw3jhlHK1U9WSGkgDmpVIYqz/W/1rFvjN5Osvw+XleOS40ZdFyStzjNL X-Received: by 2002:a05:6a00:1a06:b0:4fc:d6c5:f3d7 with SMTP id g6-20020a056a001a0600b004fcd6c5f3d7mr16282829pfv.53.1650848556591; Sun, 24 Apr 2022 18:02:36 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1650848556; cv=none; d=google.com; s=arc-20160816; b=g4SBSk3GMqE5vOhTuB4GZ9mNpFIMVwBc8UG3quwDlFC8s5LILD3dcX5T+1J9eginKo 5VEY+TCa2AU+Uw4EdDjQzyI++hXZhkTEyyy2lkafwo10I+Y75T2imB5EQz9lmsTuEGqv y6TGtawqf1FxIP9vcyv9mSbkNG0AOYSolXsC664MWVzcKyCc7bLIPmCa26cGKjHsbKfx neFh9in3l4HlvKgta/XwPWIPkwcK1lNRMBNmklPwS3uStPMaRJm1p9nW+iQ5Hbwr/U0O lUOMp0llTSq/8bKed0C06fqWKMqYflawuHAH7bG0Fhi30jiIPrFNp4T4LdTxa6QasCcU lGuA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:in-reply-to:from :references:cc:to:content-language:subject:user-agent:mime-version :date:message-id:sender:dkim-signature; bh=oIvwHP6zD5SMBHH3B5SSD7Zlsic4GGVRklDm5OZ3gsY=; b=wVLqSPCcqEAY/RPXRt26fCsPD2SPYBOOJM/Ww9whORVUB7VbEjEs8TKjFbmnkTJUmF EQ2sD4sqxc6LXvRXpqC0jfXvaZr9y5LCslQn8CVfNyYWiavwFdRHyUi8H/mNkxXafcLz 9aHrqVfodCg25J/FRwyQ9yG2o/zyng+mUr4qIcLxKhORbkYYnZZKNelYjMVtHht3MKR5 aoxxvKCGXQV4mrIXZsWJaTY3gkB0PJceVKWR6wPr6zWgo2DtDTDoQBZ6f+k6xnaJ60n/ 5c/Sb624OY5K33zbe1wllHY7cKh9YWIdR1HNQht9jMSd2sbmbSgd1E+Ar7pBADvBffuC p1fA== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@gmail.com header.s=20210112 header.b=IUXnInOw; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Return-Path: Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id a15-20020a634d0f000000b0039dade203f0si15434024pgb.292.2022.04.24.18.02.19; Sun, 24 Apr 2022 18:02:36 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass header.i=@gmail.com header.s=20210112 header.b=IUXnInOw; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S235178AbiDXQlu (ORCPT + 99 others); Sun, 24 Apr 2022 12:41:50 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:48588 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S235581AbiDXQls (ORCPT ); Sun, 24 Apr 2022 12:41:48 -0400 Received: from mail-oa1-x2d.google.com (mail-oa1-x2d.google.com [IPv6:2001:4860:4864:20::2d]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 11540138492; Sun, 24 Apr 2022 09:38:44 -0700 (PDT) Received: by mail-oa1-x2d.google.com with SMTP id 586e51a60fabf-e68392d626so8738764fac.4; Sun, 24 Apr 2022 09:38:44 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=sender:message-id:date:mime-version:user-agent:subject :content-language:to:cc:references:from:in-reply-to :content-transfer-encoding; bh=oIvwHP6zD5SMBHH3B5SSD7Zlsic4GGVRklDm5OZ3gsY=; b=IUXnInOwCZozV/p5jLBkWxyD/5Olftx1SZpDkAM+16rM6Xc+2WVFdaTI9L137HDL1m GIQIjQSv8oi0QyurLdgTaPjcbj01crbzCAxaUnuNWkqXJpCbnJZhmku/O+jeNZCloS94 15AEepjFYgKcjf4mSw+rjX1x5iXJtP4A7zqVbdUdKQnJOU23jkYy4rRBXq49QFsfGMb4 niEHlZhugXMrcj3SiO/MZYxl7QEkU3FCvrEZdSdmy6G4pREOfKcLs77B5Qt7NsWd/jM/ MJKk5pggVM8yWN6McEBaoirh2KJvzgSlixKJf4s9hRP6hz08bi3zO5k6tuIPiDXvH0/W Ac8A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:sender:message-id:date:mime-version:user-agent :subject:content-language:to:cc:references:from:in-reply-to :content-transfer-encoding; bh=oIvwHP6zD5SMBHH3B5SSD7Zlsic4GGVRklDm5OZ3gsY=; b=cAi1nNE+aZdPg0GTKqiOvdiv3lWFwiDQzrb6/n6xIuSZ/zSUcuXp/rID0ZpHLsPN3H 297yuex/UxATgQDSTgHtLaFcG8a9DqtGjmfZ7IOE3op2DeuBnhIy7f+rhCjYKF4Rv+CG sni5vwqsKRWZFlOJzE0Wh0dDl4T3cCoNKtiyGPOA40+W4w7NA5BYfolRgBoYjQb8UnvL +VFONEQW0hP9zoHAPfMeAzGvmT7e4zj8qoibeaNHjMdbRxdo2k8UY2y3q60IPH9GQv4Z ByMyv/RETYIfIuryl6ymx7TRgaocj0IPgiKy74XNp8oKsvNBVa6tsFMdY8lN5nh1L5xO QJRQ== X-Gm-Message-State: AOAM530lOmlJDOwqMoIFkHoAq348GQ1dEn3svgN5LzEPaLyBqXZ5Uejl dkbsgJapL03Wfr1X0Wi9zQM= X-Received: by 2002:a05:6870:2115:b0:e6:6ee9:628b with SMTP id f21-20020a056870211500b000e66ee9628bmr7452710oae.1.1650818323320; Sun, 24 Apr 2022 09:38:43 -0700 (PDT) Received: from ?IPV6:2600:1700:e321:62f0:329c:23ff:fee3:9d7c? ([2600:1700:e321:62f0:329c:23ff:fee3:9d7c]) by smtp.gmail.com with ESMTPSA id p15-20020a4adfcf000000b0033a48bce3afsm3236106ood.18.2022.04.24.09.38.41 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Sun, 24 Apr 2022 09:38:42 -0700 (PDT) Sender: Guenter Roeck Message-ID: <82834494-dec7-b827-2910-f7823cb48561@roeck-us.net> Date: Sun, 24 Apr 2022 09:38:40 -0700 MIME-Version: 1.0 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Thunderbird/91.7.0 Subject: Re: [PATCH v4 4/4] hwmon: add driver for the Microchip LAN966x SoC Content-Language: en-US To: Michael Walle , Jean Delvare , Rob Herring , Krzysztof Kozlowski Cc: linux-hwmon@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org References: <20220401214032.3738095-1-michael@walle.cc> <20220401214032.3738095-5-michael@walle.cc> From: Guenter Roeck In-Reply-To: <20220401214032.3738095-5-michael@walle.cc> Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit X-Spam-Status: No, score=-3.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_EF,FREEMAIL_ENVFROM_END_DIGIT, FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM,HEADER_FROM_DIFFERENT_DOMAINS, NICE_REPLY_A,RCVD_IN_DNSWL_NONE,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org On 4/1/22 14:40, Michael Walle wrote: > Add support for the temperatur sensor and the fan controller on the > Microchip LAN966x SoC. Apparently, an Analog Bits PVT sensor is used > which can measure temperature and process voltages. But only a forumlae > for the temperature sensor is known. Additionally, the SoC support a fan > tacho input as well as a PWM signal to control the fan. > > Signed-off-by: Michael Walle For my reference: Reviewed-by: Guenter Roeck I'll apply as soon as the devicetree file has been approved. Guenter > --- > Just in case someone is curious why the I state the datasheet is wrong > on the PWM frequency: The actual PWM frequency was verified by measuring > the output with an oscilloscope. Also you can write a '0' as the PWM > frequency which would result in div-by-zero; but what happens is that > the output frequency will be half the frequency of a setting of '1'. > > Documentation/hwmon/lan966x.rst | 40 +++ > drivers/hwmon/Kconfig | 12 + > drivers/hwmon/Makefile | 1 + > drivers/hwmon/lan966x-hwmon.c | 418 ++++++++++++++++++++++++++++++++ > 4 files changed, 471 insertions(+) > create mode 100644 Documentation/hwmon/lan966x.rst > create mode 100644 drivers/hwmon/lan966x-hwmon.c > > diff --git a/Documentation/hwmon/lan966x.rst b/Documentation/hwmon/lan966x.rst > new file mode 100644 > index 000000000000..1d1724afa5d2 > --- /dev/null > +++ b/Documentation/hwmon/lan966x.rst > @@ -0,0 +1,40 @@ > +.. SPDX-License-Identifier: GPL-2.0 > + > +Kernel driver lan966x-hwmon > +=========================== > + > +Supported chips: > + > + * Microchip LAN9668 (sensor in SoC) > + > + Prefix: 'lan9668-hwmon' > + > + Datasheet: https://microchip-ung.github.io/lan9668_reginfo > + > +Authors: > + > + Michael Walle > + > +Description > +----------- > + > +This driver implements support for the Microchip LAN9668 on-chip > +temperature sensor as well as its fan controller. It provides one > +temperature sensor and one fan controller. The temperature range > +of the sensor is specified from -40 to +125 degrees Celsius and > +its accuracy is +/- 5 degrees Celsius. The fan controller has a > +tacho input and a PWM output with a customizable PWM output > +frequency ranging from ~20Hz to ~650kHz. > + > +No alarms are supported by the SoC. > + > +The driver exports temperature values, fan tacho input and PWM > +settings via the following sysfs files: > + > +**temp1_input** > + > +**fan1_input** > + > +**pwm1** > + > +**pwm1_freq** > diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig > index be9773270e53..052b37b78919 100644 > --- a/drivers/hwmon/Kconfig > +++ b/drivers/hwmon/Kconfig > @@ -815,6 +815,18 @@ config SENSORS_POWR1220 > This driver can also be built as a module. If so, the module > will be called powr1220. > > +config SENSORS_LAN966X > + tristate "Microchip LAN966x Hardware Monitoring" > + depends on SOC_LAN966 || COMPILE_TEST > + select REGMAP > + select POLYNOMIAL > + help > + If you say yes here you get support for temperature monitoring > + on the Microchip LAN966x SoC. > + > + This driver can also be built as a module. If so, the module > + will be called lan966x-hwmon. > + > config SENSORS_LINEAGE > tristate "Lineage Compact Power Line Power Entry Module" > depends on I2C > diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile > index 8a03289e2aa4..51ca6956f8b7 100644 > --- a/drivers/hwmon/Makefile > +++ b/drivers/hwmon/Makefile > @@ -100,6 +100,7 @@ obj-$(CONFIG_SENSORS_IT87) += it87.o > obj-$(CONFIG_SENSORS_JC42) += jc42.o > obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o > obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o > +obj-$(CONFIG_SENSORS_LAN966X) += lan966x-hwmon.o > obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o > obj-$(CONFIG_SENSORS_LOCHNAGAR) += lochnagar-hwmon.o > obj-$(CONFIG_SENSORS_LM63) += lm63.o > diff --git a/drivers/hwmon/lan966x-hwmon.c b/drivers/hwmon/lan966x-hwmon.c > new file mode 100644 > index 000000000000..f41df053ac31 > --- /dev/null > +++ b/drivers/hwmon/lan966x-hwmon.c > @@ -0,0 +1,418 @@ > +// SPDX-License-Identifier: GPL-2.0-only > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +/* > + * The original translation formulae of the temperature (in degrees of Celsius) > + * are as follows: > + * > + * T = -3.4627e-11*(N^4) + 1.1023e-7*(N^3) + -1.9165e-4*(N^2) + > + * 3.0604e-1*(N^1) + -5.6197e1 > + * > + * where [-56.197, 136.402]C and N = [0, 1023]. > + * > + * They must be accordingly altered to be suitable for the integer arithmetics. > + * The technique is called 'factor redistribution', which just makes sure the > + * multiplications and divisions are made so to have a result of the operations > + * within the integer numbers limit. In addition we need to translate the > + * formulae to accept millidegrees of Celsius. Here what it looks like after > + * the alterations: > + * > + * T = -34627e-12*(N^4) + 110230e-9*(N^3) + -191650e-6*(N^2) + > + * 306040e-3*(N^1) + -56197 > + * > + * where T = [-56197, 136402]mC and N = [0, 1023]. > + */ > + > +static const struct polynomial poly_N_to_temp = { > + .terms = { > + {4, -34627, 1000, 1}, > + {3, 110230, 1000, 1}, > + {2, -191650, 1000, 1}, > + {1, 306040, 1000, 1}, > + {0, -56197, 1, 1} > + } > +}; > + > +#define PVT_SENSOR_CTRL 0x0 /* unused */ > +#define PVT_SENSOR_CFG 0x4 > +#define SENSOR_CFG_CLK_CFG GENMASK(27, 20) > +#define SENSOR_CFG_TRIM_VAL GENMASK(13, 9) > +#define SENSOR_CFG_SAMPLE_ENA BIT(8) > +#define SENSOR_CFG_START_CAPTURE BIT(7) > +#define SENSOR_CFG_CONTINIOUS_MODE BIT(6) > +#define SENSOR_CFG_PSAMPLE_ENA GENMASK(1, 0) > +#define PVT_SENSOR_STAT 0x8 > +#define SENSOR_STAT_DATA_VALID BIT(10) > +#define SENSOR_STAT_DATA GENMASK(9, 0) > + > +#define FAN_CFG 0x0 > +#define FAN_CFG_DUTY_CYCLE GENMASK(23, 16) > +#define INV_POL BIT(3) > +#define GATE_ENA BIT(2) > +#define PWM_OPEN_COL_ENA BIT(1) > +#define FAN_STAT_CFG BIT(0) > +#define FAN_PWM_FREQ 0x4 > +#define FAN_PWM_CYC_10US GENMASK(25, 15) > +#define FAN_PWM_FREQ_FREQ GENMASK(14, 0) > +#define FAN_CNT 0xc > +#define FAN_CNT_DATA GENMASK(15, 0) > + > +#define LAN966X_PVT_CLK 1200000 /* 1.2 MHz */ > + > +struct lan966x_hwmon { > + struct regmap *regmap_pvt; > + struct regmap *regmap_fan; > + struct clk *clk; > + unsigned long clk_rate; > +}; > + > +static int lan966x_hwmon_read_temp(struct device *dev, long *val) > +{ > + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); > + unsigned int data; > + int ret; > + > + ret = regmap_read(hwmon->regmap_pvt, PVT_SENSOR_STAT, &data); > + if (ret < 0) > + return ret; > + > + if (!(data & SENSOR_STAT_DATA_VALID)) > + return -ENODATA; > + > + *val = polynomial_calc(&poly_N_to_temp, > + FIELD_GET(SENSOR_STAT_DATA, data)); > + > + return 0; > +} > + > +static int lan966x_hwmon_read_fan(struct device *dev, long *val) > +{ > + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); > + unsigned int data; > + int ret; > + > + ret = regmap_read(hwmon->regmap_fan, FAN_CNT, &data); > + if (ret < 0) > + return ret; > + > + /* > + * Data is given in pulses per second. Assume two pulses > + * per revolution. > + */ > + *val = FIELD_GET(FAN_CNT_DATA, data) * 60 / 2; > + > + return 0; > +} > + > +static int lan966x_hwmon_read_pwm(struct device *dev, long *val) > +{ > + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); > + unsigned int data; > + int ret; > + > + ret = regmap_read(hwmon->regmap_fan, FAN_CFG, &data); > + if (ret < 0) > + return ret; > + > + *val = FIELD_GET(FAN_CFG_DUTY_CYCLE, data); > + > + return 0; > +} > + > +static int lan966x_hwmon_read_pwm_freq(struct device *dev, long *val) > +{ > + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); > + unsigned long tmp; > + unsigned int data; > + int ret; > + > + ret = regmap_read(hwmon->regmap_fan, FAN_PWM_FREQ, &data); > + if (ret < 0) > + return ret; > + > + /* > + * Datasheet says it is sys_clk / 256 / pwm_freq. But in reality > + * it is sys_clk / 256 / (pwm_freq + 1). > + */ > + data = FIELD_GET(FAN_PWM_FREQ_FREQ, data) + 1; > + tmp = DIV_ROUND_CLOSEST(hwmon->clk_rate, 256); > + *val = DIV_ROUND_CLOSEST(tmp, data); > + > + return 0; > +} > + > +static int lan966x_hwmon_read(struct device *dev, enum hwmon_sensor_types type, > + u32 attr, int channel, long *val) > +{ > + switch (type) { > + case hwmon_temp: > + return lan966x_hwmon_read_temp(dev, val); > + case hwmon_fan: > + return lan966x_hwmon_read_fan(dev, val); > + case hwmon_pwm: > + switch (attr) { > + case hwmon_pwm_input: > + return lan966x_hwmon_read_pwm(dev, val); > + case hwmon_pwm_freq: > + return lan966x_hwmon_read_pwm_freq(dev, val); > + default: > + return -EOPNOTSUPP; > + } > + default: > + return -EOPNOTSUPP; > + } > +} > + > +static int lan966x_hwmon_write_pwm(struct device *dev, long val) > +{ > + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); > + > + if (val < 0 || val > 255) > + return -EINVAL; > + > + return regmap_update_bits(hwmon->regmap_fan, FAN_CFG, > + FAN_CFG_DUTY_CYCLE, > + FIELD_PREP(FAN_CFG_DUTY_CYCLE, val)); > +} > + > +static int lan966x_hwmon_write_pwm_freq(struct device *dev, long val) > +{ > + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); > + > + if (val <= 0) > + return -EINVAL; > + > + val = DIV_ROUND_CLOSEST(hwmon->clk_rate, val); > + val = DIV_ROUND_CLOSEST(val, 256) - 1; > + val = clamp_val(val, 0, FAN_PWM_FREQ_FREQ); > + > + return regmap_update_bits(hwmon->regmap_fan, FAN_PWM_FREQ, > + FAN_PWM_FREQ_FREQ, > + FIELD_PREP(FAN_PWM_FREQ_FREQ, val)); > +} > + > +static int lan966x_hwmon_write(struct device *dev, enum hwmon_sensor_types type, > + u32 attr, int channel, long val) > +{ > + switch (type) { > + case hwmon_pwm: > + switch (attr) { > + case hwmon_pwm_input: > + return lan966x_hwmon_write_pwm(dev, val); > + case hwmon_pwm_freq: > + return lan966x_hwmon_write_pwm_freq(dev, val); > + default: > + return -EOPNOTSUPP; > + } > + default: > + return -EOPNOTSUPP; > + } > +} > + > +static umode_t lan966x_hwmon_is_visible(const void *data, > + enum hwmon_sensor_types type, > + u32 attr, int channel) > +{ > + umode_t mode = 0; > + > + switch (type) { > + case hwmon_temp: > + switch (attr) { > + case hwmon_temp_input: > + mode = 0444; > + break; > + default: > + break; > + } > + break; > + case hwmon_fan: > + switch (attr) { > + case hwmon_fan_input: > + mode = 0444; > + break; > + default: > + break; > + } > + break; > + case hwmon_pwm: > + switch (attr) { > + case hwmon_pwm_input: > + case hwmon_pwm_freq: > + mode = 0644; > + break; > + default: > + break; > + } > + break; > + default: > + break; > + } > + > + return mode; > +} > + > +static const struct hwmon_channel_info *lan966x_hwmon_info[] = { > + HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), > + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), > + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT), > + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_FREQ), > + NULL > +}; > + > +static const struct hwmon_ops lan966x_hwmon_ops = { > + .is_visible = lan966x_hwmon_is_visible, > + .read = lan966x_hwmon_read, > + .write = lan966x_hwmon_write, > +}; > + > +static const struct hwmon_chip_info lan966x_hwmon_chip_info = { > + .ops = &lan966x_hwmon_ops, > + .info = lan966x_hwmon_info, > +}; > + > +static void lan966x_hwmon_disable(void *data) > +{ > + struct lan966x_hwmon *hwmon = data; > + > + regmap_update_bits(hwmon->regmap_pvt, PVT_SENSOR_CFG, > + SENSOR_CFG_SAMPLE_ENA | SENSOR_CFG_CONTINIOUS_MODE, > + 0); > +} > + > +static int lan966x_hwmon_enable(struct device *dev, > + struct lan966x_hwmon *hwmon) > +{ > + unsigned int mask = SENSOR_CFG_CLK_CFG | > + SENSOR_CFG_SAMPLE_ENA | > + SENSOR_CFG_START_CAPTURE | > + SENSOR_CFG_CONTINIOUS_MODE | > + SENSOR_CFG_PSAMPLE_ENA; > + unsigned int val; > + unsigned int div; > + int ret; > + > + /* enable continuous mode */ > + val = SENSOR_CFG_SAMPLE_ENA | SENSOR_CFG_CONTINIOUS_MODE; > + > + /* set PVT clock to be between 1.15 and 1.25 MHz */ > + div = DIV_ROUND_CLOSEST(hwmon->clk_rate, LAN966X_PVT_CLK); > + val |= FIELD_PREP(SENSOR_CFG_CLK_CFG, div); > + > + ret = regmap_update_bits(hwmon->regmap_pvt, PVT_SENSOR_CFG, > + mask, val); > + if (ret) > + return ret; > + > + return devm_add_action_or_reset(dev, lan966x_hwmon_disable, hwmon); > +} > + > +static struct regmap *lan966x_init_regmap(struct platform_device *pdev, > + const char *name) > +{ > + struct regmap_config regmap_config = { > + .reg_bits = 32, > + .reg_stride = 4, > + .val_bits = 32, > + }; > + void __iomem *base; > + > + base = devm_platform_ioremap_resource_byname(pdev, name); > + if (IS_ERR(base)) > + return ERR_CAST(base); > + > + regmap_config.name = name; > + > + return devm_regmap_init_mmio(&pdev->dev, base, ®map_config); > +} > + > +static void lan966x_clk_disable(void *data) > +{ > + struct lan966x_hwmon *hwmon = data; > + > + clk_disable_unprepare(hwmon->clk); > +} > + > +static int lan966x_clk_enable(struct device *dev, struct lan966x_hwmon *hwmon) > +{ > + int ret; > + > + ret = clk_prepare_enable(hwmon->clk); > + if (ret) > + return ret; > + > + return devm_add_action_or_reset(dev, lan966x_clk_disable, hwmon); > +} > + > +static int lan966x_hwmon_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct lan966x_hwmon *hwmon; > + struct device *hwmon_dev; > + int ret; > + > + hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL); > + if (!hwmon) > + return -ENOMEM; > + > + hwmon->clk = devm_clk_get(dev, NULL); > + if (IS_ERR(hwmon->clk)) > + return dev_err_probe(dev, PTR_ERR(hwmon->clk), > + "failed to get clock\n"); > + > + ret = lan966x_clk_enable(dev, hwmon); > + if (ret) > + return dev_err_probe(dev, ret, "failed to enable clock\n"); > + > + hwmon->clk_rate = clk_get_rate(hwmon->clk); > + > + hwmon->regmap_pvt = lan966x_init_regmap(pdev, "pvt"); > + if (IS_ERR(hwmon->regmap_pvt)) > + return dev_err_probe(dev, PTR_ERR(hwmon->regmap_pvt), > + "failed to get regmap for PVT registers\n"); > + > + hwmon->regmap_fan = lan966x_init_regmap(pdev, "fan"); > + if (IS_ERR(hwmon->regmap_fan)) > + return dev_err_probe(dev, PTR_ERR(hwmon->regmap_fan), > + "failed to get regmap for fan registers\n"); > + > + ret = lan966x_hwmon_enable(dev, hwmon); > + if (ret) > + return dev_err_probe(dev, ret, "failed to enable sensor\n"); > + > + hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, > + "lan966x_hwmon", hwmon, > + &lan966x_hwmon_chip_info, NULL); > + if (IS_ERR(hwmon_dev)) > + return dev_err_probe(dev, PTR_ERR(hwmon_dev), > + "failed to register hwmon device\n"); > + > + return 0; > +} > + > +static const struct of_device_id lan966x_hwmon_of_match[] = { > + { .compatible = "microchip,lan9668-hwmon" }, > + {} > +}; > +MODULE_DEVICE_TABLE(of, lan966x_hwmon_of_match); > + > +static struct platform_driver lan966x_hwmon_driver = { > + .probe = lan966x_hwmon_probe, > + .driver = { > + .name = "lan966x-hwmon", > + .of_match_table = lan966x_hwmon_of_match, > + }, > +}; > +module_platform_driver(lan966x_hwmon_driver); > + > +MODULE_DESCRIPTION("LAN966x Hardware Monitoring Driver"); > +MODULE_AUTHOR("Michael Walle "); > +MODULE_LICENSE("GPL");