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