Loading Documentation/devicetree/bindings/devfreq/bimc-bwmon.txt 0 → 100644 +25 −0 Original line number Diff line number Diff line MSM BIMC bandwidth monitor device bimc-bwmon is a device that represents the MSM BIMC bandwidth monitors that can be used to measure the bandwidth of read/write traffic from the BIMC master ports. For example, the CPU subsystem sits on one BIMC master port. Required properties: - compatible: Must be "qcom,bimc-bwmon" - reg: Pairs of physical base addresses and region sizes of memory mapped registers. - reg-names: Names of the bases for the above registers. Expected bases are: "base", "global_base" - interrupts: Lists the threshold IRQ. - qcom,mport: The hardware master port that this device can monitor - qcom,target-dev: The DT device that corresponds to this master port Example: qcom,cpu-bwmon { compatible = "qcom,bimc-bwmon"; reg = <0xfc388000 0x300>, <0xfc381000 0x200>; reg-names = "base", "global_base"; interrupts = <0 183 1>; qcom,mport = <0>; qcom,target-dev = <&cpubw>; }; Documentation/devicetree/bindings/devfreq/devbw.txt 0 → 100644 +39 −0 Original line number Diff line number Diff line MSM device bandwidth device devbw is a device that represents a MSM device's BW requirements from its master port(s) to a different device's slave port(s) in a MSM SoC. This device is typically used to vote for BW requirements from a device's (Eg: CPU, GPU) master port(s) to the slave (Eg: DDR) port(s). Required properties: - compatible: Must be "qcom,devbw" - qcom,src-dst-ports: A list of tuples where each tuple consists of a bus master port number and a bus slave port number. - qcom,bw-tbl: A list of meaningful instantaneous bandwidth values (in MB/s) that can be requested from the device master port to the slave port. The list of values depend on the supported bus/slave frequencies and the bus width. Optional properties: - qcom,active-only: Indicates that the bandwidth votes need to be enforced only when the CPU subsystem is active. - governor: Initial governor to use for the device. Default: "performance" Example: qcom,cpubw { compatible = "qcom,devbw"; qcom,src-dst-ports = <1 512>, <2 512>; qcom,active-only; qcom,bw-tbl = < 572 /* 75 MHz */ >, < 1144 /* 150 MHz */ >, < 1525 /* 200 MHz */ >, < 2342 /* 307 MHz */ >, < 3509 /* 460 MHz */ >, < 4684 /* 614 MHz */ >, < 6103 /* 800 MHz */ >, < 7102 /* 931 MHz */ >; }; drivers/devfreq/Kconfig +24 −0 Original line number Diff line number Diff line Loading @@ -73,6 +73,16 @@ config DEVFREQ_GOV_PASSIVE through sysfs entries. The passive governor recommends that devfreq device uses the OPP table to get the frequency/voltage. config DEVFREQ_GOV_QCOM_BW_HWMON tristate "HW monitor based governor for device BW" depends on QCOM_BIMC_BWMON help HW monitor based governor for device to DDR bandwidth voting. This governor sets the CPU BW vote by using BIMC counters to monitor the CPU's use of DDR. Since this uses target specific counters it can conflict with existing profiling tools. This governor is unlikely to be useful for non-QCOM devices. config DEVFREQ_GOV_QCOM_CACHE_HWMON tristate "HW monitor based governor for cache frequency" help Loading Loading @@ -146,6 +156,20 @@ config DEVFREQ_SIMPLE_DEV Device driver for simple devices that control their frequency using clock APIs and don't have any form of status reporting. config QCOM_DEVFREQ_DEVBW bool "Qualcomm Technologies Inc. DEVFREQ device for device master <-> slave IB/AB BW voting" depends on ARCH_QCOM select DEVFREQ_GOV_PERFORMANCE select DEVFREQ_GOV_POWERSAVE select DEVFREQ_GOV_USERSPACE select DEVFREQ_GOV_CPUFREQ default n help Different devfreq governors use this devfreq device to make CPU to DDR IB/AB bandwidth votes. This driver provides a SoC topology agnostic interface to so that some of the devfreq governors can be shared across SoCs. source "drivers/devfreq/event/Kconfig" endif # PM_DEVFREQ drivers/devfreq/Makefile +2 −0 Original line number Diff line number Diff line Loading @@ -6,6 +6,7 @@ obj-$(CONFIG_DEVFREQ_GOV_PERFORMANCE) += governor_performance.o obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE) += governor_powersave.o obj-$(CONFIG_DEVFREQ_GOV_USERSPACE) += governor_userspace.o obj-$(CONFIG_DEVFREQ_GOV_PASSIVE) += governor_passive.o obj-$(CONFIG_DEVFREQ_GOV_QCOM_BW_HWMON) += governor_bw_hwmon.o obj-$(CONFIG_DEVFREQ_GOV_QCOM_CACHE_HWMON) += governor_cache_hwmon.o # DEVFREQ Drivers Loading @@ -13,6 +14,7 @@ obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ) += exynos-bus.o obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ) += rk3399_dmc.o obj-$(CONFIG_ARM_TEGRA_DEVFREQ) += tegra-devfreq.o obj-$(CONFIG_ARM_QCOM_DEVFREQ_FW) += devfreq_qcom_fw.o obj-$(CONFIG_QCOM_DEVFREQ_DEVBW) += devfreq_devbw.o obj-$(CONFIG_DEVFREQ_SIMPLE_DEV) += devfreq_simple_dev.o # DEVFREQ Event Drivers Loading drivers/devfreq/devfreq_devbw.c 0 → 100644 +244 −0 Original line number Diff line number Diff line // SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2013-2014, 2019, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "devbw: " fmt #include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> #include <linux/io.h> #include <linux/delay.h> #include <linux/ktime.h> #include <linux/time.h> #include <linux/err.h> #include <linux/errno.h> #include <linux/mutex.h> #include <linux/interrupt.h> #include <linux/devfreq.h> #include <linux/of.h> #include <trace/events/power.h> #include <linux/msm-bus.h> #include <linux/msm-bus-board.h> /* Has to be ULL to prevent overflow where this macro is used. */ #define MBYTE (1ULL << 20) #define MAX_PATHS 2 #define DBL_BUF 2 struct dev_data { struct msm_bus_vectors vectors[MAX_PATHS * DBL_BUF]; struct msm_bus_paths bw_levels[DBL_BUF]; struct msm_bus_scale_pdata bw_data; int num_paths; u32 bus_client; int cur_idx; int cur_ab; int cur_ib; long gov_ab; struct devfreq *df; struct devfreq_dev_profile dp; }; static int set_bw(struct device *dev, int new_ib, int new_ab) { struct dev_data *d = dev_get_drvdata(dev); int i, ret; if (d->cur_ib == new_ib && d->cur_ab == new_ab) return 0; i = (d->cur_idx + 1) % DBL_BUF; d->bw_levels[i].vectors[0].ib = new_ib * MBYTE; d->bw_levels[i].vectors[0].ab = new_ab / d->num_paths * MBYTE; d->bw_levels[i].vectors[1].ib = new_ib * MBYTE; d->bw_levels[i].vectors[1].ab = new_ab / d->num_paths * MBYTE; dev_dbg(dev, "BW MBps: AB: %d IB: %d\n", new_ab, new_ib); ret = msm_bus_scale_client_update_request(d->bus_client, i); if (ret) { dev_err(dev, "bandwidth request failed (%d)\n", ret); } else { d->cur_idx = i; d->cur_ib = new_ib; d->cur_ab = new_ab; } return ret; } static void find_freq(struct devfreq_dev_profile *p, unsigned long *freq, u32 flags) { int i; unsigned long atmost, atleast, f; atmost = p->freq_table[0]; atleast = p->freq_table[p->max_state-1]; for (i = 0; i < p->max_state; i++) { f = p->freq_table[i]; if (f <= *freq) atmost = max(f, atmost); if (f >= *freq) atleast = min(f, atleast); } if (flags & DEVFREQ_FLAG_LEAST_UPPER_BOUND) *freq = atmost; else *freq = atleast; } static int devbw_target(struct device *dev, unsigned long *freq, u32 flags) { struct dev_data *d = dev_get_drvdata(dev); find_freq(&d->dp, freq, flags); return set_bw(dev, *freq, d->gov_ab); } static int devbw_get_dev_status(struct device *dev, struct devfreq_dev_status *stat) { struct dev_data *d = dev_get_drvdata(dev); stat->private_data = &d->gov_ab; return 0; } #define PROP_PORTS "qcom,src-dst-ports" #define PROP_TBL "qcom,bw-tbl" #define PROP_ACTIVE "qcom,active-only" int devfreq_add_devbw(struct device *dev) { struct dev_data *d; struct devfreq_dev_profile *p; u32 *data, ports[MAX_PATHS * 2]; const char *gov_name; int ret, len, i, num_paths; d = devm_kzalloc(dev, sizeof(*d), GFP_KERNEL); if (!d) return -ENOMEM; dev_set_drvdata(dev, d); if (of_find_property(dev->of_node, PROP_PORTS, &len)) { len /= sizeof(ports[0]); if (len % 2 || len > ARRAY_SIZE(ports)) { dev_err(dev, "Unexpected number of ports\n"); return -EINVAL; } ret = of_property_read_u32_array(dev->of_node, PROP_PORTS, ports, len); if (ret) return ret; num_paths = len / 2; } else { return -EINVAL; } d->bw_levels[0].vectors = &d->vectors[0]; d->bw_levels[1].vectors = &d->vectors[MAX_PATHS]; d->bw_data.usecase = d->bw_levels; d->bw_data.num_usecases = ARRAY_SIZE(d->bw_levels); d->bw_data.name = dev_name(dev); d->bw_data.active_only = of_property_read_bool(dev->of_node, PROP_ACTIVE); for (i = 0; i < num_paths; i++) { d->bw_levels[0].vectors[i].src = ports[2 * i]; d->bw_levels[0].vectors[i].dst = ports[2 * i + 1]; d->bw_levels[1].vectors[i].src = ports[2 * i]; d->bw_levels[1].vectors[i].dst = ports[2 * i + 1]; } d->bw_levels[0].num_paths = num_paths; d->bw_levels[1].num_paths = num_paths; d->num_paths = num_paths; p = &d->dp; p->polling_ms = 50; p->target = devbw_target; p->get_dev_status = devbw_get_dev_status; if (of_find_property(dev->of_node, PROP_TBL, &len)) { len /= sizeof(*data); data = devm_kzalloc(dev, len * sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; p->freq_table = devm_kzalloc(dev, len * sizeof(*p->freq_table), GFP_KERNEL); if (!p->freq_table) return -ENOMEM; ret = of_property_read_u32_array(dev->of_node, PROP_TBL, data, len); if (ret) return ret; for (i = 0; i < len; i++) p->freq_table[i] = data[i]; p->max_state = len; } d->bus_client = msm_bus_scale_register_client(&d->bw_data); if (!d->bus_client) { dev_err(dev, "Unable to register bus client\n"); return -ENODEV; } if (of_property_read_string(dev->of_node, "governor", &gov_name)) gov_name = "performance"; d->df = devfreq_add_device(dev, p, gov_name, NULL); if (IS_ERR(d->df)) { msm_bus_scale_unregister_client(d->bus_client); return PTR_ERR(d->df); } return 0; } static int devfreq_devbw_probe(struct platform_device *pdev) { return devfreq_add_devbw(&pdev->dev); } int devfreq_remove_devbw(struct device *dev) { struct dev_data *d = dev_get_drvdata(dev); msm_bus_scale_unregister_client(d->bus_client); devfreq_remove_device(d->df); return 0; } static int devfreq_devbw_remove(struct platform_device *pdev) { return devfreq_remove_devbw(&pdev->dev); } static const struct of_device_id devbw_match_table[] = { { .compatible = "qcom,devbw" }, {} }; static struct platform_driver devbw_driver = { .probe = devfreq_devbw_probe, .remove = devfreq_devbw_remove, .driver = { .name = "devbw", .of_match_table = devbw_match_table, }, }; module_platform_driver(devbw_driver); MODULE_DESCRIPTION("Device DDR bandwidth voting driver MSM SoCs"); MODULE_LICENSE("GPL v2"); Loading
Documentation/devicetree/bindings/devfreq/bimc-bwmon.txt 0 → 100644 +25 −0 Original line number Diff line number Diff line MSM BIMC bandwidth monitor device bimc-bwmon is a device that represents the MSM BIMC bandwidth monitors that can be used to measure the bandwidth of read/write traffic from the BIMC master ports. For example, the CPU subsystem sits on one BIMC master port. Required properties: - compatible: Must be "qcom,bimc-bwmon" - reg: Pairs of physical base addresses and region sizes of memory mapped registers. - reg-names: Names of the bases for the above registers. Expected bases are: "base", "global_base" - interrupts: Lists the threshold IRQ. - qcom,mport: The hardware master port that this device can monitor - qcom,target-dev: The DT device that corresponds to this master port Example: qcom,cpu-bwmon { compatible = "qcom,bimc-bwmon"; reg = <0xfc388000 0x300>, <0xfc381000 0x200>; reg-names = "base", "global_base"; interrupts = <0 183 1>; qcom,mport = <0>; qcom,target-dev = <&cpubw>; };
Documentation/devicetree/bindings/devfreq/devbw.txt 0 → 100644 +39 −0 Original line number Diff line number Diff line MSM device bandwidth device devbw is a device that represents a MSM device's BW requirements from its master port(s) to a different device's slave port(s) in a MSM SoC. This device is typically used to vote for BW requirements from a device's (Eg: CPU, GPU) master port(s) to the slave (Eg: DDR) port(s). Required properties: - compatible: Must be "qcom,devbw" - qcom,src-dst-ports: A list of tuples where each tuple consists of a bus master port number and a bus slave port number. - qcom,bw-tbl: A list of meaningful instantaneous bandwidth values (in MB/s) that can be requested from the device master port to the slave port. The list of values depend on the supported bus/slave frequencies and the bus width. Optional properties: - qcom,active-only: Indicates that the bandwidth votes need to be enforced only when the CPU subsystem is active. - governor: Initial governor to use for the device. Default: "performance" Example: qcom,cpubw { compatible = "qcom,devbw"; qcom,src-dst-ports = <1 512>, <2 512>; qcom,active-only; qcom,bw-tbl = < 572 /* 75 MHz */ >, < 1144 /* 150 MHz */ >, < 1525 /* 200 MHz */ >, < 2342 /* 307 MHz */ >, < 3509 /* 460 MHz */ >, < 4684 /* 614 MHz */ >, < 6103 /* 800 MHz */ >, < 7102 /* 931 MHz */ >; };
drivers/devfreq/Kconfig +24 −0 Original line number Diff line number Diff line Loading @@ -73,6 +73,16 @@ config DEVFREQ_GOV_PASSIVE through sysfs entries. The passive governor recommends that devfreq device uses the OPP table to get the frequency/voltage. config DEVFREQ_GOV_QCOM_BW_HWMON tristate "HW monitor based governor for device BW" depends on QCOM_BIMC_BWMON help HW monitor based governor for device to DDR bandwidth voting. This governor sets the CPU BW vote by using BIMC counters to monitor the CPU's use of DDR. Since this uses target specific counters it can conflict with existing profiling tools. This governor is unlikely to be useful for non-QCOM devices. config DEVFREQ_GOV_QCOM_CACHE_HWMON tristate "HW monitor based governor for cache frequency" help Loading Loading @@ -146,6 +156,20 @@ config DEVFREQ_SIMPLE_DEV Device driver for simple devices that control their frequency using clock APIs and don't have any form of status reporting. config QCOM_DEVFREQ_DEVBW bool "Qualcomm Technologies Inc. DEVFREQ device for device master <-> slave IB/AB BW voting" depends on ARCH_QCOM select DEVFREQ_GOV_PERFORMANCE select DEVFREQ_GOV_POWERSAVE select DEVFREQ_GOV_USERSPACE select DEVFREQ_GOV_CPUFREQ default n help Different devfreq governors use this devfreq device to make CPU to DDR IB/AB bandwidth votes. This driver provides a SoC topology agnostic interface to so that some of the devfreq governors can be shared across SoCs. source "drivers/devfreq/event/Kconfig" endif # PM_DEVFREQ
drivers/devfreq/Makefile +2 −0 Original line number Diff line number Diff line Loading @@ -6,6 +6,7 @@ obj-$(CONFIG_DEVFREQ_GOV_PERFORMANCE) += governor_performance.o obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE) += governor_powersave.o obj-$(CONFIG_DEVFREQ_GOV_USERSPACE) += governor_userspace.o obj-$(CONFIG_DEVFREQ_GOV_PASSIVE) += governor_passive.o obj-$(CONFIG_DEVFREQ_GOV_QCOM_BW_HWMON) += governor_bw_hwmon.o obj-$(CONFIG_DEVFREQ_GOV_QCOM_CACHE_HWMON) += governor_cache_hwmon.o # DEVFREQ Drivers Loading @@ -13,6 +14,7 @@ obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ) += exynos-bus.o obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ) += rk3399_dmc.o obj-$(CONFIG_ARM_TEGRA_DEVFREQ) += tegra-devfreq.o obj-$(CONFIG_ARM_QCOM_DEVFREQ_FW) += devfreq_qcom_fw.o obj-$(CONFIG_QCOM_DEVFREQ_DEVBW) += devfreq_devbw.o obj-$(CONFIG_DEVFREQ_SIMPLE_DEV) += devfreq_simple_dev.o # DEVFREQ Event Drivers Loading
drivers/devfreq/devfreq_devbw.c 0 → 100644 +244 −0 Original line number Diff line number Diff line // SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2013-2014, 2019, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "devbw: " fmt #include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> #include <linux/io.h> #include <linux/delay.h> #include <linux/ktime.h> #include <linux/time.h> #include <linux/err.h> #include <linux/errno.h> #include <linux/mutex.h> #include <linux/interrupt.h> #include <linux/devfreq.h> #include <linux/of.h> #include <trace/events/power.h> #include <linux/msm-bus.h> #include <linux/msm-bus-board.h> /* Has to be ULL to prevent overflow where this macro is used. */ #define MBYTE (1ULL << 20) #define MAX_PATHS 2 #define DBL_BUF 2 struct dev_data { struct msm_bus_vectors vectors[MAX_PATHS * DBL_BUF]; struct msm_bus_paths bw_levels[DBL_BUF]; struct msm_bus_scale_pdata bw_data; int num_paths; u32 bus_client; int cur_idx; int cur_ab; int cur_ib; long gov_ab; struct devfreq *df; struct devfreq_dev_profile dp; }; static int set_bw(struct device *dev, int new_ib, int new_ab) { struct dev_data *d = dev_get_drvdata(dev); int i, ret; if (d->cur_ib == new_ib && d->cur_ab == new_ab) return 0; i = (d->cur_idx + 1) % DBL_BUF; d->bw_levels[i].vectors[0].ib = new_ib * MBYTE; d->bw_levels[i].vectors[0].ab = new_ab / d->num_paths * MBYTE; d->bw_levels[i].vectors[1].ib = new_ib * MBYTE; d->bw_levels[i].vectors[1].ab = new_ab / d->num_paths * MBYTE; dev_dbg(dev, "BW MBps: AB: %d IB: %d\n", new_ab, new_ib); ret = msm_bus_scale_client_update_request(d->bus_client, i); if (ret) { dev_err(dev, "bandwidth request failed (%d)\n", ret); } else { d->cur_idx = i; d->cur_ib = new_ib; d->cur_ab = new_ab; } return ret; } static void find_freq(struct devfreq_dev_profile *p, unsigned long *freq, u32 flags) { int i; unsigned long atmost, atleast, f; atmost = p->freq_table[0]; atleast = p->freq_table[p->max_state-1]; for (i = 0; i < p->max_state; i++) { f = p->freq_table[i]; if (f <= *freq) atmost = max(f, atmost); if (f >= *freq) atleast = min(f, atleast); } if (flags & DEVFREQ_FLAG_LEAST_UPPER_BOUND) *freq = atmost; else *freq = atleast; } static int devbw_target(struct device *dev, unsigned long *freq, u32 flags) { struct dev_data *d = dev_get_drvdata(dev); find_freq(&d->dp, freq, flags); return set_bw(dev, *freq, d->gov_ab); } static int devbw_get_dev_status(struct device *dev, struct devfreq_dev_status *stat) { struct dev_data *d = dev_get_drvdata(dev); stat->private_data = &d->gov_ab; return 0; } #define PROP_PORTS "qcom,src-dst-ports" #define PROP_TBL "qcom,bw-tbl" #define PROP_ACTIVE "qcom,active-only" int devfreq_add_devbw(struct device *dev) { struct dev_data *d; struct devfreq_dev_profile *p; u32 *data, ports[MAX_PATHS * 2]; const char *gov_name; int ret, len, i, num_paths; d = devm_kzalloc(dev, sizeof(*d), GFP_KERNEL); if (!d) return -ENOMEM; dev_set_drvdata(dev, d); if (of_find_property(dev->of_node, PROP_PORTS, &len)) { len /= sizeof(ports[0]); if (len % 2 || len > ARRAY_SIZE(ports)) { dev_err(dev, "Unexpected number of ports\n"); return -EINVAL; } ret = of_property_read_u32_array(dev->of_node, PROP_PORTS, ports, len); if (ret) return ret; num_paths = len / 2; } else { return -EINVAL; } d->bw_levels[0].vectors = &d->vectors[0]; d->bw_levels[1].vectors = &d->vectors[MAX_PATHS]; d->bw_data.usecase = d->bw_levels; d->bw_data.num_usecases = ARRAY_SIZE(d->bw_levels); d->bw_data.name = dev_name(dev); d->bw_data.active_only = of_property_read_bool(dev->of_node, PROP_ACTIVE); for (i = 0; i < num_paths; i++) { d->bw_levels[0].vectors[i].src = ports[2 * i]; d->bw_levels[0].vectors[i].dst = ports[2 * i + 1]; d->bw_levels[1].vectors[i].src = ports[2 * i]; d->bw_levels[1].vectors[i].dst = ports[2 * i + 1]; } d->bw_levels[0].num_paths = num_paths; d->bw_levels[1].num_paths = num_paths; d->num_paths = num_paths; p = &d->dp; p->polling_ms = 50; p->target = devbw_target; p->get_dev_status = devbw_get_dev_status; if (of_find_property(dev->of_node, PROP_TBL, &len)) { len /= sizeof(*data); data = devm_kzalloc(dev, len * sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; p->freq_table = devm_kzalloc(dev, len * sizeof(*p->freq_table), GFP_KERNEL); if (!p->freq_table) return -ENOMEM; ret = of_property_read_u32_array(dev->of_node, PROP_TBL, data, len); if (ret) return ret; for (i = 0; i < len; i++) p->freq_table[i] = data[i]; p->max_state = len; } d->bus_client = msm_bus_scale_register_client(&d->bw_data); if (!d->bus_client) { dev_err(dev, "Unable to register bus client\n"); return -ENODEV; } if (of_property_read_string(dev->of_node, "governor", &gov_name)) gov_name = "performance"; d->df = devfreq_add_device(dev, p, gov_name, NULL); if (IS_ERR(d->df)) { msm_bus_scale_unregister_client(d->bus_client); return PTR_ERR(d->df); } return 0; } static int devfreq_devbw_probe(struct platform_device *pdev) { return devfreq_add_devbw(&pdev->dev); } int devfreq_remove_devbw(struct device *dev) { struct dev_data *d = dev_get_drvdata(dev); msm_bus_scale_unregister_client(d->bus_client); devfreq_remove_device(d->df); return 0; } static int devfreq_devbw_remove(struct platform_device *pdev) { return devfreq_remove_devbw(&pdev->dev); } static const struct of_device_id devbw_match_table[] = { { .compatible = "qcom,devbw" }, {} }; static struct platform_driver devbw_driver = { .probe = devfreq_devbw_probe, .remove = devfreq_devbw_remove, .driver = { .name = "devbw", .of_match_table = devbw_match_table, }, }; module_platform_driver(devbw_driver); MODULE_DESCRIPTION("Device DDR bandwidth voting driver MSM SoCs"); MODULE_LICENSE("GPL v2");