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 | |
12 | static int loongson_default_data(struct plat_stmmacenet_data *plat) |
13 | { |
14 | plat->clk_csr = 2; /* clk_csr_i = 20-35MHz & MDC = clk_csr_i/16 */ |
15 | plat->has_gmac = 1; |
16 | plat->force_sf_dma_mode = 1; |
17 | |
18 | /* Set default value for multicast hash bins */ |
19 | plat->multicast_filter_bins = HASH_TABLE_SIZE; |
20 | |
21 | /* Set default value for unicast filter entries */ |
22 | plat->unicast_filter_entries = 1; |
23 | |
24 | /* Set the maxmtu to a default of JUMBO_LEN */ |
25 | plat->maxmtu = JUMBO_LEN; |
26 | |
27 | /* Set default number of RX and TX queues to use */ |
28 | plat->tx_queues_to_use = 1; |
29 | plat->rx_queues_to_use = 1; |
30 | |
31 | /* Disable Priority config by default */ |
32 | plat->tx_queues_cfg[0].use_prio = false; |
33 | plat->rx_queues_cfg[0].use_prio = false; |
34 | |
35 | /* Disable RX queues routing by default */ |
36 | plat->rx_queues_cfg[0].pkt_route = 0x0; |
37 | |
38 | /* Default to phy auto-detection */ |
39 | plat->phy_addr = -1; |
40 | |
41 | plat->dma_cfg->pbl = 32; |
42 | plat->dma_cfg->pblx8 = true; |
43 | |
44 | plat->multicast_filter_bins = 256; |
45 | return 0; |
46 | } |
47 | |
48 | static int loongson_dwmac_probe(struct pci_dev *pdev, const struct pci_device_id *id) |
49 | { |
50 | struct plat_stmmacenet_data *plat; |
51 | struct stmmac_resources res; |
52 | struct device_node *np; |
53 | int ret, i, phy_mode; |
54 | |
55 | np = dev_of_node(dev: &pdev->dev); |
56 | |
57 | if (!np) { |
58 | pr_info("dwmac_loongson_pci: No OF node\n" ); |
59 | return -ENODEV; |
60 | } |
61 | |
62 | plat = devm_kzalloc(dev: &pdev->dev, size: sizeof(*plat), GFP_KERNEL); |
63 | if (!plat) |
64 | return -ENOMEM; |
65 | |
66 | plat->mdio_bus_data = devm_kzalloc(dev: &pdev->dev, |
67 | size: sizeof(*plat->mdio_bus_data), |
68 | GFP_KERNEL); |
69 | if (!plat->mdio_bus_data) |
70 | return -ENOMEM; |
71 | |
72 | plat->mdio_node = of_get_child_by_name(node: np, name: "mdio" ); |
73 | if (plat->mdio_node) { |
74 | dev_info(&pdev->dev, "Found MDIO subnode\n" ); |
75 | plat->mdio_bus_data->needs_reset = true; |
76 | } |
77 | |
78 | plat->dma_cfg = devm_kzalloc(dev: &pdev->dev, size: sizeof(*plat->dma_cfg), GFP_KERNEL); |
79 | if (!plat->dma_cfg) { |
80 | ret = -ENOMEM; |
81 | goto err_put_node; |
82 | } |
83 | |
84 | /* Enable pci device */ |
85 | ret = pci_enable_device(dev: pdev); |
86 | if (ret) { |
87 | dev_err(&pdev->dev, "%s: ERROR: failed to enable device\n" , __func__); |
88 | goto err_put_node; |
89 | } |
90 | |
91 | /* Get the base address of device */ |
92 | for (i = 0; i < PCI_STD_NUM_BARS; i++) { |
93 | if (pci_resource_len(pdev, i) == 0) |
94 | continue; |
95 | ret = pcim_iomap_regions(pdev, BIT(0), name: pci_name(pdev)); |
96 | if (ret) |
97 | goto err_disable_device; |
98 | break; |
99 | } |
100 | |
101 | plat->bus_id = of_alias_get_id(np, stem: "ethernet" ); |
102 | if (plat->bus_id < 0) |
103 | plat->bus_id = pci_dev_id(dev: pdev); |
104 | |
105 | phy_mode = device_get_phy_mode(dev: &pdev->dev); |
106 | if (phy_mode < 0) { |
107 | dev_err(&pdev->dev, "phy_mode not found\n" ); |
108 | ret = phy_mode; |
109 | goto err_disable_device; |
110 | } |
111 | |
112 | plat->phy_interface = phy_mode; |
113 | plat->mac_interface = PHY_INTERFACE_MODE_GMII; |
114 | |
115 | pci_set_master(dev: pdev); |
116 | |
117 | loongson_default_data(plat); |
118 | pci_enable_msi(dev: pdev); |
119 | memset(&res, 0, sizeof(res)); |
120 | res.addr = pcim_iomap_table(pdev)[0]; |
121 | |
122 | res.irq = of_irq_get_byname(dev: np, name: "macirq" ); |
123 | if (res.irq < 0) { |
124 | dev_err(&pdev->dev, "IRQ macirq not found\n" ); |
125 | ret = -ENODEV; |
126 | goto err_disable_msi; |
127 | } |
128 | |
129 | res.wol_irq = of_irq_get_byname(dev: np, name: "eth_wake_irq" ); |
130 | if (res.wol_irq < 0) { |
131 | dev_info(&pdev->dev, "IRQ eth_wake_irq not found, using macirq\n" ); |
132 | res.wol_irq = res.irq; |
133 | } |
134 | |
135 | res.lpi_irq = of_irq_get_byname(dev: np, name: "eth_lpi" ); |
136 | if (res.lpi_irq < 0) { |
137 | dev_err(&pdev->dev, "IRQ eth_lpi not found\n" ); |
138 | ret = -ENODEV; |
139 | goto err_disable_msi; |
140 | } |
141 | |
142 | ret = stmmac_dvr_probe(device: &pdev->dev, plat_dat: plat, res: &res); |
143 | if (ret) |
144 | goto err_disable_msi; |
145 | |
146 | return ret; |
147 | |
148 | err_disable_msi: |
149 | pci_disable_msi(dev: pdev); |
150 | err_disable_device: |
151 | pci_disable_device(dev: pdev); |
152 | err_put_node: |
153 | of_node_put(node: plat->mdio_node); |
154 | return ret; |
155 | } |
156 | |
157 | static void loongson_dwmac_remove(struct pci_dev *pdev) |
158 | { |
159 | struct net_device *ndev = dev_get_drvdata(dev: &pdev->dev); |
160 | struct stmmac_priv *priv = netdev_priv(dev: ndev); |
161 | int i; |
162 | |
163 | of_node_put(node: priv->plat->mdio_node); |
164 | stmmac_dvr_remove(dev: &pdev->dev); |
165 | |
166 | for (i = 0; i < PCI_STD_NUM_BARS; i++) { |
167 | if (pci_resource_len(pdev, i) == 0) |
168 | continue; |
169 | pcim_iounmap_regions(pdev, BIT(i)); |
170 | break; |
171 | } |
172 | |
173 | pci_disable_msi(dev: pdev); |
174 | pci_disable_device(dev: pdev); |
175 | } |
176 | |
177 | static int __maybe_unused loongson_dwmac_suspend(struct device *dev) |
178 | { |
179 | struct pci_dev *pdev = to_pci_dev(dev); |
180 | int ret; |
181 | |
182 | ret = stmmac_suspend(dev); |
183 | if (ret) |
184 | return ret; |
185 | |
186 | ret = pci_save_state(dev: pdev); |
187 | if (ret) |
188 | return ret; |
189 | |
190 | pci_disable_device(dev: pdev); |
191 | pci_wake_from_d3(dev: pdev, enable: true); |
192 | return 0; |
193 | } |
194 | |
195 | static int __maybe_unused loongson_dwmac_resume(struct device *dev) |
196 | { |
197 | struct pci_dev *pdev = to_pci_dev(dev); |
198 | int ret; |
199 | |
200 | pci_restore_state(dev: pdev); |
201 | pci_set_power_state(dev: pdev, PCI_D0); |
202 | |
203 | ret = pci_enable_device(dev: pdev); |
204 | if (ret) |
205 | return ret; |
206 | |
207 | pci_set_master(dev: pdev); |
208 | |
209 | return stmmac_resume(dev); |
210 | } |
211 | |
212 | static SIMPLE_DEV_PM_OPS(loongson_dwmac_pm_ops, loongson_dwmac_suspend, |
213 | loongson_dwmac_resume); |
214 | |
215 | static const struct pci_device_id loongson_dwmac_id_table[] = { |
216 | { PCI_VDEVICE(LOONGSON, 0x7a03) }, |
217 | {} |
218 | }; |
219 | MODULE_DEVICE_TABLE(pci, loongson_dwmac_id_table); |
220 | |
221 | static struct pci_driver loongson_dwmac_driver = { |
222 | .name = "dwmac-loongson-pci" , |
223 | .id_table = loongson_dwmac_id_table, |
224 | .probe = loongson_dwmac_probe, |
225 | .remove = loongson_dwmac_remove, |
226 | .driver = { |
227 | .pm = &loongson_dwmac_pm_ops, |
228 | }, |
229 | }; |
230 | |
231 | module_pci_driver(loongson_dwmac_driver); |
232 | |
233 | MODULE_DESCRIPTION("Loongson DWMAC PCI driver" ); |
234 | MODULE_AUTHOR("Qing Zhang <zhangqing@loongson.cn>" ); |
235 | MODULE_LICENSE("GPL v2" ); |
236 | |