Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  1// SPDX-License-Identifier: GPL-2.0
  2/*
  3 * Microchip LAN966x PCI driver
  4 *
  5 * Copyright (c) 2024 Microchip Technology Inc. and its subsidiaries.
  6 *
  7 * Authors:
  8 *	Clément Léger <clement.leger@bootlin.com>
  9 *	Hervé Codina <herve.codina@bootlin.com>
 10 */
 11
 12#include <linux/device.h>
 13#include <linux/irq.h>
 14#include <linux/irqdomain.h>
 15#include <linux/module.h>
 16#include <linux/of_platform.h>
 17#include <linux/pci.h>
 18#include <linux/pci_ids.h>
 19#include <linux/slab.h>
 20
 21/* Embedded dtbo symbols created by cmd_wrap_S_dtb in scripts/Makefile.lib */
 22extern char __dtbo_lan966x_pci_begin[];
 23extern char __dtbo_lan966x_pci_end[];
 24
 25struct pci_dev_intr_ctrl {
 26	struct pci_dev *pci_dev;
 27	struct irq_domain *irq_domain;
 28	int irq;
 29};
 30
 31static int pci_dev_irq_domain_map(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw)
 32{
 33	irq_set_chip_and_handler(virq, &dummy_irq_chip, handle_simple_irq);
 34	return 0;
 35}
 36
 37static const struct irq_domain_ops pci_dev_irq_domain_ops = {
 38	.map = pci_dev_irq_domain_map,
 39	.xlate = irq_domain_xlate_onecell,
 40};
 41
 42static irqreturn_t pci_dev_irq_handler(int irq, void *data)
 43{
 44	struct pci_dev_intr_ctrl *intr_ctrl = data;
 45	int ret;
 46
 47	ret = generic_handle_domain_irq(intr_ctrl->irq_domain, 0);
 48	return ret ? IRQ_NONE : IRQ_HANDLED;
 49}
 50
 51static struct pci_dev_intr_ctrl *pci_dev_create_intr_ctrl(struct pci_dev *pdev)
 52{
 53	struct pci_dev_intr_ctrl *intr_ctrl __free(kfree) = NULL;
 54	struct fwnode_handle *fwnode;
 55	int ret;
 56
 57	fwnode = dev_fwnode(&pdev->dev);
 58	if (!fwnode)
 59		return ERR_PTR(-ENODEV);
 60
 61	intr_ctrl = kmalloc(sizeof(*intr_ctrl), GFP_KERNEL);
 62	if (!intr_ctrl)
 63		return ERR_PTR(-ENOMEM);
 64
 65	intr_ctrl->pci_dev = pdev;
 66
 67	intr_ctrl->irq_domain = irq_domain_create_linear(fwnode, 1, &pci_dev_irq_domain_ops,
 68							 intr_ctrl);
 69	if (!intr_ctrl->irq_domain) {
 70		pci_err(pdev, "Failed to create irqdomain\n");
 71		return ERR_PTR(-ENOMEM);
 72	}
 73
 74	ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_INTX);
 75	if (ret < 0) {
 76		pci_err(pdev, "Unable alloc irq vector (%d)\n", ret);
 77		goto err_remove_domain;
 78	}
 79	intr_ctrl->irq = pci_irq_vector(pdev, 0);
 80	ret = request_irq(intr_ctrl->irq, pci_dev_irq_handler, IRQF_SHARED,
 81			  pci_name(pdev), intr_ctrl);
 82	if (ret) {
 83		pci_err(pdev, "Unable to request irq %d (%d)\n", intr_ctrl->irq, ret);
 84		goto err_free_irq_vector;
 85	}
 86
 87	return_ptr(intr_ctrl);
 88
 89err_free_irq_vector:
 90	pci_free_irq_vectors(pdev);
 91err_remove_domain:
 92	irq_domain_remove(intr_ctrl->irq_domain);
 93	return ERR_PTR(ret);
 94}
 95
 96static void pci_dev_remove_intr_ctrl(struct pci_dev_intr_ctrl *intr_ctrl)
 97{
 98	free_irq(intr_ctrl->irq, intr_ctrl);
 99	pci_free_irq_vectors(intr_ctrl->pci_dev);
100	irq_dispose_mapping(irq_find_mapping(intr_ctrl->irq_domain, 0));
101	irq_domain_remove(intr_ctrl->irq_domain);
102	kfree(intr_ctrl);
103}
104
105static void devm_pci_dev_remove_intr_ctrl(void *intr_ctrl)
106{
107	pci_dev_remove_intr_ctrl(intr_ctrl);
108}
109
110static int devm_pci_dev_create_intr_ctrl(struct pci_dev *pdev)
111{
112	struct pci_dev_intr_ctrl *intr_ctrl;
113
114	intr_ctrl = pci_dev_create_intr_ctrl(pdev);
115	if (IS_ERR(intr_ctrl))
116		return PTR_ERR(intr_ctrl);
117
118	return devm_add_action_or_reset(&pdev->dev, devm_pci_dev_remove_intr_ctrl, intr_ctrl);
119}
120
121struct lan966x_pci {
122	struct device *dev;
123	int ovcs_id;
124};
125
126static int lan966x_pci_load_overlay(struct lan966x_pci *data)
127{
128	u32 dtbo_size = __dtbo_lan966x_pci_end - __dtbo_lan966x_pci_begin;
129	void *dtbo_start = __dtbo_lan966x_pci_begin;
130
131	return of_overlay_fdt_apply(dtbo_start, dtbo_size, &data->ovcs_id, dev_of_node(data->dev));
132}
133
134static void lan966x_pci_unload_overlay(struct lan966x_pci *data)
135{
136	of_overlay_remove(&data->ovcs_id);
137}
138
139static int lan966x_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
140{
141	struct device *dev = &pdev->dev;
142	struct lan966x_pci *data;
143	int ret;
144
145	/*
146	 * On ACPI system, fwnode can point to the ACPI node.
147	 * This driver needs an of_node to be used as the device-tree overlay
148	 * target. This of_node should be set by the PCI core if it succeeds in
149	 * creating it (CONFIG_PCI_DYNAMIC_OF_NODES feature).
150	 * Check here for the validity of this of_node.
151	 */
152	if (!dev_of_node(dev))
153		return dev_err_probe(dev, -EINVAL, "Missing of_node for device\n");
154
155	/* Need to be done before devm_pci_dev_create_intr_ctrl.
156	 * It allocates an IRQ and so pdev->irq is updated.
157	 */
158	ret = pcim_enable_device(pdev);
159	if (ret)
160		return ret;
161
162	ret = devm_pci_dev_create_intr_ctrl(pdev);
163	if (ret)
164		return ret;
165
166	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
167	if (!data)
168		return -ENOMEM;
169
170	pci_set_drvdata(pdev, data);
171	data->dev = dev;
172
173	ret = lan966x_pci_load_overlay(data);
174	if (ret)
175		return ret;
176
177	pci_set_master(pdev);
178
179	ret = of_platform_default_populate(dev_of_node(dev), NULL, dev);
180	if (ret)
181		goto err_unload_overlay;
182
183	return 0;
184
185err_unload_overlay:
186	lan966x_pci_unload_overlay(data);
187	return ret;
188}
189
190static void lan966x_pci_remove(struct pci_dev *pdev)
191{
192	struct lan966x_pci *data = pci_get_drvdata(pdev);
193
194	of_platform_depopulate(data->dev);
195
196	lan966x_pci_unload_overlay(data);
197}
198
199static struct pci_device_id lan966x_pci_ids[] = {
200	{ PCI_DEVICE(PCI_VENDOR_ID_EFAR, 0x9660) },
201	{ }
202};
203MODULE_DEVICE_TABLE(pci, lan966x_pci_ids);
204
205static struct pci_driver lan966x_pci_driver = {
206	.name = "mchp_lan966x_pci",
207	.id_table = lan966x_pci_ids,
208	.probe = lan966x_pci_probe,
209	.remove = lan966x_pci_remove,
210};
211module_pci_driver(lan966x_pci_driver);
212
213MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>");
214MODULE_DESCRIPTION("Microchip LAN966x PCI driver");
215MODULE_LICENSE("GPL");