// SPDX-License-Identifier: GPL-2.0 /* * PCIe modem control driver for S51xx series * * Copyright (C) 2019 Samsung Electronics. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "modem_prj.h" #include "modem_utils.h" #include "modem_ctrl.h" #include "s51xx_pcie.h" extern int exynos_pcie_rc_chk_link_status(int ch_num); extern int exynos_pcie_l1ss_ctrl(int enable, int id, int ch_num); static int s51xx_pcie_read_procmem(struct seq_file *m, void *v) { pr_err("Procmem READ!\n"); return 0; } static int s51xx_pcie_proc_open(struct inode *inode, struct file *file) { return single_open(file, s51xx_pcie_read_procmem, NULL); } static const struct proc_ops s51xx_pcie_proc_fops = { .proc_open = s51xx_pcie_proc_open, .proc_read = seq_read, .proc_lseek = seq_lseek, .proc_release = single_release, }; void s51xx_pcie_chk_ep_conf(struct pci_dev *pdev) { int i; u32 val1, val2, val3, val4; /* full log for (i = 0x0; i < 0x50; i += 0x10) { pci_read_config_dword(pdev, i, &val1); pci_read_config_dword(pdev, i + 0x4, &val2); pci_read_config_dword(pdev, i + 0x8, &val3); pci_read_config_dword(pdev, i + 0xC, &val4); dev_info(&pdev->dev, "0x%02x: %08x %08x %08x %08x\n", i, val1, val2, val3, val4); } */ i = 0x0; pci_read_config_dword(pdev, i, &val1); pci_read_config_dword(pdev, i + 0x4, &val2); pci_read_config_dword(pdev, i + 0x8, &val3); pci_read_config_dword(pdev, i + 0xC, &val4); dev_info(&pdev->dev, "0x%02x: %08x %08x %08x %08x\n", i, val1, val2, val3, val4); i = 0x10; pci_read_config_dword(pdev, i, &val1); pci_read_config_dword(pdev, i + 0x4, &val2); pci_read_config_dword(pdev, i + 0x8, &val3); pci_read_config_dword(pdev, i + 0xC, &val4); dev_info(&pdev->dev, "0x%02x: %08x %08x %08x %08x\n", i, val1, val2, val3, val4); /* i = 0x40; pci_read_config_dword(pdev, i, &val1); dev_info(&pdev->dev, "0x%02x: %08x\n", i, val1); */ } inline int s51xx_pcie_send_doorbell_int(struct pci_dev *pdev, int int_num) { struct s51xx_pcie *s51xx_pcie = pci_get_drvdata(pdev); struct pci_driver *driver = pdev->driver; struct modem_ctl *mc = container_of(driver, struct modem_ctl, pci_driver); u32 reg, count = 0; int cnt = 0; u16 cmd; if (s51xx_pcie->link_status == 0) { mif_err_limited("Can't send Interrupt(not enabled)!!!\n"); return -EAGAIN; } if (exynos_pcie_rc_get_cpl_timeout_state(s51xx_pcie->pcie_channel_num)) { mif_err_limited("Can't send Interrupt(cto_retry_cnt: %d)!!!\n", mc->pcie_cto_retry_cnt); return 0; } if (s51xx_check_pcie_link_status(s51xx_pcie->pcie_channel_num) == 0) { mif_err_limited("Can't send Interrupt(not linked)!!!\n"); goto check_cpl_timeout; } pci_read_config_word(pdev, PCI_COMMAND, &cmd); if ((((cmd & PCI_COMMAND_MEMORY) == 0) || (cmd & PCI_COMMAND_MASTER) == 0) || (cmd == 0xffff)) { mif_err_limited("Can't send Interrupt(not setted bme_en, 0x%04x)!!!\n", cmd); do { cnt++; /* set bme bit */ pci_set_master(pdev); pci_read_config_word(pdev, PCI_COMMAND, &cmd); mif_info("cmd reg = 0x%04x\n", cmd); /* set mse bit */ cmd |= PCI_COMMAND_MEMORY; pci_write_config_word(pdev, PCI_COMMAND, cmd); pci_read_config_word(pdev, PCI_COMMAND, &cmd); mif_info("cmd reg = 0x%04x\n", cmd); if ((cmd & PCI_COMMAND_MEMORY) && (cmd & PCI_COMMAND_MASTER) && (cmd != 0xffff)) break; } while (cnt < 10); if (cnt >= 10) { mif_err_limited("BME is not set(cnt=%d)\n", cnt); goto check_cpl_timeout; } } send_doorbell_again: iowrite32(int_num, s51xx_pcie->doorbell_addr); reg = ioread32(s51xx_pcie->doorbell_addr); /* debugging: * mif_info("s51xx_pcie.doorbell_addr = 0x%p - written(int_num=0x%x) read(reg=0x%x)\n", \ * s51xx_pcie->doorbell_addr, int_num, reg); */ if (reg == 0xffffffff) { count++; if (count < 10) { if (!in_interrupt()) udelay(1000); /* 1ms */ else { mif_err_limited("Can't send doorbell in interrupt mode (0x%08X)\n", reg); return 0; } goto send_doorbell_again; } mif_err("[Need to CHECK] Can't send doorbell int (0x%x)\n", reg); goto check_cpl_timeout; } return 0; check_cpl_timeout: if (exynos_pcie_rc_get_cpl_timeout_state(s51xx_pcie->pcie_channel_num)) { mif_err_limited("Can't send Interrupt(cto_retry_cnt: %d)!!!\n", mc->pcie_cto_retry_cnt); return 0; } exynos_pcie_rc_register_dump(s51xx_pcie->pcie_channel_num); return -EAGAIN; } void first_save_s51xx_status(struct pci_dev *pdev) { struct s51xx_pcie *s51xx_pcie = pci_get_drvdata(pdev); if (s51xx_check_pcie_link_status(s51xx_pcie->pcie_channel_num) == 0) { mif_err("It's not Linked - Ignore saving the s5100\n"); return; } pci_save_state(pdev); s51xx_pcie->pci_saved_configs = pci_store_saved_state(pdev); if (s51xx_pcie->pci_saved_configs == NULL) mif_err("MSI-DBG: s51xx pcie.pci_saved_configs is NULL(s51xx config NOT saved)\n"); else mif_info("first s51xx config status save: done\n"); } void s51xx_pcie_save_state(struct pci_dev *pdev) { struct s51xx_pcie *s51xx_pcie = pci_get_drvdata(pdev); dev_info(&pdev->dev, "[%s]\n", __func__); if (s51xx_check_pcie_link_status(s51xx_pcie->pcie_channel_num) == 0) { mif_err("It's not Linked - Ignore restore state!!!\n"); return; } /* pci_pme_active(s51xx_pcie.s51xx_pdev, 0); */ /* Disable L1.2 before PCIe power off */ s51xx_pcie_l1ss_ctrl(0); pci_clear_master(pdev); pci_save_state(pdev); s51xx_pcie->pci_saved_configs = pci_store_saved_state(pdev); s51xx_pcie_chk_ep_conf(pdev); disable_msi_int(pdev); /* pci_enable_wake(s51xx_pcie.s51xx_pdev, PCI_D0, 0); */ pci_disable_device(pdev); pci_wake_from_d3(pdev, false); if (pci_set_power_state(pdev, PCI_D3hot)) mif_err("Can't set D3 state!!!!\n"); } void s51xx_pcie_restore_state(struct pci_dev *pdev) { struct s51xx_pcie *s51xx_pcie = pci_get_drvdata(pdev); int ret; dev_info(&pdev->dev, "[%s]\n", __func__); if (s51xx_check_pcie_link_status(s51xx_pcie->pcie_channel_num) == 0) { mif_err("It's not Linked - Ignore restore state!!!\n"); return; } if (pci_set_power_state(pdev, PCI_D0)) mif_err("Can't set D0 state!!!!\n"); if (!s51xx_pcie->pci_saved_configs) dev_err(&pdev->dev, "[%s] s51xx pcie saved configs is NULL\n", __func__); pci_load_saved_state(pdev, s51xx_pcie->pci_saved_configs); pci_restore_state(pdev); /* move chk_ep_conf function after setting BME(Bus Master Enable) * s51xx_pcie_chk_ep_conf(pdev); */ pci_enable_wake(pdev, PCI_D0, 0); /* pci_enable_wake(s51xx_pcie.s51xx_pdev, PCI_D3hot, 0); */ ret = pci_enable_device(pdev); if (ret) pr_err("Can't enable PCIe Device after linkup!\n"); pci_set_master(pdev); /* DBG: print out EP config values after restore_state */ s51xx_pcie_chk_ep_conf(pdev); #if IS_ENABLED(CONFIG_DISABLE_PCIE_CP_L1_2) /* Disable L1.2 after PCIe power on */ s51xx_pcie_l1ss_ctrl(0); #else /* Enable L1.2 after PCIe power on */ s51xx_pcie_l1ss_ctrl(1); #endif s51xx_pcie->link_status = 1; /* pci_pme_active(s51xx_pcie.s51xx_pdev, 1); */ } int s51xx_check_pcie_link_status(int ch_num) { return exynos_pcie_rc_chk_link_status(ch_num); } void s51xx_pcie_l1ss_ctrl(int enable) { exynos_pcie_l1ss_ctrl(enable, PCIE_L1SS_CTRL_MODEM_IF, 1); } void disable_msi_int(struct pci_dev *pdev) { struct s51xx_pcie *s51xx_pcie = pci_get_drvdata(pdev); dev_info(&pdev->dev, "[%s]\n", __func__); s51xx_pcie->link_status = 0; /* It's not needed now... * pci_disable_msi(s51xx_pcie.s51xx_pdev); * pci_config_pm_runtime_put(&s51xx_pcie.s51xx_pdev->dev); */ } int s51xx_pcie_request_msi_int(struct pci_dev *pdev, int int_num) { int err = -EFAULT; pr_err("Enable MSI interrupts : %d\n", int_num); if (int_num > MAX_MSI_NUM) { pr_err("Too many MSI interrupts are requested(<=16)!!!\n"); return -EFAULT; } err = pci_alloc_irq_vectors_affinity(pdev, int_num, int_num, PCI_IRQ_MSI, NULL); if (err <= 0) { pr_err("Can't get msi IRQ!!!!!\n"); return -EFAULT; } return pdev->irq; } static void s51xx_pcie_linkdown_cb(struct exynos_pcie_notify *noti) { struct pci_dev *pdev = (struct pci_dev *)noti->user; struct pci_driver *driver = pdev->driver; struct modem_ctl *mc = container_of(driver, struct modem_ctl, pci_driver); pr_err("s51xx Link-Down notification callback function!!!\n"); if (mc->pcie_powered_on == false) { pr_info("%s: skip cp crash during dislink sequence\n", __func__); exynos_pcie_set_perst_gpio(mc->pcie_ch_num, 0); } else { s5100_force_crash_exit_ext(); } } static void s51xx_pcie_cpl_timeout_cb(struct exynos_pcie_notify *noti) { struct pci_dev *pdev = (struct pci_dev *)noti->user; struct pci_driver *driver = pdev->driver; struct modem_ctl *mc = container_of(driver, struct modem_ctl, pci_driver); struct s51xx_pcie *s51xx_pcie = pci_get_drvdata(pdev); pr_err("s51xx CPL_TIMEOUT notification callback function!!!\n"); pr_err("CPL: a=%d c=%d\n", mc->pcie_cto_retry_cnt_all++, mc->pcie_cto_retry_cnt); pr_err("s51xx CPLTO RC regdump \n"); exynos_pcie_rc_register_dump(s51xx_pcie->pcie_channel_num); if (mc->pcie_cto_retry_cnt++ < 10) { pr_err("[%s][%d] retry pcie poweron !!!\n", __func__, mc->pcie_cto_retry_cnt); queue_work_on(2, mc->wakeup_wq, &mc->wakeup_work); } else { pr_err("[%s][%d] force crash !!!\n", __func__, mc->pcie_cto_retry_cnt); s5100_force_crash_exit_ext(); } } static int s51xx_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { int ret; int __maybe_unused i; struct s51xx_pcie *s51xx_pcie; struct device *dev = &pdev->dev; struct pci_driver *driver = pdev->driver; struct modem_ctl *mc = container_of(driver, struct modem_ctl, pci_driver); struct device *mc_dev = mc->dev; struct pci_bus *bus = pdev->bus; struct pci_dev *bus_self = bus->self; struct resource *tmp_rsc; int resno = PCI_BRIDGE_MEM_WINDOW; u32 val, db_addr; dev_info(dev, "%s EP driver Probe(%s), chNum: %d\n", driver->name, __func__, mc->pcie_ch_num); s51xx_pcie = devm_kzalloc(dev, sizeof(*s51xx_pcie), GFP_KERNEL); s51xx_pcie->s51xx_pdev = pdev; s51xx_pcie->irq_num_base = pdev->irq; s51xx_pcie->link_status = 1; s51xx_pcie->pcie_channel_num = mc->pcie_ch_num; mc->s51xx_pdev = pdev; if (of_property_read_u32(mc_dev->of_node, "pci_db_addr", &db_addr)) { dev_err(dev, "Failed to parse the EP DB base address\n"); return -EINVAL; } pci_write_config_dword(pdev, PCI_BASE_ADDRESS_0, db_addr); pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &val); val &= PCI_BASE_ADDRESS_MEM_MASK; s51xx_pcie->dbaddr_offset = db_addr - val; dev_info(dev, "db_addr : 0x%x , val : 0x%x, offset : 0x%x\n", db_addr, val, (unsigned int)s51xx_pcie->dbaddr_offset); #if 0 pr_err("Disable BAR resources.\n"); for (i = 0; i < 6; i++) { pdev->resource[i].start = 0x0; pdev->resource[i].end = 0x0; pdev->resource[i].flags = 0x82000000; pci_assign_resource(pdev, i); } #endif /* EP BAR setup: BAR0 (4kB) */ pdev->resource[0].start = val; pdev->resource[0].end = val + SZ_4K; //pdev->resource[0].flags = 0x82000000; pci_assign_resource(pdev, 0); /* get Doorbell base address from root bus range */ tmp_rsc = bus_self->resource + resno; dev_info(&bus_self->dev, "[%s] BAR %d: tmp rsc : %pR\n", __func__, resno, tmp_rsc); s51xx_pcie->dbaddr_base = tmp_rsc->start; pr_err("Set Doorbell register address.\n"); s51xx_pcie->doorbell_addr = devm_ioremap(&pdev->dev, s51xx_pcie->dbaddr_base + s51xx_pcie->dbaddr_offset, SZ_4); ret = abox_pci_doorbell_paddr_set(s51xx_pcie->dbaddr_base + s51xx_pcie->dbaddr_offset); if (!ret) dev_err(dev, "PCIe doorbell setting for ABOX is failed \n"); pr_info("s51xx_pcie.doorbell_addr = %p (start 0x%lx offset : %lx)\n", s51xx_pcie->doorbell_addr, (unsigned long)s51xx_pcie->dbaddr_base, (unsigned long)s51xx_pcie->dbaddr_offset); if (s51xx_pcie->doorbell_addr == NULL) pr_err("Can't ioremap doorbell address!!!\n"); pr_info("Register PCIE notification LINKDOWN event...\n"); s51xx_pcie->pcie_event.events = EXYNOS_PCIE_EVENT_LINKDOWN; s51xx_pcie->pcie_event.user = pdev; s51xx_pcie->pcie_event.mode = EXYNOS_PCIE_TRIGGER_CALLBACK; s51xx_pcie->pcie_event.callback = s51xx_pcie_linkdown_cb; exynos_pcie_host_v1_register_event(&s51xx_pcie->pcie_event); pr_info("Register PCIE notification CPL_TIMEOUT event...\n"); s51xx_pcie->pcie_cpl_timeout_event.events = EXYNOS_PCIE_EVENT_CPL_TIMEOUT; s51xx_pcie->pcie_cpl_timeout_event.user = pdev; s51xx_pcie->pcie_cpl_timeout_event.mode = EXYNOS_PCIE_TRIGGER_CALLBACK; s51xx_pcie->pcie_cpl_timeout_event.callback = s51xx_pcie_cpl_timeout_cb; exynos_pcie_host_v1_register_event(&s51xx_pcie->pcie_cpl_timeout_event); pr_err("Enable PCI device...\n"); ret = pci_enable_device(pdev); pci_set_master(pdev); pci_set_drvdata(pdev, s51xx_pcie); pr_err("Check EP Conf...\n"); s51xx_pcie_chk_ep_conf(pdev); return 0; } void print_msi_register(struct pci_dev *pdev) { struct s51xx_pcie *s51xx_pcie = pci_get_drvdata(pdev); u32 msi_val; pci_read_config_dword(pdev, 0x50, &msi_val); mif_info("MSI Control Reg(0x50) : 0x%x\n", msi_val); pci_read_config_dword(pdev, 0x54, &msi_val); mif_info("MSI Message Reg(0x54) : 0x%x\n", msi_val); pci_read_config_dword(pdev, 0x58, &msi_val); mif_info("MSI MsgData Reg(0x58) : 0x%x\n", msi_val); if (msi_val == 0x0) { mif_info("MSI Message Reg == 0x0 - set MSI again!!!\n"); if (s51xx_pcie->pci_saved_configs != NULL) { mif_info("msi restore\n"); pci_restore_msi_state(pdev); } else { mif_info("[skip] msi restore: saved configs is NULL\n"); } mif_info("exynos_pcie_msi_init_ext is not implemented\n"); /* exynos_pcie_msi_init_ext(s51xx_pcie.pcie_channel_num); */ pci_read_config_dword(pdev, 0x50, &msi_val); mif_info("Recheck - MSI Control Reg : 0x%x (0x50)\n", msi_val); pci_read_config_dword(pdev, 0x54, &msi_val); mif_info("Recheck - MSI Message Reg : 0x%x (0x54)\n", msi_val); pci_read_config_dword(pdev, 0x58, &msi_val); mif_info("Recheck - MSI MsgData Reg : 0x%x (0x58)\n", msi_val); } } static void s51xx_pcie_remove(struct pci_dev *pdev) { pr_err("s51xx PCIe Remove!!!\n"); pci_release_regions(pdev); } /* For Test */ static struct pci_device_id s51xx_pci_id_tbl[] = { { PCI_VENDOR_ID_SAMSUNG, PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, }, // SC Basic { } }; MODULE_DEVICE_TABLE(pci, s51xx_pci_id_tbl); static struct pci_driver s51xx_driver = { .name = "s51xx", .id_table = s51xx_pci_id_tbl, .probe = s51xx_pcie_probe, .remove = s51xx_pcie_remove, }; /* * Initialize PCIe s51xx EP driver. */ int s51xx_pcie_init(struct modem_ctl *mc) { int ch_num = mc->pcie_ch_num; int ret; pr_err("Register PCIE drvier for s51xx.(chNum: %d, mc: 0x%p)\n", ch_num, mc); mc->pci_driver = s51xx_driver; ret = pci_register_driver(&mc->pci_driver); /* Create PROC fs */ proc_create("driver/s51xx_pcie_proc", 0, NULL, &s51xx_pcie_proc_fops); return 0; }