1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * Copyright (c) 2015, Sony Mobile Communications Inc.
4 * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
5 */
6 #include <linux/cleanup.h>
7 #include <linux/device.h>
8 #include <linux/list.h>
9 #include <linux/module.h>
10 #include <linux/of.h>
11 #include <linux/slab.h>
12 #include <linux/soc/qcom/smem_state.h>
13
14 static LIST_HEAD(smem_states);
15 static DEFINE_MUTEX(list_lock);
16
17 /**
18 * struct qcom_smem_state - state context
19 * @refcount: refcount for the state
20 * @orphan: boolean indicator that this state has been unregistered
21 * @list: entry in smem_states list
22 * @of_node: of_node to use for matching the state in DT
23 * @priv: implementation private data
24 * @ops: ops for the state
25 */
26 struct qcom_smem_state {
27 struct kref refcount;
28 bool orphan;
29
30 struct list_head list;
31 struct device_node *of_node;
32
33 void *priv;
34
35 struct qcom_smem_state_ops ops;
36 };
37
38 /**
39 * qcom_smem_state_update_bits() - update the masked bits in state with value
40 * @state: state handle acquired by calling qcom_smem_state_get()
41 * @mask: bit mask for the change
42 * @value: new value for the masked bits
43 *
44 * Returns 0 on success, otherwise negative errno.
45 */
qcom_smem_state_update_bits(struct qcom_smem_state * state,u32 mask,u32 value)46 int qcom_smem_state_update_bits(struct qcom_smem_state *state,
47 u32 mask,
48 u32 value)
49 {
50 if (state->orphan)
51 return -ENXIO;
52
53 if (!state->ops.update_bits)
54 return -ENOTSUPP;
55
56 return state->ops.update_bits(state->priv, mask, value);
57 }
58 EXPORT_SYMBOL_GPL(qcom_smem_state_update_bits);
59
of_node_to_state(struct device_node * np)60 static struct qcom_smem_state *of_node_to_state(struct device_node *np)
61 {
62 struct qcom_smem_state *state;
63
64 guard(mutex)(&list_lock);
65
66 list_for_each_entry(state, &smem_states, list) {
67 if (state->of_node == np) {
68 kref_get(&state->refcount);
69 return state;
70 }
71 }
72 return ERR_PTR(-EPROBE_DEFER);
73 }
74
75 /**
76 * qcom_smem_state_get() - acquire handle to a state
77 * @dev: client device pointer
78 * @con_id: name of the state to lookup
79 * @bit: flags from the state reference, indicating which bit's affected
80 *
81 * Returns handle to the state, or ERR_PTR(). qcom_smem_state_put() must be
82 * called to release the returned state handle.
83 */
qcom_smem_state_get(struct device * dev,const char * con_id,unsigned * bit)84 struct qcom_smem_state *qcom_smem_state_get(struct device *dev,
85 const char *con_id,
86 unsigned *bit)
87 {
88 struct qcom_smem_state *state;
89 struct of_phandle_args args;
90 int index = 0;
91 int ret;
92
93 if (con_id) {
94 index = of_property_match_string(dev->of_node,
95 "qcom,smem-state-names",
96 con_id);
97 if (index < 0) {
98 dev_err(dev, "missing qcom,smem-state-names\n");
99 return ERR_PTR(index);
100 }
101 }
102
103 ret = of_parse_phandle_with_args(dev->of_node,
104 "qcom,smem-states",
105 "#qcom,smem-state-cells",
106 index,
107 &args);
108 if (ret) {
109 dev_err(dev, "failed to parse qcom,smem-states property\n");
110 return ERR_PTR(ret);
111 }
112
113 if (args.args_count != 1) {
114 dev_err(dev, "invalid #qcom,smem-state-cells\n");
115 state = ERR_PTR(-EINVAL);
116 goto put;
117 }
118
119 state = of_node_to_state(args.np);
120 if (IS_ERR(state))
121 goto put;
122
123 *bit = args.args[0];
124
125 put:
126 of_node_put(args.np);
127 return state;
128 }
129 EXPORT_SYMBOL_GPL(qcom_smem_state_get);
130
qcom_smem_state_release(struct kref * ref)131 static void qcom_smem_state_release(struct kref *ref)
132 {
133 struct qcom_smem_state *state = container_of(ref, struct qcom_smem_state, refcount);
134
135 list_del(&state->list);
136 of_node_put(state->of_node);
137 kfree(state);
138 }
139
140 /**
141 * qcom_smem_state_put() - release state handle
142 * @state: state handle to be released
143 */
qcom_smem_state_put(struct qcom_smem_state * state)144 void qcom_smem_state_put(struct qcom_smem_state *state)
145 {
146 mutex_lock(&list_lock);
147 kref_put(&state->refcount, qcom_smem_state_release);
148 mutex_unlock(&list_lock);
149 }
150 EXPORT_SYMBOL_GPL(qcom_smem_state_put);
151
devm_qcom_smem_state_release(struct device * dev,void * res)152 static void devm_qcom_smem_state_release(struct device *dev, void *res)
153 {
154 qcom_smem_state_put(*(struct qcom_smem_state **)res);
155 }
156
157 /**
158 * devm_qcom_smem_state_get() - acquire handle to a devres managed state
159 * @dev: client device pointer
160 * @con_id: name of the state to lookup
161 * @bit: flags from the state reference, indicating which bit's affected
162 *
163 * Returns handle to the state, or ERR_PTR(). qcom_smem_state_put() is called
164 * automatically when @dev is removed.
165 */
devm_qcom_smem_state_get(struct device * dev,const char * con_id,unsigned * bit)166 struct qcom_smem_state *devm_qcom_smem_state_get(struct device *dev,
167 const char *con_id,
168 unsigned *bit)
169 {
170 struct qcom_smem_state **ptr, *state;
171
172 ptr = devres_alloc(devm_qcom_smem_state_release, sizeof(*ptr), GFP_KERNEL);
173 if (!ptr)
174 return ERR_PTR(-ENOMEM);
175
176 state = qcom_smem_state_get(dev, con_id, bit);
177 if (!IS_ERR(state)) {
178 *ptr = state;
179 devres_add(dev, ptr);
180 } else {
181 devres_free(ptr);
182 }
183
184 return state;
185 }
186 EXPORT_SYMBOL_GPL(devm_qcom_smem_state_get);
187
188 /**
189 * qcom_smem_state_register() - register a new state
190 * @of_node: of_node used for matching client lookups
191 * @ops: implementation ops
192 * @priv: implementation specific private data
193 */
qcom_smem_state_register(struct device_node * of_node,const struct qcom_smem_state_ops * ops,void * priv)194 struct qcom_smem_state *qcom_smem_state_register(struct device_node *of_node,
195 const struct qcom_smem_state_ops *ops,
196 void *priv)
197 {
198 struct qcom_smem_state *state;
199
200 state = kzalloc(sizeof(*state), GFP_KERNEL);
201 if (!state)
202 return ERR_PTR(-ENOMEM);
203
204 kref_init(&state->refcount);
205
206 state->of_node = of_node_get(of_node);
207 state->ops = *ops;
208 state->priv = priv;
209
210 mutex_lock(&list_lock);
211 list_add(&state->list, &smem_states);
212 mutex_unlock(&list_lock);
213
214 return state;
215 }
216 EXPORT_SYMBOL_GPL(qcom_smem_state_register);
217
218 /**
219 * qcom_smem_state_unregister() - unregister a registered state
220 * @state: state handle to be unregistered
221 */
qcom_smem_state_unregister(struct qcom_smem_state * state)222 void qcom_smem_state_unregister(struct qcom_smem_state *state)
223 {
224 state->orphan = true;
225 qcom_smem_state_put(state);
226 }
227 EXPORT_SYMBOL_GPL(qcom_smem_state_unregister);
228