|  | /* | 
|  | * ACPI INT3403 thermal driver | 
|  | * Copyright (c) 2013, Intel Corporation. | 
|  | * | 
|  | * This program is free software; you can redistribute it and/or modify it | 
|  | * under the terms and conditions of the GNU General Public License, | 
|  | * version 2, as published by the Free Software Foundation. | 
|  | * | 
|  | * This program is distributed in the hope it will be useful, but WITHOUT | 
|  | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | 
|  | * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for | 
|  | * more details. | 
|  | */ | 
|  |  | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/init.h> | 
|  | #include <linux/types.h> | 
|  | #include <linux/acpi.h> | 
|  | #include <linux/thermal.h> | 
|  |  | 
|  | #define INT3403_TYPE_SENSOR		0x03 | 
|  | #define INT3403_PERF_CHANGED_EVENT	0x80 | 
|  | #define INT3403_THERMAL_EVENT		0x90 | 
|  |  | 
|  | #define DECI_KELVIN_TO_MILLI_CELSIUS(t, off) (((t) - (off)) * 100) | 
|  | #define KELVIN_OFFSET	2732 | 
|  | #define MILLI_CELSIUS_TO_DECI_KELVIN(t, off) (((t) / 100) + (off)) | 
|  |  | 
|  | #define ACPI_INT3403_CLASS		"int3403" | 
|  | #define ACPI_INT3403_FILE_STATE		"state" | 
|  |  | 
|  | struct int3403_sensor { | 
|  | struct thermal_zone_device *tzone; | 
|  | unsigned long *thresholds; | 
|  | }; | 
|  |  | 
|  | static int sys_get_curr_temp(struct thermal_zone_device *tzone, | 
|  | unsigned long *temp) | 
|  | { | 
|  | struct acpi_device *device = tzone->devdata; | 
|  | unsigned long long tmp; | 
|  | acpi_status status; | 
|  |  | 
|  | status = acpi_evaluate_integer(device->handle, "_TMP", NULL, &tmp); | 
|  | if (ACPI_FAILURE(status)) | 
|  | return -EIO; | 
|  |  | 
|  | *temp = DECI_KELVIN_TO_MILLI_CELSIUS(tmp, KELVIN_OFFSET); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int sys_get_trip_hyst(struct thermal_zone_device *tzone, | 
|  | int trip, unsigned long *temp) | 
|  | { | 
|  | struct acpi_device *device = tzone->devdata; | 
|  | unsigned long long hyst; | 
|  | acpi_status status; | 
|  |  | 
|  | status = acpi_evaluate_integer(device->handle, "GTSH", NULL, &hyst); | 
|  | if (ACPI_FAILURE(status)) | 
|  | return -EIO; | 
|  |  | 
|  | /* | 
|  | * Thermal hysteresis represents a temperature difference. | 
|  | * Kelvin and Celsius have same degree size. So the | 
|  | * conversion here between tenths of degree Kelvin unit | 
|  | * and Milli-Celsius unit is just to multiply 100. | 
|  | */ | 
|  | *temp = hyst * 100; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int sys_get_trip_temp(struct thermal_zone_device *tzone, | 
|  | int trip, unsigned long *temp) | 
|  | { | 
|  | struct acpi_device *device = tzone->devdata; | 
|  | struct int3403_sensor *obj = acpi_driver_data(device); | 
|  |  | 
|  | /* | 
|  | * get_trip_temp is a mandatory callback but | 
|  | * PATx method doesn't return any value, so return | 
|  | * cached value, which was last set from user space. | 
|  | */ | 
|  | *temp = obj->thresholds[trip]; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int sys_get_trip_type(struct thermal_zone_device *thermal, | 
|  | int trip, enum thermal_trip_type *type) | 
|  | { | 
|  | /* Mandatory callback, may not mean much here */ | 
|  | *type = THERMAL_TRIP_PASSIVE; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int sys_set_trip_temp(struct thermal_zone_device *tzone, int trip, | 
|  | unsigned long temp) | 
|  | { | 
|  | struct acpi_device *device = tzone->devdata; | 
|  | acpi_status status; | 
|  | char name[10]; | 
|  | int ret = 0; | 
|  | struct int3403_sensor *obj = acpi_driver_data(device); | 
|  |  | 
|  | snprintf(name, sizeof(name), "PAT%d", trip); | 
|  | if (acpi_has_method(device->handle, name)) { | 
|  | status = acpi_execute_simple_method(device->handle, name, | 
|  | MILLI_CELSIUS_TO_DECI_KELVIN(temp, | 
|  | KELVIN_OFFSET)); | 
|  | if (ACPI_FAILURE(status)) | 
|  | ret = -EIO; | 
|  | else | 
|  | obj->thresholds[trip] = temp; | 
|  | } else { | 
|  | ret = -EIO; | 
|  | dev_err(&device->dev, "sys_set_trip_temp: method not found\n"); | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static struct thermal_zone_device_ops tzone_ops = { | 
|  | .get_temp = sys_get_curr_temp, | 
|  | .get_trip_temp = sys_get_trip_temp, | 
|  | .get_trip_type = sys_get_trip_type, | 
|  | .set_trip_temp = sys_set_trip_temp, | 
|  | .get_trip_hyst =  sys_get_trip_hyst, | 
|  | }; | 
|  |  | 
|  | static void acpi_thermal_notify(struct acpi_device *device, u32 event) | 
|  | { | 
|  | struct int3403_sensor *obj; | 
|  |  | 
|  | if (!device) | 
|  | return; | 
|  |  | 
|  | obj = acpi_driver_data(device); | 
|  | if (!obj) | 
|  | return; | 
|  |  | 
|  | switch (event) { | 
|  | case INT3403_PERF_CHANGED_EVENT: | 
|  | break; | 
|  | case INT3403_THERMAL_EVENT: | 
|  | thermal_zone_device_update(obj->tzone); | 
|  | break; | 
|  | default: | 
|  | dev_err(&device->dev, "Unsupported event [0x%x]\n", event); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | static int acpi_int3403_add(struct acpi_device *device) | 
|  | { | 
|  | int result = 0; | 
|  | unsigned long long ptyp; | 
|  | acpi_status status; | 
|  | struct int3403_sensor *obj; | 
|  | unsigned long long trip_cnt; | 
|  | int trip_mask = 0; | 
|  |  | 
|  | if (!device) | 
|  | return -EINVAL; | 
|  |  | 
|  | status = acpi_evaluate_integer(device->handle, "PTYP", NULL, &ptyp); | 
|  | if (ACPI_FAILURE(status)) | 
|  | return -EINVAL; | 
|  |  | 
|  | if (ptyp != INT3403_TYPE_SENSOR) | 
|  | return -EINVAL; | 
|  |  | 
|  | obj = devm_kzalloc(&device->dev, sizeof(*obj), GFP_KERNEL); | 
|  | if (!obj) | 
|  | return -ENOMEM; | 
|  |  | 
|  | device->driver_data = obj; | 
|  |  | 
|  | status = acpi_evaluate_integer(device->handle, "PATC", NULL, | 
|  | &trip_cnt); | 
|  | if (ACPI_FAILURE(status)) | 
|  | trip_cnt = 0; | 
|  |  | 
|  | if (trip_cnt) { | 
|  | /* We have to cache, thresholds can't be readback */ | 
|  | obj->thresholds = devm_kzalloc(&device->dev, | 
|  | sizeof(*obj->thresholds) * trip_cnt, | 
|  | GFP_KERNEL); | 
|  | if (!obj->thresholds) | 
|  | return -ENOMEM; | 
|  | trip_mask = BIT(trip_cnt) - 1; | 
|  | } | 
|  | obj->tzone = thermal_zone_device_register(acpi_device_bid(device), | 
|  | trip_cnt, trip_mask, device, &tzone_ops, | 
|  | NULL, 0, 0); | 
|  | if (IS_ERR(obj->tzone)) { | 
|  | result = PTR_ERR(obj->tzone); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | strcpy(acpi_device_name(device), "INT3403"); | 
|  | strcpy(acpi_device_class(device), ACPI_INT3403_CLASS); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int acpi_int3403_remove(struct acpi_device *device) | 
|  | { | 
|  | struct int3403_sensor *obj; | 
|  |  | 
|  | obj = acpi_driver_data(device); | 
|  | thermal_zone_device_unregister(obj->tzone); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | ACPI_MODULE_NAME("int3403"); | 
|  | static const struct acpi_device_id int3403_device_ids[] = { | 
|  | {"INT3403", 0}, | 
|  | {"", 0}, | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(acpi, int3403_device_ids); | 
|  |  | 
|  | static struct acpi_driver acpi_int3403_driver = { | 
|  | .name = "INT3403", | 
|  | .class = ACPI_INT3403_CLASS, | 
|  | .ids = int3403_device_ids, | 
|  | .ops = { | 
|  | .add = acpi_int3403_add, | 
|  | .remove = acpi_int3403_remove, | 
|  | .notify = acpi_thermal_notify, | 
|  | }, | 
|  | }; | 
|  |  | 
|  | module_acpi_driver(acpi_int3403_driver); | 
|  |  | 
|  | MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>"); | 
|  | MODULE_LICENSE("GPL v2"); | 
|  | MODULE_DESCRIPTION("ACPI INT3403 thermal driver"); |