Loading Documentation/devicetree/bindings/arm/msm/qcom,qsee_irq.txt 0 → 100644 +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>; }; }; drivers/soc/qcom/Kconfig +9 −0 Original line number Diff line number Diff line Loading @@ -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 Loading drivers/soc/qcom/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -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 Loading drivers/soc/qcom/qsee_ipc_irq.c 0 → 100644 +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"); Loading
Documentation/devicetree/bindings/arm/msm/qcom,qsee_irq.txt 0 → 100644 +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>; }; };
drivers/soc/qcom/Kconfig +9 −0 Original line number Diff line number Diff line Loading @@ -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 Loading
drivers/soc/qcom/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -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 Loading
drivers/soc/qcom/qsee_ipc_irq.c 0 → 100644 +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");