forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
313 lines
6.8 KiB
313 lines
6.8 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* |
|
* ST Thermal Sensor Driver core routines |
|
* Author: Ajit Pal Singh <[email protected]> |
|
* |
|
* Copyright (C) 2003-2014 STMicroelectronics (R&D) Limited |
|
*/ |
|
|
|
#include <linux/clk.h> |
|
#include <linux/module.h> |
|
#include <linux/of.h> |
|
#include <linux/of_device.h> |
|
|
|
#include "st_thermal.h" |
|
|
|
/* The Thermal Framework expects millidegrees */ |
|
#define mcelsius(temp) ((temp) * 1000) |
|
|
|
/* |
|
* Function to allocate regfields which are common |
|
* between syscfg and memory mapped based sensors |
|
*/ |
|
static int st_thermal_alloc_regfields(struct st_thermal_sensor *sensor) |
|
{ |
|
struct device *dev = sensor->dev; |
|
struct regmap *regmap = sensor->regmap; |
|
const struct reg_field *reg_fields = sensor->cdata->reg_fields; |
|
|
|
sensor->dcorrect = devm_regmap_field_alloc(dev, regmap, |
|
reg_fields[DCORRECT]); |
|
|
|
sensor->overflow = devm_regmap_field_alloc(dev, regmap, |
|
reg_fields[OVERFLOW]); |
|
|
|
sensor->temp_data = devm_regmap_field_alloc(dev, regmap, |
|
reg_fields[DATA]); |
|
|
|
if (IS_ERR(sensor->dcorrect) || |
|
IS_ERR(sensor->overflow) || |
|
IS_ERR(sensor->temp_data)) { |
|
dev_err(dev, "failed to allocate common regfields\n"); |
|
return -EINVAL; |
|
} |
|
|
|
return sensor->ops->alloc_regfields(sensor); |
|
} |
|
|
|
static int st_thermal_sensor_on(struct st_thermal_sensor *sensor) |
|
{ |
|
int ret; |
|
struct device *dev = sensor->dev; |
|
|
|
ret = clk_prepare_enable(sensor->clk); |
|
if (ret) { |
|
dev_err(dev, "failed to enable clk\n"); |
|
return ret; |
|
} |
|
|
|
ret = sensor->ops->power_ctrl(sensor, POWER_ON); |
|
if (ret) { |
|
dev_err(dev, "failed to power on sensor\n"); |
|
clk_disable_unprepare(sensor->clk); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static int st_thermal_sensor_off(struct st_thermal_sensor *sensor) |
|
{ |
|
int ret; |
|
|
|
ret = sensor->ops->power_ctrl(sensor, POWER_OFF); |
|
if (ret) |
|
return ret; |
|
|
|
clk_disable_unprepare(sensor->clk); |
|
|
|
return 0; |
|
} |
|
|
|
static int st_thermal_calibration(struct st_thermal_sensor *sensor) |
|
{ |
|
int ret; |
|
unsigned int val; |
|
struct device *dev = sensor->dev; |
|
|
|
/* Check if sensor calibration data is already written */ |
|
ret = regmap_field_read(sensor->dcorrect, &val); |
|
if (ret) { |
|
dev_err(dev, "failed to read calibration data\n"); |
|
return ret; |
|
} |
|
|
|
if (!val) { |
|
/* |
|
* Sensor calibration value not set by bootloader, |
|
* default calibration data to be used |
|
*/ |
|
ret = regmap_field_write(sensor->dcorrect, |
|
sensor->cdata->calibration_val); |
|
if (ret) |
|
dev_err(dev, "failed to set calibration data\n"); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
/* Callback to get temperature from HW*/ |
|
static int st_thermal_get_temp(struct thermal_zone_device *th, int *temperature) |
|
{ |
|
struct st_thermal_sensor *sensor = th->devdata; |
|
struct device *dev = sensor->dev; |
|
unsigned int temp; |
|
unsigned int overflow; |
|
int ret; |
|
|
|
ret = regmap_field_read(sensor->overflow, &overflow); |
|
if (ret) |
|
return ret; |
|
if (overflow) |
|
return -EIO; |
|
|
|
ret = regmap_field_read(sensor->temp_data, &temp); |
|
if (ret) |
|
return ret; |
|
|
|
temp += sensor->cdata->temp_adjust_val; |
|
temp = mcelsius(temp); |
|
|
|
dev_dbg(dev, "temperature: %d\n", temp); |
|
|
|
*temperature = temp; |
|
|
|
return 0; |
|
} |
|
|
|
static int st_thermal_get_trip_type(struct thermal_zone_device *th, |
|
int trip, enum thermal_trip_type *type) |
|
{ |
|
struct st_thermal_sensor *sensor = th->devdata; |
|
struct device *dev = sensor->dev; |
|
|
|
switch (trip) { |
|
case 0: |
|
*type = THERMAL_TRIP_CRITICAL; |
|
break; |
|
default: |
|
dev_err(dev, "invalid trip point\n"); |
|
return -EINVAL; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int st_thermal_get_trip_temp(struct thermal_zone_device *th, |
|
int trip, int *temp) |
|
{ |
|
struct st_thermal_sensor *sensor = th->devdata; |
|
struct device *dev = sensor->dev; |
|
|
|
switch (trip) { |
|
case 0: |
|
*temp = mcelsius(sensor->cdata->crit_temp); |
|
break; |
|
default: |
|
dev_err(dev, "Invalid trip point\n"); |
|
return -EINVAL; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static struct thermal_zone_device_ops st_tz_ops = { |
|
.get_temp = st_thermal_get_temp, |
|
.get_trip_type = st_thermal_get_trip_type, |
|
.get_trip_temp = st_thermal_get_trip_temp, |
|
}; |
|
|
|
int st_thermal_register(struct platform_device *pdev, |
|
const struct of_device_id *st_thermal_of_match) |
|
{ |
|
struct st_thermal_sensor *sensor; |
|
struct device *dev = &pdev->dev; |
|
struct device_node *np = dev->of_node; |
|
const struct of_device_id *match; |
|
|
|
int polling_delay; |
|
int ret; |
|
|
|
if (!np) { |
|
dev_err(dev, "device tree node not found\n"); |
|
return -EINVAL; |
|
} |
|
|
|
sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); |
|
if (!sensor) |
|
return -ENOMEM; |
|
|
|
sensor->dev = dev; |
|
|
|
match = of_match_device(st_thermal_of_match, dev); |
|
if (!(match && match->data)) |
|
return -EINVAL; |
|
|
|
sensor->cdata = match->data; |
|
if (!sensor->cdata->ops) |
|
return -EINVAL; |
|
|
|
sensor->ops = sensor->cdata->ops; |
|
|
|
ret = (sensor->ops->regmap_init)(sensor); |
|
if (ret) |
|
return ret; |
|
|
|
ret = st_thermal_alloc_regfields(sensor); |
|
if (ret) |
|
return ret; |
|
|
|
sensor->clk = devm_clk_get(dev, "thermal"); |
|
if (IS_ERR(sensor->clk)) { |
|
dev_err(dev, "failed to fetch clock\n"); |
|
return PTR_ERR(sensor->clk); |
|
} |
|
|
|
if (sensor->ops->register_enable_irq) { |
|
ret = sensor->ops->register_enable_irq(sensor); |
|
if (ret) |
|
return ret; |
|
} |
|
|
|
ret = st_thermal_sensor_on(sensor); |
|
if (ret) |
|
return ret; |
|
|
|
ret = st_thermal_calibration(sensor); |
|
if (ret) |
|
goto sensor_off; |
|
|
|
polling_delay = sensor->ops->register_enable_irq ? 0 : 1000; |
|
|
|
sensor->thermal_dev = |
|
thermal_zone_device_register(dev_name(dev), 1, 0, sensor, |
|
&st_tz_ops, NULL, 0, polling_delay); |
|
if (IS_ERR(sensor->thermal_dev)) { |
|
dev_err(dev, "failed to register thermal zone device\n"); |
|
ret = PTR_ERR(sensor->thermal_dev); |
|
goto sensor_off; |
|
} |
|
ret = thermal_zone_device_enable(sensor->thermal_dev); |
|
if (ret) |
|
goto tzd_unregister; |
|
|
|
platform_set_drvdata(pdev, sensor); |
|
|
|
return 0; |
|
|
|
tzd_unregister: |
|
thermal_zone_device_unregister(sensor->thermal_dev); |
|
sensor_off: |
|
st_thermal_sensor_off(sensor); |
|
|
|
return ret; |
|
} |
|
EXPORT_SYMBOL_GPL(st_thermal_register); |
|
|
|
int st_thermal_unregister(struct platform_device *pdev) |
|
{ |
|
struct st_thermal_sensor *sensor = platform_get_drvdata(pdev); |
|
|
|
st_thermal_sensor_off(sensor); |
|
thermal_zone_device_unregister(sensor->thermal_dev); |
|
|
|
return 0; |
|
} |
|
EXPORT_SYMBOL_GPL(st_thermal_unregister); |
|
|
|
#ifdef CONFIG_PM_SLEEP |
|
static int st_thermal_suspend(struct device *dev) |
|
{ |
|
struct st_thermal_sensor *sensor = dev_get_drvdata(dev); |
|
|
|
return st_thermal_sensor_off(sensor); |
|
} |
|
|
|
static int st_thermal_resume(struct device *dev) |
|
{ |
|
int ret; |
|
struct st_thermal_sensor *sensor = dev_get_drvdata(dev); |
|
|
|
ret = st_thermal_sensor_on(sensor); |
|
if (ret) |
|
return ret; |
|
|
|
ret = st_thermal_calibration(sensor); |
|
if (ret) |
|
return ret; |
|
|
|
if (sensor->ops->enable_irq) { |
|
ret = sensor->ops->enable_irq(sensor); |
|
if (ret) |
|
return ret; |
|
} |
|
|
|
return 0; |
|
} |
|
#endif |
|
|
|
SIMPLE_DEV_PM_OPS(st_thermal_pm_ops, st_thermal_suspend, st_thermal_resume); |
|
EXPORT_SYMBOL_GPL(st_thermal_pm_ops); |
|
|
|
MODULE_AUTHOR("STMicroelectronics (R&D) Limited <[email protected]>"); |
|
MODULE_DESCRIPTION("STMicroelectronics STi SoC Thermal Sensor Driver"); |
|
MODULE_LICENSE("GPL v2");
|
|
|