| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright 2025 Cix Technology Group Co., Ltd. | |
| */ | |
| #include <linux/device.h> | |
| #include <linux/err.h> | |
| #include <linux/io.h> | |
| #include <linux/interrupt.h> | |
| #include <linux/kernel.h> | |
| #include <linux/mailbox_controller.h> | |
| #include <linux/module.h> | |
| #include <linux/platform_device.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 overflow: 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, IRQF_NO_SUSPEND, | |
| 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 = MBOX_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"); |