| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Intel MIC Platform Software Stack (MPSS) |
| * |
| * Copyright(c) 2013 Intel Corporation. |
| * |
| * Disclaimer: The codes contained in these modules may be specific to |
| * the Intel Software Development Platform codenamed: Knights Ferry, and |
| * the Intel product codenamed: Knights Corner, and are not backward |
| * compatible with other Intel products. Additionally, Intel will NOT |
| * support the codes or instruction set in future products. |
| * |
| * Intel MIC Card driver. |
| */ |
| #include <linux/module.h> |
| #include <linux/pci.h> |
| #include <linux/platform_device.h> |
| |
| #include "../common/mic_dev.h" |
| #include "mic_device.h" |
| #include "mic_x100.h" |
| |
| static const char mic_driver_name[] = "mic"; |
| |
| static struct mic_driver g_drv; |
| |
| /** |
| * mic_read_spad - read from the scratchpad register |
| * @mdev: pointer to mic_device instance |
| * @idx: index to scratchpad register, 0 based |
| * |
| * This function allows reading of the 32bit scratchpad register. |
| * |
| * RETURNS: An appropriate -ERRNO error value on error, or zero for success. |
| */ |
| u32 mic_read_spad(struct mic_device *mdev, unsigned int idx) |
| { |
| return mic_mmio_read(&mdev->mmio, |
| MIC_X100_SBOX_BASE_ADDRESS + |
| MIC_X100_SBOX_SPAD0 + idx * 4); |
| } |
| |
| /** |
| * __mic_send_intr - Send interrupt to Host. |
| * @mdev: pointer to mic_device instance |
| * @doorbell: Doorbell number. |
| */ |
| void mic_send_intr(struct mic_device *mdev, int doorbell) |
| { |
| struct mic_mw *mw = &mdev->mmio; |
| |
| if (doorbell > MIC_X100_MAX_DOORBELL_IDX) |
| return; |
| /* Ensure that the interrupt is ordered w.r.t previous stores. */ |
| wmb(); |
| mic_mmio_write(mw, MIC_X100_SBOX_SDBIC0_DBREQ_BIT, |
| MIC_X100_SBOX_BASE_ADDRESS + |
| (MIC_X100_SBOX_SDBIC0 + (4 * doorbell))); |
| } |
| |
| /* |
| * mic_x100_send_sbox_intr - Send an MIC_X100_SBOX interrupt to MIC. |
| */ |
| static void mic_x100_send_sbox_intr(struct mic_mw *mw, int doorbell) |
| { |
| u64 apic_icr_offset = MIC_X100_SBOX_APICICR0 + doorbell * 8; |
| u32 apicicr_low = mic_mmio_read(mw, MIC_X100_SBOX_BASE_ADDRESS + |
| apic_icr_offset); |
| |
| /* for MIC we need to make sure we "hit" the send_icr bit (13) */ |
| apicicr_low = (apicicr_low | (1 << 13)); |
| /* |
| * Ensure that the interrupt is ordered w.r.t. previous stores |
| * to main memory. Fence instructions are not implemented in X100 |
| * since execution is in order but a compiler barrier is still |
| * required. |
| */ |
| wmb(); |
| mic_mmio_write(mw, apicicr_low, |
| MIC_X100_SBOX_BASE_ADDRESS + apic_icr_offset); |
| } |
| |
| static void mic_x100_send_rdmasr_intr(struct mic_mw *mw, int doorbell) |
| { |
| int rdmasr_offset = MIC_X100_SBOX_RDMASR0 + (doorbell << 2); |
| /* |
| * Ensure that the interrupt is ordered w.r.t. previous stores |
| * to main memory. Fence instructions are not implemented in X100 |
| * since execution is in order but a compiler barrier is still |
| * required. |
| */ |
| wmb(); |
| mic_mmio_write(mw, 0, MIC_X100_SBOX_BASE_ADDRESS + rdmasr_offset); |
| } |
| |
| /** |
| * mic_ack_interrupt - Device specific interrupt handling. |
| * @mdev: pointer to mic_device instance |
| * |
| * Returns: bitmask of doorbell events triggered. |
| */ |
| u32 mic_ack_interrupt(struct mic_device *mdev) |
| { |
| return 0; |
| } |
| |
| static inline int mic_get_sbox_irq(int db) |
| { |
| return MIC_X100_IRQ_BASE + db; |
| } |
| |
| static inline int mic_get_rdmasr_irq(int index) |
| { |
| return MIC_X100_RDMASR_IRQ_BASE + index; |
| } |
| |
| void mic_send_p2p_intr(int db, struct mic_mw *mw) |
| { |
| int rdmasr_index; |
| |
| if (db < MIC_X100_NUM_SBOX_IRQ) { |
| mic_x100_send_sbox_intr(mw, db); |
| } else { |
| rdmasr_index = db - MIC_X100_NUM_SBOX_IRQ; |
| mic_x100_send_rdmasr_intr(mw, rdmasr_index); |
| } |
| } |
| |
| /** |
| * mic_hw_intr_init - Initialize h/w specific interrupt |
| * information. |
| * @mdrv: pointer to mic_driver |
| */ |
| void mic_hw_intr_init(struct mic_driver *mdrv) |
| { |
| mdrv->intr_info.num_intr = MIC_X100_NUM_SBOX_IRQ + |
| MIC_X100_NUM_RDMASR_IRQ; |
| } |
| |
| /** |
| * mic_db_to_irq - Retrieve irq number corresponding to a doorbell. |
| * @mdrv: pointer to mic_driver |
| * @db: The doorbell obtained for which the irq is needed. Doorbell |
| * may correspond to an sbox doorbell or an rdmasr index. |
| * |
| * Returns the irq corresponding to the doorbell. |
| */ |
| int mic_db_to_irq(struct mic_driver *mdrv, int db) |
| { |
| int rdmasr_index; |
| |
| /* |
| * The total number of doorbell interrupts on the card are 16. Indices |
| * 0-8 falls in the SBOX category and 8-15 fall in the RDMASR category. |
| */ |
| if (db < MIC_X100_NUM_SBOX_IRQ) { |
| return mic_get_sbox_irq(db); |
| } else { |
| rdmasr_index = db - MIC_X100_NUM_SBOX_IRQ; |
| return mic_get_rdmasr_irq(rdmasr_index); |
| } |
| } |
| |
| /* |
| * mic_card_map - Allocate virtual address for a remote memory region. |
| * @mdev: pointer to mic_device instance. |
| * @addr: Remote DMA address. |
| * @size: Size of the region. |
| * |
| * Returns: Virtual address backing the remote memory region. |
| */ |
| void __iomem * |
| mic_card_map(struct mic_device *mdev, dma_addr_t addr, size_t size) |
| { |
| return ioremap(addr, size); |
| } |
| |
| /* |
| * mic_card_unmap - Unmap the virtual address for a remote memory region. |
| * @mdev: pointer to mic_device instance. |
| * @addr: Virtual address for remote memory region. |
| * |
| * Returns: None. |
| */ |
| void mic_card_unmap(struct mic_device *mdev, void __iomem *addr) |
| { |
| iounmap(addr); |
| } |
| |
| static inline struct mic_driver *mbdev_to_mdrv(struct mbus_device *mbdev) |
| { |
| return dev_get_drvdata(mbdev->dev.parent); |
| } |
| |
| static struct mic_irq * |
| _mic_request_threaded_irq(struct mbus_device *mbdev, |
| irq_handler_t handler, irq_handler_t thread_fn, |
| const char *name, void *data, int intr_src) |
| { |
| int rc = 0; |
| unsigned int irq = intr_src; |
| unsigned long cookie = irq; |
| |
| rc = request_threaded_irq(irq, handler, thread_fn, 0, name, data); |
| if (rc) { |
| dev_err(mbdev_to_mdrv(mbdev)->dev, |
| "request_threaded_irq failed rc = %d\n", rc); |
| return ERR_PTR(rc); |
| } |
| return (struct mic_irq *)cookie; |
| } |
| |
| static void _mic_free_irq(struct mbus_device *mbdev, |
| struct mic_irq *cookie, void *data) |
| { |
| unsigned long irq = (unsigned long)cookie; |
| free_irq(irq, data); |
| } |
| |
| static void _mic_ack_interrupt(struct mbus_device *mbdev, int num) |
| { |
| mic_ack_interrupt(&mbdev_to_mdrv(mbdev)->mdev); |
| } |
| |
| static struct mbus_hw_ops mbus_hw_ops = { |
| .request_threaded_irq = _mic_request_threaded_irq, |
| .free_irq = _mic_free_irq, |
| .ack_interrupt = _mic_ack_interrupt, |
| }; |
| |
| static int __init mic_probe(struct platform_device *pdev) |
| { |
| struct mic_driver *mdrv = &g_drv; |
| struct mic_device *mdev = &mdrv->mdev; |
| int rc = 0; |
| |
| mdrv->dev = &pdev->dev; |
| snprintf(mdrv->name, sizeof(mic_driver_name), mic_driver_name); |
| |
| /* FIXME: use dma_set_mask_and_coherent() and check result */ |
| dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); |
| |
| mdev->mmio.pa = MIC_X100_MMIO_BASE; |
| mdev->mmio.len = MIC_X100_MMIO_LEN; |
| mdev->mmio.va = devm_ioremap(&pdev->dev, MIC_X100_MMIO_BASE, |
| MIC_X100_MMIO_LEN); |
| if (!mdev->mmio.va) { |
| dev_err(&pdev->dev, "Cannot remap MMIO BAR\n"); |
| rc = -EIO; |
| goto done; |
| } |
| mic_hw_intr_init(mdrv); |
| platform_set_drvdata(pdev, mdrv); |
| mdrv->dma_mbdev = mbus_register_device(mdrv->dev, MBUS_DEV_DMA_MIC, |
| NULL, &mbus_hw_ops, 0, |
| mdrv->mdev.mmio.va); |
| if (IS_ERR(mdrv->dma_mbdev)) { |
| rc = PTR_ERR(mdrv->dma_mbdev); |
| dev_err(&pdev->dev, "mbus_add_device failed rc %d\n", rc); |
| goto done; |
| } |
| rc = mic_driver_init(mdrv); |
| if (rc) { |
| dev_err(&pdev->dev, "mic_driver_init failed rc %d\n", rc); |
| goto remove_dma; |
| } |
| done: |
| return rc; |
| remove_dma: |
| mbus_unregister_device(mdrv->dma_mbdev); |
| return rc; |
| } |
| |
| static int mic_remove(struct platform_device *pdev) |
| { |
| struct mic_driver *mdrv = &g_drv; |
| |
| mic_driver_uninit(mdrv); |
| mbus_unregister_device(mdrv->dma_mbdev); |
| return 0; |
| } |
| |
| static void mic_platform_shutdown(struct platform_device *pdev) |
| { |
| mic_remove(pdev); |
| } |
| |
| static struct platform_driver __refdata mic_platform_driver = { |
| .probe = mic_probe, |
| .remove = mic_remove, |
| .shutdown = mic_platform_shutdown, |
| .driver = { |
| .name = mic_driver_name, |
| }, |
| }; |
| |
| static struct platform_device *mic_platform_dev; |
| |
| static int __init mic_init(void) |
| { |
| int ret; |
| struct cpuinfo_x86 *c = &cpu_data(0); |
| |
| if (!(c->x86 == 11 && c->x86_model == 1)) { |
| ret = -ENODEV; |
| pr_err("%s not running on X100 ret %d\n", __func__, ret); |
| goto done; |
| } |
| |
| request_module("mic_x100_dma"); |
| mic_init_card_debugfs(); |
| |
| mic_platform_dev = platform_device_register_simple(mic_driver_name, |
| 0, NULL, 0); |
| ret = PTR_ERR_OR_ZERO(mic_platform_dev); |
| if (ret) { |
| pr_err("platform_device_register_full ret %d\n", ret); |
| goto cleanup_debugfs; |
| } |
| ret = platform_driver_register(&mic_platform_driver); |
| if (ret) { |
| pr_err("platform_driver_register ret %d\n", ret); |
| goto device_unregister; |
| } |
| return ret; |
| |
| device_unregister: |
| platform_device_unregister(mic_platform_dev); |
| cleanup_debugfs: |
| mic_exit_card_debugfs(); |
| done: |
| return ret; |
| } |
| |
| static void __exit mic_exit(void) |
| { |
| platform_driver_unregister(&mic_platform_driver); |
| platform_device_unregister(mic_platform_dev); |
| mic_exit_card_debugfs(); |
| } |
| |
| module_init(mic_init); |
| module_exit(mic_exit); |
| |
| MODULE_AUTHOR("Intel Corporation"); |
| MODULE_DESCRIPTION("Intel(R) MIC X100 Card driver"); |
| MODULE_LICENSE("GPL v2"); |