From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 816B1CCF2EF for ; Mon, 19 Jan 2026 12:09:29 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender:List-Subscribe:List-Help :List-Post:List-Archive:List-Unsubscribe:List-Id:Cc:To:In-Reply-To:References :Message-Id:Content-Transfer-Encoding:Content-Type:MIME-Version:Subject:Date: From:Reply-To:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=NhZcJjJIy7iRew6e3r4TVGpPi1CTe73TTs5G0Bc02v0=; b=1gF3ZiG5nbwJ3MPP1WzS4fP3YM JJikFNAMdvLD2y1LtLy5pqtMYkj/Olksjc58aFnhBIiFlty2zY1kHDx0srBFra4ibXt6lGYPmM/XW 1yeA2Kp01e2uoRGsbYFmNU0zHuXeJt3ANH6vEHKdry0p5s94tlpxtdIACi7t9KmdNkTyKsmCK27Lz DTuMdctgh5yVeKB5vTxGTpOowuXSzTlvXl0lkqSd3aLuc+f5z0q9U/Yrp+Cx4dL9ebhxqet5mZ/q4 4zE+R87lqcpbUzRHTwkrjM1P6Amql92r9Ln0QKD43z5bi4+zBzqfPWTPToNtDYJabAne3ZjK6GKsT ogT8zgyQ==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.98.2 #2 (Red Hat Linux)) id 1vho4L-00000001xYD-1L62; Mon, 19 Jan 2026 12:09:19 +0000 Received: from mail-wr1-x430.google.com ([2a00:1450:4864:20::430]) by bombadil.infradead.org with esmtps (Exim 4.98.2 #2 (Red Hat Linux)) id 1vho42-00000001xSg-2tQp for linux-arm-kernel@lists.infradead.org; Mon, 19 Jan 2026 12:09:01 +0000 Received: by mail-wr1-x430.google.com with SMTP id ffacd0b85a97d-432d256c2e6so3608194f8f.3 for ; Mon, 19 Jan 2026 04:08:58 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1768824537; x=1769429337; darn=lists.infradead.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=NhZcJjJIy7iRew6e3r4TVGpPi1CTe73TTs5G0Bc02v0=; b=bcXlW1F0jCKR7ZB0Uo4UL9WFuXAzo6h9cT3jzsag/kiRt4hjn6c+EFRh5B5O7QaYm1 mlKKoo9TaQxTXedRbx2JoFyyeIGskGZlLfCzVAK0P8OE9VVBrZ15TY5xpwOKNzdjq9j3 6z4okesBiWnr+JCFPubTNghiwvmKl8aS7nA9YbBDp9mWfrqq2/fK6W9skqt2XR+uvwog uY4m1Ipj6Pdb6wU8Zxhx+3W22Mc0rb3zjuMhbM5l3szFevP1hf+pDsM1cw8J60HUxQyd 9JxFtJwDR6uN2dSL+5rfNkJJVQRvvhoEEvCY2g7zaa9WkeE4cUOCfYgwCB+jUAaaxUZz zjvA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1768824537; x=1769429337; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=NhZcJjJIy7iRew6e3r4TVGpPi1CTe73TTs5G0Bc02v0=; b=fxMGBpcArb/mSaQB4oklQIr7ZVKzTSV4iFXl0bDdaDGwdXEvu6LaOK38lqkfYaVGzH E+4HEhtVwuIWVIkCAUFpZt7Kmio6glE2sov1RyMApyRTGkOHlgaOz3ZgQROkpq0UEjpi w0uMJstH5w2KCYBBaTUJyZk4bzVhV0prYgcGrpGc2/53/Ia66OX5QdOVzM/YLfURidFz MNN+bQHXY+KOnJ/PSXG+JrHON/Gew7o1d8o3ikWU4lU6oW6ikNwp+ltwkRfOOqoZKmIm mxth/Pf0ovuUY8V7CpFzvzU/KiBrVsjZtdM5WmLl61bQO0KHxvK2GIAFFac/XdXpQvgT 5nwA== X-Forwarded-Encrypted: i=1; AJvYcCWPhG35ICDS6tRZrEyX0LNjfDPMmOsx0rzHR296j/LeFP3hIM/73uiJXJd5BjmPy/7itn8lOZJYa8LSr1vJrvEv@lists.infradead.org X-Gm-Message-State: AOJu0YwInuNxDHhXSZlrrBYcjX29QOso+dvpoRZJXYIckOSSKzBpXf3M cd3HBT5QI/ZNFcTJlFq+3/kGaQnTplZ4pDZZDzo/Wz/P0K33jTeOlkB8m2y4sT7DPAk= X-Gm-Gg: AZuq6aIIYZ4mKMJC4uemWJbIyI0M6bPp9nsgIeXJaBDhOSoQRktvj5e2nlGzI+ckCjS pbXDRF3l1ItpnyPv1jI9estr/5YWxekTdDajk9Ux+NgsVCxj3D+ma12MYvu1QLC8OVwvUeVMbNj n+aHqQfkVXA+4YkzdExgGc09E5+/QoMQZatGJ+aMD4dhlk7j2NN5NafV8lssfeI6IfvWaQJu0+a hjUiKwYXZ7hrxyH7sKPl5C8noCd8H57BX8WwGn5g/nK9wpcPI6hL4c4nif125ykv/brWV7WzeIe Y43TrHzqZFXW2bzWcgE4ZzKachd9gy2Oe4dRNnpGn/0pgqWEMXNf21aVigjIHqUJ+rLsVKrqtll MNFWdrzh99qEYUuRcxb8tE+FeodPKnn2ConvWgcijV5b7ZGFEcwLduusSNXDmwNDtZ3OFiOLIJf u6kXv5lt6KAW1OV1WhhSfs3Uku2PJyQfgJFLC1udv0nP++gtOBKkidBq7/gvv/ElwuuciPAg== X-Received: by 2002:a05:6000:290e:b0:430:fdb8:8516 with SMTP id ffacd0b85a97d-43569bbafecmr14769998f8f.35.1768824536461; Mon, 19 Jan 2026 04:08:56 -0800 (PST) Received: from ta2.c.googlers.com (164.102.240.35.bc.googleusercontent.com. [35.240.102.164]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-4356997e664sm20698421f8f.30.2026.01.19.04.08.54 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 19 Jan 2026 04:08:55 -0800 (PST) From: Tudor Ambarus Date: Mon, 19 Jan 2026 12:08:50 +0000 Subject: [PATCH v2 4/7] thermal: samsung: Add support for GS101 TMU MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20260119-acpm-tmu-v2-4-e02a834f04c6@linaro.org> References: <20260119-acpm-tmu-v2-0-e02a834f04c6@linaro.org> In-Reply-To: <20260119-acpm-tmu-v2-0-e02a834f04c6@linaro.org> To: "Rafael J. Wysocki" , Daniel Lezcano , Zhang Rui , Lukasz Luba , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Krzysztof Kozlowski , Alim Akhtar , Bartlomiej Zolnierkiewicz , Kees Cook , "Gustavo A. R. Silva" , Peter Griffin , =?utf-8?q?Andr=C3=A9_Draszik?= Cc: willmcvicker@google.com, jyescas@google.com, shin.son@samsung.com, linux-samsung-soc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-pm@vger.kernel.org, devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-hardening@vger.kernel.org, Tudor Ambarus X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1768824532; l=19921; i=tudor.ambarus@linaro.org; s=20241212; h=from:subject:message-id; bh=zoQOnSwC+2Ihs8FPFbdS+lFq+c7kQwUN5grvaAYMLVE=; b=k7IsR2vUDbTq2YdFfmnkeeIWwUNgT0gN4qYdJBp+6ioS3docBgUAkv219LIlo3k1Vfz1BeN/D /J3XFzhzYcjCWg5D0cQ6M/HMJt+A65j/jZxjSQIqUcz7MkwqfWbHxr9 X-Developer-Key: i=tudor.ambarus@linaro.org; a=ed25519; pk=uQzE0NXo3dIjeowMTOPCpIiPHEz12IA/MbyzrZVh9WI= X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20260119_040858_904047_1D82C5E4 X-CRM114-Status: GOOD ( 25.00 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org Add the thermal driver for the Google GS101 SoC. The GS101 TMU utilizes a hybrid management model shared between the Application Processor (AP) and the ACPM (Alive Clock and Power Manager) firmware. The driver maintains direct memory-mapped access to the TMU interrupt pending registers to identify thermal events, while delegating functional tasks - such as sensor initialization, threshold configuration, and temperature acquisition - to the ACPM firmware via the ACPM IPC protocol. Signed-off-by: Tudor Ambarus --- drivers/thermal/samsung/Kconfig | 16 + drivers/thermal/samsung/Makefile | 2 + drivers/thermal/samsung/acpm-tmu.c | 643 +++++++++++++++++++++++++++++++++++++ 3 files changed, 661 insertions(+) diff --git a/drivers/thermal/samsung/Kconfig b/drivers/thermal/samsung/Kconfig index f4eff5a41a84ce02b12abb85d6a0f8818031d0dc..5679dfa85f4079c7d40317ac231bd6a1af93c7e7 100644 --- a/drivers/thermal/samsung/Kconfig +++ b/drivers/thermal/samsung/Kconfig @@ -9,3 +9,19 @@ config EXYNOS_THERMAL the TMU, reports temperature and handles cooling action if defined. This driver uses the Exynos core thermal APIs and TMU configuration data from the supported SoCs. + +config EXYNOS_ACPM_THERMAL + tristate "Exynos ACPM thermal management unit driver" + depends on THERMAL_OF + depends on EXYNOS_ACPM_PROTOCOL || (COMPILE_TEST && !EXYNOS_ACPM_PROTOCOL) + help + Support for the Thermal Management Unit (TMU) on Google GS101 SoC. + + The TMU on GS101 is managed through a hybrid architecture. This driver + handles direct register access for thermal interrupt status monitoring + and communicates with the Alive Clock and Power Manager (ACPM) + firmware via the ACPM IPC protocol for functional sensor control and + configuration. + + Select this if you want to monitor device temperature and enable + thermal mitigation on GS101 based devices. diff --git a/drivers/thermal/samsung/Makefile b/drivers/thermal/samsung/Makefile index f139407150d26940fc9ffcf000505cea4866223f..daed80647c349ba4f937ed8fbe6be9df06c34aac 100644 --- a/drivers/thermal/samsung/Makefile +++ b/drivers/thermal/samsung/Makefile @@ -4,3 +4,5 @@ # obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o exynos_thermal-y := exynos_tmu.o +obj-$(CONFIG_EXYNOS_ACPM_THERMAL) += exynos_acpm_thermal.o +exynos_acpm_thermal-y := acpm-tmu.o diff --git a/drivers/thermal/samsung/acpm-tmu.c b/drivers/thermal/samsung/acpm-tmu.c new file mode 100644 index 0000000000000000000000000000000000000000..9fbac174e554284ff0b8c0b188a44dcceee4c440 --- /dev/null +++ b/drivers/thermal/samsung/acpm-tmu.c @@ -0,0 +1,643 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2019 Samsung Electronics Co., Ltd. + * Copyright 2025 Google LLC. + * Copyright 2026 Linaro Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../thermal_hwmon.h" + +#define EXYNOS_TMU_SENSOR(i) BIT(i) +#define EXYNOS_TMU_SENSORS_MAX_COUNT 16 + +#define GS101_CPUCL2_SENSOR_MASK (EXYNOS_TMU_SENSOR(0) | \ + EXYNOS_TMU_SENSOR(6) | \ + EXYNOS_TMU_SENSOR(7) | \ + EXYNOS_TMU_SENSOR(8) | \ + EXYNOS_TMU_SENSOR(9)) +#define GS101_CPUCL1_SENSOR_MASK (EXYNOS_TMU_SENSOR(4) | \ + EXYNOS_TMU_SENSOR(5)) +#define GS101_CPUCL0_SENSOR_MASK (EXYNOS_TMU_SENSOR(1) | \ + EXYNOS_TMU_SENSOR(2)) + +#define GS101_REG_INTPEND(i) ((i) * 0x50 + 0xf8) + +enum { + P0_INTPEND, + P1_INTPEND, + P2_INTPEND, + P3_INTPEND, + P4_INTPEND, + P5_INTPEND, + P6_INTPEND, + P7_INTPEND, + P8_INTPEND, + P9_INTPEND, + P10_INTPEND, + P11_INTPEND, + P12_INTPEND, + P13_INTPEND, + P14_INTPEND, + P15_INTPEND, + REG_INTPEND_COUNT, +}; + +struct acpm_tmu_sensor_group { + u16 mask; + u8 id; +}; + +struct acpm_tmu_sensor { + const struct acpm_tmu_sensor_group *group; + struct thermal_zone_device *tzd; + struct acpm_tmu_priv *priv; + struct mutex lock; /* protects sensor state */ + bool enabled; +}; + +struct acpm_tmu_priv { + struct regmap_field *regmap_fields[REG_INTPEND_COUNT]; + const struct acpm_handle *handle; + struct device *dev; + struct clk *clk; + unsigned int mbox_chan_id; + unsigned int num_sensors; + int irq; + struct acpm_tmu_sensor sensors[] __counted_by(num_sensors); +}; + +struct acpm_tmu_driver_data { + const struct reg_field *reg_fields; + const struct acpm_tmu_sensor_group *sensor_groups; + unsigned int num_sensor_groups; + unsigned int mbox_chan_id; +}; + +struct acpm_tmu_initialize_tripwalkdata { + unsigned char threshold[8]; + unsigned char hysteresis[8]; + unsigned char inten; + int i; +}; + +struct acpm_tmu_trip_temp_tripwalkdata { + const struct thermal_trip *trip; + unsigned char threshold[8]; + int temperature; + int i; +}; + +#define ACPM_TMU_SENSOR_GROUP(_mask, _id) \ + { \ + .mask = _mask, \ + .id = _id, \ + } + +static const struct acpm_tmu_sensor_group gs101_sensor_groups[] = { + ACPM_TMU_SENSOR_GROUP(GS101_CPUCL2_SENSOR_MASK, 0), + ACPM_TMU_SENSOR_GROUP(GS101_CPUCL1_SENSOR_MASK, 1), + ACPM_TMU_SENSOR_GROUP(GS101_CPUCL0_SENSOR_MASK, 2), +}; + +static const struct reg_field gs101_reg_fields[REG_INTPEND_COUNT] = { + [P0_INTPEND] = REG_FIELD(GS101_REG_INTPEND(0), 0, 31), + [P1_INTPEND] = REG_FIELD(GS101_REG_INTPEND(1), 0, 31), + [P2_INTPEND] = REG_FIELD(GS101_REG_INTPEND(2), 0, 31), + [P3_INTPEND] = REG_FIELD(GS101_REG_INTPEND(3), 0, 31), + [P4_INTPEND] = REG_FIELD(GS101_REG_INTPEND(4), 0, 31), + [P5_INTPEND] = REG_FIELD(GS101_REG_INTPEND(5), 0, 31), + [P6_INTPEND] = REG_FIELD(GS101_REG_INTPEND(6), 0, 31), + [P7_INTPEND] = REG_FIELD(GS101_REG_INTPEND(7), 0, 31), + [P8_INTPEND] = REG_FIELD(GS101_REG_INTPEND(8), 0, 31), + [P9_INTPEND] = REG_FIELD(GS101_REG_INTPEND(9), 0, 31), + [P10_INTPEND] = REG_FIELD(GS101_REG_INTPEND(10), 0, 31), + [P11_INTPEND] = REG_FIELD(GS101_REG_INTPEND(11), 0, 31), + [P12_INTPEND] = REG_FIELD(GS101_REG_INTPEND(12), 0, 31), + [P13_INTPEND] = REG_FIELD(GS101_REG_INTPEND(13), 0, 31), + [P14_INTPEND] = REG_FIELD(GS101_REG_INTPEND(14), 0, 31), + [P15_INTPEND] = REG_FIELD(GS101_REG_INTPEND(15), 0, 31), +}; + +static const struct regmap_config gs101_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .use_relaxed_mmio = true, + .max_register = GS101_REG_INTPEND(15), +}; + +static const struct acpm_tmu_driver_data acpm_tmu_gs101 = { + .reg_fields = gs101_reg_fields, + .sensor_groups = gs101_sensor_groups, + .num_sensor_groups = ARRAY_SIZE(gs101_sensor_groups), + .mbox_chan_id = 9, +}; + +static int acpm_tmu_op_tz_control(struct acpm_tmu_sensor *sensor, bool on) +{ + struct acpm_tmu_priv *priv = sensor->priv; + const struct acpm_handle *handle = priv->handle; + const struct acpm_tmu_ops *ops = &handle->ops.tmu; + int ret; + + ret = ops->tz_control(handle, priv->mbox_chan_id, sensor->group->id, + on); + if (ret) + return ret; + + sensor->enabled = on; + + return 0; +} + +static int acpm_tmu_control(struct acpm_tmu_priv *priv, bool on) +{ + struct device *dev = priv->dev; + int i, ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + for (i = 0; i < priv->num_sensors; i++) { + struct acpm_tmu_sensor *sensor = &priv->sensors[i]; + + /* Skip sensors that weren't found in DT */ + if (!sensor->tzd) + continue; + + scoped_guard(mutex, &sensor->lock) { + ret = acpm_tmu_op_tz_control(sensor, on); + } + + if (ret) + goto out; + } + +out: + pm_runtime_put_autosuspend(dev); + return ret; +} + +static int acpm_tmu_get_temp(struct thermal_zone_device *tz, int *temp) +{ + struct acpm_tmu_sensor *sensor = thermal_zone_device_priv(tz); + struct acpm_tmu_priv *priv = sensor->priv; + const struct acpm_handle *handle = priv->handle; + const struct acpm_tmu_ops *ops = &handle->ops.tmu; + struct device *dev = priv->dev; + int acpm_temp, ret; + + if (!sensor->enabled) + return -EAGAIN; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + scoped_guard(mutex, &sensor->lock) { + ret = ops->read_temp(handle, priv->mbox_chan_id, + sensor->group->id, &acpm_temp); + } + + pm_runtime_put_autosuspend(dev); + + if (ret) + return ret; + + *temp = acpm_temp * MILLIDEGREE_PER_DEGREE; + + return 0; +} + +static int acpm_tmu_trip_temp_walk_cb(struct thermal_trip *trip, void *data) +{ + struct acpm_tmu_trip_temp_tripwalkdata * const twd = data; + int temperature; + + if (twd->i >= ARRAY_SIZE(twd->threshold)) + return -ERANGE; + + if (trip->type == THERMAL_TRIP_PASSIVE) + goto out; + + temperature = (trip == twd->trip) ? twd->temperature : trip->temperature; + + twd->threshold[twd->i] = temperature / MILLIDEGREE_PER_DEGREE; + +out: + ++twd->i; + return 0; +} + +static int acpm_tmu_set_trip_temp(struct thermal_zone_device *tz, + const struct thermal_trip *trip, int temperature) +{ + struct acpm_tmu_sensor *sensor = thermal_zone_device_priv(tz); + struct acpm_tmu_priv *priv = sensor->priv; + const struct acpm_handle *handle = priv->handle; + const struct acpm_tmu_ops *ops = &handle->ops.tmu; + struct device *dev = priv->dev; + struct acpm_tmu_trip_temp_tripwalkdata twd = { + .trip = trip, + .temperature = temperature, + }; + int ret; + + if (trip->type == THERMAL_TRIP_PASSIVE) + return 0; + + for_each_thermal_trip(tz, acpm_tmu_trip_temp_walk_cb, &twd); + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + guard(mutex)(&sensor->lock); + + if (!sensor->enabled) { + ret = ops->set_threshold(handle, priv->mbox_chan_id, + sensor->group->id, twd.threshold, + ARRAY_SIZE(twd.threshold)); + goto out; + } + + ret = acpm_tmu_op_tz_control(sensor, false); + if (ret) + goto out; + + ret = ops->set_threshold(handle, priv->mbox_chan_id, sensor->group->id, + twd.threshold, ARRAY_SIZE(twd.threshold)); + if (ret) + goto out; + + ret = acpm_tmu_op_tz_control(sensor, true); + +out: + pm_runtime_put_autosuspend(dev); + return ret; +} + +static const struct thermal_zone_device_ops acpm_tmu_sensor_ops = { + .get_temp = acpm_tmu_get_temp, + .set_trip_temp = acpm_tmu_set_trip_temp, +}; + +static int acpm_tmu_has_pending_irq(struct acpm_tmu_sensor *sensor, + bool *pending_irq) +{ + struct acpm_tmu_priv *priv = sensor->priv; + unsigned long mask = sensor->group->mask; + int i, ret; + u32 val; + + guard(mutex)(&sensor->lock); + + for_each_set_bit(i, &mask, EXYNOS_TMU_SENSORS_MAX_COUNT) { + ret = regmap_field_read(priv->regmap_fields[i], &val); + if (ret) + return ret; + + if (val) { + *pending_irq = true; + break; + } + } + + return 0; +} + +static irqreturn_t acpm_tmu_thread_fn(int irq, void *id) +{ + struct acpm_tmu_priv *priv = id; + const struct acpm_handle *handle = priv->handle; + const struct acpm_tmu_ops *ops = &handle->ops.tmu; + struct device *dev = priv->dev; + int i, ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) { + dev_err(dev, "Failed to resume: %d\n", ret); + return IRQ_NONE; + } + + for (i = 0; i < priv->num_sensors; i++) { + struct acpm_tmu_sensor *sensor = &priv->sensors[i]; + bool pending_irq = false; + + if (!sensor->tzd) + continue; + + ret = acpm_tmu_has_pending_irq(sensor, &pending_irq); + if (ret || !pending_irq) + continue; + + thermal_zone_device_update(sensor->tzd, + THERMAL_EVENT_UNSPECIFIED); + + scoped_guard(mutex, &sensor->lock) { + ret = ops->clear_tz_irq(handle, priv->mbox_chan_id, + sensor->group->id); + if (ret) + dev_err(priv->dev, "Sensor %d: failed to clear IRQ (%d)\n", + i, ret); + } + } + + pm_runtime_put_autosuspend(dev); + + return IRQ_HANDLED; +} + +static int acpm_tmu_init_sensor(struct acpm_tmu_sensor *sensor, + const struct acpm_tmu_initialize_tripwalkdata *twd) +{ + struct acpm_tmu_priv *priv = sensor->priv; + const struct acpm_handle *handle = priv->handle; + const struct acpm_tmu_ops *ops = &handle->ops.tmu; + unsigned int mbox_chan_id = priv->mbox_chan_id; + u8 acpm_sensor_id = sensor->group->id; + int ret; + + guard(mutex)(&sensor->lock); + + ret = ops->set_threshold(handle, mbox_chan_id, acpm_sensor_id, + twd->threshold, + ARRAY_SIZE(twd->threshold)); + if (ret) + return ret; + + ret = ops->set_hysteresis(handle, mbox_chan_id, acpm_sensor_id, + twd->hysteresis, + ARRAY_SIZE(twd->threshold)); + if (ret) + return ret; + + ret = ops->set_interrupt_enable(handle, mbox_chan_id, acpm_sensor_id, + twd->inten); + return ret; +} + +static int acpm_tmu_initialize_walk_cb(struct thermal_trip *trip, void *data) +{ + struct acpm_tmu_initialize_tripwalkdata * const twd = data; + int i; + + if (twd->i >= ARRAY_SIZE(twd->threshold)) + return -ERANGE; + + if (trip->type == THERMAL_TRIP_PASSIVE) + goto out; + + i = twd->i; + + twd->threshold[i] = trip->temperature / MILLIDEGREE_PER_DEGREE; + twd->hysteresis[i] = trip->hysteresis / MILLIDEGREE_PER_DEGREE; + + twd->inten |= BIT(i); + +out: + ++twd->i; + return 0; +} + +static int acpm_tmu_init_sensors(struct acpm_tmu_priv *priv) +{ + int i, ret; + + for (i = 0; i < priv->num_sensors; i++) { + struct acpm_tmu_sensor *sensor = &priv->sensors[i]; + struct acpm_tmu_initialize_tripwalkdata twd = {}; + + /* Skip sensors that weren't found in DT */ + if (!sensor->tzd) + continue; + + thermal_zone_for_each_trip(sensor->tzd, + acpm_tmu_initialize_walk_cb, &twd); + + ret = acpm_tmu_init_sensor(sensor, &twd); + if (ret) + return ret; + } + + return 0; +} + +static const struct of_device_id acpm_tmu_match[] = { + { .compatible = "google,gs101-tmu-top" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, acpm_tmu_match); + +static int acpm_tmu_probe(struct platform_device *pdev) +{ + const struct acpm_tmu_driver_data *data = &acpm_tmu_gs101; + const struct acpm_handle *acpm_handle; + struct device *dev = &pdev->dev; + struct acpm_tmu_priv *priv; + struct regmap *regmap; + void __iomem *base; + int i, ret; + + acpm_handle = devm_acpm_get_by_phandle(dev); + if (IS_ERR(acpm_handle)) + return dev_err_probe(dev, PTR_ERR(acpm_handle), + "Failed to get ACPM handle\n"); + + priv = devm_kzalloc(dev, + struct_size(priv, sensors, data->num_sensor_groups), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + priv->handle = acpm_handle; + priv->mbox_chan_id = data->mbox_chan_id; + priv->num_sensors = data->num_sensor_groups; + + platform_set_drvdata(pdev, priv); + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return dev_err_probe(dev, PTR_ERR(base), "Failed to ioremap resource\n"); + + regmap = devm_regmap_init_mmio(dev, base, &gs101_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Failed to init regmap\n"); + + ret = devm_regmap_field_bulk_alloc(dev, regmap, priv->regmap_fields, + data->reg_fields, REG_INTPEND_COUNT); + if (ret) + return dev_err_probe(dev, ret, + "Unable to map syscon registers\n"); + + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) + return dev_err_probe(dev, PTR_ERR(priv->clk), + "Failed to get the clock\n"); + + priv->irq = platform_get_irq(pdev, 0); + if (priv->irq < 0) + return dev_err_probe(dev, priv->irq, "Failed to get irq\n"); + + ret = devm_request_threaded_irq(dev, priv->irq, NULL, + acpm_tmu_thread_fn, IRQF_ONESHOT, + dev_name(dev), priv); + if (ret) + return dev_err_probe(dev, ret, "Failed to request irq\n"); + + pm_runtime_set_autosuspend_delay(dev, 100); + pm_runtime_use_autosuspend(dev); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable runtime PM\n"); + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to resume device\n"); + + ret = acpm_handle->ops.tmu.init(acpm_handle, priv->mbox_chan_id); + if (ret) { + ret = dev_err_probe(dev, ret, "Failed to init TMU\n"); + goto err_pm_put; + } + + for (i = 0; i < priv->num_sensors; i++) { + struct acpm_tmu_sensor *sensor = &priv->sensors[i]; + + mutex_init(&sensor->lock); + sensor->group = &data->sensor_groups[i]; + sensor->priv = priv; + + sensor->tzd = devm_thermal_of_zone_register(dev, i, sensor, + &acpm_tmu_sensor_ops); + if (IS_ERR(sensor->tzd)) { + ret = PTR_ERR(sensor->tzd); + if (ret == -ENODEV) { + sensor->tzd = NULL; + dev_dbg(dev, "Sensor %d not used in DT, skipping\n", i); + continue; + } + + ret = dev_err_probe(dev, ret, "Failed to register sensor %d\n", i); + goto err_pm_put; + } + + ret = devm_thermal_add_hwmon_sysfs(dev, sensor->tzd); + if (ret) + dev_warn(dev, "Failed to add hwmon sysfs!\n"); + } + + ret = acpm_tmu_init_sensors(priv); + if (ret) { + ret = dev_err_probe(dev, ret, "Failed to init sensors\n"); + goto err_pm_put; + } + + ret = acpm_tmu_control(priv, true); + if (ret) { + ret = dev_err_probe(dev, ret, "Failed to enable TMU\n"); + goto err_pm_put; + } + + pm_runtime_put_autosuspend(&pdev->dev); + + return 0; + +err_pm_put: + pm_runtime_put_sync(dev); + return ret; +} + +static void acpm_tmu_remove(struct platform_device *pdev) +{ + struct acpm_tmu_priv *priv = platform_get_drvdata(pdev); + + /* Stop IRQ first to prevent race with thread_fn */ + disable_irq(priv->irq); + + acpm_tmu_control(priv, false); +} + +static int acpm_tmu_suspend(struct device *dev) +{ + struct acpm_tmu_priv *priv = dev_get_drvdata(dev); + const struct acpm_handle *handle = priv->handle; + const struct acpm_tmu_ops *ops = &handle->ops.tmu; + int ret; + + ret = acpm_tmu_control(priv, false); + if (ret) + return ret; + + /* APB clock not required for this specific msg */ + return ops->suspend(handle, priv->mbox_chan_id); +} + +static int acpm_tmu_resume(struct device *dev) +{ + struct acpm_tmu_priv *priv = dev_get_drvdata(dev); + const struct acpm_handle *handle = priv->handle; + const struct acpm_tmu_ops *ops = &handle->ops.tmu; + int ret; + + /* APB clock not required for this specific msg */ + ret = ops->resume(handle, priv->mbox_chan_id); + if (ret) + return ret; + + return acpm_tmu_control(priv, true); +} + +static int acpm_tmu_runtime_suspend(struct device *dev) +{ + struct acpm_tmu_priv *priv = dev_get_drvdata(dev); + + clk_disable_unprepare(priv->clk); + + return 0; +} + +static int acpm_tmu_runtime_resume(struct device *dev) +{ + struct acpm_tmu_priv *priv = dev_get_drvdata(dev); + + return clk_prepare_enable(priv->clk); +} + +static const struct dev_pm_ops acpm_tmu_pm_ops = { + SYSTEM_SLEEP_PM_OPS(acpm_tmu_suspend, acpm_tmu_resume) + RUNTIME_PM_OPS(acpm_tmu_runtime_suspend, acpm_tmu_runtime_resume, NULL) +}; + +static struct platform_driver acpm_tmu_driver = { + .driver = { + .name = "gs-tmu", + .pm = pm_ptr(&acpm_tmu_pm_ops), + .of_match_table = acpm_tmu_match, + }, + .probe = acpm_tmu_probe, + .remove = acpm_tmu_remove, +}; +module_platform_driver(acpm_tmu_driver); + +MODULE_AUTHOR("Tudor Ambarus "); +MODULE_DESCRIPTION("Samsung Exynos ACPM TMU Driver"); +MODULE_LICENSE("GPL"); -- 2.52.0.457.g6b5491de43-goog