| /* | |
| * Copyright (C) 2016 Cavium, Inc. | |
| * | |
| * This program is free software; you can redistribute it and/or modify it | |
| * under the terms of version 2 of the GNU General Public License | |
| * as published by the Free Software Foundation. | |
| */ | |
| #include <linux/acpi.h> | |
| #include <linux/module.h> | |
| #include <linux/interrupt.h> | |
| #include <linux/pci.h> | |
| #include <linux/netdevice.h> | |
| #include <linux/etherdevice.h> | |
| #include <linux/phy.h> | |
| #include <linux/of.h> | |
| #include <linux/of_mdio.h> | |
| #include <linux/of_net.h> | |
| #include "nic.h" | |
| #include "thunder_bgx.h" | |
| #define DRV_NAME "thunder-xcv" | |
| #define DRV_VERSION "1.0" | |
| /* Register offsets */ | |
| #define XCV_RESET 0x00 | |
| #define PORT_EN BIT_ULL(63) | |
| #define CLK_RESET BIT_ULL(15) | |
| #define DLL_RESET BIT_ULL(11) | |
| #define COMP_EN BIT_ULL(7) | |
| #define TX_PKT_RESET BIT_ULL(3) | |
| #define TX_DATA_RESET BIT_ULL(2) | |
| #define RX_PKT_RESET BIT_ULL(1) | |
| #define RX_DATA_RESET BIT_ULL(0) | |
| #define XCV_DLL_CTL 0x10 | |
| #define CLKRX_BYP BIT_ULL(23) | |
| #define CLKTX_BYP BIT_ULL(15) | |
| #define XCV_COMP_CTL 0x20 | |
| #define DRV_BYP BIT_ULL(63) | |
| #define XCV_CTL 0x30 | |
| #define XCV_INT 0x40 | |
| #define XCV_INT_W1S 0x48 | |
| #define XCV_INT_ENA_W1C 0x50 | |
| #define XCV_INT_ENA_W1S 0x58 | |
| #define XCV_INBND_STATUS 0x80 | |
| #define XCV_BATCH_CRD_RET 0x100 | |
| struct xcv { | |
| void __iomem *reg_base; | |
| struct pci_dev *pdev; | |
| }; | |
| static struct xcv *xcv; | |
| /* Supported devices */ | |
| static const struct pci_device_id xcv_id_table[] = { | |
| { PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, 0xA056) }, | |
| { 0, } /* end of table */ | |
| }; | |
| MODULE_AUTHOR("Cavium Inc"); | |
| MODULE_DESCRIPTION("Cavium Thunder RGX/XCV Driver"); | |
| MODULE_LICENSE("GPL v2"); | |
| MODULE_VERSION(DRV_VERSION); | |
| MODULE_DEVICE_TABLE(pci, xcv_id_table); | |
| void xcv_init_hw(void) | |
| { | |
| u64 cfg; | |
| /* Take DLL out of reset */ | |
| cfg = readq_relaxed(xcv->reg_base + XCV_RESET); | |
| cfg &= ~DLL_RESET; | |
| writeq_relaxed(cfg, xcv->reg_base + XCV_RESET); | |
| /* Take clock tree out of reset */ | |
| cfg = readq_relaxed(xcv->reg_base + XCV_RESET); | |
| cfg &= ~CLK_RESET; | |
| writeq_relaxed(cfg, xcv->reg_base + XCV_RESET); | |
| /* Wait for DLL to lock */ | |
| msleep(1); | |
| /* Configure DLL - enable or bypass | |
| * TX no bypass, RX bypass | |
| */ | |
| cfg = readq_relaxed(xcv->reg_base + XCV_DLL_CTL); | |
| cfg &= ~0xFF03; | |
| cfg |= CLKRX_BYP; | |
| writeq_relaxed(cfg, xcv->reg_base + XCV_DLL_CTL); | |
| /* Enable compensation controller and force the | |
| * write to be visible to HW by readig back. | |
| */ | |
| cfg = readq_relaxed(xcv->reg_base + XCV_RESET); | |
| cfg |= COMP_EN; | |
| writeq_relaxed(cfg, xcv->reg_base + XCV_RESET); | |
| readq_relaxed(xcv->reg_base + XCV_RESET); | |
| /* Wait for compensation state machine to lock */ | |
| msleep(10); | |
| /* enable the XCV block */ | |
| cfg = readq_relaxed(xcv->reg_base + XCV_RESET); | |
| cfg |= PORT_EN; | |
| writeq_relaxed(cfg, xcv->reg_base + XCV_RESET); | |
| cfg = readq_relaxed(xcv->reg_base + XCV_RESET); | |
| cfg |= CLK_RESET; | |
| writeq_relaxed(cfg, xcv->reg_base + XCV_RESET); | |
| } | |
| EXPORT_SYMBOL(xcv_init_hw); | |
| void xcv_setup_link(bool link_up, int link_speed) | |
| { | |
| u64 cfg; | |
| int speed = 2; | |
| if (!xcv) { | |
| dev_err(&xcv->pdev->dev, | |
| "XCV init not done, probe may have failed\n"); | |
| return; | |
| } | |
| if (link_speed == 100) | |
| speed = 1; | |
| else if (link_speed == 10) | |
| speed = 0; | |
| if (link_up) { | |
| /* set operating speed */ | |
| cfg = readq_relaxed(xcv->reg_base + XCV_CTL); | |
| cfg &= ~0x03; | |
| cfg |= speed; | |
| writeq_relaxed(cfg, xcv->reg_base + XCV_CTL); | |
| /* Reset datapaths */ | |
| cfg = readq_relaxed(xcv->reg_base + XCV_RESET); | |
| cfg |= TX_DATA_RESET | RX_DATA_RESET; | |
| writeq_relaxed(cfg, xcv->reg_base + XCV_RESET); | |
| /* Enable the packet flow */ | |
| cfg = readq_relaxed(xcv->reg_base + XCV_RESET); | |
| cfg |= TX_PKT_RESET | RX_PKT_RESET; | |
| writeq_relaxed(cfg, xcv->reg_base + XCV_RESET); | |
| /* Return credits to RGX */ | |
| writeq_relaxed(0x01, xcv->reg_base + XCV_BATCH_CRD_RET); | |
| } else { | |
| /* Disable packet flow */ | |
| cfg = readq_relaxed(xcv->reg_base + XCV_RESET); | |
| cfg &= ~(TX_PKT_RESET | RX_PKT_RESET); | |
| writeq_relaxed(cfg, xcv->reg_base + XCV_RESET); | |
| readq_relaxed(xcv->reg_base + XCV_RESET); | |
| } | |
| } | |
| EXPORT_SYMBOL(xcv_setup_link); | |
| static int xcv_probe(struct pci_dev *pdev, const struct pci_device_id *ent) | |
| { | |
| int err; | |
| struct device *dev = &pdev->dev; | |
| xcv = devm_kzalloc(dev, sizeof(struct xcv), GFP_KERNEL); | |
| if (!xcv) | |
| return -ENOMEM; | |
| xcv->pdev = pdev; | |
| pci_set_drvdata(pdev, xcv); | |
| err = pci_enable_device(pdev); | |
| if (err) { | |
| dev_err(dev, "Failed to enable PCI device\n"); | |
| goto err_kfree; | |
| } | |
| err = pci_request_regions(pdev, DRV_NAME); | |
| if (err) { | |
| dev_err(dev, "PCI request regions failed 0x%x\n", err); | |
| goto err_disable_device; | |
| } | |
| /* MAP configuration registers */ | |
| xcv->reg_base = pcim_iomap(pdev, PCI_CFG_REG_BAR_NUM, 0); | |
| if (!xcv->reg_base) { | |
| dev_err(dev, "XCV: Cannot map CSR memory space, aborting\n"); | |
| err = -ENOMEM; | |
| goto err_release_regions; | |
| } | |
| return 0; | |
| err_release_regions: | |
| pci_release_regions(pdev); | |
| err_disable_device: | |
| pci_disable_device(pdev); | |
| err_kfree: | |
| devm_kfree(dev, xcv); | |
| xcv = NULL; | |
| return err; | |
| } | |
| static void xcv_remove(struct pci_dev *pdev) | |
| { | |
| struct device *dev = &pdev->dev; | |
| if (xcv) { | |
| devm_kfree(dev, xcv); | |
| xcv = NULL; | |
| } | |
| pci_release_regions(pdev); | |
| pci_disable_device(pdev); | |
| } | |
| static struct pci_driver xcv_driver = { | |
| .name = DRV_NAME, | |
| .id_table = xcv_id_table, | |
| .probe = xcv_probe, | |
| .remove = xcv_remove, | |
| }; | |
| static int __init xcv_init_module(void) | |
| { | |
| pr_info("%s, ver %s\n", DRV_NAME, DRV_VERSION); | |
| return pci_register_driver(&xcv_driver); | |
| } | |
| static void __exit xcv_cleanup_module(void) | |
| { | |
| pci_unregister_driver(&xcv_driver); | |
| } | |
| module_init(xcv_init_module); | |
| module_exit(xcv_cleanup_module); |