xref: /linux/drivers/soc/qcom/smem_state.c (revision 4f2c0a4acffbec01079c28f839422e64ddeff004)
197fb5e8dSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
29460ae2fSBjorn Andersson /*
39460ae2fSBjorn Andersson  * Copyright (c) 2015, Sony Mobile Communications Inc.
49460ae2fSBjorn Andersson  * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
59460ae2fSBjorn Andersson  */
69460ae2fSBjorn Andersson #include <linux/device.h>
79460ae2fSBjorn Andersson #include <linux/list.h>
89460ae2fSBjorn Andersson #include <linux/module.h>
99460ae2fSBjorn Andersson #include <linux/of.h>
109460ae2fSBjorn Andersson #include <linux/slab.h>
119460ae2fSBjorn Andersson #include <linux/soc/qcom/smem_state.h>
129460ae2fSBjorn Andersson 
139460ae2fSBjorn Andersson static LIST_HEAD(smem_states);
149460ae2fSBjorn Andersson static DEFINE_MUTEX(list_lock);
159460ae2fSBjorn Andersson 
169460ae2fSBjorn Andersson /**
179460ae2fSBjorn Andersson  * struct qcom_smem_state - state context
189460ae2fSBjorn Andersson  * @refcount:	refcount for the state
199460ae2fSBjorn Andersson  * @orphan:	boolean indicator that this state has been unregistered
209460ae2fSBjorn Andersson  * @list:	entry in smem_states list
219460ae2fSBjorn Andersson  * @of_node:	of_node to use for matching the state in DT
229460ae2fSBjorn Andersson  * @priv:	implementation private data
239460ae2fSBjorn Andersson  * @ops:	ops for the state
249460ae2fSBjorn Andersson  */
259460ae2fSBjorn Andersson struct qcom_smem_state {
269460ae2fSBjorn Andersson 	struct kref refcount;
279460ae2fSBjorn Andersson 	bool orphan;
289460ae2fSBjorn Andersson 
299460ae2fSBjorn Andersson 	struct list_head list;
309460ae2fSBjorn Andersson 	struct device_node *of_node;
319460ae2fSBjorn Andersson 
329460ae2fSBjorn Andersson 	void *priv;
339460ae2fSBjorn Andersson 
349460ae2fSBjorn Andersson 	struct qcom_smem_state_ops ops;
359460ae2fSBjorn Andersson };
369460ae2fSBjorn Andersson 
379460ae2fSBjorn Andersson /**
389460ae2fSBjorn Andersson  * qcom_smem_state_update_bits() - update the masked bits in state with value
399460ae2fSBjorn Andersson  * @state:	state handle acquired by calling qcom_smem_state_get()
409460ae2fSBjorn Andersson  * @mask:	bit mask for the change
419460ae2fSBjorn Andersson  * @value:	new value for the masked bits
429460ae2fSBjorn Andersson  *
439460ae2fSBjorn Andersson  * Returns 0 on success, otherwise negative errno.
449460ae2fSBjorn Andersson  */
qcom_smem_state_update_bits(struct qcom_smem_state * state,u32 mask,u32 value)459460ae2fSBjorn Andersson int qcom_smem_state_update_bits(struct qcom_smem_state *state,
469460ae2fSBjorn Andersson 				u32 mask,
479460ae2fSBjorn Andersson 				u32 value)
489460ae2fSBjorn Andersson {
499460ae2fSBjorn Andersson 	if (state->orphan)
509460ae2fSBjorn Andersson 		return -ENXIO;
519460ae2fSBjorn Andersson 
529460ae2fSBjorn Andersson 	if (!state->ops.update_bits)
539460ae2fSBjorn Andersson 		return -ENOTSUPP;
549460ae2fSBjorn Andersson 
559460ae2fSBjorn Andersson 	return state->ops.update_bits(state->priv, mask, value);
569460ae2fSBjorn Andersson }
579460ae2fSBjorn Andersson EXPORT_SYMBOL_GPL(qcom_smem_state_update_bits);
589460ae2fSBjorn Andersson 
of_node_to_state(struct device_node * np)599460ae2fSBjorn Andersson static struct qcom_smem_state *of_node_to_state(struct device_node *np)
609460ae2fSBjorn Andersson {
619460ae2fSBjorn Andersson 	struct qcom_smem_state *state;
629460ae2fSBjorn Andersson 
639460ae2fSBjorn Andersson 	mutex_lock(&list_lock);
649460ae2fSBjorn Andersson 
659460ae2fSBjorn Andersson 	list_for_each_entry(state, &smem_states, list) {
669460ae2fSBjorn Andersson 		if (state->of_node == np) {
679460ae2fSBjorn Andersson 			kref_get(&state->refcount);
689460ae2fSBjorn Andersson 			goto unlock;
699460ae2fSBjorn Andersson 		}
709460ae2fSBjorn Andersson 	}
719460ae2fSBjorn Andersson 	state = ERR_PTR(-EPROBE_DEFER);
729460ae2fSBjorn Andersson 
739460ae2fSBjorn Andersson unlock:
749460ae2fSBjorn Andersson 	mutex_unlock(&list_lock);
759460ae2fSBjorn Andersson 
769460ae2fSBjorn Andersson 	return state;
779460ae2fSBjorn Andersson }
789460ae2fSBjorn Andersson 
799460ae2fSBjorn Andersson /**
809460ae2fSBjorn Andersson  * qcom_smem_state_get() - acquire handle to a state
819460ae2fSBjorn Andersson  * @dev:	client device pointer
829460ae2fSBjorn Andersson  * @con_id:	name of the state to lookup
839460ae2fSBjorn Andersson  * @bit:	flags from the state reference, indicating which bit's affected
849460ae2fSBjorn Andersson  *
859460ae2fSBjorn Andersson  * Returns handle to the state, or ERR_PTR(). qcom_smem_state_put() must be
869460ae2fSBjorn Andersson  * called to release the returned state handle.
879460ae2fSBjorn Andersson  */
qcom_smem_state_get(struct device * dev,const char * con_id,unsigned * bit)889460ae2fSBjorn Andersson struct qcom_smem_state *qcom_smem_state_get(struct device *dev,
899460ae2fSBjorn Andersson 					    const char *con_id,
909460ae2fSBjorn Andersson 					    unsigned *bit)
919460ae2fSBjorn Andersson {
929460ae2fSBjorn Andersson 	struct qcom_smem_state *state;
939460ae2fSBjorn Andersson 	struct of_phandle_args args;
949460ae2fSBjorn Andersson 	int index = 0;
959460ae2fSBjorn Andersson 	int ret;
969460ae2fSBjorn Andersson 
979460ae2fSBjorn Andersson 	if (con_id) {
989460ae2fSBjorn Andersson 		index = of_property_match_string(dev->of_node,
993680a4a9SBjorn Andersson 						 "qcom,smem-state-names",
1009460ae2fSBjorn Andersson 						 con_id);
1019460ae2fSBjorn Andersson 		if (index < 0) {
1023680a4a9SBjorn Andersson 			dev_err(dev, "missing qcom,smem-state-names\n");
1039460ae2fSBjorn Andersson 			return ERR_PTR(index);
1049460ae2fSBjorn Andersson 		}
1059460ae2fSBjorn Andersson 	}
1069460ae2fSBjorn Andersson 
1079460ae2fSBjorn Andersson 	ret = of_parse_phandle_with_args(dev->of_node,
1083680a4a9SBjorn Andersson 					 "qcom,smem-states",
1093680a4a9SBjorn Andersson 					 "#qcom,smem-state-cells",
1109460ae2fSBjorn Andersson 					 index,
1119460ae2fSBjorn Andersson 					 &args);
1129460ae2fSBjorn Andersson 	if (ret) {
1133680a4a9SBjorn Andersson 		dev_err(dev, "failed to parse qcom,smem-states property\n");
1149460ae2fSBjorn Andersson 		return ERR_PTR(ret);
1159460ae2fSBjorn Andersson 	}
1169460ae2fSBjorn Andersson 
1179460ae2fSBjorn Andersson 	if (args.args_count != 1) {
1183680a4a9SBjorn Andersson 		dev_err(dev, "invalid #qcom,smem-state-cells\n");
1199460ae2fSBjorn Andersson 		return ERR_PTR(-EINVAL);
1209460ae2fSBjorn Andersson 	}
1219460ae2fSBjorn Andersson 
1229460ae2fSBjorn Andersson 	state = of_node_to_state(args.np);
1239460ae2fSBjorn Andersson 	if (IS_ERR(state))
1249460ae2fSBjorn Andersson 		goto put;
1259460ae2fSBjorn Andersson 
1269460ae2fSBjorn Andersson 	*bit = args.args[0];
1279460ae2fSBjorn Andersson 
1289460ae2fSBjorn Andersson put:
1299460ae2fSBjorn Andersson 	of_node_put(args.np);
1309460ae2fSBjorn Andersson 	return state;
1319460ae2fSBjorn Andersson }
1329460ae2fSBjorn Andersson EXPORT_SYMBOL_GPL(qcom_smem_state_get);
1339460ae2fSBjorn Andersson 
qcom_smem_state_release(struct kref * ref)1349460ae2fSBjorn Andersson static void qcom_smem_state_release(struct kref *ref)
1359460ae2fSBjorn Andersson {
1369460ae2fSBjorn Andersson 	struct qcom_smem_state *state = container_of(ref, struct qcom_smem_state, refcount);
1379460ae2fSBjorn Andersson 
1389460ae2fSBjorn Andersson 	list_del(&state->list);
139*90681f53SLiang He 	of_node_put(state->of_node);
1409460ae2fSBjorn Andersson 	kfree(state);
1419460ae2fSBjorn Andersson }
1429460ae2fSBjorn Andersson 
1439460ae2fSBjorn Andersson /**
1449460ae2fSBjorn Andersson  * qcom_smem_state_put() - release state handle
1459460ae2fSBjorn Andersson  * @state:	state handle to be released
1469460ae2fSBjorn Andersson  */
qcom_smem_state_put(struct qcom_smem_state * state)1479460ae2fSBjorn Andersson void qcom_smem_state_put(struct qcom_smem_state *state)
1489460ae2fSBjorn Andersson {
1499460ae2fSBjorn Andersson 	mutex_lock(&list_lock);
1509460ae2fSBjorn Andersson 	kref_put(&state->refcount, qcom_smem_state_release);
1519460ae2fSBjorn Andersson 	mutex_unlock(&list_lock);
1529460ae2fSBjorn Andersson }
1539460ae2fSBjorn Andersson EXPORT_SYMBOL_GPL(qcom_smem_state_put);
1549460ae2fSBjorn Andersson 
devm_qcom_smem_state_release(struct device * dev,void * res)15561d1961aSStephan Gerhold static void devm_qcom_smem_state_release(struct device *dev, void *res)
15661d1961aSStephan Gerhold {
15761d1961aSStephan Gerhold 	qcom_smem_state_put(*(struct qcom_smem_state **)res);
15861d1961aSStephan Gerhold }
15961d1961aSStephan Gerhold 
16061d1961aSStephan Gerhold /**
16161d1961aSStephan Gerhold  * devm_qcom_smem_state_get() - acquire handle to a devres managed state
16261d1961aSStephan Gerhold  * @dev:	client device pointer
16361d1961aSStephan Gerhold  * @con_id:	name of the state to lookup
16461d1961aSStephan Gerhold  * @bit:	flags from the state reference, indicating which bit's affected
16561d1961aSStephan Gerhold  *
16661d1961aSStephan Gerhold  * Returns handle to the state, or ERR_PTR(). qcom_smem_state_put() is called
16761d1961aSStephan Gerhold  * automatically when @dev is removed.
16861d1961aSStephan Gerhold  */
devm_qcom_smem_state_get(struct device * dev,const char * con_id,unsigned * bit)16961d1961aSStephan Gerhold struct qcom_smem_state *devm_qcom_smem_state_get(struct device *dev,
17061d1961aSStephan Gerhold 						 const char *con_id,
17161d1961aSStephan Gerhold 						 unsigned *bit)
17261d1961aSStephan Gerhold {
17361d1961aSStephan Gerhold 	struct qcom_smem_state **ptr, *state;
17461d1961aSStephan Gerhold 
17561d1961aSStephan Gerhold 	ptr = devres_alloc(devm_qcom_smem_state_release, sizeof(*ptr), GFP_KERNEL);
17661d1961aSStephan Gerhold 	if (!ptr)
17761d1961aSStephan Gerhold 		return ERR_PTR(-ENOMEM);
17861d1961aSStephan Gerhold 
17961d1961aSStephan Gerhold 	state = qcom_smem_state_get(dev, con_id, bit);
18061d1961aSStephan Gerhold 	if (!IS_ERR(state)) {
18161d1961aSStephan Gerhold 		*ptr = state;
18261d1961aSStephan Gerhold 		devres_add(dev, ptr);
18361d1961aSStephan Gerhold 	} else {
18461d1961aSStephan Gerhold 		devres_free(ptr);
18561d1961aSStephan Gerhold 	}
18661d1961aSStephan Gerhold 
18761d1961aSStephan Gerhold 	return state;
18861d1961aSStephan Gerhold }
18961d1961aSStephan Gerhold EXPORT_SYMBOL_GPL(devm_qcom_smem_state_get);
19061d1961aSStephan Gerhold 
1919460ae2fSBjorn Andersson /**
1929460ae2fSBjorn Andersson  * qcom_smem_state_register() - register a new state
1939460ae2fSBjorn Andersson  * @of_node:	of_node used for matching client lookups
1949460ae2fSBjorn Andersson  * @ops:	implementation ops
1959460ae2fSBjorn Andersson  * @priv:	implementation specific private data
1969460ae2fSBjorn Andersson  */
qcom_smem_state_register(struct device_node * of_node,const struct qcom_smem_state_ops * ops,void * priv)1979460ae2fSBjorn Andersson struct qcom_smem_state *qcom_smem_state_register(struct device_node *of_node,
1989460ae2fSBjorn Andersson 						 const struct qcom_smem_state_ops *ops,
1999460ae2fSBjorn Andersson 						 void *priv)
2009460ae2fSBjorn Andersson {
2019460ae2fSBjorn Andersson 	struct qcom_smem_state *state;
2029460ae2fSBjorn Andersson 
2039460ae2fSBjorn Andersson 	state = kzalloc(sizeof(*state), GFP_KERNEL);
2049460ae2fSBjorn Andersson 	if (!state)
2059460ae2fSBjorn Andersson 		return ERR_PTR(-ENOMEM);
2069460ae2fSBjorn Andersson 
2079460ae2fSBjorn Andersson 	kref_init(&state->refcount);
2089460ae2fSBjorn Andersson 
209*90681f53SLiang He 	state->of_node = of_node_get(of_node);
2109460ae2fSBjorn Andersson 	state->ops = *ops;
2119460ae2fSBjorn Andersson 	state->priv = priv;
2129460ae2fSBjorn Andersson 
2139460ae2fSBjorn Andersson 	mutex_lock(&list_lock);
2149460ae2fSBjorn Andersson 	list_add(&state->list, &smem_states);
2159460ae2fSBjorn Andersson 	mutex_unlock(&list_lock);
2169460ae2fSBjorn Andersson 
2179460ae2fSBjorn Andersson 	return state;
2189460ae2fSBjorn Andersson }
2199460ae2fSBjorn Andersson EXPORT_SYMBOL_GPL(qcom_smem_state_register);
2209460ae2fSBjorn Andersson 
2219460ae2fSBjorn Andersson /**
2229460ae2fSBjorn Andersson  * qcom_smem_state_unregister() - unregister a registered state
2239460ae2fSBjorn Andersson  * @state:	state handle to be unregistered
2249460ae2fSBjorn Andersson  */
qcom_smem_state_unregister(struct qcom_smem_state * state)2259460ae2fSBjorn Andersson void qcom_smem_state_unregister(struct qcom_smem_state *state)
2269460ae2fSBjorn Andersson {
2279460ae2fSBjorn Andersson 	state->orphan = true;
2289460ae2fSBjorn Andersson 	qcom_smem_state_put(state);
2299460ae2fSBjorn Andersson }
2309460ae2fSBjorn Andersson EXPORT_SYMBOL_GPL(qcom_smem_state_unregister);
231