1/*
2 * Copyright 2007, Hugo Santos. All Rights Reserved.
3 * Copyright 2004, Marcus Overhagen. All Rights Reserved.
4 * Distributed under the terms of the MIT License.
5 */
6
7
8extern "C" {
9#include "device.h"
10}
11
12#include <stdlib.h>
13
14#include <algorithm>
15
16#include <arch/cpu.h>
17
18extern "C" {
19#include <compat/dev/pci/pcireg.h>
20#include <compat/dev/pci/pcivar.h>
21#include <compat/machine/resource.h>
22#include <compat/sys/mutex.h>
23#include <compat/machine/bus.h>
24#include <compat/sys/rman.h>
25#include <compat/sys/bus.h>
26}
27
28// private kernel header to get B_NO_HANDLED_INFO
29#include <int.h>
30
31#include <PCI_x86.h>
32
33
34//#define DEBUG_BUS_SPACE_RW
35#ifdef DEBUG_BUS_SPACE_RW
36#	define TRACE_BUS_SPACE_RW(x) driver_printf x
37#else
38#	define TRACE_BUS_SPACE_RW(x)
39#endif
40
41//#define DEBUG_PCI
42#ifdef DEBUG_PCI
43#	define TRACE_PCI(dev, format, args...) device_printf(dev, format , ##args)
44#else
45#	define TRACE_PCI(dev, format, args...) do { } while (0)
46#endif
47
48
49#define ROUNDUP(a, b) (((a) + ((b)-1)) & ~((b)-1))
50
51
52struct internal_intr {
53	device_t		dev;
54	driver_filter_t	filter;
55	driver_intr_t	*handler;
56	void			*arg;
57	int				irq;
58	uint32			flags;
59	bool			is_msi;
60
61	thread_id		thread;
62	sem_id			sem;
63	int32			handling;
64};
65
66static int32 intr_wrapper(void *data);
67
68
69static int
70fls(int mask)
71{
72	int bit;
73	if (mask == 0)
74		return (0);
75	for (bit = 1; mask != 1; bit++)
76		mask = (unsigned int)mask >> 1;
77	return (bit);
78}
79
80
81static area_id
82map_mem(void **virtualAddr, phys_addr_t _phy, size_t size, uint32 protection,
83	const char *name)
84{
85	uint32 offset = _phy & (B_PAGE_SIZE - 1);
86	phys_addr_t physicalAddr = _phy - offset;
87	area_id area;
88
89	size = ROUNDUP(size + offset, B_PAGE_SIZE);
90	area = map_physical_memory(name, physicalAddr, size, B_ANY_KERNEL_ADDRESS,
91		protection, virtualAddr);
92	if (area < B_OK)
93		return area;
94
95	*virtualAddr = (uint8 *)(*virtualAddr) + offset;
96
97	return area;
98}
99
100
101static int
102bus_alloc_irq_resource(device_t dev, struct resource *res)
103{
104	uint8 irq = pci_read_config(dev, PCI_interrupt_line, 1);
105	if (irq == 0 || irq == 0xff)
106		return -1;
107
108	/* TODO: IRQ resources! */
109	res->r_bustag = 0;
110	res->r_bushandle = irq;
111
112	return 0;
113}
114
115
116static int
117bus_alloc_mem_resource(device_t dev, struct resource *res, int regid)
118{
119	uint32 addr = pci_read_config(dev, regid, 4) & PCI_address_memory_32_mask;
120	uint32 size = 128 * 1024; /* XXX */
121	void *virtualAddr;
122
123	res->r_mapped_area = map_mem(&virtualAddr, addr, size, 0,
124		"bus_alloc_resource(MEMORY)");
125	if (res->r_mapped_area < B_OK)
126		return -1;
127
128	res->r_bustag = I386_BUS_SPACE_MEM;
129	res->r_bushandle = (bus_space_handle_t)virtualAddr;
130	return 0;
131}
132
133
134static int
135bus_alloc_ioport_resource(device_t dev, struct resource *res, int regid)
136{
137	res->r_bustag = I386_BUS_SPACE_IO;
138	res->r_bushandle = pci_read_config(dev, regid, 4) & PCI_address_io_mask;
139	return 0;
140}
141
142
143struct resource *
144bus_alloc_resource(device_t dev, int type, int *rid, unsigned long start,
145	unsigned long end, unsigned long count, uint32 flags)
146{
147	struct resource *res;
148	int result = -1;
149
150	if (type != SYS_RES_IRQ && type != SYS_RES_MEMORY
151		&& type != SYS_RES_IOPORT)
152		return NULL;
153
154	device_printf(dev, "bus_alloc_resource(%i, [%i], 0x%lx, 0x%lx, 0x%lx,"
155		"0x%" B_PRIx32 ")\n", type, *rid, start, end, count, flags);
156
157	// maybe a local array of resources is enough
158	res = (struct resource *)malloc(sizeof(struct resource));
159	if (res == NULL)
160		return NULL;
161
162	if (type == SYS_RES_IRQ) {
163		if (*rid == 0) {
164			// pinned interrupt
165			result = bus_alloc_irq_resource(dev, res);
166		} else {
167			// msi or msi-x interrupt at index *rid - 1
168			pci_info *info;
169			info = &((struct root_device_softc *)dev->root->softc)->pci_info;
170			res->r_bustag = 1;
171			res->r_bushandle = info->u.h0.interrupt_line + *rid - 1;
172			result = 0;
173
174			// TODO: msi-x interrupts
175		}
176	} else if (type == SYS_RES_MEMORY)
177		result = bus_alloc_mem_resource(dev, res, *rid);
178	else if (type == SYS_RES_IOPORT)
179		result = bus_alloc_ioport_resource(dev, res, *rid);
180
181	if (result < 0) {
182		free(res);
183		return NULL;
184	}
185
186	res->r_type = type;
187	return res;
188}
189
190
191int
192bus_release_resource(device_t dev, int type, int rid, struct resource *res)
193{
194	if (res->r_type != type)
195		panic("bus_release_resource: mismatch");
196
197	if (type == SYS_RES_MEMORY)
198		delete_area(res->r_mapped_area);
199
200	free(res);
201	return 0;
202}
203
204
205int
206bus_alloc_resources(device_t dev, struct resource_spec *resourceSpec,
207	struct resource **resources)
208{
209	int i;
210
211	for (i = 0; resourceSpec[i].type != -1; i++) {
212		resources[i] = bus_alloc_resource_any(dev,
213			resourceSpec[i].type, &resourceSpec[i].rid, resourceSpec[i].flags);
214		if (resources[i] == NULL
215			&& (resourceSpec[i].flags & RF_OPTIONAL) == 0) {
216			for (++i; resourceSpec[i].type != -1; i++) {
217				resources[i] = NULL;
218			}
219
220			bus_release_resources(dev, resourceSpec, resources);
221			return ENXIO;
222		}
223	}
224	return 0;
225}
226
227
228void
229bus_release_resources(device_t dev, const struct resource_spec *resourceSpec,
230	struct resource **resources)
231{
232	int i;
233
234	for (i = 0; resourceSpec[i].type != -1; i++) {
235		if (resources[i] == NULL)
236			continue;
237
238		bus_release_resource(dev, resourceSpec[i].type, resourceSpec[i].rid,
239			resources[i]);
240		resources[i] = NULL;
241	}
242}
243
244
245bus_space_handle_t
246rman_get_bushandle(struct resource *res)
247{
248	return res->r_bushandle;
249}
250
251
252bus_space_tag_t
253rman_get_bustag(struct resource *res)
254{
255	return res->r_bustag;
256}
257
258
259int
260rman_get_rid(struct resource *res)
261{
262	return 0;
263}
264
265
266//	#pragma mark - Interrupt handling
267
268
269static int32
270intr_wrapper(void *data)
271{
272	struct internal_intr *intr = (struct internal_intr *)data;
273
274	//device_printf(intr->dev, "in interrupt handler.\n");
275
276	if (!HAIKU_CHECK_DISABLE_INTERRUPTS(intr->dev))
277		return B_UNHANDLED_INTERRUPT;
278
279	release_sem_etc(intr->sem, 1, B_DO_NOT_RESCHEDULE);
280	return intr->handling ? B_HANDLED_INTERRUPT : B_INVOKE_SCHEDULER;
281}
282
283
284static int32
285intr_fast_wrapper(void *data)
286{
287	struct internal_intr *intr = (struct internal_intr *)data;
288
289	intr->handler(intr->arg);
290
291	// We don't know if the interrupt has been handled.
292	return B_UNHANDLED_INTERRUPT;
293}
294
295
296static int32
297intr_handler(void *data)
298{
299	struct internal_intr *intr = (struct internal_intr *)data;
300	status_t status;
301
302	while (1) {
303		status = acquire_sem(intr->sem);
304		if (status < B_OK)
305			break;
306
307		//device_printf(intr->dev, "in soft interrupt handler.\n");
308
309		atomic_or(&intr->handling, 1);
310		intr->handler(intr->arg);
311		atomic_and(&intr->handling, 0);
312		HAIKU_REENABLE_INTERRUPTS(intr->dev);
313	}
314
315	return 0;
316}
317
318
319static void
320free_internal_intr(struct internal_intr *intr)
321{
322	if (intr->sem >= B_OK) {
323		status_t status;
324		delete_sem(intr->sem);
325		wait_for_thread(intr->thread, &status);
326	}
327
328	free(intr);
329}
330
331
332int
333bus_setup_intr(device_t dev, struct resource *res, int flags,
334	driver_filter_t filter, driver_intr_t handler, void *arg, void **_cookie)
335{
336	/* TODO check MPSAFE etc */
337
338	struct internal_intr *intr = (struct internal_intr *)malloc(
339		sizeof(struct internal_intr));
340	char semName[64];
341	status_t status;
342
343	if (intr == NULL)
344		return B_NO_MEMORY;
345
346	intr->dev = dev;
347	intr->filter = filter;
348	intr->handler = handler;
349	intr->arg = arg;
350	intr->irq = res->r_bushandle;
351	intr->flags = flags;
352	intr->is_msi = false;
353	intr->sem = -1;
354	intr->thread = -1;
355
356	if (filter != NULL) {
357		status = install_io_interrupt_handler(intr->irq,
358			(interrupt_handler)intr->filter, intr->arg, 0);
359	} else if ((flags & INTR_FAST) != 0) {
360		status = install_io_interrupt_handler(intr->irq,
361			intr_fast_wrapper, intr, B_NO_HANDLED_INFO);
362	} else {
363		snprintf(semName, sizeof(semName), "%s intr", dev->device_name);
364
365		intr->sem = create_sem(0, semName);
366		if (intr->sem < B_OK) {
367			free(intr);
368			return B_NO_MEMORY;
369		}
370
371		snprintf(semName, sizeof(semName), "%s intr handler", dev->device_name);
372
373		intr->thread = spawn_kernel_thread(intr_handler, semName,
374			B_REAL_TIME_DISPLAY_PRIORITY, intr);
375		if (intr->thread < B_OK) {
376			delete_sem(intr->sem);
377			free(intr);
378			return B_NO_MEMORY;
379		}
380
381		status = install_io_interrupt_handler(intr->irq,
382			intr_wrapper, intr, B_NO_HANDLED_INFO);
383	}
384
385	if (status == B_OK && res->r_bustag == 1 && gPCIx86 != NULL) {
386		// this is an msi, enable it
387		pci_info *info
388			= &((struct root_device_softc *)dev->root->softc)->pci_info;
389		if (gPCIx86->enable_msi(info->bus, info->device,
390				info->function) != B_OK) {
391			device_printf(dev, "enabling msi failed\n");
392			bus_teardown_intr(dev, res, intr);
393			return ENODEV;
394		}
395
396		intr->is_msi = true;
397	}
398
399	if (status < B_OK) {
400		free_internal_intr(intr);
401		return status;
402	}
403
404	resume_thread(intr->thread);
405
406	*_cookie = intr;
407	return 0;
408}
409
410
411int
412bus_teardown_intr(device_t dev, struct resource *res, void *arg)
413{
414	struct internal_intr *intr = (struct internal_intr *)arg;
415
416	if (intr->is_msi && gPCIx86 != NULL) {
417		// disable msi generation
418		pci_info *info
419			= &((struct root_device_softc *)dev->root->softc)->pci_info;
420		gPCIx86->disable_msi(info->bus, info->device, info->function);
421	}
422
423	if (intr->filter != NULL) {
424		remove_io_interrupt_handler(intr->irq, (interrupt_handler)intr->filter,
425			intr->arg);
426	} else if (intr->flags & INTR_FAST) {
427		remove_io_interrupt_handler(intr->irq, intr_fast_wrapper, intr);
428	} else {
429		remove_io_interrupt_handler(intr->irq, intr_wrapper, intr);
430	}
431
432	free_internal_intr(intr);
433	return 0;
434}
435
436
437//	#pragma mark - bus functions
438
439
440bus_dma_tag_t
441bus_get_dma_tag(device_t dev)
442{
443	return NULL;
444}
445
446
447int
448bus_generic_suspend(device_t dev)
449{
450	UNIMPLEMENTED();
451	return B_ERROR;
452}
453
454
455int
456bus_generic_resume(device_t dev)
457{
458	UNIMPLEMENTED();
459	return B_ERROR;
460}
461
462
463void
464bus_generic_shutdown(device_t dev)
465{
466	UNIMPLEMENTED();
467}
468
469
470int
471bus_print_child_header(device_t dev, device_t child)
472{
473	UNIMPLEMENTED();
474	return B_ERROR;
475}
476
477
478int
479bus_print_child_footer(device_t dev, device_t child)
480{
481	UNIMPLEMENTED();
482	return B_ERROR;
483}
484
485
486int
487bus_generic_print_child(device_t dev, device_t child)
488{
489	UNIMPLEMENTED();
490	return B_ERROR;
491}
492
493
494void
495bus_generic_driver_added(device_t dev, driver_t *driver)
496{
497	UNIMPLEMENTED();
498}
499
500
501#define BUS_SPACE_READ(size, type, fun) \
502	type bus_space_read_##size(bus_space_tag_t tag, \
503		bus_space_handle_t handle, bus_size_t offset) \
504	{ \
505		type value; \
506		if (tag == I386_BUS_SPACE_IO) \
507			value = fun(handle + offset); \
508		else \
509			value = *(volatile type *)(handle + offset); \
510		if (tag == I386_BUS_SPACE_IO) \
511			TRACE_BUS_SPACE_RW(("bus_space_read_%s(0x%lx, 0x%lx, 0x%lx) = 0x%lx\n", \
512				#size, (uint32)tag, (uint32)handle, (uint32)offset, (uint32)value)); \
513		return value; \
514	}
515
516#define BUS_SPACE_WRITE(size, type, fun) \
517	void bus_space_write_##size(bus_space_tag_t tag, \
518		bus_space_handle_t handle, bus_size_t offset, type value) \
519	{ \
520		if (tag == I386_BUS_SPACE_IO) \
521			TRACE_BUS_SPACE_RW(("bus_space_write_%s(0x%lx, 0x%lx, 0x%lx, 0x%lx)\n", \
522				#size, (uint32)tag, (uint32)handle, (uint32)offset, (uint32)value)); \
523		if (tag == I386_BUS_SPACE_IO) \
524			fun(value, handle + offset); \
525		else \
526			*(volatile type *)(handle + offset) = value; \
527	}
528
529BUS_SPACE_READ(1, uint8_t, in8)
530BUS_SPACE_READ(2, uint16_t, in16)
531BUS_SPACE_READ(4, uint32_t, in32)
532
533BUS_SPACE_WRITE(1, uint8_t, out8)
534BUS_SPACE_WRITE(2, uint16_t, out16)
535BUS_SPACE_WRITE(4, uint32_t, out32)
536
537int
538bus_child_present(device_t child)
539{
540	device_t parent = device_get_parent(child);
541	if (parent == NULL)
542		return 0;
543
544	return bus_child_present(parent);
545}
546
547
548//	#pragma mark - PCI functions
549
550
551uint32_t
552pci_read_config(device_t dev, int offset, int size)
553{
554	pci_info *info = &((struct root_device_softc *)dev->root->softc)->pci_info;
555
556	uint32_t value = gPci->read_pci_config(info->bus, info->device,
557		info->function, offset, size);
558	TRACE_PCI(dev, "pci_read_config(%i, %i) = 0x%x\n", offset, size, value);
559	return value;
560}
561
562
563void
564pci_write_config(device_t dev, int offset, uint32_t value, int size)
565{
566	pci_info *info = &((struct root_device_softc *)dev->root->softc)->pci_info;
567
568	TRACE_PCI(dev, "pci_write_config(%i, 0x%x, %i)\n", offset, value, size);
569
570	gPci->write_pci_config(info->bus, info->device, info->function, offset,
571		size, value);
572}
573
574
575uint16_t
576pci_get_vendor(device_t dev)
577{
578	return pci_read_config(dev, PCI_vendor_id, 2);
579}
580
581
582uint16_t
583pci_get_device(device_t dev)
584{
585	return pci_read_config(dev, PCI_device_id, 2);
586}
587
588
589uint16_t
590pci_get_subvendor(device_t dev)
591{
592	return pci_read_config(dev, PCI_subsystem_vendor_id, 2);
593}
594
595
596uint16_t
597pci_get_subdevice(device_t dev)
598{
599	return pci_read_config(dev, PCI_subsystem_id, 2);
600}
601
602
603uint8_t
604pci_get_revid(device_t dev)
605{
606	return pci_read_config(dev, PCI_revision, 1);
607}
608
609
610uint32_t
611pci_get_domain(device_t dev)
612{
613	return 0;
614}
615
616uint32_t
617pci_get_devid(device_t dev)
618{
619	return pci_read_config(dev, PCI_device_id, 2) << 16 |
620		pci_read_config(dev, PCI_vendor_id, 2);
621}
622
623uint8_t
624pci_get_cachelnsz(device_t dev)
625{
626	return pci_read_config(dev, PCI_line_size, 1);
627}
628
629uint8_t *
630pci_get_ether(device_t dev)
631{
632	/* used in if_dc to get the MAC from CardBus CIS for Xircom card */
633	return NULL; /* NULL is handled in the caller correctly */
634}
635
636uint8_t
637pci_get_bus(device_t dev)
638{
639	pci_info *info
640		= &((struct root_device_softc *)dev->root->softc)->pci_info;
641	return info->bus;
642}
643
644
645uint8_t
646pci_get_slot(device_t dev)
647{
648	pci_info *info
649		= &((struct root_device_softc *)dev->root->softc)->pci_info;
650	return info->device;
651}
652
653
654uint8_t
655pci_get_function(device_t dev)
656{
657	pci_info *info
658		= &((struct root_device_softc *)dev->root->softc)->pci_info;
659	return info->function;
660}
661
662
663device_t
664pci_find_dbsf(uint32_t domain, uint8_t bus, uint8_t slot, uint8_t func)
665{
666	// We don't support that yet - if we want to support the multi port
667	// feature of the Broadcom BCM 570x driver, we would have to change
668	// that.
669	return NULL;
670}
671
672
673static void
674pci_set_command_bit(device_t dev, uint16_t bit)
675{
676	uint16_t command = pci_read_config(dev, PCI_command, 2);
677	pci_write_config(dev, PCI_command, command | bit, 2);
678}
679
680
681int
682pci_enable_busmaster(device_t dev)
683{
684	pci_set_command_bit(dev, PCI_command_master);
685	return 0;
686}
687
688
689int
690pci_enable_io(device_t dev, int space)
691{
692	/* adapted from FreeBSD's pci_enable_io_method */
693	int bit = 0;
694
695	switch (space) {
696		case SYS_RES_IOPORT:
697			bit = PCI_command_io;
698			break;
699		case SYS_RES_MEMORY:
700			bit = PCI_command_memory;
701			break;
702		default:
703			return EINVAL;
704	}
705
706	pci_set_command_bit(dev, bit);
707	if (pci_read_config(dev, PCI_command, 2) & bit)
708		return 0;
709
710	device_printf(dev, "pci_enable_io(%d) failed.\n", space);
711
712	return ENXIO;
713}
714
715
716int
717pci_find_cap(device_t dev, int capability, int *capreg)
718{
719	return pci_find_extcap(dev, capability, capreg);
720}
721
722
723int
724pci_find_extcap(device_t child, int capability, int *_capabilityRegister)
725{
726	uint8 capabilityPointer;
727	uint8 headerType;
728	uint16 status;
729
730	status = pci_read_config(child, PCIR_STATUS, 2);
731	if ((status & PCIM_STATUS_CAPPRESENT) == 0)
732		return ENXIO;
733
734	headerType = pci_read_config(child, PCI_header_type, 1);
735	switch (headerType & PCIM_HDRTYPE) {
736		case 0:
737		case 1:
738			capabilityPointer = PCIR_CAP_PTR;
739			break;
740		case 2:
741			capabilityPointer = PCIR_CAP_PTR_2;
742			break;
743		default:
744			return ENXIO;
745	}
746	capabilityPointer = pci_read_config(child, capabilityPointer, 1);
747
748	while (capabilityPointer != 0) {
749		if (pci_read_config(child, capabilityPointer + PCICAP_ID, 1)
750				== capability) {
751			if (_capabilityRegister != NULL)
752				*_capabilityRegister = capabilityPointer;
753			return 0;
754		}
755		capabilityPointer = pci_read_config(child,
756			capabilityPointer + PCICAP_NEXTPTR, 1);
757	}
758
759	return ENOENT;
760}
761
762
763int
764pci_msi_count(device_t dev)
765{
766	pci_info *info;
767	if (gPCIx86 == NULL)
768		return 0;
769
770	info = &((struct root_device_softc *)dev->root->softc)->pci_info;
771	return gPCIx86->get_msi_count(info->bus, info->device, info->function);
772}
773
774
775int
776pci_alloc_msi(device_t dev, int *count)
777{
778	pci_info *info;
779	uint8 startVector = 0;
780	if (gPCIx86 == NULL)
781		return ENODEV;
782
783	info = &((struct root_device_softc *)dev->root->softc)->pci_info;
784
785	if (gPCIx86->configure_msi(info->bus, info->device, info->function, *count,
786			&startVector) != B_OK) {
787		return ENODEV;
788	}
789
790	info->u.h0.interrupt_line = startVector;
791	return EOK;
792}
793
794
795int
796pci_release_msi(device_t dev)
797{
798	pci_info *info;
799	if (gPCIx86 == NULL)
800		return ENODEV;
801
802	info = &((struct root_device_softc *)dev->root->softc)->pci_info;
803	gPCIx86->unconfigure_msi(info->bus, info->device, info->function);
804	return EOK;
805}
806
807
808int
809pci_msix_count(device_t dev)
810{
811	return 0;
812}
813
814
815int
816pci_alloc_msix(device_t dev, int *count)
817{
818	return ENODEV;
819}
820
821
822int
823pci_get_max_read_req(device_t dev)
824{
825	int cap;
826	uint16_t val;
827
828	if (pci_find_extcap(dev, PCIY_EXPRESS, &cap) != 0)
829		return (0);
830	val = pci_read_config(dev, cap + PCIR_EXPRESS_DEVICE_CTL, 2);
831	val &= PCIM_EXP_CTL_MAX_READ_REQUEST;
832	val >>= 12;
833	return (1 << (val + 7));
834}
835
836
837int
838pci_set_max_read_req(device_t dev, int size)
839{
840	int cap;
841	uint16_t val;
842
843	if (pci_find_extcap(dev, PCIY_EXPRESS, &cap) != 0)
844		return (0);
845	if (size < 128)
846		size = 128;
847	if (size > 4096)
848		size = 4096;
849	size = (1 << (fls(size) - 1));
850	val = pci_read_config(dev, cap + PCIR_EXPRESS_DEVICE_CTL, 2);
851	val &= ~PCIM_EXP_CTL_MAX_READ_REQUEST;
852	val |= (fls(size) - 8) << 12;
853	pci_write_config(dev, cap + PCIR_EXPRESS_DEVICE_CTL, val, 2);
854	return (size);
855}
856
857
858int
859pci_get_powerstate(device_t dev)
860{
861	int capabilityRegister;
862	uint16 status;
863	int powerState = PCI_POWERSTATE_D0;
864
865	if (pci_find_extcap(dev, PCIY_PMG, &capabilityRegister) != EOK)
866		return powerState;
867
868	status = pci_read_config(dev, capabilityRegister + PCIR_POWER_STATUS, 2);
869	switch (status & PCI_pm_mask) {
870		case PCI_pm_state_d0:
871			break;
872		case PCI_pm_state_d1:
873			powerState = PCI_POWERSTATE_D1;
874			break;
875		case PCI_pm_state_d2:
876			powerState = PCI_POWERSTATE_D2;
877			break;
878		case PCI_pm_state_d3:
879			powerState = PCI_POWERSTATE_D3;
880			break;
881		default:
882			powerState = PCI_POWERSTATE_UNKNOWN;
883			break;
884	}
885
886	TRACE_PCI(dev, "%s: D%i\n", __func__, powerState);
887	return powerState;
888}
889
890
891int
892pci_set_powerstate(device_t dev, int newPowerState)
893{
894	int capabilityRegister;
895	int oldPowerState;
896	uint8 currentPowerManagementStatus;
897	uint8 newPowerManagementStatus;
898	uint16 powerManagementCapabilities;
899	bigtime_t stateTransitionDelayInUs = 0;
900
901	if (pci_find_extcap(dev, PCIY_PMG, &capabilityRegister) != EOK)
902		return EOPNOTSUPP;
903
904	oldPowerState = pci_get_powerstate(dev);
905	if (oldPowerState == newPowerState)
906		return EOK;
907
908	switch (std::max(oldPowerState, newPowerState)) {
909		case PCI_POWERSTATE_D2:
910			stateTransitionDelayInUs = 200;
911			break;
912		case PCI_POWERSTATE_D3:
913			stateTransitionDelayInUs = 10000;
914			break;
915	}
916
917	currentPowerManagementStatus = pci_read_config(dev, capabilityRegister
918		+ PCIR_POWER_STATUS, 2);
919	newPowerManagementStatus = currentPowerManagementStatus & ~PCI_pm_mask;
920	powerManagementCapabilities = pci_read_config(dev, capabilityRegister
921		+ PCIR_POWER_CAP, 2);
922
923	switch (newPowerState) {
924		case PCI_POWERSTATE_D0:
925			newPowerManagementStatus |= PCIM_PSTAT_D0;
926			break;
927		case PCI_POWERSTATE_D1:
928			if ((powerManagementCapabilities & PCI_pm_d1supp) == 0)
929				return EOPNOTSUPP;
930			newPowerManagementStatus |= PCIM_PSTAT_D1;
931			break;
932		case PCI_POWERSTATE_D2:
933			if ((powerManagementCapabilities & PCI_pm_d2supp) == 0)
934				return EOPNOTSUPP;
935			newPowerManagementStatus |= PCIM_PSTAT_D2;
936			break;
937		case PCI_POWERSTATE_D3:
938			newPowerManagementStatus |= PCIM_PSTAT_D3;
939			break;
940		default:
941			return EINVAL;
942	}
943
944	TRACE_PCI(dev, "%s: D%i -> D%i\n", __func__, oldPowerState, newPowerState);
945	pci_write_config(dev, capabilityRegister + PCIR_POWER_STATUS, newPowerState,
946		2);
947	if (stateTransitionDelayInUs != 0)
948		snooze(stateTransitionDelayInUs);
949
950	return EOK;
951}
952