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