From patchwork Tue Feb 18 08:10:27 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Baolin Wang X-Patchwork-Id: 11387963 X-Patchwork-Delegate: daniel.lezcano@linaro.org Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id ACE9B139A for ; Tue, 18 Feb 2020 08:11:38 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 832CD24649 for ; Tue, 18 Feb 2020 08:11:38 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="mtwB5luA" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726312AbgBRILe (ORCPT ); Tue, 18 Feb 2020 03:11:34 -0500 Received: from mail-pl1-f195.google.com ([209.85.214.195]:42693 "EHLO mail-pl1-f195.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726186AbgBRILe (ORCPT ); Tue, 18 Feb 2020 03:11:34 -0500 Received: by mail-pl1-f195.google.com with SMTP id e8so7761065plt.9; Tue, 18 Feb 2020 00:11:33 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id; bh=QUPlxwKLP9HDsSXAYmsXVJM5TniLPju/R6y5R75AffU=; b=mtwB5luAFOm5K7gzOCix+RXz1ZERgI7b1W1WEWuYrq6mbh15n2JrrS4LFvK3xJER91 BOZ0jFtv6kfg8fuIFHhvbjdG8G4bj8Se/XQPjSXtsOtUMOHyCW3rTb+Xvwh4uzYyVWIZ NwuWwPTDDjre/6kpTtMMDln0D02xnLEX8Zfs+d5d85KHgQAqCt1pkjLGVADXLT07V9Qw FUP4lS6vtT3VEGGda7R2nCJFr8x6DPkpl+a6bnrZ6tvJ2MkCUfnSwI/Y62K+C/vaeI1w mz9S0gIqDJCZRdedJS/ifw3g4r5LlOxzWWkDjNFdRfkGs0fNLB5Pxd3n52ODSiuRhmyO KF+w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id; bh=QUPlxwKLP9HDsSXAYmsXVJM5TniLPju/R6y5R75AffU=; b=LjG5NPXcwztz/AudPo7+/W6sZiucA2upysBF4N3Rlibvyit7XLWIfSy5ZBI2kVDkol Cqk2Fc5l3tx+RDob0GwGTogW+gfulCc9pWgEWprpbt1Ef0RHQDWxYpjbnk7Y1GLm/rEM Vw7sfiXpvDEAxsIUk1oAcy1d25xyOZ8KTMO740LqOXbh/h8rB9LGKKjSQHH5R5Vzeib8 14zJTaee0+U+zyQuZrjJ+OPHtxEWZkR2Sa9QbjyHxM2VhWOLPrDz0TD65gYsB3BxdWlw 5N6DUaFWdW3yjMSno945sP61WhxMwHQD3lM+Xu+DrNO/J/+TkSu+dVoQVcp1iIpRojFk g6TQ== X-Gm-Message-State: APjAAAV2Feie+d8053dsOtS3tyP6W7rEF/eMLVdRmZ6LTTwP+f63C5iP utXQwDqefeJhLczZboPDvXA= X-Google-Smtp-Source: APXvYqz08UVvt9qDdWZuY7F9gk+8Up46fj2HHouxwZvVQcLav7iIEXePwSxmGfND/ZUTir9U1ie6Nw== X-Received: by 2002:a17:902:348:: with SMTP id 66mr19695770pld.137.1582013492909; Tue, 18 Feb 2020 00:11:32 -0800 (PST) Received: from baolinwangubtpc.spreadtrum.com ([117.18.48.82]) by smtp.gmail.com with ESMTPSA id z27sm3083411pfj.107.2020.02.18.00.11.28 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Tue, 18 Feb 2020 00:11:32 -0800 (PST) From: Baolin Wang To: rui.zhang@intel.com, daniel.lezcano@linaro.org, amit.kucheria@verdurent.com, robh+dt@kernel.org, mark.rutland@arm.com Cc: orsonzhai@gmail.com, baolin.wang7@gmail.com, freeman.liu@unisoc.com, zhang.lyra@gmail.com, linux-pm@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v3 1/2] dt-bindings: thermal: sprd: Add the Spreadtrum thermal documentation Date: Tue, 18 Feb 2020 16:10:27 +0800 Message-Id: <444e45ce0a9b390b2502dfcefd1ddb36948fa8e1.1582013101.git.baolin.wang7@gmail.com> X-Mailer: git-send-email 1.7.9.5 Sender: linux-pm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org From: Baolin Wang Add the Spreadtrum thermal documentation. Signed-off-by: Baolin Wang Signed-off-by: Baolin Wang Reviewed-by: Rob Herring --- Changes from v2: - Add missing #address-cells and #size-cells properties. - Change the child node name with patternProperties. - Simplify the nvmem-cells-names property. Changes from v1: - Change to yaml format. --- .../devicetree/bindings/thermal/sprd-thermal.yaml | 108 ++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 Documentation/devicetree/bindings/thermal/sprd-thermal.yaml diff --git a/Documentation/devicetree/bindings/thermal/sprd-thermal.yaml b/Documentation/devicetree/bindings/thermal/sprd-thermal.yaml new file mode 100644 index 0000000..3bfe055 --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/sprd-thermal.yaml @@ -0,0 +1,108 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/thermal/sprd-thermal.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Spreadtrum thermal sensor controller bindings + +maintainers: + - Orson Zhai + - Baolin Wang + - Chunyan Zhang + +properties: + compatible: + const: sprd,ums512-thermal + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + clock-names: + items: + - const: enable + + nvmem-cells: + maxItems: 2 + description: + Reference to nvmem nodes for the calibration data. + + nvmem-cell-names: + maxItems: 2 + items: + - const: thm_sign_cal + - const: thm_ratio_cal + + "#thermal-sensor-cells": + const: 1 + + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + +patternProperties: + "^([a-z]*-)?sensor(-section)?@[0-9]+$": + type: object + description: + Represent one thermal sensor. + + properties: + reg: + description: Specify the sensor id. + maxItems: 1 + + nvmem-cells: + maxItems: 1 + description: + Reference to an nvmem node for the calibration data. + + nvmem-cell-names: + const: sen_delta_cal + + required: + - reg + - nvmem-cells + - nvmem-cell-names + +required: + - compatible + - reg + - clocks + - clock-names + - nvmem-cells + - nvmem-cell-names + - "#thermal-sensor-cells" + - "#address-cells" + - "#size-cells" + +examples: + - | + ap_thm0: thermal@32200000 { + compatible = "sprd,ums512-thermal"; + reg = <0 0x32200000 0 0x10000>; + clock-names = "enable"; + clocks = <&aonapb_gate 32>; + #thermal-sensor-cells = <1>; + nvmem-cells = <&thm0_sign>, <&thm0_ratio>; + nvmem-cell-names = "thm_sign_cal", "thm_ratio_cal"; + #address-cells = <1>; + #size-cells = <0>; + + prometheus-sensor@0 { + reg = <0>; + nvmem-cells = <&thm0_sen0>; + nvmem-cell-names = "sen_delta_cal"; + }; + + ank-sensor@1 { + reg = <1>; + nvmem-cells = <&thm0_sen1>; + nvmem-cell-names = "sen_delta_cal"; + }; + }; +... From patchwork Tue Feb 18 08:10:28 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Baolin Wang X-Patchwork-Id: 11387965 X-Patchwork-Delegate: daniel.lezcano@linaro.org Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 5CB0E139A for ; Tue, 18 Feb 2020 08:11:42 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 2398924649 for ; Tue, 18 Feb 2020 08:11:42 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="kEyGQnCg" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726347AbgBRILi (ORCPT ); Tue, 18 Feb 2020 03:11:38 -0500 Received: from mail-pl1-f194.google.com ([209.85.214.194]:45980 "EHLO mail-pl1-f194.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726180AbgBRILi (ORCPT ); Tue, 18 Feb 2020 03:11:38 -0500 Received: by mail-pl1-f194.google.com with SMTP id b22so7759611pls.12; Tue, 18 Feb 2020 00:11:37 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :in-reply-to:references; bh=hwKUIa+IraGb7oitddEV2OS+g5EhB7WgvdsqrnGHzJA=; b=kEyGQnCgqIuPcNVqmz4KbBge1FdjZpMqklV60y0ybVWUAVoMnm5NWNy/z4Efq6KL88 aLBIRLrbwge+xhee6qSSEAOwZwgeGea5pjLag1JgkF8Hl5ZSZTBSd/U8qhL+oS9erk4M L9YlfOy74hbdoNHgEHzinoNNmcXXjzk7hSgh91pLGrKIRVxWIVViHUjlH9FD33G8tD1a wq9kfRl2CqO0C4EYSl+1Qeb1x6ZUtNZzi+m/VwdNed3G9YgNXFe0QoyI7Mlw09d6Ln5y GHyK/VTKLoPiNSaaxH1MOBLh+Waaf/JkIcWlYuV0yY/DQUks/sHygb/pexnMypJJoKKC 41/A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:in-reply-to:references; bh=hwKUIa+IraGb7oitddEV2OS+g5EhB7WgvdsqrnGHzJA=; b=YIXRcJLeCJyGir/TzS7JfwCKS/WUgg5wXcqEUkyqZtQxVpTYZUmvX2hhXuJKkNV0o5 cPYpF5wsHx2wNArnIo1HKp/GoH1NPZpIylShrODHcYGQJ44ZrLvwcnDn+mcMpoDrKYlt 0mZ2me8fEvWY0KazmPSk3nYqXodatXaazipe9FHZsq7yZfCEhPHLv5VAEylx/aByiQgW uF6v7FftE986s3z4D8pufdx1ORIPKMfg++WXdkZknL+7gLgcICuj5L8i8RI5yPFMZYLC PoKYDyqxEnek7fuO9ylUbwme20qwRDRweYhU0zFDghCBnktDjEZsOwu0R9ulXMvTqPHq 0jnw== X-Gm-Message-State: APjAAAUaZeXujFLwB10IqINaV1niiXAB6qlZlybnaQ3MVK/FXEeWPtOS e2zy1yI9BweXjvSn6GpfwcO37dpROxc= X-Google-Smtp-Source: APXvYqxRwQDDsBMm3NShMv79zNqVZZFztAaowEDjBXDsyQ7Bk4ECRFDwT0pHhzuqrwWDhTNi/LmiEg== X-Received: by 2002:a17:90b:1245:: with SMTP id gx5mr1194589pjb.105.1582013496822; Tue, 18 Feb 2020 00:11:36 -0800 (PST) Received: from baolinwangubtpc.spreadtrum.com ([117.18.48.82]) by smtp.gmail.com with ESMTPSA id z27sm3083411pfj.107.2020.02.18.00.11.33 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Tue, 18 Feb 2020 00:11:36 -0800 (PST) From: Baolin Wang To: rui.zhang@intel.com, daniel.lezcano@linaro.org, amit.kucheria@verdurent.com, robh+dt@kernel.org, mark.rutland@arm.com Cc: orsonzhai@gmail.com, baolin.wang7@gmail.com, freeman.liu@unisoc.com, zhang.lyra@gmail.com, linux-pm@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v3 2/2] thermal: sprd: Add Spreadtrum thermal driver support Date: Tue, 18 Feb 2020 16:10:28 +0800 Message-Id: X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <444e45ce0a9b390b2502dfcefd1ddb36948fa8e1.1582013101.git.baolin.wang7@gmail.com> References: <444e45ce0a9b390b2502dfcefd1ddb36948fa8e1.1582013101.git.baolin.wang7@gmail.com> In-Reply-To: <444e45ce0a9b390b2502dfcefd1ddb36948fa8e1.1582013101.git.baolin.wang7@gmail.com> References: <444e45ce0a9b390b2502dfcefd1ddb36948fa8e1.1582013101.git.baolin.wang7@gmail.com> Sender: linux-pm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org From: Freeman Liu This patch adds the support for Spreadtrum thermal sensor controller, which can support maximum 8 sensors. Signed-off-by: Freeman Liu Co-developed-with: Baolin Wang Signed-off-by: Baolin Wang --- Changes from v2: - Check the nvmem value's length. - Use clamp() macro instead of open codes. - Fix some typos. - Optimize the 'ratio' calculation formula. - Remove 'last_temp' variable. - Add some comments to explain why need enable interrupts. - Use THERMAL_DEVICE_ENABLED/DISABLED flag instead of 'ready' variable. Changes from v1: - None. --- drivers/thermal/Kconfig | 7 + drivers/thermal/Makefile | 1 + drivers/thermal/sprd_thermal.c | 552 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 560 insertions(+) create mode 100644 drivers/thermal/sprd_thermal.c diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 5a05db5..a2a940e 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -460,4 +460,11 @@ config UNIPHIER_THERMAL Enable this to plug in UniPhier on-chip PVT thermal driver into the thermal framework. The driver supports CPU thermal zone temperature reporting and a couple of trip points. + +config SPRD_THERMAL + tristate "Temperature sensor on Spreadtrum SoCs" + depends on ARCH_SPRD || COMPILE_TEST + help + Support for the Spreadtrum thermal sensor driver in the Linux thermal + framework. endif diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 9fb88e2..dbdd98c 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -57,3 +57,4 @@ obj-$(CONFIG_GENERIC_ADC_THERMAL) += thermal-generic-adc.o obj-$(CONFIG_ZX2967_THERMAL) += zx2967_thermal.o obj-$(CONFIG_UNIPHIER_THERMAL) += uniphier_thermal.o obj-$(CONFIG_AMLOGIC_THERMAL) += amlogic_thermal.o +obj-$(CONFIG_SPRD_THERMAL) += sprd_thermal.o diff --git a/drivers/thermal/sprd_thermal.c b/drivers/thermal/sprd_thermal.c new file mode 100644 index 0000000..a340374 --- /dev/null +++ b/drivers/thermal/sprd_thermal.c @@ -0,0 +1,552 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2020 Spreadtrum Communications Inc. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SPRD_THM_CTL 0x0 +#define SPRD_THM_INT_EN 0x4 +#define SPRD_THM_INT_STS 0x8 +#define SPRD_THM_INT_RAW_STS 0xc +#define SPRD_THM_DET_PERIOD 0x10 +#define SPRD_THM_INT_CLR 0x14 +#define SPRD_THM_INT_CLR_ST 0x18 +#define SPRD_THM_MON_PERIOD 0x4c +#define SPRD_THM_MON_CTL 0x50 +#define SPRD_THM_INTERNAL_STS1 0x54 +#define SPRD_THM_RAW_READ_MSK 0x3ff + +#define SPRD_THM_OFFSET(id) ((id) * 0x4) +#define SPRD_THM_TEMP(id) (SPRD_THM_OFFSET(id) + 0x5c) +#define SPRD_THM_THRES(id) (SPRD_THM_OFFSET(id) + 0x2c) + +#define SPRD_THM_SEN(id) BIT((id) + 2) +#define SPRD_THM_SEN_OVERHEAT_EN(id) BIT((id) + 8) +#define SPRD_THM_SEN_OVERHEAT_ALARM_EN(id) BIT((id) + 0) + +/* bits definitions for register THM_CTL */ +#define SPRD_THM_SET_RDY_ST BIT(13) +#define SPRD_THM_SET_RDY BIT(12) +#define SPRD_THM_MON_EN BIT(1) +#define SPRD_THM_EN BIT(0) + +/* bits definitions for register THM_INT_CTL */ +#define SPRD_THM_BIT_INT_EN BIT(26) +#define SPRD_THM_OVERHEAT_EN BIT(25) +#define SPRD_THM_OTP_TRIP_SHIFT 10 + +/* bits definitions for register SPRD_THM_INTERNAL_STS1 */ +#define SPRD_THM_TEMPER_RDY BIT(0) + +#define SPRD_THM_DET_PERIOD_DATA 0x800 +#define SPRD_THM_DET_PERIOD_MASK GENMASK(19, 0) +#define SPRD_THM_MON_MODE 0x7 +#define SPRD_THM_MON_MODE_MASK GENMASK(3, 0) +#define SPRD_THM_MON_PERIOD_DATA 0x10 +#define SPRD_THM_MON_PERIOD_MASK GENMASK(15, 0) +#define SPRD_THM_THRES_MASK GENMASK(19, 0) +#define SPRD_THM_INT_CLR_MASK GENMASK(24, 0) + +/* thermal sensor calibration parameters */ +#define SPRD_THM_TEMP_LOW -40000 +#define SPRD_THM_TEMP_HIGH 120000 +#define SPRD_THM_OTP_TEMP 120000 +#define SPRD_THM_HOT_TEMP 75000 +#define SPRD_THM_RAW_DATA_LOW 0 +#define SPRD_THM_RAW_DATA_HIGH 1000 +#define SPRD_THM_SEN_NUM 8 +#define SPRD_THM_DT_OFFSET 24 +#define SPRD_THM_RATION_OFFSET 17 +#define SPRD_THM_RATION_SIGN 16 + +#define SPRD_THM_RDYST_POLLING_TIME 10 +#define SPRD_THM_RDYST_TIMEOUT 700 +#define SPRD_THM_TEMP_READY_POLL_TIME 10000 +#define SPRD_THM_TEMP_READY_TIMEOUT 600000 +#define SPRD_THM_MAX_SENSOR 8 + +struct sprd_thermal_sensor { + struct thermal_zone_device *tzd; + struct sprd_thermal_data *data; + struct device *dev; + int cal_slope; + int cal_offset; + int id; +}; + +struct sprd_thermal_data { + const struct sprd_thm_variant_data *var_data; + struct sprd_thermal_sensor *sensor[SPRD_THM_MAX_SENSOR]; + struct clk *clk; + void __iomem *base; + u32 ratio_off; + int ratio_sign; + int nr_sensors; +}; + +/* + * The conversion between ADC and temperature is based on linear relationship, + * and use idea_k to specify the slope and ideal_b to specify the offset. + * + * Since different Spreadtrum SoCs have different ideal_k and ideal_b, + * we should save ideal_k and ideal_b in the device data structure. + */ +struct sprd_thm_variant_data { + u32 ideal_k; + u32 ideal_b; +}; + +static const struct sprd_thm_variant_data ums512_data = { + .ideal_k = 262, + .ideal_b = 66400, +}; + +static inline void sprd_thm_update_bits(void __iomem *reg, u32 mask, u32 val) +{ + u32 tmp, orig; + + orig = readl(reg); + tmp = orig & ~mask; + tmp |= val & mask; + writel(tmp, reg); +} + +static int sprd_thm_cal_read(struct device_node *np, const char *cell_id, + u32 *val) +{ + struct nvmem_cell *cell; + void *buf; + size_t len; + + cell = of_nvmem_cell_get(np, cell_id); + if (IS_ERR(cell)) + return PTR_ERR(cell); + + buf = nvmem_cell_read(cell, &len); + nvmem_cell_put(cell); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + if (len > sizeof(u32)) { + kfree(buf); + return -EINVAL; + } + + memcpy(val, buf, len); + + kfree(buf); + return 0; +} + +static int sprd_thm_sensor_calibration(struct device_node *np, + struct sprd_thermal_data *thm, + struct sprd_thermal_sensor *sen) +{ + int ret; + /* + * According to thermal datasheet, the default calibration offset is 64, + * and the default ratio is 1000. + */ + int dt_offset = 64, ratio = 1000; + + ret = sprd_thm_cal_read(np, "sen_delta_cal", &dt_offset); + if (ret) + return ret; + + ratio += thm->ratio_sign * thm->ratio_off; + + /* + * According to the ideal slope K and ideal offset B, combined with + * calibration value of thermal from efuse, then calibrate the real + * slope k and offset b: + * k_cal = (k * ratio) / 1000. + * b_cal = b + (dt_offset - 64) * 500. + */ + sen->cal_slope = (thm->var_data->ideal_k * ratio) / 1000; + sen->cal_offset = thm->var_data->ideal_b + (dt_offset - 128) * 250; + + return 0; +} + +static int sprd_thm_rawdata_to_temp(struct sprd_thermal_sensor *sen, + u32 rawdata) +{ + clamp(rawdata, (u32)SPRD_THM_RAW_DATA_LOW, (u32)SPRD_THM_RAW_DATA_HIGH); + + /* + * According to the thermal datasheet, the formula of converting + * adc value to the temperature value should be: + * T_final = k_cal * x - b_cal. + */ + return sen->cal_slope * rawdata - sen->cal_offset; +} + +static int sprd_thm_temp_to_rawdata(int temp, struct sprd_thermal_sensor *sen) +{ + u32 val; + + clamp(temp, (int)SPRD_THM_TEMP_LOW, (int)SPRD_THM_TEMP_HIGH); + + /* + * According to the thermal datasheet, the formula of converting + * adc value to the temperature value should be: + * T_final = k_cal * x - b_cal. + */ + val = (temp + sen->cal_offset) / sen->cal_slope; + + return clamp(val, val, (u32)(SPRD_THM_RAW_DATA_HIGH - 1)); +} + +static int sprd_thm_read_temp(void *devdata, int *temp) +{ + struct sprd_thermal_sensor *sen = devdata; + u32 data; + + data = readl(sen->data->base + SPRD_THM_TEMP(sen->id)) & + SPRD_THM_RAW_READ_MSK; + + *temp = sprd_thm_rawdata_to_temp(sen, data); + + return 0; +} + +static const struct thermal_zone_of_device_ops sprd_thm_ops = { + .get_temp = sprd_thm_read_temp, +}; + +static int sprd_thm_poll_ready_status(struct sprd_thermal_data *thm) +{ + u32 val; + int ret; + + /* + * Wait for thermal ready status before configuring thermal parameters. + */ + ret = readl_poll_timeout(thm->base + SPRD_THM_CTL, val, + !(val & SPRD_THM_SET_RDY_ST), + SPRD_THM_RDYST_POLLING_TIME, + SPRD_THM_RDYST_TIMEOUT); + if (ret) + return ret; + + sprd_thm_update_bits(thm->base + SPRD_THM_CTL, SPRD_THM_MON_EN, + SPRD_THM_MON_EN); + sprd_thm_update_bits(thm->base + SPRD_THM_CTL, SPRD_THM_SET_RDY, + SPRD_THM_SET_RDY); + return 0; +} + +static int sprd_thm_wait_temp_ready(struct sprd_thermal_data *thm) +{ + u32 val; + + /* Wait for first temperature data ready before reading temperature */ + return readl_poll_timeout(thm->base + SPRD_THM_INTERNAL_STS1, val, + !(val & SPRD_THM_TEMPER_RDY), + SPRD_THM_TEMP_READY_POLL_TIME, + SPRD_THM_TEMP_READY_TIMEOUT); +} + +static int sprd_thm_set_ready(struct sprd_thermal_data *thm) +{ + int ret; + + ret = sprd_thm_poll_ready_status(thm); + if (ret) + return ret; + + /* + * Clear interrupt status, enable thermal interrupt and enable thermal. + * + * The SPRD thermal controller integrates a hardware interrupt signal, + * which means if the temperature is overheat, it will generate an + * interrupt and notify the event to PMIC automatically to shutdown the + * system. So here we should enable the interrupt bits, though we have + * not registered an irq handler. + */ + writel(SPRD_THM_INT_CLR_MASK, thm->base + SPRD_THM_INT_CLR); + sprd_thm_update_bits(thm->base + SPRD_THM_INT_EN, + SPRD_THM_BIT_INT_EN, SPRD_THM_BIT_INT_EN); + sprd_thm_update_bits(thm->base + SPRD_THM_CTL, + SPRD_THM_EN, SPRD_THM_EN); + return 0; +} + +static void sprd_thm_sensor_init(struct sprd_thermal_data *thm, + struct sprd_thermal_sensor *sen) +{ + u32 otp_rawdata, hot_rawdata; + + otp_rawdata = sprd_thm_temp_to_rawdata(SPRD_THM_OTP_TEMP, sen); + hot_rawdata = sprd_thm_temp_to_rawdata(SPRD_THM_HOT_TEMP, sen); + + /* Enable the sensor' overheat temperature protection interrupt */ + sprd_thm_update_bits(thm->base + SPRD_THM_INT_EN, + SPRD_THM_SEN_OVERHEAT_ALARM_EN(sen->id), + SPRD_THM_SEN_OVERHEAT_ALARM_EN(sen->id)); + + /* Set the sensor' overheat and hot threshold temperature */ + sprd_thm_update_bits(thm->base + SPRD_THM_THRES(sen->id), + SPRD_THM_THRES_MASK, + (otp_rawdata << SPRD_THM_OTP_TRIP_SHIFT) | + hot_rawdata); + + /* Enable the corresponding sensor */ + sprd_thm_update_bits(thm->base + SPRD_THM_CTL, SPRD_THM_SEN(sen->id), + SPRD_THM_SEN(sen->id)); +} + +static void sprd_thm_para_config(struct sprd_thermal_data *thm) +{ + /* Set the period of two valid temperature detection action */ + sprd_thm_update_bits(thm->base + SPRD_THM_DET_PERIOD, + SPRD_THM_DET_PERIOD_MASK, SPRD_THM_DET_PERIOD); + + /* Set the sensors' monitor mode */ + sprd_thm_update_bits(thm->base + SPRD_THM_MON_CTL, + SPRD_THM_MON_MODE_MASK, SPRD_THM_MON_MODE); + + /* Set the sensors' monitor period */ + sprd_thm_update_bits(thm->base + SPRD_THM_MON_PERIOD, + SPRD_THM_MON_PERIOD_MASK, SPRD_THM_MON_PERIOD); +} + +static void sprd_thm_toggle_sensor(struct sprd_thermal_sensor *sen, bool on) +{ + struct thermal_zone_device *tzd = sen->tzd; + + tzd->ops->set_mode(tzd, + on ? THERMAL_DEVICE_ENABLED : THERMAL_DEVICE_DISABLED); +} + +static int sprd_thm_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *sen_child; + struct sprd_thermal_data *thm; + struct sprd_thermal_sensor *sen; + const struct sprd_thm_variant_data *pdata; + int ret, i; + u32 val; + + pdata = of_device_get_match_data(&pdev->dev); + if (!pdata) { + dev_err(&pdev->dev, "No matching driver data found\n"); + return -EINVAL; + } + + thm = devm_kzalloc(&pdev->dev, sizeof(*thm), GFP_KERNEL); + if (!thm) + return -ENOMEM; + + thm->var_data = pdata; + thm->base = devm_platform_ioremap_resource(pdev, 0); + if (!thm->base) + return -ENOMEM; + + thm->nr_sensors = of_get_child_count(np); + if (thm->nr_sensors == 0 || thm->nr_sensors > SPRD_THM_MAX_SENSOR) { + dev_err(&pdev->dev, "incorrect sensor count\n"); + return -EINVAL; + } + + thm->clk = devm_clk_get(&pdev->dev, "enable"); + if (IS_ERR(thm->clk)) { + dev_err(&pdev->dev, "failed to get enable clock\n"); + return PTR_ERR(thm->clk); + } + + ret = clk_prepare_enable(thm->clk); + if (ret) + return ret; + + sprd_thm_para_config(thm); + + ret = sprd_thm_cal_read(np, "thm_sign_cal", &val); + if (ret) + goto disable_clk; + + if (val > 0) + thm->ratio_sign = -1; + else + thm->ratio_sign = 1; + + ret = sprd_thm_cal_read(np, "thm_ratio_cal", &thm->ratio_off); + if (ret) + goto disable_clk; + + for_each_child_of_node(np, sen_child) { + sen = devm_kzalloc(&pdev->dev, sizeof(*sen), GFP_KERNEL); + if (!sen) { + ret = -ENOMEM; + goto disable_clk; + } + + sen->data = thm; + sen->dev = &pdev->dev; + + ret = of_property_read_u32(sen_child, "reg", &sen->id); + if (ret) { + dev_err(&pdev->dev, "get sensor reg failed"); + goto disable_clk; + } + + ret = sprd_thm_sensor_calibration(sen_child, thm, sen); + if (ret) { + dev_err(&pdev->dev, "efuse cal analysis failed"); + goto disable_clk; + } + + sprd_thm_sensor_init(thm, sen); + + sen->tzd = devm_thermal_zone_of_sensor_register(sen->dev, + sen->id, + sen, + &sprd_thm_ops); + if (IS_ERR(sen->tzd)) { + dev_err(&pdev->dev, "register thermal zone failed %d\n", + sen->id); + ret = PTR_ERR(sen->tzd); + goto disable_clk; + } + + thm->sensor[sen->id] = sen; + } + + ret = sprd_thm_set_ready(thm); + if (ret) + goto disable_clk; + + ret = sprd_thm_wait_temp_ready(thm); + if (ret) + goto disable_clk; + + for (i = 0; i < thm->nr_sensors; i++) + sprd_thm_toggle_sensor(thm->sensor[i], true); + + platform_set_drvdata(pdev, thm); + return 0; + +disable_clk: + clk_disable_unprepare(thm->clk); + return ret; +} + +#ifdef CONFIG_PM_SLEEP +static void sprd_thm_hw_suspend(struct sprd_thermal_data *thm) +{ + int i; + + for (i = 0; i < thm->nr_sensors; i++) { + sprd_thm_update_bits(thm->base + SPRD_THM_CTL, + SPRD_THM_SEN(thm->sensor[i]->id), 0); + } + + sprd_thm_update_bits(thm->base + SPRD_THM_CTL, + SPRD_THM_EN, 0x0); +} + +static int sprd_thm_suspend(struct device *dev) +{ + struct sprd_thermal_data *thm = dev_get_drvdata(dev); + int i; + + for (i = 0; i < thm->nr_sensors; i++) + sprd_thm_toggle_sensor(thm->sensor[i], false); + + sprd_thm_hw_suspend(thm); + clk_disable_unprepare(thm->clk); + + return 0; +} + +static int sprd_thm_hw_resume(struct sprd_thermal_data *thm) +{ + int ret, i; + + for (i = 0; i < thm->nr_sensors; i++) { + sprd_thm_update_bits(thm->base + SPRD_THM_CTL, + SPRD_THM_SEN(thm->sensor[i]->id), + SPRD_THM_SEN(thm->sensor[i]->id)); + } + + ret = sprd_thm_poll_ready_status(thm); + if (ret) + return ret; + + writel(SPRD_THM_INT_CLR_MASK, thm->base + SPRD_THM_INT_CLR); + sprd_thm_update_bits(thm->base + SPRD_THM_CTL, + SPRD_THM_EN, SPRD_THM_EN); + return sprd_thm_wait_temp_ready(thm); +} + +static int sprd_thm_resume(struct device *dev) +{ + struct sprd_thermal_data *thm = dev_get_drvdata(dev); + int ret, i; + + ret = clk_prepare_enable(thm->clk); + if (ret) + return ret; + + ret = sprd_thm_hw_resume(thm); + if (ret) + goto disable_clk; + + for (i = 0; i < thm->nr_sensors; i++) + sprd_thm_toggle_sensor(thm->sensor[i], true); + + return 0; + +disable_clk: + clk_disable_unprepare(thm->clk); + return ret; +} +#endif + +static int sprd_thm_remove(struct platform_device *pdev) +{ + struct sprd_thermal_data *thm = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < thm->nr_sensors; i++) { + sprd_thm_toggle_sensor(thm->sensor[i], false); + devm_thermal_zone_of_sensor_unregister(&pdev->dev, + thm->sensor[i]->tzd); + } + + clk_disable_unprepare(thm->clk); + return 0; +} + +static const struct of_device_id sprd_thermal_of_match[] = { + { .compatible = "sprd,ums512-thermal", .data = &ums512_data }, + { }, +}; + +static const struct dev_pm_ops sprd_thermal_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(sprd_thm_suspend, sprd_thm_resume) +}; + +static struct platform_driver sprd_thermal_driver = { + .probe = sprd_thm_probe, + .remove = sprd_thm_remove, + .driver = { + .name = "sprd-thermal", + .pm = &sprd_thermal_pm_ops, + .of_match_table = sprd_thermal_of_match, + }, +}; + +module_platform_driver(sprd_thermal_driver); + +MODULE_AUTHOR("Freeman Liu "); +MODULE_DESCRIPTION("Spreadtrum thermal driver"); +MODULE_LICENSE("GPL v2");