162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Driver for Broadcom BCM2835 SoC temperature sensor 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2016 Martin Sperl 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/clk.h> 962306a36Sopenharmony_ci#include <linux/debugfs.h> 1062306a36Sopenharmony_ci#include <linux/device.h> 1162306a36Sopenharmony_ci#include <linux/err.h> 1262306a36Sopenharmony_ci#include <linux/io.h> 1362306a36Sopenharmony_ci#include <linux/kernel.h> 1462306a36Sopenharmony_ci#include <linux/module.h> 1562306a36Sopenharmony_ci#include <linux/of.h> 1662306a36Sopenharmony_ci#include <linux/of_address.h> 1762306a36Sopenharmony_ci#include <linux/of_device.h> 1862306a36Sopenharmony_ci#include <linux/platform_device.h> 1962306a36Sopenharmony_ci#include <linux/thermal.h> 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci#include "../thermal_hwmon.h" 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#define BCM2835_TS_TSENSCTL 0x00 2462306a36Sopenharmony_ci#define BCM2835_TS_TSENSSTAT 0x04 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci#define BCM2835_TS_TSENSCTL_PRWDW BIT(0) 2762306a36Sopenharmony_ci#define BCM2835_TS_TSENSCTL_RSTB BIT(1) 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci/* 3062306a36Sopenharmony_ci * bandgap reference voltage in 6 mV increments 3162306a36Sopenharmony_ci * 000b = 1178 mV, 001b = 1184 mV, ... 111b = 1220 mV 3262306a36Sopenharmony_ci */ 3362306a36Sopenharmony_ci#define BCM2835_TS_TSENSCTL_CTRL_BITS 3 3462306a36Sopenharmony_ci#define BCM2835_TS_TSENSCTL_CTRL_SHIFT 2 3562306a36Sopenharmony_ci#define BCM2835_TS_TSENSCTL_CTRL_MASK \ 3662306a36Sopenharmony_ci GENMASK(BCM2835_TS_TSENSCTL_CTRL_BITS + \ 3762306a36Sopenharmony_ci BCM2835_TS_TSENSCTL_CTRL_SHIFT - 1, \ 3862306a36Sopenharmony_ci BCM2835_TS_TSENSCTL_CTRL_SHIFT) 3962306a36Sopenharmony_ci#define BCM2835_TS_TSENSCTL_CTRL_DEFAULT 1 4062306a36Sopenharmony_ci#define BCM2835_TS_TSENSCTL_EN_INT BIT(5) 4162306a36Sopenharmony_ci#define BCM2835_TS_TSENSCTL_DIRECT BIT(6) 4262306a36Sopenharmony_ci#define BCM2835_TS_TSENSCTL_CLR_INT BIT(7) 4362306a36Sopenharmony_ci#define BCM2835_TS_TSENSCTL_THOLD_SHIFT 8 4462306a36Sopenharmony_ci#define BCM2835_TS_TSENSCTL_THOLD_BITS 10 4562306a36Sopenharmony_ci#define BCM2835_TS_TSENSCTL_THOLD_MASK \ 4662306a36Sopenharmony_ci GENMASK(BCM2835_TS_TSENSCTL_THOLD_BITS + \ 4762306a36Sopenharmony_ci BCM2835_TS_TSENSCTL_THOLD_SHIFT - 1, \ 4862306a36Sopenharmony_ci BCM2835_TS_TSENSCTL_THOLD_SHIFT) 4962306a36Sopenharmony_ci/* 5062306a36Sopenharmony_ci * time how long the block to be asserted in reset 5162306a36Sopenharmony_ci * which based on a clock counter (TSENS clock assumed) 5262306a36Sopenharmony_ci */ 5362306a36Sopenharmony_ci#define BCM2835_TS_TSENSCTL_RSTDELAY_SHIFT 18 5462306a36Sopenharmony_ci#define BCM2835_TS_TSENSCTL_RSTDELAY_BITS 8 5562306a36Sopenharmony_ci#define BCM2835_TS_TSENSCTL_REGULEN BIT(26) 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci#define BCM2835_TS_TSENSSTAT_DATA_BITS 10 5862306a36Sopenharmony_ci#define BCM2835_TS_TSENSSTAT_DATA_SHIFT 0 5962306a36Sopenharmony_ci#define BCM2835_TS_TSENSSTAT_DATA_MASK \ 6062306a36Sopenharmony_ci GENMASK(BCM2835_TS_TSENSSTAT_DATA_BITS + \ 6162306a36Sopenharmony_ci BCM2835_TS_TSENSSTAT_DATA_SHIFT - 1, \ 6262306a36Sopenharmony_ci BCM2835_TS_TSENSSTAT_DATA_SHIFT) 6362306a36Sopenharmony_ci#define BCM2835_TS_TSENSSTAT_VALID BIT(10) 6462306a36Sopenharmony_ci#define BCM2835_TS_TSENSSTAT_INTERRUPT BIT(11) 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_cistruct bcm2835_thermal_data { 6762306a36Sopenharmony_ci struct thermal_zone_device *tz; 6862306a36Sopenharmony_ci void __iomem *regs; 6962306a36Sopenharmony_ci struct clk *clk; 7062306a36Sopenharmony_ci struct dentry *debugfsdir; 7162306a36Sopenharmony_ci}; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_cistatic int bcm2835_thermal_adc2temp(u32 adc, int offset, int slope) 7462306a36Sopenharmony_ci{ 7562306a36Sopenharmony_ci return offset + slope * adc; 7662306a36Sopenharmony_ci} 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_cistatic int bcm2835_thermal_temp2adc(int temp, int offset, int slope) 7962306a36Sopenharmony_ci{ 8062306a36Sopenharmony_ci temp -= offset; 8162306a36Sopenharmony_ci temp /= slope; 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci if (temp < 0) 8462306a36Sopenharmony_ci temp = 0; 8562306a36Sopenharmony_ci if (temp >= BIT(BCM2835_TS_TSENSSTAT_DATA_BITS)) 8662306a36Sopenharmony_ci temp = BIT(BCM2835_TS_TSENSSTAT_DATA_BITS) - 1; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci return temp; 8962306a36Sopenharmony_ci} 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_cistatic int bcm2835_thermal_get_temp(struct thermal_zone_device *tz, int *temp) 9262306a36Sopenharmony_ci{ 9362306a36Sopenharmony_ci struct bcm2835_thermal_data *data = thermal_zone_device_priv(tz); 9462306a36Sopenharmony_ci u32 val = readl(data->regs + BCM2835_TS_TSENSSTAT); 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci if (!(val & BCM2835_TS_TSENSSTAT_VALID)) 9762306a36Sopenharmony_ci return -EIO; 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci val &= BCM2835_TS_TSENSSTAT_DATA_MASK; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci *temp = bcm2835_thermal_adc2temp( 10262306a36Sopenharmony_ci val, 10362306a36Sopenharmony_ci thermal_zone_get_offset(data->tz), 10462306a36Sopenharmony_ci thermal_zone_get_slope(data->tz)); 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci return 0; 10762306a36Sopenharmony_ci} 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_cistatic const struct debugfs_reg32 bcm2835_thermal_regs[] = { 11062306a36Sopenharmony_ci { 11162306a36Sopenharmony_ci .name = "ctl", 11262306a36Sopenharmony_ci .offset = 0 11362306a36Sopenharmony_ci }, 11462306a36Sopenharmony_ci { 11562306a36Sopenharmony_ci .name = "stat", 11662306a36Sopenharmony_ci .offset = 4 11762306a36Sopenharmony_ci } 11862306a36Sopenharmony_ci}; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_cistatic void bcm2835_thermal_debugfs(struct platform_device *pdev) 12162306a36Sopenharmony_ci{ 12262306a36Sopenharmony_ci struct bcm2835_thermal_data *data = platform_get_drvdata(pdev); 12362306a36Sopenharmony_ci struct debugfs_regset32 *regset; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci data->debugfsdir = debugfs_create_dir("bcm2835_thermal", NULL); 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci regset = devm_kzalloc(&pdev->dev, sizeof(*regset), GFP_KERNEL); 12862306a36Sopenharmony_ci if (!regset) 12962306a36Sopenharmony_ci return; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci regset->regs = bcm2835_thermal_regs; 13262306a36Sopenharmony_ci regset->nregs = ARRAY_SIZE(bcm2835_thermal_regs); 13362306a36Sopenharmony_ci regset->base = data->regs; 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci debugfs_create_regset32("regset", 0444, data->debugfsdir, regset); 13662306a36Sopenharmony_ci} 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_cistatic const struct thermal_zone_device_ops bcm2835_thermal_ops = { 13962306a36Sopenharmony_ci .get_temp = bcm2835_thermal_get_temp, 14062306a36Sopenharmony_ci}; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci/* 14362306a36Sopenharmony_ci * Note: as per Raspberry Foundation FAQ 14462306a36Sopenharmony_ci * (https://www.raspberrypi.org/help/faqs/#performanceOperatingTemperature) 14562306a36Sopenharmony_ci * the recommended temperature range for the SoC -40C to +85C 14662306a36Sopenharmony_ci * so the trip limit is set to 80C. 14762306a36Sopenharmony_ci * this applies to all the BCM283X SoC 14862306a36Sopenharmony_ci */ 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_cistatic const struct of_device_id bcm2835_thermal_of_match_table[] = { 15162306a36Sopenharmony_ci { 15262306a36Sopenharmony_ci .compatible = "brcm,bcm2835-thermal", 15362306a36Sopenharmony_ci }, 15462306a36Sopenharmony_ci { 15562306a36Sopenharmony_ci .compatible = "brcm,bcm2836-thermal", 15662306a36Sopenharmony_ci }, 15762306a36Sopenharmony_ci { 15862306a36Sopenharmony_ci .compatible = "brcm,bcm2837-thermal", 15962306a36Sopenharmony_ci }, 16062306a36Sopenharmony_ci {}, 16162306a36Sopenharmony_ci}; 16262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, bcm2835_thermal_of_match_table); 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_cistatic int bcm2835_thermal_probe(struct platform_device *pdev) 16562306a36Sopenharmony_ci{ 16662306a36Sopenharmony_ci const struct of_device_id *match; 16762306a36Sopenharmony_ci struct thermal_zone_device *tz; 16862306a36Sopenharmony_ci struct bcm2835_thermal_data *data; 16962306a36Sopenharmony_ci int err = 0; 17062306a36Sopenharmony_ci u32 val; 17162306a36Sopenharmony_ci unsigned long rate; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); 17462306a36Sopenharmony_ci if (!data) 17562306a36Sopenharmony_ci return -ENOMEM; 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci match = of_match_device(bcm2835_thermal_of_match_table, 17862306a36Sopenharmony_ci &pdev->dev); 17962306a36Sopenharmony_ci if (!match) 18062306a36Sopenharmony_ci return -EINVAL; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci data->regs = devm_platform_get_and_ioremap_resource(pdev, 0, NULL); 18362306a36Sopenharmony_ci if (IS_ERR(data->regs)) { 18462306a36Sopenharmony_ci err = PTR_ERR(data->regs); 18562306a36Sopenharmony_ci return err; 18662306a36Sopenharmony_ci } 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci data->clk = devm_clk_get(&pdev->dev, NULL); 18962306a36Sopenharmony_ci if (IS_ERR(data->clk)) { 19062306a36Sopenharmony_ci err = PTR_ERR(data->clk); 19162306a36Sopenharmony_ci if (err != -EPROBE_DEFER) 19262306a36Sopenharmony_ci dev_err(&pdev->dev, "Could not get clk: %d\n", err); 19362306a36Sopenharmony_ci return err; 19462306a36Sopenharmony_ci } 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci err = clk_prepare_enable(data->clk); 19762306a36Sopenharmony_ci if (err) 19862306a36Sopenharmony_ci return err; 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci rate = clk_get_rate(data->clk); 20162306a36Sopenharmony_ci if ((rate < 1920000) || (rate > 5000000)) 20262306a36Sopenharmony_ci dev_warn(&pdev->dev, 20362306a36Sopenharmony_ci "Clock %pCn running at %lu Hz is outside of the recommended range: 1.92 to 5MHz\n", 20462306a36Sopenharmony_ci data->clk, rate); 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci /* register of thermal sensor and get info from DT */ 20762306a36Sopenharmony_ci tz = devm_thermal_of_zone_register(&pdev->dev, 0, data, 20862306a36Sopenharmony_ci &bcm2835_thermal_ops); 20962306a36Sopenharmony_ci if (IS_ERR(tz)) { 21062306a36Sopenharmony_ci err = PTR_ERR(tz); 21162306a36Sopenharmony_ci dev_err(&pdev->dev, 21262306a36Sopenharmony_ci "Failed to register the thermal device: %d\n", 21362306a36Sopenharmony_ci err); 21462306a36Sopenharmony_ci goto err_clk; 21562306a36Sopenharmony_ci } 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci /* 21862306a36Sopenharmony_ci * right now the FW does set up the HW-block, so we are not 21962306a36Sopenharmony_ci * touching the configuration registers. 22062306a36Sopenharmony_ci * But if the HW is not enabled, then set it up 22162306a36Sopenharmony_ci * using "sane" values used by the firmware right now. 22262306a36Sopenharmony_ci */ 22362306a36Sopenharmony_ci val = readl(data->regs + BCM2835_TS_TSENSCTL); 22462306a36Sopenharmony_ci if (!(val & BCM2835_TS_TSENSCTL_RSTB)) { 22562306a36Sopenharmony_ci struct thermal_trip trip; 22662306a36Sopenharmony_ci int offset, slope; 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci slope = thermal_zone_get_slope(tz); 22962306a36Sopenharmony_ci offset = thermal_zone_get_offset(tz); 23062306a36Sopenharmony_ci /* 23162306a36Sopenharmony_ci * For now we deal only with critical, otherwise 23262306a36Sopenharmony_ci * would need to iterate 23362306a36Sopenharmony_ci */ 23462306a36Sopenharmony_ci err = thermal_zone_get_trip(tz, 0, &trip); 23562306a36Sopenharmony_ci if (err < 0) { 23662306a36Sopenharmony_ci dev_err(&pdev->dev, 23762306a36Sopenharmony_ci "Not able to read trip_temp: %d\n", 23862306a36Sopenharmony_ci err); 23962306a36Sopenharmony_ci goto err_tz; 24062306a36Sopenharmony_ci } 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci /* set bandgap reference voltage and enable voltage regulator */ 24362306a36Sopenharmony_ci val = (BCM2835_TS_TSENSCTL_CTRL_DEFAULT << 24462306a36Sopenharmony_ci BCM2835_TS_TSENSCTL_CTRL_SHIFT) | 24562306a36Sopenharmony_ci BCM2835_TS_TSENSCTL_REGULEN; 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci /* use the recommended reset duration */ 24862306a36Sopenharmony_ci val |= (0xFE << BCM2835_TS_TSENSCTL_RSTDELAY_SHIFT); 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci /* trip_adc value from info */ 25162306a36Sopenharmony_ci val |= bcm2835_thermal_temp2adc(trip.temperature, 25262306a36Sopenharmony_ci offset, 25362306a36Sopenharmony_ci slope) 25462306a36Sopenharmony_ci << BCM2835_TS_TSENSCTL_THOLD_SHIFT; 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci /* write the value back to the register as 2 steps */ 25762306a36Sopenharmony_ci writel(val, data->regs + BCM2835_TS_TSENSCTL); 25862306a36Sopenharmony_ci val |= BCM2835_TS_TSENSCTL_RSTB; 25962306a36Sopenharmony_ci writel(val, data->regs + BCM2835_TS_TSENSCTL); 26062306a36Sopenharmony_ci } 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci data->tz = tz; 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci platform_set_drvdata(pdev, data); 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci /* 26762306a36Sopenharmony_ci * Thermal_zone doesn't enable hwmon as default, 26862306a36Sopenharmony_ci * enable it here 26962306a36Sopenharmony_ci */ 27062306a36Sopenharmony_ci err = thermal_add_hwmon_sysfs(tz); 27162306a36Sopenharmony_ci if (err) 27262306a36Sopenharmony_ci goto err_tz; 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci bcm2835_thermal_debugfs(pdev); 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci return 0; 27762306a36Sopenharmony_cierr_tz: 27862306a36Sopenharmony_ci devm_thermal_of_zone_unregister(&pdev->dev, tz); 27962306a36Sopenharmony_cierr_clk: 28062306a36Sopenharmony_ci clk_disable_unprepare(data->clk); 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci return err; 28362306a36Sopenharmony_ci} 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_cistatic int bcm2835_thermal_remove(struct platform_device *pdev) 28662306a36Sopenharmony_ci{ 28762306a36Sopenharmony_ci struct bcm2835_thermal_data *data = platform_get_drvdata(pdev); 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci debugfs_remove_recursive(data->debugfsdir); 29062306a36Sopenharmony_ci clk_disable_unprepare(data->clk); 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci return 0; 29362306a36Sopenharmony_ci} 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_cistatic struct platform_driver bcm2835_thermal_driver = { 29662306a36Sopenharmony_ci .probe = bcm2835_thermal_probe, 29762306a36Sopenharmony_ci .remove = bcm2835_thermal_remove, 29862306a36Sopenharmony_ci .driver = { 29962306a36Sopenharmony_ci .name = "bcm2835_thermal", 30062306a36Sopenharmony_ci .of_match_table = bcm2835_thermal_of_match_table, 30162306a36Sopenharmony_ci }, 30262306a36Sopenharmony_ci}; 30362306a36Sopenharmony_cimodule_platform_driver(bcm2835_thermal_driver); 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ciMODULE_AUTHOR("Martin Sperl"); 30662306a36Sopenharmony_ciMODULE_DESCRIPTION("Thermal driver for bcm2835 chip"); 30762306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 308