linux/drivers/soc/qcom/smem_state.c
<<
>>
Prefs
   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        kfree(state);
 140}
 141
 142/**
 143 * qcom_smem_state_put() - release state handle
 144 * @state:      state handle to be released
 145 */
 146void 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}
 152EXPORT_SYMBOL_GPL(qcom_smem_state_put);
 153
 154static 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 */
 168struct 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}
 188EXPORT_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 */
 196struct 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}
 218EXPORT_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 */
 224void qcom_smem_state_unregister(struct qcom_smem_state *state)
 225{
 226        state->orphan = true;
 227        qcom_smem_state_put(state);
 228}
 229EXPORT_SYMBOL_GPL(qcom_smem_state_unregister);
 230