| /* Copyright (c) 2017, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only 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/io.h> |
| #include <linux/interrupt.h> |
| #include <linux/types.h> |
| #include <linux/spinlock.h> |
| #include <linux/completion.h> |
| #include <linux/platform_device.h> |
| #include <linux/mailbox_controller.h> |
| #include <linux/module.h> |
| #include <linux/of_irq.h> |
| #include <linux/kthread.h> |
| #include <linux/workqueue.h> |
| #include <linux/mailbox/qmp.h> |
| |
| #define QMP_MAGIC 0x4d41494c /* MAIL */ |
| #define QMP_VERSION 0x1 |
| #define QMP_FEATURES 0x0 |
| #define QMP_NUM_CHANS 0x1 |
| #define QMP_TOUT_MS 5000 |
| #define QMP_TX_TOUT_MS 2000 |
| |
| #define QMP_MBOX_LINK_DOWN 0xFFFF0000 |
| #define QMP_MBOX_LINK_UP 0x0000FFFF |
| #define QMP_MBOX_CH_DISCONNECTED 0xFFFF0000 |
| #define QMP_MBOX_CH_CONNECTED 0x0000FFFF |
| |
| #define MSG_RAM_ALIGN_BYTES 3 |
| |
| /** |
| * enum qmp_local_state - definition of the local state machine |
| * @LINK_DISCONNECTED: Init state, waiting for ucore to start |
| * @LINK_NEGOTIATION: Set local link state to up, wait for ucore ack |
| * @LINK_CONNECTED: Link state up, channel not connected |
| * @LOCAL_CONNECTING: Channel opening locally, wait for ucore ack |
| * @LOCAL_CONNECTED: Channel opened locally |
| * @CHANNEL_CONNECTED: Channel fully opened |
| * @LOCAL_DISCONNECTING: Channel closing locally, wait for ucore ack |
| */ |
| enum qmp_local_state { |
| LINK_DISCONNECTED, |
| LINK_NEGOTIATION, |
| LINK_CONNECTED, |
| LOCAL_CONNECTING, |
| LOCAL_CONNECTED, |
| CHANNEL_CONNECTED, |
| LOCAL_DISCONNECTING, |
| }; |
| |
| /** |
| * struct channel_desc - description of a core's link, channel and mailbox state |
| * @link_state Current link state of core |
| * @link_state_ack Ack for other core to use when link state changes |
| * @ch_state Current channel state of core |
| * @ch_state_ack Ack for other core to use when channel state changes |
| * @mailbox_size Size of this core's mailbox |
| * @mailbox_offset Location of core's mailbox from a base smem location |
| */ |
| struct channel_desc { |
| u32 link_state; |
| u32 link_state_ack; |
| u32 ch_state; |
| u32 ch_state_ack; |
| u32 mailbox_size; |
| u32 mailbox_offset; |
| }; |
| |
| /** |
| * struct mbox_desc - description of the protocol's mailbox state |
| * @magic Magic number field to be set by ucore |
| * @version Version field to be set by ucore |
| * @features Features field to be set by ucore |
| * @ucore Channel descriptor to hold state of ucore |
| * @mcore Channel descriptor to hold state of mcore |
| * @reserved Reserved in case of future use |
| * |
| * This structure resides in SMEM and contains the control information for the |
| * mailbox channel. Each core in the link will have one channel descriptor |
| */ |
| struct mbox_desc { |
| u32 magic; |
| u32 version; |
| u32 features; |
| struct channel_desc ucore; |
| struct channel_desc mcore; |
| u32 reserved; |
| }; |
| |
| /** |
| * struct qmp_core_version - local structure to hold version and features |
| * @version Version field to indicate what version the ucore supports |
| * @features Features field to indicate what features the ucore supports |
| */ |
| struct qmp_core_version { |
| u32 version; |
| u32 features; |
| }; |
| |
| /** |
| * struct qmp_device - local information for managing a single mailbox |
| * @dev: The device that corresponds to this mailbox |
| * @mbox: The mbox controller for this mailbox |
| * @name: The name of this mailbox |
| * @local_state: Current state of the mailbox protocol |
| * @link_complete: Use to block until link negotiation with remote proc |
| * is complete |
| * @ch_complete: Use to block until the channel is fully opened |
| * @tx_sent: True if tx is sent and remote proc has not sent ack |
| * @ch_in_use: True if this mailbox's channel owned by a client |
| * @rx_buf: buffer to pass to client, holds copied data from mailbox |
| * @version: Version and features received during link negotiation |
| * @mcore_mbox_offset: Offset of mcore mbox from the msgram start |
| * @mcore_mbox_size: Size of the mcore mbox |
| * @desc: Reference to the mailbox descriptor in SMEM |
| * @msgram: Reference to the start of msgram |
| * @irq_mask: Mask written to @tx_irq_reg to trigger irq |
| * @tx_irq_reg: Reference to the register to send an irq to remote proc |
| * @rx_reset_reg: Reference to the register to reset the rx irq, if |
| * applicable |
| * @rx_irq_line: The incoming interrupt line |
| * @tx_irq_count: Number of tx interrupts triggered |
| * @rx_irq_count: Number of rx interrupts received |
| * @kwork: Work to be executed when an irq is received |
| * @kworker: Handle to entitiy to process incoming data |
| * @task: Handle to task context used to run @kworker |
| * @state_lock: Serialize mailbox state changes |
| * @dwork: Delayed work to detect timed out tx |
| * @tx_lock: Serialize access for writes to mailbox |
| */ |
| struct qmp_device { |
| struct device *dev; |
| struct mbox_controller *mbox; |
| const char *name; |
| enum qmp_local_state local_state; |
| struct completion link_complete; |
| struct completion ch_complete; |
| bool tx_sent; |
| bool ch_in_use; |
| struct qmp_pkt rx_pkt; |
| struct qmp_core_version version; |
| u32 mcore_mbox_offset; |
| u32 mcore_mbox_size; |
| void __iomem *desc; |
| void __iomem *msgram; |
| u32 irq_mask; |
| void __iomem *tx_irq_reg; |
| void __iomem *rx_reset_reg; |
| u32 rx_irq_line; |
| u32 tx_irq_count; |
| u32 rx_irq_count; |
| struct kthread_work kwork; |
| struct kthread_worker kworker; |
| struct task_struct *task; |
| struct mutex state_lock; |
| struct delayed_work dwork; |
| spinlock_t tx_lock; |
| }; |
| |
| /** |
| * send_irq() - send an irq to a remote entity as an event signal. |
| * @mdev: Which remote entity that should receive the irq. |
| */ |
| static void send_irq(struct qmp_device *mdev) |
| { |
| /* |
| * Any data associated with this event must be visable to the remote |
| * before the interrupt is triggered |
| */ |
| wmb(); |
| writel_relaxed(mdev->irq_mask, mdev->tx_irq_reg); |
| mdev->tx_irq_count++; |
| } |
| |
| /** |
| * qmp_irq_handler() - handle irq from remote entitity. |
| * @irq: irq number for the trggered interrupt. |
| * @priv: private pointer to qmp mbox device. |
| */ |
| irqreturn_t qmp_irq_handler(int irq, void *priv) |
| { |
| struct qmp_device *mdev = (struct qmp_device *)priv; |
| |
| if (mdev->rx_reset_reg) |
| writel_relaxed(mdev->irq_mask, mdev->rx_reset_reg); |
| |
| kthread_queue_work(&mdev->kworker, &mdev->kwork); |
| mdev->rx_irq_count++; |
| |
| return IRQ_HANDLED; |
| } |
| |
| static void memcpy32_toio(void *dest, void *src, size_t size) |
| { |
| u32 *dest_local = (u32 *)dest; |
| u32 *src_local = (u32 *)src; |
| |
| WARN_ON(size & MSG_RAM_ALIGN_BYTES); |
| size /= sizeof(u32); |
| while (size--) |
| iowrite32(*src_local++, dest_local++); |
| } |
| |
| static void memcpy32_fromio(void *dest, void *src, size_t size) |
| { |
| u32 *dest_local = (u32 *)dest; |
| u32 *src_local = (u32 *)src; |
| |
| WARN_ON(size & MSG_RAM_ALIGN_BYTES); |
| size /= sizeof(u32); |
| while (size--) |
| *dest_local++ = ioread32(src_local++); |
| } |
| |
| /** |
| * set_ucore_link_ack() - set the link ack in the ucore channel desc. |
| * @mdev: the mailbox for the field that is being set. |
| * @state: the value to set the ack field to. |
| */ |
| static void set_ucore_link_ack(struct qmp_device *mdev, u32 state) |
| { |
| u32 offset; |
| |
| offset = offsetof(struct mbox_desc, ucore); |
| offset += offsetof(struct channel_desc, link_state_ack); |
| iowrite32(state, mdev->desc + offset); |
| } |
| |
| /** |
| * set_ucore_ch_ack() - set the channel ack in the ucore channel desc. |
| * @mdev: the mailbox for the field that is being set. |
| * @state: the value to set the ack field to. |
| */ |
| static void set_ucore_ch_ack(struct qmp_device *mdev, u32 state) |
| { |
| u32 offset; |
| |
| offset = offsetof(struct mbox_desc, ucore); |
| offset += offsetof(struct channel_desc, ch_state_ack); |
| iowrite32(state, mdev->desc + offset); |
| } |
| |
| /** |
| * set_mcore_ch() - set the channel state in the mcore channel desc. |
| * @mdev: the mailbox for the field that is being set. |
| * @state: the value to set the channel field to. |
| */ |
| static void set_mcore_ch(struct qmp_device *mdev, u32 state) |
| { |
| u32 offset; |
| |
| offset = offsetof(struct mbox_desc, mcore); |
| offset += offsetof(struct channel_desc, ch_state); |
| iowrite32(state, mdev->desc + offset); |
| } |
| |
| /** |
| * qmp_notify_timeout() - Notify client of tx timeout with -EIO |
| * @work: Structure for work that was scheduled. |
| */ |
| static void qmp_notify_timeout(struct work_struct *work) |
| { |
| struct delayed_work *dwork = to_delayed_work(work); |
| struct qmp_device *mdev = container_of(dwork, struct qmp_device, dwork); |
| struct mbox_chan *chan = &mdev->mbox->chans[0]; |
| int err = -EIO; |
| |
| pr_err("%s: qmp tx timeout for %s\n", __func__, mdev->name); |
| mbox_chan_txdone(chan, err); |
| } |
| |
| /** |
| * qmp_startup() - Start qmp mailbox channel for communication. Waits for |
| * remote subsystem to open channel if link is not |
| * initated or until timeout. |
| * @chan: mailbox channel that is being opened. |
| * |
| * Return: 0 on succes or standard Linux error code. |
| */ |
| static int qmp_startup(struct mbox_chan *chan) |
| { |
| struct qmp_device *mdev = chan->con_priv; |
| |
| if (!mdev) |
| return -EINVAL; |
| |
| mutex_lock(&mdev->state_lock); |
| if (mdev->local_state == CHANNEL_CONNECTED) { |
| mutex_unlock(&mdev->state_lock); |
| return -EINVAL; |
| } |
| if (!completion_done(&mdev->link_complete)) { |
| mutex_unlock(&mdev->state_lock); |
| return -EAGAIN; |
| } |
| |
| set_mcore_ch(mdev, QMP_MBOX_CH_CONNECTED); |
| mdev->local_state = LOCAL_CONNECTING; |
| mutex_unlock(&mdev->state_lock); |
| |
| send_irq(mdev); |
| wait_for_completion_interruptible_timeout(&mdev->ch_complete, |
| msecs_to_jiffies(QMP_TOUT_MS)); |
| return 0; |
| } |
| |
| static inline void qmp_schedule_tx_timeout(struct qmp_device *mdev) |
| { |
| schedule_delayed_work(&mdev->dwork, msecs_to_jiffies(QMP_TX_TOUT_MS)); |
| } |
| |
| /** |
| * qmp_send_data() - Copy the data to the channel's mailbox and notify |
| * remote subsystem of new data. This function will |
| * return an error if the previous message sent has |
| * not been read. Cannot Sleep. |
| * @chan: mailbox channel that data is to be sent over. |
| * @data: Data to be sent to remote processor, should be in the format of |
| * a qmp_pkt. |
| * |
| * Return: 0 on succes or standard Linux error code. |
| */ |
| static int qmp_send_data(struct mbox_chan *chan, void *data) |
| { |
| struct qmp_device *mdev = chan->con_priv; |
| struct qmp_pkt *pkt = (struct qmp_pkt *)data; |
| void __iomem *addr; |
| unsigned long flags; |
| |
| if (!mdev || !data || mdev->local_state != CHANNEL_CONNECTED) |
| return -EINVAL; |
| |
| spin_lock_irqsave(&mdev->tx_lock, flags); |
| addr = mdev->msgram + mdev->mcore_mbox_offset; |
| if (ioread32(addr)) { |
| spin_unlock_irqrestore(&mdev->tx_lock, flags); |
| return -EBUSY; |
| } |
| |
| if (pkt->size + sizeof(pkt->size) > mdev->mcore_mbox_size) { |
| spin_unlock_irqrestore(&mdev->tx_lock, flags); |
| return -EINVAL; |
| } |
| memcpy32_toio(addr + sizeof(pkt->size), pkt->data, pkt->size); |
| iowrite32(pkt->size, addr); |
| mdev->tx_sent = true; |
| send_irq(mdev); |
| qmp_schedule_tx_timeout(mdev); |
| spin_unlock_irqrestore(&mdev->tx_lock, flags); |
| return 0; |
| } |
| |
| /** |
| * qmp_shutdown() - Disconnect this mailbox channel so the client does not |
| * receive anymore data and can reliquish control |
| * of the channel |
| * @chan: mailbox channel to be shutdown. |
| */ |
| static void qmp_shutdown(struct mbox_chan *chan) |
| { |
| struct qmp_device *mdev = chan->con_priv; |
| |
| mutex_lock(&mdev->state_lock); |
| if (mdev->local_state != LINK_DISCONNECTED) { |
| mdev->local_state = LOCAL_DISCONNECTING; |
| set_mcore_ch(mdev, QMP_MBOX_CH_DISCONNECTED); |
| send_irq(mdev); |
| } |
| mdev->ch_in_use = false; |
| mutex_unlock(&mdev->state_lock); |
| } |
| |
| /** |
| * qmp_last_tx_done() - qmp does not support polling operations, print |
| * error of unexpected usage and return true to |
| * resume operation. |
| * @chan: Corresponding mailbox channel for requested last tx. |
| * |
| * Return: true |
| */ |
| static bool qmp_last_tx_done(struct mbox_chan *chan) |
| { |
| pr_err("In %s, unexpected usage of last_tx_done\n", __func__); |
| return true; |
| } |
| |
| /** |
| * qmp_recv_data() - received notification that data is available in the |
| * mailbox. Copy data from mailbox and pass to client. |
| * @mdev: mailbox device that received the notification. |
| * @mbox_of: offset of mailbox from msgram start. |
| */ |
| static void qmp_recv_data(struct qmp_device *mdev, u32 mbox_of) |
| { |
| void __iomem *addr; |
| struct qmp_pkt *pkt; |
| |
| addr = mdev->msgram + mbox_of; |
| pkt = &mdev->rx_pkt; |
| pkt->size = ioread32(addr); |
| |
| if (pkt->size > mdev->mcore_mbox_size) |
| pr_err("%s: Invalid mailbox packet\n", __func__); |
| else { |
| memcpy32_fromio(pkt->data, addr + sizeof(pkt->size), pkt->size); |
| mbox_chan_received_data(&mdev->mbox->chans[0], &pkt); |
| } |
| iowrite32(0, addr); |
| send_irq(mdev); |
| } |
| |
| /** |
| * init_mcore_state() - initialize the mcore state of a mailbox. |
| * @mdev: mailbox device to be initialized. |
| */ |
| static void init_mcore_state(struct qmp_device *mdev) |
| { |
| struct channel_desc mcore; |
| u32 offset = offsetof(struct mbox_desc, mcore); |
| |
| mcore.link_state = QMP_MBOX_LINK_UP; |
| mcore.link_state_ack = QMP_MBOX_LINK_DOWN; |
| mcore.ch_state = QMP_MBOX_CH_DISCONNECTED; |
| mcore.ch_state_ack = QMP_MBOX_CH_DISCONNECTED; |
| mcore.mailbox_size = mdev->mcore_mbox_size; |
| mcore.mailbox_offset = mdev->mcore_mbox_offset; |
| memcpy32_toio(mdev->desc + offset, &mcore, sizeof(mcore)); |
| } |
| |
| /** |
| * __qmp_rx_worker() - Handle incoming messages from remote processor. |
| * @mdev: mailbox device that received notification. |
| */ |
| static void __qmp_rx_worker(struct qmp_device *mdev) |
| { |
| u32 msg_len; |
| struct mbox_desc desc; |
| |
| memcpy_fromio(&desc, mdev->desc, sizeof(desc)); |
| if (desc.magic != QMP_MAGIC) |
| return; |
| |
| mutex_lock(&mdev->state_lock); |
| switch (mdev->local_state) { |
| case LINK_DISCONNECTED: |
| mdev->version.version = desc.version; |
| mdev->version.features = desc.features; |
| set_ucore_link_ack(mdev, desc.ucore.link_state); |
| if (desc.mcore.mailbox_size) { |
| mdev->mcore_mbox_size = desc.mcore.mailbox_size; |
| mdev->mcore_mbox_offset = desc.mcore.mailbox_offset; |
| } |
| init_mcore_state(mdev); |
| mdev->local_state = LINK_NEGOTIATION; |
| mdev->rx_pkt.data = devm_kzalloc(mdev->dev, |
| desc.ucore.mailbox_size, |
| GFP_KERNEL); |
| if (!mdev->rx_pkt.data) { |
| pr_err("In %s: failed to allocate rx pkt\n", __func__); |
| break; |
| } |
| send_irq(mdev); |
| break; |
| case LINK_NEGOTIATION: |
| if (desc.mcore.link_state_ack != QMP_MBOX_LINK_UP || |
| desc.mcore.link_state != QMP_MBOX_LINK_UP) { |
| pr_err("In %s: rx interrupt without negotiation ack\n", |
| __func__); |
| break; |
| } |
| mdev->local_state = LINK_CONNECTED; |
| complete_all(&mdev->link_complete); |
| break; |
| case LINK_CONNECTED: |
| if (desc.ucore.ch_state == desc.ucore.ch_state_ack) { |
| pr_err("In %s: rx interrupt without channel open\n", |
| __func__); |
| break; |
| } |
| set_ucore_ch_ack(mdev, desc.ucore.ch_state); |
| send_irq(mdev); |
| break; |
| case LOCAL_CONNECTING: |
| if (desc.mcore.ch_state_ack == QMP_MBOX_CH_CONNECTED && |
| desc.mcore.ch_state == QMP_MBOX_CH_CONNECTED) |
| mdev->local_state = LOCAL_CONNECTED; |
| |
| if (desc.ucore.ch_state != desc.ucore.ch_state_ack) { |
| set_ucore_ch_ack(mdev, desc.ucore.ch_state); |
| send_irq(mdev); |
| } |
| if (mdev->local_state == LOCAL_CONNECTED && |
| desc.mcore.ch_state == QMP_MBOX_CH_CONNECTED && |
| desc.ucore.ch_state == QMP_MBOX_CH_CONNECTED) { |
| mdev->local_state = CHANNEL_CONNECTED; |
| complete_all(&mdev->ch_complete); |
| } |
| break; |
| case LOCAL_CONNECTED: |
| if (desc.ucore.ch_state == desc.ucore.ch_state_ack) { |
| pr_err("In %s: rx interrupt without remote channel open\n", |
| __func__); |
| break; |
| } |
| set_ucore_ch_ack(mdev, desc.ucore.ch_state); |
| mdev->local_state = CHANNEL_CONNECTED; |
| send_irq(mdev); |
| complete_all(&mdev->ch_complete); |
| break; |
| case CHANNEL_CONNECTED: |
| if (desc.ucore.ch_state == QMP_MBOX_CH_DISCONNECTED) { |
| set_ucore_ch_ack(mdev, desc.ucore.ch_state); |
| mdev->local_state = LOCAL_CONNECTED; |
| send_irq(mdev); |
| } |
| |
| msg_len = ioread32(mdev->msgram + desc.ucore.mailbox_offset); |
| if (msg_len) |
| qmp_recv_data(mdev, desc.ucore.mailbox_offset); |
| |
| if (mdev->tx_sent) { |
| msg_len = ioread32(mdev->msgram + |
| mdev->mcore_mbox_offset); |
| if (msg_len == 0) { |
| mdev->tx_sent = false; |
| cancel_delayed_work(&mdev->dwork); |
| mbox_chan_txdone(&mdev->mbox->chans[0], 0); |
| } |
| } |
| break; |
| case LOCAL_DISCONNECTING: |
| if (desc.mcore.ch_state_ack == QMP_MBOX_CH_DISCONNECTED && |
| desc.mcore.ch_state == desc.mcore.ch_state_ack) |
| mdev->local_state = LINK_CONNECTED; |
| reinit_completion(&mdev->ch_complete); |
| break; |
| default: |
| pr_err("In %s: Local Channel State corrupted\n", __func__); |
| } |
| mutex_unlock(&mdev->state_lock); |
| } |
| |
| static void rx_worker(struct kthread_work *work) |
| { |
| struct qmp_device *mdev; |
| |
| mdev = container_of(work, struct qmp_device, kwork); |
| __qmp_rx_worker(mdev); |
| } |
| |
| /** |
| * qmp_mbox_of_xlate() - Returns a mailbox channel to be used for this mailbox |
| * device. Make sure the channel is not already in use. |
| * @mbox: Mailbox device controlls the requested channel. |
| * @spec: Device tree arguments to specify which channel is requested. |
| */ |
| static struct mbox_chan *qmp_mbox_of_xlate(struct mbox_controller *mbox, |
| const struct of_phandle_args *spec) |
| { |
| struct qmp_device *mdev = dev_get_drvdata(mbox->dev); |
| unsigned int channel = spec->args[0]; |
| |
| if (!mdev || channel >= mbox->num_chans) |
| return ERR_PTR(-EINVAL); |
| |
| mutex_lock(&mdev->state_lock); |
| if (mdev->ch_in_use) { |
| pr_err("%s, mbox channel already in use %s\n", __func__, |
| mdev->name); |
| mutex_unlock(&mdev->state_lock); |
| return ERR_PTR(-EBUSY); |
| } |
| mdev->ch_in_use = true; |
| mutex_unlock(&mdev->state_lock); |
| return &mbox->chans[0]; |
| } |
| |
| /** |
| * parse_devicetree() - Parse the device tree information for QMP, map io |
| * memory and register for needed interrupts |
| * @pdev: platform device for this driver. |
| * @mdev: mailbox device to hold the device tree configuration. |
| * |
| * Return: 0 on succes or standard Linux error code. |
| */ |
| static int qmp_parse_devicetree(struct platform_device *pdev, |
| struct qmp_device *mdev) |
| { |
| struct device_node *node = pdev->dev.of_node; |
| char *key; |
| int rc; |
| const char *subsys_name; |
| u32 rx_irq_line, tx_irq_mask; |
| u32 desc_of = 0; |
| u32 mbox_of = 0; |
| u32 mbox_size = 0; |
| struct resource *msgram_r, *tx_irq_reg_r; |
| |
| key = "label"; |
| subsys_name = of_get_property(node, key, NULL); |
| if (!subsys_name) { |
| pr_err("%s: missing key %s\n", __func__, key); |
| return -ENODEV; |
| } |
| |
| key = "msgram"; |
| msgram_r = platform_get_resource_byname(pdev, IORESOURCE_MEM, key); |
| if (!msgram_r) { |
| pr_err("%s: missing key %s\n", __func__, key); |
| return -ENODEV; |
| } |
| |
| key = "irq-reg-base"; |
| tx_irq_reg_r = platform_get_resource_byname(pdev, IORESOURCE_MEM, key); |
| if (!tx_irq_reg_r) { |
| pr_err("%s: missing key %s\n", __func__, key); |
| return -ENODEV; |
| } |
| |
| key = "qcom,irq-mask"; |
| rc = of_property_read_u32(node, key, &tx_irq_mask); |
| if (rc) { |
| pr_err("%s: missing key %s\n", __func__, key); |
| return -ENODEV; |
| } |
| |
| key = "interrupts"; |
| rx_irq_line = irq_of_parse_and_map(node, 0); |
| if (!rx_irq_line) { |
| pr_err("%s: missing key %s\n", __func__, key); |
| return -ENODEV; |
| } |
| |
| key = "mbox-desc-offset"; |
| rc = of_property_read_u32(node, key, &desc_of); |
| if (rc) { |
| pr_err("%s: missing key %s\n", __func__, key); |
| return -ENODEV; |
| } |
| |
| key = "mbox-offset"; |
| rc = of_property_read_u32(node, key, &mbox_of); |
| if (!rc) |
| mdev->mcore_mbox_offset = mbox_of; |
| |
| key = "mbox-size"; |
| rc = of_property_read_u32(node, key, &mbox_size); |
| if (!rc) |
| mdev->mcore_mbox_size = mbox_size; |
| |
| mdev->name = subsys_name; |
| mdev->msgram = devm_ioremap_nocache(&pdev->dev, msgram_r->start, |
| resource_size(msgram_r)); |
| if (!mdev->msgram) |
| return -ENOMEM; |
| |
| mdev->desc = mdev->msgram + desc_of; |
| if (!mdev->desc) |
| return -ENOMEM; |
| |
| mdev->irq_mask = tx_irq_mask; |
| mdev->tx_irq_reg = devm_ioremap_nocache(&pdev->dev, tx_irq_reg_r->start, |
| resource_size(tx_irq_reg_r)); |
| if (!mdev->tx_irq_reg) |
| return -ENOMEM; |
| |
| mdev->rx_irq_line = rx_irq_line; |
| return 0; |
| } |
| |
| /** |
| * cleanup_workqueue() - Flush all work and stop the thread for this mailbox. |
| * @mdev: mailbox device to cleanup. |
| */ |
| static void cleanup_workqueue(struct qmp_device *mdev) |
| { |
| kthread_flush_worker(&mdev->kworker); |
| kthread_stop(mdev->task); |
| mdev->task = NULL; |
| } |
| |
| static struct mbox_chan_ops qmp_mbox_ops = { |
| .startup = qmp_startup, |
| .shutdown = qmp_shutdown, |
| .send_data = qmp_send_data, |
| .last_tx_done = qmp_last_tx_done, |
| }; |
| |
| static const struct of_device_id qmp_mbox_match_table[] = { |
| { .compatible = "qcom,qmp-mbox" }, |
| {}, |
| }; |
| |
| static int qmp_mbox_probe(struct platform_device *pdev) |
| { |
| struct device_node *node = pdev->dev.of_node; |
| struct mbox_controller *mbox; |
| struct qmp_device *mdev; |
| struct mbox_chan *chans; |
| int ret = 0; |
| |
| mdev = devm_kzalloc(&pdev->dev, sizeof(*mdev), GFP_KERNEL); |
| if (!mdev) |
| return -ENOMEM; |
| platform_set_drvdata(pdev, mdev); |
| |
| ret = qmp_parse_devicetree(pdev, mdev); |
| if (ret) |
| return ret; |
| |
| mbox = devm_kzalloc(&pdev->dev, sizeof(*mbox), GFP_KERNEL); |
| if (!mbox) |
| return -ENOMEM; |
| |
| chans = devm_kzalloc(&pdev->dev, sizeof(*chans) * QMP_NUM_CHANS, |
| GFP_KERNEL); |
| if (!chans) |
| return -ENOMEM; |
| |
| mbox->dev = &pdev->dev; |
| mbox->ops = &qmp_mbox_ops; |
| mbox->chans = chans; |
| mbox->chans[0].con_priv = mdev; |
| mbox->num_chans = QMP_NUM_CHANS; |
| mbox->txdone_irq = true; |
| mbox->txdone_poll = false; |
| mbox->of_xlate = qmp_mbox_of_xlate; |
| |
| mdev->dev = &pdev->dev; |
| mdev->mbox = mbox; |
| spin_lock_init(&mdev->tx_lock); |
| mutex_init(&mdev->state_lock); |
| mdev->local_state = LINK_DISCONNECTED; |
| kthread_init_work(&mdev->kwork, rx_worker); |
| kthread_init_worker(&mdev->kworker); |
| mdev->task = kthread_run(kthread_worker_fn, &mdev->kworker, "qmp_%s", |
| mdev->name); |
| init_completion(&mdev->link_complete); |
| init_completion(&mdev->ch_complete); |
| mdev->tx_sent = false; |
| mdev->ch_in_use = false; |
| INIT_DELAYED_WORK(&mdev->dwork, qmp_notify_timeout); |
| |
| ret = mbox_controller_register(mbox); |
| if (ret) { |
| cleanup_workqueue(mdev); |
| pr_err("%s: failed to register mbox controller %d\n", __func__, |
| ret); |
| return ret; |
| } |
| |
| ret = devm_request_irq(&pdev->dev, mdev->rx_irq_line, qmp_irq_handler, |
| IRQF_TRIGGER_RISING | IRQF_NO_SUSPEND | IRQF_SHARED, |
| node->name, mdev); |
| if (ret < 0) { |
| cleanup_workqueue(mdev); |
| mbox_controller_unregister(mdev->mbox); |
| pr_err("%s: request irq on %d failed: %d\n", __func__, |
| mdev->rx_irq_line, ret); |
| return ret; |
| } |
| ret = enable_irq_wake(mdev->rx_irq_line); |
| if (ret < 0) |
| pr_err("%s: enable_irq_wake on %d failed: %d\n", __func__, |
| mdev->rx_irq_line, ret); |
| |
| qmp_irq_handler(0, mdev); |
| return 0; |
| } |
| |
| static int qmp_mbox_remove(struct platform_device *pdev) |
| { |
| struct qmp_device *mdev = platform_get_drvdata(pdev); |
| |
| cleanup_workqueue(mdev); |
| mbox_controller_unregister(mdev->mbox); |
| return 0; |
| } |
| |
| static struct platform_driver qmp_mbox_driver = { |
| .probe = qmp_mbox_probe, |
| .remove = qmp_mbox_remove, |
| .driver = { |
| .name = "qmp_mbox", |
| .owner = THIS_MODULE, |
| .of_match_table = qmp_mbox_match_table, |
| }, |
| }; |
| |
| static int __init qmp_init(void) |
| { |
| int rc = 0; |
| |
| rc = platform_driver_register(&qmp_mbox_driver); |
| if (rc) |
| pr_err("%s: qmp_mbox_driver reg failed %d\n", __func__, rc); |
| return rc; |
| } |
| arch_initcall(qmp_init); |
| |
| MODULE_DESCRIPTION("MSM QTI Mailbox Protocol"); |
| MODULE_LICENSE("GPL v2"); |