/* * Copyright (c) 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) "governor_cdspl3: " fmt #include #include #include #include #include #include #include #include #include #include #include "governor.h" struct cdspl3 { struct device_node *of_node; struct devfreq *df; unsigned int l3_freq_hz; }; static struct cdspl3 p_me; static int cdsp_l3_request_callback(unsigned int freq_khz) { if (p_me.df) { mutex_lock(&p_me.df->lock); p_me.l3_freq_hz = freq_khz * 1000; update_devfreq(p_me.df); mutex_unlock(&p_me.df->lock); } else { pr_err("CDSP L3 request for %dKHz not served", freq_khz); return -ENODEV; } return 0; } static struct cdsprm_l3 cdsprm = { .set_l3_freq = cdsp_l3_request_callback, }; static int devfreq_get_target_freq(struct devfreq *df, unsigned long *freq) { if (freq) *freq = (unsigned long)p_me.l3_freq_hz; return 0; } static int gov_start(struct devfreq *df) { if (p_me.of_node != df->dev.parent->of_node) { dev_err(df->dev.parent, "Device match error in CDSP L3 frequency governor\n"); return -ENODEV; } p_me.df = df; p_me.l3_freq_hz = 0; /* * Send governor start message to CDSP RM driver */ cdsprm_register_cdspl3gov(&cdsprm); return 0; } static int gov_stop(struct devfreq *df) { p_me.df = 0; p_me.l3_freq_hz = 0; /* * Send governor stop message to CDSP RM driver */ cdsprm_unregister_cdspl3gov(); return 0; } static int devfreq_event_handler(struct devfreq *df, unsigned int event, void *data) { int ret; switch (event) { case DEVFREQ_GOV_START: ret = gov_start(df); if (ret) return ret; dev_info(df->dev.parent, "Successfully started CDSP L3 governor\n"); break; case DEVFREQ_GOV_STOP: dev_info(df->dev.parent, "Received stop CDSP L3 governor event\n"); ret = gov_stop(df); if (ret) return ret; break; default: break; } return 0; } static struct devfreq_governor cdsp_l3_gov = { .name = "cdspl3", .get_target_freq = devfreq_get_target_freq, .event_handler = devfreq_event_handler, }; static int cdsp_l3_driver_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; int ret; p_me.of_node = of_parse_phandle(dev->of_node, "qcom,target-dev", 0); if (!p_me.of_node) { dev_err(dev, "Couldn't find a target device\n"); return -ENODEV; } ret = devfreq_add_governor(&cdsp_l3_gov); if (ret) dev_err(dev, "Failed registering CDSP L3 requests %d\n", ret); return ret; } static const struct of_device_id cdsp_l3_match_table[] = { { .compatible = "qcom,cdsp-l3" }, {} }; static struct platform_driver cdsp_l3 = { .probe = cdsp_l3_driver_probe, .driver = { .name = "cdsp-l3", .of_match_table = cdsp_l3_match_table, } }; static int __init cdsp_l3_gov_module_init(void) { return platform_driver_register(&cdsp_l3); } module_init(cdsp_l3_gov_module_init); static void __exit cdsp_l3_gov_module_exit(void) { devfreq_remove_governor(&cdsp_l3_gov); platform_driver_unregister(&cdsp_l3); } module_exit(cdsp_l3_gov_module_exit); MODULE_LICENSE("GPL v2");