// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 MediaTek Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "spmtwam.h" #define CREATE_TRACE_POINTS #include "spmtwam_events.h" /* spmtwam node operations: * 1. setup twam speed mode (optional, default high) * echo [0|1] > /proc/spmtwam/speed_mode * 2. setup signal [0-3], id [0-31], and monitor type [0-3] for each channel * echo [0-3] > /proc/spmtwam/ch0/signal * echo [0-31] > /proc/spmtwam/ch0/id * echo [0-3] > /proc/spmtwam/ch0/monitor_type * 3. start monitor (monitor up to 4 channels at the same time) * echo 1 > /proc/spmtwam/state * 4. stop monitor (will clear all configs) * echo 0 > /proc/spmtwam/state * 5. check current config state * cat /proc/spmtwam/state */ struct spmtwam_local_cfg { bool enable; struct spmtwam_cfg cfg; }; static struct spmtwam_local_cfg cur; static void setup_default_cfg(struct spmtwam_local_cfg *c) { int i; struct spmtwam_cfg *cfg = &c->cfg; c->enable = false; cfg->spmtwam_speed_mode = DEFAULT_SPEED_MODE; /* spmtwam_window_len will be updated according to speed mode */ cfg->spmtwam_window_len = 0; for (i = 0 ; i < 4; i++) { cfg->ch[i].signal = 0; cfg->ch[i].id = 0xFFFFFFFF; /* default disabled */ cfg->ch[i].montype = DEFAULT_MONTYPE; } } static void spmtwam_handler(struct spmtwam_result *r) { int i; struct spmtwam_cfg *cfg = &r->cfg; trace_spmtwam(r->value[0], r->value[1], r->value[2], r->value[3]); for (i = 0; i < 4; i++) { if (cfg->ch[i].id < 32) pr_info("spmtwam (sel%d:%d) ratio: %u/1000 %s, %u\n", cfg->ch[i].signal, cfg->ch[i].id, cfg->spmtwam_speed_mode ? GET_EVENT_RATIO_SPEED(r->value[i]) : GET_EVENT_RATIO_NORMAL(r->value[i]), cfg->spmtwam_speed_mode ? "high" : "normal", r->value[i]); } } static void spmtwam_profile_enable(bool enable) { int ret = 0; /* verify local spmtwam config */ if (!enable) setup_default_cfg(&cur); ret = spmtwam_monitor(enable, &cur.cfg, spmtwam_handler); if (ret == 0) cur.enable = enable; } static char dbgbuf[1024] = {0}; #define log2buf(p, s, fmt, args...) \ (p += scnprintf(p, sizeof(s) - strlen(s), fmt, ##args)) #undef log #define log(fmt, args...) log2buf(p, dbgbuf, fmt, ##args) static ssize_t dbg_read(struct file *filp, char __user *userbuf, size_t count, loff_t *f_pos) { int i, len = 0; char *p = dbgbuf; struct spmtwam_cfg *cfg = &cur.cfg; p[0] = '\0'; log("spmtwam state:\n"); log("enable %d\n", cur.enable ? 1 : 0); log("speed_mode %d (0: low, 1: high)\n", cfg->spmtwam_speed_mode ? 1 : 0); log("window_len %u (0x%x)\n", cfg->spmtwam_window_len, cfg->spmtwam_window_len); for (i = 0; i < 4; i++) if (cfg->ch[i].id < 32) log("ch%d: signal %u id %u montype %u (%s)\n", i, cfg->ch[i].signal, cfg->ch[i].id, cfg->ch[i].montype, cfg->ch[i].montype == 0 ? "rising" : cfg->ch[i].montype == 1 ? "falling" : cfg->ch[i].montype == 2 ? "high level" : cfg->ch[i].montype == 3 ? "low level" : "unknown"); else log("ch%d: off\n", i); len = p - dbgbuf; return simple_read_from_buffer(userbuf, count, f_pos, dbgbuf, len); } static ssize_t dbg_write(struct file *fp, const char __user *userbuf, size_t count, loff_t *f_pos) { unsigned int en = 0; if (kstrtou32_from_user(userbuf, count, 10, &en)) return -EFAULT; spmtwam_profile_enable(en ? true : false); return count; } const static struct file_operations dbg_fops = { .owner = THIS_MODULE, .read = dbg_read, .write = dbg_write, }; static ssize_t var_read(struct file *fp, char __user *userbuf, size_t count, loff_t *f_pos) { unsigned int *v = PDE_DATA(file_inode(fp)); int len = 0; char *p = dbgbuf; p[0] = '\0'; log("%d\n", *v); len = p - dbgbuf; return simple_read_from_buffer(userbuf, count, f_pos, dbgbuf, len); } static ssize_t var_write(struct file *fp, const char __user *userbuf, size_t count, loff_t *f_pos) { unsigned int *v = PDE_DATA(file_inode(fp)); if (kstrtou32_from_user(userbuf, count, 10, v)) return -EFAULT; return count; } const static struct file_operations var_fops = { .owner = THIS_MODULE, .read = var_read, .write = var_write, }; static struct proc_dir_entry *spmtwam_droot; static int spmtwam_procfs_init(void) { int i; struct proc_dir_entry *ch[4]; struct spmtwam_cfg *cfg = &cur.cfg; /* setup local default spmtwam config*/ setup_default_cfg(&cur); /* create debugfs for this test driver */ spmtwam_droot = proc_mkdir("spmtwam", NULL); if (spmtwam_droot) { proc_create("state", 0644, spmtwam_droot, &dbg_fops); proc_create_data("speed_mode", 0644, spmtwam_droot, &var_fops, (void *) &(cfg->spmtwam_speed_mode)); proc_create_data("window_len", 0644, spmtwam_droot, &var_fops, (void *) &(cfg->spmtwam_window_len)); ch[0] = proc_mkdir("ch0", spmtwam_droot); ch[1] = proc_mkdir("ch1", spmtwam_droot); ch[2] = proc_mkdir("ch2", spmtwam_droot); ch[3] = proc_mkdir("ch3", spmtwam_droot); for (i = 0 ; i < 4; i++) { if (ch[i]) { proc_create_data("signal", 0644, ch[i], &var_fops, (void *)&(cfg->ch[i].signal)); proc_create_data("id", 0644, ch[i], &var_fops, (void *)&(cfg->ch[i].id)); proc_create_data("montype", 0644, ch[i], &var_fops, (void *)&(cfg->ch[i].montype)); } } } return 0; } static void spmtwam_procfs_exit(void) { spmtwam_profile_enable(false); remove_proc_entry("spmtwam", NULL); } /* ----------------------------------------------------------------------- */ #define SPMTWAM_COMPATIBLE_STRING "mediatek,spmtwam" static DEFINE_SPINLOCK(__spmtwam_lock); static bool g_spmtwam_init; struct spmtwam_reg_pair { char *name; void __iomem *addr; }; enum { SPM_TWAM_CON = 0, SPM_TWAM_WINDOW_LEN, SPM_TWAM_IDLE_SEL, SPM_IRQ_MASK, SPM_IRQ_STA, SPM_TWAM_LAST_STA0, SPM_TWAM_LAST_STA1, SPM_TWAM_LAST_STA2, SPM_TWAM_LAST_STA3, SPM_TWAM_MAXNUM, }; static struct spmtwam_reg_pair reg[SPM_TWAM_MAXNUM] = { {"spm_twam_con", NULL}, {"spm_twam_window_len", NULL}, {"spm_twam_idle_sel", NULL}, {"spm_irq_mask", NULL}, {"spm_irq_sta", NULL}, {"spm_twam_last_sta0", NULL}, {"spm_twam_last_sta1", NULL}, {"spm_twam_last_sta2", NULL}, {"spm_twam_last_sta3", NULL}, }; #define REG(name) (reg[name].addr) /* SPM_TWAM_CON */ #define REG_TWAM_ENABLE_LSB (1U << 0) /* 1b */ #define REG_TWAM_SPEED_MODE_EN_LSB (1U << 1) /* 1b */ /* SPM_IRQ_STA */ #define TWAM_IRQ_LSB (1U << 2) /* 1b */ /* SPM_IRQ_MASK */ #define ISRM_TWAM (1U << 2) #define ISRM_PCM_RETURN (1U << 3) #define ISRM_RET_IRQ0 (1U << 8) #define ISRM_RET_IRQ1 (1U << 9) #define ISRM_RET_IRQ2 (1U << 10) #define ISRM_RET_IRQ3 (1U << 11) #define ISRM_RET_IRQ4 (1U << 12) #define ISRM_RET_IRQ5 (1U << 13) #define ISRM_RET_IRQ6 (1U << 14) #define ISRM_RET_IRQ7 (1U << 15) #define ISRM_RET_IRQ8 (1U << 16) #define ISRM_RET_IRQ9 (1U << 17) #define ISRM_RET_IRQ10 (1U << 18) #define ISRM_RET_IRQ11 (1U << 19) #define ISRM_RET_IRQ12 (1U << 20) #define ISRM_RET_IRQ13 (1U << 21) #define ISRM_RET_IRQ14 (1U << 22) #define ISRM_RET_IRQ15 (1U << 23) #define ISRM_RET_IRQ_AUX (\ ISRM_RET_IRQ0 | ISRM_RET_IRQ1 | ISRM_RET_IRQ2 | ISRM_RET_IRQ3 | \ ISRM_RET_IRQ4 | ISRM_RET_IRQ5 | ISRM_RET_IRQ6 | ISRM_RET_IRQ7 | \ ISRM_RET_IRQ8 | ISRM_RET_IRQ9 | ISRM_RET_IRQ10 | ISRM_RET_IRQ11 | \ ISRM_RET_IRQ12 | ISRM_RET_IRQ13 | ISRM_RET_IRQ14 | ISRM_RET_IRQ15) #define ISRM_ALL_EXC_TWAM (ISRM_RET_IRQ_AUX | ISRM_PCM_RETURN) #define ISRM_ALL (ISRM_ALL_EXC_TWAM | ISRM_TWAM) #define ISRS_TWAM (1U << 2) #define ISRC_TWAM (ISRS_TWAM) #define write32(addr, value) writel(value, addr) #define read32(addr) readl(addr) #define sig(x) (cfg->ch[x].signal) #define id(x) (cfg->ch[x].id) #define montype(x) (cfg->ch[x].montype) static bool spmtwam_channel_valid[4] = {false, false, false, false}; static spmtwam_handler_t spmtwam_handler_ptr; int spmtwam_monitor(bool enable, struct spmtwam_cfg *cfg, spmtwam_handler_t handler) { unsigned long flags; int i; if (g_spmtwam_init == false) { pr_info("spmtwam: no such device\n"); return -ENODEV; } if (enable) { if (cfg == NULL || handler == NULL) { pr_info("spmtwam: null parameter(s)\n"); return -EINVAL; } if (spmtwam_handler_ptr != NULL) { pr_info("spmtwam: already enable ?\n"); return -EAGAIN; } if (REG(SPM_IRQ_MASK) == NULL || REG(SPM_TWAM_IDLE_SEL) == NULL || REG(SPM_TWAM_CON) == NULL || REG(SPM_TWAM_WINDOW_LEN) == NULL) { pr_info("spmtwam: register is not initialized\n"); return -EINVAL; } /* Set default value for high/normal speed mode */ if (cfg->spmtwam_window_len == 0) cfg->spmtwam_window_len = cfg->spmtwam_speed_mode ? WINDOW_LEN_SPEED : WINDOW_LEN_NORMAL; spin_lock_irqsave(&__spmtwam_lock, flags); spmtwam_handler_ptr = handler; for (i = 0; i < 4; i++) spmtwam_channel_valid[i] = (id(i) < 32) ? true : false; write32(REG(SPM_IRQ_MASK), read32(REG(SPM_IRQ_MASK)) & ~ISRM_TWAM); /* SPM_TWAM_IDLE_SEL * [6:0] signal select 0 (sig 2 bits, id 5 bits) * [14:8] signal select 1 * [22:16] signal select 2 * [30:24] signal select 3 */ write32(REG(SPM_TWAM_IDLE_SEL), ((((sig(0) & 0x3) << 5) | (id(0) & 0x1f)) << 0) | ((((sig(1) & 0x3) << 5) | (id(1) & 0x1f)) << 8) | ((((sig(2) & 0x3) << 5) | (id(2) & 0x1f)) << 16) | ((((sig(3) & 0x3) << 5) | (id(3) & 0x1f)) << 24)); /* SPM_TWAM_CON * [0] twam enable - 0 disable, 1 enable * [1] twam speed mode - 0 32k, 1 high speed * [5:4] monitor type 0 * [7:6] monitor type 1 * [9:8] monitor type 2 * [11:10] monitor type 3 * (0 rising, 1 falling, 2 high level, 3 low level) */ write32(REG(SPM_TWAM_CON), REG_TWAM_ENABLE_LSB | (cfg->spmtwam_speed_mode ? REG_TWAM_SPEED_MODE_EN_LSB : 0) | ((montype(0) & 0x3) << 4) | ((montype(1) & 0x3) << 6) | ((montype(2) & 0x3) << 8) | ((montype(3) & 0x3) << 10)); /* SPM_TWAM_WINDOW_LEN */ write32(REG(SPM_TWAM_WINDOW_LEN), cfg->spmtwam_window_len); spin_unlock_irqrestore(&__spmtwam_lock, flags); for (i = 0; i < 4 ; i++) if (spmtwam_channel_valid[i]) pr_debug("spmtwam: enable TWAM %u/%u (%s)\n", sig(i), id(i), cfg->spmtwam_speed_mode ? "32k" : "high speed"); } else { spin_lock_irqsave(&__spmtwam_lock, flags); spmtwam_handler_ptr = NULL; for (i = 0; i < 4; i++) spmtwam_channel_valid[i] = false; if (REG(SPM_IRQ_MASK) == NULL || REG(SPM_IRQ_STA) == NULL || REG(SPM_TWAM_CON) == NULL) { pr_info("spmtwam: register is not initialized\n"); return -EINVAL; } write32(REG(SPM_TWAM_CON), read32(REG(SPM_TWAM_CON)) & ~REG_TWAM_ENABLE_LSB); write32(REG(SPM_IRQ_MASK), read32(REG(SPM_IRQ_MASK)) | ISRM_TWAM); write32(REG(SPM_IRQ_STA), ISRC_TWAM); spin_unlock_irqrestore(&__spmtwam_lock, flags); pr_debug("spmtwam: disable TWAM\n"); } return 0; } EXPORT_SYMBOL(spmtwam_monitor); static irqreturn_t spm_irq0_handler(int irq, void *dev_id) { u32 isr = 0; unsigned long flags; struct spmtwam_result r; struct spmtwam_cfg *cfg = &r.cfg; u32 twam_idle_sel = 0; u32 twam_con = 0; int i; spin_lock_irqsave(&__spmtwam_lock, flags); /* get ISR status */ isr = read32(REG(SPM_IRQ_STA)); if (isr & ISRS_TWAM) { /* return current configs */ twam_idle_sel = read32(REG(SPM_TWAM_IDLE_SEL)); cfg->ch[0].signal = ((twam_idle_sel & 0x00000060) >> 5); cfg->ch[1].signal = ((twam_idle_sel & 0x00006000) >> 13); cfg->ch[2].signal = ((twam_idle_sel & 0x00600000) >> 21); cfg->ch[3].signal = ((twam_idle_sel & 0x60000000) >> 29); cfg->ch[0].id = (twam_idle_sel & 0x0000001F); cfg->ch[1].id = ((twam_idle_sel & 0x00001F00) >> 8); cfg->ch[2].id = ((twam_idle_sel & 0x001F0000) >> 16); cfg->ch[3].id = ((twam_idle_sel & 0x1F000000) >> 24); twam_con = read32(REG(SPM_TWAM_CON)); cfg->ch[0].montype = ((twam_con & 0x30) >> 4); cfg->ch[1].montype = ((twam_con & 0xc0) >> 6); cfg->ch[2].montype = ((twam_con & 0x300) >> 8); cfg->ch[3].montype = ((twam_con & 0xc00) >> 10); cfg->spmtwam_speed_mode = (twam_con & REG_TWAM_SPEED_MODE_EN_LSB) ? 1 : 0; cfg->spmtwam_window_len = read32(REG(SPM_TWAM_WINDOW_LEN)); /* return result */ r.value[0] = read32(REG(SPM_TWAM_LAST_STA0)); r.value[1] = read32(REG(SPM_TWAM_LAST_STA1)); r.value[2] = read32(REG(SPM_TWAM_LAST_STA2)); r.value[3] = read32(REG(SPM_TWAM_LAST_STA3)); for (i = 0; i < 4 ; i++) if (spmtwam_channel_valid[i] == false) { cfg->ch[i].id = 0xFFFFFFFF; r.value[i] = 0; } udelay(40); /* delay 1T @ 32K */ } /* clean ISR status */ write32(REG(SPM_IRQ_MASK), read32(REG(SPM_IRQ_MASK)) | ISRM_ALL_EXC_TWAM); write32(REG(SPM_IRQ_STA), isr); spin_unlock_irqrestore(&__spmtwam_lock, flags); if ((isr & ISRS_TWAM) && spmtwam_handler_ptr) spmtwam_handler_ptr(&r); return IRQ_HANDLED; } static int spmtwam_probe(struct platform_device *pdev) { int i, ret = 0; struct device_node *node; void __iomem *base; unsigned int irq0; unsigned int offset; node = of_find_compatible_node(NULL, NULL, SPMTWAM_COMPATIBLE_STRING); if (!node) { pr_info("failed to get spmtwam node\n"); return -ENOENT; } base = of_iomap(node, 0); if (!base) { pr_info("failed to get spmtwam base\n"); return -ENOENT; } irq0 = irq_of_parse_and_map(node, 0); if (!irq0) { pr_info("failed to get spmtwam irq0\n"); return -ENOENT; } for (i = 0; i < sizeof(reg)/sizeof(struct spmtwam_reg_pair); i++) { offset = 0; if (of_property_read_u32(node, reg[i].name, &offset)) { pr_info("failed to parse '%s' in spmtwam dts\n", reg[i].name); return -ENOENT; } reg[i].addr = base + offset; pr_info("%s 0x%x\n", reg[i].name, offset); } ret = request_irq(irq0, spm_irq0_handler, (IRQF_TRIGGER_NONE | IRQF_NO_SUSPEND), "TWAM", NULL); if (ret) return ret; pr_info("spmtwam base %p irq %u\n", base, irq0); return ret; } static const struct of_device_id spmtwam_of_ids[] = { {.compatible = SPMTWAM_COMPATIBLE_STRING,}, {} }; static struct platform_driver spmtwam_drv = { .probe = spmtwam_probe, .driver = { .name = "twam", .owner = THIS_MODULE, .of_match_table = spmtwam_of_ids, }, }; static int __init spmtwam_init(void) { int ret = 0; ret = platform_driver_register(&spmtwam_drv); g_spmtwam_init = (ret == 0); /* create debugfs node */ spmtwam_procfs_init(); return ret; } module_init(spmtwam_init); static void __exit spmtwam_exit(void) { /* remove debugfs node */ spmtwam_procfs_exit(); g_spmtwam_init = false; return platform_driver_unregister(&spmtwam_drv); } module_exit(spmtwam_exit); MODULE_DESCRIPTION("Mediatek MT67XX spmtwam driver"); MODULE_AUTHOR("JM Lai "); MODULE_LICENSE("GPL");