diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig index e041de6e4349..fa28e7cd1424 100644 --- a/drivers/thermal/qcom/Kconfig +++ b/drivers/thermal/qcom/Kconfig @@ -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 diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index 5a1d1b1a7998..0dcc9eb90749 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -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 diff --git a/drivers/thermal/qcom/bcl_pmic5.c b/drivers/thermal/qcom/bcl_pmic5.c new file mode 100644 index 000000000000..7c6d27167782 --- /dev/null +++ b/drivers/thermal/qcom/bcl_pmic5.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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"); diff --git a/drivers/thermal/qcom/bcl_soc.c b/drivers/thermal/qcom/bcl_soc.c new file mode 100644 index 000000000000..bce7ebeb33b9 --- /dev/null +++ b/drivers/thermal/qcom/bcl_soc.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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"); diff --git a/drivers/thermal/qcom/qmi_sensors.c b/drivers/thermal/qcom/qmi_sensors.c new file mode 100644 index 000000000000..92e039a850af --- /dev/null +++ b/drivers/thermal/qcom/qmi_sensors.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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"); diff --git a/drivers/thermal/qcom/thermal_sensor_service_v01.c b/drivers/thermal/qcom/thermal_sensor_service_v01.c new file mode 100644 index 000000000000..9cae54a23a2d --- /dev/null +++ b/drivers/thermal/qcom/thermal_sensor_service_v01.c @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2018-2019, The Linux Foundation. All rights reserved. + */ + +#include +#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, + }, +}; + diff --git a/drivers/thermal/qcom/thermal_sensor_service_v01.h b/drivers/thermal/qcom/thermal_sensor_service_v01.h new file mode 100644 index 000000000000..b66f14b5c2d9 --- /dev/null +++ b/drivers/thermal/qcom/thermal_sensor_service_v01.h @@ -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