|  | /* | 
|  | * 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); | 
|  | } |