1/*
2 * Copyright 2022, Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 */
5
6extern "C" {
7#include "device.h"
8
9#include <compat/machine/resource.h>
10#include <compat/dev/pci/pcireg.h>
11#include <compat/dev/pci/pcivar.h>
12}
13
14#include <PCI.h>
15
16
17//#define DEBUG_PCI
18#ifdef DEBUG_PCI
19#	define TRACE_PCI(dev, format, args...) device_printf(dev, format , ##args)
20#else
21#	define TRACE_PCI(dev, format, args...) do { } while (0)
22#endif
23
24
25pci_module_info *gPci;
26
27
28status_t
29init_pci()
30{
31	if (gPci != NULL)
32		return B_OK;
33
34	status_t status = get_module(B_PCI_MODULE_NAME, (module_info **)&gPci);
35	if (status != B_OK)
36		return status;
37
38	return B_OK;
39}
40
41
42void
43uninit_pci()
44{
45	if (gPci != NULL)
46		put_module(B_PCI_MODULE_NAME);
47}
48
49
50pci_info*
51get_device_pci_info(device_t device)
52{
53	struct root_device_softc* root_softc = (struct root_device_softc*)device->root->softc;
54	if (root_softc->bus != root_device_softc::BUS_pci)
55		return NULL;
56	return &root_softc->pci_info;
57}
58
59
60uint32_t
61pci_read_config(device_t dev, int offset, int size)
62{
63	pci_info* info = get_device_pci_info(dev);
64
65	uint32_t value = gPci->read_pci_config(info->bus, info->device,
66		info->function, offset, size);
67	TRACE_PCI(dev, "pci_read_config(%i, %i) = 0x%x\n", offset, size, value);
68	return value;
69}
70
71
72void
73pci_write_config(device_t dev, int offset, uint32_t value, int size)
74{
75	pci_info* info = get_device_pci_info(dev);
76
77	TRACE_PCI(dev, "pci_write_config(%i, 0x%x, %i)\n", offset, value, size);
78
79	gPci->write_pci_config(info->bus, info->device, info->function, offset,
80		size, value);
81}
82
83
84uint16_t
85pci_get_vendor(device_t dev)
86{
87	return pci_read_config(dev, PCI_vendor_id, 2);
88}
89
90
91uint16_t
92pci_get_device(device_t dev)
93{
94	return pci_read_config(dev, PCI_device_id, 2);
95}
96
97
98uint16_t
99pci_get_subvendor(device_t dev)
100{
101	return pci_read_config(dev, PCI_subsystem_vendor_id, 2);
102}
103
104
105uint16_t
106pci_get_subdevice(device_t dev)
107{
108	return pci_read_config(dev, PCI_subsystem_id, 2);
109}
110
111
112uint8_t
113pci_get_revid(device_t dev)
114{
115	return pci_read_config(dev, PCI_revision, 1);
116}
117
118
119uint32_t
120pci_get_domain(device_t dev)
121{
122	return 0;
123}
124
125uint32_t
126pci_get_devid(device_t dev)
127{
128	return pci_read_config(dev, PCI_device_id, 2) << 16 |
129		pci_read_config(dev, PCI_vendor_id, 2);
130}
131
132uint8_t
133pci_get_cachelnsz(device_t dev)
134{
135	return pci_read_config(dev, PCI_line_size, 1);
136}
137
138uint8_t *
139pci_get_ether(device_t dev)
140{
141	/* used in if_dc to get the MAC from CardBus CIS for Xircom card */
142	return NULL; /* NULL is handled in the caller correctly */
143}
144
145uint8_t
146pci_get_bus(device_t dev)
147{
148	pci_info *info
149		= &((struct root_device_softc *)dev->root->softc)->pci_info;
150	return info->bus;
151}
152
153
154uint8_t
155pci_get_slot(device_t dev)
156{
157	pci_info *info
158		= &((struct root_device_softc *)dev->root->softc)->pci_info;
159	return info->device;
160}
161
162
163uint8_t
164pci_get_function(device_t dev)
165{
166	pci_info* info = get_device_pci_info(dev);
167	return info->function;
168}
169
170
171device_t
172pci_find_dbsf(uint32_t domain, uint8_t bus, uint8_t slot, uint8_t func)
173{
174	// We don't support that yet - if we want to support the multi port
175	// feature of the Broadcom BCM 570x driver, we would have to change
176	// that.
177	return NULL;
178}
179
180
181static void
182pci_set_command_bit(device_t dev, uint16_t bit)
183{
184	uint16_t command = pci_read_config(dev, PCI_command, 2);
185	pci_write_config(dev, PCI_command, command | bit, 2);
186}
187
188
189int
190pci_enable_busmaster(device_t dev)
191{
192	// We do this a bit later than FreeBSD does.
193	if (pci_get_powerstate(dev) != PCI_POWERSTATE_D0)
194		pci_set_powerstate(dev, PCI_POWERSTATE_D0);
195
196	pci_set_command_bit(dev, PCI_command_master);
197	return 0;
198}
199
200
201int
202pci_enable_io(device_t dev, int space)
203{
204	/* adapted from FreeBSD's pci_enable_io_method */
205	int bit = 0;
206
207	switch (space) {
208		case SYS_RES_IOPORT:
209			bit = PCI_command_io;
210			break;
211		case SYS_RES_MEMORY:
212			bit = PCI_command_memory;
213			break;
214		default:
215			return EINVAL;
216	}
217
218	pci_set_command_bit(dev, bit);
219	if (pci_read_config(dev, PCI_command, 2) & bit)
220		return 0;
221
222	device_printf(dev, "pci_enable_io(%d) failed.\n", space);
223
224	return ENXIO;
225}
226
227
228int
229pci_find_cap(device_t dev, int capability, int *capreg)
230{
231	pci_info* info = get_device_pci_info(dev);
232	uint8 offset;
233	status_t status;
234
235	status = gPci->find_pci_capability(info->bus, info->device, info->function,
236		capability, &offset);
237	*capreg = offset;
238	return status;
239}
240
241
242int
243pci_find_extcap(device_t dev, int capability, int *capreg)
244{
245	pci_info* info = get_device_pci_info(dev);
246	uint16 offset;
247	status_t status;
248
249	status = gPci->find_pci_extended_capability(info->bus, info->device, info->function,
250		capability, &offset);
251	*capreg = offset;
252	return status;
253}
254
255
256int
257pci_msi_count(device_t dev)
258{
259	pci_info* info = get_device_pci_info(dev);
260	return gPci->get_msi_count(info->bus, info->device, info->function);
261}
262
263
264int
265pci_alloc_msi(device_t dev, int *count)
266{
267	pci_info* info = get_device_pci_info(dev);
268	uint32 startVector = 0;
269	if (gPci->configure_msi(info->bus, info->device, info->function, *count,
270			&startVector) != B_OK) {
271		return ENODEV;
272	}
273
274	((struct root_device_softc *)dev->root->softc)->is_msi = true;
275	info->u.h0.interrupt_line = startVector;
276	return EOK;
277}
278
279
280int
281pci_release_msi(device_t dev)
282{
283	pci_info* info = get_device_pci_info(dev);
284	gPci->unconfigure_msi(info->bus, info->device, info->function);
285	((struct root_device_softc *)dev->root->softc)->is_msi = false;
286	((struct root_device_softc *)dev->root->softc)->is_msix = false;
287	return EOK;
288}
289
290
291int
292pci_msix_table_bar(device_t dev)
293{
294	pci_info* info = get_device_pci_info(dev);
295
296	uint8 capability_offset;
297	if (gPci->find_pci_capability(info->bus, info->device, info->function,
298			PCI_cap_id_msix, &capability_offset) != B_OK)
299		return -1;
300
301	uint32 table_value = gPci->read_pci_config(info->bus, info->device, info->function,
302		capability_offset + PCI_msix_table, 4);
303
304	uint32 bar = table_value & PCI_msix_bir_mask;
305	return PCIR_BAR(bar);
306}
307
308
309int
310pci_msix_count(device_t dev)
311{
312	pci_info* info = get_device_pci_info(dev);
313	return gPci->get_msix_count(info->bus, info->device, info->function);
314}
315
316
317int
318pci_alloc_msix(device_t dev, int *count)
319{
320	pci_info* info = get_device_pci_info(dev);
321	uint32 startVector = 0;
322	if (gPci->configure_msix(info->bus, info->device, info->function, *count,
323			&startVector) != B_OK) {
324		return ENODEV;
325	}
326
327	((struct root_device_softc *)dev->root->softc)->is_msix = true;
328	info->u.h0.interrupt_line = startVector;
329	return EOK;
330}
331
332
333int
334pci_get_max_read_req(device_t dev)
335{
336	int cap;
337	uint16_t val;
338
339	if (pci_find_extcap(dev, PCIY_EXPRESS, &cap) != 0)
340		return (0);
341	val = pci_read_config(dev, cap + PCIR_EXPRESS_DEVICE_CTL, 2);
342	val &= PCIM_EXP_CTL_MAX_READ_REQUEST;
343	val >>= 12;
344	return (1 << (val + 7));
345}
346
347
348int
349pci_set_max_read_req(device_t dev, int size)
350{
351	int cap;
352	uint16_t val;
353
354	if (pci_find_extcap(dev, PCIY_EXPRESS, &cap) != 0)
355		return (0);
356	if (size < 128)
357		size = 128;
358	if (size > 4096)
359		size = 4096;
360	size = (1 << (fls(size) - 1));
361	val = pci_read_config(dev, cap + PCIR_EXPRESS_DEVICE_CTL, 2);
362	val &= ~PCIM_EXP_CTL_MAX_READ_REQUEST;
363	val |= (fls(size) - 8) << 12;
364	pci_write_config(dev, cap + PCIR_EXPRESS_DEVICE_CTL, val, 2);
365	return (size);
366}
367
368
369int
370pci_get_powerstate(device_t dev)
371{
372	int capabilityRegister;
373	uint16 status;
374	int powerState = PCI_POWERSTATE_D0;
375
376	if (pci_find_extcap(dev, PCIY_PMG, &capabilityRegister) != EOK)
377		return powerState;
378
379	status = pci_read_config(dev, capabilityRegister + PCIR_POWER_STATUS, 2);
380	switch (status & PCI_pm_mask) {
381		case PCI_pm_state_d0:
382			break;
383		case PCI_pm_state_d1:
384			powerState = PCI_POWERSTATE_D1;
385			break;
386		case PCI_pm_state_d2:
387			powerState = PCI_POWERSTATE_D2;
388			break;
389		case PCI_pm_state_d3:
390			powerState = PCI_POWERSTATE_D3;
391			break;
392		default:
393			powerState = PCI_POWERSTATE_UNKNOWN;
394			break;
395	}
396
397	TRACE_PCI(dev, "%s: D%i\n", __func__, powerState);
398	return powerState;
399}
400
401
402int
403pci_set_powerstate(device_t dev, int newPowerState)
404{
405	int capabilityRegister;
406	int oldPowerState;
407	uint8 currentPowerManagementStatus;
408	uint8 newPowerManagementStatus;
409	uint16 powerManagementCapabilities;
410	bigtime_t stateTransitionDelayInUs = 0;
411
412	if (pci_find_extcap(dev, PCIY_PMG, &capabilityRegister) != EOK)
413		return EOPNOTSUPP;
414
415	oldPowerState = pci_get_powerstate(dev);
416	if (oldPowerState == newPowerState)
417		return EOK;
418
419	switch (max_c(oldPowerState, newPowerState)) {
420		case PCI_POWERSTATE_D2:
421			stateTransitionDelayInUs = 200;
422			break;
423		case PCI_POWERSTATE_D3:
424			stateTransitionDelayInUs = 10000;
425			break;
426	}
427
428	currentPowerManagementStatus = pci_read_config(dev, capabilityRegister
429		+ PCIR_POWER_STATUS, 2);
430	newPowerManagementStatus = currentPowerManagementStatus & ~PCI_pm_mask;
431	powerManagementCapabilities = pci_read_config(dev, capabilityRegister
432		+ PCIR_POWER_CAP, 2);
433
434	switch (newPowerState) {
435		case PCI_POWERSTATE_D0:
436			newPowerManagementStatus |= PCIM_PSTAT_D0;
437			break;
438		case PCI_POWERSTATE_D1:
439			if ((powerManagementCapabilities & PCI_pm_d1supp) == 0)
440				return EOPNOTSUPP;
441			newPowerManagementStatus |= PCIM_PSTAT_D1;
442			break;
443		case PCI_POWERSTATE_D2:
444			if ((powerManagementCapabilities & PCI_pm_d2supp) == 0)
445				return EOPNOTSUPP;
446			newPowerManagementStatus |= PCIM_PSTAT_D2;
447			break;
448		case PCI_POWERSTATE_D3:
449			newPowerManagementStatus |= PCIM_PSTAT_D3;
450			break;
451		default:
452			return EINVAL;
453	}
454
455	TRACE_PCI(dev, "%s: D%i -> D%i\n", __func__, oldPowerState, newPowerState);
456	pci_write_config(dev, capabilityRegister + PCIR_POWER_STATUS,
457		newPowerManagementStatus, 2);
458	if (stateTransitionDelayInUs != 0)
459		snooze(stateTransitionDelayInUs);
460
461	return EOK;
462}
463