Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 21731e03 authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "msm: msm_bus: Add Bandwidth Monitor driver"

parents 3f08a569 a3c10524
Loading
Loading
Loading
Loading
+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";
		};
	};
+9 −0
Original line number Diff line number Diff line
@@ -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
+1 −0
Original line number Diff line number Diff line
@@ -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
+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");