1// SPDX-License-Identifier: GPL-2.0
2/* Copyright (c) 2020, Loongson Corporation
3 */
4
5#include <linux/clk-provider.h>
6#include <linux/pci.h>
7#include <linux/dmi.h>
8#include <linux/device.h>
9#include <linux/of_irq.h>
10#include "stmmac.h"
11
12struct stmmac_pci_info {
13	int (*setup)(struct pci_dev *pdev, struct plat_stmmacenet_data *plat);
14};
15
16static void common_default_data(struct pci_dev *pdev,
17				struct plat_stmmacenet_data *plat)
18{
19	plat->bus_id = (pci_domain_nr(pdev->bus) << 16) | PCI_DEVID(pdev->bus->number, pdev->devfn);
20	plat->interface = PHY_INTERFACE_MODE_GMII;
21
22	plat->clk_csr = 2;	/* clk_csr_i = 20-35MHz & MDC = clk_csr_i/16 */
23	plat->has_gmac = 1;
24	plat->force_sf_dma_mode = 1;
25
26	/* Set default value for multicast hash bins */
27	plat->multicast_filter_bins = 256;
28
29	/* Set default value for unicast filter entries */
30	plat->unicast_filter_entries = 1;
31
32	/* Set the maxmtu to a default of JUMBO_LEN */
33	plat->maxmtu = JUMBO_LEN;
34
35	/* Set default number of RX and TX queues to use */
36	plat->tx_queues_to_use = 1;
37	plat->rx_queues_to_use = 1;
38
39	/* Disable Priority config by default */
40	plat->tx_queues_cfg[0].use_prio = false;
41	plat->rx_queues_cfg[0].use_prio = false;
42
43	/* Disable RX queues routing by default */
44	plat->rx_queues_cfg[0].pkt_route = 0x0;
45
46	plat->dma_cfg->pbl = 32;
47	plat->dma_cfg->pblx8 = true;
48
49	plat->clk_ref_rate = 125000000;
50	plat->clk_ptp_rate = 125000000;
51}
52
53static int loongson_gmac_data(struct pci_dev *pdev,
54			      struct plat_stmmacenet_data *plat)
55{
56	common_default_data(pdev, plat);
57
58	plat->mdio_bus_data->phy_mask = 0;
59
60	plat->phy_addr = -1;
61	plat->phy_interface = PHY_INTERFACE_MODE_RGMII_ID;
62
63	return 0;
64}
65
66static struct stmmac_pci_info loongson_gmac_pci_info = {
67	.setup = loongson_gmac_data,
68};
69
70static void loongson_gnet_fix_speed(void *priv, unsigned int speed)
71{
72	struct net_device *ndev = (struct net_device *)(*(unsigned long *)priv);
73	struct stmmac_priv *ptr = netdev_priv(ndev);
74
75	if (speed == SPEED_1000) {
76		if (readl(ptr->ioaddr + MAC_CTRL_REG) & (1 << 15) /* PS */) {
77			/* reset phy */
78			phy_set_bits(ndev->phydev, 0 /*MII_BMCR*/,
79				     0x200 /*BMCR_ANRESTART*/);
80		}
81	}
82}
83
84static int loongson_gnet_data(struct pci_dev *pdev,
85			      struct plat_stmmacenet_data *plat)
86{
87	common_default_data(pdev, plat);
88
89	plat->mdio_bus_data->phy_mask = 0xfffffffb;
90
91	plat->phy_addr = 2;
92	plat->phy_interface = PHY_INTERFACE_MODE_GMII;
93
94	/* GNET 1000M speed need workaround */
95	plat->fix_mac_speed = loongson_gnet_fix_speed;
96
97	/* Get netdev pointer address */
98	plat->bsp_priv = &(pdev->dev.driver_data);
99
100	return 0;
101}
102
103static struct stmmac_pci_info loongson_gnet_pci_info = {
104	.setup = loongson_gnet_data,
105};
106
107static int loongson_dwmac_probe(struct pci_dev *pdev,
108				const struct pci_device_id *id)
109{
110	struct plat_stmmacenet_data *plat;
111	struct stmmac_pci_info *info;
112	struct stmmac_resources res;
113	struct device_node *np;
114	int ret, i, bus_id, phy_mode;
115	bool mdio = false;
116
117	np = dev_of_node(&pdev->dev);
118	if (np && !of_device_is_compatible(np, "loongson, pci-gmac")) {
119		pr_info("dwmac_loongson_pci: Incompatible OF node\n");
120		return -ENODEV;
121	}
122
123	plat = devm_kzalloc(&pdev->dev, sizeof(*plat), GFP_KERNEL);
124	if (!plat)
125		return -ENOMEM;
126
127	if (plat->mdio_node) {
128		dev_err(&pdev->dev, "Found MDIO subnode\n");
129		mdio = true;
130	}
131
132	plat->mdio_bus_data = devm_kzalloc(&pdev->dev,
133					   sizeof(*plat->mdio_bus_data),
134					   GFP_KERNEL);
135	if (!plat->mdio_bus_data)
136		return -ENOMEM;
137
138	if (mdio)
139		plat->mdio_bus_data->needs_reset = true;
140
141	plat->dma_cfg = devm_kzalloc(&pdev->dev, sizeof(*plat->dma_cfg), GFP_KERNEL);
142	if (!plat->dma_cfg)
143		return -ENOMEM;
144
145	/* Enable pci device */
146	ret = pci_enable_device(pdev);
147	if (ret) {
148		dev_err(&pdev->dev, "%s: ERROR: failed to enable device\n", __func__);
149		return ret;
150	}
151
152	/* Get the base address of device */
153	for (i = 0; i < PCI_STD_NUM_BARS; i++) {
154		if (pci_resource_len(pdev, i) == 0)
155			continue;
156		ret = pcim_iomap_regions(pdev, BIT(0), pci_name(pdev));
157		if (ret)
158			return ret;
159		break;
160	}
161
162	pci_set_master(pdev);
163
164	info = (struct stmmac_pci_info *)id->driver_data;
165	ret = info->setup(pdev, plat);
166	if (ret)
167		return ret;
168
169	if (np) {
170		bus_id = of_alias_get_id(np, "ethernet");
171		if (bus_id >= 0)
172			plat->bus_id = bus_id;
173
174		phy_mode = device_get_phy_mode(&pdev->dev);
175		if (phy_mode < 0) {
176			dev_err(&pdev->dev, "phy_mode not found\n");
177			return phy_mode;
178		}
179		plat->phy_interface = phy_mode;
180	}
181
182	pci_enable_msi(pdev);
183
184	memset(&res, 0, sizeof(res));
185	res.addr = pcim_iomap_table(pdev)[0];
186	if (np) {
187		res.irq = of_irq_get_byname(np, "macirq");
188		if (res.irq < 0) {
189			dev_err(&pdev->dev, "IRQ macirq not found\n");
190			ret = -ENODEV;
191		}
192
193		res.wol_irq = of_irq_get_byname(np, "eth_wake_irq");
194		if (res.wol_irq < 0) {
195			dev_info(&pdev->dev,
196				 "IRQ eth_wake_irq not found, using macirq\n");
197			res.wol_irq = res.irq;
198		}
199
200		res.lpi_irq = of_irq_get_byname(np, "eth_lpi");
201		if (res.lpi_irq < 0) {
202			dev_err(&pdev->dev, "IRQ eth_lpi not found\n");
203			ret = -ENODEV;
204		}
205	} else {
206		res.irq = pdev->irq;
207		res.wol_irq = pdev->irq;
208	}
209
210	return stmmac_dvr_probe(&pdev->dev, plat, &res);
211}
212
213static void loongson_dwmac_remove(struct pci_dev *pdev)
214{
215	int i;
216
217	stmmac_dvr_remove(&pdev->dev);
218
219	for (i = 0; i < PCI_STD_NUM_BARS; i++) {
220		if (pci_resource_len(pdev, i) == 0)
221			continue;
222		pcim_iounmap_regions(pdev, BIT(i));
223		break;
224	}
225
226	pci_disable_device(pdev);
227}
228
229static int __maybe_unused loongson_dwmac_suspend(struct device *dev)
230{
231	struct pci_dev *pdev = to_pci_dev(dev);
232	int ret;
233
234	ret = stmmac_suspend(dev);
235	if (ret)
236		return ret;
237
238	ret = pci_save_state(pdev);
239	if (ret)
240		return ret;
241
242	pci_disable_device(pdev);
243	pci_wake_from_d3(pdev, true);
244	return 0;
245}
246
247static int __maybe_unused loongson_dwmac_resume(struct device *dev)
248{
249	struct pci_dev *pdev = to_pci_dev(dev);
250	int ret;
251
252	pci_restore_state(pdev);
253	pci_set_power_state(pdev, PCI_D0);
254
255	ret = pci_enable_device(pdev);
256	if (ret)
257		return ret;
258
259	pci_set_master(pdev);
260
261	return stmmac_resume(dev);
262}
263
264static SIMPLE_DEV_PM_OPS(loongson_dwmac_pm_ops, loongson_dwmac_suspend,
265			 loongson_dwmac_resume);
266
267#define PCI_DEVICE_ID_LOONGSON_GMAC	0x7a03
268#define PCI_DEVICE_ID_LOONGSON_GNET	0x7a13
269
270static const struct pci_device_id loongson_dwmac_id_table[] = {
271	{ PCI_DEVICE_DATA(LOONGSON, GMAC, &loongson_gmac_pci_info) },
272	{ PCI_DEVICE_DATA(LOONGSON, GNET, &loongson_gnet_pci_info) },
273	{}
274};
275MODULE_DEVICE_TABLE(pci, loongson_dwmac_id_table);
276
277struct pci_driver loongson_dwmac_driver = {
278	.name = "dwmac-loongson-pci",
279	.id_table = loongson_dwmac_id_table,
280	.probe = loongson_dwmac_probe,
281	.remove = loongson_dwmac_remove,
282	.driver = {
283		.pm = &loongson_dwmac_pm_ops,
284	},
285};
286
287module_pci_driver(loongson_dwmac_driver);
288
289MODULE_DESCRIPTION("Loongson DWMAC PCI driver");
290MODULE_AUTHOR("Qing Zhang <zhangqing@loongson.cn>");
291MODULE_LICENSE("GPL v2");
292