diff options
Diffstat (limited to 'drivers/mailbox/ti-msgmgr.c')
| -rw-r--r-- | drivers/mailbox/ti-msgmgr.c | 223 |
1 files changed, 158 insertions, 65 deletions
diff --git a/drivers/mailbox/ti-msgmgr.c b/drivers/mailbox/ti-msgmgr.c index efb43b038596..8eb8df8d95a4 100644 --- a/drivers/mailbox/ti-msgmgr.c +++ b/drivers/mailbox/ti-msgmgr.c @@ -2,7 +2,7 @@ /* * Texas Instruments' Message Manager Driver * - * Copyright (C) 2015-2017 Texas Instruments Incorporated - https://www.ti.com/ + * Copyright (C) 2015-2022 Texas Instruments Incorporated - https://www.ti.com/ * Nishanth Menon */ @@ -11,13 +11,14 @@ #include <linux/device.h> #include <linux/interrupt.h> #include <linux/io.h> +#include <linux/iopoll.h> #include <linux/kernel.h> #include <linux/mailbox_controller.h> #include <linux/module.h> -#include <linux/of_device.h> #include <linux/of.h> #include <linux/of_irq.h> #include <linux/platform_device.h> +#include <linux/property.h> #include <linux/soc/ti/ti-msgmgr.h> #define Q_DATA_OFFSET(proxy, queue, reg) \ @@ -100,6 +101,7 @@ struct ti_msgmgr_desc { * @queue_ctrl: Queue Control register * @chan: Mailbox channel * @rx_buff: Receive buffer pointer allocated at probe, max_message_size + * @polled_rx_mode: Use polling for rx instead of interrupts */ struct ti_queue_inst { char name[30]; @@ -113,6 +115,7 @@ struct ti_queue_inst { void __iomem *queue_ctrl; struct mbox_chan *chan; u32 *rx_buff; + bool polled_rx_mode; }; /** @@ -190,6 +193,73 @@ static inline bool ti_msgmgr_queue_is_error(const struct ti_msgmgr_desc *d, return val ? true : false; } +static int ti_msgmgr_queue_rx_data(struct mbox_chan *chan, struct ti_queue_inst *qinst, + const struct ti_msgmgr_desc *desc) +{ + int num_words; + struct ti_msgmgr_message message; + void __iomem *data_reg; + u32 *word_data; + + /* + * I have no idea about the protocol being used to communicate with the + * remote producer - 0 could be valid data, so I wont make a judgement + * of how many bytes I should be reading. Let the client figure this + * out.. I just read the full message and pass it on.. + */ + message.len = desc->max_message_size; + message.buf = (u8 *)qinst->rx_buff; + + /* + * NOTE about register access involved here: + * the hardware block is implemented with 32bit access operations and no + * support for data splitting. We don't want the hardware to misbehave + * with sub 32bit access - For example: if the last register read is + * split into byte wise access, it can result in the queue getting + * stuck or indeterminate behavior. An out of order read operation may + * result in weird data results as well. + * Hence, we do not use memcpy_fromio or __ioread32_copy here, instead + * we depend on readl for the purpose. + * + * Also note that the final register read automatically marks the + * queue message as read. + */ + for (data_reg = qinst->queue_buff_start, word_data = qinst->rx_buff, + num_words = (desc->max_message_size / sizeof(u32)); + num_words; num_words--, data_reg += sizeof(u32), word_data++) + *word_data = readl(data_reg); + + /* + * Last register read automatically clears the IRQ if only 1 message + * is pending - so send the data up the stack.. + * NOTE: Client is expected to be as optimal as possible, since + * we invoke the handler in IRQ context. + */ + mbox_chan_received_data(chan, (void *)&message); + + return 0; +} + +static int ti_msgmgr_queue_rx_poll_timeout(struct mbox_chan *chan, int timeout_us) +{ + struct device *dev = chan->mbox->dev; + struct ti_msgmgr_inst *inst = dev_get_drvdata(dev); + struct ti_queue_inst *qinst = chan->con_priv; + const struct ti_msgmgr_desc *desc = inst->desc; + int msg_count; + int ret; + + ret = readl_poll_timeout_atomic(qinst->queue_state, msg_count, + (msg_count & desc->status_cnt_mask), + 10, timeout_us); + if (ret != 0) + return ret; + + ti_msgmgr_queue_rx_data(chan, qinst, desc); + + return 0; +} + /** * ti_msgmgr_queue_rx_interrupt() - Interrupt handler for receive Queue * @irq: Interrupt number @@ -206,10 +276,7 @@ static irqreturn_t ti_msgmgr_queue_rx_interrupt(int irq, void *p) struct ti_msgmgr_inst *inst = dev_get_drvdata(dev); struct ti_queue_inst *qinst = chan->con_priv; const struct ti_msgmgr_desc *desc; - int msg_count, num_words; - struct ti_msgmgr_message message; - void __iomem *data_reg; - u32 *word_data; + int msg_count; if (WARN_ON(!inst)) { dev_err(dev, "no platform drv data??\n"); @@ -237,41 +304,7 @@ static irqreturn_t ti_msgmgr_queue_rx_interrupt(int irq, void *p) return IRQ_NONE; } - /* - * I have no idea about the protocol being used to communicate with the - * remote producer - 0 could be valid data, so I won't make a judgement - * of how many bytes I should be reading. Let the client figure this - * out.. I just read the full message and pass it on.. - */ - message.len = desc->max_message_size; - message.buf = (u8 *)qinst->rx_buff; - - /* - * NOTE about register access involved here: - * the hardware block is implemented with 32bit access operations and no - * support for data splitting. We don't want the hardware to misbehave - * with sub 32bit access - For example: if the last register read is - * split into byte wise access, it can result in the queue getting - * stuck or indeterminate behavior. An out of order read operation may - * result in weird data results as well. - * Hence, we do not use memcpy_fromio or __ioread32_copy here, instead - * we depend on readl for the purpose. - * - * Also note that the final register read automatically marks the - * queue message as read. - */ - for (data_reg = qinst->queue_buff_start, word_data = qinst->rx_buff, - num_words = (desc->max_message_size / sizeof(u32)); - num_words; num_words--, data_reg += sizeof(u32), word_data++) - *word_data = readl(data_reg); - - /* - * Last register read automatically clears the IRQ if only 1 message - * is pending - so send the data up the stack.. - * NOTE: Client is expected to be as optimal as possible, since - * we invoke the handler in IRQ context. - */ - mbox_chan_received_data(chan, (void *)&message); + ti_msgmgr_queue_rx_data(chan, qinst, desc); return IRQ_HANDLED; } @@ -336,6 +369,17 @@ static bool ti_msgmgr_last_tx_done(struct mbox_chan *chan) return msg_count ? false : true; } +static bool ti_msgmgr_chan_has_polled_queue_rx(struct mbox_chan *chan) +{ + struct ti_queue_inst *qinst; + + if (!chan) + return false; + + qinst = chan->con_priv; + return qinst->polled_rx_mode; +} + /** * ti_msgmgr_send_data() - Send data * @chan: Channel Pointer @@ -353,6 +397,7 @@ static int ti_msgmgr_send_data(struct mbox_chan *chan, void *data) struct ti_msgmgr_message *message = data; void __iomem *data_reg; u32 *word_data; + int ret = 0; if (WARN_ON(!inst)) { dev_err(dev, "no platform drv data??\n"); @@ -385,16 +430,27 @@ static int ti_msgmgr_send_data(struct mbox_chan *chan, void *data) /* Ensure all unused data is 0 */ data_trail &= 0xFFFFFFFF >> (8 * (sizeof(u32) - trail_bytes)); writel(data_trail, data_reg); - data_reg++; + data_reg += sizeof(u32); } + /* * 'data_reg' indicates next register to write. If we did not already * write on tx complete reg(last reg), we must do so for transmit + * In addition, we also need to make sure all intermediate data + * registers(if any required), are reset to 0 for TISCI backward + * compatibility to be maintained. */ - if (data_reg <= qinst->queue_buff_end) - writel(0, qinst->queue_buff_end); + while (data_reg <= qinst->queue_buff_end) { + writel(0, data_reg); + data_reg += sizeof(u32); + } - return 0; + /* If we are in polled mode, wait for a response before proceeding */ + if (ti_msgmgr_chan_has_polled_queue_rx(message->chan_rx)) + ret = ti_msgmgr_queue_rx_poll_timeout(message->chan_rx, + message->timeout_rx_ms * 1000); + + return ret; } /** @@ -642,6 +698,54 @@ static int ti_msgmgr_queue_setup(int idx, struct device *dev, return 0; } +static int ti_msgmgr_queue_rx_set_polled_mode(struct ti_queue_inst *qinst, bool enable) +{ + if (enable) { + disable_irq(qinst->irq); + qinst->polled_rx_mode = true; + } else { + enable_irq(qinst->irq); + qinst->polled_rx_mode = false; + } + + return 0; +} + +static int ti_msgmgr_suspend(struct device *dev) +{ + struct ti_msgmgr_inst *inst = dev_get_drvdata(dev); + struct ti_queue_inst *qinst; + int i; + + /* + * We must switch operation to polled mode now as drivers and the genpd + * layer may make late TI SCI calls to change clock and device states + * from the noirq phase of suspend. + */ + for (qinst = inst->qinsts, i = 0; i < inst->num_valid_queues; qinst++, i++) { + if (!qinst->is_tx) + ti_msgmgr_queue_rx_set_polled_mode(qinst, true); + } + + return 0; +} + +static int ti_msgmgr_resume(struct device *dev) +{ + struct ti_msgmgr_inst *inst = dev_get_drvdata(dev); + struct ti_queue_inst *qinst; + int i; + + for (qinst = inst->qinsts, i = 0; i < inst->num_valid_queues; qinst++, i++) { + if (!qinst->is_tx) + ti_msgmgr_queue_rx_set_polled_mode(qinst, false); + } + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(ti_msgmgr_pm_ops, ti_msgmgr_suspend, ti_msgmgr_resume); + /* Queue operations */ static const struct mbox_chan_ops ti_msgmgr_chan_ops = { .startup = ti_msgmgr_queue_startup, @@ -706,9 +810,7 @@ MODULE_DEVICE_TABLE(of, ti_msgmgr_of_match); static int ti_msgmgr_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - const struct of_device_id *of_id; struct device_node *np; - struct resource *res; const struct ti_msgmgr_desc *desc; struct ti_msgmgr_inst *inst; struct ti_queue_inst *qinst; @@ -725,36 +827,26 @@ static int ti_msgmgr_probe(struct platform_device *pdev) } np = dev->of_node; - of_id = of_match_device(ti_msgmgr_of_match, dev); - if (!of_id) { - dev_err(dev, "OF data missing\n"); - return -EINVAL; - } - desc = of_id->data; - inst = devm_kzalloc(dev, sizeof(*inst), GFP_KERNEL); if (!inst) return -ENOMEM; inst->dev = dev; - inst->desc = desc; + inst->desc = desc = device_get_match_data(dev); - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, - desc->data_region_name); - inst->queue_proxy_region = devm_ioremap_resource(dev, res); + inst->queue_proxy_region = + devm_platform_ioremap_resource_byname(pdev, desc->data_region_name); if (IS_ERR(inst->queue_proxy_region)) return PTR_ERR(inst->queue_proxy_region); - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, - desc->status_region_name); - inst->queue_state_debug_region = devm_ioremap_resource(dev, res); + inst->queue_state_debug_region = + devm_platform_ioremap_resource_byname(pdev, desc->status_region_name); if (IS_ERR(inst->queue_state_debug_region)) return PTR_ERR(inst->queue_state_debug_region); if (desc->is_sproxy) { - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, - desc->ctrl_region_name); - inst->queue_ctrl_region = devm_ioremap_resource(dev, res); + inst->queue_ctrl_region = + devm_platform_ioremap_resource_byname(pdev, desc->ctrl_region_name); if (IS_ERR(inst->queue_ctrl_region)) return PTR_ERR(inst->queue_ctrl_region); } @@ -828,7 +920,8 @@ static struct platform_driver ti_msgmgr_driver = { .probe = ti_msgmgr_probe, .driver = { .name = "ti-msgmgr", - .of_match_table = of_match_ptr(ti_msgmgr_of_match), + .of_match_table = ti_msgmgr_of_match, + .pm = &ti_msgmgr_pm_ops, }, }; module_platform_driver(ti_msgmgr_driver); |
