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 13static LIST_HEAD(smem_states); 14static 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 */ 25struct 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 */ 45int 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} 57EXPORT_SYMBOL_GPL(qcom_smem_state_update_bits); 58 59static 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 73unlock: 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 */ 88struct 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 128put: 129 of_node_put(args.np); 130 return state; 131} 132EXPORT_SYMBOL_GPL(qcom_smem_state_get); 133 134static 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 */ 147void 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} 153EXPORT_SYMBOL_GPL(qcom_smem_state_put); 154 155/** 156 * qcom_smem_state_register() - register a new state 157 * @of_node: of_node used for matching client lookups 158 * @ops: implementation ops 159 * @priv: implementation specific private data 160 */ 161struct qcom_smem_state *qcom_smem_state_register(struct device_node *of_node, 162 const struct qcom_smem_state_ops *ops, 163 void *priv) 164{ 165 struct qcom_smem_state *state; 166 167 state = kzalloc(sizeof(*state), GFP_KERNEL); 168 if (!state) 169 return ERR_PTR(-ENOMEM); 170 171 kref_init(&state->refcount); 172 173 state->of_node = of_node_get(of_node); 174 state->ops = *ops; 175 state->priv = priv; 176 177 mutex_lock(&list_lock); 178 list_add(&state->list, &smem_states); 179 mutex_unlock(&list_lock); 180 181 return state; 182} 183EXPORT_SYMBOL_GPL(qcom_smem_state_register); 184 185/** 186 * qcom_smem_state_unregister() - unregister a registered state 187 * @state: state handle to be unregistered 188 */ 189void qcom_smem_state_unregister(struct qcom_smem_state *state) 190{ 191 state->orphan = true; 192 qcom_smem_state_put(state); 193} 194EXPORT_SYMBOL_GPL(qcom_smem_state_unregister); 195