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

Commit 65115211 authored by Raghavendra Rao Ananta's avatar Raghavendra Rao Ananta
Browse files

soc: qcom: ipcc: Add support for IPCC controller



Add support for the Inter-Processor Communication Controller (IPCC)
driver that coordinates the interrupts (inbound & outbound) for
Multiprocessor (MPROC), COMPUTE-Level0 (COMPUTE-L0) & COMPUTE-Level1
(COMPUTE-L1) protocols for the Application Processor Subsystem (APSS).

As a part of its inbound communication, the driver would be responsible
for forwarding the interrupts from various clients, such as Modem, DSPs,
PCIe, and so on, to the entities running on the APPS. As a result, it's
implemented as an irq_chip driver.

On the other hand, the driver also registers as a mailbox controller
that provides a mailbox interface to interrupt other clients connected
to the IPCC, acting as an outbound communication channel.

Change-Id: I0abdefaa3804a00f35a536b3291eac514cfe9ab2
Signed-off-by: default avatarRaghavendra Rao Ananta <rananta@codeaurora.org>
parent c85b96cf
Loading
Loading
Loading
Loading
+75 −0
Original line number Diff line number Diff line
Qualcomm Technologies, Inc. Inter-Processor Communication Controller binding

The Inter-Processor Communication Controller (IPCC) is a centralized hardware
to route the interrupts across various subsystems. It involves a three-level
addressing scheme: protocol, client and signal. For example, consider an entity
on the Application Processor Subsystem (APSS) that wants to listen to Modem's
interrupts via Shared Memory Point to Point (SMP2P) interface. In such a case,
the client would be Modem (client-id: 2) and the signal would be SMP2P
(signal-id: 2). The SMP2P itself falls under the Multiprocessor (MPROC) protocol
(protocol-id: 0). Please visit include/dt-bindings/soc/qcom/qcom,ipcc.h for the
list of IDs.

Each protocol has a dedicated interrupt line, and as a result, each protocol is
exposed as a separate interrupt controller. One of the duties of this interrupt
controller driver would be to forward the interrupt to the correct entity
on the APPS. The children inheriting the interrupt-controller would be
mentioning the client-id and signal-id that it's interested in.

On the other hand, sending an interrupt to a subsystem is done through the
mailbox interface, which again requires client-id and signal-id.

- compatible:
  Usage: required
  Value type: <string>
  Definition: Must be "qcom,kona-ipcc"

- reg:
  Usage: required
  Value type: <prop-encoded-array>
  Definition: One entry specifying the base address and size corresponding to
	      the protocol frame

- interrupts:
  Usage: required
  Value type: <prop-encoded-array>
  Definition: One entry specifying the protocol's interrupt

- interrupt-controller:
  Usage: required
  Value type: <empty>
  Definition: Specifies that the device acts as an interrupt controller

- #interrupt-cells:
  Usage: required
  Value type: <u32>
  Definition: must be 3 - denoting client-id, signal-id and interrupt type

- mbox-cells:
  Usage: required
  Value type: <u32>
  Definition: must be 2 - denoting client-id and signal-id

Example
-------
	#include <dt-bindings/soc/qcom,ipcc.h>

	ipcc_mproc: qcom,ipcc@408000 {
		compatible = "qcom,kona-ipcc";
		reg = <0x408000 0x1000>;
		interrupts = <GIC_SPI 229 IRQ_TYPE_LEVEL_HIGH>,
		interrupt-controller;
		#interrupt-cells = <3>;
		#mbox-cells = <2>;
	};

Client-example
--------------
	qcom,smp2p-modem@1799000c {
		compatible = "qcom,smp2p";
		interrupts-extended = <&ipcc_mproc IPCC_CLIENT_MPSS
				IPCC_MPROC_SIGNAL_SMP2P IRQ_TYPE_EDGE_RISING>;
		mboxes = <&ipcc_mproc IPCC_CLIENT_MPSS IPCC_MPROC_SIGNAL_SMP2P>;

		/* Other SMP2P fields */
	};
+11 −0
Original line number Diff line number Diff line
@@ -49,6 +49,17 @@ config QCOM_GSBI
          functions for connecting the underlying serial UART, SPI, and I2C
          devices to the output pins.

config QCOM_IPCC
	tristate "Qualcomm Technologies, Inc. IPCC driver"
	depends on MAILBOX
	help
	  Qualcomm Technologies, Inc. IPCC driver for MSM devices. The drivers
	  acts as an interrupt controller for the clients interested in
	  talking to the IPCC (inbound-communication). On the other hand, the
	  driver also provides a mailbox channel for outbound-communications.
	  Say Y here to compile the driver as a part of kernel or M to compile
	  as a module.

config QCOM_LLCC
	tristate "Qualcomm Technologies, Inc. LLCC driver"
	depends on ARCH_QCOM
+1 −0
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@ obj-$(CONFIG_QCOM_SMEM_STATE) += smem_state.o
obj-$(CONFIG_QCOM_SMP2P)	+= smp2p.o
obj-$(CONFIG_QCOM_SMSM)	+= smsm.o
obj-$(CONFIG_QCOM_WCNSS_CTRL) += wcnss_ctrl.o
obj-$(CONFIG_QCOM_IPCC) += qcom_ipcc.o
obj-$(CONFIG_QCOM_LLCC) += llcc-slice.o
obj-$(CONFIG_QCOM_KONA_LLCC) += llcc-kona.o
obj-$(CONFIG_QCOM_APR) += apr.o
+402 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2018, 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>
#include <dt-bindings/soc/qcom,ipcc.h>

/* IPCC Register offsets */
#define IPCC_REG_SEND_ID		0x0C
#define IPCC_REG_RECV_ID		0x10
#define IPCC_REG_RECV_SIGNAL_ENABLE	0x14
#define IPCC_REG_RECV_SIGNAL_DISABLE	0x18
#define IPCC_REG_RECV_SIGNAL_CLEAR	0x1c
#define IPCC_REG_CLIENT_CLEAR		0x38

#define IPCC_SIGNAL_ID_MASK		GENMASK(15, 0)
#define IPCC_CLIENT_ID_MASK		GENMASK(31, 16)
#define IPCC_CLIENT_ID_SHIFT		16

#define IPCC_NO_PENDING_IRQ		(~(u32)0)

/**
 * struct ipcc_protocol_data - Per-protocol data
 * @irq_domain:		irq_domain associated with this protocol-id
 * @mbox:		mailbox-controller interface
 * @chans:		The mailbox clients' channel array (created dynamically)
 * @base:		Base address of the IPCC frame associated to APPS
 * @dev:		Device associated with this instance
 */
struct ipcc_protocol_data {
	struct irq_domain *irq_domain;
	struct mbox_controller mbox;
	struct mbox_chan *chans;
	void __iomem *base;
	struct device *dev;
};

/**
 * struct ipcc_mbox_chan - Per-mailbox-channel data. Associated to each channel
 *				requested by the clients
 * @client_id:	The client-id to which the interrupt has to be triggered
 * @signalid:	The signal-id to which the interrupt has to be triggered
 * @chan:	Points to this channel's array element for this protocol's
 *		ipcc_protocol_data->chans[]
 * @proto_data: The pointer to per-protocol data associated to this channel
 */
struct ipcc_mbox_chan {
	u16 client_id;
	u16 signal_id;
	struct mbox_chan *chan;
	struct ipcc_protocol_data *proto_data;
};

static inline u32 qcom_ipcc_get_packed_id(u16 client_id, u16 signal_id)
{
	return (client_id << IPCC_CLIENT_ID_SHIFT) | signal_id;
}

static inline u16 qcom_ipcc_get_client_id(u32 packed_id)
{
	return packed_id >> IPCC_CLIENT_ID_SHIFT;
}

static inline u16 qcom_ipcc_get_signal_id(u32 packed_id)
{
	return packed_id & IPCC_SIGNAL_ID_MASK;
}

static irqreturn_t qcom_ipcc_irq_fn(int irq, void *data)
{
	int virq;
	u32 packed_id;
	struct ipcc_protocol_data *proto_data = data;

	for (;;) {
		packed_id = readl_no_log(proto_data->base + IPCC_REG_RECV_ID);
		if (packed_id == IPCC_NO_PENDING_IRQ)
			break;

		virq = irq_find_mapping(proto_data->irq_domain, packed_id);

		dev_dbg(proto_data->dev,
			"IRQ for client_id: %u; signal_id: %u; virq: %d\n",
			qcom_ipcc_get_client_id(packed_id),
			qcom_ipcc_get_signal_id(packed_id), virq);

		generic_handle_irq(virq);

		writel_no_log(packed_id,
				proto_data->base + IPCC_REG_RECV_SIGNAL_CLEAR);
	}

	return IRQ_HANDLED;
}

static void qcom_ipcc_mask_irq(struct irq_data *irqd)
{
	irq_hw_number_t hwirq = irqd_to_hwirq(irqd);
	u16 sender_client_id = qcom_ipcc_get_client_id(hwirq);
	u16 sender_signal_id = qcom_ipcc_get_signal_id(hwirq);
	struct ipcc_protocol_data *proto_data;

	proto_data = irq_data_get_irq_chip_data(irqd);

	dev_dbg(proto_data->dev,
		"%s: Disabling interrupts for: client_id: %u; signal_id: %u\n",
		__func__, sender_client_id, sender_signal_id);

	writel_no_log(hwirq, proto_data->base + IPCC_REG_RECV_SIGNAL_DISABLE);
}

static void qcom_ipcc_unmask_irq(struct irq_data *irqd)
{
	irq_hw_number_t hwirq = irqd_to_hwirq(irqd);
	u16 sender_client_id = qcom_ipcc_get_client_id(hwirq);
	u16 sender_signal_id = qcom_ipcc_get_signal_id(hwirq);
	struct ipcc_protocol_data *proto_data;

	proto_data = irq_data_get_irq_chip_data(irqd);

	dev_dbg(proto_data->dev,
		"%s: Enabling interrupts for: client_id: %u; signal_id: %u\n",
		__func__, sender_client_id, sender_signal_id);

	writel_no_log(hwirq, proto_data->base + IPCC_REG_RECV_SIGNAL_ENABLE);
}

static struct irq_chip qcom_ipcc_irq_chip = {
	.name = "ipcc",
	.irq_mask = qcom_ipcc_mask_irq,
	.irq_unmask = qcom_ipcc_unmask_irq
};

static int qcom_ipcc_domain_map(struct irq_domain *d, unsigned int irq,
				irq_hw_number_t hw)
{
	struct ipcc_protocol_data *proto_data = d->host_data;

	irq_set_chip_and_handler(irq, &qcom_ipcc_irq_chip, handle_level_irq);
	irq_set_chip_data(irq, proto_data);
	irq_set_noprobe(irq);

	return 0;
}

static int
qcom_ipcc_domain_xlate(struct irq_domain *d, struct device_node *node,
			const u32 *intspec, unsigned int intsize,
			unsigned long *out_hwirq, unsigned int *out_type)
{
	struct ipcc_protocol_data *proto_data = d->host_data;
	struct device *dev = proto_data->dev;

	if (intsize != 3)
		return -EINVAL;

	*out_hwirq = qcom_ipcc_get_packed_id(intspec[0], intspec[1]);
	*out_type = intspec[2] & IRQ_TYPE_SENSE_MASK;

	dev_dbg(dev, "%s: hwirq: 0x%x\n", __func__, *out_hwirq);

	return 0;
}

static const struct irq_domain_ops qcom_ipcc_irq_ops = {
	.map = qcom_ipcc_domain_map,
	.xlate = qcom_ipcc_domain_xlate,
};

static int qcom_ipcc_mbox_send_data(struct mbox_chan *chan, void *data)
{
	u32 packed_id;
	struct ipcc_mbox_chan *ipcc_mbox_chan = chan->con_priv;
	struct ipcc_protocol_data *proto_data = ipcc_mbox_chan->proto_data;

	dev_dbg(proto_data->dev,
		"%s: Generating IRQ for client_id: %u; signal_id: %u\n",
		__func__, ipcc_mbox_chan->client_id, ipcc_mbox_chan->signal_id);

	packed_id = qcom_ipcc_get_packed_id(ipcc_mbox_chan->client_id,
						ipcc_mbox_chan->signal_id);
	writel_no_log(packed_id, proto_data->base + IPCC_REG_SEND_ID);

	return 0;
}

static void qcom_ipcc_mbox_shutdown(struct mbox_chan *chan)
{
	struct ipcc_mbox_chan *ipcc_mbox_chan = chan->con_priv;

	chan->con_priv = NULL;
	kfree(ipcc_mbox_chan);
}

static struct mbox_chan *qcom_ipcc_mbox_xlate(struct mbox_controller *mbox,
					const struct of_phandle_args *ph)
{
	int chan_id;
	struct device *dev;
	struct ipcc_protocol_data *proto_data;
	struct ipcc_mbox_chan *ipcc_mbox_chan;

	proto_data = container_of(mbox, struct ipcc_protocol_data, mbox);
	if (WARN_ON(!proto_data))
		return ERR_PTR(-EINVAL);

	dev = proto_data->dev;

	if (ph->args_count != 2)
		return ERR_PTR(-EINVAL);

	for (chan_id = 0; chan_id < mbox->num_chans; chan_id++) {
		ipcc_mbox_chan = proto_data->chans[chan_id].con_priv;

		if (!ipcc_mbox_chan)
			break;
		else if (ipcc_mbox_chan->client_id == ph->args[0] &&
				ipcc_mbox_chan->signal_id == ph->args[1])
			return ERR_PTR(-EBUSY);
	}

	if (chan_id >= mbox->num_chans)
		return ERR_PTR(-EBUSY);

	ipcc_mbox_chan = kzalloc(sizeof(*ipcc_mbox_chan), GFP_KERNEL);
	if (!ipcc_mbox_chan)
		return ERR_PTR(-ENOMEM);

	ipcc_mbox_chan->client_id = ph->args[0];
	ipcc_mbox_chan->signal_id = ph->args[1];
	ipcc_mbox_chan->chan = &proto_data->chans[chan_id];
	ipcc_mbox_chan->proto_data = proto_data;
	ipcc_mbox_chan->chan->con_priv = ipcc_mbox_chan;

	dev_dbg(dev,
		"New mailbox channel: %u for client_id: %u; signal_id: %u\n",
		chan_id, ipcc_mbox_chan->client_id,
		ipcc_mbox_chan->signal_id);

	return ipcc_mbox_chan->chan;
}

static const struct mbox_chan_ops ipcc_mbox_chan_ops = {
	.send_data = qcom_ipcc_mbox_send_data,
	.shutdown = qcom_ipcc_mbox_shutdown
};

static int qcom_ipcc_setup_mbox(struct ipcc_protocol_data *proto_data,
				struct device_node *controller_dn)
{
	int i, j, ret;
	int num_chans = 0;
	struct mbox_controller *mbox;
	struct device_node *client_dn;
	struct of_phandle_args curr_ph;
	struct device *dev = proto_data->dev;

	/*
	 * Find out the number of clients interested in this mailbox
	 * and create channels accordingly.
	 */
	for_each_node_with_property(client_dn, "mboxes") {
		if (!of_device_is_available(client_dn))
			continue;
		i = of_count_phandle_with_args(client_dn,
						"mboxes", "#mbox-cells");
		for (j = 0; j < i; j++) {
			ret = of_parse_phandle_with_args(client_dn, "mboxes",
						"#mbox-cells", j, &curr_ph);
			of_node_put(curr_ph.np);
			if (!ret && curr_ph.np == controller_dn) {
				num_chans++;
				break;
			}
		}
	}

	/* If no clients are found, skip registering as a mbox controller */
	if (!num_chans)
		return 0;

	proto_data->chans = devm_kcalloc(dev, num_chans,
					sizeof(struct mbox_chan), GFP_KERNEL);
	if (!proto_data->chans)
		return -ENOMEM;

	mbox = &proto_data->mbox;
	mbox->dev = dev;
	mbox->num_chans = num_chans;
	mbox->chans = proto_data->chans;
	mbox->ops = &ipcc_mbox_chan_ops;
	mbox->of_xlate = qcom_ipcc_mbox_xlate;
	mbox->txdone_irq = false;
	mbox->txdone_poll = false;

	return mbox_controller_register(mbox);
}

static int qcom_ipcc_probe(struct platform_device *pdev)
{
	char *name;
	int ret, irq;
	static int id;
	struct resource *res;
	struct ipcc_protocol_data *proto_data;

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

	proto_data->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;
	}

	proto_data->base = devm_ioremap_resource(&pdev->dev, res);
	if (IS_ERR(proto_data->base)) {
		dev_err(&pdev->dev, "Failed to ioremap the ipcc base addr\n");
		return PTR_ERR(proto_data->base);
	}

	name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "ipcc_%d", ++id);
	if (!name)
		return -ENOMEM;

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

	/* Perform a SW reset on this client's protocol state */
	writel_relaxed(0x1, proto_data->base + IPCC_REG_CLIENT_CLEAR);

	proto_data->irq_domain = irq_domain_add_tree(pdev->dev.of_node,
						&qcom_ipcc_irq_ops,
						proto_data);
	if (!proto_data->irq_domain) {
		dev_err(&pdev->dev, "Failed to add irq_domain\n");
		return -ENOMEM;
	}

	ret = qcom_ipcc_setup_mbox(proto_data, pdev->dev.of_node);
	if (ret) {
		dev_err(&pdev->dev, "Failed to create mailbox\n");
		goto err_mbox;
	}

	ret = devm_request_irq(&pdev->dev, irq, qcom_ipcc_irq_fn,
				IRQF_TRIGGER_HIGH, name, proto_data);
	if (ret < 0) {
		dev_err(&pdev->dev, "Failed to register the irq: %d\n", ret);
		goto err_req_irq;
	}

	platform_set_drvdata(pdev, proto_data);

	return 0;

err_req_irq:
	mbox_controller_unregister(&proto_data->mbox);
err_mbox:
	irq_domain_remove(proto_data->irq_domain);
	return ret;
}

static int qcom_ipcc_remove(struct platform_device *pdev)
{
	struct ipcc_protocol_data *proto_data = platform_get_drvdata(pdev);

	mbox_controller_unregister(&proto_data->mbox);
	irq_domain_remove(proto_data->irq_domain);

	return 0;
}

static const struct of_device_id qcom_ipcc_of_match[] = {
	{ .compatible = "qcom,kona-ipcc"},
	{}
};
MODULE_DEVICE_TABLE(of, qcom_ipcc_of_match);

static struct platform_driver qcom_ipcc_driver = {
	.probe = qcom_ipcc_probe,
	.remove = qcom_ipcc_remove,
	.driver = {
		.name = "qcom_ipcc",
		.of_match_table = qcom_ipcc_of_match,
	},
};
module_platform_driver(qcom_ipcc_driver);

MODULE_LICENSE("GPL v2");
+37 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0 */
/*
 * Copyright (c) 2018, The Linux Foundation. All rights reserved.
 */

#ifndef __DT_BINDINGS_QCOM_IPCC_H
#define __DT_BINDINGS_QCOM_IPCC_H

/* Signal IDs for MPROC protocol */
#define IPCC_MPROC_SIGNAL_GLINK_QMP	0
#define IPCC_MPROC_SIGNAL_SMP2P		2
#define IPCC_MPROC_SIGNAL_MAX		4 /* Used by driver only */

#define IPCC_COMPUTE_L0_SIGNAL_MAX	32 /* Used by driver only */
#define IPCC_COMPUTE_L1_SIGNAL_MAX	32 /* Used by driver only */

/* Client IDs */
#define IPCC_CLIENT_AOP			0
#define IPCC_CLIENT_TZ			1
#define IPCC_CLIENT_MPSS		2
#define IPCC_CLIENT_LPASS		3
#define IPCC_CLIENT_SLPI		4
#define IPCC_CLIENT_SDC			5
#define IPCC_CLIENT_CDSP		6
#define IPCC_CLIENT_NPU			7
#define IPCC_CLIENT_APSS		8
#define IPCC_CLIENT_GPU			9
#define IPCC_CLIENT_CVP			10
#define IPCC_CLIENT_CAM			11
#define IPCC_CLIENT_VPU			12
#define IPCC_CLIENT_PCIE0		13
#define IPCC_CLIENT_PCIE1		14
#define IPCC_CLIENT_PCIE2		15
#define IPCC_CLIENT_SPSS		16
#define IPCC_CLIENT_MAX			17 /* Used by driver only */

#endif