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