1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 22b49e0c5SAndy Shevchenko /* 32b49e0c5SAndy Shevchenko * PCI driver for the High Speed UART DMA 42b49e0c5SAndy Shevchenko * 52b49e0c5SAndy Shevchenko * Copyright (C) 2015 Intel Corporation 62b49e0c5SAndy Shevchenko * Author: Andy Shevchenko <andriy.shevchenko@linux.intel.com> 72b49e0c5SAndy Shevchenko * 82b49e0c5SAndy Shevchenko * Partially based on the bits found in drivers/tty/serial/mfd.c. 92b49e0c5SAndy Shevchenko */ 102b49e0c5SAndy Shevchenko 112b49e0c5SAndy Shevchenko #include <linux/bitops.h> 122b49e0c5SAndy Shevchenko #include <linux/device.h> 132b49e0c5SAndy Shevchenko #include <linux/module.h> 142b49e0c5SAndy Shevchenko #include <linux/pci.h> 152b49e0c5SAndy Shevchenko 162b49e0c5SAndy Shevchenko #include "hsu.h" 172b49e0c5SAndy Shevchenko 182b49e0c5SAndy Shevchenko #define HSU_PCI_DMASR 0x00 192b49e0c5SAndy Shevchenko #define HSU_PCI_DMAISR 0x04 202b49e0c5SAndy Shevchenko 212b49e0c5SAndy Shevchenko #define HSU_PCI_CHAN_OFFSET 0x100 222b49e0c5SAndy Shevchenko 234831e0d9SAndy Shevchenko #define PCI_DEVICE_ID_INTEL_MFLD_HSU_DMA 0x081e 244831e0d9SAndy Shevchenko #define PCI_DEVICE_ID_INTEL_MRFLD_HSU_DMA 0x1192 254831e0d9SAndy Shevchenko 262b49e0c5SAndy Shevchenko static irqreturn_t hsu_pci_irq(int irq, void *dev) 272b49e0c5SAndy Shevchenko { 282b49e0c5SAndy Shevchenko struct hsu_dma_chip *chip = dev; 292b49e0c5SAndy Shevchenko u32 dmaisr; 30c6f82787SChuah, Kim Tatt u32 status; 312b49e0c5SAndy Shevchenko unsigned short i; 32d2f5a731SAndy Shevchenko int ret = 0; 33c6f82787SChuah, Kim Tatt int err; 342b49e0c5SAndy Shevchenko 352b49e0c5SAndy Shevchenko dmaisr = readl(chip->regs + HSU_PCI_DMAISR); 364c97ad99SHeikki Krogerus for (i = 0; i < chip->hsu->nr_channels; i++) { 37c6f82787SChuah, Kim Tatt if (dmaisr & 0x1) { 38c6f82787SChuah, Kim Tatt err = hsu_dma_get_status(chip, i, &status); 39c6f82787SChuah, Kim Tatt if (err > 0) 40d2f5a731SAndy Shevchenko ret |= 1; 41c6f82787SChuah, Kim Tatt else if (err == 0) 42c6f82787SChuah, Kim Tatt ret |= hsu_dma_do_irq(chip, i, status); 43c6f82787SChuah, Kim Tatt } 442b49e0c5SAndy Shevchenko dmaisr >>= 1; 452b49e0c5SAndy Shevchenko } 462b49e0c5SAndy Shevchenko 47d2f5a731SAndy Shevchenko return IRQ_RETVAL(ret); 482b49e0c5SAndy Shevchenko } 492b49e0c5SAndy Shevchenko 502b49e0c5SAndy Shevchenko static int hsu_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) 512b49e0c5SAndy Shevchenko { 522b49e0c5SAndy Shevchenko struct hsu_dma_chip *chip; 532b49e0c5SAndy Shevchenko int ret; 542b49e0c5SAndy Shevchenko 552b49e0c5SAndy Shevchenko ret = pcim_enable_device(pdev); 562b49e0c5SAndy Shevchenko if (ret) 572b49e0c5SAndy Shevchenko return ret; 582b49e0c5SAndy Shevchenko 592b49e0c5SAndy Shevchenko ret = pcim_iomap_regions(pdev, BIT(0), pci_name(pdev)); 602b49e0c5SAndy Shevchenko if (ret) { 612b49e0c5SAndy Shevchenko dev_err(&pdev->dev, "I/O memory remapping failed\n"); 622b49e0c5SAndy Shevchenko return ret; 632b49e0c5SAndy Shevchenko } 642b49e0c5SAndy Shevchenko 652b49e0c5SAndy Shevchenko pci_set_master(pdev); 662b49e0c5SAndy Shevchenko pci_try_set_mwi(pdev); 672b49e0c5SAndy Shevchenko 682b49e0c5SAndy Shevchenko ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); 692b49e0c5SAndy Shevchenko if (ret) 702b49e0c5SAndy Shevchenko return ret; 712b49e0c5SAndy Shevchenko 722b49e0c5SAndy Shevchenko ret = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32)); 732b49e0c5SAndy Shevchenko if (ret) 742b49e0c5SAndy Shevchenko return ret; 752b49e0c5SAndy Shevchenko 762b49e0c5SAndy Shevchenko chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); 772b49e0c5SAndy Shevchenko if (!chip) 782b49e0c5SAndy Shevchenko return -ENOMEM; 792b49e0c5SAndy Shevchenko 80e9bb8a9dSAndy Shevchenko ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES); 81e9bb8a9dSAndy Shevchenko if (ret < 0) 82e9bb8a9dSAndy Shevchenko return ret; 83e9bb8a9dSAndy Shevchenko 842b49e0c5SAndy Shevchenko chip->dev = &pdev->dev; 852b49e0c5SAndy Shevchenko chip->regs = pcim_iomap_table(pdev)[0]; 862b49e0c5SAndy Shevchenko chip->length = pci_resource_len(pdev, 0); 872b49e0c5SAndy Shevchenko chip->offset = HSU_PCI_CHAN_OFFSET; 88e9bb8a9dSAndy Shevchenko chip->irq = pci_irq_vector(pdev, 0); 892b49e0c5SAndy Shevchenko 902b49e0c5SAndy Shevchenko ret = hsu_dma_probe(chip); 912b49e0c5SAndy Shevchenko if (ret) 922b49e0c5SAndy Shevchenko return ret; 932b49e0c5SAndy Shevchenko 942b49e0c5SAndy Shevchenko ret = request_irq(chip->irq, hsu_pci_irq, 0, "hsu_dma_pci", chip); 952b49e0c5SAndy Shevchenko if (ret) 962b49e0c5SAndy Shevchenko goto err_register_irq; 972b49e0c5SAndy Shevchenko 98*035b73b2SFerry Toth /* 99*035b73b2SFerry Toth * On Intel Tangier B0 and Anniedale the interrupt line, disregarding 100*035b73b2SFerry Toth * to have different numbers, is shared between HSU DMA and UART IPs. 101*035b73b2SFerry Toth * Thus on such SoCs we are expecting that IRQ handler is called in 102*035b73b2SFerry Toth * UART driver only. Instead of handling the spurious interrupt 103*035b73b2SFerry Toth * from HSU DMA here and waste CPU time and delay HSU UART interrupt 104*035b73b2SFerry Toth * handling, disable the interrupt entirely. 105*035b73b2SFerry Toth */ 106*035b73b2SFerry Toth if (pdev->device == PCI_DEVICE_ID_INTEL_MRFLD_HSU_DMA) 107*035b73b2SFerry Toth disable_irq_nosync(chip->irq); 108*035b73b2SFerry Toth 1092b49e0c5SAndy Shevchenko pci_set_drvdata(pdev, chip); 1102b49e0c5SAndy Shevchenko 1112b49e0c5SAndy Shevchenko return 0; 1122b49e0c5SAndy Shevchenko 1132b49e0c5SAndy Shevchenko err_register_irq: 1142b49e0c5SAndy Shevchenko hsu_dma_remove(chip); 1152b49e0c5SAndy Shevchenko return ret; 1162b49e0c5SAndy Shevchenko } 1172b49e0c5SAndy Shevchenko 1182b49e0c5SAndy Shevchenko static void hsu_pci_remove(struct pci_dev *pdev) 1192b49e0c5SAndy Shevchenko { 1202b49e0c5SAndy Shevchenko struct hsu_dma_chip *chip = pci_get_drvdata(pdev); 1212b49e0c5SAndy Shevchenko 1222b49e0c5SAndy Shevchenko free_irq(chip->irq, chip); 1232b49e0c5SAndy Shevchenko hsu_dma_remove(chip); 1242b49e0c5SAndy Shevchenko } 1252b49e0c5SAndy Shevchenko 1262b49e0c5SAndy Shevchenko static const struct pci_device_id hsu_pci_id_table[] = { 1274831e0d9SAndy Shevchenko { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MFLD_HSU_DMA), 0 }, 1284831e0d9SAndy Shevchenko { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MRFLD_HSU_DMA), 0 }, 1292b49e0c5SAndy Shevchenko { } 1302b49e0c5SAndy Shevchenko }; 1312b49e0c5SAndy Shevchenko MODULE_DEVICE_TABLE(pci, hsu_pci_id_table); 1322b49e0c5SAndy Shevchenko 1332b49e0c5SAndy Shevchenko static struct pci_driver hsu_pci_driver = { 1342b49e0c5SAndy Shevchenko .name = "hsu_dma_pci", 1352b49e0c5SAndy Shevchenko .id_table = hsu_pci_id_table, 1362b49e0c5SAndy Shevchenko .probe = hsu_pci_probe, 1372b49e0c5SAndy Shevchenko .remove = hsu_pci_remove, 1382b49e0c5SAndy Shevchenko }; 1392b49e0c5SAndy Shevchenko 1402b49e0c5SAndy Shevchenko module_pci_driver(hsu_pci_driver); 1412b49e0c5SAndy Shevchenko 1422b49e0c5SAndy Shevchenko MODULE_LICENSE("GPL v2"); 1432b49e0c5SAndy Shevchenko MODULE_DESCRIPTION("High Speed UART DMA PCI driver"); 1442b49e0c5SAndy Shevchenko MODULE_AUTHOR("Andy Shevchenko <andriy.shevchenko@linux.intel.com>"); 145