18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * ddbridge.c: Digital Devices PCIe bridge driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2010-2017 Digital Devices GmbH 68c2ecf20Sopenharmony_ci * Ralph Metzler <rjkm@metzlerbros.de> 78c2ecf20Sopenharmony_ci * Marcus Metzler <mocm@metzlerbros.de> 88c2ecf20Sopenharmony_ci * 98c2ecf20Sopenharmony_ci * This program is free software; you can redistribute it and/or 108c2ecf20Sopenharmony_ci * modify it under the terms of the GNU General Public License 118c2ecf20Sopenharmony_ci * version 2 only, as published by the Free Software Foundation. 128c2ecf20Sopenharmony_ci * 138c2ecf20Sopenharmony_ci * This program is distributed in the hope that it will be useful, 148c2ecf20Sopenharmony_ci * but WITHOUT ANY WARRANTY; without even the implied warranty of 158c2ecf20Sopenharmony_ci * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 168c2ecf20Sopenharmony_ci * GNU General Public License for more details. 178c2ecf20Sopenharmony_ci */ 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#include <linux/module.h> 228c2ecf20Sopenharmony_ci#include <linux/init.h> 238c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 248c2ecf20Sopenharmony_ci#include <linux/delay.h> 258c2ecf20Sopenharmony_ci#include <linux/slab.h> 268c2ecf20Sopenharmony_ci#include <linux/poll.h> 278c2ecf20Sopenharmony_ci#include <linux/io.h> 288c2ecf20Sopenharmony_ci#include <linux/pci.h> 298c2ecf20Sopenharmony_ci#include <linux/pci_ids.h> 308c2ecf20Sopenharmony_ci#include <linux/timer.h> 318c2ecf20Sopenharmony_ci#include <linux/i2c.h> 328c2ecf20Sopenharmony_ci#include <linux/swab.h> 338c2ecf20Sopenharmony_ci#include <linux/vmalloc.h> 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci#include "ddbridge.h" 368c2ecf20Sopenharmony_ci#include "ddbridge-i2c.h" 378c2ecf20Sopenharmony_ci#include "ddbridge-regs.h" 388c2ecf20Sopenharmony_ci#include "ddbridge-hw.h" 398c2ecf20Sopenharmony_ci#include "ddbridge-io.h" 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci/****************************************************************************/ 428c2ecf20Sopenharmony_ci/* module parameters */ 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci#ifdef CONFIG_PCI_MSI 458c2ecf20Sopenharmony_ci#ifdef CONFIG_DVB_DDBRIDGE_MSIENABLE 468c2ecf20Sopenharmony_cistatic int msi = 1; 478c2ecf20Sopenharmony_ci#else 488c2ecf20Sopenharmony_cistatic int msi; 498c2ecf20Sopenharmony_ci#endif 508c2ecf20Sopenharmony_cimodule_param(msi, int, 0444); 518c2ecf20Sopenharmony_ci#ifdef CONFIG_DVB_DDBRIDGE_MSIENABLE 528c2ecf20Sopenharmony_ciMODULE_PARM_DESC(msi, "Control MSI interrupts: 0-disable, 1-enable (default)"); 538c2ecf20Sopenharmony_ci#else 548c2ecf20Sopenharmony_ciMODULE_PARM_DESC(msi, "Control MSI interrupts: 0-disable (default), 1-enable"); 558c2ecf20Sopenharmony_ci#endif 568c2ecf20Sopenharmony_ci#endif 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci/****************************************************************************/ 598c2ecf20Sopenharmony_ci/****************************************************************************/ 608c2ecf20Sopenharmony_ci/****************************************************************************/ 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_cistatic void ddb_irq_disable(struct ddb *dev) 638c2ecf20Sopenharmony_ci{ 648c2ecf20Sopenharmony_ci ddbwritel(dev, 0, INTERRUPT_ENABLE); 658c2ecf20Sopenharmony_ci ddbwritel(dev, 0, MSI1_ENABLE); 668c2ecf20Sopenharmony_ci} 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_cistatic void ddb_msi_exit(struct ddb *dev) 698c2ecf20Sopenharmony_ci{ 708c2ecf20Sopenharmony_ci#ifdef CONFIG_PCI_MSI 718c2ecf20Sopenharmony_ci if (dev->msi) 728c2ecf20Sopenharmony_ci pci_free_irq_vectors(dev->pdev); 738c2ecf20Sopenharmony_ci#endif 748c2ecf20Sopenharmony_ci} 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_cistatic void ddb_irq_exit(struct ddb *dev) 778c2ecf20Sopenharmony_ci{ 788c2ecf20Sopenharmony_ci ddb_irq_disable(dev); 798c2ecf20Sopenharmony_ci if (dev->msi == 2) 808c2ecf20Sopenharmony_ci free_irq(pci_irq_vector(dev->pdev, 1), dev); 818c2ecf20Sopenharmony_ci free_irq(pci_irq_vector(dev->pdev, 0), dev); 828c2ecf20Sopenharmony_ci} 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_cistatic void ddb_remove(struct pci_dev *pdev) 858c2ecf20Sopenharmony_ci{ 868c2ecf20Sopenharmony_ci struct ddb *dev = (struct ddb *)pci_get_drvdata(pdev); 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci ddb_device_destroy(dev); 898c2ecf20Sopenharmony_ci ddb_ports_detach(dev); 908c2ecf20Sopenharmony_ci ddb_i2c_release(dev); 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci ddb_irq_exit(dev); 938c2ecf20Sopenharmony_ci ddb_msi_exit(dev); 948c2ecf20Sopenharmony_ci ddb_ports_release(dev); 958c2ecf20Sopenharmony_ci ddb_buffers_free(dev); 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci ddb_unmap(dev); 988c2ecf20Sopenharmony_ci pci_set_drvdata(pdev, NULL); 998c2ecf20Sopenharmony_ci pci_disable_device(pdev); 1008c2ecf20Sopenharmony_ci} 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci#ifdef CONFIG_PCI_MSI 1038c2ecf20Sopenharmony_cistatic void ddb_irq_msi(struct ddb *dev, int nr) 1048c2ecf20Sopenharmony_ci{ 1058c2ecf20Sopenharmony_ci int stat; 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci if (msi && pci_msi_enabled()) { 1088c2ecf20Sopenharmony_ci stat = pci_alloc_irq_vectors(dev->pdev, 1, nr, 1098c2ecf20Sopenharmony_ci PCI_IRQ_MSI | PCI_IRQ_MSIX); 1108c2ecf20Sopenharmony_ci if (stat >= 1) { 1118c2ecf20Sopenharmony_ci dev->msi = stat; 1128c2ecf20Sopenharmony_ci dev_info(dev->dev, "using %d MSI interrupt(s)\n", 1138c2ecf20Sopenharmony_ci dev->msi); 1148c2ecf20Sopenharmony_ci } else { 1158c2ecf20Sopenharmony_ci dev_info(dev->dev, "MSI not available.\n"); 1168c2ecf20Sopenharmony_ci } 1178c2ecf20Sopenharmony_ci } 1188c2ecf20Sopenharmony_ci} 1198c2ecf20Sopenharmony_ci#endif 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_cistatic int ddb_irq_init(struct ddb *dev) 1228c2ecf20Sopenharmony_ci{ 1238c2ecf20Sopenharmony_ci int stat; 1248c2ecf20Sopenharmony_ci int irq_flag = IRQF_SHARED; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci ddbwritel(dev, 0x00000000, INTERRUPT_ENABLE); 1278c2ecf20Sopenharmony_ci ddbwritel(dev, 0x00000000, MSI1_ENABLE); 1288c2ecf20Sopenharmony_ci ddbwritel(dev, 0x00000000, MSI2_ENABLE); 1298c2ecf20Sopenharmony_ci ddbwritel(dev, 0x00000000, MSI3_ENABLE); 1308c2ecf20Sopenharmony_ci ddbwritel(dev, 0x00000000, MSI4_ENABLE); 1318c2ecf20Sopenharmony_ci ddbwritel(dev, 0x00000000, MSI5_ENABLE); 1328c2ecf20Sopenharmony_ci ddbwritel(dev, 0x00000000, MSI6_ENABLE); 1338c2ecf20Sopenharmony_ci ddbwritel(dev, 0x00000000, MSI7_ENABLE); 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci#ifdef CONFIG_PCI_MSI 1368c2ecf20Sopenharmony_ci ddb_irq_msi(dev, 2); 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci if (dev->msi) 1398c2ecf20Sopenharmony_ci irq_flag = 0; 1408c2ecf20Sopenharmony_ci if (dev->msi == 2) { 1418c2ecf20Sopenharmony_ci stat = request_irq(pci_irq_vector(dev->pdev, 0), 1428c2ecf20Sopenharmony_ci ddb_irq_handler0, irq_flag, "ddbridge", 1438c2ecf20Sopenharmony_ci (void *)dev); 1448c2ecf20Sopenharmony_ci if (stat < 0) 1458c2ecf20Sopenharmony_ci return stat; 1468c2ecf20Sopenharmony_ci stat = request_irq(pci_irq_vector(dev->pdev, 1), 1478c2ecf20Sopenharmony_ci ddb_irq_handler1, irq_flag, "ddbridge", 1488c2ecf20Sopenharmony_ci (void *)dev); 1498c2ecf20Sopenharmony_ci if (stat < 0) { 1508c2ecf20Sopenharmony_ci free_irq(pci_irq_vector(dev->pdev, 0), dev); 1518c2ecf20Sopenharmony_ci return stat; 1528c2ecf20Sopenharmony_ci } 1538c2ecf20Sopenharmony_ci } else 1548c2ecf20Sopenharmony_ci#endif 1558c2ecf20Sopenharmony_ci { 1568c2ecf20Sopenharmony_ci stat = request_irq(pci_irq_vector(dev->pdev, 0), 1578c2ecf20Sopenharmony_ci ddb_irq_handler, irq_flag, "ddbridge", 1588c2ecf20Sopenharmony_ci (void *)dev); 1598c2ecf20Sopenharmony_ci if (stat < 0) 1608c2ecf20Sopenharmony_ci return stat; 1618c2ecf20Sopenharmony_ci } 1628c2ecf20Sopenharmony_ci if (dev->msi == 2) { 1638c2ecf20Sopenharmony_ci ddbwritel(dev, 0x0fffff00, INTERRUPT_ENABLE); 1648c2ecf20Sopenharmony_ci ddbwritel(dev, 0x0000000f, MSI1_ENABLE); 1658c2ecf20Sopenharmony_ci } else { 1668c2ecf20Sopenharmony_ci ddbwritel(dev, 0x0fffff0f, INTERRUPT_ENABLE); 1678c2ecf20Sopenharmony_ci ddbwritel(dev, 0x00000000, MSI1_ENABLE); 1688c2ecf20Sopenharmony_ci } 1698c2ecf20Sopenharmony_ci return stat; 1708c2ecf20Sopenharmony_ci} 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_cistatic int ddb_probe(struct pci_dev *pdev, 1738c2ecf20Sopenharmony_ci const struct pci_device_id *id) 1748c2ecf20Sopenharmony_ci{ 1758c2ecf20Sopenharmony_ci struct ddb *dev; 1768c2ecf20Sopenharmony_ci int stat = 0; 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci if (pci_enable_device(pdev) < 0) 1798c2ecf20Sopenharmony_ci return -ENODEV; 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci pci_set_master(pdev); 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci if (pci_set_dma_mask(pdev, DMA_BIT_MASK(64))) 1848c2ecf20Sopenharmony_ci if (pci_set_dma_mask(pdev, DMA_BIT_MASK(32))) 1858c2ecf20Sopenharmony_ci return -ENODEV; 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci dev = vzalloc(sizeof(*dev)); 1888c2ecf20Sopenharmony_ci if (!dev) 1898c2ecf20Sopenharmony_ci return -ENOMEM; 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci mutex_init(&dev->mutex); 1928c2ecf20Sopenharmony_ci dev->has_dma = 1; 1938c2ecf20Sopenharmony_ci dev->pdev = pdev; 1948c2ecf20Sopenharmony_ci dev->dev = &pdev->dev; 1958c2ecf20Sopenharmony_ci pci_set_drvdata(pdev, dev); 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci dev->link[0].ids.vendor = id->vendor; 1988c2ecf20Sopenharmony_ci dev->link[0].ids.device = id->device; 1998c2ecf20Sopenharmony_ci dev->link[0].ids.subvendor = id->subvendor; 2008c2ecf20Sopenharmony_ci dev->link[0].ids.subdevice = pdev->subsystem_device; 2018c2ecf20Sopenharmony_ci dev->link[0].ids.devid = (id->device << 16) | id->vendor; 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci dev->link[0].dev = dev; 2048c2ecf20Sopenharmony_ci dev->link[0].info = get_ddb_info(id->vendor, id->device, 2058c2ecf20Sopenharmony_ci id->subvendor, pdev->subsystem_device); 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci dev_info(&pdev->dev, "detected %s\n", dev->link[0].info->name); 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci dev->regs_len = pci_resource_len(dev->pdev, 0); 2108c2ecf20Sopenharmony_ci dev->regs = ioremap(pci_resource_start(dev->pdev, 0), 2118c2ecf20Sopenharmony_ci pci_resource_len(dev->pdev, 0)); 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci if (!dev->regs) { 2148c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "not enough memory for register map\n"); 2158c2ecf20Sopenharmony_ci stat = -ENOMEM; 2168c2ecf20Sopenharmony_ci goto fail; 2178c2ecf20Sopenharmony_ci } 2188c2ecf20Sopenharmony_ci if (ddbreadl(dev, 0) == 0xffffffff) { 2198c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "cannot read registers\n"); 2208c2ecf20Sopenharmony_ci stat = -ENODEV; 2218c2ecf20Sopenharmony_ci goto fail; 2228c2ecf20Sopenharmony_ci } 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci dev->link[0].ids.hwid = ddbreadl(dev, 0); 2258c2ecf20Sopenharmony_ci dev->link[0].ids.regmapid = ddbreadl(dev, 4); 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci dev_info(&pdev->dev, "HW %08x REGMAP %08x\n", 2288c2ecf20Sopenharmony_ci dev->link[0].ids.hwid, dev->link[0].ids.regmapid); 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci ddbwritel(dev, 0, DMA_BASE_READ); 2318c2ecf20Sopenharmony_ci ddbwritel(dev, 0, DMA_BASE_WRITE); 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ci stat = ddb_irq_init(dev); 2348c2ecf20Sopenharmony_ci if (stat < 0) 2358c2ecf20Sopenharmony_ci goto fail0; 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci if (ddb_init(dev) == 0) 2388c2ecf20Sopenharmony_ci return 0; 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_ci ddb_irq_exit(dev); 2418c2ecf20Sopenharmony_cifail0: 2428c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "fail0\n"); 2438c2ecf20Sopenharmony_ci ddb_msi_exit(dev); 2448c2ecf20Sopenharmony_cifail: 2458c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "fail\n"); 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci ddb_unmap(dev); 2488c2ecf20Sopenharmony_ci pci_set_drvdata(pdev, NULL); 2498c2ecf20Sopenharmony_ci pci_disable_device(pdev); 2508c2ecf20Sopenharmony_ci return stat; 2518c2ecf20Sopenharmony_ci} 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci/****************************************************************************/ 2548c2ecf20Sopenharmony_ci/****************************************************************************/ 2558c2ecf20Sopenharmony_ci/****************************************************************************/ 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci#define DDB_DEVICE_ANY(_device) \ 2588c2ecf20Sopenharmony_ci { PCI_DEVICE_SUB(DDVID, _device, DDVID, PCI_ANY_ID) } 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_cistatic const struct pci_device_id ddb_id_table[] = { 2618c2ecf20Sopenharmony_ci DDB_DEVICE_ANY(0x0002), 2628c2ecf20Sopenharmony_ci DDB_DEVICE_ANY(0x0003), 2638c2ecf20Sopenharmony_ci DDB_DEVICE_ANY(0x0005), 2648c2ecf20Sopenharmony_ci DDB_DEVICE_ANY(0x0006), 2658c2ecf20Sopenharmony_ci DDB_DEVICE_ANY(0x0007), 2668c2ecf20Sopenharmony_ci DDB_DEVICE_ANY(0x0008), 2678c2ecf20Sopenharmony_ci DDB_DEVICE_ANY(0x0009), 2688c2ecf20Sopenharmony_ci DDB_DEVICE_ANY(0x0011), 2698c2ecf20Sopenharmony_ci DDB_DEVICE_ANY(0x0012), 2708c2ecf20Sopenharmony_ci DDB_DEVICE_ANY(0x0013), 2718c2ecf20Sopenharmony_ci DDB_DEVICE_ANY(0x0201), 2728c2ecf20Sopenharmony_ci DDB_DEVICE_ANY(0x0203), 2738c2ecf20Sopenharmony_ci DDB_DEVICE_ANY(0x0210), 2748c2ecf20Sopenharmony_ci DDB_DEVICE_ANY(0x0220), 2758c2ecf20Sopenharmony_ci DDB_DEVICE_ANY(0x0320), 2768c2ecf20Sopenharmony_ci DDB_DEVICE_ANY(0x0321), 2778c2ecf20Sopenharmony_ci DDB_DEVICE_ANY(0x0322), 2788c2ecf20Sopenharmony_ci DDB_DEVICE_ANY(0x0323), 2798c2ecf20Sopenharmony_ci DDB_DEVICE_ANY(0x0328), 2808c2ecf20Sopenharmony_ci DDB_DEVICE_ANY(0x0329), 2818c2ecf20Sopenharmony_ci {0} 2828c2ecf20Sopenharmony_ci}; 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(pci, ddb_id_table); 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_cistatic struct pci_driver ddb_pci_driver = { 2878c2ecf20Sopenharmony_ci .name = "ddbridge", 2888c2ecf20Sopenharmony_ci .id_table = ddb_id_table, 2898c2ecf20Sopenharmony_ci .probe = ddb_probe, 2908c2ecf20Sopenharmony_ci .remove = ddb_remove, 2918c2ecf20Sopenharmony_ci}; 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_cistatic __init int module_init_ddbridge(void) 2948c2ecf20Sopenharmony_ci{ 2958c2ecf20Sopenharmony_ci int stat; 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci pr_info("Digital Devices PCIE bridge driver " 2988c2ecf20Sopenharmony_ci DDBRIDGE_VERSION 2998c2ecf20Sopenharmony_ci ", Copyright (C) 2010-17 Digital Devices GmbH\n"); 3008c2ecf20Sopenharmony_ci stat = ddb_init_ddbridge(); 3018c2ecf20Sopenharmony_ci if (stat < 0) 3028c2ecf20Sopenharmony_ci return stat; 3038c2ecf20Sopenharmony_ci stat = pci_register_driver(&ddb_pci_driver); 3048c2ecf20Sopenharmony_ci if (stat < 0) 3058c2ecf20Sopenharmony_ci ddb_exit_ddbridge(0, stat); 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_ci return stat; 3088c2ecf20Sopenharmony_ci} 3098c2ecf20Sopenharmony_ci 3108c2ecf20Sopenharmony_cistatic __exit void module_exit_ddbridge(void) 3118c2ecf20Sopenharmony_ci{ 3128c2ecf20Sopenharmony_ci pci_unregister_driver(&ddb_pci_driver); 3138c2ecf20Sopenharmony_ci ddb_exit_ddbridge(0, 0); 3148c2ecf20Sopenharmony_ci} 3158c2ecf20Sopenharmony_ci 3168c2ecf20Sopenharmony_cimodule_init(module_init_ddbridge); 3178c2ecf20Sopenharmony_cimodule_exit(module_exit_ddbridge); 3188c2ecf20Sopenharmony_ci 3198c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Digital Devices PCIe Bridge"); 3208c2ecf20Sopenharmony_ciMODULE_AUTHOR("Ralph and Marcus Metzler, Metzler Brothers Systementwicklung GbR"); 3218c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 3228c2ecf20Sopenharmony_ciMODULE_VERSION(DDBRIDGE_VERSION); 323