| /* |
| * Copyright (c) 2013-2018 The Linux Foundation. All rights reserved. |
| * |
| * Previously licensed under the ISC license by Qualcomm Atheros, Inc. |
| * |
| * |
| * Permission to use, copy, modify, and/or distribute this software for |
| * any purpose with or without fee is hereby granted, provided that the |
| * above copyright notice and this permission notice appear in all |
| * copies. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL |
| * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE |
| * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL |
| * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR |
| * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER |
| * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR |
| * PERFORMANCE OF THIS SOFTWARE. |
| */ |
| |
| /* |
| * This file was originally distributed by Qualcomm Atheros, Inc. |
| * under proprietary terms before Copyright ownership was assigned |
| * to the Linux Foundation. |
| */ |
| |
| /* Include Files */ |
| #include "wlan_ipa_core.h" |
| #include "wlan_ipa_main.h" |
| #include "cdp_txrx_ipa.h" |
| #include "host_diag_core_event.h" |
| |
| |
| #ifdef FEATURE_METERING |
| void wlan_ipa_init_metering(struct wlan_ipa_priv *ipa_ctx) |
| { |
| qdf_event_create(&ipa_ctx->ipa_uc_sharing_stats_comp); |
| qdf_event_create(&ipa_ctx->ipa_uc_set_quota_comp); |
| } |
| #endif |
| |
| #ifndef CONFIG_IPA_WDI_UNIFIED_API |
| QDF_STATUS wlan_ipa_set_perf_level(struct wlan_ipa_priv *ipa_ctx, |
| uint64_t tx_packets, |
| uint64_t rx_packets) |
| { |
| uint32_t next_cons_bw, next_prod_bw; |
| qdf_ipa_rm_perf_profile_t profile; |
| int ret; |
| |
| if ((!wlan_ipa_is_enabled(ipa_ctx->config)) || |
| (!wlan_ipa_is_clk_scaling_enabled(ipa_ctx->config))) |
| return 0; |
| |
| qdf_mem_set(&profile, 0, sizeof(profile)); |
| |
| if (tx_packets > (ipa_ctx->config->bus_bw_high / 2)) |
| next_cons_bw = ipa_ctx->config->ipa_bw_high; |
| else if (tx_packets > (ipa_ctx->config->bus_bw_medium / 2)) |
| next_cons_bw = ipa_ctx->config->ipa_bw_medium; |
| else |
| next_cons_bw = ipa_ctx->config->ipa_bw_low; |
| |
| if (rx_packets > (ipa_ctx->config->bus_bw_high / 2)) |
| next_prod_bw = ipa_ctx->config->ipa_bw_high; |
| else if (rx_packets > (ipa_ctx->config->bus_bw_medium / 2)) |
| next_prod_bw = ipa_ctx->config->ipa_bw_medium; |
| else |
| next_prod_bw = ipa_ctx->config->ipa_bw_low; |
| |
| ipa_debug("CONS perf curr: %d, next: %d", ipa_ctx->curr_cons_bw, |
| next_cons_bw); |
| ipa_debug("PROD perf curr: %d, next: %d", ipa_ctx->curr_prod_bw, |
| next_prod_bw); |
| |
| if (ipa_ctx->curr_cons_bw != next_cons_bw) { |
| ipa_debug("Requesting CONS perf curr: %d, next: %d", |
| ipa_ctx->curr_cons_bw, next_cons_bw); |
| ret = cdp_ipa_set_perf_level(ipa_ctx->dp_soc, |
| QDF_IPA_RM_RESOURCE_WLAN_CONS, |
| next_cons_bw); |
| if (ret) { |
| ipa_err("RM CONS set perf profile failed: %d", ret); |
| |
| return QDF_STATUS_E_FAILURE; |
| } |
| ipa_ctx->curr_cons_bw = next_cons_bw; |
| ipa_ctx->stats.num_cons_perf_req++; |
| } |
| |
| if (ipa_ctx->curr_prod_bw != next_prod_bw) { |
| ipa_debug("Requesting PROD perf curr: %d, next: %d", |
| ipa_ctx->curr_prod_bw, next_prod_bw); |
| ret = cdp_ipa_set_perf_level(ipa_ctx->dp_soc, |
| QDF_IPA_RM_RESOURCE_WLAN_PROD, |
| next_prod_bw); |
| if (ret) { |
| ipa_err("RM PROD set perf profile failed: %d", ret); |
| return QDF_STATUS_E_FAILURE; |
| } |
| ipa_ctx->curr_prod_bw = next_prod_bw; |
| ipa_ctx->stats.num_prod_perf_req++; |
| } |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| /** |
| * wlan_ipa_rm_cons_release() - WLAN consumer resource release handler |
| * |
| * Callback function registered with IPA that is called when IPA wants |
| * to release the WLAN consumer resource |
| * |
| * Return: 0 if the request is granted, negative errno otherwise |
| */ |
| static int wlan_ipa_rm_cons_release(void) |
| { |
| return 0; |
| } |
| |
| /** |
| * wlan_ipa_wdi_rm_request() - Request resource from IPA |
| * @ipa_ctx: IPA context |
| * |
| * Return: QDF_STATUS |
| */ |
| QDF_STATUS wlan_ipa_wdi_rm_request(struct wlan_ipa_priv *ipa_ctx) |
| { |
| int ret; |
| |
| if (!wlan_ipa_is_rm_enabled(ipa_ctx->config)) |
| return QDF_STATUS_SUCCESS; |
| |
| qdf_spin_lock_bh(&ipa_ctx->rm_lock); |
| |
| switch (ipa_ctx->rm_state) { |
| case WLAN_IPA_RM_GRANTED: |
| qdf_spin_unlock_bh(&ipa_ctx->rm_lock); |
| return QDF_STATUS_SUCCESS; |
| case WLAN_IPA_RM_GRANT_PENDING: |
| qdf_spin_unlock_bh(&ipa_ctx->rm_lock); |
| return QDF_STATUS_E_PENDING; |
| case WLAN_IPA_RM_RELEASED: |
| ipa_ctx->rm_state = WLAN_IPA_RM_GRANT_PENDING; |
| break; |
| } |
| |
| qdf_spin_unlock_bh(&ipa_ctx->rm_lock); |
| |
| ret = qdf_ipa_rm_inactivity_timer_request_resource( |
| QDF_IPA_RM_RESOURCE_WLAN_PROD); |
| |
| qdf_spin_lock_bh(&ipa_ctx->rm_lock); |
| if (ret == 0) { |
| ipa_ctx->rm_state = WLAN_IPA_RM_GRANTED; |
| ipa_ctx->stats.num_rm_grant_imm++; |
| } |
| |
| if (ipa_ctx->wake_lock_released) { |
| qdf_wake_lock_acquire(&ipa_ctx->wake_lock, |
| WIFI_POWER_EVENT_WAKELOCK_IPA); |
| ipa_ctx->wake_lock_released = false; |
| } |
| qdf_spin_unlock_bh(&ipa_ctx->rm_lock); |
| |
| qdf_cancel_delayed_work(&ipa_ctx->wake_lock_work); |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| QDF_STATUS wlan_ipa_wdi_rm_try_release(struct wlan_ipa_priv *ipa_ctx) |
| { |
| int ret; |
| |
| if (!wlan_ipa_is_rm_enabled(ipa_ctx->config)) |
| return QDF_STATUS_SUCCESS; |
| |
| if (qdf_atomic_read(&ipa_ctx->tx_ref_cnt)) |
| return QDF_STATUS_E_AGAIN; |
| |
| qdf_spin_lock_bh(&ipa_ctx->pm_lock); |
| |
| if (!qdf_nbuf_is_queue_empty(&ipa_ctx->pm_queue_head)) { |
| qdf_spin_unlock_bh(&ipa_ctx->pm_lock); |
| return QDF_STATUS_E_AGAIN; |
| } |
| qdf_spin_unlock_bh(&ipa_ctx->pm_lock); |
| |
| qdf_spin_lock_bh(&ipa_ctx->rm_lock); |
| switch (ipa_ctx->rm_state) { |
| case WLAN_IPA_RM_GRANTED: |
| break; |
| case WLAN_IPA_RM_GRANT_PENDING: |
| qdf_spin_unlock_bh(&ipa_ctx->rm_lock); |
| return QDF_STATUS_E_PENDING; |
| case WLAN_IPA_RM_RELEASED: |
| qdf_spin_unlock_bh(&ipa_ctx->rm_lock); |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| /* IPA driver returns immediately so set the state here to avoid any |
| * race condition. |
| */ |
| ipa_ctx->rm_state = WLAN_IPA_RM_RELEASED; |
| ipa_ctx->stats.num_rm_release++; |
| qdf_spin_unlock_bh(&ipa_ctx->rm_lock); |
| |
| ret = qdf_ipa_rm_inactivity_timer_release_resource( |
| QDF_IPA_RM_RESOURCE_WLAN_PROD); |
| |
| if (qdf_unlikely(ret != 0)) { |
| qdf_spin_lock_bh(&ipa_ctx->rm_lock); |
| ipa_ctx->rm_state = WLAN_IPA_RM_GRANTED; |
| qdf_spin_unlock_bh(&ipa_ctx->rm_lock); |
| QDF_ASSERT(0); |
| ipa_warn("rm_inactivity_timer_release_resource ret fail"); |
| } |
| |
| /* |
| * If wake_lock is released immediately, kernel would try to suspend |
| * immediately as well, Just avoid ping-pong between suspend-resume |
| * while there is healthy amount of data transfer going on by |
| * releasing the wake_lock after some delay. |
| */ |
| qdf_sched_delayed_work(&ipa_ctx->wake_lock_work, |
| msecs_to_jiffies |
| (WLAN_IPA_RX_INACTIVITY_MSEC_DELAY)); |
| |
| return QDF_STATUS_SUCCESS; |
| } |
| |
| /** |
| * wlan_ipa_uc_rm_notify_handler() - IPA uC resource notification handler |
| * @ipa_ctx: IPA context |
| * @event: IPA RM event |
| * |
| * Return: None |
| */ |
| static void |
| wlan_ipa_uc_rm_notify_handler(struct wlan_ipa_priv *ipa_ctx, |
| qdf_ipa_rm_event_t event) |
| { |
| if (!wlan_ipa_is_rm_enabled(ipa_ctx->config)) |
| return; |
| |
| ipa_debug("event code %d", event); |
| |
| switch (event) { |
| case QDF_IPA_RM_RESOURCE_GRANTED: |
| /* Differed RM Granted */ |
| qdf_mutex_acquire(&ipa_ctx->ipa_lock); |
| if ((!ipa_ctx->resource_unloading) && |
| (!ipa_ctx->activated_fw_pipe)) { |
| wlan_ipa_uc_enable_pipes(ipa_ctx); |
| } |
| qdf_mutex_release(&ipa_ctx->ipa_lock); |
| break; |
| |
| case QDF_IPA_RM_RESOURCE_RELEASED: |
| /* Differed RM Released */ |
| ipa_ctx->resource_unloading = false; |
| break; |
| |
| default: |
| ipa_err("invalid event code %d", event); |
| break; |
| } |
| } |
| |
| /** |
| * wlan_ipa_uc_rm_notify_defer() - Defer IPA uC notification |
| * * @data: IPA context |
| * |
| * This function is called when a resource manager event is received |
| * from firmware in interrupt context. This function will defer the |
| * handling to the OL RX thread |
| * |
| * Return: None |
| */ |
| static void wlan_ipa_uc_rm_notify_defer(void *data) |
| { |
| struct wlan_ipa_priv *ipa_ctx = data; |
| qdf_ipa_rm_event_t event; |
| struct uc_rm_work_struct *uc_rm_work = &ipa_ctx->uc_rm_work; |
| |
| event = uc_rm_work->event; |
| |
| wlan_ipa_uc_rm_notify_handler(ipa_ctx, event); |
| } |
| |
| /** |
| * wlan_ipa_wake_lock_timer_func() - Wake lock work handler |
| * @data: IPA context |
| * |
| * When IPA resources are released in wlan_ipa_wdi_rm_try_release() we do |
| * not want to immediately release the wake lock since the system |
| * would then potentially try to suspend when there is a healthy data |
| * rate. Deferred work is scheduled and this function handles the |
| * work. When this function is called, if the IPA resource is still |
| * released then we release the wake lock. |
| * |
| * Return: None |
| */ |
| static void wlan_ipa_wake_lock_timer_func(void *data) |
| { |
| struct wlan_ipa_priv *ipa_ctx = data; |
| |
| qdf_spin_lock_bh(&ipa_ctx->rm_lock); |
| |
| if (ipa_ctx->rm_state != WLAN_IPA_RM_RELEASED) |
| goto end; |
| |
| ipa_ctx->wake_lock_released = true; |
| qdf_wake_lock_release(&ipa_ctx->wake_lock, |
| WIFI_POWER_EVENT_WAKELOCK_IPA); |
| |
| end: |
| qdf_spin_unlock_bh(&ipa_ctx->rm_lock); |
| } |
| |
| /** |
| * wlan_ipa_rm_cons_request() - WLAN consumer resource request handler |
| * |
| * Callback function registered with IPA that is called when IPA wants |
| * to access the WLAN consumer resource |
| * |
| * Return: 0 if the request is granted, negative errno otherwise |
| */ |
| static int wlan_ipa_rm_cons_request(void) |
| { |
| struct wlan_ipa_priv *ipa_ctx; |
| QDF_STATUS status = QDF_STATUS_SUCCESS; |
| |
| ipa_ctx = wlan_ipa_get_obj_context(); |
| |
| if (ipa_ctx->resource_loading) { |
| ipa_err("IPA resource loading in progress"); |
| ipa_ctx->pending_cons_req = true; |
| status = QDF_STATUS_E_PENDING; |
| } else if (ipa_ctx->resource_unloading) { |
| ipa_err("IPA resource unloading in progress"); |
| ipa_ctx->pending_cons_req = true; |
| status = QDF_STATUS_E_PERM; |
| } |
| |
| return qdf_status_to_os_return(status); |
| } |
| |
| /** |
| * wlan_ipa_rm_notify() - IPA resource manager notifier callback |
| * @user_data: user data registered with IPA |
| * @event: the IPA resource manager event that occurred |
| * @data: the data associated with the event |
| * |
| * Return: None |
| */ |
| static void wlan_ipa_rm_notify(void *user_data, qdf_ipa_rm_event_t event, |
| unsigned long data) |
| { |
| struct wlan_ipa_priv *ipa_ctx = user_data; |
| |
| if (qdf_unlikely(!ipa_ctx)) |
| return; |
| |
| if (!wlan_ipa_is_rm_enabled(ipa_ctx->config)) |
| return; |
| |
| ipa_debug("Evt: %d", event); |
| |
| switch (event) { |
| case QDF_IPA_RM_RESOURCE_GRANTED: |
| if (wlan_ipa_uc_is_enabled(ipa_ctx->config)) { |
| /* RM Notification comes with ISR context |
| * it should be serialized into work queue to avoid |
| * ISR sleep problem |
| */ |
| ipa_ctx->uc_rm_work.event = event; |
| qdf_sched_work(0, &ipa_ctx->uc_rm_work.work); |
| break; |
| } |
| qdf_spin_lock_bh(&ipa_ctx->rm_lock); |
| ipa_ctx->rm_state = WLAN_IPA_RM_GRANTED; |
| qdf_spin_unlock_bh(&ipa_ctx->rm_lock); |
| ipa_ctx->stats.num_rm_grant++; |
| break; |
| |
| case QDF_IPA_RM_RESOURCE_RELEASED: |
| ipa_debug("RM Release"); |
| ipa_ctx->resource_unloading = false; |
| break; |
| |
| default: |
| ipa_err("Unknown RM Evt: %d", event); |
| break; |
| } |
| } |
| |
| QDF_STATUS wlan_ipa_wdi_setup_rm(struct wlan_ipa_priv *ipa_ctx) |
| { |
| qdf_ipa_rm_create_params_t create_params; |
| int ret; |
| |
| if (!wlan_ipa_is_rm_enabled(ipa_ctx->config)) |
| return 0; |
| |
| qdf_create_work(0, &ipa_ctx->uc_rm_work.work, |
| wlan_ipa_uc_rm_notify_defer, ipa_ctx); |
| qdf_mem_set(&create_params, 0, sizeof(create_params)); |
| create_params.name = QDF_IPA_RM_RESOURCE_WLAN_PROD; |
| create_params.reg_params.user_data = ipa_ctx; |
| create_params.reg_params.notify_cb = wlan_ipa_rm_notify; |
| create_params.floor_voltage = QDF_IPA_VOLTAGE_LEVEL; |
| |
| ret = qdf_ipa_rm_create_resource(&create_params); |
| if (ret) { |
| ipa_err("Create RM resource failed: %d", ret); |
| goto setup_rm_fail; |
| } |
| |
| qdf_mem_set(&create_params, 0, sizeof(create_params)); |
| create_params.name = QDF_IPA_RM_RESOURCE_WLAN_CONS; |
| create_params.request_resource = wlan_ipa_rm_cons_request; |
| create_params.release_resource = wlan_ipa_rm_cons_release; |
| create_params.floor_voltage = QDF_IPA_VOLTAGE_LEVEL; |
| |
| ret = qdf_ipa_rm_create_resource(&create_params); |
| if (ret) { |
| ipa_err("Create RM CONS resource failed: %d", ret); |
| goto delete_prod; |
| } |
| |
| qdf_ipa_rm_add_dependency(QDF_IPA_RM_RESOURCE_WLAN_PROD, |
| QDF_IPA_RM_RESOURCE_APPS_CONS); |
| |
| ret = qdf_ipa_rm_inactivity_timer_init(QDF_IPA_RM_RESOURCE_WLAN_PROD, |
| WLAN_IPA_RX_INACTIVITY_MSEC_DELAY); |
| if (ret) { |
| ipa_err("Timer init failed: %d", ret); |
| goto timer_init_failed; |
| } |
| |
| qdf_wake_lock_create(&ipa_ctx->wake_lock, "wlan_ipa"); |
| qdf_create_delayed_work(&ipa_ctx->wake_lock_work, |
| wlan_ipa_wake_lock_timer_func, ipa_ctx); |
| qdf_spinlock_create(&ipa_ctx->rm_lock); |
| ipa_ctx->rm_state = WLAN_IPA_RM_RELEASED; |
| ipa_ctx->wake_lock_released = true; |
| qdf_atomic_set(&ipa_ctx->tx_ref_cnt, 0); |
| |
| return QDF_STATUS_SUCCESS; |
| |
| timer_init_failed: |
| qdf_ipa_rm_delete_resource(QDF_IPA_RM_RESOURCE_APPS_CONS); |
| |
| delete_prod: |
| qdf_ipa_rm_delete_resource(QDF_IPA_RM_RESOURCE_WLAN_PROD); |
| |
| setup_rm_fail: |
| return QDF_STATUS_E_FAILURE; |
| } |
| |
| void wlan_ipa_wdi_destroy_rm(struct wlan_ipa_priv *ipa_ctx) |
| { |
| int ret; |
| |
| if (!wlan_ipa_is_rm_enabled(ipa_ctx->config)) |
| return; |
| |
| qdf_cancel_delayed_work(&ipa_ctx->wake_lock_work); |
| qdf_wake_lock_destroy(&ipa_ctx->wake_lock); |
| qdf_cancel_work(&ipa_ctx->uc_rm_work.work); |
| qdf_spinlock_destroy(&ipa_ctx->rm_lock); |
| |
| ipa_rm_inactivity_timer_destroy(QDF_IPA_RM_RESOURCE_WLAN_PROD); |
| |
| ret = qdf_ipa_rm_delete_resource(QDF_IPA_RM_RESOURCE_WLAN_CONS); |
| if (ret) |
| ipa_err("RM CONS resource delete failed %d", ret); |
| |
| ret = qdf_ipa_rm_delete_resource(QDF_IPA_RM_RESOURCE_WLAN_PROD); |
| if (ret) |
| ipa_err("RM PROD resource delete failed %d", ret); |
| } |
| #endif /* CONFIG_IPA_WDI_UNIFIED_API */ |