From 0be6e4c39260188f9607ac6f30651c66f615ec70 Mon Sep 17 00:00:00 2001 From: luyulin Date: Fri, 24 May 2024 16:51:46 +0800 Subject: [PATCH 031/222] feat:pvt_sensors driver to linux 6.6. Changelogs: pvt_sensors driver to linux 6.6. --- arch/riscv/configs/win2030_defconfig | 1 + drivers/hwmon/Kconfig | 6 + drivers/hwmon/Makefile | 2 +- drivers/hwmon/eswin_pvt.c | 930 +++++++++++++++++++++++++++ drivers/hwmon/eswin_pvt.h | 242 +++++++ 5 files changed, 1180 insertions(+), 1 deletion(-) create mode 100644 drivers/hwmon/eswin_pvt.c create mode 100644 drivers/hwmon/eswin_pvt.h diff --git a/arch/riscv/configs/win2030_defconfig b/arch/riscv/configs/win2030_defconfig index d0362cdd1b1e..8d5ad3f19306 100644 --- a/arch/riscv/configs/win2030_defconfig +++ b/arch/riscv/configs/win2030_defconfig @@ -268,6 +268,7 @@ CONFIG_MEMORY=y CONFIG_PWM=y CONFIG_PWM_ESWIN=y CONFIG_SENSORS_ESWIN_FAN_CONTROL=y +CONFIG_SENSORS_ESWIN_PVT=y CONFIG_RTC_DRV_ESWIN=y CONFIG_RESET_ESWIN_WIN2030=y CONFIG_INTERCONNECT=y diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 39185cb8e3df..655ce8348386 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -568,6 +568,12 @@ config SENSORS_DA9055 This driver can also be built as a module. If so, the module will be called da9055-hwmon. +config SENSORS_ESWIN_PVT + tristate "Eswin Process, Voltage, Temperature sensor driver" + depends on HWMON + help + If you say yes here you get support for Eswin PVT sensor. + config SENSORS_I5K_AMB tristate "FB-DIMM AMB temperature sensor on Intel 5000 series chipsets" depends on PCI diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 60f32f51eeb9..273e8f758fbe 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -222,7 +222,7 @@ obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o obj-$(CONFIG_SENSORS_XGENE) += xgene-hwmon.o obj-$(CONFIG_SENSORS_PAC1934) += pac193x.o obj-$(CONFIG_SENSORS_ESWIN_FAN_CONTROL) += eswin-fan-control.o - +obj-$(CONFIG_SENSORS_ESWIN_PVT) += eswin_pvt.o obj-$(CONFIG_SENSORS_OCC) += occ/ obj-$(CONFIG_SENSORS_PECI) += peci/ obj-$(CONFIG_PMBUS) += pmbus/ diff --git a/drivers/hwmon/eswin_pvt.c b/drivers/hwmon/eswin_pvt.c new file mode 100644 index 000000000000..fd8dec5db85e --- /dev/null +++ b/drivers/hwmon/eswin_pvt.c @@ -0,0 +1,930 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ESWIN PVT device driver + * + * Copyright 2024, Beijing ESWIN Computing Technology Co., Ltd.. All rights reserved. + * SPDX-License-Identifier: GPL-2.0 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Authors: Yulin Lu + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "eswin_pvt.h" + + +/* + * For the sake of the code simplification we created the sensors info table + * with the sensor names, activation modes, threshold registers base address + * and the thresholds bit fields. + */ +static const struct pvt_sensor_info pvt_info_cpu[] = { + PVT_SENSOR_INFO(0, "CPU Core Temperature", hwmon_temp, TEMP, TTHRES), + PVT_SENSOR_INFO(0, "CPU Core Voltage", hwmon_in, VOLT, VTHRES), + PVT_SENSOR_INFO(1, "CPU Core Low-Vt", hwmon_in, LVT, LTHRES), + PVT_SENSOR_INFO(2, "CPU Core UltraLow-Vt", hwmon_in, ULVT, ULTHRES), + PVT_SENSOR_INFO(3, "CPU Core Standard-Vt", hwmon_in, SVT, STHRES), +}; + +static const struct pvt_sensor_info pvt_info_ddr[] = { + PVT_SENSOR_INFO(0, "DDR Core Temperature", hwmon_temp, TEMP, TTHRES), + PVT_SENSOR_INFO(0, "DDR Core Voltage", hwmon_in, VOLT, VTHRES), + PVT_SENSOR_INFO(1, "DDR Core Low-Vt", hwmon_in, LVT, LTHRES), + PVT_SENSOR_INFO(2, "DDR Core UltraLow-Vt", hwmon_in, ULVT, ULTHRES), + PVT_SENSOR_INFO(3, "DDR Core Standard-Vt", hwmon_in, SVT, STHRES), +}; + +/* + * The original translation formulae of the temperature (in degrees of Celsius) + * to PVT data and vice-versa are following: + * N = 6.0818e-8*(T^4) +1.2873e-5*(T^3) + 7.2244e-3*(T^2) + 3.6484*(T^1) + + * 1.6198e2, + * T = -1.8439e-11*(N^4) + 8.0705e-8*(N^3) + -1.8501e-4*(N^2) + + * 3.2843e-1*(N^1) - 4.8690e1, + * where T = [-40, 125]C and N = [27, 771]. + * 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 they look like after + * the alterations: + * N = (60818e-20*(T^4) + 12873e-14*(T^3) + 72244e-9*(T^2) + 36484e-3*T + + * 16198e2) / 1e4, + * T = -18439e-12*(N^4) + 80705e-9*(N^3) - 185010e-6*(N^2) + 328430e-3*N - + * 48690, + * where T = [-40000, 125000] mC and N = [27, 771]. + */ +static const struct pvt_poly __maybe_unused poly_temp_to_N = { + .total_divider = 10000, + .terms = { + {4, 60818, 10000, 10000}, + {3, 12873, 10000, 100}, + {2, 72244, 10000, 10}, + {1, 36484, 1000, 1}, + {0, 1619800, 1, 1} + } +}; + +static const struct pvt_poly poly_N_to_temp = { + .total_divider = 1, + .terms = { + {4, -18439, 1000, 1}, + {3, 80705, 1000, 1}, + {2, -185010, 1000, 1}, + {1, 328430, 1000, 1}, + {0, -48690, 1, 1} + } +}; + +/* + * Similar alterations are performed for the voltage conversion equations. + * The original formulae are: + * N = 1.3905e3*V - 5.7685e2, + * V = (N + 5.7685e2) / 1.3905e3, + * where V = [0.72, 0.88] V and N = [424, 646]. + * After the optimization they looks as follows: + * N = (13905e-3*V - 5768.5) / 10, + * V = (N * 10^5 / 13905 + 57685 * 10^3 / 13905) / 10. + * where V = [720, 880] mV and N = [424, 646]. + */ +static const struct pvt_poly __maybe_unused poly_volt_to_N = { + .total_divider = 10, + .terms = { + {1, 13905, 1000, 1}, + {0, -57685, 1, 10} + } +}; + +static const struct pvt_poly poly_N_to_volt = { + .total_divider = 10, + .terms = { + {1, 100000, 13905, 1}, + {0, 57685000, 1, 13905} + } +}; + +/* + * Here is the polynomial calculation function, which performs the + * redistributed terms calculations. It's pretty straightforward. We walk + * over each degree term up to the free one, and perform the redistributed + * multiplication of the term coefficient, its divider (as for the rationale + * fraction representation), data power and the rational fraction divider + * leftover. Then all of this is collected in a total sum variable, which + * value is normalized by the total divider before being returned. + */ +static long eswin_pvt_calc_poly(const struct pvt_poly *poly, long data) +{ + const struct pvt_poly_term *term = poly->terms; + long tmp, ret = 0; + int deg; + do { + tmp = term->coef; + for (deg = 0; deg < term->deg; ++deg) + tmp = mult_frac(tmp, data, term->divider); + ret += tmp / term->divider_leftover; + } while ((term++)->deg); + + return ret / poly->total_divider; +} + +static inline u32 eswin_pvt_update(void __iomem *reg, u32 mask, u32 data) +{ + u32 old; + + old = readl_relaxed(reg); + writel((old & ~mask) | (data & mask), reg); + + return old & mask; +} + +static inline void eswin_pvt_set_mode(struct pvt_hwmon *pvt, u32 mode) +{ + u32 old; + + mode = FIELD_PREP(PVT_MODE_MASK, mode); + + old = eswin_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); + eswin_pvt_update(pvt->regs + PVT_MODE, PVT_MODE_MASK, mode); + eswin_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, old); +} + +static inline u32 eswin_pvt_calc_trim(long temp) +{ + temp = clamp_val(temp, 0, PVT_TRIM_TEMP); + + return DIV_ROUND_UP(temp, PVT_TRIM_STEP); +} + +static inline void eswin_pvt_set_trim(struct pvt_hwmon *pvt, u32 val) +{ + u32 old; + + old = eswin_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); + writel(val, pvt->regs + PVT_TRIM); + eswin_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, old); +} + +static irqreturn_t eswin_pvt_hard_isr(int irq, void *data) +{ + struct pvt_hwmon *pvt = data; + struct pvt_cache *cache; + u32 val; + + eswin_pvt_update(pvt->regs + PVT_INT, PVT_INT_CLR, PVT_INT_CLR); + + /* + * Nothing special for alarm-less driver. Just read the data, update + * the cache and notify a waiter of this event. + */ + + val = readl(pvt->regs + PVT_DATA); + + cache = &pvt->cache[pvt->sensor]; + + WRITE_ONCE(cache->data, FIELD_GET(PVT_DATA_OUT, val)); + + complete(&cache->conversion); + + return IRQ_HANDLED; +} + +#define pvt_soft_isr NULL + +static inline umode_t eswin_pvt_limit_is_visible(enum pvt_sensor_type type) +{ + return 0; +} + +static inline umode_t eswin_pvt_pvt_alarm_is_visible(enum pvt_sensor_type type) +{ + return 0; +} + +static int eswin_pvt_read_data(struct pvt_hwmon *pvt, enum pvt_sensor_type type, + long *val) +{ + struct pvt_cache *cache = &pvt->cache[type]; + unsigned long timeout; + u32 data; + int ret; + const struct pvt_sensor_info *pvt_info; + + pvt_info = of_device_get_match_data(pvt->dev); + if (!pvt_info) { + dev_err(pvt->dev, "No matching device data found\n"); + return -EINVAL; + } + + /* + * Lock PVT conversion interface until data cache is updated. The + * data read procedure is following: set the requested PVT sensor + * mode, enable IRQ and conversion, wait until conversion is finished, + * then disable conversion and IRQ, and read the cached data. + */ + ret = mutex_lock_interruptible(&pvt->iface_mtx); + if (ret) + return ret; + + pvt->sensor = type; + eswin_pvt_set_mode(pvt, pvt_info[type].mode); + + eswin_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, PVT_ENA_EN); + + /* + * Wait with timeout since in case if the sensor is suddenly powered + * down the request won't be completed and the caller will hang up on + * this procedure until the power is back up again. Multiply the + * timeout by the factor of two to prevent a false timeout. + */ + timeout = 2 * usecs_to_jiffies(ktime_to_us(pvt->timeout)); + if(type==PVT_TEMP){ + timeout = 20 * usecs_to_jiffies(ktime_to_us(pvt->timeout)); + } + ret = wait_for_completion_timeout(&cache->conversion, timeout); + + eswin_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); + eswin_pvt_update(pvt->regs + PVT_INT, PVT_INT_CLR, PVT_INT_CLR); + + data = READ_ONCE(cache->data); + + mutex_unlock(&pvt->iface_mtx); + + if (!ret) + return -ETIMEDOUT; + + if (type == PVT_TEMP) + *val = eswin_pvt_calc_poly(&poly_N_to_temp, data); + + else if (type == PVT_VOLT) + *val = eswin_pvt_calc_poly(&poly_N_to_volt, data); + else + *val = data; + + return 0; +} + +static int eswin_pvt_read_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type, + bool is_low, long *val) +{ + return -EOPNOTSUPP; +} + +static int eswin_pvt_write_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type, + bool is_low, long val) +{ + return -EOPNOTSUPP; +} + +static int eswin_pvt_read_alarm(struct pvt_hwmon *pvt, enum pvt_sensor_type type, + bool is_low, long *val) +{ + return -EOPNOTSUPP; +} + +static const struct hwmon_channel_info *pvt_channel_info[] = { + HWMON_CHANNEL_INFO(chip, + HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL), + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_TYPE | HWMON_T_LABEL | + HWMON_T_OFFSET), + HWMON_CHANNEL_INFO(in, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL), + NULL +}; + +static inline bool eswin_pvt_hwmon_channel_is_valid(enum hwmon_sensor_types type, + int ch) +{ + switch (type) { + case hwmon_temp: + if (ch < 0 || ch >= PVT_TEMP_CHS) + return false; + break; + case hwmon_in: + if (ch < 0 || ch >= PVT_VOLT_CHS) + return false; + break; + default: + break; + } + + /* The rest of the types are independent from the channel number. */ + return true; +} + +static umode_t eswin_pvt_hwmon_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int ch) +{ + if (!eswin_pvt_hwmon_channel_is_valid(type, ch)) + return 0; + + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + return 0644; + } + break; + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_type: + case hwmon_temp_label: + return 0444; + case hwmon_temp_min: + case hwmon_temp_max: + return eswin_pvt_limit_is_visible(ch); + case hwmon_temp_min_alarm: + case hwmon_temp_max_alarm: + return eswin_pvt_pvt_alarm_is_visible(ch); + case hwmon_temp_offset: + return 0644; + } + break; + case hwmon_in: + switch (attr) { + case hwmon_in_input: + case hwmon_in_label: + return 0444; + case hwmon_in_min: + case hwmon_in_max: + return eswin_pvt_limit_is_visible(PVT_VOLT + ch); + case hwmon_in_min_alarm: + case hwmon_in_max_alarm: + return eswin_pvt_pvt_alarm_is_visible(PVT_VOLT + ch); + } + break; + default: + break; + } + + return 0; +} + +static int eswin_pvt_read_trim(struct pvt_hwmon *pvt, long *val) +{ + u32 data; + + data = readl(pvt->regs + PVT_TRIM); + /* *val = FIELD_GET(PVT_CTRL_TRIM_MASK, data) * PVT_TRIM_STEP; */ + *val = data; + + return 0; +} + +static int eswin_pvt_write_trim(struct pvt_hwmon *pvt, long val) +{ + int ret; + /* + * Serialize trim update, since a part of the register is changed and + * the controller is supposed to be disabled during this operation. + */ + ret = mutex_lock_interruptible(&pvt->iface_mtx); + if (ret) + return ret; + + /* trim = eswin_pvt_calc_trim(val); */ + eswin_pvt_set_trim(pvt, val); + + mutex_unlock(&pvt->iface_mtx); + + return 0; +} + +static int eswin_pvt_read_timeout(struct pvt_hwmon *pvt, long *val) +{ + int ret; + + ret = mutex_lock_interruptible(&pvt->iface_mtx); + if (ret) + return ret; + + /* Return the result in msec as hwmon sysfs interface requires. */ + *val = ktime_to_ms(pvt->timeout); + + mutex_unlock(&pvt->iface_mtx); + + return 0; +} + +static int eswin_pvt_write_timeout(struct pvt_hwmon *pvt, long val) +{ + unsigned long rate; + ktime_t kt, cache; + u32 data; + int ret; + + rate = clk_get_rate(pvt->clk); + if (!rate) + return -ENODEV; + + /* + * If alarms are enabled, the requested timeout must be divided + * between all available sensors to have the requested delay + * applicable to each individual sensor. + */ + cache = kt = ms_to_ktime(val); + + /* + * Subtract a constant lag, which always persists due to the limited + * PVT sampling rate. Make sure the timeout is not negative. + */ + kt = ktime_sub_ns(kt, PVT_TOUT_MIN); + if (ktime_to_ns(kt) < 0) + kt = ktime_set(0, 0); + + /* + * Finally recalculate the timeout in terms of the reference clock + * period. + */ + data = ktime_divns(kt * rate, NSEC_PER_SEC); + + /* + * Update the measurements delay, but lock the interface first, since + * we have to disable PVT in order to have the new delay actually + * updated. + */ + ret = mutex_lock_interruptible(&pvt->iface_mtx); + if (ret) + return ret; + + pvt->timeout = cache; + + mutex_unlock(&pvt->iface_mtx); + + return 0; +} + +static int eswin_pvt_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int ch, long *val) +{ + struct pvt_hwmon *pvt = dev_get_drvdata(dev); + + if (!eswin_pvt_hwmon_channel_is_valid(type, ch)) + return -EINVAL; + + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + return eswin_pvt_read_timeout(pvt, val); + } + break; + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + return eswin_pvt_read_data(pvt, ch, val); + case hwmon_temp_type: + *val = 1; + return 0; + case hwmon_temp_min: + return eswin_pvt_read_limit(pvt, ch, true, val); + case hwmon_temp_max: + return eswin_pvt_read_limit(pvt, ch, false, val); + case hwmon_temp_min_alarm: + return eswin_pvt_read_alarm(pvt, ch, true, val); + case hwmon_temp_max_alarm: + return eswin_pvt_read_alarm(pvt, ch, false, val); + case hwmon_temp_offset: + return eswin_pvt_read_trim(pvt, val); + } + break; + case hwmon_in: + switch (attr) { + case hwmon_in_input: + return eswin_pvt_read_data(pvt, PVT_VOLT + ch, val); + case hwmon_in_min: + return eswin_pvt_read_limit(pvt, PVT_VOLT + ch, true, val); + case hwmon_in_max: + return eswin_pvt_read_limit(pvt, PVT_VOLT + ch, false, val); + case hwmon_in_min_alarm: + return eswin_pvt_read_alarm(pvt, PVT_VOLT + ch, true, val); + case hwmon_in_max_alarm: + return eswin_pvt_read_alarm(pvt, PVT_VOLT + ch, false, val); + } + break; + default: + break; + } + + return -EOPNOTSUPP; +} + +static int eswin_pvt_hwmon_read_string(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int ch, const char **str) +{ + struct pvt_hwmon *pvt = dev_get_drvdata(dev); + + const struct pvt_sensor_info *pvt_info; + + if (!eswin_pvt_hwmon_channel_is_valid(type, ch)) + return -EINVAL; + + pvt_info = of_device_get_match_data(pvt->dev); + if (!pvt_info) { + dev_err(pvt->dev, "No matching device data found\n"); + return -EINVAL; + } + + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_label: + *str = pvt_info[ch].label; + return 0; + } + break; + case hwmon_in: + switch (attr) { + case hwmon_in_label: + *str = pvt_info[PVT_VOLT + ch].label; + return 0; + } + break; + default: + break; + } + + return -EOPNOTSUPP; +} + +static int eswin_pvt_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int ch, long val) +{ + struct pvt_hwmon *pvt = dev_get_drvdata(dev); + + if (!eswin_pvt_hwmon_channel_is_valid(type, ch)) + return -EINVAL; + + switch (type) { + case hwmon_chip: + switch (attr) { + case hwmon_chip_update_interval: + return eswin_pvt_write_timeout(pvt, val); + } + break; + case hwmon_temp: + switch (attr) { + case hwmon_temp_min: + return eswin_pvt_write_limit(pvt, ch, true, val); + case hwmon_temp_max: + return eswin_pvt_write_limit(pvt, ch, false, val); + case hwmon_temp_offset: + return eswin_pvt_write_trim(pvt, val); + } + break; + case hwmon_in: + switch (attr) { + case hwmon_in_min: + return eswin_pvt_write_limit(pvt, PVT_VOLT + ch, true, val); + case hwmon_in_max: + return eswin_pvt_write_limit(pvt, PVT_VOLT + ch, false, val); + } + break; + default: + break; + } + + return -EOPNOTSUPP; +} + +static const struct hwmon_ops pvt_hwmon_ops = { + .is_visible = eswin_pvt_hwmon_is_visible, + .read = eswin_pvt_hwmon_read, + .read_string = eswin_pvt_hwmon_read_string, + .write = eswin_pvt_hwmon_write +}; + +static const struct hwmon_chip_info pvt_hwmon_info = { + .ops = &pvt_hwmon_ops, + .info = pvt_channel_info +}; + +static void pvt_clear_data(void *data) +{ + struct pvt_hwmon *pvt = data; + int idx; + + for (idx = 0; idx < PVT_SENSORS_NUM; ++idx) + complete_all(&pvt->cache[idx].conversion); + + mutex_destroy(&pvt->iface_mtx); +} + +static struct pvt_hwmon *eswin_pvt_create_data(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pvt_hwmon *pvt; + int ret, idx; + + pvt = devm_kzalloc(dev, sizeof(*pvt), GFP_KERNEL); + if (!pvt) + return ERR_PTR(-ENOMEM); + + ret = devm_add_action(dev, pvt_clear_data, pvt); + if (ret) { + dev_err(dev, "Can't add PVT data clear action\n"); + return ERR_PTR(ret); + } + + pvt->dev = dev; + pvt->sensor = PVT_SENSOR_FIRST; + mutex_init(&pvt->iface_mtx); + + for (idx = 0; idx < PVT_SENSORS_NUM; ++idx) + init_completion(&pvt->cache[idx].conversion); + + return pvt; +} + +static int eswin_pvt_request_regs(struct pvt_hwmon *pvt) +{ + struct platform_device *pdev = to_platform_device(pvt->dev); + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(pvt->dev, "Couldn't find PVT memresource\n"); + return -EINVAL; + } + + pvt->regs = devm_ioremap_resource(pvt->dev, res); + if (IS_ERR(pvt->regs)) + return PTR_ERR(pvt->regs); + + return 0; +} + +static void eswin_pvt_remove(void *data) +{ + int ret; + struct pvt_hwmon *pvt = data; + ret = reset_control_assert(pvt->pvt_rst); + WARN_ON(0 != ret); + clk_disable_unprepare(pvt->clk); +} + +static int eswin_pvt_request_clks(struct pvt_hwmon *pvt) +{ + int ret; + + pvt->clk = devm_clk_get(pvt->dev, "pvt_clk"); + if (IS_ERR(pvt->clk)) { + dev_err(pvt->dev, "Couldn't get PVT clock\n"); + return -ENODEV; + } + + ret = clk_prepare_enable(pvt->clk); + if (ret) { + dev_err(pvt->dev, "Couldn't enable the PVT clocks\n"); + return ret; + } + + return 0; +} + +static int eswin_pvt_request_rst(struct pvt_hwmon *pvt) +{ + int ret; + pvt->pvt_rst = devm_reset_control_get_optional(pvt->dev, "pvt_rst"); + if(IS_ERR_OR_NULL(pvt->pvt_rst)){ + dev_err(pvt->dev, "Couldn't get PVT reset\n"); + } + ret = reset_control_reset(pvt->pvt_rst); + WARN_ON(0 != ret); + return 0; +} + +static int eswin_pvt_check_pwr(struct pvt_hwmon *pvt) +{ + unsigned long tout; + int ret = 0; + u32 data; + + /* + * Test out the sensor conversion functionality. If it is not done on + * time then the domain must have been unpowered and we won't be able + * to use the device later in this driver. + * Note If the power source is lost during the normal driver work the + * data read procedure will either return -ETIMEDOUT (for the + * alarm-less driver configuration) or just stop the repeated + * conversion. In the later case alas we won't be able to detect the + * problem. + */ + + eswin_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, PVT_ENA_EN); + readl(pvt->regs + PVT_DATA); + + tout = PVT_TOUT_MIN / NSEC_PER_USEC; + usleep_range(tout, 2 * tout); + + data = readl(pvt->regs + PVT_DATA); + + eswin_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); + eswin_pvt_update(pvt->regs + PVT_INT, PVT_INT_CLR, PVT_INT_CLR); + + return ret; +} + +static int eswin_pvt_init_iface(struct pvt_hwmon *pvt) +{ + unsigned long rate; + const struct pvt_sensor_info *pvt_info; + + rate = clk_get_rate(pvt->clk); + if (!rate) { + dev_err(pvt->dev, "Invalid reference clock rate\n"); + return -ENODEV; + } + pvt_info = of_device_get_match_data(pvt->dev); + if (!pvt_info) { + dev_err(pvt->dev, "No matching device data found\n"); + return -EINVAL; + } + /* + * Make sure all interrupts and controller are disabled so not to + * accidentally have ISR executed before the driver data is fully + * initialized. Clear the IRQ status as well. + */ + eswin_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); + eswin_pvt_update(pvt->regs + PVT_INT, PVT_INT_CLR, PVT_INT_CLR); + + readl(pvt->regs + PVT_DATA); + + /* Setup default sensor mode, timeout and temperature trim. */ + eswin_pvt_set_mode(pvt, pvt_info[pvt->sensor].mode); + + /* + * Preserve the current ref-clock based delay (Ttotal) between the + * sensors data samples in the driver data so not to recalculate it + * each time on the data requests and timeout reads. It consists of the + * delay introduced by the internal ref-clock timer (N / Fclk) and the + * constant timeout caused by each conversion latency (Tmin): + * Ttotal = N / Fclk + Tmin + * If alarms are enabled the sensors are polled one after another and + * in order to get the next measurement of a particular sensor the + * caller will have to wait for at most until all the others are + * polled. In that case the formulae will look a bit different: + * Ttotal = 5 * (N / Fclk + Tmin) + */ + + pvt->timeout = ktime_set(PVT_TOUT_DEF, 0); + pvt->timeout = ktime_divns(pvt->timeout, rate); + pvt->timeout = ktime_add_ns(pvt->timeout, PVT_TOUT_MIN); + + /* + if (!of_property_read_u32(pvt->dev->of_node, + "pvt-temp-offset-millicelsius", &temp)) + trim = eswin_pvt_calc_trim(temp); + eswin_pvt_set_trim(pvt, trim); + */ + + return 0; +} + +static int eswin_pvt_request_irq(struct pvt_hwmon *pvt) +{ + struct platform_device *pdev = to_platform_device(pvt->dev); + int ret; + + pvt->irq = platform_get_irq(pdev, 0); + if (pvt->irq < 0) + return pvt->irq; + + ret = devm_request_threaded_irq(pvt->dev, pvt->irq, + eswin_pvt_hard_isr, pvt_soft_isr, + IRQF_SHARED | IRQF_TRIGGER_HIGH, + "pvt", pvt); + if (ret) { + dev_err(pvt->dev, "Couldn't request PVT IRQ\n"); + return ret; + } + + return 0; +} + +static int eswin_pvt_create_hwmon(struct pvt_hwmon *pvt) +{ + pvt->hwmon = devm_hwmon_device_register_with_info(pvt->dev, "pvt", pvt, + &pvt_hwmon_info, NULL); + if (IS_ERR(pvt->hwmon)) { + dev_err(pvt->dev, "Couldn't create hwmon device\n"); + return PTR_ERR(pvt->hwmon); + } + + return 0; +} + +static int eswin_pvt_enable_iface(struct pvt_hwmon *pvt) +{ + return 0; +} + +static int eswin_pvt_probe(struct platform_device *pdev) +{ + struct pvt_hwmon *pvt; + int ret; + + pvt = eswin_pvt_create_data(pdev); + if (IS_ERR(pvt)) + return PTR_ERR(pvt); + + ret = eswin_pvt_request_regs(pvt); + if (ret) + return ret; + + ret = eswin_pvt_request_clks(pvt); + if (ret) + return ret; + + ret = eswin_pvt_request_rst(pvt); + if (ret) + return ret; + + ret = eswin_pvt_check_pwr(pvt); + if (ret) + return ret; + + ret = eswin_pvt_init_iface(pvt); + if (ret) + return ret; + + ret = eswin_pvt_request_irq(pvt); + if (ret) + return ret; + + ret = eswin_pvt_create_hwmon(pvt); + if (ret) + return ret; + + ret = eswin_pvt_enable_iface(pvt); + if (ret) + return ret; + + ret = devm_add_action_or_reset(pvt->dev, eswin_pvt_remove, pvt); + if (ret) { + dev_err(pvt->dev, "Can't add PVT clocks disable action\n"); + return ret; + } + + return 0; +} + +static const struct of_device_id pvt_of_match[] = { + { .compatible = "eswin,eswin-pvt-cpu", + .data = &pvt_info_cpu}, + { .compatible = "eswin,eswin-pvt-ddr", + .data = &pvt_info_ddr}, + { } +}; +MODULE_DEVICE_TABLE(of, pvt_of_match); + +static struct platform_driver pvt_driver = { + .probe = eswin_pvt_probe, + .driver = { + .name = "eswin-pvt", + .of_match_table = pvt_of_match + }, +}; +module_platform_driver(pvt_driver); + +MODULE_DESCRIPTION("Eswin PVT driver"); +MODULE_AUTHOR("Yulin Lu "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hwmon/eswin_pvt.h b/drivers/hwmon/eswin_pvt.h new file mode 100644 index 000000000000..aaf7c55171e9 --- /dev/null +++ b/drivers/hwmon/eswin_pvt.h @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ESWIN PVT device driver + * + * Copyright 2024, Beijing ESWIN Computing Technology Co., Ltd.. All rights reserved. + * SPDX-License-Identifier: GPL-2.0 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Authors: Yulin Lu + */ +#ifndef __HWMON_ESWIN_PVT_H__ +#define __HWMON_ESWIN_PVT_H__ + +#include +#include +#include +#include +#include +#include + +/* Eswin PVT registers and their bitfields */ +#define PVT_DIV 0x00 +#define PVT_TRIM 0x04 +#define PVT_TRIM_G GENMASK(4,0) +#define PVT_TRIM_O GENMASK(13,8) +#define PVT_MODE 0x08 +#define PVT_MODE_MASK GENMASK(2, 0) +#define PVT_CTRL_MODE_TEMP 0x0 +#define PVT_CTRL_MODE_VOLT 0x4 +#define PVT_CTRL_MODE_LVT 0x1 +#define PVT_CTRL_MODE_ULVT 0x2 +#define PVT_CTRL_MODE_SVT 0x3 +#define PVT_MODE_PSAMPLE_0 BIT(0) +#define PVT_MODE_PSAMPLE_1 BIT(1) +#define PVT_MODE_VSAMPLE BIT(2) +#define PVT_ENA 0x0c +#define PVT_ENA_EN BIT(0) +#define PVT_INT 0x10 +#define PVT_INT_CLR BIT(1) +#define PVT_DATA 0x14 +#define PVT_DATA_OUT GENMASK(9,0) + +/* alarm related */ +#define PVT_TTHRES 0x08 +#define PVT_VTHRES 0x0C +#define PVT_LTHRES 0x10 +#define PVT_ULTHRES 0x14 +#define PVT_STHRES 0x18 +#define PVT_INTR_DVALID BIT(0) +#define PVT_INTR_TTHRES_LO BIT(1) +#define PVT_INTR_TTHRES_HI BIT(2) +#define PVT_INTR_VTHRES_LO BIT(3) +#define PVT_INTR_VTHRES_HI BIT(4) +#define PVT_INTR_LTHRES_LO BIT(5) +#define PVT_INTR_LTHRES_HI BIT(6) +#define PVT_INTR_ULTHRES_LO BIT(7) +#define PVT_INTR_ULTHRES_HI BIT(8) +#define PVT_INTR_STHRES_LO BIT(9) +#define PVT_INTR_STHRES_HI BIT(10) + +/* + * PVT sensors-related limits and default values + * @PVT_TEMP_MIN: Minimal temperature in millidegrees of Celsius. + * @PVT_TEMP_MAX: Maximal temperature in millidegrees of Celsius. + * @PVT_TEMP_CHS: Number of temperature hwmon channels. + * @PVT_VOLT_MIN: Minimal voltage in mV. + * @PVT_VOLT_MAX: Maximal voltage in mV. + * @PVT_VOLT_CHS: Number of voltage hwmon channels. + * @PVT_DATA_MIN: Minimal PVT raw data value. + * @PVT_DATA_MAX: Maximal PVT raw data value. + * @PVT_TRIM_MIN: Minimal temperature sensor trim value. + * @PVT_TRIM_MAX: Maximal temperature sensor trim value. + * @PVT_TRIM_DEF: Default temperature sensor trim value (set a proper value + * when one is determined for ESWIN SoC). + * @PVT_TRIM_TEMP: Maximum temperature encoded by the trim factor. + * @PVT_TRIM_STEP: Temperature stride corresponding to the trim value. + * @PVT_TOUT_MIN: Minimal timeout between samples in nanoseconds. + * @PVT_TOUT_DEF: Default data measurements timeout. In case if alarms are + * activated the PVT IRQ is enabled to be raised after each + * conversion in order to have the thresholds checked and the + * converted value cached. Too frequent conversions may cause + * the system CPU overload. Lets set the 50ms delay between + * them by default to prevent this. + */ +#define PVT_TEMP_MIN -40000L +#define PVT_TEMP_MAX 125000L +#define PVT_TEMP_CHS 1 +#define PVT_VOLT_MIN 720L +#define PVT_VOLT_MAX 880L +#define PVT_VOLT_CHS 4 +#define PVT_DATA_MIN 0 +#define PVT_DATA_DATA_FLD 0 +#define PVT_CTRL_TRIM_FLD 4 +#define PVT_CTRL_TRIM_MASK GENMASK(8,4) +#define PVT_DATA_MAX (PVT_DATA_DATA_MASK >> PVT_DATA_DATA_FLD) +#define PVT_TRIM_MIN 0 +#define PVT_TRIM_MAX (PVT_CTRL_TRIM_MASK >> PVT_CTRL_TRIM_FLD) +#define PVT_TRIM_TEMP 7130 +#define PVT_TRIM_STEP (PVT_TRIM_TEMP / PVT_TRIM_MAX) +#define PVT_TRIM_DEF 0 +#define PVT_TOUT_MIN (NSEC_PER_SEC / 3000) +# define PVT_TOUT_DEF 0 + +/* + * enum pvt_sensor_type - ESWIN PVT sensor types (correspond to each PVT + * sampling mode) + * @PVT_SENSOR*: helpers to traverse the sensors in loops. + * @PVT_TEMP: PVT Temperature sensor. + * @PVT_VOLT: PVT Voltage sensor. + * @PVT_LVT: PVT Low-Voltage threshold sensor. + * @PVT_HVT: PVT High-Voltage threshold sensor. + * @PVT_SVT: PVT Standard-Voltage threshold sensor. + */ +enum pvt_sensor_type { + PVT_SENSOR_FIRST, + PVT_TEMP = PVT_SENSOR_FIRST, + PVT_VOLT, + PVT_LVT, + PVT_ULVT, + PVT_SVT, + PVT_SENSOR_LAST = PVT_SVT, + PVT_SENSORS_NUM +}; + +/* + * struct pvt_sensor_info - ESWIN PVT sensor informational structure + * @channel: Sensor channel ID. + * @label: hwmon sensor label. + * @mode: PVT mode corresponding to the channel. + * @thres_base: upper and lower threshold values of the sensor. + * @thres_sts_lo: low threshold status bitfield. + * @thres_sts_hi: high threshold status bitfield. + * @type: Sensor type. + * @attr_min_alarm: Min alarm attribute ID. + * @attr_min_alarm: Max alarm attribute ID. + */ +struct pvt_sensor_info { + int channel; + const char *label; + u32 mode; + unsigned long thres_base; + u32 thres_sts_lo; + u32 thres_sts_hi; + enum hwmon_sensor_types type; + u32 attr_min_alarm; + u32 attr_max_alarm; +}; + +#define PVT_SENSOR_INFO(_ch, _label, _type, _mode, _thres) \ + { \ + .channel = _ch, \ + .label = _label, \ + .mode = PVT_CTRL_MODE_ ##_mode, \ + .thres_base = PVT_ ##_thres, \ + .thres_sts_lo = PVT_INTR_ ##_thres## _LO, \ + .thres_sts_hi = PVT_INTR_ ##_thres## _HI, \ + .type = _type, \ + .attr_min_alarm = _type## _min, \ + .attr_max_alarm = _type## _max, \ + } + +/* + * struct pvt_cache - PVT sensors data cache + * @data: data cache in raw format. + * @thres_sts_lo: low threshold status saved on the previous data conversion. + * @thres_sts_hi: high threshold status saved on the previous data conversion. + * @data_seqlock: cached data seq-lock. + * @conversion: data conversion completion. + */ +struct pvt_cache { + u32 data; + struct completion conversion; +}; + +/* + * struct pvt_hwmon - Eswin PVT private data + * @dev: device structure of the PVT platform device. + * @hwmon: hwmon device structure. + * @regs: pointer to the Eswin PVT registers region. + * @irq: PVT events IRQ number. + * @clk: PVT core clock (1.2MHz). + * @pvt_rst: pointer to the reset descriptor. + * @iface_mtx: Generic interface mutex (used to lock the alarm registers + * when the alarms enabled, or the data conversion interface + * if alarms are disabled). + * @sensor: current PVT sensor the data conversion is being performed for. + * @cache: data cache descriptor. + * @timeout: conversion timeout cache. + */ +struct pvt_hwmon { + struct device *dev; + struct device *hwmon; + + void __iomem *regs; + int irq; + + struct clk *clk; + struct reset_control *pvt_rst; + struct mutex iface_mtx; + enum pvt_sensor_type sensor; + struct pvt_cache cache[PVT_SENSORS_NUM]; + ktime_t timeout; +}; + +/* + * struct pvt_poly_term - a term descriptor of the PVT data translation + * polynomial + * @deg: degree of the term. + * @coef: multiplication factor of the term. + * @divider: distributed divider per each degree. + * @divider_leftover: divider leftover, which couldn't be redistributed. + */ +struct pvt_poly_term { + unsigned int deg; + long coef; + long divider; + long divider_leftover; +}; + +/* + * struct pvt_poly - PVT data translation polynomial descriptor + * @total_divider: total data divider. + * @terms: polynomial terms up to a free one. + */ +struct pvt_poly { + long total_divider; + struct pvt_poly_term terms[]; +}; + +#endif /* __HWMON_ESWIN_PVT_H__ */ + -- 2.47.0