/* Copyright (c) 2014-2018, 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. * */ #define pr_fmt(fmt) "[drm:%s:%d]: " fmt, __func__, __LINE__ #include #include #include #include #include #include #include #include "dpu_power_handle.h" #include "dpu_trace.h" static const char *data_bus_name[DPU_POWER_HANDLE_DBUS_ID_MAX] = { [DPU_POWER_HANDLE_DBUS_ID_MNOC] = "qcom,dpu-data-bus", [DPU_POWER_HANDLE_DBUS_ID_LLCC] = "qcom,dpu-llcc-bus", [DPU_POWER_HANDLE_DBUS_ID_EBI] = "qcom,dpu-ebi-bus", }; const char *dpu_power_handle_get_dbus_name(u32 bus_id) { if (bus_id < DPU_POWER_HANDLE_DBUS_ID_MAX) return data_bus_name[bus_id]; return NULL; } static void dpu_power_event_trigger_locked(struct dpu_power_handle *phandle, u32 event_type) { struct dpu_power_event *event; list_for_each_entry(event, &phandle->event_list, list) { if (event->event_type & event_type) event->cb_fnc(event_type, event->usr); } } struct dpu_power_client *dpu_power_client_create( struct dpu_power_handle *phandle, char *client_name) { struct dpu_power_client *client; static u32 id; if (!client_name || !phandle) { pr_err("client name is null or invalid power data\n"); return ERR_PTR(-EINVAL); } client = kzalloc(sizeof(struct dpu_power_client), GFP_KERNEL); if (!client) return ERR_PTR(-ENOMEM); mutex_lock(&phandle->phandle_lock); strlcpy(client->name, client_name, MAX_CLIENT_NAME_LEN); client->usecase_ndx = VOTE_INDEX_DISABLE; client->id = id; client->active = true; pr_debug("client %s created:%pK id :%d\n", client_name, client, id); id++; list_add(&client->list, &phandle->power_client_clist); mutex_unlock(&phandle->phandle_lock); return client; } void dpu_power_client_destroy(struct dpu_power_handle *phandle, struct dpu_power_client *client) { if (!client || !phandle) { pr_err("reg bus vote: invalid client handle\n"); } else if (!client->active) { pr_err("dpu power deinit already done\n"); kfree(client); } else { pr_debug("bus vote client %s destroyed:%pK id:%u\n", client->name, client, client->id); mutex_lock(&phandle->phandle_lock); list_del_init(&client->list); mutex_unlock(&phandle->phandle_lock); kfree(client); } } void dpu_power_resource_init(struct platform_device *pdev, struct dpu_power_handle *phandle) { phandle->dev = &pdev->dev; INIT_LIST_HEAD(&phandle->power_client_clist); INIT_LIST_HEAD(&phandle->event_list); mutex_init(&phandle->phandle_lock); } void dpu_power_resource_deinit(struct platform_device *pdev, struct dpu_power_handle *phandle) { struct dpu_power_client *curr_client, *next_client; struct dpu_power_event *curr_event, *next_event; if (!phandle || !pdev) { pr_err("invalid input param\n"); return; } mutex_lock(&phandle->phandle_lock); list_for_each_entry_safe(curr_client, next_client, &phandle->power_client_clist, list) { pr_err("client:%s-%d still registered with refcount:%d\n", curr_client->name, curr_client->id, curr_client->refcount); curr_client->active = false; list_del(&curr_client->list); } list_for_each_entry_safe(curr_event, next_event, &phandle->event_list, list) { pr_err("event:%d, client:%s still registered\n", curr_event->event_type, curr_event->client_name); curr_event->active = false; list_del(&curr_event->list); } mutex_unlock(&phandle->phandle_lock); } int dpu_power_resource_enable(struct dpu_power_handle *phandle, struct dpu_power_client *pclient, bool enable) { bool changed = false; u32 max_usecase_ndx = VOTE_INDEX_DISABLE, prev_usecase_ndx; struct dpu_power_client *client; if (!phandle || !pclient) { pr_err("invalid input argument\n"); return -EINVAL; } mutex_lock(&phandle->phandle_lock); if (enable) pclient->refcount++; else if (pclient->refcount) pclient->refcount--; if (pclient->refcount) pclient->usecase_ndx = VOTE_INDEX_LOW; else pclient->usecase_ndx = VOTE_INDEX_DISABLE; list_for_each_entry(client, &phandle->power_client_clist, list) { if (client->usecase_ndx < VOTE_INDEX_MAX && client->usecase_ndx > max_usecase_ndx) max_usecase_ndx = client->usecase_ndx; } if (phandle->current_usecase_ndx != max_usecase_ndx) { changed = true; prev_usecase_ndx = phandle->current_usecase_ndx; phandle->current_usecase_ndx = max_usecase_ndx; } pr_debug("%pS: changed=%d current idx=%d request client %s id:%u enable:%d refcount:%d\n", __builtin_return_address(0), changed, max_usecase_ndx, pclient->name, pclient->id, enable, pclient->refcount); if (!changed) goto end; if (enable) { dpu_power_event_trigger_locked(phandle, DPU_POWER_EVENT_PRE_ENABLE); dpu_power_event_trigger_locked(phandle, DPU_POWER_EVENT_POST_ENABLE); } else { dpu_power_event_trigger_locked(phandle, DPU_POWER_EVENT_PRE_DISABLE); dpu_power_event_trigger_locked(phandle, DPU_POWER_EVENT_POST_DISABLE); } end: mutex_unlock(&phandle->phandle_lock); return 0; } struct dpu_power_event *dpu_power_handle_register_event( struct dpu_power_handle *phandle, u32 event_type, void (*cb_fnc)(u32 event_type, void *usr), void *usr, char *client_name) { struct dpu_power_event *event; if (!phandle) { pr_err("invalid power handle\n"); return ERR_PTR(-EINVAL); } else if (!cb_fnc || !event_type) { pr_err("no callback fnc or event type\n"); return ERR_PTR(-EINVAL); } event = kzalloc(sizeof(struct dpu_power_event), GFP_KERNEL); if (!event) return ERR_PTR(-ENOMEM); event->event_type = event_type; event->cb_fnc = cb_fnc; event->usr = usr; strlcpy(event->client_name, client_name, MAX_CLIENT_NAME_LEN); event->active = true; mutex_lock(&phandle->phandle_lock); list_add(&event->list, &phandle->event_list); mutex_unlock(&phandle->phandle_lock); return event; } void dpu_power_handle_unregister_event( struct dpu_power_handle *phandle, struct dpu_power_event *event) { if (!phandle || !event) { pr_err("invalid phandle or event\n"); } else if (!event->active) { pr_err("power handle deinit already done\n"); kfree(event); } else { mutex_lock(&phandle->phandle_lock); list_del_init(&event->list); mutex_unlock(&phandle->phandle_lock); kfree(event); } }