| /* | 
 |  * Copyright Altera Corporation (C) 2016. All rights reserved. | 
 |  * | 
 |  * This program is free software; you can redistribute it and/or modify it | 
 |  * under the terms and conditions of the GNU General Public License, | 
 |  * version 2, as published by the Free Software Foundation. | 
 |  * | 
 |  * This program is distributed in the hope 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. | 
 |  * | 
 |  * You should have received a copy of the GNU General Public License along with | 
 |  * this program.  If not, see <http://www.gnu.org/licenses/>. | 
 |  */ | 
 | #include <linux/delay.h> | 
 | #include <linux/io.h> | 
 | #include <linux/genalloc.h> | 
 | #include <linux/module.h> | 
 | #include <linux/of_address.h> | 
 | #include <linux/of_platform.h> | 
 |  | 
 | #include "core.h" | 
 |  | 
 | #define ALTR_OCRAM_CLEAR_ECC          0x00000018 | 
 | #define ALTR_OCRAM_ECC_EN             0x00000019 | 
 |  | 
 | void socfpga_init_ocram_ecc(void) | 
 | { | 
 | 	struct device_node *np; | 
 | 	void __iomem *mapped_ocr_edac_addr; | 
 |  | 
 | 	/* Find the OCRAM EDAC device tree node */ | 
 | 	np = of_find_compatible_node(NULL, NULL, "altr,socfpga-ocram-ecc"); | 
 | 	if (!np) { | 
 | 		pr_err("Unable to find socfpga-ocram-ecc\n"); | 
 | 		return; | 
 | 	} | 
 |  | 
 | 	mapped_ocr_edac_addr = of_iomap(np, 0); | 
 | 	of_node_put(np); | 
 | 	if (!mapped_ocr_edac_addr) { | 
 | 		pr_err("Unable to map OCRAM ecc regs.\n"); | 
 | 		return; | 
 | 	} | 
 |  | 
 | 	/* Clear any pending OCRAM ECC interrupts, then enable ECC */ | 
 | 	writel(ALTR_OCRAM_CLEAR_ECC, mapped_ocr_edac_addr); | 
 | 	writel(ALTR_OCRAM_ECC_EN, mapped_ocr_edac_addr); | 
 |  | 
 | 	iounmap(mapped_ocr_edac_addr); | 
 | } | 
 |  | 
 | /* Arria10 OCRAM Section */ | 
 | #define ALTR_A10_ECC_CTRL_OFST          0x08 | 
 | #define ALTR_A10_OCRAM_ECC_EN_CTL       (BIT(1) | BIT(0)) | 
 | #define ALTR_A10_ECC_INITA              BIT(16) | 
 |  | 
 | #define ALTR_A10_ECC_INITSTAT_OFST      0x0C | 
 | #define ALTR_A10_ECC_INITCOMPLETEA      BIT(0) | 
 | #define ALTR_A10_ECC_INITCOMPLETEB      BIT(8) | 
 |  | 
 | #define ALTR_A10_ECC_ERRINTEN_OFST      0x10 | 
 | #define ALTR_A10_ECC_SERRINTEN          BIT(0) | 
 |  | 
 | #define ALTR_A10_ECC_INTSTAT_OFST       0x20 | 
 | #define ALTR_A10_ECC_SERRPENA           BIT(0) | 
 | #define ALTR_A10_ECC_DERRPENA           BIT(8) | 
 | #define ALTR_A10_ECC_ERRPENA_MASK       (ALTR_A10_ECC_SERRPENA | \ | 
 | 					 ALTR_A10_ECC_DERRPENA) | 
 | /* ECC Manager Defines */ | 
 | #define A10_SYSMGR_ECC_INTMASK_SET_OFST   0x94 | 
 | #define A10_SYSMGR_ECC_INTMASK_CLR_OFST   0x98 | 
 | #define A10_SYSMGR_ECC_INTMASK_OCRAM      BIT(1) | 
 |  | 
 | #define ALTR_A10_ECC_INIT_WATCHDOG_10US   10000 | 
 |  | 
 | static inline void ecc_set_bits(u32 bit_mask, void __iomem *ioaddr) | 
 | { | 
 | 	u32 value = readl(ioaddr); | 
 |  | 
 | 	value |= bit_mask; | 
 | 	writel(value, ioaddr); | 
 | } | 
 |  | 
 | static inline void ecc_clear_bits(u32 bit_mask, void __iomem *ioaddr) | 
 | { | 
 | 	u32 value = readl(ioaddr); | 
 |  | 
 | 	value &= ~bit_mask; | 
 | 	writel(value, ioaddr); | 
 | } | 
 |  | 
 | static inline int ecc_test_bits(u32 bit_mask, void __iomem *ioaddr) | 
 | { | 
 | 	u32 value = readl(ioaddr); | 
 |  | 
 | 	return (value & bit_mask) ? 1 : 0; | 
 | } | 
 |  | 
 | /* | 
 |  * This function uses the memory initialization block in the Arria10 ECC | 
 |  * controller to initialize/clear the entire memory data and ECC data. | 
 |  */ | 
 | static int altr_init_memory_port(void __iomem *ioaddr) | 
 | { | 
 | 	int limit = ALTR_A10_ECC_INIT_WATCHDOG_10US; | 
 |  | 
 | 	ecc_set_bits(ALTR_A10_ECC_INITA, (ioaddr + ALTR_A10_ECC_CTRL_OFST)); | 
 | 	while (limit--) { | 
 | 		if (ecc_test_bits(ALTR_A10_ECC_INITCOMPLETEA, | 
 | 				  (ioaddr + ALTR_A10_ECC_INITSTAT_OFST))) | 
 | 			break; | 
 | 		udelay(1); | 
 | 	} | 
 | 	if (limit < 0) | 
 | 		return -EBUSY; | 
 |  | 
 | 	/* Clear any pending ECC interrupts */ | 
 | 	writel(ALTR_A10_ECC_ERRPENA_MASK, | 
 | 	       (ioaddr + ALTR_A10_ECC_INTSTAT_OFST)); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | void socfpga_init_arria10_ocram_ecc(void) | 
 | { | 
 | 	struct device_node *np; | 
 | 	int ret = 0; | 
 | 	void __iomem *ecc_block_base; | 
 |  | 
 | 	if (!sys_manager_base_addr) { | 
 | 		pr_err("SOCFPGA: sys-mgr is not initialized\n"); | 
 | 		return; | 
 | 	} | 
 |  | 
 | 	/* Find the OCRAM EDAC device tree node */ | 
 | 	np = of_find_compatible_node(NULL, NULL, "altr,socfpga-a10-ocram-ecc"); | 
 | 	if (!np) { | 
 | 		pr_err("Unable to find socfpga-a10-ocram-ecc\n"); | 
 | 		return; | 
 | 	} | 
 |  | 
 | 	/* Map the ECC Block */ | 
 | 	ecc_block_base = of_iomap(np, 0); | 
 | 	of_node_put(np); | 
 | 	if (!ecc_block_base) { | 
 | 		pr_err("Unable to map OCRAM ECC block\n"); | 
 | 		return; | 
 | 	} | 
 |  | 
 | 	/* Disable ECC */ | 
 | 	writel(ALTR_A10_OCRAM_ECC_EN_CTL, | 
 | 	       sys_manager_base_addr + A10_SYSMGR_ECC_INTMASK_SET_OFST); | 
 | 	ecc_clear_bits(ALTR_A10_ECC_SERRINTEN, | 
 | 		       (ecc_block_base + ALTR_A10_ECC_ERRINTEN_OFST)); | 
 | 	ecc_clear_bits(ALTR_A10_OCRAM_ECC_EN_CTL, | 
 | 		       (ecc_block_base + ALTR_A10_ECC_CTRL_OFST)); | 
 |  | 
 | 	/* Ensure all writes complete */ | 
 | 	wmb(); | 
 |  | 
 | 	/* Use HW initialization block to initialize memory for ECC */ | 
 | 	ret = altr_init_memory_port(ecc_block_base); | 
 | 	if (ret) { | 
 | 		pr_err("ECC: cannot init OCRAM PORTA memory\n"); | 
 | 		goto exit; | 
 | 	} | 
 |  | 
 | 	/* Enable ECC */ | 
 | 	ecc_set_bits(ALTR_A10_OCRAM_ECC_EN_CTL, | 
 | 		     (ecc_block_base + ALTR_A10_ECC_CTRL_OFST)); | 
 | 	ecc_set_bits(ALTR_A10_ECC_SERRINTEN, | 
 | 		     (ecc_block_base + ALTR_A10_ECC_ERRINTEN_OFST)); | 
 | 	writel(ALTR_A10_OCRAM_ECC_EN_CTL, | 
 | 	       sys_manager_base_addr + A10_SYSMGR_ECC_INTMASK_CLR_OFST); | 
 |  | 
 | 	/* Ensure all writes complete */ | 
 | 	wmb(); | 
 | exit: | 
 | 	iounmap(ecc_block_base); | 
 | } |