// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2021 Emil Renner Berthing * Copyright (C) 2021 Samin Guo */ #include #include #include #include #include #include #include #include #include #include #include #include /* * TempSensor reset. The RSTN can be de-asserted once the analog core has * powered up. Trst(min 100ns) * 0:reset 1:de-assert */ #define SFCTEMP_RSTN BIT(0) /* * TempSensor analog core power down. The analog core will be powered up * Tpu(min 50us) after PD is de-asserted. RSTN should be held low until the * analog core is powered up. * 0:power up 1:power down */ #define SFCTEMP_PD BIT(1) /* * TempSensor start conversion enable. * 0:disable 1:enable */ #define SFCTEMP_RUN BIT(2) /* * TempSensor conversion value output. * Temp(C)=DOUT*Y/4094 - K */ #define SFCTEMP_DOUT_POS 16 #define SFCTEMP_DOUT_MSK GENMASK(27, 16) /* DOUT to Celcius conversion constants */ #define SFCTEMP_Y1000 237500L #define SFCTEMP_Z 4094L #define SFCTEMP_K1000 81100L struct sfctemp { /* serialize access to hardware register and enabled below */ struct mutex lock; struct completion conversion_done; struct device *dev; void __iomem *regs; struct clk *clk_sense; struct clk *clk_bus; struct reset_control *rst_sense; struct reset_control *rst_bus; bool enabled; }; static irqreturn_t sfctemp_isr(int irq, void *data) { struct sfctemp *sfctemp = data; complete(&sfctemp->conversion_done); return IRQ_HANDLED; } static void sfctemp_power_up(struct sfctemp *sfctemp) { /* make sure we're powered down first */ writel(SFCTEMP_PD, sfctemp->regs); udelay(1); writel(0, sfctemp->regs); /* wait t_pu(50us) + t_rst(100ns) */ usleep_range(60, 200); /* de-assert reset */ writel(SFCTEMP_RSTN, sfctemp->regs); udelay(1); /* wait t_su(500ps) */ } static void sfctemp_power_down(struct sfctemp *sfctemp) { writel(SFCTEMP_PD, sfctemp->regs); } static void sfctemp_run_single(struct sfctemp *sfctemp) { writel(SFCTEMP_RSTN | SFCTEMP_RUN, sfctemp->regs); udelay(1); writel(SFCTEMP_RSTN, sfctemp->regs); } static int sfctemp_clk_enable(struct sfctemp *sfctemp) { int ret = 0; ret = clk_prepare_enable(sfctemp->clk_bus); if (ret) goto err; ret = reset_control_deassert(sfctemp->rst_bus); if (ret) goto err_disable_bus; ret = clk_prepare_enable(sfctemp->clk_sense); if (ret) goto err_assert_bus; ret = reset_control_deassert(sfctemp->rst_sense); if (ret) goto err_disable_sense; return ret; err_disable_sense: clk_disable_unprepare(sfctemp->clk_sense); err_assert_bus: reset_control_assert(sfctemp->rst_bus); err_disable_bus: clk_disable_unprepare(sfctemp->clk_bus); err: mutex_unlock(&sfctemp->lock); return ret; } static int sfctemp_clk_disable(struct sfctemp *sfctemp) { reset_control_assert(sfctemp->rst_sense); clk_disable_unprepare(sfctemp->clk_sense); reset_control_assert(sfctemp->rst_bus); clk_disable_unprepare(sfctemp->clk_bus); return 0; } static int sfctemp_enable(struct sfctemp *sfctemp) { int ret = 0; mutex_lock(&sfctemp->lock); if (sfctemp->enabled || IS_ENABLED(CONFIG_PM)) goto done; ret = sfctemp_clk_enable(sfctemp); sfctemp_power_up(sfctemp); done: sfctemp->enabled = true; mutex_unlock(&sfctemp->lock); return ret; } static int sfctemp_disable(struct sfctemp *sfctemp) { mutex_lock(&sfctemp->lock); if (!sfctemp->enabled || IS_ENABLED(CONFIG_PM)) goto done; sfctemp_power_down(sfctemp); sfctemp_clk_disable(sfctemp); done: sfctemp->enabled = false; mutex_unlock(&sfctemp->lock); return 0; } static void sfctemp_disable_action(void *data) { sfctemp_disable(data); } static int sfctemp_convert(struct sfctemp *sfctemp, long *val) { int ret; mutex_lock(&sfctemp->lock); if (!sfctemp->enabled) { ret = -ENODATA; goto out; } pm_runtime_get_sync(sfctemp->dev); sfctemp_run_single(sfctemp); ret = wait_for_completion_interruptible_timeout(&sfctemp->conversion_done, msecs_to_jiffies(10)); if (ret <= 0) { if (ret == 0) ret = -ETIMEDOUT; goto out; } /* calculate temperature in milli Celcius */ *val = (long)((readl(sfctemp->regs) & SFCTEMP_DOUT_MSK) >> SFCTEMP_DOUT_POS) * SFCTEMP_Y1000 / SFCTEMP_Z - SFCTEMP_K1000; ret = 0; pm_runtime_put(sfctemp->dev); out: mutex_unlock(&sfctemp->lock); return ret; } static umode_t sfctemp_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, int channel) { switch (type) { case hwmon_temp: switch (attr) { case hwmon_temp_enable: return 0644; case hwmon_temp_input: return 0444; } return 0; default: return 0; } } static int sfctemp_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { struct sfctemp *sfctemp = dev_get_drvdata(dev); switch (type) { case hwmon_temp: switch (attr) { case hwmon_temp_enable: *val = sfctemp->enabled; return 0; case hwmon_temp_input: return sfctemp_convert(sfctemp, val); } return -EINVAL; default: return -EINVAL; } } static int sfctemp_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long val) { struct sfctemp *sfctemp = dev_get_drvdata(dev); switch (type) { case hwmon_temp: switch (attr) { case hwmon_temp_enable: if (val == 0) return sfctemp_disable(sfctemp); if (val == 1) return sfctemp_enable(sfctemp); break; } return -EINVAL; default: return -EINVAL; } } static const struct hwmon_channel_info *sfctemp_info[] = { HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), HWMON_CHANNEL_INFO(temp, HWMON_T_ENABLE | HWMON_T_INPUT), NULL }; static const struct hwmon_ops sfctemp_hwmon_ops = { .is_visible = sfctemp_is_visible, .read = sfctemp_read, .write = sfctemp_write, }; static const struct hwmon_chip_info sfctemp_chip_info = { .ops = &sfctemp_hwmon_ops, .info = sfctemp_info, }; static int sfctemp_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device *hwmon_dev; struct sfctemp *sfctemp; int ret; sfctemp = devm_kzalloc(dev, sizeof(*sfctemp), GFP_KERNEL); if (!sfctemp) return -ENOMEM; sfctemp->dev = dev; sfctemp->enabled = false; dev_set_drvdata(dev, sfctemp); mutex_init(&sfctemp->lock); init_completion(&sfctemp->conversion_done); sfctemp->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(sfctemp->regs)) return PTR_ERR(sfctemp->regs); sfctemp->clk_sense = devm_clk_get(dev, "sense"); if (IS_ERR(sfctemp->clk_sense)) return dev_err_probe(dev, PTR_ERR(sfctemp->clk_sense), "error getting sense clock\n"); sfctemp->clk_bus = devm_clk_get(dev, "bus"); if (IS_ERR(sfctemp->clk_bus)) return dev_err_probe(dev, PTR_ERR(sfctemp->clk_bus), "error getting bus clock\n"); sfctemp->rst_sense = devm_reset_control_get_exclusive(dev, "sense"); if (IS_ERR(sfctemp->rst_sense)) return dev_err_probe(dev, PTR_ERR(sfctemp->rst_sense), "error getting sense reset\n"); sfctemp->rst_bus = devm_reset_control_get_exclusive(dev, "bus"); if (IS_ERR(sfctemp->rst_bus)) return dev_err_probe(dev, PTR_ERR(sfctemp->rst_bus), "error getting busreset\n"); ret = platform_get_irq(pdev, 0); if (ret < 0) return ret; ret = devm_request_irq(dev, ret, sfctemp_isr, 0, pdev->name, sfctemp); if (ret) return dev_err_probe(dev, ret, "error requesting irq\n"); ret = devm_add_action(dev, sfctemp_disable_action, sfctemp); if (ret) return ret; ret = sfctemp_enable(sfctemp); if (ret) return dev_err_probe(dev, ret, "error enabling temperature sensor: %d\n", ret); hwmon_dev = devm_hwmon_device_register_with_info(dev, pdev->name, sfctemp, &sfctemp_chip_info, NULL); pm_runtime_enable(dev); return PTR_ERR_OR_ZERO(hwmon_dev); } #ifdef CONFIG_PM static int starfive_temp_suspend(struct device *dev) { struct sfctemp *sfctemp = dev_get_drvdata(dev); dev_dbg(dev, "starfive temp runtime suspend\n"); sfctemp_power_down(sfctemp); return sfctemp_clk_disable(sfctemp); } static int starfive_temp_resume(struct device *dev) { struct sfctemp *sfctemp = dev_get_drvdata(dev); int ret; dev_dbg(dev, "starfive temp runtime resume\n"); ret = sfctemp_clk_enable(sfctemp); sfctemp_power_up(sfctemp); return ret; } #endif /* CONFIG_PM */ static const struct dev_pm_ops sfctemp_dev_pm_ops = { SET_RUNTIME_PM_OPS(starfive_temp_suspend, starfive_temp_resume, NULL) SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) }; static const struct of_device_id sfctemp_of_match[] = { { .compatible = "starfive,jh7100-temp" }, { .compatible = "starfive,jh7110-temp" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, sfctemp_of_match); static struct platform_driver sfctemp_driver = { .probe = sfctemp_probe, .driver = { .name = "sfctemp", .of_match_table = sfctemp_of_match, .pm = &sfctemp_dev_pm_ops, }, }; module_platform_driver(sfctemp_driver); MODULE_AUTHOR("Emil Renner Berthing"); MODULE_DESCRIPTION("StarFive JH7100 temperature sensor driver"); MODULE_LICENSE("GPL");