| /* Copyright (c) 2017, 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. |
| */ |
| |
| #include <linux/debugfs.h> |
| #include "ipa_pm.h" |
| #include "ipa_i.h" |
| |
| static const char *client_state_to_str[IPA_PM_STATE_MAX] = { |
| __stringify(IPA_PM_DEACTIVATED), |
| __stringify(IPA_PM_DEACTIVATE_IN_PROGRESS), |
| __stringify(IPA_PM_ACTIVATE_IN_PROGRESS), |
| __stringify(IPA_PM_ACTIVATED), |
| __stringify(IPA_PM_ACTIVATED_PENDING_DEACTIVATION), |
| __stringify(IPA_PM_ACTIVATED_TIMER_SET), |
| __stringify(IPA_PM_ACTIVATED_PENDING_RESCHEDULE), |
| }; |
| |
| static const char *ipa_pm_group_to_str[IPA_PM_GROUP_MAX] = { |
| __stringify(IPA_PM_GROUP_DEFAULT), |
| __stringify(IPA_PM_GROUP_APPS), |
| __stringify(IPA_PM_GROUP_MODEM), |
| }; |
| |
| |
| #define IPA_PM_DRV_NAME "ipa_pm" |
| |
| #define IPA_PM_DBG(fmt, args...) \ |
| do { \ |
| pr_debug(IPA_PM_DRV_NAME " %s:%d " fmt, \ |
| __func__, __LINE__, ## args); \ |
| IPA_IPC_LOGGING(ipa_get_ipc_logbuf(), \ |
| IPA_PM_DRV_NAME " %s:%d " fmt, ## args); \ |
| IPA_IPC_LOGGING(ipa_get_ipc_logbuf_low(), \ |
| IPA_PM_DRV_NAME " %s:%d " fmt, ## args); \ |
| } while (0) |
| #define IPA_PM_DBG_LOW(fmt, args...) \ |
| do { \ |
| pr_debug(IPA_PM_DRV_NAME " %s:%d " fmt, \ |
| __func__, __LINE__, ## args); \ |
| IPA_IPC_LOGGING(ipa_get_ipc_logbuf_low(), \ |
| IPA_PM_DRV_NAME " %s:%d " fmt, ## args); \ |
| } while (0) |
| #define IPA_PM_ERR(fmt, args...) \ |
| do { \ |
| pr_err(IPA_PM_DRV_NAME " %s:%d " fmt, \ |
| __func__, __LINE__, ## args); \ |
| IPA_IPC_LOGGING(ipa_get_ipc_logbuf(), \ |
| IPA_PM_DRV_NAME " %s:%d " fmt, ## args); \ |
| IPA_IPC_LOGGING(ipa_get_ipc_logbuf_low(), \ |
| IPA_PM_DRV_NAME " %s:%d " fmt, ## args); \ |
| } while (0) |
| #define IPA_PM_DBG_STATE(hdl, name, state) \ |
| IPA_PM_DBG_LOW("Client[%d] %s: %s\n", hdl, name, \ |
| client_state_to_str[state]) |
| |
| |
| #if IPA_PM_MAX_CLIENTS > 32 |
| #error max client greater than 32 all bitmask types should be changed |
| #endif |
| |
| /* |
| * struct ipa_pm_exception_list - holds information about an exception |
| * @pending: number of clients in exception that have not yet been adctivated |
| * @bitmask: bitmask of the clients in the exception based on handle |
| * @threshold: the threshold values for the exception |
| */ |
| struct ipa_pm_exception_list { |
| char clients[IPA_PM_MAX_EX_CL]; |
| int pending; |
| u32 bitmask; |
| int threshold[IPA_PM_THRESHOLD_MAX]; |
| }; |
| |
| /* |
| * struct clk_scaling_db - holds information about threshholds and exceptions |
| * @lock: lock the bitmasks and thresholds |
| * @exception_list: pointer to the list of exceptions |
| * @work: work for clock scaling algorithm |
| * @active_client_bitmask: the bits represent handles in the clients array that |
| * contain non-null client |
| * @threshold_size: size of the throughput threshold |
| * @exception_size: size of the exception list |
| * @cur_vote: idx of the threshold |
| * @default_threshold: the thresholds used if no exception passes |
| * @current_threshold: the current threshold of the clock plan |
| */ |
| struct clk_scaling_db { |
| spinlock_t lock; |
| struct ipa_pm_exception_list exception_list[IPA_PM_EXCEPTION_MAX]; |
| struct work_struct work; |
| u32 active_client_bitmask; |
| int threshold_size; |
| int exception_size; |
| int cur_vote; |
| int default_threshold[IPA_PM_THRESHOLD_MAX]; |
| int *current_threshold; |
| }; |
| |
| /* |
| * ipa_pm state names |
| * |
| * Timer free states: |
| * @IPA_PM_DEACTIVATED: client starting state when registered |
| * @IPA_PM_DEACTIVATE_IN_PROGRESS: deactivate was called in progress of a client |
| * activating |
| * @IPA_PM_ACTIVATE_IN_PROGRESS: client is being activated by work_queue |
| * @IPA_PM_ACTIVATED: client is activated without any timers |
| * |
| * Timer set states: |
| * @IPA_PM_ACTIVATED_PENDING_DEACTIVATION: moves to deactivate once timer pass |
| * @IPA_PM_ACTIVATED_TIMER_SET: client was activated while timer was set, so |
| * when the timer pass, client will still be activated |
| *@IPA_PM_ACTIVATED_PENDING_RESCHEDULE: state signifying extended timer when |
| * a client is deferred_deactivated when a time ris still active |
| */ |
| enum ipa_pm_state { |
| IPA_PM_DEACTIVATED, |
| IPA_PM_DEACTIVATE_IN_PROGRESS, |
| IPA_PM_ACTIVATE_IN_PROGRESS, |
| IPA_PM_ACTIVATED, |
| IPA_PM_ACTIVATED_PENDING_DEACTIVATION, |
| IPA_PM_ACTIVATED_TIMER_SET, |
| IPA_PM_ACTIVATED_PENDING_RESCHEDULE, |
| }; |
| |
| #define IPA_PM_STATE_ACTIVE(state) \ |
| (state == IPA_PM_ACTIVATED ||\ |
| state == IPA_PM_ACTIVATED_PENDING_DEACTIVATION ||\ |
| state == IPA_PM_ACTIVATED_TIMER_SET ||\ |
| state == IPA_PM_ACTIVATED_PENDING_RESCHEDULE) |
| |
| #define IPA_PM_STATE_IN_PROGRESS(state) \ |
| (state == IPA_PM_ACTIVATE_IN_PROGRESS \ |
| || state == IPA_PM_DEACTIVATE_IN_PROGRESS) |
| |
| /* |
| * struct ipa_pm_client - holds information about a specific IPA client |
| * @name: string name of the client |
| * @callback: pointer to the client's callback function |
| * @callback_params: pointer to the client's callback parameters |
| * @state: Activation state of the client |
| * @skip_clk_vote: 0 if client votes for clock when activated, 1 if no vote |
| * @group: the ipa_pm_group the client belongs to |
| * @hdl: handle of the client |
| * @throughput: the throughput of the client for clock scaling |
| * @state_lock: spinlock to lock the pm_states |
| * @activate_work: work for activate (blocking case) |
| * @deactivate work: delayed work for deferred_deactivate function |
| * @complete: generic wait-for-completion handler |
| */ |
| struct ipa_pm_client { |
| char name[IPA_PM_MAX_EX_CL]; |
| void (*callback)(void*, enum ipa_pm_cb_event); |
| void *callback_params; |
| enum ipa_pm_state state; |
| bool skip_clk_vote; |
| int group; |
| int hdl; |
| int throughput; |
| spinlock_t state_lock; |
| struct work_struct activate_work; |
| struct delayed_work deactivate_work; |
| struct completion complete; |
| }; |
| |
| /* |
| * struct ipa_pm_ctx - global ctx that will hold the client arrays and tput info |
| * @clients: array to the clients with the handle as its index |
| * @clients_by_pipe: array to the clients with endpoint as the index |
| * @wq: work queue for deferred deactivate, activate, and clk_scaling work |
| 8 @clk_scaling: pointer to clock scaling database |
| * @client_mutex: global mutex to lock the client arrays |
| * @aggragated_tput: aggragated tput value of all valid activated clients |
| * @group_tput: combined throughput for the groups |
| */ |
| struct ipa_pm_ctx { |
| struct ipa_pm_client *clients[IPA_PM_MAX_CLIENTS]; |
| struct ipa_pm_client *clients_by_pipe[IPA3_MAX_NUM_PIPES]; |
| struct workqueue_struct *wq; |
| struct clk_scaling_db clk_scaling; |
| struct mutex client_mutex; |
| int aggregated_tput; |
| int group_tput[IPA_PM_GROUP_MAX]; |
| }; |
| |
| static struct ipa_pm_ctx *ipa_pm_ctx; |
| |
| /** |
| * pop_max_from_array() -pop the max and move the last element to where the |
| * max was popped |
| * @arr: array to be searched for max |
| * @n: size of the array |
| * |
| * Returns: max value of the array |
| */ |
| static int pop_max_from_array(int *arr, int *n) |
| { |
| int i; |
| int max, max_idx; |
| |
| max_idx = *n - 1; |
| max = 0; |
| |
| if (*n == 0) |
| return 0; |
| |
| for (i = 0; i < *n; i++) { |
| if (arr[i] > max) { |
| max = arr[i]; |
| max_idx = i; |
| } |
| } |
| (*n)--; |
| arr[max_idx] = arr[*n]; |
| |
| return max; |
| } |
| |
| /** |
| * calculate_throughput() - calculate the aggregated throughput |
| * based on active clients |
| * |
| * Returns: aggregated tput value |
| */ |
| static int calculate_throughput(void) |
| { |
| int client_tput[IPA_PM_MAX_CLIENTS] = { 0 }; |
| bool group_voted[IPA_PM_GROUP_MAX] = { false }; |
| int i, n; |
| int max, second_max, aggregated_tput; |
| struct ipa_pm_client *client; |
| |
| /* Create a basic array to hold throughputs*/ |
| for (i = 0, n = 0; i < IPA_PM_MAX_CLIENTS; i++) { |
| client = ipa_pm_ctx->clients[i]; |
| if (client != NULL && IPA_PM_STATE_ACTIVE(client->state)) { |
| /* default case */ |
| if (client->group == IPA_PM_GROUP_DEFAULT) { |
| client_tput[n++] = client->throughput; |
| } else if (group_voted[client->group] == false) { |
| client_tput[n++] = ipa_pm_ctx->group_tput |
| [client->group]; |
| group_voted[client->group] = true; |
| } |
| } |
| } |
| /*the array will only use n+1 spots. n will be the last index used*/ |
| |
| aggregated_tput = 0; |
| |
| /** |
| * throughput algorithm: |
| * 1) pop the max and second_max |
| * 2) add the 2nd max to aggregated tput |
| * 3) insert the value of max - 2nd max |
| * 4) repeat until array is of size 1 |
| */ |
| while (n > 1) { |
| max = pop_max_from_array(client_tput, &n); |
| second_max = pop_max_from_array(client_tput, &n); |
| client_tput[n++] = max - second_max; |
| aggregated_tput += second_max; |
| } |
| |
| IPA_PM_DBG_LOW("Aggregated throughput: %d\n", aggregated_tput); |
| |
| return aggregated_tput; |
| } |
| |
| /** |
| * deactivate_client() - turn off the bit in the active client bitmask based on |
| * the handle passed in |
| * @hdl: The index of the client to be deactivated |
| */ |
| static void deactivate_client(u32 hdl) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&ipa_pm_ctx->clk_scaling.lock, flags); |
| ipa_pm_ctx->clk_scaling.active_client_bitmask &= ~(1 << hdl); |
| spin_unlock_irqrestore(&ipa_pm_ctx->clk_scaling.lock, flags); |
| IPA_PM_DBG_LOW("active bitmask: %x\n", |
| ipa_pm_ctx->clk_scaling.active_client_bitmask); |
| } |
| |
| /** |
| * activate_client() - turn on the bit in the active client bitmask based on |
| * the handle passed in |
| * @hdl: The index of the client to be activated |
| */ |
| static void activate_client(u32 hdl) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&ipa_pm_ctx->clk_scaling.lock, flags); |
| ipa_pm_ctx->clk_scaling.active_client_bitmask |= (1 << hdl); |
| spin_unlock_irqrestore(&ipa_pm_ctx->clk_scaling.lock, flags); |
| IPA_PM_DBG_LOW("active bitmask: %x\n", |
| ipa_pm_ctx->clk_scaling.active_client_bitmask); |
| } |
| |
| /** |
| * deactivate_client() - get threshold |
| * |
| * Returns: threshold of the exception that passes or default if none pass |
| */ |
| static void set_current_threshold(void) |
| { |
| int i; |
| struct clk_scaling_db *clk; |
| struct ipa_pm_exception_list *exception; |
| unsigned long flags; |
| |
| clk = &ipa_pm_ctx->clk_scaling; |
| |
| spin_lock_irqsave(&ipa_pm_ctx->clk_scaling.lock, flags); |
| for (i = 0; i < clk->exception_size; i++) { |
| exception = &clk->exception_list[i]; |
| if (exception->pending == 0 && (exception->bitmask |
| & ~clk->active_client_bitmask) == 0) { |
| spin_unlock_irqrestore(&ipa_pm_ctx->clk_scaling.lock, |
| flags); |
| clk->current_threshold = exception->threshold; |
| IPA_PM_DBG("Exception %d set\n", i); |
| return; |
| } |
| } |
| clk->current_threshold = clk->default_threshold; |
| spin_unlock_irqrestore(&ipa_pm_ctx->clk_scaling.lock, flags); |
| } |
| |
| /** |
| * do_clk_scaling() - set the clock based on the activated clients |
| * |
| * Returns: 0 if success, negative otherwise |
| */ |
| static int do_clk_scaling(void) |
| { |
| int i, tput; |
| int new_th_idx = 1; |
| struct clk_scaling_db *clk_scaling; |
| |
| clk_scaling = &ipa_pm_ctx->clk_scaling; |
| |
| mutex_lock(&ipa_pm_ctx->client_mutex); |
| IPA_PM_DBG("clock scaling started\n"); |
| tput = calculate_throughput(); |
| ipa_pm_ctx->aggregated_tput = tput; |
| set_current_threshold(); |
| |
| mutex_unlock(&ipa_pm_ctx->client_mutex); |
| |
| for (i = 0; i < clk_scaling->threshold_size; i++) { |
| if (tput > clk_scaling->current_threshold[i]) |
| new_th_idx++; |
| } |
| |
| IPA_PM_DBG("old idx was at %d\n", ipa_pm_ctx->clk_scaling.cur_vote); |
| |
| |
| if (ipa_pm_ctx->clk_scaling.cur_vote != new_th_idx) { |
| ipa_pm_ctx->clk_scaling.cur_vote = new_th_idx; |
| ipa3_set_clock_plan_from_pm(ipa_pm_ctx->clk_scaling.cur_vote); |
| } |
| |
| IPA_PM_DBG("new idx is at %d\n", ipa_pm_ctx->clk_scaling.cur_vote); |
| |
| return 0; |
| } |
| |
| /** |
| * clock_scaling_func() - set the clock on a work queue |
| */ |
| static void clock_scaling_func(struct work_struct *work) |
| { |
| do_clk_scaling(); |
| } |
| |
| /** |
| * activate_work_func - activate a client and vote for clock on a work queue |
| */ |
| static void activate_work_func(struct work_struct *work) |
| { |
| struct ipa_pm_client *client; |
| bool dec_clk = false; |
| unsigned long flags; |
| |
| client = container_of(work, struct ipa_pm_client, activate_work); |
| if (!client->skip_clk_vote) |
| IPA_ACTIVE_CLIENTS_INC_SPECIAL(client->name); |
| |
| spin_lock_irqsave(&client->state_lock, flags); |
| IPA_PM_DBG_STATE(client->hdl, client->name, client->state); |
| if (client->state == IPA_PM_ACTIVATE_IN_PROGRESS) { |
| client->state = IPA_PM_ACTIVATED; |
| } else if (client->state == IPA_PM_DEACTIVATE_IN_PROGRESS) { |
| client->state = IPA_PM_DEACTIVATED; |
| dec_clk = true; |
| } else { |
| IPA_PM_ERR("unexpected state %d\n", client->state); |
| WARN_ON(1); |
| } |
| spin_unlock_irqrestore(&client->state_lock, flags); |
| |
| complete_all(&client->complete); |
| |
| if (dec_clk) { |
| ipa_set_tag_process_before_gating(true); |
| if (!client->skip_clk_vote) |
| IPA_ACTIVE_CLIENTS_DEC_SPECIAL(client->name); |
| |
| IPA_PM_DBG_STATE(client->hdl, client->name, client->state); |
| return; |
| } |
| |
| activate_client(client->hdl); |
| |
| mutex_lock(&ipa_pm_ctx->client_mutex); |
| if (client->callback) { |
| client->callback(client->callback_params, |
| IPA_PM_CLIENT_ACTIVATED); |
| } else { |
| IPA_PM_ERR("client has no callback"); |
| WARN_ON(1); |
| } |
| mutex_unlock(&ipa_pm_ctx->client_mutex); |
| |
| IPA_PM_DBG_STATE(client->hdl, client->name, client->state); |
| do_clk_scaling(); |
| } |
| |
| /** |
| * delayed_deferred_deactivate_work_func - deferred deactivate on a work queue |
| */ |
| static void delayed_deferred_deactivate_work_func(struct work_struct *work) |
| { |
| struct delayed_work *dwork; |
| struct ipa_pm_client *client; |
| unsigned long flags; |
| |
| dwork = container_of(work, struct delayed_work, work); |
| client = container_of(dwork, struct ipa_pm_client, deactivate_work); |
| |
| spin_lock_irqsave(&client->state_lock, flags); |
| IPA_PM_DBG_STATE(client->hdl, client->name, client->state); |
| switch (client->state) { |
| case IPA_PM_ACTIVATED_TIMER_SET: |
| client->state = IPA_PM_ACTIVATED; |
| goto bail; |
| case IPA_PM_ACTIVATED_PENDING_RESCHEDULE: |
| queue_delayed_work(ipa_pm_ctx->wq, &client->deactivate_work, |
| msecs_to_jiffies(IPA_PM_DEFERRED_TIMEOUT)); |
| client->state = IPA_PM_ACTIVATED_PENDING_DEACTIVATION; |
| goto bail; |
| case IPA_PM_ACTIVATED_PENDING_DEACTIVATION: |
| client->state = IPA_PM_DEACTIVATED; |
| IPA_PM_DBG_STATE(client->hdl, client->name, client->state); |
| spin_unlock_irqrestore(&client->state_lock, flags); |
| ipa_set_tag_process_before_gating(true); |
| if (!client->skip_clk_vote) |
| IPA_ACTIVE_CLIENTS_DEC_SPECIAL(client->name); |
| |
| deactivate_client(client->hdl); |
| do_clk_scaling(); |
| return; |
| default: |
| IPA_PM_ERR("unexpected state %d\n", client->state); |
| WARN_ON(1); |
| goto bail; |
| } |
| |
| bail: |
| IPA_PM_DBG_STATE(client->hdl, client->name, client->state); |
| spin_unlock_irqrestore(&client->state_lock, flags); |
| } |
| |
| static int find_next_open_array_element(const char *name) |
| { |
| int i, n; |
| |
| n = -ENOBUFS; |
| |
| for (i = IPA_PM_MAX_CLIENTS - 1; i >= 0; i--) { |
| if (ipa_pm_ctx->clients[i] == NULL) { |
| n = i; |
| continue; |
| } |
| |
| if (strlen(name) == strlen(ipa_pm_ctx->clients[i]->name)) |
| if (!strcmp(name, ipa_pm_ctx->clients[i]->name)) |
| return -EEXIST; |
| } |
| return n; |
| } |
| |
| /** |
| * add_client_to_exception_list() - add client to the exception list and |
| * update pending if necessary |
| * @hdl: index of the IPA client |
| * |
| * Returns: 0 if success, negative otherwise |
| */ |
| static int add_client_to_exception_list(u32 hdl) |
| { |
| int i; |
| struct ipa_pm_exception_list *exception; |
| |
| mutex_lock(&ipa_pm_ctx->client_mutex); |
| for (i = 0; i < ipa_pm_ctx->clk_scaling.exception_size; i++) { |
| exception = &ipa_pm_ctx->clk_scaling.exception_list[i]; |
| if (strnstr(exception->clients, ipa_pm_ctx->clients[hdl]->name, |
| strlen(exception->clients))) { |
| exception->pending--; |
| |
| if (exception->pending < 0) { |
| WARN_ON(1); |
| exception->pending = 0; |
| mutex_unlock(&ipa_pm_ctx->client_mutex); |
| return -EPERM; |
| } |
| exception->bitmask |= (1 << hdl); |
| } |
| } |
| IPA_PM_DBG("%s added to exception list\n", |
| ipa_pm_ctx->clients[hdl]->name); |
| mutex_unlock(&ipa_pm_ctx->client_mutex); |
| |
| return 0; |
| } |
| |
| /** |
| * remove_client_to_exception_list() - remove client from the exception list and |
| * update pending if necessary |
| * @hdl: index of the IPA client |
| * |
| * Returns: 0 if success, negative otherwise |
| */ |
| static int remove_client_from_exception_list(u32 hdl) |
| { |
| int i; |
| struct ipa_pm_exception_list *exception; |
| |
| for (i = 0; i < ipa_pm_ctx->clk_scaling.exception_size; i++) { |
| exception = &ipa_pm_ctx->clk_scaling.exception_list[i]; |
| if (exception->bitmask & (1 << hdl)) { |
| exception->pending++; |
| exception->bitmask &= ~(1 << hdl); |
| } |
| } |
| IPA_PM_DBG("Client %d removed from exception list\n", hdl); |
| |
| return 0; |
| } |
| |
| /** |
| * ipa_pm_init() - initialize IPA PM Components |
| * @ipa_pm_init_params: parameters needed to fill exceptions and thresholds |
| * |
| * Returns: 0 on success, negative on failure |
| */ |
| int ipa_pm_init(struct ipa_pm_init_params *params) |
| { |
| int i, j; |
| struct clk_scaling_db *clk_scaling; |
| |
| if (params == NULL) { |
| IPA_PM_ERR("Invalid Params\n"); |
| return -EINVAL; |
| } |
| |
| if (params->threshold_size <= 0 |
| || params->threshold_size > IPA_PM_THRESHOLD_MAX) { |
| IPA_PM_ERR("Invalid threshold size\n"); |
| return -EINVAL; |
| } |
| |
| if (params->exception_size < 0 |
| || params->exception_size > IPA_PM_EXCEPTION_MAX) { |
| IPA_PM_ERR("Invalid exception size\n"); |
| return -EINVAL; |
| } |
| |
| IPA_PM_DBG("IPA PM initialization started\n"); |
| |
| if (ipa_pm_ctx != NULL) { |
| IPA_PM_ERR("Already initialized\n"); |
| return -EPERM; |
| } |
| |
| |
| ipa_pm_ctx = kzalloc(sizeof(*ipa_pm_ctx), GFP_KERNEL); |
| if (!ipa_pm_ctx) { |
| IPA_PM_ERR(":kzalloc err.\n"); |
| return -ENOMEM; |
| } |
| |
| ipa_pm_ctx->wq = create_singlethread_workqueue("ipa_pm_activate"); |
| if (!ipa_pm_ctx->wq) { |
| IPA_PM_ERR("create workqueue failed\n"); |
| kfree(ipa_pm_ctx); |
| return -ENOMEM; |
| } |
| |
| mutex_init(&ipa_pm_ctx->client_mutex); |
| |
| /* Populate and init locks in clk_scaling_db */ |
| clk_scaling = &ipa_pm_ctx->clk_scaling; |
| spin_lock_init(&clk_scaling->lock); |
| clk_scaling->threshold_size = params->threshold_size; |
| clk_scaling->exception_size = params->exception_size; |
| INIT_WORK(&clk_scaling->work, clock_scaling_func); |
| |
| for (i = 0; i < params->threshold_size; i++) |
| clk_scaling->default_threshold[i] = |
| params->default_threshold[i]; |
| |
| /* Populate exception list*/ |
| for (i = 0; i < params->exception_size; i++) { |
| strlcpy(clk_scaling->exception_list[i].clients, |
| params->exceptions[i].usecase, IPA_PM_MAX_EX_CL); |
| IPA_PM_DBG("Usecase: %s\n", params->exceptions[i].usecase); |
| |
| /* Parse the commas to count the size of the clients */ |
| for (j = 0; j < IPA_PM_MAX_EX_CL && |
| clk_scaling->exception_list[i].clients[j]; j++) { |
| if (clk_scaling->exception_list[i].clients[j] == ',') |
| clk_scaling->exception_list[i].pending++; |
| } |
| |
| clk_scaling->exception_list[i].pending++; |
| IPA_PM_DBG("Pending: %d\n", clk_scaling-> |
| exception_list[i].pending); |
| |
| /* populate the threshold */ |
| for (j = 0; j < params->threshold_size; j++) { |
| clk_scaling->exception_list[i].threshold[j] |
| = params->exceptions[i].threshold[j]; |
| } |
| |
| } |
| IPA_PM_DBG("initialization success"); |
| |
| return 0; |
| } |
| |
| int ipa_pm_destroy(void) |
| { |
| IPA_PM_DBG("IPA PM destroy started\n"); |
| |
| if (ipa_pm_ctx == NULL) { |
| IPA_PM_ERR("Already destroyed\n"); |
| return -EPERM; |
| } |
| |
| destroy_workqueue(ipa_pm_ctx->wq); |
| |
| kfree(ipa_pm_ctx); |
| ipa_pm_ctx = NULL; |
| |
| return 0; |
| } |
| |
| /** |
| * ipa_rm_delete_register() - register an IPA PM client with the PM |
| * @register_params: params for a client like throughput, callback, etc. |
| * @hdl: int pointer that will be used as an index to access the client |
| * |
| * Returns: 0 on success, negative on failure |
| * |
| * Side effects: *hdl is replaced with the client index or -EEXIST if |
| * client is already registered |
| */ |
| int ipa_pm_register(struct ipa_pm_register_params *params, u32 *hdl) |
| { |
| struct ipa_pm_client *client; |
| |
| if (params == NULL || hdl == NULL || params->name == NULL) { |
| IPA_PM_ERR("Invalid Params\n"); |
| return -EINVAL; |
| } |
| |
| IPA_PM_DBG("IPA PM registering client\n"); |
| |
| mutex_lock(&ipa_pm_ctx->client_mutex); |
| |
| *hdl = find_next_open_array_element(params->name); |
| |
| if (*hdl > IPA_CLIENT_MAX) { |
| mutex_unlock(&ipa_pm_ctx->client_mutex); |
| IPA_PM_ERR("client is already registered or array is full\n"); |
| return *hdl; |
| } |
| |
| ipa_pm_ctx->clients[*hdl] = kzalloc(sizeof |
| (struct ipa_pm_client), GFP_KERNEL); |
| if (!ipa_pm_ctx->clients[*hdl]) { |
| mutex_unlock(&ipa_pm_ctx->client_mutex); |
| IPA_PM_ERR(":kzalloc err.\n"); |
| return -ENOMEM; |
| } |
| mutex_unlock(&ipa_pm_ctx->client_mutex); |
| |
| client = ipa_pm_ctx->clients[*hdl]; |
| |
| spin_lock_init(&client->state_lock); |
| |
| INIT_DELAYED_WORK(&client->deactivate_work, |
| delayed_deferred_deactivate_work_func); |
| |
| INIT_WORK(&client->activate_work, activate_work_func); |
| |
| /* populate fields */ |
| strlcpy(client->name, params->name, IPA_PM_MAX_EX_CL); |
| client->callback = params->callback; |
| client->callback_params = params->user_data; |
| client->group = params->group; |
| client->hdl = *hdl; |
| client->skip_clk_vote = params->skip_clk_vote; |
| |
| /* add client to exception list */ |
| if (add_client_to_exception_list(*hdl)) { |
| ipa_pm_deregister(*hdl); |
| IPA_PM_ERR("Fail to add client to exception_list\n"); |
| return -EPERM; |
| } |
| |
| IPA_PM_DBG("IPA PM client registered with handle %d\n", *hdl); |
| return 0; |
| } |
| |
| /** |
| * ipa_pm_deregister() - deregister IPA client from the PM |
| * @hdl: index of the client in the array |
| * |
| * Returns: 0 on success, negative on failure |
| */ |
| int ipa_pm_deregister(u32 hdl) |
| { |
| struct ipa_pm_client *client; |
| int i; |
| unsigned long flags; |
| |
| if (hdl >= IPA_PM_MAX_CLIENTS) { |
| IPA_PM_ERR("Invalid Param\n"); |
| return -EINVAL; |
| } |
| |
| if (ipa_pm_ctx->clients[hdl] == NULL) { |
| IPA_PM_ERR("Client is Null\n"); |
| return -EINVAL; |
| } |
| |
| IPA_PM_DBG("IPA PM deregistering client\n"); |
| |
| client = ipa_pm_ctx->clients[hdl]; |
| spin_lock_irqsave(&client->state_lock, flags); |
| if (IPA_PM_STATE_IN_PROGRESS(client->state)) { |
| spin_unlock_irqrestore(&client->state_lock, flags); |
| wait_for_completion(&client->complete); |
| spin_lock_irqsave(&client->state_lock, flags); |
| } |
| |
| if (IPA_PM_STATE_ACTIVE(client->state)) { |
| IPA_PM_DBG("Activated clients cannot be deregistered"); |
| spin_unlock_irqrestore(&client->state_lock, flags); |
| return -EPERM; |
| } |
| spin_unlock_irqrestore(&client->state_lock, flags); |
| |
| mutex_lock(&ipa_pm_ctx->client_mutex); |
| |
| /* nullify pointers in pipe array */ |
| for (i = 0; i < IPA3_MAX_NUM_PIPES; i++) { |
| if (ipa_pm_ctx->clients_by_pipe[i] == ipa_pm_ctx->clients[hdl]) |
| ipa_pm_ctx->clients_by_pipe[i] = NULL; |
| } |
| kfree(client); |
| ipa_pm_ctx->clients[hdl] = NULL; |
| |
| remove_client_from_exception_list(hdl); |
| IPA_PM_DBG("IPA PM client %d deregistered\n", hdl); |
| mutex_unlock(&ipa_pm_ctx->client_mutex); |
| |
| return 0; |
| } |
| |
| /** |
| * ipa_pm_associate_ipa_cons_to_client() - add mapping to pipe with ipa cllent |
| * @hdl: index of the client to be mapped |
| * @consumer: the pipe/consumer name to be pipped to the client |
| * |
| * Returns: 0 on success, negative on failure |
| * |
| * Side effects: multiple pipes are allowed to be mapped to a single client |
| */ |
| int ipa_pm_associate_ipa_cons_to_client(u32 hdl, enum ipa_client_type consumer) |
| { |
| int idx; |
| |
| if (hdl >= IPA_PM_MAX_CLIENTS || consumer < 0 || |
| consumer >= IPA_CLIENT_MAX) { |
| IPA_PM_ERR("invalid params\n"); |
| return -EINVAL; |
| } |
| |
| mutex_lock(&ipa_pm_ctx->client_mutex); |
| idx = ipa_get_ep_mapping(consumer); |
| |
| IPA_PM_DBG("Mapping pipe %d to client %d\n", idx, hdl); |
| |
| if (idx < 0) { |
| mutex_unlock(&ipa_pm_ctx->client_mutex); |
| IPA_PM_DBG("Pipe is not used\n"); |
| return 0; |
| } |
| |
| if (ipa_pm_ctx->clients[hdl] == NULL) { |
| mutex_unlock(&ipa_pm_ctx->client_mutex); |
| IPA_PM_ERR("Client is NULL\n"); |
| return -EPERM; |
| } |
| |
| if (ipa_pm_ctx->clients_by_pipe[idx] != NULL) { |
| mutex_unlock(&ipa_pm_ctx->client_mutex); |
| IPA_PM_ERR("Pipe is already mapped\n"); |
| return -EPERM; |
| } |
| ipa_pm_ctx->clients_by_pipe[idx] = ipa_pm_ctx->clients[hdl]; |
| mutex_unlock(&ipa_pm_ctx->client_mutex); |
| |
| IPA_PM_DBG("Pipe %d is mapped to client %d\n", idx, hdl); |
| |
| return 0; |
| } |
| |
| static int ipa_pm_activate_helper(struct ipa_pm_client *client, bool sync) |
| { |
| struct ipa_active_client_logging_info log_info; |
| int result = 0; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&client->state_lock, flags); |
| IPA_PM_DBG_STATE(client->hdl, client->name, client->state); |
| |
| if (IPA_PM_STATE_IN_PROGRESS(client->state)) { |
| if (sync) { |
| spin_unlock_irqrestore(&client->state_lock, flags); |
| wait_for_completion(&client->complete); |
| spin_lock_irqsave(&client->state_lock, flags); |
| } else { |
| client->state = IPA_PM_ACTIVATE_IN_PROGRESS; |
| spin_unlock_irqrestore(&client->state_lock, flags); |
| return -EINPROGRESS; |
| } |
| } |
| |
| switch (client->state) { |
| case IPA_PM_ACTIVATED_PENDING_RESCHEDULE: |
| case IPA_PM_ACTIVATED_PENDING_DEACTIVATION: |
| client->state = IPA_PM_ACTIVATED_TIMER_SET; |
| case IPA_PM_ACTIVATED: |
| case IPA_PM_ACTIVATED_TIMER_SET: |
| spin_unlock_irqrestore(&client->state_lock, flags); |
| return 0; |
| case IPA_PM_DEACTIVATED: |
| break; |
| default: |
| IPA_PM_ERR("Invalid State\n"); |
| spin_unlock_irqrestore(&client->state_lock, flags); |
| return -EPERM; |
| } |
| IPA_PM_DBG_STATE(client->hdl, client->name, client->state); |
| |
| IPA_ACTIVE_CLIENTS_PREP_SPECIAL(log_info, client->name); |
| if (!client->skip_clk_vote) { |
| if (sync) { |
| client->state = IPA_PM_ACTIVATE_IN_PROGRESS; |
| spin_unlock_irqrestore(&client->state_lock, flags); |
| IPA_ACTIVE_CLIENTS_INC_SPECIAL(client->name); |
| spin_lock_irqsave(&client->state_lock, flags); |
| } else |
| result = ipa3_inc_client_enable_clks_no_block |
| (&log_info); |
| } |
| |
| /* we got the clocks */ |
| if (result == 0) { |
| client->state = IPA_PM_ACTIVATED; |
| spin_unlock_irqrestore(&client->state_lock, flags); |
| activate_client(client->hdl); |
| if (sync) |
| do_clk_scaling(); |
| else |
| queue_work(ipa_pm_ctx->wq, |
| &ipa_pm_ctx->clk_scaling.work); |
| IPA_PM_DBG_STATE(client->hdl, client->name, client->state); |
| return 0; |
| } |
| |
| client->state = IPA_PM_ACTIVATE_IN_PROGRESS; |
| init_completion(&client->complete); |
| queue_work(ipa_pm_ctx->wq, &client->activate_work); |
| spin_unlock_irqrestore(&client->state_lock, flags); |
| IPA_PM_DBG_STATE(client->hdl, client->name, client->state); |
| return -EINPROGRESS; |
| } |
| |
| /** |
| * ipa_pm_activate(): activate ipa client to vote for clock(). Can be called |
| * from atomic context and returns -EINPROGRESS if cannot be done synchronously |
| * @hdl: index of the client in the array |
| * |
| * Returns: 0 on success, -EINPROGRESS if operation cannot be done synchronously |
| * and other negatives on failure |
| */ |
| int ipa_pm_activate(u32 hdl) |
| { |
| if (hdl >= IPA_PM_MAX_CLIENTS || ipa_pm_ctx->clients[hdl] == NULL) { |
| IPA_PM_ERR("Invalid Param\n"); |
| return -EINVAL; |
| } |
| |
| return ipa_pm_activate_helper(ipa_pm_ctx->clients[hdl], false); |
| } |
| |
| /** |
| * ipa_pm_activate(): activate ipa client to vote for clock synchronously. |
| * Cannot be called from an atomic contex. |
| * @hdl: index of the client in the array |
| * |
| * Returns: 0 on success, negative on failure |
| */ |
| int ipa_pm_activate_sync(u32 hdl) |
| { |
| if (hdl >= IPA_PM_MAX_CLIENTS || ipa_pm_ctx->clients[hdl] == NULL) { |
| IPA_PM_ERR("Invalid Param\n"); |
| return -EINVAL; |
| } |
| |
| return ipa_pm_activate_helper(ipa_pm_ctx->clients[hdl], true); |
| } |
| |
| /** |
| * ipa_pm_deferred_deactivate(): schedule a timer to deactivate client and |
| * devote clock. Can be called from atomic context (asynchronously) |
| * @hdl: index of the client in the array |
| * |
| * Returns: 0 on success, negative on failure |
| */ |
| int ipa_pm_deferred_deactivate(u32 hdl) |
| { |
| struct ipa_pm_client *client; |
| unsigned long flags; |
| |
| if (hdl >= IPA_PM_MAX_CLIENTS || ipa_pm_ctx->clients[hdl] == NULL) { |
| IPA_PM_ERR("Invalid Param\n"); |
| return -EINVAL; |
| } |
| |
| client = ipa_pm_ctx->clients[hdl]; |
| IPA_PM_DBG_STATE(hdl, client->name, client->state); |
| |
| spin_lock_irqsave(&client->state_lock, flags); |
| switch (client->state) { |
| case IPA_PM_ACTIVATE_IN_PROGRESS: |
| client->state = IPA_PM_DEACTIVATE_IN_PROGRESS; |
| case IPA_PM_DEACTIVATED: |
| IPA_PM_DBG_STATE(hdl, client->name, client->state); |
| spin_unlock_irqrestore(&client->state_lock, flags); |
| return 0; |
| case IPA_PM_ACTIVATED: |
| client->state = IPA_PM_ACTIVATED_PENDING_DEACTIVATION; |
| queue_delayed_work(ipa_pm_ctx->wq, &client->deactivate_work, |
| msecs_to_jiffies(IPA_PM_DEFERRED_TIMEOUT)); |
| break; |
| case IPA_PM_ACTIVATED_TIMER_SET: |
| case IPA_PM_ACTIVATED_PENDING_DEACTIVATION: |
| client->state = IPA_PM_ACTIVATED_PENDING_RESCHEDULE; |
| case IPA_PM_DEACTIVATE_IN_PROGRESS: |
| case IPA_PM_ACTIVATED_PENDING_RESCHEDULE: |
| break; |
| } |
| IPA_PM_DBG_STATE(hdl, client->name, client->state); |
| spin_unlock_irqrestore(&client->state_lock, flags); |
| |
| return 0; |
| } |
| |
| /** |
| * ipa_pm_deactivate_all_deferred(): Cancel the deferred deactivation timer and |
| * immediately devotes for IPA clocks |
| * |
| * Returns: 0 on success, negative on failure |
| */ |
| int ipa_pm_deactivate_all_deferred(void) |
| { |
| int i; |
| bool run_algorithm = false; |
| struct ipa_pm_client *client; |
| unsigned long flags; |
| |
| for (i = 0; i < IPA_PM_MAX_CLIENTS; i++) { |
| client = ipa_pm_ctx->clients[i]; |
| |
| if (client == NULL) |
| continue; |
| |
| cancel_delayed_work_sync(&client->deactivate_work); |
| |
| if (IPA_PM_STATE_IN_PROGRESS(client->state)) { |
| wait_for_completion(&client->complete); |
| continue; |
| } |
| |
| spin_lock_irqsave(&client->state_lock, flags); |
| IPA_PM_DBG_STATE(client->hdl, client->name, client->state); |
| |
| if (client->state == IPA_PM_ACTIVATED_TIMER_SET) { |
| client->state = IPA_PM_ACTIVATED; |
| IPA_PM_DBG_STATE(client->hdl, client->name, |
| client->state); |
| spin_unlock_irqrestore(&client->state_lock, flags); |
| } else if (client->state == |
| IPA_PM_ACTIVATED_PENDING_DEACTIVATION || |
| IPA_PM_ACTIVATED_PENDING_RESCHEDULE) { |
| run_algorithm = true; |
| client->state = IPA_PM_DEACTIVATED; |
| IPA_PM_DBG_STATE(client->hdl, client->name, |
| client->state); |
| spin_unlock_irqrestore(&client->state_lock, flags); |
| ipa_set_tag_process_before_gating(true); |
| if (!client->skip_clk_vote) |
| IPA_ACTIVE_CLIENTS_DEC_SPECIAL(client->name); |
| deactivate_client(client->hdl); |
| } else /* if activated or deactivated, we do nothing */ |
| spin_unlock_irqrestore(&client->state_lock, flags); |
| } |
| |
| if (run_algorithm) |
| do_clk_scaling(); |
| |
| return 0; |
| } |
| |
| /** |
| * ipa_pm_deactivate_sync(): deactivate ipa client and devote clock. Cannot be |
| * called from atomic context. |
| * @hdl: index of the client in the array |
| * |
| * Returns: 0 on success, negative on failure |
| */ |
| int ipa_pm_deactivate_sync(u32 hdl) |
| { |
| struct ipa_pm_client *client = ipa_pm_ctx->clients[hdl]; |
| unsigned long flags; |
| |
| if (hdl >= IPA_PM_MAX_CLIENTS || ipa_pm_ctx->clients[hdl] == NULL) { |
| IPA_PM_ERR("Invalid Param\n"); |
| return -EINVAL; |
| } |
| |
| cancel_delayed_work_sync(&client->deactivate_work); |
| |
| if (IPA_PM_STATE_IN_PROGRESS(client->state)) |
| wait_for_completion(&client->complete); |
| |
| spin_lock_irqsave(&client->state_lock, flags); |
| IPA_PM_DBG_STATE(hdl, client->name, client->state); |
| |
| if (client->state == IPA_PM_DEACTIVATED) { |
| spin_unlock_irqrestore(&client->state_lock, flags); |
| return 0; |
| } |
| |
| spin_unlock_irqrestore(&client->state_lock, flags); |
| |
| /* else case (Deactivates all Activated cases)*/ |
| ipa_set_tag_process_before_gating(true); |
| if (!client->skip_clk_vote) |
| IPA_ACTIVE_CLIENTS_DEC_SPECIAL(client->name); |
| |
| spin_lock_irqsave(&client->state_lock, flags); |
| client->state = IPA_PM_DEACTIVATED; |
| IPA_PM_DBG_STATE(hdl, client->name, client->state); |
| spin_unlock_irqrestore(&client->state_lock, flags); |
| deactivate_client(hdl); |
| do_clk_scaling(); |
| |
| return 0; |
| } |
| |
| /** |
| * ipa_pm_handle_suspend(): calls the callbacks of suspended clients to wake up |
| * @pipe_bitmask: the bits represent the indexes of the clients to be woken up |
| * |
| * Returns: 0 on success, negative on failure |
| */ |
| int ipa_pm_handle_suspend(u32 pipe_bitmask) |
| { |
| int i; |
| struct ipa_pm_client *client; |
| bool client_notified[IPA_PM_MAX_CLIENTS] = { false }; |
| |
| IPA_PM_DBG_LOW("bitmask: %d", pipe_bitmask); |
| |
| if (pipe_bitmask == 0) |
| return 0; |
| |
| mutex_lock(&ipa_pm_ctx->client_mutex); |
| for (i = 0; i < IPA3_MAX_NUM_PIPES; i++) { |
| if (pipe_bitmask & (1 << i)) { |
| client = ipa_pm_ctx->clients_by_pipe[i]; |
| if (client && client_notified[client->hdl] == false) { |
| if (client->callback) { |
| client->callback(client->callback_params |
| , IPA_PM_REQUEST_WAKEUP); |
| client_notified[client->hdl] = true; |
| } else { |
| IPA_PM_ERR("client has no callback"); |
| WARN_ON(1); |
| } |
| } |
| } |
| } |
| mutex_unlock(&ipa_pm_ctx->client_mutex); |
| return 0; |
| } |
| |
| /** |
| * ipa_pm_set_perf_profile(): Adds/changes the throughput requirement to IPA PM |
| * to be used for clock scaling |
| * @hdl: index of the client in the array |
| * @throughput: the new throughput value to be set for that client |
| * |
| * Returns: 0 on success, negative on failure |
| */ |
| int ipa_pm_set_perf_profile(u32 hdl, int throughput) |
| { |
| struct ipa_pm_client *client = ipa_pm_ctx->clients[hdl]; |
| unsigned long flags; |
| |
| if (hdl >= IPA_PM_MAX_CLIENTS || ipa_pm_ctx->clients[hdl] == NULL |
| || throughput < 0) { |
| IPA_PM_ERR("Invalid Params\n"); |
| return -EINVAL; |
| } |
| |
| mutex_lock(&ipa_pm_ctx->client_mutex); |
| if (client->group == IPA_PM_GROUP_DEFAULT) |
| IPA_PM_DBG_LOW("Old throughput: %d\n", client->throughput); |
| else |
| IPA_PM_DBG_LOW("old Group %d throughput: %d\n", |
| client->group, ipa_pm_ctx->group_tput[client->group]); |
| |
| if (client->group == IPA_PM_GROUP_DEFAULT) |
| client->throughput = throughput; |
| else |
| ipa_pm_ctx->group_tput[client->group] = throughput; |
| |
| if (client->group == IPA_PM_GROUP_DEFAULT) |
| IPA_PM_DBG_LOW("New throughput: %d\n", client->throughput); |
| else |
| IPA_PM_DBG_LOW("New Group %d throughput: %d\n", |
| client->group, ipa_pm_ctx->group_tput[client->group]); |
| mutex_unlock(&ipa_pm_ctx->client_mutex); |
| |
| spin_lock_irqsave(&client->state_lock, flags); |
| if (IPA_PM_STATE_ACTIVE(client->state) || (client->group != |
| IPA_PM_GROUP_DEFAULT)) { |
| spin_unlock_irqrestore(&client->state_lock, flags); |
| do_clk_scaling(); |
| return 0; |
| } |
| spin_unlock_irqrestore(&client->state_lock, flags); |
| |
| return 0; |
| } |
| |
| /** |
| * ipa_pm_stat() - print PM stat |
| * @buf: [in] The user buff used to print |
| * @size: [in] The size of buf |
| * Returns: number of bytes used on success, negative on failure |
| * |
| * This function is called by ipa_debugfs in order to receive |
| * a picture of the clients in the PM and the throughput, threshold and cur vote |
| */ |
| int ipa_pm_stat(char *buf, int size) |
| { |
| struct ipa_pm_client *client; |
| struct clk_scaling_db *clk = &ipa_pm_ctx->clk_scaling; |
| int i, j, tput, cnt = 0, result = 0; |
| unsigned long flags; |
| |
| if (!buf || size < 0) |
| return -EINVAL; |
| |
| mutex_lock(&ipa_pm_ctx->client_mutex); |
| |
| result = scnprintf(buf + cnt, size - cnt, "\n\nCurrent threshold: ["); |
| cnt += result; |
| |
| for (i = 0; i < clk->threshold_size; i++) { |
| result = scnprintf(buf + cnt, size - cnt, |
| "%d, ", clk->current_threshold[i]); |
| cnt += result; |
| } |
| |
| result = scnprintf(buf + cnt, size - cnt, "\b\b]\n"); |
| cnt += result; |
| |
| result = scnprintf(buf + cnt, size - cnt, |
| "Aggregated tput: %d, Cur vote: %d", |
| ipa_pm_ctx->aggregated_tput, clk->cur_vote); |
| cnt += result; |
| |
| result = scnprintf(buf + cnt, size - cnt, "\n\nRegistered Clients:\n"); |
| cnt += result; |
| |
| |
| for (i = 0; i < IPA_PM_MAX_CLIENTS; i++) { |
| client = ipa_pm_ctx->clients[i]; |
| |
| if (client == NULL) |
| continue; |
| |
| spin_lock_irqsave(&client->state_lock, flags); |
| if (client->group == IPA_PM_GROUP_DEFAULT) |
| tput = client->throughput; |
| else |
| tput = ipa_pm_ctx->group_tput[client->group]; |
| |
| result = scnprintf(buf + cnt, size - cnt, |
| "Client[%d]: %s State:%s\nGroup: %s Throughput: %d Pipes: ", |
| i, client->name, client_state_to_str[client->state], |
| ipa_pm_group_to_str[client->group], tput); |
| cnt += result; |
| |
| for (j = 0; j < IPA3_MAX_NUM_PIPES; j++) { |
| if (ipa_pm_ctx->clients_by_pipe[j] == client) { |
| result = scnprintf(buf + cnt, size - cnt, |
| "%d, ", j); |
| cnt += result; |
| } |
| } |
| |
| result = scnprintf(buf + cnt, size - cnt, "\b\b\n\n"); |
| cnt += result; |
| spin_unlock_irqrestore(&client->state_lock, flags); |
| } |
| mutex_unlock(&ipa_pm_ctx->client_mutex); |
| |
| return cnt; |
| } |
| |
| /** |
| * ipa_pm_exceptions_stat() - print PM exceptions stat |
| * @buf: [in] The user buff used to print |
| * @size: [in] The size of buf |
| * Returns: number of bytes used on success, negative on failure |
| * |
| * This function is called by ipa_debugfs in order to receive |
| * a full picture of the exceptions in the PM |
| */ |
| int ipa_pm_exceptions_stat(char *buf, int size) |
| { |
| int i, j, cnt = 0, result = 0; |
| struct ipa_pm_exception_list *exception; |
| |
| if (!buf || size < 0) |
| return -EINVAL; |
| |
| result = scnprintf(buf + cnt, size - cnt, "\n"); |
| cnt += result; |
| |
| mutex_lock(&ipa_pm_ctx->client_mutex); |
| for (i = 0; i < ipa_pm_ctx->clk_scaling.exception_size; i++) { |
| exception = &ipa_pm_ctx->clk_scaling.exception_list[i]; |
| if (exception == NULL) { |
| result = scnprintf(buf + cnt, size - cnt, |
| "Exception %d is NULL\n\n", i); |
| cnt += result; |
| continue; |
| } |
| |
| result = scnprintf(buf + cnt, size - cnt, |
| "Exception %d: %s\nPending: %d Bitmask: %d Threshold: [" |
| , i, exception->clients, exception->pending, |
| exception->bitmask); |
| cnt += result; |
| for (j = 0; j < ipa_pm_ctx->clk_scaling.threshold_size; j++) { |
| result = scnprintf(buf + cnt, size - cnt, |
| "%d, ", exception->threshold[j]); |
| cnt += result; |
| } |
| result = scnprintf(buf + cnt, size - cnt, "\b\b]\n\n"); |
| cnt += result; |
| } |
| mutex_unlock(&ipa_pm_ctx->client_mutex); |
| |
| return cnt; |
| } |