| // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
| /* |
| * Copyright (c) 2019 Amlogic, Inc. All rights reserved. |
| */ |
| |
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| |
| #include <linux/completion.h> |
| #include <linux/delay.h> |
| #include <linux/err.h> |
| #include <linux/export.h> |
| #include <linux/io.h> |
| #include <linux/interrupt.h> |
| #include <linux/mailbox_controller.h> |
| #include <linux/module.h> |
| #include <linux/mutex.h> |
| #include <linux/notifier.h> |
| #include <linux/platform_device.h> |
| #include <linux/slab.h> |
| #include <linux/spinlock.h> |
| #include <linux/types.h> |
| #include <linux/mailbox_client.h> |
| #include <linux/uaccess.h> |
| #include <linux/fs.h> |
| #include <linux/cdev.h> |
| #include <linux/amlogic/scpi_protocol.h> |
| |
| #include "meson_mhu_dsp.h" |
| |
| struct device *dsp_scpi_device; |
| |
| #define DRIVER_NAME "meson_mhu_dsp" |
| |
| static char *dsp_name[] = { |
| "dsp_dev", |
| "dspb_dev", |
| }; |
| |
| static struct mbox_cdev_data mbox_cdev; |
| |
| /* for list */ |
| spinlock_t lock; |
| static LIST_HEAD(mbox_list); |
| static DEFINE_MUTEX(chan_mutex); |
| static struct mhu_ctlr *g_mhu_ctrl; |
| |
| enum USR_CMD { |
| MBOX_USER_CMD = 0x1001, |
| }; |
| |
| /* |
| * mbox_chan_report |
| * Report receive data |
| */ |
| static void mbox_chan_report(u32 status, void *msg, int idx) |
| { |
| struct mhu_data_buf *data_buf = (struct mhu_data_buf *)msg; |
| struct mbox_message *message; |
| struct mbox_message *listen_msg = NULL; |
| struct list_head *list; |
| unsigned long flags; |
| struct mbox_data *mbox_data = |
| (struct mbox_data *)(data_buf->rx_buf); |
| struct mbox_data_sync *mbox_data_sync = |
| (struct mbox_data_sync *)(data_buf->rx_buf); |
| struct device *dev = dsp_scpi_device; |
| u32 mbuf_size, ret; |
| |
| spin_lock_irqsave(&lock, flags); |
| if (list_empty(&mbox_list)) { |
| spin_unlock_irqrestore(&lock, flags); |
| goto dsp_req; |
| } |
| |
| list_for_each(list, &mbox_list) { |
| message = list_entry(list, struct mbox_message, list); |
| dev_dbg(dev, "complete %lx %x %x\n", |
| (unsigned long)(mbox_data->complete), |
| status & CMD_MASK, message->cmd); |
| if ((unsigned long)mbox_data->complete == |
| (unsigned long)&message->complete) { |
| memcpy(message->data, |
| mbox_data->data, |
| (status >> SIZE_SHIFT) & SIZE_MASK); |
| complete(&message->complete); |
| spin_unlock_irqrestore(&lock, flags); |
| return; |
| } else if (!listen_msg && |
| (status & CMD_MASK) == message->cmd) { |
| listen_msg = message; |
| } |
| } |
| |
| spin_unlock_irqrestore(&lock, flags); |
| if (listen_msg) { |
| dev_dbg(dev, "listen cmd\n"); |
| memcpy(listen_msg->data, |
| mbox_data->data, |
| (status >> SIZE_SHIFT) & SIZE_MASK); |
| complete(&listen_msg->complete); |
| return; |
| } |
| |
| dsp_req: |
| mbuf_size = (status >> SIZE_SHIFT) & SIZE_MASK; |
| |
| if (ASYNC_CMD == ((status >> SYNC_SHIFT) & SYNC_MASK)) |
| ret = scpi_req_handle(mbox_data->data, |
| mbuf_size, |
| status & CMD_MASK, |
| idx); |
| else |
| ret = scpi_req_handle(mbox_data_sync->data, |
| mbuf_size, |
| status & CMD_MASK, |
| idx); |
| |
| if (!ret) |
| dev_err(dev, "scpi request error cmd:%d\n", status & CMD_MASK); |
| |
| } |
| |
| static void mbox_dsp_clrirq(int idx) |
| { |
| void __iomem *mbox_base = g_mhu_ctrl->mbox_dspa_base; |
| |
| if (!IS_ERR(mbox_base)) |
| writel(~0, mbox_base + RX_OFFSET_CLR); |
| } |
| |
| static irqreturn_t mbox_dsp_handler(int irq, void *p) |
| { |
| struct mbox_chan *link = (struct mbox_chan *)p; |
| struct mhu_chan *chan = link->con_priv; |
| struct mhu_ctlr *ctlr = chan->ctlr; |
| void __iomem *mbox_dspa_base = ctlr->mbox_dspa_base; |
| void __iomem *mbox_dspb_base = ctlr->mbox_dspb_base; |
| void __iomem *mbox_base; |
| void __iomem *payload = ctlr->payload_base; |
| int idx = chan->index; |
| struct mhu_data_buf *data; |
| u32 status; |
| |
| if (BIT(idx) & 0xC) |
| mbox_base = mbox_dspb_base; |
| else if (BIT(idx) & 0x3) |
| mbox_base = mbox_dspa_base; |
| else |
| mbox_base = mbox_dspa_base; |
| |
| status = readl(mbox_base + RX_OFFSET_STAT); |
| |
| if (status && irq == chan->rx_irq) { |
| data = chan->data; |
| if (!data) |
| return IRQ_NONE; |
| if (data->rx_buf) { |
| memcpy_fromio(data->rx_buf, |
| payload + RX_PAYLOAD(idx), |
| data->rx_size); |
| mbox_chan_report(status, data, idx); |
| memset(data->rx_buf, 0, data->rx_size); |
| } |
| writel(~0, mbox_base + RX_OFFSET_CLR); |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int mhu_transfer_data(struct mbox_chan *link, void *msg) |
| { |
| struct mhu_chan *chan = link->con_priv; |
| struct mhu_ctlr *ctlr = chan->ctlr; |
| void __iomem *mbox_dspa_base = ctlr->mbox_dspa_base; |
| void __iomem *mbox_dspb_base = ctlr->mbox_dspb_base; |
| void __iomem *payload = ctlr->payload_base; |
| struct mhu_data_buf *data = (struct mhu_data_buf *)msg; |
| int idx = chan->index; |
| void __iomem *mbox_base; |
| |
| if (!data) |
| return -EINVAL; |
| |
| if (BIT(idx) & 0xC) |
| mbox_base = mbox_dspb_base; |
| else if (BIT(idx) & 0x3) |
| mbox_base = mbox_dspa_base; |
| else |
| mbox_base = mbox_dspa_base; |
| |
| chan->data = data; |
| if (data->tx_buf) { |
| memset_io(payload + TX_PAYLOAD(idx), |
| 0, MHU_BUFFER_SIZE); |
| memcpy_toio(payload + TX_PAYLOAD(idx), |
| data->tx_buf, data->tx_size); |
| } |
| writel(data->cmd, mbox_base + TX_OFFSET_SET); |
| |
| return 0; |
| } |
| |
| /** |
| * mbox_ack_isr_handler |
| * AP ack interrupt handler |
| */ |
| static irqreturn_t mbox_ack_isr_handler(int irq, void *p) |
| { |
| struct mbox_chan *mbox_chan = (struct mbox_chan *)p; |
| struct mhu_chan *mhu_chan = (struct mhu_chan *)mbox_chan->con_priv; |
| struct mhu_ctlr *ctlr = mhu_chan->ctlr; |
| void __iomem *payload = ctlr->payload_base; |
| struct mhu_data_buf *data; |
| int mem_idx = mhu_chan->index; |
| |
| if (irq == mhu_chan->rx_irq) { |
| data = mhu_chan->data; |
| if (!data) |
| return IRQ_NONE; /* spurious */ |
| if (data->rx_buf) |
| memcpy_fromio(data->rx_buf, |
| payload + TX_PAYLOAD(mem_idx), |
| data->rx_size); |
| mhu_chan->data = NULL; |
| mbox_chan_received_data(mbox_chan, data); |
| complete(&mbox_chan->tx_complete); |
| } |
| return IRQ_HANDLED; |
| } |
| |
| static int mhu_startup(struct mbox_chan *link) |
| { |
| struct mhu_chan *chan = link->con_priv; |
| int err, mbox_irq = chan->rx_irq; |
| |
| err = request_threaded_irq(mbox_irq, mbox_ack_isr_handler, |
| NULL, IRQF_ONESHOT, |
| DRIVER_NAME, link); |
| return err; |
| } |
| |
| static void mhu_shutdown(struct mbox_chan *link) |
| { |
| struct mhu_chan *chan = link->con_priv; |
| |
| chan->data = NULL; |
| free_irq(chan->rx_irq, link); |
| } |
| |
| static bool mhu_last_tx_done(struct mbox_chan *link) |
| { |
| struct mhu_chan *chan = link->con_priv; |
| struct mhu_ctlr *ctlr = chan->ctlr; |
| void __iomem *mbox_dspa_base = ctlr->mbox_dspa_base; |
| void __iomem *mbox_dspb_base = ctlr->mbox_dspb_base; |
| void __iomem *mbox_base; |
| int idx = chan->index; |
| |
| if (idx / 2) |
| mbox_base = mbox_dspb_base; |
| else |
| mbox_base = mbox_dspa_base; |
| return !readl(mbox_base + TX_OFFSET_STAT); |
| } |
| |
| static struct mbox_chan_ops mhu_ops = { |
| .send_data = mhu_transfer_data, |
| .startup = mhu_startup, |
| .shutdown = mhu_shutdown, |
| .last_tx_done = mhu_last_tx_done, |
| }; |
| |
| static ssize_t mbox_message_write(struct file *filp, |
| const char __user *userbuf, |
| size_t count, loff_t *ppos) |
| { |
| int ret; |
| struct mbox_message *mbox_msg; |
| struct mbox_client cl = {0}; |
| struct mbox_chan *chan; |
| struct mbox_data mbox_data; |
| struct mhu_data_buf data_buf; |
| unsigned long flags; |
| int cmd; |
| struct device *dev = dsp_scpi_device; |
| struct inode *inode = filp->f_inode; |
| int chan_index = 1; |
| struct device *dsp_dev = &mbox_cdev.dsp_dev[0]; |
| |
| if (inode->i_rdev == mbox_cdev.dsp_dev[1].devt) { |
| chan_index = 3; |
| dsp_dev = &mbox_cdev.dsp_dev[1]; |
| } |
| |
| if (count > MBOX_ALLOWED_SIZE) { |
| dev_err(dev, |
| "Message length %zd greater than max allowed\n", |
| count); |
| return -EINVAL; |
| } |
| mbox_msg = kzalloc(sizeof(*mbox_msg), GFP_KERNEL); |
| if (!mbox_msg) { |
| ret = -ENOMEM; |
| goto err_probe0; |
| } |
| |
| mbox_msg->data = kzalloc(sizeof(mbox_data), GFP_KERNEL); |
| if (!mbox_msg->data) { |
| ret = -ENOMEM; |
| goto err_probe1; |
| } |
| |
| ret = copy_from_user(mbox_data.data, userbuf |
| + MBOX_USER_CMD_LEN, |
| count - MBOX_USER_CMD_LEN); |
| if (ret) { |
| ret = -EFAULT; |
| goto err_probe2; |
| } |
| |
| ret = copy_from_user((char *)&cmd, userbuf, MBOX_USER_CMD_LEN); |
| if (ret) { |
| ret = -EFAULT; |
| goto err_probe2; |
| } |
| |
| mbox_msg->cmd = cmd & CMD_MASK; |
| init_completion(&mbox_msg->complete); |
| mbox_msg->task = current; |
| spin_lock_irqsave(&lock, flags); |
| list_add_tail(&mbox_msg->list, &mbox_list); |
| spin_unlock_irqrestore(&lock, flags); |
| |
| /*Listen data not send data to hifi*/ |
| if (cmd & LISTEN_DATA) { |
| ret = count; |
| goto err_probe0; |
| } |
| |
| mbox_data.complete = (unsigned long)(&mbox_msg->complete); |
| dev_dbg(dev, "%s %lx\n", __func__, (unsigned long)mbox_data.complete); |
| data_buf.tx_buf = (void *)&mbox_data; |
| data_buf.tx_size = count - MBOX_USER_CMD_LEN + MBOX_COMPLETE_LEN; |
| data_buf.cmd = (mbox_msg->cmd) |
| | ((data_buf.tx_size & SIZE_MASK) << SIZE_SHIFT) |
| | ASYNC_OR_SYNC(1); |
| data_buf.rx_buf = NULL; |
| cl.dev = dev; |
| cl.tx_block = true; |
| cl.tx_tout = MBOX_TIME_OUT; |
| mutex_lock(&dsp_dev->mutex); |
| chan = mbox_request_channel(&cl, chan_index); |
| if (IS_ERR(chan)) { |
| mutex_unlock(&dsp_dev->mutex); |
| dev_err(dev, "Failed Req Chan\n"); |
| ret = PTR_ERR(chan); |
| goto err_probe3; |
| } |
| ret = mbox_send_message(chan, (void *)(&data_buf)); |
| mbox_free_channel(chan); |
| mutex_unlock(&dsp_dev->mutex); |
| if (ret < 0) { |
| dev_err(dev, "Failed to send message via mailbox %d\n", ret); |
| } else { |
| dev_dbg(dev, "Ack OK\n"); |
| return count; |
| } |
| err_probe3: |
| spin_lock_irqsave(&lock, flags); |
| list_del(&mbox_msg->list); |
| spin_unlock_irqrestore(&lock, flags); |
| err_probe2: |
| kfree(mbox_msg->data); |
| err_probe1: |
| kfree(mbox_msg); |
| err_probe0: |
| return ret; |
| } |
| |
| static ssize_t mbox_message_read(struct file *filp, char __user *userbuf, |
| size_t count, loff_t *ppos) |
| { |
| int ret; |
| struct mbox_message *msg; |
| struct list_head *list; |
| unsigned long flags; |
| struct device *dev = dsp_scpi_device; |
| unsigned long wait; |
| |
| spin_lock_irqsave(&lock, flags); |
| if (list_empty(&mbox_list)) { |
| spin_unlock_irqrestore(&lock, flags); |
| return -ENXIO; |
| } |
| list_for_each(list, &mbox_list) { |
| msg = list_entry(list, struct mbox_message, list); |
| if (msg->task == current) { |
| spin_unlock_irqrestore(&lock, flags); |
| wait = msecs_to_jiffies(MBOX_TIME_OUT); |
| ret = wait_for_completion_timeout(&msg->complete, wait); |
| if (ret == 0 || ret == -ERESTARTSYS) { |
| dev_err(dev, "Read msg wait time out\n"); |
| mbox_dsp_clrirq(0); |
| spin_lock_irqsave(&lock, flags); |
| list_del(list); |
| spin_unlock_irqrestore(&lock, flags); |
| kfree(msg->data); |
| kfree(msg); |
| return -EREMOTEIO; |
| } |
| dev_dbg(dev, "Wait end %s\n", msg->data); |
| break; |
| } |
| } |
| if (list == &mbox_list) { |
| dev_err(dev, "List is null or not find data\n"); |
| spin_unlock_irqrestore(&lock, flags); |
| return -ENXIO; |
| } |
| *ppos = 0; |
| ret = simple_read_from_buffer(userbuf, count, ppos, |
| msg->data, MBOX_TX_SIZE); |
| |
| spin_lock_irqsave(&lock, flags); |
| list_del(list); |
| spin_unlock_irqrestore(&lock, flags); |
| kfree(msg->data); |
| kfree(msg); |
| return ret; |
| } |
| |
| static const struct file_operations mbox_message_ops = { |
| .write = mbox_message_write, |
| .read = mbox_message_read, |
| .open = simple_open, |
| }; |
| |
| static int init_char_cdev(struct device *dev) |
| { |
| int err; |
| int nr_minor = 0; |
| int i = 0; |
| |
| of_property_read_u32(dev->of_node, |
| "mbox-nums", &nr_minor); |
| nr_minor = nr_minor / 2; |
| if (nr_minor == 0 || nr_minor > NR_DSP) |
| nr_minor = NR_DSP; |
| |
| err = alloc_chrdev_region(&mbox_cdev.dsp_no, 0, nr_minor, DRIVER_NAME); |
| if (err < 0) { |
| dev_err(dev, "%s dsp alloc dev_t number failed\n", __func__); |
| err = -1; |
| goto err2; |
| } |
| |
| for (i = 0; i < nr_minor; i++) { |
| mbox_cdev.dsp_dev[i].init_name = dsp_name[i]; |
| cdev_init(&mbox_cdev.dsp_cdev[i], &mbox_message_ops); |
| mbox_cdev.dsp_cdev[i].owner = THIS_MODULE; |
| mbox_cdev.dsp_dev[i].devt = MKDEV(MAJOR(mbox_cdev.dsp_no), i); |
| device_initialize(&mbox_cdev.dsp_dev[i]); |
| err = cdev_device_add(&mbox_cdev.dsp_cdev[i], |
| &mbox_cdev.dsp_dev[i]); |
| if (err < 0) { |
| dev_err(dev, "%s: could not add character device\n", |
| mbox_cdev.dsp_dev[i].init_name); |
| goto err1; |
| } |
| } |
| return 0; |
| err1: |
| put_device(&mbox_cdev.dsp_dev[0]); |
| put_device(&mbox_cdev.dsp_dev[1]); |
| err2: |
| return err; |
| } |
| |
| static int mhu_dsp_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct mhu_ctlr *mhu_ctrl; |
| struct mhu_chan *mhu_chan; |
| struct mbox_chan *mbox_chans; |
| struct resource *res; |
| int idx, num_chans, memid; |
| int err = 0; |
| |
| pr_info("dsp mailbox init start\n"); |
| mhu_ctrl = devm_kzalloc(dev, sizeof(*mhu_ctrl), GFP_KERNEL); |
| if (!mhu_ctrl) |
| return -ENOMEM; |
| |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| if (!res) { |
| dev_err(dev, "failed to get mailbox memory resource\n"); |
| return -ENXIO; |
| } |
| mhu_ctrl->mbox_dspa_base = devm_ioremap_resource(dev, res); |
| if (IS_ERR(mhu_ctrl->mbox_dspa_base)) |
| return PTR_ERR(mhu_ctrl->mbox_dspa_base); |
| |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 1); |
| if (!res) { |
| dev_err(dev, "failed to get mailbox memory resource\n"); |
| return -ENXIO; |
| } |
| |
| num_chans = 0; |
| of_property_read_u32(dev->of_node, |
| "mbox-nums", &num_chans); |
| if (!num_chans) |
| num_chans = CHANNEL_MAX; |
| |
| if (num_chans == 4) { |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 1); |
| if (!res) { |
| dev_err(dev, "failed to get mailbox memory resource\n"); |
| return -ENXIO; |
| } |
| mhu_ctrl->mbox_dspb_base = devm_ioremap_resource(dev, res); |
| if (IS_ERR(mhu_ctrl->mbox_dspb_base)) |
| return PTR_ERR(mhu_ctrl->mbox_dspb_base); |
| } |
| |
| memid = num_chans / 2; |
| |
| res = platform_get_resource(pdev, IORESOURCE_MEM, memid); |
| if (!res) { |
| dev_err(dev, "failed to get payload memory resource\n"); |
| return -ENXIO; |
| } |
| mhu_ctrl->payload_base = devm_ioremap_resource(dev, res); |
| if (IS_ERR(mhu_ctrl->payload_base)) |
| return PTR_ERR(mhu_ctrl->payload_base); |
| |
| mhu_ctrl->dev = dev; |
| platform_set_drvdata(pdev, mhu_ctrl); |
| g_mhu_ctrl = mhu_ctrl; |
| |
| mbox_chans = devm_kzalloc(dev, |
| sizeof(*mbox_chans) * num_chans, |
| GFP_KERNEL); |
| if (!mbox_chans) |
| return -ENOMEM; |
| |
| mhu_ctrl->mbox_con.chans = mbox_chans; |
| mhu_ctrl->mbox_con.num_chans = num_chans; |
| mhu_ctrl->mbox_con.txdone_irq = true; |
| mhu_ctrl->mbox_con.ops = &mhu_ops; |
| mhu_ctrl->mbox_con.dev = dev; |
| |
| mhu_ctrl->channels = devm_kzalloc(dev, |
| sizeof(struct mhu_chan) * num_chans, |
| GFP_KERNEL); |
| for (idx = 0; idx < num_chans; idx++) { |
| mhu_chan = &mhu_ctrl->channels[idx]; |
| mhu_chan->index = idx; |
| mhu_chan->ctlr = mhu_ctrl; |
| mhu_chan->rx_irq = platform_get_irq(pdev, idx); |
| |
| if (mhu_chan->rx_irq < 0) { |
| dev_err(dev, "failed to get interrupt %d\n", idx); |
| return -ENXIO; |
| } |
| mbox_chans[idx].con_priv = mhu_chan; |
| } |
| |
| if (mbox_controller_register(&mhu_ctrl->mbox_con)) { |
| dev_err(dev, "failed to register mailbox controller\n"); |
| return -ENOMEM; |
| } |
| |
| dsp_scpi_device = dev; |
| |
| for (idx = 0; idx < num_chans; idx++) { |
| if (BIT(idx) & 0xA) |
| continue; |
| mhu_chan = &mhu_ctrl->channels[idx]; |
| mhu_chan->index = idx; |
| mhu_chan->data = devm_kzalloc(dev, |
| sizeof(struct mhu_data_buf), |
| GFP_KERNEL); |
| mhu_chan->data->rx_buf = devm_kzalloc(dev, |
| MHU_BUFFER_SIZE, |
| GFP_KERNEL); |
| if (!mhu_chan->data->rx_buf) |
| return -ENOMEM; |
| mhu_chan->data->rx_size = MHU_BUFFER_SIZE; |
| err = request_threaded_irq(mhu_chan->rx_irq, mbox_dsp_handler, |
| NULL, IRQF_ONESHOT, |
| DRIVER_NAME, &mbox_chans[idx]); |
| if (err) { |
| dev_err(dev, "request irq error\n"); |
| return err; |
| } |
| } |
| |
| err = init_char_cdev(dev); |
| if (err < 0) { |
| pr_info("init cdev fail\n"); |
| return err; |
| } |
| pr_info("dsp mailbox init done\n"); |
| return 0; |
| } |
| |
| static int mhu_remove(struct platform_device *pdev) |
| { |
| struct mhu_ctlr *ctlr = platform_get_drvdata(pdev); |
| |
| mbox_controller_unregister(&ctlr->mbox_con); |
| |
| platform_set_drvdata(pdev, NULL); |
| return 0; |
| } |
| |
| static const struct of_device_id mhu_of_match[] = { |
| { .compatible = "amlogic, meson_mhu_dsp" }, |
| {}, |
| }; |
| |
| static struct platform_driver mhu_driver = { |
| .probe = mhu_dsp_probe, |
| .remove = mhu_remove, |
| .driver = { |
| .owner = THIS_MODULE, |
| .name = DRIVER_NAME, |
| .of_match_table = mhu_of_match, |
| }, |
| }; |
| |
| static int __init mhu_init(void) |
| { |
| return platform_driver_register(&mhu_driver); |
| } |
| core_initcall(mhu_init); |
| |
| static void __exit mhu_exit(void) |
| { |
| platform_driver_unregister(&mhu_driver); |
| } |
| module_exit(mhu_exit); |
| |
| MODULE_AUTHOR("shunzhou jiang <shunzhou.jiang@amlogic.com>"); |
| MODULE_DESCRIPTION("MESON MHU mailbox dsp driver"); |
| MODULE_LICENSE("GPL"); |