1fa74a025SManivannan Sadhasivam // SPDX-License-Identifier: GPL-2.0-only 2fa74a025SManivannan Sadhasivam /* 3fa74a025SManivannan Sadhasivam * Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. 4fa74a025SManivannan Sadhasivam */ 5fa74a025SManivannan Sadhasivam 6fa74a025SManivannan Sadhasivam #include <linux/bitfield.h> 7fa74a025SManivannan Sadhasivam #include <linux/interrupt.h> 8fa74a025SManivannan Sadhasivam #include <linux/irq.h> 9fa74a025SManivannan Sadhasivam #include <linux/irqdomain.h> 10fa74a025SManivannan Sadhasivam #include <linux/mailbox_controller.h> 11fa74a025SManivannan Sadhasivam #include <linux/module.h> 12fa74a025SManivannan Sadhasivam #include <linux/platform_device.h> 13fa74a025SManivannan Sadhasivam 14fa74a025SManivannan Sadhasivam #include <dt-bindings/mailbox/qcom-ipcc.h> 15fa74a025SManivannan Sadhasivam 16fa74a025SManivannan Sadhasivam #define IPCC_MBOX_MAX_CHAN 48 17fa74a025SManivannan Sadhasivam 18fa74a025SManivannan Sadhasivam /* IPCC Register offsets */ 19fa74a025SManivannan Sadhasivam #define IPCC_REG_SEND_ID 0x0c 20fa74a025SManivannan Sadhasivam #define IPCC_REG_RECV_ID 0x10 21fa74a025SManivannan Sadhasivam #define IPCC_REG_RECV_SIGNAL_ENABLE 0x14 22fa74a025SManivannan Sadhasivam #define IPCC_REG_RECV_SIGNAL_DISABLE 0x18 23fa74a025SManivannan Sadhasivam #define IPCC_REG_RECV_SIGNAL_CLEAR 0x1c 24fa74a025SManivannan Sadhasivam #define IPCC_REG_CLIENT_CLEAR 0x38 25fa74a025SManivannan Sadhasivam 26fa74a025SManivannan Sadhasivam #define IPCC_SIGNAL_ID_MASK GENMASK(15, 0) 27fa74a025SManivannan Sadhasivam #define IPCC_CLIENT_ID_MASK GENMASK(31, 16) 28fa74a025SManivannan Sadhasivam 29fa74a025SManivannan Sadhasivam #define IPCC_NO_PENDING_IRQ GENMASK(31, 0) 30fa74a025SManivannan Sadhasivam 31fa74a025SManivannan Sadhasivam /** 32fa74a025SManivannan Sadhasivam * struct qcom_ipcc_chan_info - Per-mailbox-channel info 33fa74a025SManivannan Sadhasivam * @client_id: The client-id to which the interrupt has to be triggered 34fa74a025SManivannan Sadhasivam * @signal_id: The signal-id to which the interrupt has to be triggered 35fa74a025SManivannan Sadhasivam */ 36fa74a025SManivannan Sadhasivam struct qcom_ipcc_chan_info { 37fa74a025SManivannan Sadhasivam u16 client_id; 38fa74a025SManivannan Sadhasivam u16 signal_id; 39fa74a025SManivannan Sadhasivam }; 40fa74a025SManivannan Sadhasivam 41fa74a025SManivannan Sadhasivam /** 42fa74a025SManivannan Sadhasivam * struct qcom_ipcc - Holder for the mailbox driver 43fa74a025SManivannan Sadhasivam * @dev: Device associated with this instance 44fa74a025SManivannan Sadhasivam * @base: Base address of the IPCC frame associated to APSS 45fa74a025SManivannan Sadhasivam * @irq_domain: The irq_domain associated with this instance 46fa74a025SManivannan Sadhasivam * @chan: The mailbox channels array 47fa74a025SManivannan Sadhasivam * @mchan: The per-mailbox channel info array 48fa74a025SManivannan Sadhasivam * @mbox: The mailbox controller 49fa74a025SManivannan Sadhasivam * @irq: Summary irq 50fa74a025SManivannan Sadhasivam */ 51fa74a025SManivannan Sadhasivam struct qcom_ipcc { 52fa74a025SManivannan Sadhasivam struct device *dev; 53fa74a025SManivannan Sadhasivam void __iomem *base; 54fa74a025SManivannan Sadhasivam struct irq_domain *irq_domain; 55fa74a025SManivannan Sadhasivam struct mbox_chan chan[IPCC_MBOX_MAX_CHAN]; 56fa74a025SManivannan Sadhasivam struct qcom_ipcc_chan_info mchan[IPCC_MBOX_MAX_CHAN]; 57fa74a025SManivannan Sadhasivam struct mbox_controller mbox; 58fa74a025SManivannan Sadhasivam int irq; 59fa74a025SManivannan Sadhasivam }; 60fa74a025SManivannan Sadhasivam 61fa74a025SManivannan Sadhasivam static inline struct qcom_ipcc *to_qcom_ipcc(struct mbox_controller *mbox) 62fa74a025SManivannan Sadhasivam { 63fa74a025SManivannan Sadhasivam return container_of(mbox, struct qcom_ipcc, mbox); 64fa74a025SManivannan Sadhasivam } 65fa74a025SManivannan Sadhasivam 66fa74a025SManivannan Sadhasivam static inline u32 qcom_ipcc_get_hwirq(u16 client_id, u16 signal_id) 67fa74a025SManivannan Sadhasivam { 68fa74a025SManivannan Sadhasivam return FIELD_PREP(IPCC_CLIENT_ID_MASK, client_id) | 69fa74a025SManivannan Sadhasivam FIELD_PREP(IPCC_SIGNAL_ID_MASK, signal_id); 70fa74a025SManivannan Sadhasivam } 71fa74a025SManivannan Sadhasivam 72fa74a025SManivannan Sadhasivam static irqreturn_t qcom_ipcc_irq_fn(int irq, void *data) 73fa74a025SManivannan Sadhasivam { 74fa74a025SManivannan Sadhasivam struct qcom_ipcc *ipcc = data; 75fa74a025SManivannan Sadhasivam u32 hwirq; 76fa74a025SManivannan Sadhasivam int virq; 77fa74a025SManivannan Sadhasivam 78fa74a025SManivannan Sadhasivam for (;;) { 79fa74a025SManivannan Sadhasivam hwirq = readl(ipcc->base + IPCC_REG_RECV_ID); 80fa74a025SManivannan Sadhasivam if (hwirq == IPCC_NO_PENDING_IRQ) 81fa74a025SManivannan Sadhasivam break; 82fa74a025SManivannan Sadhasivam 83fa74a025SManivannan Sadhasivam virq = irq_find_mapping(ipcc->irq_domain, hwirq); 84fa74a025SManivannan Sadhasivam writel(hwirq, ipcc->base + IPCC_REG_RECV_SIGNAL_CLEAR); 85fa74a025SManivannan Sadhasivam generic_handle_irq(virq); 86fa74a025SManivannan Sadhasivam } 87fa74a025SManivannan Sadhasivam 88fa74a025SManivannan Sadhasivam return IRQ_HANDLED; 89fa74a025SManivannan Sadhasivam } 90fa74a025SManivannan Sadhasivam 91fa74a025SManivannan Sadhasivam static void qcom_ipcc_mask_irq(struct irq_data *irqd) 92fa74a025SManivannan Sadhasivam { 93fa74a025SManivannan Sadhasivam struct qcom_ipcc *ipcc = irq_data_get_irq_chip_data(irqd); 94fa74a025SManivannan Sadhasivam irq_hw_number_t hwirq = irqd_to_hwirq(irqd); 95fa74a025SManivannan Sadhasivam 96fa74a025SManivannan Sadhasivam writel(hwirq, ipcc->base + IPCC_REG_RECV_SIGNAL_DISABLE); 97fa74a025SManivannan Sadhasivam } 98fa74a025SManivannan Sadhasivam 99fa74a025SManivannan Sadhasivam static void qcom_ipcc_unmask_irq(struct irq_data *irqd) 100fa74a025SManivannan Sadhasivam { 101fa74a025SManivannan Sadhasivam struct qcom_ipcc *ipcc = irq_data_get_irq_chip_data(irqd); 102fa74a025SManivannan Sadhasivam irq_hw_number_t hwirq = irqd_to_hwirq(irqd); 103fa74a025SManivannan Sadhasivam 104fa74a025SManivannan Sadhasivam writel(hwirq, ipcc->base + IPCC_REG_RECV_SIGNAL_ENABLE); 105fa74a025SManivannan Sadhasivam } 106fa74a025SManivannan Sadhasivam 107fa74a025SManivannan Sadhasivam static struct irq_chip qcom_ipcc_irq_chip = { 108fa74a025SManivannan Sadhasivam .name = "ipcc", 109fa74a025SManivannan Sadhasivam .irq_mask = qcom_ipcc_mask_irq, 110fa74a025SManivannan Sadhasivam .irq_unmask = qcom_ipcc_unmask_irq, 111fa74a025SManivannan Sadhasivam .flags = IRQCHIP_SKIP_SET_WAKE, 112fa74a025SManivannan Sadhasivam }; 113fa74a025SManivannan Sadhasivam 114fa74a025SManivannan Sadhasivam static int qcom_ipcc_domain_map(struct irq_domain *d, unsigned int irq, 115fa74a025SManivannan Sadhasivam irq_hw_number_t hw) 116fa74a025SManivannan Sadhasivam { 117fa74a025SManivannan Sadhasivam struct qcom_ipcc *ipcc = d->host_data; 118fa74a025SManivannan Sadhasivam 119fa74a025SManivannan Sadhasivam irq_set_chip_and_handler(irq, &qcom_ipcc_irq_chip, handle_level_irq); 120fa74a025SManivannan Sadhasivam irq_set_chip_data(irq, ipcc); 121fa74a025SManivannan Sadhasivam irq_set_noprobe(irq); 122fa74a025SManivannan Sadhasivam 123fa74a025SManivannan Sadhasivam return 0; 124fa74a025SManivannan Sadhasivam } 125fa74a025SManivannan Sadhasivam 126fa74a025SManivannan Sadhasivam static int qcom_ipcc_domain_xlate(struct irq_domain *d, 127fa74a025SManivannan Sadhasivam struct device_node *node, const u32 *intspec, 128fa74a025SManivannan Sadhasivam unsigned int intsize, 129fa74a025SManivannan Sadhasivam unsigned long *out_hwirq, 130fa74a025SManivannan Sadhasivam unsigned int *out_type) 131fa74a025SManivannan Sadhasivam { 132fa74a025SManivannan Sadhasivam if (intsize != 3) 133fa74a025SManivannan Sadhasivam return -EINVAL; 134fa74a025SManivannan Sadhasivam 135fa74a025SManivannan Sadhasivam *out_hwirq = qcom_ipcc_get_hwirq(intspec[0], intspec[1]); 136fa74a025SManivannan Sadhasivam *out_type = intspec[2] & IRQ_TYPE_SENSE_MASK; 137fa74a025SManivannan Sadhasivam 138fa74a025SManivannan Sadhasivam return 0; 139fa74a025SManivannan Sadhasivam } 140fa74a025SManivannan Sadhasivam 141fa74a025SManivannan Sadhasivam static const struct irq_domain_ops qcom_ipcc_irq_ops = { 142fa74a025SManivannan Sadhasivam .map = qcom_ipcc_domain_map, 143fa74a025SManivannan Sadhasivam .xlate = qcom_ipcc_domain_xlate, 144fa74a025SManivannan Sadhasivam }; 145fa74a025SManivannan Sadhasivam 146fa74a025SManivannan Sadhasivam static int qcom_ipcc_mbox_send_data(struct mbox_chan *chan, void *data) 147fa74a025SManivannan Sadhasivam { 148fa74a025SManivannan Sadhasivam struct qcom_ipcc *ipcc = to_qcom_ipcc(chan->mbox); 149fa74a025SManivannan Sadhasivam struct qcom_ipcc_chan_info *mchan = chan->con_priv; 150fa74a025SManivannan Sadhasivam u32 hwirq; 151fa74a025SManivannan Sadhasivam 152fa74a025SManivannan Sadhasivam hwirq = qcom_ipcc_get_hwirq(mchan->client_id, mchan->signal_id); 153fa74a025SManivannan Sadhasivam writel(hwirq, ipcc->base + IPCC_REG_SEND_ID); 154fa74a025SManivannan Sadhasivam 155fa74a025SManivannan Sadhasivam return 0; 156fa74a025SManivannan Sadhasivam } 157fa74a025SManivannan Sadhasivam 158*d6fbfdbcSSibi Sankar static void qcom_ipcc_mbox_shutdown(struct mbox_chan *chan) 159*d6fbfdbcSSibi Sankar { 160*d6fbfdbcSSibi Sankar chan->con_priv = NULL; 161*d6fbfdbcSSibi Sankar } 162*d6fbfdbcSSibi Sankar 163fa74a025SManivannan Sadhasivam static struct mbox_chan *qcom_ipcc_mbox_xlate(struct mbox_controller *mbox, 164fa74a025SManivannan Sadhasivam const struct of_phandle_args *ph) 165fa74a025SManivannan Sadhasivam { 166fa74a025SManivannan Sadhasivam struct qcom_ipcc *ipcc = to_qcom_ipcc(mbox); 167fa74a025SManivannan Sadhasivam struct qcom_ipcc_chan_info *mchan; 168fa74a025SManivannan Sadhasivam struct mbox_chan *chan; 169fa74a025SManivannan Sadhasivam unsigned int i; 170fa74a025SManivannan Sadhasivam 171fa74a025SManivannan Sadhasivam if (ph->args_count != 2) 172fa74a025SManivannan Sadhasivam return ERR_PTR(-EINVAL); 173fa74a025SManivannan Sadhasivam 174fa74a025SManivannan Sadhasivam for (i = 0; i < IPCC_MBOX_MAX_CHAN; i++) { 175fa74a025SManivannan Sadhasivam chan = &ipcc->chan[i]; 176fa74a025SManivannan Sadhasivam if (!chan->con_priv) { 177fa74a025SManivannan Sadhasivam mchan = &ipcc->mchan[i]; 178fa74a025SManivannan Sadhasivam mchan->client_id = ph->args[0]; 179fa74a025SManivannan Sadhasivam mchan->signal_id = ph->args[1]; 180fa74a025SManivannan Sadhasivam chan->con_priv = mchan; 181fa74a025SManivannan Sadhasivam break; 182fa74a025SManivannan Sadhasivam } 183fa74a025SManivannan Sadhasivam 184fa74a025SManivannan Sadhasivam chan = NULL; 185fa74a025SManivannan Sadhasivam } 186fa74a025SManivannan Sadhasivam 187fa74a025SManivannan Sadhasivam return chan ?: ERR_PTR(-EBUSY); 188fa74a025SManivannan Sadhasivam } 189fa74a025SManivannan Sadhasivam 190fa74a025SManivannan Sadhasivam static const struct mbox_chan_ops ipcc_mbox_chan_ops = { 191fa74a025SManivannan Sadhasivam .send_data = qcom_ipcc_mbox_send_data, 192*d6fbfdbcSSibi Sankar .shutdown = qcom_ipcc_mbox_shutdown, 193fa74a025SManivannan Sadhasivam }; 194fa74a025SManivannan Sadhasivam 195fa74a025SManivannan Sadhasivam static int qcom_ipcc_setup_mbox(struct qcom_ipcc *ipcc) 196fa74a025SManivannan Sadhasivam { 197fa74a025SManivannan Sadhasivam struct mbox_controller *mbox; 198fa74a025SManivannan Sadhasivam struct device *dev = ipcc->dev; 199fa74a025SManivannan Sadhasivam 200fa74a025SManivannan Sadhasivam mbox = &ipcc->mbox; 201fa74a025SManivannan Sadhasivam mbox->dev = dev; 202fa74a025SManivannan Sadhasivam mbox->num_chans = IPCC_MBOX_MAX_CHAN; 203fa74a025SManivannan Sadhasivam mbox->chans = ipcc->chan; 204fa74a025SManivannan Sadhasivam mbox->ops = &ipcc_mbox_chan_ops; 205fa74a025SManivannan Sadhasivam mbox->of_xlate = qcom_ipcc_mbox_xlate; 206fa74a025SManivannan Sadhasivam mbox->txdone_irq = false; 207fa74a025SManivannan Sadhasivam mbox->txdone_poll = false; 208fa74a025SManivannan Sadhasivam 209fa74a025SManivannan Sadhasivam return devm_mbox_controller_register(dev, mbox); 210fa74a025SManivannan Sadhasivam } 211fa74a025SManivannan Sadhasivam 212fa74a025SManivannan Sadhasivam static int qcom_ipcc_probe(struct platform_device *pdev) 213fa74a025SManivannan Sadhasivam { 214fa74a025SManivannan Sadhasivam struct qcom_ipcc *ipcc; 215fa74a025SManivannan Sadhasivam int ret; 216fa74a025SManivannan Sadhasivam 217fa74a025SManivannan Sadhasivam ipcc = devm_kzalloc(&pdev->dev, sizeof(*ipcc), GFP_KERNEL); 218fa74a025SManivannan Sadhasivam if (!ipcc) 219fa74a025SManivannan Sadhasivam return -ENOMEM; 220fa74a025SManivannan Sadhasivam 221fa74a025SManivannan Sadhasivam ipcc->dev = &pdev->dev; 222fa74a025SManivannan Sadhasivam 223fa74a025SManivannan Sadhasivam ipcc->base = devm_platform_ioremap_resource(pdev, 0); 224fa74a025SManivannan Sadhasivam if (IS_ERR(ipcc->base)) 225fa74a025SManivannan Sadhasivam return PTR_ERR(ipcc->base); 226fa74a025SManivannan Sadhasivam 227fa74a025SManivannan Sadhasivam ipcc->irq = platform_get_irq(pdev, 0); 228fa74a025SManivannan Sadhasivam if (ipcc->irq < 0) 229fa74a025SManivannan Sadhasivam return ipcc->irq; 230fa74a025SManivannan Sadhasivam 231fa74a025SManivannan Sadhasivam ipcc->irq_domain = irq_domain_add_tree(pdev->dev.of_node, 232fa74a025SManivannan Sadhasivam &qcom_ipcc_irq_ops, ipcc); 233fa74a025SManivannan Sadhasivam if (!ipcc->irq_domain) 234fa74a025SManivannan Sadhasivam return -ENOMEM; 235fa74a025SManivannan Sadhasivam 236fa74a025SManivannan Sadhasivam ret = qcom_ipcc_setup_mbox(ipcc); 237fa74a025SManivannan Sadhasivam if (ret) 238fa74a025SManivannan Sadhasivam goto err_mbox; 239fa74a025SManivannan Sadhasivam 240fa74a025SManivannan Sadhasivam ret = devm_request_irq(&pdev->dev, ipcc->irq, qcom_ipcc_irq_fn, 241fa74a025SManivannan Sadhasivam IRQF_TRIGGER_HIGH, "ipcc", ipcc); 242fa74a025SManivannan Sadhasivam if (ret < 0) { 243fa74a025SManivannan Sadhasivam dev_err(&pdev->dev, "Failed to register the irq: %d\n", ret); 244fa74a025SManivannan Sadhasivam goto err_mbox; 245fa74a025SManivannan Sadhasivam } 246fa74a025SManivannan Sadhasivam 247fa74a025SManivannan Sadhasivam enable_irq_wake(ipcc->irq); 248fa74a025SManivannan Sadhasivam platform_set_drvdata(pdev, ipcc); 249fa74a025SManivannan Sadhasivam 250fa74a025SManivannan Sadhasivam return 0; 251fa74a025SManivannan Sadhasivam 252fa74a025SManivannan Sadhasivam err_mbox: 253fa74a025SManivannan Sadhasivam irq_domain_remove(ipcc->irq_domain); 254fa74a025SManivannan Sadhasivam 255fa74a025SManivannan Sadhasivam return ret; 256fa74a025SManivannan Sadhasivam } 257fa74a025SManivannan Sadhasivam 258fa74a025SManivannan Sadhasivam static int qcom_ipcc_remove(struct platform_device *pdev) 259fa74a025SManivannan Sadhasivam { 260fa74a025SManivannan Sadhasivam struct qcom_ipcc *ipcc = platform_get_drvdata(pdev); 261fa74a025SManivannan Sadhasivam 262fa74a025SManivannan Sadhasivam disable_irq_wake(ipcc->irq); 263fa74a025SManivannan Sadhasivam irq_domain_remove(ipcc->irq_domain); 264fa74a025SManivannan Sadhasivam 265fa74a025SManivannan Sadhasivam return 0; 266fa74a025SManivannan Sadhasivam } 267fa74a025SManivannan Sadhasivam 268fa74a025SManivannan Sadhasivam static const struct of_device_id qcom_ipcc_of_match[] = { 269fa74a025SManivannan Sadhasivam { .compatible = "qcom,ipcc"}, 270fa74a025SManivannan Sadhasivam {} 271fa74a025SManivannan Sadhasivam }; 272fa74a025SManivannan Sadhasivam MODULE_DEVICE_TABLE(of, qcom_ipcc_of_match); 273fa74a025SManivannan Sadhasivam 274fa74a025SManivannan Sadhasivam static struct platform_driver qcom_ipcc_driver = { 275fa74a025SManivannan Sadhasivam .probe = qcom_ipcc_probe, 276fa74a025SManivannan Sadhasivam .remove = qcom_ipcc_remove, 277fa74a025SManivannan Sadhasivam .driver = { 278fa74a025SManivannan Sadhasivam .name = "qcom-ipcc", 279fa74a025SManivannan Sadhasivam .of_match_table = qcom_ipcc_of_match, 280fa74a025SManivannan Sadhasivam }, 281fa74a025SManivannan Sadhasivam }; 282fa74a025SManivannan Sadhasivam 283fa74a025SManivannan Sadhasivam static int __init qcom_ipcc_init(void) 284fa74a025SManivannan Sadhasivam { 285fa74a025SManivannan Sadhasivam return platform_driver_register(&qcom_ipcc_driver); 286fa74a025SManivannan Sadhasivam } 287fa74a025SManivannan Sadhasivam arch_initcall(qcom_ipcc_init); 288fa74a025SManivannan Sadhasivam 289fa74a025SManivannan Sadhasivam MODULE_AUTHOR("Venkata Narendra Kumar Gutta <vnkgutta@codeaurora.org>"); 290fa74a025SManivannan Sadhasivam MODULE_AUTHOR("Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>"); 291fa74a025SManivannan Sadhasivam MODULE_DESCRIPTION("Qualcomm Technologies, Inc. IPCC driver"); 292fa74a025SManivannan Sadhasivam MODULE_LICENSE("GPL v2"); 293