|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
| 2 | +/* |
| 3 | + * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC |
| 4 | + * |
| 5 | + * Authors: |
| 6 | + |
| 7 | + * |
| 8 | + * Baikal-T1 AXI-bus driver |
| 9 | + */ |
| 10 | + |
| 11 | +#include <linux/kernel.h> |
| 12 | +#include <linux/module.h> |
| 13 | +#include <linux/types.h> |
| 14 | +#include <linux/bitfield.h> |
| 15 | +#include <linux/device.h> |
| 16 | +#include <linux/atomic.h> |
| 17 | +#include <linux/regmap.h> |
| 18 | +#include <linux/platform_device.h> |
| 19 | +#include <linux/mfd/syscon.h> |
| 20 | +#include <linux/interrupt.h> |
| 21 | +#include <linux/io.h> |
| 22 | +#include <linux/nmi.h> |
| 23 | +#include <linux/of.h> |
| 24 | +#include <linux/clk.h> |
| 25 | +#include <linux/reset.h> |
| 26 | +#include <linux/sysfs.h> |
| 27 | + |
| 28 | +#define BT1_AXI_WERRL 0x110 |
| 29 | +#define BT1_AXI_WERRH 0x114 |
| 30 | +#define BT1_AXI_WERRH_TYPE BIT(23) |
| 31 | +#define BT1_AXI_WERRH_ADDR_FLD 24 |
| 32 | +#define BT1_AXI_WERRH_ADDR_MASK GENMASK(31, BT1_AXI_WERRH_ADDR_FLD) |
| 33 | + |
| 34 | +/* |
| 35 | + * struct bt1_axi - Baikal-T1 AXI-bus private data |
| 36 | + * @dev: Pointer to the device structure. |
| 37 | + * @qos_regs: AXI Interconnect QoS tuning registers. |
| 38 | + * @sys_regs: Baikal-T1 System Controller registers map. |
| 39 | + * @irq: Errors IRQ number. |
| 40 | + * @aclk: AXI reference clock. |
| 41 | + * @arst: AXI Interconnect reset line. |
| 42 | + * @count: Number of errors detected. |
| 43 | + */ |
| 44 | +struct bt1_axi { |
| 45 | + struct device *dev; |
| 46 | + |
| 47 | + void __iomem *qos_regs; |
| 48 | + struct regmap *sys_regs; |
| 49 | + int irq; |
| 50 | + |
| 51 | + struct clk *aclk; |
| 52 | + |
| 53 | + struct reset_control *arst; |
| 54 | + |
| 55 | + atomic_t count; |
| 56 | +}; |
| 57 | + |
| 58 | +static irqreturn_t bt1_axi_isr(int irq, void *data) |
| 59 | +{ |
| 60 | + struct bt1_axi *axi = data; |
| 61 | + u32 low = 0, high = 0; |
| 62 | + |
| 63 | + regmap_read(axi->sys_regs, BT1_AXI_WERRL, &low); |
| 64 | + regmap_read(axi->sys_regs, BT1_AXI_WERRH, &high); |
| 65 | + |
| 66 | + dev_crit_ratelimited(axi->dev, |
| 67 | + "AXI-bus fault %d: %s at 0x%x%08x\n", |
| 68 | + atomic_inc_return(&axi->count), |
| 69 | + high & BT1_AXI_WERRH_TYPE ? "no slave" : "slave protocol error", |
| 70 | + high, low); |
| 71 | + |
| 72 | + /* |
| 73 | + * Print backtrace on each CPU. This might be pointless if the fault |
| 74 | + * has happened on the same CPU as the IRQ handler is executed or |
| 75 | + * the other core proceeded further execution despite the error. |
| 76 | + * But if it's not, by looking at the trace we would get straight to |
| 77 | + * the cause of the problem. |
| 78 | + */ |
| 79 | + trigger_all_cpu_backtrace(); |
| 80 | + |
| 81 | + return IRQ_HANDLED; |
| 82 | +} |
| 83 | + |
| 84 | +static void bt1_axi_clear_data(void *data) |
| 85 | +{ |
| 86 | + struct bt1_axi *axi = data; |
| 87 | + struct platform_device *pdev = to_platform_device(axi->dev); |
| 88 | + |
| 89 | + platform_set_drvdata(pdev, NULL); |
| 90 | +} |
| 91 | + |
| 92 | +static struct bt1_axi *bt1_axi_create_data(struct platform_device *pdev) |
| 93 | +{ |
| 94 | + struct device *dev = &pdev->dev; |
| 95 | + struct bt1_axi *axi; |
| 96 | + int ret; |
| 97 | + |
| 98 | + axi = devm_kzalloc(dev, sizeof(*axi), GFP_KERNEL); |
| 99 | + if (!axi) |
| 100 | + return ERR_PTR(-ENOMEM); |
| 101 | + |
| 102 | + ret = devm_add_action(dev, bt1_axi_clear_data, axi); |
| 103 | + if (ret) { |
| 104 | + dev_err(dev, "Can't add AXI EHB data clear action\n"); |
| 105 | + return ERR_PTR(ret); |
| 106 | + } |
| 107 | + |
| 108 | + axi->dev = dev; |
| 109 | + atomic_set(&axi->count, 0); |
| 110 | + platform_set_drvdata(pdev, axi); |
| 111 | + |
| 112 | + return axi; |
| 113 | +} |
| 114 | + |
| 115 | +static int bt1_axi_request_regs(struct bt1_axi *axi) |
| 116 | +{ |
| 117 | + struct platform_device *pdev = to_platform_device(axi->dev); |
| 118 | + struct device *dev = axi->dev; |
| 119 | + |
| 120 | + axi->sys_regs = syscon_regmap_lookup_by_phandle(dev->of_node, "syscon"); |
| 121 | + if (IS_ERR(axi->sys_regs)) { |
| 122 | + dev_err(dev, "Couldn't find syscon registers\n"); |
| 123 | + return PTR_ERR(axi->sys_regs); |
| 124 | + } |
| 125 | + |
| 126 | + axi->qos_regs = devm_platform_ioremap_resource_byname(pdev, "qos"); |
| 127 | + if (IS_ERR(axi->qos_regs)) { |
| 128 | + dev_err(dev, "Couldn't map AXI-bus QoS registers\n"); |
| 129 | + return PTR_ERR(axi->qos_regs); |
| 130 | + } |
| 131 | + |
| 132 | + return 0; |
| 133 | +} |
| 134 | + |
| 135 | +static int bt1_axi_request_rst(struct bt1_axi *axi) |
| 136 | +{ |
| 137 | + int ret; |
| 138 | + |
| 139 | + axi->arst = devm_reset_control_get_optional_exclusive(axi->dev, "arst"); |
| 140 | + if (IS_ERR(axi->arst)) { |
| 141 | + dev_warn(axi->dev, "Couldn't get reset control line\n"); |
| 142 | + return PTR_ERR(axi->arst); |
| 143 | + } |
| 144 | + |
| 145 | + ret = reset_control_deassert(axi->arst); |
| 146 | + if (ret) |
| 147 | + dev_err(axi->dev, "Failed to deassert the reset line\n"); |
| 148 | + |
| 149 | + return ret; |
| 150 | +} |
| 151 | + |
| 152 | +static void bt1_axi_disable_clk(void *data) |
| 153 | +{ |
| 154 | + struct bt1_axi *axi = data; |
| 155 | + |
| 156 | + clk_disable_unprepare(axi->aclk); |
| 157 | +} |
| 158 | + |
| 159 | +static int bt1_axi_request_clk(struct bt1_axi *axi) |
| 160 | +{ |
| 161 | + int ret; |
| 162 | + |
| 163 | + axi->aclk = devm_clk_get(axi->dev, "aclk"); |
| 164 | + if (IS_ERR(axi->aclk)) { |
| 165 | + dev_err(axi->dev, "Couldn't get AXI Interconnect clock\n"); |
| 166 | + return PTR_ERR(axi->aclk); |
| 167 | + } |
| 168 | + |
| 169 | + ret = clk_prepare_enable(axi->aclk); |
| 170 | + if (ret) { |
| 171 | + dev_err(axi->dev, "Couldn't enable the AXI clock\n"); |
| 172 | + return ret; |
| 173 | + } |
| 174 | + |
| 175 | + ret = devm_add_action_or_reset(axi->dev, bt1_axi_disable_clk, axi); |
| 176 | + if (ret) { |
| 177 | + dev_err(axi->dev, "Can't add AXI clock disable action\n"); |
| 178 | + return ret; |
| 179 | + } |
| 180 | + |
| 181 | + return 0; |
| 182 | +} |
| 183 | + |
| 184 | +static int bt1_axi_request_irq(struct bt1_axi *axi) |
| 185 | +{ |
| 186 | + struct platform_device *pdev = to_platform_device(axi->dev); |
| 187 | + int ret; |
| 188 | + |
| 189 | + axi->irq = platform_get_irq(pdev, 0); |
| 190 | + if (axi->irq < 0) |
| 191 | + return axi->irq; |
| 192 | + |
| 193 | + ret = devm_request_irq(axi->dev, axi->irq, bt1_axi_isr, IRQF_SHARED, |
| 194 | + "bt1-axi", axi); |
| 195 | + if (ret) { |
| 196 | + dev_err(axi->dev, "Couldn't request AXI EHB IRQ\n"); |
| 197 | + return ret; |
| 198 | + } |
| 199 | + |
| 200 | + return 0; |
| 201 | +} |
| 202 | + |
| 203 | +static ssize_t count_show(struct device *dev, |
| 204 | + struct device_attribute *attr, char *buf) |
| 205 | +{ |
| 206 | + struct bt1_axi *axi = dev_get_drvdata(dev); |
| 207 | + |
| 208 | + return scnprintf(buf, PAGE_SIZE, "%d\n", atomic_read(&axi->count)); |
| 209 | +} |
| 210 | +static DEVICE_ATTR_RO(count); |
| 211 | + |
| 212 | +static ssize_t inject_error_show(struct device *dev, |
| 213 | + struct device_attribute *attr, char *buf) |
| 214 | +{ |
| 215 | + return scnprintf(buf, PAGE_SIZE, "Error injection: bus unaligned\n"); |
| 216 | +} |
| 217 | + |
| 218 | +static ssize_t inject_error_store(struct device *dev, |
| 219 | + struct device_attribute *attr, |
| 220 | + const char *data, size_t count) |
| 221 | +{ |
| 222 | + struct bt1_axi *axi = dev_get_drvdata(dev); |
| 223 | + |
| 224 | + /* |
| 225 | + * Performing unaligned read from the memory will cause the CM2 bus |
| 226 | + * error while unaligned writing - the AXI bus write error handled |
| 227 | + * by this driver. |
| 228 | + */ |
| 229 | + if (!strncmp(data, "bus", 3)) |
| 230 | + readb(axi->qos_regs); |
| 231 | + else if (!strncmp(data, "unaligned", 9)) |
| 232 | + writeb(0, axi->qos_regs); |
| 233 | + else |
| 234 | + return -EINVAL; |
| 235 | + |
| 236 | + return count; |
| 237 | +} |
| 238 | +static DEVICE_ATTR_RW(inject_error); |
| 239 | + |
| 240 | +static struct attribute *bt1_axi_sysfs_attrs[] = { |
| 241 | + &dev_attr_count.attr, |
| 242 | + &dev_attr_inject_error.attr, |
| 243 | + NULL |
| 244 | +}; |
| 245 | +ATTRIBUTE_GROUPS(bt1_axi_sysfs); |
| 246 | + |
| 247 | +static void bt1_axi_remove_sysfs(void *data) |
| 248 | +{ |
| 249 | + struct bt1_axi *axi = data; |
| 250 | + |
| 251 | + device_remove_groups(axi->dev, bt1_axi_sysfs_groups); |
| 252 | +} |
| 253 | + |
| 254 | +static int bt1_axi_init_sysfs(struct bt1_axi *axi) |
| 255 | +{ |
| 256 | + int ret; |
| 257 | + |
| 258 | + ret = device_add_groups(axi->dev, bt1_axi_sysfs_groups); |
| 259 | + if (ret) { |
| 260 | + dev_err(axi->dev, "Failed to add sysfs files group\n"); |
| 261 | + return ret; |
| 262 | + } |
| 263 | + |
| 264 | + ret = devm_add_action_or_reset(axi->dev, bt1_axi_remove_sysfs, axi); |
| 265 | + if (ret) |
| 266 | + dev_err(axi->dev, "Can't add AXI EHB sysfs remove action\n"); |
| 267 | + |
| 268 | + return ret; |
| 269 | +} |
| 270 | + |
| 271 | +static int bt1_axi_probe(struct platform_device *pdev) |
| 272 | +{ |
| 273 | + struct bt1_axi *axi; |
| 274 | + int ret; |
| 275 | + |
| 276 | + axi = bt1_axi_create_data(pdev); |
| 277 | + if (IS_ERR(axi)) |
| 278 | + return PTR_ERR(axi); |
| 279 | + |
| 280 | + ret = bt1_axi_request_regs(axi); |
| 281 | + if (ret) |
| 282 | + return ret; |
| 283 | + |
| 284 | + ret = bt1_axi_request_rst(axi); |
| 285 | + if (ret) |
| 286 | + return ret; |
| 287 | + |
| 288 | + ret = bt1_axi_request_clk(axi); |
| 289 | + if (ret) |
| 290 | + return ret; |
| 291 | + |
| 292 | + ret = bt1_axi_request_irq(axi); |
| 293 | + if (ret) |
| 294 | + return ret; |
| 295 | + |
| 296 | + ret = bt1_axi_init_sysfs(axi); |
| 297 | + if (ret) |
| 298 | + return ret; |
| 299 | + |
| 300 | + return 0; |
| 301 | +} |
| 302 | + |
| 303 | +static const struct of_device_id bt1_axi_of_match[] = { |
| 304 | + { .compatible = "baikal,bt1-axi" }, |
| 305 | + { } |
| 306 | +}; |
| 307 | +MODULE_DEVICE_TABLE(of, bt1_axi_of_match); |
| 308 | + |
| 309 | +static struct platform_driver bt1_axi_driver = { |
| 310 | + .probe = bt1_axi_probe, |
| 311 | + .driver = { |
| 312 | + .name = "bt1-axi", |
| 313 | + .of_match_table = bt1_axi_of_match |
| 314 | + } |
| 315 | +}; |
| 316 | +module_platform_driver(bt1_axi_driver); |
| 317 | + |
| 318 | +MODULE_AUTHOR( "Serge Semin <[email protected]>"); |
| 319 | +MODULE_DESCRIPTION("Baikal-T1 AXI-bus driver"); |
| 320 | +MODULE_LICENSE("GPL v2"); |
0 commit comments