diff options
author | Mustafa Yigit Bilgen <mbilgen@nvidia.com> | 2017-08-08 20:34:12 -0400 |
---|---|---|
committer | mobile promotions <svcmobile_promotions@nvidia.com> | 2017-08-17 15:18:47 -0400 |
commit | 085d4268967d17cf01b3762ccd2efc94c4ab018f (patch) | |
tree | 12d91587acb46ec16701a21be81f6c12274940f6 | |
parent | a7dc8adc8091d45dc22900affb7a4b73ccf93c01 (diff) |
nvpmodel: support clocks from the device tree
* Look for a device tree node to enable the driver.
* Support capping arbitrary clocks, provided from the device tree.
* Reorder bwmgr registration and sysfs file creation to avoid a
potential race where the store/show callback could be called before
registration.
Bug 1959611
Change-Id: I98fba96f0b2f5dc0f7abbb9df5e7f1f5da8907f0
Signed-off-by: Mustafa Yigit Bilgen <mbilgen@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/1539842
GVS: Gerrit_Virtual_Submit
Reviewed-by: Terry Wang <terwang@nvidia.com>
Reviewed-by: Bharat Nihalani <bnihalani@nvidia.com>
-rw-r--r-- | Documentation/devicetree/bindings/nvpmodel/nvpmodel.txt | 30 | ||||
-rw-r--r-- | drivers/nvpmodel/nvpmodel_emc_cap.c | 187 |
2 files changed, 189 insertions, 28 deletions
diff --git a/Documentation/devicetree/bindings/nvpmodel/nvpmodel.txt b/Documentation/devicetree/bindings/nvpmodel/nvpmodel.txt new file mode 100644 index 000000000..5fd3af2d7 --- /dev/null +++ b/Documentation/devicetree/bindings/nvpmodel/nvpmodel.txt | |||
@@ -0,0 +1,30 @@ | |||
1 | NVIDIA nvpmodel driver bindings | ||
2 | |||
3 | Nvpmodel is a driver that provides sysfs nodes that allow capping certain clock | ||
4 | frequencies in order to keep the power consumption under a certain budget. | ||
5 | |||
6 | These caps are designed to be relatively static. They should not be used | ||
7 | during runtime (under load) to dynamically change the power budget. | ||
8 | |||
9 | Required properties: | ||
10 | |||
11 | - compatible: should be "nvidia,nvpmodel" | ||
12 | |||
13 | Optional properties: | ||
14 | |||
15 | - clocks: Clock IDs that the driver should support capping | ||
16 | - clock-names: List of strings which identify the relevant clocks | ||
17 | |||
18 | Both properties must have the same number of clocks. The strings that appear | ||
19 | in the clock-names property will be the names of the sysfs nodes that will be | ||
20 | provided. The clock IDs and names should appear in the same order. | ||
21 | |||
22 | Sample: | ||
23 | |||
24 | nvpmodel: { | ||
25 | compatible = "nvidia,nvpmodel"; | ||
26 | clocks = <&tegra_car TEGRA186_CLK_NVENC | ||
27 | &tegra_car TEGRA186_CLK_NVDEC>; | ||
28 | clock-names = "nvenc", "nvdec"; | ||
29 | status = "okay"; | ||
30 | }; | ||
diff --git a/drivers/nvpmodel/nvpmodel_emc_cap.c b/drivers/nvpmodel/nvpmodel_emc_cap.c index a55562d6b..dbff63947 100644 --- a/drivers/nvpmodel/nvpmodel_emc_cap.c +++ b/drivers/nvpmodel/nvpmodel_emc_cap.c | |||
@@ -21,13 +21,16 @@ | |||
21 | #include <linux/sysfs.h> | 21 | #include <linux/sysfs.h> |
22 | #include <linux/init.h> | 22 | #include <linux/init.h> |
23 | #include <linux/fs.h> | 23 | #include <linux/fs.h> |
24 | #include <linux/slab.h> | ||
24 | #include <linux/string.h> | 25 | #include <linux/string.h> |
26 | #include <linux/clk.h> | ||
27 | #include <linux/of.h> | ||
25 | #include <linux/platform/tegra/emc_bwmgr.h> | 28 | #include <linux/platform/tegra/emc_bwmgr.h> |
26 | #include <linux/platform/tegra/bwmgr_mc.h> | 29 | #include <linux/platform/tegra/bwmgr_mc.h> |
27 | 30 | ||
28 | #define AUTHOR "Terry Wang <terwang@nvidia.com>" | 31 | #define AUTHOR "Terry Wang <terwang@nvidia.com>" |
29 | #define DESCRIPTION "Nvpmodel EMC cap driver" | 32 | #define DESCRIPTION "Nvpmodel clock cap driver" |
30 | #define MODULE_NAME "Nvpmodel_EMC_cap" | 33 | #define MODULE_NAME "Nvpmodel_clk_cap" |
31 | #define VERSION "1.0" | 34 | #define VERSION "1.0" |
32 | 35 | ||
33 | /* Module information */ | 36 | /* Module information */ |
@@ -36,12 +39,20 @@ MODULE_DESCRIPTION(DESCRIPTION); | |||
36 | MODULE_VERSION(VERSION); | 39 | MODULE_VERSION(VERSION); |
37 | MODULE_LICENSE("GPL"); | 40 | MODULE_LICENSE("GPL"); |
38 | 41 | ||
39 | static struct kobject *emc_iso_cap_kobject; | 42 | static struct kobject *clk_cap_kobject; |
40 | static unsigned long emc_iso_cap; | 43 | static unsigned long emc_iso_cap; |
41 | 44 | ||
42 | /* bandwidth manager handle */ | 45 | /* bandwidth manager handle */ |
43 | struct tegra_bwmgr_client *bwmgr_handle; | 46 | struct tegra_bwmgr_client *bwmgr_handle; |
44 | 47 | ||
48 | struct nvpmodel_clk { | ||
49 | struct kobj_attribute attr; | ||
50 | struct clk *clk; | ||
51 | }; | ||
52 | |||
53 | static struct nvpmodel_clk *clks; | ||
54 | static int num_clocks; | ||
55 | |||
45 | static ssize_t emc_iso_cap_show(struct kobject *kobj, | 56 | static ssize_t emc_iso_cap_show(struct kobject *kobj, |
46 | struct kobj_attribute *attr, char *buf) | 57 | struct kobj_attribute *attr, char *buf) |
47 | { | 58 | { |
@@ -53,12 +64,16 @@ static ssize_t emc_iso_cap_store(struct kobject *kobj, | |||
53 | size_t count) | 64 | size_t count) |
54 | { | 65 | { |
55 | int error = 0; | 66 | int error = 0; |
56 | sscanf(buf, "%lu", &emc_iso_cap); | 67 | if (sscanf(buf, "%lu", &emc_iso_cap) != 1) |
68 | return -EINVAL; | ||
69 | |||
57 | error = tegra_bwmgr_set_emc(bwmgr_handle, emc_iso_cap, | 70 | error = tegra_bwmgr_set_emc(bwmgr_handle, emc_iso_cap, |
58 | TEGRA_BWMGR_SET_EMC_ISO_CAP); | 71 | TEGRA_BWMGR_SET_EMC_ISO_CAP); |
59 | if (error) | 72 | if (error) { |
60 | pr_warn("Nvpmodel failed to set EMC hz=%lu errno=%d\n", | 73 | pr_warn("Nvpmodel failed to set EMC hz=%lu errno=%d\n", |
61 | emc_iso_cap, error); | 74 | emc_iso_cap, error); |
75 | return error; | ||
76 | } | ||
62 | 77 | ||
63 | return count; | 78 | return count; |
64 | } | 79 | } |
@@ -66,47 +81,163 @@ static ssize_t emc_iso_cap_store(struct kobject *kobj, | |||
66 | static struct kobj_attribute emc_iso_cap_attribute = | 81 | static struct kobj_attribute emc_iso_cap_attribute = |
67 | __ATTR(emc_iso_cap, 0660, emc_iso_cap_show, emc_iso_cap_store); | 82 | __ATTR(emc_iso_cap, 0660, emc_iso_cap_show, emc_iso_cap_store); |
68 | 83 | ||
69 | static int __init nvpmodel_emc_cap_init(void) | 84 | static ssize_t clk_cap_show(struct kobject *kobj, |
85 | struct kobj_attribute *attr, char *buf) | ||
86 | { | ||
87 | long rate; | ||
88 | struct nvpmodel_clk *clk = container_of(attr, struct nvpmodel_clk, attr); | ||
89 | |||
90 | rate = clk_round_rate(clk->clk, 1UL << 63); | ||
91 | if (rate < 0) { | ||
92 | pr_err("clk_round_rate failed: %ld\n", rate); | ||
93 | return rate; | ||
94 | } | ||
95 | |||
96 | return sprintf(buf, "%ld\n", rate); | ||
97 | } | ||
98 | |||
99 | static ssize_t clk_cap_store(struct kobject *kobj, | ||
100 | struct kobj_attribute *attr, const char *buf, | ||
101 | size_t count) | ||
102 | { | ||
103 | unsigned long rate; | ||
104 | int ret; | ||
105 | struct nvpmodel_clk *clk = container_of(attr, struct nvpmodel_clk, attr); | ||
106 | |||
107 | if (sscanf(buf, "%lu", &rate) != 1) | ||
108 | return -EINVAL; | ||
109 | |||
110 | ret = clk_set_max_rate(clk->clk, rate); | ||
111 | if (ret) { | ||
112 | pr_err("setting cap failed: %d\n", ret); | ||
113 | return ret; | ||
114 | } | ||
115 | |||
116 | return count; | ||
117 | } | ||
118 | |||
119 | static void free_resources(void) | ||
120 | { | ||
121 | int i; | ||
122 | |||
123 | if (clks) { | ||
124 | for (i = 0; i < num_clocks; i++) { | ||
125 | if (clks[i].attr.attr.name) | ||
126 | kfree_const(clks[i].attr.attr.name); | ||
127 | if (clks[i].clk) | ||
128 | clk_put(clks[i].clk); | ||
129 | } | ||
130 | kfree(clks); | ||
131 | clks = NULL; | ||
132 | num_clocks = 0; | ||
133 | } | ||
134 | if (bwmgr_handle) { | ||
135 | tegra_bwmgr_unregister(bwmgr_handle); | ||
136 | bwmgr_handle = NULL; | ||
137 | } | ||
138 | if (clk_cap_kobject) { | ||
139 | kobject_put(clk_cap_kobject); | ||
140 | clk_cap_kobject = NULL; | ||
141 | } | ||
142 | } | ||
143 | |||
144 | static int __init nvpmodel_clk_cap_init(void) | ||
70 | { | 145 | { |
71 | int error = 0; | 146 | int error = 0; |
147 | int i; | ||
148 | const char *clk_name; | ||
149 | struct device_node *dn; | ||
150 | |||
151 | dn = of_find_compatible_node(NULL, NULL, "nvidia,nvpmodel"); | ||
152 | if (!dn || !of_device_is_available(dn)) { | ||
153 | of_node_put(dn); | ||
154 | return -ENODEV; | ||
155 | } | ||
72 | 156 | ||
73 | emc_iso_cap_kobject = kobject_create_and_add("nvpmodel_emc_cap", | 157 | clk_cap_kobject = kobject_create_and_add("nvpmodel_emc_cap", |
74 | kernel_kobj); | 158 | kernel_kobj); |
75 | if (!emc_iso_cap_kobject) | 159 | if (!clk_cap_kobject) { |
76 | return -ENOMEM; | 160 | error = -ENOMEM; |
77 | error = sysfs_create_file(emc_iso_cap_kobject, | 161 | goto exit; |
78 | &emc_iso_cap_attribute.attr); | ||
79 | if (error) { | ||
80 | pr_err("failed to create emc_iso_cap sysfs: error %d\n", error); | ||
81 | kobject_put(emc_iso_cap_kobject); | ||
82 | return error; | ||
83 | } | 162 | } |
84 | 163 | ||
85 | bwmgr_handle = tegra_bwmgr_register(TEGRA_BWMGR_CLIENT_NVPMODEL); | 164 | bwmgr_handle = tegra_bwmgr_register(TEGRA_BWMGR_CLIENT_NVPMODEL); |
86 | if (IS_ERR_OR_NULL(bwmgr_handle)) { | 165 | if (IS_ERR_OR_NULL(bwmgr_handle)) { |
87 | error = IS_ERR(bwmgr_handle) ? | 166 | error = IS_ERR(bwmgr_handle) ? |
88 | PTR_ERR(bwmgr_handle) : -ENODEV; | 167 | PTR_ERR(bwmgr_handle) : -ENODEV; |
89 | pr_warn("Nvpmodel can't register EMC bwmgr (%d)\n", error); | 168 | pr_err("Nvpmodel can't register EMC bwmgr (%d)\n", error); |
90 | goto put_bwmgr; | 169 | goto exit; |
91 | } | 170 | } |
92 | 171 | ||
93 | pr_info("Module initialized successfully \n"); | 172 | error = sysfs_create_file(clk_cap_kobject, |
94 | return error; | 173 | &emc_iso_cap_attribute.attr); |
174 | if (error) { | ||
175 | pr_err("failed to create emc_iso_cap sysfs: error %d\n", error); | ||
176 | goto exit; | ||
177 | } | ||
95 | 178 | ||
96 | put_bwmgr: | 179 | num_clocks = of_property_count_strings(dn, "clock-names"); |
97 | if (!IS_ERR_OR_NULL(bwmgr_handle)) | 180 | if (num_clocks <= 0) { |
98 | tegra_bwmgr_unregister(bwmgr_handle); | 181 | num_clocks = 0; |
99 | kobject_put(emc_iso_cap_kobject); | 182 | goto exit; |
183 | } | ||
100 | 184 | ||
185 | clks = kzalloc(sizeof(*clks) * num_clocks, GFP_KERNEL); | ||
186 | if (!clks) { | ||
187 | num_clocks = 0; | ||
188 | pr_err("couldn't allocate clks!\n"); | ||
189 | error = -ENOMEM; | ||
190 | goto exit; | ||
191 | } | ||
192 | |||
193 | for (i = 0; i < num_clocks; i++) { | ||
194 | if (of_property_read_string_index(dn, "clock-names", i, | ||
195 | &clk_name)) { | ||
196 | pr_warn("couldn't get clock %d from device tree\n", i); | ||
197 | continue; | ||
198 | } | ||
199 | |||
200 | clks[i].clk = of_clk_get(dn, i); | ||
201 | if (IS_ERR(clks[i].clk)) { | ||
202 | clks[i].clk = NULL; | ||
203 | pr_warn("couldn't get clock: %s, error %d\n", clk_name, | ||
204 | error); | ||
205 | continue; | ||
206 | } | ||
207 | |||
208 | clks[i].attr.attr.name = kstrdup_const(clk_name, GFP_KERNEL); | ||
209 | if (!clks[i].attr.attr.name) { | ||
210 | pr_warn("couldn't allocate memory for clock %s\n", | ||
211 | clk_name); | ||
212 | continue; | ||
213 | } | ||
214 | |||
215 | clks[i].attr.attr.mode = 0660; | ||
216 | clks[i].attr.show = clk_cap_show; | ||
217 | clks[i].attr.store = clk_cap_store; | ||
218 | if (sysfs_create_file(clk_cap_kobject, | ||
219 | &clks[i].attr.attr)) { | ||
220 | pr_warn("failed to create %s cap sysfs file: error %d\n", | ||
221 | clk_name, error); | ||
222 | continue; | ||
223 | } | ||
224 | } | ||
225 | exit: | ||
226 | if (error) { | ||
227 | free_resources(); | ||
228 | pr_err("nvpmodel: initialization failed: error %d\n", error); | ||
229 | } else { | ||
230 | pr_info("nvpmodel: initialized successfully\n"); | ||
231 | } | ||
232 | of_node_put(dn); | ||
101 | return error; | 233 | return error; |
102 | } | 234 | } |
103 | 235 | ||
104 | static void __exit nvpmodel_emc_cap_exit(void) | 236 | static void __exit nvpmodel_clk_cap_exit(void) |
105 | { | 237 | { |
106 | tegra_bwmgr_unregister(bwmgr_handle); | 238 | free_resources(); |
107 | kobject_put(emc_iso_cap_kobject); | ||
108 | pr_info("Module exit successfully \n"); | 239 | pr_info("Module exit successfully \n"); |
109 | } | 240 | } |
110 | 241 | ||
111 | module_init(nvpmodel_emc_cap_init); | 242 | module_init(nvpmodel_clk_cap_init); |
112 | module_exit(nvpmodel_emc_cap_exit); | 243 | module_exit(nvpmodel_clk_cap_exit); |