blob: ed528b0c2c23ae5cf4bc825815a66848385a03b8 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved.
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/of_platform.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/sched.h>
#include <drm/drm_panel.h>
#include "msm_drv.h"
#include "sde_dbg.h"
struct msm_display_fps_info {
uint32_t id;
enum fps fps;
struct list_head list;
struct drm_panel *panel;
};
static int msm_notifier_fps_chg_callback(struct notifier_block *nb,
unsigned long val, void *data);
static struct notifier_block msm_notifier_block = {
.notifier_call = msm_notifier_fps_chg_callback,
};
struct notifier_info {
struct notifier_block notifier;
};
struct display_list {
struct list_head list;
enum fps max_fps;
};
static struct display_list *active_displays;
static int msm_notifier_fps_chg_callback(struct notifier_block *nb,
unsigned long val, void *data)
{
int fps;
enum fps sched_fps, max_fps;
struct drm_panel_notifier *notifier_data =
(struct drm_panel_notifier *) data;
struct msm_display_fps_info *display;
bool display_id_match = false;
/*
* Get ceiling of fps from notifier data to pass to scheduler.
* Default will be FPS60 and sent to scheduler during suspend.
*/
fps = notifier_data->refresh_rate;
if (fps > FPS120)
sched_fps = FPS144;
else if (fps > FPS90)
sched_fps = FPS120;
else if (fps > FPS60)
sched_fps = FPS90;
else if (fps > FPS48)
sched_fps = FPS60;
else if (fps > FPS30)
sched_fps = FPS48;
else if (fps > FPS0)
sched_fps = FPS30;
else
sched_fps = FPS60;
max_fps = sched_fps;
/*
* Iterate displays and set id and fps if uninitialized.
* Update display's current fps if id match is found.
* Find max refresh rate to pass to scheduler.
*/
list_for_each_entry(display, &active_displays->list, list) {
if (!display->fps && !display_id_match) {
display->id = notifier_data->id;
display->fps = sched_fps;
}
if (display->id == notifier_data->id) {
display_id_match = true;
display->fps = sched_fps;
}
if (display->fps > max_fps)
max_fps = display->fps;
}
if (max_fps != active_displays->max_fps) {
SDE_EVT32(notifier_data->id,
notifier_data->refresh_rate, max_fps);
active_displays->max_fps = max_fps;
sched_set_refresh_rate(max_fps);
}
return 0;
}
static int msm_notifier_remove(struct platform_device *pdev)
{
struct msm_display_fps_info *display;
struct notifier_info *info = platform_get_drvdata(pdev);
list_for_each_entry(display, &active_displays->list, list)
drm_panel_notifier_unregister(display->panel, &info->notifier);
return 0;
}
static int msm_notifier_probe(struct platform_device *pdev)
{
int i, count, ret = 0;
struct device_node *node;
struct drm_panel *panel;
struct notifier_info *info = NULL;
struct msm_display_fps_info *display;
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
info->notifier = msm_notifier_block;
platform_set_drvdata(pdev, info);
active_displays = devm_kzalloc(&pdev->dev,
sizeof(*active_displays), GFP_KERNEL);
if (!active_displays) {
ret = -ENOMEM;
goto end;
}
INIT_LIST_HEAD(&active_displays->list);
/* Set default max fps to 0 */
active_displays->max_fps = 0;
count = of_count_phandle_with_args(pdev->dev.of_node, "panel", NULL);
for (i = 0; i < count; i++) {
node = of_parse_phandle(pdev->dev.of_node, "panel", i);
panel = of_drm_find_panel(node);
of_node_put(node);
if (!IS_ERR(panel)) {
/*
* Add new msm_display_fps_info to linked list
* of active displays. Initialize fps as
* 0 to mark unassigned node. Assign when
* you get the first callback for that display
*/
struct msm_display_fps_info *display_fps_info =
devm_kzalloc(&pdev->dev,
sizeof(*display_fps_info), GFP_KERNEL);
if (!display_fps_info) {
ret = -ENOMEM;
goto fail;
}
display_fps_info->panel = panel;
list_add(&display_fps_info->list,
&active_displays->list);
drm_panel_notifier_register(panel, &info->notifier);
}
}
pr_info("msm notifier probed successfully\n");
return ret;
fail:
list_for_each_entry(display, &active_displays->list, list)
drm_panel_notifier_unregister(display->panel, &info->notifier);
devm_kfree(&pdev->dev, active_displays);
end:
devm_kfree(&pdev->dev, info);
return ret;
}
static const struct of_device_id dt_match_msm_notifier[] = {
{ .compatible = "qcom,msm-notifier"},
{},
};
MODULE_DEVICE_TABLE(of, dt_match_msm_notifier);
static struct platform_driver msm_notifier_platform_driver = {
.probe = msm_notifier_probe,
.remove = msm_notifier_remove,
.driver = {
.name = "msm_notifier",
.of_match_table = dt_match_msm_notifier,
.suppress_bind_attrs = true,
},
};
int __init msm_notifier_register(void)
{
return platform_driver_register(&msm_notifier_platform_driver);
}
void __exit msm_notifier_unregister(void)
{
platform_driver_unregister(&msm_notifier_platform_driver);
}
#ifndef CONFIG_DRM_MSM_MODULE
late_initcall(msm_notifier_register);
module_exit(msm_notifier_unregister);
#endif