[PATCH 1/9] ARM: sunxi: introduce SoC identification support

Emilio López emilio at elopez.com.ar
Thu Jul 31 14:28:04 PDT 2014


This commit adds SoC bus support on the sunxi platform, and exposes
information such as the hardware revision to userspace and other kernel
clients during init. A message with this information is also printed to
the kernel log to ease future bug triaging.

Signed-off-by: Emilio López <emilio at elopez.com.ar>
---
 arch/arm/mach-sunxi/Kconfig        |   1 +
 arch/arm/mach-sunxi/Makefile       |   2 +-
 arch/arm/mach-sunxi/sunxi-soc-id.c | 226 +++++++++++++++++++++++++++++++++++++
 arch/arm/mach-sunxi/sunxi-soc-id.h |   6 +
 4 files changed, 234 insertions(+), 1 deletion(-)
 create mode 100644 arch/arm/mach-sunxi/sunxi-soc-id.c
 create mode 100644 arch/arm/mach-sunxi/sunxi-soc-id.h

diff --git a/arch/arm/mach-sunxi/Kconfig b/arch/arm/mach-sunxi/Kconfig
index 6434e3b..4a199df 100644
--- a/arch/arm/mach-sunxi/Kconfig
+++ b/arch/arm/mach-sunxi/Kconfig
@@ -5,6 +5,7 @@ menuconfig ARCH_SUNXI
 	select GENERIC_IRQ_CHIP
 	select PINCTRL
 	select PINCTRL_SUNXI
+	select SOC_BUS
 	select SUN4I_TIMER
 
 if ARCH_SUNXI
diff --git a/arch/arm/mach-sunxi/Makefile b/arch/arm/mach-sunxi/Makefile
index 27b168f..589239b 100644
--- a/arch/arm/mach-sunxi/Makefile
+++ b/arch/arm/mach-sunxi/Makefile
@@ -1,2 +1,2 @@
-obj-$(CONFIG_ARCH_SUNXI) += sunxi.o
+obj-$(CONFIG_ARCH_SUNXI) += sunxi.o sunxi-soc-id.o
 obj-$(CONFIG_SMP) += platsmp.o
diff --git a/arch/arm/mach-sunxi/sunxi-soc-id.c b/arch/arm/mach-sunxi/sunxi-soc-id.c
new file mode 100644
index 0000000..c7eff1c
--- /dev/null
+++ b/arch/arm/mach-sunxi/sunxi-soc-id.c
@@ -0,0 +1,226 @@
+/*
+ * SoC revision detection for sunxi SoCs
+ *
+ * Copyright 2014 Emilio López
+ *
+ * Emilio López <emilio at elopez.com.ar>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/sys_soc.h>
+
+#include "sunxi-soc-id.h"
+
+/*
+ * On the A10 SoC, we can read the revision information from the timer
+ * block. The detection logic is extracted from similar code on the
+ * Allwinner vendor tree, as this is undocumented on the user manual
+ */
+
+#define TIMER_SOC_REV_REG		0x13c
+#define TIMER_SOC_REV_CLEAR(val)	((val) & ~(0x3 << 6))
+#define TIMER_SOC_REV_GET(val)		(((val) >> 6) & 0x3)
+
+static const struct of_device_id sun4i_timer_compatible[] __initconst = {
+	{ .compatible = "allwinner,sun4i-a10-timer", },
+	{},
+};
+
+static int __init sun4i_get_soc_revision(void)
+{
+	struct device_node *np;
+	void __iomem *base;
+	u32 val;
+	int ret;
+
+	/* Find the timer node */
+	np = of_find_matching_node(NULL, sun4i_timer_compatible);
+	if (!np)
+		return -ENODEV;
+
+	/* Temporarily map it for reading */
+	base = of_iomap(np, 0);
+	if (!base) {
+		of_node_put(np);
+		return -ENOMEM;
+	}
+
+	/* Clear the SoC revision bits and rewrite the register */
+	val = readl(base + TIMER_SOC_REV_REG);
+	val = TIMER_SOC_REV_CLEAR(val);
+	writel(val, base + TIMER_SOC_REV_REG);
+
+	/* Now read it again and see what shows up */
+	val = readl(base + TIMER_SOC_REV_REG);
+	val = TIMER_SOC_REV_GET(val);
+
+	switch (val) {
+	case 0:  /* revision A */
+		ret = 'A';
+	case 3:  /* revision B */
+		ret = 'B';
+	default: /* revision C */
+		ret = 'C';
+	}
+
+	iounmap(base);
+	of_node_put(np);
+
+	return ret;
+}
+
+/*
+ * On the sun5i SoCs (A10S, A13), we can read the revision information
+ * from the first bits in the Security ID. The detection logic is
+ * extracted from similar code on the Allwinner vendor tree, as this
+ * is undocumented on the user manual.
+ */
+
+static const struct of_device_id sun5i_sid_compatible[] __initconst = {
+	{ .compatible = "allwinner,sun4i-a10-sid", },
+	{},
+};
+
+static int __init sun5i_get_soc_revision(void)
+{
+	struct device_node *np;
+	void __iomem *sid;
+	u32 val;
+	int ret;
+
+	/* Find the SID node */
+	np = of_find_matching_node(NULL, sun5i_sid_compatible);
+	if (!np)
+		return -ENODEV;
+
+	/* Temporarily map it for reading */
+	sid = of_iomap(np, 0);
+	if (!sid) {
+		of_node_put(np);
+		return -ENOMEM;
+	}
+
+	/* Read and extract the chip revision from the SID */
+	val = readl(sid);
+	val = (val >> 8) & 0xffffff;
+
+	switch (val) {
+	case 0:        /* A10S/A13 rev A */
+	case 0x162541: /* A10S/A13 rev A */
+	case 0x162565: /* A13 rev A */
+		ret = 'A';
+		break;
+	case 0x162542: /* A10S/A13 rev B */
+		ret = 'B';
+		break;
+	default:       /* Unknown chip revision */
+		ret = -ENODATA;
+	}
+
+	iounmap(sid);
+	of_node_put(np);
+
+	return ret;
+}
+
+int __init sunxi_soc_revision(void)
+{
+	static int revision = -ENODEV;
+
+	/* Try to query the hardware just once */
+	if (!IS_ERR_VALUE(revision))
+		return revision;
+
+	if (of_machine_is_compatible("allwinner,sun4i-a10")) {
+		revision = sun4i_get_soc_revision();
+	} else if (of_machine_is_compatible("allwinner,sun5i-a10s") ||
+		   of_machine_is_compatible("allwinner,sun5i-a13")) {
+		revision = sun5i_get_soc_revision();
+	}
+
+	return revision;
+}
+
+/* Matches for the sunxi SoCs we know of */
+static const struct of_device_id soc_matches[] __initconst = {
+	{ .compatible = "allwinner,sun4i-a10", .data = "A10 (sun4i)" },
+	{ .compatible = "allwinner,sun5i-a13", .data = "A13 (sun5i)" },
+	{ .compatible = "allwinner,sun5i-a10s", .data = "A10S (sun5i)" },
+	{ .compatible = "allwinner,sun6i-a31", .data = "A31 (sun6i)" },
+	{ .compatible = "allwinner,sun7i-a20", .data = "A20 (sun7i)" },
+	{ .compatible = "allwinner,sun8i-a23", .data = "A23 (sun8i)" },
+	{ /* sentinel */ },
+};
+
+static int __init sunxi_register_soc_device(void)
+{
+	struct soc_device_attribute *soc_dev_attr;
+	struct soc_device *soc_dev;
+	const struct of_device_id *match;
+	struct device_node *root;
+	int revision;
+
+	/* Only run on sunxi SoCs that we know of */
+	root = of_find_node_by_path("/");
+	match = of_match_node(soc_matches, root);
+	if (!match)
+		goto exit;
+
+	soc_dev_attr = kzalloc(sizeof(*soc_dev_attr), GFP_KERNEL);
+	if (!soc_dev_attr)
+		goto exit;
+
+	/* Read the machine name if available */
+	of_property_read_string(root, "model", &soc_dev_attr->machine);
+
+	soc_dev_attr->family = kstrdup("Allwinner A Series", GFP_KERNEL);
+	soc_dev_attr->soc_id = kstrdup(match->data, GFP_KERNEL);
+
+	/* Revision may not always be available */
+	revision = sunxi_soc_revision();
+	if (IS_ERR_VALUE(revision))
+		soc_dev_attr->revision = kstrdup("Unknown", GFP_KERNEL);
+	else
+		soc_dev_attr->revision = kasprintf(GFP_KERNEL, "%c", revision);
+
+	soc_dev = soc_device_register(soc_dev_attr);
+	if (IS_ERR(soc_dev))
+		goto free_struct;
+
+	/*
+	 * Print an informational line mentioning the hardware details.
+	 * It may come in handy during bug reports, as some early SoC
+	 * revisions have hardware quirks and do not get much testing.
+	 */
+	pr_info("SoC bus registered, running %s %s, revision %s\n",
+		soc_dev_attr->family, soc_dev_attr->soc_id,
+		soc_dev_attr->revision);
+
+	return 0;
+
+free_struct:
+	kfree(soc_dev_attr->family);
+	kfree(soc_dev_attr->soc_id);
+	kfree(soc_dev_attr->revision);
+	kfree(soc_dev_attr);
+exit:
+	of_node_put(root);
+
+	return 0;
+}
+postcore_initcall(sunxi_register_soc_device)
diff --git a/arch/arm/mach-sunxi/sunxi-soc-id.h b/arch/arm/mach-sunxi/sunxi-soc-id.h
new file mode 100644
index 0000000..d49c245
--- /dev/null
+++ b/arch/arm/mach-sunxi/sunxi-soc-id.h
@@ -0,0 +1,6 @@
+#ifndef _SUNXI_SOC_ID_H
+#define _SUNXI_SOC_ID_H
+
+int __init sunxi_soc_revision(void);
+
+#endif /* _SUNXI_SOC_ID_H */
-- 
2.0.3




More information about the linux-arm-kernel mailing list