// SPDX-License-Identifier: GPL-2.0 // // Copyright (c) 2021 MediaTek Inc. // Copyright (c) 2025 Collabora Ltd // AngeloGioacchino Del Regno #include #include #include #include #include #include #include #include #include #include #include #include #define SWINF_IDLE 0x00 #define SWINF_WFVLDCLR 0x06 #define GET_SWINF(x) (((x) >> 1) & 0x7) #define PMIF_CMD_REG_0 0 #define PMIF_CMD_REG 1 #define PMIF_CMD_EXT_REG 2 #define PMIF_CMD_EXT_REG_LONG 3 #define PMIF_DELAY_US 2 #define PMIF_TIMEOUT_US (10 * 1000) #define PMIF_CHAN_OFFSET 0x5 #define PMIF_RCS_IRQ_MASK GENMASK(7, 0) #define PMIF_MAX_BUSES 2 #define PMIF_MAX_CLKS 3 #define SPMI_OP_ST_BUSY 1 struct ch_reg { u32 ch_sta; u32 wdata; u32 rdata; u32 ch_send; u32 ch_rdy; }; struct pmif_data { const u32 *regs; const u32 *spmimst_regs; u32 soc_chan; u8 spmi_ver; u32 num_spmi_buses; }; struct pmif_bus { void __iomem *base; void __iomem *spmimst_base; struct spmi_controller *ctrl; struct irq_domain *dom; int irq; struct clk_bulk_data clks[PMIF_MAX_CLKS]; size_t nclks; u8 irq_min_sid; u8 irq_max_sid; u16 irq_en; raw_spinlock_t lock; }; struct pmif { struct pmif_bus bus[PMIF_MAX_BUSES]; struct ch_reg chan; const struct pmif_data *data; }; static const char * const pmif_clock_names[] = { "pmif_sys_ck", "pmif_tmr_ck", "spmimst_clk_mux", }; enum pmif_regs { PMIF_INIT_DONE, PMIF_INF_EN, PMIF_ARB_EN, PMIF_CMDISSUE_EN, PMIF_TIMER_CTRL, PMIF_SPI_MODE_CTRL, PMIF_IRQ_EVENT_EN_0, PMIF_IRQ_FLAG_0, PMIF_IRQ_CLR_0, PMIF_IRQ_EVENT_EN_1, PMIF_IRQ_FLAG_1, PMIF_IRQ_CLR_1, PMIF_IRQ_EVENT_EN_2, PMIF_IRQ_FLAG_2, PMIF_IRQ_CLR_2, PMIF_IRQ_EVENT_EN_3, PMIF_IRQ_FLAG_3, PMIF_IRQ_CLR_3, PMIF_IRQ_EVENT_EN_4, PMIF_IRQ_FLAG_4, PMIF_IRQ_CLR_4, PMIF_WDT_EVENT_EN_0, PMIF_WDT_FLAG_0, PMIF_WDT_EVENT_EN_1, PMIF_WDT_FLAG_1, PMIF_SWINF_0_STA, PMIF_SWINF_0_WDATA_31_0, PMIF_SWINF_0_RDATA_31_0, PMIF_SWINF_0_ACC, PMIF_SWINF_0_VLD_CLR, PMIF_SWINF_1_STA, PMIF_SWINF_1_WDATA_31_0, PMIF_SWINF_1_RDATA_31_0, PMIF_SWINF_1_ACC, PMIF_SWINF_1_VLD_CLR, PMIF_SWINF_2_STA, PMIF_SWINF_2_WDATA_31_0, PMIF_SWINF_2_RDATA_31_0, PMIF_SWINF_2_ACC, PMIF_SWINF_2_VLD_CLR, PMIF_SWINF_3_STA, PMIF_SWINF_3_WDATA_31_0, PMIF_SWINF_3_RDATA_31_0, PMIF_SWINF_3_ACC, PMIF_SWINF_3_VLD_CLR, }; static const u32 mt6873_regs[] = { [PMIF_INIT_DONE] = 0x0000, [PMIF_INF_EN] = 0x0024, [PMIF_ARB_EN] = 0x0150, [PMIF_CMDISSUE_EN] = 0x03B4, [PMIF_TIMER_CTRL] = 0x03E0, [PMIF_SPI_MODE_CTRL] = 0x0400, [PMIF_IRQ_EVENT_EN_0] = 0x0418, [PMIF_IRQ_FLAG_0] = 0x0420, [PMIF_IRQ_CLR_0] = 0x0424, [PMIF_IRQ_EVENT_EN_1] = 0x0428, [PMIF_IRQ_FLAG_1] = 0x0430, [PMIF_IRQ_CLR_1] = 0x0434, [PMIF_IRQ_EVENT_EN_2] = 0x0438, [PMIF_IRQ_FLAG_2] = 0x0440, [PMIF_IRQ_CLR_2] = 0x0444, [PMIF_IRQ_EVENT_EN_3] = 0x0448, [PMIF_IRQ_FLAG_3] = 0x0450, [PMIF_IRQ_CLR_3] = 0x0454, [PMIF_IRQ_EVENT_EN_4] = 0x0458, [PMIF_IRQ_FLAG_4] = 0x0460, [PMIF_IRQ_CLR_4] = 0x0464, [PMIF_WDT_EVENT_EN_0] = 0x046C, [PMIF_WDT_FLAG_0] = 0x0470, [PMIF_WDT_EVENT_EN_1] = 0x0474, [PMIF_WDT_FLAG_1] = 0x0478, [PMIF_SWINF_0_ACC] = 0x0C00, [PMIF_SWINF_0_WDATA_31_0] = 0x0C04, [PMIF_SWINF_0_RDATA_31_0] = 0x0C14, [PMIF_SWINF_0_VLD_CLR] = 0x0C24, [PMIF_SWINF_0_STA] = 0x0C28, [PMIF_SWINF_1_ACC] = 0x0C40, [PMIF_SWINF_1_WDATA_31_0] = 0x0C44, [PMIF_SWINF_1_RDATA_31_0] = 0x0C54, [PMIF_SWINF_1_VLD_CLR] = 0x0C64, [PMIF_SWINF_1_STA] = 0x0C68, [PMIF_SWINF_2_ACC] = 0x0C80, [PMIF_SWINF_2_WDATA_31_0] = 0x0C84, [PMIF_SWINF_2_RDATA_31_0] = 0x0C94, [PMIF_SWINF_2_VLD_CLR] = 0x0CA4, [PMIF_SWINF_2_STA] = 0x0CA8, [PMIF_SWINF_3_ACC] = 0x0CC0, [PMIF_SWINF_3_WDATA_31_0] = 0x0CC4, [PMIF_SWINF_3_RDATA_31_0] = 0x0CD4, [PMIF_SWINF_3_VLD_CLR] = 0x0CE4, [PMIF_SWINF_3_STA] = 0x0CE8, }; static const u32 mt8195_regs[] = { [PMIF_INIT_DONE] = 0x0000, [PMIF_INF_EN] = 0x0024, [PMIF_ARB_EN] = 0x0150, [PMIF_CMDISSUE_EN] = 0x03B8, [PMIF_TIMER_CTRL] = 0x03E4, [PMIF_SPI_MODE_CTRL] = 0x0408, [PMIF_IRQ_EVENT_EN_0] = 0x0420, [PMIF_IRQ_FLAG_0] = 0x0428, [PMIF_IRQ_CLR_0] = 0x042C, [PMIF_IRQ_EVENT_EN_1] = 0x0430, [PMIF_IRQ_FLAG_1] = 0x0438, [PMIF_IRQ_CLR_1] = 0x043C, [PMIF_IRQ_EVENT_EN_2] = 0x0440, [PMIF_IRQ_FLAG_2] = 0x0448, [PMIF_IRQ_CLR_2] = 0x044C, [PMIF_IRQ_EVENT_EN_3] = 0x0450, [PMIF_IRQ_FLAG_3] = 0x0458, [PMIF_IRQ_CLR_3] = 0x045C, [PMIF_IRQ_EVENT_EN_4] = 0x0460, [PMIF_IRQ_FLAG_4] = 0x0468, [PMIF_IRQ_CLR_4] = 0x046C, [PMIF_WDT_EVENT_EN_0] = 0x0474, [PMIF_WDT_FLAG_0] = 0x0478, [PMIF_WDT_EVENT_EN_1] = 0x047C, [PMIF_WDT_FLAG_1] = 0x0480, [PMIF_SWINF_0_ACC] = 0x0800, [PMIF_SWINF_0_WDATA_31_0] = 0x0804, [PMIF_SWINF_0_RDATA_31_0] = 0x0814, [PMIF_SWINF_0_VLD_CLR] = 0x0824, [PMIF_SWINF_0_STA] = 0x0828, [PMIF_SWINF_1_ACC] = 0x0840, [PMIF_SWINF_1_WDATA_31_0] = 0x0844, [PMIF_SWINF_1_RDATA_31_0] = 0x0854, [PMIF_SWINF_1_VLD_CLR] = 0x0864, [PMIF_SWINF_1_STA] = 0x0868, [PMIF_SWINF_2_ACC] = 0x0880, [PMIF_SWINF_2_WDATA_31_0] = 0x0884, [PMIF_SWINF_2_RDATA_31_0] = 0x0894, [PMIF_SWINF_2_VLD_CLR] = 0x08A4, [PMIF_SWINF_2_STA] = 0x08A8, [PMIF_SWINF_3_ACC] = 0x08C0, [PMIF_SWINF_3_WDATA_31_0] = 0x08C4, [PMIF_SWINF_3_RDATA_31_0] = 0x08D4, [PMIF_SWINF_3_VLD_CLR] = 0x08E4, [PMIF_SWINF_3_STA] = 0x08E8, }; enum spmi_regs { SPMI_OP_ST_CTRL, SPMI_GRP_ID_EN, SPMI_OP_ST_STA, SPMI_MST_SAMPL, SPMI_MST_REQ_EN, SPMI_REC_CTRL, SPMI_REC0, SPMI_REC1, SPMI_REC2, SPMI_REC3, SPMI_REC4, SPMI_MST_DBG, /* MT8195 spmi regs */ SPMI_MST_RCS_CTRL, SPMI_SLV_3_0_EINT, SPMI_SLV_7_4_EINT, SPMI_SLV_B_8_EINT, SPMI_SLV_F_C_EINT, SPMI_REC_CMD_DEC, SPMI_DEC_DBG, }; static const u32 mt6873_spmi_regs[] = { [SPMI_OP_ST_CTRL] = 0x0000, [SPMI_GRP_ID_EN] = 0x0004, [SPMI_OP_ST_STA] = 0x0008, [SPMI_MST_SAMPL] = 0x000c, [SPMI_MST_REQ_EN] = 0x0010, [SPMI_REC_CTRL] = 0x0040, [SPMI_REC0] = 0x0044, [SPMI_REC1] = 0x0048, [SPMI_REC2] = 0x004c, [SPMI_REC3] = 0x0050, [SPMI_REC4] = 0x0054, [SPMI_MST_DBG] = 0x00fc, }; static const u32 mt8195_spmi_regs[] = { [SPMI_OP_ST_CTRL] = 0x0000, [SPMI_GRP_ID_EN] = 0x0004, [SPMI_OP_ST_STA] = 0x0008, [SPMI_MST_SAMPL] = 0x000C, [SPMI_MST_REQ_EN] = 0x0010, [SPMI_MST_RCS_CTRL] = 0x0014, [SPMI_SLV_3_0_EINT] = 0x0020, [SPMI_SLV_7_4_EINT] = 0x0024, [SPMI_SLV_B_8_EINT] = 0x0028, [SPMI_SLV_F_C_EINT] = 0x002C, [SPMI_REC_CTRL] = 0x0040, [SPMI_REC0] = 0x0044, [SPMI_REC1] = 0x0048, [SPMI_REC2] = 0x004C, [SPMI_REC3] = 0x0050, [SPMI_REC4] = 0x0054, [SPMI_REC_CMD_DEC] = 0x005C, [SPMI_DEC_DBG] = 0x00F8, [SPMI_MST_DBG] = 0x00FC, }; static inline struct pmif *to_mtk_pmif(struct spmi_controller *ctrl) { return dev_get_drvdata(ctrl->dev.parent); } static u32 pmif_readl(struct pmif *arb, struct pmif_bus *pbus, enum pmif_regs reg) { return readl(pbus->base + arb->data->regs[reg]); } static void pmif_writel(struct pmif *arb, struct pmif_bus *pbus, u32 val, enum pmif_regs reg) { writel(val, pbus->base + arb->data->regs[reg]); } static u32 mtk_spmi_readl(struct pmif *arb, struct pmif_bus *pbus, enum spmi_regs reg) { return readl(pbus->spmimst_base + arb->data->spmimst_regs[reg]); } static void mtk_spmi_writel(struct pmif *arb, struct pmif_bus *pbus, u32 val, enum spmi_regs reg) { writel(val, pbus->spmimst_base + arb->data->spmimst_regs[reg]); } static bool pmif_is_fsm_vldclr(struct pmif *arb, struct pmif_bus *pbus) { u32 reg_rdata; reg_rdata = pmif_readl(arb, pbus, arb->chan.ch_sta); return GET_SWINF(reg_rdata) == SWINF_WFVLDCLR; } static int pmif_arb_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid) { struct pmif_bus *pbus = spmi_controller_get_drvdata(ctrl); struct pmif *arb = to_mtk_pmif(ctrl); u32 rdata, cmd; int ret; /* Check the opcode */ if (opc < SPMI_CMD_RESET || opc > SPMI_CMD_WAKEUP) return -EINVAL; cmd = opc - SPMI_CMD_RESET; mtk_spmi_writel(arb, pbus, (cmd << 0x4) | sid, SPMI_OP_ST_CTRL); ret = readl_poll_timeout_atomic(pbus->spmimst_base + arb->data->spmimst_regs[SPMI_OP_ST_STA], rdata, (rdata & SPMI_OP_ST_BUSY) == SPMI_OP_ST_BUSY, PMIF_DELAY_US, PMIF_TIMEOUT_US); if (ret < 0) dev_err(&ctrl->dev, "timeout, err = %d\n", ret); return ret; } static int pmif_spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, u16 addr, u8 *buf, size_t len) { struct pmif_bus *pbus = spmi_controller_get_drvdata(ctrl); struct pmif *arb = to_mtk_pmif(ctrl); struct ch_reg *inf_reg; int ret; u32 data, cmd; unsigned long flags; /* Check for argument validation. */ if (sid & ~0xf) { dev_err(&ctrl->dev, "exceed the max slv id\n"); return -EINVAL; } if (len > 4) { dev_err(&ctrl->dev, "pmif supports 1..4 bytes per trans, but:%zu requested", len); return -EINVAL; } if (opc >= 0x60 && opc <= 0x7f) opc = PMIF_CMD_REG; else if ((opc >= 0x20 && opc <= 0x2f) || (opc >= 0x38 && opc <= 0x3f)) opc = PMIF_CMD_EXT_REG_LONG; else return -EINVAL; raw_spin_lock_irqsave(&pbus->lock, flags); /* Wait for Software Interface FSM state to be IDLE. */ inf_reg = &arb->chan; ret = readl_poll_timeout_atomic(pbus->base + arb->data->regs[inf_reg->ch_sta], data, GET_SWINF(data) == SWINF_IDLE, PMIF_DELAY_US, PMIF_TIMEOUT_US); if (ret < 0) { /* set channel ready if the data has transferred */ if (pmif_is_fsm_vldclr(arb, pbus)) pmif_writel(arb, pbus, 1, inf_reg->ch_rdy); raw_spin_unlock_irqrestore(&pbus->lock, flags); dev_err(&ctrl->dev, "failed to wait for SWINF_IDLE\n"); return ret; } /* Send the command. */ cmd = (opc << 30) | (sid << 24) | ((len - 1) << 16) | addr; pmif_writel(arb, pbus, cmd, inf_reg->ch_send); /* * Wait for Software Interface FSM state to be WFVLDCLR, * read the data and clear the valid flag. */ ret = readl_poll_timeout_atomic(pbus->base + arb->data->regs[inf_reg->ch_sta], data, GET_SWINF(data) == SWINF_WFVLDCLR, PMIF_DELAY_US, PMIF_TIMEOUT_US); if (ret < 0) { raw_spin_unlock_irqrestore(&pbus->lock, flags); dev_err(&ctrl->dev, "failed to wait for SWINF_WFVLDCLR\n"); return ret; } data = pmif_readl(arb, pbus, inf_reg->rdata); pmif_writel(arb, pbus, 1, inf_reg->ch_rdy); raw_spin_unlock_irqrestore(&pbus->lock, flags); memcpy(buf, &data, len); return 0; } static int pmif_spmi_write_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, u16 addr, const u8 *buf, size_t len) { struct pmif_bus *pbus = spmi_controller_get_drvdata(ctrl); struct pmif *arb = to_mtk_pmif(ctrl); struct ch_reg *inf_reg; int ret; u32 data, wdata, cmd; unsigned long flags; /* Check for argument validation. */ if (unlikely(sid & ~0xf)) { dev_err(&ctrl->dev, "exceed the max slv id\n"); return -EINVAL; } if (len > 4) { dev_err(&ctrl->dev, "pmif supports 1..4 bytes per trans, but:%zu requested", len); return -EINVAL; } /* Check the opcode */ if (opc >= 0x40 && opc <= 0x5F) opc = PMIF_CMD_REG; else if ((opc <= 0xF) || (opc >= 0x30 && opc <= 0x37)) opc = PMIF_CMD_EXT_REG_LONG; else if (opc >= 0x80) opc = PMIF_CMD_REG_0; else return -EINVAL; /* Set the write data. */ memcpy(&wdata, buf, len); raw_spin_lock_irqsave(&pbus->lock, flags); /* Wait for Software Interface FSM state to be IDLE. */ inf_reg = &arb->chan; ret = readl_poll_timeout_atomic(pbus->base + arb->data->regs[inf_reg->ch_sta], data, GET_SWINF(data) == SWINF_IDLE, PMIF_DELAY_US, PMIF_TIMEOUT_US); if (ret < 0) { /* set channel ready if the data has transferred */ if (pmif_is_fsm_vldclr(arb, pbus)) pmif_writel(arb, pbus, 1, inf_reg->ch_rdy); raw_spin_unlock_irqrestore(&pbus->lock, flags); dev_err(&ctrl->dev, "failed to wait for SWINF_IDLE\n"); return ret; } pmif_writel(arb, pbus, wdata, inf_reg->wdata); /* Send the command. */ cmd = (opc << 30) | BIT(29) | (sid << 24) | ((len - 1) << 16) | addr; pmif_writel(arb, pbus, cmd, inf_reg->ch_send); raw_spin_unlock_irqrestore(&pbus->lock, flags); return 0; } static void mtk_spmi_handle_chained_irq(struct irq_desc *desc) { struct pmif_bus *pbus = irq_desc_get_handler_data(desc); struct irq_chip *chip = irq_desc_get_chip(desc); struct pmif *arb = to_mtk_pmif(pbus->ctrl); u8 regidx_min, regidx_max; bool irq_handled = false; unsigned int i; regidx_min = pbus->irq_min_sid / 4; regidx_min += SPMI_SLV_3_0_EINT; regidx_max = pbus->irq_max_sid / 4; regidx_max += SPMI_SLV_3_0_EINT; chained_irq_enter(chip, desc); for (i = regidx_min; i <= regidx_max; i++) { u32 val = mtk_spmi_readl(arb, pbus, i); while (val) { u8 bit = __ffs(val); u8 bank = bit / 7; u8 sid = ((i - SPMI_SLV_3_0_EINT) * 4) + bank; val &= ~(PMIF_RCS_IRQ_MASK << (8 * bank)); /* Check if IRQs for this SID are enabled */ if (!(pbus->irq_en & BIT(sid))) continue; generic_handle_domain_irq_safe(pbus->dom, sid); irq_handled = true; } } if (!irq_handled) handle_bad_irq(desc); chained_irq_exit(chip, desc); } static void mtk_spmi_rcs_irq_eoi(struct irq_data *d) { struct pmif_bus *pbus = irq_data_get_irq_chip_data(d); struct pmif *arb = to_mtk_pmif(pbus->ctrl); irq_hw_number_t irq = irqd_to_hwirq(d); unsigned int reg, shift; /* There are four interrupts (8 bits each) per register */ reg = SPMI_SLV_3_0_EINT + d->hwirq / 4; shift = (irq % 4) * 8; mtk_spmi_writel(arb, pbus, PMIF_RCS_IRQ_MASK << shift, reg); } static void mtk_spmi_rcs_irq_enable(struct irq_data *d) { struct pmif_bus *pbus = irq_data_get_irq_chip_data(d); irq_hw_number_t irq = irqd_to_hwirq(d); pbus->irq_en |= BIT(irq); } static void mtk_spmi_rcs_irq_disable(struct irq_data *d) { struct pmif_bus *pbus = irq_data_get_irq_chip_data(d); irq_hw_number_t irq = irqd_to_hwirq(d); pbus->irq_en &= ~BIT(irq); } static int mtk_spmi_rcs_irq_set_wake(struct irq_data *d, unsigned int on) { struct pmif_bus *pbus = irq_data_get_irq_chip_data(d); return irq_set_irq_wake(pbus->irq, on); } static const struct irq_chip mtk_spmi_rcs_irq_chip = { .name = "spmi_rcs", .irq_eoi = mtk_spmi_rcs_irq_eoi, .irq_enable = mtk_spmi_rcs_irq_enable, .irq_disable = mtk_spmi_rcs_irq_disable, .irq_set_wake = mtk_spmi_rcs_irq_set_wake, }; static int mtk_spmi_rcs_irq_translate(struct irq_domain *d, struct irq_fwspec *fwspec, unsigned long *out_hwirq, unsigned int *out_type) { struct pmif_bus *pbus = d->host_data; struct device *dev = &pbus->ctrl->dev; u32 *intspec = fwspec->param; if (intspec[0] > SPMI_MAX_SLAVE_ID) return -EINVAL; /* * The IRQ number in intspec[1] is ignored on purpose here! * * The controller only has knowledge of which SID raised an interrupt * and the type of irq, but doesn't know about any device irq number, * hence that must be read from the SPMI device's registers. */ *out_hwirq = intspec[0]; *out_type = intspec[2] & IRQ_TYPE_SENSE_MASK; if (pbus->irq_min_sid > intspec[0]) pbus->irq_min_sid = intspec[0]; if (pbus->irq_max_sid < intspec[0]) pbus->irq_max_sid = intspec[0]; dev_dbg(dev, "Found SPMI IRQ %u (map: 0x%lx)\n", intspec[0], *out_hwirq); return 0; } static struct lock_class_key mtk_spmi_rcs_irqlock_class, mtk_spmi_rcs_irqreq_class; static int mtk_spmi_rcs_irq_alloc(struct irq_domain *d, unsigned int virq, unsigned int nr_irqs, void *data) { struct pmif_bus *pbus = d->host_data; struct device *dev = &pbus->ctrl->dev; struct irq_fwspec *fwspec = data; irq_hw_number_t hwirq; unsigned int irqtype; int i, ret; ret = mtk_spmi_rcs_irq_translate(d, fwspec, &hwirq, &irqtype); if (ret) return ret; for (i = 0; i < nr_irqs; i++) { dev_dbg(dev, "Mapping IRQ%u (hwirq %lu) with type %u\n", virq, hwirq, irqtype); irq_set_lockdep_class(virq, &mtk_spmi_rcs_irqlock_class, &mtk_spmi_rcs_irqreq_class); irq_domain_set_info(d, virq, hwirq, &mtk_spmi_rcs_irq_chip, pbus, handle_level_irq, NULL, NULL); } return 0; } static const struct irq_domain_ops mtk_spmi_rcs_irq_domain_ops = { .alloc = mtk_spmi_rcs_irq_alloc, .free = irq_domain_free_irqs_common, .translate = mtk_spmi_rcs_irq_translate, }; static const struct pmif_data mt6873_pmif_arb = { .regs = mt6873_regs, .spmimst_regs = mt6873_spmi_regs, .soc_chan = 2, }; static const struct pmif_data mt8195_pmif_arb = { .regs = mt8195_regs, .spmimst_regs = mt8195_spmi_regs, .soc_chan = 2, }; static const struct pmif_data mt8196_pmif_arb = { .regs = mt8195_regs, .spmimst_regs = mt8195_spmi_regs, .soc_chan = 2, .spmi_ver = 2, .num_spmi_buses = 2, }; static int mtk_spmi_irq_init(struct device_node *node, const struct pmif_data *pdata, struct pmif_bus *pbus) { struct pmif *arb = to_mtk_pmif(pbus->ctrl); unsigned int i; /* No interrupts required for SPMI 1.x controller */ if (pdata->spmi_ver < 2) { pbus->dom = NULL; return 0; } pbus->irq = of_irq_get_byname(node, "rcs"); if (pbus->irq <= 0) return pbus->irq ? : -ENXIO; pbus->dom = irq_domain_create_tree(of_fwnode_handle(node), &mtk_spmi_rcs_irq_domain_ops, pbus); if (!pbus->dom) return -ENOMEM; /* Clear possible unhandled interrupts coming from bootloader SPMI init */ for (i = SPMI_SLV_3_0_EINT; i <= SPMI_SLV_F_C_EINT; i++) mtk_spmi_writel(arb, pbus, GENMASK(31, 0), i); return 0; } static void mtk_spmi_irq_remove(struct pmif_bus *pbus) { if (!pbus->dom) return; irq_set_chained_handler_and_data(pbus->irq, NULL, NULL); irq_domain_remove(pbus->dom); } static int mtk_spmi_bus_probe(struct platform_device *pdev, struct device_node *node, const struct pmif_data *pdata, struct pmif_bus *pbus) { struct spmi_controller *ctrl; int err, idx, bus_id, i; if (pdata->num_spmi_buses > 1) bus_id = of_alias_get_id(node, "spmi"); else bus_id = 0; if (bus_id < 0) return dev_err_probe(&pdev->dev, bus_id, "Cannot find SPMI Bus alias ID\n"); ctrl = devm_spmi_controller_alloc(&pdev->dev, sizeof(*pbus)); if (IS_ERR(ctrl)) return PTR_ERR(ctrl); pbus = spmi_controller_get_drvdata(ctrl); pbus->ctrl = ctrl; idx = of_property_match_string(node, "reg-names", "pmif"); if (idx < 0) return -EINVAL; pbus->base = devm_of_iomap(&pdev->dev, node, idx, NULL); if (IS_ERR(pbus->base)) return PTR_ERR(pbus->base); idx = of_property_match_string(node, "reg-names", "spmimst"); if (idx < 0) return -EINVAL; pbus->spmimst_base = devm_of_iomap(&pdev->dev, node, idx, NULL); if (IS_ERR(pbus->spmimst_base)) return PTR_ERR(pbus->spmimst_base); pbus->nclks = ARRAY_SIZE(pmif_clock_names); for (i = 0; i < pbus->nclks; i++) { pbus->clks[i].id = pmif_clock_names[i]; pbus->clks[i].clk = of_clk_get_by_name(node, pbus->clks[i].id); if (IS_ERR(pbus->clks[i].clk)) return dev_err_probe(&pdev->dev, PTR_ERR(pbus->clks[i].clk), "Failed to get clocks\n"); } err = clk_bulk_prepare_enable(pbus->nclks, pbus->clks); if (err) { dev_err_probe(&pdev->dev, err, "Failed to enable clocks\n"); goto err_put_clks; } err = mtk_spmi_irq_init(node, pdata, pbus); if (err) { dev_err_probe(&pdev->dev, err, "Cannot initialize SPMI IRQs\n"); goto err_disable_clks; } ctrl->cmd = pmif_arb_cmd; ctrl->read_cmd = pmif_spmi_read_cmd; ctrl->write_cmd = pmif_spmi_write_cmd; ctrl->dev.of_node = node; dev_set_name(&ctrl->dev, "spmi-%d", bus_id); raw_spin_lock_init(&pbus->lock); err = spmi_controller_add(ctrl); if (err) goto err_remove_irq; if (pbus->dom) irq_set_chained_handler_and_data(pbus->irq, mtk_spmi_handle_chained_irq, pbus); return 0; err_remove_irq: mtk_spmi_irq_remove(pbus); err_disable_clks: clk_bulk_disable_unprepare(pbus->nclks, pbus->clks); err_put_clks: clk_bulk_put(pbus->nclks, pbus->clks); return err; } static int mtk_spmi_probe(struct platform_device *pdev) { struct device_node *node = pdev->dev.of_node; struct pmif *arb; u32 chan_offset; u8 cur_bus = 0; int ret; arb = devm_kzalloc(&pdev->dev, sizeof(*arb), GFP_KERNEL); if (!arb) return -ENOMEM; arb->data = device_get_match_data(&pdev->dev); if (!arb->data) { dev_err(&pdev->dev, "Cannot get drv_data\n"); return -EINVAL; } platform_set_drvdata(pdev, arb); if (!arb->data->num_spmi_buses) { ret = mtk_spmi_bus_probe(pdev, node, arb->data, &arb->bus[cur_bus]); if (ret) return ret; } else { for_each_available_child_of_node_scoped(node, child) { if (!of_node_name_eq(child, "spmi")) continue; ret = mtk_spmi_bus_probe(pdev, child, arb->data, &arb->bus[cur_bus]); if (ret) return ret; cur_bus++; } } chan_offset = PMIF_CHAN_OFFSET * arb->data->soc_chan; arb->chan.ch_sta = PMIF_SWINF_0_STA + chan_offset; arb->chan.wdata = PMIF_SWINF_0_WDATA_31_0 + chan_offset; arb->chan.rdata = PMIF_SWINF_0_RDATA_31_0 + chan_offset; arb->chan.ch_send = PMIF_SWINF_0_ACC + chan_offset; arb->chan.ch_rdy = PMIF_SWINF_0_VLD_CLR + chan_offset; return 0; } static void mtk_spmi_remove(struct platform_device *pdev) { struct pmif *arb = platform_get_drvdata(pdev); int i; for (i = 0; i < PMIF_MAX_BUSES; i++) { struct pmif_bus *pbus = &arb->bus[i]; if (!pbus->ctrl) continue; mtk_spmi_irq_remove(pbus); spmi_controller_remove(pbus->ctrl); clk_bulk_disable_unprepare(pbus->nclks, pbus->clks); clk_bulk_put(pbus->nclks, pbus->clks); } } static const struct of_device_id mtk_spmi_match_table[] = { { .compatible = "mediatek,mt6873-spmi", .data = &mt6873_pmif_arb, }, { .compatible = "mediatek,mt8195-spmi", .data = &mt8195_pmif_arb, }, { .compatible = "mediatek,mt8196-spmi", .data = &mt8196_pmif_arb, }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, mtk_spmi_match_table); static struct platform_driver mtk_spmi_driver = { .driver = { .name = "spmi-mtk", .of_match_table = mtk_spmi_match_table, }, .probe = mtk_spmi_probe, .remove = mtk_spmi_remove, }; module_platform_driver(mtk_spmi_driver); MODULE_AUTHOR("AngeloGioacchino Del Regno "); MODULE_AUTHOR("Hsin-Hsiung Wang "); MODULE_DESCRIPTION("MediaTek SPMI Driver"); MODULE_LICENSE("GPL");