int3403_thermal.c (6733B)
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * ACPI INT3403 thermal driver 4 * Copyright (c) 2013, Intel Corporation. 5 */ 6 7#include <linux/kernel.h> 8#include <linux/module.h> 9#include <linux/init.h> 10#include <linux/types.h> 11#include <linux/acpi.h> 12#include <linux/thermal.h> 13#include <linux/platform_device.h> 14#include "int340x_thermal_zone.h" 15 16#define INT3403_TYPE_SENSOR 0x03 17#define INT3403_TYPE_CHARGER 0x0B 18#define INT3403_TYPE_BATTERY 0x0C 19#define INT3403_PERF_CHANGED_EVENT 0x80 20#define INT3403_PERF_TRIP_POINT_CHANGED 0x81 21#define INT3403_THERMAL_EVENT 0x90 22 23/* Preserved structure for future expandbility */ 24struct int3403_sensor { 25 struct int34x_thermal_zone *int340x_zone; 26}; 27 28struct int3403_performance_state { 29 u64 performance; 30 u64 power; 31 u64 latency; 32 u64 linear; 33 u64 control; 34 u64 raw_performace; 35 char *raw_unit; 36 int reserved; 37}; 38 39struct int3403_cdev { 40 struct thermal_cooling_device *cdev; 41 unsigned long max_state; 42}; 43 44struct int3403_priv { 45 struct platform_device *pdev; 46 struct acpi_device *adev; 47 unsigned long long type; 48 void *priv; 49}; 50 51static void int3403_notify(acpi_handle handle, 52 u32 event, void *data) 53{ 54 struct int3403_priv *priv = data; 55 struct int3403_sensor *obj; 56 57 if (!priv) 58 return; 59 60 obj = priv->priv; 61 if (priv->type != INT3403_TYPE_SENSOR || !obj) 62 return; 63 64 switch (event) { 65 case INT3403_PERF_CHANGED_EVENT: 66 break; 67 case INT3403_THERMAL_EVENT: 68 int340x_thermal_zone_device_update(obj->int340x_zone, 69 THERMAL_TRIP_VIOLATED); 70 break; 71 case INT3403_PERF_TRIP_POINT_CHANGED: 72 int340x_thermal_read_trips(obj->int340x_zone); 73 int340x_thermal_zone_device_update(obj->int340x_zone, 74 THERMAL_TRIP_CHANGED); 75 break; 76 default: 77 dev_dbg(&priv->pdev->dev, "Unsupported event [0x%x]\n", event); 78 break; 79 } 80} 81 82static int int3403_sensor_add(struct int3403_priv *priv) 83{ 84 int result = 0; 85 struct int3403_sensor *obj; 86 87 obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL); 88 if (!obj) 89 return -ENOMEM; 90 91 priv->priv = obj; 92 93 obj->int340x_zone = int340x_thermal_zone_add(priv->adev, NULL); 94 if (IS_ERR(obj->int340x_zone)) 95 return PTR_ERR(obj->int340x_zone); 96 97 result = acpi_install_notify_handler(priv->adev->handle, 98 ACPI_DEVICE_NOTIFY, int3403_notify, 99 (void *)priv); 100 if (result) 101 goto err_free_obj; 102 103 return 0; 104 105 err_free_obj: 106 int340x_thermal_zone_remove(obj->int340x_zone); 107 return result; 108} 109 110static int int3403_sensor_remove(struct int3403_priv *priv) 111{ 112 struct int3403_sensor *obj = priv->priv; 113 114 acpi_remove_notify_handler(priv->adev->handle, 115 ACPI_DEVICE_NOTIFY, int3403_notify); 116 int340x_thermal_zone_remove(obj->int340x_zone); 117 118 return 0; 119} 120 121/* INT3403 Cooling devices */ 122static int int3403_get_max_state(struct thermal_cooling_device *cdev, 123 unsigned long *state) 124{ 125 struct int3403_priv *priv = cdev->devdata; 126 struct int3403_cdev *obj = priv->priv; 127 128 *state = obj->max_state; 129 return 0; 130} 131 132static int int3403_get_cur_state(struct thermal_cooling_device *cdev, 133 unsigned long *state) 134{ 135 struct int3403_priv *priv = cdev->devdata; 136 unsigned long long level; 137 acpi_status status; 138 139 status = acpi_evaluate_integer(priv->adev->handle, "PPPC", NULL, &level); 140 if (ACPI_SUCCESS(status)) { 141 *state = level; 142 return 0; 143 } else 144 return -EINVAL; 145} 146 147static int 148int3403_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) 149{ 150 struct int3403_priv *priv = cdev->devdata; 151 acpi_status status; 152 153 status = acpi_execute_simple_method(priv->adev->handle, "SPPC", state); 154 if (ACPI_SUCCESS(status)) 155 return 0; 156 else 157 return -EINVAL; 158} 159 160static const struct thermal_cooling_device_ops int3403_cooling_ops = { 161 .get_max_state = int3403_get_max_state, 162 .get_cur_state = int3403_get_cur_state, 163 .set_cur_state = int3403_set_cur_state, 164}; 165 166static int int3403_cdev_add(struct int3403_priv *priv) 167{ 168 int result = 0; 169 acpi_status status; 170 struct int3403_cdev *obj; 171 struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; 172 union acpi_object *p; 173 174 obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL); 175 if (!obj) 176 return -ENOMEM; 177 178 status = acpi_evaluate_object(priv->adev->handle, "PPSS", NULL, &buf); 179 if (ACPI_FAILURE(status)) 180 return -ENODEV; 181 182 p = buf.pointer; 183 if (!p || (p->type != ACPI_TYPE_PACKAGE)) { 184 pr_warn("Invalid PPSS data\n"); 185 kfree(buf.pointer); 186 return -EFAULT; 187 } 188 189 priv->priv = obj; 190 obj->max_state = p->package.count - 1; 191 obj->cdev = 192 thermal_cooling_device_register(acpi_device_bid(priv->adev), 193 priv, &int3403_cooling_ops); 194 if (IS_ERR(obj->cdev)) 195 result = PTR_ERR(obj->cdev); 196 197 kfree(buf.pointer); 198 /* TODO: add ACPI notification support */ 199 200 return result; 201} 202 203static int int3403_cdev_remove(struct int3403_priv *priv) 204{ 205 struct int3403_cdev *obj = priv->priv; 206 207 thermal_cooling_device_unregister(obj->cdev); 208 return 0; 209} 210 211static int int3403_add(struct platform_device *pdev) 212{ 213 struct int3403_priv *priv; 214 int result = 0; 215 unsigned long long tmp; 216 acpi_status status; 217 218 priv = devm_kzalloc(&pdev->dev, sizeof(struct int3403_priv), 219 GFP_KERNEL); 220 if (!priv) 221 return -ENOMEM; 222 223 priv->pdev = pdev; 224 priv->adev = ACPI_COMPANION(&(pdev->dev)); 225 if (!priv->adev) { 226 result = -EINVAL; 227 goto err; 228 } 229 230 231 status = acpi_evaluate_integer(priv->adev->handle, "_TMP", 232 NULL, &tmp); 233 if (ACPI_FAILURE(status)) { 234 status = acpi_evaluate_integer(priv->adev->handle, "PTYP", 235 NULL, &priv->type); 236 if (ACPI_FAILURE(status)) { 237 result = -EINVAL; 238 goto err; 239 } 240 } else { 241 priv->type = INT3403_TYPE_SENSOR; 242 } 243 244 platform_set_drvdata(pdev, priv); 245 switch (priv->type) { 246 case INT3403_TYPE_SENSOR: 247 result = int3403_sensor_add(priv); 248 break; 249 case INT3403_TYPE_CHARGER: 250 case INT3403_TYPE_BATTERY: 251 result = int3403_cdev_add(priv); 252 break; 253 default: 254 result = -EINVAL; 255 } 256 257 if (result) 258 goto err; 259 return result; 260 261err: 262 return result; 263} 264 265static int int3403_remove(struct platform_device *pdev) 266{ 267 struct int3403_priv *priv = platform_get_drvdata(pdev); 268 269 switch (priv->type) { 270 case INT3403_TYPE_SENSOR: 271 int3403_sensor_remove(priv); 272 break; 273 case INT3403_TYPE_CHARGER: 274 case INT3403_TYPE_BATTERY: 275 int3403_cdev_remove(priv); 276 break; 277 default: 278 break; 279 } 280 281 return 0; 282} 283 284static const struct acpi_device_id int3403_device_ids[] = { 285 {"INT3403", 0}, 286 {"INTC1043", 0}, 287 {"INTC1046", 0}, 288 {"INTC1062", 0}, 289 {"INTC10A1", 0}, 290 {"", 0}, 291}; 292MODULE_DEVICE_TABLE(acpi, int3403_device_ids); 293 294static struct platform_driver int3403_driver = { 295 .probe = int3403_add, 296 .remove = int3403_remove, 297 .driver = { 298 .name = "int3403 thermal", 299 .acpi_match_table = int3403_device_ids, 300 }, 301}; 302 303module_platform_driver(int3403_driver); 304 305MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>"); 306MODULE_LICENSE("GPL v2"); 307MODULE_DESCRIPTION("ACPI INT3403 thermal driver");