blob: f8016e3f277f13e36370ae7f1f1fa1eccf7ad9b0 [file] [log] [blame]
/*
* palmas_thermal.c -- TI PALMAS THERMAL.
*
* Copyright (c) 2013, NVIDIA Corporation. All rights reserved.
*
* Author: Pradeep Goudagunta <pgoudagunta@nvidia.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation version 2.
*
* This program is distributed "as is" WITHOUT ANY WARRANTY of any kind,
* whether express or implied; 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307, USA
*/
#include <linux/module.h>
#include <linux/err.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/thermal.h>
#include <linux/mfd/palmas.h>
#define PALMAS_NORMAL_OPERATING_TEMP 100000
#define PALMAS_CRITICAL_DEFUALT_TEMP 108000
struct palmas_therm_zone {
struct device *dev;
struct palmas *palmas;
struct thermal_zone_device *tz_device;
int irq;
int is_crit_temp;
};
struct palmas_trip_point {
unsigned long temp;
enum thermal_trip_type type;
};
static struct palmas_trip_point palmas_tpoint = {
.temp = PALMAS_CRITICAL_DEFUALT_TEMP,
.type = THERMAL_TRIP_CRITICAL,
};
static int palmas_thermal_get_crit_temp(struct thermal_zone_device *tz_device,
unsigned long *temp)
{
*temp = palmas_tpoint.temp;
return 0;
}
static int palmas_thermal_get_temp(struct thermal_zone_device *tz_device,
unsigned long *temp)
{
struct palmas_therm_zone *ptherm_zone = tz_device->devdata;
if (ptherm_zone->is_crit_temp) {
/*
* Set temperature greater than Critical trip
* temp to trigger orderly power down sequence
*/
palmas_thermal_get_crit_temp(tz_device, temp);
*temp += 1;
return 0;
}
*temp = PALMAS_NORMAL_OPERATING_TEMP;
return 0;
}
static int palmas_thermal_get_trip_type(struct thermal_zone_device *tz_device,
int trip, enum thermal_trip_type *type)
{
if (trip >= 1)
return -EINVAL;
*type = palmas_tpoint.type;
return 0;
}
static int palmas_thermal_get_trip_temp(struct thermal_zone_device *tz_device,
int trip, unsigned long *temp)
{
if (trip >= 1)
return -EINVAL;
*temp = palmas_tpoint.temp;
return 0;
}
static struct thermal_zone_device_ops palmas_tz_ops = {
.get_temp = palmas_thermal_get_temp,
.get_crit_temp = palmas_thermal_get_crit_temp,
.get_trip_type = palmas_thermal_get_trip_type,
.get_trip_temp = palmas_thermal_get_trip_temp,
};
static irqreturn_t palmas_thermal_irq(int irq, void *data)
{
struct palmas_therm_zone *ptherm_zone = data;
ptherm_zone->is_crit_temp = 1;
/*
* Necessary action can be taken here
* e.g: thermal_zone_device_update(pz->tz_device);
* will trigger orderly power down sequence.
*/
return IRQ_HANDLED;
}
static int palmas_thermal_probe(struct platform_device *pdev)
{
struct palmas_therm_zone *ptherm_zone;
struct palmas_platform_data *pdata;
struct palmas *palmas = dev_get_drvdata(pdev->dev.parent);
char *default_tz_name = "palmas-junc-tz";
int ret;
u8 val;
pdata = dev_get_platdata(pdev->dev.parent);
if (!pdata || !(pdata->hd_threshold_temp)) {
dev_err(&pdev->dev, "No platform data\n");
return -ENODEV;
}
ptherm_zone = devm_kzalloc(&pdev->dev, sizeof(*ptherm_zone),
GFP_KERNEL);
if (!ptherm_zone) {
dev_err(&pdev->dev, "No available free memory\n");
return -ENOMEM;
}
platform_set_drvdata(pdev, ptherm_zone);
ptherm_zone->dev = &pdev->dev;
if (!(pdata->tz_name))
pdata->tz_name = default_tz_name;
ptherm_zone->tz_device = thermal_zone_device_register(pdata->tz_name,
1, 0, ptherm_zone, &palmas_tz_ops,
NULL, 0, 0);
if (IS_ERR_OR_NULL(ptherm_zone->tz_device)) {
dev_err(ptherm_zone->dev,
"Register thermal zone device failed.\n");
return PTR_ERR(ptherm_zone->tz_device);
}
palmas_tpoint.temp = pdata->hd_threshold_temp;
ptherm_zone->irq = platform_get_irq(pdev, 0);
ret = request_threaded_irq(ptherm_zone->irq, NULL,
palmas_thermal_irq,
IRQF_ONESHOT, dev_name(&pdev->dev),
ptherm_zone);
if (ret < 0) {
dev_err(&pdev->dev,
"request irq %d failed: %dn", ptherm_zone->irq, ret);
goto int_req_failed;
}
switch (palmas_tpoint.temp) {
case 108000:
val = 0;
break;
case 112000:
val = 1;
break;
case 116000:
val = 2;
break;
case 120000:
val = 3;
break;
default:
dev_err(&pdev->dev, "%ld threshold is not supported",
palmas_tpoint.temp);
ret = -EINVAL;
goto error;
}
val <<= PALMAS_OSC_THERM_CTRL_THERM_HD_SEL_SHIFT;
ret = palmas_update_bits(palmas, PALMAS_PMU_CONTROL_BASE,
PALMAS_OSC_THERM_CTRL,
PALMAS_OSC_THERM_CTRL_THERM_HD_SEL_MASK, val);
if (ret < 0) {
dev_err(&pdev->dev, "osc_therm_ctrl reg update failed.\n");
goto error;
}
return 0;
error:
free_irq(ptherm_zone->irq, ptherm_zone);
int_req_failed:
thermal_zone_device_unregister(ptherm_zone->tz_device);
return ret;
}
static int palmas_thermal_remove(struct platform_device *pdev)
{
struct palmas_therm_zone *ptherm_zone = platform_get_drvdata(pdev);
thermal_zone_device_unregister(ptherm_zone->tz_device);
free_irq(ptherm_zone->irq, ptherm_zone);
kfree(ptherm_zone);
return 0;
}
static struct platform_driver palmas_thermal_driver = {
.probe = palmas_thermal_probe,
.remove = palmas_thermal_remove,
.driver = {
.name = "palmas-thermal",
.owner = THIS_MODULE,
},
};
static int __init palmas_thermal_init(void)
{
return platform_driver_register(&palmas_thermal_driver);
}
module_init(palmas_thermal_init);
static void __exit palmas_thermal_exit(void)
{
platform_driver_unregister(&palmas_thermal_driver);
}
module_exit(palmas_thermal_exit);
MODULE_DESCRIPTION("Palmas Thermal driver");
MODULE_AUTHOR("Pradeep Goudagunta<pgoudagunta@nvidia.com>");
MODULE_ALIAS("platform:palmas-thermal");
MODULE_LICENSE("GPL v2");