| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Microchip LAN966x PCI driver
- *
- * Copyright (c) 2024 Microchip Technology Inc. and its subsidiaries.
- *
- * Authors:
- * Clément Léger <clement.leger@bootlin.com>
- * Hervé Codina <herve.codina@bootlin.com>
- */
- #include <linux/device.h>
- #include <linux/irq.h>
- #include <linux/irqdomain.h>
- #include <linux/module.h>
- #include <linux/of_platform.h>
- #include <linux/pci.h>
- #include <linux/pci_ids.h>
- #include <linux/slab.h>
- /* Embedded dtbo symbols created by cmd_wrap_S_dtb in scripts/Makefile.lib */
- extern char __dtbo_lan966x_pci_begin[];
- extern char __dtbo_lan966x_pci_end[];
- struct pci_dev_intr_ctrl {
- struct pci_dev *pci_dev;
- struct irq_domain *irq_domain;
- int irq;
- };
- static int pci_dev_irq_domain_map(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw)
- {
- irq_set_chip_and_handler(virq, &dummy_irq_chip, handle_simple_irq);
- return 0;
- }
- static const struct irq_domain_ops pci_dev_irq_domain_ops = {
- .map = pci_dev_irq_domain_map,
- .xlate = irq_domain_xlate_onecell,
- };
- static irqreturn_t pci_dev_irq_handler(int irq, void *data)
- {
- struct pci_dev_intr_ctrl *intr_ctrl = data;
- int ret;
- ret = generic_handle_domain_irq(intr_ctrl->irq_domain, 0);
- return ret ? IRQ_NONE : IRQ_HANDLED;
- }
- static struct pci_dev_intr_ctrl *pci_dev_create_intr_ctrl(struct pci_dev *pdev)
- {
- struct pci_dev_intr_ctrl *intr_ctrl __free(kfree) = NULL;
- struct fwnode_handle *fwnode;
- int ret;
- fwnode = dev_fwnode(&pdev->dev);
- if (!fwnode)
- return ERR_PTR(-ENODEV);
- intr_ctrl = kmalloc_obj(*intr_ctrl);
- if (!intr_ctrl)
- return ERR_PTR(-ENOMEM);
- intr_ctrl->pci_dev = pdev;
- intr_ctrl->irq_domain = irq_domain_create_linear(fwnode, 1, &pci_dev_irq_domain_ops,
- intr_ctrl);
- if (!intr_ctrl->irq_domain) {
- pci_err(pdev, "Failed to create irqdomain\n");
- return ERR_PTR(-ENOMEM);
- }
- ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_INTX);
- if (ret < 0) {
- pci_err(pdev, "Unable alloc irq vector (%d)\n", ret);
- goto err_remove_domain;
- }
- intr_ctrl->irq = pci_irq_vector(pdev, 0);
- ret = request_irq(intr_ctrl->irq, pci_dev_irq_handler, IRQF_SHARED,
- pci_name(pdev), intr_ctrl);
- if (ret) {
- pci_err(pdev, "Unable to request irq %d (%d)\n", intr_ctrl->irq, ret);
- goto err_free_irq_vector;
- }
- return_ptr(intr_ctrl);
- err_free_irq_vector:
- pci_free_irq_vectors(pdev);
- err_remove_domain:
- irq_domain_remove(intr_ctrl->irq_domain);
- return ERR_PTR(ret);
- }
- static void pci_dev_remove_intr_ctrl(struct pci_dev_intr_ctrl *intr_ctrl)
- {
- free_irq(intr_ctrl->irq, intr_ctrl);
- pci_free_irq_vectors(intr_ctrl->pci_dev);
- irq_dispose_mapping(irq_find_mapping(intr_ctrl->irq_domain, 0));
- irq_domain_remove(intr_ctrl->irq_domain);
- kfree(intr_ctrl);
- }
- static void devm_pci_dev_remove_intr_ctrl(void *intr_ctrl)
- {
- pci_dev_remove_intr_ctrl(intr_ctrl);
- }
- static int devm_pci_dev_create_intr_ctrl(struct pci_dev *pdev)
- {
- struct pci_dev_intr_ctrl *intr_ctrl;
- intr_ctrl = pci_dev_create_intr_ctrl(pdev);
- if (IS_ERR(intr_ctrl))
- return PTR_ERR(intr_ctrl);
- return devm_add_action_or_reset(&pdev->dev, devm_pci_dev_remove_intr_ctrl, intr_ctrl);
- }
- struct lan966x_pci {
- struct device *dev;
- int ovcs_id;
- };
- static int lan966x_pci_load_overlay(struct lan966x_pci *data)
- {
- u32 dtbo_size = __dtbo_lan966x_pci_end - __dtbo_lan966x_pci_begin;
- void *dtbo_start = __dtbo_lan966x_pci_begin;
- return of_overlay_fdt_apply(dtbo_start, dtbo_size, &data->ovcs_id, dev_of_node(data->dev));
- }
- static void lan966x_pci_unload_overlay(struct lan966x_pci *data)
- {
- of_overlay_remove(&data->ovcs_id);
- }
- static int lan966x_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
- {
- struct device *dev = &pdev->dev;
- struct lan966x_pci *data;
- int ret;
- /*
- * On ACPI system, fwnode can point to the ACPI node.
- * This driver needs an of_node to be used as the device-tree overlay
- * target. This of_node should be set by the PCI core if it succeeds in
- * creating it (CONFIG_PCI_DYNAMIC_OF_NODES feature).
- * Check here for the validity of this of_node.
- */
- if (!dev_of_node(dev))
- return dev_err_probe(dev, -EINVAL, "Missing of_node for device\n");
- /* Need to be done before devm_pci_dev_create_intr_ctrl.
- * It allocates an IRQ and so pdev->irq is updated.
- */
- ret = pcim_enable_device(pdev);
- if (ret)
- return ret;
- ret = devm_pci_dev_create_intr_ctrl(pdev);
- if (ret)
- return ret;
- data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
- if (!data)
- return -ENOMEM;
- pci_set_drvdata(pdev, data);
- data->dev = dev;
- ret = lan966x_pci_load_overlay(data);
- if (ret)
- return ret;
- pci_set_master(pdev);
- ret = of_platform_default_populate(dev_of_node(dev), NULL, dev);
- if (ret)
- goto err_unload_overlay;
- return 0;
- err_unload_overlay:
- lan966x_pci_unload_overlay(data);
- return ret;
- }
- static void lan966x_pci_remove(struct pci_dev *pdev)
- {
- struct lan966x_pci *data = pci_get_drvdata(pdev);
- of_platform_depopulate(data->dev);
- lan966x_pci_unload_overlay(data);
- }
- static struct pci_device_id lan966x_pci_ids[] = {
- { PCI_DEVICE(PCI_VENDOR_ID_EFAR, 0x9660) },
- { }
- };
- MODULE_DEVICE_TABLE(pci, lan966x_pci_ids);
- static struct pci_driver lan966x_pci_driver = {
- .name = "mchp_lan966x_pci",
- .id_table = lan966x_pci_ids,
- .probe = lan966x_pci_probe,
- .remove = lan966x_pci_remove,
- };
- module_pci_driver(lan966x_pci_driver);
- MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>");
- MODULE_DESCRIPTION("Microchip LAN966x PCI driver");
- MODULE_LICENSE("GPL");
|