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