Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8a43ab4a authored by Gaurav Kohli's avatar Gaurav Kohli
Browse files

soc: qcom: Add support for RIMPS mailbox controller



Add support for RIMPS mailbox controller, this driver
register as mailbox interface to support doorbell
between HLOS and RIMPS subsystem.

Change-Id: Id63eabd1692f7db925dbba37440fae5f193447fd
Signed-off-by: default avatarGaurav Kohli <gkohli@codeaurora.org>
parent 38b1718d
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -148,6 +148,16 @@ config QCOM_IPCC
         Say Y here to compile the driver as a part of kernel or M to compile
         as a module.

config QCOM_RIMPS
	tristate "Qualcomm Technologies, Inc. Rimps driver"
	depends on MAILBOX
	help
	 Qualcomm Technologies, Inc. RIMPS driver for MSM devices. This driver
	 acts as a mailbox controller to do doorbell between Apss and Rimps
	 subsystem. Say yes here to enable rx and tx channel between both
	 the subsystems.
	 If unsure, say n.

config QCOM_LLCC
	tristate "Qualcomm Technologies, Inc. LLCC driver"
	depends on ARCH_QCOM || COMPILE_TEST
+1 −0
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ obj-$(CONFIG_QCOM_SOCINFO) += socinfo.o
obj-$(CONFIG_QCOM_PROXY_OF_CONSUMER) += qcom_proxy_of_consumer.o
obj-$(CONFIG_QCOM_WCNSS_CTRL) += wcnss_ctrl.o
obj-$(CONFIG_QCOM_IPCC) += qcom_ipcc.o
obj-$(CONFIG_QCOM_RIMPS) += qcom_rimps.o
obj-$(CONFIG_QCOM_RUN_QUEUE_STATS) += rq_stats.o
obj-$(CONFIG_QCOM_APR) += apr.o
obj-$(CONFIG_QCOM_LLCC) += llcc-slice.o
+244 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2020, The Linux Foundation. All rights reserved.
 */

#include <linux/module.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/irqdomain.h>
#include <linux/platform_device.h>
#include <linux/mailbox_controller.h>

/* RIMPS Register offsets */
#define RIMPS_IPC_CHAN_SUPPORTED	2
#define RIMPS_SEND_IRQ_OFFSET		0xC
#define RIMPS_SEND_IRQ_VAL		BIT(28)
#define RIMPS_CLEAR_IRQ_OFFSET		0x308
#define RIMPS_STATUS_IRQ_OFFSET		0x30C
#define RIMPS_CLEAR_IRQ_VAL		BIT(3)
#define RIMPS_STATUS_IRQ_VAL		BIT(3)
#define RIMPS_CLOCK_DOMAIN_OFFSET	0x1000


/**
 * struct rimps_ipc     ipc per channel
 * @mbox:		mailbox-controller interface
 * @chans:		The mailbox clients' channel array
 * @dev:		Device associated with this instance
 * @irq:		Rimps to HLOS irq
 */
struct qcom_rimps_ipc {
	struct mbox_controller mbox;
	struct mbox_chan chans[RIMPS_IPC_CHAN_SUPPORTED];
	void __iomem *tx_irq_base;
	void __iomem *rx_irq_base;
	struct device *dev;
	int irq;
	int num_chan;
};

static irqreturn_t qcom_rimps_rx_interrupt(int irq, void *p)
{
	struct qcom_rimps_ipc *rimps_ipc;
	u32 val;
	int i;

	rimps_ipc = p;

	for (i = 0; i < rimps_ipc->num_chan; i++) {

		val = readl_no_log(rimps_ipc->rx_irq_base +
		RIMPS_STATUS_IRQ_OFFSET + (i * RIMPS_CLOCK_DOMAIN_OFFSET));
		if (val & RIMPS_STATUS_IRQ_VAL) {

			val = RIMPS_CLEAR_IRQ_VAL;
			writel_no_log(val, rimps_ipc->rx_irq_base +
			RIMPS_CLEAR_IRQ_OFFSET +
				(i * RIMPS_CLOCK_DOMAIN_OFFSET));
			/* Make sure register write is complete before proceeding */
			mb();

			if (rimps_ipc->chans[i].con_priv)
				mbox_chan_received_data(&rimps_ipc->chans[i]
							, NULL);
		}
	}

	return IRQ_HANDLED;
}

static void qcom_rimps_mbox_shutdown(struct mbox_chan *chan)
{
	chan->con_priv = NULL;
}

static int qcom_rimps_mbox_send_data(struct mbox_chan *chan, void *data)
{
	struct qcom_rimps_ipc *rimps_ipc = container_of(chan->mbox,
						  struct qcom_rimps_ipc, mbox);

	writel_no_log(RIMPS_SEND_IRQ_VAL,
			rimps_ipc->tx_irq_base + RIMPS_SEND_IRQ_OFFSET);
	return 0;
}

static struct mbox_chan *qcom_rimps_mbox_xlate(struct mbox_controller *mbox,
			const struct of_phandle_args *sp)
{
	struct qcom_rimps_ipc *rimps_ipc = container_of(mbox,
						  struct qcom_rimps_ipc, mbox);
	unsigned long ind = sp->args[0];

	if (sp->args_count != 1)
		return ERR_PTR(-EINVAL);

	if (ind >= mbox->num_chans)
		return ERR_PTR(-EINVAL);

	if (mbox->chans[ind].con_priv)
		return ERR_PTR(-EBUSY);

	mbox->chans[ind].con_priv = rimps_ipc;
	return &mbox->chans[ind];
}

static const struct mbox_chan_ops rimps_mbox_chan_ops = {
	.send_data = qcom_rimps_mbox_send_data,
	.shutdown = qcom_rimps_mbox_shutdown
};

static int qcom_rimps_ipc_setup_mbox(struct qcom_rimps_ipc *rimps_ipc)
{
	struct mbox_controller *mbox;
	struct device *dev = rimps_ipc->dev;
	unsigned long i;

	/* Initialize channel identifiers */
	for (i = 0; i < ARRAY_SIZE(rimps_ipc->chans); i++)
		rimps_ipc->chans[i].con_priv = NULL;

	mbox = &rimps_ipc->mbox;
	mbox->dev = dev;
	mbox->num_chans = rimps_ipc->num_chan;
	mbox->chans = rimps_ipc->chans;
	mbox->ops = &rimps_mbox_chan_ops;
	mbox->of_xlate = qcom_rimps_mbox_xlate;
	mbox->txdone_irq = false;
	mbox->txdone_poll = false;

	return mbox_controller_register(mbox);
}

static int qcom_rimps_probe(struct platform_device *pdev)
{
	struct qcom_rimps_ipc *rimps_ipc;
	struct resource *res;
	int ret;

	rimps_ipc = devm_kzalloc(&pdev->dev, sizeof(*rimps_ipc), GFP_KERNEL);
	if (!rimps_ipc)
		return -ENOMEM;

	rimps_ipc->dev = &pdev->dev;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		dev_err(&pdev->dev, "Failed to get the device base address\n");
		return -ENODEV;
	}

	rimps_ipc->tx_irq_base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
	if (!rimps_ipc->tx_irq_base) {
		dev_err(&pdev->dev, "Failed to ioremap the rimps tx irq addr\n");
		return -ENOMEM;
	}

	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
	if (!res) {
		dev_err(&pdev->dev, "Failed to get the device base address\n");
		return -ENODEV;
	}

	rimps_ipc->rx_irq_base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
	if (!rimps_ipc->rx_irq_base) {
		dev_err(&pdev->dev, "Failed to ioremap the rimps rx irq addr\n");
		return -ENOMEM;
	}

	rimps_ipc->irq = platform_get_irq(pdev, 0);
	if (rimps_ipc->irq < 0) {
		dev_err(&pdev->dev, "Failed to get the IRQ\n");
		return rimps_ipc->irq;
	}

	rimps_ipc->num_chan = RIMPS_IPC_CHAN_SUPPORTED;
	ret = qcom_rimps_ipc_setup_mbox(rimps_ipc);
	if (ret) {
		dev_err(&pdev->dev, "Failed to create mailbox\n");
		return ret;
	}

	ret = devm_request_irq(&pdev->dev, rimps_ipc->irq,
	qcom_rimps_rx_interrupt, IRQF_TRIGGER_HIGH | IRQF_NO_SUSPEND, "qcom_rimps", rimps_ipc);

	if (ret < 0) {
		dev_err(&pdev->dev, "Failed to register the irq: %d\n", ret);
		goto err_mbox;
	}
	platform_set_drvdata(pdev, rimps_ipc);

	return 0;

err_mbox:
	mbox_controller_unregister(&rimps_ipc->mbox);
	return ret;
}

static int qcom_rimps_remove(struct platform_device *pdev)
{
	struct qcom_rimps_ipc *rimps_ipc = platform_get_drvdata(pdev);

	mbox_controller_unregister(&rimps_ipc->mbox);

	return 0;
}

static const struct of_device_id qcom_rimps_of_match[] = {
	{ .compatible = "qcom,rimps"},
	{}
};
MODULE_DEVICE_TABLE(of, qcom_rimps_of_match);

static struct platform_driver qcom_rimps_driver = {
	.probe = qcom_rimps_probe,
	.remove = qcom_rimps_remove,
	.driver = {
		.name = "qcom_rimps",
		.of_match_table = qcom_rimps_of_match,
	},
};

static int __init qcom_rimps_init(void)
{
	int ret;

	ret = platform_driver_register(&qcom_rimps_driver);
	if (ret)
		pr_err("%s: qcom_rimps register failed %d\n", __func__, ret);
	return ret;
}
#if IS_MODULE(CONFIG_QCOM_RIMPS)
module_init(qcom_rimps_init);
#else
arch_initcall(qcom_rimps_init);
#endif

static __exit void qcom_rimps_exit(void)
{
	platform_driver_unregister(&qcom_rimps_driver);
}
module_exit(qcom_rimps_exit);

MODULE_DESCRIPTION("QTI RIMPS Driver");
MODULE_LICENSE("GPL v2");