Add driver for control APU tinysys APU integrated subsystem having MD32RV33 (MD32) that runs tinysys The tinsys is running on a micro processor in APU. Its firmware is load and boot from Kernel side. Kernel and tinysys use IPI to tx/rx messages. Signed-off-by: Flora Fu <flora.fu@xxxxxxxxxxxx> --- drivers/soc/mediatek/apusys/Makefile | 6 + drivers/soc/mediatek/apusys/apu-config.h | 100 +++ drivers/soc/mediatek/apusys/apu-core.c | 2 + drivers/soc/mediatek/apusys/apu-core.h | 2 + drivers/soc/mediatek/apusys/apu-ipi.c | 486 ++++++++++++ drivers/soc/mediatek/apusys/apu-mbox.c | 83 ++ drivers/soc/mediatek/apusys/apu-mbox.h | 27 + drivers/soc/mediatek/apusys/apu-rproc.c | 806 ++++++++++++++++++++ drivers/soc/mediatek/apusys/apu-sw-logger.c | 429 +++++++++++ drivers/soc/mediatek/apusys/apu.h | 256 +++++++ drivers/soc/mediatek/apusys/mt81xx-plat.c | 320 ++++++++ 11 files changed, 2517 insertions(+) create mode 100644 drivers/soc/mediatek/apusys/apu-config.h create mode 100644 drivers/soc/mediatek/apusys/apu-ipi.c create mode 100644 drivers/soc/mediatek/apusys/apu-mbox.c create mode 100644 drivers/soc/mediatek/apusys/apu-mbox.h create mode 100644 drivers/soc/mediatek/apusys/apu-rproc.c create mode 100644 drivers/soc/mediatek/apusys/apu-sw-logger.c create mode 100644 drivers/soc/mediatek/apusys/apu.h create mode 100644 drivers/soc/mediatek/apusys/mt81xx-plat.c diff --git a/drivers/soc/mediatek/apusys/Makefile b/drivers/soc/mediatek/apusys/Makefile index 8c61ed8e2c04..490de0ee4727 100644 --- a/drivers/soc/mediatek/apusys/Makefile +++ b/drivers/soc/mediatek/apusys/Makefile @@ -7,3 +7,9 @@ apu-objs += apu-core.o apu-objs += apu-pwr.o apu-objs += apu-pwr-ipi.o apu-$(CONFIG_MTK_APU_DEBUG) += apu-pwr-dbg.o + +apu-objs += apu-rproc.o +apu-objs += apu-ipi.o +apu-objs += apu-mbox.o +apu-objs += mt81xx-plat.o +apu-$(CONFIG_DEBUG_FS) += apu-sw-logger.o diff --git a/drivers/soc/mediatek/apusys/apu-config.h b/drivers/soc/mediatek/apusys/apu-config.h new file mode 100644 index 000000000000..fee3b0334502 --- /dev/null +++ b/drivers/soc/mediatek/apusys/apu-config.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021 MediaTek Inc. + */ + +#ifndef APU_CONFIG_H +#define APU_CONFIG_H + +struct apu_ipi_config { + u64 in_buf_da; + u64 out_buf_da; +} __packed; + +struct vpu_init_info { + u32 vpu_num; + u32 cfg_addr; + u32 cfg_size; + u32 algo_info_ptr[3 * 2]; + u32 rst_vec[3]; + u32 dmem_addr[3]; + u32 imem_addr[3]; + u32 iram_addr[3]; + u32 cmd_addr[3]; + u32 log_addr[3]; + u32 log_size[3]; +} __packed; + +struct apusys_chip_data { + u32 s_code; + u32 b_code; + u32 r_code; + u32 a_code; +} __packed; + +struct logger_init_info { + u32 iova; +} __packed; + +struct reviser_init_info { + u32 boundary; + u32 dram[32]; +} __packed; + +enum user_config { + APU_IPI_CONFIG = 0x0, + VPU_INIT_INFO, + APUSYS_CHIP_DATA, + LOGGER_INIT_INFO, + REVISER_INIT_INFO, + USER_CONFIG_MAX +}; + +struct config_v1_entry_table { + u32 user_entry[USER_CONFIG_MAX]; +} __packed; + +struct config_v1 { + /* header begin */ + u32 header_magic; + u32 header_rev; + u32 entry_offset; + u32 config_size; + /* header end */ + /* do not add new member before this line */ + + /* system related config begin */ + u32 ramdump_offset; + u32 ramdump_type; + u64 time_offset; + /* system related config end */ + + /* entry table */ + u8 entry_tbl[sizeof(struct config_v1_entry_table)]; + + /* user data payload begin */ + u8 user0_data[sizeof(struct apu_ipi_config)]; + u8 user1_data[sizeof(struct vpu_init_info)]; + u8 user2_data[sizeof(struct apusys_chip_data)]; + u8 user3_data[sizeof(struct logger_init_info)]; + u8 user4_data[sizeof(struct reviser_init_info)]; + /* user data payload end */ +} __packed; + +static inline void *get_apu_config_user_ptr(struct config_v1 *conf, + enum user_config user_id) +{ + struct config_v1_entry_table *entry_tbl; + + if (!conf) + return NULL; + + if (user_id >= USER_CONFIG_MAX) + return NULL; + + entry_tbl = (struct config_v1_entry_table *) + ((void *)conf + conf->entry_offset); + + return (void *)conf + entry_tbl->user_entry[user_id]; +} +#endif /* APU_CONFIG_H */ diff --git a/drivers/soc/mediatek/apusys/apu-core.c b/drivers/soc/mediatek/apusys/apu-core.c index 069e18af7a5b..80652b1d056e 100644 --- a/drivers/soc/mediatek/apusys/apu-core.c +++ b/drivers/soc/mediatek/apusys/apu-core.c @@ -19,6 +19,7 @@ static struct apusys_core_info g_core_info; */ static int (*apusys_init_func[])(struct apusys_core_info *) = { apu_power_drv_init, + apu_rproc_init, }; /* @@ -26,6 +27,7 @@ static int (*apusys_init_func[])(struct apusys_core_info *) = { * call exit function in order at apu.ko exit stage */ static void (*apusys_exit_func[])(void) = { + apu_rproc_exit, apu_power_drv_exit, }; diff --git a/drivers/soc/mediatek/apusys/apu-core.h b/drivers/soc/mediatek/apusys/apu-core.h index 77f1b39424d1..b47d95f0a1ae 100644 --- a/drivers/soc/mediatek/apusys/apu-core.h +++ b/drivers/soc/mediatek/apusys/apu-core.h @@ -11,4 +11,6 @@ struct apusys_core_info { int apu_power_drv_init(struct apusys_core_info *info); void apu_power_drv_exit(void); +int apu_rproc_init(struct apusys_core_info *info); +void apu_rproc_exit(void); #endif diff --git a/drivers/soc/mediatek/apusys/apu-ipi.c b/drivers/soc/mediatek/apusys/apu-ipi.c new file mode 100644 index 000000000000..547e034b3620 --- /dev/null +++ b/drivers/soc/mediatek/apusys/apu-ipi.c @@ -0,0 +1,486 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2021 MediaTek Inc. + */ + +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/lockdep.h> +#include <linux/time64.h> + +#include "apu.h" +#include "apu-config.h" +#include "apu-mbox.h" + +#define IPI_BUF_SIZE (round_up(sizeof(struct mtk_share_obj) * 2, PAGE_SIZE)) + +static struct lock_class_key ipi_lock_key[APU_IPI_MAX]; +static unsigned int tx_serial_no; +static unsigned int rx_serial_no; + +#if IS_ENABLED(CONFIG_MTK_APU_DEBUG) +static inline void dump_msg_buf(struct mtk_apu *apu, void *data, u32 len) +{ + struct device *dev = apu->dev; + u32 i; + int size = 64, num; + u8 buf[64], *ptr = buf; + int ret; + + dev_info(dev, "===== dump message =====\n"); + for (i = 0; i < len; i++) { + num = snprintf(ptr, size, "%02x ", ((u8 *)data)[i]); + if (num <= 0) { + dev_info(dev, "%s: snprintf return error(num = %d)\n", + __func__, num); + return; + } + size -= num; + ptr += num; + + if ((i + 1) % 4 == 0) { + ret = snprintf(ptr++, size--, " "); + if (ret <= 0) { + dev_info(dev, "%s: snprintf return error(ret = %d)\n", + __func__, ret); + return; + } + } + + if ((i + 1) % 16 == 0 || (i + 1) >= len) { + dev_info(dev, "%s\n", buf); + size = 64; + ptr = buf; + } + } + dev_info(dev, "========================\n"); +} +#endif + +static u32 calculate_csum(void *data, u32 len) +{ + u32 csum = 0, res = 0, i; + u8 *ptr; + + for (i = 0; i < (len / sizeof(csum)); i++) + csum += *(((u32 *)data) + i); + + ptr = (u8 *)data + len / sizeof(csum) * sizeof(csum); + for (i = 0; i < (len % sizeof(csum)); i++) + res |= *(ptr + i) << i * 8; + + csum += res; + + return csum; +} + +static inline bool bypass_check(u32 id) +{ + /* whitelist IPI used in power off flow */ + return id == APU_IPI_DEEP_IDLE; +} + +static void ipi_usage_cnt_update(struct mtk_apu *apu, u32 id, int diff) +{ + struct apu_ipi_desc *ipi = &apu->ipi_desc[id]; + + if (apu->platdata->ipi_attrs[id].ack != IPI_WITH_ACK) + return; + + spin_lock(&apu->usage_cnt_lock); + ipi->usage_cnt += diff; + spin_unlock(&apu->usage_cnt_lock); +} + +int apu_ipi_send(struct mtk_apu *apu, u32 id, void *data, u32 len, + u32 wait_ms) +{ + struct timespec64 ts, te; + struct apu_mbox_hdr hdr; + unsigned long timeout; + int ret = 0; + + ktime_get_ts64(&ts); + + if (!apu || + id <= APU_IPI_INIT || + id >= APU_IPI_MAX || + id == APU_IPI_NS_SERVICE || + len > APU_SHARE_BUFFER_SIZE || + !data) + return -EINVAL; + + mutex_lock(&apu->send_lock); + if (apu->platdata->ipi_attrs[id].direction == IPI_HOST_INITIATE && + apu->ipi_inbound_locked == IPI_LOCKED && !bypass_check(id)) { + dev_info(apu->dev, "%s: ipi locked, ipi=%d\n", __func__, id); + mutex_unlock(&apu->send_lock); + return -EBUSY; + } + + /* re-init inbox mask everytime for aoc */ + apu_mbox_inbox_init(apu); + apu_deepidle_power_on_aputop(apu); + ret = apu_mbox_wait_inbox(apu); + if (ret) { + dev_info(apu->dev, "wait inbox fail, ret=%d\n", ret); + goto unlock_mutex; + } + + /* copy message payload to share buffer, need to do cache flush if + * the buffer is cacheable. currently not + */ + memcpy_toio(apu->send_buf, data, len); + + hdr.id = id; + hdr.len = len; + hdr.csum = calculate_csum(data, len); + hdr.serial_no = tx_serial_no++; + + apu_mbox_write_inbox(apu, &hdr); + apu->ipi_id_ack[id] = false; + + /* poll ack from remote processor if wait_ms specified */ + if (wait_ms) { + timeout = jiffies + msecs_to_jiffies(wait_ms); + ret = wait_event_timeout(apu->ack_wq, + &apu->ipi_id_ack[id], + timeout); + + apu->ipi_id_ack[id] = false; + + if (WARN(!ret, "apu ipi %d ack timeout!", id)) { + ret = -EIO; + goto unlock_mutex; + } else { + ret = 0; + } + } + ipi_usage_cnt_update(apu, id, 1); + +unlock_mutex: + mutex_unlock(&apu->send_lock); + ktime_get_ts64(&te); + ts = timespec64_sub(te, ts); + + return ret; +} + +int apu_ipi_lock(struct mtk_apu *apu) +{ + struct apu_ipi_desc *ipi; + int i; + bool ready_to_lock = true; + + if (mutex_trylock(&apu->send_lock) == 0) + return -EBUSY; + + if (apu->ipi_inbound_locked == IPI_LOCKED) { + dev_info(apu->dev, "%s: ipi already locked\n", __func__); + mutex_unlock(&apu->send_lock); + return 0; + } + + spin_lock(&apu->usage_cnt_lock); + for (i = 0; i < APU_IPI_MAX; i++) { + ipi = &apu->ipi_desc[i]; + + if (apu->platdata->ipi_attrs[i].ack == IPI_WITH_ACK && + ipi->usage_cnt != 0 && + !bypass_check(i)) { + dev_info(apu->dev, "%s: ipi %d is still in use %d\n", + __func__, i, ipi->usage_cnt); + ready_to_lock = false; + } + } + + if (!ready_to_lock) { + spin_unlock(&apu->usage_cnt_lock); + mutex_unlock(&apu->send_lock); + return -EBUSY; + } + + apu->ipi_inbound_locked = IPI_LOCKED; + spin_unlock(&apu->usage_cnt_lock); + mutex_unlock(&apu->send_lock); + + return 0; +} + +void apu_ipi_unlock(struct mtk_apu *apu) +{ + mutex_lock(&apu->send_lock); + if (apu->ipi_inbound_locked == IPI_UNLOCKED) + dev_info(apu->dev, "%s: ipi already unlocked\n", __func__); + + spin_lock(&apu->usage_cnt_lock); + apu->ipi_inbound_locked = IPI_UNLOCKED; + spin_unlock(&apu->usage_cnt_lock); + mutex_unlock(&apu->send_lock); +} + +int apu_ipi_register(struct mtk_apu *apu, u32 id, + ipi_handler_t handler, void *priv) +{ + if (!apu || id >= APU_IPI_MAX || WARN_ON(!handler)) { + if (apu) + dev_info(apu->dev, + "%s failed. apu=%p, id=%d, handler=%p, priv=%p\n", + __func__, apu, id, handler, priv); + return -EINVAL; + } + + mutex_lock(&apu->ipi_desc[id].lock); + apu->ipi_desc[id].handler = handler; + apu->ipi_desc[id].priv = priv; + mutex_unlock(&apu->ipi_desc[id].lock); + + return 0; +} + +void apu_ipi_unregister(struct mtk_apu *apu, u32 id) +{ + if (!apu || id >= APU_IPI_MAX) { + if (apu) + dev_info(apu->dev, "%s: invalid id=%d\n", __func__, id); + return; + } + + mutex_lock(&apu->ipi_desc[id].lock); + apu->ipi_desc[id].handler = NULL; + apu->ipi_desc[id].priv = NULL; + mutex_unlock(&apu->ipi_desc[id].lock); +} + +static void apu_init_ipi_handler(void *data, unsigned int len, void *priv) +{ + struct mtk_apu *apu = priv; + struct apu_run *run = data; + struct device *dev = apu->dev; + + strscpy(apu->run.fw_ver, data, APU_FW_VER_LEN); + apu->run.signaled = 1; + wake_up_interruptible(&apu->run.wq); + dev_info(dev, "fw_ver: %s\n", run->fw_ver); +} + +static irqreturn_t apu_ipi_handler(int irq, void *priv) +{ + struct timespec64 ts, te; + struct mtk_apu *apu = priv; + struct apu_mbox_hdr hdr; + struct mtk_share_obj *recv_obj = apu->recv_buf; + ipi_handler_t handler; + u32 id, len, calc_csum; + u32 temp_buf[APU_SHARE_BUFFER_SIZE / 4] = {0}; + + hdr.id = 0; + hdr.len = 0; + hdr.serial_no = 0; + hdr.csum = 0; + + ktime_get_ts64(&ts); + apu_mbox_read_outbox(apu, &hdr); + id = hdr.id; + len = hdr.len; + if (hdr.serial_no != rx_serial_no) { + dev_info(apu->dev, "unmatched serial_no: curr=%u, recv=%u\n", + rx_serial_no, hdr.serial_no); + dev_info(apu->dev, "outbox irq=%x\n", + ioread32(apu->apu_mbox + 0xc4)); + if (ioread32(apu->apu_mbox + 0xc4) == 0) { + dev_info(apu->dev, "abnormal isr call, skip\n"); + goto ack_irq; + } + } + rx_serial_no++; + + if (len > APU_SHARE_BUFFER_SIZE) { + dev_info(apu->dev, "IPI message too long(len %d, max %d)", + len, APU_SHARE_BUFFER_SIZE); + goto ack_irq; + } + + if (id >= APU_IPI_MAX) { + dev_info(apu->dev, "no such IPI id = %d", id); + goto ack_irq; + } + + mutex_lock(&apu->ipi_desc[id].lock); + handler = apu->ipi_desc[id].handler; + if (!handler) { + dev_info(apu->dev, "IPI id=%d is not registered", id); + mutex_unlock(&apu->ipi_desc[id].lock); + goto ack_irq; + } + + memcpy_fromio(temp_buf, &recv_obj->share_buf, len); + + calc_csum = calculate_csum(temp_buf, len); + if (calc_csum != hdr.csum) { + dev_info(apu->dev, "csum error: recv=0x%08x, calc=0x%08x\n", + hdr.csum, calc_csum); +#if IS_ENABLED(CONFIG_MTK_APU_DEBUG) + dump_msg_buf(apu, temp_buf, hdr.len); +#endif + } + + handler(temp_buf, len, apu->ipi_desc[id].priv); + ipi_usage_cnt_update(apu, id, -1); + mutex_unlock(&apu->ipi_desc[id].lock); + + apu->ipi_id_ack[id] = true; + wake_up(&apu->ack_wq); + +ack_irq: + apu_mbox_ack_outbox(apu); + ktime_get_ts64(&te); + ts = timespec64_sub(te, ts); + + return IRQ_HANDLED; +} + +static int apu_send_ipi(struct platform_device *pdev, u32 id, void *buf, + unsigned int len, unsigned int wait) +{ + struct mtk_apu *apu = platform_get_drvdata(pdev); + + return apu_ipi_send(apu, id, buf, len, wait); +} + +static int apu_register_ipi(struct platform_device *pdev, u32 id, + ipi_handler_t handler, void *priv) +{ + struct mtk_apu *apu = platform_get_drvdata(pdev); + + return apu_ipi_register(apu, id, handler, priv); +} + +static void apu_unregister_ipi(struct platform_device *pdev, u32 id) +{ + struct mtk_apu *apu = platform_get_drvdata(pdev); + + apu_ipi_unregister(apu, id); +} + +static struct mtk_rpmsg_info apu_rpmsg_info = { + .send_ipi = apu_send_ipi, + .register_ipi = apu_register_ipi, + .unregister_ipi = apu_unregister_ipi, + .ns_ipi_id = APU_IPI_NS_SERVICE, +}; + +static void apu_add_rpmsg_subdev(struct mtk_apu *apu) +{ + struct platform_device *pdev = to_platform_device(apu->dev); + + apu->rpmsg_subdev = mtk_rpmsg_create_rproc_subdev(pdev, + &apu_rpmsg_info); + if (apu->rpmsg_subdev) + rproc_add_subdev(apu->rproc, apu->rpmsg_subdev); +} + +static void apu_remove_rpmsg_subdev(struct mtk_apu *apu) +{ + if (apu->rpmsg_subdev) { + rproc_remove_subdev(apu->rproc, apu->rpmsg_subdev); + mtk_rpmsg_destroy_rproc_subdev(apu->rpmsg_subdev); + apu->rpmsg_subdev = NULL; + } +} + +void apu_ipi_config_remove(struct mtk_apu *apu) +{ + dma_free_coherent(apu->dev, IPI_BUF_SIZE, + apu->recv_buf, apu->recv_buf_da); +} + +int apu_ipi_config_init(struct mtk_apu *apu) +{ + struct device *dev = apu->dev; + struct apu_ipi_config *ipi_config; + void *ipi_buf = NULL; + dma_addr_t ipi_buf_da = 0; + + ipi_config = (struct apu_ipi_config *) + get_apu_config_user_ptr(apu->conf_buf, APU_IPI_CONFIG); + + /* initialize shared buffer */ + ipi_buf = dma_alloc_coherent(dev, IPI_BUF_SIZE, + &ipi_buf_da, GFP_KERNEL); + if (!ipi_buf || !ipi_buf_da) { + dev_info(dev, "failed to allocate ipi share memory\n"); + return -ENOMEM; + } + + memset_io(ipi_buf, 0, sizeof(struct mtk_share_obj) * 2); + apu->recv_buf = ipi_buf; + apu->recv_buf_da = ipi_buf_da; + apu->send_buf = ipi_buf + sizeof(struct mtk_share_obj); + apu->send_buf_da = ipi_buf_da + sizeof(struct mtk_share_obj); + ipi_config->in_buf_da = apu->send_buf_da; + ipi_config->out_buf_da = apu->recv_buf_da; + + return 0; +} + +void apu_ipi_remove(struct mtk_apu *apu) +{ + apu_mbox_hw_exit(apu); + apu_remove_rpmsg_subdev(apu); + apu_ipi_unregister(apu, APU_IPI_INIT); +} + +int apu_ipi_init(struct platform_device *pdev, struct mtk_apu *apu) +{ + struct device *dev = apu->dev; + int i, ret; + + tx_serial_no = 0; + rx_serial_no = 0; + + mutex_init(&apu->send_lock); + spin_lock_init(&apu->usage_cnt_lock); + for (i = 0; i < APU_IPI_MAX; i++) { + mutex_init(&apu->ipi_desc[i].lock); + lockdep_set_class_and_name(&apu->ipi_desc[i].lock, + &ipi_lock_key[i], + apu->platdata->ipi_attrs[i].name); + } + + init_waitqueue_head(&apu->run.wq); + init_waitqueue_head(&apu->ack_wq); + + /* APU initialization IPI register */ + ret = apu_ipi_register(apu, APU_IPI_INIT, apu_init_ipi_handler, apu); + if (ret) { + dev_err(dev, "failed to register ipi for init, ret=%d\n", + ret); + return ret; + } + + /* add rpmsg subdev */ + apu_add_rpmsg_subdev(apu); + + /* register mailbox IRQ */ + apu->mbox0_irq_number = platform_get_irq_byname(pdev, "mbox0_irq"); + dev_info(&pdev->dev, "%s: mbox0_irq = %d\n", __func__, + apu->mbox0_irq_number); + + ret = devm_request_threaded_irq(&pdev->dev, apu->mbox0_irq_number, + NULL, apu_ipi_handler, IRQF_ONESHOT, + "apu_ipi", apu); + if (ret < 0) + goto remove_rpmsg_subdev; + + apu_mbox_hw_init(apu); + + return 0; + +remove_rpmsg_subdev: + apu_remove_rpmsg_subdev(apu); + apu_ipi_unregister(apu, APU_IPI_INIT); + + return ret; +} diff --git a/drivers/soc/mediatek/apusys/apu-mbox.c b/drivers/soc/mediatek/apusys/apu-mbox.c new file mode 100644 index 000000000000..dfd41f7c2640 --- /dev/null +++ b/drivers/soc/mediatek/apusys/apu-mbox.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2021 MediaTek Inc. + */ + +#include <linux/jiffies.h> + +#include "apu.h" +#include "apu-mbox.h" + +#define _INBOX(_m) ((_m)->apu_mbox + 0x0) +#define _OUTBOX(_m) ((_m)->apu_mbox + 0x20) +#define _DUMMY(_m) ((_m)->apu_mbox + 0x40) +#define _INBOX_IRQ(_m) ((_m)->apu_mbox + 0xc0) +#define _OUTBOX_IRQ(_m) ((_m)->apu_mbox + 0xc4) +#define _INBOX_IRQ_MASK(_m) ((_m)->apu_mbox + 0xd0) +#define _OUTBOX_IRQ_MASK(_m) ((_m)->apu_mbox + 0xd8) + +void apu_mbox_ack_outbox(struct mtk_apu *apu) +{ + iowrite32(ioread32(_OUTBOX_IRQ(apu)), + _OUTBOX_IRQ(apu)); +} + +void apu_mbox_read_outbox(struct mtk_apu *apu, struct apu_mbox_hdr *hdr) +{ + unsigned int i, val; + + for (i = 0; i < APU_MBOX_HDR_SLOTS; i++) { + val = ioread32(_OUTBOX(apu) + i * APU_MBOX_SLOT_SIZE); + ((unsigned int *)hdr)[i] = val; + } +} + +int apu_mbox_wait_inbox(struct mtk_apu *apu) +{ + unsigned long timeout; + unsigned char irq, mask; + + timeout = jiffies + msecs_to_jiffies(1000); + do { + if (time_after(jiffies, timeout)) { + dev_info(apu->dev, "timeout.\n"); + return -ETIMEDOUT; + } + + irq = ioread32(_INBOX_IRQ(apu)); + mask = ioread32(_INBOX_IRQ_MASK(apu)); + + } while (irq & ~mask); + + return 0; +} + +void apu_mbox_write_inbox(struct mtk_apu *apu, struct apu_mbox_hdr *hdr) +{ + unsigned int i, val; + + for (i = 0; i < APU_MBOX_HDR_SLOTS; i++) { + val = ((unsigned int *)hdr)[i]; + iowrite32(val, _INBOX(apu) + i * APU_MBOX_SLOT_SIZE); + } +} + +void apu_mbox_inbox_init(struct mtk_apu *apu) +{ + iowrite32(~(1 << (APU_MBOX_HDR_SLOTS - 1)), + _INBOX_IRQ_MASK(apu)); +} + +void apu_mbox_hw_init(struct mtk_apu *apu) +{ + apu_mbox_inbox_init(apu); + + /* clear outbox IRQ */ + apu_mbox_ack_outbox(apu); +} + +void apu_mbox_hw_exit(struct mtk_apu *apu) +{ + /* mask inbox IRQ */ + iowrite32(0xff, _INBOX_IRQ_MASK(apu)); +} diff --git a/drivers/soc/mediatek/apusys/apu-mbox.h b/drivers/soc/mediatek/apusys/apu-mbox.h new file mode 100644 index 000000000000..47c48d2a1c25 --- /dev/null +++ b/drivers/soc/mediatek/apusys/apu-mbox.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021 MediaTek Inc. + */ + +#ifndef APU_MBOX_H +#define APU_MBOX_H + +struct apu_mbox_hdr { + unsigned int id; + unsigned int len; + unsigned int serial_no; + unsigned int csum; +}; + +#define APU_MBOX_SLOT_SIZE (4) +#define APU_MBOX_HDR_SLOTS \ + (sizeof(struct apu_mbox_hdr) / APU_MBOX_SLOT_SIZE) + +void apu_mbox_ack_outbox(struct mtk_apu *apu); +void apu_mbox_read_outbox(struct mtk_apu *apu, struct apu_mbox_hdr *hdr); +int apu_mbox_wait_inbox(struct mtk_apu *apu); +void apu_mbox_write_inbox(struct mtk_apu *apu, struct apu_mbox_hdr *hdr); +void apu_mbox_inbox_init(struct mtk_apu *apu); +void apu_mbox_hw_init(struct mtk_apu *apu); +void apu_mbox_hw_exit(struct mtk_apu *apu); +#endif /* APU_MBOX_H */ diff --git a/drivers/soc/mediatek/apusys/apu-rproc.c b/drivers/soc/mediatek/apusys/apu-rproc.c new file mode 100644 index 000000000000..e2fe63dd6cc1 --- /dev/null +++ b/drivers/soc/mediatek/apusys/apu-rproc.c @@ -0,0 +1,806 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2021 MediaTek Inc. + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/iommu.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/remoteproc.h> +#include <linux/time64.h> +#include <linux/of_platform.h> +#include <linux/pm_domain.h> +#include <linux/pm_runtime.h> +#include <linux/sched/clock.h> +#include <linux/time64.h> +#include <linux/workqueue.h> + +#include "apu.h" +#include "apu-config.h" +#include "apu-core.h" + +/* cmd */ +enum { + DPIDLE_CMD_LOCK_IPI = 0x5a00, + DPIDLE_CMD_UNLOCK_IPI = 0x5a01, + DPIDLE_CMD_PDN_UNLOCK = 0x5a02, +}; + +/* ack */ +enum { + DPIDLE_ACK_OK = 0, + DPIDLE_ACK_LOCK_BUSY, + DPIDLE_ACK_POWER_DOWN_FAIL, +}; + +static struct work_struct *apu_pwr_work; +static struct workqueue_struct *apu_pwr_wq; +static struct dentry *dbg_root; + +static void *apu_da_to_va(struct rproc *rproc, u64 da, size_t len, + bool *is_iomem) +{ + void *ptr = NULL; + struct mtk_apu *apu = (struct mtk_apu *)rproc->priv; + + if (da >= DRAM_OFFSET && da < DRAM_OFFSET + CODE_BUF_SIZE) { + ptr = apu->code_buf + (da - DRAM_OFFSET); + } else { + dev_err(apu->dev, "%s: invalid da: da = 0x%llx, len = %zu\n", + __func__, da, len); + } + return ptr; +} + +static int apu_run(struct rproc *rproc) +{ + struct mtk_apu *apu = (struct mtk_apu *)rproc->priv; + struct mtk_apu_hw_ops *hw_ops = &apu->platdata->ops; + struct device *dev = apu->dev; + struct apu_run *run = &apu->run; + struct timespec64 begin, end, delta; + int ret; + + pm_runtime_get_sync(apu->dev); + hw_ops->start(apu); + + /* check if boot success */ + ktime_get_ts64(&begin); + ret = wait_event_interruptible_timeout(run->wq, + run->signaled, + msecs_to_jiffies(10000)); + ktime_get_ts64(&end); + if (ret == 0) { + dev_info(dev, "APU initialization timeout!!\n"); + ret = -ETIME; + goto stop; + } + if (ret == -ERESTARTSYS) { + dev_info(dev, "wait APU interrupted by a signal!!\n"); + goto stop; + } + + apu->boot_done = true; + delta = timespec64_sub(end, begin); + dev_info(dev, "APU uP boot success. boot time: %llu s, %llu ns\n", + (u64)delta.tv_sec, (u64)delta.tv_nsec); + + return 0; + +stop: + hw_ops->stop(apu); + + return ret; +} + +static int apu_start(struct rproc *rproc) +{ + return apu_run(rproc); +} + +static int apu_attach(struct rproc *rproc) +{ + return apu_run(rproc); +} + +static int apu_stop(struct rproc *rproc) +{ + struct mtk_apu *apu = (struct mtk_apu *)rproc->priv; + struct mtk_apu_hw_ops *hw_ops = &apu->platdata->ops; + + hw_ops->stop(apu); + + return 0; +} + +static const struct rproc_ops apu_ops = { + .start = apu_start, + .stop = apu_stop, + .attach = apu_attach, + .da_to_va = apu_da_to_va, +}; + +#if IS_ENABLED(CONFIG_MTK_APU_DEBUG) +static void apu_deepidle_pwron_dbg_fn(struct work_struct *work) +{ + struct mtk_apu *apu = container_of(work, struct mtk_apu, pwron_dbg_wk); + struct device *dev = apu->dev; + int i; + + dev_info(dev, "mbox dummy= 0x%08x 0x%08x 0x%08x 0x%08x\n", + ioread32(apu->apu_mbox + MBOX0_SPARE0), + ioread32(apu->apu_mbox + MBOX0_SPARE1), + ioread32(apu->apu_mbox + MBOX0_SPARE2), + ioread32(apu->apu_mbox + MBOX0_SPARE3)); + + usleep_range(0, 1000); + for (i = 0; i < 5; i++) { + dev_info(apu->dev, "apu boot: pc=%08x, sp=%08x\n", + ioread32(apu->md32_sysctrl + MD32_MON_PC), + ioread32(apu->md32_sysctrl + MD32_MON_SP)); + usleep_range(0, 1000); + } + + dev_info(dev, "%s: MD32_SYS_CTRL = 0x%x\n", + __func__, ioread32(apu->md32_sysctrl + MD32_SYS_CTRL)); +} +#endif + +static int apu_deepidle_send_ack(struct mtk_apu *apu, u32 cmd, u32 ack) +{ + struct dpidle_msg msg; + int ret; + + msg.cmd = cmd; + msg.ack = ack; + ret = apu_ipi_send(apu, APU_IPI_DEEP_IDLE, &msg, sizeof(msg), 0); + if (ret) + dev_info(apu->dev, + "%s: failed to send ack msg, ack=%d, ret=%d\n", + __func__, ack, ret); + + return ret; +} + +static void apu_deepidle_work_func(struct work_struct *work) +{ + struct mtk_apu *apu = container_of(work, struct mtk_apu, deepidle_work); + struct mtk_apu_hw_ops *hw_ops = &apu->platdata->ops; + struct dpidle_msg *msg = &apu->recv_msg; + int ret; + + switch (msg->cmd) { + case DPIDLE_CMD_LOCK_IPI: + ret = apu_ipi_lock(apu); + if (ret) { + dev_info(apu->dev, "%s: failed to lock IPI, ret=%d\n", + __func__, ret); + apu_deepidle_send_ack(apu, DPIDLE_CMD_LOCK_IPI, + DPIDLE_ACK_LOCK_BUSY); + return; + } + apu_deepidle_send_ack(apu, DPIDLE_CMD_LOCK_IPI, + DPIDLE_ACK_OK); + break; + + case DPIDLE_CMD_UNLOCK_IPI: + apu_ipi_unlock(apu); + apu_deepidle_send_ack(apu, DPIDLE_CMD_UNLOCK_IPI, + DPIDLE_ACK_OK); + break; + + case DPIDLE_CMD_PDN_UNLOCK: + apu_deepidle_send_ack(apu, DPIDLE_CMD_PDN_UNLOCK, + DPIDLE_ACK_OK); + ret = hw_ops->power_off(apu); + if (ret) { + dev_info(apu->dev, "failed to power off ret=%d\n", ret); + apu_ipi_unlock(apu); + WARN_ON(0); + return; + } + apu_ipi_unlock(apu); + break; + + default: + dev_info(apu->dev, "unknown cmd %x\n", msg->cmd); + break; + } +} + +static void apu_deepidle_ipi_handler(void *data, unsigned int len, void *priv) +{ + struct mtk_apu *apu = (struct mtk_apu *)priv; + + memcpy(&apu->recv_msg, data, len); + queue_work(apu->apu_deepidle_workq, &apu->deepidle_work); +} + +static int apu_deepidle_init(struct mtk_apu *apu) +{ + struct device *dev = apu->dev; + int ret; + + apu->apu_deepidle_workq = alloc_workqueue("apu_deepidle", + WQ_UNBOUND | WQ_HIGHPRI, 0); + if (!apu->apu_deepidle_workq) { + dev_info(apu->dev, "%s: failed to allocate rq for deep idle\n", + __func__); + return -ENOMEM; + } + INIT_WORK(&apu->deepidle_work, apu_deepidle_work_func); + + ret = apu_ipi_register(apu, APU_IPI_DEEP_IDLE, + apu_deepidle_ipi_handler, apu); + if (ret) { + dev_info(dev, + "%s: failed to register deepidle ipi, ret=%d\n", + __func__, ret); + } +#if IS_ENABLED(CONFIG_MTK_APU_DEBUG) + INIT_WORK(&apu->pwron_dbg_wk, apu_deepidle_pwron_dbg_fn); +#endif + + return ret; +} + +static void apu_deepidle_exit(struct mtk_apu *apu) +{ +#if IS_ENABLED(CONFIG_MTK_APU_DEBUG) + flush_work(&apu->pwron_dbg_wk); +#endif + apu_ipi_unregister(apu, APU_IPI_DEEP_IDLE); + if (apu->apu_deepidle_workq) + destroy_workqueue(apu->apu_deepidle_workq); +} + +void apu_deepidle_power_on_aputop(struct mtk_apu *apu) +{ + struct mtk_apu_hw_ops *hw_ops = &apu->platdata->ops; + + if (pm_runtime_suspended(apu->dev)) { + apu->conf_buf->time_offset = sched_clock(); + hw_ops->power_on(apu); + + #if IS_ENABLED(CONFIG_MTK_APU_DEBUG) + schedule_work(&apu->pwron_dbg_wk); + #endif + } +} + +static void apu_timesync_work_func(struct work_struct *work) +{ + struct mtk_apu *apu = container_of(work, struct mtk_apu, timesync_work); + int ret; + + apu->timesync_stamp = sched_clock(); + ret = apu_ipi_send(apu, APU_IPI_TIMESYNC, &apu->timesync_stamp, + sizeof(u64), 0); + if (ret) { + dev_err(apu->dev, "timsync ipi fail(%d)\n", ret); + return; + } +} + +static void apu_timesync_handler(void *data, u32 len, void *priv) +{ + struct mtk_apu *apu = (struct mtk_apu *)priv; + + queue_work(apu->timesync_wq, &apu->timesync_work); +} + +static int apu_timesync_init(struct mtk_apu *apu) +{ + int ret; + + apu->timesync_wq = alloc_workqueue("apu_timesync", + WQ_UNBOUND | WQ_HIGHPRI, 0); + if (!apu->timesync_wq) { + dev_info(apu->dev, "%s: failed to allocate wq for timesync\n", + __func__); + return -ENOMEM; + } + INIT_WORK(&apu->timesync_work, apu_timesync_work_func); + + ret = apu_ipi_register(apu, APU_IPI_TIMESYNC, apu_timesync_handler, + apu); + if (ret) { + dev_info(apu->dev, "%s: failed to register IPI\n", __func__); + destroy_workqueue(apu->timesync_wq); + apu->timesync_wq = NULL; + return ret; + } + + pr_info("%s %d\n", __func__, __LINE__); + return 0; +} + +static void apu_timesync_remove(struct mtk_apu *apu) +{ + apu_ipi_unregister(apu, APU_IPI_TIMESYNC); + + if (apu->timesync_wq) + destroy_workqueue(apu->timesync_wq); +} + +static irqreturn_t apu_wdt_isr(int irq, void *private_data) +{ + unsigned long flags; + struct mtk_apu *apu = (struct mtk_apu *)private_data; + struct mtk_apu_hw_ops *hw_ops = &apu->platdata->ops; + + spin_lock_irqsave(&apu->reg_lock, flags); + if (hw_ops->cg_gating) + hw_ops->cg_gating(apu); + + /* disable apu wdt */ + iowrite32(ioread32(apu->apu_wdt + WDT_CTRL0) & ~WDT_EN, + apu->apu_wdt + WDT_CTRL0); + /* clear wdt interrupt */ + iowrite32(0x1, apu->apu_wdt); + spin_unlock_irqrestore(&apu->reg_lock, flags); + disable_irq_nosync(apu->wdt_irq_number); + + return IRQ_HANDLED; +} + +static int apu_excep_init(struct mtk_apu *apu) +{ + struct device *dev = apu->dev; + struct platform_device *pdev = to_platform_device(dev); + u32 irq_type = irq_get_trigger_type(apu->wdt_irq_number); + int ret = 0; + + apu->wdt_irq_number = platform_get_irq_byname(pdev, "apu_wdt"); + ret = devm_request_irq(dev, apu->wdt_irq_number, apu_wdt_isr, + irq_type, "apusys_wdt", apu); + if (ret < 0) + dev_err(dev, "%s Failed to request irq %d: %d\n", + __func__, apu->wdt_irq_number, ret); + + return ret; +} + +static void apu_excep_remove(struct mtk_apu *apu) +{ + unsigned long flags; + + spin_lock_irqsave(&apu->reg_lock, flags); + /* disable apu wdt */ + iowrite32(ioread32(apu->apu_wdt + WDT_CTRL0) & ~WDT_EN, + apu->apu_wdt + WDT_CTRL0); + /* clear wdt interrupt */ + iowrite32(0x1, apu->apu_wdt); + spin_unlock_irqrestore(&apu->reg_lock, flags); + disable_irq(apu->wdt_irq_number); +} + +#define CONFIG_SIZE (round_up(sizeof(struct config_v1), PAGE_SIZE)) +static void apu_config_user_ptr_init(const struct mtk_apu *apu) +{ + struct config_v1 *config; + struct config_v1_entry_table *entry_table; + + if (!apu || !apu->conf_buf) { + pr_err("%s: error\n", __func__); + return; + } + + config = apu->conf_buf; + config->header_magic = 0xc0de0101; + config->header_rev = 0x1; + config->entry_offset = offsetof(struct config_v1, entry_tbl); + config->config_size = sizeof(struct config_v1); + + entry_table = (struct config_v1_entry_table *)((void *)config + + config->entry_offset); + + entry_table->user_entry[0] = offsetof(struct config_v1, user0_data); + entry_table->user_entry[1] = offsetof(struct config_v1, user1_data); + entry_table->user_entry[2] = offsetof(struct config_v1, user2_data); + entry_table->user_entry[3] = offsetof(struct config_v1, user3_data); + entry_table->user_entry[4] = offsetof(struct config_v1, user4_data); +} + +static int apu_config_setup(struct mtk_apu *apu) +{ + struct device *dev = apu->dev; + unsigned long flags; + int ret; + + apu->conf_buf = dma_alloc_coherent(apu->dev, CONFIG_SIZE, + &apu->conf_da, GFP_KERNEL); + + if (!apu->conf_buf || apu->conf_da == 0) { + dev_info(dev, "%s: dma_alloc_coherent fail\n", __func__); + return -ENOMEM; + } + memset(apu->conf_buf, 0, CONFIG_SIZE); + + apu_config_user_ptr_init(apu); + spin_lock_irqsave(&apu->reg_lock, flags); + iowrite32((u32)apu->conf_da, apu->apu_mbox + HOST_CONFIG_ADDR); + spin_unlock_irqrestore(&apu->reg_lock, flags); + + apu->conf_buf->time_offset = sched_clock(); + ret = apu_ipi_config_init(apu); + if (ret) { + dev_info(dev, "apu ipi config init failed\n"); + goto out; + } + + ret = sw_logger_config_init(apu); + if (ret) { + dev_err(dev, "sw logger config init failed\n"); + goto err_sw_logger; + } + + return 0; + +err_sw_logger: + apu_ipi_config_remove(apu); +out: + return ret; +} + +static void apu_config_remove(struct mtk_apu *apu) +{ + sw_logger_config_remove(apu); + apu_ipi_config_remove(apu); + dma_free_coherent(apu->dev, CONFIG_SIZE, + apu->conf_buf, apu->conf_da); +} + +static int apu_dram_boot_init(struct mtk_apu *apu) +{ + struct device *dev = apu->dev; + int ret = 0; + int map_sg_sz = 0; + void *domain; + struct sg_table sgt; + phys_addr_t pa; + u32 boundary; + u64 iova; + + domain = iommu_get_domain_for_dev(apu->dev); + if (!domain) { + dev_err(dev, "%s: iommu_get_domain_for_dev fail\n", + __func__); + return -ENOMEM; + } + + /* Allocate code buffer */ + apu->code_buf = dma_alloc_coherent(apu->dev, CODE_BUF_SIZE, + &apu->code_da, GFP_KERNEL); + if (!apu->code_buf || apu->code_da == 0) { + dev_err(dev, "%s: dma_alloc_coherent fail\n", __func__); + return -ENOMEM; + } + memset(apu->code_buf, 0, CODE_BUF_SIZE); + boundary = (u32)upper_32_bits(apu->code_da); + iova = CODE_BUF_DA | ((u64)boundary << 32); + + /* Convert IOVA to sgtable */ + sgt.sgl = NULL; + ret = dma_get_sgtable(apu->dev, &sgt, apu->code_buf, + apu->code_da, CODE_BUF_SIZE); + if (ret < 0 || !sgt.sgl) { + dev_err(dev, "get sgtable fail\n"); + return -EINVAL; + } + + /* Map sg_list to MD32_BOOT_ADDR */ + map_sg_sz = iommu_map_sg(domain, iova, sgt.sgl, + sgt.nents, IOMMU_READ | IOMMU_WRITE); + if (map_sg_sz != CODE_BUF_SIZE) + return -EINVAL; + + pa = iommu_iova_to_phys(domain, iova + CODE_BUF_SIZE - SZ_4K); + if (!pa) + ret = -EPERM; + + return ret; +} + +static void apu_dram_boot_remove(struct mtk_apu *apu) +{ + void *domain = iommu_get_domain_for_dev(apu->dev); + u32 boundary = (u32)upper_32_bits(apu->code_da); + u64 iova = CODE_BUF_DA | ((u64)boundary << 32); + + if (domain) + iommu_unmap(domain, iova, CODE_BUF_SIZE); + + dma_free_coherent(apu->dev, CODE_BUF_SIZE, apu->code_buf, apu->code_da); +} + +static void apu_power_work_fn(struct work_struct *work) +{ + struct mtk_apu *apu = container_of(work, struct mtk_apu, pwr_work); + struct mtk_apu_hw_ops *hw_ops = &apu->platdata->ops; + + if (!apu->boot_done) { + pr_info("%s: uP not boot yet, skip pm nfy\n", __func__); + return; + } + hw_ops->stop(apu); +} + +static int apu_power_genpd_notifier(struct notifier_block *nb, + unsigned long event, void *data) +{ + switch (event) { + case GENPD_NOTIFY_OFF: + pr_info("%s: apu top off\n", __func__); + queue_work(apu_pwr_wq, apu_pwr_work); + break; + case GENPD_NOTIFY_ON: + pr_info("%s: apu top on\n", __func__); + break; + default: + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block apu_genpd_nb = { + .notifier_call = apu_power_genpd_notifier, +}; + +static int apu_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct rproc *rproc; + struct mtk_apu *apu; + struct mtk_apu_platdata *data; + struct mtk_apu_hw_ops *hw_ops; + char *fw_name = "mrv.elf"; + int ret = 0; + struct device_link *link; + + data = (struct mtk_apu_platdata *)of_device_get_match_data(dev); + if (!data) { + dev_info(dev, "%s: of_device_get_match_data fail\n", __func__); + return -EINVAL; + } + hw_ops = &data->ops; + + rproc = rproc_alloc(dev, + np->name, + &apu_ops, + fw_name, + sizeof(struct mtk_apu)); + + if (!rproc) { + dev_info(dev, "unable to allocate remoteproc\n"); + return -ENOMEM; + } + + if (data->flags & F_AUTO_BOOT) + rproc->auto_boot = true; + else + rproc->auto_boot = false; + + apu = (struct mtk_apu *)rproc->priv; + apu->rproc = rproc; + apu->dev = dev; + apu->platdata = data; + platform_set_drvdata(pdev, apu); + spin_lock_init(&apu->reg_lock); + + /* detect mandaotry platform data*/ + if (!hw_ops->apu_memmap_init || !hw_ops->apu_memmap_remove || + !hw_ops->start || !hw_ops->stop || + !hw_ops->power_init || + !hw_ops->power_on || !hw_ops->power_off || + !apu->platdata->ipi_attrs) { + WARN_ON(1); + return -EINVAL; + } + + if (data->flags & F_AUTO_BOOT) { + ret = hw_ops->power_init(apu); + if (ret) + goto out_free_rproc; + + link = device_link_add(dev, apu->power_dev, DL_FLAG_PM_RUNTIME); + if (!link) + dev_err(dev, "unable to link\n"); + } + pm_runtime_enable(&pdev->dev); + pm_runtime_get_sync(&pdev->dev); + + ret = dma_set_mask_and_coherent(apu->dev, DMA_BIT_MASK(64)); + if (ret) { + pr_info("%s: dma_set_mask_and_coherent fail(%d)\n", + __func__, ret); + goto out_free_rproc; + } + + ret = hw_ops->apu_memmap_init(apu); + if (ret) + goto remove_apu_memmap; + + apu->dbg_root = dbg_root; + ret = apu_sw_logger_init(apu); + if (ret) + goto remove_apu_sw_logger; + + ret = apu_config_setup(apu); + if (ret) + goto remove_apu_config_setup; + + ret = apu_dram_boot_init(apu); + if (ret) + goto remove_apu_dram_boot; + + ret = apu_ipi_init(pdev, apu); + if (ret) + goto remove_apu_ipi; + + if (data->flags & F_AUTO_BOOT) { + ret = apu_deepidle_init(apu); + if (ret < 0) + goto remove_apu_deepidle; + } + + ret = apu_timesync_init(apu); + if (ret) + goto remove_apu_timesync; + + ret = apu_excep_init(apu); + if (ret < 0) + goto remove_apu_excep; + + if (data->flags & F_PRELOAD_FIRMWARE) + rproc->state = RPROC_DETACHED; + + ret = rproc_add(rproc); + if (ret < 0) { + dev_info(dev, "boot fail ret:%d\n", ret); + goto remove_apu_excep; + } + + if (hw_ops->init) { + ret = hw_ops->init(apu); + if (ret) + goto del_rproc; + } + + apu_pwr_wq = alloc_workqueue("apu_pwr_wq", + WQ_UNBOUND | WQ_HIGHPRI, 0); + if (!apu_pwr_wq) { + dev_info(dev, "%s: failed to allocate wq for rv power\n", + __func__); + goto del_rproc; + } + INIT_WORK(&apu->pwr_work, apu_power_work_fn); + apu_pwr_work = &apu->pwr_work; + + pm_runtime_put_sync(&pdev->dev); + dev_pm_genpd_add_notifier(dev, &apu_genpd_nb); + + return 0; + +del_rproc: + rproc_del(rproc); + +remove_apu_excep: + apu_excep_remove(apu); + +remove_apu_timesync: + apu_timesync_remove(apu); + +remove_apu_deepidle: + apu_deepidle_exit(apu); + +remove_apu_ipi: + apu_ipi_remove(apu); + +remove_apu_dram_boot: + apu_dram_boot_remove(apu); + +remove_apu_config_setup: + apu_config_remove(apu); + +remove_apu_sw_logger: + apu_sw_logger_remove(apu); + +remove_apu_memmap: + hw_ops->apu_memmap_remove(apu); + +out_free_rproc: + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + rproc_free(rproc); + + return ret; +} + +static int apu_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_apu *apu = platform_get_drvdata(pdev); + struct mtk_apu_hw_ops *hw_ops = &apu->platdata->ops; + + if (hw_ops->exit) + hw_ops->exit(apu); + + dev_pm_genpd_remove_notifier(dev); + pm_runtime_put_sync(&pdev->dev); + destroy_workqueue(apu_pwr_wq); + rproc_del(apu->rproc); + apu_deepidle_exit(apu); + apu_excep_remove(apu); + apu_timesync_remove(apu); + apu_ipi_remove(apu); + apu_dram_boot_remove(apu); + apu_config_remove(apu); + apu_sw_logger_remove(apu); + hw_ops->apu_memmap_remove(apu); + pm_runtime_disable(dev); + rproc_free(apu->rproc); + + return 0; +} + +static const struct of_device_id apu_of_match[] = { + { .compatible = "mediatek,mt8192-apusys-rv", .data = &mt8192_platdata}, + {}, +}; +MODULE_DEVICE_TABLE(of, apu_of_match); + +static int apu_runtime_suspend(struct device *dev) +{ + return 0; +} + +static int apu_runtime_resume(struct device *dev) +{ + struct mtk_apu *apu = dev_get_drvdata(dev); + struct mtk_apu_hw_ops *hw_ops = &apu->platdata->ops; + + if (!apu->boot_done) + return 0; + + if (hw_ops->resume) + return hw_ops->resume(apu); + + return 0; +} + +static const struct dev_pm_ops apu_pm_ops = { + SET_RUNTIME_PM_OPS(apu_runtime_suspend, apu_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static struct platform_driver apu_driver = { + .probe = apu_probe, + .remove = apu_remove, + .driver = { + .name = "mtk-apu", + .of_match_table = of_match_ptr(apu_of_match), + .pm = &apu_pm_ops, + }, +}; + +int apu_rproc_init(struct apusys_core_info *info) +{ + dbg_root = info->dbg_root; + return platform_driver_register(&apu_driver); +} + +void apu_rproc_exit(void) +{ + platform_driver_unregister(&apu_driver); +} + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MediaTek APU control driver"); diff --git a/drivers/soc/mediatek/apusys/apu-sw-logger.c b/drivers/soc/mediatek/apusys/apu-sw-logger.c new file mode 100644 index 000000000000..818b11cbaa29 --- /dev/null +++ b/drivers/soc/mediatek/apusys/apu-sw-logger.c @@ -0,0 +1,429 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2021 MediaTek Inc. + */ + +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/fs.h> +#include <linux/sched/signal.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/timer.h> + +#include "apu.h" +#include "apu-core.h" + +#define SW_LOGGER_DEV_NAME "apu_sw_logger" +#define APUSYS_LOGGER_DIR "logger" + +#define LOG_LINE_MAX_LENS 128 +#define APU_LOG_SIZE (1024 * 1024) +#define APU_LOG_BUF_SIZE (1024 * 1024) + +static struct dentry *log_root; +static struct dentry *log_seqlog; +static struct dentry *log_seqlogl; + +static void sw_logger_buf_invalidate(struct mtk_apu *apu) +{ + dma_sync_single_for_cpu(apu->dev, apu->handle, APU_LOG_SIZE, + DMA_FROM_DEVICE); +} + +static int sw_logger_buf_alloc(struct device *dev) +{ + struct mtk_apu *apu = dev_get_drvdata(dev); + int ret; + + ret = dma_set_coherent_mask(dev, DMA_BIT_MASK(64)); + if (ret) + return ret; + + apu->sw_log_buf = kzalloc(APU_LOG_SIZE, GFP_KERNEL); + if (!apu->sw_log_buf) + return -ENOMEM; + + apu->handle = dma_map_single(dev, apu->sw_log_buf, APU_LOG_SIZE, + DMA_FROM_DEVICE); + if (dma_mapping_error(dev, apu->handle) != 0) { + kfree(apu->sw_log_buf); + apu->sw_log_buf = NULL; + return -EFAULT; + } + + return 0; +} + +int sw_logger_config_init(struct mtk_apu *apu) +{ + int ret; + unsigned long flags; + struct logger_init_info *st_logger_init_info; + + if (!apu || !apu->conf_buf) + return -EINVAL; + + if (!apu->sw_log_buf) { + ret = sw_logger_buf_alloc(apu->dev); + if (ret) { + dev_err(apu->dev, "%s: sw_logger_buf_alloc fail\n", + __func__); + return ret; + } + } + + spin_lock_irqsave(&apu->sw_logger_spinlock, flags); + iowrite32(0, apu->apu_mbox + LOG_W_PTR); + iowrite32(0, apu->apu_mbox + LOG_R_PTR); + iowrite32(0, apu->apu_mbox + LOG_OV_FLG); + spin_unlock_irqrestore(&apu->sw_logger_spinlock, flags); + + st_logger_init_info = (struct logger_init_info *) + get_apu_config_user_ptr(apu->conf_buf, LOGGER_INIT_INFO); + + st_logger_init_info->iova = apu->handle; + + return 0; +} + +void sw_logger_config_remove(struct mtk_apu *apu) +{ + if (apu->handle) + dma_unmap_single(apu->dev, apu->handle, + APU_LOG_SIZE, DMA_FROM_DEVICE); + kfree(apu->sw_log_buf); +} + +/* + * seq_start() takes a position as an argument and returns an iterator which + * will start reading at that position. + * start->show->next->show...->next->show->next->stop->start->stop + */ +static void *seq_start(struct seq_file *s, loff_t *pos) +{ + struct mtk_apu *apu = (struct mtk_apu *)s->private; + u32 w_ptr, r_ptr, overflow_flg; + unsigned long flags; + + if (!apu->sw_log_buf) { + pr_err("%s: sw_log_buf == NULL\n", __func__); + return NULL; + } + + spin_lock_irqsave(&apu->sw_logger_spinlock, flags); + w_ptr = ioread32(apu->apu_mbox + LOG_W_PTR); + r_ptr = ioread32(apu->apu_mbox + LOG_R_PTR); + overflow_flg = ioread32(apu->apu_mbox + LOG_OV_FLG); + spin_unlock_irqrestore(&apu->sw_logger_spinlock, flags); + + sw_logger_buf_invalidate(apu); + + if (w_ptr == r_ptr && overflow_flg == 0) + return NULL; + + if (!apu->pseqdata) { + apu->pseqdata = kzalloc(sizeof(*apu->pseqdata), GFP_KERNEL); + if (apu->pseqdata) { + apu->pseqdata->w_ptr = w_ptr; + apu->pseqdata->r_ptr = r_ptr; + apu->pseqdata->overflow_flg = overflow_flg; + if (overflow_flg == 0) + apu->pseqdata->i = r_ptr; + else + apu->pseqdata->i = w_ptr; + + apu->pseqdata->is_finished = 0; + } + } + + return apu->pseqdata; +} + +/* + * seq_start() takes a position as an argument and returns an iterator which + * will start reading at that position. + */ +static void *seq_startl(struct seq_file *s, loff_t *pos) +{ + struct mtk_apu *apu = s->private; + u32 w_ptr, r_ptr, overflow_flg; + struct sw_logger_seq_data *pseqdata_lock = &apu->pseqdata_lock; + unsigned long flags; + + if (!apu->sw_log_buf) + return NULL; + + spin_lock_irqsave(&apu->sw_logger_spinlock, flags); + w_ptr = ioread32(apu->apu_mbox + LOG_W_PTR); + r_ptr = ioread32(apu->apu_mbox + LOG_R_PTR); + overflow_flg = ioread32(apu->apu_mbox + LOG_OV_FLG); + spin_unlock_irqrestore(&apu->sw_logger_spinlock, flags); + + sw_logger_buf_invalidate(apu); + + /* for ctrl-c to force exit the loop */ + while (!signal_pending(current) && w_ptr == r_ptr) { + usleep_range(10000, 12000); + + spin_lock_irqsave(&apu->sw_logger_spinlock, flags); + w_ptr = ioread32(apu->apu_mbox + LOG_W_PTR); + r_ptr = ioread32(apu->apu_mbox + LOG_R_PTR); + overflow_flg = ioread32(apu->apu_mbox + LOG_OV_FLG); + spin_unlock_irqrestore(&apu->sw_logger_spinlock, flags); + + sw_logger_buf_invalidate(apu); + + pseqdata_lock->w_ptr = w_ptr; + pseqdata_lock->r_ptr = r_ptr; + pseqdata_lock->overflow_flg = overflow_flg; + pseqdata_lock->i = r_ptr; + } + + if (pseqdata_lock->startl_first || + pseqdata_lock->i == pseqdata_lock->w_ptr) { + pseqdata_lock->startl_first = false; + pseqdata_lock->w_ptr = w_ptr; + pseqdata_lock->r_ptr = r_ptr; + pseqdata_lock->overflow_flg = overflow_flg; + pseqdata_lock->i = r_ptr; + } + + if (signal_pending(current)) + pseqdata_lock->startl_first = true; + + return pseqdata_lock; +} + +/* + * move the iterator forward to the next position in the sequence + */ +static void *seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + struct sw_logger_seq_data *psdata = v; + + if (!psdata) { + pr_err("%s: psdata == NULL\n", __func__); + return NULL; + } + + psdata->i = (psdata->i + LOG_LINE_MAX_LENS) % APU_LOG_SIZE; + + /* prevent kernel warning */ + *pos = psdata->i; + + if (psdata->i != psdata->w_ptr) + return v; + + psdata->is_finished = 1; + return NULL; +} + +/* + * move the iterator forward to the next position in the sequence + */ +static void *seq_next_lock(struct seq_file *s, void *v, loff_t *pos) +{ + struct mtk_apu *apu = s->private; + struct sw_logger_seq_data *psdata = v; + + if (!psdata) { + pr_err("%s: psdata == NULL\n", __func__); + return NULL; + } + + psdata->i = (psdata->i + LOG_LINE_MAX_LENS) % APU_LOG_SIZE; + + /* prevent kernel warning */ + *pos = psdata->i; + + if (psdata->i != psdata->w_ptr) + return v; + + iowrite32(psdata->i, apu->apu_mbox + LOG_R_PTR); + return NULL; +} + +/* + * stop() is called when iteration is complete (clean up) + */ +static void seq_stop(struct seq_file *s, void *v) +{ + struct mtk_apu *apu = (struct mtk_apu *)s->private; + unsigned long flags; + + if (apu->pseqdata) { + if (apu->pseqdata->is_finished == 1) { + spin_lock_irqsave(&apu->sw_logger_spinlock, flags); + iowrite32(apu->pseqdata->i, apu->apu_mbox + LOG_R_PTR); + /* fixme: assume next overflow won't happen + * until next seq_start + */ + iowrite32(0, apu->apu_mbox + LOG_OV_FLG); + spin_unlock_irqrestore(&apu->sw_logger_spinlock, flags); + kfree(apu->pseqdata); + apu->pseqdata = NULL; + } + } +} + +/* + * stop() is called when iteration is complete (clean up) + */ +static void seq_stopl(struct seq_file *s, void *v) +{ +} + +/* + * success return 0, otherwise return error code + */ +static int seq_show(struct seq_file *s, void *v) +{ + struct mtk_apu *apu = (struct mtk_apu *)s->private; + struct sw_logger_seq_data *psdata = v; + + seq_printf(s, "%s", apu->sw_log_buf + psdata->i); + + return 0; +} + +static int seq_showl(struct seq_file *s, void *v) +{ + struct mtk_apu *apu = (struct mtk_apu *)s->private; + struct sw_logger_seq_data *psdata = v; + + if (psdata->i != psdata->w_ptr) + seq_printf(s, "%s", apu->sw_log_buf + psdata->i); + + return 0; +} + +static const struct seq_operations seq_ops = { + .start = seq_start, + .next = seq_next, + .stop = seq_stop, + .show = seq_show +}; + +static const struct seq_operations seq_ops_lock = { + .start = seq_startl, + .next = seq_next_lock, + .stop = seq_stopl, + .show = seq_showl +}; + +static int debug_sqopen_lock(struct inode *inode, struct file *file) +{ + struct mtk_apu *apu = inode->i_private; + int ret; + + ret = seq_open(file, &seq_ops_lock); + if (ret) + return ret; + + ((struct seq_file *)file->private_data)->private = apu; + + return 0; +} + +static int debug_sqopen(struct inode *inode, struct file *file) +{ + struct mtk_apu *apu = inode->i_private; + int ret; + + ret = seq_open(file, &seq_ops); + if (ret) + return ret; + + ((struct seq_file *)file->private_data)->private = apu; + + return 0; +} + +static const struct file_operations seqlog_ops = { + .owner = THIS_MODULE, + .open = debug_sqopen, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static const struct file_operations seqlogl_ops = { + .owner = THIS_MODULE, + .open = debug_sqopen_lock, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static int sw_logger_create_debugfs(struct device *dev) +{ + struct mtk_apu *apu = dev_get_drvdata(dev); + int ret = 0; + + log_root = debugfs_create_dir(APUSYS_LOGGER_DIR, apu->dbg_root); + ret = IS_ERR_OR_NULL(log_root); + if (ret) { + dev_err(dev, "(%d)failed to create apusys_logger dir\n", ret); + goto out; + } + + log_seqlog = debugfs_create_file("seq_log", 0444, + log_root, apu, &seqlog_ops); + ret = IS_ERR_OR_NULL(log_seqlog); + if (ret) { + dev_err(dev, "(%d)failed to create apusys_logger node(seqlog)\n", + ret); + goto out; + } + + log_seqlogl = debugfs_create_file("seq_logl", 0444, + log_root, apu, &seqlogl_ops); + ret = IS_ERR_OR_NULL(log_seqlogl); + if (ret) { + dev_err(dev, "(%d)failed to create apusys_logger node(seqlogL)\n", + ret); + goto out; + } + + return 0; + +out: + debugfs_remove_recursive(log_root); + return ret; +} + +static void sw_logger_remove_debugfs(struct device *dev) +{ + debugfs_remove_recursive(log_root); +} + +int apu_sw_logger_init(struct mtk_apu *apu) +{ + struct device *dev = apu->dev; + int ret = 0; + + spin_lock_init(&apu->sw_logger_spinlock); + ret = sw_logger_create_debugfs(dev); + if (ret) { + dev_err(dev, "%s fail\n", __func__); + goto remove_debugfs; + } + + return 0; + +remove_debugfs: + sw_logger_remove_debugfs(dev); + dev_err(dev, "%s error!!\n", __func__); + + return ret; +} + +void apu_sw_logger_remove(struct mtk_apu *apu) +{ + struct device *dev = apu->dev; + + sw_logger_remove_debugfs(dev); +} diff --git a/drivers/soc/mediatek/apusys/apu.h b/drivers/soc/mediatek/apusys/apu.h new file mode 100644 index 000000000000..5bbc46416a19 --- /dev/null +++ b/drivers/soc/mediatek/apusys/apu.h @@ -0,0 +1,256 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021 MediaTek Inc. + */ + +#ifndef APU_H +#define APU_H +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/rpmsg/mtk_rpmsg.h> + +#include "apu-config.h" + +/* setup the SMC command ops */ +#define MTK_SIP_APU_START_MCU 0x00 +#define MTK_SIP_APU_STOP_MCU 0x01 + +/* md32_sysctrl register definition */ +#define MD32_SYS_CTRL 0x0 +#define MD32_MON_PC 0x838 +#define MD32_MON_LR 0x83c +#define MD32_MON_SP 0x840 +#define MD32_STATUS 0x844 + +/*wdt register */ +#define WDT_INT 0x0 +#define WDT_CTRL0 0x4 +#define WDT_EN BIT(31) + +/* apu_mbox spare regiter */ +#define MBOX0_SPARE0 0x40 +#define MBOX0_SPARE1 0x44 +#define MBOX0_SPARE2 0x48 +#define MBOX0_SPARE3 0x4C +#define MBOX6_SPARE0 0x640 +#define MBOX6_SPARE1 0x644 +#define MBOX6_SPARE2 0x648 +#define MBOX6_SPARE3 0x64C + +#define HOST_CONFIG_ADDR MBOX0_SPARE2 + +#define LOG_W_PTR (MBOX0_SPARE0) +#define LOG_R_PTR (MBOX0_SPARE1) +#define LOG_OV_FLG (MBOX0_SPARE3) + +/* rv setup */ +#define F_PRELOAD_FIRMWARE BIT(0) +#define F_AUTO_BOOT BIT(1) + +#define TCM_SIZE (128UL * 1024UL) +#define CODE_BUF_SIZE (1024UL * 1024UL) +#define DRAM_DUMP_SIZE (CODE_BUF_SIZE - TCM_SIZE) +#define REG_SIZE (4UL * 151UL) +#define TBUF_SIZE (4UL * 32UL) +#define CACHE_DUMP_SIZE (37UL * 1024UL) +#define DRAM_OFFSET (0x00000UL) +#define DRAM_DUMP_OFFSET (TCM_SIZE) +#define TCM_OFFSET (0x1d700000UL) +#define CODE_BUF_DA (DRAM_OFFSET) + +/* ipi */ +#define APU_FW_VER_LEN 32 +#define APU_SHARE_BUFFER_SIZE 256 + +#define IPI_LOCKED 1 +#define IPI_UNLOCKED 0 + +#define IPI_HOST_INITIATE 0 +#define IPI_APU_INITIATE 1 +#define IPI_WITH_ACK 1 +#define IPI_WITHOUT_ACK 0 + +enum { + APU_IPI_INIT = 0, + APU_IPI_NS_SERVICE, + APU_IPI_DEEP_IDLE, + APU_IPI_CTRL_RPMSG, + APU_IPI_MIDDLEWARE, + APU_IPI_REVISER_RPMSG, + APU_IPI_PWR_TX, + APU_IPI_PWR_RX, + APU_IPI_MDLA_TX, + APU_IPI_MDLA_RX, + APU_IPI_TIMESYNC, + APU_IPI_EDMA_TX, + APU_IPI_MNOC_TX, + APU_IPI_MAX, +}; + +struct mtk_apu; + +struct mtk_apu_hw_ops { + int (*init)(struct mtk_apu *apu); + int (*exit)(struct mtk_apu *apu); + int (*start)(struct mtk_apu *apu); + int (*stop)(struct mtk_apu *apu); + int (*resume)(struct mtk_apu *apu); + int (*apu_memmap_init)(struct mtk_apu *apu); + void (*apu_memmap_remove)(struct mtk_apu *apu); + void (*cg_gating)(struct mtk_apu *apu); + void (*cg_ungating)(struct mtk_apu *apu); + void (*rv_cachedump)(struct mtk_apu *apu); + + /* power related ops */ + int (*power_init)(struct mtk_apu *apu); + int (*power_on)(struct mtk_apu *apu); + int (*power_off)(struct mtk_apu *apu); +}; + +struct apu_ipi { + char *name; + unsigned int direction:1; + unsigned int ack:1; +}; + +struct mtk_apu_platdata { + u32 flags; + struct mtk_apu_hw_ops ops; + const struct apu_ipi *ipi_attrs; +}; + +struct dpidle_msg { + u32 cmd; + u32 ack; +}; + +struct apu_run { + s8 fw_ver[APU_FW_VER_LEN]; + u32 signaled; + wait_queue_head_t wq; +}; + +struct apu_ipi_desc { + struct mutex lock; /*ipi hanlder mutex */ + ipi_handler_t handler; + void *priv; + /* + * positive: host-initiated ipi outstanding count + * negative: apu-initiated ipi outstanding count + */ + int usage_cnt; +}; + +struct mtk_share_obj { + u8 share_buf[APU_SHARE_BUFFER_SIZE]; +}; + +struct sw_logger_seq_data { + u32 w_ptr; + u32 r_ptr; + u32 overflow_flg; + int i; + int is_finished; + char *data; + bool startl_first; +}; + +struct mtk_apu { + struct rproc *rproc; + struct device *dev; + void __iomem *apu_mbox; + void __iomem *md32_sysctrl; + void __iomem *apu_wdt; + int mbox0_irq_number; + int wdt_irq_number; + spinlock_t reg_lock; /* register r/w lock */ + + /* Buffer to place execution area */ + void *code_buf; + dma_addr_t code_da; + + /* Buffer to place config area */ + struct config_v1 *conf_buf; + dma_addr_t conf_da; + + /* to synchronize boot status of remote processor */ + struct apu_run run; + + /* to prevent multiple ipi_send run concurrently */ + struct mutex send_lock; + spinlock_t usage_cnt_lock; /* ipi occipued lock */ + struct apu_ipi_desc ipi_desc[APU_IPI_MAX]; + bool ipi_id_ack[APU_IPI_MAX]; /* per-ipi ack */ + bool ipi_inbound_locked; + wait_queue_head_t ack_wq; /* for waiting for ipi ack */ + + /* ipi */ + struct rproc_subdev *rpmsg_subdev; + dma_addr_t recv_buf_da; + struct mtk_share_obj *recv_buf; + dma_addr_t send_buf_da; + struct mtk_share_obj *send_buf; + + /* time sync */ + struct work_struct timesync_work; + struct workqueue_struct *timesync_wq; + u64 timesync_stamp; + + /*deep idle */ + struct dpidle_msg recv_msg; + struct work_struct deepidle_work; + struct workqueue_struct *apu_deepidle_workq; + struct work_struct pwron_dbg_wk; + + struct mtk_apu_platdata *platdata; + + /* link power deive */ + struct device *power_dev; + bool boot_done; + struct work_struct pwr_work; + + /* logger and debug */ + struct dentry *dbg_root; + dma_addr_t handle; + char *sw_log_buf; + spinlock_t sw_logger_spinlock; /* logger status update lock */ + struct sw_logger_seq_data pseqdata_lock; + struct sw_logger_seq_data *pseqdata; +}; + +struct apu_coredump { + char tcmdump[TCM_SIZE]; + char ramdump[DRAM_DUMP_SIZE]; + char regdump[REG_SIZE]; + char tbufdump[TBUF_SIZE]; + u32 cachedump[CACHE_DUMP_SIZE / sizeof(u32)]; +} __packed; + +int apu_ipi_config_init(struct mtk_apu *apu); +void apu_ipi_config_remove(struct mtk_apu *apu); +void apu_ipi_remove(struct mtk_apu *apu); +int apu_ipi_init(struct platform_device *pdev, struct mtk_apu *apu); +int apu_ipi_register(struct mtk_apu *apu, u32 id, + ipi_handler_t handler, void *priv); +void apu_ipi_unregister(struct mtk_apu *apu, u32 id); +int apu_ipi_send(struct mtk_apu *apu, u32 id, void *data, u32 len, + u32 wait_ms); +int apu_ipi_lock(struct mtk_apu *apu); +void apu_ipi_unlock(struct mtk_apu *apu); + +void apu_deepidle_power_on_aputop(struct mtk_apu *apu); + +#if IS_ENABLED(CONFIG_DEBUG_FS) +int sw_logger_config_init(struct mtk_apu *apu); +void sw_logger_config_remove(struct mtk_apu *apu); +int apu_sw_logger_init(struct mtk_apu *apu); +void apu_sw_logger_remove(struct mtk_apu *apu); +#else +static inline int sw_logger_config_init(struct mtk_apu *apu) { return 0; } +static inline void sw_logger_config_remove(struct mtk_apu *apu) { } +static inline int apu_sw_logger_init(struct mtk_apu *apu) { return 0; } +static inline void apu_sw_logger_remove(struct mtk_apu *apu) { } +#endif + +extern const struct mtk_apu_platdata mt8192_platdata; +#endif /* APU_H */ diff --git a/drivers/soc/mediatek/apusys/mt81xx-plat.c b/drivers/soc/mediatek/apusys/mt81xx-plat.c new file mode 100644 index 000000000000..54f75c8d07c3 --- /dev/null +++ b/drivers/soc/mediatek/apusys/mt81xx-plat.c @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2021 MediaTek Inc. + */ + +#include <linux/arm-smccc.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/of_platform.h> +#include <linux/pm_runtime.h> +#include <linux/sched/clock.h> +#include <linux/soc/mediatek/mtk_sip_svc.h> + +#include "apu.h" + +static const struct apu_ipi mt81xx_ipi_attrs[APU_IPI_MAX] = { + [APU_IPI_INIT] = { + .name = "init", + .direction = IPI_APU_INITIATE, + .ack = IPI_WITHOUT_ACK, + }, + [APU_IPI_NS_SERVICE] = { + .name = "name-service", + .direction = IPI_APU_INITIATE, + .ack = IPI_WITHOUT_ACK, + }, + [APU_IPI_DEEP_IDLE] = { + .name = "deep_idle", + .direction = IPI_APU_INITIATE, + .ack = IPI_WITH_ACK, + }, + [APU_IPI_CTRL_RPMSG] = { + .name = "apu-ctrl-rpmsg", + .direction = IPI_APU_INITIATE, + .ack = IPI_WITH_ACK, + }, + [APU_IPI_MIDDLEWARE] = { + .name = "apu-mdw-rpmsg", + .direction = IPI_HOST_INITIATE, + .ack = IPI_WITH_ACK, + }, + [APU_IPI_REVISER_RPMSG] = { + .name = "apu-reviser-rpmsg", + .direction = IPI_HOST_INITIATE, + .ack = IPI_WITH_ACK, + }, + [APU_IPI_PWR_TX] = { + .name = "apupwr-tx-rpmsg", + .direction = IPI_HOST_INITIATE, + .ack = IPI_WITH_ACK, + }, + [APU_IPI_PWR_RX] = { + .name = "apupwr-rx-rpmsg", + .direction = IPI_APU_INITIATE, + .ack = IPI_WITH_ACK, + }, + [APU_IPI_MDLA_TX] = { + .name = "mdla-tx-rpmsg", + .direction = IPI_HOST_INITIATE, + .ack = IPI_WITH_ACK, + }, + [APU_IPI_MDLA_RX] = { + .name = "mdla-rx-rpmsg", + .direction = IPI_APU_INITIATE, + .ack = IPI_WITH_ACK, + }, + [APU_IPI_TIMESYNC] = { + .name = "apu-timesync", + .direction = IPI_APU_INITIATE, + .ack = IPI_WITH_ACK, + }, + [APU_IPI_EDMA_TX] = { + .name = "apu-edma-rpmsg", + .direction = IPI_HOST_INITIATE, + .ack = IPI_WITHOUT_ACK, + }, + [APU_IPI_MNOC_TX] = { + .name = "apu-mnoc-rpmsg", + .direction = IPI_HOST_INITIATE, + .ack = IPI_WITHOUT_ACK, + }, +}; + +static void apu_reset_mcu(struct mtk_apu *apu) +{ + u32 reg; + + /* assert mcu reset */ + reg = ioread32(apu->md32_sysctrl); + iowrite32(reg & ~0x1, apu->md32_sysctrl); + mdelay(10); + iowrite32(reg | 0x1, apu->md32_sysctrl); +} + +static int apu_start_mcu(struct mtk_apu *apu) +{ + struct arm_smccc_res ares; + + /* initialize IOMMU and ACP config (iommu_tr_en=1, acp_en=0) */ + iowrite32(0xEA9, apu->md32_sysctrl); + + arm_smccc_smc(MTK_SIP_APUSYS_CONTROL, MTK_SIP_APU_START_MCU, + 0, 0, 0, 0, 0, 0, &ares); + if (ares.a0) + dev_err(apu->dev, "start mcu fail: %lu\n", ares.a0); + + return 0; +} + +static int apu_stop_mcu(struct mtk_apu *apu) +{ + struct arm_smccc_res ares; + + arm_smccc_smc(MTK_SIP_APUSYS_CONTROL, MTK_SIP_APU_STOP_MCU, + 0, 0, 0, 0, 0, 0, &ares); + if (ares.a0) + dev_err(apu->dev, "stop mcufail: %lu\n", ares.a0); + + return 0; +} + +static int mt8192_rproc_start(struct mtk_apu *apu) +{ + apu_reset_mcu(apu); + apu_start_mcu(apu); + + return 0; +} + +static int mt8192_rproc_resume(struct mtk_apu *apu) +{ + apu_start_mcu(apu); + + return 0; +} + +static int mt8192_rproc_stop(struct mtk_apu *apu) +{ + unsigned long flags; + + spin_lock_irqsave(&apu->reg_lock, flags); + /* disable apu wdt */ + iowrite32(ioread32(apu->apu_wdt + WDT_CTRL0) & ~WDT_EN, + apu->apu_wdt + WDT_CTRL0); + /* clear wdt interrupt */ + iowrite32(0x1, apu->apu_wdt); + spin_unlock_irqrestore(&apu->reg_lock, flags); + + /* Hold runstall */ + apu_stop_mcu(apu); + return 0; +} + +static int mt81xx_apu_power_init(struct mtk_apu *apu) +{ + struct device *dev = apu->dev; + struct device_node *np; + struct platform_device *pdev; + + /* power dev */ + np = of_parse_phandle(dev->of_node, "mediatek,apusys-power", 0); + if (!np) { + dev_info(dev, "failed to parse apusys_power node\n"); + return -EINVAL; + } + + if (!of_device_is_available(np)) { + dev_info(dev, "unable to find apusys_power node\n"); + of_node_put(np); + return -ENODEV; + } + + pdev = of_find_device_by_node(np); + if (!pdev) { + dev_info(dev, "apusys_power is not ready yet\n"); + of_node_put(np); + return -EPROBE_DEFER; + } + + dev_info(dev, "%s: get power_dev, name=%s\n", __func__, pdev->name); + + apu->power_dev = &pdev->dev; + of_node_put(np); + + return 0; +} + +static int mt81xx_apu_power_on(struct mtk_apu *apu) +{ + int ret; + + ret = pm_runtime_get_sync(apu->dev); + if (ret < 0) { + dev_info(apu->dev, + "%s: call to get_sync(dev) failed, ret=%d\n", + __func__, ret); + return ret; + } + + return 0; +} + +static int mt81xx_apu_power_off(struct mtk_apu *apu) +{ + int ret, timeout; + u32 val; + + /* wait remote power state */ + ret = readl_relaxed_poll_timeout(apu->apu_mbox + MBOX6_SPARE3, + val, + (val & BIT(0)), + 10, 25000); + if (ret) { + dev_err(apu->dev, "Remote WFI not ready\n"); + return ret; + } + + ret = pm_runtime_put_sync(apu->dev); + if (ret) { + dev_info(apu->dev, + "%s: call to put_sync(dev) failed, ret=%d\n", + __func__, ret); + return ret; + } + + /* polling APU TOP rpm state till suspended */ + dev_info(apu->dev, "start polling power off\n"); + timeout = 500; + while (!pm_runtime_suspended(apu->power_dev) && timeout-- > 0) + msleep(20); + if (timeout <= 0) { + dev_info(apu->dev, "%s: polling power off timeout!!\n", + __func__); + apu_ipi_unlock(apu); + WARN_ON(0); + ret = -ETIMEDOUT; + goto error_get_power_dev; + } + + dev_info(apu->dev, "polling power done\n"); + + return 0; + +error_get_power_dev: + pm_runtime_get_sync(apu->power_dev); + + return ret; +} + +static int mt8192_apu_memmap_init(struct mtk_apu *apu) +{ + struct device *dev = apu->dev; + struct platform_device *pdev = to_platform_device(dev); + struct resource *res; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "apu_mbox"); + if (!res) { + dev_info(dev, "%s: apu_mbox get resource fail\n", __func__); + return -ENODEV; + } + apu->apu_mbox = ioremap(res->start, res->end - res->start + 1); + if (IS_ERR((void const *)apu->apu_mbox)) { + dev_info(dev, "%s: apu_mbox remap base fail\n", __func__); + return -ENOMEM; + } + + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "md32_sysctrl"); + if (!res) { + dev_info(dev, "%s: md32_sysctrl get resource fail\n", __func__); + return -ENODEV; + } + apu->md32_sysctrl = ioremap(res->start, res->end - res->start + 1); + if (IS_ERR((void const *)apu->md32_sysctrl)) { + dev_info(dev, "%s: md32_sysctrl remap base fail\n", __func__); + return -ENOMEM; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "apu_wdt"); + if (!res) { + dev_info(dev, "%s: apu_wdt get resource fail\n", __func__); + return -ENODEV; + } + apu->apu_wdt = ioremap(res->start, res->end - res->start + 1); + if (IS_ERR((void const *)apu->apu_wdt)) { + dev_info(dev, "%s: apu_wdt remap base fail\n", __func__); + return -ENOMEM; + } + + return 0; +} + +static void mt8192_apu_memmap_remove(struct mtk_apu *apu) +{ + iounmap(apu->apu_wdt); + iounmap(apu->md32_sysctrl); + iounmap(apu->apu_mbox); +} + +const struct mtk_apu_platdata mt8192_platdata = { + .flags = F_AUTO_BOOT, + .ipi_attrs = mt81xx_ipi_attrs, + .ops = { + .init = NULL, + .exit = NULL, + .start = mt8192_rproc_start, + .stop = mt8192_rproc_stop, + .resume = mt8192_rproc_resume, + .apu_memmap_init = mt8192_apu_memmap_init, + .apu_memmap_remove = mt8192_apu_memmap_remove, + .cg_gating = NULL, + .cg_ungating = NULL, + .rv_cachedump = NULL, + .power_init = mt81xx_apu_power_init, + .power_on = mt81xx_apu_power_on, + .power_off = mt81xx_apu_power_off, + }, +}; -- 2.18.0