diff options
| -rw-r--r-- | drivers/thermal/Kconfig | 5 | ||||
| -rw-r--r-- | drivers/thermal/Makefile | 1 | ||||
| -rw-r--r-- | drivers/thermal/st/Kconfig | 4 | ||||
| -rw-r--r-- | drivers/thermal/st/Makefile | 1 | ||||
| -rw-r--r-- | drivers/thermal/st/st_thermal.c | 313 | ||||
| -rw-r--r-- | drivers/thermal/st/st_thermal.h | 104 |
6 files changed, 428 insertions, 0 deletions
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index f9a13867cb70..57240f5f3d59 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig | |||
| @@ -243,4 +243,9 @@ depends on ARCH_EXYNOS | |||
| 243 | source "drivers/thermal/samsung/Kconfig" | 243 | source "drivers/thermal/samsung/Kconfig" |
| 244 | endmenu | 244 | endmenu |
| 245 | 245 | ||
| 246 | menu "STMicroelectronics thermal drivers" | ||
| 247 | depends on ARCH_STI && OF | ||
| 248 | source "drivers/thermal/st/Kconfig" | ||
| 249 | endmenu | ||
| 250 | |||
| 246 | endif | 251 | endif |
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index de0636a57a64..31e232f84b6b 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile | |||
| @@ -32,3 +32,4 @@ obj-$(CONFIG_X86_PKG_TEMP_THERMAL) += x86_pkg_temp_thermal.o | |||
| 32 | obj-$(CONFIG_INTEL_SOC_DTS_THERMAL) += intel_soc_dts_thermal.o | 32 | obj-$(CONFIG_INTEL_SOC_DTS_THERMAL) += intel_soc_dts_thermal.o |
| 33 | obj-$(CONFIG_TI_SOC_THERMAL) += ti-soc-thermal/ | 33 | obj-$(CONFIG_TI_SOC_THERMAL) += ti-soc-thermal/ |
| 34 | obj-$(CONFIG_ACPI_INT3403_THERMAL) += int3403_thermal.o | 34 | obj-$(CONFIG_ACPI_INT3403_THERMAL) += int3403_thermal.o |
| 35 | obj-$(CONFIG_ST_THERMAL) += st/ | ||
diff --git a/drivers/thermal/st/Kconfig b/drivers/thermal/st/Kconfig new file mode 100644 index 000000000000..1ba67601782a --- /dev/null +++ b/drivers/thermal/st/Kconfig | |||
| @@ -0,0 +1,4 @@ | |||
| 1 | config ST_THERMAL | ||
| 2 | tristate "Thermal sensors on STMicroelectronics STi series of SoCs" | ||
| 3 | help | ||
| 4 | Support for thermal sensors on STMicroelectronics STi series of SoCs. | ||
diff --git a/drivers/thermal/st/Makefile b/drivers/thermal/st/Makefile new file mode 100644 index 000000000000..10f4bf6dd03e --- /dev/null +++ b/drivers/thermal/st/Makefile | |||
| @@ -0,0 +1 @@ | |||
| obj-$(CONFIG_ST_THERMAL) := st_thermal.o | |||
diff --git a/drivers/thermal/st/st_thermal.c b/drivers/thermal/st/st_thermal.c new file mode 100644 index 000000000000..90163b384660 --- /dev/null +++ b/drivers/thermal/st/st_thermal.c | |||
| @@ -0,0 +1,313 @@ | |||
| 1 | /* | ||
| 2 | * ST Thermal Sensor Driver core routines | ||
| 3 | * Author: Ajit Pal Singh <ajitpal.singh@st.com> | ||
| 4 | * | ||
| 5 | * Copyright (C) 2003-2014 STMicroelectronics (R&D) Limited | ||
| 6 | * | ||
| 7 | * This program is free software; you can redistribute it and/or modify | ||
| 8 | * it under the terms of the GNU General Public License as published by | ||
| 9 | * the Free Software Foundation; either version 2 of the License, or | ||
| 10 | * (at your option) any later version. | ||
| 11 | * | ||
| 12 | */ | ||
| 13 | |||
| 14 | #include <linux/clk.h> | ||
| 15 | #include <linux/module.h> | ||
| 16 | #include <linux/of.h> | ||
| 17 | #include <linux/of_device.h> | ||
| 18 | |||
| 19 | #include "st_thermal.h" | ||
| 20 | |||
| 21 | /* The Thermal Framework expects millidegrees */ | ||
| 22 | #define mcelsius(temp) ((temp) * 1000) | ||
| 23 | |||
| 24 | /* | ||
| 25 | * Function to allocate regfields which are common | ||
| 26 | * between syscfg and memory mapped based sensors | ||
| 27 | */ | ||
| 28 | int st_thermal_alloc_regfields(struct st_thermal_sensor *sensor) | ||
| 29 | { | ||
| 30 | struct device *dev = sensor->dev; | ||
| 31 | struct regmap *regmap = sensor->regmap; | ||
| 32 | const struct reg_field *reg_fields = sensor->cdata->reg_fields; | ||
| 33 | |||
| 34 | sensor->dcorrect = devm_regmap_field_alloc(dev, regmap, | ||
| 35 | reg_fields[DCORRECT]); | ||
| 36 | |||
| 37 | sensor->overflow = devm_regmap_field_alloc(dev, regmap, | ||
| 38 | reg_fields[OVERFLOW]); | ||
| 39 | |||
| 40 | sensor->temp_data = devm_regmap_field_alloc(dev, regmap, | ||
| 41 | reg_fields[DATA]); | ||
| 42 | |||
| 43 | if (IS_ERR(sensor->dcorrect) || | ||
| 44 | IS_ERR(sensor->overflow) || | ||
| 45 | IS_ERR(sensor->temp_data)) { | ||
| 46 | dev_err(dev, "failed to allocate common regfields\n"); | ||
| 47 | return -EINVAL; | ||
| 48 | } | ||
| 49 | |||
| 50 | return sensor->ops->alloc_regfields(sensor); | ||
| 51 | } | ||
| 52 | |||
| 53 | static int st_thermal_sensor_on(struct st_thermal_sensor *sensor) | ||
| 54 | { | ||
| 55 | int ret; | ||
| 56 | struct device *dev = sensor->dev; | ||
| 57 | |||
| 58 | ret = clk_prepare_enable(sensor->clk); | ||
| 59 | if (ret) { | ||
| 60 | dev_err(dev, "failed to enable clk\n"); | ||
| 61 | return ret; | ||
| 62 | } | ||
| 63 | |||
| 64 | ret = sensor->ops->power_ctrl(sensor, POWER_ON); | ||
| 65 | if (ret) { | ||
| 66 | dev_err(dev, "failed to power on sensor\n"); | ||
| 67 | clk_disable_unprepare(sensor->clk); | ||
| 68 | } | ||
| 69 | |||
| 70 | return ret; | ||
| 71 | } | ||
| 72 | |||
| 73 | static int st_thermal_sensor_off(struct st_thermal_sensor *sensor) | ||
| 74 | { | ||
| 75 | int ret; | ||
| 76 | |||
| 77 | ret = sensor->ops->power_ctrl(sensor, POWER_OFF); | ||
| 78 | if (ret) | ||
| 79 | return ret; | ||
| 80 | |||
| 81 | clk_disable_unprepare(sensor->clk); | ||
| 82 | |||
| 83 | return 0; | ||
| 84 | } | ||
| 85 | |||
| 86 | static int st_thermal_calibration(struct st_thermal_sensor *sensor) | ||
| 87 | { | ||
| 88 | int ret; | ||
| 89 | unsigned int val; | ||
| 90 | struct device *dev = sensor->dev; | ||
| 91 | |||
| 92 | /* Check if sensor calibration data is already written */ | ||
| 93 | ret = regmap_field_read(sensor->dcorrect, &val); | ||
| 94 | if (ret) { | ||
| 95 | dev_err(dev, "failed to read calibration data\n"); | ||
| 96 | return ret; | ||
| 97 | } | ||
| 98 | |||
| 99 | if (!val) { | ||
| 100 | /* | ||
| 101 | * Sensor calibration value not set by bootloader, | ||
| 102 | * default calibration data to be used | ||
| 103 | */ | ||
| 104 | ret = regmap_field_write(sensor->dcorrect, | ||
| 105 | sensor->cdata->calibration_val); | ||
| 106 | if (ret) | ||
| 107 | dev_err(dev, "failed to set calibration data\n"); | ||
| 108 | } | ||
| 109 | |||
| 110 | return ret; | ||
| 111 | } | ||
| 112 | |||
| 113 | /* Callback to get temperature from HW*/ | ||
| 114 | static int st_thermal_get_temp(struct thermal_zone_device *th, | ||
| 115 | unsigned long *temperature) | ||
| 116 | { | ||
| 117 | struct st_thermal_sensor *sensor = th->devdata; | ||
| 118 | struct device *dev = sensor->dev; | ||
| 119 | unsigned int temp; | ||
| 120 | unsigned int overflow; | ||
| 121 | int ret; | ||
| 122 | |||
| 123 | ret = regmap_field_read(sensor->overflow, &overflow); | ||
| 124 | if (ret) | ||
| 125 | return ret; | ||
| 126 | if (overflow) | ||
| 127 | return -EIO; | ||
| 128 | |||
| 129 | ret = regmap_field_read(sensor->temp_data, &temp); | ||
| 130 | if (ret) | ||
| 131 | return ret; | ||
| 132 | |||
| 133 | temp += sensor->cdata->temp_adjust_val; | ||
| 134 | temp = mcelsius(temp); | ||
| 135 | |||
| 136 | dev_dbg(dev, "temperature: %d\n", temp); | ||
| 137 | |||
| 138 | *temperature = temp; | ||
| 139 | |||
| 140 | return 0; | ||
| 141 | } | ||
| 142 | |||
| 143 | static int st_thermal_get_trip_type(struct thermal_zone_device *th, | ||
| 144 | int trip, enum thermal_trip_type *type) | ||
| 145 | { | ||
| 146 | struct st_thermal_sensor *sensor = th->devdata; | ||
| 147 | struct device *dev = sensor->dev; | ||
| 148 | |||
| 149 | switch (trip) { | ||
| 150 | case 0: | ||
| 151 | *type = THERMAL_TRIP_CRITICAL; | ||
| 152 | break; | ||
| 153 | default: | ||
| 154 | dev_err(dev, "invalid trip point\n"); | ||
| 155 | return -EINVAL; | ||
| 156 | } | ||
| 157 | |||
| 158 | return 0; | ||
| 159 | } | ||
| 160 | |||
| 161 | static int st_thermal_get_trip_temp(struct thermal_zone_device *th, | ||
| 162 | int trip, unsigned long *temp) | ||
| 163 | { | ||
| 164 | struct st_thermal_sensor *sensor = th->devdata; | ||
| 165 | struct device *dev = sensor->dev; | ||
| 166 | |||
| 167 | switch (trip) { | ||
| 168 | case 0: | ||
| 169 | *temp = mcelsius(sensor->cdata->crit_temp); | ||
| 170 | break; | ||
| 171 | default: | ||
| 172 | dev_err(dev, "Invalid trip point\n"); | ||
| 173 | return -EINVAL; | ||
| 174 | } | ||
| 175 | |||
| 176 | return 0; | ||
| 177 | } | ||
| 178 | |||
| 179 | static struct thermal_zone_device_ops st_tz_ops = { | ||
| 180 | .get_temp = st_thermal_get_temp, | ||
| 181 | .get_trip_type = st_thermal_get_trip_type, | ||
| 182 | .get_trip_temp = st_thermal_get_trip_temp, | ||
| 183 | }; | ||
| 184 | |||
| 185 | int st_thermal_register(struct platform_device *pdev, | ||
| 186 | const struct of_device_id *st_thermal_of_match) | ||
| 187 | { | ||
| 188 | struct st_thermal_sensor *sensor; | ||
| 189 | struct device *dev = &pdev->dev; | ||
| 190 | struct device_node *np = dev->of_node; | ||
| 191 | const struct of_device_id *match; | ||
| 192 | |||
| 193 | int polling_delay; | ||
| 194 | int ret; | ||
| 195 | |||
| 196 | if (!np) { | ||
| 197 | dev_err(dev, "device tree node not found\n"); | ||
| 198 | return -EINVAL; | ||
| 199 | } | ||
| 200 | |||
| 201 | sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); | ||
| 202 | if (!sensor) | ||
| 203 | return -ENOMEM; | ||
| 204 | |||
| 205 | sensor->dev = dev; | ||
| 206 | |||
| 207 | match = of_match_device(st_thermal_of_match, dev); | ||
| 208 | if (!(match && match->data)) | ||
| 209 | return -EINVAL; | ||
| 210 | |||
| 211 | sensor->cdata = match->data; | ||
| 212 | if (!sensor->cdata->ops) | ||
| 213 | return -EINVAL; | ||
| 214 | |||
| 215 | sensor->ops = sensor->cdata->ops; | ||
| 216 | |||
| 217 | ret = sensor->ops->regmap_init(sensor); | ||
| 218 | if (ret) | ||
| 219 | return ret; | ||
| 220 | |||
| 221 | ret = st_thermal_alloc_regfields(sensor); | ||
| 222 | if (ret) | ||
| 223 | return ret; | ||
| 224 | |||
| 225 | sensor->clk = devm_clk_get(dev, "thermal"); | ||
| 226 | if (IS_ERR(sensor->clk)) { | ||
| 227 | dev_err(dev, "failed to fetch clock\n"); | ||
| 228 | return PTR_ERR(sensor->clk); | ||
| 229 | } | ||
| 230 | |||
| 231 | if (sensor->ops->register_enable_irq) { | ||
| 232 | ret = sensor->ops->register_enable_irq(sensor); | ||
| 233 | if (ret) | ||
| 234 | return ret; | ||
| 235 | } | ||
| 236 | |||
| 237 | ret = st_thermal_sensor_on(sensor); | ||
| 238 | if (ret) | ||
| 239 | return ret; | ||
| 240 | |||
| 241 | ret = st_thermal_calibration(sensor); | ||
| 242 | if (ret) | ||
| 243 | goto sensor_off; | ||
| 244 | |||
| 245 | polling_delay = sensor->ops->register_enable_irq ? 0 : 1000; | ||
| 246 | |||
| 247 | sensor->thermal_dev = | ||
| 248 | thermal_zone_device_register(dev_name(dev), 1, 0, sensor, | ||
| 249 | &st_tz_ops, NULL, 0, polling_delay); | ||
| 250 | if (IS_ERR(sensor->thermal_dev)) { | ||
| 251 | dev_err(dev, "failed to register thermal zone device\n"); | ||
| 252 | ret = PTR_ERR(sensor->thermal_dev); | ||
| 253 | goto sensor_off; | ||
| 254 | } | ||
| 255 | |||
| 256 | platform_set_drvdata(pdev, sensor); | ||
| 257 | |||
| 258 | return 0; | ||
| 259 | |||
| 260 | sensor_off: | ||
| 261 | st_thermal_sensor_off(sensor); | ||
| 262 | |||
| 263 | return ret; | ||
| 264 | } | ||
| 265 | EXPORT_SYMBOL_GPL(st_thermal_register); | ||
| 266 | |||
| 267 | int st_thermal_unregister(struct platform_device *pdev) | ||
| 268 | { | ||
| 269 | struct st_thermal_sensor *sensor = platform_get_drvdata(pdev); | ||
| 270 | |||
| 271 | st_thermal_sensor_off(sensor); | ||
| 272 | thermal_zone_device_unregister(sensor->thermal_dev); | ||
| 273 | |||
| 274 | return 0; | ||
| 275 | } | ||
| 276 | EXPORT_SYMBOL_GPL(st_thermal_unregister); | ||
| 277 | |||
| 278 | static int st_thermal_suspend(struct device *dev) | ||
| 279 | { | ||
| 280 | struct platform_device *pdev = to_platform_device(dev); | ||
| 281 | struct st_thermal_sensor *sensor = platform_get_drvdata(pdev); | ||
| 282 | |||
| 283 | return st_thermal_sensor_off(sensor); | ||
| 284 | } | ||
| 285 | |||
| 286 | static int st_thermal_resume(struct device *dev) | ||
| 287 | { | ||
| 288 | int ret; | ||
| 289 | struct platform_device *pdev = to_platform_device(dev); | ||
| 290 | struct st_thermal_sensor *sensor = platform_get_drvdata(pdev); | ||
| 291 | |||
| 292 | ret = st_thermal_sensor_on(sensor); | ||
| 293 | if (ret) | ||
| 294 | return ret; | ||
| 295 | |||
| 296 | ret = st_thermal_calibration(sensor); | ||
| 297 | if (ret) | ||
| 298 | return ret; | ||
| 299 | |||
| 300 | if (sensor->ops->enable_irq) { | ||
| 301 | ret = sensor->ops->enable_irq(sensor); | ||
| 302 | if (ret) | ||
| 303 | return ret; | ||
| 304 | } | ||
| 305 | |||
| 306 | return 0; | ||
| 307 | } | ||
| 308 | SIMPLE_DEV_PM_OPS(st_thermal_pm_ops, st_thermal_suspend, st_thermal_resume); | ||
| 309 | EXPORT_SYMBOL_GPL(st_thermal_pm_ops); | ||
| 310 | |||
| 311 | MODULE_AUTHOR("STMicroelectronics (R&D) Limited <ajitpal.singh@st.com>"); | ||
| 312 | MODULE_DESCRIPTION("STMicroelectronics STi SoC Thermal Sensor Driver"); | ||
| 313 | MODULE_LICENSE("GPL v2"); | ||
diff --git a/drivers/thermal/st/st_thermal.h b/drivers/thermal/st/st_thermal.h new file mode 100644 index 000000000000..fecafbe10fa7 --- /dev/null +++ b/drivers/thermal/st/st_thermal.h | |||
| @@ -0,0 +1,104 @@ | |||
| 1 | /* | ||
| 2 | * ST Thermal Sensor Driver for STi series of SoCs | ||
| 3 | * Author: Ajit Pal Singh <ajitpal.singh@st.com> | ||
| 4 | * | ||
| 5 | * Copyright (C) 2003-2014 STMicroelectronics (R&D) Limited | ||
| 6 | * | ||
| 7 | * This program is free software; you can redistribute it and/or modify | ||
| 8 | * it under the terms of the GNU General Public License as published by | ||
| 9 | * the Free Software Foundation; either version 2 of the License, or | ||
| 10 | * (at your option) any later version. | ||
| 11 | */ | ||
| 12 | |||
| 13 | #ifndef __STI_THERMAL_SYSCFG_H | ||
| 14 | #define __STI_THERMAL_SYSCFG_H | ||
| 15 | |||
| 16 | #include <linux/interrupt.h> | ||
| 17 | #include <linux/platform_device.h> | ||
| 18 | #include <linux/regmap.h> | ||
| 19 | #include <linux/thermal.h> | ||
| 20 | |||
| 21 | enum st_thermal_regfield_ids { | ||
| 22 | INT_THRESH_HI = 0, /* Top two regfield IDs are mutually exclusive */ | ||
| 23 | TEMP_PWR = 0, | ||
| 24 | DCORRECT, | ||
| 25 | OVERFLOW, | ||
| 26 | DATA, | ||
| 27 | INT_ENABLE, | ||
| 28 | |||
| 29 | MAX_REGFIELDS | ||
| 30 | }; | ||
| 31 | |||
| 32 | /* Thermal sensor power states */ | ||
| 33 | enum st_thermal_power_state { | ||
| 34 | POWER_OFF = 0, | ||
| 35 | POWER_ON | ||
| 36 | }; | ||
| 37 | |||
| 38 | struct st_thermal_sensor; | ||
| 39 | |||
| 40 | /** | ||
| 41 | * Description of private thermal sensor ops. | ||
| 42 | * | ||
| 43 | * @power_ctrl: Function for powering on/off a sensor. Clock to the | ||
| 44 | * sensor is also controlled from this function. | ||
| 45 | * @alloc_regfields: Allocate regmap register fields, specific to a sensor. | ||
| 46 | * @do_memmap_regmap: Memory map the thermal register space and init regmap | ||
| 47 | * instance or find regmap instance. | ||
| 48 | * @register_irq: Register an interrupt handler for a sensor. | ||
| 49 | */ | ||
| 50 | struct st_thermal_sensor_ops { | ||
| 51 | int (*power_ctrl)(struct st_thermal_sensor *, enum st_thermal_power_state); | ||
| 52 | int (*alloc_regfields)(struct st_thermal_sensor *); | ||
| 53 | int (*regmap_init)(struct st_thermal_sensor *); | ||
| 54 | int (*register_enable_irq)(struct st_thermal_sensor *); | ||
| 55 | int (*enable_irq)(struct st_thermal_sensor *); | ||
| 56 | }; | ||
| 57 | |||
| 58 | /** | ||
| 59 | * Description of thermal driver compatible data. | ||
| 60 | * | ||
| 61 | * @reg_fields: Pointer to the regfields array for a sensor. | ||
| 62 | * @sys_compat: Pointer to the syscon node compatible string. | ||
| 63 | * @ops: Pointer to private thermal ops for a sensor. | ||
| 64 | * @calibration_val: Default calibration value to be written to the DCORRECT | ||
| 65 | * register field for a sensor. | ||
| 66 | * @temp_adjust_val: Value to be added/subtracted from the data read from | ||
| 67 | * the sensor. If value needs to be added please provide a | ||
| 68 | * positive value and if it is to be subtracted please | ||
| 69 | * provide a negative value. | ||
| 70 | * @crit_temp: The temperature beyond which the SoC should be shutdown | ||
| 71 | * to prevent damage. | ||
| 72 | */ | ||
| 73 | struct st_thermal_compat_data { | ||
| 74 | char *sys_compat; | ||
| 75 | const struct reg_field *reg_fields; | ||
| 76 | const struct st_thermal_sensor_ops *ops; | ||
| 77 | unsigned int calibration_val; | ||
| 78 | int temp_adjust_val; | ||
| 79 | int crit_temp; | ||
| 80 | }; | ||
| 81 | |||
| 82 | struct st_thermal_sensor { | ||
| 83 | struct device *dev; | ||
| 84 | struct thermal_zone_device *thermal_dev; | ||
| 85 | const struct st_thermal_sensor_ops *ops; | ||
| 86 | const struct st_thermal_compat_data *cdata; | ||
| 87 | struct clk *clk; | ||
| 88 | struct regmap *regmap; | ||
| 89 | struct regmap_field *pwr; | ||
| 90 | struct regmap_field *dcorrect; | ||
| 91 | struct regmap_field *overflow; | ||
| 92 | struct regmap_field *temp_data; | ||
| 93 | struct regmap_field *int_thresh_hi; | ||
| 94 | struct regmap_field *int_enable; | ||
| 95 | int irq; | ||
| 96 | void __iomem *mmio_base; | ||
| 97 | }; | ||
| 98 | |||
| 99 | extern int st_thermal_register(struct platform_device *pdev, | ||
| 100 | const struct of_device_id *st_thermal_of_match); | ||
| 101 | extern int st_thermal_unregister(struct platform_device *pdev); | ||
| 102 | extern const struct dev_pm_ops st_thermal_pm_ops; | ||
| 103 | |||
| 104 | #endif /* __STI_RESET_SYSCFG_H */ | ||
