tegra_cpufreq.c revision 1.1 1 /* $NetBSD: tegra_cpufreq.c,v 1.1 2015/05/13 11:06:13 jmcneill Exp $ */
2
3 /*-
4 * Copyright (c) 2015 Jared D. McNeill <jmcneill (at) invisible.ca>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29 #include "locators.h"
30
31 #include <sys/cdefs.h>
32 __KERNEL_RCSID(0, "$NetBSD: tegra_cpufreq.c,v 1.1 2015/05/13 11:06:13 jmcneill Exp $");
33
34 #include <sys/param.h>
35 #include <sys/bus.h>
36 #include <sys/device.h>
37 #include <sys/intr.h>
38 #include <sys/systm.h>
39 #include <sys/kernel.h>
40 #include <sys/atomic.h>
41 #include <sys/kmem.h>
42 #include <sys/xcall.h>
43 #include <sys/sysctl.h>
44
45 #include <arm/nvidia/tegra_var.h>
46
47 static u_int cpufreq_busy;
48 static struct sysctllog *cpufreq_log;
49 static int cpufreq_node_target, cpufreq_node_current, cpufreq_node_available;
50
51 static const struct tegra_cpufreq_func *cpufreq_func = NULL;
52
53 static void tegra_cpufreq_post(void *, void *);
54 static int tegra_cpufreq_freq_helper(SYSCTLFN_PROTO);
55 static char tegra_cpufreq_available[TEGRA_CPUFREQ_MAX * 5];
56
57 #define cpufreq_set_rate cpufreq_func->set_rate
58 #define cpufreq_get_rate cpufreq_func->get_rate
59 #define cpufreq_get_available cpufreq_func->get_available
60
61 void
62 tegra_cpufreq_register(const struct tegra_cpufreq_func *cf)
63 {
64 KASSERT(cpufreq_func == NULL);
65 cpufreq_func = cf;
66 }
67
68 void
69 tegra_cpufreq_init(void)
70 {
71 const struct sysctlnode *node, *cpunode, *freqnode;
72 u_int availfreq[TEGRA_CPUFREQ_MAX];
73 size_t nfreq;
74 int error;
75
76 if (cpufreq_func == NULL)
77 return;
78
79 nfreq = cpufreq_get_available(availfreq, TEGRA_CPUFREQ_MAX);
80 if (nfreq == 0)
81 return;
82
83 KASSERT(nfreq <= TEGRA_CPUFREQ_MAX);
84
85 for (int i = 0; i < nfreq; i++) {
86 char buf[6];
87 snprintf(buf, sizeof(buf), i ? " %u" : "%u", availfreq[i]);
88 strcat(tegra_cpufreq_available, buf);
89 }
90
91 error = sysctl_createv(&cpufreq_log, 0, NULL, &node,
92 CTLFLAG_PERMANENT, CTLTYPE_NODE, "machdep", NULL,
93 NULL, 0, NULL, 0, CTL_MACHDEP, CTL_EOL);
94 if (error)
95 goto sysctl_failed;
96 error = sysctl_createv(&cpufreq_log, 0, &node, &cpunode,
97 0, CTLTYPE_NODE, "cpu", NULL,
98 NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL);
99 if (error)
100 goto sysctl_failed;
101 error = sysctl_createv(&cpufreq_log, 0, &cpunode, &freqnode,
102 0, CTLTYPE_NODE, "frequency", NULL,
103 NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL);
104 if (error)
105 goto sysctl_failed;
106
107 error = sysctl_createv(&cpufreq_log, 0, &freqnode, &node,
108 CTLFLAG_READWRITE, CTLTYPE_INT, "target", NULL,
109 tegra_cpufreq_freq_helper, 0, NULL, 0,
110 CTL_CREATE, CTL_EOL);
111 if (error)
112 goto sysctl_failed;
113 cpufreq_node_target = node->sysctl_num;
114
115 error = sysctl_createv(&cpufreq_log, 0, &freqnode, &node,
116 CTLFLAG_READWRITE, CTLTYPE_INT, "current", NULL,
117 tegra_cpufreq_freq_helper, 0, NULL, 0,
118 CTL_CREATE, CTL_EOL);
119 if (error)
120 goto sysctl_failed;
121 cpufreq_node_current = node->sysctl_num;
122
123 error = sysctl_createv(&cpufreq_log, 0, &freqnode, &node,
124 0, CTLTYPE_STRING, "available", NULL,
125 NULL, 0, tegra_cpufreq_available, 0,
126 CTL_CREATE, CTL_EOL);
127 if (error)
128 goto sysctl_failed;
129 cpufreq_node_available = node->sysctl_num;
130
131 #ifdef CPUFREQ_BOOT
132 cpufreq_set_rate(CPUFREQ_BOOT);
133 tegra_cpufreq_post(NULL, NULL);
134 #endif
135
136 return;
137
138 sysctl_failed:
139 aprint_error("cpufreq: couldn't create sysctl nodes (%d)\n", error);
140 sysctl_teardown(&cpufreq_log);
141 }
142
143 static void
144 tegra_cpufreq_post(void *arg1, void *arg2)
145 {
146 struct cpu_info *ci = curcpu();
147
148 ci->ci_data.cpu_cc_freq = cpufreq_get_rate() * 1000000;
149 }
150
151 static int
152 tegra_cpufreq_freq_helper(SYSCTLFN_ARGS)
153 {
154 struct sysctlnode node;
155 int fq, oldfq = 0, error;
156 uint64_t xc;
157
158 node = *rnode;
159 node.sysctl_data = &fq;
160
161 fq = cpufreq_get_rate();
162 if (rnode->sysctl_num == cpufreq_node_target)
163 oldfq = fq;
164
165 error = sysctl_lookup(SYSCTLFN_CALL(&node));
166 if (error || newp == NULL)
167 return error;
168
169 if (fq == oldfq || rnode->sysctl_num != cpufreq_node_target)
170 return 0;
171
172 if (atomic_cas_uint(&cpufreq_busy, 0, 1) != 0)
173 return EBUSY;
174
175 error = cpufreq_set_rate(fq);
176 if (error == 0) {
177 xc = xc_broadcast(0, tegra_cpufreq_post, NULL, NULL);
178 xc_wait(xc);
179 pmf_event_inject(NULL, PMFE_SPEED_CHANGED);
180 }
181
182 atomic_dec_uint(&cpufreq_busy);
183
184 return error;
185 }
186