summaryrefslogtreecommitdiff
path: root/drivers/thermal/int340x_thermal/int3403_thermal.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2014-10-24 18:21:43 (GMT)
committerLinus Torvalds <torvalds@linux-foundation.org>2014-10-24 18:21:43 (GMT)
commit8264fce6de03f3915e2301f52f181a982718a8cb (patch)
tree2f24ce9411afc7132eb040b94d44c31bda6d13b0 /drivers/thermal/int340x_thermal/int3403_thermal.c
parent816fb4175c29b16948fb24a92053bea1e79908cc (diff)
parent6ceaf58abe25e86292152005c51169796bad3407 (diff)
downloadlinux-8264fce6de03f3915e2301f52f181a982718a8cb.tar.xz
Merge branch 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/rzhang/linux
Pull thermal management updates from Zhang Rui: "Sorry that I missed the merge window as there is a bug found in the last minute, and I have to fix it and wait for the code to be tested in linux-next tree for a few days. Now the buggy patch has been dropped entirely from my next branch. Thus I hope those changes can still be merged in 3.18-rc2 as most of them are platform thermal driver changes. Specifics: - introduce ACPI INT340X thermal drivers. Newer laptops and tablets may have thermal sensors and other devices with thermal control capabilities that are exposed for the OS to use via the ACPI INT340x device objects. Several drivers are introduced to expose the temperature information and cooling ability from these objects to user-space via the normal thermal framework. From: Lu Aaron, Lan Tianyu, Jacob Pan and Zhang Rui. - introduce a new thermal governor, which just uses a hysteresis to switch abruptly on/off a cooling device. This governor can be used to control certain fan devices that can not be throttled but just switched on or off. From: Peter Feuerer. - introduce support for some new thermal interrupt functions on i.MX6SX, in IMX thermal driver. From: Anson, Huang. - introduce tracing support on thermal framework. From: Punit Agrawal. - small fixes in OF thermal and thermal step_wise governor" * 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/rzhang/linux: (25 commits) Thermal: int340x thermal: select ACPI fan driver Thermal: int3400_thermal: use acpi_thermal_rel parsing APIs Thermal: int340x_thermal: expose acpi thermal relationship tables Thermal: introduce int3403 thermal driver Thermal: introduce INT3402 thermal driver Thermal: move the KELVIN_TO_MILLICELSIUS macro to thermal.h ACPI / Fan: support INT3404 thermal device ACPI / Fan: add ACPI 4.0 style fan support ACPI / fan: convert to platform driver ACPI / fan: use acpi_device_xxx_power instead of acpi_bus equivelant ACPI / fan: remove no need check for device pointer ACPI / fan: remove unused macro Thermal: int3400 thermal: register to thermal framework Thermal: int3400 thermal: add capability to detect supporting UUIDs Thermal: introduce int3400 thermal driver ACPI: add ACPI_TYPE_LOCAL_REFERENCE support to acpi_extract_package() ACPI: make acpi_create_platform_device() an external API thermal: step_wise: fix: Prevent from binary overflow when trend is dropping ACPI: introduce ACPI int340x thermal scan handler thermal: Added Bang-bang thermal governor ...
Diffstat (limited to 'drivers/thermal/int340x_thermal/int3403_thermal.c')
-rw-r--r--drivers/thermal/int340x_thermal/int3403_thermal.c477
1 files changed, 477 insertions, 0 deletions
diff --git a/drivers/thermal/int340x_thermal/int3403_thermal.c b/drivers/thermal/int340x_thermal/int3403_thermal.c
new file mode 100644
index 0000000..d20dba9
--- /dev/null
+++ b/drivers/thermal/int340x_thermal/int3403_thermal.c
@@ -0,0 +1,477 @@
+/*
+ * 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>
+#include <linux/platform_device.h>
+
+#define INT3403_TYPE_SENSOR 0x03
+#define INT3403_TYPE_CHARGER 0x0B
+#define INT3403_TYPE_BATTERY 0x0C
+#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))
+
+struct int3403_sensor {
+ struct thermal_zone_device *tzone;
+ unsigned long *thresholds;
+ unsigned long crit_temp;
+ int crit_trip_id;
+ unsigned long psv_temp;
+ int psv_trip_id;
+
+};
+
+struct int3403_performance_state {
+ u64 performance;
+ u64 power;
+ u64 latency;
+ u64 linear;
+ u64 control;
+ u64 raw_performace;
+ char *raw_unit;
+ int reserved;
+};
+
+struct int3403_cdev {
+ struct thermal_cooling_device *cdev;
+ unsigned long max_state;
+};
+
+struct int3403_priv {
+ struct platform_device *pdev;
+ struct acpi_device *adev;
+ unsigned long long type;
+ void *priv;
+};
+
+static int sys_get_curr_temp(struct thermal_zone_device *tzone,
+ unsigned long *temp)
+{
+ struct int3403_priv *priv = tzone->devdata;
+ struct acpi_device *device = priv->adev;
+ 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 int3403_priv *priv = tzone->devdata;
+ struct acpi_device *device = priv->adev;
+ unsigned long long hyst;
+ acpi_status status;
+
+ status = acpi_evaluate_integer(device->handle, "GTSH", NULL, &hyst);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ *temp = DECI_KELVIN_TO_MILLI_CELSIUS(hyst, KELVIN_OFFSET);
+
+ return 0;
+}
+
+static int sys_get_trip_temp(struct thermal_zone_device *tzone,
+ int trip, unsigned long *temp)
+{
+ struct int3403_priv *priv = tzone->devdata;
+ struct int3403_sensor *obj = priv->priv;
+
+ if (priv->type != INT3403_TYPE_SENSOR || !obj)
+ return -EINVAL;
+
+ if (trip == obj->crit_trip_id)
+ *temp = obj->crit_temp;
+ else if (trip == obj->psv_trip_id)
+ *temp = obj->psv_temp;
+ else {
+ /*
+ * 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)
+{
+ struct int3403_priv *priv = thermal->devdata;
+ struct int3403_sensor *obj = priv->priv;
+
+ /* Mandatory callback, may not mean much here */
+ if (trip == obj->crit_trip_id)
+ *type = THERMAL_TRIP_CRITICAL;
+ else
+ *type = THERMAL_TRIP_PASSIVE;
+
+ return 0;
+}
+
+int sys_set_trip_temp(struct thermal_zone_device *tzone, int trip,
+ unsigned long temp)
+{
+ struct int3403_priv *priv = tzone->devdata;
+ struct acpi_device *device = priv->adev;
+ struct int3403_sensor *obj = priv->priv;
+ acpi_status status;
+ char name[10];
+ int ret = 0;
+
+ 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 struct thermal_zone_params int3403_thermal_params = {
+ .governor_name = "user_space",
+ .no_hwmon = true,
+};
+
+static void int3403_notify(acpi_handle handle,
+ u32 event, void *data)
+{
+ struct int3403_priv *priv = data;
+ struct int3403_sensor *obj;
+
+ if (!priv)
+ return;
+
+ obj = priv->priv;
+ if (priv->type != INT3403_TYPE_SENSOR || !obj)
+ return;
+
+ switch (event) {
+ case INT3403_PERF_CHANGED_EVENT:
+ break;
+ case INT3403_THERMAL_EVENT:
+ thermal_zone_device_update(obj->tzone);
+ break;
+ default:
+ dev_err(&priv->pdev->dev, "Unsupported event [0x%x]\n", event);
+ break;
+ }
+}
+
+static int sys_get_trip_crt(struct acpi_device *device, unsigned long *temp)
+{
+ unsigned long long crt;
+ acpi_status status;
+
+ status = acpi_evaluate_integer(device->handle, "_CRT", NULL, &crt);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ *temp = DECI_KELVIN_TO_MILLI_CELSIUS(crt, KELVIN_OFFSET);
+
+ return 0;
+}
+
+static int sys_get_trip_psv(struct acpi_device *device, unsigned long *temp)
+{
+ unsigned long long psv;
+ acpi_status status;
+
+ status = acpi_evaluate_integer(device->handle, "_PSV", NULL, &psv);
+ if (ACPI_FAILURE(status))
+ return -EIO;
+
+ *temp = DECI_KELVIN_TO_MILLI_CELSIUS(psv, KELVIN_OFFSET);
+
+ return 0;
+}
+
+static int int3403_sensor_add(struct int3403_priv *priv)
+{
+ int result = 0;
+ acpi_status status;
+ struct int3403_sensor *obj;
+ unsigned long long trip_cnt;
+ int trip_mask = 0;
+
+ obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL);
+ if (!obj)
+ return -ENOMEM;
+
+ priv->priv = obj;
+
+ status = acpi_evaluate_integer(priv->adev->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(&priv->pdev->dev,
+ sizeof(*obj->thresholds) * trip_cnt,
+ GFP_KERNEL);
+ if (!obj->thresholds) {
+ result = -ENOMEM;
+ goto err_free_obj;
+ }
+ trip_mask = BIT(trip_cnt) - 1;
+ }
+
+ obj->psv_trip_id = -1;
+ if (!sys_get_trip_psv(priv->adev, &obj->psv_temp))
+ obj->psv_trip_id = trip_cnt++;
+
+ obj->crit_trip_id = -1;
+ if (!sys_get_trip_crt(priv->adev, &obj->crit_temp))
+ obj->crit_trip_id = trip_cnt++;
+
+ obj->tzone = thermal_zone_device_register(acpi_device_bid(priv->adev),
+ trip_cnt, trip_mask, priv, &tzone_ops,
+ &int3403_thermal_params, 0, 0);
+ if (IS_ERR(obj->tzone)) {
+ result = PTR_ERR(obj->tzone);
+ obj->tzone = NULL;
+ goto err_free_obj;
+ }
+
+ result = acpi_install_notify_handler(priv->adev->handle,
+ ACPI_DEVICE_NOTIFY, int3403_notify,
+ (void *)priv);
+ if (result)
+ goto err_free_obj;
+
+ return 0;
+
+ err_free_obj:
+ if (obj->tzone)
+ thermal_zone_device_unregister(obj->tzone);
+ return result;
+}
+
+static int int3403_sensor_remove(struct int3403_priv *priv)
+{
+ struct int3403_sensor *obj = priv->priv;
+
+ thermal_zone_device_unregister(obj->tzone);
+ return 0;
+}
+
+/* INT3403 Cooling devices */
+static int int3403_get_max_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct int3403_priv *priv = cdev->devdata;
+ struct int3403_cdev *obj = priv->priv;
+
+ *state = obj->max_state;
+ return 0;
+}
+
+static int int3403_get_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct int3403_priv *priv = cdev->devdata;
+ unsigned long long level;
+ acpi_status status;
+
+ status = acpi_evaluate_integer(priv->adev->handle, "PPPC", NULL, &level);
+ if (ACPI_SUCCESS(status)) {
+ *state = level;
+ return 0;
+ } else
+ return -EINVAL;
+}
+
+static int
+int3403_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state)
+{
+ struct int3403_priv *priv = cdev->devdata;
+ acpi_status status;
+
+ status = acpi_execute_simple_method(priv->adev->handle, "SPPC", state);
+ if (ACPI_SUCCESS(status))
+ return 0;
+ else
+ return -EINVAL;
+}
+
+static const struct thermal_cooling_device_ops int3403_cooling_ops = {
+ .get_max_state = int3403_get_max_state,
+ .get_cur_state = int3403_get_cur_state,
+ .set_cur_state = int3403_set_cur_state,
+};
+
+static int int3403_cdev_add(struct int3403_priv *priv)
+{
+ int result = 0;
+ acpi_status status;
+ struct int3403_cdev *obj;
+ struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *p;
+
+ obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL);
+ if (!obj)
+ return -ENOMEM;
+
+ status = acpi_evaluate_object(priv->adev->handle, "PPSS", NULL, &buf);
+ if (ACPI_FAILURE(status))
+ return -ENODEV;
+
+ p = buf.pointer;
+ if (!p || (p->type != ACPI_TYPE_PACKAGE)) {
+ printk(KERN_WARNING "Invalid PPSS data\n");
+ return -EFAULT;
+ }
+
+ obj->max_state = p->package.count - 1;
+ obj->cdev =
+ thermal_cooling_device_register(acpi_device_bid(priv->adev),
+ priv, &int3403_cooling_ops);
+ if (IS_ERR(obj->cdev))
+ result = PTR_ERR(obj->cdev);
+
+ priv->priv = obj;
+
+ /* TODO: add ACPI notification support */
+
+ return result;
+}
+
+static int int3403_cdev_remove(struct int3403_priv *priv)
+{
+ struct int3403_cdev *obj = priv->priv;
+
+ thermal_cooling_device_unregister(obj->cdev);
+ return 0;
+}
+
+static int int3403_add(struct platform_device *pdev)
+{
+ struct int3403_priv *priv;
+ int result = 0;
+ acpi_status status;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(struct int3403_priv),
+ GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->pdev = pdev;
+ priv->adev = ACPI_COMPANION(&(pdev->dev));
+ if (!priv->adev) {
+ result = -EINVAL;
+ goto err;
+ }
+
+ status = acpi_evaluate_integer(priv->adev->handle, "PTYP",
+ NULL, &priv->type);
+ if (ACPI_FAILURE(status)) {
+ result = -EINVAL;
+ goto err;
+ }
+
+ platform_set_drvdata(pdev, priv);
+ switch (priv->type) {
+ case INT3403_TYPE_SENSOR:
+ result = int3403_sensor_add(priv);
+ break;
+ case INT3403_TYPE_CHARGER:
+ case INT3403_TYPE_BATTERY:
+ result = int3403_cdev_add(priv);
+ break;
+ default:
+ result = -EINVAL;
+ }
+
+ if (result)
+ goto err;
+ return result;
+
+err:
+ return result;
+}
+
+static int int3403_remove(struct platform_device *pdev)
+{
+ struct int3403_priv *priv = platform_get_drvdata(pdev);
+
+ switch (priv->type) {
+ case INT3403_TYPE_SENSOR:
+ int3403_sensor_remove(priv);
+ break;
+ case INT3403_TYPE_CHARGER:
+ case INT3403_TYPE_BATTERY:
+ int3403_cdev_remove(priv);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static const struct acpi_device_id int3403_device_ids[] = {
+ {"INT3403", 0},
+ {"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, int3403_device_ids);
+
+static struct platform_driver int3403_driver = {
+ .probe = int3403_add,
+ .remove = int3403_remove,
+ .driver = {
+ .name = "int3403 thermal",
+ .owner = THIS_MODULE,
+ .acpi_match_table = int3403_device_ids,
+ },
+};
+
+module_platform_driver(int3403_driver);
+
+MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("ACPI INT3403 thermal driver");