| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Copyright (c) 2023 MediaTek Inc. All Rights Reserved. |
| * |
| * Author: Alvin Kuo <alvin.kuog@mediatek.com>, |
| */ |
| |
| #include <linux/interrupt.h> |
| #include <linux/io.h> |
| |
| #include "internal.h" |
| #include "mbox.h" |
| #include "ser.h" |
| #include "trm.h" |
| #include "wdt.h" |
| |
| #define WDT_IRQ_STATUS 0x0140B0 |
| #define TOP_WDT_MODE 0x012000 |
| #define CLUST_WDT_MODE(x) (0x512000 + 0x100 * (x)) |
| |
| #define WDT_MODE_KEY 0x22000000 |
| |
| struct watchdog_hw { |
| void __iomem *base; |
| struct mailbox_dev mgmt_mdev; |
| struct mailbox_dev offload_mdev[CORE_OFFLOAD_NUM]; |
| }; |
| |
| static struct watchdog_hw wdt = { |
| .mgmt_mdev = MBOX_SEND_MGMT_DEV(WDT), |
| .offload_mdev = { |
| [CORE_OFFLOAD_0] = MBOX_SEND_OFFLOAD_DEV(0, WDT), |
| [CORE_OFFLOAD_1] = MBOX_SEND_OFFLOAD_DEV(1, WDT), |
| [CORE_OFFLOAD_2] = MBOX_SEND_OFFLOAD_DEV(2, WDT), |
| [CORE_OFFLOAD_3] = MBOX_SEND_OFFLOAD_DEV(3, WDT), |
| }, |
| }; |
| |
| static inline void wdt_write(u32 reg, u32 val) |
| { |
| writel(val, wdt.base + reg); |
| } |
| |
| static inline void wdt_set(u32 reg, u32 mask) |
| { |
| setbits(wdt.base + reg, mask); |
| } |
| |
| static inline void wdt_clr(u32 reg, u32 mask) |
| { |
| clrbits(wdt.base + reg, mask); |
| } |
| |
| static inline void wdt_rmw(u32 reg, u32 mask, u32 val) |
| { |
| clrsetbits(wdt.base + reg, mask, val); |
| } |
| |
| static inline u32 wdt_read(u32 reg) |
| { |
| return readl(wdt.base + reg); |
| } |
| |
| static inline void wdt_irq_clr(u32 wdt_mode_reg) |
| { |
| wdt_set(wdt_mode_reg, WDT_MODE_KEY); |
| } |
| |
| static void wdt_ser_callback(struct tops_ser_params *ser_params) |
| { |
| if (ser_params->type != TOPS_SER_WDT_TO) |
| WARN_ON(1); |
| |
| mtk_trm_dump(ser_params->data.wdt.timeout_cores); |
| } |
| |
| static void wdt_ser_mcmd_setup(struct tops_ser_params *ser_params, |
| struct mcu_ctrl_cmd *mcmd) |
| { |
| mcmd->core_mask = (~ser_params->data.wdt.timeout_cores) & CORE_TOPS_MASK; |
| } |
| |
| static irqreturn_t wdt_irq_handler(int irq, void *dev_id) |
| { |
| struct tops_ser_params ser_params = { |
| .type = TOPS_SER_WDT_TO, |
| .ser_callback = wdt_ser_callback, |
| .ser_mcmd_setup = wdt_ser_mcmd_setup, |
| }; |
| u32 status; |
| u32 i; |
| |
| status = wdt_read(WDT_IRQ_STATUS); |
| if (status) { |
| ser_params.data.wdt.timeout_cores = status; |
| mtk_tops_ser(&ser_params); |
| |
| /* clear wdt irq */ |
| if (status & BIT(CORE_MGMT)) |
| wdt_irq_clr(TOP_WDT_MODE); |
| |
| for (i = 0; i < CORE_OFFLOAD_NUM; i++) |
| if (status & BIT(i)) |
| wdt_irq_clr(CLUST_WDT_MODE(i)); |
| } |
| TOPS_ERR("WDT Timeout: 0x%x\n", status); |
| |
| return IRQ_HANDLED; |
| } |
| |
| int mtk_tops_wdt_trigger_timeout(enum core_id core) |
| { |
| struct mailbox_msg msg = { |
| .msg1 = WDT_CMD_TRIGGER_TIMEOUT, |
| }; |
| |
| if (core == CORE_MGMT) |
| mbox_send_msg_no_wait(&wdt.mgmt_mdev, &msg); |
| else |
| mbox_send_msg_no_wait(&wdt.offload_mdev[core], &msg); |
| |
| return 0; |
| } |
| |
| static int mtk_tops_wdt_register_mbox(void) |
| { |
| int ret; |
| int i; |
| |
| ret = register_mbox_dev(MBOX_SEND, &wdt.mgmt_mdev); |
| if (ret) { |
| TOPS_ERR("register wdt mgmt mbox send failed: %d\n", ret); |
| return ret; |
| } |
| |
| for (i = 0; i < CORE_OFFLOAD_NUM; i++) { |
| ret = register_mbox_dev(MBOX_SEND, &wdt.offload_mdev[i]); |
| if (ret) { |
| TOPS_ERR("register wdt offload %d mbox send failed: %d\n", |
| i, ret); |
| goto err_unregister_offload_mbox; |
| } |
| } |
| |
| return ret; |
| |
| err_unregister_offload_mbox: |
| for (i -= 1; i >= 0; i--) |
| unregister_mbox_dev(MBOX_SEND, &wdt.offload_mdev[i]); |
| |
| unregister_mbox_dev(MBOX_SEND, &wdt.mgmt_mdev); |
| |
| return ret; |
| } |
| |
| static void mtk_tops_wdt_unregister_mbox(void) |
| { |
| int i; |
| |
| unregister_mbox_dev(MBOX_SEND, &wdt.mgmt_mdev); |
| |
| for (i = 0; i < CORE_OFFLOAD_NUM; i++) |
| unregister_mbox_dev(MBOX_SEND, &wdt.offload_mdev[i]); |
| } |
| |
| int mtk_tops_wdt_init(struct platform_device *pdev) |
| { |
| struct resource *res = NULL; |
| int ret; |
| int irq; |
| |
| res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tops-base"); |
| if (!res) |
| return -ENXIO; |
| |
| wdt.base = devm_ioremap(tops_dev, res->start, resource_size(res)); |
| if (!wdt.base) |
| return -ENOMEM; |
| |
| irq = platform_get_irq_byname(pdev, "wdt"); |
| if (irq < 0) { |
| TOPS_ERR("get wdt irq failed\n"); |
| return irq; |
| } |
| |
| ret = devm_request_irq(tops_dev, irq, |
| wdt_irq_handler, |
| IRQF_ONESHOT, |
| pdev->name, NULL); |
| if (ret) { |
| TOPS_ERR("request wdt irq failed\n"); |
| return ret; |
| } |
| |
| ret = mtk_tops_wdt_register_mbox(); |
| if (ret) |
| return ret; |
| |
| return ret; |
| } |
| |
| int mtk_tops_wdt_deinit(struct platform_device *pdev) |
| { |
| mtk_tops_wdt_unregister_mbox(); |
| |
| return 0; |
| } |