ARM: tegra: bbc: add thermal zones support
Hervé Fache [Wed, 10 Jul 2013 15:50:46 +0000 (16:50 +0100)]
BBC thermal information is exported as statistics.  This patch adds a
mechanism based on the modem statistics to create thermal zones for the
BBC-exported temperature sensors.

Bug 1273958

Signed-off-by: Hervé Fache <hfache@nvidia.com>
Change-Id: Id75d9be803a12c39717cfedb53022eeffbf8c239
Reviewed-on: http://git-master/r/247922
(cherry picked from commit f33ea45f628d1fd6c984d4c69512d7090012d22c)
Reviewed-on: http://git-master/r/250665
Reviewed-by: Simone Willett <swillett@nvidia.com>
Tested-by: Simone Willett <swillett@nvidia.com>

arch/arm/mach-tegra/Kconfig
arch/arm/mach-tegra/Makefile
arch/arm/mach-tegra/include/mach/tegra_bbc_thermal.h [new file with mode: 0644]
arch/arm/mach-tegra/tegra_bbc_proxy.c
arch/arm/mach-tegra/tegra_bbc_thermal.c [new file with mode: 0644]

index e685c9c..1ea1b01 100644 (file)
@@ -637,6 +637,17 @@ config TEGRA_BBC_PROXY
          and Latency Allowance settings. Additional setting adjustments
          can be done from user space through sysfs.
 
+config TEGRA_BBC_THERMAL
+       bool "Enable BBC Thermal reporting support"
+       depends on NVSHM
+       depends on TEGRA_BBC_PROXY
+       default y
+       help
+         Enables the reporting of BBC-managed temperature sensors values as
+         thermal zones.  This uses the statistics framework where the data
+         reside, and thus depends on the NVSHM driver to provides the API to
+         browse the BBC statistics and be notified of modem [re-]boot.
+
 config TEGRA_PLLM_RESTRICTED
        bool "Restrict PLLM usage as module clock source"
        depends on !ARCH_TEGRA_2x_SOC
index aad947f..e3b3f7f 100644 (file)
@@ -298,6 +298,7 @@ obj-${CONFIG_TEGRA_BB_XMM_POWER2}       += baseband-xmm-power2.o
 
 obj-${CONFIG_TEGRA_BASEBAND}            += tegra_bb.o
 obj-$(CONFIG_TEGRA_BBC_PROXY)           += tegra_bbc_proxy.o
+obj-$(CONFIG_TEGRA_BBC_THERMAL)         += tegra_bbc_thermal.o
 
 obj-${CONFIG_TEGRA_ISOMGR}              += isomgr.o
 
diff --git a/arch/arm/mach-tegra/include/mach/tegra_bbc_thermal.h b/arch/arm/mach-tegra/include/mach/tegra_bbc_thermal.h
new file mode 100644 (file)
index 0000000..5ee1d90
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2013, NVIDIA CORPORATION.  All rights reserved.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __MACH_TEGRA_BBC_THERMAL_H
+#define __MACH_TEGRA_BBC_THERMAL_H
+
+#ifdef CONFIG_TEGRA_BBC_THERMAL
+extern int tegra_bbc_thermal_init(void);
+#else
+static inline int tegra_bbc_thermal_init(void)
+{
+       return 0;
+}
+#endif
+
+#endif /* __MACH_TEGRA_BBC_THERMAL_H */
index 783cc0b..1b638e4 100644 (file)
@@ -26,6 +26,7 @@
 #include <mach/isomgr.h>
 #include <mach/latency_allowance.h>
 #include <mach/tegra_bbc_proxy.h>
+#include <mach/tegra_bbc_thermal.h>
 
 #define MAX_MODEM_EDP_STATES 10
 
@@ -778,6 +779,9 @@ static int tegra_bbc_proxy_probe(struct platform_device *pdev)
        else
                bbc->margin = MAX_ISO_BW_REQ;
 
+       /* thermal zones from bbc */
+       tegra_bbc_thermal_init();
+
        attrs = mc_attributes;
        while ((attr = *attrs++)) {
                ret = device_create_file(&pdev->dev, attr);
diff --git a/arch/arm/mach-tegra/tegra_bbc_thermal.c b/arch/arm/mach-tegra/tegra_bbc_thermal.c
new file mode 100644 (file)
index 0000000..66f91d4
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2013, NVIDIA CORPORATION.  All rights reserved.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/printk.h>
+#include <linux/thermal.h>
+#include <linux/nvshm_stats.h>
+
+struct bbc_thermal_private {
+       u32 disabled_safe;
+       const u32 *enabled_ptr;
+       struct thermal_zone_device **tzds;
+       int tz_no;
+       struct notifier_block nb;
+};
+
+static struct bbc_thermal_private private;
+
+static int bbc_get_temp(struct thermal_zone_device *tzd, unsigned long *t)
+{
+       const u32 *temp = (const u32 *) tzd->devdata;
+
+       /* Check that we thermal is enabled and temperature has been updated */
+       if (!*private.enabled_ptr || (*temp > 300))
+               return -ENODATA;
+
+       /* °C to m°C */
+       *t = *temp * 1000;
+       return 0;
+}
+
+static const struct thermal_zone_device_ops bbc_thermal_ops = {
+       .get_temp = bbc_get_temp,
+};
+
+static void bbc_thermal_remove(void)
+{
+       int i;
+
+       private.enabled_ptr = &private.disabled_safe;
+       if (!private.tzds)
+               return;
+
+       for (i = 0; i < private.tz_no; i++)
+               thermal_zone_device_unregister(private.tzds[i]);
+
+       kfree(private.tzds);
+       private.tzds = NULL;
+}
+
+static int bbc_thermal_install(void)
+{
+       struct nvshm_stats_iter it;
+       unsigned int index;
+       const u32 *enabled_ptr;
+       int rc = 0;
+
+       if (private.tzds) {
+               pr_warn("BBC thermal already registered, unregistering\n");
+               bbc_thermal_remove();
+       }
+
+       /* Get iterator for top structure */
+       enabled_ptr = nvshm_stats_top("DrvTemperatureSysStats", &it);
+       if (IS_ERR(enabled_ptr)) {
+               pr_err("BBC thermal zones missing");
+               return PTR_ERR(enabled_ptr);
+       }
+
+       private.enabled_ptr = enabled_ptr;
+       /* Look for array of sensor data structures */
+       while (nvshm_stats_type(&it) != NVSHM_STATS_END) {
+               if (!strcmp(nvshm_stats_name(&it), "sensorStats"))
+                       break;
+
+               nvshm_stats_next(&it);
+       }
+
+       if (nvshm_stats_type(&it) != NVSHM_STATS_SUB) {
+               pr_err("sensorStats not found or incorrect type: %d",
+                      nvshm_stats_type(&it));
+               return -EINVAL;
+       }
+
+       /* Parse sensors */
+       private.tz_no = nvshm_stats_elems(&it);
+       pr_info("BBC can report temperatures from %d thermal zones",
+               private.tz_no);
+       private.tzds = kmalloc(private.tz_no * sizeof(*private.tzds),
+                              GFP_KERNEL);
+       if (!private.tzds) {
+               pr_err("failed to allocate array of sensors\n");
+               return -ENOMEM;
+       }
+
+       for (index = 0; index < private.tz_no; index++) {
+               struct nvshm_stats_iter sub_it;
+               char name[16];
+
+               /* Get iterator to sensor data structure */
+               nvshm_stats_sub(&it, index, &sub_it);
+               /* We only care about temperature */
+               while (nvshm_stats_type(&sub_it) != NVSHM_STATS_END) {
+                       if (!strcmp(nvshm_stats_name(&sub_it), "tempCelcius"))
+                               break;
+
+                       nvshm_stats_next(&sub_it);
+               }
+
+               /* This will either fail at first time or not at all */
+               if (nvshm_stats_type(&sub_it) != NVSHM_STATS_UINT32) {
+                       pr_err("tempCelcius not found or incorrect type: %d",
+                              nvshm_stats_type(&sub_it));
+                       kfree(private.tzds);
+                       private.tzds = NULL;
+                       return -EINVAL;
+               }
+
+               /* Ok we got it, let's register a new thermal zone */
+               sprintf(name, "BBC-therm%d", index);
+               private.tzds[index] = thermal_zone_device_register(name, 0, 0,
+                       (void *) nvshm_stats_valueptr_uint32(&sub_it, 0),
+                       &bbc_thermal_ops, NULL, 0, 0);
+               if (IS_ERR(private.tzds)) {
+                       pr_err("failed to register thermal zone #%d, abort\n",
+                              index);
+                       rc = PTR_ERR(private.tzds);
+                       break;
+               }
+       }
+
+       if (rc)
+               bbc_thermal_remove();
+
+       return rc;
+}
+
+static int bbc_thermal_notify(struct notifier_block *self,
+                              unsigned long action,
+                              void *user)
+{
+       switch (action) {
+       case NVSHM_STATS_MODEM_UP:
+               bbc_thermal_install();
+               break;
+       case NVSHM_STATS_MODEM_DOWN:
+               bbc_thermal_remove();
+               break;
+       }
+       return NOTIFY_OK;
+}
+
+void tegra_bbc_thermal_init(void)
+{
+       private.enabled_ptr = &private.disabled_safe;
+       private.nb.notifier_call = bbc_thermal_notify;
+       nvshm_stats_register(&private.nb);
+}