| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2019 MediaTek Inc. |
| */ |
| |
| #include <linux/arm-smccc.h> |
| #include <linux/clk.h> |
| #include <linux/device.h> |
| #include <linux/err.h> |
| #include <linux/interrupt.h> |
| #include <linux/io.h> |
| #include <linux/module.h> |
| #include <linux/of_device.h> |
| #include <linux/platform_device.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/slab.h> |
| #include <linux/workqueue.h> |
| #include <mt-plat/aee.h> |
| #include <linux/soc/mediatek/mtk_sip_svc.h> |
| #include "mmsram.h" |
| |
| #define MMSYSRAM_INTEN0 (0x000) |
| #define MMSYSRAM_INTEN1 (0x004) |
| #define MMSYSRAM_INSTA0 (0x010) |
| #define MMSYSRAM_INSTA1 (0x014) |
| #define MMSYSRAM_SEC_ADDR0 (0x040) |
| #define MMSYSRAM_SEC_CTRL0 (0x060) |
| |
| #define BIT_INSTA0 (0x6FF0E0E0) |
| #define BIT_INSTA1 (0x000037F8) |
| #define BIT_SECURE_ON (0x11111111) |
| |
| /* First Violation Latch Debug */ |
| #define FVLD_APC0_LATCH_EN (0x090) |
| #define FVLD_APC1_LATCH_EN (0x0C0) |
| #define FVLD_MPU0_LATCH_EN (0x110) |
| #define FVLD_MPU1_LATCH_EN (0x140) |
| #define FVLD_APC0_VIO_ADDR (0x0A4) |
| #define FVLD_APC0_VIO_ID (0x0A8) |
| #define FVLD_APC0_VIO_WR_RD (0x0AC) |
| #define FVLD_APC1_VIO_ADDR (0x0D4) |
| #define FVLD_APC1_VIO_ID (0x0D8) |
| #define FVLD_APC1_VIO_WR_RD (0x0DC) |
| #define FVLD_MPU0_VIO_ADDR (0x124) |
| #define FVLD_MPU0_VIO_ID (0x128) |
| #define FVLD_MPU0_VIO_WR_RD (0x12C) |
| #define FVLD_MPU1_VIO_ADDR (0x154) |
| #define FVLD_MPU1_VIO_ID (0x158) |
| #define FVLD_MPU1_VIO_WR_RD (0x15C) |
| |
| #define BIT_APC_LATCH_EN (0x00000020) |
| #define BIT_MPU_LATCH_EN (0x00000FF0) |
| |
| #define DEC_APC0_VIO_DECERR(x) (((x) >> 5) & 0x1) |
| #define DEC_APC0_VIO_WR_RD(x) (((x) >> 6) & 0x3) |
| #define DEC_APC1_VIO_DECERR(x) (((x) >> 13) & 0x1) |
| #define DEC_APC1_VIO_WR_RD(x) (((x) >> 14) & 0x3) |
| #define DEC_MPU0_VIO_DEVAPC(x) (((x) >> 20) & 0xFF) |
| #define DEC_MPU0_VIO_WR_RD(x) (((x) >> 29) & 0x3) |
| #define DEC_MPU1_VIO_DEVAPC(x) (((x) >> 3) & 0xFF) |
| #define DEC_MPU1_VIO_WR_RD(x) (((x) >> 12) & 0x3) |
| |
| #define DEC_APC_LATCH_EN(x) (((x) >> 5) & 0x1) |
| #define DEC_APC_VIO_ADDR(x) (x) |
| #define DEC_APC_VIO_ID(x) ((x) & 0xFFFFF) |
| #define DEC_APC_VIO_WR_RD(x) (((x) >> 17) & 0x3) |
| #define DEC_MPU_LATCH_EN(x) (((x) >> 4) & 0xFF) |
| #define DEC_MPU_VIO_ADDR(x) ((x) & 0x1FFFFF) |
| #define DEC_MPU_VIO_ID(x) ((x) & 0xFFFFF) |
| #define DEC_MPU_VIO_WR_RD(x) (((x) >> 17) & 0x3) |
| |
| #define MAX_CLK_NUM (8) |
| |
| struct mmsram_dev { |
| struct device *dev; |
| void __iomem *ctrl_base; |
| void __iomem *sram_paddr; |
| void __iomem *sram_vaddr; |
| ssize_t sram_size; |
| struct clk *clk[MAX_CLK_NUM]; |
| const char *clk_name[MAX_CLK_NUM]; |
| }; |
| |
| enum smc_mmsram_request { |
| MMSRAM_ENABLE_SECURE, |
| }; |
| |
| static struct work_struct dump_reg_work; |
| |
| static struct mmsram_dev *mmsram; |
| #if !IS_ENABLED(CONFIG_FPGA_EARLY_PORTING) |
| static atomic_t clk_ref = ATOMIC_INIT(0); |
| #endif |
| static bool is_secure_on; |
| static bool debug_enable; |
| |
| /* MTCMOS clocks should be defined before CG clocks in DTS */ |
| static int set_clk_enable(bool is_enable) |
| { |
| int ret = 0; |
| |
| #if !IS_ENABLED(CONFIG_FPGA_EARLY_PORTING) |
| int i, j; |
| |
| if (is_enable) { |
| pm_runtime_get_sync(mmsram->dev); |
| for (i = 0; i < MAX_CLK_NUM; i++) { |
| if (mmsram->clk[i]) |
| ret = clk_prepare_enable(mmsram->clk[i]); |
| |
| if (ret) { |
| pr_notice("mmsram clk(%s) enable fail:%d\n", |
| mmsram->clk_name[i], ret); |
| for (j = i - 1; j >= 0; j--) |
| clk_disable_unprepare(mmsram->clk[j]); |
| return ret; |
| } |
| } |
| |
| atomic_inc(&clk_ref); |
| } else { |
| for (i = MAX_CLK_NUM - 1; i >= 0; i--) |
| if (mmsram->clk[i]) |
| clk_disable_unprepare(mmsram->clk[i]); |
| pm_runtime_put_sync(mmsram->dev); |
| atomic_dec(&clk_ref); |
| } |
| #endif |
| if (debug_enable) |
| pr_notice("%s:%d\n", __func__, is_enable); |
| return ret; |
| } |
| |
| static void set_reg_secure(bool is_on) |
| { |
| struct arm_smccc_res res; |
| |
| arm_smccc_smc(MTK_SIP_MMSRAM_CONTROL, MMSRAM_ENABLE_SECURE, |
| is_on ? 1 : 0, 0, 0, 0, 0, 0, &res); |
| } |
| |
| static s32 before_reg_rw(void) |
| { |
| if (set_clk_enable(true)) { |
| pr_notice("%s: enable clk fail\n", __func__); |
| return -EINVAL; |
| } |
| set_reg_secure(false); |
| return 0; |
| } |
| |
| static void after_reg_rw(void) |
| { |
| set_reg_secure(true); |
| set_clk_enable(false); |
| } |
| |
| |
| void mmsram_set_secure(bool secure_on) |
| { |
| if (secure_on == is_secure_on) |
| return; |
| is_secure_on = secure_on; |
| if (before_reg_rw()) { |
| pr_notice("%s: error before reg rw\n", __func__); |
| return; |
| } |
| if (secure_on) |
| writel(BIT_SECURE_ON, mmsram->ctrl_base + MMSYSRAM_SEC_CTRL0); |
| else |
| writel(0, mmsram->ctrl_base + MMSYSRAM_SEC_CTRL0); |
| after_reg_rw(); |
| } |
| EXPORT_SYMBOL_GPL(mmsram_set_secure); |
| |
| static void init_mmsram_reg(void) |
| { |
| if (before_reg_rw()) { |
| pr_notice("%s: error before reg rw\n", __func__); |
| return; |
| } |
| writel((0x1 << 24) | 0x160000, |
| mmsram->ctrl_base + MMSYSRAM_SEC_ADDR0); |
| |
| writel(0x0, mmsram->ctrl_base + MMSYSRAM_INSTA0); |
| writel(0x0, mmsram->ctrl_base + MMSYSRAM_INSTA1); |
| |
| writel(BIT_APC_LATCH_EN, mmsram->ctrl_base + FVLD_APC0_LATCH_EN); |
| writel(BIT_APC_LATCH_EN, mmsram->ctrl_base + FVLD_APC1_LATCH_EN); |
| writel(BIT_MPU_LATCH_EN, mmsram->ctrl_base + FVLD_MPU0_LATCH_EN); |
| writel(BIT_MPU_LATCH_EN, mmsram->ctrl_base + FVLD_MPU1_LATCH_EN); |
| writel(BIT_INSTA0, mmsram->ctrl_base + MMSYSRAM_INTEN0); |
| writel(BIT_INSTA1, mmsram->ctrl_base + MMSYSRAM_INTEN1); |
| |
| after_reg_rw(); |
| } |
| |
| int mmsram_power_on(void) |
| { |
| int ret = 0; |
| |
| set_clk_enable(true); |
| init_mmsram_reg(); |
| if (debug_enable) |
| pr_notice("mmsram power on\n"); |
| return ret; |
| } |
| EXPORT_SYMBOL_GPL(mmsram_power_on); |
| |
| void mmsram_power_off(void) |
| { |
| set_clk_enable(false); |
| if (debug_enable) |
| pr_notice("mmsram power off\n"); |
| } |
| EXPORT_SYMBOL_GPL(mmsram_power_off); |
| |
| int enable_mmsram(void) |
| { |
| pr_notice("enable mmsram\n"); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(enable_mmsram); |
| |
| void disable_mmsram(void) |
| { |
| pr_notice("disable mmsram\n"); |
| } |
| EXPORT_SYMBOL_GPL(disable_mmsram); |
| |
| void mmsram_get_info(struct mmsram_data *data) |
| { |
| data->paddr = mmsram->sram_paddr; |
| data->vaddr = mmsram->sram_vaddr; |
| data->size = mmsram->sram_size; |
| pr_notice("%s: pa:%#lx va:%#lx size:%#lx\n", |
| __func__, (unsigned long)data->paddr, |
| (unsigned long)data->vaddr, data->size); |
| } |
| EXPORT_SYMBOL_GPL(mmsram_get_info); |
| |
| static void dump_reg_func(struct work_struct *work) |
| { |
| u32 interrupt_monitor0, interrupt_monitor1; |
| void __iomem *ctrl_base = mmsram->ctrl_base; |
| |
| if (before_reg_rw()) { |
| pr_notice("%s: error before reg rw\n", __func__); |
| return; |
| } |
| |
| /* Print debug log */ |
| interrupt_monitor0 = readl(ctrl_base + MMSYSRAM_INSTA0); |
| interrupt_monitor1 = readl(ctrl_base + MMSYSRAM_INSTA1); |
| pr_notice("apc0_vio_decerr:%#x apc0_vio_wr_rd:%#x\n", |
| DEC_APC0_VIO_DECERR(interrupt_monitor0), |
| DEC_APC0_VIO_WR_RD(interrupt_monitor0)); |
| pr_notice("apc1_vio_decerr:%#x apc1_vio_wr_rd:%#x\n", |
| DEC_APC1_VIO_DECERR(interrupt_monitor0), |
| DEC_APC1_VIO_WR_RD(interrupt_monitor0)); |
| pr_notice("mpu0_vio_devapc:%#x mpu0_vio_wr_rd:%#x\n", |
| DEC_MPU0_VIO_DEVAPC(interrupt_monitor0), |
| DEC_MPU0_VIO_WR_RD(interrupt_monitor0)); |
| pr_notice("mpu1_vio_devapc:%#x mpu1_vio_wr_rd:%#x\n", |
| DEC_MPU1_VIO_DEVAPC(interrupt_monitor1), |
| DEC_MPU1_VIO_WR_RD(interrupt_monitor1)); |
| pr_notice("apc0_latch_en:%#x apc0_vio_addr:%#x\n", |
| DEC_APC_LATCH_EN(readl(ctrl_base + FVLD_APC0_LATCH_EN)), |
| DEC_APC_VIO_ADDR(readl(ctrl_base + FVLD_APC0_VIO_ADDR))); |
| pr_notice("apc0_vio_id:%#x apc0_vio_wr_rd:%#x\n", |
| DEC_APC_VIO_ID(readl(ctrl_base + FVLD_APC0_VIO_ID)), |
| DEC_APC_VIO_WR_RD(readl(ctrl_base + FVLD_APC0_VIO_WR_RD))); |
| |
| pr_notice("apc1_latch_en:%#x apc1_vio_addr:%#x\n", |
| DEC_APC_LATCH_EN(readl(ctrl_base + FVLD_APC1_LATCH_EN)), |
| DEC_APC_VIO_ADDR(readl(ctrl_base + FVLD_APC1_VIO_ADDR))); |
| pr_notice("apc1_vio_id:%#x apc1_vio_wr_rd:%#x\n", |
| DEC_APC_VIO_ID(readl(ctrl_base + FVLD_APC1_VIO_ID)), |
| DEC_APC_VIO_WR_RD(readl(ctrl_base + FVLD_APC1_VIO_WR_RD))); |
| |
| pr_notice("mpu0_latch_en:%#x mpu0_vio_addr:%#x\n", |
| DEC_MPU_LATCH_EN(readl(ctrl_base + FVLD_MPU0_LATCH_EN)), |
| DEC_MPU_VIO_ADDR(readl(ctrl_base + FVLD_MPU0_VIO_ADDR))); |
| |
| pr_notice("mpu0_vio_id:%#x mpu0_vio_wr_rd:%#x\n", |
| DEC_MPU_VIO_ID(readl(ctrl_base + FVLD_MPU0_VIO_ID)), |
| DEC_MPU_VIO_WR_RD(readl(ctrl_base + FVLD_MPU0_VIO_WR_RD))); |
| pr_notice("mpu1_latch_en:%#x mpu1_vio_addr:%#x\n", |
| DEC_MPU_LATCH_EN(readl(ctrl_base + FVLD_MPU1_LATCH_EN)), |
| DEC_MPU_VIO_ADDR(readl(ctrl_base + FVLD_MPU1_VIO_ADDR))); |
| pr_notice("mpu1_vio_id:%#x mpu1_vio_wr_rd:%#x\n", |
| DEC_MPU_VIO_ID(readl(ctrl_base + FVLD_MPU1_VIO_ID)), |
| DEC_MPU_VIO_WR_RD(readl(ctrl_base + FVLD_MPU1_VIO_WR_RD))); |
| |
| writel(0x0, ctrl_base + MMSYSRAM_INSTA0); |
| writel(0x0, ctrl_base + MMSYSRAM_INSTA1); |
| |
| after_reg_rw(); |
| #if IS_ENABLED(CONFIG_MTK_AEE_FEATURE) |
| aee_kernel_warning("MMSRAM", "MMSRAM Violation."); |
| #endif |
| } |
| |
| static irqreturn_t mmsram_irq_handler(int irq, void *data) |
| { |
| pr_notice("handle mmsram irq!\n"); |
| schedule_work(&dump_reg_work); |
| return IRQ_HANDLED; |
| } |
| |
| static int mmsram_probe(struct platform_device *pdev) |
| { |
| struct resource *res; |
| int irq, err, clk_num, i; |
| |
| mmsram = devm_kzalloc(&pdev->dev, sizeof(*mmsram), GFP_KERNEL); |
| if (!mmsram) |
| return -ENOMEM; |
| |
| mmsram->dev = &pdev->dev; |
| |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| if (!res) { |
| dev_notice(&pdev->dev, "could not get resource for ctrl\n"); |
| return -EINVAL; |
| } |
| |
| mmsram->ctrl_base = devm_ioremap_resource(&pdev->dev, res); |
| if (IS_ERR(mmsram->ctrl_base)) { |
| dev_notice(&pdev->dev, |
| "could not ioremap resource for ctrl\n"); |
| return PTR_ERR(mmsram->ctrl_base); |
| } |
| |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 1); |
| if (!res) { |
| dev_notice(&pdev->dev, "could not get resource for memory\n"); |
| return -EINVAL; |
| } |
| mmsram->sram_paddr = (void *)res->start; |
| mmsram->sram_size = resource_size(res); |
| mmsram->sram_vaddr = (void __iomem *) devm_memremap(&pdev->dev, |
| res->start, mmsram->sram_size, MEMREMAP_WT); |
| if (IS_ERR(mmsram->sram_vaddr)) { |
| dev_notice(&pdev->dev, |
| "could not ioremap resource for memory\n"); |
| return PTR_ERR(mmsram->sram_vaddr); |
| } |
| |
| dev_notice(&pdev->dev, "probe va=%p pa=%p size=%#lx\n", |
| mmsram->sram_vaddr, mmsram->sram_paddr, |
| mmsram->sram_size); |
| |
| if (!IS_ENABLED(CONFIG_FPGA_EARLY_PORTING)) { |
| clk_num = of_property_read_string_array(pdev->dev.of_node, |
| "clock-names", &mmsram->clk_name[0], MAX_CLK_NUM); |
| for (i = 0; i < clk_num; i++) { |
| mmsram->clk[i] = devm_clk_get(&pdev->dev, |
| mmsram->clk_name[i]); |
| if (IS_ERR(mmsram->clk[i])) { |
| dev_notice(&pdev->dev, |
| "could not get mmsram clk(%s) info\n", |
| mmsram->clk_name[i]); |
| return PTR_ERR(mmsram->clk[i]); |
| } |
| } |
| } |
| |
| irq = platform_get_irq(pdev, 0); |
| if (irq < 0) { |
| dev_notice(&pdev->dev, "failed to get irq (%d)\n", irq); |
| return -EINVAL; |
| } |
| err = devm_request_irq(&pdev->dev, irq, mmsram_irq_handler, IRQF_SHARED, |
| "mtk_mmsram", mmsram); |
| if (err) { |
| dev_notice(&pdev->dev, |
| "failed to register ISR %d (%d)", irq, err); |
| return err; |
| } |
| pm_runtime_enable(&pdev->dev); |
| |
| INIT_WORK(&dump_reg_work, dump_reg_func); |
| |
| return 0; |
| } |
| |
| #define RESULT_STR_LEN 8 |
| int test_mmsram; |
| struct mmsram_data *data; |
| int set_test_mmsram(const char *val, const struct kernel_param *kp) |
| { |
| int result; |
| u32 test_case, offset, value; |
| const char *test_str = "12345678"; |
| char result_str[RESULT_STR_LEN + 1] = {0}; |
| |
| result = sscanf(val, "%d %i %i", &test_case, &offset, &value); |
| if (result != 3) { |
| pr_notice("invalid input: %s, result(%d)\n", val, result); |
| return -EINVAL; |
| } |
| pr_notice("%s (test_case, offset, value): (%d,%#x,%#x)\n", |
| __func__, test_case, offset, value); |
| |
| switch (test_case) { |
| case 0: /* Initialize */ |
| data = kzalloc(sizeof(*data), GFP_KERNEL); |
| mmsram_get_info(data); |
| enable_mmsram(); |
| mmsram_power_on(); |
| break; |
| case 1: /* Uninitialize */ |
| mmsram_power_off(); |
| disable_mmsram(); |
| kfree(data); |
| break; |
| case 2: /* Write value to offset */ |
| writel(value, data->vaddr + offset); |
| value = readl(data->vaddr + offset); |
| pr_notice("write %#x success\n", value); |
| break; |
| case 3: /* Read value from offset */ |
| value = readl(data->vaddr + offset); |
| pr_notice("read %#x success\n", value); |
| break; |
| case 4: /* Write test string to offset */ |
| memcpy_toio(data->vaddr + offset, test_str, RESULT_STR_LEN); |
| pr_notice("write str:%s success\n", test_str); |
| break; |
| case 5: /* Write test string from offset */ |
| memcpy_fromio(result_str, data->vaddr, RESULT_STR_LEN); |
| result_str[RESULT_STR_LEN] = '\0'; |
| pr_notice("read str:%s success\n", result_str); |
| break; |
| default: |
| pr_notice("wrong input test_case:%d\n", test_case); |
| } |
| |
| return 0; |
| } |
| static struct kernel_param_ops test_mmsram_ops = { |
| .set = set_test_mmsram, |
| .get = param_get_int, |
| }; |
| module_param_cb(test_mmsram, &test_mmsram_ops, &test_mmsram, 0644); |
| MODULE_PARM_DESC(test_mmsram, "test mmsram"); |
| |
| static struct kernel_param_ops debug_enable_ops = { |
| .set = param_set_bool, |
| .get = param_get_bool, |
| }; |
| module_param_cb( |
| debug_enable, &debug_enable_ops, &debug_enable, 0644); |
| MODULE_PARM_DESC(debug_enable, "enable or disable mmsram debug log"); |
| |
| static const struct of_device_id of_mmsram_match_tbl[] = { |
| { |
| .compatible = "mediatek,mmsram", |
| }, |
| {} |
| }; |
| |
| static struct platform_driver mmsram_drv = { |
| .probe = mmsram_probe, |
| .driver = { |
| .name = "mtk-mmsram", |
| .owner = THIS_MODULE, |
| .of_match_table = of_mmsram_match_tbl, |
| }, |
| }; |
| static int __init mtk_mmsram_init(void) |
| { |
| s32 status; |
| |
| status = platform_driver_register(&mmsram_drv); |
| if (status) { |
| pr_notice("Failed to register mmsram driver(%d)\n", status); |
| return -ENODEV; |
| } |
| return 0; |
| } |
| static void __exit mtk_mmsram_exit(void) |
| { |
| platform_driver_unregister(&mmsram_drv); |
| } |
| module_init(mtk_mmsram_init); |
| module_exit(mtk_mmsram_exit); |
| MODULE_DESCRIPTION("MTK MMSRAM driver"); |
| MODULE_AUTHOR("Anthony Huang<anthony.huang@mediatek.com>"); |
| MODULE_LICENSE("GPL"); |