Merge "drivers: thermal: Add a snapshot of bcl soc driver"

This commit is contained in:
qctecmdr 2019-12-18 03:32:56 -08:00 • committed by Gerrit - the friendly Code Review server
commit 1966f732fa
7 changed files with 1948 additions and 0 deletions

View file

@ -21,6 +21,35 @@ config QCOM_SPMI_TEMP_ALARM
real time die temperature if an ADC is present or an estimate of the
temperature based upon the over temperature stage value.
config QTI_QMI_SENSOR
tristate "QTI QMI sensor driver"
depends on QCOM_QMI_HELPERS && THERMAL_OF && QTI_THERMAL
help
This enables to list the QTI remote subsystem temperature sensors.
This driver can read the temperature of the remote sensor.
These sensors can take thresholds and notify the thermal
framework when the threshold is reached.
config QTI_BCL_PMIC5
tristate "BCL driver for BCL peripherals in PMIC5"
depends on SPMI && THERMAL_OF && QTI_THERMAL
help
This driver provides routines to configure and monitor the BCL
PMIC peripheral. This driver registers the battery current and
voltage sensors with the thermal core framework and can take
threshold input and notify the thermal core when the threshold is
reached.
config QTI_BCL_SOC_DRIVER
tristate "QTI Battery state of charge sensor driver"
depends on THERMAL_OF && POWER_SUPPLY && QTI_THERMAL
help
This driver registers battery state of charge as a sensor with
thermal framework. This sensor can monitor for state of charge
thresholds and notify the thermal framework when the thresholds
are reached and cleared. This will help to monitor and apply any
mitigation when state of charge goes below a certain threshold.
config QTI_CPU_ISOLATE_COOLING_DEVICE
tristate "QTI CPU Isolate cooling devices"
depends on THERMAL_OF && QTI_THERMAL

View file

@ -4,4 +4,8 @@ obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o
qcom_tsens-y += tsens.o tsens-common.o tsens-v0_1.o \
tsens-8960.o tsens-v2.o tsens-v1.o
obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o
obj-$(CONFIG_QTI_QMI_SENSOR) += qti_qmi_sensor.o
qti_qmi_sensor-y += thermal_sensor_service_v01.o qmi_sensors.o
obj-$(CONFIG_QTI_CPU_ISOLATE_COOLING_DEVICE) += cpu_isolate.o
obj-$(CONFIG_QTI_BCL_PMIC5) += bcl_pmic5.o
obj-$(CONFIG_QTI_BCL_SOC_DRIVER) += bcl_soc.o

View file

@ -0,0 +1,667 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2018-2019, The Linux Foundation. All rights reserved.
*/
#define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/kernel.h>
#include <linux/regmap.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/spmi.h>
#include <linux/platform_device.h>
#include <linux/mutex.h>
#include <linux/thermal.h>
#include "../thermal_core.h"
#define BCL_DRIVER_NAME "bcl_pmic5"
#define BCL_MONITOR_EN 0x46
#define BCL_IRQ_STATUS 0x08
#define BCL_IBAT_HIGH 0x4B
#define BCL_IBAT_TOO_HIGH 0x4C
#define BCL_IBAT_READ 0x86
#define BCL_IBAT_SCALING_UA 78127
#define BCL_VBAT_READ 0x76
#define BCL_VBAT_ADC_LOW 0x48
#define BCL_VBAT_COMP_LOW 0x49
#define BCL_VBAT_COMP_TLOW 0x4A
#define BCL_IRQ_L0 0x1
#define BCL_IRQ_L1 0x2
#define BCL_IRQ_L2 0x4
#define BCL_VBAT_SCALING_UV 49827
#define BCL_VBAT_NO_READING 127
#define BCL_VBAT_BASE_MV 2000
#define BCL_VBAT_INC_MV 25
#define BCL_VBAT_MAX_MV 3600
#define BCL_VBAT_THRESH_BASE 2250
#define MAX_PERPH_COUNT 2
enum bcl_dev_type {
BCL_IBAT_LVL0,
BCL_IBAT_LVL1,
BCL_VBAT_LVL0,
BCL_VBAT_LVL1,
BCL_VBAT_LVL2,
BCL_LVL0,
BCL_LVL1,
BCL_LVL2,
BCL_TYPE_MAX,
};
static char bcl_int_names[BCL_TYPE_MAX][25] = {
"bcl-ibat-lvl0",
"bcl-ibat-lvl1",
"bcl-vbat-lvl0",
"bcl-vbat-lvl1",
"bcl-vbat-lvl2",
"bcl-lvl0",
"bcl-lvl1",
"bcl-lvl2",
};
struct bcl_device;
struct bcl_peripheral_data {
int irq_num;
int status_bit_idx;
long trip_thresh;
int last_val;
struct mutex state_trans_lock;
bool irq_enabled;
enum bcl_dev_type type;
struct thermal_zone_of_device_ops ops;
struct thermal_zone_device *tz_dev;
struct bcl_device *dev;
};
struct bcl_device {
struct device *dev;
struct regmap *regmap;
uint16_t fg_bcl_addr;
struct bcl_peripheral_data param[BCL_TYPE_MAX];
};
static struct bcl_device *bcl_devices[MAX_PERPH_COUNT];
static int bcl_device_ct;
static bool ibat_use_qg_adc;
static int bcl_read_register(struct bcl_device *bcl_perph, int16_t reg_offset,
unsigned int *data)
{
int ret = 0;
if (!bcl_perph) {
pr_err("BCL device not initialized\n");
return -EINVAL;
}
ret = regmap_read(bcl_perph->regmap,
(bcl_perph->fg_bcl_addr + reg_offset),
data);
if (ret < 0)
pr_err("Error reading register 0x%04x err:%d\n",
bcl_perph->fg_bcl_addr + reg_offset, ret);
else
pr_debug("Read register:0x%04x value:0x%02x\n",
bcl_perph->fg_bcl_addr + reg_offset,
*data);
return ret;
}
static int bcl_write_register(struct bcl_device *bcl_perph,
int16_t reg_offset, uint8_t data)
{
int ret = 0;
uint8_t *write_buf = &data;
uint16_t base;
if (!bcl_perph) {
pr_err("BCL device not initialized\n");
return -EINVAL;
}
base = bcl_perph->fg_bcl_addr;
ret = regmap_write(bcl_perph->regmap, (base + reg_offset), *write_buf);
if (ret < 0) {
pr_err("Error reading register:0x%04x val:0x%02x err:%d\n",
base + reg_offset, data, ret);
return ret;
}
pr_debug("wrote 0x%02x to 0x%04x\n", data, base + reg_offset);
return ret;
}
static void convert_adc_to_vbat_thresh_val(int *val)
{
/*
* Threshold register is bit shifted from ADC MSB.
* So the scaling factor is half.
*/
*val = (*val * BCL_VBAT_SCALING_UV) / 2000;
}
static void convert_adc_to_vbat_val(int *val)
{
*val = (*val * BCL_VBAT_SCALING_UV) / 1000;
}
static void convert_ibat_to_adc_val(int *val)
{
/*
* Threshold register is bit shifted from ADC MSB.
* So the scaling factor is half.
*/
if (ibat_use_qg_adc)
*val = (int)div_s64(*val * 2000 * 2, BCL_IBAT_SCALING_UA);
else
*val = (int)div_s64(*val * 2000, BCL_IBAT_SCALING_UA);
}
static void convert_adc_to_ibat_val(int *val)
{
/* Scaling factor will be half if ibat_use_qg_adc is true */
if (ibat_use_qg_adc)
*val = (int)div_s64(*val * BCL_IBAT_SCALING_UA, 2 * 1000);
else
*val = (int)div_s64(*val * BCL_IBAT_SCALING_UA, 1000);
}
static int bcl_set_ibat(void *data, int low, int high)
{
int ret = 0, ibat_ua, thresh_value;
int8_t val = 0;
int16_t addr;
struct bcl_peripheral_data *bat_data =
(struct bcl_peripheral_data *)data;
mutex_lock(&bat_data->state_trans_lock);
thresh_value = high;
if (bat_data->trip_thresh == thresh_value)
goto set_trip_exit;
if (bat_data->irq_num && bat_data->irq_enabled) {
disable_irq_nosync(bat_data->irq_num);
bat_data->irq_enabled = false;
}
if (thresh_value == INT_MAX) {
bat_data->trip_thresh = thresh_value;
goto set_trip_exit;
}
ibat_ua = thresh_value;
convert_ibat_to_adc_val(&thresh_value);
val = (int8_t)thresh_value;
switch (bat_data->type) {
case BCL_IBAT_LVL0:
addr = BCL_IBAT_HIGH;
pr_debug("ibat high threshold:%d mA ADC:0x%02x\n",
ibat_ua, val);
break;
case BCL_IBAT_LVL1:
addr = BCL_IBAT_TOO_HIGH;
pr_debug("ibat too high threshold:%d mA ADC:0x%02x\n",
ibat_ua, val);
break;
default:
goto set_trip_exit;
}
ret = bcl_write_register(bat_data->dev, addr, val);
if (ret)
goto set_trip_exit;
bat_data->trip_thresh = ibat_ua;
if (bat_data->irq_num && !bat_data->irq_enabled) {
enable_irq(bat_data->irq_num);
bat_data->irq_enabled = true;
}
set_trip_exit:
mutex_unlock(&bat_data->state_trans_lock);
return ret;
}
static int bcl_read_ibat(void *data, int *adc_value)
{
int ret = 0;
unsigned int val = 0;
struct bcl_peripheral_data *bat_data =
(struct bcl_peripheral_data *)data;
*adc_value = val;
ret = bcl_read_register(bat_data->dev, BCL_IBAT_READ, &val);
if (ret)
goto bcl_read_exit;
/* IBat ADC reading is in 2's compliment form */
*adc_value = sign_extend32(val, 7);
if (val == 0) {
/*
* The sensor sometime can read a value 0 if there is
* consequtive reads
*/
*adc_value = bat_data->last_val;
} else {
convert_adc_to_ibat_val(adc_value);
bat_data->last_val = *adc_value;
}
pr_debug("ibat:%d mA ADC:0x%02x\n", bat_data->last_val, val);
bcl_read_exit:
return ret;
}
static int bcl_get_vbat_trip(struct thermal_zone_device *tzd,
int type, int *trip)
{
int ret = 0;
unsigned int val = 0;
struct bcl_peripheral_data *bat_data =
(struct bcl_peripheral_data *)tzd->devdata;
int16_t addr;
*trip = 0;
switch (type + BCL_VBAT_LVL0) {
case BCL_VBAT_LVL0:
addr = BCL_VBAT_ADC_LOW;
break;
case BCL_VBAT_LVL1:
addr = BCL_VBAT_COMP_LOW;
break;
case BCL_VBAT_LVL2:
addr = BCL_VBAT_COMP_TLOW;
break;
default:
return -ENODEV;
}
ret = bcl_read_register(bat_data->dev, addr, &val);
if (ret)
return ret;
if (addr == BCL_VBAT_ADC_LOW) {
*trip = val;
convert_adc_to_vbat_thresh_val(trip);
pr_debug("vbat trip: %d mV ADC:0x%02x\n", *trip, val);
} else {
*trip = BCL_VBAT_THRESH_BASE + val * 25;
if (*trip > BCL_VBAT_MAX_MV)
*trip = BCL_VBAT_MAX_MV;
pr_debug("vbat-%s-low trip: %d mV ADC:0x%02x\n",
(addr == BCL_VBAT_COMP_LOW) ?
"too" : "critical",
*trip, val);
}
return 0;
}
static int bcl_read_vbat_tz(struct thermal_zone_device *tzd, int *adc_value)
{
int ret = 0;
unsigned int val = 0;
struct bcl_peripheral_data *bat_data =
(struct bcl_peripheral_data *)tzd->devdata;
*adc_value = val;
ret = bcl_read_register(bat_data->dev, BCL_VBAT_READ, &val);
if (ret)
goto bcl_read_exit;
*adc_value = val;
if (*adc_value == BCL_VBAT_NO_READING) {
*adc_value = bat_data->last_val;
} else {
convert_adc_to_vbat_val(adc_value);
bat_data->last_val = *adc_value;
}
pr_debug("vbat:%d mv\n", bat_data->last_val);
bcl_read_exit:
return ret;
}
static int bcl_read_vbat_type(struct thermal_zone_device *tzd, int trip,
enum thermal_trip_type *type)
{
*type = THERMAL_TRIP_PASSIVE;
return 0;
}
static struct thermal_zone_device_ops vbat_tzd_ops = {
.get_temp = bcl_read_vbat_tz,
.get_trip_temp = bcl_get_vbat_trip,
.get_trip_type = bcl_read_vbat_type,
};
static struct thermal_zone_params vbat_tzp = {
.governor_name = "step_wise",
.no_hwmon = true,
.num_tbps = 0,
.tbp = NULL,
.sustainable_power = 0,
.k_po = 0,
.k_pu = 0,
.k_i = 0,
.k_d = 0,
.integral_cutoff = 0,
.slope = 1,
.offset = 0
};
static int bcl_set_lbat(void *data, int low, int high)
{
struct bcl_peripheral_data *bat_data =
(struct bcl_peripheral_data *)data;
mutex_lock(&bat_data->state_trans_lock);
if (high == INT_MAX &&
bat_data->irq_num && bat_data->irq_enabled) {
disable_irq_nosync(bat_data->irq_num);
bat_data->irq_enabled = false;
pr_debug("lbat[%d]: disable irq:%d\n",
bat_data->type,
bat_data->irq_num);
} else if (high != INT_MAX &&
bat_data->irq_num && !bat_data->irq_enabled) {
enable_irq(bat_data->irq_num);
bat_data->irq_enabled = true;
pr_debug("lbat[%d]: enable irq:%d\n",
bat_data->type,
bat_data->irq_num);
}
mutex_unlock(&bat_data->state_trans_lock);
return 0;
}
static int bcl_read_lbat(void *data, int *adc_value)
{
int ret = 0;
unsigned int val = 0;
struct bcl_peripheral_data *bat_data =
(struct bcl_peripheral_data *)data;
struct bcl_device *bcl_perph = bat_data->dev;
*adc_value = val;
ret = bcl_read_register(bcl_perph, BCL_IRQ_STATUS, &val);
if (ret)
goto bcl_read_exit;
switch (bat_data->type) {
case BCL_LVL0:
*adc_value = val & BCL_IRQ_L0;
break;
case BCL_LVL1:
*adc_value = val & BCL_IRQ_L1;
break;
case BCL_LVL2:
*adc_value = val & BCL_IRQ_L2;
break;
default:
pr_err("Invalid sensor type:%d\n", bat_data->type);
ret = -ENODEV;
goto bcl_read_exit;
}
bat_data->last_val = *adc_value;
pr_debug("lbat:%d val:%d\n", bat_data->type,
bat_data->last_val);
bcl_read_exit:
return ret;
}
static irqreturn_t bcl_handle_irq(int irq, void *data)
{
struct bcl_peripheral_data *perph_data =
(struct bcl_peripheral_data *)data;
unsigned int irq_status = 0;
struct bcl_device *bcl_perph;
bcl_perph = perph_data->dev;
bcl_read_register(bcl_perph, BCL_IRQ_STATUS, &irq_status);
if (irq_status & perph_data->status_bit_idx) {
pr_debug("Irq:%d triggered for bcl type:%s. status:%u\n",
irq, bcl_int_names[perph_data->type],
irq_status);
of_thermal_handle_trip_temp(perph_data->dev->dev,
perph_data->tz_dev,
perph_data->status_bit_idx);
}
return IRQ_HANDLED;
}
static int bcl_get_devicetree_data(struct platform_device *pdev,
struct bcl_device *bcl_perph)
{
int ret = 0;
const __be32 *prop = NULL;
struct device_node *dev_node = pdev->dev.of_node;
prop = of_get_address(dev_node, 0, NULL, NULL);
if (prop) {
bcl_perph->fg_bcl_addr = be32_to_cpu(*prop);
pr_debug("fg_bcl@%04x\n", bcl_perph->fg_bcl_addr);
} else {
dev_err(&pdev->dev, "No fg_bcl registers found\n");
return -ENODEV;
}
ibat_use_qg_adc = of_property_read_bool(dev_node,
"qcom,ibat-use-qg-adc-5a");
return ret;
}
static void bcl_fetch_trip(struct platform_device *pdev, enum bcl_dev_type type,
struct bcl_peripheral_data *data,
irqreturn_t (*handle)(int, void *))
{
int ret = 0, irq_num = 0;
char *int_name = bcl_int_names[type];
mutex_lock(&data->state_trans_lock);
data->irq_num = 0;
data->irq_enabled = false;
irq_num = platform_get_irq_byname(pdev, int_name);
if (irq_num > 0 && handle) {
ret = devm_request_threaded_irq(&pdev->dev,
irq_num, NULL, handle,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
int_name, data);
if (ret) {
dev_err(&pdev->dev,
"Error requesting trip irq. err:%d\n",
ret);
mutex_unlock(&data->state_trans_lock);
return;
}
disable_irq_nosync(irq_num);
data->irq_num = irq_num;
} else if (irq_num > 0 && !handle) {
disable_irq_nosync(irq_num);
data->irq_num = irq_num;
}
mutex_unlock(&data->state_trans_lock);
}
static void bcl_vbat_init(struct platform_device *pdev,
enum bcl_dev_type type, struct bcl_device *bcl_perph)
{
struct bcl_peripheral_data *vbat = &bcl_perph->param[type];
mutex_init(&vbat->state_trans_lock);
vbat->dev = bcl_perph;
vbat->irq_num = 0;
vbat->irq_enabled = false;
vbat->tz_dev = thermal_zone_device_register("vbat", 3, 0, vbat,
&vbat_tzd_ops, &vbat_tzp, 0, 0);
if (IS_ERR(vbat->tz_dev)) {
pr_debug("vbat[%s] register failed. err:%ld\n",
bcl_int_names[type],
PTR_ERR(vbat->tz_dev));
vbat->tz_dev = NULL;
return;
}
thermal_zone_device_update(vbat->tz_dev, THERMAL_DEVICE_UP);
}
static void bcl_probe_vbat(struct platform_device *pdev,
struct bcl_device *bcl_perph)
{
bcl_vbat_init(pdev, BCL_VBAT_LVL0, bcl_perph);
}
static void bcl_ibat_init(struct platform_device *pdev,
enum bcl_dev_type type, struct bcl_device *bcl_perph)
{
struct bcl_peripheral_data *ibat = &bcl_perph->param[type];
mutex_init(&ibat->state_trans_lock);
ibat->type = type;
ibat->dev = bcl_perph;
ibat->irq_num = 0;
ibat->irq_enabled = false;
ibat->ops.get_temp = bcl_read_ibat;
ibat->ops.set_trips = bcl_set_ibat;
ibat->tz_dev = thermal_zone_of_sensor_register(&pdev->dev,
type, ibat, &ibat->ops);
if (IS_ERR(ibat->tz_dev)) {
pr_debug("ibat:[%s] register failed. err:%ld\n",
bcl_int_names[type],
PTR_ERR(ibat->tz_dev));
ibat->tz_dev = NULL;
return;
}
thermal_zone_device_update(ibat->tz_dev, THERMAL_DEVICE_UP);
}
static void bcl_probe_ibat(struct platform_device *pdev,
struct bcl_device *bcl_perph)
{
bcl_ibat_init(pdev, BCL_IBAT_LVL0, bcl_perph);
bcl_ibat_init(pdev, BCL_IBAT_LVL1, bcl_perph);
}
static void bcl_lvl_init(struct platform_device *pdev,
enum bcl_dev_type type, int sts_bit_idx, struct bcl_device *bcl_perph)
{
struct bcl_peripheral_data *lbat = &bcl_perph->param[type];
mutex_init(&lbat->state_trans_lock);
lbat->type = type;
lbat->dev = bcl_perph;
lbat->status_bit_idx = sts_bit_idx;
bcl_fetch_trip(pdev, type, lbat, bcl_handle_irq);
if (lbat->irq_num <= 0)
return;
lbat->ops.get_temp = bcl_read_lbat;
lbat->ops.set_trips = bcl_set_lbat;
lbat->tz_dev = thermal_zone_of_sensor_register(&pdev->dev,
type, lbat, &lbat->ops);
if (IS_ERR(lbat->tz_dev)) {
pr_debug("lbat:[%s] register failed. err:%ld\n",
bcl_int_names[type],
PTR_ERR(lbat->tz_dev));
lbat->tz_dev = NULL;
return;
}
thermal_zone_device_update(lbat->tz_dev, THERMAL_DEVICE_UP);
}
static void bcl_probe_lvls(struct platform_device *pdev,
struct bcl_device *bcl_perph)
{
bcl_lvl_init(pdev, BCL_LVL0, BCL_IRQ_L0, bcl_perph);
bcl_lvl_init(pdev, BCL_LVL1, BCL_IRQ_L1, bcl_perph);
bcl_lvl_init(pdev, BCL_LVL2, BCL_IRQ_L2, bcl_perph);
}
static void bcl_configure_bcl_peripheral(struct bcl_device *bcl_perph)
{
bcl_write_register(bcl_perph, BCL_MONITOR_EN, BIT(7));
}
static int bcl_remove(struct platform_device *pdev)
{
int i = 0;
struct bcl_device *bcl_perph =
(struct bcl_device *)dev_get_drvdata(&pdev->dev);
for (; i < BCL_TYPE_MAX; i++) {
if (!bcl_perph->param[i].tz_dev)
continue;
thermal_zone_of_sensor_unregister(&pdev->dev,
bcl_perph->param[i].tz_dev);
}
return 0;
}
static int bcl_probe(struct platform_device *pdev)
{
struct bcl_device *bcl_perph = NULL;
if (bcl_device_ct >= MAX_PERPH_COUNT) {
dev_err(&pdev->dev, "Max bcl peripheral supported already.\n");
return -EINVAL;
}
bcl_devices[bcl_device_ct] = devm_kzalloc(&pdev->dev,
sizeof(*bcl_devices[0]), GFP_KERNEL);
if (!bcl_devices[bcl_device_ct])
return -ENOMEM;
bcl_perph = bcl_devices[bcl_device_ct];
bcl_perph->dev = &pdev->dev;
bcl_perph->regmap = dev_get_regmap(pdev->dev.parent, NULL);
if (!bcl_perph->regmap) {
dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
return -EINVAL;
}
bcl_device_ct++;
bcl_get_devicetree_data(pdev, bcl_perph);
bcl_probe_vbat(pdev, bcl_perph);
bcl_probe_ibat(pdev, bcl_perph);
bcl_probe_lvls(pdev, bcl_perph);
bcl_configure_bcl_peripheral(bcl_perph);
dev_set_drvdata(&pdev->dev, bcl_perph);
return 0;
}
static const struct of_device_id bcl_match[] = {
{
.compatible = "qcom,bcl-v5",
},
{},
};
static struct platform_driver bcl_driver = {
.probe = bcl_probe,
.remove = bcl_remove,
.driver = {
.name = BCL_DRIVER_NAME,
.of_match_table = bcl_match,
},
};
module_platform_driver(bcl_driver);
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,189 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2018-2019, The Linux Foundation. All rights reserved.
*/
#define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__
#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/mutex.h>
#include <linux/power_supply.h>
#include <linux/thermal.h>
#include "../thermal_core.h"
#define BCL_DRIVER_NAME "bcl_soc_peripheral"
struct bcl_device {
struct device *dev;
struct notifier_block psy_nb;
struct work_struct soc_eval_work;
long trip_temp;
int trip_val;
struct mutex state_trans_lock;
bool irq_enabled;
struct thermal_zone_device *tz_dev;
struct thermal_zone_of_device_ops ops;
};
static struct bcl_device *bcl_perph;
static int bcl_set_soc(void *data, int low, int high)
{
if (low == bcl_perph->trip_temp)
return 0;
mutex_lock(&bcl_perph->state_trans_lock);
pr_debug("low soc threshold:%d\n", low);
bcl_perph->trip_temp = low;
if (low == INT_MIN) {
bcl_perph->irq_enabled = false;
goto unlock_and_exit;
}
bcl_perph->irq_enabled = true;
schedule_work(&bcl_perph->soc_eval_work);
unlock_and_exit:
mutex_unlock(&bcl_perph->state_trans_lock);
return 0;
}
static int bcl_read_soc(void *data, int *val)
{
static struct power_supply *batt_psy;
union power_supply_propval ret = {0,};
int err = 0;
*val = 100;
if (!batt_psy)
batt_psy = power_supply_get_by_name("battery");
if (batt_psy) {
err = power_supply_get_property(batt_psy,
POWER_SUPPLY_PROP_CAPACITY, &ret);
if (err) {
pr_err("battery percentage read error:%d\n",
err);
return err;
}
*val = ret.intval;
}
pr_debug("soc:%d\n", *val);
return err;
}
static void bcl_evaluate_soc(struct work_struct *work)
{
int battery_percentage;
if (!bcl_perph->tz_dev)
return;
if (bcl_read_soc(NULL, &battery_percentage))
return;
mutex_lock(&bcl_perph->state_trans_lock);
if (!bcl_perph->irq_enabled)
goto eval_exit;
if (battery_percentage > bcl_perph->trip_temp)
goto eval_exit;
bcl_perph->trip_val = battery_percentage;
mutex_unlock(&bcl_perph->state_trans_lock);
of_thermal_handle_trip_temp(bcl_perph->dev,
bcl_perph->tz_dev, bcl_perph->trip_val);
return;
eval_exit:
mutex_unlock(&bcl_perph->state_trans_lock);
}
static int battery_supply_callback(struct notifier_block *nb,
unsigned long event, void *data)
{
struct power_supply *psy = data;
if (strcmp(psy->desc->name, "battery"))
return NOTIFY_OK;
schedule_work(&bcl_perph->soc_eval_work);
return NOTIFY_OK;
}
static int bcl_soc_remove(struct platform_device *pdev)
{
power_supply_unreg_notifier(&bcl_perph->psy_nb);
flush_work(&bcl_perph->soc_eval_work);
if (bcl_perph->tz_dev)
thermal_zone_of_sensor_unregister(&pdev->dev,
bcl_perph->tz_dev);
return 0;
}
static int bcl_soc_probe(struct platform_device *pdev)
{
int ret = 0;
bcl_perph = devm_kzalloc(&pdev->dev, sizeof(*bcl_perph), GFP_KERNEL);
if (!bcl_perph)
return -ENOMEM;
mutex_init(&bcl_perph->state_trans_lock);
bcl_perph->dev = &pdev->dev;
bcl_perph->ops.get_temp = bcl_read_soc;
bcl_perph->ops.set_trips = bcl_set_soc;
INIT_WORK(&bcl_perph->soc_eval_work, bcl_evaluate_soc);
bcl_perph->psy_nb.notifier_call = battery_supply_callback;
ret = power_supply_reg_notifier(&bcl_perph->psy_nb);
if (ret < 0) {
pr_err("soc notifier registration error. defer. err:%d\n",
ret);
ret = -EPROBE_DEFER;
goto bcl_soc_probe_exit;
}
bcl_perph->tz_dev = thermal_zone_of_sensor_register(&pdev->dev,
0, bcl_perph, &bcl_perph->ops);
if (IS_ERR(bcl_perph->tz_dev)) {
pr_err("soc TZ register failed. err:%ld\n",
PTR_ERR(bcl_perph->tz_dev));
ret = PTR_ERR(bcl_perph->tz_dev);
bcl_perph->tz_dev = NULL;
goto bcl_soc_probe_exit;
}
thermal_zone_device_update(bcl_perph->tz_dev, THERMAL_DEVICE_UP);
schedule_work(&bcl_perph->soc_eval_work);
dev_set_drvdata(&pdev->dev, bcl_perph);
return 0;
bcl_soc_probe_exit:
bcl_soc_remove(pdev);
return ret;
}
static const struct of_device_id bcl_match[] = {
{
.compatible = "qcom,msm-bcl-soc",
},
{},
};
static struct platform_driver bcl_driver = {
.probe = bcl_soc_probe,
.remove = bcl_soc_remove,
.driver = {
.name = BCL_DRIVER_NAME,
.of_match_table = bcl_match,
},
};
module_platform_driver(bcl_driver);
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,724 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2018-2019, The Linux Foundation. All rights reserved.
*/
#define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/thermal.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/soc/qcom/qmi.h>
#include <linux/net.h>
#include <linux/kernel.h>
#include <linux/suspend.h>
#include "thermal_sensor_service_v01.h"
#include "../thermal_core.h"
#define QMI_SENS_DRIVER "qmi-therm-sensors"
#define QMI_TS_RESP_TOUT msecs_to_jiffies(100)
#define QMI_CLIENT_NAME_LENGTH 40
#define QMI_FL_SIGN 0x80000000
#define QMI_FL_EXP 0x7f800000
#define QMI_FL_MANTISSA 0x007fffff
#define QMI_FL_NORM 0x00800000
#define QMI_FL_SIGN_BIT 31
#define QMI_MANTISSA_MSB 23
enum qmi_ts_sensor {
QMI_TS_PA,
QMI_TS_PA_1,
QMI_TS_PA_2,
QMI_TS_QFE_PA_0,
QMI_TS_QFE_WTR_0,
QMI_TS_MODEM_MODEM,
QMI_TS_MMW_0,
QMI_TS_MMW_1,
QMI_TS_MMW_2,
QMI_TS_MMW_3,
QMI_TS_MODEM_SKIN,
QMI_TS_QFE_PA_MDM,
QMI_TS_QFE_PA_WTR,
QMI_TS_STREAMER_0,
QMI_TS_MOD_MMW_0,
QMI_TS_MOD_MMW_1,
QMI_TS_MOD_MMW_2,
QMI_TS_MOD_MMW_3,
QMI_TS_RET_PA_0,
QMI_TS_WTR_PA_0,
QMI_TS_WTR_PA_1,
QMI_TS_WTR_PA_2,
QMI_TS_WTR_PA_3,
QMI_SYS_THERM1,
QMI_SYS_THERM2,
QMI_TS_TSENS_1,
QMI_TS_MAX_NR
};
struct qmi_sensor {
struct device *dev;
char qmi_name[QMI_CLIENT_NAME_LENGTH];
bool connection_active;
struct list_head ts_node;
struct thermal_zone_device *tz_dev;
int32_t last_reading;
int32_t high_thresh;
int32_t low_thresh;
struct qmi_ts_instance *ts;
enum qmi_ts_sensor sens_type;
struct work_struct therm_notify_work;
};
struct qmi_ts_instance {
struct device *dev;
struct qmi_handle handle;
struct mutex mutex;
uint32_t inst_id;
struct list_head ts_sensor_list;
struct work_struct svc_arrive_work;
};
static struct qmi_ts_instance *ts_instances;
static int ts_inst_cnt;
static atomic_t in_suspend;
static char sensor_clients[QMI_TS_MAX_NR][QMI_CLIENT_NAME_LENGTH] = {
{"pa"},
{"pa_1"},
{"pa_2"},
{"qfe_pa0"},
{"qfe_wtr0"},
{"modem_tsens"},
{"qfe_mmw0"},
{"qfe_mmw1"},
{"qfe_mmw2"},
{"qfe_mmw3"},
{"xo_therm"},
{"qfe_pa_mdm"},
{"qfe_pa_wtr"},
{"qfe_mmw_streamer0"},
{"qfe_mmw0_mod"},
{"qfe_mmw1_mod"},
{"qfe_mmw2_mod"},
{"qfe_mmw3_mod"},
{"qfe_ret_pa0"},
{"qfe_wtr_pa0"},
{"qfe_wtr_pa1"},
{"qfe_wtr_pa2"},
{"qfe_wtr_pa3"},
{"sys_therm1"},
{"sys_therm2"},
{"modem_tsens1"},
};
static int32_t encode_qmi(int32_t val)
{
uint32_t shift = 0, local_val = 0;
int32_t temp_val = 0;
if (val == INT_MAX || val == INT_MIN)
return 0;
temp_val = val = val / 1000;
if (val < 0) {
temp_val *= -1;
local_val |= 1 << QMI_FL_SIGN_BIT;
}
shift = find_last_bit((const unsigned long *)&temp_val,
sizeof(temp_val) * 8);
local_val |= ((shift + 127) << QMI_MANTISSA_MSB);
temp_val &= ~(1 << shift);
local_val |= temp_val << (QMI_MANTISSA_MSB - shift);
pr_debug("inp:%d shift:%d out:%x temp_val:%x\n",
val, shift, local_val, temp_val);
return local_val;
}
static int32_t decode_qmi(int32_t val)
{
int32_t sign = 0, shift = 0, local_val;
sign = (val & QMI_FL_SIGN) ? -1 : 1;
shift = (val & QMI_FL_EXP) >> QMI_MANTISSA_MSB;
shift = QMI_MANTISSA_MSB - (shift - 127);
local_val = (val & QMI_FL_MANTISSA) | QMI_FL_NORM;
pr_debug("val:0x%x sign:%d shift:%d mantissa:%x temp:%d\n",
val, sign, shift, local_val,
sign * (local_val >> shift));
return sign * (local_val >> shift);
}
static int qmi_sensor_pm_notify(struct notifier_block *nb,
unsigned long mode, void *_unused)
{
switch (mode) {
case PM_HIBERNATION_PREPARE:
case PM_RESTORE_PREPARE:
case PM_SUSPEND_PREPARE:
atomic_set(&in_suspend, 1);
break;
case PM_POST_HIBERNATION:
case PM_POST_RESTORE:
case PM_POST_SUSPEND:
atomic_set(&in_suspend, 0);
break;
default:
break;
}
return 0;
}
static struct notifier_block qmi_sensor_pm_nb = {
.notifier_call = qmi_sensor_pm_notify,
};
static void qmi_ts_thresh_notify(struct work_struct *work)
{
struct qmi_sensor *qmi_sens = container_of(work,
struct qmi_sensor,
therm_notify_work);
of_thermal_handle_trip_temp(qmi_sens->dev, qmi_sens->tz_dev,
qmi_sens->last_reading);
};
static void qmi_ts_update_temperature(struct qmi_ts_instance *ts,
const struct ts_temp_report_ind_msg_v01 *ind_msg,
uint8_t notify)
{
struct qmi_sensor *qmi_sens;
list_for_each_entry(qmi_sens, &ts->ts_sensor_list,
ts_node) {
if ((strncasecmp(qmi_sens->qmi_name,
ind_msg->sensor_id.sensor_id,
QMI_TS_SENSOR_ID_LENGTH_MAX_V01)))
continue;
qmi_sens->last_reading =
decode_qmi(ind_msg->temp) * 1000;
pr_debug("sensor:%s temperature:%d\n",
qmi_sens->qmi_name, qmi_sens->last_reading);
if (!qmi_sens->tz_dev)
return;
if (notify &&
((qmi_sens->high_thresh != INT_MAX &&
qmi_sens->last_reading >= qmi_sens->high_thresh) ||
(qmi_sens->low_thresh != INT_MIN &&
qmi_sens->last_reading <= qmi_sens->low_thresh))) {
pr_debug("Sensor:%s Notify. temp:%d\n",
ind_msg->sensor_id.sensor_id,
qmi_sens->last_reading);
queue_work(system_highpri_wq,
&qmi_sens->therm_notify_work);
}
return;
}
}
void qmi_ts_ind_cb(struct qmi_handle *qmi, struct sockaddr_qrtr *sq,
struct qmi_txn *txn, const void *decoded)
{
const struct ts_temp_report_ind_msg_v01 *ind_msg = decoded;
uint8_t notify = 0;
struct qmi_ts_instance *ts = container_of(qmi, struct qmi_ts_instance,
handle);
if (!txn) {
pr_err("Invalid transaction\n");
return;
}
if ((ind_msg->report_type != QMI_TS_TEMP_REPORT_CURRENT_TEMP_V01) ||
ind_msg->seq_num_valid)
notify = 1;
if (ind_msg->temp_valid)
qmi_ts_update_temperature(ts, ind_msg, notify);
else
pr_err("Error invalid temperature field.\n");
}
static int qmi_ts_request(struct qmi_sensor *qmi_sens,
bool send_current_temp_report)
{
int ret = 0;
struct ts_register_notification_temp_resp_msg_v01 resp;
struct ts_register_notification_temp_req_msg_v01 req;
struct qmi_ts_instance *ts = qmi_sens->ts;
struct qmi_txn txn;
memset(&req, 0, sizeof(req));
memset(&resp, 0, sizeof(resp));
strlcpy(req.sensor_id.sensor_id, qmi_sens->qmi_name,
QMI_TS_SENSOR_ID_LENGTH_MAX_V01);
req.seq_num = 0;
if (send_current_temp_report) {
req.send_current_temp_report = 1;
req.seq_num_valid = true;
} else {
req.seq_num_valid = false;
req.temp_threshold_high_valid =
qmi_sens->high_thresh != INT_MAX;
req.temp_threshold_high =
encode_qmi(qmi_sens->high_thresh);
req.temp_threshold_low_valid =
qmi_sens->low_thresh != INT_MIN;
req.temp_threshold_low =
encode_qmi(qmi_sens->low_thresh);
}
mutex_lock(&ts->mutex);
ret = qmi_txn_init(&ts->handle, &txn,
ts_register_notification_temp_resp_msg_v01_ei, &resp);
if (ret < 0) {
pr_err("qmi txn init failed for %s ret:%d\n",
qmi_sens->qmi_name, ret);
goto qmi_send_exit;
}
ret = qmi_send_request(&ts->handle, NULL, &txn,
QMI_TS_REGISTER_NOTIFICATION_TEMP_REQ_V01,
TS_REGISTER_NOTIFICATION_TEMP_REQ_MSG_V01_MAX_MSG_LEN,
ts_register_notification_temp_req_msg_v01_ei, &req);
if (ret < 0) {
pr_err("qmi txn send failed for %s ret:%d\n",
qmi_sens->qmi_name, ret);
qmi_txn_cancel(&txn);
goto qmi_send_exit;
}
ret = qmi_txn_wait(&txn, QMI_TS_RESP_TOUT);
if (ret < 0) {
pr_err("qmi txn wait failed for %s ret:%d\n",
qmi_sens->qmi_name, ret);
goto qmi_send_exit;
}
if (resp.resp.result != QMI_RESULT_SUCCESS_V01) {
ret = resp.resp.result;
pr_err("qmi NOT success for %s ret:%d\n",
qmi_sens->qmi_name, ret);
goto qmi_send_exit;
}
ret = 0;
qmi_send_exit:
mutex_unlock(&ts->mutex);
return ret;
}
static int qmi_sensor_read(void *data, int *temp)
{
struct qmi_sensor *qmi_sens = (struct qmi_sensor *)data;
if (qmi_sens->connection_active && !atomic_read(&in_suspend))
qmi_ts_request(qmi_sens, true);
*temp = qmi_sens->last_reading;
return 0;
}
static int qmi_sensor_set_trips(void *data, int low, int high)
{
struct qmi_sensor *qmi_sens = (struct qmi_sensor *)data;
int ret = 0;
if (qmi_sens->high_thresh == high &&
qmi_sens->low_thresh == low)
return ret;
qmi_sens->high_thresh = high;
qmi_sens->low_thresh = low;
if (!qmi_sens->connection_active)
return ret;
ret = qmi_ts_request(qmi_sens, false);
if (ret)
pr_err("Sensor:%s set high trip:%d low trip:%d error%d\n",
qmi_sens->qmi_name,
qmi_sens->high_thresh,
qmi_sens->low_thresh,
ret);
return ret;
}
static struct thermal_zone_of_device_ops qmi_sensor_ops = {
.get_temp = qmi_sensor_read,
.set_trips = qmi_sensor_set_trips,
};
static struct qmi_msg_handler handlers[] = {
{
.type = QMI_INDICATION,
.msg_id = QMI_TS_TEMP_REPORT_IND_V01,
.ei = ts_temp_report_ind_msg_v01_ei,
.decoded_size = sizeof(struct ts_temp_report_ind_msg_v01),
.fn = qmi_ts_ind_cb
},
{}
};
static int qmi_register_sensor_device(struct qmi_sensor *qmi_sens)
{
int ret = 0;
qmi_sens->tz_dev = thermal_zone_of_sensor_register(
qmi_sens->dev,
qmi_sens->sens_type + qmi_sens->ts->inst_id,
qmi_sens, &qmi_sensor_ops);
if (IS_ERR(qmi_sens->tz_dev)) {
ret = PTR_ERR(qmi_sens->tz_dev);
if (ret != -ENODEV)
pr_err("sensor register failed for %s, ret:%d\n",
qmi_sens->qmi_name, ret);
qmi_sens->tz_dev = NULL;
return ret;
}
pr_debug("Sensor register success for %s\n", qmi_sens->qmi_name);
return 0;
}
static int verify_sensor_and_register(struct qmi_ts_instance *ts)
{
struct ts_get_sensor_list_req_msg_v01 req;
struct ts_get_sensor_list_resp_msg_v01 *ts_resp;
int ret = 0, i;
struct qmi_txn txn;
memset(&req, 0, sizeof(req));
/* size of ts_resp is very high, use heap memory rather than stack */
ts_resp = kzalloc(sizeof(*ts_resp), GFP_KERNEL);
if (!ts_resp)
return -ENOMEM;
mutex_lock(&ts->mutex);
ret = qmi_txn_init(&ts->handle, &txn,
ts_get_sensor_list_resp_msg_v01_ei, ts_resp);
if (ret < 0) {
pr_err("Transaction Init error for inst_id:0x%x ret:%d\n",
ts->inst_id, ret);
goto reg_exit;
}
ret = qmi_send_request(&ts->handle, NULL, &txn,
QMI_TS_GET_SENSOR_LIST_REQ_V01,
TS_GET_SENSOR_LIST_REQ_MSG_V01_MAX_MSG_LEN,
ts_get_sensor_list_req_msg_v01_ei,
&req);
if (ret < 0) {
qmi_txn_cancel(&txn);
goto reg_exit;
}
ret = qmi_txn_wait(&txn, QMI_TS_RESP_TOUT);
if (ret < 0) {
pr_err("Transaction wait error for inst_id:0x%x ret:%d\n",
ts->inst_id, ret);
goto reg_exit;
}
if (ts_resp->resp.result != QMI_RESULT_SUCCESS_V01) {
ret = ts_resp->resp.result;
pr_err("Get sensor list NOT success for inst_id:0x%x ret:%d\n",
ts->inst_id, ret);
goto reg_exit;
}
mutex_unlock(&ts->mutex);
for (i = 0; i < ts_resp->sensor_list_len; i++) {
struct qmi_sensor *qmi_sens = NULL;
list_for_each_entry(qmi_sens, &ts->ts_sensor_list,
ts_node) {
if ((strncasecmp(qmi_sens->qmi_name,
ts_resp->sensor_list[i].sensor_id,
QMI_TS_SENSOR_ID_LENGTH_MAX_V01)))
continue;
qmi_sens->connection_active = true;
/*
* Send a temperature request notification.
*/
qmi_ts_request(qmi_sens, true);
if (!qmi_sens->tz_dev)
ret = qmi_register_sensor_device(qmi_sens);
break;
}
}
kfree(ts_resp);
return ret;
reg_exit:
mutex_unlock(&ts->mutex);
kfree(ts_resp);
return ret;
}
static void qmi_ts_svc_arrive(struct work_struct *work)
{
struct qmi_ts_instance *ts = container_of(work,
struct qmi_ts_instance,
svc_arrive_work);
verify_sensor_and_register(ts);
}
static void thermal_qmi_net_reset(struct qmi_handle *qmi)
{
struct qmi_ts_instance *ts = container_of(qmi,
struct qmi_ts_instance,
handle);
struct qmi_sensor *qmi_sens = NULL;
int ret;
pr_debug("reset QMI server\n");
list_for_each_entry(qmi_sens, &ts->ts_sensor_list,
ts_node) {
if (!qmi_sens->connection_active)
continue;
qmi_ts_request(qmi_sens, true);
ret = qmi_ts_request(qmi_sens, false);
if (ret)
pr_err("Sensor:%s set high trip:%d low trip:%d err%d\n",
qmi_sens->tz_dev->type,
qmi_sens->high_thresh,
qmi_sens->low_thresh,
ret);
}
}
static void thermal_qmi_del_server(struct qmi_handle *qmi,
struct qmi_service *service)
{
struct qmi_ts_instance *ts = container_of(qmi,
struct qmi_ts_instance,
handle);
struct qmi_sensor *qmi_sens = NULL;
pr_debug("QMI server deleted\n");
list_for_each_entry(qmi_sens, &ts->ts_sensor_list, ts_node)
qmi_sens->connection_active = false;
}
static int thermal_qmi_new_server(struct qmi_handle *qmi,
struct qmi_service *service)
{
struct qmi_ts_instance *ts = container_of(qmi,
struct qmi_ts_instance,
handle);
struct sockaddr_qrtr sq = {AF_QIPCRTR, service->node, service->port};
mutex_lock(&ts->mutex);
kernel_connect(qmi->sock, (struct sockaddr *)&sq, sizeof(sq), 0);
mutex_unlock(&ts->mutex);
queue_work(system_highpri_wq, &ts->svc_arrive_work);
return 0;
}
static struct qmi_ops thermal_qmi_event_ops = {
.new_server = thermal_qmi_new_server,
.del_server = thermal_qmi_del_server,
.net_reset = thermal_qmi_net_reset,
};
static void qmi_ts_cleanup(void)
{
struct qmi_ts_instance *ts;
struct qmi_sensor *qmi_sens, *c_next;
int idx = 0;
for (; idx < ts_inst_cnt; idx++) {
ts = &ts_instances[idx];
mutex_lock(&ts->mutex);
list_for_each_entry_safe(qmi_sens, c_next,
&ts->ts_sensor_list, ts_node) {
qmi_sens->connection_active = false;
if (qmi_sens->tz_dev)
thermal_zone_of_sensor_unregister(
qmi_sens->dev, qmi_sens->tz_dev);
list_del(&qmi_sens->ts_node);
}
qmi_handle_release(&ts->handle);
mutex_unlock(&ts->mutex);
}
ts_inst_cnt = 0;
}
static int of_get_qmi_ts_platform_data(struct device *dev)
{
int ret = 0, i = 0, idx = 0;
struct device_node *np = dev->of_node;
struct device_node *subsys_np = NULL;
struct qmi_ts_instance *ts;
struct qmi_sensor *qmi_sens;
int sens_name_max = 0, sens_idx = 0, subsys_cnt = 0;
subsys_cnt = of_get_available_child_count(np);
if (!subsys_cnt) {
dev_err(dev, "No child node to process\n");
return -EFAULT;
}
ts = devm_kcalloc(dev, subsys_cnt, sizeof(*ts), GFP_KERNEL);
if (!ts)
return -ENOMEM;
for_each_available_child_of_node(np, subsys_np) {
if (idx >= subsys_cnt)
break;
ret = of_property_read_u32(subsys_np, "qcom,instance-id",
&ts[idx].inst_id);
if (ret) {
dev_err(dev, "error reading qcom,insance-id. ret:%d\n",
ret);
goto data_fetch_err;
}
ts[idx].dev = dev;
mutex_init(&ts[idx].mutex);
INIT_LIST_HEAD(&ts[idx].ts_sensor_list);
INIT_WORK(&ts[idx].svc_arrive_work, qmi_ts_svc_arrive);
sens_name_max = of_property_count_strings(subsys_np,
"qcom,qmi-sensor-names");
if (sens_name_max <= 0) {
dev_err(dev, "Invalid or no sensor. err:%d\n",
sens_name_max);
ret = -EINVAL;
goto data_fetch_err;
}
for (sens_idx = 0; sens_idx < sens_name_max; sens_idx++) {
const char *qmi_name;
qmi_sens = devm_kzalloc(dev, sizeof(*qmi_sens),
GFP_KERNEL);
if (!qmi_sens) {
ret = -ENOMEM;
goto data_fetch_err;
}
of_property_read_string_index(subsys_np,
"qcom,qmi-sensor-names", sens_idx,
&qmi_name);
strlcpy(qmi_sens->qmi_name, qmi_name,
QMI_CLIENT_NAME_LENGTH);
/* Check for supported qmi sensors */
for (i = 0; i < QMI_TS_MAX_NR; i++) {
if (!strcmp(sensor_clients[i],
qmi_sens->qmi_name))
break;
}
if (i >= QMI_TS_MAX_NR) {
dev_err(dev, "Unknown sensor:%s\n",
qmi_sens->qmi_name);
ret = -EINVAL;
goto data_fetch_err;
}
dev_dbg(dev, "QMI sensor:%s available\n", qmi_name);
qmi_sens->sens_type = i;
qmi_sens->ts = &ts[idx];
qmi_sens->dev = dev;
qmi_sens->last_reading = 0;
qmi_sens->high_thresh = INT_MAX;
qmi_sens->low_thresh = INT_MIN;
INIT_WORK(&qmi_sens->therm_notify_work,
qmi_ts_thresh_notify);
list_add(&qmi_sens->ts_node, &ts[idx].ts_sensor_list);
}
idx++;
}
ts_instances = ts;
ts_inst_cnt = subsys_cnt;
return 0;
data_fetch_err:
of_node_put(subsys_np);
return ret;
}
static int qmi_sens_device_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int ret = 0, idx = 0;
struct qmi_ts_instance *ts;
ret = of_get_qmi_ts_platform_data(dev);
if (ret)
goto probe_err;
if (!ts_instances || !ts_inst_cnt) {
dev_err(dev, "Empty ts instances\n");
return -EINVAL;
}
for (; idx < ts_inst_cnt; idx++) {
ts = &ts_instances[idx];
if (list_empty(&ts->ts_sensor_list)) {
ret = -ENODEV;
goto probe_err;
}
ret = qmi_handle_init(&ts->handle,
TS_GET_SENSOR_LIST_RESP_MSG_V01_MAX_MSG_LEN,
&thermal_qmi_event_ops, handlers);
if (ret < 0) {
dev_err(dev, "QMI[0x%x] handle init failed. err:%d\n",
ts->inst_id, ret);
goto probe_err;
}
ret = qmi_add_lookup(&ts->handle, TS_SERVICE_ID_V01,
TS_SERVICE_VERS_V01, ts->inst_id);
if (ret < 0) {
dev_err(dev, "QMI register failed for 0x%x, ret:%d\n",
ts->inst_id, ret);
goto probe_err;
}
}
atomic_set(&in_suspend, 0);
register_pm_notifier(&qmi_sensor_pm_nb);
return 0;
probe_err:
qmi_ts_cleanup();
return ret;
}
static int qmi_sens_device_remove(struct platform_device *pdev)
{
qmi_ts_cleanup();
unregister_pm_notifier(&qmi_sensor_pm_nb);
return 0;
}
static const struct of_device_id qmi_sens_device_match[] = {
{.compatible = "qcom,qmi-sensors"},
{}
};
static struct platform_driver qmi_sens_device_driver = {
.probe = qmi_sens_device_probe,
.remove = qmi_sens_device_remove,
.driver = {
.name = QMI_SENS_DRIVER,
.of_match_table = qmi_sens_device_match,
},
};
module_platform_driver(qmi_sens_device_driver);
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,253 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2018-2019, The Linux Foundation. All rights reserved.
*/
#include <linux/soc/qcom/qmi.h>
#include "thermal_sensor_service_v01.h"
static struct qmi_elem_info ts_sensor_type_v01_ei[] = {
{
.data_type = QMI_STRING,
.elem_len = QMI_TS_SENSOR_ID_LENGTH_MAX_V01 + 1,
.elem_size = sizeof(char),
.array_type = NO_ARRAY,
.tlv_type = 0,
.offset = offsetof(struct ts_sensor_type_v01,
sensor_id),
},
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.array_type = QMI_COMMON_TLV_TYPE,
},
};
struct qmi_elem_info ts_get_sensor_list_req_msg_v01_ei[] = {
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.array_type = QMI_COMMON_TLV_TYPE,
},
};
struct qmi_elem_info ts_get_sensor_list_resp_msg_v01_ei[] = {
{
.data_type = QMI_STRUCT,
.elem_len = 1,
.elem_size = sizeof(struct qmi_response_type_v01),
.array_type = NO_ARRAY,
.tlv_type = 0x02,
.offset =
offsetof(struct ts_get_sensor_list_resp_msg_v01, resp),
.ei_array = qmi_response_type_v01_ei,
},
{
.data_type = QMI_OPT_FLAG,
.elem_len = 1,
.elem_size = sizeof(uint8_t),
.array_type = NO_ARRAY,
.tlv_type = 0x10,
.offset =
offsetof(struct ts_get_sensor_list_resp_msg_v01,
sensor_list_valid),
},
{
.data_type = QMI_DATA_LEN,
.elem_len = 1,
.elem_size = sizeof(uint8_t),
.array_type = NO_ARRAY,
.tlv_type = 0x10,
.offset =
offsetof(struct ts_get_sensor_list_resp_msg_v01,
sensor_list_len),
},
{
.data_type = QMI_STRUCT,
.elem_len = QMI_TS_SENSOR_LIST_MAX_V01,
.elem_size = sizeof(struct ts_sensor_type_v01),
.array_type = VAR_LEN_ARRAY,
.tlv_type = 0x10,
.offset =
offsetof(struct ts_get_sensor_list_resp_msg_v01,
sensor_list),
.ei_array = ts_sensor_type_v01_ei,
},
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.array_type = QMI_COMMON_TLV_TYPE,
},
};
struct qmi_elem_info ts_register_notification_temp_req_msg_v01_ei[] = {
{
.data_type = QMI_STRUCT,
.elem_len = 1,
.elem_size = sizeof(struct ts_sensor_type_v01),
.array_type = NO_ARRAY,
.tlv_type = 0x01,
.offset = offsetof(
struct ts_register_notification_temp_req_msg_v01,
sensor_id),
.ei_array = ts_sensor_type_v01_ei,
},
{
.data_type = QMI_UNSIGNED_1_BYTE,
.elem_len = 1,
.elem_size = sizeof(uint8_t),
.array_type = NO_ARRAY,
.tlv_type = 0x02,
.offset = offsetof(
struct ts_register_notification_temp_req_msg_v01,
send_current_temp_report),
},
{
.data_type = QMI_OPT_FLAG,
.elem_len = 1,
.elem_size = sizeof(uint8_t),
.array_type = NO_ARRAY,
.tlv_type = 0x10,
.offset = offsetof(
struct ts_register_notification_temp_req_msg_v01,
temp_threshold_high_valid),
},
{
.data_type = QMI_UNSIGNED_4_BYTE,
.elem_len = 1,
.elem_size = sizeof(int),
.array_type = NO_ARRAY,
.tlv_type = 0x10,
.offset = offsetof(
struct ts_register_notification_temp_req_msg_v01,
temp_threshold_high),
},
{
.data_type = QMI_OPT_FLAG,
.elem_len = 1,
.elem_size = sizeof(uint8_t),
.array_type = NO_ARRAY,
.tlv_type = 0x11,
.offset = offsetof(
struct ts_register_notification_temp_req_msg_v01,
temp_threshold_low_valid),
},
{
.data_type = QMI_UNSIGNED_4_BYTE,
.elem_len = 1,
.elem_size = sizeof(int),
.array_type = NO_ARRAY,
.tlv_type = 0x11,
.offset = offsetof(
struct ts_register_notification_temp_req_msg_v01,
temp_threshold_low),
},
{
.data_type = QMI_OPT_FLAG,
.elem_len = 1,
.elem_size = sizeof(uint8_t),
.array_type = NO_ARRAY,
.tlv_type = 0x12,
.offset = offsetof(
struct ts_register_notification_temp_req_msg_v01,
seq_num_valid),
},
{
.data_type = QMI_UNSIGNED_4_BYTE,
.elem_len = 1,
.elem_size = sizeof(uint32_t),
.array_type = NO_ARRAY,
.tlv_type = 0x12,
.offset = offsetof(
struct ts_register_notification_temp_req_msg_v01,
seq_num),
},
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.array_type = QMI_COMMON_TLV_TYPE,
},
};
struct qmi_elem_info ts_register_notification_temp_resp_msg_v01_ei[] = {
{
.data_type = QMI_STRUCT,
.elem_len = 1,
.elem_size = sizeof(struct qmi_response_type_v01),
.array_type = NO_ARRAY,
.tlv_type = 0x02,
.offset = offsetof(
struct ts_register_notification_temp_resp_msg_v01,
resp),
.ei_array = qmi_response_type_v01_ei,
},
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.array_type = QMI_COMMON_TLV_TYPE,
},
};
struct qmi_elem_info ts_temp_report_ind_msg_v01_ei[] = {
{
.data_type = QMI_STRUCT,
.elem_len = 1,
.elem_size = sizeof(struct ts_sensor_type_v01),
.array_type = NO_ARRAY,
.tlv_type = 0x01,
.offset = offsetof(struct ts_temp_report_ind_msg_v01,
sensor_id),
.ei_array = ts_sensor_type_v01_ei,
},
{
.data_type = QMI_SIGNED_4_BYTE_ENUM,
.elem_len = 1,
.elem_size = sizeof(enum ts_temp_report_type_enum_v01),
.array_type = NO_ARRAY,
.tlv_type = 0x02,
.offset = offsetof(struct ts_temp_report_ind_msg_v01,
report_type),
},
{
.data_type = QMI_OPT_FLAG,
.elem_len = 1,
.elem_size = sizeof(uint8_t),
.array_type = NO_ARRAY,
.tlv_type = 0x10,
.offset = offsetof(struct ts_temp_report_ind_msg_v01,
temp_valid),
},
{
.data_type = QMI_UNSIGNED_4_BYTE,
.elem_len = 1,
.elem_size = sizeof(int),
.array_type = NO_ARRAY,
.tlv_type = 0x10,
.offset = offsetof(struct ts_temp_report_ind_msg_v01,
temp),
},
{
.data_type = QMI_OPT_FLAG,
.elem_len = 1,
.elem_size = sizeof(uint8_t),
.array_type = NO_ARRAY,
.tlv_type = 0x11,
.offset = offsetof(struct ts_temp_report_ind_msg_v01,
seq_num_valid),
},
{
.data_type = QMI_UNSIGNED_4_BYTE,
.elem_len = 1,
.elem_size = sizeof(uint32_t),
.array_type = NO_ARRAY,
.tlv_type = 0x11,
.offset = offsetof(struct ts_temp_report_ind_msg_v01,
seq_num),
},
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.array_type = QMI_COMMON_TLV_TYPE,
},
};

View file

@ -0,0 +1,82 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2018-2019, The Linux Foundation. All rights reserved.
*/
#ifndef THERMAL_SENSOR_SERVICE_V01_H
#define THERMAL_SENSOR_SERVICE_V01_H
#define TS_SERVICE_ID_V01 0x17
#define TS_SERVICE_VERS_V01 0x01
#define QMI_TS_GET_SENSOR_LIST_RESP_V01 0x0020
#define QMI_TS_GET_SUPPORTED_MSGS_REQ_V01 0x001E
#define QMI_TS_GET_SUPPORTED_MSGS_RESP_V01 0x001E
#define QMI_TS_REGISTER_NOTIFICATION_TEMP_REQ_V01 0x0021
#define QMI_TS_REGISTER_NOTIFICATION_TEMP_RESP_V01 0x0021
#define QMI_TS_GET_SUPPORTED_FIELDS_RESP_V01 0x001F
#define QMI_TS_GET_SENSOR_LIST_REQ_V01 0x0020
#define QMI_TS_TEMP_REPORT_IND_V01 0x0022
#define QMI_TS_GET_SUPPORTED_FIELDS_REQ_V01 0x001F
#define QMI_TS_SENSOR_ID_LENGTH_MAX_V01 32
#define QMI_TS_SENSOR_LIST_MAX_V01 32
struct ts_sensor_type_v01 {
char sensor_id[QMI_TS_SENSOR_ID_LENGTH_MAX_V01 + 1];
};
struct ts_get_sensor_list_req_msg_v01 {
char placeholder;
};
#define TS_GET_SENSOR_LIST_REQ_MSG_V01_MAX_MSG_LEN 0
extern struct qmi_elem_info ts_get_sensor_list_req_msg_v01_ei[];
struct ts_get_sensor_list_resp_msg_v01 {
struct qmi_response_type_v01 resp;
uint8_t sensor_list_valid;
uint32_t sensor_list_len;
struct ts_sensor_type_v01 sensor_list[QMI_TS_SENSOR_LIST_MAX_V01];
};
#define TS_GET_SENSOR_LIST_RESP_MSG_V01_MAX_MSG_LEN 1067
extern struct qmi_elem_info ts_get_sensor_list_resp_msg_v01_ei[];
struct ts_register_notification_temp_req_msg_v01 {
struct ts_sensor_type_v01 sensor_id;
uint8_t send_current_temp_report;
uint8_t temp_threshold_high_valid;
int temp_threshold_high;
uint8_t temp_threshold_low_valid;
int temp_threshold_low;
uint8_t seq_num_valid;
uint32_t seq_num;
};
#define TS_REGISTER_NOTIFICATION_TEMP_REQ_MSG_V01_MAX_MSG_LEN 61
extern struct qmi_elem_info ts_register_notification_temp_req_msg_v01_ei[];
struct ts_register_notification_temp_resp_msg_v01 {
struct qmi_response_type_v01 resp;
};
#define TS_REGISTER_NOTIFICATION_TEMP_RESP_MSG_V01_MAX_MSG_LEN 7
extern struct qmi_elem_info ts_register_notification_temp_resp_msg_v01_ei[];
enum ts_temp_report_type_enum_v01 {
TS_TEMP_REPORT_TYPE_ENUM_MIN_VAL_V01 = INT_MIN,
QMI_TS_TEMP_REPORT_CURRENT_TEMP_V01 = 0,
QMI_TS_TEMP_REPORT_THRESHOLD_HIGH_V01 = 1,
QMI_TS_TEMP_REPORT_THRESHOLD_LOW_V01 = 2,
TS_TEMP_REPORT_TYPE_ENUM_MAX_VAL_V01 = INT_MAX,
};
struct ts_temp_report_ind_msg_v01 {
struct ts_sensor_type_v01 sensor_id;
enum ts_temp_report_type_enum_v01 report_type;
uint8_t temp_valid;
long temp;
uint8_t seq_num_valid;
uint32_t seq_num;
};
#define TS_TEMP_REPORT_IND_MSG_V01_MAX_MSG_LEN 57
extern struct qmi_elem_info ts_temp_report_ind_msg_v01_ei[];
#endif