xref: /openbmc/linux/drivers/pnp/quirks.c (revision a442ac51)
11da177e4SLinus Torvalds /*
21da177e4SLinus Torvalds  *  This file contains quirk handling code for PnP devices
31da177e4SLinus Torvalds  *  Some devices do not report all their resources, and need to have extra
41da177e4SLinus Torvalds  *  resources added. This is most easily accomplished at initialisation time
51da177e4SLinus Torvalds  *  when building up the resource structure for the first time.
61da177e4SLinus Torvalds  *
71da177e4SLinus Torvalds  *  Copyright (c) 2000 Peter Denison <peterd@pnd-pc.demon.co.uk>
81da177e4SLinus Torvalds  *
91da177e4SLinus Torvalds  *  Heavily based on PCI quirks handling which is
101da177e4SLinus Torvalds  *
111da177e4SLinus Torvalds  *  Copyright (c) 1999 Martin Mares <mj@ucw.cz>
121da177e4SLinus Torvalds  */
131da177e4SLinus Torvalds 
141da177e4SLinus Torvalds #include <linux/types.h>
151da177e4SLinus Torvalds #include <linux/kernel.h>
161da177e4SLinus Torvalds #include <linux/string.h>
171da177e4SLinus Torvalds #include <linux/slab.h>
181da177e4SLinus Torvalds #include <linux/pnp.h>
19a1e7e636SBjorn Helgaas #include <linux/io.h>
20a05d0781SBjorn Helgaas #include <linux/kallsyms.h>
211da177e4SLinus Torvalds #include "base.h"
221da177e4SLinus Torvalds 
231da177e4SLinus Torvalds static void quirk_awe32_resources(struct pnp_dev *dev)
241da177e4SLinus Torvalds {
251da177e4SLinus Torvalds 	struct pnp_port *port, *port2, *port3;
261da177e4SLinus Torvalds 	struct pnp_option *res = dev->dependent;
271da177e4SLinus Torvalds 
281da177e4SLinus Torvalds 	/*
291da177e4SLinus Torvalds 	 * Unfortunately the isapnp_add_port_resource is too tightly bound
301da177e4SLinus Torvalds 	 * into the PnP discovery sequence, and cannot be used. Link in the
311da177e4SLinus Torvalds 	 * two extra ports (at offset 0x400 and 0x800 from the one given) by
321da177e4SLinus Torvalds 	 * hand.
331da177e4SLinus Torvalds 	 */
341da177e4SLinus Torvalds 	for (; res; res = res->next) {
351da177e4SLinus Torvalds 		port2 = pnp_alloc(sizeof(struct pnp_port));
361da177e4SLinus Torvalds 		if (!port2)
371da177e4SLinus Torvalds 			return;
381da177e4SLinus Torvalds 		port3 = pnp_alloc(sizeof(struct pnp_port));
391da177e4SLinus Torvalds 		if (!port3) {
401da177e4SLinus Torvalds 			kfree(port2);
411da177e4SLinus Torvalds 			return;
421da177e4SLinus Torvalds 		}
431da177e4SLinus Torvalds 		port = res->port;
441da177e4SLinus Torvalds 		memcpy(port2, port, sizeof(struct pnp_port));
451da177e4SLinus Torvalds 		memcpy(port3, port, sizeof(struct pnp_port));
461da177e4SLinus Torvalds 		port->next = port2;
471da177e4SLinus Torvalds 		port2->next = port3;
481da177e4SLinus Torvalds 		port2->min += 0x400;
491da177e4SLinus Torvalds 		port2->max += 0x400;
501da177e4SLinus Torvalds 		port3->min += 0x800;
511da177e4SLinus Torvalds 		port3->max += 0x800;
521e3832b0SBjorn Helgaas 		dev_info(&dev->dev,
531e3832b0SBjorn Helgaas 			"AWE32 quirk - added ioports 0x%lx and 0x%lx\n",
541e3832b0SBjorn Helgaas 			(unsigned long)port2->min,
551e3832b0SBjorn Helgaas 			(unsigned long)port3->min);
561da177e4SLinus Torvalds 	}
571da177e4SLinus Torvalds }
581da177e4SLinus Torvalds 
591da177e4SLinus Torvalds static void quirk_cmi8330_resources(struct pnp_dev *dev)
601da177e4SLinus Torvalds {
611da177e4SLinus Torvalds 	struct pnp_option *res = dev->dependent;
621da177e4SLinus Torvalds 	unsigned long tmp;
631da177e4SLinus Torvalds 
641da177e4SLinus Torvalds 	for (; res; res = res->next) {
651da177e4SLinus Torvalds 
661da177e4SLinus Torvalds 		struct pnp_irq *irq;
671da177e4SLinus Torvalds 		struct pnp_dma *dma;
681da177e4SLinus Torvalds 
691da177e4SLinus Torvalds 		for (irq = res->irq; irq; irq = irq->next) {	// Valid irqs are 5, 7, 10
701da177e4SLinus Torvalds 			tmp = 0x04A0;
711da177e4SLinus Torvalds 			bitmap_copy(irq->map, &tmp, 16);	// 0000 0100 1010 0000
721da177e4SLinus Torvalds 		}
731da177e4SLinus Torvalds 
741da177e4SLinus Torvalds 		for (dma = res->dma; dma; dma = dma->next)	// Valid 8bit dma channels are 1,3
759dd78466SBjorn Helgaas 			if ((dma->flags & IORESOURCE_DMA_TYPE_MASK) ==
769dd78466SBjorn Helgaas 			    IORESOURCE_DMA_8BIT)
771da177e4SLinus Torvalds 				dma->map = 0x000A;
781da177e4SLinus Torvalds 	}
791e3832b0SBjorn Helgaas 	dev_info(&dev->dev, "CMI8330 quirk - forced possible IRQs to 5, 7, 10 "
801e3832b0SBjorn Helgaas 		"and DMA channels to 1, 3\n");
811da177e4SLinus Torvalds }
821da177e4SLinus Torvalds 
831da177e4SLinus Torvalds static void quirk_sb16audio_resources(struct pnp_dev *dev)
841da177e4SLinus Torvalds {
851da177e4SLinus Torvalds 	struct pnp_port *port;
861da177e4SLinus Torvalds 	struct pnp_option *res = dev->dependent;
871da177e4SLinus Torvalds 	int changed = 0;
881da177e4SLinus Torvalds 
891da177e4SLinus Torvalds 	/*
901da177e4SLinus Torvalds 	 * The default range on the mpu port for these devices is 0x388-0x388.
911da177e4SLinus Torvalds 	 * Here we increase that range so that two such cards can be
921da177e4SLinus Torvalds 	 * auto-configured.
931da177e4SLinus Torvalds 	 */
941da177e4SLinus Torvalds 
951da177e4SLinus Torvalds 	for (; res; res = res->next) {
961da177e4SLinus Torvalds 		port = res->port;
971da177e4SLinus Torvalds 		if (!port)
981da177e4SLinus Torvalds 			continue;
991da177e4SLinus Torvalds 		port = port->next;
1001da177e4SLinus Torvalds 		if (!port)
1011da177e4SLinus Torvalds 			continue;
1021da177e4SLinus Torvalds 		port = port->next;
1031da177e4SLinus Torvalds 		if (!port)
1041da177e4SLinus Torvalds 			continue;
1051da177e4SLinus Torvalds 		if (port->min != port->max)
1061da177e4SLinus Torvalds 			continue;
1071da177e4SLinus Torvalds 		port->max += 0x70;
1081da177e4SLinus Torvalds 		changed = 1;
1091da177e4SLinus Torvalds 	}
1101da177e4SLinus Torvalds 	if (changed)
1111e3832b0SBjorn Helgaas 		dev_info(&dev->dev, "SB audio device quirk - increased port range\n");
1121da177e4SLinus Torvalds }
1131da177e4SLinus Torvalds 
1143b73a223SRene Herman static struct pnp_option *quirk_isapnp_mpu_options(struct pnp_dev *dev)
1153b73a223SRene Herman {
1163b73a223SRene Herman 	struct pnp_option *head = NULL;
1173b73a223SRene Herman 	struct pnp_option *prev = NULL;
1183b73a223SRene Herman 	struct pnp_option *res;
1193b73a223SRene Herman 
1203b73a223SRene Herman 	/*
1213b73a223SRene Herman 	 * Build a functional IRQ-less variant of each MPU option.
1223b73a223SRene Herman 	 */
1233b73a223SRene Herman 
1243b73a223SRene Herman 	for (res = dev->dependent; res; res = res->next) {
1253b73a223SRene Herman 		struct pnp_option *curr;
1263b73a223SRene Herman 		struct pnp_port *port;
1273b73a223SRene Herman 		struct pnp_port *copy;
1283b73a223SRene Herman 
1293b73a223SRene Herman 		port = res->port;
1303b73a223SRene Herman 		if (!port || !res->irq)
1313b73a223SRene Herman 			continue;
1323b73a223SRene Herman 
1333b73a223SRene Herman 		copy = pnp_alloc(sizeof *copy);
1343b73a223SRene Herman 		if (!copy)
1353b73a223SRene Herman 			break;
1363b73a223SRene Herman 
1373b73a223SRene Herman 		copy->min = port->min;
1383b73a223SRene Herman 		copy->max = port->max;
1393b73a223SRene Herman 		copy->align = port->align;
1403b73a223SRene Herman 		copy->size = port->size;
1413b73a223SRene Herman 		copy->flags = port->flags;
1423b73a223SRene Herman 
1433b73a223SRene Herman 		curr = pnp_build_option(PNP_RES_PRIORITY_FUNCTIONAL);
1443b73a223SRene Herman 		if (!curr) {
1453b73a223SRene Herman 			kfree(copy);
1463b73a223SRene Herman 			break;
1473b73a223SRene Herman 		}
1483b73a223SRene Herman 		curr->port = copy;
1493b73a223SRene Herman 
1503b73a223SRene Herman 		if (prev)
1513b73a223SRene Herman 			prev->next = curr;
1523b73a223SRene Herman 		else
1533b73a223SRene Herman 			head = curr;
1543b73a223SRene Herman 		prev = curr;
1553b73a223SRene Herman 	}
1563b73a223SRene Herman 	if (head)
1573b73a223SRene Herman 		dev_info(&dev->dev, "adding IRQ-less MPU options\n");
1583b73a223SRene Herman 
1593b73a223SRene Herman 	return head;
1603b73a223SRene Herman }
1613b73a223SRene Herman 
1623b73a223SRene Herman static void quirk_ad1815_mpu_resources(struct pnp_dev *dev)
1633b73a223SRene Herman {
1643b73a223SRene Herman 	struct pnp_option *res;
1653b73a223SRene Herman 	struct pnp_irq *irq;
1663b73a223SRene Herman 
1673b73a223SRene Herman 	/*
1683b73a223SRene Herman 	 * Distribute the independent IRQ over the dependent options
1693b73a223SRene Herman 	 */
1703b73a223SRene Herman 
1713b73a223SRene Herman 	res = dev->independent;
1723b73a223SRene Herman 	if (!res)
1733b73a223SRene Herman 		return;
1743b73a223SRene Herman 
1753b73a223SRene Herman 	irq = res->irq;
1763b73a223SRene Herman 	if (!irq || irq->next)
1773b73a223SRene Herman 		return;
1783b73a223SRene Herman 
1793b73a223SRene Herman 	res = dev->dependent;
1803b73a223SRene Herman 	if (!res)
1813b73a223SRene Herman 		return;
1823b73a223SRene Herman 
1833b73a223SRene Herman 	while (1) {
1843b73a223SRene Herman 		struct pnp_irq *copy;
1853b73a223SRene Herman 
1863b73a223SRene Herman 		copy = pnp_alloc(sizeof *copy);
1873b73a223SRene Herman 		if (!copy)
1883b73a223SRene Herman 			break;
1893b73a223SRene Herman 
1903b73a223SRene Herman 		memcpy(copy->map, irq->map, sizeof copy->map);
1913b73a223SRene Herman 		copy->flags = irq->flags;
1923b73a223SRene Herman 
1933b73a223SRene Herman 		copy->next = res->irq; /* Yes, this is NULL */
1943b73a223SRene Herman 		res->irq = copy;
1953b73a223SRene Herman 
1963b73a223SRene Herman 		if (!res->next)
1973b73a223SRene Herman 			break;
1983b73a223SRene Herman 		res = res->next;
1993b73a223SRene Herman 	}
2003b73a223SRene Herman 	kfree(irq);
2013b73a223SRene Herman 
2023b73a223SRene Herman 	res->next = quirk_isapnp_mpu_options(dev);
2033b73a223SRene Herman 
2043b73a223SRene Herman 	res = dev->independent;
2053b73a223SRene Herman 	res->irq = NULL;
2063b73a223SRene Herman }
2073b73a223SRene Herman 
2083b73a223SRene Herman static void quirk_isapnp_mpu_resources(struct pnp_dev *dev)
2093b73a223SRene Herman {
2103b73a223SRene Herman 	struct pnp_option *res;
2113b73a223SRene Herman 
2123b73a223SRene Herman 	res = dev->dependent;
2133b73a223SRene Herman 	if (!res)
2143b73a223SRene Herman 		return;
2153b73a223SRene Herman 
2163b73a223SRene Herman 	while (res->next)
2173b73a223SRene Herman 		res = res->next;
2183b73a223SRene Herman 
2193b73a223SRene Herman 	res->next = quirk_isapnp_mpu_options(dev);
2203b73a223SRene Herman }
2210509ad5eSBjorn Helgaas 
2220509ad5eSBjorn Helgaas #include <linux/pci.h>
2230509ad5eSBjorn Helgaas 
2240509ad5eSBjorn Helgaas static void quirk_system_pci_resources(struct pnp_dev *dev)
2250509ad5eSBjorn Helgaas {
2260509ad5eSBjorn Helgaas 	struct pci_dev *pdev = NULL;
22753052febSBjorn Helgaas 	struct resource *res;
2280509ad5eSBjorn Helgaas 	resource_size_t pnp_start, pnp_end, pci_start, pci_end;
2290509ad5eSBjorn Helgaas 	int i, j;
2300509ad5eSBjorn Helgaas 
2310509ad5eSBjorn Helgaas 	/*
2320509ad5eSBjorn Helgaas 	 * Some BIOSes have PNP motherboard devices with resources that
2330509ad5eSBjorn Helgaas 	 * partially overlap PCI BARs.  The PNP system driver claims these
2340509ad5eSBjorn Helgaas 	 * motherboard resources, which prevents the normal PCI driver from
2350509ad5eSBjorn Helgaas 	 * requesting them later.
2360509ad5eSBjorn Helgaas 	 *
2370509ad5eSBjorn Helgaas 	 * This patch disables the PNP resources that conflict with PCI BARs
2380509ad5eSBjorn Helgaas 	 * so they won't be claimed by the PNP system driver.
2390509ad5eSBjorn Helgaas 	 */
2400509ad5eSBjorn Helgaas 	for_each_pci_dev(pdev) {
2410509ad5eSBjorn Helgaas 		for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) {
2420509ad5eSBjorn Helgaas 			if (!(pci_resource_flags(pdev, i) & IORESOURCE_MEM) ||
2430509ad5eSBjorn Helgaas 			    pci_resource_len(pdev, i) == 0)
2440509ad5eSBjorn Helgaas 				continue;
2450509ad5eSBjorn Helgaas 
2460509ad5eSBjorn Helgaas 			pci_start = pci_resource_start(pdev, i);
2470509ad5eSBjorn Helgaas 			pci_end = pci_resource_end(pdev, i);
24895ab3669SBjorn Helgaas 			for (j = 0;
24995ab3669SBjorn Helgaas 			     (res = pnp_get_resource(dev, IORESOURCE_MEM, j));
25095ab3669SBjorn Helgaas 			     j++) {
25195ab3669SBjorn Helgaas 				if (res->flags & IORESOURCE_UNSET ||
25295ab3669SBjorn Helgaas 				    (res->start == 0 && res->end == 0))
2530509ad5eSBjorn Helgaas 					continue;
2540509ad5eSBjorn Helgaas 
25595ab3669SBjorn Helgaas 				pnp_start = res->start;
25695ab3669SBjorn Helgaas 				pnp_end = res->end;
2570509ad5eSBjorn Helgaas 
2580509ad5eSBjorn Helgaas 				/*
2590509ad5eSBjorn Helgaas 				 * If the PNP region doesn't overlap the PCI
2600509ad5eSBjorn Helgaas 				 * region at all, there's no problem.
2610509ad5eSBjorn Helgaas 				 */
2620509ad5eSBjorn Helgaas 				if (pnp_end < pci_start || pnp_start > pci_end)
2630509ad5eSBjorn Helgaas 					continue;
2640509ad5eSBjorn Helgaas 
2650509ad5eSBjorn Helgaas 				/*
2660509ad5eSBjorn Helgaas 				 * If the PNP region completely encloses (or is
2670509ad5eSBjorn Helgaas 				 * at least as large as) the PCI region, that's
2680509ad5eSBjorn Helgaas 				 * also OK.  For example, this happens when the
2690509ad5eSBjorn Helgaas 				 * PNP device describes a bridge with PCI
2700509ad5eSBjorn Helgaas 				 * behind it.
2710509ad5eSBjorn Helgaas 				 */
2720509ad5eSBjorn Helgaas 				if (pnp_start <= pci_start &&
2730509ad5eSBjorn Helgaas 				    pnp_end >= pci_end)
2740509ad5eSBjorn Helgaas 					continue;
2750509ad5eSBjorn Helgaas 
2760509ad5eSBjorn Helgaas 				/*
2770509ad5eSBjorn Helgaas 				 * Otherwise, the PNP region overlaps *part* of
2780509ad5eSBjorn Helgaas 				 * the PCI region, and that might prevent a PCI
2790509ad5eSBjorn Helgaas 				 * driver from requesting its resources.
2800509ad5eSBjorn Helgaas 				 */
2810509ad5eSBjorn Helgaas 				dev_warn(&dev->dev, "mem resource "
2820509ad5eSBjorn Helgaas 					"(0x%llx-0x%llx) overlaps %s BAR %d "
2830509ad5eSBjorn Helgaas 					"(0x%llx-0x%llx), disabling\n",
2840509ad5eSBjorn Helgaas 					(unsigned long long) pnp_start,
2850509ad5eSBjorn Helgaas 					(unsigned long long) pnp_end,
2860509ad5eSBjorn Helgaas 					pci_name(pdev), i,
2870509ad5eSBjorn Helgaas 					(unsigned long long) pci_start,
2880509ad5eSBjorn Helgaas 					(unsigned long long) pci_end);
28953052febSBjorn Helgaas 				res->flags = 0;
2900509ad5eSBjorn Helgaas 			}
2910509ad5eSBjorn Helgaas 		}
2920509ad5eSBjorn Helgaas 	}
2930509ad5eSBjorn Helgaas }
2940509ad5eSBjorn Helgaas 
2951da177e4SLinus Torvalds /*
2961da177e4SLinus Torvalds  *  PnP Quirks
2971da177e4SLinus Torvalds  *  Cards or devices that need some tweaking due to incomplete resource info
2981da177e4SLinus Torvalds  */
2991da177e4SLinus Torvalds 
3001da177e4SLinus Torvalds static struct pnp_fixup pnp_fixups[] = {
3011da177e4SLinus Torvalds 	/* Soundblaster awe io port quirk */
3021da177e4SLinus Torvalds 	{"CTL0021", quirk_awe32_resources},
3031da177e4SLinus Torvalds 	{"CTL0022", quirk_awe32_resources},
3041da177e4SLinus Torvalds 	{"CTL0023", quirk_awe32_resources},
3051da177e4SLinus Torvalds 	/* CMI 8330 interrupt and dma fix */
3061da177e4SLinus Torvalds 	{"@X@0001", quirk_cmi8330_resources},
3071da177e4SLinus Torvalds 	/* Soundblaster audio device io port range quirk */
3081da177e4SLinus Torvalds 	{"CTL0001", quirk_sb16audio_resources},
3091da177e4SLinus Torvalds 	{"CTL0031", quirk_sb16audio_resources},
3101da177e4SLinus Torvalds 	{"CTL0041", quirk_sb16audio_resources},
3111da177e4SLinus Torvalds 	{"CTL0042", quirk_sb16audio_resources},
3121da177e4SLinus Torvalds 	{"CTL0043", quirk_sb16audio_resources},
3131da177e4SLinus Torvalds 	{"CTL0044", quirk_sb16audio_resources},
3141da177e4SLinus Torvalds 	{"CTL0045", quirk_sb16audio_resources},
3153b73a223SRene Herman 	/* Add IRQ-less MPU options */
3163b73a223SRene Herman 	{"ADS7151", quirk_ad1815_mpu_resources},
3173b73a223SRene Herman 	{"ADS7181", quirk_isapnp_mpu_resources},
3183b73a223SRene Herman 	{"AZT0002", quirk_isapnp_mpu_resources},
3193b73a223SRene Herman 	/* PnP resources that might overlap PCI BARs */
3200509ad5eSBjorn Helgaas 	{"PNP0c01", quirk_system_pci_resources},
3210509ad5eSBjorn Helgaas 	{"PNP0c02", quirk_system_pci_resources},
3221da177e4SLinus Torvalds 	{""}
3231da177e4SLinus Torvalds };
3241da177e4SLinus Torvalds 
3251da177e4SLinus Torvalds void pnp_fixup_device(struct pnp_dev *dev)
3261da177e4SLinus Torvalds {
327726a7a3dSRene Herman 	struct pnp_fixup *f;
3281da177e4SLinus Torvalds 
329726a7a3dSRene Herman 	for (f = pnp_fixups; *f->id; f++) {
330726a7a3dSRene Herman 		if (!compare_pnp_id(dev->id, f->id))
331726a7a3dSRene Herman 			continue;
332a05d0781SBjorn Helgaas #ifdef DEBUG
333726a7a3dSRene Herman 		dev_dbg(&dev->dev, "%s: calling ", f->id);
334a442ac51SLinus Torvalds 		print_fn_descriptor_symbol("%s\n", f->quirk_function);
335a05d0781SBjorn Helgaas #endif
336726a7a3dSRene Herman 		f->quirk_function(dev);
3371da177e4SLinus Torvalds 	}
3381da177e4SLinus Torvalds }
339