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