231 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*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 context18 * @refcount: refcount for the state19 * @orphan: boolean indicator that this state has been unregistered20 * @list: entry in smem_states list21 * @of_node: of_node to use for matching the state in DT22 * @priv: implementation private data23 * @ops: ops for the state24 */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 value39 * @state: state handle acquired by calling qcom_smem_state_get()40 * @mask: bit mask for the change41 * @value: new value for the masked bits42 *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 state81 * @dev: client device pointer82 * @con_id: name of the state to lookup83 * @bit: flags from the state reference, indicating which bit's affected84 *85 * Returns handle to the state, or ERR_PTR(). qcom_smem_state_put() must be86 * 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 handle145 * @state: state handle to be released146 */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 155static 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 state162 * @dev: client device pointer163 * @con_id: name of the state to lookup164 * @bit: flags from the state reference, indicating which bit's affected165 *166 * Returns handle to the state, or ERR_PTR(). qcom_smem_state_put() is called167 * automatically when @dev is removed.168 */169struct 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}189EXPORT_SYMBOL_GPL(devm_qcom_smem_state_get);190 191/**192 * qcom_smem_state_register() - register a new state193 * @of_node: of_node used for matching client lookups194 * @ops: implementation ops195 * @priv: implementation specific private data196 */197struct 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}219EXPORT_SYMBOL_GPL(qcom_smem_state_register);220 221/**222 * qcom_smem_state_unregister() - unregister a registered state223 * @state: state handle to be unregistered224 */225void qcom_smem_state_unregister(struct qcom_smem_state *state)226{227 state->orphan = true;228 qcom_smem_state_put(state);229}230EXPORT_SYMBOL_GPL(qcom_smem_state_unregister);231