diff options
Diffstat (limited to 'drivers/soc/qcom/smem_state.c')
-rw-r--r-- | drivers/soc/qcom/smem_state.c | 201 |
1 files changed, 201 insertions, 0 deletions
diff --git a/drivers/soc/qcom/smem_state.c b/drivers/soc/qcom/smem_state.c new file mode 100644 index 000000000000..54261decb369 --- /dev/null +++ b/drivers/soc/qcom/smem_state.c @@ -0,0 +1,201 @@ +/* + * 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 <linux/device.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/soc/qcom/smem_state.h> + +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,state-names", + con_id); + if (index < 0) { + dev_err(dev, "missing qcom,state-names\n"); + return ERR_PTR(index); + } + } + + ret = of_parse_phandle_with_args(dev->of_node, + "qcom,state", + "#qcom,state-cells", + index, + &args); + if (ret) { + dev_err(dev, "failed to parse qcom,state property\n"); + return ERR_PTR(ret); + } + + if (args.args_count != 1) { + dev_err(dev, "invalid #qcom,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); |