| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright (C) 2021 Emil Renner Berthing <kernel@esmil.dk> | |
| * Copyright (C) 2021 Samin Guo <samin.guo@starfivetech.com> | |
| */ | |
| #include <linux/bits.h> | |
| #include <linux/clk.h> | |
| #include <linux/delay.h> | |
| #include <linux/hwmon.h> | |
| #include <linux/io.h> | |
| #include <linux/module.h> | |
| #include <linux/of.h> | |
| #include <linux/platform_device.h> | |
| #include <linux/reset.h> | |
| /* | |
| * 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 { | |
| void __iomem *regs; | |
| struct clk *clk_sense; | |
| struct clk *clk_bus; | |
| struct reset_control *rst_sense; | |
| struct reset_control *rst_bus; | |
| bool enabled; | |
| }; | |
| 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(struct sfctemp *sfctemp) | |
| { | |
| writel(SFCTEMP_RSTN | SFCTEMP_RUN, sfctemp->regs); | |
| udelay(1); | |
| } | |
| static void sfctemp_stop(struct sfctemp *sfctemp) | |
| { | |
| writel(SFCTEMP_RSTN, sfctemp->regs); | |
| } | |
| static int sfctemp_enable(struct sfctemp *sfctemp) | |
| { | |
| int ret; | |
| if (sfctemp->enabled) | |
| return 0; | |
| ret = clk_prepare_enable(sfctemp->clk_bus); | |
| if (ret) | |
| return ret; | |
| 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; | |
| sfctemp_power_up(sfctemp); | |
| sfctemp_run(sfctemp); | |
| sfctemp->enabled = true; | |
| return 0; | |
| 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); | |
| return ret; | |
| } | |
| static int sfctemp_disable(struct sfctemp *sfctemp) | |
| { | |
| if (!sfctemp->enabled) | |
| return 0; | |
| sfctemp_stop(sfctemp); | |
| sfctemp_power_down(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); | |
| sfctemp->enabled = false; | |
| return 0; | |
| } | |
| static void sfctemp_disable_action(void *data) | |
| { | |
| sfctemp_disable(data); | |
| } | |
| static int sfctemp_convert(struct sfctemp *sfctemp, long *val) | |
| { | |
| if (!sfctemp->enabled) | |
| return -ENODATA; | |
| /* calculate temperature in milli Celcius */ | |
| *val = (long)((readl(sfctemp->regs) & SFCTEMP_DOUT_MSK) >> SFCTEMP_DOUT_POS) | |
| * SFCTEMP_Y1000 / SFCTEMP_Z - SFCTEMP_K1000; | |
| return 0; | |
| } | |
| 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; | |
| default: | |
| 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); | |
| default: | |
| 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); | |
| return -EINVAL; | |
| default: | |
| 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; | |
| dev_set_drvdata(dev, sfctemp); | |
| 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 = reset_control_assert(sfctemp->rst_sense); | |
| if (ret) | |
| return dev_err_probe(dev, ret, "error asserting sense reset\n"); | |
| ret = reset_control_assert(sfctemp->rst_bus); | |
| if (ret) | |
| return dev_err_probe(dev, ret, "error asserting bus reset\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\n"); | |
| hwmon_dev = devm_hwmon_device_register_with_info(dev, "sfctemp", sfctemp, | |
| &sfctemp_chip_info, NULL); | |
| return PTR_ERR_OR_ZERO(hwmon_dev); | |
| } | |
| 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, | |
| }, | |
| }; | |
| module_platform_driver(sfctemp_driver); | |
| MODULE_AUTHOR("Emil Renner Berthing"); | |
| MODULE_DESCRIPTION("StarFive JH71x0 temperature sensor driver"); | |
| MODULE_LICENSE("GPL"); |