@@ -34,6 +34,7 @@ struct syscon {
struct regmap *regmap;
struct reset_control *reset;
struct list_head list;
+ struct kref refcount;
};
static const struct regmap_config syscon_regmap_config = {
@@ -147,6 +148,8 @@ static struct syscon *of_syscon_register(struct device_node *np, bool check_res)
syscon->regmap = regmap;
syscon->np = np;
+ of_node_get(syscon->np);
+ kref_init(&syscon->refcount);
spin_lock(&syscon_list_slock);
list_add_tail(&syscon->list, &syscon_list);
@@ -168,7 +171,30 @@ static struct syscon *of_syscon_register(struct device_node *np, bool check_res)
return ERR_PTR(ret);
}
-static struct regmap *device_node_get_regmap(struct device_node *np,
+static void syscon_free(struct kref *kref)
+{
+ struct syscon *syscon = container_of(kref, struct syscon, refcount);
+
+ spin_lock(&syscon_list_slock);
+ list_del(&syscon->list);
+ spin_unlock(&syscon_list_slock);
+
+ regmap_exit(syscon->regmap);
+ of_node_put(syscon->np);
+ kfree(syscon);
+}
+
+static void syscon_get(struct syscon *syscon)
+{
+ kref_get(&syscon->refcount);
+}
+
+static void syscon_put(struct syscon *syscon)
+{
+ kref_put(&syscon->refcount, syscon_free);
+}
+
+static struct syscon *device_node_get_syscon(struct device_node *np,
bool check_res)
{
struct syscon *entry, *syscon = NULL;
@@ -183,9 +209,23 @@ static struct regmap *device_node_get_regmap(struct device_node *np,
spin_unlock(&syscon_list_slock);
- if (!syscon)
+ if (!syscon) {
syscon = of_syscon_register(np, check_res);
+ if (IS_ERR(syscon))
+ return ERR_CAST(syscon);
+ } else {
+ syscon_get(syscon);
+ }
+
+ return syscon;
+}
+static struct regmap *device_node_get_regmap(struct device_node *np,
+ bool check_res)
+{
+ struct syscon *syscon;
+
+ syscon = device_node_get_syscon(np, check_res);
if (IS_ERR(syscon))
return ERR_CAST(syscon);
@@ -246,12 +286,23 @@ struct regmap *device_node_to_regmap(struct device_node *np)
}
EXPORT_SYMBOL_GPL(device_node_to_regmap);
-struct regmap *syscon_node_to_regmap(struct device_node *np)
+static struct syscon *syscon_node_to_syscon(struct device_node *np)
{
if (!of_device_is_compatible(np, "syscon"))
return ERR_PTR(-EINVAL);
- return device_node_get_regmap(np, true);
+ return device_node_get_syscon(np, true);
+}
+
+struct regmap *syscon_node_to_regmap(struct device_node *np)
+{
+ struct syscon *syscon;
+
+ syscon = syscon_node_to_syscon(np);
+ if (IS_ERR(syscon))
+ return ERR_CAST(syscon);
+
+ return syscon->regmap;
}
EXPORT_SYMBOL_GPL(syscon_node_to_regmap);
@@ -271,11 +322,11 @@ struct regmap *syscon_regmap_lookup_by_compatible(const char *s)
}
EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_compatible);
-struct regmap *syscon_regmap_lookup_by_phandle(struct device_node *np,
- const char *property)
+static struct syscon *syscon_lookup_by_phandle(struct device_node *np,
+ const char *property)
{
struct device_node *syscon_np;
- struct regmap *regmap;
+ struct syscon *syscon;
if (property)
syscon_np = of_parse_phandle(np, property, 0);
@@ -285,12 +336,24 @@ struct regmap *syscon_regmap_lookup_by_phandle(struct device_node *np,
if (!syscon_np)
return ERR_PTR(-ENODEV);
- regmap = syscon_node_to_regmap(syscon_np);
+ syscon = syscon_node_to_syscon(syscon_np);
if (property)
of_node_put(syscon_np);
- return regmap;
+ return syscon;
+}
+
+struct regmap *syscon_regmap_lookup_by_phandle(struct device_node *np,
+ const char *property)
+{
+ struct syscon *syscon;
+
+ syscon = syscon_lookup_by_phandle(np, property);
+ if (IS_ERR(syscon))
+ return ERR_CAST(syscon);
+
+ return syscon->regmap;
}
EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle);
@@ -341,6 +404,70 @@ struct regmap *syscon_regmap_lookup_by_phandle_optional(struct device_node *np,
}
EXPORT_SYMBOL_GPL(syscon_regmap_lookup_by_phandle_optional);
+static struct syscon *syscon_from_regmap(struct regmap *regmap)
+{
+ struct syscon *entry, *syscon = NULL;
+
+ spin_lock(&syscon_list_slock);
+
+ list_for_each_entry(entry, &syscon_list, list)
+ if (entry->regmap == regmap) {
+ syscon = entry;
+ break;
+ }
+
+ spin_unlock(&syscon_list_slock);
+
+ return syscon;
+}
+
+void syscon_put_regmap(struct regmap *regmap)
+{
+ struct syscon *syscon;
+
+ syscon = syscon_from_regmap(regmap);
+ if (!syscon)
+ return;
+
+ syscon_put(syscon);
+}
+EXPORT_SYMBOL_GPL(syscon_put_regmap);
+
+static void devm_syscon_release(struct device *dev, void *res)
+{
+ syscon_put(*(struct syscon **)res);
+}
+
+static struct regmap *__devm_syscon_get(struct device *dev,
+ struct syscon *syscon)
+{
+ struct syscon **ptr;
+
+ if (IS_ERR(syscon))
+ return ERR_CAST(syscon);
+
+ ptr = devres_alloc(devm_syscon_release, sizeof(struct syscon *), GFP_KERNEL);
+ if (!ptr) {
+ syscon_put(syscon);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ *ptr = syscon;
+ devres_add(dev, ptr);
+
+ return syscon->regmap;
+}
+
+struct regmap *devm_syscon_regmap_lookup_by_phandle(struct device *dev,
+ struct device_node *np,
+ const char *property)
+{
+ struct syscon *syscon = syscon_lookup_by_phandle(np, property);
+
+ return __devm_syscon_get(dev, syscon);
+}
+EXPORT_SYMBOL_GPL(devm_syscon_regmap_lookup_by_phandle);
+
static int syscon_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -15,6 +15,7 @@
#include <linux/errno.h>
struct device_node;
+struct device;
#ifdef CONFIG_MFD_SYSCON
struct regmap *device_node_to_regmap(struct device_node *np);
@@ -30,6 +31,10 @@ struct regmap *syscon_regmap_lookup_by_phandle_optional(struct device_node *np,
const char *property);
int of_syscon_register_regmap(struct device_node *np,
struct regmap *regmap);
+void syscon_put_regmap(struct regmap *regmap);
+struct regmap *devm_syscon_regmap_lookup_by_phandle(struct device *dev,
+ struct device_node *np,
+ const char *property);
#else
static inline struct regmap *device_node_to_regmap(struct device_node *np)
{
@@ -73,6 +78,17 @@ static inline int of_syscon_register_regmap(struct device_node *np,
struct regmap *regmap)
{
return -EOPNOTSUPP;
+
+static inline void syscon_put_regmap(struct regmap *regmap)
+{
+}
+
+static inline
+struct regmap *devm_syscon_regmap_lookup_by_phandle(struct device *dev,
+ struct device_node *np,
+ const char *property)
+{
+ return NULL;
}
#endif