162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (c) 2015, Sony Mobile Communications Inc. 462306a36Sopenharmony_ci * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci#include <linux/device.h> 762306a36Sopenharmony_ci#include <linux/list.h> 862306a36Sopenharmony_ci#include <linux/module.h> 962306a36Sopenharmony_ci#include <linux/of.h> 1062306a36Sopenharmony_ci#include <linux/slab.h> 1162306a36Sopenharmony_ci#include <linux/soc/qcom/smem_state.h> 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_cistatic LIST_HEAD(smem_states); 1462306a36Sopenharmony_cistatic DEFINE_MUTEX(list_lock); 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci/** 1762306a36Sopenharmony_ci * struct qcom_smem_state - state context 1862306a36Sopenharmony_ci * @refcount: refcount for the state 1962306a36Sopenharmony_ci * @orphan: boolean indicator that this state has been unregistered 2062306a36Sopenharmony_ci * @list: entry in smem_states list 2162306a36Sopenharmony_ci * @of_node: of_node to use for matching the state in DT 2262306a36Sopenharmony_ci * @priv: implementation private data 2362306a36Sopenharmony_ci * @ops: ops for the state 2462306a36Sopenharmony_ci */ 2562306a36Sopenharmony_cistruct qcom_smem_state { 2662306a36Sopenharmony_ci struct kref refcount; 2762306a36Sopenharmony_ci bool orphan; 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci struct list_head list; 3062306a36Sopenharmony_ci struct device_node *of_node; 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci void *priv; 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci struct qcom_smem_state_ops ops; 3562306a36Sopenharmony_ci}; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci/** 3862306a36Sopenharmony_ci * qcom_smem_state_update_bits() - update the masked bits in state with value 3962306a36Sopenharmony_ci * @state: state handle acquired by calling qcom_smem_state_get() 4062306a36Sopenharmony_ci * @mask: bit mask for the change 4162306a36Sopenharmony_ci * @value: new value for the masked bits 4262306a36Sopenharmony_ci * 4362306a36Sopenharmony_ci * Returns 0 on success, otherwise negative errno. 4462306a36Sopenharmony_ci */ 4562306a36Sopenharmony_ciint qcom_smem_state_update_bits(struct qcom_smem_state *state, 4662306a36Sopenharmony_ci u32 mask, 4762306a36Sopenharmony_ci u32 value) 4862306a36Sopenharmony_ci{ 4962306a36Sopenharmony_ci if (state->orphan) 5062306a36Sopenharmony_ci return -ENXIO; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci if (!state->ops.update_bits) 5362306a36Sopenharmony_ci return -ENOTSUPP; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci return state->ops.update_bits(state->priv, mask, value); 5662306a36Sopenharmony_ci} 5762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_smem_state_update_bits); 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_cistatic struct qcom_smem_state *of_node_to_state(struct device_node *np) 6062306a36Sopenharmony_ci{ 6162306a36Sopenharmony_ci struct qcom_smem_state *state; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci mutex_lock(&list_lock); 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci list_for_each_entry(state, &smem_states, list) { 6662306a36Sopenharmony_ci if (state->of_node == np) { 6762306a36Sopenharmony_ci kref_get(&state->refcount); 6862306a36Sopenharmony_ci goto unlock; 6962306a36Sopenharmony_ci } 7062306a36Sopenharmony_ci } 7162306a36Sopenharmony_ci state = ERR_PTR(-EPROBE_DEFER); 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ciunlock: 7462306a36Sopenharmony_ci mutex_unlock(&list_lock); 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci return state; 7762306a36Sopenharmony_ci} 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci/** 8062306a36Sopenharmony_ci * qcom_smem_state_get() - acquire handle to a state 8162306a36Sopenharmony_ci * @dev: client device pointer 8262306a36Sopenharmony_ci * @con_id: name of the state to lookup 8362306a36Sopenharmony_ci * @bit: flags from the state reference, indicating which bit's affected 8462306a36Sopenharmony_ci * 8562306a36Sopenharmony_ci * Returns handle to the state, or ERR_PTR(). qcom_smem_state_put() must be 8662306a36Sopenharmony_ci * called to release the returned state handle. 8762306a36Sopenharmony_ci */ 8862306a36Sopenharmony_cistruct qcom_smem_state *qcom_smem_state_get(struct device *dev, 8962306a36Sopenharmony_ci const char *con_id, 9062306a36Sopenharmony_ci unsigned *bit) 9162306a36Sopenharmony_ci{ 9262306a36Sopenharmony_ci struct qcom_smem_state *state; 9362306a36Sopenharmony_ci struct of_phandle_args args; 9462306a36Sopenharmony_ci int index = 0; 9562306a36Sopenharmony_ci int ret; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci if (con_id) { 9862306a36Sopenharmony_ci index = of_property_match_string(dev->of_node, 9962306a36Sopenharmony_ci "qcom,smem-state-names", 10062306a36Sopenharmony_ci con_id); 10162306a36Sopenharmony_ci if (index < 0) { 10262306a36Sopenharmony_ci dev_err(dev, "missing qcom,smem-state-names\n"); 10362306a36Sopenharmony_ci return ERR_PTR(index); 10462306a36Sopenharmony_ci } 10562306a36Sopenharmony_ci } 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci ret = of_parse_phandle_with_args(dev->of_node, 10862306a36Sopenharmony_ci "qcom,smem-states", 10962306a36Sopenharmony_ci "#qcom,smem-state-cells", 11062306a36Sopenharmony_ci index, 11162306a36Sopenharmony_ci &args); 11262306a36Sopenharmony_ci if (ret) { 11362306a36Sopenharmony_ci dev_err(dev, "failed to parse qcom,smem-states property\n"); 11462306a36Sopenharmony_ci return ERR_PTR(ret); 11562306a36Sopenharmony_ci } 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci if (args.args_count != 1) { 11862306a36Sopenharmony_ci dev_err(dev, "invalid #qcom,smem-state-cells\n"); 11962306a36Sopenharmony_ci return ERR_PTR(-EINVAL); 12062306a36Sopenharmony_ci } 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci state = of_node_to_state(args.np); 12362306a36Sopenharmony_ci if (IS_ERR(state)) 12462306a36Sopenharmony_ci goto put; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci *bit = args.args[0]; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ciput: 12962306a36Sopenharmony_ci of_node_put(args.np); 13062306a36Sopenharmony_ci return state; 13162306a36Sopenharmony_ci} 13262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_smem_state_get); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_cistatic void qcom_smem_state_release(struct kref *ref) 13562306a36Sopenharmony_ci{ 13662306a36Sopenharmony_ci struct qcom_smem_state *state = container_of(ref, struct qcom_smem_state, refcount); 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci list_del(&state->list); 13962306a36Sopenharmony_ci of_node_put(state->of_node); 14062306a36Sopenharmony_ci kfree(state); 14162306a36Sopenharmony_ci} 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci/** 14462306a36Sopenharmony_ci * qcom_smem_state_put() - release state handle 14562306a36Sopenharmony_ci * @state: state handle to be released 14662306a36Sopenharmony_ci */ 14762306a36Sopenharmony_civoid qcom_smem_state_put(struct qcom_smem_state *state) 14862306a36Sopenharmony_ci{ 14962306a36Sopenharmony_ci mutex_lock(&list_lock); 15062306a36Sopenharmony_ci kref_put(&state->refcount, qcom_smem_state_release); 15162306a36Sopenharmony_ci mutex_unlock(&list_lock); 15262306a36Sopenharmony_ci} 15362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_smem_state_put); 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_cistatic void devm_qcom_smem_state_release(struct device *dev, void *res) 15662306a36Sopenharmony_ci{ 15762306a36Sopenharmony_ci qcom_smem_state_put(*(struct qcom_smem_state **)res); 15862306a36Sopenharmony_ci} 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci/** 16162306a36Sopenharmony_ci * devm_qcom_smem_state_get() - acquire handle to a devres managed state 16262306a36Sopenharmony_ci * @dev: client device pointer 16362306a36Sopenharmony_ci * @con_id: name of the state to lookup 16462306a36Sopenharmony_ci * @bit: flags from the state reference, indicating which bit's affected 16562306a36Sopenharmony_ci * 16662306a36Sopenharmony_ci * Returns handle to the state, or ERR_PTR(). qcom_smem_state_put() is called 16762306a36Sopenharmony_ci * automatically when @dev is removed. 16862306a36Sopenharmony_ci */ 16962306a36Sopenharmony_cistruct qcom_smem_state *devm_qcom_smem_state_get(struct device *dev, 17062306a36Sopenharmony_ci const char *con_id, 17162306a36Sopenharmony_ci unsigned *bit) 17262306a36Sopenharmony_ci{ 17362306a36Sopenharmony_ci struct qcom_smem_state **ptr, *state; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci ptr = devres_alloc(devm_qcom_smem_state_release, sizeof(*ptr), GFP_KERNEL); 17662306a36Sopenharmony_ci if (!ptr) 17762306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci state = qcom_smem_state_get(dev, con_id, bit); 18062306a36Sopenharmony_ci if (!IS_ERR(state)) { 18162306a36Sopenharmony_ci *ptr = state; 18262306a36Sopenharmony_ci devres_add(dev, ptr); 18362306a36Sopenharmony_ci } else { 18462306a36Sopenharmony_ci devres_free(ptr); 18562306a36Sopenharmony_ci } 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci return state; 18862306a36Sopenharmony_ci} 18962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devm_qcom_smem_state_get); 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci/** 19262306a36Sopenharmony_ci * qcom_smem_state_register() - register a new state 19362306a36Sopenharmony_ci * @of_node: of_node used for matching client lookups 19462306a36Sopenharmony_ci * @ops: implementation ops 19562306a36Sopenharmony_ci * @priv: implementation specific private data 19662306a36Sopenharmony_ci */ 19762306a36Sopenharmony_cistruct qcom_smem_state *qcom_smem_state_register(struct device_node *of_node, 19862306a36Sopenharmony_ci const struct qcom_smem_state_ops *ops, 19962306a36Sopenharmony_ci void *priv) 20062306a36Sopenharmony_ci{ 20162306a36Sopenharmony_ci struct qcom_smem_state *state; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci state = kzalloc(sizeof(*state), GFP_KERNEL); 20462306a36Sopenharmony_ci if (!state) 20562306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci kref_init(&state->refcount); 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci state->of_node = of_node_get(of_node); 21062306a36Sopenharmony_ci state->ops = *ops; 21162306a36Sopenharmony_ci state->priv = priv; 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci mutex_lock(&list_lock); 21462306a36Sopenharmony_ci list_add(&state->list, &smem_states); 21562306a36Sopenharmony_ci mutex_unlock(&list_lock); 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci return state; 21862306a36Sopenharmony_ci} 21962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_smem_state_register); 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci/** 22262306a36Sopenharmony_ci * qcom_smem_state_unregister() - unregister a registered state 22362306a36Sopenharmony_ci * @state: state handle to be unregistered 22462306a36Sopenharmony_ci */ 22562306a36Sopenharmony_civoid qcom_smem_state_unregister(struct qcom_smem_state *state) 22662306a36Sopenharmony_ci{ 22762306a36Sopenharmony_ci state->orphan = true; 22862306a36Sopenharmony_ci qcom_smem_state_put(state); 22962306a36Sopenharmony_ci} 23062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_smem_state_unregister); 231