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