1 /* 2 * INT3400 thermal driver 3 * 4 * Copyright (C) 2014, Intel Corporation 5 * Authors: Zhang Rui <rui.zhang@intel.com> 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 version 2 as 9 * published by the Free Software Foundation. 10 * 11 */ 12 13 #include <linux/module.h> 14 #include <linux/platform_device.h> 15 #include <linux/acpi.h> 16 #include <linux/thermal.h> 17 #include "acpi_thermal_rel.h" 18 19 #define INT3400_THERMAL_TABLE_CHANGED 0x83 20 21 enum int3400_thermal_uuid { 22 INT3400_THERMAL_PASSIVE_1, 23 INT3400_THERMAL_ACTIVE, 24 INT3400_THERMAL_CRITICAL, 25 INT3400_THERMAL_ADAPTIVE_PERFORMANCE, 26 INT3400_THERMAL_EMERGENCY_CALL_MODE, 27 INT3400_THERMAL_PASSIVE_2, 28 INT3400_THERMAL_POWER_BOSS, 29 INT3400_THERMAL_VIRTUAL_SENSOR, 30 INT3400_THERMAL_COOLING_MODE, 31 INT3400_THERMAL_HARDWARE_DUTY_CYCLING, 32 INT3400_THERMAL_MAXIMUM_UUID, 33 }; 34 35 static char *int3400_thermal_uuids[INT3400_THERMAL_MAXIMUM_UUID] = { 36 "42A441D6-AE6A-462b-A84B-4A8CE79027D3", 37 "3A95C389-E4B8-4629-A526-C52C88626BAE", 38 "97C68AE7-15FA-499c-B8C9-5DA81D606E0A", 39 "63BE270F-1C11-48FD-A6F7-3AF253FF3E2D", 40 "5349962F-71E6-431D-9AE8-0A635B710AEE", 41 "9E04115A-AE87-4D1C-9500-0F3E340BFE75", 42 "F5A35014-C209-46A4-993A-EB56DE7530A1", 43 "6ED722A7-9240-48A5-B479-31EEF723D7CF", 44 "16CAF1B7-DD38-40ED-B1C1-1B8A1913D531", 45 "BE84BABF-C4D4-403D-B495-3128FD44dAC1", 46 }; 47 48 struct int3400_thermal_priv { 49 struct acpi_device *adev; 50 struct thermal_zone_device *thermal; 51 int mode; 52 int art_count; 53 struct art *arts; 54 int trt_count; 55 struct trt *trts; 56 u8 uuid_bitmap; 57 int rel_misc_dev_res; 58 int current_uuid_index; 59 }; 60 61 static ssize_t available_uuids_show(struct device *dev, 62 struct device_attribute *attr, 63 char *buf) 64 { 65 struct int3400_thermal_priv *priv = dev_get_drvdata(dev); 66 int i; 67 int length = 0; 68 69 for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; i++) { 70 if (priv->uuid_bitmap & (1 << i)) 71 if (PAGE_SIZE - length > 0) 72 length += snprintf(&buf[length], 73 PAGE_SIZE - length, 74 "%s\n", 75 int3400_thermal_uuids[i]); 76 } 77 78 return length; 79 } 80 81 static ssize_t current_uuid_show(struct device *dev, 82 struct device_attribute *devattr, char *buf) 83 { 84 struct int3400_thermal_priv *priv = dev_get_drvdata(dev); 85 86 if (priv->uuid_bitmap & (1 << priv->current_uuid_index)) 87 return sprintf(buf, "%s\n", 88 int3400_thermal_uuids[priv->current_uuid_index]); 89 else 90 return sprintf(buf, "INVALID\n"); 91 } 92 93 static ssize_t current_uuid_store(struct device *dev, 94 struct device_attribute *attr, 95 const char *buf, size_t count) 96 { 97 struct int3400_thermal_priv *priv = dev_get_drvdata(dev); 98 int i; 99 100 for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; ++i) { 101 if ((priv->uuid_bitmap & (1 << i)) && 102 !(strncmp(buf, int3400_thermal_uuids[i], 103 sizeof(int3400_thermal_uuids[i]) - 1))) { 104 priv->current_uuid_index = i; 105 return count; 106 } 107 } 108 109 return -EINVAL; 110 } 111 112 static DEVICE_ATTR_RW(current_uuid); 113 static DEVICE_ATTR_RO(available_uuids); 114 static struct attribute *uuid_attrs[] = { 115 &dev_attr_available_uuids.attr, 116 &dev_attr_current_uuid.attr, 117 NULL 118 }; 119 120 static const struct attribute_group uuid_attribute_group = { 121 .attrs = uuid_attrs, 122 .name = "uuids" 123 }; 124 125 static int int3400_thermal_get_uuids(struct int3400_thermal_priv *priv) 126 { 127 struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL}; 128 union acpi_object *obja, *objb; 129 int i, j; 130 int result = 0; 131 acpi_status status; 132 133 status = acpi_evaluate_object(priv->adev->handle, "IDSP", NULL, &buf); 134 if (ACPI_FAILURE(status)) 135 return -ENODEV; 136 137 obja = (union acpi_object *)buf.pointer; 138 if (obja->type != ACPI_TYPE_PACKAGE) { 139 result = -EINVAL; 140 goto end; 141 } 142 143 for (i = 0; i < obja->package.count; i++) { 144 objb = &obja->package.elements[i]; 145 if (objb->type != ACPI_TYPE_BUFFER) { 146 result = -EINVAL; 147 goto end; 148 } 149 150 /* UUID must be 16 bytes */ 151 if (objb->buffer.length != 16) { 152 result = -EINVAL; 153 goto end; 154 } 155 156 for (j = 0; j < INT3400_THERMAL_MAXIMUM_UUID; j++) { 157 guid_t guid; 158 159 guid_parse(int3400_thermal_uuids[j], &guid); 160 if (guid_equal((guid_t *)objb->buffer.pointer, &guid)) { 161 priv->uuid_bitmap |= (1 << j); 162 break; 163 } 164 } 165 } 166 167 end: 168 kfree(buf.pointer); 169 return result; 170 } 171 172 static int int3400_thermal_run_osc(acpi_handle handle, 173 enum int3400_thermal_uuid uuid, bool enable) 174 { 175 u32 ret, buf[2]; 176 acpi_status status; 177 int result = 0; 178 struct acpi_osc_context context = { 179 .uuid_str = int3400_thermal_uuids[uuid], 180 .rev = 1, 181 .cap.length = 8, 182 }; 183 184 buf[OSC_QUERY_DWORD] = 0; 185 buf[OSC_SUPPORT_DWORD] = enable; 186 187 context.cap.pointer = buf; 188 189 status = acpi_run_osc(handle, &context); 190 if (ACPI_SUCCESS(status)) { 191 ret = *((u32 *)(context.ret.pointer + 4)); 192 if (ret != enable) 193 result = -EPERM; 194 } else 195 result = -EPERM; 196 197 kfree(context.ret.pointer); 198 return result; 199 } 200 201 static void int3400_notify(acpi_handle handle, 202 u32 event, 203 void *data) 204 { 205 struct int3400_thermal_priv *priv = data; 206 char *thermal_prop[5]; 207 208 if (!priv) 209 return; 210 211 switch (event) { 212 case INT3400_THERMAL_TABLE_CHANGED: 213 thermal_prop[0] = kasprintf(GFP_KERNEL, "NAME=%s", 214 priv->thermal->type); 215 thermal_prop[1] = kasprintf(GFP_KERNEL, "TEMP=%d", 216 priv->thermal->temperature); 217 thermal_prop[2] = kasprintf(GFP_KERNEL, "TRIP="); 218 thermal_prop[3] = kasprintf(GFP_KERNEL, "EVENT=%d", 219 THERMAL_TABLE_CHANGED); 220 thermal_prop[4] = NULL; 221 kobject_uevent_env(&priv->thermal->device.kobj, KOBJ_CHANGE, 222 thermal_prop); 223 break; 224 default: 225 /* Ignore unknown notification codes sent to INT3400 device */ 226 break; 227 } 228 } 229 230 static int int3400_thermal_get_temp(struct thermal_zone_device *thermal, 231 int *temp) 232 { 233 *temp = 20 * 1000; /* faked temp sensor with 20C */ 234 return 0; 235 } 236 237 static int int3400_thermal_get_mode(struct thermal_zone_device *thermal, 238 enum thermal_device_mode *mode) 239 { 240 struct int3400_thermal_priv *priv = thermal->devdata; 241 242 if (!priv) 243 return -EINVAL; 244 245 *mode = priv->mode; 246 247 return 0; 248 } 249 250 static int int3400_thermal_set_mode(struct thermal_zone_device *thermal, 251 enum thermal_device_mode mode) 252 { 253 struct int3400_thermal_priv *priv = thermal->devdata; 254 bool enable; 255 int result = 0; 256 257 if (!priv) 258 return -EINVAL; 259 260 if (mode == THERMAL_DEVICE_ENABLED) 261 enable = true; 262 else if (mode == THERMAL_DEVICE_DISABLED) 263 enable = false; 264 else 265 return -EINVAL; 266 267 if (enable != priv->mode) { 268 priv->mode = enable; 269 result = int3400_thermal_run_osc(priv->adev->handle, 270 priv->current_uuid_index, 271 enable); 272 } 273 return result; 274 } 275 276 static struct thermal_zone_device_ops int3400_thermal_ops = { 277 .get_temp = int3400_thermal_get_temp, 278 }; 279 280 static struct thermal_zone_params int3400_thermal_params = { 281 .governor_name = "user_space", 282 .no_hwmon = true, 283 }; 284 285 static int int3400_thermal_probe(struct platform_device *pdev) 286 { 287 struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); 288 struct int3400_thermal_priv *priv; 289 int result; 290 291 if (!adev) 292 return -ENODEV; 293 294 priv = kzalloc(sizeof(struct int3400_thermal_priv), GFP_KERNEL); 295 if (!priv) 296 return -ENOMEM; 297 298 priv->adev = adev; 299 300 result = int3400_thermal_get_uuids(priv); 301 if (result) 302 goto free_priv; 303 304 result = acpi_parse_art(priv->adev->handle, &priv->art_count, 305 &priv->arts, true); 306 if (result) 307 dev_dbg(&pdev->dev, "_ART table parsing error\n"); 308 309 result = acpi_parse_trt(priv->adev->handle, &priv->trt_count, 310 &priv->trts, true); 311 if (result) 312 dev_dbg(&pdev->dev, "_TRT table parsing error\n"); 313 314 platform_set_drvdata(pdev, priv); 315 316 int3400_thermal_ops.get_mode = int3400_thermal_get_mode; 317 int3400_thermal_ops.set_mode = int3400_thermal_set_mode; 318 319 priv->thermal = thermal_zone_device_register("INT3400 Thermal", 0, 0, 320 priv, &int3400_thermal_ops, 321 &int3400_thermal_params, 0, 0); 322 if (IS_ERR(priv->thermal)) { 323 result = PTR_ERR(priv->thermal); 324 goto free_art_trt; 325 } 326 327 priv->rel_misc_dev_res = acpi_thermal_rel_misc_device_add( 328 priv->adev->handle); 329 330 result = sysfs_create_group(&pdev->dev.kobj, &uuid_attribute_group); 331 if (result) 332 goto free_rel_misc; 333 334 result = acpi_install_notify_handler( 335 priv->adev->handle, ACPI_DEVICE_NOTIFY, int3400_notify, 336 (void *)priv); 337 if (result) 338 goto free_sysfs; 339 340 return 0; 341 342 free_sysfs: 343 sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group); 344 free_rel_misc: 345 if (!priv->rel_misc_dev_res) 346 acpi_thermal_rel_misc_device_remove(priv->adev->handle); 347 thermal_zone_device_unregister(priv->thermal); 348 free_art_trt: 349 kfree(priv->trts); 350 kfree(priv->arts); 351 free_priv: 352 kfree(priv); 353 return result; 354 } 355 356 static int int3400_thermal_remove(struct platform_device *pdev) 357 { 358 struct int3400_thermal_priv *priv = platform_get_drvdata(pdev); 359 360 acpi_remove_notify_handler( 361 priv->adev->handle, ACPI_DEVICE_NOTIFY, 362 int3400_notify); 363 364 if (!priv->rel_misc_dev_res) 365 acpi_thermal_rel_misc_device_remove(priv->adev->handle); 366 367 sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group); 368 thermal_zone_device_unregister(priv->thermal); 369 kfree(priv->trts); 370 kfree(priv->arts); 371 kfree(priv); 372 return 0; 373 } 374 375 static const struct acpi_device_id int3400_thermal_match[] = { 376 {"INT3400", 0}, 377 {} 378 }; 379 380 MODULE_DEVICE_TABLE(acpi, int3400_thermal_match); 381 382 static struct platform_driver int3400_thermal_driver = { 383 .probe = int3400_thermal_probe, 384 .remove = int3400_thermal_remove, 385 .driver = { 386 .name = "int3400 thermal", 387 .acpi_match_table = ACPI_PTR(int3400_thermal_match), 388 }, 389 }; 390 391 module_platform_driver(int3400_thermal_driver); 392 393 MODULE_DESCRIPTION("INT3400 Thermal driver"); 394 MODULE_AUTHOR("Zhang Rui <rui.zhang@intel.com>"); 395 MODULE_LICENSE("GPL"); 396