// SPDX-License-Identifier: GPL-2.0 /* * Driver for Xilinx TMR Manager IP. * * Copyright (C) 2022 Advanced Micro Devices, Inc. * * Description: * This driver is developed for TMR Manager,The Triple Modular Redundancy(TMR) * Manager is responsible for handling the TMR subsystem state, including * fault detection and error recovery. The core is triplicated in each of * the sub-blocks in the TMR subsystem, and provides majority voting of * its internal state provides soft error detection, correction and * recovery. */ #include <asm/xilinx_mb_manager.h> #include <linux/module.h> #include <linux/of.h> #include <linux/platform_device.h> /* TMR Manager Register offsets */ #define XTMR_MANAGER_CR_OFFSET 0x0 #define XTMR_MANAGER_FFR_OFFSET 0x4 #define XTMR_MANAGER_CMR0_OFFSET 0x8 #define XTMR_MANAGER_CMR1_OFFSET 0xC #define XTMR_MANAGER_BDIR_OFFSET 0x10 #define XTMR_MANAGER_SEMIMR_OFFSET 0x1C /* Register Bitmasks/shifts */ #define XTMR_MANAGER_CR_MAGIC1_MASK GENMASK(7, 0) #define XTMR_MANAGER_CR_MAGIC2_MASK GENMASK(15, 8) #define XTMR_MANAGER_CR_RIR_MASK BIT(16) #define XTMR_MANAGER_FFR_LM12_MASK BIT(0) #define XTMR_MANAGER_FFR_LM13_MASK BIT(1) #define XTMR_MANAGER_FFR_LM23_MASK BIT(2) #define XTMR_MANAGER_CR_MAGIC2_SHIFT 4 #define XTMR_MANAGER_CR_RIR_SHIFT 16 #define XTMR_MANAGER_CR_BB_SHIFT 18 #define XTMR_MANAGER_MAGIC1_MAX_VAL 255 /** * struct xtmr_manager_dev - Driver data for TMR Manager * @regs: device physical base address * @cr_val: control register value * @magic1: Magic 1 hardware configuration value * @err_cnt: error statistics count * @phys_baseaddr: Physical base address */ struct xtmr_manager_dev { void __iomem *regs; u32 cr_val; u32 magic1; u32 err_cnt; resource_size_t phys_baseaddr; }; /* IO accessors */ static inline void xtmr_manager_write(struct xtmr_manager_dev *xtmr_manager, u32 addr, u32 value) { iowrite32(value, xtmr_manager->regs + addr); } static inline u32 xtmr_manager_read(struct xtmr_manager_dev *xtmr_manager, u32 addr) { return ioread32(xtmr_manager->regs + addr); } static void xmb_manager_reset_handler(struct xtmr_manager_dev *xtmr_manager) { /* Clear the FFR Register contents as a part of recovery process. */ xtmr_manager_write(xtmr_manager, XTMR_MANAGER_FFR_OFFSET, 0); } static void xmb_manager_update_errcnt(struct xtmr_manager_dev *xtmr_manager) { xtmr_manager->err_cnt++; } static ssize_t errcnt_show(struct device *dev, struct device_attribute *attr, char *buf) { struct xtmr_manager_dev *xtmr_manager = dev_get_drvdata(dev); return sysfs_emit(buf, "%x\n", xtmr_manager->err_cnt); } static DEVICE_ATTR_RO(errcnt); static ssize_t dis_block_break_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct xtmr_manager_dev *xtmr_manager = dev_get_drvdata(dev); int ret; long value; ret = kstrtoul(buf, 16, &value); if (ret) return ret; /* unblock the break signal*/ xtmr_manager->cr_val &= ~(1 << XTMR_MANAGER_CR_BB_SHIFT); xtmr_manager_write(xtmr_manager, XTMR_MANAGER_CR_OFFSET, xtmr_manager->cr_val); return size; } static DEVICE_ATTR_WO(dis_block_break); static struct attribute *xtmr_manager_dev_attrs[] = { &dev_attr_dis_block_break.attr, &dev_attr_errcnt.attr, NULL, }; ATTRIBUTE_GROUPS(xtmr_manager_dev); static void xtmr_manager_init(struct xtmr_manager_dev *xtmr_manager) { /* Clear the SEM interrupt mask register to disable the interrupt */ xtmr_manager_write(xtmr_manager, XTMR_MANAGER_SEMIMR_OFFSET, 0); /* Allow recovery reset by default */ xtmr_manager->cr_val = (1 << XTMR_MANAGER_CR_RIR_SHIFT) | xtmr_manager->magic1; xtmr_manager_write(xtmr_manager, XTMR_MANAGER_CR_OFFSET, xtmr_manager->cr_val); /* * Configure Break Delay Initialization Register to zero so that * break occurs immediately */ xtmr_manager_write(xtmr_manager, XTMR_MANAGER_BDIR_OFFSET, 0); /* * To come out of break handler need to block the break signal * in the tmr manager, update the xtmr_manager cr_val for the same */ xtmr_manager->cr_val |= (1 << XTMR_MANAGER_CR_BB_SHIFT); /* * When the break vector gets asserted because of error injection, * the break signal must be blocked before exiting from the * break handler, Below api updates the TMR manager address and * control register and error counter callback arguments, * which will be used by the break handler to block the * break and call the callback function. */ xmb_manager_register(xtmr_manager->phys_baseaddr, xtmr_manager->cr_val, (void *)xmb_manager_update_errcnt, xtmr_manager, (void *)xmb_manager_reset_handler); } /** * xtmr_manager_probe - Driver probe function * @pdev: Pointer to the platform_device structure * * This is the driver probe routine. It does all the memory * allocation for the device. * * Return: 0 on success and failure value on error */ static int xtmr_manager_probe(struct platform_device *pdev) { struct xtmr_manager_dev *xtmr_manager; struct resource *res; int err; xtmr_manager = devm_kzalloc(&pdev->dev, sizeof(*xtmr_manager), GFP_KERNEL); if (!xtmr_manager) return -ENOMEM; xtmr_manager->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); if (IS_ERR(xtmr_manager->regs)) return PTR_ERR(xtmr_manager->regs); xtmr_manager->phys_baseaddr = res->start; err = of_property_read_u32(pdev->dev.of_node, "xlnx,magic1", &xtmr_manager->magic1); if (err < 0) { dev_err(&pdev->dev, "unable to read xlnx,magic1 property"); return err; } if (xtmr_manager->magic1 > XTMR_MANAGER_MAGIC1_MAX_VAL) { dev_err(&pdev->dev, "invalid xlnx,magic1 property value"); return -EINVAL; } /* Initialize TMR Manager */ xtmr_manager_init(xtmr_manager); platform_set_drvdata(pdev, xtmr_manager); return 0; } static const struct of_device_id xtmr_manager_of_match[] = { { .compatible = "xlnx,tmr-manager-1.0", }, { /* end of table */ } }; MODULE_DEVICE_TABLE(of, xtmr_manager_of_match); static struct platform_driver xtmr_manager_driver = { .driver = { .name = "xilinx-tmr_manager", .of_match_table = xtmr_manager_of_match, .dev_groups = xtmr_manager_dev_groups, }, .probe = xtmr_manager_probe, }; module_platform_driver(xtmr_manager_driver); MODULE_AUTHOR("Advanced Micro Devices, Inc"); MODULE_DESCRIPTION("Xilinx TMR Manager Driver"); MODULE_LICENSE("GPL");