/** Network Driver for Beckhoff CCAT communication controller Copyright (C) 2014-2015 Beckhoff Automation GmbH Author: Patrick Bruenn This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include "module.h" MODULE_DESCRIPTION(DRV_DESCRIPTION); MODULE_AUTHOR("Patrick Bruenn "); MODULE_LICENSE("GPL"); MODULE_VERSION(DRV_VERSION); #if (LINUX_VERSION_CODE < KERNEL_VERSION(3,12,27)) /* * Set both the DMA mask and the coherent DMA mask to the same thing. * Note that we don't check the return value from dma_set_coherent_mask() * as the DMA API guarantees that the coherent DMA mask can be set to * the same or smaller than the streaming DMA mask. */ static inline int dma_set_mask_and_coherent(struct device *dev, u64 mask) { int rc = dma_set_mask(dev, mask); if (rc == 0) dma_set_coherent_mask(dev, mask); return rc; } #endif /** * configure the drivers capabilities here */ static const struct ccat_driver *const drivers[] = { #ifdef CONFIG_GENERIC_ISA_DMA ð_dma_driver, /* load Ethernet MAC/EtherCAT Master driver with DMA support from netdev.c */ #endif ð_eim_driver, /* load Ethernet MAC/EtherCAT Master driver without DMA support from */ #ifdef CONFIG_GPIO &gpio_driver, /* load GPIO driver from gpio.c */ #endif &sram_driver, /* load SRAM driver from sram.c */ &update_driver, /* load Update driver from update.c */ }; static int __init ccat_class_init(struct ccat_class *base) { if (1 == atomic_inc_return(&base->instances)) { if (alloc_chrdev_region (&base->dev, 0, base->count, KBUILD_MODNAME)) { pr_warn("alloc_chrdev_region() for '%s' failed\n", base->name); return -1; } base->class = class_create(THIS_MODULE, base->name); if (!base->class) { pr_warn("Create device class '%s' failed\n", base->name); unregister_chrdev_region(base->dev, base->count); return -1; } } return 0; } static void ccat_class_exit(struct ccat_class *base) { if (!atomic_dec_return(&base->instances)) { class_destroy(base->class); unregister_chrdev_region(base->dev, base->count); } } static void free_ccat_cdev(struct ccat_cdev *ccdev) { ccat_class_exit(ccdev->class); ccdev->dev = 0; } static struct ccat_cdev *alloc_ccat_cdev(struct ccat_class *base) { int i = 0; ccat_class_init(base); for (i = 0; i < base->count; ++i) { if (base->devices[i].dev == 0) { base->devices[i].dev = MKDEV(MAJOR(base->dev), i); return &base->devices[i]; } } pr_warn("exceeding max. number of '%s' devices (%d)\n", base->class->name, base->count); atomic_dec_return(&base->instances); return NULL; } static int ccat_cdev_init(struct cdev *cdev, dev_t dev, struct class *class, struct file_operations *fops) { if (!device_create (class, NULL, dev, NULL, "%s%d", class->name, MINOR(dev))) { pr_warn("device_create() failed\n"); return -1; } cdev_init(cdev, fops); cdev->owner = fops->owner; if (cdev_add(cdev, dev, 1)) { pr_warn("add update device failed\n"); device_destroy(class, dev); return -1; } pr_info("registered %s%d.\n", class->name, MINOR(dev)); return 0; } int ccat_cdev_open(struct inode *const i, struct file *const f) { struct ccat_cdev *ccdev = container_of(i->i_cdev, struct ccat_cdev, cdev); struct cdev_buffer *buf; if (!atomic_dec_and_test(&ccdev->in_use)) { atomic_inc(&ccdev->in_use); return -EBUSY; } buf = kzalloc(sizeof(*buf) + ccdev->iosize, GFP_KERNEL); if (!buf) { atomic_inc(&ccdev->in_use); return -ENOMEM; } buf->ccdev = ccdev; f->private_data = buf; return 0; } int ccat_cdev_probe(struct ccat_function *func, struct ccat_class *cdev_class, size_t iosize) { struct ccat_cdev *const ccdev = alloc_ccat_cdev(cdev_class); if (!ccdev) { return -ENOMEM; } ccdev->ioaddr = func->ccat->bar_0 + func->info.addr; ccdev->iosize = iosize; atomic_set(&ccdev->in_use, 1); if (ccat_cdev_init (&ccdev->cdev, ccdev->dev, cdev_class->class, &cdev_class->fops)) { pr_warn("ccat_cdev_probe() failed\n"); free_ccat_cdev(ccdev); return -1; } ccdev->class = cdev_class; func->private_data = ccdev; return 0; } int ccat_cdev_release(struct inode *const i, struct file *const f) { const struct cdev_buffer *const buf = f->private_data; struct ccat_cdev *const ccdev = buf->ccdev; kfree(f->private_data); atomic_inc(&ccdev->in_use); return 0; } void ccat_cdev_remove(struct ccat_function *func) { struct ccat_cdev *const ccdev = func->private_data; cdev_del(&ccdev->cdev); device_destroy(ccdev->class->class, ccdev->dev); free_ccat_cdev(ccdev); } static const struct ccat_driver *ccat_function_connect(struct ccat_function *const func) { int i; for (i = 0; i < ARRAY_SIZE(drivers); ++i) { if (func->info.type == drivers[i]->type) { return drivers[i]->probe(func) ? NULL : drivers[i]; } } return NULL; } /** * Initialize all available CCAT functions. * * Return: count of failed functions */ static int ccat_functions_init(struct ccat_device *const ccatdev) { static const size_t block_size = sizeof(struct ccat_info_block); struct ccat_function *next = kzalloc(sizeof(*next), GFP_KERNEL); void __iomem *addr = ccatdev->bar_0; /** first block is the CCAT information block entry */ const u8 num_func = ioread8(addr + 4); /** number of CCAT function blocks is at offset 0x4 */ const void __iomem *end = addr + (block_size * num_func); INIT_LIST_HEAD(&ccatdev->functions); for (; addr < end && next; addr += block_size) { memcpy_fromio(&next->info, addr, sizeof(next->info)); if (CCATINFO_NOTUSED != next->info.type) { next->ccat = ccatdev; next->drv = ccat_function_connect(next); if (next->drv) { list_add(&next->list, &ccatdev->functions); next = kzalloc(sizeof(*next), GFP_KERNEL); } } } kfree(next); return list_empty(&ccatdev->functions); } /** * Destroy all previously initialized CCAT functions */ static void ccat_functions_remove(struct ccat_device *const dev) { struct ccat_function *func; struct ccat_function *tmp; list_for_each_entry_safe(func, tmp, &dev->functions, list) { if (func->drv) { func->drv->remove(func); func->drv = NULL; } list_del(&func->list); kfree(func); } } #ifdef CONFIG_GENERIC_ISA_DMA static int ccat_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { struct ccat_device *ccatdev; u8 rev; int status; ccatdev = devm_kzalloc(&pdev->dev, sizeof(*ccatdev), GFP_KERNEL); if (!ccatdev) { pr_err("%s() out of memory.\n", __FUNCTION__); return -ENOMEM; } ccatdev->pdev = pdev; pci_set_drvdata(pdev, ccatdev); status = pci_enable_device_mem(pdev); if (status) { pr_err("enable %s failed: %d\n", pdev->dev.kobj.name, status); return status; } status = pci_read_config_byte(pdev, PCI_REVISION_ID, &rev); if (status) { pr_err("read CCAT pci revision failed with %d\n", status); goto disable_device; } status = pci_request_regions(pdev, KBUILD_MODNAME); if (status) { pr_err("allocate mem_regions failed.\n"); goto disable_device; } status = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); if (status) { status = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); if (status) { pr_err("No suitable DMA available, pci rev: %u\n", rev); goto release_regions; } pr_debug("32 bit DMA supported, pci rev: %u\n", rev); } else { pr_debug("64 bit DMA supported, pci rev: %u\n", rev); } ccatdev->bar_0 = pci_iomap(pdev, 0, 0); if (!ccatdev->bar_0) { pr_err("initialization of bar0 failed.\n"); status = -EIO; goto release_regions; } ccatdev->bar_2 = pci_iomap(pdev, 2, 0); if (!ccatdev->bar_2) { pr_warn("initialization of optional bar2 failed.\n"); } pci_set_master(pdev); if (ccat_functions_init(ccatdev)) { pr_warn("some functions couldn't be initialized\n"); } return 0; release_regions: pci_release_regions(pdev); disable_device: pci_disable_device(pdev); return status; } static void ccat_pci_remove(struct pci_dev *pdev) { struct ccat_device *ccatdev = pci_get_drvdata(pdev); if (ccatdev) { ccat_functions_remove(ccatdev); if (ccatdev->bar_2) pci_iounmap(pdev, ccatdev->bar_2); pci_iounmap(pdev, ccatdev->bar_0); pci_release_regions(pdev); pci_disable_device(pdev); } } #define PCI_DEVICE_ID_BECKHOFF_CCAT 0x5000 #define PCI_VENDOR_ID_BECKHOFF 0x15EC static const struct pci_device_id pci_ids[] = { {PCI_DEVICE(PCI_VENDOR_ID_BECKHOFF, PCI_DEVICE_ID_BECKHOFF_CCAT)}, {0,}, }; /* prevent auto-loading. */ /* MODULE_DEVICE_TABLE(pci, pci_ids); */ static struct pci_driver ccat_pci_driver = { .name = KBUILD_MODNAME, .id_table = pci_ids, .probe = ccat_pci_probe, .remove = ccat_pci_remove, }; module_pci_driver(ccat_pci_driver); #else /* #ifdef CONFIG_GENERIC_ISA_DMA */ static int ccat_eim_probe(struct platform_device *pdev) { struct ccat_device *ccatdev; ccatdev = devm_kzalloc(&pdev->dev, sizeof(*ccatdev), GFP_KERNEL); if (!ccatdev) { pr_err("%s() out of memory.\n", __FUNCTION__); return -ENOMEM; } ccatdev->pdev = pdev; platform_set_drvdata(pdev, ccatdev); if (!request_mem_region(0xf0000000, 0x02000000, pdev->name)) { pr_warn("request mem region failed.\n"); return -EIO; } if (!(ccatdev->bar_0 = ioremap(0xf0000000, 0x02000000))) { pr_warn("initialization of bar0 failed.\n"); return -EIO; } ccatdev->bar_2 = NULL; if (ccat_functions_init(ccatdev)) { pr_warn("some functions couldn't be initialized\n"); } return 0; } static int ccat_eim_remove(struct platform_device *pdev) { struct ccat_device *ccatdev = platform_get_drvdata(pdev); if (ccatdev) { ccat_functions_remove(ccatdev); iounmap(ccatdev->bar_0); release_mem_region(0xf0000000, 0x02000000); } return 0; } static const struct of_device_id bhf_eim_ccat_ids[] = { {.compatible = "bhf,emi-ccat",}, {} }; /* prevent auto-loading. */ /* MODULE_DEVICE_TABLE(of, bhf_eim_ccat_ids); */ static struct platform_driver ccat_eim_driver = { .driver = { .name = KBUILD_MODNAME, .of_match_table = bhf_eim_ccat_ids, }, .probe = ccat_eim_probe, .remove = ccat_eim_remove, }; module_platform_driver(ccat_eim_driver); #endif /* #ifdef CONFIG_GENERIC_ISA_DMA */