* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that 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.
*/
#include "ka_kernel_def_pub.h"
#include "ka_driver_pub.h"
#include "devdrv_util.h"
#include "devdrv_pci.h"
#include "devdrv_ctrl.h"
#include "devdrv_pcie_link_info.h"
#include "pbl/pbl_uda.h"
static u32 g_pcie_channel_status = DEVDRV_PCIE_COMMON_CHANNEL_INIT;
static ka_atomic_t g_peer_pcie_status;
#define PCIE_DUMP_LTSSM_TRACER_FN_NAME "pcie_dump_ltssm_tracer"
typedef void (*pcie_dump_ltssm_tracer_fn)(void);
__attribute__((unused)) STATIC pcie_dump_ltssm_tracer_fn g_pcie_dump_ltssm_tracer_fn = NULL;
void devdrv_get_pcie_dump_ltssm_tracer_symbol(void)
{
#ifdef CFG_FEATURE_PCIE_LINK_INFO
g_pcie_dump_ltssm_tracer_fn =
(pcie_dump_ltssm_tracer_fn)(uintptr_t)__ka_system_symbol_get(PCIE_DUMP_LTSSM_TRACER_FN_NAME);
if (g_pcie_dump_ltssm_tracer_fn == NULL) {
devdrv_warn("pcie_dump_ltssm_tracer symbol not find.\n");
}
#endif
}
void devdrv_put_pcie_dump_ltssm_tracer_symbol(void)
{
#ifdef CFG_FEATURE_PCIE_LINK_INFO
if (g_pcie_dump_ltssm_tracer_fn == NULL) {
devdrv_warn("pcie_dump_ltssm_tracer symbol not find.\n");
return;
}
__ka_system_symbol_put(PCIE_DUMP_LTSSM_TRACER_FN_NAME);
#endif
}
STATIC void devdrv_pcie_dump_ltssm_tracer(void)
{
#ifdef CFG_FEATURE_PCIE_LINK_INFO
if (g_pcie_dump_ltssm_tracer_fn == NULL) {
devdrv_warn("pcie_dump_ltssm_tracer symbol not find.\n");
return;
}
g_pcie_dump_ltssm_tracer_fn();
return;
#endif
}
void devdrv_set_pcie_channel_status(u32 status)
{
#ifdef CFG_FEATURE_PCIE_LINK_INFO
g_pcie_channel_status = status;
devdrv_info("set pcie channel status. (status=%u)\n", status);
#endif
return;
}
KA_EXPORT_SYMBOL(devdrv_set_pcie_channel_status);
u32 devdrv_get_pcie_channel_status(void)
{
return g_pcie_channel_status;
}
void devdrv_peer_pcie_status_init(void)
{
ka_base_atomic_set(&g_peer_pcie_status, DEVDRV_PEER_STATUS_NORMAL);
return;
}
void devdrv_set_peer_pcie_status(u32 status)
{
#ifdef CFG_FEATURE_PCIE_LINK_INFO
ka_base_atomic_set(&g_peer_pcie_status, status);
#endif
return;
}
u32 devdrv_get_peer_pcie_status(void)
{
return ka_base_atomic_read(&g_peer_pcie_status);
}
KA_EXPORT_SYMBOL(devdrv_get_peer_pcie_status);
STATIC int devdrv_get_pcie_mac_link_info(struct devdrv_pcie_link_info_para *pcie_link_info)
{
u32 mac_reg_link_value;
u32 ltssm_st;
void __ka_mm_iomem *apb_base = NULL;
if (pcie_link_info == NULL) {
devdrv_err("pcie_link_info is NULL.\n");
return -EINVAL;
}
apb_base = ka_mm_ioremap(PCIE_BASE_ADDR, PCIE_REG_SIZE);
if (apb_base == NULL) {
devdrv_err("ka_mm_ioremap fail, apb_base is NULL.\n");
return -ENOMEM;
}
mac_reg_link_value = ka_mm_readl(apb_base + PCIE_MAC_REG_BASE + PCIE_MAC_REG_LINK_ADDR);
pcie_link_info->link_status = DEVDRV_PCIE_LINK_STATUS_DOWN;
ltssm_st = (mac_reg_link_value >> PCIE_MAC_REG_LINK_LTSSM_ST_OFFSET) & 0x3F;
if (ltssm_st == PCIE_MAC_REG_LINK_LTSSM_L0) {
pcie_link_info->link_status = DEVDRV_PCIE_LINK_STATUS_OK;
} else {
ka_mm_iounmap(apb_base);
apb_base = NULL;
devdrv_limit_exclusive(warn, DEVDRV_LIMIT_LOG_0x00, "read mac reg link. (reg_value=0x%x)\n",
mac_reg_link_value);
return 0;
}
pcie_link_info->rate_mode = (mac_reg_link_value >> PCIE_MAC_REG_LINK_SPEED_OFFSET) & 0xF;
pcie_link_info->lane_num = mac_reg_link_value & 0x3F;
ka_mm_iounmap(apb_base);
apb_base = NULL;
return 0;
}
int devdrv_get_pcie_link_info(u32 udevid, struct devdrv_pcie_link_info_para* pcie_link_info)
{
int ret;
if (pcie_link_info == NULL) {
devdrv_err("pcie_link_info is NULL.\n");
return -EINVAL;
}
ret = devdrv_get_pcie_mac_link_info(pcie_link_info);
if (ret != 0) {
devdrv_err("get mac link info err. (ret=%d, udevid=%u)\n", ret, udevid);
return ret;
}
if (pcie_link_info->link_status == DEVDRV_PCIE_LINK_STATUS_DOWN) {
devdrv_pcie_dump_ltssm_tracer();
return 0;
}
if (g_pcie_channel_status != DEVDRV_PCIE_COMMON_CHANNEL_OK) {
pcie_link_info->link_status = DEVDRV_PCIE_LINK_STATUS_CHANNEL_ERR;
}
return 0;
}
KA_EXPORT_SYMBOL(devdrv_get_pcie_link_info);
int devdrv_set_err_out_gpio(void)
{
int ret;
ret = gpio_request(PCIE_ERR_OUT_GPIO5_00, NULL);
if (ret != 0) {
devdrv_err("gpio request fail. (ret=%d)\n", ret);
return ret;
}
ret = gpio_direction_output(PCIE_ERR_OUT_GPIO5_00, 1);
if (ret != 0) {
devdrv_err("gpio set direction fail. (ret=%d)\n", ret);
gpio_free(PCIE_ERR_OUT_GPIO5_00);
return ret;
}
gpio_free(PCIE_ERR_OUT_GPIO5_00);
return 0;
}
STATIC int devdrv_set_pcie_linkdown(void)
{
void __ka_mm_iomem *apb_base = NULL;
u32 tmp;
apb_base = ka_mm_ioremap(PCIE_BASE_ADDR, PCIE_REG_SIZE);
if (apb_base == NULL) {
devdrv_err("ka_mm_ioremap fail, apb_base is NULL.\n");
return -ENOMEM;
}
tmp = ka_mm_readl(apb_base + PCIE_MAC_REG_BASE + PCIE_MAC_REG_MAC_INT_MASK_OFFSET);
tmp |= 0x2U;
ka_mm_writel(tmp, apb_base + PCIE_MAC_REG_BASE + PCIE_MAC_REG_MAC_INT_MASK_OFFSET);
ka_mm_writel(0U, apb_base + PCIE_CORE_GLOBAL_REG_BASE + PCIE_PORT_EN_OFFSET);
ka_mm_iounmap(apb_base);
return 0;
}
int devdrv_force_linkdown_inner(u32 index_id)
{
struct devdrv_pci_ctrl *pci_ctrl;
int ret;
if (index_id >= MAX_DEV_CNT) {
devdrv_err("index_id invalid\n");
return -EINVAL;
}
ret = devdrv_set_pcie_linkdown();
if (ret != 0) {
return ret;
}
(void)devdrv_set_err_out_gpio();
devdrv_peer_fault_notifier(DEVDRV_PEER_STATUS_LINKDOWN);
devdrv_set_peer_pcie_status(DEVDRV_PEER_STATUS_LINKDOWN);
devdrv_set_pcie_channel_status(DEVDRV_PCIE_COMMON_CHANNEL_LINKDOWN);
pci_ctrl = devdrv_pci_ctrl_get(index_id);
if (pci_ctrl == NULL) {
devdrv_warn("pcie not ready\n");
return 0;
}
devdrv_set_device_status(pci_ctrl, DEVDRV_DEVICE_DEAD);
if (pci_ctrl->dma_dev != NULL) {
devdrv_set_dma_status(pci_ctrl->dma_dev, DEVDRV_DMA_DEAD);
devdrv_dma_stop_business((unsigned long)(uintptr_t)pci_ctrl->dma_dev);
}
devdrv_pci_ctrl_put(pci_ctrl);
return 0;
}
int devdrv_force_linkdown(u32 udevid)
{
u32 index_id;
(void)uda_udevid_to_add_id(udevid, &index_id);
return devdrv_force_linkdown_inner(index_id);
}
KA_EXPORT_SYMBOL(devdrv_force_linkdown);