// 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");