| /* |
| * CDSP Request Manager |
| * |
| * Copyright (c) 2018-2019, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| /* This module uses rpmsg to communicate with CDSP and receive requests |
| * for CPU L3 frequency and QoS along with Cx Limit management and |
| * thermal cooling handling. |
| */ |
| |
| #define pr_fmt(fmt) "cdsprm: " fmt |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/completion.h> |
| #include <linux/string.h> |
| #include <linux/err.h> |
| #include <linux/platform_device.h> |
| #include <linux/of.h> |
| #include <linux/of_device.h> |
| #include <linux/of_platform.h> |
| #include <linux/kthread.h> |
| #include <linux/workqueue.h> |
| #include <linux/pm_qos.h> |
| #include <linux/delay.h> |
| #include <linux/list.h> |
| #include <linux/spinlock.h> |
| #include <linux/rpmsg.h> |
| #include <linux/thermal.h> |
| #include <linux/debugfs.h> |
| #include <asm/arch_timer.h> |
| #include <linux/soc/qcom/cdsprm.h> |
| #include <linux/soc/qcom/cdsprm_cxlimit.h> |
| |
| #define SYSMON_CDSP_FEATURE_L3_RX 1 |
| #define SYSMON_CDSP_FEATURE_RM_RX 2 |
| #define SYSMON_CDSP_FEATURE_COMPUTE_PRIO_TX 3 |
| #define SYSMON_CDSP_FEATURE_NPU_LIMIT_TX 4 |
| #define SYSMON_CDSP_FEATURE_NPU_LIMIT_RX 5 |
| #define SYSMON_CDSP_FEATURE_NPU_ACTIVITY_TX 6 |
| #define SYSMON_CDSP_FEATURE_NPU_ACTIVITY_RX 7 |
| #define SYSMON_CDSP_FEATURE_NPU_CORNER_TX 8 |
| #define SYSMON_CDSP_FEATURE_NPU_CORNER_RX 9 |
| #define SYSMON_CDSP_FEATURE_THERMAL_LIMIT_TX 10 |
| #define SYSMON_CDSP_FEATURE_CAMERA_ACTIVITY_TX 11 |
| #define SYSMON_CDSP_FEATURE_VERSION_RX 12 |
| |
| #define SYSMON_CDSP_QOS_FLAG_IGNORE 0 |
| #define SYSMON_CDSP_QOS_FLAG_ENABLE 1 |
| #define SYSMON_CDSP_QOS_FLAG_DISABLE 2 |
| #define QOS_LATENCY_DISABLE_VALUE -1 |
| #define SYS_CLK_TICKS_PER_MS 19200 |
| #define CDSPRM_MSG_QUEUE_DEPTH 50 |
| #define CDSP_THERMAL_MAX_STATE 10 |
| #define HVX_THERMAL_MAX_STATE 10 |
| |
| struct sysmon_l3_msg { |
| unsigned int l3_clock_khz; |
| }; |
| |
| struct sysmon_rm_msg { |
| unsigned int b_qos_flag; |
| unsigned int timetick_low; |
| unsigned int timetick_high; |
| }; |
| |
| struct sysmon_npu_limit_msg { |
| unsigned int corner; |
| }; |
| |
| struct sysmon_npu_limit_ack { |
| unsigned int corner; |
| }; |
| |
| struct sysmon_compute_prio_msg { |
| unsigned int priority_idx; |
| }; |
| |
| struct sysmon_npu_activity_msg { |
| unsigned int b_enabled; |
| }; |
| |
| struct sysmon_npu_corner_msg { |
| unsigned int corner; |
| }; |
| |
| struct sysmon_thermal_msg { |
| unsigned short hvx_level; |
| unsigned short cdsp_level; |
| }; |
| |
| struct sysmon_camera_msg { |
| unsigned int b_enabled; |
| }; |
| |
| struct sysmon_version_msg { |
| unsigned int id; |
| }; |
| |
| struct sysmon_msg { |
| unsigned int feature_id; |
| union { |
| struct sysmon_l3_msg l3_struct; |
| struct sysmon_rm_msg rm_struct; |
| struct sysmon_npu_limit_msg npu_limit; |
| struct sysmon_npu_activity_msg npu_activity; |
| struct sysmon_npu_corner_msg npu_corner; |
| struct sysmon_version_msg version; |
| } fs; |
| unsigned int size; |
| }; |
| |
| struct sysmon_msg_tx { |
| unsigned int feature_id; |
| union { |
| struct sysmon_npu_limit_ack npu_limit_ack; |
| struct sysmon_compute_prio_msg compute_prio; |
| struct sysmon_npu_activity_msg npu_activity; |
| struct sysmon_thermal_msg thermal; |
| struct sysmon_npu_corner_msg npu_corner; |
| struct sysmon_camera_msg camera; |
| } fs; |
| unsigned int size; |
| }; |
| |
| enum delay_state { |
| CDSP_DELAY_THREAD_NOT_STARTED = 0, |
| CDSP_DELAY_THREAD_STARTED = 1, |
| CDSP_DELAY_THREAD_BEFORE_SLEEP = 2, |
| CDSP_DELAY_THREAD_AFTER_SLEEP = 3, |
| CDSP_DELAY_THREAD_EXITING = 4, |
| }; |
| |
| struct cdsprm_request { |
| struct list_head node; |
| struct sysmon_msg msg; |
| bool busy; |
| }; |
| |
| struct cdsprm { |
| unsigned int cdsp_version; |
| unsigned int event; |
| struct completion msg_avail; |
| struct cdsprm_request msg_queue[CDSPRM_MSG_QUEUE_DEPTH]; |
| unsigned int msg_queue_idx; |
| struct task_struct *cdsprm_wq_task; |
| struct workqueue_struct *delay_work_queue; |
| struct work_struct cdsprm_delay_work; |
| struct mutex rm_lock; |
| spinlock_t l3_lock; |
| spinlock_t list_lock; |
| struct mutex rpmsg_lock; |
| struct rpmsg_device *rpmsgdev; |
| enum delay_state dt_state; |
| unsigned long long timestamp; |
| struct pm_qos_request pm_qos_req; |
| unsigned int qos_latency_us; |
| unsigned int qos_max_ms; |
| unsigned int compute_prio_idx; |
| struct mutex npu_activity_lock; |
| bool b_cx_limit_en; |
| unsigned int b_npu_enabled; |
| unsigned int b_camera_enabled; |
| unsigned int b_npu_activity_waiting; |
| unsigned int b_npu_corner_waiting; |
| struct completion npu_activity_complete; |
| struct completion npu_corner_complete; |
| unsigned int npu_enable_cnt; |
| enum cdsprm_npu_corner npu_corner; |
| enum cdsprm_npu_corner allowed_npu_corner; |
| enum cdsprm_npu_corner npu_corner_limit; |
| struct mutex thermal_lock; |
| unsigned int thermal_cdsp_level; |
| unsigned int thermal_hvx_level; |
| struct thermal_cooling_device *cdsp_tcdev; |
| struct thermal_cooling_device *hvx_tcdev; |
| bool qos_request; |
| bool b_rpmsg_register; |
| bool b_qosinitdone; |
| bool b_applyingNpuLimit; |
| int latency_request; |
| struct dentry *debugfs_dir; |
| struct dentry *debugfs_file; |
| int (*set_l3_freq)(unsigned int freq_khz); |
| int (*set_l3_freq_cached)(unsigned int freq_khz); |
| int (*set_corner_limit)(enum cdsprm_npu_corner); |
| int (*set_corner_limit_cached)(enum cdsprm_npu_corner); |
| }; |
| |
| static struct cdsprm gcdsprm; |
| static LIST_HEAD(cdsprm_list); |
| DECLARE_WAIT_QUEUE_HEAD(cdsprm_wq); |
| |
| /** |
| * cdsprm_register_cdspl3gov() - Register a method to set L3 clock |
| * frequency |
| * @arg: cdsprm_l3 structure with set L3 clock frequency method |
| * |
| * Note: To be called from cdspl3 governor only. Called when the governor is |
| * started. |
| */ |
| void cdsprm_register_cdspl3gov(struct cdsprm_l3 *arg) |
| { |
| unsigned long flags; |
| |
| if (!arg) |
| return; |
| |
| spin_lock_irqsave(&gcdsprm.l3_lock, flags); |
| gcdsprm.set_l3_freq = arg->set_l3_freq; |
| spin_unlock_irqrestore(&gcdsprm.l3_lock, flags); |
| } |
| EXPORT_SYMBOL(cdsprm_register_cdspl3gov); |
| |
| int cdsprm_cxlimit_npu_limit_register( |
| const struct cdsprm_npu_limit_cbs *npu_limit_cb) |
| { |
| if (!npu_limit_cb) |
| return -EINVAL; |
| |
| gcdsprm.set_corner_limit = npu_limit_cb->set_corner_limit; |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(cdsprm_cxlimit_npu_limit_register); |
| |
| int cdsprm_cxlimit_npu_limit_deregister(void) |
| { |
| if (!gcdsprm.set_corner_limit) |
| return -EINVAL; |
| |
| gcdsprm.set_corner_limit = NULL; |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(cdsprm_cxlimit_npu_limit_deregister); |
| |
| int cdsprm_compute_core_set_priority(unsigned int priority_idx) |
| { |
| struct sysmon_msg_tx rpmsg_msg_tx; |
| |
| gcdsprm.compute_prio_idx = priority_idx; |
| |
| if (gcdsprm.rpmsgdev && gcdsprm.cdsp_version) { |
| rpmsg_msg_tx.feature_id = |
| SYSMON_CDSP_FEATURE_COMPUTE_PRIO_TX; |
| rpmsg_msg_tx.fs.compute_prio.priority_idx = |
| priority_idx; |
| rpmsg_msg_tx.size = sizeof(rpmsg_msg_tx); |
| rpmsg_send(gcdsprm.rpmsgdev->ept, |
| &rpmsg_msg_tx, |
| sizeof(rpmsg_msg_tx)); |
| pr_debug("Compute core priority set to %d\n", |
| priority_idx); |
| } |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(cdsprm_compute_core_set_priority); |
| |
| int cdsprm_cxlimit_npu_activity_notify(unsigned int b_enabled) |
| { |
| int result = -EINVAL; |
| struct sysmon_msg_tx rpmsg_msg_tx; |
| |
| if (!gcdsprm.b_cx_limit_en) |
| return result; |
| |
| mutex_lock(&gcdsprm.npu_activity_lock); |
| if (b_enabled) |
| gcdsprm.npu_enable_cnt++; |
| else if (gcdsprm.npu_enable_cnt) |
| gcdsprm.npu_enable_cnt--; |
| |
| if ((gcdsprm.npu_enable_cnt && |
| gcdsprm.b_npu_enabled) || |
| (!gcdsprm.npu_enable_cnt && |
| !gcdsprm.b_npu_enabled)) { |
| mutex_unlock(&gcdsprm.npu_activity_lock); |
| return 0; |
| } |
| |
| gcdsprm.b_npu_enabled = b_enabled; |
| |
| if (gcdsprm.rpmsgdev && gcdsprm.cdsp_version) { |
| if (gcdsprm.b_npu_enabled) |
| gcdsprm.b_npu_activity_waiting++; |
| rpmsg_msg_tx.feature_id = |
| SYSMON_CDSP_FEATURE_NPU_ACTIVITY_TX; |
| rpmsg_msg_tx.fs.npu_activity.b_enabled = |
| gcdsprm.b_npu_enabled; |
| rpmsg_msg_tx.size = sizeof(rpmsg_msg_tx); |
| result = rpmsg_send(gcdsprm.rpmsgdev->ept, |
| &rpmsg_msg_tx, |
| sizeof(rpmsg_msg_tx)); |
| if (gcdsprm.b_npu_enabled && result) |
| gcdsprm.b_npu_activity_waiting--; |
| } |
| |
| if (gcdsprm.b_npu_enabled && !result) { |
| mutex_unlock(&gcdsprm.npu_activity_lock); |
| wait_for_completion(&gcdsprm.npu_activity_complete); |
| mutex_lock(&gcdsprm.npu_activity_lock); |
| gcdsprm.b_npu_activity_waiting--; |
| } |
| |
| mutex_unlock(&gcdsprm.npu_activity_lock); |
| return result; |
| } |
| EXPORT_SYMBOL(cdsprm_cxlimit_npu_activity_notify); |
| |
| enum cdsprm_npu_corner cdsprm_cxlimit_npu_corner_notify( |
| enum cdsprm_npu_corner corner) |
| { |
| int result = -EINVAL; |
| enum cdsprm_npu_corner past_npu_corner; |
| enum cdsprm_npu_corner return_npu_corner = corner; |
| struct sysmon_msg_tx rpmsg_msg_tx; |
| |
| if (gcdsprm.b_applyingNpuLimit || !gcdsprm.b_cx_limit_en) |
| return corner; |
| |
| mutex_lock(&gcdsprm.npu_activity_lock); |
| past_npu_corner = gcdsprm.npu_corner; |
| gcdsprm.npu_corner = corner; |
| |
| if (gcdsprm.rpmsgdev && gcdsprm.cdsp_version) { |
| if ((gcdsprm.npu_corner > past_npu_corner) || |
| !gcdsprm.npu_corner) |
| gcdsprm.b_npu_corner_waiting++; |
| rpmsg_msg_tx.feature_id = |
| SYSMON_CDSP_FEATURE_NPU_CORNER_TX; |
| rpmsg_msg_tx.fs.npu_corner.corner = |
| (unsigned int)gcdsprm.npu_corner; |
| rpmsg_msg_tx.size = sizeof(rpmsg_msg_tx); |
| result = rpmsg_send(gcdsprm.rpmsgdev->ept, |
| &rpmsg_msg_tx, |
| sizeof(rpmsg_msg_tx)); |
| if (((gcdsprm.npu_corner > past_npu_corner) || |
| !gcdsprm.npu_corner) && result) |
| gcdsprm.b_npu_corner_waiting--; |
| } |
| |
| if (((gcdsprm.npu_corner > past_npu_corner) || |
| !gcdsprm.npu_corner) && !result) { |
| mutex_unlock(&gcdsprm.npu_activity_lock); |
| wait_for_completion(&gcdsprm.npu_corner_complete); |
| mutex_lock(&gcdsprm.npu_activity_lock); |
| if (gcdsprm.allowed_npu_corner) { |
| return_npu_corner = gcdsprm.allowed_npu_corner; |
| gcdsprm.npu_corner = gcdsprm.allowed_npu_corner; |
| } |
| gcdsprm.b_npu_corner_waiting--; |
| } |
| |
| mutex_unlock(&gcdsprm.npu_activity_lock); |
| return return_npu_corner; |
| } |
| EXPORT_SYMBOL(cdsprm_cxlimit_npu_corner_notify); |
| |
| int cdsprm_cxlimit_camera_activity_notify(unsigned int b_enabled) |
| { |
| struct sysmon_msg_tx rpmsg_msg_tx; |
| |
| if (!gcdsprm.b_cx_limit_en) |
| return -EINVAL; |
| |
| gcdsprm.b_camera_enabled = b_enabled; |
| |
| if (gcdsprm.rpmsgdev && gcdsprm.cdsp_version) { |
| rpmsg_msg_tx.feature_id = |
| SYSMON_CDSP_FEATURE_CAMERA_ACTIVITY_TX; |
| rpmsg_msg_tx.fs.camera.b_enabled = |
| b_enabled; |
| rpmsg_msg_tx.size = sizeof(rpmsg_msg_tx); |
| rpmsg_send(gcdsprm.rpmsgdev->ept, |
| &rpmsg_msg_tx, |
| sizeof(rpmsg_msg_tx)); |
| } |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(cdsprm_cxlimit_camera_activity_notify); |
| |
| static int cdsprm_thermal_cdsp_clk_limit(unsigned int level) |
| { |
| int result = -EINVAL; |
| struct sysmon_msg_tx rpmsg_msg_tx; |
| |
| mutex_lock(&gcdsprm.thermal_lock); |
| |
| if (gcdsprm.rpmsgdev && gcdsprm.cdsp_version) { |
| rpmsg_msg_tx.feature_id = |
| SYSMON_CDSP_FEATURE_THERMAL_LIMIT_TX; |
| rpmsg_msg_tx.fs.thermal.hvx_level = |
| gcdsprm.thermal_hvx_level; |
| rpmsg_msg_tx.fs.thermal.cdsp_level = level; |
| rpmsg_msg_tx.size = sizeof(rpmsg_msg_tx); |
| result = rpmsg_send(gcdsprm.rpmsgdev->ept, |
| &rpmsg_msg_tx, |
| sizeof(rpmsg_msg_tx)); |
| } |
| |
| if (result == 0) |
| gcdsprm.thermal_cdsp_level = level; |
| |
| mutex_unlock(&gcdsprm.thermal_lock); |
| |
| return result; |
| } |
| |
| static int cdsprm_thermal_hvx_instruction_limit(unsigned int level) |
| { |
| int result = -EINVAL; |
| struct sysmon_msg_tx rpmsg_msg_tx; |
| |
| mutex_lock(&gcdsprm.thermal_lock); |
| |
| if (gcdsprm.rpmsgdev && gcdsprm.cdsp_version) { |
| rpmsg_msg_tx.feature_id = |
| SYSMON_CDSP_FEATURE_THERMAL_LIMIT_TX; |
| rpmsg_msg_tx.fs.thermal.hvx_level = level; |
| rpmsg_msg_tx.fs.thermal.cdsp_level = |
| gcdsprm.thermal_cdsp_level; |
| rpmsg_msg_tx.size = sizeof(rpmsg_msg_tx); |
| result = rpmsg_send(gcdsprm.rpmsgdev->ept, |
| &rpmsg_msg_tx, |
| sizeof(rpmsg_msg_tx)); |
| } |
| |
| if (result == 0) |
| gcdsprm.thermal_hvx_level = level; |
| |
| mutex_unlock(&gcdsprm.thermal_lock); |
| |
| return result; |
| } |
| |
| /** |
| * cdsprm_unregister_cdspl3gov() - Unregister the method to set L3 clock |
| * frequency |
| * |
| * Note: To be called from cdspl3 governor only. Called when the governor is |
| * stopped |
| */ |
| void cdsprm_unregister_cdspl3gov(void) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&gcdsprm.l3_lock, flags); |
| gcdsprm.set_l3_freq = NULL; |
| spin_unlock_irqrestore(&gcdsprm.l3_lock, flags); |
| } |
| EXPORT_SYMBOL(cdsprm_unregister_cdspl3gov); |
| |
| static void set_qos_latency(int latency) |
| { |
| if (!gcdsprm.qos_request) { |
| pm_qos_add_request(&gcdsprm.pm_qos_req, |
| PM_QOS_CPU_DMA_LATENCY, latency); |
| gcdsprm.qos_request = true; |
| } else { |
| pm_qos_update_request(&gcdsprm.pm_qos_req, |
| latency); |
| } |
| } |
| |
| static void process_rm_request(struct sysmon_msg *msg) |
| { |
| struct sysmon_rm_msg *rm_msg; |
| |
| if (!msg) |
| return; |
| |
| if (msg->feature_id == SYSMON_CDSP_FEATURE_RM_RX) { |
| mutex_lock(&gcdsprm.rm_lock); |
| rm_msg = &msg->fs.rm_struct; |
| if (rm_msg->b_qos_flag == |
| SYSMON_CDSP_QOS_FLAG_ENABLE) { |
| if (gcdsprm.latency_request != |
| gcdsprm.qos_latency_us) { |
| set_qos_latency(gcdsprm.qos_latency_us); |
| gcdsprm.latency_request = |
| gcdsprm.qos_latency_us; |
| pr_debug("Set qos latency to %d\n", |
| gcdsprm.latency_request); |
| } |
| gcdsprm.timestamp = ((rm_msg->timetick_low) | |
| ((unsigned long long)rm_msg->timetick_high << 32)); |
| if (gcdsprm.dt_state >= CDSP_DELAY_THREAD_AFTER_SLEEP) { |
| flush_workqueue(gcdsprm.delay_work_queue); |
| if (gcdsprm.dt_state == |
| CDSP_DELAY_THREAD_EXITING) { |
| gcdsprm.dt_state = |
| CDSP_DELAY_THREAD_STARTED; |
| queue_work(gcdsprm.delay_work_queue, |
| &gcdsprm.cdsprm_delay_work); |
| } |
| } else if (gcdsprm.dt_state == |
| CDSP_DELAY_THREAD_NOT_STARTED) { |
| gcdsprm.dt_state = CDSP_DELAY_THREAD_STARTED; |
| queue_work(gcdsprm.delay_work_queue, |
| &gcdsprm.cdsprm_delay_work); |
| } |
| } else if ((rm_msg->b_qos_flag == |
| SYSMON_CDSP_QOS_FLAG_DISABLE) && |
| (gcdsprm.latency_request != |
| QOS_LATENCY_DISABLE_VALUE)) { |
| set_qos_latency(QOS_LATENCY_DISABLE_VALUE); |
| gcdsprm.latency_request = QOS_LATENCY_DISABLE_VALUE; |
| pr_debug("Set qos latency to %d\n", |
| gcdsprm.latency_request); |
| } |
| mutex_unlock(&gcdsprm.rm_lock); |
| } else { |
| pr_err("Received incorrect msg on rm queue: %d\n", |
| msg->feature_id); |
| } |
| } |
| |
| static void process_delayed_rm_request(struct work_struct *work) |
| { |
| unsigned long long timestamp, curr_timestamp; |
| unsigned int time_ms = 0; |
| |
| mutex_lock(&gcdsprm.rm_lock); |
| |
| timestamp = gcdsprm.timestamp; |
| curr_timestamp = arch_counter_get_cntvct(); |
| |
| while ((gcdsprm.latency_request == |
| gcdsprm.qos_latency_us) && |
| (curr_timestamp < timestamp)) { |
| if ((timestamp - curr_timestamp) < |
| (gcdsprm.qos_max_ms * SYS_CLK_TICKS_PER_MS)) |
| time_ms = (timestamp - curr_timestamp) / |
| SYS_CLK_TICKS_PER_MS; |
| else |
| break; |
| gcdsprm.dt_state = CDSP_DELAY_THREAD_BEFORE_SLEEP; |
| |
| mutex_unlock(&gcdsprm.rm_lock); |
| usleep_range(time_ms * 1000, (time_ms + 2) * 1000); |
| mutex_lock(&gcdsprm.rm_lock); |
| |
| gcdsprm.dt_state = CDSP_DELAY_THREAD_AFTER_SLEEP; |
| timestamp = gcdsprm.timestamp; |
| curr_timestamp = arch_counter_get_cntvct(); |
| } |
| |
| set_qos_latency(QOS_LATENCY_DISABLE_VALUE); |
| gcdsprm.latency_request = QOS_LATENCY_DISABLE_VALUE; |
| pr_debug("Set qos latency to %d\n", gcdsprm.latency_request); |
| gcdsprm.dt_state = CDSP_DELAY_THREAD_EXITING; |
| |
| mutex_unlock(&gcdsprm.rm_lock); |
| } |
| |
| static void cdsprm_rpmsg_send_details(void) |
| { |
| struct sysmon_msg_tx rpmsg_msg_tx; |
| |
| if (!gcdsprm.cdsp_version) |
| return; |
| |
| if (gcdsprm.b_cx_limit_en) { |
| reinit_completion(&gcdsprm.npu_activity_complete); |
| reinit_completion(&gcdsprm.npu_corner_complete); |
| |
| if (gcdsprm.npu_corner) { |
| rpmsg_msg_tx.feature_id = |
| SYSMON_CDSP_FEATURE_NPU_CORNER_TX; |
| rpmsg_msg_tx.fs.npu_corner.corner = |
| (unsigned int)gcdsprm.npu_corner; |
| rpmsg_msg_tx.size = sizeof(rpmsg_msg_tx); |
| rpmsg_send(gcdsprm.rpmsgdev->ept, |
| &rpmsg_msg_tx, |
| sizeof(rpmsg_msg_tx)); |
| } |
| |
| if (gcdsprm.b_npu_enabled) { |
| rpmsg_msg_tx.feature_id = |
| SYSMON_CDSP_FEATURE_NPU_ACTIVITY_TX; |
| rpmsg_msg_tx.fs.npu_activity.b_enabled = |
| gcdsprm.b_npu_enabled; |
| rpmsg_msg_tx.size = sizeof(rpmsg_msg_tx); |
| rpmsg_send(gcdsprm.rpmsgdev->ept, |
| &rpmsg_msg_tx, |
| sizeof(rpmsg_msg_tx)); |
| } |
| |
| cdsprm_compute_core_set_priority(gcdsprm.compute_prio_idx); |
| |
| if (gcdsprm.b_camera_enabled) { |
| rpmsg_msg_tx.feature_id = |
| SYSMON_CDSP_FEATURE_CAMERA_ACTIVITY_TX; |
| rpmsg_msg_tx.fs.camera.b_enabled = |
| gcdsprm.b_camera_enabled; |
| rpmsg_msg_tx.size = sizeof(rpmsg_msg_tx); |
| rpmsg_send(gcdsprm.rpmsgdev->ept, |
| &rpmsg_msg_tx, |
| sizeof(rpmsg_msg_tx)); |
| } |
| } |
| |
| if (gcdsprm.thermal_cdsp_level) { |
| cdsprm_thermal_cdsp_clk_limit( |
| gcdsprm.thermal_cdsp_level); |
| } else if (gcdsprm.thermal_hvx_level) { |
| cdsprm_thermal_hvx_instruction_limit( |
| gcdsprm.thermal_hvx_level); |
| } |
| } |
| |
| static struct cdsprm_request *get_next_request(void) |
| { |
| struct cdsprm_request *req = NULL; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&gcdsprm.list_lock, flags); |
| req = list_first_entry_or_null(&cdsprm_list, |
| struct cdsprm_request, node); |
| spin_unlock_irqrestore(&gcdsprm.list_lock, |
| flags); |
| |
| return req; |
| } |
| |
| static int process_cdsp_request_thread(void *data) |
| { |
| struct cdsprm_request *req = NULL; |
| struct sysmon_msg *msg = NULL; |
| unsigned int l3_clock_khz; |
| unsigned long flags; |
| int result = 0; |
| struct sysmon_msg_tx rpmsg_msg_tx; |
| |
| while (!kthread_should_stop()) { |
| result = wait_event_interruptible(cdsprm_wq, |
| (req = get_next_request())); |
| |
| if (result) |
| continue; |
| |
| msg = &req->msg; |
| |
| if ((msg->feature_id == SYSMON_CDSP_FEATURE_RM_RX) && |
| gcdsprm.b_qosinitdone) { |
| process_rm_request(msg); |
| } else if (msg->feature_id == |
| SYSMON_CDSP_FEATURE_L3_RX) { |
| l3_clock_khz = msg->fs.l3_struct.l3_clock_khz; |
| |
| spin_lock_irqsave(&gcdsprm.l3_lock, flags); |
| gcdsprm.set_l3_freq_cached = gcdsprm.set_l3_freq; |
| spin_unlock_irqrestore(&gcdsprm.l3_lock, flags); |
| |
| if (gcdsprm.set_l3_freq_cached) { |
| gcdsprm.set_l3_freq_cached(l3_clock_khz); |
| pr_debug("Set L3 clock %d done\n", |
| l3_clock_khz); |
| } |
| } else if (msg->feature_id == |
| SYSMON_CDSP_FEATURE_NPU_LIMIT_RX) { |
| mutex_lock(&gcdsprm.npu_activity_lock); |
| |
| gcdsprm.set_corner_limit_cached = |
| gcdsprm.set_corner_limit; |
| |
| if (gcdsprm.set_corner_limit_cached) { |
| gcdsprm.npu_corner_limit = |
| msg->fs.npu_limit.corner; |
| gcdsprm.b_applyingNpuLimit = true; |
| result = gcdsprm.set_corner_limit_cached( |
| gcdsprm.npu_corner_limit); |
| gcdsprm.b_applyingNpuLimit = false; |
| pr_debug("Set NPU limit to %d\n", |
| msg->fs.npu_limit.corner); |
| } else { |
| result = -ENOMSG; |
| pr_debug("NPU limit not registered\n"); |
| } |
| |
| mutex_unlock(&gcdsprm.npu_activity_lock); |
| /* |
| * Send Limit ack back to DSP |
| */ |
| rpmsg_msg_tx.feature_id = |
| SYSMON_CDSP_FEATURE_NPU_LIMIT_TX; |
| |
| if (result == 0) { |
| rpmsg_msg_tx.fs.npu_limit_ack.corner = |
| msg->fs.npu_limit.corner; |
| } else { |
| rpmsg_msg_tx.fs.npu_limit_ack.corner = |
| CDSPRM_NPU_CLK_OFF; |
| } |
| |
| rpmsg_msg_tx.size = sizeof(rpmsg_msg_tx); |
| result = rpmsg_send(gcdsprm.rpmsgdev->ept, |
| &rpmsg_msg_tx, |
| sizeof(rpmsg_msg_tx)); |
| |
| if (result) |
| pr_err("rpmsg send failed %d\n", result); |
| else |
| pr_debug("NPU limit ack sent\n"); |
| } else if (msg->feature_id == |
| SYSMON_CDSP_FEATURE_VERSION_RX) { |
| cdsprm_rpmsg_send_details(); |
| pr_debug("Sent preserved data to DSP\n"); |
| } |
| |
| spin_lock_irqsave(&gcdsprm.list_lock, flags); |
| list_del(&req->node); |
| req->busy = false; |
| spin_unlock_irqrestore(&gcdsprm.list_lock, flags); |
| } |
| |
| do_exit(0); |
| } |
| |
| static int cdsprm_rpmsg_probe(struct rpmsg_device *dev) |
| { |
| /* Populate child nodes as platform devices */ |
| of_platform_populate(dev->dev.of_node, NULL, NULL, &dev->dev); |
| gcdsprm.rpmsgdev = dev; |
| dev_dbg(&dev->dev, "rpmsg probe called for cdsp\n"); |
| |
| return 0; |
| } |
| |
| static void cdsprm_rpmsg_remove(struct rpmsg_device *dev) |
| { |
| gcdsprm.rpmsgdev = NULL; |
| gcdsprm.cdsp_version = 0; |
| |
| if (gcdsprm.b_cx_limit_en) { |
| mutex_lock(&gcdsprm.npu_activity_lock); |
| complete_all(&gcdsprm.npu_activity_complete); |
| complete_all(&gcdsprm.npu_corner_complete); |
| mutex_unlock(&gcdsprm.npu_activity_lock); |
| |
| gcdsprm.set_corner_limit_cached = gcdsprm.set_corner_limit; |
| |
| if ((gcdsprm.npu_corner_limit < CDSPRM_NPU_TURBO_L1) && |
| gcdsprm.set_corner_limit_cached) |
| gcdsprm.set_corner_limit_cached(CDSPRM_NPU_TURBO_L1); |
| } |
| } |
| |
| static int cdsprm_rpmsg_callback(struct rpmsg_device *dev, void *data, |
| int len, void *priv, u32 addr) |
| { |
| struct sysmon_msg *msg = (struct sysmon_msg *)data; |
| bool b_valid = false; |
| struct cdsprm_request *req; |
| unsigned long flags; |
| |
| if (!data || (len < sizeof(*msg))) { |
| dev_err(&dev->dev, |
| "Invalid message in rpmsg callback, length: %d, expected: %lu\n", |
| len, sizeof(*msg)); |
| return -EINVAL; |
| } |
| |
| if ((msg->feature_id == SYSMON_CDSP_FEATURE_RM_RX) && |
| gcdsprm.b_qosinitdone) { |
| dev_dbg(&dev->dev, "Processing RM request\n"); |
| b_valid = true; |
| } else if (msg->feature_id == SYSMON_CDSP_FEATURE_L3_RX) { |
| dev_dbg(&dev->dev, "Processing L3 request\n"); |
| spin_lock_irqsave(&gcdsprm.l3_lock, flags); |
| gcdsprm.set_l3_freq_cached = gcdsprm.set_l3_freq; |
| spin_unlock_irqrestore(&gcdsprm.l3_lock, flags); |
| if (gcdsprm.set_l3_freq_cached) |
| b_valid = true; |
| } else if ((msg->feature_id == SYSMON_CDSP_FEATURE_NPU_CORNER_RX) && |
| (gcdsprm.b_cx_limit_en)) { |
| gcdsprm.allowed_npu_corner = msg->fs.npu_corner.corner; |
| dev_dbg(&dev->dev, |
| "Processing NPU corner request ack for %d\n", |
| gcdsprm.allowed_npu_corner); |
| if (gcdsprm.b_npu_corner_waiting) |
| complete(&gcdsprm.npu_corner_complete); |
| } else if ((msg->feature_id == SYSMON_CDSP_FEATURE_NPU_LIMIT_RX) && |
| (gcdsprm.b_cx_limit_en)) { |
| dev_dbg(&dev->dev, "Processing NPU limit request for %d\n", |
| msg->fs.npu_limit.corner); |
| b_valid = true; |
| } else if ((msg->feature_id == SYSMON_CDSP_FEATURE_NPU_ACTIVITY_RX) && |
| (gcdsprm.b_cx_limit_en)) { |
| dev_dbg(&dev->dev, "Processing NPU activity request ack\n"); |
| if (gcdsprm.b_npu_activity_waiting) |
| complete(&gcdsprm.npu_activity_complete); |
| } else if (msg->feature_id == SYSMON_CDSP_FEATURE_VERSION_RX) { |
| gcdsprm.cdsp_version = msg->fs.version.id; |
| b_valid = true; |
| dev_dbg(&dev->dev, "Received CDSP version 0x%x\n", |
| gcdsprm.cdsp_version); |
| } else { |
| dev_err(&dev->dev, "Received incorrect msg feature %d\n", |
| msg->feature_id); |
| } |
| |
| if (b_valid) { |
| spin_lock_irqsave(&gcdsprm.list_lock, flags); |
| |
| if (!gcdsprm.msg_queue[gcdsprm.msg_queue_idx].busy) { |
| req = &gcdsprm.msg_queue[gcdsprm.msg_queue_idx]; |
| req->busy = true; |
| req->msg = *msg; |
| if (gcdsprm.msg_queue_idx < |
| (CDSPRM_MSG_QUEUE_DEPTH - 1)) |
| gcdsprm.msg_queue_idx++; |
| else |
| gcdsprm.msg_queue_idx = 0; |
| } else { |
| spin_unlock_irqrestore(&gcdsprm.list_lock, flags); |
| dev_dbg(&dev->dev, |
| "Unable to queue cdsp request, no memory\n"); |
| return -ENOMEM; |
| } |
| |
| list_add_tail(&req->node, &cdsprm_list); |
| spin_unlock_irqrestore(&gcdsprm.list_lock, flags); |
| wake_up_interruptible(&cdsprm_wq); |
| } |
| |
| return 0; |
| } |
| |
| static int cdsp_get_max_state(struct thermal_cooling_device *cdev, |
| unsigned long *state) |
| { |
| *state = CDSP_THERMAL_MAX_STATE; |
| |
| return 0; |
| } |
| |
| static int cdsp_get_cur_state(struct thermal_cooling_device *cdev, |
| unsigned long *state) |
| { |
| *state = gcdsprm.thermal_cdsp_level; |
| |
| return 0; |
| } |
| |
| static int cdsp_set_cur_state(struct thermal_cooling_device *cdev, |
| unsigned long state) |
| { |
| if (gcdsprm.thermal_cdsp_level == state) |
| return 0; |
| |
| cdsprm_thermal_cdsp_clk_limit(state); |
| |
| return 0; |
| } |
| |
| static const struct thermal_cooling_device_ops cdsp_cooling_ops = { |
| .get_max_state = cdsp_get_max_state, |
| .get_cur_state = cdsp_get_cur_state, |
| .set_cur_state = cdsp_set_cur_state, |
| }; |
| |
| static int hvx_get_max_state(struct thermal_cooling_device *cdev, |
| unsigned long *state) |
| { |
| *state = HVX_THERMAL_MAX_STATE; |
| |
| return 0; |
| } |
| |
| static int hvx_get_cur_state(struct thermal_cooling_device *cdev, |
| unsigned long *state) |
| { |
| *state = gcdsprm.thermal_hvx_level; |
| |
| return 0; |
| } |
| |
| static int hvx_set_cur_state(struct thermal_cooling_device *cdev, |
| unsigned long state) |
| { |
| if (gcdsprm.thermal_hvx_level == state) |
| return 0; |
| |
| cdsprm_thermal_hvx_instruction_limit(state); |
| |
| return 0; |
| } |
| |
| static int cdsprm_compute_prio_read(void *data, u64 *val) |
| { |
| *val = gcdsprm.compute_prio_idx; |
| |
| return 0; |
| } |
| |
| static int cdsprm_compute_prio_write(void *data, u64 val) |
| { |
| cdsprm_compute_core_set_priority((unsigned int)val); |
| |
| return 0; |
| } |
| |
| DEFINE_SIMPLE_ATTRIBUTE(cdsprm_debugfs_fops, |
| cdsprm_compute_prio_read, |
| cdsprm_compute_prio_write, |
| "%llu\n"); |
| |
| static const struct thermal_cooling_device_ops hvx_cooling_ops = { |
| .get_max_state = hvx_get_max_state, |
| .get_cur_state = hvx_get_cur_state, |
| .set_cur_state = hvx_set_cur_state, |
| }; |
| |
| static int cdsp_rm_driver_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct thermal_cooling_device *tcdev = 0; |
| unsigned int cooling_cells = 0; |
| |
| if (of_property_read_u32(dev->of_node, |
| "qcom,qos-latency-us", &gcdsprm.qos_latency_us)) { |
| return -EINVAL; |
| } |
| |
| if (of_property_read_u32(dev->of_node, |
| "qcom,qos-maxhold-ms", &gcdsprm.qos_max_ms)) { |
| return -EINVAL; |
| } |
| |
| gcdsprm.compute_prio_idx = CDSPRM_COMPUTE_AIX_OVER_HVX; |
| of_property_read_u32(dev->of_node, |
| "qcom,compute-priority-mode", |
| &gcdsprm.compute_prio_idx); |
| |
| gcdsprm.b_cx_limit_en = of_property_read_bool(dev->of_node, |
| "qcom,compute-cx-limit-en"); |
| |
| if (gcdsprm.b_cx_limit_en) { |
| gcdsprm.debugfs_dir = debugfs_create_dir("compute", NULL); |
| |
| if (!gcdsprm.debugfs_dir) { |
| dev_err(dev, |
| "Failed to create debugfs directory for cdsprm\n"); |
| } else { |
| gcdsprm.debugfs_file = debugfs_create_file("priority", |
| 0644, gcdsprm.debugfs_dir, |
| NULL, &cdsprm_debugfs_fops); |
| if (!gcdsprm.debugfs_file) { |
| debugfs_remove_recursive(gcdsprm.debugfs_dir); |
| dev_err(dev, |
| "Failed to create debugfs file\n"); |
| } |
| } |
| } |
| |
| of_property_read_u32(dev->of_node, |
| "#cooling-cells", |
| &cooling_cells); |
| |
| if (cooling_cells && IS_ENABLED(CONFIG_THERMAL)) { |
| tcdev = thermal_of_cooling_device_register(dev->of_node, |
| "cdsp", NULL, |
| &cdsp_cooling_ops); |
| if (IS_ERR(tcdev)) { |
| dev_err(dev, |
| "CDSP thermal driver reg failed\n"); |
| } |
| gcdsprm.cdsp_tcdev = tcdev; |
| thermal_cdev_update(tcdev); |
| } |
| |
| dev_info(dev, "CDSP request manager driver probe called\n"); |
| gcdsprm.b_qosinitdone = true; |
| |
| return 0; |
| } |
| |
| static int hvx_rm_driver_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct thermal_cooling_device *tcdev = 0; |
| unsigned int cooling_cells = 0; |
| |
| of_property_read_u32(dev->of_node, |
| "#cooling-cells", |
| &cooling_cells); |
| |
| if (cooling_cells && IS_ENABLED(CONFIG_THERMAL)) { |
| tcdev = thermal_of_cooling_device_register(dev->of_node, |
| "hvx", NULL, |
| &hvx_cooling_ops); |
| if (IS_ERR(tcdev)) { |
| dev_err(dev, |
| "HVX thermal driver reg failed\n"); |
| } |
| gcdsprm.hvx_tcdev = tcdev; |
| thermal_cdev_update(tcdev); |
| } |
| |
| dev_dbg(dev, "HVX request manager driver probe called\n"); |
| |
| return 0; |
| } |
| |
| static const struct rpmsg_device_id cdsprm_rpmsg_match[] = { |
| { "cdsprmglink-apps-dsp" }, |
| { }, |
| }; |
| |
| static const struct of_device_id cdsprm_rpmsg_of_match[] = { |
| { .compatible = "qcom,msm-cdsprm-rpmsg" }, |
| { }, |
| }; |
| MODULE_DEVICE_TABLE(of, cdsprm_rpmsg_of_match); |
| |
| static struct rpmsg_driver cdsprm_rpmsg_client = { |
| .id_table = cdsprm_rpmsg_match, |
| .probe = cdsprm_rpmsg_probe, |
| .remove = cdsprm_rpmsg_remove, |
| .callback = cdsprm_rpmsg_callback, |
| .drv = { |
| .name = "qcom,msm_cdsprm_rpmsg", |
| .of_match_table = cdsprm_rpmsg_of_match, |
| }, |
| }; |
| |
| static const struct of_device_id cdsp_rm_match_table[] = { |
| { .compatible = "qcom,msm-cdsp-rm" }, |
| { }, |
| }; |
| |
| static struct platform_driver cdsp_rm = { |
| .probe = cdsp_rm_driver_probe, |
| .driver = { |
| .name = "msm_cdsp_rm", |
| .of_match_table = cdsp_rm_match_table, |
| }, |
| }; |
| |
| static const struct of_device_id hvx_rm_match_table[] = { |
| { .compatible = "qcom,msm-hvx-rm" }, |
| { }, |
| }; |
| |
| static struct platform_driver hvx_rm = { |
| .probe = hvx_rm_driver_probe, |
| .driver = { |
| .name = "msm_hvx_rm", |
| .of_match_table = hvx_rm_match_table, |
| }, |
| }; |
| |
| static int __init cdsprm_init(void) |
| { |
| int err; |
| |
| mutex_init(&gcdsprm.rm_lock); |
| mutex_init(&gcdsprm.rpmsg_lock); |
| mutex_init(&gcdsprm.npu_activity_lock); |
| mutex_init(&gcdsprm.thermal_lock); |
| spin_lock_init(&gcdsprm.l3_lock); |
| spin_lock_init(&gcdsprm.list_lock); |
| init_completion(&gcdsprm.msg_avail); |
| init_completion(&gcdsprm.npu_activity_complete); |
| init_completion(&gcdsprm.npu_corner_complete); |
| |
| gcdsprm.cdsprm_wq_task = kthread_run(process_cdsp_request_thread, |
| NULL, "cdsprm-wq"); |
| |
| if (!gcdsprm.cdsprm_wq_task) { |
| pr_err("Failed to create kernel thread\n"); |
| return -ENOMEM; |
| } |
| |
| gcdsprm.delay_work_queue = |
| create_singlethread_workqueue("cdsprm-wq-delay"); |
| |
| if (!gcdsprm.delay_work_queue) { |
| err = -ENOMEM; |
| pr_err("Failed to create rm delay work queue\n"); |
| goto err_wq; |
| } |
| |
| INIT_WORK(&gcdsprm.cdsprm_delay_work, process_delayed_rm_request); |
| err = platform_driver_register(&cdsp_rm); |
| |
| if (err) { |
| pr_err("Failed to register cdsprm platform driver: %d\n", |
| err); |
| goto bail; |
| } |
| |
| err = platform_driver_register(&hvx_rm); |
| |
| if (err) { |
| pr_err("Failed to register hvxrm platform driver: %d\n", |
| err); |
| goto bail; |
| } |
| |
| err = register_rpmsg_driver(&cdsprm_rpmsg_client); |
| |
| if (err) { |
| pr_err("Failed registering rpmsg driver with return %d\n", |
| err); |
| goto bail; |
| } |
| |
| gcdsprm.b_rpmsg_register = true; |
| |
| pr_debug("Init successful\n"); |
| |
| return 0; |
| bail: |
| destroy_workqueue(gcdsprm.delay_work_queue); |
| err_wq: |
| kthread_stop(gcdsprm.cdsprm_wq_task); |
| |
| return err; |
| } |
| |
| static void __exit cdsprm_exit(void) |
| { |
| if (gcdsprm.b_rpmsg_register) |
| unregister_rpmsg_driver(&cdsprm_rpmsg_client); |
| |
| gcdsprm.b_rpmsg_register = false; |
| platform_driver_unregister(&cdsp_rm); |
| platform_driver_unregister(&hvx_rm); |
| complete(&gcdsprm.msg_avail); |
| |
| if (gcdsprm.cdsprm_wq_task) |
| kthread_stop(gcdsprm.cdsprm_wq_task); |
| |
| destroy_workqueue(gcdsprm.delay_work_queue); |
| debugfs_remove_recursive(gcdsprm.debugfs_dir); |
| } |
| |
| module_init(cdsprm_init); |
| module_exit(cdsprm_exit); |
| |
| MODULE_LICENSE("GPL v2"); |