diff options
author | Srinivas Kandagatla <srinivas.kandagatla@linaro.org> | 2015-01-30 17:42:05 +0000 |
---|---|---|
committer | Srinivas Kandagatla <srinivas.kandagatla@linaro.org> | 2015-01-30 17:42:05 +0000 |
commit | e37b32df53fb60634cc189f14b4bc21be517ce30 (patch) | |
tree | 4331fec8b5a115c70f79d9534c960c406e0fb5d0 | |
parent | 35d233256a2c4359ec474ae64d4332e436899100 (diff) | |
parent | 279fe9083762118a8dd87e41b8ec5149996b5a57 (diff) |
Merge branch 'tracking-qcomlt-tsens' into integration-linux-qcomltqcomlt-v3.19-rc6-30012015
* tracking-qcomlt-tsens:
thermal: qcom: Add 8960 thermal sensor support
WIP: Add wrappers for qfprom access via syscon
WIP: mfd: syscon: Add register stride to DT bindings.
Conflicts:
drivers/soc/qcom/Kconfig
drivers/soc/qcom/Makefile
-rw-r--r-- | Documentation/devicetree/bindings/mfd/syscon.txt | 3 | ||||
-rw-r--r-- | Documentation/devicetree/bindings/soc/qcom/qfprom.txt | 29 | ||||
-rw-r--r-- | drivers/mfd/syscon.c | 9 | ||||
-rw-r--r-- | drivers/soc/qcom/Kconfig | 7 | ||||
-rw-r--r-- | drivers/soc/qcom/Makefile | 1 | ||||
-rw-r--r-- | drivers/soc/qcom/qfprom.c | 134 | ||||
-rw-r--r-- | drivers/thermal/Kconfig | 11 | ||||
-rw-r--r-- | drivers/thermal/Makefile | 1 | ||||
-rw-r--r-- | drivers/thermal/msm8960_tsens.c | 740 | ||||
-rw-r--r-- | include/linux/soc/qcom/qfprom.h | 38 |
10 files changed, 973 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/mfd/syscon.txt b/Documentation/devicetree/bindings/mfd/syscon.txt index fe8150bb3248..7f06ec1ff591 100644 --- a/Documentation/devicetree/bindings/mfd/syscon.txt +++ b/Documentation/devicetree/bindings/mfd/syscon.txt @@ -13,6 +13,9 @@ Required properties: - compatible: Should contain "syscon". - reg: the register region can be accessed from syscon +Optional properties: +- stride : register address stride in bytes. + Examples: gpr: iomuxc-gpr@020e0000 { compatible = "fsl,imx6q-iomuxc-gpr", "syscon"; diff --git a/Documentation/devicetree/bindings/soc/qcom/qfprom.txt b/Documentation/devicetree/bindings/soc/qcom/qfprom.txt new file mode 100644 index 000000000000..3ed73090ad09 --- /dev/null +++ b/Documentation/devicetree/bindings/soc/qcom/qfprom.txt @@ -0,0 +1,29 @@ +QCOM QFPROM + +QFPROM is basically some efuses where things like calibration data, speed bins, +etc are stored. This data is accessed by various drivers like the cpufreq, +thermal, etc. + +Required properties: +- compatible: must contain "qcom,qfprom" followed by "syscon" +- reg: Address range for QFPROM +- stride : register address stride. + 1 for byte. + 2 for 2 bytes + 3 for 3 bytes + 4 for a word. + + +Example: + qfprom: qfprom@00700000 { + compatible = "qcom,qfprom", "syscon"; + reg = <0x00700000 0x1000>; + stride = <1>; + }; + + tsens@34000 { + compatible = "qcom,tsens-apq8064"; + reg = <0x34000 0x1000>; + qcom,qfprom = <&qfprom 0x18 0x10>, <&qfprom 0x28 0x10>; + qcom,qfprom-names = "calib", "backup_calib"; + }; diff --git a/drivers/mfd/syscon.c b/drivers/mfd/syscon.c index 176bf0fa2685..98769d554f1c 100644 --- a/drivers/mfd/syscon.c +++ b/drivers/mfd/syscon.c @@ -48,6 +48,7 @@ static struct syscon *of_syscon_register(struct device_node *np) struct regmap *regmap; void __iomem *base; int ret; + u32 stride; struct regmap_config syscon_config = syscon_regmap_config; if (!of_device_is_compatible(np, "syscon")) @@ -69,6 +70,14 @@ static struct syscon *of_syscon_register(struct device_node *np) else if (of_property_read_bool(np, "little-endian")) syscon_config.val_format_endian = REGMAP_ENDIAN_LITTLE; + if (!of_property_read_u32(np, "stride", &stride)) { + if (stride > 4) + stride = 4; + + syscon_config.reg_stride = stride; + syscon_config.val_bits = 8 * stride; + } + regmap = regmap_init_mmio(NULL, base, &syscon_config); if (IS_ERR(regmap)) { pr_err("regmap init failed\n"); diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 012fb37b3ba9..389ec3ea7190 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -19,3 +19,10 @@ config QCOM_PM QCOM Platform specific power driver to manage cores and L2 low power modes. It interface with various system drivers to put the cores in low power modes. + +config QCOM_QFPROM + tristate "QCOM QFPROM Interface" + depends on ARCH_QCOM && OF + help + Say y here to enable QFPROM support. The QFPROM provides access + functions for QFPROM data to rest of the drivers via syscon wrappers. diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 4d4ff4a09b7d..54249f557778 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -2,3 +2,4 @@ obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o obj-$(CONFIG_QCOM_PM) += spm.o CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1) obj-$(CONFIG_QCOM_SCM) += scm.o scm-boot.o scm-pas.o +obj-$(CONFIG_QCOM_QFPROM) += qfprom.o diff --git a/drivers/soc/qcom/qfprom.c b/drivers/soc/qcom/qfprom.c new file mode 100644 index 000000000000..d00ed25235d7 --- /dev/null +++ b/drivers/soc/qcom/qfprom.c @@ -0,0 +1,134 @@ +#include <linux/err.h> +#include <linux/of.h> +#include <linux/device.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> +#include <linux/soc/qcom/qfprom.h> +#include <linux/slab.h> + +#define QFPROM_MAX_ARGS 2 + +static char *__qfprom_get_data(struct device *dev, + bool devm, int idx, int *len) +{ + struct device_node *syscon_np, *np = dev->of_node; + struct regmap *rm; + struct of_phandle_args args; + int rc, stride = 4; + u32 offset, size; + char *data; + + if (!np) + return ERR_PTR(-EINVAL); + + rc = of_parse_phandle_with_fixed_args(np, "qcom,qfprom", + QFPROM_MAX_ARGS, idx, &args); + if (rc) + return ERR_PTR(rc); + + syscon_np = args.np; + + of_property_read_u32(syscon_np, "stride", &stride); + + if (stride >= 4) + stride = 4; + + if (args.args_count < QFPROM_MAX_ARGS) { + dev_err(dev, "Insufficient qfprom arguments %d\n", + args.args_count); + return ERR_PTR(-EINVAL); + } + + rm = syscon_node_to_regmap(syscon_np); + if (IS_ERR(rm)) + return ERR_CAST(rm); + + offset = args.args[0]; + size = args.args[1]; + + of_node_put(syscon_np); + + if (devm) + data = devm_kzalloc(dev, size, GFP_KERNEL | GFP_ATOMIC); + else + data = kzalloc(size, GFP_KERNEL | GFP_ATOMIC); + + if (!data) + return ERR_PTR(-ENOMEM); + + rc = regmap_bulk_read(rm, offset, data, size/stride); + if (rc < 0) { + if (devm) + devm_kfree(dev, data); + else + kfree(data); + + return ERR_PTR(rc); + } + + *len = size; + + return data; +} + +static char *__qfprom_get_data_byname(struct device *dev, + bool devm, const char *name, int *len) +{ + int index = 0; + + if (name) + index = of_property_match_string(dev->of_node, + "qcom,qfprom-names", name); + + return __qfprom_get_data(dev, devm, index, len); +} + +char *devm_qfprom_get_data_byname(struct device *dev, + const char *name, int *len) +{ + return __qfprom_get_data_byname(dev, true, name, len); +} +EXPORT_SYMBOL_GPL(devm_qfprom_get_data_byname); + +char *devm_qfprom_get_data(struct device *dev, + int index, int *len) +{ + return __qfprom_get_data(dev, true, index, len); +} +EXPORT_SYMBOL_GPL(devm_qfprom_get_data); + +/** + * qfprom_get_data_byname(): Reads qfprom data by name + * + * @dev: device which is requesting qfprom data + * @index: name of qfprom resources specified "qcom,qfprom-names" DT property. + * @len: length of data read from qfprom. + * + * The return value will be an ERR_PTR() on error or a valid pointer + * to a data buffer. The buffer should be freed by the user once its + * finished working with it kfree. + **/ +char *qfprom_get_data_byname(struct device *dev, + const char *name, int *len) +{ + return __qfprom_get_data_byname(dev, false, name, len); +} +EXPORT_SYMBOL_GPL(qfprom_get_data_byname); + +/** + * qfprom_get_data(): Reads qfprom data from the index + * + * @dev: device which is requesting qfprom data + * @index: index into qfprom resources specified "qcom,qfprom" DT property. + * @len: length of data read from qfprom. + * + * The return value will be an ERR_PTR() on error or a valid pointer + * to a data buffer. The buffer should be freed by the user once its + * finished working with it kfree. + **/ +char *qfprom_get_data(struct device *dev, + int index, int *len) +{ + return __qfprom_get_data(dev, false, index, len); +} +EXPORT_SYMBOL_GPL(qfprom_get_data); diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index af40db0df58e..4b4346da8d44 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -281,6 +281,17 @@ config INT340X_THERMAL information to allow the user to select his laptop to run without turning on the fans. +config THERMAL_TSENS8960 + tristate "Qualcomm 8960 Tsens Temperature Alarm" + depends on THERMAL + select QCOM_QFPROM + help + This enables the thermal sysfs driver for the Tsens device. It shows + up in Sysfs as a thermal zone with mutiple trip points. Disabling the + thermal zone device via the mode file results in disabling the sensor. + Also able to set threshold temperature for both hot and cold and update + when a threshold is reached. + config ACPI_THERMAL_REL tristate depends on ACPI diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index fa0dc486790f..2e695c0da885 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -35,6 +35,7 @@ obj-$(CONFIG_DB8500_CPUFREQ_COOLING) += db8500_cpufreq_cooling.o obj-$(CONFIG_INTEL_POWERCLAMP) += intel_powerclamp.o obj-$(CONFIG_X86_PKG_TEMP_THERMAL) += x86_pkg_temp_thermal.o obj-$(CONFIG_INTEL_SOC_DTS_THERMAL) += intel_soc_dts_thermal.o +obj-$(CONFIG_THERMAL_TSENS8960) += msm8960_tsens.o obj-$(CONFIG_TI_SOC_THERMAL) += ti-soc-thermal/ obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal/ obj-$(CONFIG_ST_THERMAL) += st/ diff --git a/drivers/thermal/msm8960_tsens.c b/drivers/thermal/msm8960_tsens.c new file mode 100644 index 000000000000..f13f10770cde --- /dev/null +++ b/drivers/thermal/msm8960_tsens.c @@ -0,0 +1,740 @@ +/* + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/mfd/syscon.h> +#include <linux/thermal.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/pm.h> +#include <linux/soc/qcom/qfprom.h> +#include <linux/regmap.h> +#include <linux/of.h> + +#define TSENS_CNTL_ADDR 0x3620 +#define TSENS_EN BIT(0) +#define TSENS_SW_RST BIT(1) +#define TSENS_ADC_CLK_SEL BIT(2) +#define SENSOR0_EN BIT(3) +#define TSENS_SENSOR0_SHIFT 3 +#define TSENS_MASK1 1 +#define SENSORS_EN_MASK(n) GENMASK((n + 2), 3) + +#define TSENS_TH_ADDR 0x3624 +#define TSENS_TH_MAX_LIMIT_SHIFT 24 +#define TSENS_TH_MIN_LIMIT_SHIFT 16 +#define TSENS_TH_UPPER_LIMIT_SHIFT 8 +#define TSENS_TH_LOWER_LIMIT_SHIFT 0 +#define TSENS_TH_MAX_LIMIT_MASK GENMASK(31, 24) +#define TSENS_TH_MIN_LIMIT_MASK GENMASK(23, 16) +#define TSENS_TH_UPPER_LIMIT_MASK GENMASK(15, 8) +#define TSENS_TH_LOWER_LIMIT_MASK GENMASK(7, 0) + +#define TSENS_S0_STATUS_ADDR 0x3628 +#define TSENS_STATUS_ADDR_OFFSET 2 + +#define TSENS_INT_STATUS_ADDR 0x363c +#define TSENS_LOWER_INT_MASK BIT(1) +#define TSENS_UPPER_INT_MASK BIT(2) +#define TSENS_MAX_INT_MASK BIT(3) +#define TSENS_TRDY_MASK BIT(7) + +#define TSENS_TH_MAX_CODE 0xff +#define TSENS_TH_MIN_CODE 0 +#define TSENS_TRDY_RDY_MIN_TIME 1000 +#define TSENS_TRDY_RDY_MAX_TIME 1100 + +#define TSENS_MEASURE_PERIOD 1 +/* Initial temperature threshold values */ +#define TSENS_LOWER_LIMIT_TH 0x50 +#define TSENS_UPPER_LIMIT_TH 0xdf +#define TSENS_MIN_LIMIT_TH 0x0 +#define TSENS_MAX_LIMIT_TH 0xff + +#define TSENS_MIN_STATUS_MASK(offset) BIT((offset)) +#define TSENS_LOWER_STATUS_CLR(offset) BIT((offset + 1)) +#define TSENS_UPPER_STATUS_CLR(offset) BIT((offset + 2)) +#define TSENS_MAX_STATUS_MASK(offset) BIT((offset + 3)) + +/* QFPROM addresses */ +#define TSENS_8960_QFPROM_ADDR0 0x0 +#define TSENS_8960_QFPROM_SPARE_OFFSET 0x10 +/* 8960 Specifics */ +#define TSENS_8960_CONFIG 0x9b +#define TSENS_8960_CONFIG_MASK GENMASK(7, 0) +#define TSENS_8960_CONFIG_ADDR 0x3640 +#define TSENS_8064_STATUS_CNTL 0x3660 +#define TSENS_8064_SEQ_SENSORS 5 +#define TSENS_8064_S4_S5_OFFSET 40 + +#define TSENS_CAL_MILLI_DEGC 30000 +#define TSENS_MAX_SENSORS 11 + +/* Trips: from very hot to very cold */ +enum tsens_trip_type { + TSENS_TRIP_STAGE3 = 0, + TSENS_TRIP_STAGE2, + TSENS_TRIP_STAGE1, + TSENS_TRIP_STAGE0, + TSENS_TRIP_NUM, +}; + +struct tsens_tm_device; + +struct tsens_tm_device_sensor { + struct thermal_zone_device *tzone; + enum thermal_device_mode mode; + struct tsens_tm_device *tmdev; + unsigned int sensor_num; + int offset; + uint32_t slope; + bool user_zone; +}; + +struct tsens_variant_data { + int slope[TSENS_MAX_SENSORS]; + u32 nsensors; + /* enable to tsens slp clock */ + u32 slp_clk_ena; + u32 sctrl_reg; + u32 sctrl_offset; + u32 config_reg; + u32 config_mask; + u32 config; + int (*calib_sensors)(struct tsens_tm_device *tmdev, struct device *dev); +}; + +struct tsens_tm_device { + struct regmap *base; + struct tsens_variant_data *data; + struct work_struct tsens_work; + int nsensors; + int irq; + /* tsens ready bit for reading valid temperature */ + bool trdy; + struct tsens_tm_device_sensor sensor[0]; +}; + +/* Temperature on y axis and ADC-code on x-axis */ +static inline int tsens_code_to_degc(struct tsens_tm_device_sensor *s, + int adc_code) +{ + return adc_code * s->slope + s->offset; +} + +static inline int tsens_degc_to_code(struct tsens_tm_device_sensor *s, int degc) +{ + return (degc - s->offset + s->slope / 2) / s->slope; +} + +static int __tsens_get_temp(void *data, long *temp) +{ + struct tsens_tm_device_sensor *tm_sensor = data; + unsigned int code, offset = 0; + struct tsens_tm_device *tmdev = tm_sensor->tmdev; + int sensor_num = tm_sensor->sensor_num; + struct regmap *base = tmdev->base; + + if (!tmdev->trdy) { + u32 val; + + regmap_read(base, TSENS_INT_STATUS_ADDR, &val); + + while (!(val & TSENS_TRDY_MASK)) { + usleep_range(TSENS_TRDY_RDY_MIN_TIME, + TSENS_TRDY_RDY_MAX_TIME); + regmap_read(base, TSENS_INT_STATUS_ADDR, &val); + } + tmdev->trdy = true; + } +//FIXME + if (sensor_num >= TSENS_8064_SEQ_SENSORS) + offset = TSENS_8064_S4_S5_OFFSET; + + regmap_read(base, TSENS_S0_STATUS_ADDR + offset + + (sensor_num << TSENS_STATUS_ADDR_OFFSET), &code); + *temp = tsens_code_to_degc(tm_sensor, code); + + return 0; +} + +static int tsens_get_temp(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + struct tsens_tm_device_sensor *tm_sensor = thermal->devdata; + + if (!tm_sensor || tm_sensor->mode != THERMAL_DEVICE_ENABLED) + return -EINVAL; + + return __tsens_get_temp(tm_sensor, temp); + +} + +static int tsens_get_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode *mode) +{ + struct tsens_tm_device_sensor *tm_sensor = thermal->devdata; + + if (tm_sensor) + *mode = tm_sensor->mode; + + return 0; +} + +/** + * Function to enable the mode. + * If the main sensor is disabled all the sensors are disable and + * the clock is disabled. + * If the main sensor is not enabled and sub sensor is enabled + * returns with an error stating the main sensor is not enabled. + **/ + +static int tsens_set_mode(struct thermal_zone_device *thermal, + enum thermal_device_mode mode) +{ + struct tsens_tm_device_sensor *tm_sensor = thermal->devdata; + struct tsens_tm_device *tmdev = tm_sensor->tmdev; + struct tsens_variant_data *data = tmdev->data; + struct regmap *base = tmdev->base; + unsigned int reg, mask, i; + + if (!tm_sensor) + return -EINVAL; + + if (mode != tm_sensor->mode) { + regmap_read(base, TSENS_CNTL_ADDR, ®); + mask = BIT(tm_sensor->sensor_num + TSENS_SENSOR0_SHIFT); + if (mode == THERMAL_DEVICE_ENABLED) { + if ((mask != SENSOR0_EN) && !(reg & SENSOR0_EN)) { + pr_info("Main sensor not enabled\n"); + return -EINVAL; + } + regmap_write(base, TSENS_CNTL_ADDR, reg | TSENS_SW_RST); + reg |= mask | data->slp_clk_ena | TSENS_EN; + tmdev->trdy = false; + } else { + reg &= ~mask; + if (!(reg & SENSOR0_EN)) { + reg &= ~(SENSORS_EN_MASK(tmdev->nsensors) | + data->slp_clk_ena | + TSENS_EN); + + for (i = 1; i < tmdev->nsensors; i++) + tmdev->sensor[i].mode = mode; + + } + } + regmap_write(base, TSENS_CNTL_ADDR, reg); + } + tm_sensor->mode = mode; + + return 0; +} + +static int tsens_get_trip_type(struct thermal_zone_device *thermal, + int trip, enum thermal_trip_type *type) +{ + struct tsens_tm_device_sensor *tm_sensor = thermal->devdata; + + if (!tm_sensor || trip < 0 || !type) + return -EINVAL; + + switch (trip) { + case TSENS_TRIP_STAGE3: + *type = THERMAL_TRIP_CRITICAL; + break; + case TSENS_TRIP_STAGE2: + *type = THERMAL_TRIP_HOT; + break; + case TSENS_TRIP_STAGE1: + *type = THERMAL_TRIP_PASSIVE; + break; + case TSENS_TRIP_STAGE0: + *type = THERMAL_TRIP_ACTIVE; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int tsens_get_trip_temp(struct thermal_zone_device *thermal, + int trip, unsigned long *temp) +{ + struct tsens_tm_device_sensor *tm_sensor = thermal->devdata; + struct tsens_tm_device *tmdev = tm_sensor->tmdev; + unsigned int reg; + + if (!tm_sensor || trip < 0 || !temp) + return -EINVAL; + + regmap_read(tmdev->base, TSENS_TH_ADDR, ®); + switch (trip) { + case TSENS_TRIP_STAGE3: + reg = (reg & TSENS_TH_MAX_LIMIT_MASK) + >> TSENS_TH_MAX_LIMIT_SHIFT; + break; + case TSENS_TRIP_STAGE2: + reg = (reg & TSENS_TH_UPPER_LIMIT_MASK) + >> TSENS_TH_UPPER_LIMIT_SHIFT; + break; + case TSENS_TRIP_STAGE1: + reg = (reg & TSENS_TH_LOWER_LIMIT_MASK) + >> TSENS_TH_LOWER_LIMIT_SHIFT; + break; + case TSENS_TRIP_STAGE0: + reg = (reg & TSENS_TH_MIN_LIMIT_MASK) + >> TSENS_TH_MIN_LIMIT_SHIFT; + break; + default: + return -EINVAL; + } + + *temp = tsens_code_to_degc(tm_sensor, reg); + + return 0; +} + +static int tsens_set_trip_temp(struct thermal_zone_device *thermal, + int trip, unsigned long temp) +{ + struct tsens_tm_device_sensor *tm_sensor = thermal->devdata; + unsigned int reg_th, reg_cntl; + int code, hi_code, lo_code, code_err_chk; + struct tsens_tm_device *tmdev = tm_sensor->tmdev; + struct regmap *base = tmdev->base; + struct tsens_variant_data *data = tmdev->data; + u32 offset = data->sctrl_offset; + + code_err_chk = code = tsens_degc_to_code(tm_sensor, temp); + if (!tm_sensor || trip < 0) + return -EINVAL; + + lo_code = TSENS_TH_MIN_CODE; + hi_code = TSENS_TH_MAX_CODE; + + regmap_read(base, data->sctrl_reg, ®_cntl); + + regmap_read(base, TSENS_TH_ADDR, ®_th); + switch (trip) { + case TSENS_TRIP_STAGE3: + code <<= TSENS_TH_MAX_LIMIT_SHIFT; + reg_th &= ~TSENS_TH_MAX_LIMIT_MASK; + + if (!(reg_cntl & TSENS_UPPER_STATUS_CLR(offset))) + lo_code = (reg_th & TSENS_TH_UPPER_LIMIT_MASK) + >> TSENS_TH_UPPER_LIMIT_SHIFT; + else if (!(reg_cntl & TSENS_LOWER_STATUS_CLR(offset))) + lo_code = (reg_th & TSENS_TH_LOWER_LIMIT_MASK) + >> TSENS_TH_LOWER_LIMIT_SHIFT; + else if (!(reg_cntl & TSENS_MIN_STATUS_MASK(offset))) + lo_code = (reg_th & TSENS_TH_MIN_LIMIT_MASK) + >> TSENS_TH_MIN_LIMIT_SHIFT; + break; + case TSENS_TRIP_STAGE2: + code <<= TSENS_TH_UPPER_LIMIT_SHIFT; + reg_th &= ~TSENS_TH_UPPER_LIMIT_MASK; + + if (!(reg_cntl & TSENS_MAX_STATUS_MASK(offset))) + hi_code = (reg_th & TSENS_TH_MAX_LIMIT_MASK) + >> TSENS_TH_MAX_LIMIT_SHIFT; + if (!(reg_cntl & TSENS_LOWER_STATUS_CLR(offset))) + lo_code = (reg_th & TSENS_TH_LOWER_LIMIT_MASK) + >> TSENS_TH_LOWER_LIMIT_SHIFT; + else if (!(reg_cntl & TSENS_MIN_STATUS_MASK(offset))) + lo_code = (reg_th & TSENS_TH_MIN_LIMIT_MASK) + >> TSENS_TH_MIN_LIMIT_SHIFT; + break; + case TSENS_TRIP_STAGE1: + code <<= TSENS_TH_LOWER_LIMIT_SHIFT; + reg_th &= ~TSENS_TH_LOWER_LIMIT_MASK; + + if (!(reg_cntl & TSENS_MIN_STATUS_MASK(offset))) + lo_code = (reg_th & TSENS_TH_MIN_LIMIT_MASK) + >> TSENS_TH_MIN_LIMIT_SHIFT; + if (!(reg_cntl & TSENS_UPPER_STATUS_CLR(offset))) + hi_code = (reg_th & TSENS_TH_UPPER_LIMIT_MASK) + >> TSENS_TH_UPPER_LIMIT_SHIFT; + else if (!(reg_cntl & TSENS_MAX_STATUS_MASK(offset))) + hi_code = (reg_th & TSENS_TH_MAX_LIMIT_MASK) + >> TSENS_TH_MAX_LIMIT_SHIFT; + break; + case TSENS_TRIP_STAGE0: + code <<= TSENS_TH_MIN_LIMIT_SHIFT; + reg_th &= ~TSENS_TH_MIN_LIMIT_MASK; + + if (!(reg_cntl & TSENS_LOWER_STATUS_CLR(offset))) + hi_code = (reg_th & TSENS_TH_LOWER_LIMIT_MASK) + >> TSENS_TH_LOWER_LIMIT_SHIFT; + else if (!(reg_cntl & TSENS_UPPER_STATUS_CLR(offset))) + hi_code = (reg_th & TSENS_TH_UPPER_LIMIT_MASK) + >> TSENS_TH_UPPER_LIMIT_SHIFT; + else if (!(reg_cntl & TSENS_MAX_STATUS_MASK(offset))) + hi_code = (reg_th & TSENS_TH_MAX_LIMIT_MASK) + >> TSENS_TH_MAX_LIMIT_SHIFT; + break; + default: + return -EINVAL; + } + + if (code_err_chk < lo_code || code_err_chk > hi_code) + return -EINVAL; + + regmap_write(base, TSENS_TH_ADDR, reg_th | code); + + return 0; +} + +static int tsens_get_crit_temp(struct thermal_zone_device *thermal, + unsigned long *temp) +{ + return tsens_get_trip_temp(thermal, TSENS_TRIP_STAGE3, temp); +} + +static struct thermal_zone_device_ops tsens_thermal_zone_ops = { + .get_temp = tsens_get_temp, + .get_mode = tsens_get_mode, + .set_mode = tsens_set_mode, + .get_trip_type = tsens_get_trip_type, + .get_trip_temp = tsens_get_trip_temp, + .set_trip_temp = tsens_set_trip_temp, + .get_crit_temp = tsens_get_crit_temp, +}; + +static const struct thermal_zone_of_device_ops tsens_of_thermal_ops = { + .get_temp = __tsens_get_temp, +}; + +static void tsens_scheduler_fn(struct work_struct *work) +{ + struct tsens_tm_device *tmdev = container_of(work, + struct tsens_tm_device, tsens_work); + unsigned int threshold, threshold_low, i, code, reg, sensor, mask; + unsigned int sensor_addr; + bool upper_th_x, lower_th_x; + struct regmap *base = tmdev->base; + struct tsens_variant_data *data = tmdev->data; + u32 offset = data->sctrl_offset; + + regmap_read(base, data->sctrl_reg, ®); + regmap_write(base, data->sctrl_reg, reg | + TSENS_LOWER_STATUS_CLR(offset) | + TSENS_UPPER_STATUS_CLR(offset)); + + mask = ~(TSENS_LOWER_STATUS_CLR(offset) | TSENS_UPPER_STATUS_CLR(offset)); + regmap_read(tmdev->base, TSENS_TH_ADDR, &threshold); + threshold_low = (threshold & TSENS_TH_LOWER_LIMIT_MASK) + >> TSENS_TH_LOWER_LIMIT_SHIFT; + threshold = (threshold & TSENS_TH_UPPER_LIMIT_MASK) + >> TSENS_TH_UPPER_LIMIT_SHIFT; + + regmap_read(base, TSENS_CNTL_ADDR, &sensor); + sensor &= SENSORS_EN_MASK(tmdev->nsensors); + sensor >>= TSENS_SENSOR0_SHIFT; + sensor_addr = TSENS_S0_STATUS_ADDR; + for (i = 0; i < tmdev->nsensors; i++) { + if (i == TSENS_8064_SEQ_SENSORS) + sensor_addr += TSENS_8064_S4_S5_OFFSET; + if (sensor & TSENS_MASK1) { + regmap_read(base, sensor_addr, &code); + upper_th_x = code >= threshold; + lower_th_x = code <= threshold_low; + if (upper_th_x) + mask |= TSENS_UPPER_STATUS_CLR(offset); + if (lower_th_x) + mask |= TSENS_LOWER_STATUS_CLR(offset); + if (upper_th_x || lower_th_x) + thermal_zone_device_update(tmdev->sensor[i].tzone); + } + sensor >>= 1; + sensor_addr += 4; + } + regmap_read(base, data->sctrl_reg, ®); + regmap_write(base, data->sctrl_reg, reg & mask); +} + +static irqreturn_t tsens_isr(int irq, void *dev) +{ + struct tsens_tm_device *tmdev = dev; + + schedule_work(&tmdev->tsens_work); + + return IRQ_HANDLED; +} + +static void tsens_disable_mode(struct tsens_tm_device *tmdev) +{ + unsigned int reg_cntl = 0; + struct regmap *base = tmdev->base; + struct tsens_variant_data *data = tmdev->data; + + regmap_read(base, TSENS_CNTL_ADDR, ®_cntl); + regmap_write(base, TSENS_CNTL_ADDR, reg_cntl & + ~(SENSORS_EN_MASK(tmdev->nsensors) | data->slp_clk_ena + | TSENS_EN)); +} + +static void tsens_hw_init(struct tsens_tm_device *tmdev) +{ + unsigned int reg_cntl = 0, reg_cfg = 0, reg_thr = 0; + unsigned int reg_status_cntl = 0; + struct regmap *base = tmdev->base; + struct tsens_variant_data *data = tmdev->data; + u32 offset = data->sctrl_offset; + + regmap_read(base, TSENS_CNTL_ADDR, ®_cntl); + regmap_write(base, TSENS_CNTL_ADDR, reg_cntl | TSENS_SW_RST); + + reg_cntl |= data->slp_clk_ena | + (TSENS_MEASURE_PERIOD << 18) | + SENSORS_EN_MASK(tmdev->nsensors); + regmap_write(base, TSENS_CNTL_ADDR, reg_cntl); + + regmap_read(base, data->sctrl_reg, ®_status_cntl); + reg_status_cntl |= TSENS_LOWER_STATUS_CLR(offset) | + TSENS_UPPER_STATUS_CLR(offset) | + TSENS_MIN_STATUS_MASK(offset) | + TSENS_MAX_STATUS_MASK(offset); + + regmap_write(base, data->sctrl_reg, reg_status_cntl); + + + reg_cntl |= TSENS_EN; + regmap_write(base, TSENS_CNTL_ADDR, reg_cntl); + + regmap_read(base, data->config_reg, ®_cfg); + reg_cfg = (reg_cfg & ~data->config_mask) | data->config; + regmap_write(base, data->config_reg, reg_cfg); + + reg_thr |= (TSENS_LOWER_LIMIT_TH << TSENS_TH_LOWER_LIMIT_SHIFT) | + (TSENS_UPPER_LIMIT_TH << TSENS_TH_UPPER_LIMIT_SHIFT) | + (TSENS_MIN_LIMIT_TH << TSENS_TH_MIN_LIMIT_SHIFT) | + (TSENS_MAX_LIMIT_TH << TSENS_TH_MAX_LIMIT_SHIFT); + regmap_write(base, TSENS_TH_ADDR, reg_thr); +} + +static int tsens_8960_calib_sensors(struct tsens_tm_device *tmdev, struct device *dev) +{ + + uint8_t calib_data, calib_data_backup; + int sz, i;// = round_up(tmdev->qfprom_size, sizeof(int)); + u8 *cdata, *cdata_backup; + + cdata = devm_qfprom_get_data(dev, 0, &sz); + cdata_backup = devm_qfprom_get_data(dev, 1, &sz); + + if (!cdata || !cdata_backup) + return -EINVAL; + + for (i = 0; i < tmdev->nsensors; i++) { + calib_data = cdata[i]; + calib_data_backup = cdata_backup[i]; + if (calib_data_backup) + calib_data = calib_data_backup; + + if (!calib_data) { + pr_err("QFPROM TSENS calibration data not present\n"); + return -ENODEV; + } + tmdev->sensor[i].offset = TSENS_CAL_MILLI_DEGC - (calib_data * tmdev->sensor[i].slope); + tmdev->trdy = false; + } + + return 0; +} + +static int tsens_calib_sensors(struct tsens_tm_device *tmdev, struct device *dev) +{ + if (tmdev->data->calib_sensors) + return tmdev->data->calib_sensors(tmdev, dev); + + return -ENODEV; +} + +struct tsens_variant_data msm8960_data = +{ + /** + * Slope for a thermocouple in a given part is always same + * for desired range of temperature measurements + **/ + .slope = {910, 910, 910, 910, 910}, + .nsensors = 5, + .slp_clk_ena = BIT(26), + .sctrl_reg = TSENS_CNTL_ADDR, + .sctrl_offset = 8, + .config_reg = TSENS_8960_CONFIG_ADDR, + .config_mask = TSENS_8960_CONFIG_MASK, + .config = TSENS_8960_CONFIG, + .calib_sensors = tsens_8960_calib_sensors, +}; + +struct tsens_variant_data apq8064_data = { + /** + * Slope for a thermocouple in a given part is always same + * for desired range of temperature measurements + **/ + .slope = {1176, 1176, 1154, 1176, 1111, 1132, + 1132, 1199, 1132, 1199, 1132}, + .nsensors = 11, + .slp_clk_ena = BIT(26), + .sctrl_reg = TSENS_8064_STATUS_CNTL, + .sctrl_offset = 0, + .config_reg = TSENS_8960_CONFIG_ADDR, + .config_mask = TSENS_8960_CONFIG_MASK, + .config = TSENS_8960_CONFIG, + .calib_sensors = tsens_8960_calib_sensors, +}; + +static struct of_device_id qcom_tsens_of_match[] = { + { .compatible = "qcom,msm8960-tsens", .data = &msm8960_data }, + { .compatible = "qcom,apq8064-tsens", .data = &apq8064_data }, +}; +MODULE_DEVICE_TABLE(of, qcom_tsens_of_match); + +static int tsens_tm_probe(struct platform_device *pdev) +{ + int rc, i; + struct device *dev = &pdev->dev; + struct tsens_tm_device *tmdev; + struct tsens_variant_data *data; + struct device_node *np = pdev->dev.of_node; + + if (!np) { + dev_err(dev, "Non DT not supported\n"); + return -EINVAL; + } + + data = (struct tsens_variant_data *)of_match_node(qcom_tsens_of_match, np)->data; + tmdev = devm_kzalloc(dev, sizeof(struct tsens_tm_device) + + data->nsensors * + sizeof(struct tsens_tm_device_sensor), + GFP_ATOMIC); + if (tmdev == NULL) { + pr_err("%s: kzalloc() failed.\n", __func__); + return -ENOMEM; + } + + for (i = 0; i < data->nsensors; i++) + tmdev->sensor[i].slope = data->slope[i]; + + tmdev->data = data; + tmdev->nsensors = data->nsensors; + tmdev->base = dev_get_regmap(dev->parent, NULL); + + if (!tmdev->base) { + dev_err(&pdev->dev, "Parent regmap unavailable.\n"); + return -ENXIO; + } + + rc = tsens_calib_sensors(tmdev, dev); + if (rc < 0) + return rc; + + tsens_hw_init(tmdev); + + for (i = 0; i < tmdev->nsensors; i++) { + char name[18]; + snprintf(name, sizeof(name), "tsens_sensor%d", i); + tmdev->sensor[i].mode = THERMAL_DEVICE_ENABLED; + tmdev->sensor[i].sensor_num = i; + tmdev->sensor[i].tmdev = tmdev; + + tmdev->sensor[i].tzone = thermal_zone_of_sensor_register(dev, i, + &tmdev->sensor[i], &tsens_of_thermal_ops); + + if (IS_ERR(tmdev->sensor[i].tzone)) { + tmdev->sensor[i].tzone = thermal_zone_device_register( + name, + TSENS_TRIP_NUM, + GENMASK(TSENS_TRIP_NUM - 1 , 0), + &tmdev->sensor[i], + &tsens_thermal_zone_ops, NULL, 0, 0); + if (IS_ERR(tmdev->sensor[i].tzone)) { + dev_err(dev, "thermal_zone_device_register() failed.\n"); + rc = -ENODEV; + goto fail; + } + tmdev->sensor[i].user_zone = true; + } + } + + tmdev->irq = platform_get_irq_byname(pdev, "tsens-ul"); + if (tmdev->irq > 0) { + rc = devm_request_irq(dev, tmdev->irq, tsens_isr, + IRQF_TRIGGER_RISING, + "tsens_interrupt", tmdev); + if (rc < 0) { + pr_err("%s: request_irq FAIL: %d\n", __func__, rc); + for (i = 0; i < tmdev->nsensors; i++) + if (tmdev->sensor[i].user_zone) + thermal_zone_device_unregister( + tmdev->sensor[i].tzone); + else + thermal_zone_of_sensor_unregister(dev, + tmdev->sensor[i].tzone); + goto fail; + } + INIT_WORK(&tmdev->tsens_work, tsens_scheduler_fn); + } + platform_set_drvdata(pdev, tmdev); + + dev_info(dev, "Probed sucessfully\n"); + + return 0; +fail: + tsens_disable_mode(tmdev); + + return rc; +} + +static int tsens_tm_remove(struct platform_device *pdev) +{ + int i; + struct tsens_tm_device *tmdev = platform_get_drvdata(pdev); + struct tsens_tm_device_sensor *s; + + tsens_disable_mode(tmdev); + + for (i = 0; i < tmdev->nsensors; i++) { + s = &tmdev->sensor[i]; + if (s->user_zone) + thermal_zone_device_unregister(s->tzone); + else + thermal_zone_of_sensor_unregister(&pdev->dev, + s->tzone); + } + + return 0; +} + +static struct platform_driver tsens_tm_driver = { + .probe = tsens_tm_probe, + .remove = tsens_tm_remove, + .driver = { + .name = "tsens8960-tm", + .owner = THIS_MODULE, + .of_match_table = qcom_tsens_of_match, + }, +}; + +module_platform_driver(tsens_tm_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MSM8960 Temperature Sensor driver"); +MODULE_VERSION("1.0"); +MODULE_ALIAS("platform:tsens8960-tm"); diff --git a/include/linux/soc/qcom/qfprom.h b/include/linux/soc/qcom/qfprom.h new file mode 100644 index 000000000000..b56b64220e60 --- /dev/null +++ b/include/linux/soc/qcom/qfprom.h @@ -0,0 +1,38 @@ +#ifndef __QCOM_QFPROM_H__ +#define __QCOM_QFPROM_H__ + +#ifdef CONFIG_QCOM_QFPROM + +char *qfprom_get_data_byname(struct device *dev, const char *name, int *len); +char *devm_qfprom_get_data_byname(struct device *dev, + const char *name, int *len); +char *qfprom_get_data(struct device *dev, int index, int *len); +char *devm_qfprom_get_data(struct device *dev, int index, int *len); + +#else + +static inline char *qfprom_get_data_byname(struct device *dev, + const char *name) +{ + return NULL: +} + +static inline char *qfprom_get_data(struct device *dev, int index) +{ + return NULL: +} + +static inline char *devm_qfprom_get_data_byname(struct device *dev, + const char *name); +{ + return NULL: +} + +static inline char *devm_qfprom_get_data(struct device *dev, int index) +{ + return NULL: +} + +#endif /* CONFIG_QCOM_QFPROM */ + +#endif /* __QCOM_QFPROM_H__ */ |