18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci * ID and revision information for mvebu SoCs
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci * Copyright (C) 2014 Marvell
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * Gregory CLEMENT <gregory.clement@free-electrons.com>
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci * This file is licensed under the terms of the GNU General Public
98c2ecf20Sopenharmony_ci * License version 2.  This program is licensed "as is" without any
108c2ecf20Sopenharmony_ci * warranty of any kind, whether express or implied.
118c2ecf20Sopenharmony_ci *
128c2ecf20Sopenharmony_ci * All the mvebu SoCs have information related to their variant and
138c2ecf20Sopenharmony_ci * revision that can be read from the PCI control register. This is
148c2ecf20Sopenharmony_ci * done before the PCI initialization to avoid any conflict. Once the
158c2ecf20Sopenharmony_ci * ID and revision are retrieved, the mapping is freed.
168c2ecf20Sopenharmony_ci */
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci#define pr_fmt(fmt) "mvebu-soc-id: " fmt
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci#include <linux/clk.h>
218c2ecf20Sopenharmony_ci#include <linux/init.h>
228c2ecf20Sopenharmony_ci#include <linux/io.h>
238c2ecf20Sopenharmony_ci#include <linux/kernel.h>
248c2ecf20Sopenharmony_ci#include <linux/of.h>
258c2ecf20Sopenharmony_ci#include <linux/of_address.h>
268c2ecf20Sopenharmony_ci#include <linux/slab.h>
278c2ecf20Sopenharmony_ci#include <linux/sys_soc.h>
288c2ecf20Sopenharmony_ci#include "common.h"
298c2ecf20Sopenharmony_ci#include "mvebu-soc-id.h"
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci#define PCIE_DEV_ID_OFF		0x0
328c2ecf20Sopenharmony_ci#define PCIE_DEV_REV_OFF	0x8
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci#define SOC_ID_MASK	    0xFFFF0000
358c2ecf20Sopenharmony_ci#define SOC_REV_MASK	    0xFF
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_cistatic u32 soc_dev_id;
388c2ecf20Sopenharmony_cistatic u32 soc_rev;
398c2ecf20Sopenharmony_cistatic bool is_id_valid;
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_cistatic const struct of_device_id mvebu_pcie_of_match_table[] = {
428c2ecf20Sopenharmony_ci	{ .compatible = "marvell,armada-xp-pcie", },
438c2ecf20Sopenharmony_ci	{ .compatible = "marvell,armada-370-pcie", },
448c2ecf20Sopenharmony_ci	{ .compatible = "marvell,kirkwood-pcie" },
458c2ecf20Sopenharmony_ci	{},
468c2ecf20Sopenharmony_ci};
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ciint mvebu_get_soc_id(u32 *dev, u32 *rev)
498c2ecf20Sopenharmony_ci{
508c2ecf20Sopenharmony_ci	if (is_id_valid) {
518c2ecf20Sopenharmony_ci		*dev = soc_dev_id;
528c2ecf20Sopenharmony_ci		*rev = soc_rev;
538c2ecf20Sopenharmony_ci		return 0;
548c2ecf20Sopenharmony_ci	} else
558c2ecf20Sopenharmony_ci		return -ENODEV;
568c2ecf20Sopenharmony_ci}
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_cistatic int __init get_soc_id_by_pci(void)
598c2ecf20Sopenharmony_ci{
608c2ecf20Sopenharmony_ci	struct device_node *np;
618c2ecf20Sopenharmony_ci	int ret = 0;
628c2ecf20Sopenharmony_ci	void __iomem *pci_base;
638c2ecf20Sopenharmony_ci	struct clk *clk;
648c2ecf20Sopenharmony_ci	struct device_node *child;
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci	np = of_find_matching_node(NULL, mvebu_pcie_of_match_table);
678c2ecf20Sopenharmony_ci	if (!np)
688c2ecf20Sopenharmony_ci		return ret;
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	/*
718c2ecf20Sopenharmony_ci	 * ID and revision are available from any port, so we
728c2ecf20Sopenharmony_ci	 * just pick the first one
738c2ecf20Sopenharmony_ci	 */
748c2ecf20Sopenharmony_ci	child = of_get_next_child(np, NULL);
758c2ecf20Sopenharmony_ci	if (child == NULL) {
768c2ecf20Sopenharmony_ci		pr_err("cannot get pci node\n");
778c2ecf20Sopenharmony_ci		ret = -ENOMEM;
788c2ecf20Sopenharmony_ci		goto clk_err;
798c2ecf20Sopenharmony_ci	}
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci	clk = of_clk_get_by_name(child, NULL);
828c2ecf20Sopenharmony_ci	if (IS_ERR(clk)) {
838c2ecf20Sopenharmony_ci		pr_err("cannot get clock\n");
848c2ecf20Sopenharmony_ci		ret = -ENOMEM;
858c2ecf20Sopenharmony_ci		goto clk_err;
868c2ecf20Sopenharmony_ci	}
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	ret = clk_prepare_enable(clk);
898c2ecf20Sopenharmony_ci	if (ret) {
908c2ecf20Sopenharmony_ci		pr_err("cannot enable clock\n");
918c2ecf20Sopenharmony_ci		goto clk_err;
928c2ecf20Sopenharmony_ci	}
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	pci_base = of_iomap(child, 0);
958c2ecf20Sopenharmony_ci	if (pci_base == NULL) {
968c2ecf20Sopenharmony_ci		pr_err("cannot map registers\n");
978c2ecf20Sopenharmony_ci		ret = -ENOMEM;
988c2ecf20Sopenharmony_ci		goto res_ioremap;
998c2ecf20Sopenharmony_ci	}
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	/* SoC ID */
1028c2ecf20Sopenharmony_ci	soc_dev_id = readl(pci_base + PCIE_DEV_ID_OFF) >> 16;
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci	/* SoC revision */
1058c2ecf20Sopenharmony_ci	soc_rev = readl(pci_base + PCIE_DEV_REV_OFF) & SOC_REV_MASK;
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	is_id_valid = true;
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	pr_info("MVEBU SoC ID=0x%X, Rev=0x%X\n", soc_dev_id, soc_rev);
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	iounmap(pci_base);
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_cires_ioremap:
1148c2ecf20Sopenharmony_ci	/*
1158c2ecf20Sopenharmony_ci	 * If the PCIe unit is actually enabled and we have PCI
1168c2ecf20Sopenharmony_ci	 * support in the kernel, we intentionally do not release the
1178c2ecf20Sopenharmony_ci	 * reference to the clock. We want to keep it running since
1188c2ecf20Sopenharmony_ci	 * the bootloader does some PCIe link configuration that the
1198c2ecf20Sopenharmony_ci	 * kernel is for now unable to do, and gating the clock would
1208c2ecf20Sopenharmony_ci	 * make us loose this precious configuration.
1218c2ecf20Sopenharmony_ci	 */
1228c2ecf20Sopenharmony_ci	if (!of_device_is_available(child) || !IS_ENABLED(CONFIG_PCI_MVEBU)) {
1238c2ecf20Sopenharmony_ci		clk_disable_unprepare(clk);
1248c2ecf20Sopenharmony_ci		clk_put(clk);
1258c2ecf20Sopenharmony_ci	}
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ciclk_err:
1288c2ecf20Sopenharmony_ci	of_node_put(child);
1298c2ecf20Sopenharmony_ci	of_node_put(np);
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	return ret;
1328c2ecf20Sopenharmony_ci}
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_cistatic int __init mvebu_soc_id_init(void)
1358c2ecf20Sopenharmony_ci{
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	/*
1388c2ecf20Sopenharmony_ci	 * First try to get the ID and the revision by the system
1398c2ecf20Sopenharmony_ci	 * register and use PCI registers only if it is not possible
1408c2ecf20Sopenharmony_ci	 */
1418c2ecf20Sopenharmony_ci	if (!mvebu_system_controller_get_soc_id(&soc_dev_id, &soc_rev)) {
1428c2ecf20Sopenharmony_ci		is_id_valid = true;
1438c2ecf20Sopenharmony_ci		pr_info("MVEBU SoC ID=0x%X, Rev=0x%X\n", soc_dev_id, soc_rev);
1448c2ecf20Sopenharmony_ci		return 0;
1458c2ecf20Sopenharmony_ci	}
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	return get_soc_id_by_pci();
1488c2ecf20Sopenharmony_ci}
1498c2ecf20Sopenharmony_ciearly_initcall(mvebu_soc_id_init);
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_cistatic int __init mvebu_soc_device(void)
1528c2ecf20Sopenharmony_ci{
1538c2ecf20Sopenharmony_ci	struct soc_device_attribute *soc_dev_attr;
1548c2ecf20Sopenharmony_ci	struct soc_device *soc_dev;
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci	/* Also protects against running on non-mvebu systems */
1578c2ecf20Sopenharmony_ci	if (!is_id_valid)
1588c2ecf20Sopenharmony_ci		return 0;
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci	soc_dev_attr = kzalloc(sizeof(*soc_dev_attr), GFP_KERNEL);
1618c2ecf20Sopenharmony_ci	if (!soc_dev_attr)
1628c2ecf20Sopenharmony_ci		return -ENOMEM;
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci	soc_dev_attr->family = kasprintf(GFP_KERNEL, "Marvell");
1658c2ecf20Sopenharmony_ci	soc_dev_attr->revision = kasprintf(GFP_KERNEL, "%X", soc_rev);
1668c2ecf20Sopenharmony_ci	soc_dev_attr->soc_id = kasprintf(GFP_KERNEL, "%X", soc_dev_id);
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	soc_dev = soc_device_register(soc_dev_attr);
1698c2ecf20Sopenharmony_ci	if (IS_ERR(soc_dev)) {
1708c2ecf20Sopenharmony_ci		kfree(soc_dev_attr->family);
1718c2ecf20Sopenharmony_ci		kfree(soc_dev_attr->revision);
1728c2ecf20Sopenharmony_ci		kfree(soc_dev_attr->soc_id);
1738c2ecf20Sopenharmony_ci		kfree(soc_dev_attr);
1748c2ecf20Sopenharmony_ci	}
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ci	return 0;
1778c2ecf20Sopenharmony_ci}
1788c2ecf20Sopenharmony_cipostcore_initcall(mvebu_soc_device);
179