--- zzzz-none-000/linux-4.4.271/arch/mips/pci/pci-ar724x.c 2021-06-03 06:22:09.000000000 +0000 +++ dakota-7530ac-750/linux-4.4.271/arch/mips/pci/pci-ar724x.c 2023-01-11 09:25:41.000000000 +0000 @@ -13,8 +13,15 @@ #include #include #include +#include +#include +#include +#include +#include #include #include +#include +#include #define AR724X_PCI_REG_RESET 0x18 #define AR724X_PCI_REG_INT_STATUS 0x4c @@ -35,6 +42,23 @@ PCI_COMMAND_SERR | \ PCI_COMMAND_FAST_BACK) +#define PCIE_INT_STATUS_LINK_DOWN_MASK 0x08000000 +#define PCIE_INT_STATUS_LINK_REQ_RST_MASK 0x04000000 +#define PCIE_INT_STATUS_SYS_ERR_MASK 0x00002000 +#define PCIE_INT_STATUS_AER_INT_MASK 0x00000800 +#define PCIE_INT_STATUS_RADMX_COMP_LOOKUP_ERR_MASK 0x00000010 +#define PCIE_INT_STATUS_GM_COMP_LOOKUP_ERR_MASK 0x00000008 +#define PCIE_INT_STATUS_FATAL_ERR_MASK 0x00000004 + +// Following are FATAL error indications per Register Specification +#define AR724X_PCI_ERROR_MASK (PCIE_INT_STATUS_LINK_DOWN_MASK | \ + PCIE_INT_STATUS_LINK_REQ_RST_MASK | \ + PCIE_INT_STATUS_SYS_ERR_MASK | \ + PCIE_INT_STATUS_AER_INT_MASK | \ + PCIE_INT_STATUS_RADMX_COMP_LOOKUP_ERR_MASK | \ + PCIE_INT_STATUS_GM_COMP_LOOKUP_ERR_MASK | \ + PCIE_INT_STATUS_FATAL_ERR_MASK) + struct ar724x_pci_controller { void __iomem *devcfg_base; void __iomem *ctrl_base; @@ -50,6 +74,8 @@ struct pci_controller pci_controller; struct resource io_res; struct resource mem_res; + + struct notifier_block die_info_nb; }; static inline bool ar724x_pci_check_link(struct ar724x_pci_controller *apc) @@ -225,6 +251,81 @@ .write = ar724x_pci_write, }; +#ifdef CONFIG_AVM_PCI_RECOVERY +static void ath_pci_reset_work_func(struct work_struct *work) +{ + ath_pci_reset(); +} +DECLARE_WORK(ath_pci_reset_work, ath_pci_reset_work_func); +#endif + +static void ar724x_pci_error_handler(struct irq_desc *desc, u32 error) +{ + struct ar724x_pci_controller *apc; +#ifdef CONFIG_AVM_PCI_RECOVERY + struct pci_dev *d, *tmp; + int should_recover = 1; +#endif + + apc = irq_desc_get_handler_data(desc); + + pr_err("PCI Error occurred: 0x%08x\n", error); + + /* + * Odly the irq_data associated with desc is describes + * the parent irq_handler and not the apc irq handler. + * + * Therefore we cant simply reuse this here. + */ + + /* + * Mask these off, else the interrupt does not stop + * the handler is invoked repeatedly and is not able + * to panic. + * + * Because we don't have the correct irq_data, do this + * manually. + */ + __raw_writel(0, apc->ctrl_base + AR724X_PCI_REG_INT_MASK); + + apc->link_up = 0; + +#ifdef CONFIG_AVM_PCI_RECOVERY + /* + * Notify all devices on this bus, that an error occurred. Use a permanent + * failure at the moment, as recovery means a complete reset of the RC. + * This also means that all devices are gone, which indicates the permanence + */ + list_for_each_entry_safe(d, tmp, &apc->pci_controller.bus->devices, bus_list) { + enum pci_ers_result res; + + d->error_state = pci_channel_io_perm_failure; + + if (!d->driver) + continue; + + if (d->driver->err_handler && d->driver->err_handler->error_detected) + res = d->driver->err_handler->error_detected(d, pci_channel_io_perm_failure); + else + res = PCI_ERS_RESULT_NONE; + + /* + * TODO: currently everything will result in a complet RC reset, we should + * allow the driver more control over what to do + */ + if (res == PCI_ERS_RESULT_NONE) { + pr_err("PCI driver %s does not support recovery\n", d->driver->name); + should_recover = 0; + } + } + + if (should_recover) + schedule_work(&ath_pci_reset_work); + else +#endif + die("PCI Error", get_irq_regs()); +} + static void ar724x_pci_irq_handler(struct irq_desc *desc) { struct ar724x_pci_controller *apc; @@ -237,7 +338,10 @@ pending = __raw_readl(base + AR724X_PCI_REG_INT_STATUS) & __raw_readl(base + AR724X_PCI_REG_INT_MASK); - if (pending & AR724X_PCI_INT_DEV0) + if (pending & AR724X_PCI_ERROR_MASK) + ar724x_pci_error_handler(desc, pending & AR724X_PCI_ERROR_MASK); + + else if (pending & AR724X_PCI_INT_DEV0) generic_handle_irq(apc->irq_base + 0); else @@ -258,7 +362,7 @@ switch (offset) { case 0: t = __raw_readl(base + AR724X_PCI_REG_INT_MASK); - __raw_writel(t | AR724X_PCI_INT_DEV0, + __raw_writel(t | (AR724X_PCI_INT_DEV0 | AR724X_PCI_ERROR_MASK), base + AR724X_PCI_REG_INT_MASK); /* flush write */ __raw_readl(base + AR724X_PCI_REG_INT_MASK); @@ -279,15 +383,16 @@ switch (offset) { case 0: t = __raw_readl(base + AR724X_PCI_REG_INT_MASK); - __raw_writel(t & ~AR724X_PCI_INT_DEV0, - base + AR724X_PCI_REG_INT_MASK); + t &= ~(AR724X_PCI_INT_DEV0 | AR724X_PCI_ERROR_MASK); + __raw_writel(t, base + AR724X_PCI_REG_INT_MASK); /* flush write */ __raw_readl(base + AR724X_PCI_REG_INT_MASK); t = __raw_readl(base + AR724X_PCI_REG_INT_STATUS); - __raw_writel(t | AR724X_PCI_INT_DEV0, - base + AR724X_PCI_REG_INT_STATUS); + t |= AR724X_PCI_INT_DEV0; + t &= ~AR724X_PCI_ERROR_MASK; + __raw_writel(t, base + AR724X_PCI_REG_INT_STATUS); /* flush write */ __raw_readl(base + AR724X_PCI_REG_INT_STATUS); @@ -325,11 +430,153 @@ apc); } +static int die_info_notify(struct notifier_block *self, unsigned long dummy, void *param) +{ + unsigned long uer; + unsigned long cer; + + struct ar724x_pci_controller *apc = container_of(self, struct ar724x_pci_controller, die_info_nb); + + unsigned long int_status = ath_reg_rd(apc->ctrl_base + 0x4c); + unsigned long aer = ath_reg_rd(apc->ctrl_base + 0x04); + unsigned long debug = ath_reg_rd(apc->ctrl_base + 0x1c); + unsigned long mac_phy = ath_reg_rd(apc->ctrl_base + 0x30); + unsigned long phy_mac = ath_reg_rd(apc->ctrl_base + 0x34); + unsigned long err_cnt = ath_reg_rd(apc->ctrl_base + 0x54); + + if (soc_is_qca953x()) { + uer = cer = 0xFFFFFFFF; + } else { + uer = ath_reg_rd(apc->crp_base + 0x104); + cer = ath_reg_rd(apc->crp_base + 0x110); + } + + pr_emerg("PCI: int=%08lx aer=%08lx uer=%08lx cer=%08lx debug=%08lx " + "mac_phy=%08lx phy_mac=%08lx err_cnt=%08lx\n", + int_status, aer, uer, cer, debug, mac_phy, phy_mac, err_cnt); + + return NOTIFY_OK; +} + +/*------------------------------------------------------------------------------------------*\ +\*------------------------------------------------------------------------------------------*/ +static void ar724x_pci_board_init(struct ar724x_pci_controller *apc) +{ + uint32_t cmd; + int gpio_pcie_reset = 0; + int gpio_device_reset = 0; + + avm_get_hw_config(AVM_HW_CONFIG_VERSION, "gpio_avm_pcie_reset", &gpio_pcie_reset, NULL); + avm_get_hw_config(AVM_HW_CONFIG_VERSION, "gpio_avm_peregrine_reset", &gpio_device_reset, NULL); + + /*----------------------------------------------------------------------*\ + * PCIE aus dem Reset holen + \*----------------------------------------------------------------------*/ +#if defined(CONFIG_SOC_AR724X) + ath_reg_rmw_set(ATH_RESET,RST_RESET_PCIE_PHY_SERIAL_SET(1)); + mdelay(100); +#endif + + ath_reg_rmw_set(ATH_RESET, RST_RESET_PCIE_PHY_RESET_SET(1)); + mdelay(10); + + ath_reg_rmw_set(ATH_RESET, RST_RESET_PCIE_RESET_SET(1)); + mdelay(10); +#if defined(CONFIG_SOC_QCN550X) + ath_reg_rmw_set(ATH_RESET_2, RST_RESET_PCIE_PHY_RESET_SET(1)); + mdelay(10); + ath_reg_rmw_set(ATH_RESET_2, RST_RESET_PCIE_RESET_SET(1)); + mdelay(10); +#endif + +#if ! defined(CONFIG_SOC_AR724X) + ath_reg_rmw_clear(RST_MISC2_ADDRESS, RST_MISC2_PERSTN_RCPHY_SET(1)); + mdelay(10); + + ath_reg_wr_nf(apc->ctrl_base + AR724X_PCI_REG_RESET, 0); // Put endpoint in reset + if (gpio_pcie_reset) + ath_avm_gpio_out_bit(gpio_pcie_reset, 0); + if (gpio_device_reset) + ath_avm_gpio_out_bit(gpio_device_reset, 0); + mdelay(100); + + ath_reg_rmw_set(RST_MISC2_ADDRESS, RST_MISC2_PERSTN_RCPHY_SET(1)); + mdelay(10); +#endif + +#if defined(CONFIG_SOC_AR724X) + ath_reg_rmw_clear(ATH_RESET,RST_RESET_PCIE_PHY_SERIAL_SET(1)); + mdelay(10); +#endif + + ath_reg_rmw_clear(ATH_RESET, RST_RESET_PCIE_PHY_RESET_SET(1)); + mdelay(10); + + ath_reg_rmw_clear(ATH_RESET, RST_RESET_PCIE_RESET_SET(1)); + mdelay(10); +#if defined(CONFIG_SOC_QCN550X) + ath_reg_rmw_clear(ATH_RESET_2, RST_RESET_PCIE_PHY_RESET_SET(1)); + mdelay(10); + ath_reg_rmw_clear(ATH_RESET_2, RST_RESET_PCIE_RESET_SET(1)); + mdelay(10); +#endif + + ath_reg_wr_nf(apc->ctrl_base, PCIE_APP_PCIE_BAR_MSN_SET(1) | + PCIE_APP_CFG_BE_SET(0xf) | + PCIE_APP_SLV_RESP_ERR_MAP_SET(0x3f) | + PCIE_APP_LTSSM_ENABLE_SET(1)); + + /* TODO: Commenting this out doesn't seem to change anything. + * Can we get rid of this local_write stuff? + */ + cmd = PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER | PCI_COMMAND_INVALIDATE | + PCI_COMMAND_PARITY|PCI_COMMAND_SERR|PCI_COMMAND_FAST_BACK; + + /* === Emulated Link is up for this commands === */ + apc->link_up = 1; + /* rc1 */ + ar724x_pci_local_write(apc, PCI_COMMAND, 4, cmd); + ar724x_pci_local_write(apc, 0x20, 4, 0x1ff01000); + ar724x_pci_local_write(apc, 0x24, 4, 0x1ff01000); +#if defined(CONFIG_SOC_QCN550X) + /* rc2 */ + ar724x_pci_local_write(apc, 0x70c, 4, 0x1b403200); +#endif + apc->link_up = 0; + + ath_reg_wr_nf(apc->ctrl_base + AR724X_PCI_REG_RESET, 4); + if (gpio_pcie_reset) + ath_avm_gpio_out_bit(gpio_pcie_reset, 1); + if (gpio_device_reset) + ath_avm_gpio_out_bit(gpio_device_reset, 1); + mdelay(100); + + /* + * Check if the WLAN PCI-E H/W is present, If the + * WLAN H/W is not present, skip the PCI platform + * initialization code and return + */ + if (((ath_reg_rd(apc->ctrl_base + AR724X_PCI_REG_RESET)) & 0x1) == 0x0) + pr_err("\n"); + + apc->die_info_nb.notifier_call = die_info_notify; + apc->die_info_nb.priority = INT_MAX; + register_die_notifier(&apc->die_info_nb); +} + + static int ar724x_pci_probe(struct platform_device *pdev) { struct ar724x_pci_controller *apc; struct resource *res; int id; + int ret; + + if (pdev->dev.of_node) { + ret = of_alias_get_id(pdev->dev.of_node, "pci"); + if (ret >= 0) + pdev->id = ret; + } id = pdev->id; if (id == -1) @@ -340,6 +587,8 @@ if (!apc) return -ENOMEM; + dev_set_drvdata(&pdev->dev, apc); + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ctrl_base"); apc->ctrl_base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(apc->ctrl_base)) @@ -359,7 +608,7 @@ if (apc->irq < 0) return -EINVAL; - res = platform_get_resource_byname(pdev, IORESOURCE_IO, "io_base"); + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "io_base"); if (!res) return -EINVAL; @@ -383,6 +632,8 @@ apc->pci_controller.io_resource = &apc->io_res; apc->pci_controller.mem_resource = &apc->mem_res; + ar724x_pci_board_init(apc); + apc->link_up = ar724x_pci_check_link(apc); if (!apc->link_up) dev_warn(&pdev->dev, "PCIe link is down\n"); @@ -396,10 +647,31 @@ return 0; } +static int ar724x_pci_remove(struct platform_device *pdev) +{ + struct ar724x_pci_controller *apc; + + apc = dev_get_drvdata(&pdev->dev); + + unregister_pci_controller(&apc->pci_controller); + devm_kfree(&pdev->dev, apc); + + unregister_die_notifier(&apc->die_info_nb); + + return 0; +} + +static const struct of_device_id ath79_pci_of_match[] = { + {.compatible = "qca,ar724x-pci", }, + { }, +}; + static struct platform_driver ar724x_pci_driver = { .probe = ar724x_pci_probe, + .remove = ar724x_pci_remove, .driver = { .name = "ar724x-pci", + .of_match_table = ath79_pci_of_match, }, }; @@ -408,4 +680,59 @@ return platform_driver_register(&ar724x_pci_driver); } -postcore_initcall(ar724x_pci_init); +subsys_initcall(ar724x_pci_init); + +#ifdef CONFIG_AVM_PCI_RECOVERY + +void ath_pci_down(void) +{ + pr_err("Unloading PCI platform driver...\n"); + + platform_driver_unregister(&ar724x_pci_driver); + pr_err("PCI platform driver unloaded\n"); +} + +void ath_pci_up(void) +{ + pr_err("Reloading PCI platform driver...\n"); + + pcibios_reset_busno(); + platform_driver_register(&ar724x_pci_driver); + pr_err("PCI platform driver reloaded\n"); +} + +void ath_pci_reset(void) +{ + ath_pci_down(); + ath_pci_up(); +} + +static int avm_debug_pci_recovery_write(char *string, void *reg) +{ + int gpio_pcie_reset = 0; + int gpio_device_reset = 0; + + avm_get_hw_config(AVM_HW_CONFIG_VERSION, "gpio_avm_pcie_reset", &gpio_pcie_reset, NULL); + avm_get_hw_config(AVM_HW_CONFIG_VERSION, "gpio_avm_peregrine_reset", &gpio_device_reset, NULL); + + pr_err("[ath_pci] Kill pci wlan device...\n"); + + if (gpio_pcie_reset) + ath_avm_gpio_out_bit(gpio_pcie_reset, 0); + if (gpio_device_reset) + ath_avm_gpio_out_bit(gpio_device_reset, 0); + + return 0; +} + +static int __init avm_debug_pci_recovery_init(void) +{ + proc_mkdir("avm/debug", NULL); + + add_simple_proc_file("avm/debug/pci_recovery", avm_debug_pci_recovery_write, NULL, NULL); + + return 0; +} + +late_initcall(avm_debug_pci_recovery_init); +#endif