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