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 kfree(state); 140 } 141 142 /** 143 * qcom_smem_state_put() - release state handle 144 * @state: state handle to be released 145 */ 146 void qcom_smem_state_put(struct qcom_smem_state *state) 147 { 148 mutex_lock(&list_lock); 149 kref_put(&state->refcount, qcom_smem_state_release); 150 mutex_unlock(&list_lock); 151 } 152 EXPORT_SYMBOL_GPL(qcom_smem_state_put); 153 154 static void devm_qcom_smem_state_release(struct device *dev, void *res) 155 { 156 qcom_smem_state_put(*(struct qcom_smem_state **)res); 157 } 158 159 /** 160 * devm_qcom_smem_state_get() - acquire handle to a devres managed state 161 * @dev: client device pointer 162 * @con_id: name of the state to lookup 163 * @bit: flags from the state reference, indicating which bit's affected 164 * 165 * Returns handle to the state, or ERR_PTR(). qcom_smem_state_put() is called 166 * automatically when @dev is removed. 167 */ 168 struct qcom_smem_state *devm_qcom_smem_state_get(struct device *dev, 169 const char *con_id, 170 unsigned *bit) 171 { 172 struct qcom_smem_state **ptr, *state; 173 174 ptr = devres_alloc(devm_qcom_smem_state_release, sizeof(*ptr), GFP_KERNEL); 175 if (!ptr) 176 return ERR_PTR(-ENOMEM); 177 178 state = qcom_smem_state_get(dev, con_id, bit); 179 if (!IS_ERR(state)) { 180 *ptr = state; 181 devres_add(dev, ptr); 182 } else { 183 devres_free(ptr); 184 } 185 186 return state; 187 } 188 EXPORT_SYMBOL_GPL(devm_qcom_smem_state_get); 189 190 /** 191 * qcom_smem_state_register() - register a new state 192 * @of_node: of_node used for matching client lookups 193 * @ops: implementation ops 194 * @priv: implementation specific private data 195 */ 196 struct qcom_smem_state *qcom_smem_state_register(struct device_node *of_node, 197 const struct qcom_smem_state_ops *ops, 198 void *priv) 199 { 200 struct qcom_smem_state *state; 201 202 state = kzalloc(sizeof(*state), GFP_KERNEL); 203 if (!state) 204 return ERR_PTR(-ENOMEM); 205 206 kref_init(&state->refcount); 207 208 state->of_node = of_node; 209 state->ops = *ops; 210 state->priv = priv; 211 212 mutex_lock(&list_lock); 213 list_add(&state->list, &smem_states); 214 mutex_unlock(&list_lock); 215 216 return state; 217 } 218 EXPORT_SYMBOL_GPL(qcom_smem_state_register); 219 220 /** 221 * qcom_smem_state_unregister() - unregister a registered state 222 * @state: state handle to be unregistered 223 */ 224 void qcom_smem_state_unregister(struct qcom_smem_state *state) 225 { 226 state->orphan = true; 227 qcom_smem_state_put(state); 228 } 229 EXPORT_SYMBOL_GPL(qcom_smem_state_unregister); 230