// SPDX-License-Identifier: GPL-2.0 /* * Copyright 2025 Cix Technology Group Co., Ltd. */ #include #include #include #include #include #include #include #include #include "mailbox.h" /* * The maximum transmission size is 32 words or 128 bytes. */ #define CIX_MBOX_MSG_WORDS 32 /* Max length = 32 words */ #define CIX_MBOX_MSG_LEN_MASK 0x7fL /* Max length = 128 bytes */ /* [0~7] Fast channel * [8] doorbell base channel * [9]fifo base channel * [10] register base channel */ #define CIX_MBOX_FAST_IDX 7 #define CIX_MBOX_DB_IDX 8 #define CIX_MBOX_FIFO_IDX 9 #define CIX_MBOX_REG_IDX 10 #define CIX_MBOX_CHANS 11 /* Register define */ #define CIX_REG_MSG(n) (0x0 + 0x4*(n)) /* 0x0~0x7c */ #define CIX_REG_DB_ACK CIX_REG_MSG(CIX_MBOX_MSG_WORDS) /* 0x80 */ #define CIX_ERR_COMP (CIX_REG_DB_ACK + 0x4) /* 0x84 */ #define CIX_ERR_COMP_CLR (CIX_REG_DB_ACK + 0x8) /* 0x88 */ #define CIX_REG_F_INT(IDX) (CIX_ERR_COMP_CLR + 0x4*(IDX+1)) /* 0x8c~0xa8 */ #define CIX_FIFO_WR (CIX_REG_F_INT(CIX_MBOX_FAST_IDX+1)) /* 0xac */ #define CIX_FIFO_RD (CIX_FIFO_WR + 0x4) /* 0xb0 */ #define CIX_FIFO_STAS (CIX_FIFO_WR + 0x8) /* 0xb4 */ #define CIX_FIFO_WM (CIX_FIFO_WR + 0xc) /* 0xb8 */ #define CIX_INT_ENABLE (CIX_FIFO_WR + 0x10) /* 0xbc */ #define CIX_INT_ENABLE_SIDE_B (CIX_FIFO_WR + 0x14) /* 0xc0 */ #define CIX_INT_CLEAR (CIX_FIFO_WR + 0x18) /* 0xc4 */ #define CIX_INT_STATUS (CIX_FIFO_WR + 0x1c) /* 0xc8 */ #define CIX_FIFO_RST (CIX_FIFO_WR + 0x20) /* 0xcc */ #define CIX_MBOX_TX 0 #define CIX_MBOX_RX 1 #define CIX_DB_INT_BIT BIT(0) #define CIX_DB_ACK_INT_BIT BIT(1) #define CIX_FIFO_WM_DEFAULT CIX_MBOX_MSG_WORDS #define CIX_FIFO_STAS_WMK BIT(0) #define CIX_FIFO_STAS_FULL BIT(1) #define CIX_FIFO_STAS_EMPTY BIT(2) #define CIX_FIFO_STAS_UFLOW BIT(3) #define CIX_FIFO_STAS_OFLOW BIT(4) #define CIX_FIFO_RST_BIT BIT(0) #define CIX_DB_INT BIT(0) #define CIX_ACK_INT BIT(1) #define CIX_FIFO_FULL_INT BIT(2) #define CIX_FIFO_EMPTY_INT BIT(3) #define CIX_FIFO_WM01_INT BIT(4) #define CIX_FIFO_WM10_INT BIT(5) #define CIX_FIFO_OFLOW_INT BIT(6) #define CIX_FIFO_UFLOW_INT BIT(7) #define CIX_FIFO_N_EMPTY_INT BIT(8) #define CIX_FAST_CH_INT(IDX) BIT((IDX)+9) #define CIX_SHMEM_OFFSET 0x80 enum cix_mbox_chan_type { CIX_MBOX_TYPE_DB, CIX_MBOX_TYPE_REG, CIX_MBOX_TYPE_FIFO, CIX_MBOX_TYPE_FAST, }; struct cix_mbox_con_priv { enum cix_mbox_chan_type type; struct mbox_chan *chan; int index; }; struct cix_mbox_priv { struct device *dev; int irq; int dir; void __iomem *base; /* region for mailbox */ struct cix_mbox_con_priv con_priv[CIX_MBOX_CHANS]; struct mbox_chan mbox_chans[CIX_MBOX_CHANS]; struct mbox_controller mbox; bool use_shmem; }; /* * The CIX mailbox supports four types of transfers: * CIX_MBOX_TYPE_DB, CIX_MBOX_TYPE_FAST, CIX_MBOX_TYPE_REG, and CIX_MBOX_TYPE_FIFO. * For the REG and FIFO types of transfers, the message format is as follows: */ union cix_mbox_msg_reg_fifo { u32 length; /* unit is byte */ u32 buf[CIX_MBOX_MSG_WORDS]; /* buf[0] must be the byte length of this array */ }; static struct cix_mbox_priv *to_cix_mbox_priv(struct mbox_controller *mbox) { return container_of(mbox, struct cix_mbox_priv, mbox); } static void cix_mbox_write(struct cix_mbox_priv *priv, u32 val, u32 offset) { if (priv->use_shmem) iowrite32(val, priv->base + offset - CIX_SHMEM_OFFSET); else iowrite32(val, priv->base + offset); } static u32 cix_mbox_read(struct cix_mbox_priv *priv, u32 offset) { if (priv->use_shmem) return ioread32(priv->base + offset - CIX_SHMEM_OFFSET); else return ioread32(priv->base + offset); } static bool mbox_fifo_empty(struct mbox_chan *chan) { struct cix_mbox_priv *priv = to_cix_mbox_priv(chan->mbox); return ((cix_mbox_read(priv, CIX_FIFO_STAS) & CIX_FIFO_STAS_EMPTY) ? true : false); } /* *The transmission unit of the CIX mailbox is word. *The byte length should be converted into the word length. */ static inline u32 mbox_get_msg_size(void *msg) { u32 len; len = ((u32 *)msg)[0] & CIX_MBOX_MSG_LEN_MASK; return DIV_ROUND_UP(len, 4); } static int cix_mbox_send_data_db(struct mbox_chan *chan, void *data) { struct cix_mbox_priv *priv = to_cix_mbox_priv(chan->mbox); /* trigger doorbell irq */ cix_mbox_write(priv, CIX_DB_INT_BIT, CIX_REG_DB_ACK); return 0; } static int cix_mbox_send_data_reg(struct mbox_chan *chan, void *data) { struct cix_mbox_priv *priv = to_cix_mbox_priv(chan->mbox); union cix_mbox_msg_reg_fifo *msg = data; u32 len, i; if (!data) return -EINVAL; len = mbox_get_msg_size(data); for (i = 0; i < len; i++) cix_mbox_write(priv, msg->buf[i], CIX_REG_MSG(i)); /* trigger doorbell irq */ cix_mbox_write(priv, CIX_DB_INT_BIT, CIX_REG_DB_ACK); return 0; } static int cix_mbox_send_data_fifo(struct mbox_chan *chan, void *data) { struct cix_mbox_priv *priv = to_cix_mbox_priv(chan->mbox); union cix_mbox_msg_reg_fifo *msg = data; u32 len, val, i; if (!data) return -EINVAL; len = mbox_get_msg_size(data); cix_mbox_write(priv, len, CIX_FIFO_WM); for (i = 0; i < len; i++) cix_mbox_write(priv, msg->buf[i], CIX_FIFO_WR); /* Enable fifo empty interrupt */ val = cix_mbox_read(priv, CIX_INT_ENABLE); val |= CIX_FIFO_EMPTY_INT; cix_mbox_write(priv, val, CIX_INT_ENABLE); return 0; } static int cix_mbox_send_data_fast(struct mbox_chan *chan, void *data) { struct cix_mbox_priv *priv = to_cix_mbox_priv(chan->mbox); struct cix_mbox_con_priv *cp = chan->con_priv; u32 *arg = (u32 *)data; int index = cp->index; if (!data) return -EINVAL; if (index < 0 || index > CIX_MBOX_FAST_IDX) { dev_err(priv->dev, "Invalid Mbox index %d\n", index); return -EINVAL; } cix_mbox_write(priv, arg[0], CIX_REG_F_INT(index)); return 0; } static int cix_mbox_send_data(struct mbox_chan *chan, void *data) { struct cix_mbox_priv *priv = to_cix_mbox_priv(chan->mbox); struct cix_mbox_con_priv *cp = chan->con_priv; if (priv->dir != CIX_MBOX_TX) { dev_err(priv->dev, "Invalid Mbox dir %d\n", priv->dir); return -EINVAL; } switch (cp->type) { case CIX_MBOX_TYPE_DB: cix_mbox_send_data_db(chan, data); break; case CIX_MBOX_TYPE_REG: cix_mbox_send_data_reg(chan, data); break; case CIX_MBOX_TYPE_FIFO: cix_mbox_send_data_fifo(chan, data); break; case CIX_MBOX_TYPE_FAST: cix_mbox_send_data_fast(chan, data); break; default: dev_err(priv->dev, "Invalid channel type: %d\n", cp->type); return -EINVAL; } return 0; } static void cix_mbox_isr_db(struct mbox_chan *chan) { struct cix_mbox_priv *priv = to_cix_mbox_priv(chan->mbox); u32 int_status; int_status = cix_mbox_read(priv, CIX_INT_STATUS); if (priv->dir == CIX_MBOX_RX) { /* rx interrupt is triggered */ if (int_status & CIX_DB_INT) { cix_mbox_write(priv, CIX_DB_INT, CIX_INT_CLEAR); mbox_chan_received_data(chan, NULL); /* trigger ack interrupt */ cix_mbox_write(priv, CIX_DB_ACK_INT_BIT, CIX_REG_DB_ACK); } } else { /* tx ack interrupt is triggered */ if (int_status & CIX_ACK_INT) { cix_mbox_write(priv, CIX_ACK_INT, CIX_INT_CLEAR); mbox_chan_received_data(chan, NULL); } } } static void cix_mbox_isr_reg(struct mbox_chan *chan) { struct cix_mbox_priv *priv = to_cix_mbox_priv(chan->mbox); u32 int_status; int_status = cix_mbox_read(priv, CIX_INT_STATUS); if (priv->dir == CIX_MBOX_RX) { /* rx interrupt is triggered */ if (int_status & CIX_DB_INT) { u32 data[CIX_MBOX_MSG_WORDS], len, i; cix_mbox_write(priv, CIX_DB_INT, CIX_INT_CLEAR); data[0] = cix_mbox_read(priv, CIX_REG_MSG(0)); len = mbox_get_msg_size(data); for (i = 1; i < len; i++) data[i] = cix_mbox_read(priv, CIX_REG_MSG(i)); /* trigger ack interrupt */ cix_mbox_write(priv, CIX_DB_ACK_INT_BIT, CIX_REG_DB_ACK); mbox_chan_received_data(chan, data); } } else { /* tx ack interrupt is triggered */ if (int_status & CIX_ACK_INT) { cix_mbox_write(priv, CIX_ACK_INT, CIX_INT_CLEAR); mbox_chan_txdone(chan, 0); } } } static void cix_mbox_isr_fifo(struct mbox_chan *chan) { struct cix_mbox_priv *priv = to_cix_mbox_priv(chan->mbox); u32 int_status, status; int_status = cix_mbox_read(priv, CIX_INT_STATUS); if (priv->dir == CIX_MBOX_RX) { /* FIFO waterMark interrupt is generated */ if (int_status & (CIX_FIFO_FULL_INT | CIX_FIFO_WM01_INT)) { u32 data[CIX_MBOX_MSG_WORDS] = { 0 }, i = 0; cix_mbox_write(priv, (CIX_FIFO_FULL_INT | CIX_FIFO_WM01_INT), CIX_INT_CLEAR); do { data[i++] = cix_mbox_read(priv, CIX_FIFO_RD); } while (!mbox_fifo_empty(chan) && i < CIX_MBOX_MSG_WORDS); mbox_chan_received_data(chan, data); } /* FIFO underflow is generated */ if (int_status & CIX_FIFO_UFLOW_INT) { status = cix_mbox_read(priv, CIX_FIFO_STAS); dev_err(priv->dev, "fifo underflow: int_stats %d\n", status); cix_mbox_write(priv, CIX_FIFO_UFLOW_INT, CIX_INT_CLEAR); } } else { /* FIFO empty interrupt is generated */ if (int_status & CIX_FIFO_EMPTY_INT) { u32 val; cix_mbox_write(priv, CIX_FIFO_EMPTY_INT, CIX_INT_CLEAR); /* Disable empty irq*/ val = cix_mbox_read(priv, CIX_INT_ENABLE); val &= ~CIX_FIFO_EMPTY_INT; cix_mbox_write(priv, val, CIX_INT_ENABLE); mbox_chan_txdone(chan, 0); } /* FIFO overflow is generated */ if (int_status & CIX_FIFO_OFLOW_INT) { status = cix_mbox_read(priv, CIX_FIFO_STAS); dev_err(priv->dev, "fifo overlow: int_stats %d\n", status); cix_mbox_write(priv, CIX_FIFO_OFLOW_INT, CIX_INT_CLEAR); } } } static void cix_mbox_isr_fast(struct mbox_chan *chan) { struct cix_mbox_priv *priv = to_cix_mbox_priv(chan->mbox); struct cix_mbox_con_priv *cp = chan->con_priv; u32 int_status, data; /* no irq will be trigger for TX dir mbox */ if (priv->dir != CIX_MBOX_RX) return; int_status = cix_mbox_read(priv, CIX_INT_STATUS); if (int_status & CIX_FAST_CH_INT(cp->index)) { cix_mbox_write(priv, CIX_FAST_CH_INT(cp->index), CIX_INT_CLEAR); data = cix_mbox_read(priv, CIX_REG_F_INT(cp->index)); mbox_chan_received_data(chan, &data); } } static irqreturn_t cix_mbox_isr(int irq, void *arg) { struct mbox_chan *chan = arg; struct cix_mbox_priv *priv = to_cix_mbox_priv(chan->mbox); struct cix_mbox_con_priv *cp = chan->con_priv; switch (cp->type) { case CIX_MBOX_TYPE_DB: cix_mbox_isr_db(chan); break; case CIX_MBOX_TYPE_REG: cix_mbox_isr_reg(chan); break; case CIX_MBOX_TYPE_FIFO: cix_mbox_isr_fifo(chan); break; case CIX_MBOX_TYPE_FAST: cix_mbox_isr_fast(chan); break; default: dev_err(priv->dev, "Invalid channel type: %d\n", cp->type); return IRQ_NONE; } return IRQ_HANDLED; } static int cix_mbox_startup(struct mbox_chan *chan) { struct cix_mbox_priv *priv = to_cix_mbox_priv(chan->mbox); struct cix_mbox_con_priv *cp = chan->con_priv; int index = cp->index, ret; u32 val; ret = request_irq(priv->irq, cix_mbox_isr, 0, dev_name(priv->dev), chan); if (ret) { dev_err(priv->dev, "Unable to acquire IRQ %d\n", priv->irq); return ret; } switch (cp->type) { case CIX_MBOX_TYPE_DB: /* Overwrite txdone_method for DB channel */ chan->txdone_method = TXDONE_BY_ACK; fallthrough; case CIX_MBOX_TYPE_REG: if (priv->dir == CIX_MBOX_TX) { /* Enable ACK interrupt */ val = cix_mbox_read(priv, CIX_INT_ENABLE); val |= CIX_ACK_INT; cix_mbox_write(priv, val, CIX_INT_ENABLE); } else { /* Enable Doorbell interrupt */ val = cix_mbox_read(priv, CIX_INT_ENABLE_SIDE_B); val |= CIX_DB_INT; cix_mbox_write(priv, val, CIX_INT_ENABLE_SIDE_B); } break; case CIX_MBOX_TYPE_FIFO: /* reset fifo */ cix_mbox_write(priv, CIX_FIFO_RST_BIT, CIX_FIFO_RST); /* set default watermark */ cix_mbox_write(priv, CIX_FIFO_WM_DEFAULT, CIX_FIFO_WM); if (priv->dir == CIX_MBOX_TX) { /* Enable fifo overflow interrupt */ val = cix_mbox_read(priv, CIX_INT_ENABLE); val |= CIX_FIFO_OFLOW_INT; cix_mbox_write(priv, val, CIX_INT_ENABLE); } else { /* Enable fifo full/underflow interrupt */ val = cix_mbox_read(priv, CIX_INT_ENABLE_SIDE_B); val |= CIX_FIFO_UFLOW_INT|CIX_FIFO_WM01_INT; cix_mbox_write(priv, val, CIX_INT_ENABLE_SIDE_B); } break; case CIX_MBOX_TYPE_FAST: /* Only RX channel has intterupt */ if (priv->dir == CIX_MBOX_RX) { if (index < 0 || index > CIX_MBOX_FAST_IDX) { dev_err(priv->dev, "Invalid index %d\n", index); ret = -EINVAL; goto failed; } /* enable fast channel interrupt */ val = cix_mbox_read(priv, CIX_INT_ENABLE_SIDE_B); val |= CIX_FAST_CH_INT(index); cix_mbox_write(priv, val, CIX_INT_ENABLE_SIDE_B); } break; default: dev_err(priv->dev, "Invalid channel type: %d\n", cp->type); ret = -EINVAL; goto failed; } return 0; failed: free_irq(priv->irq, chan); return ret; } static void cix_mbox_shutdown(struct mbox_chan *chan) { struct cix_mbox_priv *priv = to_cix_mbox_priv(chan->mbox); struct cix_mbox_con_priv *cp = chan->con_priv; int index = cp->index; u32 val; switch (cp->type) { case CIX_MBOX_TYPE_DB: case CIX_MBOX_TYPE_REG: if (priv->dir == CIX_MBOX_TX) { /* Disable ACK interrupt */ val = cix_mbox_read(priv, CIX_INT_ENABLE); val &= ~CIX_ACK_INT; cix_mbox_write(priv, val, CIX_INT_ENABLE); } else if (priv->dir == CIX_MBOX_RX) { /* Disable Doorbell interrupt */ val = cix_mbox_read(priv, CIX_INT_ENABLE_SIDE_B); val &= ~CIX_DB_INT; cix_mbox_write(priv, val, CIX_INT_ENABLE_SIDE_B); } break; case CIX_MBOX_TYPE_FIFO: if (priv->dir == CIX_MBOX_TX) { /* Disable empty/fifo overflow irq*/ val = cix_mbox_read(priv, CIX_INT_ENABLE); val &= ~(CIX_FIFO_EMPTY_INT | CIX_FIFO_OFLOW_INT); cix_mbox_write(priv, val, CIX_INT_ENABLE); } else if (priv->dir == CIX_MBOX_RX) { /* Disable fifo WM01/underflow interrupt */ val = cix_mbox_read(priv, CIX_INT_ENABLE_SIDE_B); val &= ~(CIX_FIFO_UFLOW_INT | CIX_FIFO_WM01_INT); cix_mbox_write(priv, val, CIX_INT_ENABLE_SIDE_B); } break; case CIX_MBOX_TYPE_FAST: if (priv->dir == CIX_MBOX_RX) { if (index < 0 || index > CIX_MBOX_FAST_IDX) { dev_err(priv->dev, "Invalid index %d\n", index); break; } /* Disable fast channel interrupt */ val = cix_mbox_read(priv, CIX_INT_ENABLE_SIDE_B); val &= ~CIX_FAST_CH_INT(index); cix_mbox_write(priv, val, CIX_INT_ENABLE_SIDE_B); } break; default: dev_err(priv->dev, "Invalid channel type: %d\n", cp->type); break; } free_irq(priv->irq, chan); } static const struct mbox_chan_ops cix_mbox_chan_ops = { .send_data = cix_mbox_send_data, .startup = cix_mbox_startup, .shutdown = cix_mbox_shutdown, }; static void cix_mbox_init(struct cix_mbox_priv *priv) { struct cix_mbox_con_priv *cp; int i; for (i = 0; i < CIX_MBOX_CHANS; i++) { cp = &priv->con_priv[i]; cp->index = i; cp->chan = &priv->mbox_chans[i]; priv->mbox_chans[i].con_priv = cp; if (cp->index <= CIX_MBOX_FAST_IDX) cp->type = CIX_MBOX_TYPE_FAST; if (cp->index == CIX_MBOX_DB_IDX) cp->type = CIX_MBOX_TYPE_DB; if (cp->index == CIX_MBOX_FIFO_IDX) cp->type = CIX_MBOX_TYPE_FIFO; if (cp->index == CIX_MBOX_REG_IDX) cp->type = CIX_MBOX_TYPE_REG; } } static int cix_mbox_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct cix_mbox_priv *priv; struct resource *res; const char *dir_str; int ret; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; priv->dev = dev; priv->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); if (IS_ERR(priv->base)) return PTR_ERR(priv->base); /* * The first 0x80 bytes of the register space of the cix mailbox controller * can be used as shared memory for clients. When this shared memory is in * use, the base address of the mailbox is offset by 0x80. Therefore, when * performing subsequent read/write operations, it is necessary to subtract * the offset CIX_SHMEM_OFFSET. * * When the base address of the mailbox is offset by 0x80, it indicates * that shmem is in use. */ priv->use_shmem = !!(res->start & CIX_SHMEM_OFFSET); priv->irq = platform_get_irq(pdev, 0); if (priv->irq < 0) return priv->irq; if (device_property_read_string(dev, "cix,mbox-dir", &dir_str)) { dev_err(priv->dev, "cix,mbox_dir property not found\n"); return -EINVAL; } if (!strcmp(dir_str, "tx")) priv->dir = 0; else if (!strcmp(dir_str, "rx")) priv->dir = 1; else { dev_err(priv->dev, "cix,mbox_dir=%s is not expected\n", dir_str); return -EINVAL; } cix_mbox_init(priv); priv->mbox.dev = dev; priv->mbox.ops = &cix_mbox_chan_ops; priv->mbox.chans = priv->mbox_chans; priv->mbox.txdone_irq = true; priv->mbox.num_chans = CIX_MBOX_CHANS; priv->mbox.of_xlate = NULL; platform_set_drvdata(pdev, priv); ret = devm_mbox_controller_register(dev, &priv->mbox); if (ret) dev_err(dev, "Failed to register mailbox %d\n", ret); return ret; } static const struct of_device_id cix_mbox_dt_ids[] = { { .compatible = "cix,sky1-mbox" }, { }, }; MODULE_DEVICE_TABLE(of, cix_mbox_dt_ids); static struct platform_driver cix_mbox_driver = { .probe = cix_mbox_probe, .driver = { .name = "cix_mbox", .of_match_table = cix_mbox_dt_ids, }, }; static int __init cix_mailbox_init(void) { return platform_driver_register(&cix_mbox_driver); } arch_initcall(cix_mailbox_init); MODULE_AUTHOR("Cix Technology Group Co., Ltd."); MODULE_DESCRIPTION("CIX mailbox driver"); MODULE_LICENSE("GPL");