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 */ 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 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 */ 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 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 */ 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 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 */ 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 */ 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 */ 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