summaryrefslogtreecommitdiff
path: root/gcip-kernel-driver/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'gcip-kernel-driver/drivers')
-rw-r--r--gcip-kernel-driver/drivers/gcip/Makefile7
-rw-r--r--gcip-kernel-driver/drivers/gcip/gcip-alloc-helper.c2
-rw-r--r--gcip-kernel-driver/drivers/gcip/gcip-dma-fence.c2
-rw-r--r--gcip-kernel-driver/drivers/gcip/gcip-domain-pool.c56
-rw-r--r--gcip-kernel-driver/drivers/gcip/gcip-firmware.c4
-rw-r--r--gcip-kernel-driver/drivers/gcip/gcip-image-config.c2
-rw-r--r--gcip-kernel-driver/drivers/gcip/gcip-kci.c2
-rw-r--r--gcip-kernel-driver/drivers/gcip/gcip-mailbox.c2
-rw-r--r--gcip-kernel-driver/drivers/gcip/gcip-mem-pool.c27
-rw-r--r--gcip-kernel-driver/drivers/gcip/gcip-pm.c37
-rw-r--r--gcip-kernel-driver/drivers/gcip/gcip-telemetry.c3
-rw-r--r--gcip-kernel-driver/drivers/gcip/gcip-thermal.c517
12 files changed, 603 insertions, 58 deletions
diff --git a/gcip-kernel-driver/drivers/gcip/Makefile b/gcip-kernel-driver/drivers/gcip/Makefile
index bc370e5..7de0874 100644
--- a/gcip-kernel-driver/drivers/gcip/Makefile
+++ b/gcip-kernel-driver/drivers/gcip/Makefile
@@ -1,4 +1,4 @@
-# SPDX-License-Identifier: GPL-2.0
+# SPDX-License-Identifier: GPL-2.0-only
#
# Makefile for GCIP framework.
#
@@ -15,7 +15,8 @@ gcip-objs := gcip-alloc-helper.o \
gcip-mailbox.o \
gcip-mem-pool.o \
gcip-pm.o \
- gcip-telemetry.o
+ gcip-telemetry.o \
+ gcip-thermal.o
CURRENT_DIR=$(dir $(abspath $(lastword $(MAKEFILE_LIST))))
@@ -23,6 +24,8 @@ ccflags-y += -I$(CURRENT_DIR)/../../include
ifdef CONFIG_GCIP_TEST
obj-y += unittests/
+include $(srctree)/drivers/gcip/unittests/Makefile.include
+$(call include_test_path, $(gcip-objs))
endif
modules modules_install clean:
diff --git a/gcip-kernel-driver/drivers/gcip/gcip-alloc-helper.c b/gcip-kernel-driver/drivers/gcip/gcip-alloc-helper.c
index 85af8e5..4008dff 100644
--- a/gcip-kernel-driver/drivers/gcip/gcip-alloc-helper.c
+++ b/gcip-kernel-driver/drivers/gcip/gcip-alloc-helper.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0
+// SPDX-License-Identifier: GPL-2.0-only
/*
* GCIP helpers for allocating memories.
*
diff --git a/gcip-kernel-driver/drivers/gcip/gcip-dma-fence.c b/gcip-kernel-driver/drivers/gcip/gcip-dma-fence.c
index 4f83670..ca49526 100644
--- a/gcip-kernel-driver/drivers/gcip/gcip-dma-fence.c
+++ b/gcip-kernel-driver/drivers/gcip/gcip-dma-fence.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0
+// SPDX-License-Identifier: GPL-2.0-only
/*
* GCIP support of DMA fences.
*
diff --git a/gcip-kernel-driver/drivers/gcip/gcip-domain-pool.c b/gcip-kernel-driver/drivers/gcip/gcip-domain-pool.c
index 2341b52..882aa80 100644
--- a/gcip-kernel-driver/drivers/gcip/gcip-domain-pool.c
+++ b/gcip-kernel-driver/drivers/gcip/gcip-domain-pool.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0
+// SPDX-License-Identifier: GPL-2.0-only
/*
* GCIP IOMMU domain allocator.
*
@@ -12,6 +12,11 @@
#include <gcip/gcip-domain-pool.h>
+struct dynamic_domain {
+ struct list_head list_entry;
+ struct iommu_domain *domain;
+};
+
int gcip_domain_pool_init(struct device *dev, struct gcip_domain_pool *pool, unsigned int size)
{
unsigned int i;
@@ -19,6 +24,8 @@ int gcip_domain_pool_init(struct device *dev, struct gcip_domain_pool *pool, uns
pool->size = size;
pool->dev = dev;
+ INIT_LIST_HEAD(&pool->dynamic_domains);
+ mutex_init(&pool->lock);
if (!size)
return 0;
@@ -48,9 +55,23 @@ int gcip_domain_pool_init(struct device *dev, struct gcip_domain_pool *pool, uns
struct iommu_domain *gcip_domain_pool_alloc(struct gcip_domain_pool *pool)
{
int id;
+ struct dynamic_domain *ddomain;
- if (!pool->size)
- return iommu_domain_alloc(pool->dev->bus);
+ if (!pool->size) {
+ ddomain = vzalloc(sizeof(*ddomain));
+ if (!ddomain)
+ return NULL;
+
+ ddomain->domain = iommu_domain_alloc(pool->dev->bus);
+ if (!ddomain->domain) {
+ vfree(ddomain);
+ return NULL;
+ }
+ mutex_lock(&pool->lock);
+ list_add_tail(&ddomain->list_entry, &pool->dynamic_domains);
+ mutex_unlock(&pool->lock);
+ return ddomain->domain;
+ }
id = ida_alloc_max(&pool->idp, pool->size - 1, GFP_KERNEL);
@@ -67,11 +88,25 @@ struct iommu_domain *gcip_domain_pool_alloc(struct gcip_domain_pool *pool)
void gcip_domain_pool_free(struct gcip_domain_pool *pool, struct iommu_domain *domain)
{
int id;
+ struct dynamic_domain *ddomain;
+ struct list_head *cur, *nxt;
if (!pool->size) {
- iommu_domain_free(domain);
+ mutex_lock(&pool->lock);
+ list_for_each_safe(cur, nxt, &pool->dynamic_domains) {
+ ddomain = container_of(cur, struct dynamic_domain, list_entry);
+ if (ddomain->domain == domain) {
+ list_del(&ddomain->list_entry);
+ mutex_unlock(&pool->lock);
+ iommu_domain_free(domain);
+ vfree(ddomain);
+ return;
+ }
+ }
+ mutex_unlock(&pool->lock);
return;
}
+
for (id = 0; id < pool->size; id++) {
if (pool->array[id] == domain) {
dev_dbg(pool->dev, "Released domain from pool with id = %d\n", id);
@@ -85,9 +120,20 @@ void gcip_domain_pool_free(struct gcip_domain_pool *pool, struct iommu_domain *d
void gcip_domain_pool_destroy(struct gcip_domain_pool *pool)
{
int i;
+ struct dynamic_domain *ddomain;
+ struct list_head *cur, *nxt;
- if (!pool->size)
+ if (!pool->size) {
+ mutex_lock(&pool->lock);
+ list_for_each_safe(cur, nxt, &pool->dynamic_domains) {
+ ddomain = container_of(cur, struct dynamic_domain, list_entry);
+ list_del(&ddomain->list_entry);
+ iommu_domain_free(ddomain->domain);
+ vfree(ddomain);
+ }
+ mutex_unlock(&pool->lock);
return;
+ }
dev_dbg(pool->dev, "Destroying domain pool with %u domains\n", pool->size);
diff --git a/gcip-kernel-driver/drivers/gcip/gcip-firmware.c b/gcip-kernel-driver/drivers/gcip/gcip-firmware.c
index 1d9392c..52c3940 100644
--- a/gcip-kernel-driver/drivers/gcip/gcip-firmware.c
+++ b/gcip-kernel-driver/drivers/gcip/gcip-firmware.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0
+// SPDX-License-Identifier: GPL-2.0-only
/*
* GCIP firmware interface.
*
@@ -136,7 +136,7 @@ void gcip_firmware_tracing_destroy(struct gcip_fw_tracing *fw_tracing)
kfree(fw_tracing);
}
-int gcip_firmware_tracing_restore(struct gcip_fw_tracing *fw_tracing)
+int gcip_firmware_tracing_restore_on_powering(struct gcip_fw_tracing *fw_tracing)
{
int ret = 0;
diff --git a/gcip-kernel-driver/drivers/gcip/gcip-image-config.c b/gcip-kernel-driver/drivers/gcip/gcip-image-config.c
index 62acd0b..98a3546 100644
--- a/gcip-kernel-driver/drivers/gcip/gcip-image-config.c
+++ b/gcip-kernel-driver/drivers/gcip/gcip-image-config.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0
+// SPDX-License-Identifier: GPL-2.0-only
/*
* Framework for parsing the firmware image configuration.
*
diff --git a/gcip-kernel-driver/drivers/gcip/gcip-kci.c b/gcip-kernel-driver/drivers/gcip/gcip-kci.c
index 15b2c53..c3da416 100644
--- a/gcip-kernel-driver/drivers/gcip/gcip-kci.c
+++ b/gcip-kernel-driver/drivers/gcip/gcip-kci.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0
+// SPDX-License-Identifier: GPL-2.0-only
/*
* Kernel Control Interface, implements the protocol between AP kernel and GCIP firmware.
*
diff --git a/gcip-kernel-driver/drivers/gcip/gcip-mailbox.c b/gcip-kernel-driver/drivers/gcip/gcip-mailbox.c
index 334a51d..6d20771 100644
--- a/gcip-kernel-driver/drivers/gcip/gcip-mailbox.c
+++ b/gcip-kernel-driver/drivers/gcip/gcip-mailbox.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0
+// SPDX-License-Identifier: GPL-2.0-only
/*
* GCIP Mailbox Interface.
*
diff --git a/gcip-kernel-driver/drivers/gcip/gcip-mem-pool.c b/gcip-kernel-driver/drivers/gcip/gcip-mem-pool.c
index 3e18051..564991b 100644
--- a/gcip-kernel-driver/drivers/gcip/gcip-mem-pool.c
+++ b/gcip-kernel-driver/drivers/gcip/gcip-mem-pool.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0
+// SPDX-License-Identifier: GPL-2.0-only
/*
* A simple memory allocator to help allocating reserved memory pools.
*
@@ -12,21 +12,21 @@
#include <gcip/gcip-mem-pool.h>
-int gcip_mem_pool_init(struct gcip_mem_pool *pool, struct device *dev, phys_addr_t base_paddr,
+int gcip_mem_pool_init(struct gcip_mem_pool *pool, struct device *dev, unsigned long base_addr,
size_t size, size_t granule)
{
int ret;
- if (!base_paddr || granule == 0)
+ if (!base_addr || granule == 0)
return -EINVAL;
- if (base_paddr % granule || size % granule)
+ if (base_addr % granule || size % granule)
return -EINVAL;
pool->gen_pool = gen_pool_create(ilog2(granule), -1);
if (!pool->gen_pool) {
dev_err(dev, "gcip memory pool allocate gen_pool failed");
return -ENOMEM;
}
- ret = gen_pool_add(pool->gen_pool, base_paddr, size, -1);
+ ret = gen_pool_add(pool->gen_pool, base_addr, size, -1);
if (ret) {
gen_pool_destroy(pool->gen_pool);
pool->gen_pool = NULL;
@@ -35,7 +35,7 @@ int gcip_mem_pool_init(struct gcip_mem_pool *pool, struct device *dev, phys_addr
}
pool->dev = dev;
pool->granule = granule;
- pool->base_paddr = base_paddr;
+ pool->base_addr = base_addr;
return 0;
}
@@ -47,23 +47,20 @@ void gcip_mem_pool_exit(struct gcip_mem_pool *pool)
pool->gen_pool = NULL;
}
-phys_addr_t gcip_mem_pool_alloc(struct gcip_mem_pool *pool, size_t size)
+unsigned long gcip_mem_pool_alloc(struct gcip_mem_pool *pool, size_t size)
{
unsigned long addr;
- size_t aligned_size = ALIGN(size, pool->granule);
- addr = gen_pool_alloc(pool->gen_pool, aligned_size);
+ addr = gen_pool_alloc(pool->gen_pool, size);
if (!addr)
return 0;
- dev_dbg(pool->dev, "%s @ size = %#zx paddr=%#lx", __func__, size, addr);
- return (phys_addr_t)addr;
+ dev_dbg(pool->dev, "%s @ size = %#zx addr=%#lx", __func__, size, addr);
+ return addr;
}
-void gcip_mem_pool_free(struct gcip_mem_pool *pool, phys_addr_t paddr, size_t size)
+void gcip_mem_pool_free(struct gcip_mem_pool *pool, unsigned long addr, size_t size)
{
- unsigned long addr = paddr;
-
- dev_dbg(pool->dev, "%s @ size = %#zx paddr=%#lx", __func__, size, addr);
+ dev_dbg(pool->dev, "%s @ size = %#zx addr=%#lx", __func__, size, addr);
size = ALIGN(size, pool->granule);
gen_pool_free(pool->gen_pool, addr, size);
}
diff --git a/gcip-kernel-driver/drivers/gcip/gcip-pm.c b/gcip-kernel-driver/drivers/gcip/gcip-pm.c
index 43d9654..b9907a1 100644
--- a/gcip-kernel-driver/drivers/gcip/gcip-pm.c
+++ b/gcip-kernel-driver/drivers/gcip/gcip-pm.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0
+// SPDX-License-Identifier: GPL-2.0-only
/*
* Power management interface for GCIP devices.
*
@@ -113,8 +113,10 @@ static int gcip_pm_get_locked(struct gcip_pm *pm)
gcip_pm_lockdep_assert_held(pm);
if (!pm->count) {
- pm->power_down_pending = false;
- ret = pm->power_up(pm->data);
+ if (pm->power_down_pending)
+ pm->power_down_pending = false;
+ else
+ ret = pm->power_up(pm->data);
}
if (!ret)
@@ -163,7 +165,7 @@ int gcip_pm_get(struct gcip_pm *pm)
return ret;
}
-static void __gcip_pm_put(struct gcip_pm *pm, bool async)
+void gcip_pm_put(struct gcip_pm *pm)
{
if (!pm)
return;
@@ -175,10 +177,7 @@ static void __gcip_pm_put(struct gcip_pm *pm, bool async)
if (!--pm->count) {
pm->power_down_pending = true;
- if (async)
- schedule_delayed_work(&pm->power_down_work, 0);
- else
- gcip_pm_try_power_down(pm);
+ gcip_pm_try_power_down(pm);
}
dev_dbg(pm->dev, "%s: %d\n", __func__, pm->count);
@@ -187,29 +186,12 @@ unlock:
mutex_unlock(&pm->lock);
}
-void gcip_pm_put(struct gcip_pm *pm)
-{
- __gcip_pm_put(pm, false);
-}
-
-void gcip_pm_put_async(struct gcip_pm *pm)
-{
- __gcip_pm_put(pm, true);
-}
-
int gcip_pm_get_count(struct gcip_pm *pm)
{
- int count = -EAGAIN;
-
if (!pm)
return 0;
- if (mutex_trylock(&pm->lock)) {
- count = pm->count;
- mutex_unlock(&pm->lock);
- }
-
- return count;
+ return pm->count;
}
bool gcip_pm_is_powered(struct gcip_pm *pm)
@@ -228,8 +210,7 @@ void gcip_pm_shutdown(struct gcip_pm *pm, bool force)
if (pm->count) {
if (!force)
goto unlock;
- else
- dev_warn(pm->dev, "Force shutdown with power up count: %d", pm->count);
+ dev_warn(pm->dev, "Force shutdown with power up count: %d", pm->count);
}
gcip_pm_try_power_down(pm);
diff --git a/gcip-kernel-driver/drivers/gcip/gcip-telemetry.c b/gcip-kernel-driver/drivers/gcip/gcip-telemetry.c
index f557c24..1599889 100644
--- a/gcip-kernel-driver/drivers/gcip/gcip-telemetry.c
+++ b/gcip-kernel-driver/drivers/gcip/gcip-telemetry.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0
+// SPDX-License-Identifier: GPL-2.0-only
/*
* GCIP telemetry: logging and tracing.
*
@@ -126,6 +126,7 @@ void gcip_telemetry_fw_log(struct gcip_telemetry *log)
case GCIP_FW_LOG_LEVEL_WARN:
dev_warn(dev, "%s", buffer);
break;
+ case GCIP_FW_LOG_LEVEL_FATAL:
case GCIP_FW_LOG_LEVEL_ERROR:
dev_err(dev, "%s", buffer);
break;
diff --git a/gcip-kernel-driver/drivers/gcip/gcip-thermal.c b/gcip-kernel-driver/drivers/gcip/gcip-thermal.c
new file mode 100644
index 0000000..bc06cd5
--- /dev/null
+++ b/gcip-kernel-driver/drivers/gcip/gcip-thermal.c
@@ -0,0 +1,517 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Thermal management support for GCIP devices.
+ *
+ * Copyright (C) 2023 Google LLC
+ */
+
+#include <linux/debugfs.h>
+#include <linux/device.h>
+#include <linux/minmax.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+#include <linux/thermal.h>
+#include <linux/version.h>
+
+#include <gcip/gcip-pm.h>
+#include <gcip/gcip-thermal.h>
+
+#define OF_DATA_NUM_MAX (GCIP_THERMAL_MAX_NUM_STATES * 2)
+
+#define to_cdev(dev) container_of(dev, struct thermal_cooling_device, device)
+#define to_gcip_thermal(dev) ((struct gcip_thermal *)to_cdev(dev)->devdata)
+
+/* Struct for state to rate and state to power mappings. */
+struct gcip_rate_pwr {
+ unsigned long rate;
+ u32 power;
+};
+
+static struct gcip_rate_pwr state_map[GCIP_THERMAL_MAX_NUM_STATES] = { 0 };
+
+static int gcip_thermal_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state)
+{
+ struct gcip_thermal *thermal = cdev->devdata;
+
+ if (!thermal->num_states)
+ return -ENODEV;
+
+ *state = thermal->num_states - 1;
+
+ return 0;
+}
+
+static int gcip_thermal_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *state)
+{
+ struct gcip_thermal *thermal = cdev->devdata;
+
+ mutex_lock(&thermal->lock);
+ *state = thermal->state;
+ mutex_unlock(&thermal->lock);
+
+ return 0;
+}
+
+static int gcip_thermal_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state)
+{
+ struct gcip_thermal *thermal = cdev->devdata;
+ int i, ret = 0;
+
+ if (state >= thermal->num_states) {
+ dev_err(thermal->dev, "Invalid thermal cooling state %lu\n", state);
+ return -EINVAL;
+ }
+
+ mutex_lock(&thermal->lock);
+
+ thermal->vote[GCIP_THERMAL_COOLING_DEVICE] = state;
+ for (i = 0; i < GCIP_THERMAL_MAX_NUM_VOTERS; i++)
+ state = max(state, thermal->vote[i]);
+
+ if (state == thermal->state)
+ goto out;
+
+ if (!gcip_pm_get_if_powered(thermal->pm, false)) {
+ ret = thermal->set_rate(thermal->data, state_map[state].rate);
+ gcip_pm_put(thermal->pm);
+ }
+
+ if (ret)
+ dev_err(thermal->dev, "Failed to set thermal cooling state: %d\n", ret);
+ else
+ thermal->state = state;
+out:
+ mutex_unlock(&thermal->lock);
+
+ return ret;
+}
+
+static int gcip_thermal_rate2power_internal(struct gcip_thermal *thermal, unsigned long rate,
+ u32 *power)
+{
+ int i;
+
+ for (i = 0; i < thermal->num_states; i++) {
+ if (rate == state_map[i].rate) {
+ *power = state_map[i].power;
+ return 0;
+ }
+ }
+
+ dev_err(thermal->dev, "Unknown rate for: %lu\n", rate);
+ *power = 0;
+
+ return -EINVAL;
+}
+
+static int gcip_thermal_get_requested_power(struct thermal_cooling_device *cdev, u32 *power)
+{
+ struct gcip_thermal *thermal = cdev->devdata;
+ unsigned long rate;
+ int ret;
+
+ if (gcip_pm_get_if_powered(thermal->pm, false)) {
+ *power = 0;
+ return 0;
+ }
+
+ mutex_lock(&thermal->lock);
+
+ ret = thermal->get_rate(thermal->data, &rate);
+
+ mutex_unlock(&thermal->lock);
+ gcip_pm_put(thermal->pm);
+
+ if (ret)
+ return ret;
+
+ return gcip_thermal_rate2power_internal(thermal, rate, power);
+}
+
+static int gcip_thermal_state2power(struct thermal_cooling_device *cdev, unsigned long state,
+ u32 *power)
+{
+ struct gcip_thermal *thermal = cdev->devdata;
+
+ if (state >= thermal->num_states) {
+ dev_err(thermal->dev, "Invalid state: %lu\n", state);
+ return -EINVAL;
+ }
+
+ return gcip_thermal_rate2power_internal(thermal, state_map[state].rate, power);
+}
+
+static int gcip_thermal_power2state(struct thermal_cooling_device *cdev, u32 power,
+ unsigned long *state)
+{
+ struct gcip_thermal *thermal = cdev->devdata;
+
+ if (!thermal->num_states)
+ return -ENODEV;
+
+ /*
+ * Argument "power" is the maximum allowed power consumption in mW as defined by the PID
+ * control loop. Checks for the first state that is less than or equal to the current
+ * allowed power. state_map is descending, so lowest power consumption is last value in the
+ * array. Returns lowest state even if it consumes more power than allowed as not all
+ * platforms can handle throttling below an active state.
+ */
+ for (*state = 0; *state < thermal->num_states; (*state)++)
+ if (power >= state_map[*state].power)
+ return 0;
+
+ *state = thermal->num_states - 1;
+
+ return 0;
+}
+
+static const struct thermal_cooling_device_ops gcip_thermal_ops = {
+ .get_max_state = gcip_thermal_get_max_state,
+ .get_cur_state = gcip_thermal_get_cur_state,
+ .set_cur_state = gcip_thermal_set_cur_state,
+ .get_requested_power = gcip_thermal_get_requested_power,
+ .state2power = gcip_thermal_state2power,
+ .power2state = gcip_thermal_power2state,
+};
+
+/* This API was removed, but Android still uses it to update thermal request. */
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) && IS_ENABLED(CONFIG_ANDROID)
+void thermal_cdev_update(struct thermal_cooling_device *cdev);
+#endif
+
+static void gcip_thermal_update(struct gcip_thermal *thermal)
+{
+ struct thermal_cooling_device *cdev = thermal->cdev;
+
+ cdev->updated = false;
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 12, 0) || IS_ENABLED(CONFIG_ANDROID)
+ thermal_cdev_update(cdev);
+#elif IS_ENABLED(CONFIG_THERMAL)
+ dev_err_once(thermal->dev, "Thermal update not implemented");
+#endif
+}
+
+static ssize_t user_vote_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct gcip_thermal *thermal = to_gcip_thermal(dev);
+ ssize_t ret;
+
+ if (!thermal)
+ return -ENODEV;
+
+ mutex_lock(&thermal->lock);
+ ret = sysfs_emit(buf, "%lu\n", thermal->vote[GCIP_THERMAL_SYSFS]);
+ mutex_unlock(&thermal->lock);
+
+ return ret;
+}
+
+static ssize_t user_vote_store(struct device *dev, struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ struct gcip_thermal *thermal = to_gcip_thermal(dev);
+ unsigned long state;
+ int ret;
+
+ if (!thermal)
+ return -ENODEV;
+
+ ret = kstrtoul(buf, 0, &state);
+ if (ret)
+ return ret;
+
+ if (state >= thermal->num_states)
+ return -EINVAL;
+
+ mutex_lock(&thermal->lock);
+ thermal->vote[GCIP_THERMAL_SYSFS] = state;
+ mutex_unlock(&thermal->lock);
+
+ gcip_thermal_update(thermal);
+
+ return count;
+}
+
+static DEVICE_ATTR_RW(user_vote);
+
+static int gcip_thermal_rate2state(struct gcip_thermal *thermal, unsigned long rate)
+{
+ int i;
+
+ for (i = 0; i < thermal->num_states; i++) {
+ if (state_map[i].rate <= rate)
+ return i;
+ }
+
+ /* Returns lowest state on an invalid input. */
+ return thermal->num_states - 1;
+}
+
+static int gcip_thermal_notifier(struct notifier_block *nb, unsigned long rate, void *nb_data)
+{
+ struct gcip_thermal *thermal = container_of(nb, struct gcip_thermal, nb);
+ unsigned long state = gcip_thermal_rate2state(thermal, rate);
+
+ dev_dbg(thermal->dev, "Thermal notifier req original: %lu, state: %lu\n", rate, state);
+
+ mutex_lock(&thermal->lock);
+ thermal->vote[GCIP_THERMAL_NOTIFIER_BLOCK] = state;
+ mutex_unlock(&thermal->lock);
+
+ gcip_thermal_update(thermal);
+
+ return NOTIFY_OK;
+}
+
+struct notifier_block *gcip_thermal_get_notifier_block(struct gcip_thermal *thermal)
+{
+ if (IS_ERR_OR_NULL(thermal))
+ return NULL;
+
+ return &thermal->nb;
+}
+
+void gcip_thermal_destroy(struct gcip_thermal *thermal)
+{
+ if (IS_ERR_OR_NULL(thermal))
+ return;
+
+ debugfs_remove_recursive(thermal->dentry);
+ thermal_cooling_device_unregister(thermal->cdev);
+ devm_kfree(thermal->dev, thermal);
+}
+
+static int gcip_thermal_enable_get(void *data, u64 *val)
+{
+ struct gcip_thermal *thermal = (struct gcip_thermal *)data;
+
+ mutex_lock(&thermal->lock);
+ *val = thermal->enabled;
+ mutex_unlock(&thermal->lock);
+
+ return 0;
+}
+
+static int gcip_thermal_enable_set(void *data, u64 val)
+{
+ struct gcip_thermal *thermal = (struct gcip_thermal *)data;
+ int ret = 0;
+
+ mutex_lock(&thermal->lock);
+
+ if (thermal->enabled != (bool)val) {
+ /*
+ * If the device is not powered, the value will be restored by
+ * gcip_thermal_restore_on_powering in next fw boot.
+ */
+ if (!gcip_pm_get_if_powered(thermal->pm, false)) {
+ ret = thermal->control(thermal->data, val);
+ gcip_pm_put(thermal->pm);
+ }
+
+ if (!ret) {
+ thermal->enabled = val;
+ dev_info_ratelimited(thermal->dev, "%s thermal control",
+ thermal->enabled ? "Enable" : "Disable");
+ } else {
+ dev_err(thermal->dev, "Failed to %s thermal control: %d ",
+ val ? "enable" : "disable", ret);
+ }
+ }
+
+ mutex_unlock(&thermal->lock);
+
+ return ret;
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(fops_gcip_thermal_enable, gcip_thermal_enable_get, gcip_thermal_enable_set,
+ "%llu\n");
+
+static int gcip_thermal_parse_dvfs_table(struct gcip_thermal *thermal)
+{
+ int row_size, col_size, tbl_size, i;
+ int of_data_int_array[OF_DATA_NUM_MAX];
+
+ if (of_property_read_u32_array(thermal->dev->of_node, GCIP_THERMAL_TABLE_SIZE_NAME,
+ of_data_int_array, 2))
+ goto error;
+
+ row_size = of_data_int_array[0];
+ col_size = of_data_int_array[1];
+ tbl_size = row_size * col_size;
+ if (row_size > GCIP_THERMAL_MAX_NUM_STATES) {
+ dev_err(thermal->dev, "Too many states\n");
+ goto error;
+ }
+
+ if (tbl_size > OF_DATA_NUM_MAX)
+ goto error;
+
+ if (of_property_read_u32_array(thermal->dev->of_node, GCIP_THERMAL_TABLE_NAME,
+ of_data_int_array, tbl_size))
+ goto error;
+
+ thermal->num_states = row_size;
+ for (i = 0; i < row_size; ++i) {
+ int idx = col_size * i;
+
+ state_map[i].rate = of_data_int_array[idx];
+ state_map[i].power = of_data_int_array[idx + 1];
+ }
+
+ return 0;
+
+error:
+ dev_err(thermal->dev, "Failed to parse DVFS table\n");
+
+ return -EINVAL;
+}
+
+static int gcip_thermal_cooling_register(struct gcip_thermal *thermal, const char *type,
+ const char *node_name)
+{
+ struct device_node *node = NULL;
+ int ret;
+
+ ret = gcip_thermal_parse_dvfs_table(thermal);
+ if (ret)
+ return ret;
+
+ if (node_name)
+ node = of_find_node_by_name(NULL, node_name);
+ if (!node)
+ dev_warn(thermal->dev, "Failed to find thermal cooling node\n");
+
+ thermal->cdev = thermal_of_cooling_device_register(node, type, thermal, &gcip_thermal_ops);
+ if (IS_ERR(thermal->cdev))
+ return PTR_ERR(thermal->cdev);
+
+ ret = device_create_file(&thermal->cdev->device, &dev_attr_user_vote);
+ if (ret)
+ thermal_cooling_device_unregister(thermal->cdev);
+
+ return ret;
+}
+
+struct gcip_thermal *gcip_thermal_create(const struct gcip_thermal_args *args)
+{
+ struct gcip_thermal *thermal;
+ int ret;
+
+ if (!args->dev || !args->get_rate || !args->set_rate || !args->control)
+ return ERR_PTR(-EINVAL);
+
+ thermal = devm_kzalloc(args->dev, sizeof(*thermal), GFP_KERNEL);
+ if (!thermal)
+ return ERR_PTR(-ENOMEM);
+
+ thermal->dev = args->dev;
+ thermal->nb.notifier_call = gcip_thermal_notifier;
+ thermal->pm = args->pm;
+ thermal->enabled = true;
+ thermal->data = args->data;
+ thermal->get_rate = args->get_rate;
+ thermal->set_rate = args->set_rate;
+ thermal->control = args->control;
+
+ mutex_init(&thermal->lock);
+
+ ret = gcip_thermal_cooling_register(thermal, args->type, args->node_name);
+ if (ret) {
+ dev_err(args->dev, "Failed to initialize external thermal cooling\n");
+ devm_kfree(args->dev, thermal);
+ return ERR_PTR(ret);
+ }
+
+ thermal->dentry = debugfs_create_dir("cooling", args->dentry);
+ /* Don't let debugfs creation failure abort the init procedure. */
+ if (IS_ERR_OR_NULL(thermal->dentry))
+ dev_warn(args->dev, "Failed to create debugfs for thermal cooling");
+ else
+ debugfs_create_file("enable", 0660, thermal->dentry, thermal,
+ &fops_gcip_thermal_enable);
+
+ return thermal;
+}
+
+int gcip_thermal_suspend_device(struct gcip_thermal *thermal)
+{
+ int ret = 0;
+
+ if (IS_ERR_OR_NULL(thermal))
+ return 0;
+
+ mutex_lock(&thermal->lock);
+
+ /*
+ * Always sets as suspended even when the request cannot be handled for unknown reasons
+ * because we still want to prevent the client from using device.
+ */
+ thermal->device_suspended = true;
+ if (!gcip_pm_get_if_powered(thermal->pm, false)) {
+ ret = thermal->set_rate(thermal->data, 0);
+ gcip_pm_put(thermal->pm);
+ }
+
+ mutex_unlock(&thermal->lock);
+
+ return ret;
+}
+
+int gcip_thermal_resume_device(struct gcip_thermal *thermal)
+{
+ int ret = 0;
+
+ if (IS_ERR_OR_NULL(thermal))
+ return 0;
+
+ mutex_lock(&thermal->lock);
+
+ if (!gcip_pm_get_if_powered(thermal->pm, false)) {
+ ret = thermal->set_rate(thermal->data, state_map[thermal->state].rate);
+ gcip_pm_put(thermal->pm);
+ }
+
+ /*
+ * Unlike gcip_thermal_suspend_device(), only sets the device as resumed if the request is
+ * fulfilled.
+ */
+ if (!ret)
+ thermal->device_suspended = false;
+
+ mutex_unlock(&thermal->lock);
+
+ return ret;
+}
+
+bool gcip_thermal_is_device_suspended(struct gcip_thermal *thermal)
+{
+ if (IS_ERR_OR_NULL(thermal))
+ return false;
+
+ return thermal->device_suspended;
+}
+
+int gcip_thermal_restore_on_powering(struct gcip_thermal *thermal)
+{
+ int ret = 0;
+
+ if (IS_ERR_OR_NULL(thermal))
+ return 0;
+
+ gcip_pm_lockdep_assert_held(thermal->pm);
+ mutex_lock(&thermal->lock);
+
+ if (!thermal->enabled)
+ ret = thermal->control(thermal->data, thermal->enabled);
+ else if (thermal->device_suspended)
+ ret = thermal->set_rate(thermal->data, 0);
+ else if (thermal->state)
+ /* Skips state 0 since it's the default thermal state. */
+ ret = thermal->set_rate(thermal->data, state_map[thermal->state].rate);
+
+ mutex_unlock(&thermal->lock);
+
+ return ret;
+}