| // SPDX-License-Identifier: GPL-2.0-only |
| /* Copyright (C) 2015 - 2016 Thomas Körper, esd electronic system design gmbh |
| * Copyright (C) 2017 - 2023 Stefan Mätje, esd electronics gmbh |
| */ |
| |
| #include <linux/can/dev.h> |
| #include <linux/can.h> |
| #include <linux/can/netlink.h> |
| #include <linux/delay.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/ethtool.h> |
| #include <linux/interrupt.h> |
| #include <linux/io.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/netdevice.h> |
| #include <linux/pci.h> |
| |
| #include "esdacc.h" |
| |
| #define ESD_PCI_DEVICE_ID_PCIE402 0x0402 |
| |
| #define PCI402_FPGA_VER_MIN 0x003d |
| #define PCI402_MAX_CORES 6 |
| #define PCI402_BAR 0 |
| #define PCI402_IO_OV_OFFS 0 |
| #define PCI402_IO_PCIEP_OFFS 0x10000 |
| #define PCI402_IO_LEN_TOTAL 0x20000 |
| #define PCI402_IO_LEN_CORE 0x2000 |
| #define PCI402_PCICFG_MSICAP 0x50 |
| |
| #define PCI402_DMA_MASK DMA_BIT_MASK(32) |
| #define PCI402_DMA_SIZE ALIGN(0x10000, PAGE_SIZE) |
| |
| #define PCI402_PCIEP_OF_INT_ENABLE 0x0050 |
| #define PCI402_PCIEP_OF_BM_ADDR_LO 0x1000 |
| #define PCI402_PCIEP_OF_BM_ADDR_HI 0x1004 |
| #define PCI402_PCIEP_OF_MSI_ADDR_LO 0x1008 |
| #define PCI402_PCIEP_OF_MSI_ADDR_HI 0x100c |
| |
| struct pci402_card { |
| /* Actually mapped io space, all other iomem derived from this */ |
| void __iomem *addr; |
| void __iomem *addr_pciep; |
| |
| void *dma_buf; |
| dma_addr_t dma_hnd; |
| |
| struct acc_ov ov; |
| struct acc_core *cores; |
| |
| bool msi_enabled; |
| }; |
| |
| /* The BTR register capabilities described by the can_bittiming_const structures |
| * below are valid since esdACC version 0x0032. |
| */ |
| |
| /* Used if the esdACC FPGA is built as CAN-Classic version. */ |
| static const struct can_bittiming_const pci402_bittiming_const = { |
| .name = "esd_402", |
| .tseg1_min = 1, |
| .tseg1_max = 16, |
| .tseg2_min = 1, |
| .tseg2_max = 8, |
| .sjw_max = 4, |
| .brp_min = 1, |
| .brp_max = 512, |
| .brp_inc = 1, |
| }; |
| |
| /* Used if the esdACC FPGA is built as CAN-FD version. */ |
| static const struct can_bittiming_const pci402_bittiming_const_canfd = { |
| .name = "esd_402fd", |
| .tseg1_min = 1, |
| .tseg1_max = 256, |
| .tseg2_min = 1, |
| .tseg2_max = 128, |
| .sjw_max = 128, |
| .brp_min = 1, |
| .brp_max = 256, |
| .brp_inc = 1, |
| }; |
| |
| static const struct net_device_ops pci402_acc_netdev_ops = { |
| .ndo_open = acc_open, |
| .ndo_stop = acc_close, |
| .ndo_start_xmit = acc_start_xmit, |
| .ndo_change_mtu = can_change_mtu, |
| .ndo_eth_ioctl = can_eth_ioctl_hwts, |
| }; |
| |
| static const struct ethtool_ops pci402_acc_ethtool_ops = { |
| .get_ts_info = can_ethtool_op_get_ts_info_hwts, |
| }; |
| |
| static irqreturn_t pci402_interrupt(int irq, void *dev_id) |
| { |
| struct pci_dev *pdev = dev_id; |
| struct pci402_card *card = pci_get_drvdata(pdev); |
| irqreturn_t irq_status; |
| |
| irq_status = acc_card_interrupt(&card->ov, card->cores); |
| |
| return irq_status; |
| } |
| |
| static int pci402_set_msiconfig(struct pci_dev *pdev) |
| { |
| struct pci402_card *card = pci_get_drvdata(pdev); |
| u32 addr_lo_offs = 0; |
| u32 addr_lo = 0; |
| u32 addr_hi = 0; |
| u32 data = 0; |
| u16 csr = 0; |
| int err; |
| |
| /* The FPGA hard IP PCIe core implements a 64-bit MSI Capability |
| * Register Format |
| */ |
| err = pci_read_config_word(pdev, PCI402_PCICFG_MSICAP + PCI_MSI_FLAGS, &csr); |
| if (err) |
| goto failed; |
| |
| err = pci_read_config_dword(pdev, PCI402_PCICFG_MSICAP + PCI_MSI_ADDRESS_LO, |
| &addr_lo); |
| if (err) |
| goto failed; |
| err = pci_read_config_dword(pdev, PCI402_PCICFG_MSICAP + PCI_MSI_ADDRESS_HI, |
| &addr_hi); |
| if (err) |
| goto failed; |
| |
| err = pci_read_config_dword(pdev, PCI402_PCICFG_MSICAP + PCI_MSI_DATA_64, |
| &data); |
| if (err) |
| goto failed; |
| |
| addr_lo_offs = addr_lo & 0x0000ffff; |
| addr_lo &= 0xffff0000; |
| |
| if (addr_hi) |
| addr_lo |= 1; /* To enable 64-Bit addressing in PCIe endpoint */ |
| |
| if (!(csr & PCI_MSI_FLAGS_ENABLE)) { |
| err = -EINVAL; |
| goto failed; |
| } |
| |
| iowrite32(addr_lo, card->addr_pciep + PCI402_PCIEP_OF_MSI_ADDR_LO); |
| iowrite32(addr_hi, card->addr_pciep + PCI402_PCIEP_OF_MSI_ADDR_HI); |
| acc_ov_write32(&card->ov, ACC_OV_OF_MSI_ADDRESSOFFSET, addr_lo_offs); |
| acc_ov_write32(&card->ov, ACC_OV_OF_MSI_DATA, data); |
| |
| return 0; |
| |
| failed: |
| pci_warn(pdev, "Error while setting MSI configuration:\n" |
| "CSR: 0x%.4x, addr: 0x%.8x%.8x, offs: 0x%.4x, data: 0x%.8x\n", |
| csr, addr_hi, addr_lo, addr_lo_offs, data); |
| |
| return err; |
| } |
| |
| static int pci402_init_card(struct pci_dev *pdev) |
| { |
| struct pci402_card *card = pci_get_drvdata(pdev); |
| |
| card->ov.addr = card->addr + PCI402_IO_OV_OFFS; |
| card->addr_pciep = card->addr + PCI402_IO_PCIEP_OFFS; |
| |
| acc_reset_fpga(&card->ov); |
| acc_init_ov(&card->ov, &pdev->dev); |
| |
| if (card->ov.version < PCI402_FPGA_VER_MIN) { |
| pci_err(pdev, |
| "esdACC version (0x%.4x) outdated, please update\n", |
| card->ov.version); |
| return -EINVAL; |
| } |
| |
| if (card->ov.timestamp_frequency != ACC_TS_FREQ_80MHZ) { |
| pci_err(pdev, |
| "esdACC timestamp frequency of %uHz not supported by driver. Aborted.\n", |
| card->ov.timestamp_frequency); |
| return -EINVAL; |
| } |
| |
| if (card->ov.active_cores > PCI402_MAX_CORES) { |
| pci_err(pdev, |
| "Card with %u active cores not supported by driver. Aborted.\n", |
| card->ov.active_cores); |
| return -EINVAL; |
| } |
| card->cores = devm_kcalloc(&pdev->dev, card->ov.active_cores, |
| sizeof(struct acc_core), GFP_KERNEL); |
| if (!card->cores) |
| return -ENOMEM; |
| |
| if (card->ov.features & ACC_OV_REG_FEAT_MASK_CANFD) { |
| pci_warn(pdev, |
| "esdACC with CAN-FD feature detected. This driver doesn't support CAN-FD yet.\n"); |
| } |
| |
| #ifdef __LITTLE_ENDIAN |
| /* So card converts all busmastered data to LE for us: */ |
| acc_ov_set_bits(&card->ov, ACC_OV_OF_MODE, |
| ACC_OV_REG_MODE_MASK_ENDIAN_LITTLE); |
| #endif |
| |
| return 0; |
| } |
| |
| static int pci402_init_interrupt(struct pci_dev *pdev) |
| { |
| struct pci402_card *card = pci_get_drvdata(pdev); |
| int err; |
| |
| err = pci_enable_msi(pdev); |
| if (!err) { |
| err = pci402_set_msiconfig(pdev); |
| if (!err) { |
| card->msi_enabled = true; |
| acc_ov_set_bits(&card->ov, ACC_OV_OF_MODE, |
| ACC_OV_REG_MODE_MASK_MSI_ENABLE); |
| pci_dbg(pdev, "MSI preparation done\n"); |
| } |
| } |
| |
| err = devm_request_irq(&pdev->dev, pdev->irq, pci402_interrupt, |
| IRQF_SHARED, dev_name(&pdev->dev), pdev); |
| if (err) |
| goto failure_msidis; |
| |
| iowrite32(1, card->addr_pciep + PCI402_PCIEP_OF_INT_ENABLE); |
| |
| return 0; |
| |
| failure_msidis: |
| if (card->msi_enabled) { |
| acc_ov_clear_bits(&card->ov, ACC_OV_OF_MODE, |
| ACC_OV_REG_MODE_MASK_MSI_ENABLE); |
| pci_disable_msi(pdev); |
| card->msi_enabled = false; |
| } |
| |
| return err; |
| } |
| |
| static void pci402_finish_interrupt(struct pci_dev *pdev) |
| { |
| struct pci402_card *card = pci_get_drvdata(pdev); |
| |
| iowrite32(0, card->addr_pciep + PCI402_PCIEP_OF_INT_ENABLE); |
| devm_free_irq(&pdev->dev, pdev->irq, pdev); |
| |
| if (card->msi_enabled) { |
| acc_ov_clear_bits(&card->ov, ACC_OV_OF_MODE, |
| ACC_OV_REG_MODE_MASK_MSI_ENABLE); |
| pci_disable_msi(pdev); |
| card->msi_enabled = false; |
| } |
| } |
| |
| static int pci402_init_dma(struct pci_dev *pdev) |
| { |
| struct pci402_card *card = pci_get_drvdata(pdev); |
| int err; |
| |
| err = dma_set_coherent_mask(&pdev->dev, PCI402_DMA_MASK); |
| if (err) { |
| pci_err(pdev, "DMA set mask failed!\n"); |
| return err; |
| } |
| |
| /* The esdACC DMA engine needs the DMA buffer aligned to a 64k |
| * boundary. The DMA API guarantees to align the returned buffer to the |
| * smallest PAGE_SIZE order which is greater than or equal to the |
| * requested size. With PCI402_DMA_SIZE == 64kB this suffices here. |
| */ |
| card->dma_buf = dma_alloc_coherent(&pdev->dev, PCI402_DMA_SIZE, |
| &card->dma_hnd, GFP_KERNEL); |
| if (!card->dma_buf) |
| return -ENOMEM; |
| |
| acc_init_bm_ptr(&card->ov, card->cores, card->dma_buf); |
| |
| iowrite32(card->dma_hnd, |
| card->addr_pciep + PCI402_PCIEP_OF_BM_ADDR_LO); |
| iowrite32(0, card->addr_pciep + PCI402_PCIEP_OF_BM_ADDR_HI); |
| |
| pci_set_master(pdev); |
| |
| acc_ov_set_bits(&card->ov, ACC_OV_OF_MODE, |
| ACC_OV_REG_MODE_MASK_BM_ENABLE); |
| |
| return 0; |
| } |
| |
| static void pci402_finish_dma(struct pci_dev *pdev) |
| { |
| struct pci402_card *card = pci_get_drvdata(pdev); |
| int i; |
| |
| acc_ov_clear_bits(&card->ov, ACC_OV_OF_MODE, |
| ACC_OV_REG_MODE_MASK_BM_ENABLE); |
| |
| pci_clear_master(pdev); |
| |
| iowrite32(0, card->addr_pciep + PCI402_PCIEP_OF_BM_ADDR_LO); |
| iowrite32(0, card->addr_pciep + PCI402_PCIEP_OF_BM_ADDR_HI); |
| |
| card->ov.bmfifo.messages = NULL; |
| card->ov.bmfifo.irq_cnt = NULL; |
| for (i = 0; i < card->ov.active_cores; i++) { |
| struct acc_core *core = &card->cores[i]; |
| |
| core->bmfifo.messages = NULL; |
| core->bmfifo.irq_cnt = NULL; |
| } |
| |
| dma_free_coherent(&pdev->dev, PCI402_DMA_SIZE, card->dma_buf, |
| card->dma_hnd); |
| card->dma_buf = NULL; |
| } |
| |
| static void pci402_unregister_core(struct acc_core *core) |
| { |
| netdev_info(core->netdev, "unregister\n"); |
| unregister_candev(core->netdev); |
| |
| free_candev(core->netdev); |
| core->netdev = NULL; |
| } |
| |
| static int pci402_init_cores(struct pci_dev *pdev) |
| { |
| struct pci402_card *card = pci_get_drvdata(pdev); |
| int err; |
| int i; |
| |
| for (i = 0; i < card->ov.active_cores; i++) { |
| struct acc_core *core = &card->cores[i]; |
| struct acc_net_priv *priv; |
| struct net_device *netdev; |
| u32 fifo_config; |
| |
| core->addr = card->ov.addr + (i + 1) * PCI402_IO_LEN_CORE; |
| |
| fifo_config = acc_read32(core, ACC_CORE_OF_TXFIFO_CONFIG); |
| core->tx_fifo_size = (fifo_config >> 24); |
| if (core->tx_fifo_size <= 1) { |
| pci_err(pdev, "Invalid tx_fifo_size!\n"); |
| err = -EINVAL; |
| goto failure; |
| } |
| |
| netdev = alloc_candev(sizeof(*priv), core->tx_fifo_size); |
| if (!netdev) { |
| err = -ENOMEM; |
| goto failure; |
| } |
| core->netdev = netdev; |
| |
| netdev->flags |= IFF_ECHO; |
| netdev->dev_port = i; |
| netdev->netdev_ops = &pci402_acc_netdev_ops; |
| netdev->ethtool_ops = &pci402_acc_ethtool_ops; |
| SET_NETDEV_DEV(netdev, &pdev->dev); |
| |
| priv = netdev_priv(netdev); |
| priv->can.clock.freq = card->ov.core_frequency; |
| priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK | |
| CAN_CTRLMODE_LISTENONLY | |
| CAN_CTRLMODE_BERR_REPORTING | |
| CAN_CTRLMODE_CC_LEN8_DLC; |
| if (card->ov.features & ACC_OV_REG_FEAT_MASK_DAR) |
| priv->can.ctrlmode_supported |= CAN_CTRLMODE_ONE_SHOT; |
| if (card->ov.features & ACC_OV_REG_FEAT_MASK_CANFD) |
| priv->can.bittiming_const = &pci402_bittiming_const_canfd; |
| else |
| priv->can.bittiming_const = &pci402_bittiming_const; |
| priv->can.do_set_bittiming = acc_set_bittiming; |
| priv->can.do_set_mode = acc_set_mode; |
| priv->can.do_get_berr_counter = acc_get_berr_counter; |
| |
| priv->core = core; |
| priv->ov = &card->ov; |
| |
| err = register_candev(netdev); |
| if (err) { |
| free_candev(core->netdev); |
| core->netdev = NULL; |
| goto failure; |
| } |
| |
| netdev_info(netdev, "registered\n"); |
| } |
| |
| return 0; |
| |
| failure: |
| for (i--; i >= 0; i--) |
| pci402_unregister_core(&card->cores[i]); |
| |
| return err; |
| } |
| |
| static void pci402_finish_cores(struct pci_dev *pdev) |
| { |
| struct pci402_card *card = pci_get_drvdata(pdev); |
| int i; |
| |
| for (i = 0; i < card->ov.active_cores; i++) |
| pci402_unregister_core(&card->cores[i]); |
| } |
| |
| static int pci402_probe(struct pci_dev *pdev, const struct pci_device_id *ent) |
| { |
| struct pci402_card *card = NULL; |
| int err; |
| |
| err = pci_enable_device(pdev); |
| if (err) |
| return err; |
| |
| card = devm_kzalloc(&pdev->dev, sizeof(*card), GFP_KERNEL); |
| if (!card) { |
| err = -ENOMEM; |
| goto failure_disable_pci; |
| } |
| |
| pci_set_drvdata(pdev, card); |
| |
| err = pci_request_regions(pdev, pci_name(pdev)); |
| if (err) |
| goto failure_disable_pci; |
| |
| card->addr = pci_iomap(pdev, PCI402_BAR, PCI402_IO_LEN_TOTAL); |
| if (!card->addr) { |
| err = -ENOMEM; |
| goto failure_release_regions; |
| } |
| |
| err = pci402_init_card(pdev); |
| if (err) |
| goto failure_unmap; |
| |
| err = pci402_init_dma(pdev); |
| if (err) |
| goto failure_unmap; |
| |
| err = pci402_init_interrupt(pdev); |
| if (err) |
| goto failure_finish_dma; |
| |
| err = pci402_init_cores(pdev); |
| if (err) |
| goto failure_finish_interrupt; |
| |
| return 0; |
| |
| failure_finish_interrupt: |
| pci402_finish_interrupt(pdev); |
| |
| failure_finish_dma: |
| pci402_finish_dma(pdev); |
| |
| failure_unmap: |
| pci_iounmap(pdev, card->addr); |
| |
| failure_release_regions: |
| pci_release_regions(pdev); |
| |
| failure_disable_pci: |
| pci_disable_device(pdev); |
| |
| return err; |
| } |
| |
| static void pci402_remove(struct pci_dev *pdev) |
| { |
| struct pci402_card *card = pci_get_drvdata(pdev); |
| |
| pci402_finish_interrupt(pdev); |
| pci402_finish_cores(pdev); |
| pci402_finish_dma(pdev); |
| pci_iounmap(pdev, card->addr); |
| pci_release_regions(pdev); |
| pci_disable_device(pdev); |
| } |
| |
| static const struct pci_device_id pci402_tbl[] = { |
| { |
| .vendor = PCI_VENDOR_ID_ESDGMBH, |
| .device = ESD_PCI_DEVICE_ID_PCIE402, |
| .subvendor = PCI_VENDOR_ID_ESDGMBH, |
| .subdevice = PCI_ANY_ID, |
| }, |
| { 0, } |
| }; |
| MODULE_DEVICE_TABLE(pci, pci402_tbl); |
| |
| static struct pci_driver pci402_driver = { |
| .name = KBUILD_MODNAME, |
| .id_table = pci402_tbl, |
| .probe = pci402_probe, |
| .remove = pci402_remove, |
| }; |
| module_pci_driver(pci402_driver); |
| |
| MODULE_DESCRIPTION("Socket-CAN driver for esd CAN 402 card family with esdACC core on PCIe"); |
| MODULE_AUTHOR("Thomas Körper <socketcan@esd.eu>"); |
| MODULE_AUTHOR("Stefan Mätje <stefan.maetje@esd.eu>"); |
| MODULE_LICENSE("GPL"); |