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

Commit f2d71b2d authored by Chris Lew's avatar Chris Lew
Browse files

soc: qcom: Introduce QSEE IPC IRQ driver



This introduces a basic driver for cascading interrupts from QSEE. This
driver will perform the common interrupt handling code for interrupts
from QSEE incluiding clearing the interrupt through a register write.

Change-Id: I5739e89812d76a08e0f5e350a83fe28aab1c50b7
Signed-off-by: default avatarChris Lew <clew@codeaurora.org>
parent d732694b
Loading
Loading
Loading
Loading
+81 −0
Original line number Diff line number Diff line
Binding for the QTI Secure Execution Environment IRQ controller
===============================================================

The QTI Secure Execution Environment (QSEE) IRQ controller facilitates receiving
and clearing interrupts from QSEE. Each interrupt from QSEE has a set of control
registers to mask, clear and get the status of interrupts. This controller will
create an interrupt for clients to register with based on the bits available in
the control registers.

- compatible:
	Usage: required
	Value type: <string>
	Definition: must be one of:
		    "qcom,sdm855-qsee-irq"

- syscon:
	usage: required
	Value type: <prop-encoded-array>
	Definition: phandle to a syscon node representing the scsr registers

- interrupts:
	Usage: required
	Value type: <prop-encoded array>
	Definition: multiple entries specifying the interrupts from QSEE

- interrupt-names:
	Usage: required
	Value type: <string>
	Definition: Interrupt names should be one of the following to map the
		    interrupt back to the correct registers.
		    - sp_ipc%d
		    - sp_rmb

- interrupt-controller:
	Usage: required
	Value type: <empty>
	Definition: Identifies this node as an interrupt controller

- #interrupt-cells
	Usage: required
	Value type: <u32>
	Definition: must be 3 - for interrupts to encode these properties:
		    - u32 denoting index of desired interrupt in @interrupts
		    - u32 denoting bit of interrupt bank
		    - u32 denoting IRQ flags

= EXAMPLE
The following example shows the QSEE_IRQ setup with the GLINK SPSS node, defined
from the sdm855 apps processor's point-of-view. In this example the GLINK node
registers for the sp_ipc0 interrupt(index 0 in interrupt-names) and the 0th
bit on the sp_ipc0 interrupt bank.

sp_scsr_block: syscon@1880000 {
	compatible = “syscon”;
	reg = <0x1880000 0x10000>;
};

intsp: qcom,qsee_irq {
	compatible = "qcom,sdm855-qsee-irq";

	syscon = <&sp_scsr_block>;
	interrupts = <0 348 IRQ_TYPE_LEVEL_HIGH>,
		     <0 349 IRQ_TYPE_LEVEL_HIGH>;

	interrupt-names = "sp_ipc0",
			  "sp_ipc1";

	interrupt-controller;
	#interrupt-cells = <3>;
};

spss {
	...
	glink {
		qcom,remote-pid = <8>;
		mboxes = <&sp_scsr 0>;
		mbox-names = "spss_spss";
		interrupts = <&intsp 0 0 IRQ_TYPE_EDGE_RISING>;
	};
};
+9 −0
Original line number Diff line number Diff line
@@ -426,6 +426,15 @@ config QTI_RPMH_API
config QTI_SYSTEM_PM
	bool

config QSEE_IPC_IRQ
	bool "QSEE interrupt manager"
	help
	  The QSEE IPC IRQ controller manages the interrupts from the QTI
	  secure execution environment. This interrupt controller will use
	  the registers in the spcs region to mask and clear interrupts.
	  Clients can use this driver to avoid adding common interrupt handling
	  code.

config QCOM_GLINK
	tristate "GLINK Probe Helper"
	depends on RPMSG_QCOM_GLINK_SMEM
+1 −0
Original line number Diff line number Diff line
@@ -54,6 +54,7 @@ ifdef CONFIG_MSM_SUBSYSTEM_RESTART
       obj-y += ramdump.o
endif
obj-$(CONFIG_QCOM_EUD) += eud.o
obj-$(CONFIG_QSEE_IPC_IRQ) += qsee_ipc_irq.o
obj-$(CONFIG_QCOM_GLINK) += glink_probe.o
obj-$(CONFIG_QCOM_GLINK_PKT) += glink_pkt.o
obj-$(CONFIG_QCOM_QDSS_BRIDGE) += qdss_bridge.o
+362 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2018, 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/interrupt.h>
#include <linux/list.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>

#define MAX_BANK_IRQ 32

#define hwirq_to_index(_irq) (_irq / MAX_BANK_IRQ)
#define hwirq_to_bit(_irq) (_irq % MAX_BANK_IRQ)

struct qsee_irq_data {
	const char *name;
	u32 status;
	u32 clear;
	u32 mask;
	u32 msb;
};

struct qsee_irq_bank {
	const struct qsee_irq_data *data;
	int irq;

	DECLARE_BITMAP(irq_enabled, MAX_BANK_IRQ);
	DECLARE_BITMAP(irq_rising, MAX_BANK_IRQ);
	DECLARE_BITMAP(irq_falling, MAX_BANK_IRQ);
};

struct qsee_irq {
	struct device *dev;

	struct irq_domain *domain;
	struct regmap *regmap;

	int num_banks;
	struct qsee_irq_bank *banks;
};

/**
 * qsee_intr() - interrupt handler for incoming notifications
 * @irq:	unused
 * @data:	qsee driver context
 *
 * Handle notifications from the remote side to handle newly allocated entries
 * or any changes to the state bits of existing entries.
 */
static irqreturn_t qsee_intr(int irq, void *data)
{
	struct qsee_irq *qirq = data;
	struct qsee_irq_bank *bank = NULL;
	struct irq_desc *desc;
	int irq_pin;
	u32 status;
	u32 mask;
	int i;

	for (i = 0; i < qirq->num_banks; i++) {
		if (qirq->banks[i].irq == irq) {
			bank = &qirq->banks[i];
			break;
		}
	}
	if (!bank) {
		dev_err(qirq->dev, "Unable to find bank for irq:%d\n", irq);
		return IRQ_HANDLED;
	}

	if (regmap_read(qirq->regmap, bank->data->status, &status)) {
		dev_err(qirq->dev, "Error reading irq %d status\n", irq);
		return IRQ_HANDLED;
	}
	if (regmap_read(qirq->regmap, bank->data->mask, &mask)) {
		dev_err(qirq->dev, "Error reading irq %d mask\n", irq);
		return IRQ_HANDLED;
	}

	for_each_set_bit(i, bank->irq_enabled, bank->data->msb) {
		if (!(status & BIT(i)))
			continue;

		irq_pin = irq_find_mapping(qirq->domain, i);
		desc = irq_to_desc(irq_pin);
		handle_simple_irq(desc);
		regmap_write(qirq->regmap, bank->data->clear, BIT(i));
	}

	return IRQ_HANDLED;
}

static void qsee_mask_irq(struct irq_data *irqd)
{
	struct qsee_irq *qirq = irq_data_get_irq_chip_data(irqd);
	irq_hw_number_t irq = irqd_to_hwirq(irqd);
	struct qsee_irq_bank *bank;
	int index;
	u32 mask;
	int bit;

	index = hwirq_to_index(irq);
	bit = hwirq_to_bit(irq);
	bank = &qirq->banks[index];

	regmap_read(qirq->regmap, bank->data->mask, &mask);
	mask |= BIT(bit);
	regmap_write(qirq->regmap, bank->data->mask, mask);

	clear_bit(bit, bank->irq_enabled);
}

static void qsee_unmask_irq(struct irq_data *irqd)
{
	struct qsee_irq *qirq = irq_data_get_irq_chip_data(irqd);
	irq_hw_number_t irq = irqd_to_hwirq(irqd);
	struct qsee_irq_bank *bank;
	int index;
	u32 mask;
	int bit;

	index = hwirq_to_index(irq);
	bit = hwirq_to_bit(irq);
	bank = &qirq->banks[index];

	regmap_read(qirq->regmap, bank->data->mask, &mask);
	mask &= ~(BIT(bit));
	regmap_write(qirq->regmap, bank->data->mask, mask);

	set_bit(bit, bank->irq_enabled);
}

static int qsee_set_irq_type(struct irq_data *irqd, unsigned int type)
{
	struct qsee_irq *qirq = irq_data_get_irq_chip_data(irqd);
	irq_hw_number_t irq = irqd_to_hwirq(irqd);
	struct qsee_irq_bank *bank;
	int index;
	int bit;

	index = hwirq_to_index(irq);
	bit = hwirq_to_bit(irq);
	bank = &qirq->banks[index];

	if (!(type & IRQ_TYPE_EDGE_BOTH))
		return -EINVAL;

	if (type & IRQ_TYPE_EDGE_RISING)
		set_bit(bit, bank->irq_rising);
	else
		clear_bit(bit, bank->irq_rising);

	if (type & IRQ_TYPE_EDGE_FALLING)
		set_bit(bit, bank->irq_falling);
	else
		clear_bit(bit, bank->irq_falling);

	return 0;
}

static struct irq_chip qsee_irq_chip = {
	.name           = "qsee",
	.irq_mask       = qsee_mask_irq,
	.irq_unmask     = qsee_unmask_irq,
	.irq_set_type	= qsee_set_irq_type,
};

static int qsee_irq_map(struct irq_domain *d,
			 unsigned int irq,
			 irq_hw_number_t hw)
{
	struct qsee_irq *qirq = d->host_data;

	irq_set_chip_and_handler(irq, &qsee_irq_chip, handle_level_irq);
	irq_set_chip_data(irq, qirq);
	irq_set_noprobe(irq);

	return 0;
}

static int qsee_irq_xlate_threecell(struct irq_domain *d,
				    struct device_node *ctrlr,
				    const u32 *intspec,
				    unsigned int intsize,
				    irq_hw_number_t *out_hwirq,
				    unsigned int *out_type)
{
	struct qsee_irq *qirq = d->host_data;
	u32 index, bit;

	if (WARN_ON(intsize < 3))
		return -EINVAL;

	index = intspec[0];
	if (WARN_ON(index >= qirq->num_banks))
		return -EINVAL;

	bit = intspec[1];
	if (WARN_ON(bit >= qirq->banks[index].data->msb))
		return -EINVAL;

	*out_hwirq = (index * MAX_BANK_IRQ) + bit;
	*out_type = intspec[2] & IRQ_TYPE_SENSE_MASK;

	return 0;
}

static const struct irq_domain_ops qsee_irq_ops = {
	.map = qsee_irq_map,
	.xlate = qsee_irq_xlate_threecell,
};

static int qsee_irq_probe(struct platform_device *pdev)
{
	const struct qsee_irq_data *data;
	struct device *dev = &pdev->dev;
	struct qsee_irq_bank *bank;
	struct device_node *syscon;
	struct qsee_irq *qirq;
	int irq_count;
	u32 mask;
	int idx;
	int ret;
	int i;

	qirq = devm_kzalloc(dev, sizeof(*qirq), GFP_KERNEL);
	if (!qirq)
		return -ENOMEM;

	platform_set_drvdata(pdev, qirq);
	qirq->dev = dev;

	syscon = of_parse_phandle(dev->of_node, "syscon", 0);
	if (!syscon) {
		dev_err(dev, "no syscon node\n");
		return -ENODEV;
	}

	qirq->regmap = syscon_node_to_regmap(syscon);
	if (IS_ERR(qirq->regmap))
		return PTR_ERR(qirq->regmap);

	data = (struct qsee_irq_data *)of_device_get_match_data(dev);
	if (!data)
		return -ENODEV;

	irq_count = platform_irq_count(pdev);
	qirq->banks = devm_kzalloc(dev, sizeof(*qirq->banks) * irq_count,
				   GFP_KERNEL);
	if (!qirq->banks)
		return -ENOMEM;

	qirq->num_banks = irq_count;
	for (i = 0; data[i].name; i++) {
		idx = of_property_match_string(dev->of_node, "interrupt-names",
					       data[i].name);
		if (idx < 0)
			return -EINVAL;

		bank = &qirq->banks[idx];
		bank->data = &data[i];
		bank->irq = platform_get_irq(pdev, idx);
		if (bank->irq < 0) {
			dev_err(dev, "unable to acquire %s interrupt\n",
				data[i].name);
			return -EINVAL;
		}

		/* Mask all interrupts until client registers */
		mask = (1 << bank->data->msb) - 1;
		regmap_write(qirq->regmap, bank->data->mask, mask);

		ret = devm_request_irq(dev, bank->irq, qsee_intr,
				       IRQF_NO_SUSPEND, "qsee_irq", qirq);
		if (ret) {
			dev_err(dev, "failed to request interrupt\n");
			return ret;
		}
	}

	qirq->domain = irq_domain_add_linear(dev->of_node, 32 * irq_count,
						 &qsee_irq_ops, qirq);
	if (!qirq->domain) {
		dev_err(dev, "failed to add irq_domain\n");
		return -ENOMEM;
	}

	return 0;
}

static int qsee_irq_remove(struct platform_device *pdev)
{
	struct qsee_irq *qirq = platform_get_drvdata(pdev);

	irq_domain_remove(qirq->domain);

	return 0;
}

static const struct qsee_irq_data qsee_irq_data_init[] = {
	{
		.name = "sp_ipc0",
		.status = 0x6000,
		.clear = 0x6008,
		.mask = 0x601C,
		.msb = 4,
	},
	{
		.name = "sp_ipc1",
		.status = 0x8000,
		.clear = 0x8008,
		.mask = 0x801C,
		.msb = 4,
	},
	{},
};

static const struct of_device_id qsee_irq_of_match[] = {
	{ .compatible = "qcom,sdm855-qsee-irq", .data = &qsee_irq_data_init},
	{},
};
MODULE_DEVICE_TABLE(of, qsee_irq_of_match);

static struct platform_driver qsee_irq_driver = {
	.probe = qsee_irq_probe,
	.remove = qsee_irq_remove,
	.driver = {
			.name = "qsee_irq",
			.of_match_table = qsee_irq_of_match,
	},
};

static int __init qsee_irq_init(void)
{
	int rc;

	rc = platform_driver_register(&qsee_irq_driver);
	if (rc)
		pr_err("%s: platform driver reg failed %d\n", __func__, rc);

	return rc;
}
postcore_initcall(qsee_irq_init);

MODULE_DESCRIPTION("QTI Secure Execution Environment IRQ driver");
MODULE_LICENSE("GPL v2");