/* * Copyright (c) 2015, Sony Mobile Communications Inc. * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include static LIST_HEAD(smem_states); static DEFINE_MUTEX(list_lock); /** * struct qcom_smem_state - state context * @refcount: refcount for the state * @orphan: boolean indicator that this state has been unregistered * @list: entry in smem_states list * @of_node: of_node to use for matching the state in DT * @priv: implementation private data * @ops: ops for the state */ struct qcom_smem_state { struct kref refcount; bool orphan; struct list_head list; struct device_node *of_node; void *priv; struct qcom_smem_state_ops ops; }; /** * qcom_smem_state_update_bits() - update the masked bits in state with value * @state: state handle acquired by calling qcom_smem_state_get() * @mask: bit mask for the change * @value: new value for the masked bits * * Returns 0 on success, otherwise negative errno. */ int qcom_smem_state_update_bits(struct qcom_smem_state *state, u32 mask, u32 value) { if (state->orphan) return -ENXIO; if (!state->ops.update_bits) return -ENOTSUPP; return state->ops.update_bits(state->priv, mask, value); } EXPORT_SYMBOL_GPL(qcom_smem_state_update_bits); static struct qcom_smem_state *of_node_to_state(struct device_node *np) { struct qcom_smem_state *state; mutex_lock(&list_lock); list_for_each_entry(state, &smem_states, list) { if (state->of_node == np) { kref_get(&state->refcount); goto unlock; } } state = ERR_PTR(-EPROBE_DEFER); unlock: mutex_unlock(&list_lock); return state; } /** * qcom_smem_state_get() - acquire handle to a state * @dev: client device pointer * @con_id: name of the state to lookup * @bit: flags from the state reference, indicating which bit's affected * * Returns handle to the state, or ERR_PTR(). qcom_smem_state_put() must be * called to release the returned state handle. */ struct qcom_smem_state *qcom_smem_state_get(struct device *dev, const char *con_id, unsigned *bit) { struct qcom_smem_state *state; struct of_phandle_args args; int index = 0; int ret; if (con_id) { index = of_property_match_string(dev->of_node, "qcom,smem-state-names", con_id); if (index < 0) { dev_err(dev, "missing qcom,smem-state-names\n"); return ERR_PTR(index); } } ret = of_parse_phandle_with_args(dev->of_node, "qcom,smem-states", "#qcom,smem-state-cells", index, &args); if (ret) { dev_err(dev, "failed to parse qcom,smem-states property\n"); return ERR_PTR(ret); } if (args.args_count != 1) { dev_err(dev, "invalid #qcom,smem-state-cells\n"); return ERR_PTR(-EINVAL); } state = of_node_to_state(args.np); if (IS_ERR(state)) goto put; *bit = args.args[0]; put: of_node_put(args.np); return state; } EXPORT_SYMBOL_GPL(qcom_smem_state_get); static void qcom_smem_state_release(struct kref *ref) { struct qcom_smem_state *state = container_of(ref, struct qcom_smem_state, refcount); list_del(&state->list); kfree(state); } /** * qcom_smem_state_put() - release state handle * @state: state handle to be released */ void qcom_smem_state_put(struct qcom_smem_state *state) { mutex_lock(&list_lock); kref_put(&state->refcount, qcom_smem_state_release); mutex_unlock(&list_lock); } EXPORT_SYMBOL_GPL(qcom_smem_state_put); /** * qcom_smem_state_register() - register a new state * @of_node: of_node used for matching client lookups * @ops: implementation ops * @priv: implementation specific private data */ struct qcom_smem_state *qcom_smem_state_register(struct device_node *of_node, const struct qcom_smem_state_ops *ops, void *priv) { struct qcom_smem_state *state; state = kzalloc(sizeof(*state), GFP_KERNEL); if (!state) return ERR_PTR(-ENOMEM); kref_init(&state->refcount); state->of_node = of_node; state->ops = *ops; state->priv = priv; mutex_lock(&list_lock); list_add(&state->list, &smem_states); mutex_unlock(&list_lock); return state; } EXPORT_SYMBOL_GPL(qcom_smem_state_register); /** * qcom_smem_state_unregister() - unregister a registered state * @state: state handle to be unregistered */ void qcom_smem_state_unregister(struct qcom_smem_state *state) { state->orphan = true; qcom_smem_state_put(state); } EXPORT_SYMBOL_GPL(qcom_smem_state_unregister);