diff options
| author | Dmitry Torokhov <[email protected]> | 2023-08-30 16:06:38 -0700 | 
|---|---|---|
| committer | Dmitry Torokhov <[email protected]> | 2023-08-30 16:06:38 -0700 | 
| commit | 1ac731c529cd4d6adbce134754b51ff7d822b145 (patch) | |
| tree | 143ab3f35ca5f3b69f583c84e6964b17139c2ec1 /drivers/hwmon/sfctemp.c | |
| parent | 07b4c950f27bef0362dc6ad7ee713aab61d58149 (diff) | |
| parent | 54116d442e001e1b6bd482122043b1870998a1f3 (diff) | |
Merge branch 'next' into for-linus
Prepare input updates for 6.6 merge window.
Diffstat (limited to 'drivers/hwmon/sfctemp.c')
| -rw-r--r-- | drivers/hwmon/sfctemp.c | 331 | 
1 files changed, 331 insertions, 0 deletions
diff --git a/drivers/hwmon/sfctemp.c b/drivers/hwmon/sfctemp.c new file mode 100644 index 000000000000..fb1da93383d7 --- /dev/null +++ b/drivers/hwmon/sfctemp.c @@ -0,0 +1,331 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 Emil Renner Berthing <[email protected]> + * Copyright (C) 2021 Samin Guo <[email protected]> + */ + +#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/mutex.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 { +	/* serialize access to hardware register and enabled below */ +	struct mutex lock; +	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 = 0; + +	mutex_lock(&sfctemp->lock); +	if (sfctemp->enabled) +		goto done; + +	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; + +	sfctemp_power_up(sfctemp); +	sfctemp_run(sfctemp); +	sfctemp->enabled = true; +done: +	mutex_unlock(&sfctemp->lock); +	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_disable(struct sfctemp *sfctemp) +{ +	mutex_lock(&sfctemp->lock); +	if (!sfctemp->enabled) +		goto done; + +	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; +done: +	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; +	} + +	/* calculate temperature in milli Celcius */ +	*val = (long)((readl(sfctemp->regs) & SFCTEMP_DOUT_MSK) >> SFCTEMP_DOUT_POS) +		* SFCTEMP_Y1000 / SFCTEMP_Z - SFCTEMP_K1000; + +	ret = 0; +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; +		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); +	mutex_init(&sfctemp->lock); + +	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");  |