blob: a7029104e886a8932de43b3f52da8cf3d031e5a4 [file] [log] [blame]
/* Copyright (c) 2015-2016, 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.
*/
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/string.h>
#include "governor.h"
#include "../msm_vidc_debug.h"
#include "../msm_vidc_res_parse.h"
#include "../msm_vidc_internal.h"
#include "../venus_hfi.h"
#include "../vidc_hfi_api.h"
enum bus_profile {
VIDC_BUS_PROFILE_NORMAL = BIT(0),
VIDC_BUS_PROFILE_LOW = BIT(1),
VIDC_BUS_PROFILE_UBWC = BIT(2),
};
struct bus_profile_entry {
struct {
u32 load, freq;
} *bus_table;
u32 bus_table_size;
u32 codec_mask;
enum bus_profile profile;
};
struct msm_vidc_bus_table_gov {
struct bus_profile_entry *bus_prof_entries;
u32 count;
struct devfreq_governor devfreq_gov;
};
static int __get_bus_freq(struct msm_vidc_bus_table_gov *gov,
struct vidc_bus_vote_data *data,
enum bus_profile profile)
{
int i = 0, load = 0, freq = 0;
enum vidc_vote_data_session sess_type = 0;
struct bus_profile_entry *entry = NULL;
bool found = false;
load = NUM_MBS_PER_SEC(data->width, data->height, data->fps);
sess_type = VIDC_VOTE_DATA_SESSION_VAL(data->codec, data->domain);
/* check if ubwc bus profile is present */
for (i = 0; i < gov->count; i++) {
entry = &gov->bus_prof_entries[i];
if (!entry->bus_table || !entry->bus_table_size)
continue;
if (!venus_hfi_is_session_supported(
entry->codec_mask, sess_type))
continue;
if (entry->profile == profile) {
found = true;
break;
}
}
if (found) {
/* loop over bus table and select frequency */
for (i = entry->bus_table_size - 1; i >= 0; --i) {
/*load is arranged in descending order */
freq = entry->bus_table[i].freq;
if (load <= entry->bus_table[i].load)
break;
}
}
return freq;
}
static int msm_vidc_table_get_target_freq(struct devfreq *dev,
unsigned long *frequency)
{
struct devfreq_dev_status status = {0};
struct msm_vidc_gov_data *vidc_data = NULL;
struct msm_vidc_bus_table_gov *gov = NULL;
enum bus_profile profile = 0;
int i = 0;
if (!dev || !frequency) {
dprintk(VIDC_ERR, "%s: Invalid params %pK, %pK\n",
__func__, dev, frequency);
return -EINVAL;
}
gov = container_of(dev->governor,
struct msm_vidc_bus_table_gov, devfreq_gov);
if (!gov) {
dprintk(VIDC_ERR, "%s: governor not found\n", __func__);
return -EINVAL;
}
dev->profile->get_dev_status(dev->dev.parent, &status);
vidc_data = (struct msm_vidc_gov_data *)status.private_data;
*frequency = 0;
for (i = 0; i < vidc_data->data_count; i++) {
struct vidc_bus_vote_data *data = &vidc_data->data[i];
int freq = 0;
if (data->power_mode == VIDC_POWER_TURBO) {
dprintk(VIDC_DBG, "bus: found turbo session[%d] %#x\n",
i, VIDC_VOTE_DATA_SESSION_VAL(data->codec,
data->domain));
*frequency = INT_MAX;
goto exit;
}
profile = VIDC_BUS_PROFILE_NORMAL;
if (data->color_formats[0] == HAL_COLOR_FORMAT_NV12_TP10_UBWC ||
data->color_formats[0] == HAL_COLOR_FORMAT_NV12_UBWC)
profile = VIDC_BUS_PROFILE_UBWC;
freq = __get_bus_freq(gov, data, profile);
/* chose frequency from normal profile
* if specific profile frequency was not found.
*/
if (!freq)
freq = __get_bus_freq(gov, data,
VIDC_BUS_PROFILE_NORMAL);
*frequency += (unsigned long)freq;
dprintk(VIDC_DBG,
"session[%d] %#x: wxh %dx%d, fps %d, bus_profile %#x, freq %d, total_freq %ld KBps\n",
i, VIDC_VOTE_DATA_SESSION_VAL(
data->codec, data->domain), data->width,
data->height, data->fps, profile,
freq, *frequency);
}
exit:
return 0;
}
int msm_vidc_table_event_handler(struct devfreq *devfreq,
unsigned int event, void *data)
{
int rc = 0;
if (!devfreq) {
dprintk(VIDC_ERR, "%s: NULL devfreq\n", __func__);
return -EINVAL;
}
switch (event) {
case DEVFREQ_GOV_START:
case DEVFREQ_GOV_RESUME:
mutex_lock(&devfreq->lock);
rc = update_devfreq(devfreq);
mutex_unlock(&devfreq->lock);
break;
}
return rc;
}
static int msm_vidc_free_bus_table(struct platform_device *pdev,
struct msm_vidc_bus_table_gov *data)
{
int rc = 0, i = 0;
if (!pdev || !data) {
dprintk(VIDC_ERR, "%s: invalid args %pK %pK\n",
__func__, pdev, data);
return -EINVAL;
}
for (i = 0; i < data->count; i++)
data->bus_prof_entries[i].bus_table = NULL;
data->bus_prof_entries = NULL;
data->count = 0;
return rc;
}
static int msm_vidc_load_bus_table(struct platform_device *pdev,
struct msm_vidc_bus_table_gov *data)
{
int rc = 0, i = 0, j = 0;
const char *name = NULL;
struct bus_profile_entry *entry = NULL;
struct device_node *parent_node = NULL;
struct device_node *child_node = NULL;
if (!pdev || !data) {
dprintk(VIDC_ERR, "%s: invalid args %pK %pK\n",
__func__, pdev, data);
return -EINVAL;
}
of_property_read_string(pdev->dev.of_node, "name", &name);
if (strlen(name) > ARRAY_SIZE(data->devfreq_gov.name) - 1) {
dprintk(VIDC_ERR,
"%s: name is too long, max should be %zu chars\n",
__func__, ARRAY_SIZE(data->devfreq_gov.name) - 1);
return -EINVAL;
}
strlcpy((char *)data->devfreq_gov.name, name,
ARRAY_SIZE(data->devfreq_gov.name));
data->devfreq_gov.get_target_freq = msm_vidc_table_get_target_freq;
data->devfreq_gov.event_handler = msm_vidc_table_event_handler;
parent_node = of_find_node_by_name(pdev->dev.of_node,
"qcom,bus-freq-table");
if (!parent_node) {
dprintk(VIDC_DBG, "Node qcom,bus-freq-table not found.\n");
return 0;
}
data->count = of_get_child_count(parent_node);
if (!data->count) {
dprintk(VIDC_DBG, "No child nodes in qcom,bus-freq-table\n");
return 0;
}
data->bus_prof_entries = devm_kzalloc(&pdev->dev,
sizeof(*data->bus_prof_entries) * data->count,
GFP_KERNEL);
if (!data->bus_prof_entries) {
dprintk(VIDC_DBG, "no memory to allocate bus_prof_entries\n");
return -ENOMEM;
}
for_each_child_of_node(parent_node, child_node) {
if (i >= data->count) {
dprintk(VIDC_ERR,
"qcom,bus-freq-table: invalid child node %d, max is %d\n",
i, data->count);
break;
}
entry = &data->bus_prof_entries[i];
if (of_find_property(child_node, "qcom,codec-mask", NULL)) {
rc = of_property_read_u32(child_node,
"qcom,codec-mask", &entry->codec_mask);
if (rc) {
dprintk(VIDC_ERR,
"qcom,codec-mask not found\n");
break;
}
}
if (of_find_property(child_node, "qcom,low-power-mode", NULL))
entry->profile = VIDC_BUS_PROFILE_LOW;
else if (of_find_property(child_node, "qcom,ubwc-mode", NULL))
entry->profile = VIDC_BUS_PROFILE_UBWC;
else
entry->profile = VIDC_BUS_PROFILE_NORMAL;
if (of_find_property(child_node,
"qcom,load-busfreq-tbl", NULL)) {
rc = msm_vidc_load_u32_table(pdev, child_node,
"qcom,load-busfreq-tbl",
sizeof(*entry->bus_table),
(u32 **)&entry->bus_table,
&entry->bus_table_size);
if (rc) {
dprintk(VIDC_ERR,
"qcom,load-busfreq-tbl failed\n");
break;
}
} else {
entry->bus_table = NULL;
entry->bus_table_size = 0;
}
dprintk(VIDC_DBG,
"qcom,load-busfreq-tbl: size %d, codec_mask %#x, profile %#x\n",
entry->bus_table_size, entry->codec_mask,
entry->profile);
for (j = 0; j < entry->bus_table_size; j++)
dprintk(VIDC_DBG, " load %8d freq %8d\n",
entry->bus_table[j].load,
entry->bus_table[j].freq);
i++;
}
return rc;
}
static int msm_vidc_bus_table_probe(struct platform_device *pdev)
{
int rc = 0;
struct msm_vidc_bus_table_gov *gov = NULL;
dprintk(VIDC_DBG, "%s\n", __func__);
gov = devm_kzalloc(&pdev->dev, sizeof(*gov), GFP_KERNEL);
if (!gov) {
dprintk(VIDC_ERR, "%s: allocation failed\n", __func__);
return -ENOMEM;
}
platform_set_drvdata(pdev, gov);
rc = msm_vidc_load_bus_table(pdev, gov);
if (rc)
return rc;
rc = devfreq_add_governor(&gov->devfreq_gov);
if (rc)
dprintk(VIDC_ERR, "%s: add governor failed\n", __func__);
return rc;
}
static int msm_vidc_bus_table_remove(struct platform_device *pdev)
{
int rc = 0;
struct msm_vidc_bus_table_gov *gov = NULL;
dprintk(VIDC_DBG, "%s\n", __func__);
gov = platform_get_drvdata(pdev);
if (IS_ERR_OR_NULL(gov))
return PTR_ERR(gov);
rc = msm_vidc_free_bus_table(pdev, gov);
if (rc)
dprintk(VIDC_WARN, "%s: free bus table failed\n", __func__);
rc = devfreq_remove_governor(&gov->devfreq_gov);
return rc;
}
static const struct of_device_id device_id[] = {
{.compatible = "qcom,msm-vidc,governor,table"},
{}
};
static struct platform_driver msm_vidc_bus_table_driver = {
.probe = msm_vidc_bus_table_probe,
.remove = msm_vidc_bus_table_remove,
.driver = {
.name = "msm_vidc_bus_table_governor",
.owner = THIS_MODULE,
.of_match_table = device_id,
},
};
static int __init msm_vidc_bus_table_init(void)
{
dprintk(VIDC_DBG, "%s\n", __func__);
return platform_driver_register(&msm_vidc_bus_table_driver);
}
module_init(msm_vidc_bus_table_init);
static void __exit msm_vidc_bus_table_exit(void)
{
dprintk(VIDC_DBG, "%s\n", __func__);
platform_driver_unregister(&msm_vidc_bus_table_driver);
}
module_exit(msm_vidc_bus_table_exit);
MODULE_LICENSE("GPL v2");