18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (c) 2015, Sony Mobile Communications Inc. 48c2ecf20Sopenharmony_ci * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. 58c2ecf20Sopenharmony_ci */ 68c2ecf20Sopenharmony_ci#include <linux/device.h> 78c2ecf20Sopenharmony_ci#include <linux/list.h> 88c2ecf20Sopenharmony_ci#include <linux/module.h> 98c2ecf20Sopenharmony_ci#include <linux/of.h> 108c2ecf20Sopenharmony_ci#include <linux/slab.h> 118c2ecf20Sopenharmony_ci#include <linux/soc/qcom/smem_state.h> 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_cistatic LIST_HEAD(smem_states); 148c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(list_lock); 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci/** 178c2ecf20Sopenharmony_ci * struct qcom_smem_state - state context 188c2ecf20Sopenharmony_ci * @refcount: refcount for the state 198c2ecf20Sopenharmony_ci * @orphan: boolean indicator that this state has been unregistered 208c2ecf20Sopenharmony_ci * @list: entry in smem_states list 218c2ecf20Sopenharmony_ci * @of_node: of_node to use for matching the state in DT 228c2ecf20Sopenharmony_ci * @priv: implementation private data 238c2ecf20Sopenharmony_ci * @ops: ops for the state 248c2ecf20Sopenharmony_ci */ 258c2ecf20Sopenharmony_cistruct qcom_smem_state { 268c2ecf20Sopenharmony_ci struct kref refcount; 278c2ecf20Sopenharmony_ci bool orphan; 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci struct list_head list; 308c2ecf20Sopenharmony_ci struct device_node *of_node; 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci void *priv; 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci struct qcom_smem_state_ops ops; 358c2ecf20Sopenharmony_ci}; 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci/** 388c2ecf20Sopenharmony_ci * qcom_smem_state_update_bits() - update the masked bits in state with value 398c2ecf20Sopenharmony_ci * @state: state handle acquired by calling qcom_smem_state_get() 408c2ecf20Sopenharmony_ci * @mask: bit mask for the change 418c2ecf20Sopenharmony_ci * @value: new value for the masked bits 428c2ecf20Sopenharmony_ci * 438c2ecf20Sopenharmony_ci * Returns 0 on success, otherwise negative errno. 448c2ecf20Sopenharmony_ci */ 458c2ecf20Sopenharmony_ciint qcom_smem_state_update_bits(struct qcom_smem_state *state, 468c2ecf20Sopenharmony_ci u32 mask, 478c2ecf20Sopenharmony_ci u32 value) 488c2ecf20Sopenharmony_ci{ 498c2ecf20Sopenharmony_ci if (state->orphan) 508c2ecf20Sopenharmony_ci return -ENXIO; 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci if (!state->ops.update_bits) 538c2ecf20Sopenharmony_ci return -ENOTSUPP; 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci return state->ops.update_bits(state->priv, mask, value); 568c2ecf20Sopenharmony_ci} 578c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_smem_state_update_bits); 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_cistatic struct qcom_smem_state *of_node_to_state(struct device_node *np) 608c2ecf20Sopenharmony_ci{ 618c2ecf20Sopenharmony_ci struct qcom_smem_state *state; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci mutex_lock(&list_lock); 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci list_for_each_entry(state, &smem_states, list) { 668c2ecf20Sopenharmony_ci if (state->of_node == np) { 678c2ecf20Sopenharmony_ci kref_get(&state->refcount); 688c2ecf20Sopenharmony_ci goto unlock; 698c2ecf20Sopenharmony_ci } 708c2ecf20Sopenharmony_ci } 718c2ecf20Sopenharmony_ci state = ERR_PTR(-EPROBE_DEFER); 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ciunlock: 748c2ecf20Sopenharmony_ci mutex_unlock(&list_lock); 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci return state; 778c2ecf20Sopenharmony_ci} 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci/** 808c2ecf20Sopenharmony_ci * qcom_smem_state_get() - acquire handle to a state 818c2ecf20Sopenharmony_ci * @dev: client device pointer 828c2ecf20Sopenharmony_ci * @con_id: name of the state to lookup 838c2ecf20Sopenharmony_ci * @bit: flags from the state reference, indicating which bit's affected 848c2ecf20Sopenharmony_ci * 858c2ecf20Sopenharmony_ci * Returns handle to the state, or ERR_PTR(). qcom_smem_state_put() must be 868c2ecf20Sopenharmony_ci * called to release the returned state handle. 878c2ecf20Sopenharmony_ci */ 888c2ecf20Sopenharmony_cistruct qcom_smem_state *qcom_smem_state_get(struct device *dev, 898c2ecf20Sopenharmony_ci const char *con_id, 908c2ecf20Sopenharmony_ci unsigned *bit) 918c2ecf20Sopenharmony_ci{ 928c2ecf20Sopenharmony_ci struct qcom_smem_state *state; 938c2ecf20Sopenharmony_ci struct of_phandle_args args; 948c2ecf20Sopenharmony_ci int index = 0; 958c2ecf20Sopenharmony_ci int ret; 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci if (con_id) { 988c2ecf20Sopenharmony_ci index = of_property_match_string(dev->of_node, 998c2ecf20Sopenharmony_ci "qcom,smem-state-names", 1008c2ecf20Sopenharmony_ci con_id); 1018c2ecf20Sopenharmony_ci if (index < 0) { 1028c2ecf20Sopenharmony_ci dev_err(dev, "missing qcom,smem-state-names\n"); 1038c2ecf20Sopenharmony_ci return ERR_PTR(index); 1048c2ecf20Sopenharmony_ci } 1058c2ecf20Sopenharmony_ci } 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci ret = of_parse_phandle_with_args(dev->of_node, 1088c2ecf20Sopenharmony_ci "qcom,smem-states", 1098c2ecf20Sopenharmony_ci "#qcom,smem-state-cells", 1108c2ecf20Sopenharmony_ci index, 1118c2ecf20Sopenharmony_ci &args); 1128c2ecf20Sopenharmony_ci if (ret) { 1138c2ecf20Sopenharmony_ci dev_err(dev, "failed to parse qcom,smem-states property\n"); 1148c2ecf20Sopenharmony_ci return ERR_PTR(ret); 1158c2ecf20Sopenharmony_ci } 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci if (args.args_count != 1) { 1188c2ecf20Sopenharmony_ci dev_err(dev, "invalid #qcom,smem-state-cells\n"); 1198c2ecf20Sopenharmony_ci return ERR_PTR(-EINVAL); 1208c2ecf20Sopenharmony_ci } 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci state = of_node_to_state(args.np); 1238c2ecf20Sopenharmony_ci if (IS_ERR(state)) 1248c2ecf20Sopenharmony_ci goto put; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci *bit = args.args[0]; 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ciput: 1298c2ecf20Sopenharmony_ci of_node_put(args.np); 1308c2ecf20Sopenharmony_ci return state; 1318c2ecf20Sopenharmony_ci} 1328c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_smem_state_get); 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_cistatic void qcom_smem_state_release(struct kref *ref) 1358c2ecf20Sopenharmony_ci{ 1368c2ecf20Sopenharmony_ci struct qcom_smem_state *state = container_of(ref, struct qcom_smem_state, refcount); 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci list_del(&state->list); 1398c2ecf20Sopenharmony_ci of_node_put(state->of_node); 1408c2ecf20Sopenharmony_ci kfree(state); 1418c2ecf20Sopenharmony_ci} 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci/** 1448c2ecf20Sopenharmony_ci * qcom_smem_state_put() - release state handle 1458c2ecf20Sopenharmony_ci * @state: state handle to be released 1468c2ecf20Sopenharmony_ci */ 1478c2ecf20Sopenharmony_civoid qcom_smem_state_put(struct qcom_smem_state *state) 1488c2ecf20Sopenharmony_ci{ 1498c2ecf20Sopenharmony_ci mutex_lock(&list_lock); 1508c2ecf20Sopenharmony_ci kref_put(&state->refcount, qcom_smem_state_release); 1518c2ecf20Sopenharmony_ci mutex_unlock(&list_lock); 1528c2ecf20Sopenharmony_ci} 1538c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_smem_state_put); 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci/** 1568c2ecf20Sopenharmony_ci * qcom_smem_state_register() - register a new state 1578c2ecf20Sopenharmony_ci * @of_node: of_node used for matching client lookups 1588c2ecf20Sopenharmony_ci * @ops: implementation ops 1598c2ecf20Sopenharmony_ci * @priv: implementation specific private data 1608c2ecf20Sopenharmony_ci */ 1618c2ecf20Sopenharmony_cistruct qcom_smem_state *qcom_smem_state_register(struct device_node *of_node, 1628c2ecf20Sopenharmony_ci const struct qcom_smem_state_ops *ops, 1638c2ecf20Sopenharmony_ci void *priv) 1648c2ecf20Sopenharmony_ci{ 1658c2ecf20Sopenharmony_ci struct qcom_smem_state *state; 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci state = kzalloc(sizeof(*state), GFP_KERNEL); 1688c2ecf20Sopenharmony_ci if (!state) 1698c2ecf20Sopenharmony_ci return ERR_PTR(-ENOMEM); 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci kref_init(&state->refcount); 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci state->of_node = of_node_get(of_node); 1748c2ecf20Sopenharmony_ci state->ops = *ops; 1758c2ecf20Sopenharmony_ci state->priv = priv; 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci mutex_lock(&list_lock); 1788c2ecf20Sopenharmony_ci list_add(&state->list, &smem_states); 1798c2ecf20Sopenharmony_ci mutex_unlock(&list_lock); 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci return state; 1828c2ecf20Sopenharmony_ci} 1838c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_smem_state_register); 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci/** 1868c2ecf20Sopenharmony_ci * qcom_smem_state_unregister() - unregister a registered state 1878c2ecf20Sopenharmony_ci * @state: state handle to be unregistered 1888c2ecf20Sopenharmony_ci */ 1898c2ecf20Sopenharmony_civoid qcom_smem_state_unregister(struct qcom_smem_state *state) 1908c2ecf20Sopenharmony_ci{ 1918c2ecf20Sopenharmony_ci state->orphan = true; 1928c2ecf20Sopenharmony_ci qcom_smem_state_put(state); 1938c2ecf20Sopenharmony_ci} 1948c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(qcom_smem_state_unregister); 195