| /* Copyright (c) 2014, 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) "%s: " fmt, __func__ |
| |
| #include <linux/module.h> |
| #include <linux/err.h> |
| #include <linux/string.h> |
| #include <linux/kernel.h> |
| #include <linux/init.h> |
| #include <linux/of.h> |
| #include <linux/of_device.h> |
| #include <linux/platform_device.h> |
| #include <linux/qpnp/qpnp-adc.h> |
| #include <soc/qcom/rpm-smd.h> |
| |
| struct boost_dynamic_controller { |
| /* ADC_TM parameters */ |
| struct qpnp_adc_tm_chip *boost_adc_tm_dev; |
| struct qpnp_adc_tm_btm_param boost_monitor_params; |
| u32 vph_high_thresh_uv; |
| u32 vph_low_thresh_uv; |
| |
| /* Boost mode parameters */ |
| int old_boost_mode; |
| |
| /* RPM control parameters */ |
| const char *boost_resource_type; |
| const char *boost_resource_key; |
| int resource_type; |
| u32 resource_key; |
| u32 resource_id; |
| |
| struct mutex boost_mutex; |
| }; |
| |
| #define BOOST_DYNAMIC_CONTROLLER_DRIVER_NAME "qcom,boost-dynamic-controller" |
| |
| static u32 rpm_vreg_string_to_int(const u8 *str) |
| { |
| int i, len; |
| u32 output = 0; |
| |
| len = strnlen(str, sizeof(u32)); |
| for (i = 0; i < len; i++) |
| output |= str[i] << (i * 8); |
| |
| return output; |
| } |
| |
| static int boost_dynamic_controller_parse_dt( |
| struct boost_dynamic_controller *boost_controller, |
| struct device_node *of_node) |
| { |
| int ret; |
| |
| ret = of_property_read_u32(of_node, |
| "qcom,boost-dynamic-controller-vph-high-threshold-uv", |
| &boost_controller->vph_high_thresh_uv); |
| if (ret < 0) { |
| pr_err("qcom,boost-dynamic-controller-vph-high-threshold-uv is missing, ret = %d", |
| ret); |
| return ret; |
| } |
| |
| ret = of_property_read_u32(of_node, |
| "qcom,boost-dynamic-controller-vph-low-threshold-uv", |
| &boost_controller->vph_low_thresh_uv); |
| if (ret < 0) { |
| pr_err("qcom,boost-dynamic-controller-vph-low-threshold-uv is missing, ret = %d", |
| ret); |
| return ret; |
| } |
| |
| ret = of_property_read_u32(of_node, |
| "qcom,boost-dynamic-controller-boost-resource-id", |
| &boost_controller->resource_id); |
| if (ret < 0) { |
| pr_err("qcom,boost-dynamic-controller-boost-resource-id is missing, ret = %d", |
| ret); |
| return ret; |
| } |
| |
| ret = of_property_read_string(of_node, |
| "qcom,boost-dynamic-controller-boost-resource-type", |
| &boost_controller->boost_resource_type); |
| if (ret < 0) { |
| pr_err("qcom,boost-dynamic-controller-boost-resource-type is missing, ret = %d", |
| ret); |
| return ret; |
| } |
| |
| ret = of_property_read_string(of_node, |
| "qcom,boost-dynamic-controller-boost-resource-key", |
| &boost_controller->boost_resource_key); |
| if (ret < 0) { |
| pr_err("qcom,boost-dynamic-controller-boost-resource-key is missing, ret = %d", |
| ret); |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| static int boost_rpm_send_message( |
| struct boost_dynamic_controller *boost_controller, |
| u32 data) |
| { |
| int ret; |
| struct msm_rpm_kvp kvp = { |
| .key = boost_controller->resource_key, |
| .data = (void *)&data, |
| .length = sizeof(data), |
| }; |
| |
| ret = msm_rpm_send_message(MSM_RPM_CTX_ACTIVE_SET, |
| boost_controller->resource_type, |
| boost_controller->resource_id, |
| &kvp, 1); |
| if (ret < 0) |
| pr_err("failed to inform RPM! (err = %d)\n", ret); |
| |
| return ret; |
| } |
| |
| static void boost_dynamic_controller_notification(enum qpnp_tm_state state, |
| void *ctx) |
| { |
| struct boost_dynamic_controller *boost_controller = ctx; |
| int ret; |
| |
| mutex_lock(&boost_controller->boost_mutex); |
| |
| if (state == ADC_TM_LOW_STATE && |
| boost_controller->old_boost_mode != ADC_TM_LOW_STATE) { |
| ret = boost_rpm_send_message(boost_controller, 1); |
| if (ret) { |
| pr_err("sending boost mode to RPM failed, ret=%d\n", |
| ret); |
| boost_controller->boost_monitor_params.state_request = |
| ADC_TM_LOW_THR_ENABLE; |
| } else { |
| boost_controller->old_boost_mode = ADC_TM_LOW_STATE; |
| boost_controller->boost_monitor_params.state_request = |
| ADC_TM_HIGH_THR_ENABLE; |
| } |
| } else if (state == ADC_TM_HIGH_STATE && |
| boost_controller->old_boost_mode != ADC_TM_HIGH_STATE) { |
| ret = boost_rpm_send_message(boost_controller, 0); |
| if (ret) { |
| pr_err("sending boost mode to RPM failed, ret=%d\n", |
| ret); |
| boost_controller->boost_monitor_params.state_request = |
| ADC_TM_HIGH_THR_ENABLE; |
| } else { |
| boost_controller->old_boost_mode = ADC_TM_HIGH_STATE; |
| boost_controller->boost_monitor_params.state_request = |
| ADC_TM_LOW_THR_ENABLE; |
| } |
| } |
| |
| mutex_unlock(&boost_controller->boost_mutex); |
| |
| qpnp_adc_tm_channel_measure(boost_controller->boost_adc_tm_dev, |
| &boost_controller->boost_monitor_params); |
| } |
| |
| static int boost_dynamic_controller_setup_vph_monitoring( |
| struct boost_dynamic_controller *boost_controller, |
| struct device *dev) |
| { |
| int ret; |
| char *key = NULL; |
| struct qpnp_vadc_chip *vadc_dev; |
| struct qpnp_vadc_result adc_result; |
| |
| key = "vph-threshold"; |
| boost_controller->boost_adc_tm_dev = qpnp_get_adc_tm(dev, key); |
| if (IS_ERR(boost_controller->boost_adc_tm_dev)) { |
| ret = PTR_ERR(boost_controller->boost_adc_tm_dev); |
| if (ret != -EPROBE_DEFER) |
| pr_err("adc_tm property missing, ret=%d\n", ret); |
| return ret; |
| } |
| |
| boost_controller->boost_monitor_params.high_thr = |
| boost_controller->vph_high_thresh_uv; |
| boost_controller->boost_monitor_params.low_thr = |
| boost_controller->vph_low_thresh_uv; |
| boost_controller->boost_monitor_params.channel = VSYS; |
| boost_controller->boost_monitor_params.btm_ctx = |
| (void *)boost_controller; |
| boost_controller->boost_monitor_params.timer_interval = |
| ADC_MEAS1_INTERVAL_1S; |
| boost_controller->boost_monitor_params.threshold_notification = |
| &boost_dynamic_controller_notification; |
| |
| key = "vph"; |
| vadc_dev = qpnp_get_vadc(dev, key); |
| if (IS_ERR(vadc_dev)) { |
| ret = PTR_ERR(vadc_dev); |
| if (ret != -EPROBE_DEFER) |
| pr_err("vadc property missing, ret=%d\n", ret); |
| return ret; |
| } |
| |
| ret = qpnp_vadc_read(vadc_dev, VSYS, &adc_result); |
| if (ret) { |
| pr_err("Unable to read VPH ret=%d\n", ret); |
| return ret; |
| } |
| |
| if (adc_result.physical > boost_controller->vph_high_thresh_uv) { |
| ret = boost_rpm_send_message(boost_controller, 0); |
| if (ret) { |
| pr_err("sending boost mode to RPM failed, ret=%d\n", |
| ret); |
| return ret; |
| } |
| boost_controller->old_boost_mode = ADC_TM_HIGH_STATE; |
| boost_controller->boost_monitor_params.state_request = |
| ADC_TM_LOW_THR_ENABLE; |
| } else { |
| ret = boost_rpm_send_message(boost_controller, 1); |
| if (ret) { |
| pr_err("sending boost mode to RPM failed, ret=%d\n", |
| ret); |
| return ret; |
| } |
| boost_controller->old_boost_mode = ADC_TM_LOW_STATE; |
| boost_controller->boost_monitor_params.state_request = |
| ADC_TM_HIGH_THR_ENABLE; |
| } |
| |
| ret = qpnp_adc_tm_channel_measure(boost_controller->boost_adc_tm_dev, |
| &boost_controller->boost_monitor_params); |
| if (ret) { |
| pr_err("adc-tm setup failed: ret = %d\n", ret); |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| static int boost_dynamic_controller_probe(struct platform_device *pdev) |
| { |
| struct boost_dynamic_controller *boost_controller = NULL; |
| struct device *dev = &pdev->dev; |
| int ret; |
| |
| boost_controller = devm_kzalloc(dev, |
| sizeof(struct boost_dynamic_controller), GFP_KERNEL); |
| if (!boost_controller) { |
| dev_err(dev, "Cannot allocate boost_dynamic_controller\n"); |
| return -ENOMEM; |
| } |
| |
| if (!dev->of_node) { |
| dev_err(dev, "Device tree node is missing\n"); |
| return -EINVAL; |
| } |
| |
| ret = boost_dynamic_controller_parse_dt(boost_controller, dev->of_node); |
| if (ret) { |
| pr_err("Wrong DT parameter specified: ret = %d\n", ret); |
| return ret; |
| } |
| |
| boost_controller->resource_type = |
| rpm_vreg_string_to_int(boost_controller->boost_resource_type); |
| boost_controller->resource_key = |
| rpm_vreg_string_to_int(boost_controller->boost_resource_key); |
| |
| mutex_init(&boost_controller->boost_mutex); |
| |
| ret = boost_dynamic_controller_setup_vph_monitoring(boost_controller, |
| dev); |
| |
| if (ret) { |
| mutex_destroy(&boost_controller->boost_mutex); |
| if (ret != -EPROBE_DEFER) |
| pr_err("Vph monitor setup failure, ret = %d\n", ret); |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| static int boost_dynamic_controller_remove(struct platform_device *pdev) |
| { |
| struct boost_dynamic_controller *boost_controller; |
| |
| boost_controller = platform_get_drvdata(pdev); |
| mutex_destroy(&boost_controller->boost_mutex); |
| return 0; |
| } |
| |
| static struct of_device_id boost_dynamic_controller_match_table[] = { |
| { .compatible = BOOST_DYNAMIC_CONTROLLER_DRIVER_NAME, }, |
| {} |
| }; |
| |
| static struct platform_driver boost_dynamic_controller_driver = { |
| .driver = { |
| .name = BOOST_DYNAMIC_CONTROLLER_DRIVER_NAME, |
| .of_match_table = boost_dynamic_controller_match_table, |
| .owner = THIS_MODULE, |
| }, |
| .probe = boost_dynamic_controller_probe, |
| .remove = boost_dynamic_controller_remove, |
| }; |
| |
| static int __init boost_dynamic_controller_init(void) |
| { |
| return platform_driver_register(&boost_dynamic_controller_driver); |
| } |
| module_init(boost_dynamic_controller_init); |
| |
| static void __exit boost_dynamic_controller_exit(void) |
| { |
| platform_driver_unregister(&boost_dynamic_controller_driver); |
| } |
| module_exit(boost_dynamic_controller_exit); |
| |
| MODULE_DESCRIPTION("Boost dynamic controller driver"); |
| MODULE_LICENSE("GPL v2"); |