blob: 1431dcf7a0357accede796597090821aa9aa8623 [file] [log] [blame]
/* Copyright (c) 2013-2016, 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/slab.h>
#include <linux/workqueue.h>
#include <linux/ipa.h>
#include "ipa_rm_dependency_graph.h"
#include "ipa_rm_i.h"
#include "ipa_common_i.h"
static const char *resource_name_to_str[IPA_RM_RESOURCE_MAX] = {
__stringify(IPA_RM_RESOURCE_Q6_PROD),
__stringify(IPA_RM_RESOURCE_USB_PROD),
__stringify(IPA_RM_RESOURCE_USB_DPL_DUMMY_PROD),
__stringify(IPA_RM_RESOURCE_HSIC_PROD),
__stringify(IPA_RM_RESOURCE_STD_ECM_PROD),
__stringify(IPA_RM_RESOURCE_RNDIS_PROD),
__stringify(IPA_RM_RESOURCE_WWAN_0_PROD),
__stringify(IPA_RM_RESOURCE_WLAN_PROD),
__stringify(IPA_RM_RESOURCE_ODU_ADAPT_PROD),
__stringify(IPA_RM_RESOURCE_MHI_PROD),
__stringify(IPA_RM_RESOURCE_Q6_CONS),
__stringify(IPA_RM_RESOURCE_USB_CONS),
__stringify(IPA_RM_RESOURCE_USB_DPL_CONS),
__stringify(IPA_RM_RESOURCE_HSIC_CONS),
__stringify(IPA_RM_RESOURCE_WLAN_CONS),
__stringify(IPA_RM_RESOURCE_APPS_CONS),
__stringify(IPA_RM_RESOURCE_ODU_ADAPT_CONS),
__stringify(IPA_RM_RESOURCE_MHI_CONS),
};
struct ipa_rm_profile_vote_type {
enum ipa_voltage_level volt[IPA_RM_RESOURCE_MAX];
enum ipa_voltage_level curr_volt;
u32 bw_prods[IPA_RM_RESOURCE_PROD_MAX];
u32 bw_cons[IPA_RM_RESOURCE_CONS_MAX];
u32 curr_bw;
};
struct ipa_rm_context_type {
struct ipa_rm_dep_graph *dep_graph;
struct workqueue_struct *ipa_rm_wq;
spinlock_t ipa_rm_lock;
struct ipa_rm_profile_vote_type prof_vote;
};
static struct ipa_rm_context_type *ipa_rm_ctx;
struct ipa_rm_notify_ipa_work_type {
struct work_struct work;
enum ipa_voltage_level volt;
u32 bandwidth_mbps;
};
/**
* ipa_rm_create_resource() - create resource
* @create_params: [in] parameters needed
* for resource initialization
*
* Returns: 0 on success, negative on failure
*
* This function is called by IPA RM client to initialize client's resources.
* This API should be called before any other IPA RM API on a given resource
* name.
*
*/
int ipa_rm_create_resource(struct ipa_rm_create_params *create_params)
{
struct ipa_rm_resource *resource;
unsigned long flags;
int result;
if (unlikely(!ipa_rm_ctx)) {
IPA_RM_ERR("IPA RM was not initialized\n");
return -EINVAL;
}
if (!create_params) {
IPA_RM_ERR("invalid args\n");
return -EINVAL;
}
IPA_RM_DBG("%s\n", ipa_rm_resource_str(create_params->name));
if (create_params->floor_voltage < 0 ||
create_params->floor_voltage >= IPA_VOLTAGE_MAX) {
IPA_RM_ERR("invalid voltage %d\n",
create_params->floor_voltage);
return -EINVAL;
}
spin_lock_irqsave(&ipa_rm_ctx->ipa_rm_lock, flags);
if (ipa_rm_dep_graph_get_resource(ipa_rm_ctx->dep_graph,
create_params->name,
&resource) == 0) {
IPA_RM_ERR("resource already exists\n");
result = -EEXIST;
goto bail;
}
result = ipa_rm_resource_create(create_params,
&resource);
if (result) {
IPA_RM_ERR("ipa_rm_resource_create() failed\n");
goto bail;
}
result = ipa_rm_dep_graph_add(ipa_rm_ctx->dep_graph, resource);
if (result) {
IPA_RM_ERR("ipa_rm_dep_graph_add() failed\n");
ipa_rm_resource_delete(resource);
goto bail;
}
bail:
spin_unlock_irqrestore(&ipa_rm_ctx->ipa_rm_lock, flags);
IPA_RM_DBG("EXIT with %d\n", result);
return result;
}
EXPORT_SYMBOL(ipa_rm_create_resource);
/**
* ipa_rm_delete_resource() - delete resource
* @resource_name: name of resource to be deleted
*
* Returns: 0 on success, negative on failure
*
* This function is called by IPA RM client to delete client's resources.
*
*/
int ipa_rm_delete_resource(enum ipa_rm_resource_name resource_name)
{
struct ipa_rm_resource *resource;
unsigned long flags;
int result;
if (unlikely(!ipa_rm_ctx)) {
IPA_RM_ERR("IPA RM was not initialized\n");
return -EINVAL;
}
IPA_RM_DBG("%s\n", ipa_rm_resource_str(resource_name));
spin_lock_irqsave(&ipa_rm_ctx->ipa_rm_lock, flags);
if (ipa_rm_dep_graph_get_resource(ipa_rm_ctx->dep_graph,
resource_name,
&resource) != 0) {
IPA_RM_ERR("resource does not exist\n");
result = -EINVAL;
goto bail;
}
result = ipa_rm_resource_delete(resource);
if (result) {
IPA_RM_ERR("ipa_rm_resource_delete() failed\n");
goto bail;
}
result = ipa_rm_dep_graph_remove(ipa_rm_ctx->dep_graph,
resource_name);
if (result) {
IPA_RM_ERR("ipa_rm_dep_graph_remove() failed\n");
goto bail;
}
bail:
spin_unlock_irqrestore(&ipa_rm_ctx->ipa_rm_lock, flags);
IPA_RM_DBG("EXIT with %d\n", result);
return result;
}
EXPORT_SYMBOL(ipa_rm_delete_resource);
static int _ipa_rm_add_dependency(enum ipa_rm_resource_name resource_name,
enum ipa_rm_resource_name depends_on_name,
bool userspace_dep)
{
unsigned long flags;
int result;
if (unlikely(!ipa_rm_ctx)) {
IPA_RM_ERR("IPA RM was not initialized\n");
return -EINVAL;
}
IPA_RM_DBG("%s -> %s\n", ipa_rm_resource_str(resource_name),
ipa_rm_resource_str(depends_on_name));
spin_lock_irqsave(&ipa_rm_ctx->ipa_rm_lock, flags);
result = ipa_rm_dep_graph_add_dependency(
ipa_rm_ctx->dep_graph,
resource_name,
depends_on_name,
userspace_dep);
spin_unlock_irqrestore(&ipa_rm_ctx->ipa_rm_lock, flags);
IPA_RM_DBG("EXIT with %d\n", result);
return result;
}
/**
* ipa_rm_add_dependency() - create dependency between 2 resources
* @resource_name: name of dependent resource
* @depends_on_name: name of its dependency
*
* Returns: 0 on success, negative on failure
*
* Side effects: IPA_RM_RESORCE_GRANTED could be generated
* in case client registered with IPA RM
*/
int ipa_rm_add_dependency(enum ipa_rm_resource_name resource_name,
enum ipa_rm_resource_name depends_on_name)
{
return _ipa_rm_add_dependency(resource_name, depends_on_name, false);
}
EXPORT_SYMBOL(ipa_rm_add_dependency);
/**
* ipa_rm_add_dependency_from_ioctl() - create dependency between 2 resources
* @resource_name: name of dependent resource
* @depends_on_name: name of its dependency
*
* This function is expected to be called from IOCTL and the dependency will be
* marked as is was added by the userspace.
*
* Returns: 0 on success, negative on failure
*
* Side effects: IPA_RM_RESORCE_GRANTED could be generated
* in case client registered with IPA RM
*/
int ipa_rm_add_dependency_from_ioctl(enum ipa_rm_resource_name resource_name,
enum ipa_rm_resource_name depends_on_name)
{
return _ipa_rm_add_dependency(resource_name, depends_on_name, true);
}
static int _ipa_rm_add_dependency_sync(enum ipa_rm_resource_name resource_name,
enum ipa_rm_resource_name depends_on_name,
bool userspsace_dep)
{
int result;
struct ipa_rm_resource *consumer;
unsigned long time;
unsigned long flags;
if (unlikely(!ipa_rm_ctx)) {
IPA_RM_ERR("IPA RM was not initialized\n");
return -EINVAL;
}
IPA_RM_DBG("%s -> %s\n", ipa_rm_resource_str(resource_name),
ipa_rm_resource_str(depends_on_name));
spin_lock_irqsave(&ipa_rm_ctx->ipa_rm_lock, flags);
result = ipa_rm_dep_graph_add_dependency(
ipa_rm_ctx->dep_graph,
resource_name,
depends_on_name,
userspsace_dep);
spin_unlock_irqrestore(&ipa_rm_ctx->ipa_rm_lock, flags);
if (result == -EINPROGRESS) {
ipa_rm_dep_graph_get_resource(ipa_rm_ctx->dep_graph,
depends_on_name,
&consumer);
IPA_RM_DBG("%s waits for GRANT of %s.\n",
ipa_rm_resource_str(resource_name),
ipa_rm_resource_str(depends_on_name));
time = wait_for_completion_timeout(
&((struct ipa_rm_resource_cons *)consumer)->
request_consumer_in_progress,
HZ * 5);
result = 0;
if (!time) {
IPA_RM_ERR("TIMEOUT waiting for %s GRANT event.",
ipa_rm_resource_str(depends_on_name));
result = -ETIME;
} else {
IPA_RM_DBG("%s waited for %s GRANT %lu time.\n",
ipa_rm_resource_str(resource_name),
ipa_rm_resource_str(depends_on_name),
time);
}
}
IPA_RM_DBG("EXIT with %d\n", result);
return result;
}
/**
* ipa_rm_add_dependency_sync() - Create a dependency between 2 resources
* in a synchronized fashion. In case a producer resource is in GRANTED state
* and the newly added consumer resource is in RELEASED state, the consumer
* entity will be requested and the function will block until the consumer
* is granted.
* @resource_name: name of dependent resource
* @depends_on_name: name of its dependency
*
* This function is expected to be called from IOCTL and the dependency will be
* marked as is was added by the userspace.
*
* Returns: 0 on success, negative on failure
*
* Side effects: May block. See documentation above.
*/
int ipa_rm_add_dependency_sync(enum ipa_rm_resource_name resource_name,
enum ipa_rm_resource_name depends_on_name)
{
return _ipa_rm_add_dependency_sync(resource_name, depends_on_name,
false);
}
EXPORT_SYMBOL(ipa_rm_add_dependency_sync);
/**
* ipa_rm_add_dependency_sync_from_ioctl() - Create a dependency between 2
* resources in a synchronized fashion. In case a producer resource is in
* GRANTED state and the newly added consumer resource is in RELEASED state,
* the consumer entity will be requested and the function will block until
* the consumer is granted.
* @resource_name: name of dependent resource
* @depends_on_name: name of its dependency
*
* Returns: 0 on success, negative on failure
*
* Side effects: May block. See documentation above.
*/
int ipa_rm_add_dependency_sync_from_ioctl(
enum ipa_rm_resource_name resource_name,
enum ipa_rm_resource_name depends_on_name)
{
return _ipa_rm_add_dependency_sync(resource_name, depends_on_name,
true);
}
static int _ipa_rm_delete_dependency(enum ipa_rm_resource_name resource_name,
enum ipa_rm_resource_name depends_on_name,
bool userspace_dep)
{
unsigned long flags;
int result;
if (unlikely(!ipa_rm_ctx)) {
IPA_RM_ERR("IPA RM was not initialized\n");
return -EINVAL;
}
IPA_RM_DBG("%s -> %s\n", ipa_rm_resource_str(resource_name),
ipa_rm_resource_str(depends_on_name));
spin_lock_irqsave(&ipa_rm_ctx->ipa_rm_lock, flags);
result = ipa_rm_dep_graph_delete_dependency(
ipa_rm_ctx->dep_graph,
resource_name,
depends_on_name,
userspace_dep);
spin_unlock_irqrestore(&ipa_rm_ctx->ipa_rm_lock, flags);
IPA_RM_DBG("EXIT with %d\n", result);
return result;
}
/**
* ipa_rm_delete_dependency() - delete dependency between 2 resources
* @resource_name: name of dependent resource
* @depends_on_name: name of its dependency
*
* Returns: 0 on success, negative on failure
*
* Side effects: IPA_RM_RESORCE_GRANTED could be generated
* in case client registered with IPA RM
*/
int ipa_rm_delete_dependency(enum ipa_rm_resource_name resource_name,
enum ipa_rm_resource_name depends_on_name)
{
return _ipa_rm_delete_dependency(resource_name, depends_on_name, false);
}
EXPORT_SYMBOL(ipa_rm_delete_dependency);
/**
* ipa_rm_delete_dependency_fron_ioctl() - delete dependency between 2 resources
* @resource_name: name of dependent resource
* @depends_on_name: name of its dependency
*
* This function is expected to be called from IOCTL and the dependency will be
* marked as is was added by the userspace.
*
* Returns: 0 on success, negative on failure
*
* Side effects: IPA_RM_RESORCE_GRANTED could be generated
* in case client registered with IPA RM
*/
int ipa_rm_delete_dependency_from_ioctl(enum ipa_rm_resource_name resource_name,
enum ipa_rm_resource_name depends_on_name)
{
return _ipa_rm_delete_dependency(resource_name, depends_on_name, true);
}
/**
* ipa_rm_request_resource() - request resource
* @resource_name: [in] name of the requested resource
*
* Returns: 0 on success, negative on failure
*
* All registered callbacks are called with IPA_RM_RESOURCE_GRANTED
* on successful completion of this operation.
*/
int ipa_rm_request_resource(enum ipa_rm_resource_name resource_name)
{
struct ipa_rm_resource *resource;
unsigned long flags;
int result;
if (unlikely(!ipa_rm_ctx)) {
IPA_RM_ERR("IPA RM was not initialized\n");
return -EINVAL;
}
if (!IPA_RM_RESORCE_IS_PROD(resource_name)) {
IPA_RM_ERR("can be called on PROD only\n");
return -EINVAL;
}
spin_lock_irqsave(&ipa_rm_ctx->ipa_rm_lock, flags);
if (ipa_rm_dep_graph_get_resource(ipa_rm_ctx->dep_graph,
resource_name,
&resource) != 0) {
IPA_RM_ERR("resource does not exists\n");
result = -EPERM;
goto bail;
}
result = ipa_rm_resource_producer_request(
(struct ipa_rm_resource_prod *)resource);
bail:
spin_unlock_irqrestore(&ipa_rm_ctx->ipa_rm_lock, flags);
return result;
}
EXPORT_SYMBOL(ipa_rm_request_resource);
void delayed_release_work_func(struct work_struct *work)
{
unsigned long flags;
struct ipa_rm_resource *resource;
struct ipa_rm_delayed_release_work_type *rwork = container_of(
to_delayed_work(work),
struct ipa_rm_delayed_release_work_type,
work);
if (!IPA_RM_RESORCE_IS_CONS(rwork->resource_name)) {
IPA_RM_ERR("can be called on CONS only\n");
kfree(rwork);
return;
}
spin_lock_irqsave(&ipa_rm_ctx->ipa_rm_lock, flags);
if (ipa_rm_dep_graph_get_resource(ipa_rm_ctx->dep_graph,
rwork->resource_name,
&resource) != 0) {
IPA_RM_ERR("resource does not exists\n");
goto bail;
}
ipa_rm_resource_consumer_release(
(struct ipa_rm_resource_cons *)resource, rwork->needed_bw,
rwork->dec_usage_count);
bail:
spin_unlock_irqrestore(&ipa_rm_ctx->ipa_rm_lock, flags);
kfree(rwork);
}
/**
* ipa_rm_request_resource_with_timer() - requests the specified consumer
* resource and releases it after 1 second
* @resource_name: name of the requested resource
*
* Returns: 0 on success, negative on failure
*/
int ipa_rm_request_resource_with_timer(enum ipa_rm_resource_name resource_name)
{
unsigned long flags;
struct ipa_rm_resource *resource;
struct ipa_rm_delayed_release_work_type *release_work;
int result;
if (!IPA_RM_RESORCE_IS_CONS(resource_name)) {
IPA_RM_ERR("can be called on CONS only\n");
return -EINVAL;
}
spin_lock_irqsave(&ipa_rm_ctx->ipa_rm_lock, flags);
if (ipa_rm_dep_graph_get_resource(ipa_rm_ctx->dep_graph,
resource_name,
&resource) != 0) {
IPA_RM_ERR("resource does not exists\n");
result = -EPERM;
goto bail;
}
result = ipa_rm_resource_consumer_request(
(struct ipa_rm_resource_cons *)resource, 0, false, true);
if (result != 0 && result != -EINPROGRESS) {
IPA_RM_ERR("consumer request returned error %d\n", result);
result = -EPERM;
goto bail;
}
release_work = kzalloc(sizeof(*release_work), GFP_ATOMIC);
if (!release_work) {
result = -ENOMEM;
goto bail;
}
release_work->resource_name = resource->name;
release_work->needed_bw = 0;
release_work->dec_usage_count = false;
INIT_DELAYED_WORK(&release_work->work, delayed_release_work_func);
schedule_delayed_work(&release_work->work,
msecs_to_jiffies(IPA_RM_RELEASE_DELAY_IN_MSEC));
result = 0;
bail:
spin_unlock_irqrestore(&ipa_rm_ctx->ipa_rm_lock, flags);
return result;
}
/**
* ipa_rm_release_resource() - release resource
* @resource_name: [in] name of the requested resource
*
* Returns: 0 on success, negative on failure
*
* All registered callbacks are called with IPA_RM_RESOURCE_RELEASED
* on successful completion of this operation.
*/
int ipa_rm_release_resource(enum ipa_rm_resource_name resource_name)
{
unsigned long flags;
struct ipa_rm_resource *resource;
int result;
if (unlikely(!ipa_rm_ctx)) {
IPA_RM_ERR("IPA RM was not initialized\n");
return -EINVAL;
}
if (!IPA_RM_RESORCE_IS_PROD(resource_name)) {
IPA_RM_ERR("can be called on PROD only\n");
return -EINVAL;
}
spin_lock_irqsave(&ipa_rm_ctx->ipa_rm_lock, flags);
if (ipa_rm_dep_graph_get_resource(ipa_rm_ctx->dep_graph,
resource_name,
&resource) != 0) {
IPA_RM_ERR("resource does not exists\n");
result = -EPERM;
goto bail;
}
result = ipa_rm_resource_producer_release(
(struct ipa_rm_resource_prod *)resource);
bail:
spin_unlock_irqrestore(&ipa_rm_ctx->ipa_rm_lock, flags);
return result;
}
EXPORT_SYMBOL(ipa_rm_release_resource);
/**
* ipa_rm_register() - register for event
* @resource_name: resource name
* @reg_params: [in] registration parameters
*
* Returns: 0 on success, negative on failure
*
* Registration parameters provided here should be the same
* as provided later in ipa_rm_deregister() call.
*/
int ipa_rm_register(enum ipa_rm_resource_name resource_name,
struct ipa_rm_register_params *reg_params)
{
int result;
unsigned long flags;
struct ipa_rm_resource *resource;
IPA_RM_DBG("%s\n", ipa_rm_resource_str(resource_name));
if (!IPA_RM_RESORCE_IS_PROD(resource_name)) {
IPA_RM_ERR("can be called on PROD only\n");
return -EINVAL;
}
spin_lock_irqsave(&ipa_rm_ctx->ipa_rm_lock, flags);
if (ipa_rm_dep_graph_get_resource(ipa_rm_ctx->dep_graph,
resource_name,
&resource) != 0) {
IPA_RM_ERR("resource does not exists\n");
result = -EPERM;
goto bail;
}
result = ipa_rm_resource_producer_register(
(struct ipa_rm_resource_prod *)resource,
reg_params,
true);
bail:
spin_unlock_irqrestore(&ipa_rm_ctx->ipa_rm_lock, flags);
IPA_RM_DBG("EXIT with %d\n", result);
return result;
}
EXPORT_SYMBOL(ipa_rm_register);
/**
* ipa_rm_deregister() - cancel the registration
* @resource_name: resource name
* @reg_params: [in] registration parameters
*
* Returns: 0 on success, negative on failure
*
* Registration parameters provided here should be the same
* as provided in ipa_rm_register() call.
*/
int ipa_rm_deregister(enum ipa_rm_resource_name resource_name,
struct ipa_rm_register_params *reg_params)
{
int result;
unsigned long flags;
struct ipa_rm_resource *resource;
IPA_RM_DBG("%s\n", ipa_rm_resource_str(resource_name));
if (!IPA_RM_RESORCE_IS_PROD(resource_name)) {
IPA_RM_ERR("can be called on PROD only\n");
return -EINVAL;
}
spin_lock_irqsave(&ipa_rm_ctx->ipa_rm_lock, flags);
if (ipa_rm_dep_graph_get_resource(ipa_rm_ctx->dep_graph,
resource_name,
&resource) != 0) {
IPA_RM_ERR("resource does not exists\n");
result = -EPERM;
goto bail;
}
result = ipa_rm_resource_producer_deregister(
(struct ipa_rm_resource_prod *)resource,
reg_params);
bail:
spin_unlock_irqrestore(&ipa_rm_ctx->ipa_rm_lock, flags);
IPA_RM_DBG("EXIT with %d\n", result);
return result;
}
EXPORT_SYMBOL(ipa_rm_deregister);
/**
* ipa_rm_set_perf_profile() - set performance profile
* @resource_name: resource name
* @profile: [in] profile information.
*
* Returns: 0 on success, negative on failure
*
* Set resource performance profile.
* Updates IPA driver if performance level changed.
*/
int ipa_rm_set_perf_profile(enum ipa_rm_resource_name resource_name,
struct ipa_rm_perf_profile *profile)
{
int result;
unsigned long flags;
struct ipa_rm_resource *resource;
if (unlikely(!ipa_rm_ctx)) {
IPA_RM_ERR("IPA RM was not initialized\n");
return -EINVAL;
}
IPA_RM_DBG("%s\n", ipa_rm_resource_str(resource_name));
if (profile)
IPA_RM_DBG("BW: %d\n", profile->max_supported_bandwidth_mbps);
spin_lock_irqsave(&ipa_rm_ctx->ipa_rm_lock, flags);
if (ipa_rm_dep_graph_get_resource(ipa_rm_ctx->dep_graph,
resource_name,
&resource) != 0) {
IPA_RM_ERR("resource does not exists\n");
result = -EPERM;
goto bail;
}
result = ipa_rm_resource_set_perf_profile(resource, profile);
if (result) {
IPA_RM_ERR("ipa_rm_resource_set_perf_profile failed %d\n",
result);
goto bail;
}
result = 0;
bail:
spin_unlock_irqrestore(&ipa_rm_ctx->ipa_rm_lock, flags);
IPA_RM_DBG("EXIT with %d\n", result);
return result;
}
EXPORT_SYMBOL(ipa_rm_set_perf_profile);
/**
* ipa_rm_notify_completion() -
* consumer driver notification for
* request_resource / release_resource operations
* completion
* @event: notified event
* @resource_name: resource name
*
* Returns: 0 on success, negative on failure
*/
int ipa_rm_notify_completion(enum ipa_rm_event event,
enum ipa_rm_resource_name resource_name)
{
int result;
if (unlikely(!ipa_rm_ctx)) {
IPA_RM_ERR("IPA RM was not initialized\n");
return -EINVAL;
}
IPA_RM_DBG("event %d on %s\n", event,
ipa_rm_resource_str(resource_name));
if (!IPA_RM_RESORCE_IS_CONS(resource_name)) {
IPA_RM_ERR("can be called on CONS only\n");
result = -EINVAL;
goto bail;
}
ipa_rm_wq_send_cmd(IPA_RM_WQ_RESOURCE_CB,
resource_name,
event,
false);
result = 0;
bail:
IPA_RM_DBG("EXIT with %d\n", result);
return result;
}
EXPORT_SYMBOL(ipa_rm_notify_completion);
static void ipa_rm_wq_handler(struct work_struct *work)
{
unsigned long flags;
struct ipa_rm_resource *resource;
struct ipa_rm_wq_work_type *ipa_rm_work =
container_of(work,
struct ipa_rm_wq_work_type,
work);
IPA_RM_DBG_LOW("%s cmd=%d event=%d notify_registered_only=%d\n",
ipa_rm_resource_str(ipa_rm_work->resource_name),
ipa_rm_work->wq_cmd,
ipa_rm_work->event,
ipa_rm_work->notify_registered_only);
switch (ipa_rm_work->wq_cmd) {
case IPA_RM_WQ_NOTIFY_PROD:
if (!IPA_RM_RESORCE_IS_PROD(ipa_rm_work->resource_name)) {
IPA_RM_ERR("resource is not PROD\n");
goto free_work;
}
spin_lock_irqsave(&ipa_rm_ctx->ipa_rm_lock, flags);
if (ipa_rm_dep_graph_get_resource(ipa_rm_ctx->dep_graph,
ipa_rm_work->resource_name,
&resource) != 0){
IPA_RM_ERR("resource does not exists\n");
spin_unlock_irqrestore(&ipa_rm_ctx->ipa_rm_lock, flags);
goto free_work;
}
ipa_rm_resource_producer_notify_clients(
(struct ipa_rm_resource_prod *)resource,
ipa_rm_work->event,
ipa_rm_work->notify_registered_only);
spin_unlock_irqrestore(&ipa_rm_ctx->ipa_rm_lock, flags);
break;
case IPA_RM_WQ_NOTIFY_CONS:
break;
case IPA_RM_WQ_RESOURCE_CB:
spin_lock_irqsave(&ipa_rm_ctx->ipa_rm_lock, flags);
if (ipa_rm_dep_graph_get_resource(ipa_rm_ctx->dep_graph,
ipa_rm_work->resource_name,
&resource) != 0){
IPA_RM_ERR("resource does not exists\n");
spin_unlock_irqrestore(&ipa_rm_ctx->ipa_rm_lock, flags);
goto free_work;
}
ipa_rm_resource_consumer_handle_cb(
(struct ipa_rm_resource_cons *)resource,
ipa_rm_work->event);
spin_unlock_irqrestore(&ipa_rm_ctx->ipa_rm_lock, flags);
break;
default:
break;
}
free_work:
kfree((void *) work);
}
static void ipa_rm_wq_resume_handler(struct work_struct *work)
{
unsigned long flags;
struct ipa_rm_resource *resource;
struct ipa_rm_wq_suspend_resume_work_type *ipa_rm_work =
container_of(work,
struct ipa_rm_wq_suspend_resume_work_type,
work);
IPA_RM_DBG_LOW("resume work handler: %s",
ipa_rm_resource_str(ipa_rm_work->resource_name));
if (!IPA_RM_RESORCE_IS_CONS(ipa_rm_work->resource_name)) {
IPA_RM_ERR("resource is not CONS\n");
return;
}
IPA_ACTIVE_CLIENTS_INC_RESOURCE(ipa_rm_resource_str(
ipa_rm_work->resource_name));
spin_lock_irqsave(&ipa_rm_ctx->ipa_rm_lock, flags);
if (ipa_rm_dep_graph_get_resource(ipa_rm_ctx->dep_graph,
ipa_rm_work->resource_name,
&resource) != 0){
IPA_RM_ERR("resource does not exists\n");
spin_unlock_irqrestore(&ipa_rm_ctx->ipa_rm_lock, flags);
IPA_ACTIVE_CLIENTS_DEC_RESOURCE(ipa_rm_resource_str(
ipa_rm_work->resource_name));
goto bail;
}
ipa_rm_resource_consumer_request_work(
(struct ipa_rm_resource_cons *)resource,
ipa_rm_work->prev_state, ipa_rm_work->needed_bw, true,
ipa_rm_work->inc_usage_count);
spin_unlock_irqrestore(&ipa_rm_ctx->ipa_rm_lock, flags);
bail:
kfree(ipa_rm_work);
}
static void ipa_rm_wq_suspend_handler(struct work_struct *work)
{
unsigned long flags;
struct ipa_rm_resource *resource;
struct ipa_rm_wq_suspend_resume_work_type *ipa_rm_work =
container_of(work,
struct ipa_rm_wq_suspend_resume_work_type,
work);
IPA_RM_DBG_LOW("suspend work handler: %s",
ipa_rm_resource_str(ipa_rm_work->resource_name));
if (!IPA_RM_RESORCE_IS_CONS(ipa_rm_work->resource_name)) {
IPA_RM_ERR("resource is not CONS\n");
return;
}
ipa_suspend_resource_sync(ipa_rm_work->resource_name);
spin_lock_irqsave(&ipa_rm_ctx->ipa_rm_lock, flags);
if (ipa_rm_dep_graph_get_resource(ipa_rm_ctx->dep_graph,
ipa_rm_work->resource_name,
&resource) != 0){
IPA_RM_ERR("resource does not exists\n");
spin_unlock_irqrestore(&ipa_rm_ctx->ipa_rm_lock, flags);
return;
}
ipa_rm_resource_consumer_release_work(
(struct ipa_rm_resource_cons *)resource,
ipa_rm_work->prev_state,
true);
spin_unlock_irqrestore(&ipa_rm_ctx->ipa_rm_lock, flags);
kfree(ipa_rm_work);
}
/**
* ipa_rm_wq_send_cmd() - send a command for deferred work
* @wq_cmd: command that should be executed
* @resource_name: resource on which command should be executed
* @notify_registered_only: notify only clients registered by
* ipa_rm_register()
*
* Returns: 0 on success, negative otherwise
*/
int ipa_rm_wq_send_cmd(enum ipa_rm_wq_cmd wq_cmd,
enum ipa_rm_resource_name resource_name,
enum ipa_rm_event event,
bool notify_registered_only)
{
int result = -ENOMEM;
struct ipa_rm_wq_work_type *work = kzalloc(sizeof(*work), GFP_ATOMIC);
if (work) {
INIT_WORK((struct work_struct *)work, ipa_rm_wq_handler);
work->wq_cmd = wq_cmd;
work->resource_name = resource_name;
work->event = event;
work->notify_registered_only = notify_registered_only;
result = queue_work(ipa_rm_ctx->ipa_rm_wq,
(struct work_struct *)work);
} else {
IPA_RM_ERR("no mem\n");
}
return result;
}
int ipa_rm_wq_send_suspend_cmd(enum ipa_rm_resource_name resource_name,
enum ipa_rm_resource_state prev_state,
u32 needed_bw)
{
int result = -ENOMEM;
struct ipa_rm_wq_suspend_resume_work_type *work = kzalloc(sizeof(*work),
GFP_ATOMIC);
if (work) {
INIT_WORK((struct work_struct *)work,
ipa_rm_wq_suspend_handler);
work->resource_name = resource_name;
work->prev_state = prev_state;
work->needed_bw = needed_bw;
result = queue_work(ipa_rm_ctx->ipa_rm_wq,
(struct work_struct *)work);
} else {
IPA_RM_ERR("no mem\n");
}
return result;
}
int ipa_rm_wq_send_resume_cmd(enum ipa_rm_resource_name resource_name,
enum ipa_rm_resource_state prev_state,
u32 needed_bw,
bool inc_usage_count)
{
int result = -ENOMEM;
struct ipa_rm_wq_suspend_resume_work_type *work = kzalloc(sizeof(*work),
GFP_ATOMIC);
if (work) {
INIT_WORK((struct work_struct *)work, ipa_rm_wq_resume_handler);
work->resource_name = resource_name;
work->prev_state = prev_state;
work->needed_bw = needed_bw;
work->inc_usage_count = inc_usage_count;
result = queue_work(ipa_rm_ctx->ipa_rm_wq,
(struct work_struct *)work);
} else {
IPA_RM_ERR("no mem\n");
}
return result;
}
/**
* ipa_rm_initialize() - initialize IPA RM component
*
* Returns: 0 on success, negative otherwise
*/
int ipa_rm_initialize(void)
{
int result;
ipa_rm_ctx = kzalloc(sizeof(*ipa_rm_ctx), GFP_KERNEL);
if (!ipa_rm_ctx) {
IPA_RM_ERR("no mem\n");
result = -ENOMEM;
goto bail;
}
ipa_rm_ctx->ipa_rm_wq = create_singlethread_workqueue("ipa_rm_wq");
if (!ipa_rm_ctx->ipa_rm_wq) {
IPA_RM_ERR("create workqueue failed\n");
result = -ENOMEM;
goto create_wq_fail;
}
result = ipa_rm_dep_graph_create(&(ipa_rm_ctx->dep_graph));
if (result) {
IPA_RM_ERR("create dependency graph failed\n");
goto graph_alloc_fail;
}
spin_lock_init(&ipa_rm_ctx->ipa_rm_lock);
IPA_RM_DBG("SUCCESS\n");
return 0;
graph_alloc_fail:
destroy_workqueue(ipa_rm_ctx->ipa_rm_wq);
create_wq_fail:
kfree(ipa_rm_ctx);
bail:
return result;
}
/**
* ipa_rm_stat() - print RM 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 current state of the RM
*/
int ipa_rm_stat(char *buf, int size)
{
unsigned long flags;
int i, cnt = 0, result = EINVAL;
struct ipa_rm_resource *resource = NULL;
u32 sum_bw_prod = 0;
u32 sum_bw_cons = 0;
if (!buf || size < 0)
return result;
spin_lock_irqsave(&ipa_rm_ctx->ipa_rm_lock, flags);
for (i = 0; i < IPA_RM_RESOURCE_PROD_MAX; ++i) {
result = ipa_rm_dep_graph_get_resource(
ipa_rm_ctx->dep_graph,
i,
&resource);
if (!result) {
result = ipa_rm_resource_producer_print_stat(
resource, buf + cnt,
size-cnt);
if (result < 0)
goto bail;
cnt += result;
}
}
for (i = 0; i < IPA_RM_RESOURCE_PROD_MAX; i++)
sum_bw_prod += ipa_rm_ctx->prof_vote.bw_prods[i];
for (i = 0; i < IPA_RM_RESOURCE_CONS_MAX; i++)
sum_bw_cons += ipa_rm_ctx->prof_vote.bw_cons[i];
result = scnprintf(buf + cnt, size - cnt,
"All prod bandwidth: %d, All cons bandwidth: %d\n",
sum_bw_prod, sum_bw_cons);
cnt += result;
result = scnprintf(buf + cnt, size - cnt,
"Voting: voltage %d, bandwidth %d\n",
ipa_rm_ctx->prof_vote.curr_volt,
ipa_rm_ctx->prof_vote.curr_bw);
cnt += result;
result = cnt;
bail:
spin_unlock_irqrestore(&ipa_rm_ctx->ipa_rm_lock, flags);
return result;
}
/**
* ipa_rm_resource_str() - returns string that represent the resource
* @resource_name: [in] resource name
*/
const char *ipa_rm_resource_str(enum ipa_rm_resource_name resource_name)
{
if (resource_name < 0 || resource_name >= IPA_RM_RESOURCE_MAX)
return "INVALID RESOURCE";
return resource_name_to_str[resource_name];
};
static void ipa_rm_perf_profile_notify_to_ipa_work(struct work_struct *work)
{
struct ipa_rm_notify_ipa_work_type *notify_work = container_of(work,
struct ipa_rm_notify_ipa_work_type,
work);
int res;
IPA_RM_DBG_LOW("calling to IPA driver. voltage %d bandwidth %d\n",
notify_work->volt, notify_work->bandwidth_mbps);
res = ipa_set_required_perf_profile(notify_work->volt,
notify_work->bandwidth_mbps);
if (res) {
IPA_RM_ERR("ipa_set_required_perf_profile failed %d\n", res);
goto bail;
}
IPA_RM_DBG_LOW("IPA driver notified\n");
bail:
kfree(notify_work);
}
static void ipa_rm_perf_profile_notify_to_ipa(enum ipa_voltage_level volt,
u32 bandwidth)
{
struct ipa_rm_notify_ipa_work_type *work;
work = kzalloc(sizeof(*work), GFP_ATOMIC);
if (!work) {
IPA_RM_ERR("no mem\n");
return;
}
INIT_WORK(&work->work, ipa_rm_perf_profile_notify_to_ipa_work);
work->volt = volt;
work->bandwidth_mbps = bandwidth;
queue_work(ipa_rm_ctx->ipa_rm_wq, &work->work);
}
/**
* ipa_rm_perf_profile_change() - change performance profile vote for resource
* @resource_name: [in] resource name
*
* change bandwidth and voltage vote based on resource state.
*/
void ipa_rm_perf_profile_change(enum ipa_rm_resource_name resource_name)
{
enum ipa_voltage_level old_volt;
u32 *bw_ptr;
u32 old_bw;
struct ipa_rm_resource *resource;
int i;
u32 sum_bw_prod = 0;
u32 sum_bw_cons = 0;
IPA_RM_DBG_LOW("%s\n", ipa_rm_resource_str(resource_name));
if (ipa_rm_dep_graph_get_resource(ipa_rm_ctx->dep_graph,
resource_name,
&resource) != 0) {
IPA_RM_ERR("resource does not exists\n");
WARN_ON(1);
return;
}
old_volt = ipa_rm_ctx->prof_vote.curr_volt;
old_bw = ipa_rm_ctx->prof_vote.curr_bw;
if (IPA_RM_RESORCE_IS_PROD(resource_name)) {
bw_ptr = &ipa_rm_ctx->prof_vote.bw_prods[resource_name];
} else if (IPA_RM_RESORCE_IS_CONS(resource_name)) {
bw_ptr = &ipa_rm_ctx->prof_vote.bw_cons[
resource_name - IPA_RM_RESOURCE_PROD_MAX];
} else {
IPA_RM_ERR("Invalid resource_name\n");
return;
}
switch (resource->state) {
case IPA_RM_GRANTED:
case IPA_RM_REQUEST_IN_PROGRESS:
IPA_RM_DBG_LOW("max_bw = %d, needed_bw = %d\n",
resource->max_bw, resource->needed_bw);
*bw_ptr = min(resource->max_bw, resource->needed_bw);
ipa_rm_ctx->prof_vote.volt[resource_name] =
resource->floor_voltage;
break;
case IPA_RM_RELEASE_IN_PROGRESS:
case IPA_RM_RELEASED:
*bw_ptr = 0;
ipa_rm_ctx->prof_vote.volt[resource_name] = 0;
break;
default:
IPA_RM_ERR("unknown state %d\n", resource->state);
WARN_ON(1);
return;
}
IPA_RM_DBG_LOW("resource bandwidth: %d voltage: %d\n", *bw_ptr,
resource->floor_voltage);
ipa_rm_ctx->prof_vote.curr_volt = IPA_VOLTAGE_UNSPECIFIED;
for (i = 0; i < IPA_RM_RESOURCE_MAX; i++) {
if (ipa_rm_ctx->prof_vote.volt[i] >
ipa_rm_ctx->prof_vote.curr_volt) {
ipa_rm_ctx->prof_vote.curr_volt =
ipa_rm_ctx->prof_vote.volt[i];
}
}
for (i = 0; i < IPA_RM_RESOURCE_PROD_MAX; i++)
sum_bw_prod += ipa_rm_ctx->prof_vote.bw_prods[i];
for (i = 0; i < IPA_RM_RESOURCE_CONS_MAX; i++)
sum_bw_cons += ipa_rm_ctx->prof_vote.bw_cons[i];
IPA_RM_DBG_LOW("all prod bandwidth: %d all cons bandwidth: %d\n",
sum_bw_prod, sum_bw_cons);
ipa_rm_ctx->prof_vote.curr_bw = min(sum_bw_prod, sum_bw_cons);
if (ipa_rm_ctx->prof_vote.curr_volt == old_volt &&
ipa_rm_ctx->prof_vote.curr_bw == old_bw) {
IPA_RM_DBG_LOW("same voting\n");
return;
}
IPA_RM_DBG_LOW("new voting: voltage %d bandwidth %d\n",
ipa_rm_ctx->prof_vote.curr_volt,
ipa_rm_ctx->prof_vote.curr_bw);
ipa_rm_perf_profile_notify_to_ipa(ipa_rm_ctx->prof_vote.curr_volt,
ipa_rm_ctx->prof_vote.curr_bw);
return;
};
/**
* ipa_rm_exit() - free all IPA RM resources
*/
void ipa_rm_exit(void)
{
IPA_RM_DBG("ENTER\n");
ipa_rm_dep_graph_delete(ipa_rm_ctx->dep_graph);
destroy_workqueue(ipa_rm_ctx->ipa_rm_wq);
kfree(ipa_rm_ctx);
ipa_rm_ctx = NULL;
IPA_RM_DBG("EXIT\n");
}