diff options
Diffstat (limited to 'gcip-kernel-driver/drivers')
-rw-r--r-- | gcip-kernel-driver/drivers/gcip/Makefile | 7 | ||||
-rw-r--r-- | gcip-kernel-driver/drivers/gcip/gcip-alloc-helper.c | 2 | ||||
-rw-r--r-- | gcip-kernel-driver/drivers/gcip/gcip-dma-fence.c | 2 | ||||
-rw-r--r-- | gcip-kernel-driver/drivers/gcip/gcip-domain-pool.c | 56 | ||||
-rw-r--r-- | gcip-kernel-driver/drivers/gcip/gcip-firmware.c | 4 | ||||
-rw-r--r-- | gcip-kernel-driver/drivers/gcip/gcip-image-config.c | 2 | ||||
-rw-r--r-- | gcip-kernel-driver/drivers/gcip/gcip-kci.c | 2 | ||||
-rw-r--r-- | gcip-kernel-driver/drivers/gcip/gcip-mailbox.c | 2 | ||||
-rw-r--r-- | gcip-kernel-driver/drivers/gcip/gcip-mem-pool.c | 27 | ||||
-rw-r--r-- | gcip-kernel-driver/drivers/gcip/gcip-pm.c | 37 | ||||
-rw-r--r-- | gcip-kernel-driver/drivers/gcip/gcip-telemetry.c | 3 | ||||
-rw-r--r-- | gcip-kernel-driver/drivers/gcip/gcip-thermal.c | 517 |
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; +} |