/* * 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) "%s:%s " fmt, KBUILD_MODNAME, __func__ #include #include #include #include #include #include #include #include #include #include #include "../thermal_core.h" #define BCL_DRIVER_NAME "bcl_soc_peripheral" struct bcl_device { struct notifier_block psy_nb; struct work_struct soc_eval_work; long int trip_temp; int trip_val; struct mutex state_trans_lock; bool irq_enabled; struct thermal_zone_device *tz_dev; struct thermal_zone_of_device_ops ops; }; static struct bcl_device *bcl_perph; static int bcl_set_soc(void *data, int low, int high) { if (low == bcl_perph->trip_temp) return 0; mutex_lock(&bcl_perph->state_trans_lock); pr_debug("low soc threshold:%d\n", low); bcl_perph->trip_temp = low; if (low == INT_MIN) { bcl_perph->irq_enabled = false; goto unlock_and_exit; } bcl_perph->irq_enabled = true; schedule_work(&bcl_perph->soc_eval_work); unlock_and_exit: mutex_unlock(&bcl_perph->state_trans_lock); return 0; } static int bcl_read_soc(void *data, int *val) { static struct power_supply *batt_psy; union power_supply_propval ret = {0,}; int err = 0; *val = 100; if (!batt_psy) batt_psy = power_supply_get_by_name("battery"); if (batt_psy) { err = power_supply_get_property(batt_psy, POWER_SUPPLY_PROP_CAPACITY, &ret); if (err) { pr_err("battery percentage read error:%d\n", err); return err; } *val = ret.intval; } pr_debug("soc:%d\n", *val); return err; } static void bcl_evaluate_soc(struct work_struct *work) { int battery_percentage; if (bcl_read_soc(NULL, &battery_percentage)) return; mutex_lock(&bcl_perph->state_trans_lock); if (!bcl_perph->irq_enabled) goto eval_exit; if (battery_percentage > bcl_perph->trip_temp) goto eval_exit; bcl_perph->trip_val = battery_percentage; mutex_unlock(&bcl_perph->state_trans_lock); of_thermal_handle_trip(bcl_perph->tz_dev); return; eval_exit: mutex_unlock(&bcl_perph->state_trans_lock); } static int battery_supply_callback(struct notifier_block *nb, unsigned long event, void *data) { struct power_supply *psy = data; if (strcmp(psy->desc->name, "battery")) return NOTIFY_OK; schedule_work(&bcl_perph->soc_eval_work); return NOTIFY_OK; } static int bcl_soc_remove(struct platform_device *pdev) { power_supply_unreg_notifier(&bcl_perph->psy_nb); flush_work(&bcl_perph->soc_eval_work); if (bcl_perph->tz_dev) thermal_zone_of_sensor_unregister(&pdev->dev, bcl_perph->tz_dev); return 0; } static int bcl_soc_probe(struct platform_device *pdev) { int ret = 0; bcl_perph = devm_kzalloc(&pdev->dev, sizeof(*bcl_perph), GFP_KERNEL); if (!bcl_perph) return -ENOMEM; mutex_init(&bcl_perph->state_trans_lock); bcl_perph->ops.get_temp = bcl_read_soc; bcl_perph->ops.set_trips = bcl_set_soc; INIT_WORK(&bcl_perph->soc_eval_work, bcl_evaluate_soc); bcl_perph->psy_nb.notifier_call = battery_supply_callback; ret = power_supply_reg_notifier(&bcl_perph->psy_nb); if (ret < 0) { pr_err("soc notifier registration error. defer. err:%d\n", ret); ret = -EPROBE_DEFER; goto bcl_soc_probe_exit; } bcl_perph->tz_dev = thermal_zone_of_sensor_register(&pdev->dev, 0, bcl_perph, &bcl_perph->ops); if (IS_ERR(bcl_perph->tz_dev)) { pr_err("soc TZ register failed. err:%ld\n", PTR_ERR(bcl_perph->tz_dev)); ret = PTR_ERR(bcl_perph->tz_dev); bcl_perph->tz_dev = NULL; goto bcl_soc_probe_exit; } thermal_zone_device_update(bcl_perph->tz_dev, THERMAL_DEVICE_UP); schedule_work(&bcl_perph->soc_eval_work); dev_set_drvdata(&pdev->dev, bcl_perph); return 0; bcl_soc_probe_exit: bcl_soc_remove(pdev); return ret; } static const struct of_device_id bcl_match[] = { { .compatible = "qcom,msm-bcl-soc", }, {}, }; static struct platform_driver bcl_driver = { .probe = bcl_soc_probe, .remove = bcl_soc_remove, .driver = { .name = BCL_DRIVER_NAME, .owner = THIS_MODULE, .of_match_table = bcl_match, }, }; builtin_platform_driver(bcl_driver);