352 lines
8.9 KiB
C
352 lines
8.9 KiB
C
/*
|
|
* AMS AS3722 THERMAL.
|
|
*
|
|
* Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved.
|
|
*
|
|
* Author: Bibek Basu <bbasu@nvidia.com>
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* This program is distributed in the hope that 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/debugfs.h>
|
|
#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/suspend.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/thermal.h>
|
|
#include <linux/mfd/as3722.h>
|
|
|
|
#define AS3722_MAX_TEMP 145000
|
|
#define AS3722_ALARM_TEMP 110000
|
|
|
|
enum sensor_id {
|
|
SD0 = 0,
|
|
SD1,
|
|
SD6,
|
|
SD_MAX,
|
|
};
|
|
|
|
struct as3722_therm_zone {
|
|
struct device *dev;
|
|
struct as3722 *as3722;
|
|
struct delayed_work timeout_work;
|
|
struct thermal_zone_device *tzd[SD_MAX];
|
|
long cur_temp[SD_MAX];
|
|
long high_limit[SD_MAX];
|
|
struct mutex update_lock;
|
|
int irq[SD_MAX];
|
|
bool irq_masked[SD_MAX];
|
|
u32 adc_ch[SD_MAX];
|
|
};
|
|
|
|
static void as3722_timeout_work(struct work_struct *work)
|
|
{
|
|
struct as3722_therm_zone *ptherm_zone = container_of(work,
|
|
struct as3722_therm_zone, timeout_work.work);
|
|
int i;
|
|
|
|
mutex_lock(&ptherm_zone->update_lock);
|
|
for (i = 0; i < SD_MAX; i++) {
|
|
if (ptherm_zone->irq_masked[i]) {
|
|
enable_irq(ptherm_zone->irq[i]);
|
|
ptherm_zone->irq_masked[i] = false;
|
|
}
|
|
}
|
|
mutex_unlock(&ptherm_zone->update_lock);
|
|
}
|
|
|
|
static int as3722_therm_get_temp(struct as3722_therm_zone *ptherm_zone,
|
|
long *temp, int channel)
|
|
{
|
|
struct as3722 *as3722 = ptherm_zone->as3722;
|
|
int tries = 10;
|
|
int ret;
|
|
int result;
|
|
u32 val;
|
|
|
|
/* Configure adc1 */
|
|
ret = as3722_update_bits(as3722, AS3722_ADC1_CONTROL_REG,
|
|
AS3722_ADC1_SOURCE_SELECT_MASK,
|
|
channel);
|
|
if (ret < 0) {
|
|
dev_err(ptherm_zone->dev,
|
|
"ADC1_CONTROL channel write failed %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
do {
|
|
int timeout = 10;
|
|
|
|
/* Start ADC */
|
|
ret = as3722_update_bits(as3722, AS3722_ADC1_CONTROL_REG,
|
|
AS3722_ADC1_CONV_START,
|
|
AS3722_ADC1_CONV_START);
|
|
if (ret < 0) {
|
|
dev_err(ptherm_zone->dev,
|
|
"Reg 0x%02x write failed: %d\n",
|
|
AS3722_ADC1_CONTROL_REG, ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Wait for conversion */
|
|
do {
|
|
ret = as3722_read(as3722, AS3722_ADC1_MSB_RESULT_REG,
|
|
&val);
|
|
if (ret < 0) {
|
|
dev_err(ptherm_zone->dev,
|
|
"Reg 0x%02x read failed: %d\n",
|
|
AS3722_ADC1_MSB_RESULT_REG, ret);
|
|
return ret;
|
|
}
|
|
if (!(val & AS3722_ADC1_CONV_NOTREADY))
|
|
break;
|
|
udelay(100);
|
|
} while (--timeout > 0);
|
|
if (!timeout)
|
|
continue;
|
|
|
|
/* ADC Result = {MSB[6:0], LSB[3:0]} */
|
|
result = ((val & AS3722_ADC_MSB_VAL_MASK) << 3);
|
|
ret = as3722_read(as3722, AS3722_ADC1_LSB_RESULT_REG, &val);
|
|
if (ret < 0) {
|
|
dev_err(ptherm_zone->dev,
|
|
"Reg 0x%02x read failed: %d\n",
|
|
AS3722_ADC1_LSB_RESULT_REG, ret);
|
|
return ret;
|
|
}
|
|
result |= val & AS3722_ADC_LSB_VAL_MASK;
|
|
|
|
/* Temp (mC) = 326500 - (ADC result * 3734) / 10 */
|
|
*temp = 326500 - (result * 3734) / 10;
|
|
if (*temp <= AS3722_MAX_TEMP)
|
|
return 0;
|
|
} while (--tries > 0);
|
|
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
static int as3722_therm_get_sd0_temp(void *dev, long *temp)
|
|
{
|
|
struct as3722_therm_zone *ptherm_zone = dev;
|
|
int ret;
|
|
|
|
ret = as3722_therm_get_temp(ptherm_zone, temp,
|
|
ptherm_zone->adc_ch[SD0]);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int as3722_therm_get_sd1_temp(void *dev, long *temp)
|
|
{
|
|
struct as3722_therm_zone *ptherm_zone = dev;
|
|
int ret;
|
|
|
|
ret = as3722_therm_get_temp(ptherm_zone, temp,
|
|
ptherm_zone->adc_ch[SD1]);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int as3722_therm_get_sd6_temp(void *dev, long *temp)
|
|
{
|
|
struct as3722_therm_zone *ptherm_zone = dev;
|
|
int ret;
|
|
|
|
ret = as3722_therm_get_temp(ptherm_zone, temp,
|
|
ptherm_zone->adc_ch[SD6]);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void as3722_therm_update_limits(struct as3722_therm_zone *ptherm_zone,
|
|
int sensor)
|
|
{
|
|
long high_temp = AS3722_MAX_TEMP;
|
|
long trip_temp;
|
|
int i;
|
|
|
|
for (i = 0; i < ptherm_zone->tzd[sensor]->trips; i++) {
|
|
ptherm_zone->tzd[sensor]->ops->get_trip_temp(
|
|
ptherm_zone->tzd[sensor], i, &trip_temp);
|
|
high_temp = min(high_temp, trip_temp);
|
|
}
|
|
ptherm_zone->high_limit[sensor] = high_temp;
|
|
}
|
|
|
|
static irqreturn_t as3722_thermal_irq(int irq, void *data)
|
|
{
|
|
struct as3722_therm_zone *ptherm_zone = data;
|
|
int i;
|
|
|
|
for (i = 0; i < SD_MAX; i++) {
|
|
if (irq != ptherm_zone->irq[i])
|
|
continue;
|
|
if (thermal_zone_get_temp(ptherm_zone->tzd[i],
|
|
&ptherm_zone->cur_temp[i]))
|
|
ptherm_zone->cur_temp[i] = AS3722_ALARM_TEMP;
|
|
|
|
if (ptherm_zone->cur_temp[i] >= ptherm_zone->high_limit[i]) {
|
|
/*
|
|
* Interrupt is disabled to stop flooding of
|
|
* interrupts due to high temp alarm.
|
|
*/
|
|
mutex_lock(&ptherm_zone->update_lock);
|
|
disable_irq_nosync(irq);
|
|
ptherm_zone->irq_masked[i] = true;
|
|
mutex_unlock(&ptherm_zone->update_lock);
|
|
thermal_zone_device_update(ptherm_zone->tzd[i]);
|
|
/* When the timer expires, interrupt is re-enabled */
|
|
mod_delayed_work(system_freezable_wq,
|
|
&ptherm_zone->timeout_work,
|
|
jiffies + msecs_to_jiffies(200));
|
|
dev_info(ptherm_zone->dev,
|
|
"Thermal alarm, current temp %ldmC\n",
|
|
ptherm_zone->cur_temp[i]);
|
|
}
|
|
}
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
struct as3722_therm_zone *as3722_therm_get_pdata(struct platform_device *pdev)
|
|
{
|
|
struct as3722_therm_zone *ptherm_zone = NULL;
|
|
struct device_node *np = pdev->dev.parent->of_node;
|
|
|
|
if (!np)
|
|
return ptherm_zone;
|
|
else
|
|
ptherm_zone = devm_kzalloc(&pdev->dev, sizeof(*ptherm_zone),
|
|
GFP_KERNEL);
|
|
|
|
if (!ptherm_zone) {
|
|
dev_err(&pdev->dev, "No available free memory\n");
|
|
return ptherm_zone;
|
|
}
|
|
|
|
if (of_property_read_u32(np, "ams,sd0-adc-ch",
|
|
&ptherm_zone->adc_ch[SD0]))
|
|
goto err;
|
|
if (of_property_read_u32(np, "ams,sd1-adc-ch",
|
|
&ptherm_zone->adc_ch[SD1]))
|
|
goto err;
|
|
if (of_property_read_u32(np, "ams,sd6-adc-ch",
|
|
&ptherm_zone->adc_ch[SD6]))
|
|
goto err;
|
|
|
|
return ptherm_zone;
|
|
err:
|
|
return NULL;
|
|
}
|
|
|
|
static int as3722_thermal_probe(struct platform_device *pdev)
|
|
{
|
|
struct as3722 *as3722 = dev_get_drvdata(pdev->dev.parent);
|
|
struct as3722_therm_zone *ptherm_zone;
|
|
int i, ret;
|
|
|
|
ptherm_zone = as3722_therm_get_pdata(pdev);
|
|
if (!ptherm_zone)
|
|
return -ENOMEM;
|
|
|
|
ptherm_zone->dev = &pdev->dev;
|
|
ptherm_zone->as3722 = as3722;
|
|
mutex_init(&ptherm_zone->update_lock);
|
|
INIT_DELAYED_WORK(&ptherm_zone->timeout_work, as3722_timeout_work);
|
|
platform_set_drvdata(pdev, ptherm_zone);
|
|
ptherm_zone->tzd[SD0] =
|
|
thermal_zone_of_sensor_register(as3722->dev, SD0, ptherm_zone,
|
|
as3722_therm_get_sd0_temp,
|
|
NULL);
|
|
if (IS_ERR(ptherm_zone->tzd[SD0])) {
|
|
ret = PTR_ERR(ptherm_zone->tzd[SD0]);
|
|
goto err;
|
|
}
|
|
ptherm_zone->tzd[SD1] =
|
|
thermal_zone_of_sensor_register(as3722->dev, SD1, ptherm_zone,
|
|
as3722_therm_get_sd1_temp,
|
|
NULL);
|
|
if (IS_ERR(ptherm_zone->tzd[SD1])) {
|
|
ret = PTR_ERR(ptherm_zone->tzd[SD1]);
|
|
goto err;
|
|
}
|
|
ptherm_zone->tzd[SD6] =
|
|
thermal_zone_of_sensor_register(as3722->dev, SD6, ptherm_zone,
|
|
as3722_therm_get_sd6_temp,
|
|
NULL);
|
|
if (IS_ERR(ptherm_zone->tzd[SD6])) {
|
|
ret = PTR_ERR(ptherm_zone->tzd[SD6]);
|
|
goto err;
|
|
}
|
|
|
|
for (i = 0; i < SD_MAX; i++) {
|
|
as3722_therm_update_limits(ptherm_zone, i);
|
|
thermal_update_governor(ptherm_zone->tzd[i], "pid_thermal_gov");
|
|
if (thermal_zone_get_temp(ptherm_zone->tzd[i],
|
|
&ptherm_zone->cur_temp[i]))
|
|
ptherm_zone->cur_temp[i] = 25000;
|
|
ptherm_zone->irq[i] = platform_get_irq(pdev, i);
|
|
ret = devm_request_threaded_irq(&pdev->dev, ptherm_zone->irq[i],
|
|
NULL, as3722_thermal_irq, IRQF_ONESHOT,
|
|
dev_name(&pdev->dev), ptherm_zone);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "Request irq %d failed: %d\nn",
|
|
ptherm_zone->irq[i], ret);
|
|
goto err;
|
|
}
|
|
}
|
|
return 0;
|
|
|
|
err:
|
|
cancel_delayed_work_sync(&ptherm_zone->timeout_work);
|
|
for (i = 0; i < SD_MAX; i++) {
|
|
if (!IS_ERR(ptherm_zone->tzd[i]))
|
|
thermal_zone_of_sensor_unregister(as3722->dev,
|
|
ptherm_zone->tzd[i]);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int as3722_thermal_remove(struct platform_device *pdev)
|
|
{
|
|
struct as3722_therm_zone *ptherm_zone = platform_get_drvdata(pdev);
|
|
struct as3722 *as3722 = dev_get_drvdata(pdev->dev.parent);
|
|
int i;
|
|
|
|
cancel_delayed_work_sync(&ptherm_zone->timeout_work);
|
|
for (i = 0; i < SD_MAX; i++)
|
|
thermal_zone_of_sensor_unregister(as3722->dev,
|
|
ptherm_zone->tzd[i]);
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver as3722_thermal_driver = {
|
|
.probe = as3722_thermal_probe,
|
|
.remove = as3722_thermal_remove,
|
|
.driver = {
|
|
.name = "as3722-thermal",
|
|
.owner = THIS_MODULE,
|
|
},
|
|
};
|
|
module_platform_driver(as3722_thermal_driver);
|
|
|
|
MODULE_DESCRIPTION("AS3722 Thermal driver");
|
|
MODULE_AUTHOR("Bibek Basu <bbasu@nvidia.com>");
|
|
MODULE_ALIAS("platform:as3722-thermal");
|
|
MODULE_LICENSE("GPL v2");
|