18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Authors:
68c2ecf20Sopenharmony_ci *   Serge Semin <Sergey.Semin@baikalelectronics.ru>
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci * Baikal-T1 AXI-bus driver
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#include <linux/kernel.h>
128c2ecf20Sopenharmony_ci#include <linux/module.h>
138c2ecf20Sopenharmony_ci#include <linux/types.h>
148c2ecf20Sopenharmony_ci#include <linux/bitfield.h>
158c2ecf20Sopenharmony_ci#include <linux/device.h>
168c2ecf20Sopenharmony_ci#include <linux/atomic.h>
178c2ecf20Sopenharmony_ci#include <linux/regmap.h>
188c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
198c2ecf20Sopenharmony_ci#include <linux/mfd/syscon.h>
208c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
218c2ecf20Sopenharmony_ci#include <linux/io.h>
228c2ecf20Sopenharmony_ci#include <linux/nmi.h>
238c2ecf20Sopenharmony_ci#include <linux/of.h>
248c2ecf20Sopenharmony_ci#include <linux/clk.h>
258c2ecf20Sopenharmony_ci#include <linux/reset.h>
268c2ecf20Sopenharmony_ci#include <linux/sysfs.h>
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ci#define BT1_AXI_WERRL			0x110
298c2ecf20Sopenharmony_ci#define BT1_AXI_WERRH			0x114
308c2ecf20Sopenharmony_ci#define BT1_AXI_WERRH_TYPE		BIT(23)
318c2ecf20Sopenharmony_ci#define BT1_AXI_WERRH_ADDR_FLD		24
328c2ecf20Sopenharmony_ci#define BT1_AXI_WERRH_ADDR_MASK		GENMASK(31, BT1_AXI_WERRH_ADDR_FLD)
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci/*
358c2ecf20Sopenharmony_ci * struct bt1_axi - Baikal-T1 AXI-bus private data
368c2ecf20Sopenharmony_ci * @dev: Pointer to the device structure.
378c2ecf20Sopenharmony_ci * @qos_regs: AXI Interconnect QoS tuning registers.
388c2ecf20Sopenharmony_ci * @sys_regs: Baikal-T1 System Controller registers map.
398c2ecf20Sopenharmony_ci * @irq: Errors IRQ number.
408c2ecf20Sopenharmony_ci * @aclk: AXI reference clock.
418c2ecf20Sopenharmony_ci * @arst: AXI Interconnect reset line.
428c2ecf20Sopenharmony_ci * @count: Number of errors detected.
438c2ecf20Sopenharmony_ci */
448c2ecf20Sopenharmony_cistruct bt1_axi {
458c2ecf20Sopenharmony_ci	struct device *dev;
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci	void __iomem *qos_regs;
488c2ecf20Sopenharmony_ci	struct regmap *sys_regs;
498c2ecf20Sopenharmony_ci	int irq;
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci	struct clk *aclk;
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci	struct reset_control *arst;
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci	atomic_t count;
568c2ecf20Sopenharmony_ci};
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_cistatic irqreturn_t bt1_axi_isr(int irq, void *data)
598c2ecf20Sopenharmony_ci{
608c2ecf20Sopenharmony_ci	struct bt1_axi *axi = data;
618c2ecf20Sopenharmony_ci	u32 low = 0, high = 0;
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci	regmap_read(axi->sys_regs, BT1_AXI_WERRL, &low);
648c2ecf20Sopenharmony_ci	regmap_read(axi->sys_regs, BT1_AXI_WERRH, &high);
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci	dev_crit_ratelimited(axi->dev,
678c2ecf20Sopenharmony_ci		"AXI-bus fault %d: %s at 0x%x%08x\n",
688c2ecf20Sopenharmony_ci		atomic_inc_return(&axi->count),
698c2ecf20Sopenharmony_ci		high & BT1_AXI_WERRH_TYPE ? "no slave" : "slave protocol error",
708c2ecf20Sopenharmony_ci		high, low);
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	/*
738c2ecf20Sopenharmony_ci	 * Print backtrace on each CPU. This might be pointless if the fault
748c2ecf20Sopenharmony_ci	 * has happened on the same CPU as the IRQ handler is executed or
758c2ecf20Sopenharmony_ci	 * the other core proceeded further execution despite the error.
768c2ecf20Sopenharmony_ci	 * But if it's not, by looking at the trace we would get straight to
778c2ecf20Sopenharmony_ci	 * the cause of the problem.
788c2ecf20Sopenharmony_ci	 */
798c2ecf20Sopenharmony_ci	trigger_all_cpu_backtrace();
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
828c2ecf20Sopenharmony_ci}
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_cistatic void bt1_axi_clear_data(void *data)
858c2ecf20Sopenharmony_ci{
868c2ecf20Sopenharmony_ci	struct bt1_axi *axi = data;
878c2ecf20Sopenharmony_ci	struct platform_device *pdev = to_platform_device(axi->dev);
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, NULL);
908c2ecf20Sopenharmony_ci}
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_cistatic struct bt1_axi *bt1_axi_create_data(struct platform_device *pdev)
938c2ecf20Sopenharmony_ci{
948c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
958c2ecf20Sopenharmony_ci	struct bt1_axi *axi;
968c2ecf20Sopenharmony_ci	int ret;
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	axi = devm_kzalloc(dev, sizeof(*axi), GFP_KERNEL);
998c2ecf20Sopenharmony_ci	if (!axi)
1008c2ecf20Sopenharmony_ci		return ERR_PTR(-ENOMEM);
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	ret = devm_add_action(dev, bt1_axi_clear_data, axi);
1038c2ecf20Sopenharmony_ci	if (ret) {
1048c2ecf20Sopenharmony_ci		dev_err(dev, "Can't add AXI EHB data clear action\n");
1058c2ecf20Sopenharmony_ci		return ERR_PTR(ret);
1068c2ecf20Sopenharmony_ci	}
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci	axi->dev = dev;
1098c2ecf20Sopenharmony_ci	atomic_set(&axi->count, 0);
1108c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, axi);
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	return axi;
1138c2ecf20Sopenharmony_ci}
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_cistatic int bt1_axi_request_regs(struct bt1_axi *axi)
1168c2ecf20Sopenharmony_ci{
1178c2ecf20Sopenharmony_ci	struct platform_device *pdev = to_platform_device(axi->dev);
1188c2ecf20Sopenharmony_ci	struct device *dev = axi->dev;
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	axi->sys_regs = syscon_regmap_lookup_by_phandle(dev->of_node, "syscon");
1218c2ecf20Sopenharmony_ci	if (IS_ERR(axi->sys_regs)) {
1228c2ecf20Sopenharmony_ci		dev_err(dev, "Couldn't find syscon registers\n");
1238c2ecf20Sopenharmony_ci		return PTR_ERR(axi->sys_regs);
1248c2ecf20Sopenharmony_ci	}
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	axi->qos_regs = devm_platform_ioremap_resource_byname(pdev, "qos");
1278c2ecf20Sopenharmony_ci	if (IS_ERR(axi->qos_regs))
1288c2ecf20Sopenharmony_ci		dev_err(dev, "Couldn't map AXI-bus QoS registers\n");
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	return PTR_ERR_OR_ZERO(axi->qos_regs);
1318c2ecf20Sopenharmony_ci}
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_cistatic int bt1_axi_request_rst(struct bt1_axi *axi)
1348c2ecf20Sopenharmony_ci{
1358c2ecf20Sopenharmony_ci	int ret;
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	axi->arst = devm_reset_control_get_optional_exclusive(axi->dev, "arst");
1388c2ecf20Sopenharmony_ci	if (IS_ERR(axi->arst)) {
1398c2ecf20Sopenharmony_ci		dev_warn(axi->dev, "Couldn't get reset control line\n");
1408c2ecf20Sopenharmony_ci		return PTR_ERR(axi->arst);
1418c2ecf20Sopenharmony_ci	}
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci	ret = reset_control_deassert(axi->arst);
1448c2ecf20Sopenharmony_ci	if (ret)
1458c2ecf20Sopenharmony_ci		dev_err(axi->dev, "Failed to deassert the reset line\n");
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	return ret;
1488c2ecf20Sopenharmony_ci}
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_cistatic void bt1_axi_disable_clk(void *data)
1518c2ecf20Sopenharmony_ci{
1528c2ecf20Sopenharmony_ci	struct bt1_axi *axi = data;
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	clk_disable_unprepare(axi->aclk);
1558c2ecf20Sopenharmony_ci}
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_cistatic int bt1_axi_request_clk(struct bt1_axi *axi)
1588c2ecf20Sopenharmony_ci{
1598c2ecf20Sopenharmony_ci	int ret;
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	axi->aclk = devm_clk_get(axi->dev, "aclk");
1628c2ecf20Sopenharmony_ci	if (IS_ERR(axi->aclk)) {
1638c2ecf20Sopenharmony_ci		dev_err(axi->dev, "Couldn't get AXI Interconnect clock\n");
1648c2ecf20Sopenharmony_ci		return PTR_ERR(axi->aclk);
1658c2ecf20Sopenharmony_ci	}
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	ret = clk_prepare_enable(axi->aclk);
1688c2ecf20Sopenharmony_ci	if (ret) {
1698c2ecf20Sopenharmony_ci		dev_err(axi->dev, "Couldn't enable the AXI clock\n");
1708c2ecf20Sopenharmony_ci		return ret;
1718c2ecf20Sopenharmony_ci	}
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci	ret = devm_add_action_or_reset(axi->dev, bt1_axi_disable_clk, axi);
1748c2ecf20Sopenharmony_ci	if (ret)
1758c2ecf20Sopenharmony_ci		dev_err(axi->dev, "Can't add AXI clock disable action\n");
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	return ret;
1788c2ecf20Sopenharmony_ci}
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_cistatic int bt1_axi_request_irq(struct bt1_axi *axi)
1818c2ecf20Sopenharmony_ci{
1828c2ecf20Sopenharmony_ci	struct platform_device *pdev = to_platform_device(axi->dev);
1838c2ecf20Sopenharmony_ci	int ret;
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci	axi->irq = platform_get_irq(pdev, 0);
1868c2ecf20Sopenharmony_ci	if (axi->irq < 0)
1878c2ecf20Sopenharmony_ci		return axi->irq;
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	ret = devm_request_irq(axi->dev, axi->irq, bt1_axi_isr, IRQF_SHARED,
1908c2ecf20Sopenharmony_ci			       "bt1-axi", axi);
1918c2ecf20Sopenharmony_ci	if (ret)
1928c2ecf20Sopenharmony_ci		dev_err(axi->dev, "Couldn't request AXI EHB IRQ\n");
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci	return ret;
1958c2ecf20Sopenharmony_ci}
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_cistatic ssize_t count_show(struct device *dev,
1988c2ecf20Sopenharmony_ci			  struct device_attribute *attr, char *buf)
1998c2ecf20Sopenharmony_ci{
2008c2ecf20Sopenharmony_ci	struct bt1_axi *axi = dev_get_drvdata(dev);
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_ci	return scnprintf(buf, PAGE_SIZE, "%d\n", atomic_read(&axi->count));
2038c2ecf20Sopenharmony_ci}
2048c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RO(count);
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_cistatic ssize_t inject_error_show(struct device *dev,
2078c2ecf20Sopenharmony_ci				 struct device_attribute *attr, char *buf)
2088c2ecf20Sopenharmony_ci{
2098c2ecf20Sopenharmony_ci	return scnprintf(buf, PAGE_SIZE, "Error injection: bus unaligned\n");
2108c2ecf20Sopenharmony_ci}
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_cistatic ssize_t inject_error_store(struct device *dev,
2138c2ecf20Sopenharmony_ci				  struct device_attribute *attr,
2148c2ecf20Sopenharmony_ci				  const char *data, size_t count)
2158c2ecf20Sopenharmony_ci{
2168c2ecf20Sopenharmony_ci	struct bt1_axi *axi = dev_get_drvdata(dev);
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ci	/*
2198c2ecf20Sopenharmony_ci	 * Performing unaligned read from the memory will cause the CM2 bus
2208c2ecf20Sopenharmony_ci	 * error while unaligned writing - the AXI bus write error handled
2218c2ecf20Sopenharmony_ci	 * by this driver.
2228c2ecf20Sopenharmony_ci	 */
2238c2ecf20Sopenharmony_ci	if (sysfs_streq(data, "bus"))
2248c2ecf20Sopenharmony_ci		readb(axi->qos_regs);
2258c2ecf20Sopenharmony_ci	else if (sysfs_streq(data, "unaligned"))
2268c2ecf20Sopenharmony_ci		writeb(0, axi->qos_regs);
2278c2ecf20Sopenharmony_ci	else
2288c2ecf20Sopenharmony_ci		return -EINVAL;
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ci	return count;
2318c2ecf20Sopenharmony_ci}
2328c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RW(inject_error);
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_cistatic struct attribute *bt1_axi_sysfs_attrs[] = {
2358c2ecf20Sopenharmony_ci	&dev_attr_count.attr,
2368c2ecf20Sopenharmony_ci	&dev_attr_inject_error.attr,
2378c2ecf20Sopenharmony_ci	NULL
2388c2ecf20Sopenharmony_ci};
2398c2ecf20Sopenharmony_ciATTRIBUTE_GROUPS(bt1_axi_sysfs);
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_cistatic void bt1_axi_remove_sysfs(void *data)
2428c2ecf20Sopenharmony_ci{
2438c2ecf20Sopenharmony_ci	struct bt1_axi *axi = data;
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_ci	device_remove_groups(axi->dev, bt1_axi_sysfs_groups);
2468c2ecf20Sopenharmony_ci}
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_cistatic int bt1_axi_init_sysfs(struct bt1_axi *axi)
2498c2ecf20Sopenharmony_ci{
2508c2ecf20Sopenharmony_ci	int ret;
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_ci	ret = device_add_groups(axi->dev, bt1_axi_sysfs_groups);
2538c2ecf20Sopenharmony_ci	if (ret) {
2548c2ecf20Sopenharmony_ci		dev_err(axi->dev, "Failed to add sysfs files group\n");
2558c2ecf20Sopenharmony_ci		return ret;
2568c2ecf20Sopenharmony_ci	}
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci	ret = devm_add_action_or_reset(axi->dev, bt1_axi_remove_sysfs, axi);
2598c2ecf20Sopenharmony_ci	if (ret)
2608c2ecf20Sopenharmony_ci		dev_err(axi->dev, "Can't add AXI EHB sysfs remove action\n");
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci	return ret;
2638c2ecf20Sopenharmony_ci}
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_cistatic int bt1_axi_probe(struct platform_device *pdev)
2668c2ecf20Sopenharmony_ci{
2678c2ecf20Sopenharmony_ci	struct bt1_axi *axi;
2688c2ecf20Sopenharmony_ci	int ret;
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_ci	axi = bt1_axi_create_data(pdev);
2718c2ecf20Sopenharmony_ci	if (IS_ERR(axi))
2728c2ecf20Sopenharmony_ci		return PTR_ERR(axi);
2738c2ecf20Sopenharmony_ci
2748c2ecf20Sopenharmony_ci	ret = bt1_axi_request_regs(axi);
2758c2ecf20Sopenharmony_ci	if (ret)
2768c2ecf20Sopenharmony_ci		return ret;
2778c2ecf20Sopenharmony_ci
2788c2ecf20Sopenharmony_ci	ret = bt1_axi_request_rst(axi);
2798c2ecf20Sopenharmony_ci	if (ret)
2808c2ecf20Sopenharmony_ci		return ret;
2818c2ecf20Sopenharmony_ci
2828c2ecf20Sopenharmony_ci	ret = bt1_axi_request_clk(axi);
2838c2ecf20Sopenharmony_ci	if (ret)
2848c2ecf20Sopenharmony_ci		return ret;
2858c2ecf20Sopenharmony_ci
2868c2ecf20Sopenharmony_ci	ret = bt1_axi_request_irq(axi);
2878c2ecf20Sopenharmony_ci	if (ret)
2888c2ecf20Sopenharmony_ci		return ret;
2898c2ecf20Sopenharmony_ci
2908c2ecf20Sopenharmony_ci	ret = bt1_axi_init_sysfs(axi);
2918c2ecf20Sopenharmony_ci	if (ret)
2928c2ecf20Sopenharmony_ci		return ret;
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_ci	return 0;
2958c2ecf20Sopenharmony_ci}
2968c2ecf20Sopenharmony_ci
2978c2ecf20Sopenharmony_cistatic const struct of_device_id bt1_axi_of_match[] = {
2988c2ecf20Sopenharmony_ci	{ .compatible = "baikal,bt1-axi" },
2998c2ecf20Sopenharmony_ci	{ }
3008c2ecf20Sopenharmony_ci};
3018c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, bt1_axi_of_match);
3028c2ecf20Sopenharmony_ci
3038c2ecf20Sopenharmony_cistatic struct platform_driver bt1_axi_driver = {
3048c2ecf20Sopenharmony_ci	.probe = bt1_axi_probe,
3058c2ecf20Sopenharmony_ci	.driver = {
3068c2ecf20Sopenharmony_ci		.name = "bt1-axi",
3078c2ecf20Sopenharmony_ci		.of_match_table = bt1_axi_of_match
3088c2ecf20Sopenharmony_ci	}
3098c2ecf20Sopenharmony_ci};
3108c2ecf20Sopenharmony_cimodule_platform_driver(bt1_axi_driver);
3118c2ecf20Sopenharmony_ci
3128c2ecf20Sopenharmony_ciMODULE_AUTHOR("Serge Semin <Sergey.Semin@baikalelectronics.ru>");
3138c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Baikal-T1 AXI-bus driver");
3148c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
315