Loading Documentation/devicetree/bindings/arm/msm/msm_bandwidth_monitor.txt 0 → 100644 +52 −0 Original line number Diff line number Diff line Bandwidth Monitor driver(BW_MONITOR) ===================================== Bandwidth Monitor driver creates logical sensor nodes with thermal core framework for the specified bus. This logical sensor will be used to monitor and mitigate the bus bandwidth. This driver can be configured to register any bus bandwidth as a sensor for it to be monitored. The device tree parameters for bandwidth monitor driver(BW_MONITOR) driver are: Properties: - compatible: Usage: required Value type: <string> Definition: shall be "qcom,bm-sensors" - <child node> Usage: required Definition: Each child node represents a bandwidth sensor and name of the node will be used as bandwidth sensor name. Each sensor can represent either single bus bandwidth or aggregation of multiple bus bandwidth. Child node Properties: - qcom,bm-sensor: Usage: required Value type: <array of phandle> Definition: This bandwidth sensor monitors aggregated bandwidth of these bus bandwidth. - qcom,bm-sensor-field: Usage: required Value type: <string> Definition: This property defines the bandwidth to be monitored, either average bandwidth(ab) or instantaneous bandwidth(ib). Example: qcom,bm-sensors { compatible = "qcom,bm-sensors"; bw_mm { qcom,bm-sensor = <&slv_snoc_bimc_0 &slv_snoc_bimc_2>; qcom,bm-sensor-field = "ab"; }; bw_apps { qcom,bm-sensor = <&mas_apps_proc>; qcom,bm-sensor-field = "ib"; }; }; drivers/platform/msm/Kconfig +9 −0 Original line number Diff line number Diff line Loading @@ -300,6 +300,15 @@ config MSM_11AD If you choose to build it as a module, it will be called msm_11ad_proxy. config BW_MONITOR bool "Bandwidth monitor driver" depends on BUS_TOPOLOGY_ADHOC && THERMAL help Enable this module if any bandwidth of a bus needs to be monitored and limited by thermal core framework. It does it by registering the bus bandwidth as a sensor in thermal core framework. source "drivers/platform/msm/spmi/Kconfig" endmenu drivers/platform/msm/msm_bus/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ ifdef CONFIG_BUS_TOPOLOGY_ADHOC obj-$(CONFIG_OF) += msm_bus_of_adhoc.o obj-$(CONFIG_DEBUG_BUS_VOTER) += msm_bus_dbg_voter.o obj-$(CONFIG_CORESIGHT) += msm_buspm_coresight_adhoc.o obj-$(CONFIG_BW_MONITOR) += msm_bandwidth_monitor.o else obj-y += msm_bus_fabric.o msm_bus_config.o msm_bus_arb.o \ msm_bus_bimc.o msm_bus_noc.o Loading drivers/platform/msm/msm_bus/msm_bandwidth_monitor.c 0 → 100644 +637 −0 Original line number Diff line number Diff line /* Copyright (c) 2016, 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 <linux/module.h> #include <linux/platform_device.h> #include <linux/workqueue.h> #include <linux/of.h> #include <linux/mutex.h> #include <linux/thermal.h> #include <dt-bindings/msm/msm-bus-ids.h> #include <dt-bindings/msm/msm-bus-rule-ops.h> #include <linux/msm_bus_rules.h> #define BM_SENSORS_DRIVER_NAME "bm_sensors" #define ALLOC_KMEM(dev, ptr, type, size, ret) \ do { \ ret = 0; \ ptr = devm_kzalloc(dev, \ sizeof(type) * size,\ GFP_KERNEL); \ if (!ptr) \ ret = -ENOMEM; \ } while (0) /* Trips: HIGH and LOW */ enum bm_threshold_id { BM_HIGH_THRESHOLD = 0, BM_LOW_THRESHOLD, MAX_BM_THRESHOLD, }; #define BM_WRITABLE_TRIPS_MASK ((1 << MAX_BM_THRESHOLD) - 1) struct bm_thresh_state { enum thermal_trip_activation_mode state; int trip_bw; }; struct bm_tz_sensor { /* Sensor info from dt */ char bm_sensor[THERMAL_NAME_LENGTH]; int src_cnt; int *src_id; int src_field; int op; /* bus rule interface struct */ struct bus_rule_type thresh_bus_rule[MAX_BM_THRESHOLD]; uint32_t bus_rule_action[MAX_BM_THRESHOLD]; unsigned long triggered[BITS_TO_LONGS(MAX_BM_THRESHOLD)]; bool bus_reg_done; /* Common callback notifier*/ struct notifier_block bm_nb; /* thermal zone handling */ struct thermal_zone_device *tz_dev; enum thermal_device_mode mode; struct bm_thresh_state thresh_state[MAX_BM_THRESHOLD]; }; struct bm_sensors_drv_data { struct platform_device *pdev; /* Workqueue Related */ struct work_struct bm_wq; /* Serialize workqueue handling */ struct mutex rule_lock; struct bm_tz_sensor *bm_tz_sensors; int bm_sensor_cnt; }; static struct bm_sensors_drv_data *bmdev; static void notify_bw_threshold_event(struct bm_tz_sensor *sensor, struct bus_rule_type *rule, uint32_t action) { enum bm_threshold_id thresh_id; u64 bw = 0; int ret = 0; if (!sensor || !rule) { pr_err("Invalid input parameters\n"); return; } thresh_id = *(int *)(rule->client_data); if (action == RULE_STATE_APPLIED) { sensor->thresh_state[thresh_id].state = THERMAL_TRIP_ACTIVATION_DISABLED; ret = msm_rule_query_bandwidth(rule, &bw, &sensor->bm_nb); if (ret < 0) { pr_err("Error in reading curr bw, ret:%d\n", ret); return; } thermal_sensor_trip(sensor->tz_dev, (thresh_id == BM_HIGH_THRESHOLD) ? THERMAL_TRIP_CONFIGURABLE_HI : THERMAL_TRIP_CONFIGURABLE_LOW, (long)bw); } else if (action != RULE_STATE_NOT_APPLIED) { pr_err_ratelimited("Error in high threshold interrupt\n"); } } static int create_bus_rule(struct bus_rule_type *rule, int src_cnt, int *src_ids, int src_field, int op, u64 val, int thresh_id) { int i = 0, ret = 0; if (!rule->src_id) { ALLOC_KMEM(&bmdev->pdev->dev, rule->src_id, int, src_cnt, ret); if (ret) goto rule_exit; } if (!rule->src_field) { ALLOC_KMEM(&bmdev->pdev->dev, rule->src_field, int, 1, ret); if (ret) goto rule_exit; } if (!rule->op) { ALLOC_KMEM(&bmdev->pdev->dev, rule->op, int, 1, ret); if (ret) goto rule_exit; } if (!rule->thresh) { ALLOC_KMEM(&bmdev->pdev->dev, rule->thresh, u64, 1, ret); if (ret) goto rule_exit; } if (!rule->client_data) { ALLOC_KMEM(&bmdev->pdev->dev, rule->client_data, int, 1, ret); if (ret) goto rule_exit; } rule->num_src = src_cnt; for (; i < src_cnt; i++) rule->src_id[i] = src_ids[i]; *(rule->src_field) = src_field; *(rule->op) = op; rule->num_thresh = 1; *(rule->thresh) = val; rule->num_dst = 0; rule->dst_node = NULL; rule->mode = THROTTLE_OFF; *(int *)(rule->client_data) = thresh_id; rule->dst_bw = 0; rule->combo_op = 0; rule_exit: if (ret) { if (rule->src_id) devm_kfree(&bmdev->pdev->dev, rule->src_id); if (rule->src_field) devm_kfree(&bmdev->pdev->dev, rule->src_field); if (rule->op) devm_kfree(&bmdev->pdev->dev, rule->op); if (rule->thresh) devm_kfree(&bmdev->pdev->dev, rule->thresh); if (rule->client_data) devm_kfree(&bmdev->pdev->dev, rule->client_data); } return ret; } static void bm_tz_notify_wq(struct work_struct *work) { int i = 0, j = 0; mutex_lock(&bmdev->rule_lock); /* Notify bw threshold events to thermal zone */ for (i = 0; i < bmdev->bm_sensor_cnt; i++) { if (!bitmap_weight(bmdev->bm_tz_sensors[i].triggered, MAX_BM_THRESHOLD)) continue; for_each_set_bit(j, bmdev->bm_tz_sensors[i].triggered, MAX_BM_THRESHOLD) { notify_bw_threshold_event(&bmdev->bm_tz_sensors[i], &bmdev->bm_tz_sensors[i].thresh_bus_rule[j], bmdev->bm_tz_sensors[i].bus_rule_action[j]); clear_bit(j, bmdev->bm_tz_sensors[i].triggered); } } mutex_unlock(&bmdev->rule_lock); } static int bm_threshold_cb(struct notifier_block *nb, unsigned long action, void *data) { struct bm_tz_sensor *bm_sensor = container_of(nb, struct bm_tz_sensor, bm_nb); struct bus_rule_type *rule = data; enum bm_threshold_id thresh_id = *(int *)(rule->client_data); pr_debug( "srcid:%d, srcfld:%d op:%d thresh:%llu threshid:%d action:%lu curr_bw:%llu\n", *(rule->src_id), *(rule->src_field), *(rule->op), *(rule->thresh), thresh_id, action, rule->curr_bw); set_bit(thresh_id, bm_sensor->triggered); bm_sensor->bus_rule_action[thresh_id] = action; queue_work(system_freezable_wq, &bmdev->bm_wq); return 0; } static int bm_tz_get_curr_bw(struct thermal_zone_device *bmdev, unsigned long *bw) { struct bm_tz_sensor *bm_sensor = bmdev->devdata; int ret = 0; if (!bm_sensor || bm_sensor->mode != THERMAL_DEVICE_ENABLED || !bw) return -EINVAL; ret = msm_rule_query_bandwidth( &bm_sensor->thresh_bus_rule[BM_HIGH_THRESHOLD], (u64 *)bw, &bm_sensor->bm_nb); if (ret < 0) { pr_err("Error in reading curr bw, ret:%d\n", ret); return -EINVAL; } pr_debug("bm sensor[%s] curr bw:%lu\n", bm_sensor->bm_sensor, *bw); return 0; } static int bm_tz_get_mode(struct thermal_zone_device *bm, enum thermal_device_mode *mode) { struct bm_tz_sensor *bm_sensor = bm->devdata; if (!bm_sensor || !mode) return -EINVAL; *mode = bm_sensor->mode; return 0; } static int bm_tz_get_trip_type(struct thermal_zone_device *bm, int trip, enum thermal_trip_type *type) { struct bm_tz_sensor *bm_sensor = bm->devdata; if (!bm_sensor || trip < 0 || !type) return -EINVAL; switch (trip) { case BM_HIGH_THRESHOLD: *type = THERMAL_TRIP_CONFIGURABLE_HI; break; case BM_LOW_THRESHOLD: *type = THERMAL_TRIP_CONFIGURABLE_LOW; break; default: return -EINVAL; } return 0; } static int bm_tz_activate_trip_type(struct thermal_zone_device *bm, int trip, enum thermal_trip_activation_mode mode) { struct bm_tz_sensor *bm_sensor = bm->devdata; struct bus_rule_type new_rule = {0}; int op = -1; int thresh, ret = 0; if (!bm_sensor || trip < 0) return -EINVAL; switch (trip) { case BM_HIGH_THRESHOLD: bm_sensor->thresh_state[BM_HIGH_THRESHOLD].state = mode; if (mode != THERMAL_TRIP_ACTIVATION_DISABLED) thresh = bm_sensor->thresh_state[BM_HIGH_THRESHOLD].trip_bw; else thresh = UINT_MAX; op = OP_GT; break; case BM_LOW_THRESHOLD: bm_sensor->thresh_state[BM_LOW_THRESHOLD].state = mode; if (mode != THERMAL_TRIP_ACTIVATION_DISABLED) thresh = bm_sensor->thresh_state[BM_LOW_THRESHOLD].trip_bw; else thresh = 0; op = OP_LT; break; default: pr_err("Unknown trip %d\n", trip); return -EINVAL; } ret = create_bus_rule(&new_rule, bm_sensor->src_cnt, bm_sensor->src_id, bm_sensor->src_field, op, thresh, trip); if (ret) goto exit; bm_sensor->bm_nb.notifier_call = bm_threshold_cb; msm_rule_update(&bm_sensor->thresh_bus_rule[trip], &new_rule, &bm_sensor->bm_nb); memcpy(&bm_sensor->thresh_bus_rule[trip], &new_rule, sizeof(struct bus_rule_type)); msm_rule_evaluate_rules(bm_sensor->thresh_bus_rule[trip].src_id[0]); pr_debug( "srcid:%d, srcfld:%d op:%d thresh:%llu threshid:%d\n", *(bm_sensor->thresh_bus_rule[trip].src_id), *(bm_sensor->thresh_bus_rule[trip].src_field), *(bm_sensor->thresh_bus_rule[trip].op), *(bm_sensor->thresh_bus_rule[trip].thresh), *(int *)(bm_sensor->thresh_bus_rule[trip].client_data)); exit: return 0; }; static int bm_tz_get_trip_bw(struct thermal_zone_device *bmdev, int trip, unsigned long *bw) { struct bm_tz_sensor *bm_sensor = bmdev->devdata; if (!bm_sensor || trip < 0 || trip >= MAX_BM_THRESHOLD || !bw) return -EINVAL; *bw = bm_sensor->thresh_state[trip].trip_bw; return 0; }; static int bm_tz_set_trip_bw(struct thermal_zone_device *bmdev, int trip, unsigned long bw) { struct bm_tz_sensor *bm_sensor = bmdev->devdata; if (!bm_sensor || trip < 0 || trip >= MAX_BM_THRESHOLD) return -EINVAL; bm_sensor->thresh_state[trip].trip_bw = bw; return 0; }; static struct thermal_zone_device_ops bm_sensor_thermal_zone_ops = { .get_temp = bm_tz_get_curr_bw, .get_mode = bm_tz_get_mode, .get_trip_type = bm_tz_get_trip_type, .activate_trip_type = bm_tz_activate_trip_type, .get_trip_temp = bm_tz_get_trip_bw, .set_trip_temp = bm_tz_set_trip_bw, }; static int bm_sensors_register_thermal_zone(void) { int i = 0, ret = 0; for (i = 0; i < bmdev->bm_sensor_cnt; i++) { struct bm_tz_sensor *bm_sensor = &bmdev->bm_tz_sensors[i]; bm_sensor->mode = THERMAL_DEVICE_ENABLED; bm_sensor->tz_dev = thermal_zone_device_register(bm_sensor->bm_sensor, MAX_BM_THRESHOLD, BM_WRITABLE_TRIPS_MASK, bm_sensor, &bm_sensor_thermal_zone_ops, NULL, 0, 0); if (IS_ERR(bm_sensor->tz_dev)) { ret = PTR_ERR(bm_sensor->tz_dev); pr_err( "%s:thermal zone register failed. ret:%d\n", __func__, ret); goto fail; } } fail: return ret; } static void bm_sensors_unregister_thermal_zone(void) { int i = 0; for (i = 0; i < bmdev->bm_sensor_cnt; i++) { struct bm_tz_sensor *bm_sensor = &bmdev->bm_tz_sensors[i]; if (bm_sensor->tz_dev) thermal_zone_device_unregister(bm_sensor->tz_dev); bm_sensor->mode = THERMAL_DEVICE_DISABLED; } } static int read_device_tree_data(struct platform_device *pdev, struct bm_sensors_drv_data *bmdev) { int bm_sensor_cnt = 0, i = 0, err = 0, idx = 0; char *key = NULL; const char *src_f = NULL; struct device_node *node = pdev->dev.of_node, *child_node = NULL; struct bm_tz_sensor *bm_tz_sensors = NULL; struct device_node *bus_node = NULL; bm_sensor_cnt = of_get_child_count(node); if (bm_sensor_cnt == 0) { pr_err("No child node found\n"); err = -ENODEV; goto read_node_fail; } ALLOC_KMEM(&pdev->dev, bm_tz_sensors, struct bm_tz_sensor, bm_sensor_cnt, err); if (err) goto read_node_fail; for_each_child_of_node(node, child_node) { snprintf(bm_tz_sensors[i].bm_sensor, THERMAL_NAME_LENGTH, child_node->name); key = "qcom,bm-sensor"; if (!of_get_property(child_node, key, &bm_tz_sensors[i].src_cnt) || bm_tz_sensors[i].src_cnt <= 0) { pr_err("src id phandles are not defined\n"); err = -ENODEV; goto read_node_fail; } bm_tz_sensors[i].src_cnt /= sizeof(__be32); ALLOC_KMEM(&pdev->dev, bm_tz_sensors[i].src_id, int, bm_tz_sensors[i].src_cnt, err); if (err) goto read_node_fail; for (idx = 0; idx < bm_tz_sensors[i].src_cnt; idx++) { bus_node = of_parse_phandle(child_node, key, idx); if (!bus_node) { pr_err( "No src id phandle is defined with index:%d\n", idx); err = -ENODEV; goto read_node_fail; } err = of_property_read_u32(bus_node, "cell-id", &bm_tz_sensors[i].src_id[idx]); if (err) { pr_err("Bus node is missing cell-id. err:%d\n", err); goto read_node_fail; } } key = "qcom,bm-sensor-field"; err = of_property_read_string(child_node, key, &src_f); if (err) { pr_err("No %s field is found. err:%d\n", key, err); goto read_node_fail; } if (!strcmp(src_f, "ab")) { bm_tz_sensors[i].src_field = FLD_AB; } else if (!strcmp(src_f, "ib")) { bm_tz_sensors[i].src_field = FLD_IB; } else { pr_err("Unknown src field\n"); err = -EINVAL; goto read_node_fail; } i++; } bmdev->bm_tz_sensors = bm_tz_sensors; bmdev->bm_sensor_cnt = bm_sensor_cnt; read_node_fail: return err; } static void bus_rules_unregister(void) { int i = 0; for (; i < bmdev->bm_sensor_cnt; i++) { struct bm_tz_sensor *bm_sens = &bmdev->bm_tz_sensors[i]; if (bm_sens->bus_reg_done) { msm_rule_unregister(MAX_BM_THRESHOLD, bm_sens->thresh_bus_rule, &bm_sens->bm_nb); bm_sens->bus_reg_done = false; } } } static int init_bm_default_rules(void) { int ret = 0, i = 0; for (i = 0; i < bmdev->bm_sensor_cnt; i++) { struct bm_tz_sensor *bm_sens = &bmdev->bm_tz_sensors[i]; bitmap_zero(bm_sens->triggered, MAX_BM_THRESHOLD); ret = create_bus_rule( &bm_sens->thresh_bus_rule[BM_HIGH_THRESHOLD], bm_sens->src_cnt, bm_sens->src_id, bm_sens->src_field, OP_GT, ULLONG_MAX, BM_HIGH_THRESHOLD); if (ret) { pr_err("Creating bus rule is failed. ret:%d\n", ret); goto exit; } ret = create_bus_rule( &bm_sens->thresh_bus_rule[BM_LOW_THRESHOLD], bm_sens->src_cnt, bm_sens->src_id, bm_sens->src_field, OP_LT, 0, BM_LOW_THRESHOLD); if (ret) { pr_err("Creating bus rule is failed. ret:%d\n", ret); goto exit; } bm_sens->bm_nb.notifier_call = bm_threshold_cb; msm_rule_register(MAX_BM_THRESHOLD, bm_sens->thresh_bus_rule, &bm_sens->bm_nb); msm_rule_evaluate_rules( bm_sens->thresh_bus_rule[BM_HIGH_THRESHOLD].src_id[0]); bm_sens->bus_reg_done = true; } exit: if (ret) bus_rules_unregister(); return ret; } static void cleanup_drv_data(void) { if (bmdev) { bm_sensors_unregister_thermal_zone(); bus_rules_unregister(); flush_work(&bmdev->bm_wq); } } static int bm_tz_sensors_probe(struct platform_device *pdev) { int ret = 0; ALLOC_KMEM(&pdev->dev, bmdev, struct bm_sensors_drv_data, 1, ret); if (ret) goto probe_exit; bmdev->pdev = pdev; ret = read_device_tree_data(pdev, bmdev); if (ret) goto probe_exit; mutex_init(&bmdev->rule_lock); INIT_WORK(&bmdev->bm_wq, bm_tz_notify_wq); ret = init_bm_default_rules(); if (ret) goto probe_exit; ret = bm_sensors_register_thermal_zone(); if (ret) goto probe_exit; platform_set_drvdata(pdev, bmdev); probe_exit: if (ret) cleanup_drv_data(); return ret; } static int bm_tz_sensors_remove(struct platform_device *pdev) { cleanup_drv_data(); return 0; } struct of_device_id bm_sensors_match[] = { { .compatible = "qcom,bm-sensors", }, {}, }; static struct platform_driver bm_sensors_driver = { .probe = bm_tz_sensors_probe, .remove = bm_tz_sensors_remove, .driver = { .name = BM_SENSORS_DRIVER_NAME, .owner = THIS_MODULE, .of_match_table = bm_sensors_match, }, }; int __init bm_sensors_driver_init(void) { return platform_driver_register(&bm_sensors_driver); } static void __exit bm_sensors_driver_exit(void) { platform_driver_unregister(&bm_sensors_driver); } late_initcall(bm_sensors_driver_init); module_exit(bm_sensors_driver_exit); MODULE_DESCRIPTION("BANDWIDTH TZ SENSORS"); MODULE_ALIAS("platform:" BM_SENSORS_DRIVER_NAME); MODULE_LICENSE("GPL v2"); Loading
Documentation/devicetree/bindings/arm/msm/msm_bandwidth_monitor.txt 0 → 100644 +52 −0 Original line number Diff line number Diff line Bandwidth Monitor driver(BW_MONITOR) ===================================== Bandwidth Monitor driver creates logical sensor nodes with thermal core framework for the specified bus. This logical sensor will be used to monitor and mitigate the bus bandwidth. This driver can be configured to register any bus bandwidth as a sensor for it to be monitored. The device tree parameters for bandwidth monitor driver(BW_MONITOR) driver are: Properties: - compatible: Usage: required Value type: <string> Definition: shall be "qcom,bm-sensors" - <child node> Usage: required Definition: Each child node represents a bandwidth sensor and name of the node will be used as bandwidth sensor name. Each sensor can represent either single bus bandwidth or aggregation of multiple bus bandwidth. Child node Properties: - qcom,bm-sensor: Usage: required Value type: <array of phandle> Definition: This bandwidth sensor monitors aggregated bandwidth of these bus bandwidth. - qcom,bm-sensor-field: Usage: required Value type: <string> Definition: This property defines the bandwidth to be monitored, either average bandwidth(ab) or instantaneous bandwidth(ib). Example: qcom,bm-sensors { compatible = "qcom,bm-sensors"; bw_mm { qcom,bm-sensor = <&slv_snoc_bimc_0 &slv_snoc_bimc_2>; qcom,bm-sensor-field = "ab"; }; bw_apps { qcom,bm-sensor = <&mas_apps_proc>; qcom,bm-sensor-field = "ib"; }; };
drivers/platform/msm/Kconfig +9 −0 Original line number Diff line number Diff line Loading @@ -300,6 +300,15 @@ config MSM_11AD If you choose to build it as a module, it will be called msm_11ad_proxy. config BW_MONITOR bool "Bandwidth monitor driver" depends on BUS_TOPOLOGY_ADHOC && THERMAL help Enable this module if any bandwidth of a bus needs to be monitored and limited by thermal core framework. It does it by registering the bus bandwidth as a sensor in thermal core framework. source "drivers/platform/msm/spmi/Kconfig" endmenu
drivers/platform/msm/msm_bus/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ ifdef CONFIG_BUS_TOPOLOGY_ADHOC obj-$(CONFIG_OF) += msm_bus_of_adhoc.o obj-$(CONFIG_DEBUG_BUS_VOTER) += msm_bus_dbg_voter.o obj-$(CONFIG_CORESIGHT) += msm_buspm_coresight_adhoc.o obj-$(CONFIG_BW_MONITOR) += msm_bandwidth_monitor.o else obj-y += msm_bus_fabric.o msm_bus_config.o msm_bus_arb.o \ msm_bus_bimc.o msm_bus_noc.o Loading
drivers/platform/msm/msm_bus/msm_bandwidth_monitor.c 0 → 100644 +637 −0 Original line number Diff line number Diff line /* Copyright (c) 2016, 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 <linux/module.h> #include <linux/platform_device.h> #include <linux/workqueue.h> #include <linux/of.h> #include <linux/mutex.h> #include <linux/thermal.h> #include <dt-bindings/msm/msm-bus-ids.h> #include <dt-bindings/msm/msm-bus-rule-ops.h> #include <linux/msm_bus_rules.h> #define BM_SENSORS_DRIVER_NAME "bm_sensors" #define ALLOC_KMEM(dev, ptr, type, size, ret) \ do { \ ret = 0; \ ptr = devm_kzalloc(dev, \ sizeof(type) * size,\ GFP_KERNEL); \ if (!ptr) \ ret = -ENOMEM; \ } while (0) /* Trips: HIGH and LOW */ enum bm_threshold_id { BM_HIGH_THRESHOLD = 0, BM_LOW_THRESHOLD, MAX_BM_THRESHOLD, }; #define BM_WRITABLE_TRIPS_MASK ((1 << MAX_BM_THRESHOLD) - 1) struct bm_thresh_state { enum thermal_trip_activation_mode state; int trip_bw; }; struct bm_tz_sensor { /* Sensor info from dt */ char bm_sensor[THERMAL_NAME_LENGTH]; int src_cnt; int *src_id; int src_field; int op; /* bus rule interface struct */ struct bus_rule_type thresh_bus_rule[MAX_BM_THRESHOLD]; uint32_t bus_rule_action[MAX_BM_THRESHOLD]; unsigned long triggered[BITS_TO_LONGS(MAX_BM_THRESHOLD)]; bool bus_reg_done; /* Common callback notifier*/ struct notifier_block bm_nb; /* thermal zone handling */ struct thermal_zone_device *tz_dev; enum thermal_device_mode mode; struct bm_thresh_state thresh_state[MAX_BM_THRESHOLD]; }; struct bm_sensors_drv_data { struct platform_device *pdev; /* Workqueue Related */ struct work_struct bm_wq; /* Serialize workqueue handling */ struct mutex rule_lock; struct bm_tz_sensor *bm_tz_sensors; int bm_sensor_cnt; }; static struct bm_sensors_drv_data *bmdev; static void notify_bw_threshold_event(struct bm_tz_sensor *sensor, struct bus_rule_type *rule, uint32_t action) { enum bm_threshold_id thresh_id; u64 bw = 0; int ret = 0; if (!sensor || !rule) { pr_err("Invalid input parameters\n"); return; } thresh_id = *(int *)(rule->client_data); if (action == RULE_STATE_APPLIED) { sensor->thresh_state[thresh_id].state = THERMAL_TRIP_ACTIVATION_DISABLED; ret = msm_rule_query_bandwidth(rule, &bw, &sensor->bm_nb); if (ret < 0) { pr_err("Error in reading curr bw, ret:%d\n", ret); return; } thermal_sensor_trip(sensor->tz_dev, (thresh_id == BM_HIGH_THRESHOLD) ? THERMAL_TRIP_CONFIGURABLE_HI : THERMAL_TRIP_CONFIGURABLE_LOW, (long)bw); } else if (action != RULE_STATE_NOT_APPLIED) { pr_err_ratelimited("Error in high threshold interrupt\n"); } } static int create_bus_rule(struct bus_rule_type *rule, int src_cnt, int *src_ids, int src_field, int op, u64 val, int thresh_id) { int i = 0, ret = 0; if (!rule->src_id) { ALLOC_KMEM(&bmdev->pdev->dev, rule->src_id, int, src_cnt, ret); if (ret) goto rule_exit; } if (!rule->src_field) { ALLOC_KMEM(&bmdev->pdev->dev, rule->src_field, int, 1, ret); if (ret) goto rule_exit; } if (!rule->op) { ALLOC_KMEM(&bmdev->pdev->dev, rule->op, int, 1, ret); if (ret) goto rule_exit; } if (!rule->thresh) { ALLOC_KMEM(&bmdev->pdev->dev, rule->thresh, u64, 1, ret); if (ret) goto rule_exit; } if (!rule->client_data) { ALLOC_KMEM(&bmdev->pdev->dev, rule->client_data, int, 1, ret); if (ret) goto rule_exit; } rule->num_src = src_cnt; for (; i < src_cnt; i++) rule->src_id[i] = src_ids[i]; *(rule->src_field) = src_field; *(rule->op) = op; rule->num_thresh = 1; *(rule->thresh) = val; rule->num_dst = 0; rule->dst_node = NULL; rule->mode = THROTTLE_OFF; *(int *)(rule->client_data) = thresh_id; rule->dst_bw = 0; rule->combo_op = 0; rule_exit: if (ret) { if (rule->src_id) devm_kfree(&bmdev->pdev->dev, rule->src_id); if (rule->src_field) devm_kfree(&bmdev->pdev->dev, rule->src_field); if (rule->op) devm_kfree(&bmdev->pdev->dev, rule->op); if (rule->thresh) devm_kfree(&bmdev->pdev->dev, rule->thresh); if (rule->client_data) devm_kfree(&bmdev->pdev->dev, rule->client_data); } return ret; } static void bm_tz_notify_wq(struct work_struct *work) { int i = 0, j = 0; mutex_lock(&bmdev->rule_lock); /* Notify bw threshold events to thermal zone */ for (i = 0; i < bmdev->bm_sensor_cnt; i++) { if (!bitmap_weight(bmdev->bm_tz_sensors[i].triggered, MAX_BM_THRESHOLD)) continue; for_each_set_bit(j, bmdev->bm_tz_sensors[i].triggered, MAX_BM_THRESHOLD) { notify_bw_threshold_event(&bmdev->bm_tz_sensors[i], &bmdev->bm_tz_sensors[i].thresh_bus_rule[j], bmdev->bm_tz_sensors[i].bus_rule_action[j]); clear_bit(j, bmdev->bm_tz_sensors[i].triggered); } } mutex_unlock(&bmdev->rule_lock); } static int bm_threshold_cb(struct notifier_block *nb, unsigned long action, void *data) { struct bm_tz_sensor *bm_sensor = container_of(nb, struct bm_tz_sensor, bm_nb); struct bus_rule_type *rule = data; enum bm_threshold_id thresh_id = *(int *)(rule->client_data); pr_debug( "srcid:%d, srcfld:%d op:%d thresh:%llu threshid:%d action:%lu curr_bw:%llu\n", *(rule->src_id), *(rule->src_field), *(rule->op), *(rule->thresh), thresh_id, action, rule->curr_bw); set_bit(thresh_id, bm_sensor->triggered); bm_sensor->bus_rule_action[thresh_id] = action; queue_work(system_freezable_wq, &bmdev->bm_wq); return 0; } static int bm_tz_get_curr_bw(struct thermal_zone_device *bmdev, unsigned long *bw) { struct bm_tz_sensor *bm_sensor = bmdev->devdata; int ret = 0; if (!bm_sensor || bm_sensor->mode != THERMAL_DEVICE_ENABLED || !bw) return -EINVAL; ret = msm_rule_query_bandwidth( &bm_sensor->thresh_bus_rule[BM_HIGH_THRESHOLD], (u64 *)bw, &bm_sensor->bm_nb); if (ret < 0) { pr_err("Error in reading curr bw, ret:%d\n", ret); return -EINVAL; } pr_debug("bm sensor[%s] curr bw:%lu\n", bm_sensor->bm_sensor, *bw); return 0; } static int bm_tz_get_mode(struct thermal_zone_device *bm, enum thermal_device_mode *mode) { struct bm_tz_sensor *bm_sensor = bm->devdata; if (!bm_sensor || !mode) return -EINVAL; *mode = bm_sensor->mode; return 0; } static int bm_tz_get_trip_type(struct thermal_zone_device *bm, int trip, enum thermal_trip_type *type) { struct bm_tz_sensor *bm_sensor = bm->devdata; if (!bm_sensor || trip < 0 || !type) return -EINVAL; switch (trip) { case BM_HIGH_THRESHOLD: *type = THERMAL_TRIP_CONFIGURABLE_HI; break; case BM_LOW_THRESHOLD: *type = THERMAL_TRIP_CONFIGURABLE_LOW; break; default: return -EINVAL; } return 0; } static int bm_tz_activate_trip_type(struct thermal_zone_device *bm, int trip, enum thermal_trip_activation_mode mode) { struct bm_tz_sensor *bm_sensor = bm->devdata; struct bus_rule_type new_rule = {0}; int op = -1; int thresh, ret = 0; if (!bm_sensor || trip < 0) return -EINVAL; switch (trip) { case BM_HIGH_THRESHOLD: bm_sensor->thresh_state[BM_HIGH_THRESHOLD].state = mode; if (mode != THERMAL_TRIP_ACTIVATION_DISABLED) thresh = bm_sensor->thresh_state[BM_HIGH_THRESHOLD].trip_bw; else thresh = UINT_MAX; op = OP_GT; break; case BM_LOW_THRESHOLD: bm_sensor->thresh_state[BM_LOW_THRESHOLD].state = mode; if (mode != THERMAL_TRIP_ACTIVATION_DISABLED) thresh = bm_sensor->thresh_state[BM_LOW_THRESHOLD].trip_bw; else thresh = 0; op = OP_LT; break; default: pr_err("Unknown trip %d\n", trip); return -EINVAL; } ret = create_bus_rule(&new_rule, bm_sensor->src_cnt, bm_sensor->src_id, bm_sensor->src_field, op, thresh, trip); if (ret) goto exit; bm_sensor->bm_nb.notifier_call = bm_threshold_cb; msm_rule_update(&bm_sensor->thresh_bus_rule[trip], &new_rule, &bm_sensor->bm_nb); memcpy(&bm_sensor->thresh_bus_rule[trip], &new_rule, sizeof(struct bus_rule_type)); msm_rule_evaluate_rules(bm_sensor->thresh_bus_rule[trip].src_id[0]); pr_debug( "srcid:%d, srcfld:%d op:%d thresh:%llu threshid:%d\n", *(bm_sensor->thresh_bus_rule[trip].src_id), *(bm_sensor->thresh_bus_rule[trip].src_field), *(bm_sensor->thresh_bus_rule[trip].op), *(bm_sensor->thresh_bus_rule[trip].thresh), *(int *)(bm_sensor->thresh_bus_rule[trip].client_data)); exit: return 0; }; static int bm_tz_get_trip_bw(struct thermal_zone_device *bmdev, int trip, unsigned long *bw) { struct bm_tz_sensor *bm_sensor = bmdev->devdata; if (!bm_sensor || trip < 0 || trip >= MAX_BM_THRESHOLD || !bw) return -EINVAL; *bw = bm_sensor->thresh_state[trip].trip_bw; return 0; }; static int bm_tz_set_trip_bw(struct thermal_zone_device *bmdev, int trip, unsigned long bw) { struct bm_tz_sensor *bm_sensor = bmdev->devdata; if (!bm_sensor || trip < 0 || trip >= MAX_BM_THRESHOLD) return -EINVAL; bm_sensor->thresh_state[trip].trip_bw = bw; return 0; }; static struct thermal_zone_device_ops bm_sensor_thermal_zone_ops = { .get_temp = bm_tz_get_curr_bw, .get_mode = bm_tz_get_mode, .get_trip_type = bm_tz_get_trip_type, .activate_trip_type = bm_tz_activate_trip_type, .get_trip_temp = bm_tz_get_trip_bw, .set_trip_temp = bm_tz_set_trip_bw, }; static int bm_sensors_register_thermal_zone(void) { int i = 0, ret = 0; for (i = 0; i < bmdev->bm_sensor_cnt; i++) { struct bm_tz_sensor *bm_sensor = &bmdev->bm_tz_sensors[i]; bm_sensor->mode = THERMAL_DEVICE_ENABLED; bm_sensor->tz_dev = thermal_zone_device_register(bm_sensor->bm_sensor, MAX_BM_THRESHOLD, BM_WRITABLE_TRIPS_MASK, bm_sensor, &bm_sensor_thermal_zone_ops, NULL, 0, 0); if (IS_ERR(bm_sensor->tz_dev)) { ret = PTR_ERR(bm_sensor->tz_dev); pr_err( "%s:thermal zone register failed. ret:%d\n", __func__, ret); goto fail; } } fail: return ret; } static void bm_sensors_unregister_thermal_zone(void) { int i = 0; for (i = 0; i < bmdev->bm_sensor_cnt; i++) { struct bm_tz_sensor *bm_sensor = &bmdev->bm_tz_sensors[i]; if (bm_sensor->tz_dev) thermal_zone_device_unregister(bm_sensor->tz_dev); bm_sensor->mode = THERMAL_DEVICE_DISABLED; } } static int read_device_tree_data(struct platform_device *pdev, struct bm_sensors_drv_data *bmdev) { int bm_sensor_cnt = 0, i = 0, err = 0, idx = 0; char *key = NULL; const char *src_f = NULL; struct device_node *node = pdev->dev.of_node, *child_node = NULL; struct bm_tz_sensor *bm_tz_sensors = NULL; struct device_node *bus_node = NULL; bm_sensor_cnt = of_get_child_count(node); if (bm_sensor_cnt == 0) { pr_err("No child node found\n"); err = -ENODEV; goto read_node_fail; } ALLOC_KMEM(&pdev->dev, bm_tz_sensors, struct bm_tz_sensor, bm_sensor_cnt, err); if (err) goto read_node_fail; for_each_child_of_node(node, child_node) { snprintf(bm_tz_sensors[i].bm_sensor, THERMAL_NAME_LENGTH, child_node->name); key = "qcom,bm-sensor"; if (!of_get_property(child_node, key, &bm_tz_sensors[i].src_cnt) || bm_tz_sensors[i].src_cnt <= 0) { pr_err("src id phandles are not defined\n"); err = -ENODEV; goto read_node_fail; } bm_tz_sensors[i].src_cnt /= sizeof(__be32); ALLOC_KMEM(&pdev->dev, bm_tz_sensors[i].src_id, int, bm_tz_sensors[i].src_cnt, err); if (err) goto read_node_fail; for (idx = 0; idx < bm_tz_sensors[i].src_cnt; idx++) { bus_node = of_parse_phandle(child_node, key, idx); if (!bus_node) { pr_err( "No src id phandle is defined with index:%d\n", idx); err = -ENODEV; goto read_node_fail; } err = of_property_read_u32(bus_node, "cell-id", &bm_tz_sensors[i].src_id[idx]); if (err) { pr_err("Bus node is missing cell-id. err:%d\n", err); goto read_node_fail; } } key = "qcom,bm-sensor-field"; err = of_property_read_string(child_node, key, &src_f); if (err) { pr_err("No %s field is found. err:%d\n", key, err); goto read_node_fail; } if (!strcmp(src_f, "ab")) { bm_tz_sensors[i].src_field = FLD_AB; } else if (!strcmp(src_f, "ib")) { bm_tz_sensors[i].src_field = FLD_IB; } else { pr_err("Unknown src field\n"); err = -EINVAL; goto read_node_fail; } i++; } bmdev->bm_tz_sensors = bm_tz_sensors; bmdev->bm_sensor_cnt = bm_sensor_cnt; read_node_fail: return err; } static void bus_rules_unregister(void) { int i = 0; for (; i < bmdev->bm_sensor_cnt; i++) { struct bm_tz_sensor *bm_sens = &bmdev->bm_tz_sensors[i]; if (bm_sens->bus_reg_done) { msm_rule_unregister(MAX_BM_THRESHOLD, bm_sens->thresh_bus_rule, &bm_sens->bm_nb); bm_sens->bus_reg_done = false; } } } static int init_bm_default_rules(void) { int ret = 0, i = 0; for (i = 0; i < bmdev->bm_sensor_cnt; i++) { struct bm_tz_sensor *bm_sens = &bmdev->bm_tz_sensors[i]; bitmap_zero(bm_sens->triggered, MAX_BM_THRESHOLD); ret = create_bus_rule( &bm_sens->thresh_bus_rule[BM_HIGH_THRESHOLD], bm_sens->src_cnt, bm_sens->src_id, bm_sens->src_field, OP_GT, ULLONG_MAX, BM_HIGH_THRESHOLD); if (ret) { pr_err("Creating bus rule is failed. ret:%d\n", ret); goto exit; } ret = create_bus_rule( &bm_sens->thresh_bus_rule[BM_LOW_THRESHOLD], bm_sens->src_cnt, bm_sens->src_id, bm_sens->src_field, OP_LT, 0, BM_LOW_THRESHOLD); if (ret) { pr_err("Creating bus rule is failed. ret:%d\n", ret); goto exit; } bm_sens->bm_nb.notifier_call = bm_threshold_cb; msm_rule_register(MAX_BM_THRESHOLD, bm_sens->thresh_bus_rule, &bm_sens->bm_nb); msm_rule_evaluate_rules( bm_sens->thresh_bus_rule[BM_HIGH_THRESHOLD].src_id[0]); bm_sens->bus_reg_done = true; } exit: if (ret) bus_rules_unregister(); return ret; } static void cleanup_drv_data(void) { if (bmdev) { bm_sensors_unregister_thermal_zone(); bus_rules_unregister(); flush_work(&bmdev->bm_wq); } } static int bm_tz_sensors_probe(struct platform_device *pdev) { int ret = 0; ALLOC_KMEM(&pdev->dev, bmdev, struct bm_sensors_drv_data, 1, ret); if (ret) goto probe_exit; bmdev->pdev = pdev; ret = read_device_tree_data(pdev, bmdev); if (ret) goto probe_exit; mutex_init(&bmdev->rule_lock); INIT_WORK(&bmdev->bm_wq, bm_tz_notify_wq); ret = init_bm_default_rules(); if (ret) goto probe_exit; ret = bm_sensors_register_thermal_zone(); if (ret) goto probe_exit; platform_set_drvdata(pdev, bmdev); probe_exit: if (ret) cleanup_drv_data(); return ret; } static int bm_tz_sensors_remove(struct platform_device *pdev) { cleanup_drv_data(); return 0; } struct of_device_id bm_sensors_match[] = { { .compatible = "qcom,bm-sensors", }, {}, }; static struct platform_driver bm_sensors_driver = { .probe = bm_tz_sensors_probe, .remove = bm_tz_sensors_remove, .driver = { .name = BM_SENSORS_DRIVER_NAME, .owner = THIS_MODULE, .of_match_table = bm_sensors_match, }, }; int __init bm_sensors_driver_init(void) { return platform_driver_register(&bm_sensors_driver); } static void __exit bm_sensors_driver_exit(void) { platform_driver_unregister(&bm_sensors_driver); } late_initcall(bm_sensors_driver_init); module_exit(bm_sensors_driver_exit); MODULE_DESCRIPTION("BANDWIDTH TZ SENSORS"); MODULE_ALIAS("platform:" BM_SENSORS_DRIVER_NAME); MODULE_LICENSE("GPL v2");