| /* |
| * Copyright (c) 2019 MediaTek Inc. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/of.h> |
| #include <linux/slab.h> |
| #include <linux/errno.h> |
| #include <linux/mailbox_controller.h> |
| #include <linux/mailbox_client.h> |
| #include <linux/mailbox/mtk-rpmsg-mailbox.h> |
| #include "../misc/mediatek/include/mt-plat/mtk-mbox.h" |
| |
| static struct mtk_mbox_chan *to_mtk_mbox_chan(struct mbox_chan *chan) |
| { |
| if (!chan || !chan->con_priv) |
| return NULL; |
| |
| return (struct mtk_mbox_chan *)chan->con_priv; |
| } |
| |
| int mtk_mbox_send_ipi(struct mtk_mbox_chan *mchan, void *data) |
| { |
| struct mtk_mbox_device *mbdev; |
| struct mtk_ipi_msg *msg; |
| unsigned int status; |
| int ret; |
| |
| if (WARN_ON(!data)) { |
| pr_notice("mbox fw:%u warning\n", mchan->mbox); |
| return -EINVAL; |
| } |
| |
| mbdev = mchan->mbdev; |
| msg = (struct mtk_ipi_msg *)data; |
| |
| status = mtk_mbox_check_send_irq(mbdev, mchan->mbox, |
| mchan->send_pin_index); |
| if (status != 0) { |
| mchan->ipimsg = data; |
| return -EBUSY; |
| } |
| |
| ret = mtk_mbox_write_hd(mbdev, mchan->mbox, mchan->send_slot, msg); |
| if (ret != MBOX_DONE) |
| return -EIO; |
| /* |
| * Ensure that all writes to SRAM are committed before sending the |
| * interrupt to mbox. |
| */ |
| mb(); |
| ret = mtk_mbox_trigger_irq(mbdev, mchan->mbox, |
| 0x1 << mchan->send_pin_index); |
| if (ret != MBOX_DONE) |
| pr_notice("mbox fw:%u irq fail\n", mchan->mbox); |
| |
| return ret; |
| } |
| |
| bool mtk_mbox_tx_done(struct mtk_mbox_chan *mchan) |
| { |
| struct mtk_mbox_device *mbdev; |
| |
| struct mbox_chan *chan; |
| unsigned int status; |
| unsigned long flags; |
| |
| chan = mchan->chan; |
| mbdev = mchan->mbdev; |
| |
| spin_lock_irqsave(&chan->lock, flags); |
| status = mtk_mbox_check_send_irq(mbdev, mchan->mbox, |
| mchan->send_pin_index); |
| spin_unlock_irqrestore(&chan->lock, flags); |
| |
| return status ? false : true; |
| } |
| |
| int mtk_mbox_start(struct mtk_mbox_chan *mchan) |
| { |
| return 0; |
| } |
| |
| void mtk_mbox_down(struct mtk_mbox_chan *mchan) |
| { |
| } |
| |
| int mtk_mbox_send_data(struct mbox_chan *chan, void *data) |
| { |
| struct mtk_mbox_chan *mchan = to_mtk_mbox_chan(chan); |
| |
| if (mchan) |
| return mchan->ops->mtk_send_ipi(mchan, data); |
| |
| return false; |
| } |
| |
| bool mtk_mbox_last_tx_done(struct mbox_chan *chan) |
| { |
| struct mtk_mbox_chan *mchan = to_mtk_mbox_chan(chan); |
| |
| if (mchan) |
| return mchan->ops->mtk_tx_done(mchan); |
| |
| return false; |
| } |
| |
| int mtk_mbox_startup(struct mbox_chan *chan) |
| { |
| struct mtk_mbox_chan *mchan = to_mtk_mbox_chan(chan); |
| |
| if (mchan) |
| return mchan->ops->mtk_startup(mchan); |
| |
| return 0; |
| } |
| |
| void mtk_mbox_shutdown(struct mbox_chan *chan) |
| { |
| struct mtk_mbox_chan *mchan = to_mtk_mbox_chan(chan); |
| |
| if (mchan) |
| mchan->ops->mtk_shutdown(mchan); |
| } |
| |
| static const struct mbox_chan_ops mtk_ipi_mbox_chan_ops = { |
| .send_data = mtk_mbox_send_data, |
| .last_tx_done = mtk_mbox_last_tx_done, |
| .startup = mtk_mbox_startup, |
| .shutdown = mtk_mbox_shutdown, |
| }; |
| |
| static struct mtk_mbox_operations mtk_mbox_ops = { |
| .mtk_send_ipi = mtk_mbox_send_ipi, |
| .mtk_tx_done = mtk_mbox_tx_done, |
| .mtk_startup = mtk_mbox_start, |
| .mtk_shutdown = mtk_mbox_down, |
| }; |
| |
| static struct mbox_chan *mtk_rpmsg_mbox_xlate(struct mbox_controller *mbox, |
| const struct of_phandle_args *sp) |
| { |
| int chan_id = sp->args[0]; |
| |
| if (chan_id >= mbox->num_chans) |
| return ERR_PTR(-EINVAL); |
| |
| return &mbox->chans[chan_id]; |
| } |
| |
| |
| int mtk_mbox_chan_create(struct mbox_controller *mboxctrl, |
| struct mtk_mbox_device *mbdev, int num) |
| { |
| struct mtk_mbox_chan *mchan; |
| struct mtk_mbox_pin_send *msend; |
| struct mbox_chan *chan; |
| unsigned int i, j, count; |
| |
| mchan = kcalloc(num, sizeof(struct mtk_mbox_chan), GFP_KERNEL); |
| if (!mchan) |
| return -ENOMEM; |
| |
| chan = kcalloc(num, sizeof(struct mbox_chan), GFP_KERNEL); |
| if (!chan) { |
| kfree(mchan); |
| return -ENOMEM; |
| } |
| |
| count = mbdev->send_count; |
| for (i = 0; i < num; i++) { |
| chan[i].con_priv = &mchan[i]; |
| mchan[i].chan = &chan[i]; |
| mchan[i].mbdev = mbdev; |
| mchan[i].ops = &mtk_mbox_ops; |
| for (j = 0; j < count; j++) { |
| msend = &(mbdev->pin_send_table[j]); |
| if (i == msend->chan_id) { |
| mchan[i].mbox = msend->mbox; |
| mchan[i].send_slot = msend->offset; |
| mchan[i].send_slot_size = msend->msg_size; |
| mchan[i].send_pin_index = msend->pin_index; |
| mchan[i].send_pin_offset = j; |
| } |
| } |
| } |
| mboxctrl->ops = &mtk_ipi_mbox_chan_ops; |
| mboxctrl->chans = chan; |
| mboxctrl->of_xlate = mtk_rpmsg_mbox_xlate; |
| |
| return 0; |
| } |