Commit | Line | Data |
---|---|---|
9460ae2f BA |
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, | |
3680a4a9 | 107 | "qcom,smem-state-names", |
9460ae2f BA |
108 | con_id); |
109 | if (index < 0) { | |
3680a4a9 | 110 | dev_err(dev, "missing qcom,smem-state-names\n"); |
9460ae2f BA |
111 | return ERR_PTR(index); |
112 | } | |
113 | } | |
114 | ||
115 | ret = of_parse_phandle_with_args(dev->of_node, | |
3680a4a9 BA |
116 | "qcom,smem-states", |
117 | "#qcom,smem-state-cells", | |
9460ae2f BA |
118 | index, |
119 | &args); | |
120 | if (ret) { | |
3680a4a9 | 121 | dev_err(dev, "failed to parse qcom,smem-states property\n"); |
9460ae2f BA |
122 | return ERR_PTR(ret); |
123 | } | |
124 | ||
125 | if (args.args_count != 1) { | |
3680a4a9 | 126 | dev_err(dev, "invalid #qcom,smem-state-cells\n"); |
9460ae2f BA |
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); |