162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Driver for Xilinx TMR Manager IP.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2022 Advanced Micro Devices, Inc.
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Description:
862306a36Sopenharmony_ci * This driver is developed for TMR Manager,The Triple Modular Redundancy(TMR)
962306a36Sopenharmony_ci * Manager is responsible for handling the TMR subsystem state, including
1062306a36Sopenharmony_ci * fault detection and error recovery. The core is triplicated in each of
1162306a36Sopenharmony_ci * the sub-blocks in the TMR subsystem, and provides majority voting of
1262306a36Sopenharmony_ci * its internal state provides soft error detection, correction and
1362306a36Sopenharmony_ci * recovery.
1462306a36Sopenharmony_ci */
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include <asm/xilinx_mb_manager.h>
1762306a36Sopenharmony_ci#include <linux/module.h>
1862306a36Sopenharmony_ci#include <linux/of.h>
1962306a36Sopenharmony_ci#include <linux/platform_device.h>
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci/* TMR Manager Register offsets */
2262306a36Sopenharmony_ci#define XTMR_MANAGER_CR_OFFSET		0x0
2362306a36Sopenharmony_ci#define XTMR_MANAGER_FFR_OFFSET		0x4
2462306a36Sopenharmony_ci#define XTMR_MANAGER_CMR0_OFFSET	0x8
2562306a36Sopenharmony_ci#define XTMR_MANAGER_CMR1_OFFSET	0xC
2662306a36Sopenharmony_ci#define XTMR_MANAGER_BDIR_OFFSET	0x10
2762306a36Sopenharmony_ci#define XTMR_MANAGER_SEMIMR_OFFSET	0x1C
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci/* Register Bitmasks/shifts */
3062306a36Sopenharmony_ci#define XTMR_MANAGER_CR_MAGIC1_MASK	GENMASK(7, 0)
3162306a36Sopenharmony_ci#define XTMR_MANAGER_CR_MAGIC2_MASK	GENMASK(15, 8)
3262306a36Sopenharmony_ci#define XTMR_MANAGER_CR_RIR_MASK	BIT(16)
3362306a36Sopenharmony_ci#define XTMR_MANAGER_FFR_LM12_MASK	BIT(0)
3462306a36Sopenharmony_ci#define XTMR_MANAGER_FFR_LM13_MASK	BIT(1)
3562306a36Sopenharmony_ci#define XTMR_MANAGER_FFR_LM23_MASK	BIT(2)
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci#define XTMR_MANAGER_CR_MAGIC2_SHIFT	4
3862306a36Sopenharmony_ci#define XTMR_MANAGER_CR_RIR_SHIFT	16
3962306a36Sopenharmony_ci#define XTMR_MANAGER_CR_BB_SHIFT	18
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci#define XTMR_MANAGER_MAGIC1_MAX_VAL	255
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci/**
4462306a36Sopenharmony_ci * struct xtmr_manager_dev - Driver data for TMR Manager
4562306a36Sopenharmony_ci * @regs: device physical base address
4662306a36Sopenharmony_ci * @cr_val: control register value
4762306a36Sopenharmony_ci * @magic1: Magic 1 hardware configuration value
4862306a36Sopenharmony_ci * @err_cnt: error statistics count
4962306a36Sopenharmony_ci * @phys_baseaddr: Physical base address
5062306a36Sopenharmony_ci */
5162306a36Sopenharmony_cistruct xtmr_manager_dev {
5262306a36Sopenharmony_ci	void __iomem *regs;
5362306a36Sopenharmony_ci	u32 cr_val;
5462306a36Sopenharmony_ci	u32 magic1;
5562306a36Sopenharmony_ci	u32 err_cnt;
5662306a36Sopenharmony_ci	resource_size_t phys_baseaddr;
5762306a36Sopenharmony_ci};
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci/* IO accessors */
6062306a36Sopenharmony_cistatic inline void xtmr_manager_write(struct xtmr_manager_dev *xtmr_manager,
6162306a36Sopenharmony_ci				      u32 addr, u32 value)
6262306a36Sopenharmony_ci{
6362306a36Sopenharmony_ci	iowrite32(value, xtmr_manager->regs + addr);
6462306a36Sopenharmony_ci}
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_cistatic inline u32 xtmr_manager_read(struct xtmr_manager_dev *xtmr_manager,
6762306a36Sopenharmony_ci				    u32 addr)
6862306a36Sopenharmony_ci{
6962306a36Sopenharmony_ci	return ioread32(xtmr_manager->regs + addr);
7062306a36Sopenharmony_ci}
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_cistatic void xmb_manager_reset_handler(struct xtmr_manager_dev *xtmr_manager)
7362306a36Sopenharmony_ci{
7462306a36Sopenharmony_ci	/* Clear the FFR Register contents as a part of recovery process. */
7562306a36Sopenharmony_ci	xtmr_manager_write(xtmr_manager, XTMR_MANAGER_FFR_OFFSET, 0);
7662306a36Sopenharmony_ci}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_cistatic void xmb_manager_update_errcnt(struct xtmr_manager_dev *xtmr_manager)
7962306a36Sopenharmony_ci{
8062306a36Sopenharmony_ci	xtmr_manager->err_cnt++;
8162306a36Sopenharmony_ci}
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_cistatic ssize_t errcnt_show(struct device *dev, struct device_attribute *attr,
8462306a36Sopenharmony_ci			   char *buf)
8562306a36Sopenharmony_ci{
8662306a36Sopenharmony_ci	struct xtmr_manager_dev *xtmr_manager = dev_get_drvdata(dev);
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	return sysfs_emit(buf, "%x\n", xtmr_manager->err_cnt);
8962306a36Sopenharmony_ci}
9062306a36Sopenharmony_cistatic DEVICE_ATTR_RO(errcnt);
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_cistatic ssize_t dis_block_break_store(struct device *dev,
9362306a36Sopenharmony_ci				     struct device_attribute *attr,
9462306a36Sopenharmony_ci				     const char *buf, size_t size)
9562306a36Sopenharmony_ci{
9662306a36Sopenharmony_ci	struct xtmr_manager_dev *xtmr_manager = dev_get_drvdata(dev);
9762306a36Sopenharmony_ci	int ret;
9862306a36Sopenharmony_ci	long value;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	ret = kstrtoul(buf, 16, &value);
10162306a36Sopenharmony_ci	if (ret)
10262306a36Sopenharmony_ci		return ret;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	/* unblock the break signal*/
10562306a36Sopenharmony_ci	xtmr_manager->cr_val &= ~(1 << XTMR_MANAGER_CR_BB_SHIFT);
10662306a36Sopenharmony_ci	xtmr_manager_write(xtmr_manager, XTMR_MANAGER_CR_OFFSET,
10762306a36Sopenharmony_ci			   xtmr_manager->cr_val);
10862306a36Sopenharmony_ci	return size;
10962306a36Sopenharmony_ci}
11062306a36Sopenharmony_cistatic DEVICE_ATTR_WO(dis_block_break);
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_cistatic struct attribute *xtmr_manager_dev_attrs[] = {
11362306a36Sopenharmony_ci	&dev_attr_dis_block_break.attr,
11462306a36Sopenharmony_ci	&dev_attr_errcnt.attr,
11562306a36Sopenharmony_ci	NULL,
11662306a36Sopenharmony_ci};
11762306a36Sopenharmony_ciATTRIBUTE_GROUPS(xtmr_manager_dev);
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_cistatic void xtmr_manager_init(struct xtmr_manager_dev *xtmr_manager)
12062306a36Sopenharmony_ci{
12162306a36Sopenharmony_ci	/* Clear the SEM interrupt mask register to disable the interrupt */
12262306a36Sopenharmony_ci	xtmr_manager_write(xtmr_manager, XTMR_MANAGER_SEMIMR_OFFSET, 0);
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	/* Allow recovery reset by default */
12562306a36Sopenharmony_ci	xtmr_manager->cr_val = (1 << XTMR_MANAGER_CR_RIR_SHIFT) |
12662306a36Sopenharmony_ci				xtmr_manager->magic1;
12762306a36Sopenharmony_ci	xtmr_manager_write(xtmr_manager, XTMR_MANAGER_CR_OFFSET,
12862306a36Sopenharmony_ci			   xtmr_manager->cr_val);
12962306a36Sopenharmony_ci	/*
13062306a36Sopenharmony_ci	 * Configure Break Delay Initialization Register to zero so that
13162306a36Sopenharmony_ci	 * break occurs immediately
13262306a36Sopenharmony_ci	 */
13362306a36Sopenharmony_ci	xtmr_manager_write(xtmr_manager, XTMR_MANAGER_BDIR_OFFSET, 0);
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	/*
13662306a36Sopenharmony_ci	 * To come out of break handler need to block the break signal
13762306a36Sopenharmony_ci	 * in the tmr manager, update the xtmr_manager cr_val for the same
13862306a36Sopenharmony_ci	 */
13962306a36Sopenharmony_ci	xtmr_manager->cr_val |= (1 << XTMR_MANAGER_CR_BB_SHIFT);
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	/*
14262306a36Sopenharmony_ci	 * When the break vector gets asserted because of error injection,
14362306a36Sopenharmony_ci	 * the break signal must be blocked before exiting from the
14462306a36Sopenharmony_ci	 * break handler, Below api updates the TMR manager address and
14562306a36Sopenharmony_ci	 * control register and error counter callback arguments,
14662306a36Sopenharmony_ci	 * which will be used by the break handler to block the
14762306a36Sopenharmony_ci	 * break and call the callback function.
14862306a36Sopenharmony_ci	 */
14962306a36Sopenharmony_ci	xmb_manager_register(xtmr_manager->phys_baseaddr, xtmr_manager->cr_val,
15062306a36Sopenharmony_ci			     (void *)xmb_manager_update_errcnt,
15162306a36Sopenharmony_ci			     xtmr_manager, (void *)xmb_manager_reset_handler);
15262306a36Sopenharmony_ci}
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci/**
15562306a36Sopenharmony_ci * xtmr_manager_probe - Driver probe function
15662306a36Sopenharmony_ci * @pdev: Pointer to the platform_device structure
15762306a36Sopenharmony_ci *
15862306a36Sopenharmony_ci * This is the driver probe routine. It does all the memory
15962306a36Sopenharmony_ci * allocation for the device.
16062306a36Sopenharmony_ci *
16162306a36Sopenharmony_ci * Return: 0 on success and failure value on error
16262306a36Sopenharmony_ci */
16362306a36Sopenharmony_cistatic int xtmr_manager_probe(struct platform_device *pdev)
16462306a36Sopenharmony_ci{
16562306a36Sopenharmony_ci	struct xtmr_manager_dev *xtmr_manager;
16662306a36Sopenharmony_ci	struct resource *res;
16762306a36Sopenharmony_ci	int err;
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	xtmr_manager = devm_kzalloc(&pdev->dev, sizeof(*xtmr_manager),
17062306a36Sopenharmony_ci				    GFP_KERNEL);
17162306a36Sopenharmony_ci	if (!xtmr_manager)
17262306a36Sopenharmony_ci		return -ENOMEM;
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	xtmr_manager->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
17562306a36Sopenharmony_ci	if (IS_ERR(xtmr_manager->regs))
17662306a36Sopenharmony_ci		return PTR_ERR(xtmr_manager->regs);
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	xtmr_manager->phys_baseaddr = res->start;
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	err = of_property_read_u32(pdev->dev.of_node, "xlnx,magic1",
18162306a36Sopenharmony_ci				   &xtmr_manager->magic1);
18262306a36Sopenharmony_ci	if (err < 0) {
18362306a36Sopenharmony_ci		dev_err(&pdev->dev, "unable to read xlnx,magic1 property");
18462306a36Sopenharmony_ci		return err;
18562306a36Sopenharmony_ci	}
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	if (xtmr_manager->magic1 > XTMR_MANAGER_MAGIC1_MAX_VAL) {
18862306a36Sopenharmony_ci		dev_err(&pdev->dev, "invalid xlnx,magic1 property value");
18962306a36Sopenharmony_ci		return -EINVAL;
19062306a36Sopenharmony_ci	}
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	/* Initialize TMR Manager */
19362306a36Sopenharmony_ci	xtmr_manager_init(xtmr_manager);
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	platform_set_drvdata(pdev, xtmr_manager);
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	return 0;
19862306a36Sopenharmony_ci}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_cistatic const struct of_device_id xtmr_manager_of_match[] = {
20162306a36Sopenharmony_ci	{
20262306a36Sopenharmony_ci		.compatible = "xlnx,tmr-manager-1.0",
20362306a36Sopenharmony_ci	},
20462306a36Sopenharmony_ci	{ /* end of table */ }
20562306a36Sopenharmony_ci};
20662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, xtmr_manager_of_match);
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_cistatic struct platform_driver xtmr_manager_driver = {
20962306a36Sopenharmony_ci	.driver = {
21062306a36Sopenharmony_ci		.name = "xilinx-tmr_manager",
21162306a36Sopenharmony_ci		.of_match_table = xtmr_manager_of_match,
21262306a36Sopenharmony_ci		.dev_groups = xtmr_manager_dev_groups,
21362306a36Sopenharmony_ci	},
21462306a36Sopenharmony_ci	.probe = xtmr_manager_probe,
21562306a36Sopenharmony_ci};
21662306a36Sopenharmony_cimodule_platform_driver(xtmr_manager_driver);
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ciMODULE_AUTHOR("Advanced Micro Devices, Inc");
21962306a36Sopenharmony_ciMODULE_DESCRIPTION("Xilinx TMR Manager Driver");
22062306a36Sopenharmony_ciMODULE_LICENSE("GPL");
221