diff options
author | Thor Thayer <tthayer@opensource.altera.com> | 2016-03-21 12:01:44 -0400 |
---|---|---|
committer | Borislav Petkov <bp@suse.de> | 2016-03-29 04:34:06 -0400 |
commit | 588cb03ea208b303e6dee7e916f329043fd0fc26 (patch) | |
tree | acc1e591b446c8bee8016c33f6b7fbf8bf277b8c | |
parent | 8b39ab7290d571b91867b15c02a59edf0a5b00bb (diff) |
EDAC, altera: Add Arria10 L2 Cache ECC handling
Add a private data structure for Arria10 L2 cache ECC and the probe
function for it.
The Arria10 ECC device IRQs are in a shared register so the ECC Manager
parent/child relationship requires a different probe function.
Signed-off-by: Thor Thayer <tthayer@opensource.altera.com>
Cc: devicetree@vger.kernel.org
Cc: dinguyen@opensource.altera.com
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux@arm.linux.org.uk
Cc: linux-edac <linux-edac@vger.kernel.org>
Link: http://lkml.kernel.org/r/1458576106-24505-8-git-send-email-tthayer@opensource.altera.com
Signed-off-by: Borislav Petkov <bp@suse.de>
-rw-r--r-- | drivers/edac/altera_edac.c | 231 | ||||
-rw-r--r-- | drivers/edac/altera_edac.h | 42 |
2 files changed, 273 insertions, 0 deletions
diff --git a/drivers/edac/altera_edac.c b/drivers/edac/altera_edac.c index 502bf1fcf9e5..0afdc582766e 100644 --- a/drivers/edac/altera_edac.c +++ b/drivers/edac/altera_edac.c | |||
@@ -24,6 +24,7 @@ | |||
24 | #include <linux/interrupt.h> | 24 | #include <linux/interrupt.h> |
25 | #include <linux/kernel.h> | 25 | #include <linux/kernel.h> |
26 | #include <linux/mfd/syscon.h> | 26 | #include <linux/mfd/syscon.h> |
27 | #include <linux/of_address.h> | ||
27 | #include <linux/of_platform.h> | 28 | #include <linux/of_platform.h> |
28 | #include <linux/platform_device.h> | 29 | #include <linux/platform_device.h> |
29 | #include <linux/regmap.h> | 30 | #include <linux/regmap.h> |
@@ -549,6 +550,7 @@ module_platform_driver(altr_edac_driver); | |||
549 | 550 | ||
550 | const struct edac_device_prv_data ocramecc_data; | 551 | const struct edac_device_prv_data ocramecc_data; |
551 | const struct edac_device_prv_data l2ecc_data; | 552 | const struct edac_device_prv_data l2ecc_data; |
553 | const struct edac_device_prv_data a10_l2ecc_data; | ||
552 | 554 | ||
553 | static irqreturn_t altr_edac_device_handler(int irq, void *dev_id) | 555 | static irqreturn_t altr_edac_device_handler(int irq, void *dev_id) |
554 | { | 556 | { |
@@ -673,6 +675,8 @@ static void altr_create_edacdev_dbgfs(struct edac_device_ctl_info *edac_dci, | |||
673 | static const struct of_device_id altr_edac_device_of_match[] = { | 675 | static const struct of_device_id altr_edac_device_of_match[] = { |
674 | #ifdef CONFIG_EDAC_ALTERA_L2C | 676 | #ifdef CONFIG_EDAC_ALTERA_L2C |
675 | { .compatible = "altr,socfpga-l2-ecc", .data = (void *)&l2ecc_data }, | 677 | { .compatible = "altr,socfpga-l2-ecc", .data = (void *)&l2ecc_data }, |
678 | { .compatible = "altr,socfpga-a10-l2-ecc", | ||
679 | .data = (void *)&a10_l2ecc_data }, | ||
676 | #endif | 680 | #endif |
677 | #ifdef CONFIG_EDAC_ALTERA_OCRAM | 681 | #ifdef CONFIG_EDAC_ALTERA_OCRAM |
678 | { .compatible = "altr,socfpga-ocram-ecc", | 682 | { .compatible = "altr,socfpga-ocram-ecc", |
@@ -941,6 +945,24 @@ static int altr_l2_check_deps(struct altr_edac_device_dev *device) | |||
941 | return -ENODEV; | 945 | return -ENODEV; |
942 | } | 946 | } |
943 | 947 | ||
948 | static irqreturn_t altr_edac_a10_l2_irq(struct altr_edac_device_dev *dci, | ||
949 | bool sberr) | ||
950 | { | ||
951 | if (sberr) { | ||
952 | regmap_write(dci->edac->ecc_mgr_map, | ||
953 | A10_SYSGMR_MPU_CLEAR_L2_ECC_OFST, | ||
954 | A10_SYSGMR_MPU_CLEAR_L2_ECC_SB); | ||
955 | edac_device_handle_ce(dci->edac_dev, 0, 0, dci->edac_dev_name); | ||
956 | } else { | ||
957 | regmap_write(dci->edac->ecc_mgr_map, | ||
958 | A10_SYSGMR_MPU_CLEAR_L2_ECC_OFST, | ||
959 | A10_SYSGMR_MPU_CLEAR_L2_ECC_MB); | ||
960 | edac_device_handle_ue(dci->edac_dev, 0, 0, dci->edac_dev_name); | ||
961 | panic("\nEDAC:ECC_DEVICE[Uncorrectable errors]\n"); | ||
962 | } | ||
963 | return IRQ_HANDLED; | ||
964 | } | ||
965 | |||
944 | const struct edac_device_prv_data l2ecc_data = { | 966 | const struct edac_device_prv_data l2ecc_data = { |
945 | .setup = altr_l2_check_deps, | 967 | .setup = altr_l2_check_deps, |
946 | .ce_clear_mask = 0, | 968 | .ce_clear_mask = 0, |
@@ -955,8 +977,217 @@ const struct edac_device_prv_data l2ecc_data = { | |||
955 | .trig_alloc_sz = ALTR_TRIG_L2C_BYTE_SIZE, | 977 | .trig_alloc_sz = ALTR_TRIG_L2C_BYTE_SIZE, |
956 | }; | 978 | }; |
957 | 979 | ||
980 | const struct edac_device_prv_data a10_l2ecc_data = { | ||
981 | .setup = altr_l2_check_deps, | ||
982 | .ce_clear_mask = ALTR_A10_L2_ECC_SERR_CLR, | ||
983 | .ue_clear_mask = ALTR_A10_L2_ECC_MERR_CLR, | ||
984 | .irq_status_mask = A10_SYSMGR_ECC_INTSTAT_L2, | ||
985 | .dbgfs_name = "altr_l2_trigger", | ||
986 | .alloc_mem = l2_alloc_mem, | ||
987 | .free_mem = l2_free_mem, | ||
988 | .ecc_enable_mask = ALTR_A10_L2_ECC_EN_CTL, | ||
989 | .ce_set_mask = ALTR_A10_L2_ECC_CE_INJ_MASK, | ||
990 | .ue_set_mask = ALTR_A10_L2_ECC_UE_INJ_MASK, | ||
991 | .set_err_ofst = ALTR_A10_L2_ECC_INJ_OFST, | ||
992 | .ecc_irq_handler = altr_edac_a10_l2_irq, | ||
993 | .trig_alloc_sz = ALTR_TRIG_L2C_BYTE_SIZE, | ||
994 | }; | ||
995 | |||
958 | #endif /* CONFIG_EDAC_ALTERA_L2C */ | 996 | #endif /* CONFIG_EDAC_ALTERA_L2C */ |
959 | 997 | ||
998 | /********************* Arria10 EDAC Device Functions *************************/ | ||
999 | |||
1000 | /* | ||
1001 | * The Arria10 EDAC Device Functions differ from the Cyclone5/Arria5 | ||
1002 | * because 2 IRQs are shared among the all ECC peripherals. The ECC | ||
1003 | * manager manages the IRQs and the children. | ||
1004 | * Based on xgene_edac.c peripheral code. | ||
1005 | */ | ||
1006 | |||
1007 | static irqreturn_t altr_edac_a10_irq_handler(int irq, void *dev_id) | ||
1008 | { | ||
1009 | irqreturn_t rc = IRQ_NONE; | ||
1010 | struct altr_arria10_edac *edac = dev_id; | ||
1011 | struct altr_edac_device_dev *dci; | ||
1012 | int irq_status; | ||
1013 | bool sberr = (irq == edac->sb_irq) ? 1 : 0; | ||
1014 | int sm_offset = sberr ? A10_SYSMGR_ECC_INTSTAT_SERR_OFST : | ||
1015 | A10_SYSMGR_ECC_INTSTAT_DERR_OFST; | ||
1016 | |||
1017 | regmap_read(edac->ecc_mgr_map, sm_offset, &irq_status); | ||
1018 | |||
1019 | if ((irq != edac->sb_irq) && (irq != edac->db_irq)) { | ||
1020 | WARN_ON(1); | ||
1021 | } else { | ||
1022 | list_for_each_entry(dci, &edac->a10_ecc_devices, next) { | ||
1023 | if (irq_status & dci->data->irq_status_mask) | ||
1024 | rc = dci->data->ecc_irq_handler(dci, sberr); | ||
1025 | } | ||
1026 | } | ||
1027 | |||
1028 | return rc; | ||
1029 | } | ||
1030 | |||
1031 | static int altr_edac_a10_device_add(struct altr_arria10_edac *edac, | ||
1032 | struct device_node *np) | ||
1033 | { | ||
1034 | struct edac_device_ctl_info *dci; | ||
1035 | struct altr_edac_device_dev *altdev; | ||
1036 | char *ecc_name = (char *)np->name; | ||
1037 | struct resource res; | ||
1038 | int edac_idx; | ||
1039 | int rc = 0; | ||
1040 | const struct edac_device_prv_data *prv; | ||
1041 | /* Get matching node and check for valid result */ | ||
1042 | const struct of_device_id *pdev_id = | ||
1043 | of_match_node(altr_edac_device_of_match, np); | ||
1044 | if (IS_ERR_OR_NULL(pdev_id)) | ||
1045 | return -ENODEV; | ||
1046 | |||
1047 | /* Get driver specific data for this EDAC device */ | ||
1048 | prv = pdev_id->data; | ||
1049 | if (IS_ERR_OR_NULL(prv)) | ||
1050 | return -ENODEV; | ||
1051 | |||
1052 | if (!devres_open_group(edac->dev, altr_edac_a10_device_add, GFP_KERNEL)) | ||
1053 | return -ENOMEM; | ||
1054 | |||
1055 | rc = of_address_to_resource(np, 0, &res); | ||
1056 | if (rc < 0) { | ||
1057 | edac_printk(KERN_ERR, EDAC_DEVICE, | ||
1058 | "%s: no resource address\n", ecc_name); | ||
1059 | goto err_release_group; | ||
1060 | } | ||
1061 | |||
1062 | edac_idx = edac_device_alloc_index(); | ||
1063 | dci = edac_device_alloc_ctl_info(sizeof(*altdev), ecc_name, | ||
1064 | 1, ecc_name, 1, 0, NULL, 0, | ||
1065 | edac_idx); | ||
1066 | |||
1067 | if (!dci) { | ||
1068 | edac_printk(KERN_ERR, EDAC_DEVICE, | ||
1069 | "%s: Unable to allocate EDAC device\n", ecc_name); | ||
1070 | rc = -ENOMEM; | ||
1071 | goto err_release_group; | ||
1072 | } | ||
1073 | |||
1074 | altdev = dci->pvt_info; | ||
1075 | dci->dev = edac->dev; | ||
1076 | altdev->edac_dev_name = ecc_name; | ||
1077 | altdev->edac_idx = edac_idx; | ||
1078 | altdev->edac = edac; | ||
1079 | altdev->edac_dev = dci; | ||
1080 | altdev->data = prv; | ||
1081 | altdev->ddev = *edac->dev; | ||
1082 | dci->dev = &altdev->ddev; | ||
1083 | dci->ctl_name = "Altera ECC Manager"; | ||
1084 | dci->mod_name = ecc_name; | ||
1085 | dci->dev_name = ecc_name; | ||
1086 | |||
1087 | altdev->base = devm_ioremap_resource(edac->dev, &res); | ||
1088 | if (IS_ERR(altdev->base)) { | ||
1089 | rc = PTR_ERR(altdev->base); | ||
1090 | goto err_release_group1; | ||
1091 | } | ||
1092 | |||
1093 | /* Check specific dependencies for the module */ | ||
1094 | if (altdev->data->setup) { | ||
1095 | rc = altdev->data->setup(altdev); | ||
1096 | if (rc) | ||
1097 | goto err_release_group1; | ||
1098 | } | ||
1099 | |||
1100 | rc = edac_device_add_device(dci); | ||
1101 | if (rc) { | ||
1102 | dev_err(edac->dev, "edac_device_add_device failed\n"); | ||
1103 | rc = -ENOMEM; | ||
1104 | goto err_release_group1; | ||
1105 | } | ||
1106 | |||
1107 | altr_create_edacdev_dbgfs(dci, prv); | ||
1108 | |||
1109 | list_add(&altdev->next, &edac->a10_ecc_devices); | ||
1110 | |||
1111 | devres_remove_group(edac->dev, altr_edac_a10_device_add); | ||
1112 | |||
1113 | return 0; | ||
1114 | |||
1115 | err_release_group1: | ||
1116 | edac_device_free_ctl_info(dci); | ||
1117 | err_release_group: | ||
1118 | edac_printk(KERN_ALERT, EDAC_DEVICE, "%s: %d\n", __func__, __LINE__); | ||
1119 | devres_release_group(edac->dev, NULL); | ||
1120 | edac_printk(KERN_ERR, EDAC_DEVICE, | ||
1121 | "%s:Error setting up EDAC device: %d\n", ecc_name, rc); | ||
1122 | |||
1123 | return rc; | ||
1124 | } | ||
1125 | |||
1126 | static int altr_edac_a10_probe(struct platform_device *pdev) | ||
1127 | { | ||
1128 | struct altr_arria10_edac *edac; | ||
1129 | struct device_node *child; | ||
1130 | int rc; | ||
1131 | |||
1132 | edac = devm_kzalloc(&pdev->dev, sizeof(*edac), GFP_KERNEL); | ||
1133 | if (!edac) | ||
1134 | return -ENOMEM; | ||
1135 | |||
1136 | edac->dev = &pdev->dev; | ||
1137 | platform_set_drvdata(pdev, edac); | ||
1138 | INIT_LIST_HEAD(&edac->a10_ecc_devices); | ||
1139 | |||
1140 | edac->ecc_mgr_map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, | ||
1141 | "altr,sysmgr-syscon"); | ||
1142 | if (IS_ERR(edac->ecc_mgr_map)) { | ||
1143 | edac_printk(KERN_ERR, EDAC_DEVICE, | ||
1144 | "Unable to get syscon altr,sysmgr-syscon\n"); | ||
1145 | return PTR_ERR(edac->ecc_mgr_map); | ||
1146 | } | ||
1147 | |||
1148 | edac->sb_irq = platform_get_irq(pdev, 0); | ||
1149 | rc = devm_request_irq(&pdev->dev, edac->sb_irq, | ||
1150 | altr_edac_a10_irq_handler, | ||
1151 | IRQF_SHARED, dev_name(&pdev->dev), edac); | ||
1152 | if (rc) { | ||
1153 | edac_printk(KERN_ERR, EDAC_DEVICE, "No SBERR IRQ resource\n"); | ||
1154 | return rc; | ||
1155 | } | ||
1156 | |||
1157 | edac->db_irq = platform_get_irq(pdev, 1); | ||
1158 | rc = devm_request_irq(&pdev->dev, edac->db_irq, | ||
1159 | altr_edac_a10_irq_handler, | ||
1160 | IRQF_SHARED, dev_name(&pdev->dev), edac); | ||
1161 | if (rc) { | ||
1162 | edac_printk(KERN_ERR, EDAC_DEVICE, "No DBERR IRQ resource\n"); | ||
1163 | return rc; | ||
1164 | } | ||
1165 | |||
1166 | for_each_child_of_node(pdev->dev.of_node, child) { | ||
1167 | if (!of_device_is_available(child)) | ||
1168 | continue; | ||
1169 | if (of_device_is_compatible(child, "altr,socfpga-a10-l2-ecc")) | ||
1170 | altr_edac_a10_device_add(edac, child); | ||
1171 | } | ||
1172 | |||
1173 | return 0; | ||
1174 | } | ||
1175 | |||
1176 | static const struct of_device_id altr_edac_a10_of_match[] = { | ||
1177 | { .compatible = "altr,socfpga-a10-ecc-manager" }, | ||
1178 | {}, | ||
1179 | }; | ||
1180 | MODULE_DEVICE_TABLE(of, altr_edac_a10_of_match); | ||
1181 | |||
1182 | static struct platform_driver altr_edac_a10_driver = { | ||
1183 | .probe = altr_edac_a10_probe, | ||
1184 | .driver = { | ||
1185 | .name = "socfpga_a10_ecc_manager", | ||
1186 | .of_match_table = altr_edac_a10_of_match, | ||
1187 | }, | ||
1188 | }; | ||
1189 | module_platform_driver(altr_edac_a10_driver); | ||
1190 | |||
960 | MODULE_LICENSE("GPL v2"); | 1191 | MODULE_LICENSE("GPL v2"); |
961 | MODULE_AUTHOR("Thor Thayer"); | 1192 | MODULE_AUTHOR("Thor Thayer"); |
962 | MODULE_DESCRIPTION("EDAC Driver for Altera Memories"); | 1193 | MODULE_DESCRIPTION("EDAC Driver for Altera Memories"); |
diff --git a/drivers/edac/altera_edac.h b/drivers/edac/altera_edac.h index d7ef94c13b98..b0a17d03c715 100644 --- a/drivers/edac/altera_edac.h +++ b/drivers/edac/altera_edac.h | |||
@@ -219,12 +219,39 @@ struct altr_sdram_mc_data { | |||
219 | #define ALTR_L2_ECC_INJS BIT(1) | 219 | #define ALTR_L2_ECC_INJS BIT(1) |
220 | #define ALTR_L2_ECC_INJD BIT(2) | 220 | #define ALTR_L2_ECC_INJD BIT(2) |
221 | 221 | ||
222 | /* Arria10 General ECC Block Module Defines */ | ||
223 | #define A10_SYSMGR_ECC_INTSTAT_SERR_OFST 0x9C | ||
224 | #define A10_SYSMGR_ECC_INTSTAT_DERR_OFST 0xA0 | ||
225 | #define A10_SYSMGR_ECC_INTSTAT_L2 BIT(0) | ||
226 | |||
227 | #define A10_SYSGMR_MPU_CLEAR_L2_ECC_OFST 0xA8 | ||
228 | #define A10_SYSGMR_MPU_CLEAR_L2_ECC_SB BIT(15) | ||
229 | #define A10_SYSGMR_MPU_CLEAR_L2_ECC_MB BIT(31) | ||
230 | |||
231 | /* Arria 10 L2 ECC Management Group Defines */ | ||
232 | #define ALTR_A10_L2_ECC_CTL_OFST 0x0 | ||
233 | #define ALTR_A10_L2_ECC_EN_CTL BIT(0) | ||
234 | |||
235 | #define ALTR_A10_L2_ECC_STATUS 0xFFD060A4 | ||
236 | #define ALTR_A10_L2_ECC_STAT_OFST 0xA4 | ||
237 | #define ALTR_A10_L2_ECC_SERR_PEND BIT(0) | ||
238 | #define ALTR_A10_L2_ECC_MERR_PEND BIT(0) | ||
239 | |||
240 | #define ALTR_A10_L2_ECC_CLR_OFST 0x4 | ||
241 | #define ALTR_A10_L2_ECC_SERR_CLR BIT(15) | ||
242 | #define ALTR_A10_L2_ECC_MERR_CLR BIT(31) | ||
243 | |||
244 | #define ALTR_A10_L2_ECC_INJ_OFST ALTR_A10_L2_ECC_CTL_OFST | ||
245 | #define ALTR_A10_L2_ECC_CE_INJ_MASK 0x00000101 | ||
246 | #define ALTR_A10_L2_ECC_UE_INJ_MASK 0x00010101 | ||
247 | |||
222 | struct altr_edac_device_dev; | 248 | struct altr_edac_device_dev; |
223 | 249 | ||
224 | struct edac_device_prv_data { | 250 | struct edac_device_prv_data { |
225 | int (*setup)(struct altr_edac_device_dev *device); | 251 | int (*setup)(struct altr_edac_device_dev *device); |
226 | int ce_clear_mask; | 252 | int ce_clear_mask; |
227 | int ue_clear_mask; | 253 | int ue_clear_mask; |
254 | int irq_status_mask; | ||
228 | char dbgfs_name[20]; | 255 | char dbgfs_name[20]; |
229 | void * (*alloc_mem)(size_t size, void **other); | 256 | void * (*alloc_mem)(size_t size, void **other); |
230 | void (*free_mem)(void *p, size_t size, void *other); | 257 | void (*free_mem)(void *p, size_t size, void *other); |
@@ -232,16 +259,31 @@ struct edac_device_prv_data { | |||
232 | int ce_set_mask; | 259 | int ce_set_mask; |
233 | int ue_set_mask; | 260 | int ue_set_mask; |
234 | int set_err_ofst; | 261 | int set_err_ofst; |
262 | irqreturn_t (*ecc_irq_handler)(struct altr_edac_device_dev *dci, | ||
263 | bool sb); | ||
235 | int trig_alloc_sz; | 264 | int trig_alloc_sz; |
236 | }; | 265 | }; |
237 | 266 | ||
238 | struct altr_edac_device_dev { | 267 | struct altr_edac_device_dev { |
268 | struct list_head next; | ||
239 | void __iomem *base; | 269 | void __iomem *base; |
240 | int sb_irq; | 270 | int sb_irq; |
241 | int db_irq; | 271 | int db_irq; |
242 | const struct edac_device_prv_data *data; | 272 | const struct edac_device_prv_data *data; |
243 | struct dentry *debugfs_dir; | 273 | struct dentry *debugfs_dir; |
244 | char *edac_dev_name; | 274 | char *edac_dev_name; |
275 | struct altr_arria10_edac *edac; | ||
276 | struct edac_device_ctl_info *edac_dev; | ||
277 | struct device ddev; | ||
278 | int edac_idx; | ||
279 | }; | ||
280 | |||
281 | struct altr_arria10_edac { | ||
282 | struct device *dev; | ||
283 | struct regmap *ecc_mgr_map; | ||
284 | int sb_irq; | ||
285 | int db_irq; | ||
286 | struct list_head a10_ecc_devices; | ||
245 | }; | 287 | }; |
246 | 288 | ||
247 | #endif /* #ifndef _ALTERA_EDAC_H */ | 289 | #endif /* #ifndef _ALTERA_EDAC_H */ |