blob: b3c4222cfdb5c15660c86789086aa3e1f8e50100 [file] [log] [blame]
b.liue9582032025-04-17 19:18:16 +08001// SPDX-License-Identifier: GPL-2.0
2/*
3 * Support for asr spi controller
4 *
5 * Copyright (C) 2021 ASR Micro Limited
6 *
7 */
8
9#include <linux/ctype.h>
10#include <linux/device.h>
11#include <linux/err.h>
12#include <linux/hrtimer.h>
13#include <linux/list.h>
14#include <linux/rbtree.h>
15#include <linux/slab.h>
16#include <linux/pm_qos.h>
17
18static DEFINE_MUTEX(qos_lock);
19
20struct pmqos {
21 char *name;
22 struct rb_node node;
23 struct pm_qos_request qr;
24};
25
26/*1 to 1 mapped to the pm_qos_class_id in pm_qos.h*/
27static struct rb_root pmqos_tree[PM_QOS_NUM_CLASSES] = {
28 RB_ROOT,
29 RB_ROOT,
30 RB_ROOT,
31 RB_ROOT,
32 RB_ROOT,
33 RB_ROOT,
34
35 RB_ROOT,
36 RB_ROOT,
37
38 RB_ROOT,
39 RB_ROOT,
40};
41
42ssize_t pm_show_pmqos(char *buf, int pm_qos_class)
43{
44 struct rb_node *node;
45 struct pmqos *pq;
46 char *str = buf;
47 char *end = buf + PAGE_SIZE;
48
49 mutex_lock(&qos_lock);
50
51 for (node = rb_first(&pmqos_tree[pm_qos_class]); node; node = rb_next(node)) {
52 pq = rb_entry(node, struct pmqos, node);
53 if (pq->qr.pm_qos_class == pm_qos_class)
54 str += scnprintf(str, end - str, "%s ", pq->name);
55 }
56 if (str > buf)
57 str--;
58
59 str += scnprintf(str, end - str, "\n");
60
61 mutex_unlock(&qos_lock);
62 return (str - buf);
63}
64
65static struct pmqos *pmqos_lookup_add(const char *name, int pm_qos_class,
66 size_t len, bool add_if_not_found)
67{
68 struct rb_node **node = &pmqos_tree[pm_qos_class].rb_node;
69 struct rb_node *parent = *node;
70 struct pmqos *pq;
71
72 while (*node) {
73 int diff;
74
75 parent = *node;
76 pq = rb_entry(*node, struct pmqos, node);
77 diff = strncmp(name, pq->name, len);
78 if (diff == 0) {
79 if (pq->name[len])
80 diff = -1;
81 else
82 return pq;
83 }
84 if (diff < 0)
85 node = &(*node)->rb_left;
86 else
87 node = &(*node)->rb_right;
88 }
89 if (!add_if_not_found)
90 return ERR_PTR(-EINVAL);
91
92 /* Not found, we have to add a new one. */
93 pq = kzalloc(sizeof(*pq), GFP_KERNEL);
94 if (!pq)
95 return ERR_PTR(-ENOMEM);
96
97 pq->name = kstrndup(name, len, GFP_KERNEL);
98 if (!pq->name) {
99 kfree(pq);
100 return ERR_PTR(-ENOMEM);
101 }
102 pq->qr.name = pq->name;
103 pm_qos_add_request(&pq->qr,
104 pm_qos_class, PM_QOS_DEFAULT_VALUE);
105 rb_link_node(&pq->node, parent, node);
106 rb_insert_color(&pq->node, &pmqos_tree[pm_qos_class]);
107 return pq;
108}
109
110int pm_freq_qos(const char *buf, int pm_qos_class)
111{
112 const char *str = buf;
113 struct pmqos *pq;
114 u64 timeout_ns = 0;
115 unsigned long freq = 0;
116 size_t len;
117 int ret = 0;
118
119 while (*str && !isspace(*str))
120 str++;
121
122 len = str - buf;
123 if (!len)
124 return -EINVAL;
125
126 str = skip_spaces(str);
127 if (*str && *str != '\n') {
128 /*duplicate the rest of the string so that we can support multiple args*/
129 char *strdup = kzalloc((strlen(str) + 1), GFP_KERNEL);
130 char *strstart = strdup;
131 char *strpos = strdup;
132 char *strpos2 = NULL;
133
134 if (!strdup)
135 return -ENOMEM;
136 else
137 strcpy(strdup, str);
138
139 while (*strdup && !isspace(*strdup))
140 strdup++;
141 strpos2 = strdup;
142 strdup = skip_spaces(strdup);
143 if(strdup != strpos2) {
144 /*There is 2nd arg as timeout value*/
145 if (*strdup && *strdup != '\n') {
146 *strpos2 = '\0';
147 /* Find out if there's a valid timeout string appended. */
148 ret = kstrtou64(strdup, 10, &timeout_ns);
149 if (ret) {
150 kfree(strstart);
151 return -EINVAL;
152 }
153 }
154 }
155 /*Here is the 1st arg as freq value*/
156 ret = kstrtoul(strpos, 10, &freq);
157 if (ret) {
158 kfree(strstart);
159 return -EINVAL;
160 }
161 /* free strstart to avoid memleak */
162 kfree(strstart);
163 }
164
165 mutex_lock(&qos_lock);
166 pq = pmqos_lookup_add(buf, pm_qos_class, len, true);
167 if (IS_ERR(pq)) {
168 ret = PTR_ERR(pq);
169 goto out;
170 }
171 if (timeout_ns)
172 {
173 /* up bound to usecs, conver to us by do_div */
174 u64 timeout_us = timeout_ns + NSEC_PER_USEC - 1;
175
176 do_div(timeout_us, NSEC_PER_USEC);
177 pm_qos_update_request_timeout(&pq->qr, freq, timeout_us);
178 } else {
179 pm_qos_update_request(&pq->qr, freq);
180 }
181
182 out:
183 mutex_unlock(&qos_lock);
184 return ret;
185}
186
187int pm_freq_unqos(const char *buf, int pm_qos_class)
188{
189 struct pmqos *pq;
190 size_t len;
191 int ret = 0;
192
193 len = strlen(buf);
194 if (!len)
195 return -EINVAL;
196
197 if (buf[len-1] == '\n')
198 len--;
199
200 if (!len)
201 return -EINVAL;
202
203 mutex_lock(&qos_lock);
204
205 pq = pmqos_lookup_add(buf, pm_qos_class, len, false);
206 if (IS_ERR(pq)) {
207 ret = PTR_ERR(pq);
208 goto out;
209 }
210 pm_qos_remove_request(&pq->qr);
211 rb_erase(&pq->node, &pmqos_tree[pm_qos_class]);
212 kfree(pq->name);
213 kfree(pq);
214
215 out:
216 mutex_unlock(&qos_lock);
217 return ret;
218}