/*
* 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 .
*/
#define pr_fmt(fmt) "%s: " fmt, __func__
#include
#include
#include
#include
#include
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);
}