summaryrefslogtreecommitdiffstats
path: root/drivers/base/soc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/base/soc.c')
-rw-r--r--drivers/base/soc.c282
1 files changed, 282 insertions, 0 deletions
diff --git a/drivers/base/soc.c b/drivers/base/soc.c
new file mode 100644
index 0000000000..8dec5228fd
--- /dev/null
+++ b/drivers/base/soc.c
@@ -0,0 +1,282 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Author: Lee Jones <lee.jones@linaro.org> for ST-Ericsson.
+ */
+
+#include <linux/sysfs.h>
+#include <linux/init.h>
+#include <linux/of.h>
+#include <linux/stat.h>
+#include <linux/slab.h>
+#include <linux/idr.h>
+#include <linux/spinlock.h>
+#include <linux/sys_soc.h>
+#include <linux/err.h>
+#include <linux/glob.h>
+
+static DEFINE_IDA(soc_ida);
+
+/* Prototype to allow declarations of DEVICE_ATTR(<foo>) before soc_info_show */
+static ssize_t soc_info_show(struct device *dev, struct device_attribute *attr,
+ char *buf);
+
+struct soc_device {
+ struct device dev;
+ struct soc_device_attribute *attr;
+ int soc_dev_num;
+};
+
+static struct bus_type soc_bus_type = {
+ .name = "soc",
+};
+static bool soc_bus_registered;
+
+static DEVICE_ATTR(machine, 0444, soc_info_show, NULL);
+static DEVICE_ATTR(family, 0444, soc_info_show, NULL);
+static DEVICE_ATTR(serial_number, 0444, soc_info_show, NULL);
+static DEVICE_ATTR(soc_id, 0444, soc_info_show, NULL);
+static DEVICE_ATTR(revision, 0444, soc_info_show, NULL);
+
+struct device *soc_device_to_device(struct soc_device *soc_dev)
+{
+ return &soc_dev->dev;
+}
+
+static umode_t soc_attribute_mode(struct kobject *kobj,
+ struct attribute *attr,
+ int index)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct soc_device *soc_dev = container_of(dev, struct soc_device, dev);
+
+ if ((attr == &dev_attr_machine.attr) && soc_dev->attr->machine)
+ return attr->mode;
+ if ((attr == &dev_attr_family.attr) && soc_dev->attr->family)
+ return attr->mode;
+ if ((attr == &dev_attr_revision.attr) && soc_dev->attr->revision)
+ return attr->mode;
+ if ((attr == &dev_attr_serial_number.attr) && soc_dev->attr->serial_number)
+ return attr->mode;
+ if ((attr == &dev_attr_soc_id.attr) && soc_dev->attr->soc_id)
+ return attr->mode;
+
+ /* Unknown or unfilled attribute */
+ return 0;
+}
+
+static ssize_t soc_info_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct soc_device *soc_dev = container_of(dev, struct soc_device, dev);
+ const char *output;
+
+ if (attr == &dev_attr_machine)
+ output = soc_dev->attr->machine;
+ else if (attr == &dev_attr_family)
+ output = soc_dev->attr->family;
+ else if (attr == &dev_attr_revision)
+ output = soc_dev->attr->revision;
+ else if (attr == &dev_attr_serial_number)
+ output = soc_dev->attr->serial_number;
+ else if (attr == &dev_attr_soc_id)
+ output = soc_dev->attr->soc_id;
+ else
+ return -EINVAL;
+
+ return sysfs_emit(buf, "%s\n", output);
+}
+
+static struct attribute *soc_attr[] = {
+ &dev_attr_machine.attr,
+ &dev_attr_family.attr,
+ &dev_attr_serial_number.attr,
+ &dev_attr_soc_id.attr,
+ &dev_attr_revision.attr,
+ NULL,
+};
+
+static const struct attribute_group soc_attr_group = {
+ .attrs = soc_attr,
+ .is_visible = soc_attribute_mode,
+};
+
+static void soc_release(struct device *dev)
+{
+ struct soc_device *soc_dev = container_of(dev, struct soc_device, dev);
+
+ ida_simple_remove(&soc_ida, soc_dev->soc_dev_num);
+ kfree(soc_dev->dev.groups);
+ kfree(soc_dev);
+}
+
+static void soc_device_get_machine(struct soc_device_attribute *soc_dev_attr)
+{
+ struct device_node *np;
+
+ if (soc_dev_attr->machine)
+ return;
+
+ np = of_find_node_by_path("/");
+ of_property_read_string(np, "model", &soc_dev_attr->machine);
+ of_node_put(np);
+}
+
+static struct soc_device_attribute *early_soc_dev_attr;
+
+struct soc_device *soc_device_register(struct soc_device_attribute *soc_dev_attr)
+{
+ struct soc_device *soc_dev;
+ const struct attribute_group **soc_attr_groups;
+ int ret;
+
+ soc_device_get_machine(soc_dev_attr);
+
+ if (!soc_bus_registered) {
+ if (early_soc_dev_attr)
+ return ERR_PTR(-EBUSY);
+ early_soc_dev_attr = soc_dev_attr;
+ return NULL;
+ }
+
+ soc_dev = kzalloc(sizeof(*soc_dev), GFP_KERNEL);
+ if (!soc_dev) {
+ ret = -ENOMEM;
+ goto out1;
+ }
+
+ soc_attr_groups = kcalloc(3, sizeof(*soc_attr_groups), GFP_KERNEL);
+ if (!soc_attr_groups) {
+ ret = -ENOMEM;
+ goto out2;
+ }
+ soc_attr_groups[0] = &soc_attr_group;
+ soc_attr_groups[1] = soc_dev_attr->custom_attr_group;
+
+ /* Fetch a unique (reclaimable) SOC ID. */
+ ret = ida_simple_get(&soc_ida, 0, 0, GFP_KERNEL);
+ if (ret < 0)
+ goto out3;
+ soc_dev->soc_dev_num = ret;
+
+ soc_dev->attr = soc_dev_attr;
+ soc_dev->dev.bus = &soc_bus_type;
+ soc_dev->dev.groups = soc_attr_groups;
+ soc_dev->dev.release = soc_release;
+
+ dev_set_name(&soc_dev->dev, "soc%d", soc_dev->soc_dev_num);
+
+ ret = device_register(&soc_dev->dev);
+ if (ret) {
+ put_device(&soc_dev->dev);
+ return ERR_PTR(ret);
+ }
+
+ return soc_dev;
+
+out3:
+ kfree(soc_attr_groups);
+out2:
+ kfree(soc_dev);
+out1:
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(soc_device_register);
+
+/* Ensure soc_dev->attr is freed after calling soc_device_unregister. */
+void soc_device_unregister(struct soc_device *soc_dev)
+{
+ device_unregister(&soc_dev->dev);
+ early_soc_dev_attr = NULL;
+}
+EXPORT_SYMBOL_GPL(soc_device_unregister);
+
+static int __init soc_bus_register(void)
+{
+ int ret;
+
+ ret = bus_register(&soc_bus_type);
+ if (ret)
+ return ret;
+ soc_bus_registered = true;
+
+ if (early_soc_dev_attr)
+ return PTR_ERR(soc_device_register(early_soc_dev_attr));
+
+ return 0;
+}
+core_initcall(soc_bus_register);
+
+static int soc_device_match_attr(const struct soc_device_attribute *attr,
+ const struct soc_device_attribute *match)
+{
+ if (match->machine &&
+ (!attr->machine || !glob_match(match->machine, attr->machine)))
+ return 0;
+
+ if (match->family &&
+ (!attr->family || !glob_match(match->family, attr->family)))
+ return 0;
+
+ if (match->revision &&
+ (!attr->revision || !glob_match(match->revision, attr->revision)))
+ return 0;
+
+ if (match->soc_id &&
+ (!attr->soc_id || !glob_match(match->soc_id, attr->soc_id)))
+ return 0;
+
+ return 1;
+}
+
+static int soc_device_match_one(struct device *dev, void *arg)
+{
+ struct soc_device *soc_dev = container_of(dev, struct soc_device, dev);
+
+ return soc_device_match_attr(soc_dev->attr, arg);
+}
+
+/*
+ * soc_device_match - identify the SoC in the machine
+ * @matches: zero-terminated array of possible matches
+ *
+ * returns the first matching entry of the argument array, or NULL
+ * if none of them match.
+ *
+ * This function is meant as a helper in place of of_match_node()
+ * in cases where either no device tree is available or the information
+ * in a device node is insufficient to identify a particular variant
+ * by its compatible strings or other properties. For new devices,
+ * the DT binding should always provide unique compatible strings
+ * that allow the use of of_match_node() instead.
+ *
+ * The calling function can use the .data entry of the
+ * soc_device_attribute to pass a structure or function pointer for
+ * each entry.
+ */
+const struct soc_device_attribute *soc_device_match(
+ const struct soc_device_attribute *matches)
+{
+ int ret;
+
+ if (!matches)
+ return NULL;
+
+ while (matches->machine || matches->family || matches->revision ||
+ matches->soc_id) {
+ ret = bus_for_each_dev(&soc_bus_type, NULL, (void *)matches,
+ soc_device_match_one);
+ if (ret < 0 && early_soc_dev_attr)
+ ret = soc_device_match_attr(early_soc_dev_attr,
+ matches);
+ if (ret < 0)
+ return NULL;
+ if (ret)
+ return matches;
+
+ matches++;
+ }
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(soc_device_match);