diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index b2e445b5536e..08c29685126a 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2083,6 +2083,17 @@ config SENSORS_DME1737 This driver can also be built as a module. If so, the module will be called dme1737. +config SENSORS_EIC7700_PVT + tristate "Eswin EIC7700 Voltage, Temperature sensor driver" + depends on ARCH_ESWIN || COMPILE_TEST + select POLYNOMIAL + help + If you say yes here you get support for Eswin EIC7700 PVT sensor + embedded into the SoC. + + This driver can also be built as a module. If so, the module will be + called eic7700-pvt. + config SENSORS_EMC1403 tristate "SMSC EMC1403/23 thermal sensor" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index caea1f55222d..63809eeec2f4 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -72,6 +72,7 @@ obj-$(CONFIG_SENSORS_DME1737) += dme1737.o obj-$(CONFIG_SENSORS_DRIVETEMP) += drivetemp.o obj-$(CONFIG_SENSORS_DS620) += ds620.o obj-$(CONFIG_SENSORS_DS1621) += ds1621.o +obj-$(CONFIG_SENSORS_EIC7700_PVT) += eic7700-pvt.o obj-$(CONFIG_SENSORS_EMC1403) += emc1403.o obj-$(CONFIG_SENSORS_EMC1812) += emc1812.o obj-$(CONFIG_SENSORS_EMC2103) += emc2103.o diff --git a/drivers/hwmon/eic7700-pvt.c b/drivers/hwmon/eic7700-pvt.c new file mode 100644 index 000000000000..d7403ab33f4e --- /dev/null +++ b/drivers/hwmon/eic7700-pvt.c @@ -0,0 +1,509 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ESWIN EIC7700 Voltage, Temperature sensor driver + * + * Copyright 2026, Beijing ESWIN Computing Technology Co., Ltd. + * + * Authors: + * Yulin Lu + * Huan He + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "eic7700-pvt.h" + +static const struct pvt_sensor_info pvt_info[] = { + PVT_SENSOR_INFO(0, "Temperature", hwmon_temp, TEMP), + PVT_SENSOR_INFO(0, "Voltage", hwmon_in, VOLT), +}; + +static const char * const pvt_clk_names[PVT_CLK_NUM] = {"enable", "apb"}; + +/* + * 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 polynomial 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 polynomial poly_N_to_volt = { + .total_divider = 10, + .terms = { + {1, 100000, 13905, 1}, + {0, 57685000, 1, 13905} + } +}; + +static inline u32 eic7700_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 eic7700_pvt_set_mode(struct pvt_hwmon *pvt, u32 mode) +{ + u32 old; + + mode = FIELD_PREP(PVT_MODE_MASK, mode); + + old = eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); + eic7700_pvt_update(pvt->regs + PVT_MODE, PVT_MODE_MASK, mode); + eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, old); +} + +static inline void eic7700_pvt_set_trim(struct pvt_hwmon *pvt, u32 val) +{ + u32 old; + + old = eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); + writel(val, pvt->regs + PVT_TRIM); + eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, old); +} + +static irqreturn_t eic7700_pvt_hard_isr(int irq, void *data) +{ + struct pvt_hwmon *pvt = data; + u32 stat, val; + int active; + + if (IS_ENABLED(CONFIG_PM)) { + active = pm_runtime_get_if_active(pvt->dev); + if (active <= 0) + return IRQ_NONE; + } + + stat = readl(pvt->regs + PVT_INT); + if (!(stat & PVT_INT_STAT)) { + if (IS_ENABLED(CONFIG_PM)) + pm_runtime_put(pvt->dev); + return IRQ_NONE; + } + + eic7700_pvt_update(pvt->regs + PVT_INT, PVT_INT_CLR, PVT_INT_CLR); + /* + * Read the data, update the cache and notify a waiter of this event. + */ + val = readl(pvt->regs + PVT_DATA); + WRITE_ONCE(pvt->data_cache, FIELD_GET(PVT_DATA_OUT, val)); + complete(&pvt->conversion); + + if (IS_ENABLED(CONFIG_PM)) + pm_runtime_put(pvt->dev); + + return IRQ_HANDLED; +} + +static int eic7700_pvt_read_data(struct pvt_hwmon *pvt, + enum pvt_sensor_type type, long *val) +{ + unsigned long timeout; + u32 data; + int ret; + + /* + * Wait for PVT conversion to complete and update the data cache. The + * data read procedure is following: set the requested PVT sensor mode, + * enable conversion, wait until conversion is finished, then disable + * conversion and IRQ, and read the cached data. + */ + reinit_completion(&pvt->conversion); + + eic7700_pvt_set_mode(pvt, pvt_info[type].mode); + eic7700_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)); + ret = wait_for_completion_timeout(&pvt->conversion, timeout); + + eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); + eic7700_pvt_update(pvt->regs + PVT_INT, PVT_INT_CLR, PVT_INT_CLR); + + if (!ret) + synchronize_irq(pvt->irq); + + data = READ_ONCE(pvt->data_cache); + + if (!ret) + return -ETIMEDOUT; + + if (type == PVT_TEMP) + *val = polynomial_calc(&poly_N_to_temp, data); + else + *val = polynomial_calc(&poly_N_to_volt, data); + + return 0; +} + +static const struct hwmon_channel_info *pvt_channel_info[] = { + HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_LABEL), + HWMON_CHANNEL_INFO(in, HWMON_I_INPUT | HWMON_I_LABEL), + NULL +}; + +static umode_t eic7700_pvt_hwmon_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int ch) +{ + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + case hwmon_temp_label: + return 0444; + } + break; + case hwmon_in: + switch (attr) { + case hwmon_in_input: + case hwmon_in_label: + return 0444; + } + break; + default: + break; + } + + return 0; +} + +static int eic7700_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); + int ret; + + ret = pm_runtime_get_sync(pvt->dev); + if (ret < 0) { + dev_err(pvt->dev, "Failed to resume PVT device: %d\n", ret); + pm_runtime_put_noidle(pvt->dev); + return ret; + } + + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + ret = eic7700_pvt_read_data(pvt, ch, val); + break; + default: + ret = -EOPNOTSUPP; + } + break; + case hwmon_in: + if (attr == hwmon_in_input) + ret = eic7700_pvt_read_data(pvt, PVT_VOLT + ch, val); + else + ret = -EOPNOTSUPP; + break; + default: + ret = -EOPNOTSUPP; + } + + pm_runtime_mark_last_busy(pvt->dev); + pm_runtime_put_autosuspend(pvt->dev); + return ret; +} + +static int eic7700_pvt_hwmon_read_string(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int ch, const char **str) +{ + switch (type) { + case hwmon_temp: + if (attr == hwmon_temp_label) { + *str = pvt_info[ch].label; + return 0; + } + break; + case hwmon_in: + if (attr == hwmon_in_label) { + *str = pvt_info[PVT_VOLT + ch].label; + return 0; + } + break; + default: + break; + } + + return -EOPNOTSUPP; +} + +static const struct hwmon_ops pvt_hwmon_ops = { + .is_visible = eic7700_pvt_hwmon_is_visible, + .read = eic7700_pvt_hwmon_read, + .read_string = eic7700_pvt_hwmon_read_string +}; + +static const struct hwmon_chip_info pvt_hwmon_info = { + .ops = &pvt_hwmon_ops, + .info = pvt_channel_info +}; + +static struct pvt_hwmon *eic7700_pvt_create_data(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pvt_hwmon *pvt; + + pvt = devm_kzalloc(dev, sizeof(*pvt), GFP_KERNEL); + if (!pvt) + return ERR_PTR(-ENOMEM); + + pvt->dev = dev; + init_completion(&pvt->conversion); + + return pvt; +} + +static int eic7700_pvt_init_iface(struct pvt_hwmon *pvt) +{ + /* + * Make sure controller are disabled so not to accidentally have ISR + * executed before the driver data is fully initialized. Clear the IRQ + * status as well. + */ + eic7700_pvt_update(pvt->regs + PVT_ENA, PVT_ENA_EN, 0); + eic7700_pvt_update(pvt->regs + PVT_INT, PVT_INT_CLR, PVT_INT_CLR); + readl(pvt->regs + PVT_INT); + readl(pvt->regs + PVT_DATA); + + /* Setup default sensor mode and temperature trim. */ + eic7700_pvt_set_mode(pvt, pvt_info[PVT_TEMP].mode); + + /* + * Max conversion latency (~333 µs) derived from PVT spec: + * maximum sampling rate = 3000 samples/sec. + */ + pvt->timeout = ns_to_ktime(PVT_TOUT_MIN); + + eic7700_pvt_set_trim(pvt, PVT_TRIM_DEF); + + return 0; +} + +static int eic7700_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, + eic7700_pvt_hard_isr, NULL, + IRQF_TRIGGER_HIGH, "pvt", pvt); + if (ret) { + dev_err(pvt->dev, "Couldn't request PVT IRQ\n"); + return ret; + } + + return 0; +} + +static int eic7700_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 void eic7700_pvt_disable_pm_runtime(void *data) +{ + struct pvt_hwmon *pvt = data; + + pm_runtime_dont_use_autosuspend(pvt->dev); + pm_runtime_disable(pvt->dev); + + if (!pm_runtime_status_suspended(pvt->dev)) { + clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks); + pm_runtime_set_suspended(pvt->dev); + } +} + +static int eic7700_pvt_probe(struct platform_device *pdev) +{ + struct reset_control *rst; + struct pvt_hwmon *pvt; + int i, ret; + + pvt = eic7700_pvt_create_data(pdev); + if (IS_ERR(pvt)) + return PTR_ERR(pvt); + + platform_set_drvdata(pdev, pvt); + + pvt->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(pvt->regs)) + return PTR_ERR(pvt->regs); + + for (i = 0; i < PVT_CLK_NUM; i++) + pvt->clks[i].id = pvt_clk_names[i]; + + ret = devm_clk_bulk_get(&pdev->dev, PVT_CLK_NUM, pvt->clks); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Couldn't get clock descriptors\n"); + + rst = devm_reset_control_get_exclusive_deasserted(&pdev->dev, NULL); + if (IS_ERR(rst)) + return dev_err_probe(pvt->dev, PTR_ERR(rst), + "Couldn't get reset control\n"); + + ret = clk_bulk_prepare_enable(PVT_CLK_NUM, pvt->clks); + if (ret) + return dev_err_probe(pvt->dev, ret, + "Failed to enable clocks\n"); + + ret = eic7700_pvt_init_iface(pvt); + if (ret) { + clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks); + return ret; + } + + if (IS_ENABLED(CONFIG_PM)) + clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks); + + pm_runtime_enable(&pdev->dev); + pm_runtime_set_autosuspend_delay(&pdev->dev, 3000); + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_get_noresume(&pdev->dev); + + ret = devm_add_action_or_reset(pvt->dev, eic7700_pvt_disable_pm_runtime, + pvt); + if (ret) { + pm_runtime_put_noidle(&pdev->dev); + return dev_err_probe(&pdev->dev, ret, + "Can't register PM cleanup\n"); + } + + ret = eic7700_pvt_request_irq(pvt); + if (ret) + goto err_put_pm_runtime; + + ret = eic7700_pvt_create_hwmon(pvt); + if (ret) + goto err_put_pm_runtime; + + pm_runtime_put_autosuspend(&pdev->dev); + + return 0; + +err_put_pm_runtime: + pm_runtime_put_noidle(&pdev->dev); + return ret; +} + +static int __maybe_unused eic7700_pvt_runtime_resume(struct device *dev) +{ + struct pvt_hwmon *pvt = dev_get_drvdata(dev); + int ret; + + ret = clk_bulk_prepare_enable(PVT_CLK_NUM, pvt->clks); + if (ret) { + dev_err(dev, "Failed to enable clocks: %d\n", ret); + return ret; + } + + eic7700_pvt_set_trim(pvt, PVT_TRIM_DEF); + + return 0; +} + +static int __maybe_unused eic7700_pvt_runtime_suspend(struct device *dev) +{ + struct pvt_hwmon *pvt = dev_get_drvdata(dev); + + clk_bulk_disable_unprepare(PVT_CLK_NUM, pvt->clks); + + return 0; +} + +static const struct dev_pm_ops eic7700_pvt_pm_ops = { + SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) + RUNTIME_PM_OPS(eic7700_pvt_runtime_suspend, eic7700_pvt_runtime_resume, + NULL) +}; + +static const struct of_device_id pvt_of_match[] = { + { .compatible = "eswin,eic7700-pvt"}, + { } +}; +MODULE_DEVICE_TABLE(of, pvt_of_match); + +static struct platform_driver pvt_driver = { + .probe = eic7700_pvt_probe, + .driver = { + .name = "eic7700-pvt", + .of_match_table = pvt_of_match, + .pm = pm_ptr(&eic7700_pvt_pm_ops), + }, +}; +module_platform_driver(pvt_driver); + +MODULE_AUTHOR("Yulin Lu "); +MODULE_AUTHOR("Huan He "); +MODULE_DESCRIPTION("Eswin eic7700 PVT driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/eic7700-pvt.h b/drivers/hwmon/eic7700-pvt.h new file mode 100644 index 000000000000..fb10f9e4e93a --- /dev/null +++ b/drivers/hwmon/eic7700-pvt.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * ESWIN EIC7700 Voltage, Temperature sensor driver + * + * Copyright 2026, Beijing ESWIN Computing Technology Co., Ltd. + */ +#ifndef __HWMON_EIC7700_PVT_H__ +#define __HWMON_EIC7700_PVT_H__ + +#include +#include +#include +#include + +/* ESWIN EIC7700 PVT registers and their bitfields */ +#define PVT_TRIM 0x04 +#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_ENA 0x0c +#define PVT_ENA_EN BIT(0) +#define PVT_INT 0x10 +#define PVT_INT_STAT BIT(0) +#define PVT_INT_CLR BIT(1) +#define PVT_DATA 0x14 +#define PVT_DATA_OUT GENMASK(9, 0) + +/* + * PVT sensors-related limits and default values + * @PVT_TEMP_CHS: Number of temperature hwmon channels. + * @PVT_VOLT_CHS: Number of voltage hwmon channels. + * @PVT_TRIM_DEF: Default temperature sensor trim value (set a proper value + * when one is determined for ESWIN EIC7700 SoC). + * @PVT_TOUT_MIN: Minimal timeout between samples in nanoseconds. + */ +#define PVT_TEMP_CHS 1 +#define PVT_VOLT_CHS 1 +#define PVT_TRIM_DEF 0 +#define PVT_TOUT_MIN (NSEC_PER_SEC / 3000) + +/* + * enum pvt_sensor_type - ESWIN EIC7700 PVT sensor types (correspond to each PVT + * sampling mode) + * @PVT_TEMP: PVT Temperature sensor. + * @PVT_VOLT: PVT Voltage sensor. + */ +enum pvt_sensor_type { + PVT_TEMP = 0, + PVT_VOLT +}; + +#define PVT_CLK_NUM 2 + +/* + * struct pvt_sensor_info - ESWIN EIC7700 PVT sensor informational structure + * @channel: Sensor channel ID. + * @label: hwmon sensor label. + * @mode: PVT mode corresponding to the channel. + * @type: Sensor type. + */ +struct pvt_sensor_info { + int channel; + const char *label; + u32 mode; + enum hwmon_sensor_types type; +}; + +#define PVT_SENSOR_INFO(_ch, _label, _type, _mode) \ + { \ + .channel = _ch, \ + .label = _label, \ + .mode = PVT_CTRL_MODE_ ##_mode, \ + .type = _type, \ + } + +/* + * struct pvt_hwmon - Eswin EIC7700 PVT private data + * @dev: device structure of the PVT platform device. + * @hwmon: hwmon device structure. + * @regs: pointer to the Eswin EIC7700 PVT registers region. + * @irq: PVT events IRQ number. + * @clks: PVT clock descriptors. + * @data_cache: data cache in raw format. + * @conversion: data conversion completion. + * @timeout: conversion timeout. + */ +struct pvt_hwmon { + struct device *dev; + struct device *hwmon; + void __iomem *regs; + int irq; + struct clk_bulk_data clks[PVT_CLK_NUM]; + u32 data_cache; + struct completion conversion; + ktime_t timeout; +}; + +#endif /* __HWMON_EIC7700_PVT_H__ */