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 */ 24 struct int3403_sensor { 25 struct int34x_thermal_zone *int340x_zone; 26 }; 27 28 struct 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 39 struct int3403_cdev { 40 struct thermal_cooling_device *cdev; 41 unsigned long max_state; 42 }; 43 44 struct int3403_priv { 45 struct platform_device *pdev; 46 struct acpi_device *adev; 47 unsigned long long type; 48 void *priv; 49 }; 50 51 static 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_err(&priv->pdev->dev, "Unsupported event [0x%x]\n", event); 78 break; 79 } 80 } 81 82 static 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 110 static 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 */ 122 static 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 132 static 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 147 static int 148 int3403_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 160 static 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 166 static 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 printk(KERN_WARNING "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 203 static 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 211 static 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 261 err: 262 return result; 263 } 264 265 static 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 284 static const struct acpi_device_id int3403_device_ids[] = { 285 {"INT3403", 0}, 286 {"", 0}, 287 }; 288 MODULE_DEVICE_TABLE(acpi, int3403_device_ids); 289 290 static struct platform_driver int3403_driver = { 291 .probe = int3403_add, 292 .remove = int3403_remove, 293 .driver = { 294 .name = "int3403 thermal", 295 .acpi_match_table = int3403_device_ids, 296 }, 297 }; 298 299 module_platform_driver(int3403_driver); 300 301 MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>"); 302 MODULE_LICENSE("GPL v2"); 303 MODULE_DESCRIPTION("ACPI INT3403 thermal driver"); 304