diff options
Diffstat (limited to 'arch/x86/platform')
75 files changed, 9990 insertions, 2295 deletions
diff --git a/arch/x86/platform/Makefile b/arch/x86/platform/Makefile index 7bf70b812fa..85afde1fa3e 100644 --- a/arch/x86/platform/Makefile +++ b/arch/x86/platform/Makefile @@ -1,8 +1,12 @@  # Platform specific code goes here +obj-y	+= ce4100/  obj-y	+= efi/ -obj-y	+= mrst/ +obj-y	+= geode/ +obj-y	+= goldfish/ +obj-y	+= iris/ +obj-y	+= intel-mid/  obj-y	+= olpc/  obj-y	+= scx200/  obj-y	+= sfi/ -obj-y	+= visws/ +obj-y	+= ts5500/  obj-y	+= uv/ diff --git a/arch/x86/platform/ce4100/Makefile b/arch/x86/platform/ce4100/Makefile new file mode 100644 index 00000000000..91fc92971d9 --- /dev/null +++ b/arch/x86/platform/ce4100/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_X86_INTEL_CE)	+= ce4100.o diff --git a/arch/x86/platform/ce4100/ce4100.c b/arch/x86/platform/ce4100/ce4100.c new file mode 100644 index 00000000000..8244f5ec2f4 --- /dev/null +++ b/arch/x86/platform/ce4100/ce4100.c @@ -0,0 +1,175 @@ +/* + * Intel CE4100  platform specific setup code + * + * (C) Copyright 2010 Intel Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + */ +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/reboot.h> +#include <linux/serial_reg.h> +#include <linux/serial_8250.h> +#include <linux/reboot.h> + +#include <asm/ce4100.h> +#include <asm/prom.h> +#include <asm/setup.h> +#include <asm/i8259.h> +#include <asm/io.h> +#include <asm/io_apic.h> +#include <asm/emergency-restart.h> + +static int ce4100_i8042_detect(void) +{ +	return 0; +} + +/* + * The CE4100 platform has an internal 8051 Microcontroller which is + * responsible for signaling to the external Power Management Unit the + * intention to reset, reboot or power off the system. This 8051 device has + * its command register mapped at I/O port 0xcf9 and the value 0x4 is used + * to power off the system. + */ +static void ce4100_power_off(void) +{ +	outb(0x4, 0xcf9); +} + +#ifdef CONFIG_SERIAL_8250 + +static unsigned int mem_serial_in(struct uart_port *p, int offset) +{ +	offset = offset << p->regshift; +	return readl(p->membase + offset); +} + +/* + * The UART Tx interrupts are not set under some conditions and therefore serial + * transmission hangs. This is a silicon issue and has not been root caused. The + * workaround for this silicon issue checks UART_LSR_THRE bit and UART_LSR_TEMT + * bit of LSR register in interrupt handler to see whether at least one of these + * two bits is set, if so then process the transmit request. If this workaround + * is not applied, then the serial transmission may hang. This workaround is for + * errata number 9 in Errata - B step. +*/ + +static unsigned int ce4100_mem_serial_in(struct uart_port *p, int offset) +{ +	unsigned int ret, ier, lsr; + +	if (offset == UART_IIR) { +		offset = offset << p->regshift; +		ret = readl(p->membase + offset); +		if (ret & UART_IIR_NO_INT) { +			/* see if the TX interrupt should have really set */ +			ier = mem_serial_in(p, UART_IER); +			/* see if the UART's XMIT interrupt is enabled */ +			if (ier & UART_IER_THRI) { +				lsr = mem_serial_in(p, UART_LSR); +				/* now check to see if the UART should be +				   generating an interrupt (but isn't) */ +				if (lsr & (UART_LSR_THRE | UART_LSR_TEMT)) +					ret &= ~UART_IIR_NO_INT; +			} +		} +	} else +		ret =  mem_serial_in(p, offset); +	return ret; +} + +static void ce4100_mem_serial_out(struct uart_port *p, int offset, int value) +{ +	offset = offset << p->regshift; +	writel(value, p->membase + offset); +} + +static void ce4100_serial_fixup(int port, struct uart_port *up, +	unsigned short *capabilites) +{ +#ifdef CONFIG_EARLY_PRINTK +	/* +	 * Over ride the legacy port configuration that comes from +	 * asm/serial.h. Using the ioport driver then switching to the +	 * PCI memmaped driver hangs the IOAPIC +	 */ +	if (up->iotype !=  UPIO_MEM32) { +		up->uartclk  = 14745600; +		up->mapbase = 0xdffe0200; +		set_fixmap_nocache(FIX_EARLYCON_MEM_BASE, +				up->mapbase & PAGE_MASK); +		up->membase = +			(void __iomem *)__fix_to_virt(FIX_EARLYCON_MEM_BASE); +		up->membase += up->mapbase & ~PAGE_MASK; +		up->mapbase += port * 0x100; +		up->membase += port * 0x100; +		up->iotype   = UPIO_MEM32; +		up->regshift = 2; +		up->irq = 4; +	} +#endif +	up->iobase = 0; +	up->serial_in = ce4100_mem_serial_in; +	up->serial_out = ce4100_mem_serial_out; + +	*capabilites |= (1 << 12); +} + +static __init void sdv_serial_fixup(void) +{ +	serial8250_set_isa_configurator(ce4100_serial_fixup); +} + +#else +static inline void sdv_serial_fixup(void) {}; +#endif + +static void __init sdv_arch_setup(void) +{ +	sdv_serial_fixup(); +} + +#ifdef CONFIG_X86_IO_APIC +static void sdv_pci_init(void) +{ +	x86_of_pci_init(); +	/* We can't set this earlier, because we need to calibrate the timer */ +	legacy_pic = &null_legacy_pic; +} +#endif + +/* + * CE4100 specific x86_init function overrides and early setup + * calls. + */ +void __init x86_ce4100_early_setup(void) +{ +	x86_init.oem.arch_setup = sdv_arch_setup; +	x86_platform.i8042_detect = ce4100_i8042_detect; +	x86_init.resources.probe_roms = x86_init_noop; +	x86_init.mpparse.get_smp_config = x86_init_uint_noop; +	x86_init.mpparse.find_smp_config = x86_init_noop; +	x86_init.pci.init = ce4100_pci_init; + +	/* +	 * By default, the reboot method is ACPI which is supported by the +	 * CE4100 bootloader CEFDK using FADT.ResetReg Address and ResetValue +	 * the bootloader will however issue a system power off instead of +	 * reboot. By using BOOT_KBD we ensure proper system reboot as +	 * expected. +	 */ +	reboot_type = BOOT_KBD; + +#ifdef CONFIG_X86_IO_APIC +	x86_init.pci.init_irq = sdv_pci_init; +	x86_init.mpparse.setup_ioapic_ids = setup_ioapic_ids_from_mpc_nocheck; +#endif + +	pm_power_off = ce4100_power_off; +} diff --git a/arch/x86/platform/ce4100/falconfalls.dts b/arch/x86/platform/ce4100/falconfalls.dts new file mode 100644 index 00000000000..ce874f872cc --- /dev/null +++ b/arch/x86/platform/ce4100/falconfalls.dts @@ -0,0 +1,433 @@ +/* + * CE4100 on Falcon Falls + * + * (c) Copyright 2010 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; version 2 of the License. + */ +/dts-v1/; +/ { +	model = "intel,falconfalls"; +	compatible = "intel,falconfalls"; +	#address-cells = <1>; +	#size-cells = <1>; + +	cpus { +		#address-cells = <1>; +		#size-cells = <0>; + +		cpu@0 { +			device_type = "cpu"; +			compatible = "intel,ce4100"; +			reg = <0>; +			lapic = <&lapic0>; +		}; +	}; + +	soc@0 { +		#address-cells = <1>; +		#size-cells = <1>; +		compatible = "intel,ce4100-cp"; +		ranges; + +		ioapic1: interrupt-controller@fec00000 { +			#interrupt-cells = <2>; +			compatible = "intel,ce4100-ioapic"; +			interrupt-controller; +			reg = <0xfec00000 0x1000>; +		}; + +		timer@fed00000 { +			compatible = "intel,ce4100-hpet"; +			reg = <0xfed00000 0x200>; +		}; + +		lapic0: interrupt-controller@fee00000 { +			compatible = "intel,ce4100-lapic"; +			reg = <0xfee00000 0x1000>; +		}; + +		pci@3fc { +			#address-cells = <3>; +			#size-cells = <2>; +			compatible = "intel,ce4100-pci", "pci"; +			device_type = "pci"; +			bus-range = <0 0>; +			ranges = <0x2000000 0 0xbffff000 0xbffff000 0 0x1000 +				  0x2000000 0 0xdffe0000 0xdffe0000 0 0x1000 +				  0x0000000 0 0x0	 0x0	    0 0x100>; + +			/* Secondary IO-APIC */ +			ioapic2: interrupt-controller@0,1 { +				#interrupt-cells = <2>; +				compatible = "intel,ce4100-ioapic"; +				interrupt-controller; +				reg = <0x100 0x0 0x0 0x0 0x0>; +				assigned-addresses = <0x02000000 0x0 0xbffff000 0x0 0x1000>; +			}; + +			pci@1,0 { +				#address-cells = <3>; +				#size-cells = <2>; +				compatible = "intel,ce4100-pci", "pci"; +				device_type = "pci"; +				bus-range = <1 1>; +				reg = <0x0800 0x0 0x0 0x0 0x0>; +				ranges = <0x2000000 0 0xdffe0000 0x2000000 0 0xdffe0000 0 0x1000>; + +				interrupt-parent = <&ioapic2>; + +				display@2,0 { +					compatible = "pci8086,2e5b.2", +						   "pci8086,2e5b", +						   "pciclass038000", +						   "pciclass0380"; + +					reg = <0x11000 0x0 0x0 0x0 0x0>; +					interrupts = <0 1>; +				}; + +				multimedia@3,0 { +					compatible = "pci8086,2e5c.2", +						   "pci8086,2e5c", +						   "pciclass048000", +						   "pciclass0480"; + +					reg = <0x11800 0x0 0x0 0x0 0x0>; +					interrupts = <2 1>; +				}; + +				multimedia@4,0 { +					compatible = "pci8086,2e5d.2", +						   "pci8086,2e5d", +						   "pciclass048000", +						   "pciclass0480"; + +					reg = <0x12000 0x0 0x0 0x0 0x0>; +					interrupts = <4 1>; +				}; + +				multimedia@4,1 { +					compatible = "pci8086,2e5e.2", +						   "pci8086,2e5e", +						   "pciclass048000", +						   "pciclass0480"; + +					reg = <0x12100 0x0 0x0 0x0 0x0>; +					interrupts = <5 1>; +				}; + +				sound@6,0 { +					compatible = "pci8086,2e5f.2", +						   "pci8086,2e5f", +						   "pciclass040100", +						   "pciclass0401"; + +					reg = <0x13000 0x0 0x0 0x0 0x0>; +					interrupts = <6 1>; +				}; + +				sound@6,1 { +					compatible = "pci8086,2e5f.2", +						   "pci8086,2e5f", +						   "pciclass040100", +						   "pciclass0401"; + +					reg = <0x13100 0x0 0x0 0x0 0x0>; +					interrupts = <7 1>; +				}; + +				sound@6,2 { +					compatible = "pci8086,2e60.2", +						   "pci8086,2e60", +						   "pciclass040100", +						   "pciclass0401"; + +					reg = <0x13200 0x0 0x0 0x0 0x0>; +					interrupts = <8 1>; +				}; + +				display@8,0 { +					compatible = "pci8086,2e61.2", +						   "pci8086,2e61", +						   "pciclass038000", +						   "pciclass0380"; + +					reg = <0x14000 0x0 0x0 0x0 0x0>; +					interrupts = <9 1>; +				}; + +				display@8,1 { +					compatible = "pci8086,2e62.2", +						   "pci8086,2e62", +						   "pciclass038000", +						   "pciclass0380"; + +					reg = <0x14100 0x0 0x0 0x0 0x0>; +					interrupts = <10 1>; +				}; + +				multimedia@8,2 { +					compatible = "pci8086,2e63.2", +						   "pci8086,2e63", +						   "pciclass048000", +						   "pciclass0480"; + +					reg = <0x14200 0x0 0x0 0x0 0x0>; +					interrupts = <11 1>; +				}; + +				entertainment-encryption@9,0 { +					compatible = "pci8086,2e64.2", +						   "pci8086,2e64", +						   "pciclass101000", +						   "pciclass1010"; + +					reg = <0x14800 0x0 0x0 0x0 0x0>; +					interrupts = <12 1>; +				}; + +				localbus@a,0 { +					compatible = "pci8086,2e65.2", +						   "pci8086,2e65", +						   "pciclassff0000", +						   "pciclassff00"; + +					reg = <0x15000 0x0 0x0 0x0 0x0>; +				}; + +				serial@b,0 { +					compatible = "pci8086,2e66.2", +						   "pci8086,2e66", +						   "pciclass070003", +						   "pciclass0700"; + +					reg = <0x15800 0x0 0x0 0x0 0x0>; +					interrupts = <14 1>; +				}; + +				pcigpio: gpio@b,1 { +					#gpio-cells = <2>; +					#interrupt-cells = <2>; +					compatible = "pci8086,2e67.2", +						   "pci8086,2e67", +						   "pciclassff0000", +						   "pciclassff00"; + +					reg = <0x15900 0x0 0x0 0x0 0x0>; +					interrupts = <15 1>; +					interrupt-controller; +					gpio-controller; +					intel,muxctl = <0>; +				}; + +				i2c-controller@b,2 { +					#address-cells = <2>; +					#size-cells = <1>; +					compatible = "pci8086,2e68.2", +						   "pci8086,2e68", +						   "pciclass,ff0000", +						   "pciclass,ff00"; + +					reg = <0x15a00 0x0 0x0 0x0 0x0>; +					interrupts = <16 1>; +					ranges = <0 0	0x02000000 0 0xdffe0500	0x100 +						  1 0	0x02000000 0 0xdffe0600	0x100 +						  2 0	0x02000000 0 0xdffe0700	0x100>; + +					i2c@0 { +						#address-cells = <1>; +						#size-cells = <0>; +						compatible = "intel,ce4100-i2c-controller"; +						reg = <0 0 0x100>; +					}; + +					i2c@1 { +						#address-cells = <1>; +						#size-cells = <0>; +						compatible = "intel,ce4100-i2c-controller"; +						reg = <1 0 0x100>; + +						gpio@26 { +							#gpio-cells = <2>; +							compatible = "ti,pcf8575"; +							reg = <0x26>; +							gpio-controller; +						}; +					}; + +					i2c@2 { +						#address-cells = <1>; +						#size-cells = <0>; +						compatible = "intel,ce4100-i2c-controller"; +						reg = <2 0 0x100>; + +						gpio@26 { +							#gpio-cells = <2>; +							compatible = "ti,pcf8575"; +							reg = <0x26>; +							gpio-controller; +						}; +					}; +				}; + +				smard-card@b,3 { +					compatible = "pci8086,2e69.2", +						   "pci8086,2e69", +						   "pciclass070500", +						   "pciclass0705"; + +					reg = <0x15b00 0x0 0x0 0x0 0x0>; +					interrupts = <15 1>; +				}; + +				spi-controller@b,4 { +					#address-cells = <1>; +					#size-cells = <0>; +					compatible = +						"pci8086,2e6a.2", +						"pci8086,2e6a", +						"pciclass,ff0000", +						"pciclass,ff00"; + +					reg = <0x15c00 0x0 0x0 0x0 0x0>; +					interrupts = <15 1>; + +					dac@0 { +						compatible = "ti,pcm1755"; +						reg = <0>; +						spi-max-frequency = <115200>; +					}; + +					dac@1 { +						compatible = "ti,pcm1609a"; +						reg = <1>; +						spi-max-frequency = <115200>; +					}; + +					eeprom@2 { +						compatible = "atmel,at93c46"; +						reg = <2>; +						spi-max-frequency = <115200>; +					}; +				}; + +				multimedia@b,7 { +					compatible = "pci8086,2e6d.2", +						   "pci8086,2e6d", +						   "pciclassff0000", +						   "pciclassff00"; + +					reg = <0x15f00 0x0 0x0 0x0 0x0>; +				}; + +				ethernet@c,0 { +					compatible = "pci8086,2e6e.2", +						   "pci8086,2e6e", +						   "pciclass020000", +						   "pciclass0200"; + +					reg = <0x16000 0x0 0x0 0x0 0x0>; +					interrupts = <21 1>; +				}; + +				clock@c,1 { +					compatible = "pci8086,2e6f.2", +						   "pci8086,2e6f", +						   "pciclassff0000", +						   "pciclassff00"; + +					reg = <0x16100 0x0 0x0 0x0 0x0>; +					interrupts = <3 1>; +				}; + +				usb@d,0 { +					compatible = "pci8086,2e70.2", +						   "pci8086,2e70", +						   "pciclass0c0320", +						   "pciclass0c03"; + +					reg = <0x16800 0x0 0x0 0x0 0x0>; +					interrupts = <22 1>; +				}; + +				usb@d,1 { +					compatible = "pci8086,2e70.2", +						   "pci8086,2e70", +						   "pciclass0c0320", +						   "pciclass0c03"; + +					reg = <0x16900 0x0 0x0 0x0 0x0>; +					interrupts = <22 1>; +				}; + +				sata@e,0 { +					compatible = "pci8086,2e71.0", +						   "pci8086,2e71", +						   "pciclass010601", +						   "pciclass0106"; + +					reg = <0x17000 0x0 0x0 0x0 0x0>; +					interrupts = <23 1>; +				}; + +				flash@f,0 { +					compatible = "pci8086,701.1", +						   "pci8086,701", +						   "pciclass050100", +						   "pciclass0501"; + +					reg = <0x17800 0x0 0x0 0x0 0x0>; +					interrupts = <13 1>; +				}; + +				entertainment-encryption@10,0 { +					compatible = "pci8086,702.1", +						   "pci8086,702", +						   "pciclass101000", +						   "pciclass1010"; + +					reg = <0x18000 0x0 0x0 0x0 0x0>; +				}; + +				co-processor@11,0 { +					compatible = "pci8086,703.1", +						   "pci8086,703", +						   "pciclass0b4000", +						   "pciclass0b40"; + +					reg = <0x18800 0x0 0x0 0x0 0x0>; +					interrupts = <1 1>; +				}; + +				multimedia@12,0 { +					compatible = "pci8086,704.0", +						   "pci8086,704", +						   "pciclass048000", +						   "pciclass0480"; + +					reg = <0x19000 0x0 0x0 0x0 0x0>; +				}; +			}; + +			isa@1f,0 { +				#address-cells = <2>; +				#size-cells = <1>; +				compatible = "isa"; +				reg = <0xf800 0x0 0x0 0x0 0x0>; +				ranges = <1 0 0 0 0 0x100>; + +				rtc@70 { +					compatible = "intel,ce4100-rtc", "motorola,mc146818"; +					interrupts = <8 3>; +					interrupt-parent = <&ioapic1>; +					ctrl-reg = <2>; +					freq-reg = <0x26>; +					reg = <1 0x70 2>; +				}; +			}; +		}; +	}; +}; diff --git a/arch/x86/platform/efi/Makefile b/arch/x86/platform/efi/Makefile index 73b8be0f367..d51045afcaa 100644 --- a/arch/x86/platform/efi/Makefile +++ b/arch/x86/platform/efi/Makefile @@ -1 +1,4 @@  obj-$(CONFIG_EFI) 		+= efi.o efi_$(BITS).o efi_stub_$(BITS).o +obj-$(CONFIG_ACPI_BGRT) += efi-bgrt.o +obj-$(CONFIG_EARLY_PRINTK_EFI)	+= early_printk.o +obj-$(CONFIG_EFI_MIXED)		+= efi_thunk_$(BITS).o diff --git a/arch/x86/platform/efi/early_printk.c b/arch/x86/platform/efi/early_printk.c new file mode 100644 index 00000000000..52414211729 --- /dev/null +++ b/arch/x86/platform/efi/early_printk.c @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2013 Intel Corporation; author Matt Fleming + * + *  This file is part of the Linux kernel, and is made available under + *  the terms of the GNU General Public License version 2. + */ + +#include <linux/console.h> +#include <linux/efi.h> +#include <linux/font.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <asm/setup.h> + +static const struct font_desc *font; +static u32 efi_x, efi_y; +static void *efi_fb; +static bool early_efi_keep; + +/* + * efi earlyprintk need use early_ioremap to map the framebuffer. + * But early_ioremap is not usable for earlyprintk=efi,keep, ioremap should + * be used instead. ioremap will be available after paging_init() which is + * earlier than initcall callbacks. Thus adding this early initcall function + * early_efi_map_fb to map the whole efi framebuffer. + */ +static __init int early_efi_map_fb(void) +{ +	unsigned long base, size; + +	if (!early_efi_keep) +		return 0; + +	base = boot_params.screen_info.lfb_base; +	size = boot_params.screen_info.lfb_size; +	efi_fb = ioremap(base, size); + +	return efi_fb ? 0 : -ENOMEM; +} +early_initcall(early_efi_map_fb); + +/* + * early_efi_map maps efi framebuffer region [start, start + len -1] + * In case earlyprintk=efi,keep we have the whole framebuffer mapped already + * so just return the offset efi_fb + start. + */ +static __init_refok void *early_efi_map(unsigned long start, unsigned long len) +{ +	unsigned long base; + +	base = boot_params.screen_info.lfb_base; + +	if (efi_fb) +		return (efi_fb + start); +	else +		return early_ioremap(base + start, len); +} + +static __init_refok void early_efi_unmap(void *addr, unsigned long len) +{ +	if (!efi_fb) +		early_iounmap(addr, len); +} + +static void early_efi_clear_scanline(unsigned int y) +{ +	unsigned long *dst; +	u16 len; + +	len = boot_params.screen_info.lfb_linelength; +	dst = early_efi_map(y*len, len); +	if (!dst) +		return; + +	memset(dst, 0, len); +	early_efi_unmap(dst, len); +} + +static void early_efi_scroll_up(void) +{ +	unsigned long *dst, *src; +	u16 len; +	u32 i, height; + +	len = boot_params.screen_info.lfb_linelength; +	height = boot_params.screen_info.lfb_height; + +	for (i = 0; i < height - font->height; i++) { +		dst = early_efi_map(i*len, len); +		if (!dst) +			return; + +		src = early_efi_map((i + font->height) * len, len); +		if (!src) { +			early_efi_unmap(dst, len); +			return; +		} + +		memmove(dst, src, len); + +		early_efi_unmap(src, len); +		early_efi_unmap(dst, len); +	} +} + +static void early_efi_write_char(u32 *dst, unsigned char c, unsigned int h) +{ +	const u32 color_black = 0x00000000; +	const u32 color_white = 0x00ffffff; +	const u8 *src; +	u8 s8; +	int m; + +	src = font->data + c * font->height; +	s8 = *(src + h); + +	for (m = 0; m < 8; m++) { +		if ((s8 >> (7 - m)) & 1) +			*dst = color_white; +		else +			*dst = color_black; +		dst++; +	} +} + +static void +early_efi_write(struct console *con, const char *str, unsigned int num) +{ +	struct screen_info *si; +	unsigned int len; +	const char *s; +	void *dst; + +	si = &boot_params.screen_info; +	len = si->lfb_linelength; + +	while (num) { +		unsigned int linemax; +		unsigned int h, count = 0; + +		for (s = str; *s && *s != '\n'; s++) { +			if (count == num) +				break; +			count++; +		} + +		linemax = (si->lfb_width - efi_x) / font->width; +		if (count > linemax) +			count = linemax; + +		for (h = 0; h < font->height; h++) { +			unsigned int n, x; + +			dst = early_efi_map((efi_y + h) * len, len); +			if (!dst) +				return; + +			s = str; +			n = count; +			x = efi_x; + +			while (n-- > 0) { +				early_efi_write_char(dst + x*4, *s, h); +				x += font->width; +				s++; +			} + +			early_efi_unmap(dst, len); +		} + +		num -= count; +		efi_x += count * font->width; +		str += count; + +		if (num > 0 && *s == '\n') { +			efi_x = 0; +			efi_y += font->height; +			str++; +			num--; +		} + +		if (efi_x >= si->lfb_width) { +			efi_x = 0; +			efi_y += font->height; +		} + +		if (efi_y + font->height > si->lfb_height) { +			u32 i; + +			efi_y -= font->height; +			early_efi_scroll_up(); + +			for (i = 0; i < font->height; i++) +				early_efi_clear_scanline(efi_y + i); +		} +	} +} + +static __init int early_efi_setup(struct console *con, char *options) +{ +	struct screen_info *si; +	u16 xres, yres; +	u32 i; + +	si = &boot_params.screen_info; +	xres = si->lfb_width; +	yres = si->lfb_height; + +	/* +	 * early_efi_write_char() implicitly assumes a framebuffer with +	 * 32-bits per pixel. +	 */ +	if (si->lfb_depth != 32) +		return -ENODEV; + +	font = get_default_font(xres, yres, -1, -1); +	if (!font) +		return -ENODEV; + +	efi_y = rounddown(yres, font->height) - font->height; +	for (i = 0; i < (yres - efi_y) / font->height; i++) +		early_efi_scroll_up(); + +	/* early_console_register will unset CON_BOOT in case ,keep */ +	if (!(con->flags & CON_BOOT)) +		early_efi_keep = true; +	return 0; +} + +struct console early_efi_console = { +	.name =		"earlyefi", +	.write =	early_efi_write, +	.setup =	early_efi_setup, +	.flags =	CON_PRINTBUFFER, +	.index =	-1, +}; diff --git a/arch/x86/platform/efi/efi-bgrt.c b/arch/x86/platform/efi/efi-bgrt.c new file mode 100644 index 00000000000..f15103dff4b --- /dev/null +++ b/arch/x86/platform/efi/efi-bgrt.c @@ -0,0 +1,81 @@ +/* + * Copyright 2012 Intel Corporation + * Author: Josh Triplett <josh@joshtriplett.org> + * + * Based on the bgrt driver: + * Copyright 2012 Red Hat, Inc <mjg@redhat.com> + * Author: Matthew Garrett + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/acpi.h> +#include <linux/efi.h> +#include <linux/efi-bgrt.h> + +struct acpi_table_bgrt *bgrt_tab; +void *__initdata bgrt_image; +size_t __initdata bgrt_image_size; + +struct bmp_header { +	u16 id; +	u32 size; +} __packed; + +void __init efi_bgrt_init(void) +{ +	acpi_status status; +	void __iomem *image; +	bool ioremapped = false; +	struct bmp_header bmp_header; + +	if (acpi_disabled) +		return; + +	status = acpi_get_table("BGRT", 0, +	                        (struct acpi_table_header **)&bgrt_tab); +	if (ACPI_FAILURE(status)) +		return; + +	if (bgrt_tab->header.length < sizeof(*bgrt_tab)) +		return; +	if (bgrt_tab->version != 1 || bgrt_tab->status != 1) +		return; +	if (bgrt_tab->image_type != 0 || !bgrt_tab->image_address) +		return; + +	image = efi_lookup_mapped_addr(bgrt_tab->image_address); +	if (!image) { +		image = early_memremap(bgrt_tab->image_address, +				       sizeof(bmp_header)); +		ioremapped = true; +		if (!image) +			return; +	} + +	memcpy_fromio(&bmp_header, image, sizeof(bmp_header)); +	if (ioremapped) +		early_iounmap(image, sizeof(bmp_header)); +	bgrt_image_size = bmp_header.size; + +	bgrt_image = kmalloc(bgrt_image_size, GFP_KERNEL); +	if (!bgrt_image) +		return; + +	if (ioremapped) { +		image = early_memremap(bgrt_tab->image_address, +				       bmp_header.size); +		if (!image) { +			kfree(bgrt_image); +			bgrt_image = NULL; +			return; +		} +	} + +	memcpy_fromio(bgrt_image, image, bgrt_image_size); +	if (ioremapped) +		early_iounmap(image, bmp_header.size); +} diff --git a/arch/x86/platform/efi/efi.c b/arch/x86/platform/efi/efi.c index 0fe27d7c625..87fc96bcc13 100644 --- a/arch/x86/platform/efi/efi.c +++ b/arch/x86/platform/efi/efi.c @@ -12,6 +12,8 @@   *	Bibo Mao <bibo.mao@intel.com>   *	Chandramouli Narayanan <mouli@linux.intel.com>   *	Huang Ying <ying.huang@intel.com> + * Copyright (C) 2013 SuSE Labs + *	Borislav Petkov <bp@suse.de> - runtime services VA mapping   *   * Copied from efi_32.c to eliminate the duplicated code between EFI   * 32/64 support code. --ying 2007-10-26 @@ -26,10 +28,15 @@   *	Skip non-WB memory and ignore empty memory ranges.   */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +  #include <linux/kernel.h>  #include <linux/init.h>  #include <linux/efi.h> +#include <linux/efi-bgrt.h> +#include <linux/export.h>  #include <linux/bootmem.h> +#include <linux/slab.h>  #include <linux/memblock.h>  #include <linux/spinlock.h>  #include <linux/uaccess.h> @@ -44,24 +51,36 @@  #include <asm/cacheflush.h>  #include <asm/tlbflush.h>  #include <asm/x86_init.h> +#include <asm/rtc.h> +#include <asm/uv/uv.h> + +#define EFI_DEBUG -#define EFI_DEBUG	1 -#define PFX 		"EFI: " +#define EFI_MIN_RESERVE 5120 -int efi_enabled; -EXPORT_SYMBOL(efi_enabled); +#define EFI_DUMMY_GUID \ +	EFI_GUID(0x4424ac57, 0xbe4b, 0x47dd, 0x9e, 0x97, 0xed, 0x50, 0xf0, 0x9f, 0x92, 0xa9) -struct efi efi; -EXPORT_SYMBOL(efi); +static efi_char16_t efi_dummy_name[6] = { 'D', 'U', 'M', 'M', 'Y', 0 };  struct efi_memory_map memmap;  static struct efi efi_phys __initdata;  static efi_system_table_t efi_systab __initdata; +static efi_config_table_type_t arch_tables[] __initdata = { +#ifdef CONFIG_X86_UV +	{UV_SYSTEM_TABLE_GUID, "UVsystab", &efi.uv_systab}, +#endif +	{NULL_GUID, NULL, NULL}, +}; + +u64 efi_setup;		/* efi setup_data physical address */ + +static bool disable_runtime __initdata = false;  static int __init setup_noefi(char *arg)  { -	efi_enabled = 0; +	disable_runtime = true;  	return 0;  }  early_param("noefi", setup_noefi); @@ -76,29 +95,59 @@ static int __init setup_add_efi_memmap(char *arg)  }  early_param("add_efi_memmap", setup_add_efi_memmap); +static bool efi_no_storage_paranoia; + +static int __init setup_storage_paranoia(char *arg) +{ +	efi_no_storage_paranoia = true; +	return 0; +} +early_param("efi_no_storage_paranoia", setup_storage_paranoia);  static efi_status_t virt_efi_get_time(efi_time_t *tm, efi_time_cap_t *tc)  { -	return efi_call_virt2(get_time, tm, tc); +	unsigned long flags; +	efi_status_t status; + +	spin_lock_irqsave(&rtc_lock, flags); +	status = efi_call_virt(get_time, tm, tc); +	spin_unlock_irqrestore(&rtc_lock, flags); +	return status;  }  static efi_status_t virt_efi_set_time(efi_time_t *tm)  { -	return efi_call_virt1(set_time, tm); +	unsigned long flags; +	efi_status_t status; + +	spin_lock_irqsave(&rtc_lock, flags); +	status = efi_call_virt(set_time, tm); +	spin_unlock_irqrestore(&rtc_lock, flags); +	return status;  }  static efi_status_t virt_efi_get_wakeup_time(efi_bool_t *enabled,  					     efi_bool_t *pending,  					     efi_time_t *tm)  { -	return efi_call_virt3(get_wakeup_time, -			      enabled, pending, tm); +	unsigned long flags; +	efi_status_t status; + +	spin_lock_irqsave(&rtc_lock, flags); +	status = efi_call_virt(get_wakeup_time, enabled, pending, tm); +	spin_unlock_irqrestore(&rtc_lock, flags); +	return status;  }  static efi_status_t virt_efi_set_wakeup_time(efi_bool_t enabled, efi_time_t *tm)  { -	return efi_call_virt2(set_wakeup_time, -			      enabled, tm); +	unsigned long flags; +	efi_status_t status; + +	spin_lock_irqsave(&rtc_lock, flags); +	status = efi_call_virt(set_wakeup_time, enabled, tm); +	spin_unlock_irqrestore(&rtc_lock, flags); +	return status;  }  static efi_status_t virt_efi_get_variable(efi_char16_t *name, @@ -107,33 +156,45 @@ static efi_status_t virt_efi_get_variable(efi_char16_t *name,  					  unsigned long *data_size,  					  void *data)  { -	return efi_call_virt5(get_variable, -			      name, vendor, attr, -			      data_size, data); +	return efi_call_virt(get_variable, +			     name, vendor, attr, +			     data_size, data);  }  static efi_status_t virt_efi_get_next_variable(unsigned long *name_size,  					       efi_char16_t *name,  					       efi_guid_t *vendor)  { -	return efi_call_virt3(get_next_variable, -			      name_size, name, vendor); +	return efi_call_virt(get_next_variable, +			     name_size, name, vendor);  }  static efi_status_t virt_efi_set_variable(efi_char16_t *name,  					  efi_guid_t *vendor, -					  unsigned long attr, +					  u32 attr,  					  unsigned long data_size,  					  void *data)  { -	return efi_call_virt5(set_variable, -			      name, vendor, attr, -			      data_size, data); +	return efi_call_virt(set_variable, +			     name, vendor, attr, +			     data_size, data); +} + +static efi_status_t virt_efi_query_variable_info(u32 attr, +						 u64 *storage_space, +						 u64 *remaining_space, +						 u64 *max_variable_size) +{ +	if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION) +		return EFI_UNSUPPORTED; + +	return efi_call_virt(query_variable_info, attr, storage_space, +			     remaining_space, max_variable_size);  }  static efi_status_t virt_efi_get_next_high_mono_count(u32 *count)  { -	return efi_call_virt1(get_next_high_mono_count, count); +	return efi_call_virt(get_next_high_mono_count, count);  }  static void virt_efi_reset_system(int reset_type, @@ -141,19 +202,30 @@ static void virt_efi_reset_system(int reset_type,  				  unsigned long data_size,  				  efi_char16_t *data)  { -	efi_call_virt4(reset_system, reset_type, status, -		       data_size, data); +	__efi_call_virt(reset_system, reset_type, status, +			data_size, data);  } -static efi_status_t virt_efi_set_virtual_address_map( -	unsigned long memory_map_size, -	unsigned long descriptor_size, -	u32 descriptor_version, -	efi_memory_desc_t *virtual_map) +static efi_status_t virt_efi_update_capsule(efi_capsule_header_t **capsules, +					    unsigned long count, +					    unsigned long sg_list) +{ +	if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION) +		return EFI_UNSUPPORTED; + +	return efi_call_virt(update_capsule, capsules, count, sg_list); +} + +static efi_status_t virt_efi_query_capsule_caps(efi_capsule_header_t **capsules, +						unsigned long count, +						u64 *max_size, +						int *reset_type)  { -	return efi_call_virt4(set_virtual_address_map, -			      memory_map_size, descriptor_size, -			      descriptor_version, virtual_map); +	if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION) +		return EFI_UNSUPPORTED; + +	return efi_call_virt(query_capsule_caps, capsules, count, max_size, +			     reset_type);  }  static efi_status_t __init phys_efi_set_virtual_address_map( @@ -165,54 +237,50 @@ static efi_status_t __init phys_efi_set_virtual_address_map(  	efi_status_t status;  	efi_call_phys_prelog(); -	status = efi_call_phys4(efi_phys.set_virtual_address_map, -				memory_map_size, descriptor_size, -				descriptor_version, virtual_map); +	status = efi_call_phys(efi_phys.set_virtual_address_map, +			       memory_map_size, descriptor_size, +			       descriptor_version, virtual_map);  	efi_call_phys_epilog();  	return status;  } -static efi_status_t __init phys_efi_get_time(efi_time_t *tm, -					     efi_time_cap_t *tc) +int efi_set_rtc_mmss(const struct timespec *now)  { -	efi_status_t status; - -	efi_call_phys_prelog(); -	status = efi_call_phys2(efi_phys.get_time, tm, tc); -	efi_call_phys_epilog(); -	return status; -} - -int efi_set_rtc_mmss(unsigned long nowtime) -{ -	int real_seconds, real_minutes; -	efi_status_t 	status; -	efi_time_t 	eft; -	efi_time_cap_t 	cap; +	unsigned long nowtime = now->tv_sec; +	efi_status_t	status; +	efi_time_t	eft; +	efi_time_cap_t	cap; +	struct rtc_time	tm;  	status = efi.get_time(&eft, &cap);  	if (status != EFI_SUCCESS) { -		printk(KERN_ERR "Oops: efitime: can't read time!\n"); +		pr_err("Oops: efitime: can't read time!\n");  		return -1;  	} -	real_seconds = nowtime % 60; -	real_minutes = nowtime / 60; -	if (((abs(real_minutes - eft.minute) + 15)/30) & 1) -		real_minutes += 30; -	real_minutes %= 60; -	eft.minute = real_minutes; -	eft.second = real_seconds; +	rtc_time_to_tm(nowtime, &tm); +	if (!rtc_valid_tm(&tm)) { +		eft.year = tm.tm_year + 1900; +		eft.month = tm.tm_mon + 1; +		eft.day = tm.tm_mday; +		eft.minute = tm.tm_min; +		eft.second = tm.tm_sec; +		eft.nanosecond = 0; +	} else { +		pr_err("%s: Invalid EFI RTC value: write of %lx to EFI RTC failed\n", +		       __func__, nowtime); +		return -1; +	}  	status = efi.set_time(&eft);  	if (status != EFI_SUCCESS) { -		printk(KERN_ERR "Oops: efitime: can't write time!\n"); +		pr_err("Oops: efitime: can't write time!\n");  		return -1;  	}  	return 0;  } -unsigned long efi_get_time(void) +void efi_get_time(struct timespec *now)  {  	efi_status_t status;  	efi_time_t eft; @@ -220,10 +288,11 @@ unsigned long efi_get_time(void)  	status = efi.get_time(&eft, &cap);  	if (status != EFI_SUCCESS) -		printk(KERN_ERR "Oops: efitime: can't read time!\n"); +		pr_err("Oops: efitime: can't read time!\n"); -	return mktime(eft.year, eft.month, eft.day, eft.hour, -		      eft.minute, eft.second); +	now->tv_sec = mktime(eft.year, eft.month, eft.day, eft.hour, +			     eft.minute, eft.second); +	now->tv_nsec = 0;  }  /* @@ -276,28 +345,37 @@ static void __init do_add_efi_memmap(void)  	sanitize_e820_map(e820.map, ARRAY_SIZE(e820.map), &e820.nr_map);  } -void __init efi_memblock_x86_reserve_range(void) +int __init efi_memblock_x86_reserve_range(void)  { +	struct efi_info *e = &boot_params.efi_info;  	unsigned long pmap;  #ifdef CONFIG_X86_32 -	pmap = boot_params.efi_info.efi_memmap; +	/* Can't handle data above 4GB at this time */ +	if (e->efi_memmap_hi) { +		pr_err("Memory map is above 4GB, disabling EFI.\n"); +		return -EINVAL; +	} +	pmap =  e->efi_memmap;  #else -	pmap = (boot_params.efi_info.efi_memmap | -		((__u64)boot_params.efi_info.efi_memmap_hi<<32)); +	pmap = (e->efi_memmap |	((__u64)e->efi_memmap_hi << 32));  #endif -	memmap.phys_map = (void *)pmap; -	memmap.nr_map = boot_params.efi_info.efi_memmap_size / -		boot_params.efi_info.efi_memdesc_size; -	memmap.desc_version = boot_params.efi_info.efi_memdesc_version; -	memmap.desc_size = boot_params.efi_info.efi_memdesc_size; -	memblock_x86_reserve_range(pmap, pmap + memmap.nr_map * memmap.desc_size, -		      "EFI memmap"); +	memmap.phys_map		= (void *)pmap; +	memmap.nr_map		= e->efi_memmap_size / +				  e->efi_memdesc_size; +	memmap.desc_size	= e->efi_memdesc_size; +	memmap.desc_version	= e->efi_memdesc_version; + +	memblock_reserve(pmap, memmap.nr_map * memmap.desc_size); + +	efi.memmap = &memmap; + +	return 0;  } -#if EFI_DEBUG  static void __init print_efi_memmap(void)  { +#ifdef EFI_DEBUG  	efi_memory_desc_t *md;  	void *p;  	int i; @@ -306,112 +384,232 @@ static void __init print_efi_memmap(void)  	     p < memmap.map_end;  	     p += memmap.desc_size, i++) {  		md = p; -		printk(KERN_INFO PFX "mem%02u: type=%u, attr=0x%llx, " -			"range=[0x%016llx-0x%016llx) (%lluMB)\n", +		pr_info("mem%02u: type=%u, attr=0x%llx, range=[0x%016llx-0x%016llx) (%lluMB)\n",  			i, md->type, md->attribute, md->phys_addr,  			md->phys_addr + (md->num_pages << EFI_PAGE_SHIFT),  			(md->num_pages >> (20 - EFI_PAGE_SHIFT)));  	} -}  #endif  /*  EFI_DEBUG  */ +} -void __init efi_init(void) +void __init efi_reserve_boot_services(void)  { -	efi_config_table_t *config_tables; -	efi_runtime_services_t *runtime; -	efi_char16_t *c16; -	char vendor[100] = "unknown"; -	int i = 0; -	void *tmp; +	void *p; + +	for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) { +		efi_memory_desc_t *md = p; +		u64 start = md->phys_addr; +		u64 size = md->num_pages << EFI_PAGE_SHIFT; + +		if (md->type != EFI_BOOT_SERVICES_CODE && +		    md->type != EFI_BOOT_SERVICES_DATA) +			continue; +		/* Only reserve where possible: +		 * - Not within any already allocated areas +		 * - Not over any memory area (really needed, if above?) +		 * - Not within any part of the kernel +		 * - Not the bios reserved area +		*/ +		if ((start + size > __pa_symbol(_text) +				&& start <= __pa_symbol(_end)) || +			!e820_all_mapped(start, start+size, E820_RAM) || +			memblock_is_region_reserved(start, size)) { +			/* Could not reserve, skip it */ +			md->num_pages = 0; +			memblock_dbg("Could not reserve boot range [0x%010llx-0x%010llx]\n", +				     start, start+size-1); +		} else +			memblock_reserve(start, size); +	} +} + +void __init efi_unmap_memmap(void) +{ +	clear_bit(EFI_MEMMAP, &efi.flags); +	if (memmap.map) { +		early_iounmap(memmap.map, memmap.nr_map * memmap.desc_size); +		memmap.map = NULL; +	} +} + +void __init efi_free_boot_services(void) +{ +	void *p; + +	for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) { +		efi_memory_desc_t *md = p; +		unsigned long long start = md->phys_addr; +		unsigned long long size = md->num_pages << EFI_PAGE_SHIFT; + +		if (md->type != EFI_BOOT_SERVICES_CODE && +		    md->type != EFI_BOOT_SERVICES_DATA) +			continue; + +		/* Could not reserve boot area */ +		if (!size) +			continue; +		free_bootmem_late(start, size); +	} + +	efi_unmap_memmap(); +} + +static int __init efi_systab_init(void *phys) +{ +	if (efi_enabled(EFI_64BIT)) { +		efi_system_table_64_t *systab64; +		struct efi_setup_data *data = NULL; +		u64 tmp = 0; + +		if (efi_setup) { +			data = early_memremap(efi_setup, sizeof(*data)); +			if (!data) +				return -ENOMEM; +		} +		systab64 = early_ioremap((unsigned long)phys, +					 sizeof(*systab64)); +		if (systab64 == NULL) { +			pr_err("Couldn't map the system table!\n"); +			if (data) +				early_iounmap(data, sizeof(*data)); +			return -ENOMEM; +		} + +		efi_systab.hdr = systab64->hdr; +		efi_systab.fw_vendor = data ? (unsigned long)data->fw_vendor : +					      systab64->fw_vendor; +		tmp |= data ? data->fw_vendor : systab64->fw_vendor; +		efi_systab.fw_revision = systab64->fw_revision; +		efi_systab.con_in_handle = systab64->con_in_handle; +		tmp |= systab64->con_in_handle; +		efi_systab.con_in = systab64->con_in; +		tmp |= systab64->con_in; +		efi_systab.con_out_handle = systab64->con_out_handle; +		tmp |= systab64->con_out_handle; +		efi_systab.con_out = systab64->con_out; +		tmp |= systab64->con_out; +		efi_systab.stderr_handle = systab64->stderr_handle; +		tmp |= systab64->stderr_handle; +		efi_systab.stderr = systab64->stderr; +		tmp |= systab64->stderr; +		efi_systab.runtime = data ? +				     (void *)(unsigned long)data->runtime : +				     (void *)(unsigned long)systab64->runtime; +		tmp |= data ? data->runtime : systab64->runtime; +		efi_systab.boottime = (void *)(unsigned long)systab64->boottime; +		tmp |= systab64->boottime; +		efi_systab.nr_tables = systab64->nr_tables; +		efi_systab.tables = data ? (unsigned long)data->tables : +					   systab64->tables; +		tmp |= data ? data->tables : systab64->tables; + +		early_iounmap(systab64, sizeof(*systab64)); +		if (data) +			early_iounmap(data, sizeof(*data));  #ifdef CONFIG_X86_32 -	efi_phys.systab = (efi_system_table_t *)boot_params.efi_info.efi_systab; -#else -	efi_phys.systab = (efi_system_table_t *) -		(boot_params.efi_info.efi_systab | -		 ((__u64)boot_params.efi_info.efi_systab_hi<<32)); +		if (tmp >> 32) { +			pr_err("EFI data located above 4GB, disabling EFI.\n"); +			return -EINVAL; +		}  #endif +	} else { +		efi_system_table_32_t *systab32; + +		systab32 = early_ioremap((unsigned long)phys, +					 sizeof(*systab32)); +		if (systab32 == NULL) { +			pr_err("Couldn't map the system table!\n"); +			return -ENOMEM; +		} + +		efi_systab.hdr = systab32->hdr; +		efi_systab.fw_vendor = systab32->fw_vendor; +		efi_systab.fw_revision = systab32->fw_revision; +		efi_systab.con_in_handle = systab32->con_in_handle; +		efi_systab.con_in = systab32->con_in; +		efi_systab.con_out_handle = systab32->con_out_handle; +		efi_systab.con_out = systab32->con_out; +		efi_systab.stderr_handle = systab32->stderr_handle; +		efi_systab.stderr = systab32->stderr; +		efi_systab.runtime = (void *)(unsigned long)systab32->runtime; +		efi_systab.boottime = (void *)(unsigned long)systab32->boottime; +		efi_systab.nr_tables = systab32->nr_tables; +		efi_systab.tables = systab32->tables; + +		early_iounmap(systab32, sizeof(*systab32)); +	} -	efi.systab = early_ioremap((unsigned long)efi_phys.systab, -				   sizeof(efi_system_table_t)); -	if (efi.systab == NULL) -		printk(KERN_ERR "Couldn't map the EFI system table!\n"); -	memcpy(&efi_systab, efi.systab, sizeof(efi_system_table_t)); -	early_iounmap(efi.systab, sizeof(efi_system_table_t));  	efi.systab = &efi_systab;  	/*  	 * Verify the EFI Table  	 */ -	if (efi.systab->hdr.signature != EFI_SYSTEM_TABLE_SIGNATURE) -		printk(KERN_ERR "EFI system table signature incorrect!\n"); +	if (efi.systab->hdr.signature != EFI_SYSTEM_TABLE_SIGNATURE) { +		pr_err("System table signature incorrect!\n"); +		return -EINVAL; +	}  	if ((efi.systab->hdr.revision >> 16) == 0) -		printk(KERN_ERR "Warning: EFI system table version " -		       "%d.%02d, expected 1.00 or greater!\n", +		pr_err("Warning: System table version %d.%02d, expected 1.00 or greater!\n",  		       efi.systab->hdr.revision >> 16,  		       efi.systab->hdr.revision & 0xffff); +	set_bit(EFI_SYSTEM_TABLES, &efi.flags); + +	return 0; +} + +static int __init efi_runtime_init32(void) +{ +	efi_runtime_services_32_t *runtime; + +	runtime = early_ioremap((unsigned long)efi.systab->runtime, +			sizeof(efi_runtime_services_32_t)); +	if (!runtime) { +		pr_err("Could not map the runtime service table!\n"); +		return -ENOMEM; +	} +  	/* -	 * Show what we know for posterity +	 * We will only need *early* access to the following two +	 * EFI runtime services before set_virtual_address_map +	 * is invoked.  	 */ -	c16 = tmp = early_ioremap(efi.systab->fw_vendor, 2); -	if (c16) { -		for (i = 0; i < sizeof(vendor) - 1 && *c16; ++i) -			vendor[i] = *c16++; -		vendor[i] = '\0'; -	} else -		printk(KERN_ERR PFX "Could not map the firmware vendor!\n"); -	early_iounmap(tmp, 2); +	efi_phys.set_virtual_address_map = +			(efi_set_virtual_address_map_t *) +			(unsigned long)runtime->set_virtual_address_map; +	early_iounmap(runtime, sizeof(efi_runtime_services_32_t)); -	printk(KERN_INFO "EFI v%u.%.02u by %s\n", -	       efi.systab->hdr.revision >> 16, -	       efi.systab->hdr.revision & 0xffff, vendor); +	return 0; +} + +static int __init efi_runtime_init64(void) +{ +	efi_runtime_services_64_t *runtime; + +	runtime = early_ioremap((unsigned long)efi.systab->runtime, +			sizeof(efi_runtime_services_64_t)); +	if (!runtime) { +		pr_err("Could not map the runtime service table!\n"); +		return -ENOMEM; +	}  	/* -	 * Let's see what config tables the firmware passed to us. +	 * We will only need *early* access to the following two +	 * EFI runtime services before set_virtual_address_map +	 * is invoked.  	 */ -	config_tables = early_ioremap( -		efi.systab->tables, -		efi.systab->nr_tables * sizeof(efi_config_table_t)); -	if (config_tables == NULL) -		printk(KERN_ERR "Could not map EFI Configuration Table!\n"); +	efi_phys.set_virtual_address_map = +			(efi_set_virtual_address_map_t *) +			(unsigned long)runtime->set_virtual_address_map; +	early_iounmap(runtime, sizeof(efi_runtime_services_64_t)); -	printk(KERN_INFO); -	for (i = 0; i < efi.systab->nr_tables; i++) { -		if (!efi_guidcmp(config_tables[i].guid, MPS_TABLE_GUID)) { -			efi.mps = config_tables[i].table; -			printk(" MPS=0x%lx ", config_tables[i].table); -		} else if (!efi_guidcmp(config_tables[i].guid, -					ACPI_20_TABLE_GUID)) { -			efi.acpi20 = config_tables[i].table; -			printk(" ACPI 2.0=0x%lx ", config_tables[i].table); -		} else if (!efi_guidcmp(config_tables[i].guid, -					ACPI_TABLE_GUID)) { -			efi.acpi = config_tables[i].table; -			printk(" ACPI=0x%lx ", config_tables[i].table); -		} else if (!efi_guidcmp(config_tables[i].guid, -					SMBIOS_TABLE_GUID)) { -			efi.smbios = config_tables[i].table; -			printk(" SMBIOS=0x%lx ", config_tables[i].table); -#ifdef CONFIG_X86_UV -		} else if (!efi_guidcmp(config_tables[i].guid, -					UV_SYSTEM_TABLE_GUID)) { -			efi.uv_systab = config_tables[i].table; -			printk(" UVsystab=0x%lx ", config_tables[i].table); -#endif -		} else if (!efi_guidcmp(config_tables[i].guid, -					HCDP_TABLE_GUID)) { -			efi.hcdp = config_tables[i].table; -			printk(" HCDP=0x%lx ", config_tables[i].table); -		} else if (!efi_guidcmp(config_tables[i].guid, -					UGA_IO_PROTOCOL_GUID)) { -			efi.uga = config_tables[i].table; -			printk(" UGA=0x%lx ", config_tables[i].table); -		} -	} -	printk("\n"); -	early_iounmap(config_tables, -			  efi.systab->nr_tables * sizeof(efi_config_table_t)); +	return 0; +} + +static int __init efi_runtime_init(void) +{ +	int rv;  	/*  	 * Check out the runtime services table. We need to map @@ -419,60 +617,188 @@ void __init efi_init(void)  	 * address of several of the EFI runtime functions, needed to  	 * set the firmware into virtual mode.  	 */ -	runtime = early_ioremap((unsigned long)efi.systab->runtime, -				sizeof(efi_runtime_services_t)); -	if (runtime != NULL) { -		/* -		 * We will only need *early* access to the following -		 * two EFI runtime services before set_virtual_address_map -		 * is invoked. -		 */ -		efi_phys.get_time = (efi_get_time_t *)runtime->get_time; -		efi_phys.set_virtual_address_map = -			(efi_set_virtual_address_map_t *) -			runtime->set_virtual_address_map; -		/* -		 * Make efi_get_time can be called before entering -		 * virtual mode. -		 */ -		efi.get_time = phys_efi_get_time; -	} else -		printk(KERN_ERR "Could not map the EFI runtime service " -		       "table!\n"); -	early_iounmap(runtime, sizeof(efi_runtime_services_t)); +	if (efi_enabled(EFI_64BIT)) +		rv = efi_runtime_init64(); +	else +		rv = efi_runtime_init32(); + +	if (rv) +		return rv; + +	set_bit(EFI_RUNTIME_SERVICES, &efi.flags); +	return 0; +} + +static int __init efi_memmap_init(void) +{  	/* Map the EFI memory map */  	memmap.map = early_ioremap((unsigned long)memmap.phys_map,  				   memmap.nr_map * memmap.desc_size); -	if (memmap.map == NULL) -		printk(KERN_ERR "Could not map the EFI memory map!\n"); +	if (memmap.map == NULL) { +		pr_err("Could not map the memory map!\n"); +		return -ENOMEM; +	}  	memmap.map_end = memmap.map + (memmap.nr_map * memmap.desc_size); -	if (memmap.desc_size != sizeof(efi_memory_desc_t)) -		printk(KERN_WARNING -		  "Kernel-defined memdesc doesn't match the one from EFI!\n"); -  	if (add_efi_memmap)  		do_add_efi_memmap(); +	set_bit(EFI_MEMMAP, &efi.flags); + +	return 0; +} + +/* + * A number of config table entries get remapped to virtual addresses + * after entering EFI virtual mode. However, the kexec kernel requires + * their physical addresses therefore we pass them via setup_data and + * correct those entries to their respective physical addresses here. + * + * Currently only handles smbios which is necessary for some firmware + * implementation. + */ +static int __init efi_reuse_config(u64 tables, int nr_tables) +{ +	int i, sz, ret = 0; +	void *p, *tablep; +	struct efi_setup_data *data; + +	if (!efi_setup) +		return 0; + +	if (!efi_enabled(EFI_64BIT)) +		return 0; + +	data = early_memremap(efi_setup, sizeof(*data)); +	if (!data) { +		ret = -ENOMEM; +		goto out; +	} + +	if (!data->smbios) +		goto out_memremap; + +	sz = sizeof(efi_config_table_64_t); + +	p = tablep = early_memremap(tables, nr_tables * sz); +	if (!p) { +		pr_err("Could not map Configuration table!\n"); +		ret = -ENOMEM; +		goto out_memremap; +	} + +	for (i = 0; i < efi.systab->nr_tables; i++) { +		efi_guid_t guid; + +		guid = ((efi_config_table_64_t *)p)->guid; + +		if (!efi_guidcmp(guid, SMBIOS_TABLE_GUID)) +			((efi_config_table_64_t *)p)->table = data->smbios; +		p += sz; +	} +	early_iounmap(tablep, nr_tables * sz); + +out_memremap: +	early_iounmap(data, sizeof(*data)); +out: +	return ret; +} + +void __init efi_init(void) +{ +	efi_char16_t *c16; +	char vendor[100] = "unknown"; +	int i = 0; +	void *tmp; +  #ifdef CONFIG_X86_32 -	x86_platform.get_wallclock = efi_get_time; -	x86_platform.set_wallclock = efi_set_rtc_mmss; +	if (boot_params.efi_info.efi_systab_hi || +	    boot_params.efi_info.efi_memmap_hi) { +		pr_info("Table located above 4GB, disabling EFI.\n"); +		return; +	} +	efi_phys.systab = (efi_system_table_t *)boot_params.efi_info.efi_systab; +#else +	efi_phys.systab = (efi_system_table_t *) +			  (boot_params.efi_info.efi_systab | +			  ((__u64)boot_params.efi_info.efi_systab_hi<<32));  #endif -	/* Setup for EFI runtime service */ -	reboot_type = BOOT_EFI; +	if (efi_systab_init(efi_phys.systab)) +		return; + +	set_bit(EFI_SYSTEM_TABLES, &efi.flags); + +	efi.config_table = (unsigned long)efi.systab->tables; +	efi.fw_vendor	 = (unsigned long)efi.systab->fw_vendor; +	efi.runtime	 = (unsigned long)efi.systab->runtime; + +	/* +	 * Show what we know for posterity +	 */ +	c16 = tmp = early_ioremap(efi.systab->fw_vendor, 2); +	if (c16) { +		for (i = 0; i < sizeof(vendor) - 1 && *c16; ++i) +			vendor[i] = *c16++; +		vendor[i] = '\0'; +	} else +		pr_err("Could not map the firmware vendor!\n"); +	early_iounmap(tmp, 2); + +	pr_info("EFI v%u.%.02u by %s\n", +		efi.systab->hdr.revision >> 16, +		efi.systab->hdr.revision & 0xffff, vendor); + +	if (efi_reuse_config(efi.systab->tables, efi.systab->nr_tables)) +		return; + +	if (efi_config_init(arch_tables)) +		return; + +	/* +	 * Note: We currently don't support runtime services on an EFI +	 * that doesn't match the kernel 32/64-bit mode. +	 */ + +	if (!efi_runtime_supported()) +		pr_info("No EFI runtime due to 32/64-bit mismatch with kernel\n"); +	else { +		if (disable_runtime || efi_runtime_init()) +			return; +	} +	if (efi_memmap_init()) +		return; + +	set_bit(EFI_MEMMAP, &efi.flags); -#if EFI_DEBUG  	print_efi_memmap(); -#endif  } -static void __init runtime_code_page_mkexec(void) +void __init efi_late_init(void) +{ +	efi_bgrt_init(); +} + +void __init efi_set_executable(efi_memory_desc_t *md, bool executable) +{ +	u64 addr, npages; + +	addr = md->virt_addr; +	npages = md->num_pages; + +	memrange_efi_to_native(&addr, &npages); + +	if (executable) +		set_memory_x(addr, npages); +	else +		set_memory_nx(addr, npages); +} + +void __init runtime_code_page_mkexec(void)  {  	efi_memory_desc_t *md;  	void *p; -	u64 addr, npages;  	/* Make EFI runtime service code area executable */  	for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) { @@ -481,79 +807,329 @@ static void __init runtime_code_page_mkexec(void)  		if (md->type != EFI_RUNTIME_SERVICES_CODE)  			continue; -		addr = md->virt_addr; -		npages = md->num_pages; -		memrange_efi_to_native(&addr, &npages); -		set_memory_x(addr, npages); +		efi_set_executable(md, true);  	}  } -/* - * This function will switch the EFI runtime services to virtual mode. - * Essentially, look through the EFI memmap and map every region that - * has the runtime attribute bit set in its memory descriptor and update - * that memory descriptor with the virtual address obtained from ioremap(). - * This enables the runtime services to be called without having to - * thunk back into physical mode for every invocation. - */ -void __init efi_enter_virtual_mode(void) +void efi_memory_uc(u64 addr, unsigned long size)  { -	efi_memory_desc_t *md; -	efi_status_t status; +	unsigned long page_shift = 1UL << EFI_PAGE_SHIFT; +	u64 npages; + +	npages = round_up(size, page_shift) / page_shift; +	memrange_efi_to_native(&addr, &npages); +	set_memory_uc(addr, npages); +} + +void __init old_map_region(efi_memory_desc_t *md) +{ +	u64 start_pfn, end_pfn, end;  	unsigned long size; -	u64 end, systab, addr, npages, end_pfn; -	void *p, *va; +	void *va; + +	start_pfn = PFN_DOWN(md->phys_addr); +	size	  = md->num_pages << PAGE_SHIFT; +	end	  = md->phys_addr + size; +	end_pfn   = PFN_UP(end); + +	if (pfn_range_is_mapped(start_pfn, end_pfn)) { +		va = __va(md->phys_addr); + +		if (!(md->attribute & EFI_MEMORY_WB)) +			efi_memory_uc((u64)(unsigned long)va, size); +	} else +		va = efi_ioremap(md->phys_addr, size, +				 md->type, md->attribute); + +	md->virt_addr = (u64) (unsigned long) va; +	if (!va) +		pr_err("ioremap of 0x%llX failed!\n", +		       (unsigned long long)md->phys_addr); +} + +static void native_runtime_setup(void) +{ +	efi.get_time = virt_efi_get_time; +	efi.set_time = virt_efi_set_time; +	efi.get_wakeup_time = virt_efi_get_wakeup_time; +	efi.set_wakeup_time = virt_efi_set_wakeup_time; +	efi.get_variable = virt_efi_get_variable; +	efi.get_next_variable = virt_efi_get_next_variable; +	efi.set_variable = virt_efi_set_variable; +	efi.get_next_high_mono_count = virt_efi_get_next_high_mono_count; +	efi.reset_system = virt_efi_reset_system; +	efi.query_variable_info = virt_efi_query_variable_info; +	efi.update_capsule = virt_efi_update_capsule; +	efi.query_capsule_caps = virt_efi_query_capsule_caps; +} + +/* Merge contiguous regions of the same type and attribute */ +static void __init efi_merge_regions(void) +{ +	void *p; +	efi_memory_desc_t *md, *prev_md = NULL; -	efi.systab = NULL;  	for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) { +		u64 prev_size;  		md = p; -		if (!(md->attribute & EFI_MEMORY_RUNTIME)) -			continue; -		size = md->num_pages << EFI_PAGE_SHIFT; -		end = md->phys_addr + size; +		if (!prev_md) { +			prev_md = md; +			continue; +		} -		end_pfn = PFN_UP(end); -		if (end_pfn <= max_low_pfn_mapped -		    || (end_pfn > (1UL << (32 - PAGE_SHIFT)) -			&& end_pfn <= max_pfn_mapped)) -			va = __va(md->phys_addr); -		else -			va = efi_ioremap(md->phys_addr, size, md->type); +		if (prev_md->type != md->type || +		    prev_md->attribute != md->attribute) { +			prev_md = md; +			continue; +		} -		md->virt_addr = (u64) (unsigned long) va; +		prev_size = prev_md->num_pages << EFI_PAGE_SHIFT; -		if (!va) { -			printk(KERN_ERR PFX "ioremap of 0x%llX failed!\n", -			       (unsigned long long)md->phys_addr); +		if (md->phys_addr == (prev_md->phys_addr + prev_size)) { +			prev_md->num_pages += md->num_pages; +			md->type = EFI_RESERVED_TYPE; +			md->attribute = 0;  			continue;  		} +		prev_md = md; +	} +} + +static void __init get_systab_virt_addr(efi_memory_desc_t *md) +{ +	unsigned long size; +	u64 end, systab; + +	size = md->num_pages << EFI_PAGE_SHIFT; +	end = md->phys_addr + size; +	systab = (u64)(unsigned long)efi_phys.systab; +	if (md->phys_addr <= systab && systab < end) { +		systab += md->virt_addr - md->phys_addr; +		efi.systab = (efi_system_table_t *)(unsigned long)systab; +	} +} + +static void __init save_runtime_map(void) +{ +#ifdef CONFIG_KEXEC +	efi_memory_desc_t *md; +	void *tmp, *p, *q = NULL; +	int count = 0; + +	if (efi_enabled(EFI_OLD_MEMMAP)) +		return; -		if (!(md->attribute & EFI_MEMORY_WB)) { -			addr = md->virt_addr; -			npages = md->num_pages; -			memrange_efi_to_native(&addr, &npages); -			set_memory_uc(addr, npages); +	for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) { +		md = p; + +		if (!(md->attribute & EFI_MEMORY_RUNTIME) || +		    (md->type == EFI_BOOT_SERVICES_CODE) || +		    (md->type == EFI_BOOT_SERVICES_DATA)) +			continue; +		tmp = krealloc(q, (count + 1) * memmap.desc_size, GFP_KERNEL); +		if (!tmp) +			goto out; +		q = tmp; + +		memcpy(q + count * memmap.desc_size, md, memmap.desc_size); +		count++; +	} + +	efi_runtime_map_setup(q, count, memmap.desc_size); +	return; + +out: +	kfree(q); +	pr_err("Error saving runtime map, efi runtime on kexec non-functional!!\n"); +#endif +} + +static void *realloc_pages(void *old_memmap, int old_shift) +{ +	void *ret; + +	ret = (void *)__get_free_pages(GFP_KERNEL, old_shift + 1); +	if (!ret) +		goto out; + +	/* +	 * A first-time allocation doesn't have anything to copy. +	 */ +	if (!old_memmap) +		return ret; + +	memcpy(ret, old_memmap, PAGE_SIZE << old_shift); + +out: +	free_pages((unsigned long)old_memmap, old_shift); +	return ret; +} + +/* + * Map the efi memory ranges of the runtime services and update new_mmap with + * virtual addresses. + */ +static void * __init efi_map_regions(int *count, int *pg_shift) +{ +	void *p, *new_memmap = NULL; +	unsigned long left = 0; +	efi_memory_desc_t *md; + +	for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) { +		md = p; +		if (!(md->attribute & EFI_MEMORY_RUNTIME)) { +#ifdef CONFIG_X86_64 +			if (md->type != EFI_BOOT_SERVICES_CODE && +			    md->type != EFI_BOOT_SERVICES_DATA) +#endif +				continue;  		} -		systab = (u64) (unsigned long) efi_phys.systab; -		if (md->phys_addr <= systab && systab < end) { -			systab += md->virt_addr - md->phys_addr; -			efi.systab = (efi_system_table_t *) (unsigned long) systab; +		efi_map_region(md); +		get_systab_virt_addr(md); + +		if (left < memmap.desc_size) { +			new_memmap = realloc_pages(new_memmap, *pg_shift); +			if (!new_memmap) +				return NULL; + +			left += PAGE_SIZE << *pg_shift; +			(*pg_shift)++;  		} + +		memcpy(new_memmap + (*count * memmap.desc_size), md, +		       memmap.desc_size); + +		left -= memmap.desc_size; +		(*count)++; +	} + +	return new_memmap; +} + +static void __init kexec_enter_virtual_mode(void) +{ +#ifdef CONFIG_KEXEC +	efi_memory_desc_t *md; +	void *p; + +	efi.systab = NULL; + +	/* +	 * We don't do virtual mode, since we don't do runtime services, on +	 * non-native EFI +	 */ +	if (!efi_is_native()) { +		efi_unmap_memmap(); +		return;  	} +	/* +	* Map efi regions which were passed via setup_data. The virt_addr is a +	* fixed addr which was used in first kernel of a kexec boot. +	*/ +	for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) { +		md = p; +		efi_map_region_fixed(md); /* FIXME: add error handling */ +		get_systab_virt_addr(md); +	} + +	save_runtime_map(); +  	BUG_ON(!efi.systab); -	status = phys_efi_set_virtual_address_map( -		memmap.desc_size * memmap.nr_map, -		memmap.desc_size, -		memmap.desc_version, -		memmap.phys_map); +	efi_sync_low_kernel_mappings(); + +	/* +	 * Now that EFI is in virtual mode, update the function +	 * pointers in the runtime service table to the new virtual addresses. +	 * +	 * Call EFI services through wrapper functions. +	 */ +	efi.runtime_version = efi_systab.hdr.revision; + +	native_runtime_setup(); + +	efi.set_virtual_address_map = NULL; + +	if (efi_enabled(EFI_OLD_MEMMAP) && (__supported_pte_mask & _PAGE_NX)) +		runtime_code_page_mkexec(); + +	/* clean DUMMY object */ +	efi.set_variable(efi_dummy_name, &EFI_DUMMY_GUID, +			 EFI_VARIABLE_NON_VOLATILE | +			 EFI_VARIABLE_BOOTSERVICE_ACCESS | +			 EFI_VARIABLE_RUNTIME_ACCESS, +			 0, NULL); +#endif +} + +/* + * This function will switch the EFI runtime services to virtual mode. + * Essentially, we look through the EFI memmap and map every region that + * has the runtime attribute bit set in its memory descriptor into the + * ->trampoline_pgd page table using a top-down VA allocation scheme. + * + * The old method which used to update that memory descriptor with the + * virtual address obtained from ioremap() is still supported when the + * kernel is booted with efi=old_map on its command line. Same old + * method enabled the runtime services to be called without having to + * thunk back into physical mode for every invocation. + * + * The new method does a pagetable switch in a preemption-safe manner + * so that we're in a different address space when calling a runtime + * function. For function arguments passing we do copy the PGDs of the + * kernel page table into ->trampoline_pgd prior to each call. + * + * Specially for kexec boot, efi runtime maps in previous kernel should + * be passed in via setup_data. In that case runtime ranges will be mapped + * to the same virtual addresses as the first kernel, see + * kexec_enter_virtual_mode(). + */ +static void __init __efi_enter_virtual_mode(void) +{ +	int count = 0, pg_shift = 0; +	void *new_memmap = NULL; +	efi_status_t status; + +	efi.systab = NULL; + +	efi_merge_regions(); +	new_memmap = efi_map_regions(&count, &pg_shift); +	if (!new_memmap) { +		pr_err("Error reallocating memory, EFI runtime non-functional!\n"); +		return; +	} + +	save_runtime_map(); + +	BUG_ON(!efi.systab); + +	if (efi_setup_page_tables(__pa(new_memmap), 1 << pg_shift)) +		return; + +	efi_sync_low_kernel_mappings(); +	efi_dump_pagetable(); + +	if (efi_is_native()) { +		status = phys_efi_set_virtual_address_map( +				memmap.desc_size * count, +				memmap.desc_size, +				memmap.desc_version, +				(efi_memory_desc_t *)__pa(new_memmap)); +	} else { +		status = efi_thunk_set_virtual_address_map( +				efi_phys.set_virtual_address_map, +				memmap.desc_size * count, +				memmap.desc_size, +				memmap.desc_version, +				(efi_memory_desc_t *)__pa(new_memmap)); +	}  	if (status != EFI_SUCCESS) { -		printk(KERN_ALERT "Unable to switch EFI into virtual mode " -		       "(status=%lx)!\n", status); +		pr_alert("Unable to switch EFI into virtual mode (status=%lx)!\n", +			 status);  		panic("EFI call to SetVirtualAddressMap() failed!");  	} @@ -563,20 +1139,59 @@ void __init efi_enter_virtual_mode(void)  	 *  	 * Call EFI services through wrapper functions.  	 */ -	efi.get_time = virt_efi_get_time; -	efi.set_time = virt_efi_set_time; -	efi.get_wakeup_time = virt_efi_get_wakeup_time; -	efi.set_wakeup_time = virt_efi_set_wakeup_time; -	efi.get_variable = virt_efi_get_variable; -	efi.get_next_variable = virt_efi_get_next_variable; -	efi.set_variable = virt_efi_set_variable; -	efi.get_next_high_mono_count = virt_efi_get_next_high_mono_count; -	efi.reset_system = virt_efi_reset_system; -	efi.set_virtual_address_map = virt_efi_set_virtual_address_map; -	if (__supported_pte_mask & _PAGE_NX) -		runtime_code_page_mkexec(); -	early_iounmap(memmap.map, memmap.nr_map * memmap.desc_size); -	memmap.map = NULL; +	efi.runtime_version = efi_systab.hdr.revision; + +	if (efi_is_native()) +		native_runtime_setup(); +	else +		efi_thunk_runtime_setup(); + +	efi.set_virtual_address_map = NULL; + +	efi_runtime_mkexec(); + +	/* +	 * We mapped the descriptor array into the EFI pagetable above but we're +	 * not unmapping it here. Here's why: +	 * +	 * We're copying select PGDs from the kernel page table to the EFI page +	 * table and when we do so and make changes to those PGDs like unmapping +	 * stuff from them, those changes appear in the kernel page table and we +	 * go boom. +	 * +	 * From setup_real_mode(): +	 * +	 * ... +	 * trampoline_pgd[0] = init_level4_pgt[pgd_index(__PAGE_OFFSET)].pgd; +	 * +	 * In this particular case, our allocation is in PGD 0 of the EFI page +	 * table but we've copied that PGD from PGD[272] of the EFI page table: +	 * +	 *	pgd_index(__PAGE_OFFSET = 0xffff880000000000) = 272 +	 * +	 * where the direct memory mapping in kernel space is. +	 * +	 * new_memmap's VA comes from that direct mapping and thus clearing it, +	 * it would get cleared in the kernel page table too. +	 * +	 * efi_cleanup_page_tables(__pa(new_memmap), 1 << pg_shift); +	 */ +	free_pages((unsigned long)new_memmap, pg_shift); + +	/* clean DUMMY object */ +	efi.set_variable(efi_dummy_name, &EFI_DUMMY_GUID, +			 EFI_VARIABLE_NON_VOLATILE | +			 EFI_VARIABLE_BOOTSERVICE_ACCESS | +			 EFI_VARIABLE_RUNTIME_ACCESS, +			 0, NULL); +} + +void __init efi_enter_virtual_mode(void) +{ +	if (efi_setup) +		kexec_enter_virtual_mode(); +	else +		__efi_enter_virtual_mode();  }  /* @@ -587,6 +1202,9 @@ u32 efi_mem_type(unsigned long phys_addr)  	efi_memory_desc_t *md;  	void *p; +	if (!efi_enabled(EFI_MEMMAP)) +		return 0; +  	for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) {  		md = p;  		if ((md->phys_addr <= phys_addr) && @@ -611,3 +1229,114 @@ u64 efi_mem_attributes(unsigned long phys_addr)  	}  	return 0;  } + +/* + * Some firmware implementations refuse to boot if there's insufficient space + * in the variable store. Ensure that we never use more than a safe limit. + * + * Return EFI_SUCCESS if it is safe to write 'size' bytes to the variable + * store. + */ +efi_status_t efi_query_variable_store(u32 attributes, unsigned long size) +{ +	efi_status_t status; +	u64 storage_size, remaining_size, max_size; + +	if (!(attributes & EFI_VARIABLE_NON_VOLATILE)) +		return 0; + +	status = efi.query_variable_info(attributes, &storage_size, +					 &remaining_size, &max_size); +	if (status != EFI_SUCCESS) +		return status; + +	/* +	 * We account for that by refusing the write if permitting it would +	 * reduce the available space to under 5KB. This figure was provided by +	 * Samsung, so should be safe. +	 */ +	if ((remaining_size - size < EFI_MIN_RESERVE) && +		!efi_no_storage_paranoia) { + +		/* +		 * Triggering garbage collection may require that the firmware +		 * generate a real EFI_OUT_OF_RESOURCES error. We can force +		 * that by attempting to use more space than is available. +		 */ +		unsigned long dummy_size = remaining_size + 1024; +		void *dummy = kzalloc(dummy_size, GFP_ATOMIC); + +		if (!dummy) +			return EFI_OUT_OF_RESOURCES; + +		status = efi.set_variable(efi_dummy_name, &EFI_DUMMY_GUID, +					  EFI_VARIABLE_NON_VOLATILE | +					  EFI_VARIABLE_BOOTSERVICE_ACCESS | +					  EFI_VARIABLE_RUNTIME_ACCESS, +					  dummy_size, dummy); + +		if (status == EFI_SUCCESS) { +			/* +			 * This should have failed, so if it didn't make sure +			 * that we delete it... +			 */ +			efi.set_variable(efi_dummy_name, &EFI_DUMMY_GUID, +					 EFI_VARIABLE_NON_VOLATILE | +					 EFI_VARIABLE_BOOTSERVICE_ACCESS | +					 EFI_VARIABLE_RUNTIME_ACCESS, +					 0, dummy); +		} + +		kfree(dummy); + +		/* +		 * The runtime code may now have triggered a garbage collection +		 * run, so check the variable info again +		 */ +		status = efi.query_variable_info(attributes, &storage_size, +						 &remaining_size, &max_size); + +		if (status != EFI_SUCCESS) +			return status; + +		/* +		 * There still isn't enough room, so return an error +		 */ +		if (remaining_size - size < EFI_MIN_RESERVE) +			return EFI_OUT_OF_RESOURCES; +	} + +	return EFI_SUCCESS; +} +EXPORT_SYMBOL_GPL(efi_query_variable_store); + +static int __init parse_efi_cmdline(char *str) +{ +	if (*str == '=') +		str++; + +	if (!strncmp(str, "old_map", 7)) +		set_bit(EFI_OLD_MEMMAP, &efi.flags); + +	return 0; +} +early_param("efi", parse_efi_cmdline); + +void __init efi_apply_memmap_quirks(void) +{ +	/* +	 * Once setup is done earlier, unmap the EFI memory map on mismatched +	 * firmware/kernel architectures since there is no support for runtime +	 * services. +	 */ +	if (!efi_runtime_supported()) { +		pr_info("efi: Setup done, disabling due to 32/64-bit mismatch\n"); +		efi_unmap_memmap(); +	} + +	/* +	 * UV doesn't support the new EFI pagetable mapping yet. +	 */ +	if (is_uv_system()) +		set_bit(EFI_OLD_MEMMAP, &efi.flags); +} diff --git a/arch/x86/platform/efi/efi_32.c b/arch/x86/platform/efi/efi_32.c index 5cab48ee61a..9ee3491e31f 100644 --- a/arch/x86/platform/efi/efi_32.c +++ b/arch/x86/platform/efi/efi_32.c @@ -25,6 +25,7 @@  #include <linux/efi.h>  #include <asm/io.h> +#include <asm/desc.h>  #include <asm/page.h>  #include <asm/pgtable.h>  #include <asm/tlbflush.h> @@ -36,45 +37,31 @@   * claim EFI runtime service handler exclusively and to duplicate a memory in   * low memory space say 0 - 3G.   */ -  static unsigned long efi_rt_eflags; -static pgd_t efi_bak_pg_dir_pointer[2]; + +void efi_sync_low_kernel_mappings(void) {} +void __init efi_dump_pagetable(void) {} +int efi_setup_page_tables(unsigned long pa_memmap, unsigned num_pages) +{ +	return 0; +} +void efi_cleanup_page_tables(unsigned long pa_memmap, unsigned num_pages) {} + +void __init efi_map_region(efi_memory_desc_t *md) +{ +	old_map_region(md); +} + +void __init efi_map_region_fixed(efi_memory_desc_t *md) {} +void __init parse_efi_setup(u64 phys_addr, u32 data_len) {}  void efi_call_phys_prelog(void)  { -	unsigned long cr4; -	unsigned long temp;  	struct desc_ptr gdt_descr;  	local_irq_save(efi_rt_eflags); -	/* -	 * If I don't have PAE, I should just duplicate two entries in page -	 * directory. If I have PAE, I just need to duplicate one entry in -	 * page directory. -	 */ -	cr4 = read_cr4_safe(); - -	if (cr4 & X86_CR4_PAE) { -		efi_bak_pg_dir_pointer[0].pgd = -		    swapper_pg_dir[pgd_index(0)].pgd; -		swapper_pg_dir[0].pgd = -		    swapper_pg_dir[pgd_index(PAGE_OFFSET)].pgd; -	} else { -		efi_bak_pg_dir_pointer[0].pgd = -		    swapper_pg_dir[pgd_index(0)].pgd; -		efi_bak_pg_dir_pointer[1].pgd = -		    swapper_pg_dir[pgd_index(0x400000)].pgd; -		swapper_pg_dir[pgd_index(0)].pgd = -		    swapper_pg_dir[pgd_index(PAGE_OFFSET)].pgd; -		temp = PAGE_OFFSET + 0x400000; -		swapper_pg_dir[pgd_index(0x400000)].pgd = -		    swapper_pg_dir[pgd_index(temp)].pgd; -	} - -	/* -	 * After the lock is released, the original page table is restored. -	 */ +	load_cr3(initial_page_table);  	__flush_tlb_all();  	gdt_descr.address = __pa(get_cpu_gdt_table(0)); @@ -84,29 +71,20 @@ void efi_call_phys_prelog(void)  void efi_call_phys_epilog(void)  { -	unsigned long cr4;  	struct desc_ptr gdt_descr;  	gdt_descr.address = (unsigned long)get_cpu_gdt_table(0);  	gdt_descr.size = GDT_SIZE - 1;  	load_gdt(&gdt_descr); -	cr4 = read_cr4_safe(); - -	if (cr4 & X86_CR4_PAE) { -		swapper_pg_dir[pgd_index(0)].pgd = -		    efi_bak_pg_dir_pointer[0].pgd; -	} else { -		swapper_pg_dir[pgd_index(0)].pgd = -		    efi_bak_pg_dir_pointer[0].pgd; -		swapper_pg_dir[pgd_index(0x400000)].pgd = -		    efi_bak_pg_dir_pointer[1].pgd; -	} - -	/* -	 * After the lock is released, the original page table is restored. -	 */ +	load_cr3(swapper_pg_dir);  	__flush_tlb_all();  	local_irq_restore(efi_rt_eflags);  } + +void __init efi_runtime_mkexec(void) +{ +	if (__supported_pte_mask & _PAGE_NX) +		runtime_code_page_mkexec(); +} diff --git a/arch/x86/platform/efi/efi_64.c b/arch/x86/platform/efi/efi_64.c index ac0621a7ac3..290d397e1dd 100644 --- a/arch/x86/platform/efi/efi_64.c +++ b/arch/x86/platform/efi/efi_64.c @@ -27,6 +27,7 @@  #include <linux/uaccess.h>  #include <linux/io.h>  #include <linux/reboot.h> +#include <linux/slab.h>  #include <asm/setup.h>  #include <asm/page.h> @@ -37,26 +38,31 @@  #include <asm/efi.h>  #include <asm/cacheflush.h>  #include <asm/fixmap.h> +#include <asm/realmode.h> +#include <asm/time.h> -static pgd_t save_pgd __initdata; +static pgd_t *save_pgd __initdata;  static unsigned long efi_flags __initdata; -static void __init early_mapping_set_exec(unsigned long start, -					  unsigned long end, -					  int executable) -{ -	unsigned long num_pages; +/* + * We allocate runtime services regions bottom-up, starting from -4G, i.e. + * 0xffff_ffff_0000_0000 and limit EFI VA mapping space to 64G. + */ +static u64 efi_va	= -4 * (1UL << 30); +#define EFI_VA_END	(-68 * (1UL << 30)) -	start &= PMD_MASK; -	end = (end + PMD_SIZE - 1) & PMD_MASK; -	num_pages = (end - start) >> PAGE_SHIFT; -	if (executable) -		set_memory_x((unsigned long)__va(start), num_pages); -	else -		set_memory_nx((unsigned long)__va(start), num_pages); -} +/* + * Scratch space used for switching the pagetable in the EFI stub + */ +struct efi_scratch { +	u64 r15; +	u64 prev_cr3; +	pgd_t *efi_pgt; +	bool use_pgd; +	u64 phys_stack; +} __packed; -static void __init early_runtime_code_mapping_set_exec(int executable) +static void __init early_code_mapping_set_exec(int executable)  {  	efi_memory_desc_t *md;  	void *p; @@ -64,26 +70,35 @@ static void __init early_runtime_code_mapping_set_exec(int executable)  	if (!(__supported_pte_mask & _PAGE_NX))  		return; -	/* Make EFI runtime service code area executable */ +	/* Make EFI service code area executable */  	for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) {  		md = p; -		if (md->type == EFI_RUNTIME_SERVICES_CODE) { -			unsigned long end; -			end = md->phys_addr + (md->num_pages << EFI_PAGE_SHIFT); -			early_mapping_set_exec(md->phys_addr, end, executable); -		} +		if (md->type == EFI_RUNTIME_SERVICES_CODE || +		    md->type == EFI_BOOT_SERVICES_CODE) +			efi_set_executable(md, executable);  	}  }  void __init efi_call_phys_prelog(void)  {  	unsigned long vaddress; +	int pgd; +	int n_pgds; -	early_runtime_code_mapping_set_exec(1); +	if (!efi_enabled(EFI_OLD_MEMMAP)) +		return; + +	early_code_mapping_set_exec(1);  	local_irq_save(efi_flags); -	vaddress = (unsigned long)__va(0x0UL); -	save_pgd = *pgd_offset_k(0x0UL); -	set_pgd(pgd_offset_k(0x0UL), *pgd_offset_k(vaddress)); + +	n_pgds = DIV_ROUND_UP((max_pfn << PAGE_SHIFT), PGDIR_SIZE); +	save_pgd = kmalloc(n_pgds * sizeof(pgd_t), GFP_KERNEL); + +	for (pgd = 0; pgd < n_pgds; pgd++) { +		save_pgd[pgd] = *pgd_offset_k(pgd * PGDIR_SIZE); +		vaddress = (unsigned long)__va(pgd * PGDIR_SIZE); +		set_pgd(pgd_offset_k(pgd * PGDIR_SIZE), *pgd_offset_k(vaddress)); +	}  	__flush_tlb_all();  } @@ -92,14 +107,174 @@ void __init efi_call_phys_epilog(void)  	/*  	 * After the lock is released, the original page table is restored.  	 */ -	set_pgd(pgd_offset_k(0x0UL), save_pgd); +	int pgd; +	int n_pgds = DIV_ROUND_UP((max_pfn << PAGE_SHIFT) , PGDIR_SIZE); + +	if (!efi_enabled(EFI_OLD_MEMMAP)) +		return; + +	for (pgd = 0; pgd < n_pgds; pgd++) +		set_pgd(pgd_offset_k(pgd * PGDIR_SIZE), save_pgd[pgd]); +	kfree(save_pgd);  	__flush_tlb_all();  	local_irq_restore(efi_flags); -	early_runtime_code_mapping_set_exec(0); +	early_code_mapping_set_exec(0); +} + +/* + * Add low kernel mappings for passing arguments to EFI functions. + */ +void efi_sync_low_kernel_mappings(void) +{ +	unsigned num_pgds; +	pgd_t *pgd = (pgd_t *)__va(real_mode_header->trampoline_pgd); + +	if (efi_enabled(EFI_OLD_MEMMAP)) +		return; + +	num_pgds = pgd_index(MODULES_END - 1) - pgd_index(PAGE_OFFSET); + +	memcpy(pgd + pgd_index(PAGE_OFFSET), +		init_mm.pgd + pgd_index(PAGE_OFFSET), +		sizeof(pgd_t) * num_pgds); +} + +int efi_setup_page_tables(unsigned long pa_memmap, unsigned num_pages) +{ +	unsigned long text; +	struct page *page; +	unsigned npages; +	pgd_t *pgd; + +	if (efi_enabled(EFI_OLD_MEMMAP)) +		return 0; + +	efi_scratch.efi_pgt = (pgd_t *)(unsigned long)real_mode_header->trampoline_pgd; +	pgd = __va(efi_scratch.efi_pgt); + +	/* +	 * It can happen that the physical address of new_memmap lands in memory +	 * which is not mapped in the EFI page table. Therefore we need to go +	 * and ident-map those pages containing the map before calling +	 * phys_efi_set_virtual_address_map(). +	 */ +	if (kernel_map_pages_in_pgd(pgd, pa_memmap, pa_memmap, num_pages, _PAGE_NX)) { +		pr_err("Error ident-mapping new memmap (0x%lx)!\n", pa_memmap); +		return 1; +	} + +	efi_scratch.use_pgd = true; + +	/* +	 * When making calls to the firmware everything needs to be 1:1 +	 * mapped and addressable with 32-bit pointers. Map the kernel +	 * text and allocate a new stack because we can't rely on the +	 * stack pointer being < 4GB. +	 */ +	if (!IS_ENABLED(CONFIG_EFI_MIXED)) +		return 0; + +	page = alloc_page(GFP_KERNEL|__GFP_DMA32); +	if (!page) +		panic("Unable to allocate EFI runtime stack < 4GB\n"); + +	efi_scratch.phys_stack = virt_to_phys(page_address(page)); +	efi_scratch.phys_stack += PAGE_SIZE; /* stack grows down */ + +	npages = (_end - _text) >> PAGE_SHIFT; +	text = __pa(_text); + +	if (kernel_map_pages_in_pgd(pgd, text >> PAGE_SHIFT, text, npages, 0)) { +		pr_err("Failed to map kernel text 1:1\n"); +		return 1; +	} + +	return 0; +} + +void efi_cleanup_page_tables(unsigned long pa_memmap, unsigned num_pages) +{ +	pgd_t *pgd = (pgd_t *)__va(real_mode_header->trampoline_pgd); + +	kernel_unmap_pages_in_pgd(pgd, pa_memmap, num_pages); +} + +static void __init __map_region(efi_memory_desc_t *md, u64 va) +{ +	pgd_t *pgd = (pgd_t *)__va(real_mode_header->trampoline_pgd); +	unsigned long pf = 0; + +	if (!(md->attribute & EFI_MEMORY_WB)) +		pf |= _PAGE_PCD; + +	if (kernel_map_pages_in_pgd(pgd, md->phys_addr, va, md->num_pages, pf)) +		pr_warn("Error mapping PA 0x%llx -> VA 0x%llx!\n", +			   md->phys_addr, va); +} + +void __init efi_map_region(efi_memory_desc_t *md) +{ +	unsigned long size = md->num_pages << PAGE_SHIFT; +	u64 pa = md->phys_addr; + +	if (efi_enabled(EFI_OLD_MEMMAP)) +		return old_map_region(md); + +	/* +	 * Make sure the 1:1 mappings are present as a catch-all for b0rked +	 * firmware which doesn't update all internal pointers after switching +	 * to virtual mode and would otherwise crap on us. +	 */ +	__map_region(md, md->phys_addr); + +	/* +	 * Enforce the 1:1 mapping as the default virtual address when +	 * booting in EFI mixed mode, because even though we may be +	 * running a 64-bit kernel, the firmware may only be 32-bit. +	 */ +	if (!efi_is_native () && IS_ENABLED(CONFIG_EFI_MIXED)) { +		md->virt_addr = md->phys_addr; +		return; +	} + +	efi_va -= size; + +	/* Is PA 2M-aligned? */ +	if (!(pa & (PMD_SIZE - 1))) { +		efi_va &= PMD_MASK; +	} else { +		u64 pa_offset = pa & (PMD_SIZE - 1); +		u64 prev_va = efi_va; + +		/* get us the same offset within this 2M page */ +		efi_va = (efi_va & PMD_MASK) + pa_offset; + +		if (efi_va > prev_va) +			efi_va -= PMD_SIZE; +	} + +	if (efi_va < EFI_VA_END) { +		pr_warn(FW_WARN "VA address range overflow!\n"); +		return; +	} + +	/* Do the VA map */ +	__map_region(md, efi_va); +	md->virt_addr = efi_va; +} + +/* + * kexec kernel will use efi_map_region_fixed to map efi runtime memory ranges. + * md->virt_addr is the original virtual address which had been mapped in kexec + * 1st kernel. + */ +void __init efi_map_region_fixed(efi_memory_desc_t *md) +{ +	__map_region(md, md->virt_addr);  }  void __iomem *__init efi_ioremap(unsigned long phys_addr, unsigned long size, -				 u32 type) +				 u32 type, u64 attribute)  {  	unsigned long last_map_pfn; @@ -107,8 +282,323 @@ void __iomem *__init efi_ioremap(unsigned long phys_addr, unsigned long size,  		return ioremap(phys_addr, size);  	last_map_pfn = init_memory_mapping(phys_addr, phys_addr + size); -	if ((last_map_pfn << PAGE_SHIFT) < phys_addr + size) -		return NULL; +	if ((last_map_pfn << PAGE_SHIFT) < phys_addr + size) { +		unsigned long top = last_map_pfn << PAGE_SHIFT; +		efi_ioremap(top, size - (top - phys_addr), type, attribute); +	} + +	if (!(attribute & EFI_MEMORY_WB)) +		efi_memory_uc((u64)(unsigned long)__va(phys_addr), size);  	return (void __iomem *)__va(phys_addr);  } + +void __init parse_efi_setup(u64 phys_addr, u32 data_len) +{ +	efi_setup = phys_addr + sizeof(struct setup_data); +} + +void __init efi_runtime_mkexec(void) +{ +	if (!efi_enabled(EFI_OLD_MEMMAP)) +		return; + +	if (__supported_pte_mask & _PAGE_NX) +		runtime_code_page_mkexec(); +} + +void __init efi_dump_pagetable(void) +{ +#ifdef CONFIG_EFI_PGT_DUMP +	pgd_t *pgd = (pgd_t *)__va(real_mode_header->trampoline_pgd); + +	ptdump_walk_pgd_level(NULL, pgd); +#endif +} + +#ifdef CONFIG_EFI_MIXED +extern efi_status_t efi64_thunk(u32, ...); + +#define runtime_service32(func)						 \ +({									 \ +	u32 table = (u32)(unsigned long)efi.systab;			 \ +	u32 *rt, *___f;							 \ +									 \ +	rt = (u32 *)(table + offsetof(efi_system_table_32_t, runtime));	 \ +	___f = (u32 *)(*rt + offsetof(efi_runtime_services_32_t, func)); \ +	*___f;								 \ +}) + +/* + * Switch to the EFI page tables early so that we can access the 1:1 + * runtime services mappings which are not mapped in any other page + * tables. This function must be called before runtime_service32(). + * + * Also, disable interrupts because the IDT points to 64-bit handlers, + * which aren't going to function correctly when we switch to 32-bit. + */ +#define efi_thunk(f, ...)						\ +({									\ +	efi_status_t __s;						\ +	unsigned long flags;						\ +	u32 func;							\ +									\ +	efi_sync_low_kernel_mappings();					\ +	local_irq_save(flags);						\ +									\ +	efi_scratch.prev_cr3 = read_cr3();				\ +	write_cr3((unsigned long)efi_scratch.efi_pgt);			\ +	__flush_tlb_all();						\ +									\ +	func = runtime_service32(f);					\ +	__s = efi64_thunk(func, __VA_ARGS__);			\ +									\ +	write_cr3(efi_scratch.prev_cr3);				\ +	__flush_tlb_all();						\ +	local_irq_restore(flags);					\ +									\ +	__s;								\ +}) + +efi_status_t efi_thunk_set_virtual_address_map( +	void *phys_set_virtual_address_map, +	unsigned long memory_map_size, +	unsigned long descriptor_size, +	u32 descriptor_version, +	efi_memory_desc_t *virtual_map) +{ +	efi_status_t status; +	unsigned long flags; +	u32 func; + +	efi_sync_low_kernel_mappings(); +	local_irq_save(flags); + +	efi_scratch.prev_cr3 = read_cr3(); +	write_cr3((unsigned long)efi_scratch.efi_pgt); +	__flush_tlb_all(); + +	func = (u32)(unsigned long)phys_set_virtual_address_map; +	status = efi64_thunk(func, memory_map_size, descriptor_size, +			     descriptor_version, virtual_map); + +	write_cr3(efi_scratch.prev_cr3); +	__flush_tlb_all(); +	local_irq_restore(flags); + +	return status; +} + +static efi_status_t efi_thunk_get_time(efi_time_t *tm, efi_time_cap_t *tc) +{ +	efi_status_t status; +	u32 phys_tm, phys_tc; + +	spin_lock(&rtc_lock); + +	phys_tm = virt_to_phys(tm); +	phys_tc = virt_to_phys(tc); + +	status = efi_thunk(get_time, phys_tm, phys_tc); + +	spin_unlock(&rtc_lock); + +	return status; +} + +static efi_status_t efi_thunk_set_time(efi_time_t *tm) +{ +	efi_status_t status; +	u32 phys_tm; + +	spin_lock(&rtc_lock); + +	phys_tm = virt_to_phys(tm); + +	status = efi_thunk(set_time, phys_tm); + +	spin_unlock(&rtc_lock); + +	return status; +} + +static efi_status_t +efi_thunk_get_wakeup_time(efi_bool_t *enabled, efi_bool_t *pending, +			  efi_time_t *tm) +{ +	efi_status_t status; +	u32 phys_enabled, phys_pending, phys_tm; + +	spin_lock(&rtc_lock); + +	phys_enabled = virt_to_phys(enabled); +	phys_pending = virt_to_phys(pending); +	phys_tm = virt_to_phys(tm); + +	status = efi_thunk(get_wakeup_time, phys_enabled, +			     phys_pending, phys_tm); + +	spin_unlock(&rtc_lock); + +	return status; +} + +static efi_status_t +efi_thunk_set_wakeup_time(efi_bool_t enabled, efi_time_t *tm) +{ +	efi_status_t status; +	u32 phys_tm; + +	spin_lock(&rtc_lock); + +	phys_tm = virt_to_phys(tm); + +	status = efi_thunk(set_wakeup_time, enabled, phys_tm); + +	spin_unlock(&rtc_lock); + +	return status; +} + + +static efi_status_t +efi_thunk_get_variable(efi_char16_t *name, efi_guid_t *vendor, +		       u32 *attr, unsigned long *data_size, void *data) +{ +	efi_status_t status; +	u32 phys_name, phys_vendor, phys_attr; +	u32 phys_data_size, phys_data; + +	phys_data_size = virt_to_phys(data_size); +	phys_vendor = virt_to_phys(vendor); +	phys_name = virt_to_phys(name); +	phys_attr = virt_to_phys(attr); +	phys_data = virt_to_phys(data); + +	status = efi_thunk(get_variable, phys_name, phys_vendor, +			   phys_attr, phys_data_size, phys_data); + +	return status; +} + +static efi_status_t +efi_thunk_set_variable(efi_char16_t *name, efi_guid_t *vendor, +		       u32 attr, unsigned long data_size, void *data) +{ +	u32 phys_name, phys_vendor, phys_data; +	efi_status_t status; + +	phys_name = virt_to_phys(name); +	phys_vendor = virt_to_phys(vendor); +	phys_data = virt_to_phys(data); + +	/* If data_size is > sizeof(u32) we've got problems */ +	status = efi_thunk(set_variable, phys_name, phys_vendor, +			   attr, data_size, phys_data); + +	return status; +} + +static efi_status_t +efi_thunk_get_next_variable(unsigned long *name_size, +			    efi_char16_t *name, +			    efi_guid_t *vendor) +{ +	efi_status_t status; +	u32 phys_name_size, phys_name, phys_vendor; + +	phys_name_size = virt_to_phys(name_size); +	phys_vendor = virt_to_phys(vendor); +	phys_name = virt_to_phys(name); + +	status = efi_thunk(get_next_variable, phys_name_size, +			   phys_name, phys_vendor); + +	return status; +} + +static efi_status_t +efi_thunk_get_next_high_mono_count(u32 *count) +{ +	efi_status_t status; +	u32 phys_count; + +	phys_count = virt_to_phys(count); +	status = efi_thunk(get_next_high_mono_count, phys_count); + +	return status; +} + +static void +efi_thunk_reset_system(int reset_type, efi_status_t status, +		       unsigned long data_size, efi_char16_t *data) +{ +	u32 phys_data; + +	phys_data = virt_to_phys(data); + +	efi_thunk(reset_system, reset_type, status, data_size, phys_data); +} + +static efi_status_t +efi_thunk_update_capsule(efi_capsule_header_t **capsules, +			 unsigned long count, unsigned long sg_list) +{ +	/* +	 * To properly support this function we would need to repackage +	 * 'capsules' because the firmware doesn't understand 64-bit +	 * pointers. +	 */ +	return EFI_UNSUPPORTED; +} + +static efi_status_t +efi_thunk_query_variable_info(u32 attr, u64 *storage_space, +			      u64 *remaining_space, +			      u64 *max_variable_size) +{ +	efi_status_t status; +	u32 phys_storage, phys_remaining, phys_max; + +	if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION) +		return EFI_UNSUPPORTED; + +	phys_storage = virt_to_phys(storage_space); +	phys_remaining = virt_to_phys(remaining_space); +	phys_max = virt_to_phys(max_variable_size); + +	status = efi_thunk(query_variable_info, attr, phys_storage, +			   phys_remaining, phys_max); + +	return status; +} + +static efi_status_t +efi_thunk_query_capsule_caps(efi_capsule_header_t **capsules, +			     unsigned long count, u64 *max_size, +			     int *reset_type) +{ +	/* +	 * To properly support this function we would need to repackage +	 * 'capsules' because the firmware doesn't understand 64-bit +	 * pointers. +	 */ +	return EFI_UNSUPPORTED; +} + +void efi_thunk_runtime_setup(void) +{ +	efi.get_time = efi_thunk_get_time; +	efi.set_time = efi_thunk_set_time; +	efi.get_wakeup_time = efi_thunk_get_wakeup_time; +	efi.set_wakeup_time = efi_thunk_set_wakeup_time; +	efi.get_variable = efi_thunk_get_variable; +	efi.get_next_variable = efi_thunk_get_next_variable; +	efi.set_variable = efi_thunk_set_variable; +	efi.get_next_high_mono_count = efi_thunk_get_next_high_mono_count; +	efi.reset_system = efi_thunk_reset_system; +	efi.query_variable_info = efi_thunk_query_variable_info; +	efi.update_capsule = efi_thunk_update_capsule; +	efi.query_capsule_caps = efi_thunk_query_capsule_caps; +} +#endif /* CONFIG_EFI_MIXED */ diff --git a/arch/x86/platform/efi/efi_stub_64.S b/arch/x86/platform/efi/efi_stub_64.S index 4c07ccab814..5fcda727255 100644 --- a/arch/x86/platform/efi/efi_stub_64.S +++ b/arch/x86/platform/efi/efi_stub_64.S @@ -7,6 +7,10 @@   */  #include <linux/linkage.h> +#include <asm/segment.h> +#include <asm/msr.h> +#include <asm/processor-flags.h> +#include <asm/page_types.h>  #define SAVE_XMM			\  	mov %rsp, %rax;			\ @@ -34,72 +38,42 @@  	mov %rsi, %cr0;			\  	mov (%rsp), %rsp -ENTRY(efi_call0) -	SAVE_XMM -	subq $32, %rsp -	call *%rdi -	addq $32, %rsp -	RESTORE_XMM -	ret -ENDPROC(efi_call0) +	/* stolen from gcc */ +	.macro FLUSH_TLB_ALL +	movq %r15, efi_scratch(%rip) +	movq %r14, efi_scratch+8(%rip) +	movq %cr4, %r15 +	movq %r15, %r14 +	andb $0x7f, %r14b +	movq %r14, %cr4 +	movq %r15, %cr4 +	movq efi_scratch+8(%rip), %r14 +	movq efi_scratch(%rip), %r15 +	.endm -ENTRY(efi_call1) -	SAVE_XMM -	subq $32, %rsp -	mov  %rsi, %rcx -	call *%rdi -	addq $32, %rsp -	RESTORE_XMM -	ret -ENDPROC(efi_call1) +	.macro SWITCH_PGT +	cmpb $0, efi_scratch+24(%rip) +	je 1f +	movq %r15, efi_scratch(%rip)		# r15 +	# save previous CR3 +	movq %cr3, %r15 +	movq %r15, efi_scratch+8(%rip)		# prev_cr3 +	movq efi_scratch+16(%rip), %r15		# EFI pgt +	movq %r15, %cr3 +	1: +	.endm -ENTRY(efi_call2) -	SAVE_XMM -	subq $32, %rsp -	mov  %rsi, %rcx -	call *%rdi -	addq $32, %rsp -	RESTORE_XMM -	ret -ENDPROC(efi_call2) +	.macro RESTORE_PGT +	cmpb $0, efi_scratch+24(%rip) +	je 2f +	movq efi_scratch+8(%rip), %r15 +	movq %r15, %cr3 +	movq efi_scratch(%rip), %r15 +	FLUSH_TLB_ALL +	2: +	.endm -ENTRY(efi_call3) -	SAVE_XMM -	subq $32, %rsp -	mov  %rcx, %r8 -	mov  %rsi, %rcx -	call *%rdi -	addq $32, %rsp -	RESTORE_XMM -	ret -ENDPROC(efi_call3) - -ENTRY(efi_call4) -	SAVE_XMM -	subq $32, %rsp -	mov %r8, %r9 -	mov %rcx, %r8 -	mov %rsi, %rcx -	call *%rdi -	addq $32, %rsp -	RESTORE_XMM -	ret -ENDPROC(efi_call4) - -ENTRY(efi_call5) -	SAVE_XMM -	subq $48, %rsp -	mov %r9, 32(%rsp) -	mov %r8, %r9 -	mov %rcx, %r8 -	mov %rsi, %rcx -	call *%rdi -	addq $48, %rsp -	RESTORE_XMM -	ret -ENDPROC(efi_call5) - -ENTRY(efi_call6) +ENTRY(efi_call)  	SAVE_XMM  	mov (%rsp), %rax  	mov 8(%rax), %rax @@ -109,8 +83,177 @@ ENTRY(efi_call6)  	mov %r8, %r9  	mov %rcx, %r8  	mov %rsi, %rcx +	SWITCH_PGT  	call *%rdi +	RESTORE_PGT  	addq $48, %rsp  	RESTORE_XMM  	ret -ENDPROC(efi_call6) +ENDPROC(efi_call) + +#ifdef CONFIG_EFI_MIXED + +/* + * We run this function from the 1:1 mapping. + * + * This function must be invoked with a 1:1 mapped stack. + */ +ENTRY(__efi64_thunk) +	movl	%ds, %eax +	push	%rax +	movl	%es, %eax +	push	%rax +	movl	%ss, %eax +	push	%rax + +	subq	$32, %rsp +	movl	%esi, 0x0(%rsp) +	movl	%edx, 0x4(%rsp) +	movl	%ecx, 0x8(%rsp) +	movq	%r8, %rsi +	movl	%esi, 0xc(%rsp) +	movq	%r9, %rsi +	movl	%esi,  0x10(%rsp) + +	sgdt	save_gdt(%rip) + +	leaq	1f(%rip), %rbx +	movq	%rbx, func_rt_ptr(%rip) + +	/* Switch to gdt with 32-bit segments */ +	movl	64(%rsp), %eax +	lgdt	(%rax) + +	leaq	efi_enter32(%rip), %rax +	pushq	$__KERNEL_CS +	pushq	%rax +	lretq + +1:	addq	$32, %rsp + +	lgdt	save_gdt(%rip) + +	pop	%rbx +	movl	%ebx, %ss +	pop	%rbx +	movl	%ebx, %es +	pop	%rbx +	movl	%ebx, %ds + +	/* +	 * Convert 32-bit status code into 64-bit. +	 */ +	test	%rax, %rax +	jz	1f +	movl	%eax, %ecx +	andl	$0x0fffffff, %ecx +	andl	$0xf0000000, %eax +	shl	$32, %rax +	or	%rcx, %rax +1: +	ret +ENDPROC(__efi64_thunk) + +ENTRY(efi_exit32) +	movq	func_rt_ptr(%rip), %rax +	push	%rax +	mov	%rdi, %rax +	ret +ENDPROC(efi_exit32) + +	.code32 +/* + * EFI service pointer must be in %edi. + * + * The stack should represent the 32-bit calling convention. + */ +ENTRY(efi_enter32) +	movl	$__KERNEL_DS, %eax +	movl	%eax, %ds +	movl	%eax, %es +	movl	%eax, %ss + +	/* Reload pgtables */ +	movl	%cr3, %eax +	movl	%eax, %cr3 + +	/* Disable paging */ +	movl	%cr0, %eax +	btrl	$X86_CR0_PG_BIT, %eax +	movl	%eax, %cr0 + +	/* Disable long mode via EFER */ +	movl	$MSR_EFER, %ecx +	rdmsr +	btrl	$_EFER_LME, %eax +	wrmsr + +	call	*%edi + +	/* We must preserve return value */ +	movl	%eax, %edi + +	/* +	 * Some firmware will return with interrupts enabled. Be sure to +	 * disable them before we switch GDTs. +	 */ +	cli + +	movl	68(%esp), %eax +	movl	%eax, 2(%eax) +	lgdtl	(%eax) + +	movl	%cr4, %eax +	btsl	$(X86_CR4_PAE_BIT), %eax +	movl	%eax, %cr4 + +	movl	%cr3, %eax +	movl	%eax, %cr3 + +	movl	$MSR_EFER, %ecx +	rdmsr +	btsl	$_EFER_LME, %eax +	wrmsr + +	xorl	%eax, %eax +	lldt	%ax + +	movl	72(%esp), %eax +	pushl	$__KERNEL_CS +	pushl	%eax + +	/* Enable paging */ +	movl	%cr0, %eax +	btsl	$X86_CR0_PG_BIT, %eax +	movl	%eax, %cr0 +	lret +ENDPROC(efi_enter32) + +	.data +	.balign	8 +	.global	efi32_boot_gdt +efi32_boot_gdt:	.word	0 +		.quad	0 + +save_gdt:	.word	0 +		.quad	0 +func_rt_ptr:	.quad	0 + +	.global efi_gdt64 +efi_gdt64: +	.word	efi_gdt64_end - efi_gdt64 +	.long	0			/* Filled out by user */ +	.word	0 +	.quad	0x0000000000000000	/* NULL descriptor */ +	.quad	0x00af9a000000ffff	/* __KERNEL_CS */ +	.quad	0x00cf92000000ffff	/* __KERNEL_DS */ +	.quad	0x0080890000000000	/* TS descriptor */ +	.quad   0x0000000000000000	/* TS continued */ +efi_gdt64_end: +#endif /* CONFIG_EFI_MIXED */ + +	.data +ENTRY(efi_scratch) +	.fill 3,8,0 +	.byte 0 +	.quad 0 diff --git a/arch/x86/platform/efi/efi_thunk_64.S b/arch/x86/platform/efi/efi_thunk_64.S new file mode 100644 index 00000000000..8806fa73e6e --- /dev/null +++ b/arch/x86/platform/efi/efi_thunk_64.S @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2014 Intel Corporation; author Matt Fleming + */ + +#include <linux/linkage.h> +#include <asm/page_types.h> + +	.text +	.code64 +ENTRY(efi64_thunk) +	push	%rbp +	push	%rbx + +	/* +	 * Switch to 1:1 mapped 32-bit stack pointer. +	 */ +	movq	%rsp, efi_saved_sp(%rip) +	movq	efi_scratch+25(%rip), %rsp + +	/* +	 * Calculate the physical address of the kernel text. +	 */ +	movq	$__START_KERNEL_map, %rax +	subq	phys_base(%rip), %rax + +	/* +	 * Push some physical addresses onto the stack. This is easier +	 * to do now in a code64 section while the assembler can address +	 * 64-bit values. Note that all the addresses on the stack are +	 * 32-bit. +	 */ +	subq	$16, %rsp +	leaq	efi_exit32(%rip), %rbx +	subq	%rax, %rbx +	movl	%ebx, 8(%rsp) +	leaq	efi_gdt64(%rip), %rbx +	subq	%rax, %rbx +	movl	%ebx, 2(%ebx) +	movl	%ebx, 4(%rsp) +	leaq	efi_gdt32(%rip), %rbx +	subq	%rax, %rbx +	movl	%ebx, 2(%ebx) +	movl	%ebx, (%rsp) + +	leaq	__efi64_thunk(%rip), %rbx +	subq	%rax, %rbx +	call	*%rbx + +	movq	efi_saved_sp(%rip), %rsp +	pop	%rbx +	pop	%rbp +	retq +ENDPROC(efi64_thunk) + +	.data +efi_gdt32: +	.word 	efi_gdt32_end - efi_gdt32 +	.long	0			/* Filled out above */ +	.word	0 +	.quad	0x0000000000000000	/* NULL descriptor */ +	.quad	0x00cf9a000000ffff	/* __KERNEL_CS */ +	.quad	0x00cf93000000ffff	/* __KERNEL_DS */ +efi_gdt32_end: + +efi_saved_sp:		.quad 0 diff --git a/arch/x86/platform/geode/Makefile b/arch/x86/platform/geode/Makefile new file mode 100644 index 00000000000..5b51194f4c8 --- /dev/null +++ b/arch/x86/platform/geode/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_ALIX)		+= alix.o +obj-$(CONFIG_NET5501)		+= net5501.o +obj-$(CONFIG_GEOS)		+= geos.o diff --git a/arch/x86/platform/geode/alix.c b/arch/x86/platform/geode/alix.c new file mode 100644 index 00000000000..76b6632d314 --- /dev/null +++ b/arch/x86/platform/geode/alix.c @@ -0,0 +1,200 @@ +/* + * System Specific setup for PCEngines ALIX. + * At the moment this means setup of GPIO control of LEDs + * on Alix.2/3/6 boards. + * + * + * Copyright (C) 2008 Constantin Baranov <const@mimas.ru> + * Copyright (C) 2011 Ed Wildgoose <kernel@wildgooses.com> + *                and Philip Prindeville <philipp@redfish-solutions.com> + * + * TODO: There are large similarities with leds-net5501.c + * by Alessandro Zummo <a.zummo@towertech.it> + * In the future leds-net5501.c should be migrated over to platform + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/string.h> +#include <linux/module.h> +#include <linux/leds.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/input.h> +#include <linux/gpio_keys.h> +#include <linux/dmi.h> + +#include <asm/geode.h> + +#define BIOS_SIGNATURE_TINYBIOS		0xf0000 +#define BIOS_SIGNATURE_COREBOOT		0x500 +#define BIOS_REGION_SIZE		0x10000 + +static bool force = 0; +module_param(force, bool, 0444); +/* FIXME: Award bios is not automatically detected as Alix platform */ +MODULE_PARM_DESC(force, "Force detection as ALIX.2/ALIX.3 platform"); + +static struct gpio_keys_button alix_gpio_buttons[] = { +	{ +		.code			= KEY_RESTART, +		.gpio			= 24, +		.active_low		= 1, +		.desc			= "Reset button", +		.type			= EV_KEY, +		.wakeup			= 0, +		.debounce_interval	= 100, +		.can_disable		= 0, +	} +}; +static struct gpio_keys_platform_data alix_buttons_data = { +	.buttons			= alix_gpio_buttons, +	.nbuttons			= ARRAY_SIZE(alix_gpio_buttons), +	.poll_interval			= 20, +}; + +static struct platform_device alix_buttons_dev = { +	.name				= "gpio-keys-polled", +	.id				= 1, +	.dev = { +		.platform_data		= &alix_buttons_data, +	} +}; + +static struct gpio_led alix_leds[] = { +	{ +		.name = "alix:1", +		.gpio = 6, +		.default_trigger = "default-on", +		.active_low = 1, +	}, +	{ +		.name = "alix:2", +		.gpio = 25, +		.default_trigger = "default-off", +		.active_low = 1, +	}, +	{ +		.name = "alix:3", +		.gpio = 27, +		.default_trigger = "default-off", +		.active_low = 1, +	}, +}; + +static struct gpio_led_platform_data alix_leds_data = { +	.num_leds = ARRAY_SIZE(alix_leds), +	.leds = alix_leds, +}; + +static struct platform_device alix_leds_dev = { +	.name = "leds-gpio", +	.id = -1, +	.dev.platform_data = &alix_leds_data, +}; + +static struct platform_device *alix_devs[] __initdata = { +	&alix_buttons_dev, +	&alix_leds_dev, +}; + +static void __init register_alix(void) +{ +	/* Setup LED control through leds-gpio driver */ +	platform_add_devices(alix_devs, ARRAY_SIZE(alix_devs)); +} + +static bool __init alix_present(unsigned long bios_phys, +				const char *alix_sig, +				size_t alix_sig_len) +{ +	const size_t bios_len = BIOS_REGION_SIZE; +	const char *bios_virt; +	const char *scan_end; +	const char *p; +	char name[64]; + +	if (force) { +		printk(KERN_NOTICE "%s: forced to skip BIOS test, " +		       "assume system is ALIX.2/ALIX.3\n", +		       KBUILD_MODNAME); +		return true; +	} + +	bios_virt = phys_to_virt(bios_phys); +	scan_end = bios_virt + bios_len - (alix_sig_len + 2); +	for (p = bios_virt; p < scan_end; p++) { +		const char *tail; +		char *a; + +		if (memcmp(p, alix_sig, alix_sig_len) != 0) +			continue; + +		memcpy(name, p, sizeof(name)); + +		/* remove the first \0 character from string */ +		a = strchr(name, '\0'); +		if (a) +			*a = ' '; + +		/* cut the string at a newline */ +		a = strchr(name, '\r'); +		if (a) +			*a = '\0'; + +		tail = p + alix_sig_len; +		if ((tail[0] == '2' || tail[0] == '3' || tail[0] == '6')) { +			printk(KERN_INFO +			       "%s: system is recognized as \"%s\"\n", +			       KBUILD_MODNAME, name); +			return true; +		} +	} + +	return false; +} + +static bool __init alix_present_dmi(void) +{ +	const char *vendor, *product; + +	vendor = dmi_get_system_info(DMI_SYS_VENDOR); +	if (!vendor || strcmp(vendor, "PC Engines")) +		return false; + +	product = dmi_get_system_info(DMI_PRODUCT_NAME); +	if (!product || (strcmp(product, "ALIX.2D") && strcmp(product, "ALIX.6"))) +		return false; + +	printk(KERN_INFO "%s: system is recognized as \"%s %s\"\n", +	       KBUILD_MODNAME, vendor, product); + +	return true; +} + +static int __init alix_init(void) +{ +	const char tinybios_sig[] = "PC Engines ALIX."; +	const char coreboot_sig[] = "PC Engines\0ALIX."; + +	if (!is_geode()) +		return 0; + +	if (alix_present(BIOS_SIGNATURE_TINYBIOS, tinybios_sig, sizeof(tinybios_sig) - 1) || +	    alix_present(BIOS_SIGNATURE_COREBOOT, coreboot_sig, sizeof(coreboot_sig) - 1) || +	    alix_present_dmi()) +		register_alix(); + +	return 0; +} + +module_init(alix_init); + +MODULE_AUTHOR("Ed Wildgoose <kernel@wildgooses.com>"); +MODULE_DESCRIPTION("PCEngines ALIX System Setup"); +MODULE_LICENSE("GPL"); diff --git a/arch/x86/platform/geode/geos.c b/arch/x86/platform/geode/geos.c new file mode 100644 index 00000000000..aa733fba247 --- /dev/null +++ b/arch/x86/platform/geode/geos.c @@ -0,0 +1,128 @@ +/* + * System Specific setup for Traverse Technologies GEOS. + * At the moment this means setup of GPIO control of LEDs. + * + * Copyright (C) 2008 Constantin Baranov <const@mimas.ru> + * Copyright (C) 2011 Ed Wildgoose <kernel@wildgooses.com> + *                and Philip Prindeville <philipp@redfish-solutions.com> + * + * TODO: There are large similarities with leds-net5501.c + * by Alessandro Zummo <a.zummo@towertech.it> + * In the future leds-net5501.c should be migrated over to platform + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/string.h> +#include <linux/module.h> +#include <linux/leds.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/input.h> +#include <linux/gpio_keys.h> +#include <linux/dmi.h> + +#include <asm/geode.h> + +static struct gpio_keys_button geos_gpio_buttons[] = { +	{ +		.code = KEY_RESTART, +		.gpio = 3, +		.active_low = 1, +		.desc = "Reset button", +		.type = EV_KEY, +		.wakeup = 0, +		.debounce_interval = 100, +		.can_disable = 0, +	} +}; +static struct gpio_keys_platform_data geos_buttons_data = { +	.buttons = geos_gpio_buttons, +	.nbuttons = ARRAY_SIZE(geos_gpio_buttons), +	.poll_interval = 20, +}; + +static struct platform_device geos_buttons_dev = { +	.name = "gpio-keys-polled", +	.id = 1, +	.dev = { +		.platform_data = &geos_buttons_data, +	} +}; + +static struct gpio_led geos_leds[] = { +	{ +		.name = "geos:1", +		.gpio = 6, +		.default_trigger = "default-on", +		.active_low = 1, +	}, +	{ +		.name = "geos:2", +		.gpio = 25, +		.default_trigger = "default-off", +		.active_low = 1, +	}, +	{ +		.name = "geos:3", +		.gpio = 27, +		.default_trigger = "default-off", +		.active_low = 1, +	}, +}; + +static struct gpio_led_platform_data geos_leds_data = { +	.num_leds = ARRAY_SIZE(geos_leds), +	.leds = geos_leds, +}; + +static struct platform_device geos_leds_dev = { +	.name = "leds-gpio", +	.id = -1, +	.dev.platform_data = &geos_leds_data, +}; + +static struct platform_device *geos_devs[] __initdata = { +	&geos_buttons_dev, +	&geos_leds_dev, +}; + +static void __init register_geos(void) +{ +	/* Setup LED control through leds-gpio driver */ +	platform_add_devices(geos_devs, ARRAY_SIZE(geos_devs)); +} + +static int __init geos_init(void) +{ +	const char *vendor, *product; + +	if (!is_geode()) +		return 0; + +	vendor = dmi_get_system_info(DMI_SYS_VENDOR); +	if (!vendor || strcmp(vendor, "Traverse Technologies")) +		return 0; + +	product = dmi_get_system_info(DMI_PRODUCT_NAME); +	if (!product || strcmp(product, "Geos")) +		return 0; + +	printk(KERN_INFO "%s: system is recognized as \"%s %s\"\n", +	       KBUILD_MODNAME, vendor, product); + +	register_geos(); + +	return 0; +} + +module_init(geos_init); + +MODULE_AUTHOR("Philip Prindeville <philipp@redfish-solutions.com>"); +MODULE_DESCRIPTION("Traverse Technologies Geos System Setup"); +MODULE_LICENSE("GPL"); diff --git a/arch/x86/platform/geode/net5501.c b/arch/x86/platform/geode/net5501.c new file mode 100644 index 00000000000..927e38c0089 --- /dev/null +++ b/arch/x86/platform/geode/net5501.c @@ -0,0 +1,154 @@ +/* + * System Specific setup for Soekris net5501 + * At the moment this means setup of GPIO control of LEDs and buttons + * on net5501 boards. + * + * + * Copyright (C) 2008-2009 Tower Technologies + * Written by Alessandro Zummo <a.zummo@towertech.it> + * + * Copyright (C) 2008 Constantin Baranov <const@mimas.ru> + * Copyright (C) 2011 Ed Wildgoose <kernel@wildgooses.com> + *                and Philip Prindeville <philipp@redfish-solutions.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/string.h> +#include <linux/module.h> +#include <linux/leds.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/input.h> +#include <linux/gpio_keys.h> + +#include <asm/geode.h> + +#define BIOS_REGION_BASE		0xffff0000 +#define BIOS_REGION_SIZE		0x00010000 + +static struct gpio_keys_button net5501_gpio_buttons[] = { +	{ +		.code = KEY_RESTART, +		.gpio = 24, +		.active_low = 1, +		.desc = "Reset button", +		.type = EV_KEY, +		.wakeup = 0, +		.debounce_interval = 100, +		.can_disable = 0, +	} +}; +static struct gpio_keys_platform_data net5501_buttons_data = { +	.buttons = net5501_gpio_buttons, +	.nbuttons = ARRAY_SIZE(net5501_gpio_buttons), +	.poll_interval = 20, +}; + +static struct platform_device net5501_buttons_dev = { +	.name = "gpio-keys-polled", +	.id = 1, +	.dev = { +		.platform_data = &net5501_buttons_data, +	} +}; + +static struct gpio_led net5501_leds[] = { +	{ +		.name = "net5501:1", +		.gpio = 6, +		.default_trigger = "default-on", +		.active_low = 0, +	}, +}; + +static struct gpio_led_platform_data net5501_leds_data = { +	.num_leds = ARRAY_SIZE(net5501_leds), +	.leds = net5501_leds, +}; + +static struct platform_device net5501_leds_dev = { +	.name = "leds-gpio", +	.id = -1, +	.dev.platform_data = &net5501_leds_data, +}; + +static struct platform_device *net5501_devs[] __initdata = { +	&net5501_buttons_dev, +	&net5501_leds_dev, +}; + +static void __init register_net5501(void) +{ +	/* Setup LED control through leds-gpio driver */ +	platform_add_devices(net5501_devs, ARRAY_SIZE(net5501_devs)); +} + +struct net5501_board { +	u16	offset; +	u16	len; +	char	*sig; +}; + +static struct net5501_board __initdata boards[] = { +	{ 0xb7b, 7, "net5501" },	/* net5501 v1.33/1.33c */ +	{ 0xb1f, 7, "net5501" },	/* net5501 v1.32i */ +}; + +static bool __init net5501_present(void) +{ +	int i; +	unsigned char *rombase, *bios; +	bool found = false; + +	rombase = ioremap(BIOS_REGION_BASE, BIOS_REGION_SIZE - 1); +	if (!rombase) { +		printk(KERN_ERR "%s: failed to get rombase\n", KBUILD_MODNAME); +		return found; +	} + +	bios = rombase + 0x20;	/* null terminated */ + +	if (memcmp(bios, "comBIOS", 7)) +		goto unmap; + +	for (i = 0; i < ARRAY_SIZE(boards); i++) { +		unsigned char *model = rombase + boards[i].offset; + +		if (!memcmp(model, boards[i].sig, boards[i].len)) { +			printk(KERN_INFO "%s: system is recognized as \"%s\"\n", +			       KBUILD_MODNAME, model); + +			found = true; +			break; +		} +	} + +unmap: +	iounmap(rombase); +	return found; +} + +static int __init net5501_init(void) +{ +	if (!is_geode()) +		return 0; + +	if (!net5501_present()) +		return 0; + +	register_net5501(); + +	return 0; +} + +module_init(net5501_init); + +MODULE_AUTHOR("Philip Prindeville <philipp@redfish-solutions.com>"); +MODULE_DESCRIPTION("Soekris net5501 System Setup"); +MODULE_LICENSE("GPL"); diff --git a/arch/x86/platform/goldfish/Makefile b/arch/x86/platform/goldfish/Makefile new file mode 100644 index 00000000000..f030b532fdf --- /dev/null +++ b/arch/x86/platform/goldfish/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_GOLDFISH)	+= goldfish.o diff --git a/arch/x86/platform/goldfish/goldfish.c b/arch/x86/platform/goldfish/goldfish.c new file mode 100644 index 00000000000..1693107a518 --- /dev/null +++ b/arch/x86/platform/goldfish/goldfish.c @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2007 Google, Inc. + * Copyright (C) 2011 Intel, Inc. + * Copyright (C) 2013 Intel, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + */ + +#include <linux/kernel.h> +#include <linux/irq.h> +#include <linux/platform_device.h> + +/* + * Where in virtual device memory the IO devices (timers, system controllers + * and so on) + */ + +#define GOLDFISH_PDEV_BUS_BASE	(0xff001000) +#define GOLDFISH_PDEV_BUS_END	(0xff7fffff) +#define GOLDFISH_PDEV_BUS_IRQ	(4) + +#define GOLDFISH_TTY_BASE	(0x2000) + +static struct resource goldfish_pdev_bus_resources[] = { +	{ +		.start  = GOLDFISH_PDEV_BUS_BASE, +		.end    = GOLDFISH_PDEV_BUS_END, +		.flags  = IORESOURCE_MEM, +	}, +	{ +		.start	= GOLDFISH_PDEV_BUS_IRQ, +		.end	= GOLDFISH_PDEV_BUS_IRQ, +		.flags	= IORESOURCE_IRQ, +	} +}; + +static int __init goldfish_init(void) +{ +	platform_device_register_simple("goldfish_pdev_bus", -1, +						goldfish_pdev_bus_resources, 2); +	return 0; +} +device_initcall(goldfish_init); diff --git a/arch/x86/platform/intel-mid/Makefile b/arch/x86/platform/intel-mid/Makefile new file mode 100644 index 00000000000..0a8ee703b9f --- /dev/null +++ b/arch/x86/platform/intel-mid/Makefile @@ -0,0 +1,7 @@ +obj-$(CONFIG_X86_INTEL_MID) += intel-mid.o intel_mid_vrtc.o mfld.o mrfl.o +obj-$(CONFIG_EARLY_PRINTK_INTEL_MID) += early_printk_intel_mid.o + +# SFI specific code +ifdef CONFIG_X86_INTEL_MID +obj-$(CONFIG_SFI) += sfi.o device_libs/ +endif diff --git a/arch/x86/platform/intel-mid/device_libs/Makefile b/arch/x86/platform/intel-mid/device_libs/Makefile new file mode 100644 index 00000000000..af9307f2cc2 --- /dev/null +++ b/arch/x86/platform/intel-mid/device_libs/Makefile @@ -0,0 +1,23 @@ +# IPC Devices +obj-y += platform_ipc.o +obj-$(subst m,y,$(CONFIG_MFD_INTEL_MSIC)) += platform_msic.o +obj-$(subst m,y,$(CONFIG_SND_MFLD_MACHINE)) += platform_msic_audio.o +obj-$(subst m,y,$(CONFIG_GPIO_MSIC)) += platform_msic_gpio.o +obj-$(subst m,y,$(CONFIG_MFD_INTEL_MSIC)) += platform_msic_ocd.o +obj-$(subst m,y,$(CONFIG_MFD_INTEL_MSIC)) += platform_msic_battery.o +obj-$(subst m,y,$(CONFIG_INTEL_MID_POWER_BUTTON)) += platform_msic_power_btn.o +obj-$(subst m,y,$(CONFIG_GPIO_INTEL_PMIC)) += platform_pmic_gpio.o +obj-$(subst m,y,$(CONFIG_INTEL_MFLD_THERMAL)) += platform_msic_thermal.o +# I2C Devices +obj-$(subst m,y,$(CONFIG_SENSORS_EMC1403)) += platform_emc1403.o +obj-$(subst m,y,$(CONFIG_SENSORS_LIS3LV02D)) += platform_lis331.o +obj-$(subst m,y,$(CONFIG_GPIO_PCA953X)) += platform_max7315.o +obj-$(subst m,y,$(CONFIG_INPUT_MPU3050)) += platform_mpu3050.o +obj-$(subst m,y,$(CONFIG_INPUT_BMA150)) += platform_bma023.o +obj-$(subst m,y,$(CONFIG_GPIO_PCA953X)) += platform_tca6416.o +obj-$(subst m,y,$(CONFIG_DRM_MEDFIELD)) += platform_tc35876x.o +# SPI Devices +obj-$(subst m,y,$(CONFIG_SERIAL_MRST_MAX3110)) += platform_max3111.o +# MISC Devices +obj-$(subst m,y,$(CONFIG_KEYBOARD_GPIO)) += platform_gpio_keys.o +obj-$(subst m,y,$(CONFIG_INTEL_MID_WATCHDOG)) += platform_wdt.o diff --git a/arch/x86/platform/intel-mid/device_libs/platform_bma023.c b/arch/x86/platform/intel-mid/device_libs/platform_bma023.c new file mode 100644 index 00000000000..0ae7f2ae229 --- /dev/null +++ b/arch/x86/platform/intel-mid/device_libs/platform_bma023.c @@ -0,0 +1,20 @@ +/* + * platform_bma023.c: bma023 platform data initilization file + * + * (C) Copyright 2013 Intel Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + */ + +#include <asm/intel-mid.h> + +static const struct devs_id bma023_dev_id __initconst = { +	.name = "bma023", +	.type = SFI_DEV_TYPE_I2C, +	.delay = 1, +}; + +sfi_device(bma023_dev_id); diff --git a/arch/x86/platform/intel-mid/device_libs/platform_emc1403.c b/arch/x86/platform/intel-mid/device_libs/platform_emc1403.c new file mode 100644 index 00000000000..69a783689d2 --- /dev/null +++ b/arch/x86/platform/intel-mid/device_libs/platform_emc1403.c @@ -0,0 +1,43 @@ +/* + * platform_emc1403.c: emc1403 platform data initilization file + * + * (C) Copyright 2013 Intel Corporation + * Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + */ + +#include <linux/init.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <asm/intel-mid.h> + +static void __init *emc1403_platform_data(void *info) +{ +	static short intr2nd_pdata; +	struct i2c_board_info *i2c_info = info; +	int intr = get_gpio_by_name("thermal_int"); +	int intr2nd = get_gpio_by_name("thermal_alert"); + +	if (intr < 0) +		return NULL; +	if (intr2nd < 0) +		return NULL; + +	i2c_info->irq = intr + INTEL_MID_IRQ_OFFSET; +	intr2nd_pdata = intr2nd + INTEL_MID_IRQ_OFFSET; + +	return &intr2nd_pdata; +} + +static const struct devs_id emc1403_dev_id __initconst = { +	.name = "emc1403", +	.type = SFI_DEV_TYPE_I2C, +	.delay = 1, +	.get_platform_data = &emc1403_platform_data, +}; + +sfi_device(emc1403_dev_id); diff --git a/arch/x86/platform/intel-mid/device_libs/platform_gpio_keys.c b/arch/x86/platform/intel-mid/device_libs/platform_gpio_keys.c new file mode 100644 index 00000000000..dccae6b0413 --- /dev/null +++ b/arch/x86/platform/intel-mid/device_libs/platform_gpio_keys.c @@ -0,0 +1,83 @@ +/* + * platform_gpio_keys.c: gpio_keys platform data initilization file + * + * (C) Copyright 2013 Intel Corporation + * Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + */ + +#include <linux/input.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/gpio.h> +#include <linux/gpio_keys.h> +#include <linux/platform_device.h> +#include <asm/intel-mid.h> + +#define DEVICE_NAME "gpio-keys" + +/* + * we will search these buttons in SFI GPIO table (by name) + * and register them dynamically. Please add all possible + * buttons here, we will shrink them if no GPIO found. + */ +static struct gpio_keys_button gpio_button[] = { +	{KEY_POWER,		-1, 1, "power_btn",	EV_KEY, 0, 3000}, +	{KEY_PROG1,		-1, 1, "prog_btn1",	EV_KEY, 0, 20}, +	{KEY_PROG2,		-1, 1, "prog_btn2",	EV_KEY, 0, 20}, +	{SW_LID,		-1, 1, "lid_switch",	EV_SW,  0, 20}, +	{KEY_VOLUMEUP,		-1, 1, "vol_up",	EV_KEY, 0, 20}, +	{KEY_VOLUMEDOWN,	-1, 1, "vol_down",	EV_KEY, 0, 20}, +	{KEY_CAMERA,		-1, 1, "camera_full",	EV_KEY, 0, 20}, +	{KEY_CAMERA_FOCUS,	-1, 1, "camera_half",	EV_KEY, 0, 20}, +	{SW_KEYPAD_SLIDE,	-1, 1, "MagSw1",	EV_SW,  0, 20}, +	{SW_KEYPAD_SLIDE,	-1, 1, "MagSw2",	EV_SW,  0, 20}, +}; + +static struct gpio_keys_platform_data gpio_keys = { +	.buttons	= gpio_button, +	.rep		= 1, +	.nbuttons	= -1, /* will fill it after search */ +}; + +static struct platform_device pb_device = { +	.name		= DEVICE_NAME, +	.id		= -1, +	.dev		= { +		.platform_data	= &gpio_keys, +	}, +}; + +/* + * Shrink the non-existent buttons, register the gpio button + * device if there is some + */ +static int __init pb_keys_init(void) +{ +	struct gpio_keys_button *gb = gpio_button; +	int i, num, good = 0; + +	num = sizeof(gpio_button) / sizeof(struct gpio_keys_button); +	for (i = 0; i < num; i++) { +		gb[i].gpio = get_gpio_by_name(gb[i].desc); +		pr_debug("info[%2d]: name = %s, gpio = %d\n", i, gb[i].desc, +					gb[i].gpio); +		if (gb[i].gpio < 0) +			continue; + +		if (i != good) +			gb[good] = gb[i]; +		good++; +	} + +	if (good) { +		gpio_keys.nbuttons = good; +		return platform_device_register(&pb_device); +	} +	return 0; +} +late_initcall(pb_keys_init); diff --git a/arch/x86/platform/intel-mid/device_libs/platform_ipc.c b/arch/x86/platform/intel-mid/device_libs/platform_ipc.c new file mode 100644 index 00000000000..a84b73d6c4a --- /dev/null +++ b/arch/x86/platform/intel-mid/device_libs/platform_ipc.c @@ -0,0 +1,68 @@ +/* + * platform_ipc.c: IPC platform library file + * + * (C) Copyright 2013 Intel Corporation + * Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/sfi.h> +#include <linux/gpio.h> +#include <asm/intel-mid.h> +#include "platform_ipc.h" + +void __init ipc_device_handler(struct sfi_device_table_entry *pentry, +				struct devs_id *dev) +{ +	struct platform_device *pdev; +	void *pdata = NULL; +	static struct resource res __initdata = { +		.name = "IRQ", +		.flags = IORESOURCE_IRQ, +	}; + +	pr_debug("IPC bus, name = %16.16s, irq = 0x%2x\n", +		pentry->name, pentry->irq); + +	/* +	 * We need to call platform init of IPC devices to fill misc_pdata +	 * structure. It will be used in msic_init for initialization. +	 */ +	if (dev != NULL) +		pdata = dev->get_platform_data(pentry); + +	/* +	 * On Medfield the platform device creation is handled by the MSIC +	 * MFD driver so we don't need to do it here. +	 */ +	if (intel_mid_has_msic()) +		return; + +	pdev = platform_device_alloc(pentry->name, 0); +	if (pdev == NULL) { +		pr_err("out of memory for SFI platform device '%s'.\n", +			pentry->name); +		return; +	} +	res.start = pentry->irq; +	platform_device_add_resources(pdev, &res, 1); + +	pdev->dev.platform_data = pdata; +	intel_scu_device_register(pdev); +} + +static const struct devs_id pmic_audio_dev_id __initconst = { +	.name = "pmic_audio", +	.type = SFI_DEV_TYPE_IPC, +	.delay = 1, +	.device_handler = &ipc_device_handler, +}; + +sfi_device(pmic_audio_dev_id); diff --git a/arch/x86/platform/intel-mid/device_libs/platform_ipc.h b/arch/x86/platform/intel-mid/device_libs/platform_ipc.h new file mode 100644 index 00000000000..79bb09d4f71 --- /dev/null +++ b/arch/x86/platform/intel-mid/device_libs/platform_ipc.h @@ -0,0 +1,18 @@ +/* + * platform_ipc.h: IPC platform library header file + * + * (C) Copyright 2013 Intel Corporation + * Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + */ +#ifndef _PLATFORM_IPC_H_ +#define _PLATFORM_IPC_H_ + +void __init +ipc_device_handler(struct sfi_device_table_entry *pentry, struct devs_id *dev); + +#endif diff --git a/arch/x86/platform/intel-mid/device_libs/platform_lis331.c b/arch/x86/platform/intel-mid/device_libs/platform_lis331.c new file mode 100644 index 00000000000..54226de7541 --- /dev/null +++ b/arch/x86/platform/intel-mid/device_libs/platform_lis331.c @@ -0,0 +1,41 @@ +/* + * platform_lis331.c:  lis331 platform data initilization file + * + * (C) Copyright 2013 Intel Corporation + * Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + */ + +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <asm/intel-mid.h> + +static void __init *lis331dl_platform_data(void *info) +{ +	static short intr2nd_pdata; +	struct i2c_board_info *i2c_info = info; +	int intr = get_gpio_by_name("accel_int"); +	int intr2nd = get_gpio_by_name("accel_2"); + +	if (intr < 0) +		return NULL; +	if (intr2nd < 0) +		return NULL; + +	i2c_info->irq = intr + INTEL_MID_IRQ_OFFSET; +	intr2nd_pdata = intr2nd + INTEL_MID_IRQ_OFFSET; + +	return &intr2nd_pdata; +} + +static const struct devs_id lis331dl_dev_id __initconst = { +	.name = "i2c_accel", +	.type = SFI_DEV_TYPE_I2C, +	.get_platform_data = &lis331dl_platform_data, +}; + +sfi_device(lis331dl_dev_id); diff --git a/arch/x86/platform/intel-mid/device_libs/platform_max3111.c b/arch/x86/platform/intel-mid/device_libs/platform_max3111.c new file mode 100644 index 00000000000..afd1df94e0e --- /dev/null +++ b/arch/x86/platform/intel-mid/device_libs/platform_max3111.c @@ -0,0 +1,35 @@ +/* + * platform_max3111.c: max3111 platform data initilization file + * + * (C) Copyright 2013 Intel Corporation + * Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + */ + +#include <linux/gpio.h> +#include <linux/spi/spi.h> +#include <asm/intel-mid.h> + +static void __init *max3111_platform_data(void *info) +{ +	struct spi_board_info *spi_info = info; +	int intr = get_gpio_by_name("max3111_int"); + +	spi_info->mode = SPI_MODE_0; +	if (intr == -1) +		return NULL; +	spi_info->irq = intr + INTEL_MID_IRQ_OFFSET; +	return NULL; +} + +static const struct devs_id max3111_dev_id __initconst = { +	.name = "spi_max3111", +	.type = SFI_DEV_TYPE_SPI, +	.get_platform_data = &max3111_platform_data, +}; + +sfi_device(max3111_dev_id); diff --git a/arch/x86/platform/intel-mid/device_libs/platform_max7315.c b/arch/x86/platform/intel-mid/device_libs/platform_max7315.c new file mode 100644 index 00000000000..2c8acbc1e9a --- /dev/null +++ b/arch/x86/platform/intel-mid/device_libs/platform_max7315.c @@ -0,0 +1,79 @@ +/* + * platform_max7315.c: max7315 platform data initilization file + * + * (C) Copyright 2013 Intel Corporation + * Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + */ + +#include <linux/init.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/platform_data/pca953x.h> +#include <asm/intel-mid.h> + +#define MAX7315_NUM 2 + +static void __init *max7315_platform_data(void *info) +{ +	static struct pca953x_platform_data max7315_pdata[MAX7315_NUM]; +	static int nr; +	struct pca953x_platform_data *max7315 = &max7315_pdata[nr]; +	struct i2c_board_info *i2c_info = info; +	int gpio_base, intr; +	char base_pin_name[SFI_NAME_LEN + 1]; +	char intr_pin_name[SFI_NAME_LEN + 1]; + +	if (nr == MAX7315_NUM) { +		pr_err("too many max7315s, we only support %d\n", +				MAX7315_NUM); +		return NULL; +	} +	/* we have several max7315 on the board, we only need load several +	 * instances of the same pca953x driver to cover them +	 */ +	strcpy(i2c_info->type, "max7315"); +	if (nr++) { +		sprintf(base_pin_name, "max7315_%d_base", nr); +		sprintf(intr_pin_name, "max7315_%d_int", nr); +	} else { +		strcpy(base_pin_name, "max7315_base"); +		strcpy(intr_pin_name, "max7315_int"); +	} + +	gpio_base = get_gpio_by_name(base_pin_name); +	intr = get_gpio_by_name(intr_pin_name); + +	if (gpio_base < 0) +		return NULL; +	max7315->gpio_base = gpio_base; +	if (intr != -1) { +		i2c_info->irq = intr + INTEL_MID_IRQ_OFFSET; +		max7315->irq_base = gpio_base + INTEL_MID_IRQ_OFFSET; +	} else { +		i2c_info->irq = -1; +		max7315->irq_base = -1; +	} +	return max7315; +} + +static const struct devs_id max7315_dev_id __initconst = { +	.name = "i2c_max7315", +	.type = SFI_DEV_TYPE_I2C, +	.delay = 1, +	.get_platform_data = &max7315_platform_data, +}; + +static const struct devs_id max7315_2_dev_id __initconst = { +	.name = "i2c_max7315_2", +	.type = SFI_DEV_TYPE_I2C, +	.delay = 1, +	.get_platform_data = &max7315_platform_data, +}; + +sfi_device(max7315_dev_id); +sfi_device(max7315_2_dev_id); diff --git a/arch/x86/platform/intel-mid/device_libs/platform_mpu3050.c b/arch/x86/platform/intel-mid/device_libs/platform_mpu3050.c new file mode 100644 index 00000000000..cfe9a47a1e8 --- /dev/null +++ b/arch/x86/platform/intel-mid/device_libs/platform_mpu3050.c @@ -0,0 +1,36 @@ +/* + * platform_mpu3050.c: mpu3050 platform data initilization file + * + * (C) Copyright 2013 Intel Corporation + * Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + */ + +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <asm/intel-mid.h> + +static void *mpu3050_platform_data(void *info) +{ +	struct i2c_board_info *i2c_info = info; +	int intr = get_gpio_by_name("mpu3050_int"); + +	if (intr < 0) +		return NULL; + +	i2c_info->irq = intr + INTEL_MID_IRQ_OFFSET; +	return NULL; +} + +static const struct devs_id mpu3050_dev_id __initconst = { +	.name = "mpu3050", +	.type = SFI_DEV_TYPE_I2C, +	.delay = 1, +	.get_platform_data = &mpu3050_platform_data, +}; + +sfi_device(mpu3050_dev_id); diff --git a/arch/x86/platform/intel-mid/device_libs/platform_msic.c b/arch/x86/platform/intel-mid/device_libs/platform_msic.c new file mode 100644 index 00000000000..9f4a775a69d --- /dev/null +++ b/arch/x86/platform/intel-mid/device_libs/platform_msic.c @@ -0,0 +1,87 @@ +/* + * platform_msic.c: MSIC platform data initilization file + * + * (C) Copyright 2013 Intel Corporation + * Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + */ + +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/scatterlist.h> +#include <linux/init.h> +#include <linux/sfi.h> +#include <linux/mfd/intel_msic.h> +#include <asm/intel_scu_ipc.h> +#include <asm/intel-mid.h> +#include "platform_msic.h" + +struct intel_msic_platform_data msic_pdata; + +static struct resource msic_resources[] = { +	{ +		.start	= INTEL_MSIC_IRQ_PHYS_BASE, +		.end	= INTEL_MSIC_IRQ_PHYS_BASE + 64 - 1, +		.flags	= IORESOURCE_MEM, +	}, +}; + +static struct platform_device msic_device = { +	.name		= "intel_msic", +	.id		= -1, +	.dev		= { +		.platform_data	= &msic_pdata, +	}, +	.num_resources	= ARRAY_SIZE(msic_resources), +	.resource	= msic_resources, +}; + +static int msic_scu_status_change(struct notifier_block *nb, +				  unsigned long code, void *data) +{ +	if (code == SCU_DOWN) { +		platform_device_unregister(&msic_device); +		return 0; +	} + +	return platform_device_register(&msic_device); +} + +static int __init msic_init(void) +{ +	static struct notifier_block msic_scu_notifier = { +		.notifier_call	= msic_scu_status_change, +	}; + +	/* +	 * We need to be sure that the SCU IPC is ready before MSIC device +	 * can be registered. +	 */ +	if (intel_mid_has_msic()) +		intel_scu_notifier_add(&msic_scu_notifier); + +	return 0; +} +arch_initcall(msic_init); + +/* + * msic_generic_platform_data - sets generic platform data for the block + * @info: pointer to the SFI device table entry for this block + * @block: MSIC block + * + * Function sets IRQ number from the SFI table entry for given device to + * the MSIC platform data. + */ +void *msic_generic_platform_data(void *info, enum intel_msic_block block) +{ +	struct sfi_device_table_entry *entry = info; + +	BUG_ON(block < 0 || block >= INTEL_MSIC_BLOCK_LAST); +	msic_pdata.irq[block] = entry->irq; + +	return NULL; +} diff --git a/arch/x86/platform/intel-mid/device_libs/platform_msic.h b/arch/x86/platform/intel-mid/device_libs/platform_msic.h new file mode 100644 index 00000000000..b7be1d041da --- /dev/null +++ b/arch/x86/platform/intel-mid/device_libs/platform_msic.h @@ -0,0 +1,19 @@ +/* + * platform_msic.h: MSIC platform data header file + * + * (C) Copyright 2013 Intel Corporation + * Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + */ +#ifndef _PLATFORM_MSIC_H_ +#define _PLATFORM_MSIC_H_ + +extern struct intel_msic_platform_data msic_pdata; + +void *msic_generic_platform_data(void *info, enum intel_msic_block block); + +#endif diff --git a/arch/x86/platform/intel-mid/device_libs/platform_msic_audio.c b/arch/x86/platform/intel-mid/device_libs/platform_msic_audio.c new file mode 100644 index 00000000000..29629397d2b --- /dev/null +++ b/arch/x86/platform/intel-mid/device_libs/platform_msic_audio.c @@ -0,0 +1,47 @@ +/* + * platform_msic_audio.c: MSIC audio platform data initilization file + * + * (C) Copyright 2013 Intel Corporation + * Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + */ + +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/scatterlist.h> +#include <linux/init.h> +#include <linux/sfi.h> +#include <linux/platform_device.h> +#include <linux/mfd/intel_msic.h> +#include <asm/intel-mid.h> + +#include "platform_msic.h" +#include "platform_ipc.h" + +static void *msic_audio_platform_data(void *info) +{ +	struct platform_device *pdev; + +	pdev = platform_device_register_simple("sst-platform", -1, NULL, 0); + +	if (IS_ERR(pdev)) { +		pr_err("failed to create audio platform device\n"); +		return NULL; +	} + +	return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_AUDIO); +} + +static const struct devs_id msic_audio_dev_id __initconst = { +	.name = "msic_audio", +	.type = SFI_DEV_TYPE_IPC, +	.delay = 1, +	.get_platform_data = &msic_audio_platform_data, +	.device_handler = &ipc_device_handler, +}; + +sfi_device(msic_audio_dev_id); diff --git a/arch/x86/platform/intel-mid/device_libs/platform_msic_battery.c b/arch/x86/platform/intel-mid/device_libs/platform_msic_battery.c new file mode 100644 index 00000000000..f446c33df1a --- /dev/null +++ b/arch/x86/platform/intel-mid/device_libs/platform_msic_battery.c @@ -0,0 +1,37 @@ +/* + * platform_msic_battery.c: MSIC battery platform data initilization file + * + * (C) Copyright 2013 Intel Corporation + * Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + */ + +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/scatterlist.h> +#include <linux/init.h> +#include <linux/sfi.h> +#include <linux/mfd/intel_msic.h> +#include <asm/intel-mid.h> + +#include "platform_msic.h" +#include "platform_ipc.h" + +static void __init *msic_battery_platform_data(void *info) +{ +	return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_BATTERY); +} + +static const struct devs_id msic_battery_dev_id __initconst = { +	.name = "msic_battery", +	.type = SFI_DEV_TYPE_IPC, +	.delay = 1, +	.get_platform_data = &msic_battery_platform_data, +	.device_handler = &ipc_device_handler, +}; + +sfi_device(msic_battery_dev_id); diff --git a/arch/x86/platform/intel-mid/device_libs/platform_msic_gpio.c b/arch/x86/platform/intel-mid/device_libs/platform_msic_gpio.c new file mode 100644 index 00000000000..2a4f7b1dd91 --- /dev/null +++ b/arch/x86/platform/intel-mid/device_libs/platform_msic_gpio.c @@ -0,0 +1,48 @@ +/* + * platform_msic_gpio.c: MSIC GPIO platform data initilization file + * + * (C) Copyright 2013 Intel Corporation + * Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + */ + +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/scatterlist.h> +#include <linux/sfi.h> +#include <linux/init.h> +#include <linux/gpio.h> +#include <linux/mfd/intel_msic.h> +#include <asm/intel-mid.h> + +#include "platform_msic.h" +#include "platform_ipc.h" + +static void __init *msic_gpio_platform_data(void *info) +{ +	static struct intel_msic_gpio_pdata msic_gpio_pdata; + +	int gpio = get_gpio_by_name("msic_gpio_base"); + +	if (gpio < 0) +		return NULL; + +	msic_gpio_pdata.gpio_base = gpio; +	msic_pdata.gpio = &msic_gpio_pdata; + +	return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_GPIO); +} + +static const struct devs_id msic_gpio_dev_id __initconst = { +	.name = "msic_gpio", +	.type = SFI_DEV_TYPE_IPC, +	.delay = 1, +	.get_platform_data = &msic_gpio_platform_data, +	.device_handler = &ipc_device_handler, +}; + +sfi_device(msic_gpio_dev_id); diff --git a/arch/x86/platform/intel-mid/device_libs/platform_msic_ocd.c b/arch/x86/platform/intel-mid/device_libs/platform_msic_ocd.c new file mode 100644 index 00000000000..6497111ddb5 --- /dev/null +++ b/arch/x86/platform/intel-mid/device_libs/platform_msic_ocd.c @@ -0,0 +1,49 @@ +/* + * platform_msic_ocd.c: MSIC OCD platform data initilization file + * + * (C) Copyright 2013 Intel Corporation + * Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + */ + +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/scatterlist.h> +#include <linux/sfi.h> +#include <linux/init.h> +#include <linux/gpio.h> +#include <linux/mfd/intel_msic.h> +#include <asm/intel-mid.h> + +#include "platform_msic.h" +#include "platform_ipc.h" + +static void __init *msic_ocd_platform_data(void *info) +{ +	static struct intel_msic_ocd_pdata msic_ocd_pdata; +	int gpio; + +	gpio = get_gpio_by_name("ocd_gpio"); + +	if (gpio < 0) +		return NULL; + +	msic_ocd_pdata.gpio = gpio; +	msic_pdata.ocd = &msic_ocd_pdata; + +	return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_OCD); +} + +static const struct devs_id msic_ocd_dev_id __initconst = { +	.name = "msic_ocd", +	.type = SFI_DEV_TYPE_IPC, +	.delay = 1, +	.get_platform_data = &msic_ocd_platform_data, +	.device_handler = &ipc_device_handler, +}; + +sfi_device(msic_ocd_dev_id); diff --git a/arch/x86/platform/intel-mid/device_libs/platform_msic_power_btn.c b/arch/x86/platform/intel-mid/device_libs/platform_msic_power_btn.c new file mode 100644 index 00000000000..83a3459bc33 --- /dev/null +++ b/arch/x86/platform/intel-mid/device_libs/platform_msic_power_btn.c @@ -0,0 +1,36 @@ +/* + * platform_msic_power_btn.c: MSIC power btn platform data initilization file + * + * (C) Copyright 2013 Intel Corporation + * Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + */ +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/scatterlist.h> +#include <linux/sfi.h> +#include <linux/init.h> +#include <linux/mfd/intel_msic.h> +#include <asm/intel-mid.h> + +#include "platform_msic.h" +#include "platform_ipc.h" + +static void __init *msic_power_btn_platform_data(void *info) +{ +	return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_POWER_BTN); +} + +static const struct devs_id msic_power_btn_dev_id __initconst = { +	.name = "msic_power_btn", +	.type = SFI_DEV_TYPE_IPC, +	.delay = 1, +	.get_platform_data = &msic_power_btn_platform_data, +	.device_handler = &ipc_device_handler, +}; + +sfi_device(msic_power_btn_dev_id); diff --git a/arch/x86/platform/intel-mid/device_libs/platform_msic_thermal.c b/arch/x86/platform/intel-mid/device_libs/platform_msic_thermal.c new file mode 100644 index 00000000000..a351878b96b --- /dev/null +++ b/arch/x86/platform/intel-mid/device_libs/platform_msic_thermal.c @@ -0,0 +1,37 @@ +/* + * platform_msic_thermal.c: msic_thermal platform data initilization file + * + * (C) Copyright 2013 Intel Corporation + * Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + */ + +#include <linux/input.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/gpio.h> +#include <linux/platform_device.h> +#include <linux/mfd/intel_msic.h> +#include <asm/intel-mid.h> + +#include "platform_msic.h" +#include "platform_ipc.h" + +static void __init *msic_thermal_platform_data(void *info) +{ +	return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_THERMAL); +} + +static const struct devs_id msic_thermal_dev_id __initconst = { +	.name = "msic_thermal", +	.type = SFI_DEV_TYPE_IPC, +	.delay = 1, +	.get_platform_data = &msic_thermal_platform_data, +	.device_handler = &ipc_device_handler, +}; + +sfi_device(msic_thermal_dev_id); diff --git a/arch/x86/platform/intel-mid/device_libs/platform_pmic_gpio.c b/arch/x86/platform/intel-mid/device_libs/platform_pmic_gpio.c new file mode 100644 index 00000000000..65c2a9a19db --- /dev/null +++ b/arch/x86/platform/intel-mid/device_libs/platform_pmic_gpio.c @@ -0,0 +1,54 @@ +/* + * platform_pmic_gpio.c: PMIC GPIO platform data initilization file + * + * (C) Copyright 2013 Intel Corporation + * Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + */ + +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/scatterlist.h> +#include <linux/gpio.h> +#include <linux/init.h> +#include <linux/sfi.h> +#include <linux/intel_pmic_gpio.h> +#include <asm/intel-mid.h> + +#include "platform_ipc.h" + +static void __init *pmic_gpio_platform_data(void *info) +{ +	static struct intel_pmic_gpio_platform_data pmic_gpio_pdata; +	int gpio_base = get_gpio_by_name("pmic_gpio_base"); + +	if (gpio_base < 0) +		gpio_base = 64; +	pmic_gpio_pdata.gpio_base = gpio_base; +	pmic_gpio_pdata.irq_base = gpio_base + INTEL_MID_IRQ_OFFSET; +	pmic_gpio_pdata.gpiointr = 0xffffeff8; + +	return &pmic_gpio_pdata; +} + +static const struct devs_id pmic_gpio_spi_dev_id __initconst = { +	.name = "pmic_gpio", +	.type = SFI_DEV_TYPE_SPI, +	.delay = 1, +	.get_platform_data = &pmic_gpio_platform_data, +}; + +static const struct devs_id pmic_gpio_ipc_dev_id __initconst = { +	.name = "pmic_gpio", +	.type = SFI_DEV_TYPE_IPC, +	.delay = 1, +	.get_platform_data = &pmic_gpio_platform_data, +	.device_handler = &ipc_device_handler +}; + +sfi_device(pmic_gpio_spi_dev_id); +sfi_device(pmic_gpio_ipc_dev_id); diff --git a/arch/x86/platform/intel-mid/device_libs/platform_tc35876x.c b/arch/x86/platform/intel-mid/device_libs/platform_tc35876x.c new file mode 100644 index 00000000000..740fc757050 --- /dev/null +++ b/arch/x86/platform/intel-mid/device_libs/platform_tc35876x.c @@ -0,0 +1,36 @@ +/* + * platform_tc35876x.c: tc35876x platform data initilization file + * + * (C) Copyright 2013 Intel Corporation + * Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + */ + +#include <linux/gpio.h> +#include <linux/i2c/tc35876x.h> +#include <asm/intel-mid.h> + +/*tc35876x DSI_LVDS bridge chip and panel platform data*/ +static void *tc35876x_platform_data(void *data) +{ +	static struct tc35876x_platform_data pdata; + +	/* gpio pins set to -1 will not be used by the driver */ +	pdata.gpio_bridge_reset = get_gpio_by_name("LCMB_RXEN"); +	pdata.gpio_panel_bl_en = get_gpio_by_name("6S6P_BL_EN"); +	pdata.gpio_panel_vadd = get_gpio_by_name("EN_VREG_LCD_V3P3"); + +	return &pdata; +} + +static const struct devs_id tc35876x_dev_id __initconst = { +	.name = "i2c_disp_brig", +	.type = SFI_DEV_TYPE_I2C, +	.get_platform_data = &tc35876x_platform_data, +}; + +sfi_device(tc35876x_dev_id); diff --git a/arch/x86/platform/intel-mid/device_libs/platform_tca6416.c b/arch/x86/platform/intel-mid/device_libs/platform_tca6416.c new file mode 100644 index 00000000000..33be0b3be6e --- /dev/null +++ b/arch/x86/platform/intel-mid/device_libs/platform_tca6416.c @@ -0,0 +1,57 @@ +/* + * platform_tca6416.c: tca6416 platform data initilization file + * + * (C) Copyright 2013 Intel Corporation + * Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + */ + +#include <linux/platform_data/pca953x.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <asm/intel-mid.h> + +#define TCA6416_NAME	"tca6416" +#define TCA6416_BASE	"tca6416_base" +#define TCA6416_INTR	"tca6416_int" + +static void *tca6416_platform_data(void *info) +{ +	static struct pca953x_platform_data tca6416; +	struct i2c_board_info *i2c_info = info; +	int gpio_base, intr; +	char base_pin_name[SFI_NAME_LEN + 1]; +	char intr_pin_name[SFI_NAME_LEN + 1]; + +	strcpy(i2c_info->type, TCA6416_NAME); +	strcpy(base_pin_name, TCA6416_BASE); +	strcpy(intr_pin_name, TCA6416_INTR); + +	gpio_base = get_gpio_by_name(base_pin_name); +	intr = get_gpio_by_name(intr_pin_name); + +	if (gpio_base < 0) +		return NULL; +	tca6416.gpio_base = gpio_base; +	if (intr >= 0) { +		i2c_info->irq = intr + INTEL_MID_IRQ_OFFSET; +		tca6416.irq_base = gpio_base + INTEL_MID_IRQ_OFFSET; +	} else { +		i2c_info->irq = -1; +		tca6416.irq_base = -1; +	} +	return &tca6416; +} + +static const struct devs_id tca6416_dev_id __initconst = { +	.name = "tca6416", +	.type = SFI_DEV_TYPE_I2C, +	.delay = 1, +	.get_platform_data = &tca6416_platform_data, +}; + +sfi_device(tca6416_dev_id); diff --git a/arch/x86/platform/intel-mid/device_libs/platform_wdt.c b/arch/x86/platform/intel-mid/device_libs/platform_wdt.c new file mode 100644 index 00000000000..973cf3bfa9f --- /dev/null +++ b/arch/x86/platform/intel-mid/device_libs/platform_wdt.c @@ -0,0 +1,72 @@ +/* + * platform_wdt.c: Watchdog platform library file + * + * (C) Copyright 2014 Intel Corporation + * Author: David Cohen <david.a.cohen@linux.intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + */ + +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/platform_data/intel-mid_wdt.h> +#include <asm/intel-mid.h> +#include <asm/io_apic.h> + +#define TANGIER_EXT_TIMER0_MSI 15 + +static struct platform_device wdt_dev = { +	.name = "intel_mid_wdt", +	.id = -1, +}; + +static int tangier_probe(struct platform_device *pdev) +{ +	int ioapic; +	int irq; +	struct intel_mid_wdt_pdata *pdata = pdev->dev.platform_data; +	struct io_apic_irq_attr irq_attr = { 0 }; + +	if (!pdata) +		return -EINVAL; + +	irq = pdata->irq; +	ioapic = mp_find_ioapic(irq); +	if (ioapic >= 0) { +		int ret; +		irq_attr.ioapic = ioapic; +		irq_attr.ioapic_pin = irq; +		irq_attr.trigger = 1; +		/* irq_attr.polarity = 0; -> Active high */ +		ret = io_apic_set_pci_routing(NULL, irq, &irq_attr); +		if (ret) +			return ret; +	} else { +		dev_warn(&pdev->dev, "cannot find interrupt %d in ioapic\n", +			 irq); +		return -EINVAL; +	} + +	return 0; +} + +static struct intel_mid_wdt_pdata tangier_pdata = { +	.irq = TANGIER_EXT_TIMER0_MSI, +	.probe = tangier_probe, +}; + +static int __init register_mid_wdt(void) +{ +	if (intel_mid_identify_cpu() == INTEL_MID_CPU_CHIP_TANGIER) { +		wdt_dev.dev.platform_data = &tangier_pdata; +		return platform_device_register(&wdt_dev); +	} + +	return -ENODEV; +} + +rootfs_initcall(register_mid_wdt); diff --git a/arch/x86/platform/intel-mid/early_printk_intel_mid.c b/arch/x86/platform/intel-mid/early_printk_intel_mid.c new file mode 100644 index 00000000000..e0bd082a80e --- /dev/null +++ b/arch/x86/platform/intel-mid/early_printk_intel_mid.c @@ -0,0 +1,324 @@ +/* + * early_printk_intel_mid.c - early consoles for Intel MID platforms + * + * Copyright (c) 2008-2010, Intel Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + */ + +/* + * This file implements two early consoles named mrst and hsu. + * mrst is based on Maxim3110 spi-uart device, it exists in both + * Moorestown and Medfield platforms, while hsu is based on a High + * Speed UART device which only exists in the Medfield platform + */ + +#include <linux/serial_reg.h> +#include <linux/serial_mfd.h> +#include <linux/kmsg_dump.h> +#include <linux/console.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/io.h> + +#include <asm/fixmap.h> +#include <asm/pgtable.h> +#include <asm/intel-mid.h> + +#define MRST_SPI_TIMEOUT		0x200000 +#define MRST_REGBASE_SPI0		0xff128000 +#define MRST_REGBASE_SPI1		0xff128400 +#define MRST_CLK_SPI0_REG		0xff11d86c + +/* Bit fields in CTRLR0 */ +#define SPI_DFS_OFFSET			0 + +#define SPI_FRF_OFFSET			4 +#define SPI_FRF_SPI			0x0 +#define SPI_FRF_SSP			0x1 +#define SPI_FRF_MICROWIRE		0x2 +#define SPI_FRF_RESV			0x3 + +#define SPI_MODE_OFFSET			6 +#define SPI_SCPH_OFFSET			6 +#define SPI_SCOL_OFFSET			7 +#define SPI_TMOD_OFFSET			8 +#define	SPI_TMOD_TR			0x0		/* xmit & recv */ +#define SPI_TMOD_TO			0x1		/* xmit only */ +#define SPI_TMOD_RO			0x2		/* recv only */ +#define SPI_TMOD_EPROMREAD		0x3		/* eeprom read mode */ + +#define SPI_SLVOE_OFFSET		10 +#define SPI_SRL_OFFSET			11 +#define SPI_CFS_OFFSET			12 + +/* Bit fields in SR, 7 bits */ +#define SR_MASK				0x7f		/* cover 7 bits */ +#define SR_BUSY				(1 << 0) +#define SR_TF_NOT_FULL			(1 << 1) +#define SR_TF_EMPT			(1 << 2) +#define SR_RF_NOT_EMPT			(1 << 3) +#define SR_RF_FULL			(1 << 4) +#define SR_TX_ERR			(1 << 5) +#define SR_DCOL				(1 << 6) + +struct dw_spi_reg { +	u32	ctrl0; +	u32	ctrl1; +	u32	ssienr; +	u32	mwcr; +	u32	ser; +	u32	baudr; +	u32	txfltr; +	u32	rxfltr; +	u32	txflr; +	u32	rxflr; +	u32	sr; +	u32	imr; +	u32	isr; +	u32	risr; +	u32	txoicr; +	u32	rxoicr; +	u32	rxuicr; +	u32	msticr; +	u32	icr; +	u32	dmacr; +	u32	dmatdlr; +	u32	dmardlr; +	u32	idr; +	u32	version; + +	/* Currently operates as 32 bits, though only the low 16 bits matter */ +	u32	dr; +} __packed; + +#define dw_readl(dw, name)		__raw_readl(&(dw)->name) +#define dw_writel(dw, name, val)	__raw_writel((val), &(dw)->name) + +/* Default use SPI0 register for mrst, we will detect Penwell and use SPI1 */ +static unsigned long mrst_spi_paddr = MRST_REGBASE_SPI0; + +static u32 *pclk_spi0; +/* Always contains an accessible address, start with 0 */ +static struct dw_spi_reg *pspi; + +static struct kmsg_dumper dw_dumper; +static int dumper_registered; + +static void dw_kmsg_dump(struct kmsg_dumper *dumper, +			 enum kmsg_dump_reason reason) +{ +	static char line[1024]; +	size_t len; + +	/* When run to this, we'd better re-init the HW */ +	mrst_early_console_init(); + +	while (kmsg_dump_get_line(dumper, true, line, sizeof(line), &len)) +		early_mrst_console.write(&early_mrst_console, line, len); +} + +/* Set the ratio rate to 115200, 8n1, IRQ disabled */ +static void max3110_write_config(void) +{ +	u16 config; + +	config = 0xc001; +	dw_writel(pspi, dr, config); +} + +/* Translate char to a eligible word and send to max3110 */ +static void max3110_write_data(char c) +{ +	u16 data; + +	data = 0x8000 | c; +	dw_writel(pspi, dr, data); +} + +void mrst_early_console_init(void) +{ +	u32 ctrlr0 = 0; +	u32 spi0_cdiv; +	u32 freq; /* Freqency info only need be searched once */ + +	/* Base clk is 100 MHz, the actual clk = 100M / (clk_divider + 1) */ +	pclk_spi0 = (void *)set_fixmap_offset_nocache(FIX_EARLYCON_MEM_BASE, +							MRST_CLK_SPI0_REG); +	spi0_cdiv = ((*pclk_spi0) & 0xe00) >> 9; +	freq = 100000000 / (spi0_cdiv + 1); + +	if (intel_mid_identify_cpu() == INTEL_MID_CPU_CHIP_PENWELL) +		mrst_spi_paddr = MRST_REGBASE_SPI1; + +	pspi = (void *)set_fixmap_offset_nocache(FIX_EARLYCON_MEM_BASE, +						mrst_spi_paddr); + +	/* Disable SPI controller */ +	dw_writel(pspi, ssienr, 0); + +	/* Set control param, 8 bits, transmit only mode */ +	ctrlr0 = dw_readl(pspi, ctrl0); + +	ctrlr0 &= 0xfcc0; +	ctrlr0 |= 0xf | (SPI_FRF_SPI << SPI_FRF_OFFSET) +		      | (SPI_TMOD_TO << SPI_TMOD_OFFSET); +	dw_writel(pspi, ctrl0, ctrlr0); + +	/* +	 * Change the spi0 clk to comply with 115200 bps, use 100000 to +	 * calculate the clk dividor to make the clock a little slower +	 * than real baud rate. +	 */ +	dw_writel(pspi, baudr, freq/100000); + +	/* Disable all INT for early phase */ +	dw_writel(pspi, imr, 0x0); + +	/* Set the cs to spi-uart */ +	dw_writel(pspi, ser, 0x2); + +	/* Enable the HW, the last step for HW init */ +	dw_writel(pspi, ssienr, 0x1); + +	/* Set the default configuration */ +	max3110_write_config(); + +	/* Register the kmsg dumper */ +	if (!dumper_registered) { +		dw_dumper.dump = dw_kmsg_dump; +		kmsg_dump_register(&dw_dumper); +		dumper_registered = 1; +	} +} + +/* Slave select should be called in the read/write function */ +static void early_mrst_spi_putc(char c) +{ +	unsigned int timeout; +	u32 sr; + +	timeout = MRST_SPI_TIMEOUT; +	/* Early putc needs to make sure the TX FIFO is not full */ +	while (--timeout) { +		sr = dw_readl(pspi, sr); +		if (!(sr & SR_TF_NOT_FULL)) +			cpu_relax(); +		else +			break; +	} + +	if (!timeout) +		pr_warn("MRST earlycon: timed out\n"); +	else +		max3110_write_data(c); +} + +/* Early SPI only uses polling mode */ +static void early_mrst_spi_write(struct console *con, const char *str, +					unsigned n) +{ +	int i; + +	for (i = 0; i < n && *str; i++) { +		if (*str == '\n') +			early_mrst_spi_putc('\r'); +		early_mrst_spi_putc(*str); +		str++; +	} +} + +struct console early_mrst_console = { +	.name =		"earlymrst", +	.write =	early_mrst_spi_write, +	.flags =	CON_PRINTBUFFER, +	.index =	-1, +}; + +/* + * Following is the early console based on Medfield HSU (High + * Speed UART) device. + */ +#define HSU_PORT_BASE		0xffa28080 + +static void __iomem *phsu; + +void hsu_early_console_init(const char *s) +{ +	unsigned long paddr, port = 0; +	u8 lcr; + +	/* +	 * Select the early HSU console port if specified by user in the +	 * kernel command line. +	 */ +	if (*s && !kstrtoul(s, 10, &port)) +		port = clamp_val(port, 0, 2); + +	paddr = HSU_PORT_BASE + port * 0x80; +	phsu = (void *)set_fixmap_offset_nocache(FIX_EARLYCON_MEM_BASE, paddr); + +	/* Disable FIFO */ +	writeb(0x0, phsu + UART_FCR); + +	/* Set to default 115200 bps, 8n1 */ +	lcr = readb(phsu + UART_LCR); +	writeb((0x80 | lcr), phsu + UART_LCR); +	writeb(0x18, phsu + UART_DLL); +	writeb(lcr,  phsu + UART_LCR); +	writel(0x3600, phsu + UART_MUL*4); + +	writeb(0x8, phsu + UART_MCR); +	writeb(0x7, phsu + UART_FCR); +	writeb(0x3, phsu + UART_LCR); + +	/* Clear IRQ status */ +	readb(phsu + UART_LSR); +	readb(phsu + UART_RX); +	readb(phsu + UART_IIR); +	readb(phsu + UART_MSR); + +	/* Enable FIFO */ +	writeb(0x7, phsu + UART_FCR); +} + +#define BOTH_EMPTY (UART_LSR_TEMT | UART_LSR_THRE) + +static void early_hsu_putc(char ch) +{ +	unsigned int timeout = 10000; /* 10ms */ +	u8 status; + +	while (--timeout) { +		status = readb(phsu + UART_LSR); +		if (status & BOTH_EMPTY) +			break; +		udelay(1); +	} + +	/* Only write the char when there was no timeout */ +	if (timeout) +		writeb(ch, phsu + UART_TX); +} + +static void early_hsu_write(struct console *con, const char *str, unsigned n) +{ +	int i; + +	for (i = 0; i < n && *str; i++) { +		if (*str == '\n') +			early_hsu_putc('\r'); +		early_hsu_putc(*str); +		str++; +	} +} + +struct console early_hsu_console = { +	.name =		"earlyhsu", +	.write =	early_hsu_write, +	.flags =	CON_PRINTBUFFER, +	.index =	-1, +}; diff --git a/arch/x86/platform/intel-mid/intel-mid.c b/arch/x86/platform/intel-mid/intel-mid.c new file mode 100644 index 00000000000..1bbedc4b0f8 --- /dev/null +++ b/arch/x86/platform/intel-mid/intel-mid.c @@ -0,0 +1,217 @@ +/* + * intel-mid.c: Intel MID platform setup code + * + * (C) Copyright 2008, 2012 Intel Corporation + * Author: Jacob Pan (jacob.jun.pan@intel.com) + * Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + */ + +#define pr_fmt(fmt) "intel_mid: " fmt + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/scatterlist.h> +#include <linux/sfi.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/notifier.h> + +#include <asm/setup.h> +#include <asm/mpspec_def.h> +#include <asm/hw_irq.h> +#include <asm/apic.h> +#include <asm/io_apic.h> +#include <asm/intel-mid.h> +#include <asm/intel_mid_vrtc.h> +#include <asm/io.h> +#include <asm/i8259.h> +#include <asm/intel_scu_ipc.h> +#include <asm/apb_timer.h> +#include <asm/reboot.h> + +#include "intel_mid_weak_decls.h" + +/* + * the clockevent devices on Moorestown/Medfield can be APBT or LAPIC clock, + * cmdline option x86_intel_mid_timer can be used to override the configuration + * to prefer one or the other. + * at runtime, there are basically three timer configurations: + * 1. per cpu apbt clock only + * 2. per cpu always-on lapic clocks only, this is Penwell/Medfield only + * 3. per cpu lapic clock (C3STOP) and one apbt clock, with broadcast. + * + * by default (without cmdline option), platform code first detects cpu type + * to see if we are on lincroft or penwell, then set up both lapic or apbt + * clocks accordingly. + * i.e. by default, medfield uses configuration #2, moorestown uses #1. + * config #3 is supported but not recommended on medfield. + * + * rating and feature summary: + * lapic (with C3STOP) --------- 100 + * apbt (always-on) ------------ 110 + * lapic (always-on,ARAT) ------ 150 + */ + +enum intel_mid_timer_options intel_mid_timer_options; + +/* intel_mid_ops to store sub arch ops */ +struct intel_mid_ops *intel_mid_ops; +/* getter function for sub arch ops*/ +static void *(*get_intel_mid_ops[])(void) = INTEL_MID_OPS_INIT; +enum intel_mid_cpu_type __intel_mid_cpu_chip; +EXPORT_SYMBOL_GPL(__intel_mid_cpu_chip); + +static void intel_mid_power_off(void) +{ +}; + +static void intel_mid_reboot(void) +{ +	intel_scu_ipc_simple_command(IPCMSG_COLD_BOOT, 0); +} + +static unsigned long __init intel_mid_calibrate_tsc(void) +{ +	return 0; +} + +static void __init intel_mid_time_init(void) +{ +	sfi_table_parse(SFI_SIG_MTMR, NULL, NULL, sfi_parse_mtmr); +	switch (intel_mid_timer_options) { +	case INTEL_MID_TIMER_APBT_ONLY: +		break; +	case INTEL_MID_TIMER_LAPIC_APBT: +		x86_init.timers.setup_percpu_clockev = setup_boot_APIC_clock; +		x86_cpuinit.setup_percpu_clockev = setup_secondary_APIC_clock; +		break; +	default: +		if (!boot_cpu_has(X86_FEATURE_ARAT)) +			break; +		x86_init.timers.setup_percpu_clockev = setup_boot_APIC_clock; +		x86_cpuinit.setup_percpu_clockev = setup_secondary_APIC_clock; +		return; +	} +	/* we need at least one APB timer */ +	pre_init_apic_IRQ0(); +	apbt_time_init(); +} + +static void intel_mid_arch_setup(void) +{ +	if (boot_cpu_data.x86 != 6) { +		pr_err("Unknown Intel MID CPU (%d:%d), default to Penwell\n", +			boot_cpu_data.x86, boot_cpu_data.x86_model); +		__intel_mid_cpu_chip = INTEL_MID_CPU_CHIP_PENWELL; +		goto out; +	} + +	switch (boot_cpu_data.x86_model) { +	case 0x35: +		__intel_mid_cpu_chip = INTEL_MID_CPU_CHIP_CLOVERVIEW; +		break; +	case 0x3C: +	case 0x4A: +		__intel_mid_cpu_chip = INTEL_MID_CPU_CHIP_TANGIER; +		break; +	case 0x27: +	default: +		__intel_mid_cpu_chip = INTEL_MID_CPU_CHIP_PENWELL; +		break; +	} + +	if (__intel_mid_cpu_chip < MAX_CPU_OPS(get_intel_mid_ops)) +		intel_mid_ops = get_intel_mid_ops[__intel_mid_cpu_chip](); +	else { +		intel_mid_ops = get_intel_mid_ops[INTEL_MID_CPU_CHIP_PENWELL](); +		pr_info("ARCH: Uknown SoC, assuming PENWELL!\n"); +	} + +out: +	if (intel_mid_ops->arch_setup) +		intel_mid_ops->arch_setup(); +} + +/* MID systems don't have i8042 controller */ +static int intel_mid_i8042_detect(void) +{ +	return 0; +} + +/* + * Moorestown does not have external NMI source nor port 0x61 to report + * NMI status. The possible NMI sources are from pmu as a result of NMI + * watchdog or lock debug. Reading io port 0x61 results in 0xff which + * misled NMI handler. + */ +static unsigned char intel_mid_get_nmi_reason(void) +{ +	return 0; +} + +/* + * Moorestown specific x86_init function overrides and early setup + * calls. + */ +void __init x86_intel_mid_early_setup(void) +{ +	x86_init.resources.probe_roms = x86_init_noop; +	x86_init.resources.reserve_resources = x86_init_noop; + +	x86_init.timers.timer_init = intel_mid_time_init; +	x86_init.timers.setup_percpu_clockev = x86_init_noop; + +	x86_init.irqs.pre_vector_init = x86_init_noop; + +	x86_init.oem.arch_setup = intel_mid_arch_setup; + +	x86_cpuinit.setup_percpu_clockev = apbt_setup_secondary_clock; + +	x86_platform.calibrate_tsc = intel_mid_calibrate_tsc; +	x86_platform.i8042_detect = intel_mid_i8042_detect; +	x86_init.timers.wallclock_init = intel_mid_rtc_init; +	x86_platform.get_nmi_reason = intel_mid_get_nmi_reason; + +	x86_init.pci.init = intel_mid_pci_init; +	x86_init.pci.fixup_irqs = x86_init_noop; + +	legacy_pic = &null_legacy_pic; + +	pm_power_off = intel_mid_power_off; +	machine_ops.emergency_restart  = intel_mid_reboot; + +	/* Avoid searching for BIOS MP tables */ +	x86_init.mpparse.find_smp_config = x86_init_noop; +	x86_init.mpparse.get_smp_config = x86_init_uint_noop; +	set_bit(MP_BUS_ISA, mp_bus_not_pci); +} + +/* + * if user does not want to use per CPU apb timer, just give it a lower rating + * than local apic timer and skip the late per cpu timer init. + */ +static inline int __init setup_x86_intel_mid_timer(char *arg) +{ +	if (!arg) +		return -EINVAL; + +	if (strcmp("apbt_only", arg) == 0) +		intel_mid_timer_options = INTEL_MID_TIMER_APBT_ONLY; +	else if (strcmp("lapic_and_apbt", arg) == 0) +		intel_mid_timer_options = INTEL_MID_TIMER_LAPIC_APBT; +	else { +		pr_warn("X86 INTEL_MID timer option %s not recognised" +			   " use x86_intel_mid_timer=apbt_only or lapic_and_apbt\n", +			   arg); +		return -EINVAL; +	} +	return 0; +} +__setup("x86_intel_mid_timer=", setup_x86_intel_mid_timer); + diff --git a/arch/x86/platform/intel-mid/intel_mid_vrtc.c b/arch/x86/platform/intel-mid/intel_mid_vrtc.c new file mode 100644 index 00000000000..4762cff7fac --- /dev/null +++ b/arch/x86/platform/intel-mid/intel_mid_vrtc.c @@ -0,0 +1,177 @@ +/* + * intel_mid_vrtc.c: Driver for virtual RTC device on Intel MID platform + * + * (C) Copyright 2009 Intel Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + * + * Note: + * VRTC is emulated by system controller firmware, the real HW + * RTC is located in the PMIC device. SCU FW shadows PMIC RTC + * in a memory mapped IO space that is visible to the host IA + * processor. + * + * This driver is based on RTC CMOS driver. + */ + +#include <linux/kernel.h> +#include <linux/export.h> +#include <linux/init.h> +#include <linux/sfi.h> +#include <linux/platform_device.h> + +#include <asm/intel-mid.h> +#include <asm/intel_mid_vrtc.h> +#include <asm/time.h> +#include <asm/fixmap.h> + +static unsigned char __iomem *vrtc_virt_base; + +unsigned char vrtc_cmos_read(unsigned char reg) +{ +	unsigned char retval; + +	/* vRTC's registers range from 0x0 to 0xD */ +	if (reg > 0xd || !vrtc_virt_base) +		return 0xff; + +	lock_cmos_prefix(reg); +	retval = __raw_readb(vrtc_virt_base + (reg << 2)); +	lock_cmos_suffix(reg); +	return retval; +} +EXPORT_SYMBOL_GPL(vrtc_cmos_read); + +void vrtc_cmos_write(unsigned char val, unsigned char reg) +{ +	if (reg > 0xd || !vrtc_virt_base) +		return; + +	lock_cmos_prefix(reg); +	__raw_writeb(val, vrtc_virt_base + (reg << 2)); +	lock_cmos_suffix(reg); +} +EXPORT_SYMBOL_GPL(vrtc_cmos_write); + +void vrtc_get_time(struct timespec *now) +{ +	u8 sec, min, hour, mday, mon; +	unsigned long flags; +	u32 year; + +	spin_lock_irqsave(&rtc_lock, flags); + +	while ((vrtc_cmos_read(RTC_FREQ_SELECT) & RTC_UIP)) +		cpu_relax(); + +	sec = vrtc_cmos_read(RTC_SECONDS); +	min = vrtc_cmos_read(RTC_MINUTES); +	hour = vrtc_cmos_read(RTC_HOURS); +	mday = vrtc_cmos_read(RTC_DAY_OF_MONTH); +	mon = vrtc_cmos_read(RTC_MONTH); +	year = vrtc_cmos_read(RTC_YEAR); + +	spin_unlock_irqrestore(&rtc_lock, flags); + +	/* vRTC YEAR reg contains the offset to 1972 */ +	year += 1972; + +	pr_info("vRTC: sec: %d min: %d hour: %d day: %d " +		"mon: %d year: %d\n", sec, min, hour, mday, mon, year); + +	now->tv_sec = mktime(year, mon, mday, hour, min, sec); +	now->tv_nsec = 0; +} + +int vrtc_set_mmss(const struct timespec *now) +{ +	unsigned long flags; +	struct rtc_time tm; +	int year; +	int retval = 0; + +	rtc_time_to_tm(now->tv_sec, &tm); +	if (!rtc_valid_tm(&tm) && tm.tm_year >= 72) { +		/* +		 * tm.year is the number of years since 1900, and the +		 * vrtc need the years since 1972. +		 */ +		year = tm.tm_year - 72; +		spin_lock_irqsave(&rtc_lock, flags); +		vrtc_cmos_write(year, RTC_YEAR); +		vrtc_cmos_write(tm.tm_mon, RTC_MONTH); +		vrtc_cmos_write(tm.tm_mday, RTC_DAY_OF_MONTH); +		vrtc_cmos_write(tm.tm_hour, RTC_HOURS); +		vrtc_cmos_write(tm.tm_min, RTC_MINUTES); +		vrtc_cmos_write(tm.tm_sec, RTC_SECONDS); +		spin_unlock_irqrestore(&rtc_lock, flags); +	} else { +		pr_err("%s: Invalid vRTC value: write of %lx to vRTC failed\n", +			__FUNCTION__, now->tv_sec); +		retval = -EINVAL; +	} +	return retval; +} + +void __init intel_mid_rtc_init(void) +{ +	unsigned long vrtc_paddr; + +	sfi_table_parse(SFI_SIG_MRTC, NULL, NULL, sfi_parse_mrtc); + +	vrtc_paddr = sfi_mrtc_array[0].phys_addr; +	if (!sfi_mrtc_num || !vrtc_paddr) +		return; + +	vrtc_virt_base = (void __iomem *)set_fixmap_offset_nocache(FIX_LNW_VRTC, +								vrtc_paddr); +	x86_platform.get_wallclock = vrtc_get_time; +	x86_platform.set_wallclock = vrtc_set_mmss; +} + +/* + * The Moorestown platform has a memory mapped virtual RTC device that emulates + * the programming interface of the RTC. + */ + +static struct resource vrtc_resources[] = { +	[0] = { +		.flags	= IORESOURCE_MEM, +	}, +	[1] = { +		.flags	= IORESOURCE_IRQ, +	} +}; + +static struct platform_device vrtc_device = { +	.name		= "rtc_mrst", +	.id		= -1, +	.resource	= vrtc_resources, +	.num_resources	= ARRAY_SIZE(vrtc_resources), +}; + +/* Register the RTC device if appropriate */ +static int __init intel_mid_device_create(void) +{ +	/* No Moorestown, no device */ +	if (!intel_mid_identify_cpu()) +		return -ENODEV; +	/* No timer, no device */ +	if (!sfi_mrtc_num) +		return -ENODEV; + +	/* iomem resource */ +	vrtc_resources[0].start = sfi_mrtc_array[0].phys_addr; +	vrtc_resources[0].end = sfi_mrtc_array[0].phys_addr + +				MRST_VRTC_MAP_SZ; +	/* irq resource */ +	vrtc_resources[1].start = sfi_mrtc_array[0].irq; +	vrtc_resources[1].end = sfi_mrtc_array[0].irq; + +	return platform_device_register(&vrtc_device); +} + +module_init(intel_mid_device_create); diff --git a/arch/x86/platform/intel-mid/intel_mid_weak_decls.h b/arch/x86/platform/intel-mid/intel_mid_weak_decls.h new file mode 100644 index 00000000000..46aa25c8ce0 --- /dev/null +++ b/arch/x86/platform/intel-mid/intel_mid_weak_decls.h @@ -0,0 +1,19 @@ +/* + * intel_mid_weak_decls.h: Weak declarations of intel-mid.c + * + * (C) Copyright 2013 Intel Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + */ + + +/* __attribute__((weak)) makes these declarations overridable */ +/* For every CPU addition a new get_<cpuname>_ops interface needs + * to be added. + */ +extern void *get_penwell_ops(void) __attribute__((weak)); +extern void *get_cloverview_ops(void) __attribute__((weak)); +extern void *get_tangier_ops(void) __attribute__((weak)); diff --git a/arch/x86/platform/intel-mid/mfld.c b/arch/x86/platform/intel-mid/mfld.c new file mode 100644 index 00000000000..23381d2174a --- /dev/null +++ b/arch/x86/platform/intel-mid/mfld.c @@ -0,0 +1,75 @@ +/* + * mfld.c: Intel Medfield platform setup code + * + * (C) Copyright 2013 Intel Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + */ + +#include <linux/init.h> + +#include <asm/apic.h> +#include <asm/intel-mid.h> +#include <asm/intel_mid_vrtc.h> + +#include "intel_mid_weak_decls.h" + +static void penwell_arch_setup(void); +/* penwell arch ops */ +static struct intel_mid_ops penwell_ops = { +	.arch_setup = penwell_arch_setup, +}; + +static void mfld_power_off(void) +{ +} + +static unsigned long __init mfld_calibrate_tsc(void) +{ +	unsigned long fast_calibrate; +	u32 lo, hi, ratio, fsb; + +	rdmsr(MSR_IA32_PERF_STATUS, lo, hi); +	pr_debug("IA32 perf status is 0x%x, 0x%0x\n", lo, hi); +	ratio = (hi >> 8) & 0x1f; +	pr_debug("ratio is %d\n", ratio); +	if (!ratio) { +		pr_err("read a zero ratio, should be incorrect!\n"); +		pr_err("force tsc ratio to 16 ...\n"); +		ratio = 16; +	} +	rdmsr(MSR_FSB_FREQ, lo, hi); +	if ((lo & 0x7) == 0x7) +		fsb = FSB_FREQ_83SKU; +	else +		fsb = FSB_FREQ_100SKU; +	fast_calibrate = ratio * fsb; +	pr_debug("read penwell tsc %lu khz\n", fast_calibrate); +	lapic_timer_frequency = fsb * 1000 / HZ; +	/* mark tsc clocksource as reliable */ +	set_cpu_cap(&boot_cpu_data, X86_FEATURE_TSC_RELIABLE); + +	if (fast_calibrate) +		return fast_calibrate; + +	return 0; +} + +static void __init penwell_arch_setup(void) +{ +	x86_platform.calibrate_tsc = mfld_calibrate_tsc; +	pm_power_off = mfld_power_off; +} + +void *get_penwell_ops(void) +{ +	return &penwell_ops; +} + +void *get_cloverview_ops(void) +{ +	return &penwell_ops; +} diff --git a/arch/x86/platform/intel-mid/mrfl.c b/arch/x86/platform/intel-mid/mrfl.c new file mode 100644 index 00000000000..aaca91753d3 --- /dev/null +++ b/arch/x86/platform/intel-mid/mrfl.c @@ -0,0 +1,103 @@ +/* + * mrfl.c: Intel Merrifield platform specific setup code + * + * (C) Copyright 2013 Intel Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + */ + +#include <linux/init.h> + +#include <asm/apic.h> +#include <asm/intel-mid.h> + +#include "intel_mid_weak_decls.h" + +static unsigned long __init tangier_calibrate_tsc(void) +{ +	unsigned long fast_calibrate; +	u32 lo, hi, ratio, fsb, bus_freq; + +	/* *********************** */ +	/* Compute TSC:Ratio * FSB */ +	/* *********************** */ + +	/* Compute Ratio */ +	rdmsr(MSR_PLATFORM_INFO, lo, hi); +	pr_debug("IA32 PLATFORM_INFO is 0x%x : %x\n", hi, lo); + +	ratio = (lo >> 8) & 0xFF; +	pr_debug("ratio is %d\n", ratio); +	if (!ratio) { +		pr_err("Read a zero ratio, force tsc ratio to 4 ...\n"); +		ratio = 4; +	} + +	/* Compute FSB */ +	rdmsr(MSR_FSB_FREQ, lo, hi); +	pr_debug("Actual FSB frequency detected by SOC 0x%x : %x\n", +			hi, lo); + +	bus_freq = lo & 0x7; +	pr_debug("bus_freq = 0x%x\n", bus_freq); + +	if (bus_freq == 0) +		fsb = FSB_FREQ_100SKU; +	else if (bus_freq == 1) +		fsb = FSB_FREQ_100SKU; +	else if (bus_freq == 2) +		fsb = FSB_FREQ_133SKU; +	else if (bus_freq == 3) +		fsb = FSB_FREQ_167SKU; +	else if (bus_freq == 4) +		fsb = FSB_FREQ_83SKU; +	else if (bus_freq == 5) +		fsb = FSB_FREQ_400SKU; +	else if (bus_freq == 6) +		fsb = FSB_FREQ_267SKU; +	else if (bus_freq == 7) +		fsb = FSB_FREQ_333SKU; +	else { +		BUG(); +		pr_err("Invalid bus_freq! Setting to minimal value!\n"); +		fsb = FSB_FREQ_100SKU; +	} + +	/* TSC = FSB Freq * Resolved HFM Ratio */ +	fast_calibrate = ratio * fsb; +	pr_debug("calculate tangier tsc %lu KHz\n", fast_calibrate); + +	/* ************************************ */ +	/* Calculate Local APIC Timer Frequency */ +	/* ************************************ */ +	lapic_timer_frequency = (fsb * 1000) / HZ; + +	pr_debug("Setting lapic_timer_frequency = %d\n", +			lapic_timer_frequency); + +	/* mark tsc clocksource as reliable */ +	set_cpu_cap(&boot_cpu_data, X86_FEATURE_TSC_RELIABLE); + +	if (fast_calibrate) +		return fast_calibrate; + +	return 0; +} + +static void __init tangier_arch_setup(void) +{ +	x86_platform.calibrate_tsc = tangier_calibrate_tsc; +} + +/* tangier arch ops */ +static struct intel_mid_ops tangier_ops = { +	.arch_setup = tangier_arch_setup, +}; + +void *get_tangier_ops(void) +{ +	return &tangier_ops; +} diff --git a/arch/x86/platform/intel-mid/sfi.c b/arch/x86/platform/intel-mid/sfi.c new file mode 100644 index 00000000000..994c40bd7cb --- /dev/null +++ b/arch/x86/platform/intel-mid/sfi.c @@ -0,0 +1,516 @@ +/* + * intel_mid_sfi.c: Intel MID SFI initialization code + * + * (C) Copyright 2013 Intel Corporation + * Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/scatterlist.h> +#include <linux/sfi.h> +#include <linux/intel_pmic_gpio.h> +#include <linux/spi/spi.h> +#include <linux/i2c.h> +#include <linux/skbuff.h> +#include <linux/gpio.h> +#include <linux/gpio_keys.h> +#include <linux/input.h> +#include <linux/platform_device.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/notifier.h> +#include <linux/mmc/core.h> +#include <linux/mmc/card.h> +#include <linux/blkdev.h> + +#include <asm/setup.h> +#include <asm/mpspec_def.h> +#include <asm/hw_irq.h> +#include <asm/apic.h> +#include <asm/io_apic.h> +#include <asm/intel-mid.h> +#include <asm/intel_mid_vrtc.h> +#include <asm/io.h> +#include <asm/i8259.h> +#include <asm/intel_scu_ipc.h> +#include <asm/apb_timer.h> +#include <asm/reboot.h> + +#define	SFI_SIG_OEM0	"OEM0" +#define MAX_IPCDEVS	24 +#define MAX_SCU_SPI	24 +#define MAX_SCU_I2C	24 + +static struct platform_device *ipc_devs[MAX_IPCDEVS]; +static struct spi_board_info *spi_devs[MAX_SCU_SPI]; +static struct i2c_board_info *i2c_devs[MAX_SCU_I2C]; +static struct sfi_gpio_table_entry *gpio_table; +static struct sfi_timer_table_entry sfi_mtimer_array[SFI_MTMR_MAX_NUM]; +static int ipc_next_dev; +static int spi_next_dev; +static int i2c_next_dev; +static int i2c_bus[MAX_SCU_I2C]; +static int gpio_num_entry; +static u32 sfi_mtimer_usage[SFI_MTMR_MAX_NUM]; +int sfi_mrtc_num; +int sfi_mtimer_num; + +struct sfi_rtc_table_entry sfi_mrtc_array[SFI_MRTC_MAX]; +EXPORT_SYMBOL_GPL(sfi_mrtc_array); + +struct blocking_notifier_head intel_scu_notifier = +			BLOCKING_NOTIFIER_INIT(intel_scu_notifier); +EXPORT_SYMBOL_GPL(intel_scu_notifier); + +#define intel_mid_sfi_get_pdata(dev, priv)	\ +	((dev)->get_platform_data ? (dev)->get_platform_data(priv) : NULL) + +/* parse all the mtimer info to a static mtimer array */ +int __init sfi_parse_mtmr(struct sfi_table_header *table) +{ +	struct sfi_table_simple *sb; +	struct sfi_timer_table_entry *pentry; +	struct mpc_intsrc mp_irq; +	int totallen; + +	sb = (struct sfi_table_simple *)table; +	if (!sfi_mtimer_num) { +		sfi_mtimer_num = SFI_GET_NUM_ENTRIES(sb, +					struct sfi_timer_table_entry); +		pentry = (struct sfi_timer_table_entry *) sb->pentry; +		totallen = sfi_mtimer_num * sizeof(*pentry); +		memcpy(sfi_mtimer_array, pentry, totallen); +	} + +	pr_debug("SFI MTIMER info (num = %d):\n", sfi_mtimer_num); +	pentry = sfi_mtimer_array; +	for (totallen = 0; totallen < sfi_mtimer_num; totallen++, pentry++) { +		pr_debug("timer[%d]: paddr = 0x%08x, freq = %dHz, irq = %d\n", +			totallen, (u32)pentry->phys_addr, +			pentry->freq_hz, pentry->irq); +			if (!pentry->irq) +				continue; +			mp_irq.type = MP_INTSRC; +			mp_irq.irqtype = mp_INT; +/* triggering mode edge bit 2-3, active high polarity bit 0-1 */ +			mp_irq.irqflag = 5; +			mp_irq.srcbus = MP_BUS_ISA; +			mp_irq.srcbusirq = pentry->irq;	/* IRQ */ +			mp_irq.dstapic = MP_APIC_ALL; +			mp_irq.dstirq = pentry->irq; +			mp_save_irq(&mp_irq); +	} + +	return 0; +} + +struct sfi_timer_table_entry *sfi_get_mtmr(int hint) +{ +	int i; +	if (hint < sfi_mtimer_num) { +		if (!sfi_mtimer_usage[hint]) { +			pr_debug("hint taken for timer %d irq %d\n", +				hint, sfi_mtimer_array[hint].irq); +			sfi_mtimer_usage[hint] = 1; +			return &sfi_mtimer_array[hint]; +		} +	} +	/* take the first timer available */ +	for (i = 0; i < sfi_mtimer_num;) { +		if (!sfi_mtimer_usage[i]) { +			sfi_mtimer_usage[i] = 1; +			return &sfi_mtimer_array[i]; +		} +		i++; +	} +	return NULL; +} + +void sfi_free_mtmr(struct sfi_timer_table_entry *mtmr) +{ +	int i; +	for (i = 0; i < sfi_mtimer_num;) { +		if (mtmr->irq == sfi_mtimer_array[i].irq) { +			sfi_mtimer_usage[i] = 0; +			return; +		} +		i++; +	} +} + +/* parse all the mrtc info to a global mrtc array */ +int __init sfi_parse_mrtc(struct sfi_table_header *table) +{ +	struct sfi_table_simple *sb; +	struct sfi_rtc_table_entry *pentry; +	struct mpc_intsrc mp_irq; + +	int totallen; + +	sb = (struct sfi_table_simple *)table; +	if (!sfi_mrtc_num) { +		sfi_mrtc_num = SFI_GET_NUM_ENTRIES(sb, +						struct sfi_rtc_table_entry); +		pentry = (struct sfi_rtc_table_entry *)sb->pentry; +		totallen = sfi_mrtc_num * sizeof(*pentry); +		memcpy(sfi_mrtc_array, pentry, totallen); +	} + +	pr_debug("SFI RTC info (num = %d):\n", sfi_mrtc_num); +	pentry = sfi_mrtc_array; +	for (totallen = 0; totallen < sfi_mrtc_num; totallen++, pentry++) { +		pr_debug("RTC[%d]: paddr = 0x%08x, irq = %d\n", +			totallen, (u32)pentry->phys_addr, pentry->irq); +		mp_irq.type = MP_INTSRC; +		mp_irq.irqtype = mp_INT; +		mp_irq.irqflag = 0xf;	/* level trigger and active low */ +		mp_irq.srcbus = MP_BUS_ISA; +		mp_irq.srcbusirq = pentry->irq;	/* IRQ */ +		mp_irq.dstapic = MP_APIC_ALL; +		mp_irq.dstirq = pentry->irq; +		mp_save_irq(&mp_irq); +	} +	return 0; +} + + +/* + * Parsing GPIO table first, since the DEVS table will need this table + * to map the pin name to the actual pin. + */ +static int __init sfi_parse_gpio(struct sfi_table_header *table) +{ +	struct sfi_table_simple *sb; +	struct sfi_gpio_table_entry *pentry; +	int num, i; + +	if (gpio_table) +		return 0; +	sb = (struct sfi_table_simple *)table; +	num = SFI_GET_NUM_ENTRIES(sb, struct sfi_gpio_table_entry); +	pentry = (struct sfi_gpio_table_entry *)sb->pentry; + +	gpio_table = kmalloc(num * sizeof(*pentry), GFP_KERNEL); +	if (!gpio_table) +		return -1; +	memcpy(gpio_table, pentry, num * sizeof(*pentry)); +	gpio_num_entry = num; + +	pr_debug("GPIO pin info:\n"); +	for (i = 0; i < num; i++, pentry++) +		pr_debug("info[%2d]: controller = %16.16s, pin_name = %16.16s," +		" pin = %d\n", i, +			pentry->controller_name, +			pentry->pin_name, +			pentry->pin_no); +	return 0; +} + +int get_gpio_by_name(const char *name) +{ +	struct sfi_gpio_table_entry *pentry = gpio_table; +	int i; + +	if (!pentry) +		return -1; +	for (i = 0; i < gpio_num_entry; i++, pentry++) { +		if (!strncmp(name, pentry->pin_name, SFI_NAME_LEN)) +			return pentry->pin_no; +	} +	return -EINVAL; +} + +void __init intel_scu_device_register(struct platform_device *pdev) +{ +	if (ipc_next_dev == MAX_IPCDEVS) +		pr_err("too many SCU IPC devices"); +	else +		ipc_devs[ipc_next_dev++] = pdev; +} + +static void __init intel_scu_spi_device_register(struct spi_board_info *sdev) +{ +	struct spi_board_info *new_dev; + +	if (spi_next_dev == MAX_SCU_SPI) { +		pr_err("too many SCU SPI devices"); +		return; +	} + +	new_dev = kzalloc(sizeof(*sdev), GFP_KERNEL); +	if (!new_dev) { +		pr_err("failed to alloc mem for delayed spi dev %s\n", +			sdev->modalias); +		return; +	} +	*new_dev = *sdev; + +	spi_devs[spi_next_dev++] = new_dev; +} + +static void __init intel_scu_i2c_device_register(int bus, +						struct i2c_board_info *idev) +{ +	struct i2c_board_info *new_dev; + +	if (i2c_next_dev == MAX_SCU_I2C) { +		pr_err("too many SCU I2C devices"); +		return; +	} + +	new_dev = kzalloc(sizeof(*idev), GFP_KERNEL); +	if (!new_dev) { +		pr_err("failed to alloc mem for delayed i2c dev %s\n", +			idev->type); +		return; +	} +	*new_dev = *idev; + +	i2c_bus[i2c_next_dev] = bus; +	i2c_devs[i2c_next_dev++] = new_dev; +} + +/* Called by IPC driver */ +void intel_scu_devices_create(void) +{ +	int i; + +	for (i = 0; i < ipc_next_dev; i++) +		platform_device_add(ipc_devs[i]); + +	for (i = 0; i < spi_next_dev; i++) +		spi_register_board_info(spi_devs[i], 1); + +	for (i = 0; i < i2c_next_dev; i++) { +		struct i2c_adapter *adapter; +		struct i2c_client *client; + +		adapter = i2c_get_adapter(i2c_bus[i]); +		if (adapter) { +			client = i2c_new_device(adapter, i2c_devs[i]); +			if (!client) +				pr_err("can't create i2c device %s\n", +					i2c_devs[i]->type); +		} else +			i2c_register_board_info(i2c_bus[i], i2c_devs[i], 1); +	} +	intel_scu_notifier_post(SCU_AVAILABLE, NULL); +} +EXPORT_SYMBOL_GPL(intel_scu_devices_create); + +/* Called by IPC driver */ +void intel_scu_devices_destroy(void) +{ +	int i; + +	intel_scu_notifier_post(SCU_DOWN, NULL); + +	for (i = 0; i < ipc_next_dev; i++) +		platform_device_del(ipc_devs[i]); +} +EXPORT_SYMBOL_GPL(intel_scu_devices_destroy); + +static void __init install_irq_resource(struct platform_device *pdev, int irq) +{ +	/* Single threaded */ +	static struct resource res __initdata = { +		.name = "IRQ", +		.flags = IORESOURCE_IRQ, +	}; +	res.start = irq; +	platform_device_add_resources(pdev, &res, 1); +} + +static void __init sfi_handle_ipc_dev(struct sfi_device_table_entry *pentry, +					struct devs_id *dev) +{ +	struct platform_device *pdev; +	void *pdata = NULL; + +	pr_debug("IPC bus, name = %16.16s, irq = 0x%2x\n", +		pentry->name, pentry->irq); +	pdata = intel_mid_sfi_get_pdata(dev, pentry); +	if (IS_ERR(pdata)) +		return; + +	pdev = platform_device_alloc(pentry->name, 0); +	if (pdev == NULL) { +		pr_err("out of memory for SFI platform device '%s'.\n", +			pentry->name); +		return; +	} +	install_irq_resource(pdev, pentry->irq); + +	pdev->dev.platform_data = pdata; +	platform_device_add(pdev); +} + +static void __init sfi_handle_spi_dev(struct sfi_device_table_entry *pentry, +					struct devs_id *dev) +{ +	struct spi_board_info spi_info; +	void *pdata = NULL; + +	memset(&spi_info, 0, sizeof(spi_info)); +	strncpy(spi_info.modalias, pentry->name, SFI_NAME_LEN); +	spi_info.irq = ((pentry->irq == (u8)0xff) ? 0 : pentry->irq); +	spi_info.bus_num = pentry->host_num; +	spi_info.chip_select = pentry->addr; +	spi_info.max_speed_hz = pentry->max_freq; +	pr_debug("SPI bus=%d, name=%16.16s, irq=0x%2x, max_freq=%d, cs=%d\n", +		spi_info.bus_num, +		spi_info.modalias, +		spi_info.irq, +		spi_info.max_speed_hz, +		spi_info.chip_select); + +	pdata = intel_mid_sfi_get_pdata(dev, &spi_info); +	if (IS_ERR(pdata)) +		return; + +	spi_info.platform_data = pdata; +	if (dev->delay) +		intel_scu_spi_device_register(&spi_info); +	else +		spi_register_board_info(&spi_info, 1); +} + +static void __init sfi_handle_i2c_dev(struct sfi_device_table_entry *pentry, +					struct devs_id *dev) +{ +	struct i2c_board_info i2c_info; +	void *pdata = NULL; + +	memset(&i2c_info, 0, sizeof(i2c_info)); +	strncpy(i2c_info.type, pentry->name, SFI_NAME_LEN); +	i2c_info.irq = ((pentry->irq == (u8)0xff) ? 0 : pentry->irq); +	i2c_info.addr = pentry->addr; +	pr_debug("I2C bus = %d, name = %16.16s, irq = 0x%2x, addr = 0x%x\n", +		pentry->host_num, +		i2c_info.type, +		i2c_info.irq, +		i2c_info.addr); +	pdata = intel_mid_sfi_get_pdata(dev, &i2c_info); +	i2c_info.platform_data = pdata; +	if (IS_ERR(pdata)) +		return; + +	if (dev->delay) +		intel_scu_i2c_device_register(pentry->host_num, &i2c_info); +	else +		i2c_register_board_info(pentry->host_num, &i2c_info, 1); +} + +extern struct devs_id *const __x86_intel_mid_dev_start[], +		      *const __x86_intel_mid_dev_end[]; + +static struct devs_id __init *get_device_id(u8 type, char *name) +{ +	struct devs_id *const *dev_table; + +	for (dev_table = __x86_intel_mid_dev_start; +			dev_table < __x86_intel_mid_dev_end; dev_table++) { +		struct devs_id *dev = *dev_table; +		if (dev->type == type && +			!strncmp(dev->name, name, SFI_NAME_LEN)) { +			return dev; +		} +	} + +	return NULL; +} + +static int __init sfi_parse_devs(struct sfi_table_header *table) +{ +	struct sfi_table_simple *sb; +	struct sfi_device_table_entry *pentry; +	struct devs_id *dev = NULL; +	int num, i; +	int ioapic; +	struct io_apic_irq_attr irq_attr; + +	sb = (struct sfi_table_simple *)table; +	num = SFI_GET_NUM_ENTRIES(sb, struct sfi_device_table_entry); +	pentry = (struct sfi_device_table_entry *)sb->pentry; + +	for (i = 0; i < num; i++, pentry++) { +		int irq = pentry->irq; + +		if (irq != (u8)0xff) { /* native RTE case */ +			/* these SPI2 devices are not exposed to system as PCI +			 * devices, but they have separate RTE entry in IOAPIC +			 * so we have to enable them one by one here +			 */ +			ioapic = mp_find_ioapic(irq); +			if (ioapic >= 0) { +				irq_attr.ioapic = ioapic; +				irq_attr.ioapic_pin = irq; +				irq_attr.trigger = 1; +				if (intel_mid_identify_cpu() == +						INTEL_MID_CPU_CHIP_TANGIER) { +					if (!strncmp(pentry->name, +							"r69001-ts-i2c", 13)) +						/* active low */ +						irq_attr.polarity = 1; +					else if (!strncmp(pentry->name, +							"synaptics_3202", 14)) +						/* active low */ +						irq_attr.polarity = 1; +					else if (irq == 41) +						/* fast_int_1 */ +						irq_attr.polarity = 1; +					else +						/* active high */ +						irq_attr.polarity = 0; +				} else { +					/* PNW and CLV go with active low */ +					irq_attr.polarity = 1; +				} +				io_apic_set_pci_routing(NULL, irq, &irq_attr); +			} +		} else { +			irq = 0; /* No irq */ +		} + +		dev = get_device_id(pentry->type, pentry->name); + +		if (!dev) +			continue; + +		if (dev->device_handler) { +			dev->device_handler(pentry, dev); +		} else { +			switch (pentry->type) { +			case SFI_DEV_TYPE_IPC: +				sfi_handle_ipc_dev(pentry, dev); +				break; +			case SFI_DEV_TYPE_SPI: +				sfi_handle_spi_dev(pentry, dev); +				break; +			case SFI_DEV_TYPE_I2C: +				sfi_handle_i2c_dev(pentry, dev); +				break; +			case SFI_DEV_TYPE_UART: +			case SFI_DEV_TYPE_HSI: +			default: +				break; +			} +		} +	} +	return 0; +} + +static int __init intel_mid_platform_init(void) +{ +	sfi_table_parse(SFI_SIG_GPIO, NULL, NULL, sfi_parse_gpio); +	sfi_table_parse(SFI_SIG_DEVS, NULL, NULL, sfi_parse_devs); +	return 0; +} +arch_initcall(intel_mid_platform_init); diff --git a/arch/x86/platform/iris/Makefile b/arch/x86/platform/iris/Makefile new file mode 100644 index 00000000000..db921983a10 --- /dev/null +++ b/arch/x86/platform/iris/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_X86_32_IRIS)		+= iris.o diff --git a/arch/x86/platform/iris/iris.c b/arch/x86/platform/iris/iris.c new file mode 100644 index 00000000000..4d171e8640e --- /dev/null +++ b/arch/x86/platform/iris/iris.c @@ -0,0 +1,137 @@ +/* + * Eurobraille/Iris power off support. + * + * Eurobraille's Iris machine is a PC with no APM or ACPI support. + * It is shutdown by a special I/O sequence which this module provides. + * + *  Copyright (C) Shérab <Sebastien.Hinderer@ens-lyon.org> + * + * This program is free software ; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation ; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY ; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the program ; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/moduleparam.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <asm/io.h> + +#define IRIS_GIO_BASE		0x340 +#define IRIS_GIO_INPUT		IRIS_GIO_BASE +#define IRIS_GIO_OUTPUT		(IRIS_GIO_BASE + 1) +#define IRIS_GIO_PULSE		0x80 /* First byte to send */ +#define IRIS_GIO_REST		0x00 /* Second byte to send */ +#define IRIS_GIO_NODEV		0xff /* Likely not an Iris */ + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>"); +MODULE_DESCRIPTION("A power_off handler for Iris devices from EuroBraille"); +MODULE_SUPPORTED_DEVICE("Eurobraille/Iris"); + +static bool force; + +module_param(force, bool, 0); +MODULE_PARM_DESC(force, "Set to one to force poweroff handler installation."); + +static void (*old_pm_power_off)(void); + +static void iris_power_off(void) +{ +	outb(IRIS_GIO_PULSE, IRIS_GIO_OUTPUT); +	msleep(850); +	outb(IRIS_GIO_REST, IRIS_GIO_OUTPUT); +} + +/* + * Before installing the power_off handler, try to make sure the OS is + * running on an Iris.  Since Iris does not support DMI, this is done + * by reading its input port and seeing whether the read value is + * meaningful. + */ +static int iris_probe(struct platform_device *pdev) +{ +	unsigned char status = inb(IRIS_GIO_INPUT); +	if (status == IRIS_GIO_NODEV) { +		printk(KERN_ERR "This machine does not seem to be an Iris. " +			"Power off handler not installed.\n"); +		return -ENODEV; +	} +	old_pm_power_off = pm_power_off; +	pm_power_off = &iris_power_off; +	printk(KERN_INFO "Iris power_off handler installed.\n"); +	return 0; +} + +static int iris_remove(struct platform_device *pdev) +{ +	pm_power_off = old_pm_power_off; +	printk(KERN_INFO "Iris power_off handler uninstalled.\n"); +	return 0; +} + +static struct platform_driver iris_driver = { +	.driver		= { +		.name   = "iris", +		.owner  = THIS_MODULE, +	}, +	.probe          = iris_probe, +	.remove         = iris_remove, +}; + +static struct resource iris_resources[] = { +	{ +		.start  = IRIS_GIO_BASE, +		.end    = IRIS_GIO_OUTPUT, +		.flags  = IORESOURCE_IO, +		.name   = "address" +	} +}; + +static struct platform_device *iris_device; + +static int iris_init(void) +{ +	int ret; +	if (force != 1) { +		printk(KERN_ERR "The force parameter has not been set to 1." +			" The Iris poweroff handler will not be installed.\n"); +		return -ENODEV; +	} +	ret = platform_driver_register(&iris_driver); +	if (ret < 0) { +		printk(KERN_ERR "Failed to register iris platform driver: %d\n", +			ret); +		return ret; +	} +	iris_device = platform_device_register_simple("iris", (-1), +				iris_resources, ARRAY_SIZE(iris_resources)); +	if (IS_ERR(iris_device)) { +		printk(KERN_ERR "Failed to register iris platform device\n"); +		platform_driver_unregister(&iris_driver); +		return PTR_ERR(iris_device); +	} +	return 0; +} + +static void iris_exit(void) +{ +	platform_device_unregister(iris_device); +	platform_driver_unregister(&iris_driver); +} + +module_init(iris_init); +module_exit(iris_exit); diff --git a/arch/x86/platform/mrst/Makefile b/arch/x86/platform/mrst/Makefile deleted file mode 100644 index efbbc552fa9..00000000000 --- a/arch/x86/platform/mrst/Makefile +++ /dev/null @@ -1 +0,0 @@ -obj-$(CONFIG_X86_MRST)		+= mrst.o diff --git a/arch/x86/platform/mrst/mrst.c b/arch/x86/platform/mrst/mrst.c deleted file mode 100644 index 79ae68154e8..00000000000 --- a/arch/x86/platform/mrst/mrst.c +++ /dev/null @@ -1,311 +0,0 @@ -/* - * mrst.c: Intel Moorestown platform specific setup code - * - * (C) Copyright 2008 Intel Corporation - * Author: Jacob Pan (jacob.jun.pan@intel.com) - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; version 2 - * of the License. - */ -#include <linux/init.h> -#include <linux/kernel.h> -#include <linux/sfi.h> -#include <linux/irq.h> -#include <linux/module.h> - -#include <asm/setup.h> -#include <asm/mpspec_def.h> -#include <asm/hw_irq.h> -#include <asm/apic.h> -#include <asm/io_apic.h> -#include <asm/mrst.h> -#include <asm/io.h> -#include <asm/i8259.h> -#include <asm/apb_timer.h> - -/* - * the clockevent devices on Moorestown/Medfield can be APBT or LAPIC clock, - * cmdline option x86_mrst_timer can be used to override the configuration - * to prefer one or the other. - * at runtime, there are basically three timer configurations: - * 1. per cpu apbt clock only - * 2. per cpu always-on lapic clocks only, this is Penwell/Medfield only - * 3. per cpu lapic clock (C3STOP) and one apbt clock, with broadcast. - * - * by default (without cmdline option), platform code first detects cpu type - * to see if we are on lincroft or penwell, then set up both lapic or apbt - * clocks accordingly. - * i.e. by default, medfield uses configuration #2, moorestown uses #1. - * config #3 is supported but not recommended on medfield. - * - * rating and feature summary: - * lapic (with C3STOP) --------- 100 - * apbt (always-on) ------------ 110 - * lapic (always-on,ARAT) ------ 150 - */ - -__cpuinitdata enum mrst_timer_options mrst_timer_options; - -static u32 sfi_mtimer_usage[SFI_MTMR_MAX_NUM]; -static struct sfi_timer_table_entry sfi_mtimer_array[SFI_MTMR_MAX_NUM]; -enum mrst_cpu_type __mrst_cpu_chip; -EXPORT_SYMBOL_GPL(__mrst_cpu_chip); - -int sfi_mtimer_num; - -struct sfi_rtc_table_entry sfi_mrtc_array[SFI_MRTC_MAX]; -EXPORT_SYMBOL_GPL(sfi_mrtc_array); -int sfi_mrtc_num; - -static inline void assign_to_mp_irq(struct mpc_intsrc *m, -				    struct mpc_intsrc *mp_irq) -{ -	memcpy(mp_irq, m, sizeof(struct mpc_intsrc)); -} - -static inline int mp_irq_cmp(struct mpc_intsrc *mp_irq, -				struct mpc_intsrc *m) -{ -	return memcmp(mp_irq, m, sizeof(struct mpc_intsrc)); -} - -static void save_mp_irq(struct mpc_intsrc *m) -{ -	int i; - -	for (i = 0; i < mp_irq_entries; i++) { -		if (!mp_irq_cmp(&mp_irqs[i], m)) -			return; -	} - -	assign_to_mp_irq(m, &mp_irqs[mp_irq_entries]); -	if (++mp_irq_entries == MAX_IRQ_SOURCES) -		panic("Max # of irq sources exceeded!!\n"); -} - -/* parse all the mtimer info to a static mtimer array */ -static int __init sfi_parse_mtmr(struct sfi_table_header *table) -{ -	struct sfi_table_simple *sb; -	struct sfi_timer_table_entry *pentry; -	struct mpc_intsrc mp_irq; -	int totallen; - -	sb = (struct sfi_table_simple *)table; -	if (!sfi_mtimer_num) { -		sfi_mtimer_num = SFI_GET_NUM_ENTRIES(sb, -					struct sfi_timer_table_entry); -		pentry = (struct sfi_timer_table_entry *) sb->pentry; -		totallen = sfi_mtimer_num * sizeof(*pentry); -		memcpy(sfi_mtimer_array, pentry, totallen); -	} - -	printk(KERN_INFO "SFI: MTIMER info (num = %d):\n", sfi_mtimer_num); -	pentry = sfi_mtimer_array; -	for (totallen = 0; totallen < sfi_mtimer_num; totallen++, pentry++) { -		printk(KERN_INFO "timer[%d]: paddr = 0x%08x, freq = %dHz," -			" irq = %d\n", totallen, (u32)pentry->phys_addr, -			pentry->freq_hz, pentry->irq); -			if (!pentry->irq) -				continue; -			mp_irq.type = MP_IOAPIC; -			mp_irq.irqtype = mp_INT; -/* triggering mode edge bit 2-3, active high polarity bit 0-1 */ -			mp_irq.irqflag = 5; -			mp_irq.srcbus = 0; -			mp_irq.srcbusirq = pentry->irq;	/* IRQ */ -			mp_irq.dstapic = MP_APIC_ALL; -			mp_irq.dstirq = pentry->irq; -			save_mp_irq(&mp_irq); -	} - -	return 0; -} - -struct sfi_timer_table_entry *sfi_get_mtmr(int hint) -{ -	int i; -	if (hint < sfi_mtimer_num) { -		if (!sfi_mtimer_usage[hint]) { -			pr_debug("hint taken for timer %d irq %d\n",\ -				hint, sfi_mtimer_array[hint].irq); -			sfi_mtimer_usage[hint] = 1; -			return &sfi_mtimer_array[hint]; -		} -	} -	/* take the first timer available */ -	for (i = 0; i < sfi_mtimer_num;) { -		if (!sfi_mtimer_usage[i]) { -			sfi_mtimer_usage[i] = 1; -			return &sfi_mtimer_array[i]; -		} -		i++; -	} -	return NULL; -} - -void sfi_free_mtmr(struct sfi_timer_table_entry *mtmr) -{ -	int i; -	for (i = 0; i < sfi_mtimer_num;) { -		if (mtmr->irq == sfi_mtimer_array[i].irq) { -			sfi_mtimer_usage[i] = 0; -			return; -		} -		i++; -	} -} - -/* parse all the mrtc info to a global mrtc array */ -int __init sfi_parse_mrtc(struct sfi_table_header *table) -{ -	struct sfi_table_simple *sb; -	struct sfi_rtc_table_entry *pentry; -	struct mpc_intsrc mp_irq; - -	int totallen; - -	sb = (struct sfi_table_simple *)table; -	if (!sfi_mrtc_num) { -		sfi_mrtc_num = SFI_GET_NUM_ENTRIES(sb, -						struct sfi_rtc_table_entry); -		pentry = (struct sfi_rtc_table_entry *)sb->pentry; -		totallen = sfi_mrtc_num * sizeof(*pentry); -		memcpy(sfi_mrtc_array, pentry, totallen); -	} - -	printk(KERN_INFO "SFI: RTC info (num = %d):\n", sfi_mrtc_num); -	pentry = sfi_mrtc_array; -	for (totallen = 0; totallen < sfi_mrtc_num; totallen++, pentry++) { -		printk(KERN_INFO "RTC[%d]: paddr = 0x%08x, irq = %d\n", -			totallen, (u32)pentry->phys_addr, pentry->irq); -		mp_irq.type = MP_IOAPIC; -		mp_irq.irqtype = mp_INT; -		mp_irq.irqflag = 0; -		mp_irq.srcbus = 0; -		mp_irq.srcbusirq = pentry->irq;	/* IRQ */ -		mp_irq.dstapic = MP_APIC_ALL; -		mp_irq.dstirq = pentry->irq; -		save_mp_irq(&mp_irq); -	} -	return 0; -} - -static unsigned long __init mrst_calibrate_tsc(void) -{ -	unsigned long flags, fast_calibrate; - -	local_irq_save(flags); -	fast_calibrate = apbt_quick_calibrate(); -	local_irq_restore(flags); - -	if (fast_calibrate) -		return fast_calibrate; - -	return 0; -} - -void __init mrst_time_init(void) -{ -	switch (mrst_timer_options) { -	case MRST_TIMER_APBT_ONLY: -		break; -	case MRST_TIMER_LAPIC_APBT: -		x86_init.timers.setup_percpu_clockev = setup_boot_APIC_clock; -		x86_cpuinit.setup_percpu_clockev = setup_secondary_APIC_clock; -		break; -	default: -		if (!boot_cpu_has(X86_FEATURE_ARAT)) -			break; -		x86_init.timers.setup_percpu_clockev = setup_boot_APIC_clock; -		x86_cpuinit.setup_percpu_clockev = setup_secondary_APIC_clock; -		return; -	} -	/* we need at least one APB timer */ -	sfi_table_parse(SFI_SIG_MTMR, NULL, NULL, sfi_parse_mtmr); -	pre_init_apic_IRQ0(); -	apbt_time_init(); -} - -void __init mrst_rtc_init(void) -{ -	sfi_table_parse(SFI_SIG_MRTC, NULL, NULL, sfi_parse_mrtc); -} - -void __cpuinit mrst_arch_setup(void) -{ -	if (boot_cpu_data.x86 == 6 && boot_cpu_data.x86_model == 0x27) -		__mrst_cpu_chip = MRST_CPU_CHIP_PENWELL; -	else if (boot_cpu_data.x86 == 6 && boot_cpu_data.x86_model == 0x26) -		__mrst_cpu_chip = MRST_CPU_CHIP_LINCROFT; -	else { -		pr_err("Unknown Moorestown CPU (%d:%d), default to Lincroft\n", -			boot_cpu_data.x86, boot_cpu_data.x86_model); -		__mrst_cpu_chip = MRST_CPU_CHIP_LINCROFT; -	} -	pr_debug("Moorestown CPU %s identified\n", -		(__mrst_cpu_chip == MRST_CPU_CHIP_LINCROFT) ? -		"Lincroft" : "Penwell"); -} - -/* MID systems don't have i8042 controller */ -static int mrst_i8042_detect(void) -{ -	return 0; -} - -/* - * Moorestown specific x86_init function overrides and early setup - * calls. - */ -void __init x86_mrst_early_setup(void) -{ -	x86_init.resources.probe_roms = x86_init_noop; -	x86_init.resources.reserve_resources = x86_init_noop; - -	x86_init.timers.timer_init = mrst_time_init; -	x86_init.timers.setup_percpu_clockev = x86_init_noop; - -	x86_init.irqs.pre_vector_init = x86_init_noop; - -	x86_init.oem.arch_setup = mrst_arch_setup; - -	x86_cpuinit.setup_percpu_clockev = apbt_setup_secondary_clock; - -	x86_platform.calibrate_tsc = mrst_calibrate_tsc; -	x86_platform.i8042_detect = mrst_i8042_detect; -	x86_init.pci.init = pci_mrst_init; -	x86_init.pci.fixup_irqs = x86_init_noop; - -	legacy_pic = &null_legacy_pic; - -	/* Avoid searching for BIOS MP tables */ -	x86_init.mpparse.find_smp_config = x86_init_noop; -	x86_init.mpparse.get_smp_config = x86_init_uint_noop; - -} - -/* - * if user does not want to use per CPU apb timer, just give it a lower rating - * than local apic timer and skip the late per cpu timer init. - */ -static inline int __init setup_x86_mrst_timer(char *arg) -{ -	if (!arg) -		return -EINVAL; - -	if (strcmp("apbt_only", arg) == 0) -		mrst_timer_options = MRST_TIMER_APBT_ONLY; -	else if (strcmp("lapic_and_apbt", arg) == 0) -		mrst_timer_options = MRST_TIMER_LAPIC_APBT; -	else { -		pr_warning("X86 MRST timer option %s not recognised" -			   " use x86_mrst_timer=apbt_only or lapic_and_apbt\n", -			   arg); -		return -EINVAL; -	} -	return 0; -} -__setup("x86_mrst_timer=", setup_x86_mrst_timer); diff --git a/arch/x86/platform/olpc/Makefile b/arch/x86/platform/olpc/Makefile index c31b8fcb5a8..fd332c53394 100644 --- a/arch/x86/platform/olpc/Makefile +++ b/arch/x86/platform/olpc/Makefile @@ -1,3 +1,5 @@ -obj-$(CONFIG_OLPC)		+= olpc.o -obj-$(CONFIG_OLPC_XO1)		+= olpc-xo1.o -obj-$(CONFIG_OLPC_OPENFIRMWARE)	+= olpc_ofw.o +obj-$(CONFIG_OLPC)		+= olpc.o olpc_ofw.o olpc_dt.o +obj-$(CONFIG_OLPC_XO1_PM)		+= olpc-xo1-pm.o xo1-wakeup.o +obj-$(CONFIG_OLPC_XO1_RTC)	+= olpc-xo1-rtc.o +obj-$(CONFIG_OLPC_XO1_SCI)	+= olpc-xo1-sci.o +obj-$(CONFIG_OLPC_XO15_SCI)	+= olpc-xo15-sci.o diff --git a/arch/x86/platform/olpc/olpc-xo1-pm.c b/arch/x86/platform/olpc/olpc-xo1-pm.c new file mode 100644 index 00000000000..a9acde72d4e --- /dev/null +++ b/arch/x86/platform/olpc/olpc-xo1-pm.c @@ -0,0 +1,202 @@ +/* + * Support for power management features of the OLPC XO-1 laptop + * + * Copyright (C) 2010 Andres Salomon <dilinger@queued.net> + * Copyright (C) 2010 One Laptop per Child + * Copyright (C) 2006 Red Hat, Inc. + * Copyright (C) 2006 Advanced Micro Devices, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/cs5535.h> +#include <linux/platform_device.h> +#include <linux/export.h> +#include <linux/pm.h> +#include <linux/mfd/core.h> +#include <linux/suspend.h> +#include <linux/olpc-ec.h> + +#include <asm/io.h> +#include <asm/olpc.h> + +#define DRV_NAME "olpc-xo1-pm" + +static unsigned long acpi_base; +static unsigned long pms_base; + +static u16 wakeup_mask = CS5536_PM_PWRBTN; + +static struct { +	unsigned long address; +	unsigned short segment; +} ofw_bios_entry = { 0xF0000 + PAGE_OFFSET, __KERNEL_CS }; + +/* Set bits in the wakeup mask */ +void olpc_xo1_pm_wakeup_set(u16 value) +{ +	wakeup_mask |= value; +} +EXPORT_SYMBOL_GPL(olpc_xo1_pm_wakeup_set); + +/* Clear bits in the wakeup mask */ +void olpc_xo1_pm_wakeup_clear(u16 value) +{ +	wakeup_mask &= ~value; +} +EXPORT_SYMBOL_GPL(olpc_xo1_pm_wakeup_clear); + +static int xo1_power_state_enter(suspend_state_t pm_state) +{ +	unsigned long saved_sci_mask; + +	/* Only STR is supported */ +	if (pm_state != PM_SUSPEND_MEM) +		return -EINVAL; + +	/* +	 * Save SCI mask (this gets lost since PM1_EN is used as a mask for +	 * wakeup events, which is not necessarily the same event set) +	 */ +	saved_sci_mask = inl(acpi_base + CS5536_PM1_STS); +	saved_sci_mask &= 0xffff0000; + +	/* Save CPU state */ +	do_olpc_suspend_lowlevel(); + +	/* Resume path starts here */ + +	/* Restore SCI mask (using dword access to CS5536_PM1_EN) */ +	outl(saved_sci_mask, acpi_base + CS5536_PM1_STS); + +	return 0; +} + +asmlinkage __visible int xo1_do_sleep(u8 sleep_state) +{ +	void *pgd_addr = __va(read_cr3()); + +	/* Program wakeup mask (using dword access to CS5536_PM1_EN) */ +	outl(wakeup_mask << 16, acpi_base + CS5536_PM1_STS); + +	__asm__("movl %0,%%eax" : : "r" (pgd_addr)); +	__asm__("call *(%%edi); cld" +		: : "D" (&ofw_bios_entry)); +	__asm__("movb $0x34, %al\n\t" +		"outb %al, $0x70\n\t" +		"movb $0x30, %al\n\t" +		"outb %al, $0x71\n\t"); +	return 0; +} + +static void xo1_power_off(void) +{ +	printk(KERN_INFO "OLPC XO-1 power off sequence...\n"); + +	/* Enable all of these controls with 0 delay */ +	outl(0x40000000, pms_base + CS5536_PM_SCLK); +	outl(0x40000000, pms_base + CS5536_PM_IN_SLPCTL); +	outl(0x40000000, pms_base + CS5536_PM_WKXD); +	outl(0x40000000, pms_base + CS5536_PM_WKD); + +	/* Clear status bits (possibly unnecessary) */ +	outl(0x0002ffff, pms_base  + CS5536_PM_SSC); +	outl(0xffffffff, acpi_base + CS5536_PM_GPE0_STS); + +	/* Write SLP_EN bit to start the machinery */ +	outl(0x00002000, acpi_base + CS5536_PM1_CNT); +} + +static int xo1_power_state_valid(suspend_state_t pm_state) +{ +	/* suspend-to-RAM only */ +	return pm_state == PM_SUSPEND_MEM; +} + +static const struct platform_suspend_ops xo1_suspend_ops = { +	.valid = xo1_power_state_valid, +	.enter = xo1_power_state_enter, +}; + +static int xo1_pm_probe(struct platform_device *pdev) +{ +	struct resource *res; +	int err; + +	/* don't run on non-XOs */ +	if (!machine_is_olpc()) +		return -ENODEV; + +	err = mfd_cell_enable(pdev); +	if (err) +		return err; + +	res = platform_get_resource(pdev, IORESOURCE_IO, 0); +	if (!res) { +		dev_err(&pdev->dev, "can't fetch device resource info\n"); +		return -EIO; +	} +	if (strcmp(pdev->name, "cs5535-pms") == 0) +		pms_base = res->start; +	else if (strcmp(pdev->name, "olpc-xo1-pm-acpi") == 0) +		acpi_base = res->start; + +	/* If we have both addresses, we can override the poweroff hook */ +	if (pms_base && acpi_base) { +		suspend_set_ops(&xo1_suspend_ops); +		pm_power_off = xo1_power_off; +		printk(KERN_INFO "OLPC XO-1 support registered\n"); +	} + +	return 0; +} + +static int xo1_pm_remove(struct platform_device *pdev) +{ +	mfd_cell_disable(pdev); + +	if (strcmp(pdev->name, "cs5535-pms") == 0) +		pms_base = 0; +	else if (strcmp(pdev->name, "olpc-xo1-pm-acpi") == 0) +		acpi_base = 0; + +	pm_power_off = NULL; +	return 0; +} + +static struct platform_driver cs5535_pms_driver = { +	.driver = { +		.name = "cs5535-pms", +		.owner = THIS_MODULE, +	}, +	.probe = xo1_pm_probe, +	.remove = xo1_pm_remove, +}; + +static struct platform_driver cs5535_acpi_driver = { +	.driver = { +		.name = "olpc-xo1-pm-acpi", +		.owner = THIS_MODULE, +	}, +	.probe = xo1_pm_probe, +	.remove = xo1_pm_remove, +}; + +static int __init xo1_pm_init(void) +{ +	int r; + +	r = platform_driver_register(&cs5535_pms_driver); +	if (r) +		return r; + +	r = platform_driver_register(&cs5535_acpi_driver); +	if (r) +		platform_driver_unregister(&cs5535_pms_driver); + +	return r; +} +arch_initcall(xo1_pm_init); diff --git a/arch/x86/platform/olpc/olpc-xo1-rtc.c b/arch/x86/platform/olpc/olpc-xo1-rtc.c new file mode 100644 index 00000000000..a2b4efddd61 --- /dev/null +++ b/arch/x86/platform/olpc/olpc-xo1-rtc.c @@ -0,0 +1,81 @@ +/* + * Support for OLPC XO-1 Real Time Clock (RTC) + * + * Copyright (C) 2011 One Laptop per Child + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/mc146818rtc.h> +#include <linux/platform_device.h> +#include <linux/rtc.h> +#include <linux/of.h> + +#include <asm/msr.h> +#include <asm/olpc.h> + +static void rtc_wake_on(struct device *dev) +{ +	olpc_xo1_pm_wakeup_set(CS5536_PM_RTC); +} + +static void rtc_wake_off(struct device *dev) +{ +	olpc_xo1_pm_wakeup_clear(CS5536_PM_RTC); +} + +static struct resource rtc_platform_resource[] = { +	[0] = { +		.start	= RTC_PORT(0), +		.end	= RTC_PORT(1), +		.flags	= IORESOURCE_IO, +	}, +	[1] = { +		.start	= RTC_IRQ, +		.end	= RTC_IRQ, +		.flags	= IORESOURCE_IRQ, +	} +}; + +static struct cmos_rtc_board_info rtc_info = { +	.rtc_day_alarm = 0, +	.rtc_mon_alarm = 0, +	.rtc_century = 0, +	.wake_on = rtc_wake_on, +	.wake_off = rtc_wake_off, +}; + +static struct platform_device xo1_rtc_device = { +	.name = "rtc_cmos", +	.id = -1, +	.num_resources = ARRAY_SIZE(rtc_platform_resource), +	.dev.platform_data = &rtc_info, +	.resource = rtc_platform_resource, +}; + +static int __init xo1_rtc_init(void) +{ +	int r; +	struct device_node *node; + +	node = of_find_compatible_node(NULL, NULL, "olpc,xo1-rtc"); +	if (!node) +		return 0; +	of_node_put(node); + +	pr_info("olpc-xo1-rtc: Initializing OLPC XO-1 RTC\n"); +	rdmsrl(MSR_RTC_DOMA_OFFSET, rtc_info.rtc_day_alarm); +	rdmsrl(MSR_RTC_MONA_OFFSET, rtc_info.rtc_mon_alarm); +	rdmsrl(MSR_RTC_CEN_OFFSET, rtc_info.rtc_century); + +	r = platform_device_register(&xo1_rtc_device); +	if (r) +		return r; + +	device_init_wakeup(&xo1_rtc_device.dev, 1); +	return 0; +} +arch_initcall(xo1_rtc_init); diff --git a/arch/x86/platform/olpc/olpc-xo1-sci.c b/arch/x86/platform/olpc/olpc-xo1-sci.c new file mode 100644 index 00000000000..9a2e590dd20 --- /dev/null +++ b/arch/x86/platform/olpc/olpc-xo1-sci.c @@ -0,0 +1,642 @@ +/* + * Support for OLPC XO-1 System Control Interrupts (SCI) + * + * Copyright (C) 2010 One Laptop per Child + * Copyright (C) 2006 Red Hat, Inc. + * Copyright (C) 2006 Advanced Micro Devices, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/cs5535.h> +#include <linux/device.h> +#include <linux/gpio.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/pm_wakeup.h> +#include <linux/mfd/core.h> +#include <linux/power_supply.h> +#include <linux/suspend.h> +#include <linux/workqueue.h> +#include <linux/olpc-ec.h> + +#include <asm/io.h> +#include <asm/msr.h> +#include <asm/olpc.h> + +#define DRV_NAME	"olpc-xo1-sci" +#define PFX		DRV_NAME ": " + +static unsigned long acpi_base; +static struct input_dev *power_button_idev; +static struct input_dev *ebook_switch_idev; +static struct input_dev *lid_switch_idev; + +static int sci_irq; + +static bool lid_open; +static bool lid_inverted; +static int lid_wake_mode; + +enum lid_wake_modes { +	LID_WAKE_ALWAYS, +	LID_WAKE_OPEN, +	LID_WAKE_CLOSE, +}; + +static const char * const lid_wake_mode_names[] = { +	[LID_WAKE_ALWAYS] = "always", +	[LID_WAKE_OPEN] = "open", +	[LID_WAKE_CLOSE] = "close", +}; + +static void battery_status_changed(void) +{ +	struct power_supply *psy = power_supply_get_by_name("olpc-battery"); + +	if (psy) { +		power_supply_changed(psy); +		put_device(psy->dev); +	} +} + +static void ac_status_changed(void) +{ +	struct power_supply *psy = power_supply_get_by_name("olpc-ac"); + +	if (psy) { +		power_supply_changed(psy); +		put_device(psy->dev); +	} +} + +/* Report current ebook switch state through input layer */ +static void send_ebook_state(void) +{ +	unsigned char state; + +	if (olpc_ec_cmd(EC_READ_EB_MODE, NULL, 0, &state, 1)) { +		pr_err(PFX "failed to get ebook state\n"); +		return; +	} + +	if (!!test_bit(SW_TABLET_MODE, ebook_switch_idev->sw) == state) +		return; /* Nothing new to report. */ + +	input_report_switch(ebook_switch_idev, SW_TABLET_MODE, state); +	input_sync(ebook_switch_idev); +	pm_wakeup_event(&ebook_switch_idev->dev, 0); +} + +static void flip_lid_inverter(void) +{ +	/* gpio is high; invert so we'll get l->h event interrupt */ +	if (lid_inverted) +		cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_INPUT_INVERT); +	else +		cs5535_gpio_set(OLPC_GPIO_LID, GPIO_INPUT_INVERT); +	lid_inverted = !lid_inverted; +} + +static void detect_lid_state(void) +{ +	/* +	 * the edge detector hookup on the gpio inputs on the geode is +	 * odd, to say the least.  See http://dev.laptop.org/ticket/5703 +	 * for details, but in a nutshell:  we don't use the edge +	 * detectors.  instead, we make use of an anomoly:  with the both +	 * edge detectors turned off, we still get an edge event on a +	 * positive edge transition.  to take advantage of this, we use the +	 * front-end inverter to ensure that that's the edge we're always +	 * going to see next. +	 */ + +	int state; + +	state = cs5535_gpio_isset(OLPC_GPIO_LID, GPIO_READ_BACK); +	lid_open = !state ^ !lid_inverted; /* x ^^ y */ +	if (!state) +		return; + +	flip_lid_inverter(); +} + +/* Report current lid switch state through input layer */ +static void send_lid_state(void) +{ +	if (!!test_bit(SW_LID, lid_switch_idev->sw) == !lid_open) +		return; /* Nothing new to report. */ + +	input_report_switch(lid_switch_idev, SW_LID, !lid_open); +	input_sync(lid_switch_idev); +	pm_wakeup_event(&lid_switch_idev->dev, 0); +} + +static ssize_t lid_wake_mode_show(struct device *dev, +				  struct device_attribute *attr, char *buf) +{ +	const char *mode = lid_wake_mode_names[lid_wake_mode]; +	return sprintf(buf, "%s\n", mode); +} +static ssize_t lid_wake_mode_set(struct device *dev, +				 struct device_attribute *attr, +				 const char *buf, size_t count) +{ +	int i; +	for (i = 0; i < ARRAY_SIZE(lid_wake_mode_names); i++) { +		const char *mode = lid_wake_mode_names[i]; +		if (strlen(mode) != count || strncasecmp(mode, buf, count)) +			continue; + +		lid_wake_mode = i; +		return count; +	} +	return -EINVAL; +} +static DEVICE_ATTR(lid_wake_mode, S_IWUSR | S_IRUGO, lid_wake_mode_show, +		   lid_wake_mode_set); + +/* + * Process all items in the EC's SCI queue. + * + * This is handled in a workqueue because olpc_ec_cmd can be slow (and + * can even timeout). + * + * If propagate_events is false, the queue is drained without events being + * generated for the interrupts. + */ +static void process_sci_queue(bool propagate_events) +{ +	int r; +	u16 data; + +	do { +		r = olpc_ec_sci_query(&data); +		if (r || !data) +			break; + +		pr_debug(PFX "SCI 0x%x received\n", data); + +		switch (data) { +		case EC_SCI_SRC_BATERR: +		case EC_SCI_SRC_BATSOC: +		case EC_SCI_SRC_BATTERY: +		case EC_SCI_SRC_BATCRIT: +			battery_status_changed(); +			break; +		case EC_SCI_SRC_ACPWR: +			ac_status_changed(); +			break; +		} + +		if (data == EC_SCI_SRC_EBOOK && propagate_events) +			send_ebook_state(); +	} while (data); + +	if (r) +		pr_err(PFX "Failed to clear SCI queue"); +} + +static void process_sci_queue_work(struct work_struct *work) +{ +	process_sci_queue(true); +} + +static DECLARE_WORK(sci_work, process_sci_queue_work); + +static irqreturn_t xo1_sci_intr(int irq, void *dev_id) +{ +	struct platform_device *pdev = dev_id; +	u32 sts; +	u32 gpe; + +	sts = inl(acpi_base + CS5536_PM1_STS); +	outl(sts | 0xffff, acpi_base + CS5536_PM1_STS); + +	gpe = inl(acpi_base + CS5536_PM_GPE0_STS); +	outl(0xffffffff, acpi_base + CS5536_PM_GPE0_STS); + +	dev_dbg(&pdev->dev, "sts %x gpe %x\n", sts, gpe); + +	if (sts & CS5536_PWRBTN_FLAG) { +		if (!(sts & CS5536_WAK_FLAG)) { +			/* Only report power button input when it was pressed +			 * during regular operation (as opposed to when it +			 * was used to wake the system). */ +			input_report_key(power_button_idev, KEY_POWER, 1); +			input_sync(power_button_idev); +			input_report_key(power_button_idev, KEY_POWER, 0); +			input_sync(power_button_idev); +		} +		/* Report the wakeup event in all cases. */ +		pm_wakeup_event(&power_button_idev->dev, 0); +	} + +	if ((sts & (CS5536_RTC_FLAG | CS5536_WAK_FLAG)) == +			(CS5536_RTC_FLAG | CS5536_WAK_FLAG)) { +		/* When the system is woken by the RTC alarm, report the +		 * event on the rtc device. */ +		struct device *rtc = bus_find_device_by_name( +			&platform_bus_type, NULL, "rtc_cmos"); +		if (rtc) { +			pm_wakeup_event(rtc, 0); +			put_device(rtc); +		} +	} + +	if (gpe & CS5536_GPIOM7_PME_FLAG) { /* EC GPIO */ +		cs5535_gpio_set(OLPC_GPIO_ECSCI, GPIO_NEGATIVE_EDGE_STS); +		schedule_work(&sci_work); +	} + +	cs5535_gpio_set(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_STS); +	cs5535_gpio_set(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_STS); +	detect_lid_state(); +	send_lid_state(); + +	return IRQ_HANDLED; +} + +static int xo1_sci_suspend(struct platform_device *pdev, pm_message_t state) +{ +	if (device_may_wakeup(&power_button_idev->dev)) +		olpc_xo1_pm_wakeup_set(CS5536_PM_PWRBTN); +	else +		olpc_xo1_pm_wakeup_clear(CS5536_PM_PWRBTN); + +	if (device_may_wakeup(&ebook_switch_idev->dev)) +		olpc_ec_wakeup_set(EC_SCI_SRC_EBOOK); +	else +		olpc_ec_wakeup_clear(EC_SCI_SRC_EBOOK); + +	if (!device_may_wakeup(&lid_switch_idev->dev)) { +		cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE); +	} else if ((lid_open && lid_wake_mode == LID_WAKE_OPEN) || +		   (!lid_open && lid_wake_mode == LID_WAKE_CLOSE)) { +		flip_lid_inverter(); + +		/* we may have just caused an event */ +		cs5535_gpio_set(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_STS); +		cs5535_gpio_set(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_STS); + +		cs5535_gpio_set(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE); +	} + +	return 0; +} + +static int xo1_sci_resume(struct platform_device *pdev) +{ +	/* +	 * We don't know what may have happened while we were asleep. +	 * Reestablish our lid setup so we're sure to catch all transitions. +	 */ +	detect_lid_state(); +	send_lid_state(); +	cs5535_gpio_set(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE); + +	/* Enable all EC events */ +	olpc_ec_mask_write(EC_SCI_SRC_ALL); + +	/* Power/battery status might have changed too */ +	battery_status_changed(); +	ac_status_changed(); +	return 0; +} + +static int setup_sci_interrupt(struct platform_device *pdev) +{ +	u32 lo, hi; +	u32 sts; +	int r; + +	rdmsr(0x51400020, lo, hi); +	sci_irq = (lo >> 20) & 15; + +	if (sci_irq) { +		dev_info(&pdev->dev, "SCI is mapped to IRQ %d\n", sci_irq); +	} else { +		/* Zero means masked */ +		dev_info(&pdev->dev, "SCI unmapped. Mapping to IRQ 3\n"); +		sci_irq = 3; +		lo |= 0x00300000; +		wrmsrl(0x51400020, lo); +	} + +	/* Select level triggered in PIC */ +	if (sci_irq < 8) { +		lo = inb(CS5536_PIC_INT_SEL1); +		lo |= 1 << sci_irq; +		outb(lo, CS5536_PIC_INT_SEL1); +	} else { +		lo = inb(CS5536_PIC_INT_SEL2); +		lo |= 1 << (sci_irq - 8); +		outb(lo, CS5536_PIC_INT_SEL2); +	} + +	/* Enable interesting SCI events, and clear pending interrupts */ +	sts = inl(acpi_base + CS5536_PM1_STS); +	outl(((CS5536_PM_PWRBTN | CS5536_PM_RTC) << 16) | 0xffff, +	     acpi_base + CS5536_PM1_STS); + +	r = request_irq(sci_irq, xo1_sci_intr, 0, DRV_NAME, pdev); +	if (r) +		dev_err(&pdev->dev, "can't request interrupt\n"); + +	return r; +} + +static int setup_ec_sci(void) +{ +	int r; + +	r = gpio_request(OLPC_GPIO_ECSCI, "OLPC-ECSCI"); +	if (r) +		return r; + +	gpio_direction_input(OLPC_GPIO_ECSCI); + +	/* Clear pending EC SCI events */ +	cs5535_gpio_set(OLPC_GPIO_ECSCI, GPIO_NEGATIVE_EDGE_STS); +	cs5535_gpio_set(OLPC_GPIO_ECSCI, GPIO_POSITIVE_EDGE_STS); + +	/* +	 * Enable EC SCI events, and map them to both a PME and the SCI +	 * interrupt. +	 * +	 * Ordinarily, in addition to functioning as GPIOs, Geode GPIOs can +	 * be mapped to regular interrupts *or* Geode-specific Power +	 * Management Events (PMEs) - events that bring the system out of +	 * suspend. In this case, we want both of those things - the system +	 * wakeup, *and* the ability to get an interrupt when an event occurs. +	 * +	 * To achieve this, we map the GPIO to a PME, and then we use one +	 * of the many generic knobs on the CS5535 PIC to additionally map the +	 * PME to the regular SCI interrupt line. +	 */ +	cs5535_gpio_set(OLPC_GPIO_ECSCI, GPIO_EVENTS_ENABLE); + +	/* Set the SCI to cause a PME event on group 7 */ +	cs5535_gpio_setup_event(OLPC_GPIO_ECSCI, 7, 1); + +	/* And have group 7 also fire the SCI interrupt */ +	cs5535_pic_unreqz_select_high(7, sci_irq); + +	return 0; +} + +static void free_ec_sci(void) +{ +	gpio_free(OLPC_GPIO_ECSCI); +} + +static int setup_lid_events(void) +{ +	int r; + +	r = gpio_request(OLPC_GPIO_LID, "OLPC-LID"); +	if (r) +		return r; + +	gpio_direction_input(OLPC_GPIO_LID); + +	cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_INPUT_INVERT); +	lid_inverted = 0; + +	/* Clear edge detection and event enable for now */ +	cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE); +	cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_EN); +	cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_EN); +	cs5535_gpio_set(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_STS); +	cs5535_gpio_set(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_STS); + +	/* Set the LID to cause an PME event on group 6 */ +	cs5535_gpio_setup_event(OLPC_GPIO_LID, 6, 1); + +	/* Set PME group 6 to fire the SCI interrupt */ +	cs5535_gpio_set_irq(6, sci_irq); + +	/* Enable the event */ +	cs5535_gpio_set(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE); + +	return 0; +} + +static void free_lid_events(void) +{ +	gpio_free(OLPC_GPIO_LID); +} + +static int setup_power_button(struct platform_device *pdev) +{ +	int r; + +	power_button_idev = input_allocate_device(); +	if (!power_button_idev) +		return -ENOMEM; + +	power_button_idev->name = "Power Button"; +	power_button_idev->phys = DRV_NAME "/input0"; +	set_bit(EV_KEY, power_button_idev->evbit); +	set_bit(KEY_POWER, power_button_idev->keybit); + +	power_button_idev->dev.parent = &pdev->dev; +	device_init_wakeup(&power_button_idev->dev, 1); + +	r = input_register_device(power_button_idev); +	if (r) { +		dev_err(&pdev->dev, "failed to register power button: %d\n", r); +		input_free_device(power_button_idev); +	} + +	return r; +} + +static void free_power_button(void) +{ +	input_unregister_device(power_button_idev); +} + +static int setup_ebook_switch(struct platform_device *pdev) +{ +	int r; + +	ebook_switch_idev = input_allocate_device(); +	if (!ebook_switch_idev) +		return -ENOMEM; + +	ebook_switch_idev->name = "EBook Switch"; +	ebook_switch_idev->phys = DRV_NAME "/input1"; +	set_bit(EV_SW, ebook_switch_idev->evbit); +	set_bit(SW_TABLET_MODE, ebook_switch_idev->swbit); + +	ebook_switch_idev->dev.parent = &pdev->dev; +	device_set_wakeup_capable(&ebook_switch_idev->dev, true); + +	r = input_register_device(ebook_switch_idev); +	if (r) { +		dev_err(&pdev->dev, "failed to register ebook switch: %d\n", r); +		input_free_device(ebook_switch_idev); +	} + +	return r; +} + +static void free_ebook_switch(void) +{ +	input_unregister_device(ebook_switch_idev); +} + +static int setup_lid_switch(struct platform_device *pdev) +{ +	int r; + +	lid_switch_idev = input_allocate_device(); +	if (!lid_switch_idev) +		return -ENOMEM; + +	lid_switch_idev->name = "Lid Switch"; +	lid_switch_idev->phys = DRV_NAME "/input2"; +	set_bit(EV_SW, lid_switch_idev->evbit); +	set_bit(SW_LID, lid_switch_idev->swbit); + +	lid_switch_idev->dev.parent = &pdev->dev; +	device_set_wakeup_capable(&lid_switch_idev->dev, true); + +	r = input_register_device(lid_switch_idev); +	if (r) { +		dev_err(&pdev->dev, "failed to register lid switch: %d\n", r); +		goto err_register; +	} + +	r = device_create_file(&lid_switch_idev->dev, &dev_attr_lid_wake_mode); +	if (r) { +		dev_err(&pdev->dev, "failed to create wake mode attr: %d\n", r); +		goto err_create_attr; +	} + +	return 0; + +err_create_attr: +	input_unregister_device(lid_switch_idev); +	lid_switch_idev = NULL; +err_register: +	input_free_device(lid_switch_idev); +	return r; +} + +static void free_lid_switch(void) +{ +	device_remove_file(&lid_switch_idev->dev, &dev_attr_lid_wake_mode); +	input_unregister_device(lid_switch_idev); +} + +static int xo1_sci_probe(struct platform_device *pdev) +{ +	struct resource *res; +	int r; + +	/* don't run on non-XOs */ +	if (!machine_is_olpc()) +		return -ENODEV; + +	r = mfd_cell_enable(pdev); +	if (r) +		return r; + +	res = platform_get_resource(pdev, IORESOURCE_IO, 0); +	if (!res) { +		dev_err(&pdev->dev, "can't fetch device resource info\n"); +		return -EIO; +	} +	acpi_base = res->start; + +	r = setup_power_button(pdev); +	if (r) +		return r; + +	r = setup_ebook_switch(pdev); +	if (r) +		goto err_ebook; + +	r = setup_lid_switch(pdev); +	if (r) +		goto err_lid; + +	r = setup_lid_events(); +	if (r) +		goto err_lidevt; + +	r = setup_ec_sci(); +	if (r) +		goto err_ecsci; + +	/* Enable PME generation for EC-generated events */ +	outl(CS5536_GPIOM6_PME_EN | CS5536_GPIOM7_PME_EN, +		acpi_base + CS5536_PM_GPE0_EN); + +	/* Clear pending events */ +	outl(0xffffffff, acpi_base + CS5536_PM_GPE0_STS); +	process_sci_queue(false); + +	/* Initial sync */ +	send_ebook_state(); +	detect_lid_state(); +	send_lid_state(); + +	r = setup_sci_interrupt(pdev); +	if (r) +		goto err_sci; + +	/* Enable all EC events */ +	olpc_ec_mask_write(EC_SCI_SRC_ALL); + +	return r; + +err_sci: +	free_ec_sci(); +err_ecsci: +	free_lid_events(); +err_lidevt: +	free_lid_switch(); +err_lid: +	free_ebook_switch(); +err_ebook: +	free_power_button(); +	return r; +} + +static int xo1_sci_remove(struct platform_device *pdev) +{ +	mfd_cell_disable(pdev); +	free_irq(sci_irq, pdev); +	cancel_work_sync(&sci_work); +	free_ec_sci(); +	free_lid_events(); +	free_lid_switch(); +	free_ebook_switch(); +	free_power_button(); +	acpi_base = 0; +	return 0; +} + +static struct platform_driver xo1_sci_driver = { +	.driver = { +		.name = "olpc-xo1-sci-acpi", +	}, +	.probe = xo1_sci_probe, +	.remove = xo1_sci_remove, +	.suspend = xo1_sci_suspend, +	.resume = xo1_sci_resume, +}; + +static int __init xo1_sci_init(void) +{ +	return platform_driver_register(&xo1_sci_driver); +} +arch_initcall(xo1_sci_init); diff --git a/arch/x86/platform/olpc/olpc-xo1.c b/arch/x86/platform/olpc/olpc-xo1.c deleted file mode 100644 index f5442c03abc..00000000000 --- a/arch/x86/platform/olpc/olpc-xo1.c +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Support for features of the OLPC XO-1 laptop - * - * Copyright (C) 2010 One Laptop per Child - * Copyright (C) 2006 Red Hat, Inc. - * Copyright (C) 2006 Advanced Micro Devices, Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - */ - -#include <linux/module.h> -#include <linux/pci.h> -#include <linux/pci_ids.h> -#include <linux/platform_device.h> -#include <linux/pm.h> - -#include <asm/io.h> -#include <asm/olpc.h> - -#define DRV_NAME "olpc-xo1" - -#define PMS_BAR		4 -#define ACPI_BAR	5 - -/* PMC registers (PMS block) */ -#define PM_SCLK		0x10 -#define PM_IN_SLPCTL	0x20 -#define PM_WKXD		0x34 -#define PM_WKD		0x30 -#define PM_SSC		0x54 - -/* PM registers (ACPI block) */ -#define PM1_CNT		0x08 -#define PM_GPE0_STS	0x18 - -static unsigned long acpi_base; -static unsigned long pms_base; - -static void xo1_power_off(void) -{ -	printk(KERN_INFO "OLPC XO-1 power off sequence...\n"); - -	/* Enable all of these controls with 0 delay */ -	outl(0x40000000, pms_base + PM_SCLK); -	outl(0x40000000, pms_base + PM_IN_SLPCTL); -	outl(0x40000000, pms_base + PM_WKXD); -	outl(0x40000000, pms_base + PM_WKD); - -	/* Clear status bits (possibly unnecessary) */ -	outl(0x0002ffff, pms_base  + PM_SSC); -	outl(0xffffffff, acpi_base + PM_GPE0_STS); - -	/* Write SLP_EN bit to start the machinery */ -	outl(0x00002000, acpi_base + PM1_CNT); -} - -/* Read the base addresses from the PCI BAR info */ -static int __devinit setup_bases(struct pci_dev *pdev) -{ -	int r; - -	r = pci_enable_device_io(pdev); -	if (r) { -		dev_err(&pdev->dev, "can't enable device IO\n"); -		return r; -	} - -	r = pci_request_region(pdev, ACPI_BAR, DRV_NAME); -	if (r) { -		dev_err(&pdev->dev, "can't alloc PCI BAR #%d\n", ACPI_BAR); -		return r; -	} - -	r = pci_request_region(pdev, PMS_BAR, DRV_NAME); -	if (r) { -		dev_err(&pdev->dev, "can't alloc PCI BAR #%d\n", PMS_BAR); -		pci_release_region(pdev, ACPI_BAR); -		return r; -	} - -	acpi_base = pci_resource_start(pdev, ACPI_BAR); -	pms_base = pci_resource_start(pdev, PMS_BAR); - -	return 0; -} - -static int __devinit olpc_xo1_probe(struct platform_device *pdev) -{ -	struct pci_dev *pcidev; -	int r; - -	pcidev = pci_get_device(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CS5536_ISA, -				NULL); -	if (!pdev) -		return -ENODEV; - -	r = setup_bases(pcidev); -	if (r) -		return r; - -	pm_power_off = xo1_power_off; - -	printk(KERN_INFO "OLPC XO-1 support registered\n"); -	return 0; -} - -static int __devexit olpc_xo1_remove(struct platform_device *pdev) -{ -	pm_power_off = NULL; -	return 0; -} - -static struct platform_driver olpc_xo1_driver = { -	.driver = { -		.name = DRV_NAME, -		.owner = THIS_MODULE, -	}, -	.probe = olpc_xo1_probe, -	.remove = __devexit_p(olpc_xo1_remove), -}; - -static int __init olpc_xo1_init(void) -{ -	return platform_driver_register(&olpc_xo1_driver); -} - -static void __exit olpc_xo1_exit(void) -{ -	platform_driver_unregister(&olpc_xo1_driver); -} - -MODULE_AUTHOR("Daniel Drake <dsd@laptop.org>"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:olpc-xo1"); - -module_init(olpc_xo1_init); -module_exit(olpc_xo1_exit); diff --git a/arch/x86/platform/olpc/olpc-xo15-sci.c b/arch/x86/platform/olpc/olpc-xo15-sci.c new file mode 100644 index 00000000000..08e350e757d --- /dev/null +++ b/arch/x86/platform/olpc/olpc-xo15-sci.c @@ -0,0 +1,233 @@ +/* + * Support for OLPC XO-1.5 System Control Interrupts (SCI) + * + * Copyright (C) 2009-2010 One Laptop per Child + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/power_supply.h> +#include <linux/olpc-ec.h> + +#include <linux/acpi.h> +#include <asm/olpc.h> + +#define DRV_NAME			"olpc-xo15-sci" +#define PFX				DRV_NAME ": " +#define XO15_SCI_CLASS			DRV_NAME +#define XO15_SCI_DEVICE_NAME		"OLPC XO-1.5 SCI" + +static unsigned long			xo15_sci_gpe; +static bool				lid_wake_on_close; + +/* + * The normal ACPI LID wakeup behavior is wake-on-open, but not + * wake-on-close. This is implemented as standard by the XO-1.5 DSDT. + * + * We provide here a sysfs attribute that will additionally enable + * wake-on-close behavior. This is useful (e.g.) when we oportunistically + * suspend with the display running; if the lid is then closed, we want to + * wake up to turn the display off. + * + * This is controlled through a custom method in the XO-1.5 DSDT. + */ +static int set_lid_wake_behavior(bool wake_on_close) +{ +	acpi_status status; + +	status = acpi_execute_simple_method(NULL, "\\_SB.PCI0.LID.LIDW", wake_on_close); +	if (ACPI_FAILURE(status)) { +		pr_warning(PFX "failed to set lid behavior\n"); +		return 1; +	} + +	lid_wake_on_close = wake_on_close; + +	return 0; +} + +static ssize_t +lid_wake_on_close_show(struct kobject *s, struct kobj_attribute *attr, char *buf) +{ +	return sprintf(buf, "%u\n", lid_wake_on_close); +} + +static ssize_t lid_wake_on_close_store(struct kobject *s, +				       struct kobj_attribute *attr, +				       const char *buf, size_t n) +{ +	unsigned int val; + +	if (sscanf(buf, "%u", &val) != 1) +		return -EINVAL; + +	set_lid_wake_behavior(!!val); + +	return n; +} + +static struct kobj_attribute lid_wake_on_close_attr = +	__ATTR(lid_wake_on_close, 0644, +	       lid_wake_on_close_show, +	       lid_wake_on_close_store); + +static void battery_status_changed(void) +{ +	struct power_supply *psy = power_supply_get_by_name("olpc-battery"); + +	if (psy) { +		power_supply_changed(psy); +		put_device(psy->dev); +	} +} + +static void ac_status_changed(void) +{ +	struct power_supply *psy = power_supply_get_by_name("olpc-ac"); + +	if (psy) { +		power_supply_changed(psy); +		put_device(psy->dev); +	} +} + +static void process_sci_queue(void) +{ +	u16 data; +	int r; + +	do { +		r = olpc_ec_sci_query(&data); +		if (r || !data) +			break; + +		pr_debug(PFX "SCI 0x%x received\n", data); + +		switch (data) { +		case EC_SCI_SRC_BATERR: +		case EC_SCI_SRC_BATSOC: +		case EC_SCI_SRC_BATTERY: +		case EC_SCI_SRC_BATCRIT: +			battery_status_changed(); +			break; +		case EC_SCI_SRC_ACPWR: +			ac_status_changed(); +			break; +		} +	} while (data); + +	if (r) +		pr_err(PFX "Failed to clear SCI queue"); +} + +static void process_sci_queue_work(struct work_struct *work) +{ +	process_sci_queue(); +} + +static DECLARE_WORK(sci_work, process_sci_queue_work); + +static u32 xo15_sci_gpe_handler(acpi_handle gpe_device, u32 gpe, void *context) +{ +	schedule_work(&sci_work); +	return ACPI_INTERRUPT_HANDLED | ACPI_REENABLE_GPE; +} + +static int xo15_sci_add(struct acpi_device *device) +{ +	unsigned long long tmp; +	acpi_status status; +	int r; + +	if (!device) +		return -EINVAL; + +	strcpy(acpi_device_name(device), XO15_SCI_DEVICE_NAME); +	strcpy(acpi_device_class(device), XO15_SCI_CLASS); + +	/* Get GPE bit assignment (EC events). */ +	status = acpi_evaluate_integer(device->handle, "_GPE", NULL, &tmp); +	if (ACPI_FAILURE(status)) +		return -EINVAL; + +	xo15_sci_gpe = tmp; +	status = acpi_install_gpe_handler(NULL, xo15_sci_gpe, +					  ACPI_GPE_EDGE_TRIGGERED, +					  xo15_sci_gpe_handler, device); +	if (ACPI_FAILURE(status)) +		return -ENODEV; + +	dev_info(&device->dev, "Initialized, GPE = 0x%lx\n", xo15_sci_gpe); + +	r = sysfs_create_file(&device->dev.kobj, &lid_wake_on_close_attr.attr); +	if (r) +		goto err_sysfs; + +	/* Flush queue, and enable all SCI events */ +	process_sci_queue(); +	olpc_ec_mask_write(EC_SCI_SRC_ALL); + +	acpi_enable_gpe(NULL, xo15_sci_gpe); + +	/* Enable wake-on-EC */ +	if (device->wakeup.flags.valid) +		device_init_wakeup(&device->dev, true); + +	return 0; + +err_sysfs: +	acpi_remove_gpe_handler(NULL, xo15_sci_gpe, xo15_sci_gpe_handler); +	cancel_work_sync(&sci_work); +	return r; +} + +static int xo15_sci_remove(struct acpi_device *device) +{ +	acpi_disable_gpe(NULL, xo15_sci_gpe); +	acpi_remove_gpe_handler(NULL, xo15_sci_gpe, xo15_sci_gpe_handler); +	cancel_work_sync(&sci_work); +	sysfs_remove_file(&device->dev.kobj, &lid_wake_on_close_attr.attr); +	return 0; +} + +static int xo15_sci_resume(struct device *dev) +{ +	/* Enable all EC events */ +	olpc_ec_mask_write(EC_SCI_SRC_ALL); + +	/* Power/battery status might have changed */ +	battery_status_changed(); +	ac_status_changed(); + +	return 0; +} + +static SIMPLE_DEV_PM_OPS(xo15_sci_pm, NULL, xo15_sci_resume); + +static const struct acpi_device_id xo15_sci_device_ids[] = { +	{"XO15EC", 0}, +	{"", 0}, +}; + +static struct acpi_driver xo15_sci_drv = { +	.name = DRV_NAME, +	.class = XO15_SCI_CLASS, +	.ids = xo15_sci_device_ids, +	.ops = { +		.add = xo15_sci_add, +		.remove = xo15_sci_remove, +	}, +	.drv.pm = &xo15_sci_pm, +}; + +static int __init xo15_sci_init(void) +{ +	return acpi_bus_register_driver(&xo15_sci_drv); +} +device_initcall(xo15_sci_init); diff --git a/arch/x86/platform/olpc/olpc.c b/arch/x86/platform/olpc/olpc.c index edaf3fe8dc5..27376081dde 100644 --- a/arch/x86/platform/olpc/olpc.c +++ b/arch/x86/platform/olpc/olpc.c @@ -14,10 +14,13 @@  #include <linux/init.h>  #include <linux/module.h>  #include <linux/delay.h> -#include <linux/spinlock.h>  #include <linux/io.h>  #include <linux/string.h>  #include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/syscore_ops.h> +#include <linux/mutex.h> +#include <linux/olpc-ec.h>  #include <asm/geode.h>  #include <asm/setup.h> @@ -27,7 +30,8 @@  struct olpc_platform_t olpc_platform_info;  EXPORT_SYMBOL_GPL(olpc_platform_info); -static DEFINE_SPINLOCK(ec_lock); +/* EC event mask to be applied during suspend (defining wakeup sources). */ +static u16 ec_wakeup_mask;  /* what the timeout *should* be (in ms) */  #define EC_BASE_TIMEOUT 20 @@ -109,16 +113,13 @@ static int __wait_on_obf(unsigned int line, unsigned int port, int desired)   * <http://wiki.laptop.org/go/Ec_specification>.  Unfortunately, while   * OpenFirmware's source is available, the EC's is not.   */ -int olpc_ec_cmd(unsigned char cmd, unsigned char *inbuf, size_t inlen, -		unsigned char *outbuf,  size_t outlen) +static int olpc_xo1_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *outbuf, +		size_t outlen, void *arg)  { -	unsigned long flags;  	int ret = -EIO;  	int i;  	int restarts = 0; -	spin_lock_irqsave(&ec_lock, flags); -  	/* Clear OBF */  	for (i = 0; i < 10 && (obf_status(0x6c) == 1); i++)  		inb(0x68); @@ -156,13 +157,13 @@ restart:  	if (inbuf && inlen) {  		/* write data to EC */  		for (i = 0; i < inlen; i++) { +			pr_devel("olpc-ec:  sending cmd arg 0x%x\n", inbuf[i]); +			outb(inbuf[i], 0x68);  			if (wait_on_ibf(0x6c, 0)) {  				printk(KERN_ERR "olpc-ec:  timeout waiting for"  						" EC accept data!\n");  				goto err;  			} -			pr_devel("olpc-ec:  sending cmd arg 0x%x\n", inbuf[i]); -			outb(inbuf[i], 0x68);  		}  	}  	if (outbuf && outlen) { @@ -182,46 +183,123 @@ restart:  	ret = 0;  err: -	spin_unlock_irqrestore(&ec_lock, flags);  	return ret;  } -EXPORT_SYMBOL_GPL(olpc_ec_cmd); -static bool __init check_ofw_architecture(void) +void olpc_ec_wakeup_set(u16 value)  { -	size_t propsize; -	char olpc_arch[5]; -	const void *args[] = { NULL, "architecture", olpc_arch, (void *)5 }; -	void *res[] = { &propsize }; +	ec_wakeup_mask |= value; +} +EXPORT_SYMBOL_GPL(olpc_ec_wakeup_set); -	if (olpc_ofw("getprop", args, res)) { -		printk(KERN_ERR "ofw: getprop call failed!\n"); +void olpc_ec_wakeup_clear(u16 value) +{ +	ec_wakeup_mask &= ~value; +} +EXPORT_SYMBOL_GPL(olpc_ec_wakeup_clear); + +/* + * Returns true if the compile and runtime configurations allow for EC events + * to wake the system. + */ +bool olpc_ec_wakeup_available(void) +{ +	if (!machine_is_olpc())  		return false; + +	/* +	 * XO-1 EC wakeups are available when olpc-xo1-sci driver is +	 * compiled in +	 */ +#ifdef CONFIG_OLPC_XO1_SCI +	if (olpc_platform_info.boardrev < olpc_board_pre(0xd0)) /* XO-1 */ +		return true; +#endif + +	/* +	 * XO-1.5 EC wakeups are available when olpc-xo15-sci driver is +	 * compiled in +	 */ +#ifdef CONFIG_OLPC_XO15_SCI +	if (olpc_platform_info.boardrev >= olpc_board_pre(0xd0)) /* XO-1.5 */ +		return true; +#endif + +	return false; +} +EXPORT_SYMBOL_GPL(olpc_ec_wakeup_available); + +int olpc_ec_mask_write(u16 bits) +{ +	if (olpc_platform_info.flags & OLPC_F_EC_WIDE_SCI) { +		__be16 ec_word = cpu_to_be16(bits); +		return olpc_ec_cmd(EC_WRITE_EXT_SCI_MASK, (void *) &ec_word, 2, +				   NULL, 0); +	} else { +		unsigned char ec_byte = bits & 0xff; +		return olpc_ec_cmd(EC_WRITE_SCI_MASK, &ec_byte, 1, NULL, 0);  	} -	return propsize == 5 && strncmp("OLPC", olpc_arch, 5) == 0;  } +EXPORT_SYMBOL_GPL(olpc_ec_mask_write); -static u32 __init get_board_revision(void) +int olpc_ec_sci_query(u16 *sci_value)  { -	size_t propsize; -	__be32 rev; -	const void *args[] = { NULL, "board-revision-int", &rev, (void *)4 }; -	void *res[] = { &propsize }; - -	if (olpc_ofw("getprop", args, res) || propsize != 4) { -		printk(KERN_ERR "ofw: getprop call failed!\n"); -		return cpu_to_be32(0); +	int ret; + +	if (olpc_platform_info.flags & OLPC_F_EC_WIDE_SCI) { +		__be16 ec_word; +		ret = olpc_ec_cmd(EC_EXT_SCI_QUERY, +			NULL, 0, (void *) &ec_word, 2); +		if (ret == 0) +			*sci_value = be16_to_cpu(ec_word); +	} else { +		unsigned char ec_byte; +		ret = olpc_ec_cmd(EC_SCI_QUERY, NULL, 0, &ec_byte, 1); +		if (ret == 0) +			*sci_value = ec_byte;  	} -	return be32_to_cpu(rev); + +	return ret; +} +EXPORT_SYMBOL_GPL(olpc_ec_sci_query); + +static bool __init check_ofw_architecture(struct device_node *root) +{ +	const char *olpc_arch; +	int propsize; + +	olpc_arch = of_get_property(root, "architecture", &propsize); +	return propsize == 5 && strncmp("OLPC", olpc_arch, 5) == 0; +} + +static u32 __init get_board_revision(struct device_node *root) +{ +	int propsize; +	const __be32 *rev; + +	rev = of_get_property(root, "board-revision-int", &propsize); +	if (propsize != 4) +		return 0; + +	return be32_to_cpu(*rev);  }  static bool __init platform_detect(void)  { -	if (!check_ofw_architecture()) +	struct device_node *root = of_find_node_by_path("/"); +	bool success; + +	if (!root)  		return false; -	olpc_platform_info.flags |= OLPC_F_PRESENT; -	olpc_platform_info.boardrev = get_board_revision(); -	return true; + +	success = check_ofw_architecture(root); +	if (success) { +		olpc_platform_info.boardrev = get_board_revision(root); +		olpc_platform_info.flags |= OLPC_F_PRESENT; +	} + +	of_node_put(root); +	return success;  }  static int __init add_xo1_platform_devices(void) @@ -239,6 +317,61 @@ static int __init add_xo1_platform_devices(void)  	return 0;  } +static int olpc_xo1_ec_probe(struct platform_device *pdev) +{ +	/* get the EC revision */ +	olpc_ec_cmd(EC_FIRMWARE_REV, NULL, 0, +			(unsigned char *) &olpc_platform_info.ecver, 1); + +	/* EC version 0x5f adds support for wide SCI mask */ +	if (olpc_platform_info.ecver >= 0x5f) +		olpc_platform_info.flags |= OLPC_F_EC_WIDE_SCI; + +	pr_info("OLPC board revision %s%X (EC=%x)\n", +			((olpc_platform_info.boardrev & 0xf) < 8) ? "pre" : "", +			olpc_platform_info.boardrev >> 4, +			olpc_platform_info.ecver); + +	return 0; +} +static int olpc_xo1_ec_suspend(struct platform_device *pdev) +{ +	olpc_ec_mask_write(ec_wakeup_mask); + +	/* +	 * Squelch SCIs while suspended.  This is a fix for +	 * <http://dev.laptop.org/ticket/1835>. +	 */ +	return olpc_ec_cmd(EC_SET_SCI_INHIBIT, NULL, 0, NULL, 0); +} + +static int olpc_xo1_ec_resume(struct platform_device *pdev) +{ +	/* Tell the EC to stop inhibiting SCIs */ +	olpc_ec_cmd(EC_SET_SCI_INHIBIT_RELEASE, NULL, 0, NULL, 0); + +	/* +	 * Tell the wireless module to restart USB communication. +	 * Must be done twice. +	 */ +	olpc_ec_cmd(EC_WAKE_UP_WLAN, NULL, 0, NULL, 0); +	olpc_ec_cmd(EC_WAKE_UP_WLAN, NULL, 0, NULL, 0); + +	return 0; +} + +static struct olpc_ec_driver ec_xo1_driver = { +	.probe = olpc_xo1_ec_probe, +	.suspend = olpc_xo1_ec_suspend, +	.resume = olpc_xo1_ec_resume, +	.ec_cmd = olpc_xo1_ec_cmd, +}; + +static struct olpc_ec_driver ec_xo1_5_driver = { +	.probe = olpc_xo1_ec_probe, +	.ec_cmd = olpc_xo1_ec_cmd, +}; +  static int __init olpc_init(void)  {  	int r = 0; @@ -246,16 +379,17 @@ static int __init olpc_init(void)  	if (!olpc_ofw_present() || !platform_detect())  		return 0; -	spin_lock_init(&ec_lock); +	/* register the XO-1 and 1.5-specific EC handler */ +	if (olpc_platform_info.boardrev < olpc_board_pre(0xd0))	/* XO-1 */ +		olpc_ec_driver_register(&ec_xo1_driver, NULL); +	else +		olpc_ec_driver_register(&ec_xo1_5_driver, NULL); +	platform_device_register_simple("olpc-ec", -1, NULL, 0);  	/* assume B1 and above models always have a DCON */  	if (olpc_board_at_least(olpc_board(0xb1)))  		olpc_platform_info.flags |= OLPC_F_DCON; -	/* get the EC revision */ -	olpc_ec_cmd(EC_FIRMWARE_REV, NULL, 0, -			(unsigned char *) &olpc_platform_info.ecver, 1); -  #ifdef CONFIG_PCI_OLPC  	/* If the VSA exists let it emulate PCI, if not emulate in kernel.  	 * XO-1 only. */ @@ -264,11 +398,6 @@ static int __init olpc_init(void)  		x86_init.pci.arch_init = pci_olpc_init;  #endif -	printk(KERN_INFO "OLPC board revision %s%X (EC=%x)\n", -			((olpc_platform_info.boardrev & 0xf) < 8) ? "pre" : "", -			olpc_platform_info.boardrev >> 4, -			olpc_platform_info.ecver); -  	if (olpc_platform_info.boardrev < olpc_board_pre(0xd0)) { /* XO-1 */  		r = add_xo1_platform_devices();  		if (r) diff --git a/arch/x86/platform/olpc/olpc_dt.c b/arch/x86/platform/olpc/olpc_dt.c new file mode 100644 index 00000000000..d6ee9298692 --- /dev/null +++ b/arch/x86/platform/olpc/olpc_dt.c @@ -0,0 +1,304 @@ +/* + * OLPC-specific OFW device tree support code. + * + * Paul Mackerras	August 1996. + * Copyright (C) 1996-2005 Paul Mackerras. + * + *  Adapted for 64bit PowerPC by Dave Engebretsen and Peter Bergner. + *    {engebret|bergner}@us.ibm.com + * + *  Adapted for sparc by David S. Miller davem@davemloft.net + *  Adapted for x86/OLPC by Andres Salomon <dilinger@queued.net> + * + *      This program is free software; you can redistribute it and/or + *      modify it under the terms of the GNU General Public License + *      as published by the Free Software Foundation; either version + *      2 of the License, or (at your option) any later version. + */ + +#include <linux/kernel.h> +#include <linux/bootmem.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/of_pdt.h> +#include <asm/olpc.h> +#include <asm/olpc_ofw.h> + +static phandle __init olpc_dt_getsibling(phandle node) +{ +	const void *args[] = { (void *)node }; +	void *res[] = { &node }; + +	if ((s32)node == -1) +		return 0; + +	if (olpc_ofw("peer", args, res) || (s32)node == -1) +		return 0; + +	return node; +} + +static phandle __init olpc_dt_getchild(phandle node) +{ +	const void *args[] = { (void *)node }; +	void *res[] = { &node }; + +	if ((s32)node == -1) +		return 0; + +	if (olpc_ofw("child", args, res) || (s32)node == -1) { +		pr_err("PROM: %s: fetching child failed!\n", __func__); +		return 0; +	} + +	return node; +} + +static int __init olpc_dt_getproplen(phandle node, const char *prop) +{ +	const void *args[] = { (void *)node, prop }; +	int len; +	void *res[] = { &len }; + +	if ((s32)node == -1) +		return -1; + +	if (olpc_ofw("getproplen", args, res)) { +		pr_err("PROM: %s: getproplen failed!\n", __func__); +		return -1; +	} + +	return len; +} + +static int __init olpc_dt_getproperty(phandle node, const char *prop, +		char *buf, int bufsize) +{ +	int plen; + +	plen = olpc_dt_getproplen(node, prop); +	if (plen > bufsize || plen < 1) { +		return -1; +	} else { +		const void *args[] = { (void *)node, prop, buf, (void *)plen }; +		void *res[] = { &plen }; + +		if (olpc_ofw("getprop", args, res)) { +			pr_err("PROM: %s: getprop failed!\n", __func__); +			return -1; +		} +	} + +	return plen; +} + +static int __init olpc_dt_nextprop(phandle node, char *prev, char *buf) +{ +	const void *args[] = { (void *)node, prev, buf }; +	int success; +	void *res[] = { &success }; + +	buf[0] = '\0'; + +	if ((s32)node == -1) +		return -1; + +	if (olpc_ofw("nextprop", args, res) || success != 1) +		return -1; + +	return 0; +} + +static int __init olpc_dt_pkg2path(phandle node, char *buf, +		const int buflen, int *len) +{ +	const void *args[] = { (void *)node, buf, (void *)buflen }; +	void *res[] = { len }; + +	if ((s32)node == -1) +		return -1; + +	if (olpc_ofw("package-to-path", args, res) || *len < 1) +		return -1; + +	return 0; +} + +static unsigned int prom_early_allocated __initdata; + +void * __init prom_early_alloc(unsigned long size) +{ +	static u8 *mem; +	static size_t free_mem; +	void *res; + +	if (free_mem < size) { +		const size_t chunk_size = max(PAGE_SIZE, size); + +		/* +		 * To mimimize the number of allocations, grab at least +		 * PAGE_SIZE of memory (that's an arbitrary choice that's +		 * fast enough on the platforms we care about while minimizing +		 * wasted bootmem) and hand off chunks of it to callers. +		 */ +		res = alloc_bootmem(chunk_size); +		BUG_ON(!res); +		prom_early_allocated += chunk_size; +		memset(res, 0, chunk_size); +		free_mem = chunk_size; +		mem = res; +	} + +	/* allocate from the local cache */ +	free_mem -= size; +	res = mem; +	mem += size; +	return res; +} + +static struct of_pdt_ops prom_olpc_ops __initdata = { +	.nextprop = olpc_dt_nextprop, +	.getproplen = olpc_dt_getproplen, +	.getproperty = olpc_dt_getproperty, +	.getchild = olpc_dt_getchild, +	.getsibling = olpc_dt_getsibling, +	.pkg2path = olpc_dt_pkg2path, +}; + +static phandle __init olpc_dt_finddevice(const char *path) +{ +	phandle node; +	const void *args[] = { path }; +	void *res[] = { &node }; + +	if (olpc_ofw("finddevice", args, res)) { +		pr_err("olpc_dt: finddevice failed!\n"); +		return 0; +	} + +	if ((s32) node == -1) +		return 0; + +	return node; +} + +static int __init olpc_dt_interpret(const char *words) +{ +	int result; +	const void *args[] = { words }; +	void *res[] = { &result }; + +	if (olpc_ofw("interpret", args, res)) { +		pr_err("olpc_dt: interpret failed!\n"); +		return -1; +	} + +	return result; +} + +/* + * Extract board revision directly from OFW device tree. + * We can't use olpc_platform_info because that hasn't been set up yet. + */ +static u32 __init olpc_dt_get_board_revision(void) +{ +	phandle node; +	__be32 rev; +	int r; + +	node = olpc_dt_finddevice("/"); +	if (!node) +		return 0; + +	r = olpc_dt_getproperty(node, "board-revision-int", +				(char *) &rev, sizeof(rev)); +	if (r < 0) +		return 0; + +	return be32_to_cpu(rev); +} + +void __init olpc_dt_fixup(void) +{ +	int r; +	char buf[64]; +	phandle node; +	u32 board_rev; + +	node = olpc_dt_finddevice("/battery@0"); +	if (!node) +		return; + +	/* +	 * If the battery node has a compatible property, we are running a new +	 * enough firmware and don't have fixups to make. +	 */ +	r = olpc_dt_getproperty(node, "compatible", buf, sizeof(buf)); +	if (r > 0) +		return; + +	pr_info("PROM DT: Old firmware detected, applying fixes\n"); + +	/* Add olpc,xo1-battery compatible marker to battery node */ +	olpc_dt_interpret("\" /battery@0\" find-device" +		" \" olpc,xo1-battery\" +compatible" +		" device-end"); + +	board_rev = olpc_dt_get_board_revision(); +	if (!board_rev) +		return; + +	if (board_rev >= olpc_board_pre(0xd0)) { +		/* XO-1.5: add dcon device */ +		olpc_dt_interpret("\" /pci/display@1\" find-device" +			" new-device" +			" \" dcon\" device-name \" olpc,xo1-dcon\" +compatible" +			" finish-device device-end"); +	} else { +		/* XO-1: add dcon device, mark RTC as olpc,xo1-rtc */ +		olpc_dt_interpret("\" /pci/display@1,1\" find-device" +			" new-device" +			" \" dcon\" device-name \" olpc,xo1-dcon\" +compatible" +			" finish-device device-end" +			" \" /rtc\" find-device" +			" \" olpc,xo1-rtc\" +compatible" +			" device-end"); +	} +} + +void __init olpc_dt_build_devicetree(void) +{ +	phandle root; + +	if (!olpc_ofw_is_installed()) +		return; + +	olpc_dt_fixup(); + +	root = olpc_dt_getsibling(0); +	if (!root) { +		pr_err("PROM: unable to get root node from OFW!\n"); +		return; +	} +	of_pdt_build_devicetree(root, &prom_olpc_ops); + +	pr_info("PROM DT: Built device tree with %u bytes of memory.\n", +			prom_early_allocated); +} + +/* A list of DT node/bus matches that we want to expose as platform devices */ +static struct of_device_id __initdata of_ids[] = { +	{ .compatible = "olpc,xo1-battery" }, +	{ .compatible = "olpc,xo1-dcon" }, +	{ .compatible = "olpc,xo1-rtc" }, +	{}, +}; + +static int __init olpc_create_platform_devices(void) +{ +	if (machine_is_olpc()) +		return of_platform_bus_probe(NULL, of_ids, NULL); +	else +		return 0; +} +device_initcall(olpc_create_platform_devices); diff --git a/arch/x86/platform/olpc/olpc_ofw.c b/arch/x86/platform/olpc/olpc_ofw.c index 78732046437..e7604f62870 100644 --- a/arch/x86/platform/olpc/olpc_ofw.c +++ b/arch/x86/platform/olpc/olpc_ofw.c @@ -110,3 +110,8 @@ void __init olpc_ofw_detect(void)  			(unsigned long)olpc_ofw_cif, (-start) >> 20);  	reserve_top_address(-start);  } + +bool __init olpc_ofw_is_installed(void) +{ +	return olpc_ofw_cif != NULL; +} diff --git a/arch/x86/platform/olpc/xo1-wakeup.S b/arch/x86/platform/olpc/xo1-wakeup.S new file mode 100644 index 00000000000..948deb28975 --- /dev/null +++ b/arch/x86/platform/olpc/xo1-wakeup.S @@ -0,0 +1,124 @@ +.text +#include <linux/linkage.h> +#include <asm/segment.h> +#include <asm/page.h> +#include <asm/pgtable_32.h> + +	.macro writepost,value +		movb $0x34, %al +		outb %al, $0x70 +		movb $\value, %al +		outb %al, $0x71 +	.endm + +wakeup_start: +	# OFW lands us here, running in protected mode, with a +	# kernel-compatible GDT already setup. + +	# Clear any dangerous flags +	pushl $0 +	popfl + +	writepost 0x31 + +	# Set up %cr3 +	movl $initial_page_table - __PAGE_OFFSET, %eax +	movl %eax, %cr3 + +	movl saved_cr4, %eax +	movl %eax, %cr4 + +	movl saved_cr0, %eax +	movl %eax, %cr0 + +	# Control registers were modified, pipeline resync is needed +	jmp 1f +1: + +	movw    $__KERNEL_DS, %ax +	movw    %ax, %ss +	movw    %ax, %ds +	movw    %ax, %es +	movw    %ax, %fs +	movw    %ax, %gs + +	lgdt    saved_gdt +	lidt    saved_idt +	lldt    saved_ldt +	ljmp    $(__KERNEL_CS),$1f +1: +	movl    %cr3, %eax +	movl    %eax, %cr3 +	wbinvd + +	# Go back to the return point +	jmp ret_point + +save_registers: +	sgdt  saved_gdt +	sidt  saved_idt +	sldt  saved_ldt + +	pushl %edx +	movl %cr4, %edx +	movl %edx, saved_cr4 + +	movl %cr0, %edx +	movl %edx, saved_cr0 + +	popl %edx + +	movl %ebx, saved_context_ebx +	movl %ebp, saved_context_ebp +	movl %esi, saved_context_esi +	movl %edi, saved_context_edi + +	pushfl +	popl saved_context_eflags + +	ret + +restore_registers: +	movl saved_context_ebp, %ebp +	movl saved_context_ebx, %ebx +	movl saved_context_esi, %esi +	movl saved_context_edi, %edi + +	pushl saved_context_eflags +	popfl + +	ret + +ENTRY(do_olpc_suspend_lowlevel) +	call	save_processor_state +	call	save_registers + +	# This is the stack context we want to remember +	movl %esp, saved_context_esp + +	pushl	$3 +	call	xo1_do_sleep + +	jmp	wakeup_start +	.p2align 4,,7 +ret_point: +	movl    saved_context_esp, %esp + +	writepost 0x32 + +	call	restore_registers +	call	restore_processor_state +	ret + +.data +saved_gdt:             .long   0,0 +saved_idt:             .long   0,0 +saved_ldt:             .long   0 +saved_cr4:             .long   0 +saved_cr0:             .long   0 +saved_context_esp:     .long   0 +saved_context_edi:     .long   0 +saved_context_esi:     .long   0 +saved_context_ebx:     .long   0 +saved_context_ebp:     .long   0 +saved_context_eflags:  .long   0 diff --git a/arch/x86/platform/scx200/scx200_32.c b/arch/x86/platform/scx200/scx200_32.c index 7e004acbe52..3dc9aee41d9 100644 --- a/arch/x86/platform/scx200/scx200_32.c +++ b/arch/x86/platform/scx200/scx200_32.c @@ -17,8 +17,6 @@  /* Verify that the configuration block really is there */  #define scx200_cb_probe(base) (inw((base) + SCx200_CBA) == (base)) -#define NAME "scx200" -  MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>");  MODULE_DESCRIPTION("NatSemi SCx200 Driver");  MODULE_LICENSE("GPL"); @@ -29,15 +27,15 @@ unsigned long scx200_gpio_shadow[2];  unsigned scx200_cb_base = 0;  static struct pci_device_id scx200_tbl[] = { -	{ PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_SCx200_BRIDGE) }, -	{ PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_SC1100_BRIDGE) }, -	{ PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_SCx200_XBUS)   }, -	{ PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_SC1100_XBUS)   }, +	{ PCI_VDEVICE(NS, PCI_DEVICE_ID_NS_SCx200_BRIDGE) }, +	{ PCI_VDEVICE(NS, PCI_DEVICE_ID_NS_SC1100_BRIDGE) }, +	{ PCI_VDEVICE(NS, PCI_DEVICE_ID_NS_SCx200_XBUS)   }, +	{ PCI_VDEVICE(NS, PCI_DEVICE_ID_NS_SC1100_XBUS)   },  	{ },  };  MODULE_DEVICE_TABLE(pci,scx200_tbl); -static int __devinit scx200_probe(struct pci_dev *, const struct pci_device_id *); +static int scx200_probe(struct pci_dev *, const struct pci_device_id *);  static struct pci_driver scx200_pci_driver = {  	.name = "scx200", @@ -47,7 +45,7 @@ static struct pci_driver scx200_pci_driver = {  static DEFINE_MUTEX(scx200_gpio_config_lock); -static void __devinit scx200_init_shadow(void) +static void scx200_init_shadow(void)  {  	int bank; @@ -56,17 +54,18 @@ static void __devinit scx200_init_shadow(void)  		scx200_gpio_shadow[bank] = inl(scx200_gpio_base + 0x10 * bank);  } -static int __devinit scx200_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +static int scx200_probe(struct pci_dev *pdev, const struct pci_device_id *ent)  {  	unsigned base;  	if (pdev->device == PCI_DEVICE_ID_NS_SCx200_BRIDGE ||  	    pdev->device == PCI_DEVICE_ID_NS_SC1100_BRIDGE) {  		base = pci_resource_start(pdev, 0); -		printk(KERN_INFO NAME ": GPIO base 0x%x\n", base); +		pr_info("GPIO base 0x%x\n", base); -		if (!request_region(base, SCx200_GPIO_SIZE, "NatSemi SCx200 GPIO")) { -			printk(KERN_ERR NAME ": can't allocate I/O for GPIOs\n"); +		if (!request_region(base, SCx200_GPIO_SIZE, +				    "NatSemi SCx200 GPIO")) { +			pr_err("can't allocate I/O for GPIOs\n");  			return -EBUSY;  		} @@ -82,11 +81,11 @@ static int __devinit scx200_probe(struct pci_dev *pdev, const struct pci_device_  			if (scx200_cb_probe(base)) {  				scx200_cb_base = base;  			} else { -				printk(KERN_WARNING NAME ": Configuration Block not found\n"); +				pr_warn("Configuration Block not found\n");  				return -ENODEV;  			}  		} -		printk(KERN_INFO NAME ": Configuration Block base 0x%x\n", scx200_cb_base); +		pr_info("Configuration Block base 0x%x\n", scx200_cb_base);  	}  	return 0; @@ -111,8 +110,7 @@ u32 scx200_gpio_configure(unsigned index, u32 mask, u32 bits)  static int __init scx200_init(void)  { -	printk(KERN_INFO NAME ": NatSemi SCx200 Driver\n"); - +	pr_info("NatSemi SCx200 Driver\n");  	return pci_register_driver(&scx200_pci_driver);  } diff --git a/arch/x86/platform/sfi/sfi.c b/arch/x86/platform/sfi/sfi.c index dd4c281ffe5..bcd1a703e3e 100644 --- a/arch/x86/platform/sfi/sfi.c +++ b/arch/x86/platform/sfi/sfi.c @@ -34,23 +34,12 @@  #ifdef CONFIG_X86_LOCAL_APIC  static unsigned long sfi_lapic_addr __initdata = APIC_DEFAULT_PHYS_BASE; -static void __init mp_sfi_register_lapic_address(unsigned long address) -{ -	mp_lapic_addr = address; - -	set_fixmap_nocache(FIX_APIC_BASE, mp_lapic_addr); -	if (boot_cpu_physical_apicid == -1U) -		boot_cpu_physical_apicid = read_apic_id(); - -	pr_info("Boot CPU = %d\n", boot_cpu_physical_apicid); -} -  /* All CPUs enumerated by SFI must be present and enabled */ -static void __cpuinit mp_sfi_register_lapic(u8 id) +static void __init mp_sfi_register_lapic(u8 id)  { -	if (MAX_APICS - id <= 0) { +	if (MAX_LOCAL_APIC - id <= 0) {  		pr_warning("Processor #%d invalid (max %d)\n", -			id, MAX_APICS); +			id, MAX_LOCAL_APIC);  		return;  	} @@ -110,7 +99,7 @@ static int __init sfi_parse_ioapic(struct sfi_table_header *table)  int __init sfi_platform_init(void)  {  #ifdef CONFIG_X86_LOCAL_APIC -	mp_sfi_register_lapic_address(sfi_lapic_addr); +	register_lapic_address(sfi_lapic_addr);  	sfi_table_parse(SFI_SIG_CPUS, NULL, NULL, sfi_parse_cpus);  #endif  #ifdef CONFIG_X86_IO_APIC diff --git a/arch/x86/platform/ts5500/Makefile b/arch/x86/platform/ts5500/Makefile new file mode 100644 index 00000000000..c54e348c96a --- /dev/null +++ b/arch/x86/platform/ts5500/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_TS5500)	+= ts5500.o diff --git a/arch/x86/platform/ts5500/ts5500.c b/arch/x86/platform/ts5500/ts5500.c new file mode 100644 index 00000000000..9471b9456f2 --- /dev/null +++ b/arch/x86/platform/ts5500/ts5500.c @@ -0,0 +1,339 @@ +/* + * Technologic Systems TS-5500 Single Board Computer support + * + * Copyright (C) 2013 Savoir-faire Linux Inc. + *	Vivien Didelot <vivien.didelot@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * + * This driver registers the Technologic Systems TS-5500 Single Board Computer + * (SBC) and its devices, and exposes information to userspace such as jumpers' + * state or available options. For further information about sysfs entries, see + * Documentation/ABI/testing/sysfs-platform-ts5500. + * + * This code actually supports the TS-5500 platform, but it may be extended to + * support similar Technologic Systems x86-based platforms, such as the TS-5600. + */ + +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/platform_data/gpio-ts5500.h> +#include <linux/platform_data/max197.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +/* Product code register */ +#define TS5500_PRODUCT_CODE_ADDR	0x74 +#define TS5500_PRODUCT_CODE		0x60	/* TS-5500 product code */ + +/* SRAM/RS-485/ADC options, and RS-485 RTS/Automatic RS-485 flags register */ +#define TS5500_SRAM_RS485_ADC_ADDR	0x75 +#define TS5500_SRAM			BIT(0)	/* SRAM option */ +#define TS5500_RS485			BIT(1)	/* RS-485 option */ +#define TS5500_ADC			BIT(2)	/* A/D converter option */ +#define TS5500_RS485_RTS		BIT(6)	/* RTS for RS-485 */ +#define TS5500_RS485_AUTO		BIT(7)	/* Automatic RS-485 */ + +/* External Reset/Industrial Temperature Range options register */ +#define TS5500_ERESET_ITR_ADDR		0x76 +#define TS5500_ERESET			BIT(0)	/* External Reset option */ +#define TS5500_ITR			BIT(1)	/* Indust. Temp. Range option */ + +/* LED/Jumpers register */ +#define TS5500_LED_JP_ADDR		0x77 +#define TS5500_LED			BIT(0)	/* LED flag */ +#define TS5500_JP1			BIT(1)	/* Automatic CMOS */ +#define TS5500_JP2			BIT(2)	/* Enable Serial Console */ +#define TS5500_JP3			BIT(3)	/* Write Enable Drive A */ +#define TS5500_JP4			BIT(4)	/* Fast Console (115K baud) */ +#define TS5500_JP5			BIT(5)	/* User Jumper */ +#define TS5500_JP6			BIT(6)	/* Console on COM1 (req. JP2) */ +#define TS5500_JP7			BIT(7)	/* Undocumented (Unused) */ + +/* A/D Converter registers */ +#define TS5500_ADC_CONV_BUSY_ADDR	0x195	/* Conversion state register */ +#define TS5500_ADC_CONV_BUSY		BIT(0) +#define TS5500_ADC_CONV_INIT_LSB_ADDR	0x196	/* Start conv. / LSB register */ +#define TS5500_ADC_CONV_MSB_ADDR	0x197	/* MSB register */ +#define TS5500_ADC_CONV_DELAY		12	/* usec */ + +/** + * struct ts5500_sbc - TS-5500 board description + * @id:		Board product ID. + * @sram:	Flag for SRAM option. + * @rs485:	Flag for RS-485 option. + * @adc:	Flag for Analog/Digital converter option. + * @ereset:	Flag for External Reset option. + * @itr:	Flag for Industrial Temperature Range option. + * @jumpers:	Bitfield for jumpers' state. + */ +struct ts5500_sbc { +	int	id; +	bool	sram; +	bool	rs485; +	bool	adc; +	bool	ereset; +	bool	itr; +	u8	jumpers; +}; + +/* Board signatures in BIOS shadow RAM */ +static const struct { +	const char * const string; +	const ssize_t offset; +} ts5500_signatures[] __initconst = { +	{ "TS-5x00 AMD Elan", 0xb14 }, +}; + +static int __init ts5500_check_signature(void) +{ +	void __iomem *bios; +	int i, ret = -ENODEV; + +	bios = ioremap(0xf0000, 0x10000); +	if (!bios) +		return -ENOMEM; + +	for (i = 0; i < ARRAY_SIZE(ts5500_signatures); i++) { +		if (check_signature(bios + ts5500_signatures[i].offset, +				    ts5500_signatures[i].string, +				    strlen(ts5500_signatures[i].string))) { +			ret = 0; +			break; +		} +	} + +	iounmap(bios); +	return ret; +} + +static int __init ts5500_detect_config(struct ts5500_sbc *sbc) +{ +	u8 tmp; +	int ret = 0; + +	if (!request_region(TS5500_PRODUCT_CODE_ADDR, 4, "ts5500")) +		return -EBUSY; + +	tmp = inb(TS5500_PRODUCT_CODE_ADDR); +	if (tmp != TS5500_PRODUCT_CODE) { +		pr_err("This platform is not a TS-5500 (found ID 0x%x)\n", tmp); +		ret = -ENODEV; +		goto cleanup; +	} +	sbc->id = tmp; + +	tmp = inb(TS5500_SRAM_RS485_ADC_ADDR); +	sbc->sram = tmp & TS5500_SRAM; +	sbc->rs485 = tmp & TS5500_RS485; +	sbc->adc = tmp & TS5500_ADC; + +	tmp = inb(TS5500_ERESET_ITR_ADDR); +	sbc->ereset = tmp & TS5500_ERESET; +	sbc->itr = tmp & TS5500_ITR; + +	tmp = inb(TS5500_LED_JP_ADDR); +	sbc->jumpers = tmp & ~TS5500_LED; + +cleanup: +	release_region(TS5500_PRODUCT_CODE_ADDR, 4); +	return ret; +} + +static ssize_t ts5500_show_id(struct device *dev, +			      struct device_attribute *attr, char *buf) +{ +	struct ts5500_sbc *sbc = dev_get_drvdata(dev); + +	return sprintf(buf, "0x%.2x\n", sbc->id); +} + +static ssize_t ts5500_show_jumpers(struct device *dev, +				   struct device_attribute *attr, +				   char *buf) +{ +	struct ts5500_sbc *sbc = dev_get_drvdata(dev); + +	return sprintf(buf, "0x%.2x\n", sbc->jumpers >> 1); +} + +#define TS5500_SHOW(field)					\ +	static ssize_t ts5500_show_##field(struct device *dev,	\ +			struct device_attribute *attr,		\ +			char *buf)				\ +	{							\ +		struct ts5500_sbc *sbc = dev_get_drvdata(dev);	\ +		return sprintf(buf, "%d\n", sbc->field);	\ +	} + +TS5500_SHOW(sram) +TS5500_SHOW(rs485) +TS5500_SHOW(adc) +TS5500_SHOW(ereset) +TS5500_SHOW(itr) + +static DEVICE_ATTR(id, S_IRUGO, ts5500_show_id, NULL); +static DEVICE_ATTR(jumpers, S_IRUGO, ts5500_show_jumpers, NULL); +static DEVICE_ATTR(sram, S_IRUGO, ts5500_show_sram, NULL); +static DEVICE_ATTR(rs485, S_IRUGO, ts5500_show_rs485, NULL); +static DEVICE_ATTR(adc, S_IRUGO, ts5500_show_adc, NULL); +static DEVICE_ATTR(ereset, S_IRUGO, ts5500_show_ereset, NULL); +static DEVICE_ATTR(itr, S_IRUGO, ts5500_show_itr, NULL); + +static struct attribute *ts5500_attributes[] = { +	&dev_attr_id.attr, +	&dev_attr_jumpers.attr, +	&dev_attr_sram.attr, +	&dev_attr_rs485.attr, +	&dev_attr_adc.attr, +	&dev_attr_ereset.attr, +	&dev_attr_itr.attr, +	NULL +}; + +static const struct attribute_group ts5500_attr_group = { +	.attrs = ts5500_attributes, +}; + +static struct resource ts5500_dio1_resource[] = { +	DEFINE_RES_IRQ_NAMED(7, "DIO1 interrupt"), +}; + +static struct platform_device ts5500_dio1_pdev = { +	.name = "ts5500-dio1", +	.id = -1, +	.resource = ts5500_dio1_resource, +	.num_resources = 1, +}; + +static struct resource ts5500_dio2_resource[] = { +	DEFINE_RES_IRQ_NAMED(6, "DIO2 interrupt"), +}; + +static struct platform_device ts5500_dio2_pdev = { +	.name = "ts5500-dio2", +	.id = -1, +	.resource = ts5500_dio2_resource, +	.num_resources = 1, +}; + +static void ts5500_led_set(struct led_classdev *led_cdev, +			   enum led_brightness brightness) +{ +	outb(!!brightness, TS5500_LED_JP_ADDR); +} + +static enum led_brightness ts5500_led_get(struct led_classdev *led_cdev) +{ +	return (inb(TS5500_LED_JP_ADDR) & TS5500_LED) ? LED_FULL : LED_OFF; +} + +static struct led_classdev ts5500_led_cdev = { +	.name = "ts5500:green:", +	.brightness_set = ts5500_led_set, +	.brightness_get = ts5500_led_get, +}; + +static int ts5500_adc_convert(u8 ctrl) +{ +	u8 lsb, msb; + +	/* Start conversion (ensure the 3 MSB are set to 0) */ +	outb(ctrl & 0x1f, TS5500_ADC_CONV_INIT_LSB_ADDR); + +	/* +	 * The platform has CPLD logic driving the A/D converter. +	 * The conversion must complete within 11 microseconds, +	 * otherwise we have to re-initiate a conversion. +	 */ +	udelay(TS5500_ADC_CONV_DELAY); +	if (inb(TS5500_ADC_CONV_BUSY_ADDR) & TS5500_ADC_CONV_BUSY) +		return -EBUSY; + +	/* Read the raw data */ +	lsb = inb(TS5500_ADC_CONV_INIT_LSB_ADDR); +	msb = inb(TS5500_ADC_CONV_MSB_ADDR); + +	return (msb << 8) | lsb; +} + +static struct max197_platform_data ts5500_adc_pdata = { +	.convert = ts5500_adc_convert, +}; + +static struct platform_device ts5500_adc_pdev = { +	.name = "max197", +	.id = -1, +	.dev = { +		.platform_data = &ts5500_adc_pdata, +	}, +}; + +static int __init ts5500_init(void) +{ +	struct platform_device *pdev; +	struct ts5500_sbc *sbc; +	int err; + +	/* +	 * There is no DMI available or PCI bridge subvendor info, +	 * only the BIOS provides a 16-bit identification call. +	 * It is safer to find a signature in the BIOS shadow RAM. +	 */ +	err = ts5500_check_signature(); +	if (err) +		return err; + +	pdev = platform_device_register_simple("ts5500", -1, NULL, 0); +	if (IS_ERR(pdev)) +		return PTR_ERR(pdev); + +	sbc = devm_kzalloc(&pdev->dev, sizeof(struct ts5500_sbc), GFP_KERNEL); +	if (!sbc) { +		err = -ENOMEM; +		goto error; +	} + +	err = ts5500_detect_config(sbc); +	if (err) +		goto error; + +	platform_set_drvdata(pdev, sbc); + +	err = sysfs_create_group(&pdev->dev.kobj, &ts5500_attr_group); +	if (err) +		goto error; + +	ts5500_dio1_pdev.dev.parent = &pdev->dev; +	if (platform_device_register(&ts5500_dio1_pdev)) +		dev_warn(&pdev->dev, "DIO1 block registration failed\n"); +	ts5500_dio2_pdev.dev.parent = &pdev->dev; +	if (platform_device_register(&ts5500_dio2_pdev)) +		dev_warn(&pdev->dev, "DIO2 block registration failed\n"); + +	if (led_classdev_register(&pdev->dev, &ts5500_led_cdev)) +		dev_warn(&pdev->dev, "LED registration failed\n"); + +	if (sbc->adc) { +		ts5500_adc_pdev.dev.parent = &pdev->dev; +		if (platform_device_register(&ts5500_adc_pdev)) +			dev_warn(&pdev->dev, "ADC registration failed\n"); +	} + +	return 0; +error: +	platform_device_unregister(pdev); +	return err; +} +device_initcall(ts5500_init); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Savoir-faire Linux Inc. <kernel@savoirfairelinux.com>"); +MODULE_DESCRIPTION("Technologic Systems TS-5500 platform driver"); diff --git a/arch/x86/platform/uv/Makefile b/arch/x86/platform/uv/Makefile index 6c40995fefb..52079bebd01 100644 --- a/arch/x86/platform/uv/Makefile +++ b/arch/x86/platform/uv/Makefile @@ -1 +1 @@ -obj-$(CONFIG_X86_UV)		+= tlb_uv.o bios_uv.o uv_irq.o uv_sysfs.o uv_time.o +obj-$(CONFIG_X86_UV)		+= tlb_uv.o bios_uv.o uv_irq.o uv_sysfs.o uv_time.o uv_nmi.o diff --git a/arch/x86/platform/uv/bios_uv.c b/arch/x86/platform/uv/bios_uv.c index 8bc57baaa9a..1584cbed0dc 100644 --- a/arch/x86/platform/uv/bios_uv.c +++ b/arch/x86/platform/uv/bios_uv.c @@ -20,6 +20,7 @@   */  #include <linux/efi.h> +#include <linux/export.h>  #include <asm/efi.h>  #include <linux/io.h>  #include <asm/uv/bios.h> @@ -38,7 +39,7 @@ s64 uv_bios_call(enum uv_bios_cmd which, u64 a1, u64 a2, u64 a3, u64 a4, u64 a5)  		 */  		return BIOS_STATUS_UNIMPLEMENTED; -	ret = efi_call6((void *)__va(tab->function), (u64)which, +	ret = efi_call((void *)__va(tab->function), (u64)which,  			a1, a2, a3, a4, a5);  	return ret;  } diff --git a/arch/x86/platform/uv/tlb_uv.c b/arch/x86/platform/uv/tlb_uv.c index a318194002b..dfe605ac1bc 100644 --- a/arch/x86/platform/uv/tlb_uv.c +++ b/arch/x86/platform/uv/tlb_uv.c @@ -1,7 +1,7 @@  /*   *	SGI UltraViolet TLB flush routines.   * - *	(c) 2008-2010 Cliff Wickman <cpw@sgi.com>, SGI. + *	(c) 2008-2012 Cliff Wickman <cpw@sgi.com>, SGI.   *   *	This code is released under the GNU General Public License version 2 or   *	later. @@ -11,6 +11,7 @@  #include <linux/debugfs.h>  #include <linux/kernel.h>  #include <linux/slab.h> +#include <linux/delay.h>  #include <asm/mmu_context.h>  #include <asm/uv/uv.h> @@ -34,27 +35,79 @@ static int timeout_base_ns[] = {  		5242880,  		167772160  }; +  static int timeout_us;  static int nobau; -static int baudisabled; -static spinlock_t disable_lock; +static int nobau_perm;  static cycles_t congested_cycles;  /* tunables: */ -static int max_bau_concurrent = MAX_BAU_CONCURRENT; -static int max_bau_concurrent_constant = MAX_BAU_CONCURRENT; -static int plugged_delay = PLUGGED_DELAY; -static int plugsb4reset = PLUGSB4RESET; -static int timeoutsb4reset = TIMEOUTSB4RESET; -static int ipi_reset_limit = IPI_RESET_LIMIT; -static int complete_threshold = COMPLETE_THRESHOLD; -static int congested_response_us = CONGESTED_RESPONSE_US; -static int congested_reps = CONGESTED_REPS; -static int congested_period = CONGESTED_PERIOD; +static int max_concurr		= MAX_BAU_CONCURRENT; +static int max_concurr_const	= MAX_BAU_CONCURRENT; +static int plugged_delay	= PLUGGED_DELAY; +static int plugsb4reset		= PLUGSB4RESET; +static int giveup_limit		= GIVEUP_LIMIT; +static int timeoutsb4reset	= TIMEOUTSB4RESET; +static int ipi_reset_limit	= IPI_RESET_LIMIT; +static int complete_threshold	= COMPLETE_THRESHOLD; +static int congested_respns_us	= CONGESTED_RESPONSE_US; +static int congested_reps	= CONGESTED_REPS; +static int disabled_period	= DISABLED_PERIOD; + +static struct tunables tunables[] = { +	{&max_concurr, MAX_BAU_CONCURRENT}, /* must be [0] */ +	{&plugged_delay, PLUGGED_DELAY}, +	{&plugsb4reset, PLUGSB4RESET}, +	{&timeoutsb4reset, TIMEOUTSB4RESET}, +	{&ipi_reset_limit, IPI_RESET_LIMIT}, +	{&complete_threshold, COMPLETE_THRESHOLD}, +	{&congested_respns_us, CONGESTED_RESPONSE_US}, +	{&congested_reps, CONGESTED_REPS}, +	{&disabled_period, DISABLED_PERIOD}, +	{&giveup_limit, GIVEUP_LIMIT} +}; +  static struct dentry *tunables_dir;  static struct dentry *tunables_file; -static int __init setup_nobau(char *arg) +/* these correspond to the statistics printed by ptc_seq_show() */ +static char *stat_description[] = { +	"sent:     number of shootdown messages sent", +	"stime:    time spent sending messages", +	"numuvhubs: number of hubs targeted with shootdown", +	"numuvhubs16: number times 16 or more hubs targeted", +	"numuvhubs8: number times 8 or more hubs targeted", +	"numuvhubs4: number times 4 or more hubs targeted", +	"numuvhubs2: number times 2 or more hubs targeted", +	"numuvhubs1: number times 1 hub targeted", +	"numcpus:  number of cpus targeted with shootdown", +	"dto:      number of destination timeouts", +	"retries:  destination timeout retries sent", +	"rok:   :  destination timeouts successfully retried", +	"resetp:   ipi-style resource resets for plugs", +	"resett:   ipi-style resource resets for timeouts", +	"giveup:   fall-backs to ipi-style shootdowns", +	"sto:      number of source timeouts", +	"bz:       number of stay-busy's", +	"throt:    number times spun in throttle", +	"swack:   image of UVH_LB_BAU_INTD_SOFTWARE_ACKNOWLEDGE", +	"recv:     shootdown messages received", +	"rtime:    time spent processing messages", +	"all:      shootdown all-tlb messages", +	"one:      shootdown one-tlb messages", +	"mult:     interrupts that found multiple messages", +	"none:     interrupts that found no messages", +	"retry:    number of retry messages processed", +	"canc:     number messages canceled by retries", +	"nocan:    number retries that found nothing to cancel", +	"reset:    number of ipi-style reset requests processed", +	"rcan:     number messages canceled by reset requests", +	"disable:  number times use of the BAU was disabled", +	"enable:   number times use of the BAU was re-enabled" +}; + +static int __init +setup_nobau(char *arg)  {  	nobau = 1;  	return 0; @@ -62,15 +115,46 @@ static int __init setup_nobau(char *arg)  early_param("nobau", setup_nobau);  /* base pnode in this partition */ -static int uv_partition_base_pnode __read_mostly; -/* position of pnode (which is nasid>>1): */ -static int uv_nshift __read_mostly; -static unsigned long uv_mmask __read_mostly; +static int uv_base_pnode __read_mostly;  static DEFINE_PER_CPU(struct ptc_stats, ptcstats);  static DEFINE_PER_CPU(struct bau_control, bau_control);  static DEFINE_PER_CPU(cpumask_var_t, uv_flush_tlb_mask); +static void +set_bau_on(void) +{ +	int cpu; +	struct bau_control *bcp; + +	if (nobau_perm) { +		pr_info("BAU not initialized; cannot be turned on\n"); +		return; +	} +	nobau = 0; +	for_each_present_cpu(cpu) { +		bcp = &per_cpu(bau_control, cpu); +		bcp->nobau = 0; +	} +	pr_info("BAU turned on\n"); +	return; +} + +static void +set_bau_off(void) +{ +	int cpu; +	struct bau_control *bcp; + +	nobau = 1; +	for_each_present_cpu(cpu) { +		bcp = &per_cpu(bau_control, cpu); +		bcp->nobau = 1; +	} +	pr_info("BAU turned off\n"); +	return; +} +  /*   * Determine the first node on a uvhub. 'Nodes' are used for kernel   * memory allocation. @@ -108,60 +192,53 @@ static int __init uvhub_to_first_apicid(int uvhub)   * clear of the Timeout bit (as well) will free the resource. No reply will   * be sent (the hardware will only do one reply per message).   */ -static inline void uv_reply_to_message(struct msg_desc *mdp, -				       struct bau_control *bcp) +static void reply_to_message(struct msg_desc *mdp, struct bau_control *bcp, +						int do_acknowledge)  {  	unsigned long dw; -	struct bau_payload_queue_entry *msg; +	struct bau_pq_entry *msg;  	msg = mdp->msg; -	if (!msg->canceled) { -		dw = (msg->sw_ack_vector << UV_SW_ACK_NPENDING) | -						msg->sw_ack_vector; -		uv_write_local_mmr( -				UVH_LB_BAU_INTD_SOFTWARE_ACKNOWLEDGE_ALIAS, dw); +	if (!msg->canceled && do_acknowledge) { +		dw = (msg->swack_vec << UV_SW_ACK_NPENDING) | msg->swack_vec; +		write_mmr_sw_ack(dw);  	}  	msg->replied_to = 1; -	msg->sw_ack_vector = 0; +	msg->swack_vec = 0;  }  /*   * Process the receipt of a RETRY message   */ -static inline void uv_bau_process_retry_msg(struct msg_desc *mdp, -					    struct bau_control *bcp) +static void bau_process_retry_msg(struct msg_desc *mdp, +					struct bau_control *bcp)  {  	int i;  	int cancel_count = 0; -	int slot2;  	unsigned long msg_res;  	unsigned long mmr = 0; -	struct bau_payload_queue_entry *msg; -	struct bau_payload_queue_entry *msg2; -	struct ptc_stats *stat; +	struct bau_pq_entry *msg = mdp->msg; +	struct bau_pq_entry *msg2; +	struct ptc_stats *stat = bcp->statp; -	msg = mdp->msg; -	stat = bcp->statp;  	stat->d_retries++;  	/*  	 * cancel any message from msg+1 to the retry itself  	 */  	for (msg2 = msg+1, i = 0; i < DEST_Q_SIZE; msg2++, i++) { -		if (msg2 > mdp->va_queue_last) -			msg2 = mdp->va_queue_first; +		if (msg2 > mdp->queue_last) +			msg2 = mdp->queue_first;  		if (msg2 == msg)  			break; -		/* same conditions for cancellation as uv_do_reset */ +		/* same conditions for cancellation as do_reset */  		if ((msg2->replied_to == 0) && (msg2->canceled == 0) && -		    (msg2->sw_ack_vector) && ((msg2->sw_ack_vector & -			msg->sw_ack_vector) == 0) && +		    (msg2->swack_vec) && ((msg2->swack_vec & +			msg->swack_vec) == 0) &&  		    (msg2->sending_cpu == msg->sending_cpu) &&  		    (msg2->msg_type != MSG_NOOP)) { -			slot2 = msg2 - mdp->va_queue_first; -			mmr = uv_read_local_mmr -				(UVH_LB_BAU_INTD_SOFTWARE_ACKNOWLEDGE); -			msg_res = msg2->sw_ack_vector; +			mmr = read_mmr_sw_ack(); +			msg_res = msg2->swack_vec;  			/*  			 * This is a message retry; clear the resources held  			 * by the previous message only if they timed out. @@ -169,17 +246,16 @@ static inline void uv_bau_process_retry_msg(struct msg_desc *mdp,  			 * situation to report.  			 */  			if (mmr & (msg_res << UV_SW_ACK_NPENDING)) { +				unsigned long mr;  				/* -				 * is the resource timed out? -				 * make everyone ignore the cancelled message. +				 * Is the resource timed out? +				 * Make everyone ignore the cancelled message.  				 */  				msg2->canceled = 1;  				stat->d_canceled++;  				cancel_count++; -				uv_write_local_mmr( -				    UVH_LB_BAU_INTD_SOFTWARE_ACKNOWLEDGE_ALIAS, -					(msg_res << UV_SW_ACK_NPENDING) | -					 msg_res); +				mr = (msg_res << UV_SW_ACK_NPENDING) | msg_res; +				write_mmr_sw_ack(mr);  			}  		}  	} @@ -191,20 +267,19 @@ static inline void uv_bau_process_retry_msg(struct msg_desc *mdp,   * Do all the things a cpu should do for a TLB shootdown message.   * Other cpu's may come here at the same time for this message.   */ -static void uv_bau_process_message(struct msg_desc *mdp, -				   struct bau_control *bcp) +static void bau_process_message(struct msg_desc *mdp, struct bau_control *bcp, +						int do_acknowledge)  { -	int msg_ack_count;  	short socket_ack_count = 0; -	struct ptc_stats *stat; -	struct bau_payload_queue_entry *msg; +	short *sp; +	struct atomic_short *asp; +	struct ptc_stats *stat = bcp->statp; +	struct bau_pq_entry *msg = mdp->msg;  	struct bau_control *smaster = bcp->socket_master;  	/*  	 * This must be a normal message, or retry of a normal message  	 */ -	msg = mdp->msg; -	stat = bcp->statp;  	if (msg->address == TLB_FLUSH_ALL) {  		local_flush_tlb();  		stat->d_alltlb++; @@ -221,30 +296,33 @@ static void uv_bau_process_message(struct msg_desc *mdp,  	 * cpu number.  	 */  	if (msg->msg_type == MSG_RETRY && bcp == bcp->uvhub_master) -		uv_bau_process_retry_msg(mdp, bcp); +		bau_process_retry_msg(mdp, bcp);  	/* -	 * This is a sw_ack message, so we have to reply to it. +	 * This is a swack message, so we have to reply to it.  	 * Count each responding cpu on the socket. This avoids  	 * pinging the count's cache line back and forth between  	 * the sockets.  	 */ -	socket_ack_count = atomic_add_short_return(1, (struct atomic_short *) -			&smaster->socket_acknowledge_count[mdp->msg_slot]); +	sp = &smaster->socket_acknowledge_count[mdp->msg_slot]; +	asp = (struct atomic_short *)sp; +	socket_ack_count = atom_asr(1, asp);  	if (socket_ack_count == bcp->cpus_in_socket) { +		int msg_ack_count;  		/*  		 * Both sockets dump their completed count total into  		 * the message's count.  		 */ -		smaster->socket_acknowledge_count[mdp->msg_slot] = 0; -		msg_ack_count = atomic_add_short_return(socket_ack_count, -				(struct atomic_short *)&msg->acknowledge_count); +		*sp = 0; +		asp = (struct atomic_short *)&msg->acknowledge_count; +		msg_ack_count = atom_asr(socket_ack_count, asp);  		if (msg_ack_count == bcp->cpus_in_uvhub) {  			/*  			 * All cpus in uvhub saw it; reply +			 * (unless we are in the UV2 workaround)  			 */ -			uv_reply_to_message(mdp, bcp); +			reply_to_message(mdp, bcp, do_acknowledge);  		}  	} @@ -252,14 +330,18 @@ static void uv_bau_process_message(struct msg_desc *mdp,  }  /* - * Determine the first cpu on a uvhub. + * Determine the first cpu on a pnode.   */ -static int uvhub_to_first_cpu(int uvhub) +static int pnode_to_first_cpu(int pnode, struct bau_control *smaster)  {  	int cpu; -	for_each_present_cpu(cpu) -		if (uvhub == uv_cpu_to_blade_id(cpu)) +	struct hub_and_pnode *hpp; + +	for_each_present_cpu(cpu) { +		hpp = &smaster->thp[cpu]; +		if (pnode == hpp->pnode)  			return cpu; +	}  	return -1;  } @@ -267,62 +349,51 @@ static int uvhub_to_first_cpu(int uvhub)   * Last resort when we get a large number of destination timeouts is   * to clear resources held by a given cpu.   * Do this with IPI so that all messages in the BAU message queue - * can be identified by their nonzero sw_ack_vector field. + * can be identified by their nonzero swack_vec field.   *   * This is entered for a single cpu on the uvhub.   * The sender want's this uvhub to free a specific message's - * sw_ack resources. + * swack resources.   */ -static void -uv_do_reset(void *ptr) +static void do_reset(void *ptr)  {  	int i; -	int slot; -	int count = 0; -	unsigned long mmr; -	unsigned long msg_res; -	struct bau_control *bcp; -	struct reset_args *rap; -	struct bau_payload_queue_entry *msg; -	struct ptc_stats *stat; +	struct bau_control *bcp = &per_cpu(bau_control, smp_processor_id()); +	struct reset_args *rap = (struct reset_args *)ptr; +	struct bau_pq_entry *msg; +	struct ptc_stats *stat = bcp->statp; -	bcp = &per_cpu(bau_control, smp_processor_id()); -	rap = (struct reset_args *)ptr; -	stat = bcp->statp;  	stat->d_resets++; -  	/*  	 * We're looking for the given sender, and -	 * will free its sw_ack resource. +	 * will free its swack resource.  	 * If all cpu's finally responded after the timeout, its  	 * message 'replied_to' was set.  	 */ -	for (msg = bcp->va_queue_first, i = 0; i < DEST_Q_SIZE; msg++, i++) { -		/* uv_do_reset: same conditions for cancellation as -		   uv_bau_process_retry_msg() */ +	for (msg = bcp->queue_first, i = 0; i < DEST_Q_SIZE; msg++, i++) { +		unsigned long msg_res; +		/* do_reset: same conditions for cancellation as +		   bau_process_retry_msg() */  		if ((msg->replied_to == 0) &&  		    (msg->canceled == 0) &&  		    (msg->sending_cpu == rap->sender) && -		    (msg->sw_ack_vector) && +		    (msg->swack_vec) &&  		    (msg->msg_type != MSG_NOOP)) { +			unsigned long mmr; +			unsigned long mr;  			/*  			 * make everyone else ignore this message  			 */  			msg->canceled = 1; -			slot = msg - bcp->va_queue_first; -			count++;  			/*  			 * only reset the resource if it is still pending  			 */ -			mmr = uv_read_local_mmr -					(UVH_LB_BAU_INTD_SOFTWARE_ACKNOWLEDGE); -			msg_res = msg->sw_ack_vector; +			mmr = read_mmr_sw_ack(); +			msg_res = msg->swack_vec; +			mr = (msg_res << UV_SW_ACK_NPENDING) | msg_res;  			if (mmr & msg_res) {  				stat->d_rcanceled++; -				uv_write_local_mmr( -				    UVH_LB_BAU_INTD_SOFTWARE_ACKNOWLEDGE_ALIAS, -					(msg_res << UV_SW_ACK_NPENDING) | -					 msg_res); +				write_mmr_sw_ack(mr);  			}  		}  	} @@ -333,41 +404,78 @@ uv_do_reset(void *ptr)   * Use IPI to get all target uvhubs to release resources held by   * a given sending cpu number.   */ -static void uv_reset_with_ipi(struct bau_target_uvhubmask *distribution, -			      int sender) +static void reset_with_ipi(struct pnmask *distribution, struct bau_control *bcp)  { -	int uvhub; -	int cpu; -	cpumask_t mask; +	int pnode; +	int apnode; +	int maskbits; +	int sender = bcp->cpu; +	cpumask_t *mask = bcp->uvhub_master->cpumask; +	struct bau_control *smaster = bcp->socket_master;  	struct reset_args reset_args;  	reset_args.sender = sender; - -	cpus_clear(mask); +	cpus_clear(*mask);  	/* find a single cpu for each uvhub in this distribution mask */ -	for (uvhub = 0; -		    uvhub < sizeof(struct bau_target_uvhubmask) * BITSPERBYTE; -		    uvhub++) { -		if (!bau_uvhub_isset(uvhub, distribution)) +	maskbits = sizeof(struct pnmask) * BITSPERBYTE; +	/* each bit is a pnode relative to the partition base pnode */ +	for (pnode = 0; pnode < maskbits; pnode++) { +		int cpu; +		if (!bau_uvhub_isset(pnode, distribution))  			continue; -		/* find a cpu for this uvhub */ -		cpu = uvhub_to_first_cpu(uvhub); -		cpu_set(cpu, mask); +		apnode = pnode + bcp->partition_base_pnode; +		cpu = pnode_to_first_cpu(apnode, smaster); +		cpu_set(cpu, *mask);  	} -	/* IPI all cpus; Preemption is already disabled */ -	smp_call_function_many(&mask, uv_do_reset, (void *)&reset_args, 1); + +	/* IPI all cpus; preemption is already disabled */ +	smp_call_function_many(mask, do_reset, (void *)&reset_args, 1);  	return;  } -static inline unsigned long -cycles_2_us(unsigned long long cyc) +/* + * Not to be confused with cycles_2_ns() from tsc.c; this gives a relative + * number, not an absolute. It converts a duration in cycles to a duration in + * ns. + */ +static inline unsigned long long cycles_2_ns(unsigned long long cyc)  { +	struct cyc2ns_data *data = cyc2ns_read_begin();  	unsigned long long ns; -	unsigned long us; -	ns =  (cyc * per_cpu(cyc2ns, smp_processor_id())) -						>> CYC2NS_SCALE_FACTOR; -	us = ns / 1000; -	return us; + +	ns = mul_u64_u32_shr(cyc, data->cyc2ns_mul, data->cyc2ns_shift); + +	cyc2ns_read_end(data); +	return ns; +} + +/* + * The reverse of the above; converts a duration in ns to a duration in cycles. + */  +static inline unsigned long long ns_2_cycles(unsigned long long ns) +{ +	struct cyc2ns_data *data = cyc2ns_read_begin(); +	unsigned long long cyc; + +	cyc = (ns << data->cyc2ns_shift) / data->cyc2ns_mul; + +	cyc2ns_read_end(data); +	return cyc; +} + +static inline unsigned long cycles_2_us(unsigned long long cyc) +{ +	return cycles_2_ns(cyc) / NSEC_PER_USEC; +} + +static inline cycles_t sec_2_cycles(unsigned long sec) +{ +	return ns_2_cycles(sec * NSEC_PER_SEC); +} + +static inline unsigned long long usec_2_cycles(unsigned long usec) +{ +	return ns_2_cycles(usec * NSEC_PER_USEC);  }  /* @@ -375,56 +483,56 @@ cycles_2_us(unsigned long long cyc)   * leaves uvhub_quiesce set so that no new broadcasts are started by   * bau_flush_send_and_wait()   */ -static inline void -quiesce_local_uvhub(struct bau_control *hmaster) +static inline void quiesce_local_uvhub(struct bau_control *hmaster)  { -	atomic_add_short_return(1, (struct atomic_short *) -		 &hmaster->uvhub_quiesce); +	atom_asr(1, (struct atomic_short *)&hmaster->uvhub_quiesce);  }  /*   * mark this quiet-requestor as done   */ -static inline void -end_uvhub_quiesce(struct bau_control *hmaster) +static inline void end_uvhub_quiesce(struct bau_control *hmaster)  { -	atomic_add_short_return(-1, (struct atomic_short *) -		&hmaster->uvhub_quiesce); +	atom_asr(-1, (struct atomic_short *)&hmaster->uvhub_quiesce); +} + +static unsigned long uv1_read_status(unsigned long mmr_offset, int right_shift) +{ +	unsigned long descriptor_status; + +	descriptor_status = uv_read_local_mmr(mmr_offset); +	descriptor_status >>= right_shift; +	descriptor_status &= UV_ACT_STATUS_MASK; +	return descriptor_status;  }  /*   * Wait for completion of a broadcast software ack message   * return COMPLETE, RETRY(PLUGGED or TIMEOUT) or GIVEUP   */ -static int uv_wait_completion(struct bau_desc *bau_desc, -	unsigned long mmr_offset, int right_shift, int this_cpu, -	struct bau_control *bcp, struct bau_control *smaster, long try) +static int uv1_wait_completion(struct bau_desc *bau_desc, +				unsigned long mmr_offset, int right_shift, +				struct bau_control *bcp, long try)  {  	unsigned long descriptor_status; -	cycles_t ttime; +	cycles_t ttm;  	struct ptc_stats *stat = bcp->statp; -	struct bau_control *hmaster; - -	hmaster = bcp->uvhub_master; +	descriptor_status = uv1_read_status(mmr_offset, right_shift);  	/* spin on the status MMR, waiting for it to go idle */ -	while ((descriptor_status = (((unsigned long) -		uv_read_local_mmr(mmr_offset) >> -			right_shift) & UV_ACT_STATUS_MASK)) != -			DESC_STATUS_IDLE) { +	while ((descriptor_status != DS_IDLE)) {  		/* -		 * Our software ack messages may be blocked because there are -		 * no swack resources available.  As long as none of them -		 * has timed out hardware will NACK our message and its -		 * state will stay IDLE. +		 * Our software ack messages may be blocked because +		 * there are no swack resources available.  As long +		 * as none of them has timed out hardware will NACK +		 * our message and its state will stay IDLE.  		 */ -		if (descriptor_status == DESC_STATUS_SOURCE_TIMEOUT) { +		if (descriptor_status == DS_SOURCE_TIMEOUT) {  			stat->s_stimeout++;  			return FLUSH_GIVEUP; -		} else if (descriptor_status == -					DESC_STATUS_DESTINATION_TIMEOUT) { +		} else if (descriptor_status == DS_DESTINATION_TIMEOUT) {  			stat->s_dtimeout++; -			ttime = get_cycles(); +			ttm = get_cycles();  			/*  			 * Our retries may be blocked by all destination @@ -432,8 +540,7 @@ static int uv_wait_completion(struct bau_desc *bau_desc,  			 * pending.  In that case hardware returns the  			 * ERROR that looks like a destination timeout.  			 */ -			if (cycles_2_us(ttime - bcp->send_message) < -							timeout_us) { +			if (cycles_2_us(ttm - bcp->send_message) < timeout_us) {  				bcp->conseccompletes = 0;  				return FLUSH_RETRY_PLUGGED;  			} @@ -446,117 +553,319 @@ static int uv_wait_completion(struct bau_desc *bau_desc,  			 */  			cpu_relax();  		} +		descriptor_status = uv1_read_status(mmr_offset, right_shift);  	}  	bcp->conseccompletes++;  	return FLUSH_COMPLETE;  } -static inline cycles_t -sec_2_cycles(unsigned long sec) +/* + * UV2 could have an extra bit of status in the ACTIVATION_STATUS_2 register. + * But not currently used. + */ +static unsigned long uv2_read_status(unsigned long offset, int rshft, int desc)  { -	unsigned long ns; -	cycles_t cyc; +	unsigned long descriptor_status; -	ns = sec * 1000000000; -	cyc = (ns << CYC2NS_SCALE_FACTOR)/(per_cpu(cyc2ns, smp_processor_id())); -	return cyc; +	descriptor_status = +		((read_lmmr(offset) >> rshft) & UV_ACT_STATUS_MASK) << 1; +	return descriptor_status;  }  /* - * conditionally add 1 to *v, unless *v is >= u - * return 0 if we cannot add 1 to *v because it is >= u - * return 1 if we can add 1 to *v because it is < u - * the add is atomic - * - * This is close to atomic_add_unless(), but this allows the 'u' value - * to be lowered below the current 'v'.  atomic_add_unless can only stop - * on equal. + * Return whether the status of the descriptor that is normally used for this + * cpu (the one indexed by its hub-relative cpu number) is busy. + * The status of the original 32 descriptors is always reflected in the 64 + * bits of UVH_LB_BAU_SB_ACTIVATION_STATUS_0. + * The bit provided by the activation_status_2 register is irrelevant to + * the status if it is only being tested for busy or not busy.   */ -static inline int atomic_inc_unless_ge(spinlock_t *lock, atomic_t *v, int u) +int normal_busy(struct bau_control *bcp)  { -	spin_lock(lock); -	if (atomic_read(v) >= u) { -		spin_unlock(lock); -		return 0; +	int cpu = bcp->uvhub_cpu; +	int mmr_offset; +	int right_shift; + +	mmr_offset = UVH_LB_BAU_SB_ACTIVATION_STATUS_0; +	right_shift = cpu * UV_ACT_STATUS_SIZE; +	return (((((read_lmmr(mmr_offset) >> right_shift) & +				UV_ACT_STATUS_MASK)) << 1) == UV2H_DESC_BUSY); +} + +/* + * Entered when a bau descriptor has gone into a permanent busy wait because + * of a hardware bug. + * Workaround the bug. + */ +int handle_uv2_busy(struct bau_control *bcp) +{ +	struct ptc_stats *stat = bcp->statp; + +	stat->s_uv2_wars++; +	bcp->busy = 1; +	return FLUSH_GIVEUP; +} + +static int uv2_wait_completion(struct bau_desc *bau_desc, +				unsigned long mmr_offset, int right_shift, +				struct bau_control *bcp, long try) +{ +	unsigned long descriptor_stat; +	cycles_t ttm; +	int desc = bcp->uvhub_cpu; +	long busy_reps = 0; +	struct ptc_stats *stat = bcp->statp; + +	descriptor_stat = uv2_read_status(mmr_offset, right_shift, desc); + +	/* spin on the status MMR, waiting for it to go idle */ +	while (descriptor_stat != UV2H_DESC_IDLE) { +		if ((descriptor_stat == UV2H_DESC_SOURCE_TIMEOUT)) { +			/* +			 * A h/w bug on the destination side may +			 * have prevented the message being marked +			 * pending, thus it doesn't get replied to +			 * and gets continually nacked until it times +			 * out with a SOURCE_TIMEOUT. +			 */ +			stat->s_stimeout++; +			return FLUSH_GIVEUP; +		} else if (descriptor_stat == UV2H_DESC_DEST_TIMEOUT) { +			ttm = get_cycles(); + +			/* +			 * Our retries may be blocked by all destination +			 * swack resources being consumed, and a timeout +			 * pending.  In that case hardware returns the +			 * ERROR that looks like a destination timeout. +			 * Without using the extended status we have to +			 * deduce from the short time that this was a +			 * strong nack. +			 */ +			if (cycles_2_us(ttm - bcp->send_message) < timeout_us) { +				bcp->conseccompletes = 0; +				stat->s_plugged++; +				/* FLUSH_RETRY_PLUGGED causes hang on boot */ +				return FLUSH_GIVEUP; +			} +			stat->s_dtimeout++; +			bcp->conseccompletes = 0; +			/* FLUSH_RETRY_TIMEOUT causes hang on boot */ +			return FLUSH_GIVEUP; +		} else { +			busy_reps++; +			if (busy_reps > 1000000) { +				/* not to hammer on the clock */ +				busy_reps = 0; +				ttm = get_cycles(); +				if ((ttm - bcp->send_message) > +						bcp->timeout_interval) +					return handle_uv2_busy(bcp); +			} +			/* +			 * descriptor_stat is still BUSY +			 */ +			cpu_relax(); +		} +		descriptor_stat = uv2_read_status(mmr_offset, right_shift, +									desc);  	} -	atomic_inc(v); -	spin_unlock(lock); -	return 1; +	bcp->conseccompletes++; +	return FLUSH_COMPLETE; +} + +/* + * There are 2 status registers; each and array[32] of 2 bits. Set up for + * which register to read and position in that register based on cpu in + * current hub. + */ +static int wait_completion(struct bau_desc *bau_desc, +				struct bau_control *bcp, long try) +{ +	int right_shift; +	unsigned long mmr_offset; +	int desc = bcp->uvhub_cpu; + +	if (desc < UV_CPUS_PER_AS) { +		mmr_offset = UVH_LB_BAU_SB_ACTIVATION_STATUS_0; +		right_shift = desc * UV_ACT_STATUS_SIZE; +	} else { +		mmr_offset = UVH_LB_BAU_SB_ACTIVATION_STATUS_1; +		right_shift = ((desc - UV_CPUS_PER_AS) * UV_ACT_STATUS_SIZE); +	} + +	if (bcp->uvhub_version == 1) +		return uv1_wait_completion(bau_desc, mmr_offset, right_shift, +								bcp, try); +	else +		return uv2_wait_completion(bau_desc, mmr_offset, right_shift, +								bcp, try);  }  /* - * Our retries are blocked by all destination swack resources being + * Our retries are blocked by all destination sw ack resources being   * in use, and a timeout is pending. In that case hardware immediately   * returns the ERROR that looks like a destination timeout.   */ -static void -destination_plugged(struct bau_desc *bau_desc, struct bau_control *bcp, +static void destination_plugged(struct bau_desc *bau_desc, +			struct bau_control *bcp,  			struct bau_control *hmaster, struct ptc_stats *stat)  {  	udelay(bcp->plugged_delay);  	bcp->plugged_tries++; +  	if (bcp->plugged_tries >= bcp->plugsb4reset) {  		bcp->plugged_tries = 0; +  		quiesce_local_uvhub(hmaster); +  		spin_lock(&hmaster->queue_lock); -		uv_reset_with_ipi(&bau_desc->distribution, bcp->cpu); +		reset_with_ipi(&bau_desc->distribution, bcp);  		spin_unlock(&hmaster->queue_lock); +  		end_uvhub_quiesce(hmaster); +  		bcp->ipi_attempts++;  		stat->s_resets_plug++;  	}  } -static void -destination_timeout(struct bau_desc *bau_desc, struct bau_control *bcp, -			struct bau_control *hmaster, struct ptc_stats *stat) +static void destination_timeout(struct bau_desc *bau_desc, +			struct bau_control *bcp, struct bau_control *hmaster, +			struct ptc_stats *stat)  { -	hmaster->max_bau_concurrent = 1; +	hmaster->max_concurr = 1;  	bcp->timeout_tries++;  	if (bcp->timeout_tries >= bcp->timeoutsb4reset) {  		bcp->timeout_tries = 0; +  		quiesce_local_uvhub(hmaster); +  		spin_lock(&hmaster->queue_lock); -		uv_reset_with_ipi(&bau_desc->distribution, bcp->cpu); +		reset_with_ipi(&bau_desc->distribution, bcp);  		spin_unlock(&hmaster->queue_lock); +  		end_uvhub_quiesce(hmaster); +  		bcp->ipi_attempts++;  		stat->s_resets_timeout++;  	}  }  /* - * Completions are taking a very long time due to a congested numalink - * network. + * Stop all cpus on a uvhub from using the BAU for a period of time. + * This is reversed by check_enable.   */ -static void -disable_for_congestion(struct bau_control *bcp, struct ptc_stats *stat) +static void disable_for_period(struct bau_control *bcp, struct ptc_stats *stat)  {  	int tcpu;  	struct bau_control *tbcp; +	struct bau_control *hmaster; +	cycles_t tm1; -	/* let only one cpu do this disabling */ -	spin_lock(&disable_lock); -	if (!baudisabled && bcp->period_requests && -	    ((bcp->period_time / bcp->period_requests) > congested_cycles)) { -		/* it becomes this cpu's job to turn on the use of the -		   BAU again */ -		baudisabled = 1; -		bcp->set_bau_off = 1; -		bcp->set_bau_on_time = get_cycles() + -			sec_2_cycles(bcp->congested_period); +	hmaster = bcp->uvhub_master; +	spin_lock(&hmaster->disable_lock); +	if (!bcp->baudisabled) {  		stat->s_bau_disabled++; +		tm1 = get_cycles();  		for_each_present_cpu(tcpu) {  			tbcp = &per_cpu(bau_control, tcpu); +			if (tbcp->uvhub_master == hmaster) {  				tbcp->baudisabled = 1; +				tbcp->set_bau_on_time = +					tm1 + bcp->disabled_period; +			}  		}  	} -	spin_unlock(&disable_lock); +	spin_unlock(&hmaster->disable_lock);  } -/** - * uv_flush_send_and_wait - * +static void count_max_concurr(int stat, struct bau_control *bcp, +				struct bau_control *hmaster) +{ +	bcp->plugged_tries = 0; +	bcp->timeout_tries = 0; +	if (stat != FLUSH_COMPLETE) +		return; +	if (bcp->conseccompletes <= bcp->complete_threshold) +		return; +	if (hmaster->max_concurr >= hmaster->max_concurr_const) +		return; +	hmaster->max_concurr++; +} + +static void record_send_stats(cycles_t time1, cycles_t time2, +		struct bau_control *bcp, struct ptc_stats *stat, +		int completion_status, int try) +{ +	cycles_t elapsed; + +	if (time2 > time1) { +		elapsed = time2 - time1; +		stat->s_time += elapsed; + +		if ((completion_status == FLUSH_COMPLETE) && (try == 1)) { +			bcp->period_requests++; +			bcp->period_time += elapsed; +			if ((elapsed > congested_cycles) && +			    (bcp->period_requests > bcp->cong_reps) && +			    ((bcp->period_time / bcp->period_requests) > +							congested_cycles)) { +				stat->s_congested++; +				disable_for_period(bcp, stat); +			} +		} +	} else +		stat->s_requestor--; + +	if (completion_status == FLUSH_COMPLETE && try > 1) +		stat->s_retriesok++; +	else if (completion_status == FLUSH_GIVEUP) { +		stat->s_giveup++; +		if (get_cycles() > bcp->period_end) +			bcp->period_giveups = 0; +		bcp->period_giveups++; +		if (bcp->period_giveups == 1) +			bcp->period_end = get_cycles() + bcp->disabled_period; +		if (bcp->period_giveups > bcp->giveup_limit) { +			disable_for_period(bcp, stat); +			stat->s_giveuplimit++; +		} +	} +} + +/* + * Because of a uv1 hardware bug only a limited number of concurrent + * requests can be made. + */ +static void uv1_throttle(struct bau_control *hmaster, struct ptc_stats *stat) +{ +	spinlock_t *lock = &hmaster->uvhub_lock; +	atomic_t *v; + +	v = &hmaster->active_descriptor_count; +	if (!atomic_inc_unless_ge(lock, v, hmaster->max_concurr)) { +		stat->s_throttles++; +		do { +			cpu_relax(); +		} while (!atomic_inc_unless_ge(lock, v, hmaster->max_concurr)); +	} +} + +/* + * Handle the completion status of a message send. + */ +static void handle_cmplt(int completion_status, struct bau_desc *bau_desc, +			struct bau_control *bcp, struct bau_control *hmaster, +			struct ptc_stats *stat) +{ +	if (completion_status == FLUSH_RETRY_PLUGGED) +		destination_plugged(bau_desc, bcp, hmaster, stat); +	else if (completion_status == FLUSH_RETRY_TIMEOUT) +		destination_timeout(bau_desc, bcp, hmaster, stat); +} + +/*   * Send a broadcast and wait for it to complete.   *   * The flush_mask contains the cpus the broadcast is to be sent to including @@ -566,115 +875,191 @@ disable_for_congestion(struct bau_control *bcp, struct ptc_stats *stat)   * Returns 1 if it gives up entirely and the original cpu mask is to be   * returned to the kernel.   */ -int uv_flush_send_and_wait(struct bau_desc *bau_desc, -			   struct cpumask *flush_mask, struct bau_control *bcp) +int uv_flush_send_and_wait(struct cpumask *flush_mask, struct bau_control *bcp, +	struct bau_desc *bau_desc)  { -	int right_shift; -	int completion_status = 0;  	int seq_number = 0; +	int completion_stat = 0; +	int uv1 = 0;  	long try = 0; -	int cpu = bcp->uvhub_cpu; -	int this_cpu = bcp->cpu; -	unsigned long mmr_offset;  	unsigned long index;  	cycles_t time1;  	cycles_t time2; -	cycles_t elapsed;  	struct ptc_stats *stat = bcp->statp; -	struct bau_control *smaster = bcp->socket_master;  	struct bau_control *hmaster = bcp->uvhub_master; +	struct uv1_bau_msg_header *uv1_hdr = NULL; +	struct uv2_bau_msg_header *uv2_hdr = NULL; -	if (!atomic_inc_unless_ge(&hmaster->uvhub_lock, -			&hmaster->active_descriptor_count, -			hmaster->max_bau_concurrent)) { -		stat->s_throttles++; -		do { -			cpu_relax(); -		} while (!atomic_inc_unless_ge(&hmaster->uvhub_lock, -			&hmaster->active_descriptor_count, -			hmaster->max_bau_concurrent)); +	if (bcp->uvhub_version == 1) { +		uv1 = 1; +		uv1_throttle(hmaster, stat);  	} +  	while (hmaster->uvhub_quiesce)  		cpu_relax(); -	if (cpu < UV_CPUS_PER_ACT_STATUS) { -		mmr_offset = UVH_LB_BAU_SB_ACTIVATION_STATUS_0; -		right_shift = cpu * UV_ACT_STATUS_SIZE; -	} else { -		mmr_offset = UVH_LB_BAU_SB_ACTIVATION_STATUS_1; -		right_shift = -		    ((cpu - UV_CPUS_PER_ACT_STATUS) * UV_ACT_STATUS_SIZE); -	}  	time1 = get_cycles(); +	if (uv1) +		uv1_hdr = &bau_desc->header.uv1_hdr; +	else +		uv2_hdr = &bau_desc->header.uv2_hdr; +  	do {  		if (try == 0) { -			bau_desc->header.msg_type = MSG_REGULAR; +			if (uv1) +				uv1_hdr->msg_type = MSG_REGULAR; +			else +				uv2_hdr->msg_type = MSG_REGULAR;  			seq_number = bcp->message_number++;  		} else { -			bau_desc->header.msg_type = MSG_RETRY; +			if (uv1) +				uv1_hdr->msg_type = MSG_RETRY; +			else +				uv2_hdr->msg_type = MSG_RETRY;  			stat->s_retry_messages++;  		} -		bau_desc->header.sequence = seq_number; -		index = (1UL << UVH_LB_BAU_SB_ACTIVATION_CONTROL_PUSH_SHFT) | -			bcp->uvhub_cpu; + +		if (uv1) +			uv1_hdr->sequence = seq_number; +		else +			uv2_hdr->sequence = seq_number; +		index = (1UL << AS_PUSH_SHIFT) | bcp->uvhub_cpu;  		bcp->send_message = get_cycles(); -		uv_write_local_mmr(UVH_LB_BAU_SB_ACTIVATION_CONTROL, index); + +		write_mmr_activation(index); +  		try++; -		completion_status = uv_wait_completion(bau_desc, mmr_offset, -			right_shift, this_cpu, bcp, smaster, try); +		completion_stat = wait_completion(bau_desc, bcp, try); + +		handle_cmplt(completion_stat, bau_desc, bcp, hmaster, stat); -		if (completion_status == FLUSH_RETRY_PLUGGED) { -			destination_plugged(bau_desc, bcp, hmaster, stat); -		} else if (completion_status == FLUSH_RETRY_TIMEOUT) { -			destination_timeout(bau_desc, bcp, hmaster, stat); -		}  		if (bcp->ipi_attempts >= bcp->ipi_reset_limit) {  			bcp->ipi_attempts = 0; -			completion_status = FLUSH_GIVEUP; +			stat->s_overipilimit++; +			completion_stat = FLUSH_GIVEUP;  			break;  		}  		cpu_relax(); -	} while ((completion_status == FLUSH_RETRY_PLUGGED) || -		 (completion_status == FLUSH_RETRY_TIMEOUT)); +	} while ((completion_stat == FLUSH_RETRY_PLUGGED) || +		 (completion_stat == FLUSH_RETRY_TIMEOUT)); +  	time2 = get_cycles(); -	bcp->plugged_tries = 0; -	bcp->timeout_tries = 0; -	if ((completion_status == FLUSH_COMPLETE) && -	    (bcp->conseccompletes > bcp->complete_threshold) && -	    (hmaster->max_bau_concurrent < -					hmaster->max_bau_concurrent_constant)) -			hmaster->max_bau_concurrent++; + +	count_max_concurr(completion_stat, bcp, hmaster); +  	while (hmaster->uvhub_quiesce)  		cpu_relax(); +  	atomic_dec(&hmaster->active_descriptor_count); -	if (time2 > time1) { -		elapsed = time2 - time1; -		stat->s_time += elapsed; -		if ((completion_status == FLUSH_COMPLETE) && (try == 1)) { -			bcp->period_requests++; -			bcp->period_time += elapsed; -			if ((elapsed > congested_cycles) && -			    (bcp->period_requests > bcp->congested_reps)) { -				disable_for_congestion(bcp, stat); + +	record_send_stats(time1, time2, bcp, stat, completion_stat, try); + +	if (completion_stat == FLUSH_GIVEUP) +		/* FLUSH_GIVEUP will fall back to using IPI's for tlb flush */ +		return 1; +	return 0; +} + +/* + * The BAU is disabled for this uvhub. When the disabled time period has + * expired re-enable it. + * Return 0 if it is re-enabled for all cpus on this uvhub. + */ +static int check_enable(struct bau_control *bcp, struct ptc_stats *stat) +{ +	int tcpu; +	struct bau_control *tbcp; +	struct bau_control *hmaster; + +	hmaster = bcp->uvhub_master; +	spin_lock(&hmaster->disable_lock); +	if (bcp->baudisabled && (get_cycles() >= bcp->set_bau_on_time)) { +		stat->s_bau_reenabled++; +		for_each_present_cpu(tcpu) { +			tbcp = &per_cpu(bau_control, tcpu); +			if (tbcp->uvhub_master == hmaster) { +				tbcp->baudisabled = 0; +				tbcp->period_requests = 0; +				tbcp->period_time = 0; +				tbcp->period_giveups = 0;  			}  		} +		spin_unlock(&hmaster->disable_lock); +		return 0; +	} +	spin_unlock(&hmaster->disable_lock); +	return -1; +} + +static void record_send_statistics(struct ptc_stats *stat, int locals, int hubs, +				int remotes, struct bau_desc *bau_desc) +{ +	stat->s_requestor++; +	stat->s_ntargcpu += remotes + locals; +	stat->s_ntargremotes += remotes; +	stat->s_ntarglocals += locals; + +	/* uvhub statistics */ +	hubs = bau_uvhub_weight(&bau_desc->distribution); +	if (locals) { +		stat->s_ntarglocaluvhub++; +		stat->s_ntargremoteuvhub += (hubs - 1);  	} else -		stat->s_requestor--; -	if (completion_status == FLUSH_COMPLETE && try > 1) -		stat->s_retriesok++; -	else if (completion_status == FLUSH_GIVEUP) { -		stat->s_giveup++; -		return 1; +		stat->s_ntargremoteuvhub += hubs; + +	stat->s_ntarguvhub += hubs; + +	if (hubs >= 16) +		stat->s_ntarguvhub16++; +	else if (hubs >= 8) +		stat->s_ntarguvhub8++; +	else if (hubs >= 4) +		stat->s_ntarguvhub4++; +	else if (hubs >= 2) +		stat->s_ntarguvhub2++; +	else +		stat->s_ntarguvhub1++; +} + +/* + * Translate a cpu mask to the uvhub distribution mask in the BAU + * activation descriptor. + */ +static int set_distrib_bits(struct cpumask *flush_mask, struct bau_control *bcp, +			struct bau_desc *bau_desc, int *localsp, int *remotesp) +{ +	int cpu; +	int pnode; +	int cnt = 0; +	struct hub_and_pnode *hpp; + +	for_each_cpu(cpu, flush_mask) { +		/* +		 * The distribution vector is a bit map of pnodes, relative +		 * to the partition base pnode (and the partition base nasid +		 * in the header). +		 * Translate cpu to pnode and hub using a local memory array. +		 */ +		hpp = &bcp->socket_master->thp[cpu]; +		pnode = hpp->pnode - bcp->partition_base_pnode; +		bau_uvhub_set(pnode, &bau_desc->distribution); +		cnt++; +		if (hpp->uvhub == bcp->uvhub) +			(*localsp)++; +		else +			(*remotesp)++;  	} +	if (!cnt) +		return 1;  	return 0;  } -/** - * uv_flush_tlb_others - globally purge translation cache of a virtual - * address or all TLB's +/* + * globally purge translation cache of a virtual address or all TLB's   * @cpumask: mask of all cpu's in which the address is to be removed   * @mm: mm_struct containing virtual address range - * @va: virtual address to be removed (or TLB_FLUSH_ALL for all TLB's on cpu) + * @start: start virtual address to be removed from TLB + * @end: end virtual address to be remove from TLB   * @cpu: the current cpu   *   * This is the entry point for initiating any UV global TLB shootdown. @@ -695,11 +1080,9 @@ int uv_flush_send_and_wait(struct bau_desc *bau_desc,   * done.  The returned pointer is valid till preemption is re-enabled.   */  const struct cpumask *uv_flush_tlb_others(const struct cpumask *cpumask, -					  struct mm_struct *mm, -					  unsigned long va, unsigned int cpu) +				struct mm_struct *mm, unsigned long start, +				unsigned long end, unsigned int cpu)  { -	int tcpu; -	int uvhub;  	int locals = 0;  	int remotes = 0;  	int hubs = 0; @@ -707,31 +1090,33 @@ const struct cpumask *uv_flush_tlb_others(const struct cpumask *cpumask,  	struct cpumask *flush_mask;  	struct ptc_stats *stat;  	struct bau_control *bcp; -	struct bau_control *tbcp; +	unsigned long descriptor_status; +	unsigned long status; + +	bcp = &per_cpu(bau_control, cpu); -	/* kernel was booted 'nobau' */ -	if (nobau) +	if (bcp->nobau)  		return cpumask; -	bcp = &per_cpu(bau_control, cpu);  	stat = bcp->statp; +	stat->s_enters++; + +	if (bcp->busy) { +		descriptor_status = +			read_lmmr(UVH_LB_BAU_SB_ACTIVATION_STATUS_0); +		status = ((descriptor_status >> (bcp->uvhub_cpu * +			UV_ACT_STATUS_SIZE)) & UV_ACT_STATUS_MASK) << 1; +		if (status == UV2H_DESC_BUSY) +			return cpumask; +		bcp->busy = 0; +	}  	/* bau was disabled due to slow response */  	if (bcp->baudisabled) { -		/* the cpu that disabled it must re-enable it */ -		if (bcp->set_bau_off) { -			if (get_cycles() >= bcp->set_bau_on_time) { -				stat->s_bau_reenabled++; -				baudisabled = 0; -				for_each_present_cpu(tcpu) { -					tbcp = &per_cpu(bau_control, tcpu); -					tbcp->baudisabled = 0; -					tbcp->period_requests = 0; -					tbcp->period_time = 0; -				} -			} +		if (check_enable(bcp, stat)) { +			stat->s_ipifordisabled++; +			return cpumask;  		} -		return cpumask;  	}  	/* @@ -742,63 +1127,107 @@ const struct cpumask *uv_flush_tlb_others(const struct cpumask *cpumask,  	flush_mask = (struct cpumask *)per_cpu(uv_flush_tlb_mask, cpu);  	/* don't actually do a shootdown of the local cpu */  	cpumask_andnot(flush_mask, cpumask, cpumask_of(cpu)); +  	if (cpu_isset(cpu, *cpumask))  		stat->s_ntargself++;  	bau_desc = bcp->descriptor_base; -	bau_desc += UV_ITEMS_PER_DESCRIPTOR * bcp->uvhub_cpu; +	bau_desc += (ITEMS_PER_DESC * bcp->uvhub_cpu);  	bau_uvhubs_clear(&bau_desc->distribution, UV_DISTRIBUTION_SIZE); - -	/* cpu statistics */ -	for_each_cpu(tcpu, flush_mask) { -		uvhub = uv_cpu_to_blade_id(tcpu); -		bau_uvhub_set(uvhub, &bau_desc->distribution); -		if (uvhub == bcp->uvhub) -			locals++; -		else -			remotes++; -	} -	if ((locals + remotes) == 0) +	if (set_distrib_bits(flush_mask, bcp, bau_desc, &locals, &remotes))  		return NULL; -	stat->s_requestor++; -	stat->s_ntargcpu += remotes + locals; -	stat->s_ntargremotes += remotes; -	stat->s_ntarglocals += locals; -	remotes = bau_uvhub_weight(&bau_desc->distribution); -	/* uvhub statistics */ -	hubs = bau_uvhub_weight(&bau_desc->distribution); -	if (locals) { -		stat->s_ntarglocaluvhub++; -		stat->s_ntargremoteuvhub += (hubs - 1); -	} else -		stat->s_ntargremoteuvhub += hubs; -	stat->s_ntarguvhub += hubs; -	if (hubs >= 16) -		stat->s_ntarguvhub16++; -	else if (hubs >= 8) -		stat->s_ntarguvhub8++; -	else if (hubs >= 4) -		stat->s_ntarguvhub4++; -	else if (hubs >= 2) -		stat->s_ntarguvhub2++; -	else -		stat->s_ntarguvhub1++; +	record_send_statistics(stat, locals, hubs, remotes, bau_desc); -	bau_desc->payload.address = va; +	if (!end || (end - start) <= PAGE_SIZE) +		bau_desc->payload.address = start; +	else +		bau_desc->payload.address = TLB_FLUSH_ALL;  	bau_desc->payload.sending_cpu = cpu; -  	/*  	 * uv_flush_send_and_wait returns 0 if all cpu's were messaged,  	 * or 1 if it gave up and the original cpumask should be returned.  	 */ -	if (!uv_flush_send_and_wait(bau_desc, flush_mask, bcp)) +	if (!uv_flush_send_and_wait(flush_mask, bcp, bau_desc))  		return NULL;  	else  		return cpumask;  }  /* + * Search the message queue for any 'other' unprocessed message with the + * same software acknowledge resource bit vector as the 'msg' message. + */ +struct bau_pq_entry *find_another_by_swack(struct bau_pq_entry *msg, +					   struct bau_control *bcp) +{ +	struct bau_pq_entry *msg_next = msg + 1; +	unsigned char swack_vec = msg->swack_vec; + +	if (msg_next > bcp->queue_last) +		msg_next = bcp->queue_first; +	while (msg_next != msg) { +		if ((msg_next->canceled == 0) && (msg_next->replied_to == 0) && +				(msg_next->swack_vec == swack_vec)) +			return msg_next; +		msg_next++; +		if (msg_next > bcp->queue_last) +			msg_next = bcp->queue_first; +	} +	return NULL; +} + +/* + * UV2 needs to work around a bug in which an arriving message has not + * set a bit in the UVH_LB_BAU_INTD_SOFTWARE_ACKNOWLEDGE register. + * Such a message must be ignored. + */ +void process_uv2_message(struct msg_desc *mdp, struct bau_control *bcp) +{ +	unsigned long mmr_image; +	unsigned char swack_vec; +	struct bau_pq_entry *msg = mdp->msg; +	struct bau_pq_entry *other_msg; + +	mmr_image = read_mmr_sw_ack(); +	swack_vec = msg->swack_vec; + +	if ((swack_vec & mmr_image) == 0) { +		/* +		 * This message was assigned a swack resource, but no +		 * reserved acknowlegment is pending. +		 * The bug has prevented this message from setting the MMR. +		 */ +		/* +		 * Some message has set the MMR 'pending' bit; it might have +		 * been another message.  Look for that message. +		 */ +		other_msg = find_another_by_swack(msg, bcp); +		if (other_msg) { +			/* +			 * There is another. Process this one but do not +			 * ack it. +			 */ +			bau_process_message(mdp, bcp, 0); +			/* +			 * Let the natural processing of that other message +			 * acknowledge it. Don't get the processing of sw_ack's +			 * out of order. +			 */ +			return; +		} +	} + +	/* +	 * Either the MMR shows this one pending a reply or there is no +	 * other message using this sw_ack, so it is safe to acknowledge it. +	 */ +	bau_process_message(mdp, bcp, 1); + +	return; +} + +/*   * The BAU message interrupt comes here. (registered by set_intr_gate)   * See entry_64.S   * @@ -816,26 +1245,34 @@ void uv_bau_message_interrupt(struct pt_regs *regs)  {  	int count = 0;  	cycles_t time_start; -	struct bau_payload_queue_entry *msg; +	struct bau_pq_entry *msg;  	struct bau_control *bcp;  	struct ptc_stats *stat;  	struct msg_desc msgdesc; +	ack_APIC_irq();  	time_start = get_cycles(); +  	bcp = &per_cpu(bau_control, smp_processor_id());  	stat = bcp->statp; -	msgdesc.va_queue_first = bcp->va_queue_first; -	msgdesc.va_queue_last = bcp->va_queue_last; + +	msgdesc.queue_first = bcp->queue_first; +	msgdesc.queue_last = bcp->queue_last; +  	msg = bcp->bau_msg_head; -	while (msg->sw_ack_vector) { +	while (msg->swack_vec) {  		count++; -		msgdesc.msg_slot = msg - msgdesc.va_queue_first; -		msgdesc.sw_ack_slot = ffs(msg->sw_ack_vector) - 1; + +		msgdesc.msg_slot = msg - msgdesc.queue_first;  		msgdesc.msg = msg; -		uv_bau_process_message(&msgdesc, bcp); +		if (bcp->uvhub_version == 2) +			process_uv2_message(&msgdesc, bcp); +		else +			bau_process_message(&msgdesc, bcp, 1); +  		msg++; -		if (msg > msgdesc.va_queue_last) -			msg = msgdesc.va_queue_first; +		if (msg > msgdesc.queue_last) +			msg = msgdesc.queue_first;  		bcp->bau_msg_head = msg;  	}  	stat->d_time += (get_cycles() - time_start); @@ -843,18 +1280,15 @@ void uv_bau_message_interrupt(struct pt_regs *regs)  		stat->d_nomsg++;  	else if (count > 1)  		stat->d_multmsg++; -	ack_APIC_irq();  }  /* - * uv_enable_timeouts - * - * Each target uvhub (i.e. a uvhub that has no cpu's) needs to have + * Each target uvhub (i.e. a uvhub that has cpu's) needs to have   * shootdown message timeouts enabled.  The timeout does not cause   * an interrupt, but causes an error message to be returned to   * the sender.   */ -static void uv_enable_timeouts(void) +static void __init enable_timeouts(void)  {  	int uvhub;  	int nuvhubs; @@ -868,47 +1302,44 @@ static void uv_enable_timeouts(void)  			continue;  		pnode = uv_blade_to_pnode(uvhub); -		mmr_image = -		    uv_read_global_mmr64(pnode, UVH_LB_BAU_MISC_CONTROL); +		mmr_image = read_mmr_misc_control(pnode);  		/*  		 * Set the timeout period and then lock it in, in three  		 * steps; captures and locks in the period.  		 *  		 * To program the period, the SOFT_ACK_MODE must be off.  		 */ -		mmr_image &= ~((unsigned long)1 << -		    UVH_LB_BAU_MISC_CONTROL_ENABLE_INTD_SOFT_ACK_MODE_SHFT); -		uv_write_global_mmr64 -		    (pnode, UVH_LB_BAU_MISC_CONTROL, mmr_image); +		mmr_image &= ~(1L << SOFTACK_MSHIFT); +		write_mmr_misc_control(pnode, mmr_image);  		/*  		 * Set the 4-bit period.  		 */ -		mmr_image &= ~((unsigned long)0xf << -		     UVH_LB_BAU_MISC_CONTROL_INTD_SOFT_ACK_TIMEOUT_PERIOD_SHFT); -		mmr_image |= (UV_INTD_SOFT_ACK_TIMEOUT_PERIOD << -		     UVH_LB_BAU_MISC_CONTROL_INTD_SOFT_ACK_TIMEOUT_PERIOD_SHFT); -		uv_write_global_mmr64 -		    (pnode, UVH_LB_BAU_MISC_CONTROL, mmr_image); +		mmr_image &= ~((unsigned long)0xf << SOFTACK_PSHIFT); +		mmr_image |= (SOFTACK_TIMEOUT_PERIOD << SOFTACK_PSHIFT); +		write_mmr_misc_control(pnode, mmr_image);  		/* +		 * UV1:  		 * Subsequent reversals of the timebase bit (3) cause an  		 * immediate timeout of one or all INTD resources as  		 * indicated in bits 2:0 (7 causes all of them to timeout).  		 */ -		mmr_image |= ((unsigned long)1 << -		    UVH_LB_BAU_MISC_CONTROL_ENABLE_INTD_SOFT_ACK_MODE_SHFT); -		uv_write_global_mmr64 -		    (pnode, UVH_LB_BAU_MISC_CONTROL, mmr_image); +		mmr_image |= (1L << SOFTACK_MSHIFT); +		if (is_uv2_hub()) { +			/* hw bug workaround; do not use extended status */ +			mmr_image &= ~(1L << UV2_EXT_SHFT); +		} +		write_mmr_misc_control(pnode, mmr_image);  	}  } -static void *uv_ptc_seq_start(struct seq_file *file, loff_t *offset) +static void *ptc_seq_start(struct seq_file *file, loff_t *offset)  {  	if (*offset < num_possible_cpus())  		return offset;  	return NULL;  } -static void *uv_ptc_seq_next(struct seq_file *file, void *data, loff_t *offset) +static void *ptc_seq_next(struct seq_file *file, void *data, loff_t *offset)  {  	(*offset)++;  	if (*offset < num_possible_cpus()) @@ -916,82 +1347,77 @@ static void *uv_ptc_seq_next(struct seq_file *file, void *data, loff_t *offset)  	return NULL;  } -static void uv_ptc_seq_stop(struct seq_file *file, void *data) -{ -} - -static inline unsigned long long -microsec_2_cycles(unsigned long microsec) +static void ptc_seq_stop(struct seq_file *file, void *data)  { -	unsigned long ns; -	unsigned long long cyc; - -	ns = microsec * 1000; -	cyc = (ns << CYC2NS_SCALE_FACTOR)/(per_cpu(cyc2ns, smp_processor_id())); -	return cyc;  }  /* - * Display the statistics thru /proc. + * Display the statistics thru /proc/sgi_uv/ptc_statistics   * 'data' points to the cpu number + * Note: see the descriptions in stat_description[].   */ -static int uv_ptc_seq_show(struct seq_file *file, void *data) +static int ptc_seq_show(struct seq_file *file, void *data)  {  	struct ptc_stats *stat; +	struct bau_control *bcp;  	int cpu;  	cpu = *(loff_t *)data; -  	if (!cpu) {  		seq_printf(file, -			"# cpu sent stime self locals remotes ncpus localhub "); +		 "# cpu bauoff sent stime self locals remotes ncpus localhub ");  		seq_printf(file,  			"remotehub numuvhubs numuvhubs16 numuvhubs8 ");  		seq_printf(file, -			"numuvhubs4 numuvhubs2 numuvhubs1 dto "); +			"numuvhubs4 numuvhubs2 numuvhubs1 dto snacks retries ");  		seq_printf(file, -			"retries rok resetp resett giveup sto bz throt "); +			"rok resetp resett giveup sto bz throt disable ");  		seq_printf(file, -			"sw_ack recv rtime all "); +			"enable wars warshw warwaits enters ipidis plugged ");  		seq_printf(file, -			"one mult none retry canc nocan reset rcan "); +			"ipiover glim cong swack recv rtime all one mult ");  		seq_printf(file, -			"disable enable\n"); +			"none retry canc nocan reset rcan\n");  	}  	if (cpu < num_possible_cpus() && cpu_online(cpu)) { -		stat = &per_cpu(ptcstats, cpu); +		bcp = &per_cpu(bau_control, cpu); +		stat = bcp->statp;  		/* source side statistics */  		seq_printf(file, -			"cpu %d %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld ", -			   cpu, stat->s_requestor, cycles_2_us(stat->s_time), +			"cpu %d %d %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld ", +			   cpu, bcp->nobau, stat->s_requestor, +			   cycles_2_us(stat->s_time),  			   stat->s_ntargself, stat->s_ntarglocals,  			   stat->s_ntargremotes, stat->s_ntargcpu,  			   stat->s_ntarglocaluvhub, stat->s_ntargremoteuvhub,  			   stat->s_ntarguvhub, stat->s_ntarguvhub16); -		seq_printf(file, "%ld %ld %ld %ld %ld ", +		seq_printf(file, "%ld %ld %ld %ld %ld %ld ",  			   stat->s_ntarguvhub8, stat->s_ntarguvhub4,  			   stat->s_ntarguvhub2, stat->s_ntarguvhub1, -			   stat->s_dtimeout); +			   stat->s_dtimeout, stat->s_strongnacks);  		seq_printf(file, "%ld %ld %ld %ld %ld %ld %ld %ld ",  			   stat->s_retry_messages, stat->s_retriesok,  			   stat->s_resets_plug, stat->s_resets_timeout,  			   stat->s_giveup, stat->s_stimeout,  			   stat->s_busy, stat->s_throttles); +		seq_printf(file, "%ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld ", +			   stat->s_bau_disabled, stat->s_bau_reenabled, +			   stat->s_uv2_wars, stat->s_uv2_wars_hw, +			   stat->s_uv2_war_waits, stat->s_enters, +			   stat->s_ipifordisabled, stat->s_plugged, +			   stat->s_overipilimit, stat->s_giveuplimit, +			   stat->s_congested);  		/* destination side statistics */  		seq_printf(file, -			   "%lx %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld ", -			   uv_read_global_mmr64(uv_cpu_to_pnode(cpu), -					UVH_LB_BAU_INTD_SOFTWARE_ACKNOWLEDGE), +			"%lx %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld\n", +			   read_gmmr_sw_ack(uv_cpu_to_pnode(cpu)),  			   stat->d_requestee, cycles_2_us(stat->d_time),  			   stat->d_alltlb, stat->d_onetlb, stat->d_multmsg,  			   stat->d_nomsg, stat->d_retries, stat->d_canceled,  			   stat->d_nocanceled, stat->d_resets,  			   stat->d_rcanceled); -		seq_printf(file, "%ld %ld\n", -			stat->s_bau_disabled, stat->s_bau_reenabled);  	} -  	return 0;  } @@ -999,18 +1425,19 @@ static int uv_ptc_seq_show(struct seq_file *file, void *data)   * Display the tunables thru debugfs   */  static ssize_t tunables_read(struct file *file, char __user *userbuf, -						size_t count, loff_t *ppos) +				size_t count, loff_t *ppos)  {  	char *buf;  	int ret; -	buf = kasprintf(GFP_KERNEL, "%s %s %s\n%d %d %d %d %d %d %d %d %d\n", -		"max_bau_concurrent plugged_delay plugsb4reset", -		"timeoutsb4reset ipi_reset_limit complete_threshold", -		"congested_response_us congested_reps congested_period", -		max_bau_concurrent, plugged_delay, plugsb4reset, +	buf = kasprintf(GFP_KERNEL, "%s %s %s\n%d %d %d %d %d %d %d %d %d %d\n", +		"max_concur plugged_delay plugsb4reset timeoutsb4reset", +		"ipi_reset_limit complete_threshold congested_response_us", +		"congested_reps disabled_period giveup_limit", +		max_concurr, plugged_delay, plugsb4reset,  		timeoutsb4reset, ipi_reset_limit, complete_threshold, -		congested_response_us, congested_reps, congested_period); +		congested_respns_us, congested_reps, disabled_period, +		giveup_limit);  	if (!buf)  		return -ENOMEM; @@ -1021,13 +1448,16 @@ static ssize_t tunables_read(struct file *file, char __user *userbuf,  }  /* - * -1: resetf the statistics + * handle a write to /proc/sgi_uv/ptc_statistics + * -1: reset the statistics   *  0: display meaning of the statistics   */ -static ssize_t uv_ptc_proc_write(struct file *file, const char __user *user, -				 size_t count, loff_t *data) +static ssize_t ptc_proc_write(struct file *file, const char __user *user, +				size_t count, loff_t *data)  {  	int cpu; +	int i; +	int elements;  	long input_arg;  	char optstr[64];  	struct ptc_stats *stat; @@ -1037,79 +1467,26 @@ static ssize_t uv_ptc_proc_write(struct file *file, const char __user *user,  	if (copy_from_user(optstr, user, count))  		return -EFAULT;  	optstr[count - 1] = '\0'; + +	if (!strcmp(optstr, "on")) { +		set_bau_on(); +		return count; +	} else if (!strcmp(optstr, "off")) { +		set_bau_off(); +		return count; +	} +  	if (strict_strtol(optstr, 10, &input_arg) < 0) {  		printk(KERN_DEBUG "%s is invalid\n", optstr);  		return -EINVAL;  	}  	if (input_arg == 0) { +		elements = ARRAY_SIZE(stat_description);  		printk(KERN_DEBUG "# cpu:      cpu number\n");  		printk(KERN_DEBUG "Sender statistics:\n"); -		printk(KERN_DEBUG -		"sent:     number of shootdown messages sent\n"); -		printk(KERN_DEBUG -		"stime:    time spent sending messages\n"); -		printk(KERN_DEBUG -		"numuvhubs: number of hubs targeted with shootdown\n"); -		printk(KERN_DEBUG -		"numuvhubs16: number times 16 or more hubs targeted\n"); -		printk(KERN_DEBUG -		"numuvhubs8: number times 8 or more hubs targeted\n"); -		printk(KERN_DEBUG -		"numuvhubs4: number times 4 or more hubs targeted\n"); -		printk(KERN_DEBUG -		"numuvhubs2: number times 2 or more hubs targeted\n"); -		printk(KERN_DEBUG -		"numuvhubs1: number times 1 hub targeted\n"); -		printk(KERN_DEBUG -		"numcpus:  number of cpus targeted with shootdown\n"); -		printk(KERN_DEBUG -		"dto:      number of destination timeouts\n"); -		printk(KERN_DEBUG -		"retries:  destination timeout retries sent\n"); -		printk(KERN_DEBUG -		"rok:   :  destination timeouts successfully retried\n"); -		printk(KERN_DEBUG -		"resetp:   ipi-style resource resets for plugs\n"); -		printk(KERN_DEBUG -		"resett:   ipi-style resource resets for timeouts\n"); -		printk(KERN_DEBUG -		"giveup:   fall-backs to ipi-style shootdowns\n"); -		printk(KERN_DEBUG -		"sto:      number of source timeouts\n"); -		printk(KERN_DEBUG -		"bz:       number of stay-busy's\n"); -		printk(KERN_DEBUG -		"throt:    number times spun in throttle\n"); -		printk(KERN_DEBUG "Destination side statistics:\n"); -		printk(KERN_DEBUG -		"sw_ack:   image of UVH_LB_BAU_INTD_SOFTWARE_ACKNOWLEDGE\n"); -		printk(KERN_DEBUG -		"recv:     shootdown messages received\n"); -		printk(KERN_DEBUG -		"rtime:    time spent processing messages\n"); -		printk(KERN_DEBUG -		"all:      shootdown all-tlb messages\n"); -		printk(KERN_DEBUG -		"one:      shootdown one-tlb messages\n"); -		printk(KERN_DEBUG -		"mult:     interrupts that found multiple messages\n"); -		printk(KERN_DEBUG -		"none:     interrupts that found no messages\n"); -		printk(KERN_DEBUG -		"retry:    number of retry messages processed\n"); -		printk(KERN_DEBUG -		"canc:     number messages canceled by retries\n"); -		printk(KERN_DEBUG -		"nocan:    number retries that found nothing to cancel\n"); -		printk(KERN_DEBUG -		"reset:    number of ipi-style reset requests processed\n"); -		printk(KERN_DEBUG -		"rcan:     number messages canceled by reset requests\n"); -		printk(KERN_DEBUG -		"disable:  number times use of the BAU was disabled\n"); -		printk(KERN_DEBUG -		"enable:   number times use of the BAU was re-enabled\n"); +		for (i = 0; i < elements; i++) +			printk(KERN_DEBUG "%s\n", stat_description[i]);  	} else if (input_arg == -1) {  		for_each_present_cpu(cpu) {  			stat = &per_cpu(ptcstats, cpu); @@ -1136,27 +1513,18 @@ static int local_atoi(const char *name)  }  /* - * set the tunables - * 0 values reset them to defaults + * Parse the values written to /sys/kernel/debug/sgi_uv/bau_tunables. + * Zero values reset them to defaults.   */ -static ssize_t tunables_write(struct file *file, const char __user *user, -				 size_t count, loff_t *data) +static int parse_tunables_write(struct bau_control *bcp, char *instr, +				int count)  { -	int cpu; -	int cnt = 0; -	int val;  	char *p;  	char *q; -	char instr[64]; -	struct bau_control *bcp; - -	if (count == 0 || count > sizeof(instr)-1) -		return -EINVAL; -	if (copy_from_user(instr, user, count)) -		return -EFAULT; +	int cnt = 0; +	int val; +	int e = ARRAY_SIZE(tunables); -	instr[count] = '\0'; -	/* count the fields */  	p = instr + strspn(instr, WHITESPACE);  	q = p;  	for (; *p; p = q + strspn(q, WHITESPACE)) { @@ -1165,8 +1533,8 @@ static ssize_t tunables_write(struct file *file, const char __user *user,  		if (q == p)  			break;  	} -	if (cnt != 9) { -		printk(KERN_INFO "bau tunable error: should be 9 numbers\n"); +	if (cnt != e) { +		printk(KERN_INFO "bau tunable error: should be %d values\n", e);  		return -EINVAL;  	} @@ -1178,97 +1546,82 @@ static ssize_t tunables_write(struct file *file, const char __user *user,  		switch (cnt) {  		case 0:  			if (val == 0) { -				max_bau_concurrent = MAX_BAU_CONCURRENT; -				max_bau_concurrent_constant = -							MAX_BAU_CONCURRENT; +				max_concurr = MAX_BAU_CONCURRENT; +				max_concurr_const = MAX_BAU_CONCURRENT;  				continue;  			} -			bcp = &per_cpu(bau_control, smp_processor_id());  			if (val < 1 || val > bcp->cpus_in_uvhub) {  				printk(KERN_DEBUG  				"Error: BAU max concurrent %d is invalid\n",  				val);  				return -EINVAL;  			} -			max_bau_concurrent = val; -			max_bau_concurrent_constant = val; -			continue; -		case 1: -			if (val == 0) -				plugged_delay = PLUGGED_DELAY; -			else -				plugged_delay = val; -			continue; -		case 2: -			if (val == 0) -				plugsb4reset = PLUGSB4RESET; -			else -				plugsb4reset = val; -			continue; -		case 3: -			if (val == 0) -				timeoutsb4reset = TIMEOUTSB4RESET; -			else -				timeoutsb4reset = val; -			continue; -		case 4: -			if (val == 0) -				ipi_reset_limit = IPI_RESET_LIMIT; -			else -				ipi_reset_limit = val; +			max_concurr = val; +			max_concurr_const = val;  			continue; -		case 5: -			if (val == 0) -				complete_threshold = COMPLETE_THRESHOLD; -			else -				complete_threshold = val; -			continue; -		case 6: -			if (val == 0) -				congested_response_us = CONGESTED_RESPONSE_US; -			else -				congested_response_us = val; -			continue; -		case 7: -			if (val == 0) -				congested_reps = CONGESTED_REPS; -			else -				congested_reps = val; -			continue; -		case 8: +		default:  			if (val == 0) -				congested_period = CONGESTED_PERIOD; +				*tunables[cnt].tunp = tunables[cnt].deflt;  			else -				congested_period = val; +				*tunables[cnt].tunp = val;  			continue;  		}  		if (q == p)  			break;  	} +	return 0; +} + +/* + * Handle a write to debugfs. (/sys/kernel/debug/sgi_uv/bau_tunables) + */ +static ssize_t tunables_write(struct file *file, const char __user *user, +				size_t count, loff_t *data) +{ +	int cpu; +	int ret; +	char instr[100]; +	struct bau_control *bcp; + +	if (count == 0 || count > sizeof(instr)-1) +		return -EINVAL; +	if (copy_from_user(instr, user, count)) +		return -EFAULT; + +	instr[count] = '\0'; + +	cpu = get_cpu(); +	bcp = &per_cpu(bau_control, cpu); +	ret = parse_tunables_write(bcp, instr, count); +	put_cpu(); +	if (ret) +		return ret; +  	for_each_present_cpu(cpu) {  		bcp = &per_cpu(bau_control, cpu); -		bcp->max_bau_concurrent = max_bau_concurrent; -		bcp->max_bau_concurrent_constant = max_bau_concurrent; -		bcp->plugged_delay = plugged_delay; -		bcp->plugsb4reset = plugsb4reset; -		bcp->timeoutsb4reset = timeoutsb4reset; -		bcp->ipi_reset_limit = ipi_reset_limit; -		bcp->complete_threshold = complete_threshold; -		bcp->congested_response_us = congested_response_us; -		bcp->congested_reps = congested_reps; -		bcp->congested_period = congested_period; +		bcp->max_concurr =		max_concurr; +		bcp->max_concurr_const =	max_concurr; +		bcp->plugged_delay =		plugged_delay; +		bcp->plugsb4reset =		plugsb4reset; +		bcp->timeoutsb4reset =		timeoutsb4reset; +		bcp->ipi_reset_limit =		ipi_reset_limit; +		bcp->complete_threshold =	complete_threshold; +		bcp->cong_response_us =		congested_respns_us; +		bcp->cong_reps =		congested_reps; +		bcp->disabled_period =		sec_2_cycles(disabled_period); +		bcp->giveup_limit =		giveup_limit;  	}  	return count;  }  static const struct seq_operations uv_ptc_seq_ops = { -	.start		= uv_ptc_seq_start, -	.next		= uv_ptc_seq_next, -	.stop		= uv_ptc_seq_stop, -	.show		= uv_ptc_seq_show +	.start		= ptc_seq_start, +	.next		= ptc_seq_next, +	.stop		= ptc_seq_stop, +	.show		= ptc_seq_show  }; -static int uv_ptc_proc_open(struct inode *inode, struct file *file) +static int ptc_proc_open(struct inode *inode, struct file *file)  {  	return seq_open(file, &uv_ptc_seq_ops);  } @@ -1279,9 +1632,9 @@ static int tunables_open(struct inode *inode, struct file *file)  }  static const struct file_operations proc_uv_ptc_operations = { -	.open		= uv_ptc_proc_open, +	.open		= ptc_proc_open,  	.read		= seq_read, -	.write		= uv_ptc_proc_write, +	.write		= ptc_proc_write,  	.llseek		= seq_lseek,  	.release	= seq_release,  }; @@ -1315,7 +1668,7 @@ static int __init uv_ptc_init(void)  		return -EINVAL;  	}  	tunables_file = debugfs_create_file(UV_BAU_TUNABLES_FILE, 0600, -			tunables_dir, NULL, &tunables_fops); +					tunables_dir, NULL, &tunables_fops);  	if (!tunables_file) {  		printk(KERN_ERR "unable to create debugfs file %s\n",  		       UV_BAU_TUNABLES_FILE); @@ -1325,57 +1678,77 @@ static int __init uv_ptc_init(void)  }  /* - * initialize the sending side's sending buffers + * Initialize the sending side's sending buffers.   */ -static void -uv_activation_descriptor_init(int node, int pnode) +static void activation_descriptor_init(int node, int pnode, int base_pnode)  {  	int i;  	int cpu; -	unsigned long pa; +	int uv1 = 0; +	unsigned long gpa;  	unsigned long m;  	unsigned long n; +	size_t dsize;  	struct bau_desc *bau_desc;  	struct bau_desc *bd2; +	struct uv1_bau_msg_header *uv1_hdr; +	struct uv2_bau_msg_header *uv2_hdr;  	struct bau_control *bcp;  	/* -	 * each bau_desc is 64 bytes; there are 8 (UV_ITEMS_PER_DESCRIPTOR) -	 * per cpu; and up to 32 (UV_ADP_SIZE) cpu's per uvhub +	 * each bau_desc is 64 bytes; there are 8 (ITEMS_PER_DESC) +	 * per cpu; and one per cpu on the uvhub (ADP_SZ)  	 */ -	bau_desc = kmalloc_node(sizeof(struct bau_desc) * UV_ADP_SIZE -				* UV_ITEMS_PER_DESCRIPTOR, GFP_KERNEL, node); +	dsize = sizeof(struct bau_desc) * ADP_SZ * ITEMS_PER_DESC; +	bau_desc = kmalloc_node(dsize, GFP_KERNEL, node);  	BUG_ON(!bau_desc); -	pa = uv_gpa(bau_desc); /* need the real nasid*/ -	n = pa >> uv_nshift; -	m = pa & uv_mmask; - -	uv_write_global_mmr64(pnode, UVH_LB_BAU_SB_DESCRIPTOR_BASE, -			      (n << UV_DESC_BASE_PNODE_SHIFT | m)); +	gpa = uv_gpa(bau_desc); +	n = uv_gpa_to_gnode(gpa); +	m = uv_gpa_to_offset(gpa); +	if (is_uv1_hub()) +		uv1 = 1; +	/* the 14-bit pnode */ +	write_mmr_descriptor_base(pnode, (n << UV_DESC_PSHIFT | m));  	/* -	 * initializing all 8 (UV_ITEMS_PER_DESCRIPTOR) descriptors for each +	 * Initializing all 8 (ITEMS_PER_DESC) descriptors for each  	 * cpu even though we only use the first one; one descriptor can  	 * describe a broadcast to 256 uv hubs.  	 */ -	for (i = 0, bd2 = bau_desc; i < (UV_ADP_SIZE*UV_ITEMS_PER_DESCRIPTOR); -		i++, bd2++) { +	for (i = 0, bd2 = bau_desc; i < (ADP_SZ * ITEMS_PER_DESC); i++, bd2++) {  		memset(bd2, 0, sizeof(struct bau_desc)); -		bd2->header.sw_ack_flag = 1; -		/* -		 * base_dest_nodeid is the nasid (pnode<<1) of the first uvhub -		 * in the partition. The bit map will indicate uvhub numbers, -		 * which are 0-N in a partition. Pnodes are unique system-wide. -		 */ -		bd2->header.base_dest_nodeid = uv_partition_base_pnode << 1; -		bd2->header.dest_subnodeid = 0x10; /* the LB */ -		bd2->header.command = UV_NET_ENDPOINT_INTD; -		bd2->header.int_both = 1; -		/* -		 * all others need to be set to zero: -		 *   fairness chaining multilevel count replied_to -		 */ +		if (uv1) { +			uv1_hdr = &bd2->header.uv1_hdr; +			uv1_hdr->swack_flag =	1; +			/* +			 * The base_dest_nasid set in the message header +			 * is the nasid of the first uvhub in the partition. +			 * The bit map will indicate destination pnode numbers +			 * relative to that base. They may not be consecutive +			 * if nasid striding is being used. +			 */ +			uv1_hdr->base_dest_nasid = +						UV_PNODE_TO_NASID(base_pnode); +			uv1_hdr->dest_subnodeid =	UV_LB_SUBNODEID; +			uv1_hdr->command =		UV_NET_ENDPOINT_INTD; +			uv1_hdr->int_both =		1; +			/* +			 * all others need to be set to zero: +			 *   fairness chaining multilevel count replied_to +			 */ +		} else { +			/* +			 * BIOS uses legacy mode, but UV2 hardware always +			 * uses native mode for selective broadcasts. +			 */ +			uv2_hdr = &bd2->header.uv2_hdr; +			uv2_hdr->swack_flag =	1; +			uv2_hdr->base_dest_nasid = +						UV_PNODE_TO_NASID(base_pnode); +			uv2_hdr->dest_subnodeid =	UV_LB_SUBNODEID; +			uv2_hdr->command =		UV_NET_ENDPOINT_INTD; +		}  	}  	for_each_present_cpu(cpu) {  		if (pnode != uv_blade_to_pnode(uv_cpu_to_blade_id(cpu))) @@ -1391,57 +1764,56 @@ uv_activation_descriptor_init(int node, int pnode)   * - node is first node (kernel memory notion) on the uvhub   * - pnode is the uvhub's physical identifier   */ -static void -uv_payload_queue_init(int node, int pnode) +static void pq_init(int node, int pnode)  { -	int pn;  	int cpu; +	size_t plsize;  	char *cp; -	unsigned long pa; -	struct bau_payload_queue_entry *pqp; -	struct bau_payload_queue_entry *pqp_malloc; +	void *vp; +	unsigned long pn; +	unsigned long first; +	unsigned long pn_first; +	unsigned long last; +	struct bau_pq_entry *pqp;  	struct bau_control *bcp; -	pqp = kmalloc_node((DEST_Q_SIZE + 1) -			   * sizeof(struct bau_payload_queue_entry), -			   GFP_KERNEL, node); +	plsize = (DEST_Q_SIZE + 1) * sizeof(struct bau_pq_entry); +	vp = kmalloc_node(plsize, GFP_KERNEL, node); +	pqp = (struct bau_pq_entry *)vp;  	BUG_ON(!pqp); -	pqp_malloc = pqp;  	cp = (char *)pqp + 31; -	pqp = (struct bau_payload_queue_entry *)(((unsigned long)cp >> 5) << 5); +	pqp = (struct bau_pq_entry *)(((unsigned long)cp >> 5) << 5);  	for_each_present_cpu(cpu) {  		if (pnode != uv_cpu_to_pnode(cpu))  			continue;  		/* for every cpu on this pnode: */  		bcp = &per_cpu(bau_control, cpu); -		bcp->va_queue_first = pqp; -		bcp->bau_msg_head = pqp; -		bcp->va_queue_last = pqp + (DEST_Q_SIZE - 1); +		bcp->queue_first	= pqp; +		bcp->bau_msg_head	= pqp; +		bcp->queue_last		= pqp + (DEST_Q_SIZE - 1);  	}  	/* -	 * need the pnode of where the memory was really allocated +	 * need the gnode of where the memory was really allocated  	 */ -	pa = uv_gpa(pqp); -	pn = pa >> uv_nshift; -	uv_write_global_mmr64(pnode, -			      UVH_LB_BAU_INTD_PAYLOAD_QUEUE_FIRST, -			      ((unsigned long)pn << UV_PAYLOADQ_PNODE_SHIFT) | -			      uv_physnodeaddr(pqp)); -	uv_write_global_mmr64(pnode, UVH_LB_BAU_INTD_PAYLOAD_QUEUE_TAIL, -			      uv_physnodeaddr(pqp)); -	uv_write_global_mmr64(pnode, UVH_LB_BAU_INTD_PAYLOAD_QUEUE_LAST, -			      (unsigned long) -			      uv_physnodeaddr(pqp + (DEST_Q_SIZE - 1))); +	pn = uv_gpa_to_gnode(uv_gpa(pqp)); +	first = uv_physnodeaddr(pqp); +	pn_first = ((unsigned long)pn << UV_PAYLOADQ_PNODE_SHIFT) | first; +	last = uv_physnodeaddr(pqp + (DEST_Q_SIZE - 1)); +	write_mmr_payload_first(pnode, pn_first); +	write_mmr_payload_tail(pnode, first); +	write_mmr_payload_last(pnode, last); +	write_gmmr_sw_ack(pnode, 0xffffUL); +  	/* in effect, all msg_type's are set to MSG_NOOP */ -	memset(pqp, 0, sizeof(struct bau_payload_queue_entry) * DEST_Q_SIZE); +	memset(pqp, 0, sizeof(struct bau_pq_entry) * DEST_Q_SIZE);  }  /*   * Initialization of each UV hub's structures   */ -static void __init uv_init_uvhub(int uvhub, int vector) +static void __init init_uvhub(int uvhub, int vector, int base_pnode)  {  	int node;  	int pnode; @@ -1449,24 +1821,24 @@ static void __init uv_init_uvhub(int uvhub, int vector)  	node = uvhub_to_first_node(uvhub);  	pnode = uv_blade_to_pnode(uvhub); -	uv_activation_descriptor_init(node, pnode); -	uv_payload_queue_init(node, pnode); + +	activation_descriptor_init(node, pnode, base_pnode); + +	pq_init(node, pnode);  	/* -	 * the below initialization can't be in firmware because the -	 * messaging IRQ will be determined by the OS +	 * The below initialization can't be in firmware because the +	 * messaging IRQ will be determined by the OS.  	 */ -	apicid = uvhub_to_first_apicid(uvhub); -	uv_write_global_mmr64(pnode, UVH_BAU_DATA_CONFIG, -				      ((apicid << 32) | vector)); +	apicid = uvhub_to_first_apicid(uvhub) | uv_apicid_hibits; +	write_mmr_data_config(pnode, ((apicid << 32) | vector));  }  /*   * We will set BAU_MISC_CONTROL with a timeout period.   * But the BIOS has set UVH_AGING_PRESCALE_SEL and UVH_TRANSACTION_TIMEOUT. - * So the destination timeout period has be be calculated from them. + * So the destination timeout period has to be calculated from them.   */ -static int -calculate_destination_timeout(void) +static int calculate_destination_timeout(void)  {  	unsigned long mmr_image;  	int mult1; @@ -1476,125 +1848,254 @@ calculate_destination_timeout(void)  	int ret;  	unsigned long ts_ns; -	mult1 = UV_INTD_SOFT_ACK_TIMEOUT_PERIOD & BAU_MISC_CONTROL_MULT_MASK; -	mmr_image = uv_read_local_mmr(UVH_AGING_PRESCALE_SEL); -	index = (mmr_image >> BAU_URGENCY_7_SHIFT) & BAU_URGENCY_7_MASK; -	mmr_image = uv_read_local_mmr(UVH_TRANSACTION_TIMEOUT); -	mult2 = (mmr_image >> BAU_TRANS_SHIFT) & BAU_TRANS_MASK; -	base = timeout_base_ns[index]; -	ts_ns = base * mult1 * mult2; -	ret = ts_ns / 1000; +	if (is_uv1_hub()) { +		mult1 = SOFTACK_TIMEOUT_PERIOD & BAU_MISC_CONTROL_MULT_MASK; +		mmr_image = uv_read_local_mmr(UVH_AGING_PRESCALE_SEL); +		index = (mmr_image >> BAU_URGENCY_7_SHIFT) & BAU_URGENCY_7_MASK; +		mmr_image = uv_read_local_mmr(UVH_TRANSACTION_TIMEOUT); +		mult2 = (mmr_image >> BAU_TRANS_SHIFT) & BAU_TRANS_MASK; +		ts_ns = timeout_base_ns[index]; +		ts_ns *= (mult1 * mult2); +		ret = ts_ns / 1000; +	} else { +		/* 4 bits  0/1 for 10/80us base, 3 bits of multiplier */ +		mmr_image = uv_read_local_mmr(UVH_LB_BAU_MISC_CONTROL); +		mmr_image = (mmr_image & UV_SA_MASK) >> UV_SA_SHFT; +		if (mmr_image & (1L << UV2_ACK_UNITS_SHFT)) +			base = 80; +		else +			base = 10; +		mult1 = mmr_image & UV2_ACK_MASK; +		ret = mult1 * base; +	}  	return ret;  } +static void __init init_per_cpu_tunables(void) +{ +	int cpu; +	struct bau_control *bcp; + +	for_each_present_cpu(cpu) { +		bcp = &per_cpu(bau_control, cpu); +		bcp->baudisabled		= 0; +		if (nobau) +			bcp->nobau		= 1; +		bcp->statp			= &per_cpu(ptcstats, cpu); +		/* time interval to catch a hardware stay-busy bug */ +		bcp->timeout_interval		= usec_2_cycles(2*timeout_us); +		bcp->max_concurr		= max_concurr; +		bcp->max_concurr_const		= max_concurr; +		bcp->plugged_delay		= plugged_delay; +		bcp->plugsb4reset		= plugsb4reset; +		bcp->timeoutsb4reset		= timeoutsb4reset; +		bcp->ipi_reset_limit		= ipi_reset_limit; +		bcp->complete_threshold		= complete_threshold; +		bcp->cong_response_us		= congested_respns_us; +		bcp->cong_reps			= congested_reps; +		bcp->disabled_period =		sec_2_cycles(disabled_period); +		bcp->giveup_limit =		giveup_limit; +		spin_lock_init(&bcp->queue_lock); +		spin_lock_init(&bcp->uvhub_lock); +		spin_lock_init(&bcp->disable_lock); +	} +} +  /* - * initialize the bau_control structure for each cpu + * Scan all cpus to collect blade and socket summaries.   */ -static void __init uv_init_per_cpu(int nuvhubs) +static int __init get_cpu_topology(int base_pnode, +					struct uvhub_desc *uvhub_descs, +					unsigned char *uvhub_mask)  { -	int i;  	int cpu;  	int pnode;  	int uvhub; -	int have_hmaster; -	short socket = 0; -	unsigned short socket_mask; -	unsigned char *uvhub_mask; +	int socket;  	struct bau_control *bcp;  	struct uvhub_desc *bdp;  	struct socket_desc *sdp; -	struct bau_control *hmaster = NULL; -	struct bau_control *smaster = NULL; -	struct socket_desc { -		short num_cpus; -		short cpu_number[16]; -	}; -	struct uvhub_desc { -		unsigned short socket_mask; -		short num_cpus; -		short uvhub; -		short pnode; -		struct socket_desc socket[2]; -	}; -	struct uvhub_desc *uvhub_descs; - -	timeout_us = calculate_destination_timeout(); -	uvhub_descs = kmalloc(nuvhubs * sizeof(struct uvhub_desc), GFP_KERNEL); -	memset(uvhub_descs, 0, nuvhubs * sizeof(struct uvhub_desc)); -	uvhub_mask = kzalloc((nuvhubs+7)/8, GFP_KERNEL);  	for_each_present_cpu(cpu) {  		bcp = &per_cpu(bau_control, cpu); +  		memset(bcp, 0, sizeof(struct bau_control)); +  		pnode = uv_cpu_hub_info(cpu)->pnode; +		if ((pnode - base_pnode) >= UV_DISTRIBUTION_SIZE) { +			printk(KERN_EMERG +				"cpu %d pnode %d-%d beyond %d; BAU disabled\n", +				cpu, pnode, base_pnode, UV_DISTRIBUTION_SIZE); +			return 1; +		} + +		bcp->osnode = cpu_to_node(cpu); +		bcp->partition_base_pnode = base_pnode; +  		uvhub = uv_cpu_hub_info(cpu)->numa_blade_id;  		*(uvhub_mask + (uvhub/8)) |= (1 << (uvhub%8));  		bdp = &uvhub_descs[uvhub]; +  		bdp->num_cpus++;  		bdp->uvhub = uvhub;  		bdp->pnode = pnode; +  		/* kludge: 'assuming' one node per socket, and assuming that  		   disabling a socket just leaves a gap in node numbers */ -		socket = (cpu_to_node(cpu) & 1); +		socket = bcp->osnode & 1;  		bdp->socket_mask |= (1 << socket);  		sdp = &bdp->socket[socket];  		sdp->cpu_number[sdp->num_cpus] = cpu;  		sdp->num_cpus++; +		if (sdp->num_cpus > MAX_CPUS_PER_SOCKET) { +			printk(KERN_EMERG "%d cpus per socket invalid\n", +				sdp->num_cpus); +			return 1; +		}  	} +	return 0; +} + +/* + * Each socket is to get a local array of pnodes/hubs. + */ +static void make_per_cpu_thp(struct bau_control *smaster) +{ +	int cpu; +	size_t hpsz = sizeof(struct hub_and_pnode) * num_possible_cpus(); + +	smaster->thp = kmalloc_node(hpsz, GFP_KERNEL, smaster->osnode); +	memset(smaster->thp, 0, hpsz); +	for_each_present_cpu(cpu) { +		smaster->thp[cpu].pnode = uv_cpu_hub_info(cpu)->pnode; +		smaster->thp[cpu].uvhub = uv_cpu_hub_info(cpu)->numa_blade_id; +	} +} + +/* + * Each uvhub is to get a local cpumask. + */ +static void make_per_hub_cpumask(struct bau_control *hmaster) +{ +	int sz = sizeof(cpumask_t); + +	hmaster->cpumask = kzalloc_node(sz, GFP_KERNEL, hmaster->osnode); +} + +/* + * Initialize all the per_cpu information for the cpu's on a given socket, + * given what has been gathered into the socket_desc struct. + * And reports the chosen hub and socket masters back to the caller. + */ +static int scan_sock(struct socket_desc *sdp, struct uvhub_desc *bdp, +			struct bau_control **smasterp, +			struct bau_control **hmasterp) +{ +	int i; +	int cpu; +	struct bau_control *bcp; + +	for (i = 0; i < sdp->num_cpus; i++) { +		cpu = sdp->cpu_number[i]; +		bcp = &per_cpu(bau_control, cpu); +		bcp->cpu = cpu; +		if (i == 0) { +			*smasterp = bcp; +			if (!(*hmasterp)) +				*hmasterp = bcp; +		} +		bcp->cpus_in_uvhub = bdp->num_cpus; +		bcp->cpus_in_socket = sdp->num_cpus; +		bcp->socket_master = *smasterp; +		bcp->uvhub = bdp->uvhub; +		if (is_uv1_hub()) +			bcp->uvhub_version = 1; +		else if (is_uv2_hub()) +			bcp->uvhub_version = 2; +		else { +			printk(KERN_EMERG "uvhub version not 1 or 2\n"); +			return 1; +		} +		bcp->uvhub_master = *hmasterp; +		bcp->uvhub_cpu = uv_cpu_hub_info(cpu)->blade_processor_id; +		if (bcp->uvhub_cpu >= MAX_CPUS_PER_UVHUB) { +			printk(KERN_EMERG "%d cpus per uvhub invalid\n", +				bcp->uvhub_cpu); +			return 1; +		} +	} +	return 0; +} + +/* + * Summarize the blade and socket topology into the per_cpu structures. + */ +static int __init summarize_uvhub_sockets(int nuvhubs, +			struct uvhub_desc *uvhub_descs, +			unsigned char *uvhub_mask) +{ +	int socket; +	int uvhub; +	unsigned short socket_mask; +  	for (uvhub = 0; uvhub < nuvhubs; uvhub++) { +		struct uvhub_desc *bdp; +		struct bau_control *smaster = NULL; +		struct bau_control *hmaster = NULL; +  		if (!(*(uvhub_mask + (uvhub/8)) & (1 << (uvhub%8))))  			continue; -		have_hmaster = 0; +  		bdp = &uvhub_descs[uvhub];  		socket_mask = bdp->socket_mask;  		socket = 0;  		while (socket_mask) { -			if (!(socket_mask & 1)) -				goto nextsocket; -			sdp = &bdp->socket[socket]; -			for (i = 0; i < sdp->num_cpus; i++) { -				cpu = sdp->cpu_number[i]; -				bcp = &per_cpu(bau_control, cpu); -				bcp->cpu = cpu; -				if (i == 0) { -					smaster = bcp; -					if (!have_hmaster) { -						have_hmaster++; -						hmaster = bcp; -					} -				} -				bcp->cpus_in_uvhub = bdp->num_cpus; -				bcp->cpus_in_socket = sdp->num_cpus; -				bcp->socket_master = smaster; -				bcp->uvhub = bdp->uvhub; -				bcp->uvhub_master = hmaster; -				bcp->uvhub_cpu = uv_cpu_hub_info(cpu)-> -						blade_processor_id; +			struct socket_desc *sdp; +			if ((socket_mask & 1)) { +				sdp = &bdp->socket[socket]; +				if (scan_sock(sdp, bdp, &smaster, &hmaster)) +					return 1; +				make_per_cpu_thp(smaster);  			} -nextsocket:  			socket++;  			socket_mask = (socket_mask >> 1);  		} +		make_per_hub_cpumask(hmaster);  	} +	return 0; +} + +/* + * initialize the bau_control structure for each cpu + */ +static int __init init_per_cpu(int nuvhubs, int base_part_pnode) +{ +	unsigned char *uvhub_mask; +	void *vp; +	struct uvhub_desc *uvhub_descs; + +	timeout_us = calculate_destination_timeout(); + +	vp = kmalloc(nuvhubs * sizeof(struct uvhub_desc), GFP_KERNEL); +	uvhub_descs = (struct uvhub_desc *)vp; +	memset(uvhub_descs, 0, nuvhubs * sizeof(struct uvhub_desc)); +	uvhub_mask = kzalloc((nuvhubs+7)/8, GFP_KERNEL); + +	if (get_cpu_topology(base_part_pnode, uvhub_descs, uvhub_mask)) +		goto fail; + +	if (summarize_uvhub_sockets(nuvhubs, uvhub_descs, uvhub_mask)) +		goto fail; +  	kfree(uvhub_descs);  	kfree(uvhub_mask); -	for_each_present_cpu(cpu) { -		bcp = &per_cpu(bau_control, cpu); -		bcp->baudisabled = 0; -		bcp->statp = &per_cpu(ptcstats, cpu); -		/* time interval to catch a hardware stay-busy bug */ -		bcp->timeout_interval = microsec_2_cycles(2*timeout_us); -		bcp->max_bau_concurrent = max_bau_concurrent; -		bcp->max_bau_concurrent_constant = max_bau_concurrent; -		bcp->plugged_delay = plugged_delay; -		bcp->plugsb4reset = plugsb4reset; -		bcp->timeoutsb4reset = timeoutsb4reset; -		bcp->ipi_reset_limit = ipi_reset_limit; -		bcp->complete_threshold = complete_threshold; -		bcp->congested_response_us = congested_response_us; -		bcp->congested_reps = congested_reps; -		bcp->congested_period = congested_period; -	} +	init_per_cpu_tunables(); +	return 0; + +fail: +	kfree(uvhub_descs); +	kfree(uvhub_mask); +	return 1;  }  /* @@ -1606,51 +2107,54 @@ static int __init uv_bau_init(void)  	int pnode;  	int nuvhubs;  	int cur_cpu; +	int cpus;  	int vector; -	unsigned long mmr; +	cpumask_var_t *mask;  	if (!is_uv_system())  		return 0; -	if (nobau) -		return 0; - -	for_each_possible_cpu(cur_cpu) -		zalloc_cpumask_var_node(&per_cpu(uv_flush_tlb_mask, cur_cpu), -				       GFP_KERNEL, cpu_to_node(cur_cpu)); +	for_each_possible_cpu(cur_cpu) { +		mask = &per_cpu(uv_flush_tlb_mask, cur_cpu); +		zalloc_cpumask_var_node(mask, GFP_KERNEL, cpu_to_node(cur_cpu)); +	} -	uv_nshift = uv_hub_info->m_val; -	uv_mmask = (1UL << uv_hub_info->m_val) - 1;  	nuvhubs = uv_num_possible_blades(); -	spin_lock_init(&disable_lock); -	congested_cycles = microsec_2_cycles(congested_response_us); +	congested_cycles = usec_2_cycles(congested_respns_us); -	uv_init_per_cpu(nuvhubs); +	uv_base_pnode = 0x7fffffff; +	for (uvhub = 0; uvhub < nuvhubs; uvhub++) { +		cpus = uv_blade_nr_possible_cpus(uvhub); +		if (cpus && (uv_blade_to_pnode(uvhub) < uv_base_pnode)) +			uv_base_pnode = uv_blade_to_pnode(uvhub); +	} + +	enable_timeouts(); -	uv_partition_base_pnode = 0x7fffffff; -	for (uvhub = 0; uvhub < nuvhubs; uvhub++) -		if (uv_blade_nr_possible_cpus(uvhub) && -			(uv_blade_to_pnode(uvhub) < uv_partition_base_pnode)) -			uv_partition_base_pnode = uv_blade_to_pnode(uvhub); +	if (init_per_cpu(nuvhubs, uv_base_pnode)) { +		set_bau_off(); +		nobau_perm = 1; +		return 0; +	}  	vector = UV_BAU_MESSAGE;  	for_each_possible_blade(uvhub)  		if (uv_blade_nr_possible_cpus(uvhub)) -			uv_init_uvhub(uvhub, vector); +			init_uvhub(uvhub, vector, uv_base_pnode); -	uv_enable_timeouts();  	alloc_intr_gate(vector, uv_bau_message_intr1);  	for_each_possible_blade(uvhub) {  		if (uv_blade_nr_possible_cpus(uvhub)) { +			unsigned long val; +			unsigned long mmr;  			pnode = uv_blade_to_pnode(uvhub);  			/* INIT the bau */ -			uv_write_global_mmr64(pnode, -					UVH_LB_BAU_SB_ACTIVATION_CONTROL, -					((unsigned long)1 << 63)); +			val = 1L << 63; +			write_gmmr_activation(pnode, val);  			mmr = 1; /* should be 1 to broadcast to both sockets */ -			uv_write_global_mmr64(pnode, UVH_BAU_DATA_BROADCAST, -						mmr); +			if (!is_uv1_hub()) +				write_mmr_data_broadcast(pnode, mmr);  		}  	} diff --git a/arch/x86/platform/uv/uv_irq.c b/arch/x86/platform/uv/uv_irq.c index 7b24460917d..b233681af4d 100644 --- a/arch/x86/platform/uv/uv_irq.c +++ b/arch/x86/platform/uv/uv_irq.c @@ -25,7 +25,7 @@ struct uv_irq_2_mmr_pnode{  	int			irq;  }; -static spinlock_t		uv_irq_lock; +static DEFINE_SPINLOCK(uv_irq_lock);  static struct rb_root		uv_irq_root;  static int uv_set_irq_affinity(struct irq_data *, const struct cpumask *, bool); @@ -131,10 +131,11 @@ arch_enable_uv_irq(char *irq_name, unsigned int irq, int cpu, int mmr_blade,  		       unsigned long mmr_offset, int limit)  {  	const struct cpumask *eligible_cpu = cpumask_of(cpu); -	struct irq_cfg *cfg = get_irq_chip_data(irq); +	struct irq_cfg *cfg = irq_get_chip_data(irq);  	unsigned long mmr_value;  	struct uv_IO_APIC_route_entry *entry;  	int mmr_pnode, err; +	unsigned int dest;  	BUILD_BUG_ON(sizeof(struct uv_IO_APIC_route_entry) !=  			sizeof(unsigned long)); @@ -143,12 +144,16 @@ arch_enable_uv_irq(char *irq_name, unsigned int irq, int cpu, int mmr_blade,  	if (err != 0)  		return err; +	err = apic->cpu_mask_to_apicid_and(eligible_cpu, eligible_cpu, &dest); +	if (err != 0) +		return err; +  	if (limit == UV_AFFINITY_CPU)  		irq_set_status_flags(irq, IRQ_NO_BALANCING);  	else  		irq_set_status_flags(irq, IRQ_MOVE_PCNTXT); -	set_irq_chip_and_handler_name(irq, &uv_irq_chip, handle_percpu_irq, +	irq_set_chip_and_handler_name(irq, &uv_irq_chip, handle_percpu_irq,  				      irq_name);  	mmr_value = 0; @@ -159,7 +164,7 @@ arch_enable_uv_irq(char *irq_name, unsigned int irq, int cpu, int mmr_blade,  	entry->polarity		= 0;  	entry->trigger		= 0;  	entry->mask		= 0; -	entry->dest		= apic->cpu_mask_to_apicid(eligible_cpu); +	entry->dest		= dest;  	mmr_pnode = uv_blade_to_pnode(mmr_blade);  	uv_write_global_mmr64(mmr_pnode, mmr_offset, mmr_value); @@ -222,7 +227,7 @@ uv_set_irq_affinity(struct irq_data *data, const struct cpumask *mask,  	if (cfg->move_in_progress)  		send_cleanup_vector(cfg); -	return 0; +	return IRQ_SET_MASK_OK_NOCOPY;  }  /* @@ -233,11 +238,9 @@ uv_set_irq_affinity(struct irq_data *data, const struct cpumask *mask,  int uv_setup_irq(char *irq_name, int cpu, int mmr_blade,  		 unsigned long mmr_offset, int limit)  { -	int irq, ret; - -	irq = create_irq_nr(NR_IRQS_LEGACY, uv_blade_to_memory_nid(mmr_blade)); +	int ret, irq = irq_alloc_hwirq(uv_blade_to_memory_nid(mmr_blade)); -	if (irq <= 0) +	if (!irq)  		return -EBUSY;  	ret = arch_enable_uv_irq(irq_name, irq, cpu, mmr_blade, mmr_offset, @@ -245,7 +248,7 @@ int uv_setup_irq(char *irq_name, int cpu, int mmr_blade,  	if (ret == irq)  		uv_set_irq_2_mmr_info(irq, mmr_offset, mmr_blade);  	else -		destroy_irq(irq); +		irq_free_hwirq(irq);  	return ret;  } @@ -280,6 +283,6 @@ void uv_teardown_irq(unsigned int irq)  			n = n->rb_right;  	}  	spin_unlock_irqrestore(&uv_irq_lock, irqflags); -	destroy_irq(irq); +	irq_free_hwirq(irq);  }  EXPORT_SYMBOL_GPL(uv_teardown_irq); diff --git a/arch/x86/platform/uv/uv_nmi.c b/arch/x86/platform/uv/uv_nmi.c new file mode 100644 index 00000000000..c89c93320c1 --- /dev/null +++ b/arch/x86/platform/uv/uv_nmi.c @@ -0,0 +1,727 @@ +/* + * SGI NMI support routines + * + *  This program is free software; you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation; either version 2 of the License, or + *  (at your option) any later version. + * + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details. + * + *  You should have received a copy of the GNU General Public License + *  along with this program; if not, write to the Free Software + *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA + * + *  Copyright (c) 2009-2013 Silicon Graphics, Inc.  All Rights Reserved. + *  Copyright (c) Mike Travis + */ + +#include <linux/cpu.h> +#include <linux/delay.h> +#include <linux/kdb.h> +#include <linux/kexec.h> +#include <linux/kgdb.h> +#include <linux/module.h> +#include <linux/nmi.h> +#include <linux/sched.h> +#include <linux/slab.h> + +#include <asm/apic.h> +#include <asm/current.h> +#include <asm/kdebug.h> +#include <asm/local64.h> +#include <asm/nmi.h> +#include <asm/traps.h> +#include <asm/uv/uv.h> +#include <asm/uv/uv_hub.h> +#include <asm/uv/uv_mmrs.h> + +/* + * UV handler for NMI + * + * Handle system-wide NMI events generated by the global 'power nmi' command. + * + * Basic operation is to field the NMI interrupt on each cpu and wait + * until all cpus have arrived into the nmi handler.  If some cpus do not + * make it into the handler, try and force them in with the IPI(NMI) signal. + * + * We also have to lessen UV Hub MMR accesses as much as possible as this + * disrupts the UV Hub's primary mission of directing NumaLink traffic and + * can cause system problems to occur. + * + * To do this we register our primary NMI notifier on the NMI_UNKNOWN + * chain.  This reduces the number of false NMI calls when the perf + * tools are running which generate an enormous number of NMIs per + * second (~4M/s for 1024 cpu threads).  Our secondary NMI handler is + * very short as it only checks that if it has been "pinged" with the + * IPI(NMI) signal as mentioned above, and does not read the UV Hub's MMR. + * + */ + +static struct uv_hub_nmi_s **uv_hub_nmi_list; + +DEFINE_PER_CPU(struct uv_cpu_nmi_s, __uv_cpu_nmi); +EXPORT_PER_CPU_SYMBOL_GPL(__uv_cpu_nmi); + +static unsigned long nmi_mmr; +static unsigned long nmi_mmr_clear; +static unsigned long nmi_mmr_pending; + +static atomic_t	uv_in_nmi; +static atomic_t uv_nmi_cpu = ATOMIC_INIT(-1); +static atomic_t uv_nmi_cpus_in_nmi = ATOMIC_INIT(-1); +static atomic_t uv_nmi_slave_continue; +static cpumask_var_t uv_nmi_cpu_mask; + +/* Values for uv_nmi_slave_continue */ +#define SLAVE_CLEAR	0 +#define SLAVE_CONTINUE	1 +#define SLAVE_EXIT	2 + +/* + * Default is all stack dumps go to the console and buffer. + * Lower level to send to log buffer only. + */ +static int uv_nmi_loglevel = CONSOLE_LOGLEVEL_DEFAULT; +module_param_named(dump_loglevel, uv_nmi_loglevel, int, 0644); + +/* + * The following values show statistics on how perf events are affecting + * this system. + */ +static int param_get_local64(char *buffer, const struct kernel_param *kp) +{ +	return sprintf(buffer, "%lu\n", local64_read((local64_t *)kp->arg)); +} + +static int param_set_local64(const char *val, const struct kernel_param *kp) +{ +	/* clear on any write */ +	local64_set((local64_t *)kp->arg, 0); +	return 0; +} + +static struct kernel_param_ops param_ops_local64 = { +	.get = param_get_local64, +	.set = param_set_local64, +}; +#define param_check_local64(name, p) __param_check(name, p, local64_t) + +static local64_t uv_nmi_count; +module_param_named(nmi_count, uv_nmi_count, local64, 0644); + +static local64_t uv_nmi_misses; +module_param_named(nmi_misses, uv_nmi_misses, local64, 0644); + +static local64_t uv_nmi_ping_count; +module_param_named(ping_count, uv_nmi_ping_count, local64, 0644); + +static local64_t uv_nmi_ping_misses; +module_param_named(ping_misses, uv_nmi_ping_misses, local64, 0644); + +/* + * Following values allow tuning for large systems under heavy loading + */ +static int uv_nmi_initial_delay = 100; +module_param_named(initial_delay, uv_nmi_initial_delay, int, 0644); + +static int uv_nmi_slave_delay = 100; +module_param_named(slave_delay, uv_nmi_slave_delay, int, 0644); + +static int uv_nmi_loop_delay = 100; +module_param_named(loop_delay, uv_nmi_loop_delay, int, 0644); + +static int uv_nmi_trigger_delay = 10000; +module_param_named(trigger_delay, uv_nmi_trigger_delay, int, 0644); + +static int uv_nmi_wait_count = 100; +module_param_named(wait_count, uv_nmi_wait_count, int, 0644); + +static int uv_nmi_retry_count = 500; +module_param_named(retry_count, uv_nmi_retry_count, int, 0644); + +/* + * Valid NMI Actions: + *  "dump"	- dump process stack for each cpu + *  "ips"	- dump IP info for each cpu + *  "kdump"	- do crash dump + *  "kdb"	- enter KDB (default) + *  "kgdb"	- enter KGDB + */ +static char uv_nmi_action[8] = "kdb"; +module_param_string(action, uv_nmi_action, sizeof(uv_nmi_action), 0644); + +static inline bool uv_nmi_action_is(const char *action) +{ +	return (strncmp(uv_nmi_action, action, strlen(action)) == 0); +} + +/* Setup which NMI support is present in system */ +static void uv_nmi_setup_mmrs(void) +{ +	if (uv_read_local_mmr(UVH_NMI_MMRX_SUPPORTED)) { +		uv_write_local_mmr(UVH_NMI_MMRX_REQ, +					1UL << UVH_NMI_MMRX_REQ_SHIFT); +		nmi_mmr = UVH_NMI_MMRX; +		nmi_mmr_clear = UVH_NMI_MMRX_CLEAR; +		nmi_mmr_pending = 1UL << UVH_NMI_MMRX_SHIFT; +		pr_info("UV: SMI NMI support: %s\n", UVH_NMI_MMRX_TYPE); +	} else { +		nmi_mmr = UVH_NMI_MMR; +		nmi_mmr_clear = UVH_NMI_MMR_CLEAR; +		nmi_mmr_pending = 1UL << UVH_NMI_MMR_SHIFT; +		pr_info("UV: SMI NMI support: %s\n", UVH_NMI_MMR_TYPE); +	} +} + +/* Read NMI MMR and check if NMI flag was set by BMC. */ +static inline int uv_nmi_test_mmr(struct uv_hub_nmi_s *hub_nmi) +{ +	hub_nmi->nmi_value = uv_read_local_mmr(nmi_mmr); +	atomic_inc(&hub_nmi->read_mmr_count); +	return !!(hub_nmi->nmi_value & nmi_mmr_pending); +} + +static inline void uv_local_mmr_clear_nmi(void) +{ +	uv_write_local_mmr(nmi_mmr_clear, nmi_mmr_pending); +} + +/* + * If first cpu in on this hub, set hub_nmi "in_nmi" and "owner" values and + * return true.  If first cpu in on the system, set global "in_nmi" flag. + */ +static int uv_set_in_nmi(int cpu, struct uv_hub_nmi_s *hub_nmi) +{ +	int first = atomic_add_unless(&hub_nmi->in_nmi, 1, 1); + +	if (first) { +		atomic_set(&hub_nmi->cpu_owner, cpu); +		if (atomic_add_unless(&uv_in_nmi, 1, 1)) +			atomic_set(&uv_nmi_cpu, cpu); + +		atomic_inc(&hub_nmi->nmi_count); +	} +	return first; +} + +/* Check if this is a system NMI event */ +static int uv_check_nmi(struct uv_hub_nmi_s *hub_nmi) +{ +	int cpu = smp_processor_id(); +	int nmi = 0; + +	local64_inc(&uv_nmi_count); +	uv_cpu_nmi.queries++; + +	do { +		nmi = atomic_read(&hub_nmi->in_nmi); +		if (nmi) +			break; + +		if (raw_spin_trylock(&hub_nmi->nmi_lock)) { + +			/* check hub MMR NMI flag */ +			if (uv_nmi_test_mmr(hub_nmi)) { +				uv_set_in_nmi(cpu, hub_nmi); +				nmi = 1; +				break; +			} + +			/* MMR NMI flag is clear */ +			raw_spin_unlock(&hub_nmi->nmi_lock); + +		} else { +			/* wait a moment for the hub nmi locker to set flag */ +			cpu_relax(); +			udelay(uv_nmi_slave_delay); + +			/* re-check hub in_nmi flag */ +			nmi = atomic_read(&hub_nmi->in_nmi); +			if (nmi) +				break; +		} + +		/* check if this BMC missed setting the MMR NMI flag */ +		if (!nmi) { +			nmi = atomic_read(&uv_in_nmi); +			if (nmi) +				uv_set_in_nmi(cpu, hub_nmi); +		} + +	} while (0); + +	if (!nmi) +		local64_inc(&uv_nmi_misses); + +	return nmi; +} + +/* Need to reset the NMI MMR register, but only once per hub. */ +static inline void uv_clear_nmi(int cpu) +{ +	struct uv_hub_nmi_s *hub_nmi = uv_hub_nmi; + +	if (cpu == atomic_read(&hub_nmi->cpu_owner)) { +		atomic_set(&hub_nmi->cpu_owner, -1); +		atomic_set(&hub_nmi->in_nmi, 0); +		uv_local_mmr_clear_nmi(); +		raw_spin_unlock(&hub_nmi->nmi_lock); +	} +} + +/* Print non-responding cpus */ +static void uv_nmi_nr_cpus_pr(char *fmt) +{ +	static char cpu_list[1024]; +	int len = sizeof(cpu_list); +	int c = cpumask_weight(uv_nmi_cpu_mask); +	int n = cpulist_scnprintf(cpu_list, len, uv_nmi_cpu_mask); + +	if (n >= len-1) +		strcpy(&cpu_list[len - 6], "...\n"); + +	printk(fmt, c, cpu_list); +} + +/* Ping non-responding cpus attemping to force them into the NMI handler */ +static void uv_nmi_nr_cpus_ping(void) +{ +	int cpu; + +	for_each_cpu(cpu, uv_nmi_cpu_mask) +		atomic_set(&uv_cpu_nmi_per(cpu).pinging, 1); + +	apic->send_IPI_mask(uv_nmi_cpu_mask, APIC_DM_NMI); +} + +/* Clean up flags for cpus that ignored both NMI and ping */ +static void uv_nmi_cleanup_mask(void) +{ +	int cpu; + +	for_each_cpu(cpu, uv_nmi_cpu_mask) { +		atomic_set(&uv_cpu_nmi_per(cpu).pinging, 0); +		atomic_set(&uv_cpu_nmi_per(cpu).state, UV_NMI_STATE_OUT); +		cpumask_clear_cpu(cpu, uv_nmi_cpu_mask); +	} +} + +/* Loop waiting as cpus enter nmi handler */ +static int uv_nmi_wait_cpus(int first) +{ +	int i, j, k, n = num_online_cpus(); +	int last_k = 0, waiting = 0; + +	if (first) { +		cpumask_copy(uv_nmi_cpu_mask, cpu_online_mask); +		k = 0; +	} else { +		k = n - cpumask_weight(uv_nmi_cpu_mask); +	} + +	udelay(uv_nmi_initial_delay); +	for (i = 0; i < uv_nmi_retry_count; i++) { +		int loop_delay = uv_nmi_loop_delay; + +		for_each_cpu(j, uv_nmi_cpu_mask) { +			if (atomic_read(&uv_cpu_nmi_per(j).state)) { +				cpumask_clear_cpu(j, uv_nmi_cpu_mask); +				if (++k >= n) +					break; +			} +		} +		if (k >= n) {		/* all in? */ +			k = n; +			break; +		} +		if (last_k != k) {	/* abort if no new cpus coming in */ +			last_k = k; +			waiting = 0; +		} else if (++waiting > uv_nmi_wait_count) +			break; + +		/* extend delay if waiting only for cpu 0 */ +		if (waiting && (n - k) == 1 && +		    cpumask_test_cpu(0, uv_nmi_cpu_mask)) +			loop_delay *= 100; + +		udelay(loop_delay); +	} +	atomic_set(&uv_nmi_cpus_in_nmi, k); +	return n - k; +} + +/* Wait until all slave cpus have entered UV NMI handler */ +static void uv_nmi_wait(int master) +{ +	/* indicate this cpu is in */ +	atomic_set(&uv_cpu_nmi.state, UV_NMI_STATE_IN); + +	/* if not the first cpu in (the master), then we are a slave cpu */ +	if (!master) +		return; + +	do { +		/* wait for all other cpus to gather here */ +		if (!uv_nmi_wait_cpus(1)) +			break; + +		/* if not all made it in, send IPI NMI to them */ +		uv_nmi_nr_cpus_pr(KERN_ALERT +			"UV: Sending NMI IPI to %d non-responding CPUs: %s\n"); +		uv_nmi_nr_cpus_ping(); + +		/* if all cpus are in, then done */ +		if (!uv_nmi_wait_cpus(0)) +			break; + +		uv_nmi_nr_cpus_pr(KERN_ALERT +			"UV: %d CPUs not in NMI loop: %s\n"); +	} while (0); + +	pr_alert("UV: %d of %d CPUs in NMI\n", +		atomic_read(&uv_nmi_cpus_in_nmi), num_online_cpus()); +} + +static void uv_nmi_dump_cpu_ip_hdr(void) +{ +	printk(KERN_DEFAULT +		"\nUV: %4s %6s %-32s %s   (Note: PID 0 not listed)\n", +		"CPU", "PID", "COMMAND", "IP"); +} + +static void uv_nmi_dump_cpu_ip(int cpu, struct pt_regs *regs) +{ +	printk(KERN_DEFAULT "UV: %4d %6d %-32.32s ", +		cpu, current->pid, current->comm); + +	printk_address(regs->ip); +} + +/* Dump this cpu's state */ +static void uv_nmi_dump_state_cpu(int cpu, struct pt_regs *regs) +{ +	const char *dots = " ................................. "; + +	if (uv_nmi_action_is("ips")) { +		if (cpu == 0) +			uv_nmi_dump_cpu_ip_hdr(); + +		if (current->pid != 0) +			uv_nmi_dump_cpu_ip(cpu, regs); + +	} else if (uv_nmi_action_is("dump")) { +		printk(KERN_DEFAULT +			"UV:%sNMI process trace for CPU %d\n", dots, cpu); +		show_regs(regs); +	} +	atomic_set(&uv_cpu_nmi.state, UV_NMI_STATE_DUMP_DONE); +} + +/* Trigger a slave cpu to dump it's state */ +static void uv_nmi_trigger_dump(int cpu) +{ +	int retry = uv_nmi_trigger_delay; + +	if (atomic_read(&uv_cpu_nmi_per(cpu).state) != UV_NMI_STATE_IN) +		return; + +	atomic_set(&uv_cpu_nmi_per(cpu).state, UV_NMI_STATE_DUMP); +	do { +		cpu_relax(); +		udelay(10); +		if (atomic_read(&uv_cpu_nmi_per(cpu).state) +				!= UV_NMI_STATE_DUMP) +			return; +	} while (--retry > 0); + +	pr_crit("UV: CPU %d stuck in process dump function\n", cpu); +	atomic_set(&uv_cpu_nmi_per(cpu).state, UV_NMI_STATE_DUMP_DONE); +} + +/* Wait until all cpus ready to exit */ +static void uv_nmi_sync_exit(int master) +{ +	atomic_dec(&uv_nmi_cpus_in_nmi); +	if (master) { +		while (atomic_read(&uv_nmi_cpus_in_nmi) > 0) +			cpu_relax(); +		atomic_set(&uv_nmi_slave_continue, SLAVE_CLEAR); +	} else { +		while (atomic_read(&uv_nmi_slave_continue)) +			cpu_relax(); +	} +} + +/* Walk through cpu list and dump state of each */ +static void uv_nmi_dump_state(int cpu, struct pt_regs *regs, int master) +{ +	if (master) { +		int tcpu; +		int ignored = 0; +		int saved_console_loglevel = console_loglevel; + +		pr_alert("UV: tracing %s for %d CPUs from CPU %d\n", +			uv_nmi_action_is("ips") ? "IPs" : "processes", +			atomic_read(&uv_nmi_cpus_in_nmi), cpu); + +		console_loglevel = uv_nmi_loglevel; +		atomic_set(&uv_nmi_slave_continue, SLAVE_EXIT); +		for_each_online_cpu(tcpu) { +			if (cpumask_test_cpu(tcpu, uv_nmi_cpu_mask)) +				ignored++; +			else if (tcpu == cpu) +				uv_nmi_dump_state_cpu(tcpu, regs); +			else +				uv_nmi_trigger_dump(tcpu); +		} +		if (ignored) +			printk(KERN_DEFAULT "UV: %d CPUs ignored NMI\n", +				ignored); + +		console_loglevel = saved_console_loglevel; +		pr_alert("UV: process trace complete\n"); +	} else { +		while (!atomic_read(&uv_nmi_slave_continue)) +			cpu_relax(); +		while (atomic_read(&uv_cpu_nmi.state) != UV_NMI_STATE_DUMP) +			cpu_relax(); +		uv_nmi_dump_state_cpu(cpu, regs); +	} +	uv_nmi_sync_exit(master); +} + +static void uv_nmi_touch_watchdogs(void) +{ +	touch_softlockup_watchdog_sync(); +	clocksource_touch_watchdog(); +	rcu_cpu_stall_reset(); +	touch_nmi_watchdog(); +} + +#if defined(CONFIG_KEXEC) +static atomic_t uv_nmi_kexec_failed; +static void uv_nmi_kdump(int cpu, int master, struct pt_regs *regs) +{ +	/* Call crash to dump system state */ +	if (master) { +		pr_emerg("UV: NMI executing crash_kexec on CPU%d\n", cpu); +		crash_kexec(regs); + +		pr_emerg("UV: crash_kexec unexpectedly returned, "); +		if (!kexec_crash_image) { +			pr_cont("crash kernel not loaded\n"); +			atomic_set(&uv_nmi_kexec_failed, 1); +			uv_nmi_sync_exit(1); +			return; +		} +		pr_cont("kexec busy, stalling cpus while waiting\n"); +	} + +	/* If crash exec fails the slaves should return, otherwise stall */ +	while (atomic_read(&uv_nmi_kexec_failed) == 0) +		mdelay(10); + +	/* Crash kernel most likely not loaded, return in an orderly fashion */ +	uv_nmi_sync_exit(0); +} + +#else /* !CONFIG_KEXEC */ +static inline void uv_nmi_kdump(int cpu, int master, struct pt_regs *regs) +{ +	if (master) +		pr_err("UV: NMI kdump: KEXEC not supported in this kernel\n"); +} +#endif /* !CONFIG_KEXEC */ + +#ifdef CONFIG_KGDB +#ifdef CONFIG_KGDB_KDB +static inline int uv_nmi_kdb_reason(void) +{ +	return KDB_REASON_SYSTEM_NMI; +} +#else /* !CONFIG_KGDB_KDB */ +static inline int uv_nmi_kdb_reason(void) +{ +	/* Insure user is expecting to attach gdb remote */ +	if (uv_nmi_action_is("kgdb")) +		return 0; + +	pr_err("UV: NMI error: KDB is not enabled in this kernel\n"); +	return -1; +} +#endif /* CONFIG_KGDB_KDB */ + +/* + * Call KGDB/KDB from NMI handler + * + * Note that if both KGDB and KDB are configured, then the action of 'kgdb' or + * 'kdb' has no affect on which is used.  See the KGDB documention for further + * information. + */ +static void uv_call_kgdb_kdb(int cpu, struct pt_regs *regs, int master) +{ +	if (master) { +		int reason = uv_nmi_kdb_reason(); +		int ret; + +		if (reason < 0) +			return; + +		/* call KGDB NMI handler as MASTER */ +		ret = kgdb_nmicallin(cpu, X86_TRAP_NMI, regs, reason, +				&uv_nmi_slave_continue); +		if (ret) { +			pr_alert("KGDB returned error, is kgdboc set?\n"); +			atomic_set(&uv_nmi_slave_continue, SLAVE_EXIT); +		} +	} else { +		/* wait for KGDB signal that it's ready for slaves to enter */ +		int sig; + +		do { +			cpu_relax(); +			sig = atomic_read(&uv_nmi_slave_continue); +		} while (!sig); + +		/* call KGDB as slave */ +		if (sig == SLAVE_CONTINUE) +			kgdb_nmicallback(cpu, regs); +	} +	uv_nmi_sync_exit(master); +} + +#else /* !CONFIG_KGDB */ +static inline void uv_call_kgdb_kdb(int cpu, struct pt_regs *regs, int master) +{ +	pr_err("UV: NMI error: KGDB is not enabled in this kernel\n"); +} +#endif /* !CONFIG_KGDB */ + +/* + * UV NMI handler + */ +int uv_handle_nmi(unsigned int reason, struct pt_regs *regs) +{ +	struct uv_hub_nmi_s *hub_nmi = uv_hub_nmi; +	int cpu = smp_processor_id(); +	int master = 0; +	unsigned long flags; + +	local_irq_save(flags); + +	/* If not a UV System NMI, ignore */ +	if (!atomic_read(&uv_cpu_nmi.pinging) && !uv_check_nmi(hub_nmi)) { +		local_irq_restore(flags); +		return NMI_DONE; +	} + +	/* Indicate we are the first CPU into the NMI handler */ +	master = (atomic_read(&uv_nmi_cpu) == cpu); + +	/* If NMI action is "kdump", then attempt to do it */ +	if (uv_nmi_action_is("kdump")) +		uv_nmi_kdump(cpu, master, regs); + +	/* Pause as all cpus enter the NMI handler */ +	uv_nmi_wait(master); + +	/* Dump state of each cpu */ +	if (uv_nmi_action_is("ips") || uv_nmi_action_is("dump")) +		uv_nmi_dump_state(cpu, regs, master); + +	/* Call KGDB/KDB if enabled */ +	else if (uv_nmi_action_is("kdb") || uv_nmi_action_is("kgdb")) +		uv_call_kgdb_kdb(cpu, regs, master); + +	/* Clear per_cpu "in nmi" flag */ +	atomic_set(&uv_cpu_nmi.state, UV_NMI_STATE_OUT); + +	/* Clear MMR NMI flag on each hub */ +	uv_clear_nmi(cpu); + +	/* Clear global flags */ +	if (master) { +		if (cpumask_weight(uv_nmi_cpu_mask)) +			uv_nmi_cleanup_mask(); +		atomic_set(&uv_nmi_cpus_in_nmi, -1); +		atomic_set(&uv_nmi_cpu, -1); +		atomic_set(&uv_in_nmi, 0); +	} + +	uv_nmi_touch_watchdogs(); +	local_irq_restore(flags); + +	return NMI_HANDLED; +} + +/* + * NMI handler for pulling in CPUs when perf events are grabbing our NMI + */ +static int uv_handle_nmi_ping(unsigned int reason, struct pt_regs *regs) +{ +	int ret; + +	uv_cpu_nmi.queries++; +	if (!atomic_read(&uv_cpu_nmi.pinging)) { +		local64_inc(&uv_nmi_ping_misses); +		return NMI_DONE; +	} + +	uv_cpu_nmi.pings++; +	local64_inc(&uv_nmi_ping_count); +	ret = uv_handle_nmi(reason, regs); +	atomic_set(&uv_cpu_nmi.pinging, 0); +	return ret; +} + +static void uv_register_nmi_notifier(void) +{ +	if (register_nmi_handler(NMI_UNKNOWN, uv_handle_nmi, 0, "uv")) +		pr_warn("UV: NMI handler failed to register\n"); + +	if (register_nmi_handler(NMI_LOCAL, uv_handle_nmi_ping, 0, "uvping")) +		pr_warn("UV: PING NMI handler failed to register\n"); +} + +void uv_nmi_init(void) +{ +	unsigned int value; + +	/* +	 * Unmask NMI on all cpus +	 */ +	value = apic_read(APIC_LVT1) | APIC_DM_NMI; +	value &= ~APIC_LVT_MASKED; +	apic_write(APIC_LVT1, value); +} + +void uv_nmi_setup(void) +{ +	int size = sizeof(void *) * (1 << NODES_SHIFT); +	int cpu, nid; + +	/* Setup hub nmi info */ +	uv_nmi_setup_mmrs(); +	uv_hub_nmi_list = kzalloc(size, GFP_KERNEL); +	pr_info("UV: NMI hub list @ 0x%p (%d)\n", uv_hub_nmi_list, size); +	BUG_ON(!uv_hub_nmi_list); +	size = sizeof(struct uv_hub_nmi_s); +	for_each_present_cpu(cpu) { +		nid = cpu_to_node(cpu); +		if (uv_hub_nmi_list[nid] == NULL) { +			uv_hub_nmi_list[nid] = kzalloc_node(size, +							    GFP_KERNEL, nid); +			BUG_ON(!uv_hub_nmi_list[nid]); +			raw_spin_lock_init(&(uv_hub_nmi_list[nid]->nmi_lock)); +			atomic_set(&uv_hub_nmi_list[nid]->cpu_owner, -1); +		} +		uv_hub_nmi_per(cpu) = uv_hub_nmi_list[nid]; +	} +	BUG_ON(!alloc_cpumask_var(&uv_nmi_cpu_mask, GFP_KERNEL)); +	uv_register_nmi_notifier(); +} diff --git a/arch/x86/platform/uv/uv_sysfs.c b/arch/x86/platform/uv/uv_sysfs.c index 309c70fb775..5d4ba301e77 100644 --- a/arch/x86/platform/uv/uv_sysfs.c +++ b/arch/x86/platform/uv/uv_sysfs.c @@ -19,7 +19,7 @@   *  Copyright (c) Russ Anderson   */ -#include <linux/sysdev.h> +#include <linux/device.h>  #include <asm/uv/bios.h>  #include <asm/uv/uv.h> diff --git a/arch/x86/platform/uv/uv_time.c b/arch/x86/platform/uv/uv_time.c index 56e421bc379..5c86786bbfd 100644 --- a/arch/x86/platform/uv/uv_time.c +++ b/arch/x86/platform/uv/uv_time.c @@ -15,7 +15,7 @@   *  along with this program; if not, write to the Free Software   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA   * - *  Copyright (c) 2009 Silicon Graphics, Inc.  All Rights Reserved. + *  Copyright (c) 2009-2013 Silicon Graphics, Inc.  All Rights Reserved.   *  Copyright (c) Dimitri Sivanich   */  #include <linux/clockchips.h> @@ -37,10 +37,9 @@ static void uv_rtc_timer_setup(enum clock_event_mode,  static struct clocksource clocksource_uv = {  	.name		= RTC_NAME, -	.rating		= 400, +	.rating		= 299,  	.read		= uv_read_rtc,  	.mask		= (cycle_t)UVH_RTC_REAL_TIME_CLOCK_MASK, -	.shift		= 10,  	.flags		= CLOCK_SOURCE_IS_CONTINUOUS,  }; @@ -89,6 +88,7 @@ static void uv_rtc_send_IPI(int cpu)  	apicid = cpu_physical_id(cpu);  	pnode = uv_apicid_to_pnode(apicid); +	apicid |= uv_apicid_hibits;  	val = (1UL << UVH_IPI_INT_SEND_SHFT) |  	      (apicid << UVH_IPI_INT_APIC_ID_SHFT) |  	      (X86_PLATFORM_IPI_VECTOR << UVH_IPI_INT_VECTOR_SHFT); @@ -99,25 +99,35 @@ static void uv_rtc_send_IPI(int cpu)  /* Check for an RTC interrupt pending */  static int uv_intr_pending(int pnode)  { -	return uv_read_global_mmr64(pnode, UVH_EVENT_OCCURRED0) & -		UVH_EVENT_OCCURRED0_RTC1_MASK; +	if (is_uv1_hub()) +		return uv_read_global_mmr64(pnode, UVH_EVENT_OCCURRED0) & +			UV1H_EVENT_OCCURRED0_RTC1_MASK; +	else if (is_uvx_hub()) +		return uv_read_global_mmr64(pnode, UVXH_EVENT_OCCURRED2) & +			UVXH_EVENT_OCCURRED2_RTC_1_MASK; +	return 0;  }  /* Setup interrupt and return non-zero if early expiration occurred. */  static int uv_setup_intr(int cpu, u64 expires)  {  	u64 val; +	unsigned long apicid = cpu_physical_id(cpu) | uv_apicid_hibits;  	int pnode = uv_cpu_to_pnode(cpu);  	uv_write_global_mmr64(pnode, UVH_RTC1_INT_CONFIG,  		UVH_RTC1_INT_CONFIG_M_MASK);  	uv_write_global_mmr64(pnode, UVH_INT_CMPB, -1L); -	uv_write_global_mmr64(pnode, UVH_EVENT_OCCURRED0_ALIAS, -		UVH_EVENT_OCCURRED0_RTC1_MASK); +	if (is_uv1_hub()) +		uv_write_global_mmr64(pnode, UVH_EVENT_OCCURRED0_ALIAS, +				UV1H_EVENT_OCCURRED0_RTC1_MASK); +	else +		uv_write_global_mmr64(pnode, UVXH_EVENT_OCCURRED2_ALIAS, +				UVXH_EVENT_OCCURRED2_RTC_1_MASK);  	val = (X86_PLATFORM_IPI_VECTOR << UVH_RTC1_INT_CONFIG_VECTOR_SHFT) | -		((u64)cpu_physical_id(cpu) << UVH_RTC1_INT_CONFIG_APIC_ID_SHFT); +		((u64)apicid << UVH_RTC1_INT_CONFIG_APIC_ID_SHFT);  	/* Set configuration */  	uv_write_global_mmr64(pnode, UVH_RTC1_INT_CONFIG, val); @@ -149,10 +159,9 @@ static __init int uv_rtc_allocate_timers(void)  {  	int cpu; -	blade_info = kmalloc(uv_possible_blades * sizeof(void *), GFP_KERNEL); +	blade_info = kzalloc(uv_possible_blades * sizeof(void *), GFP_KERNEL);  	if (!blade_info)  		return -ENOMEM; -	memset(blade_info, 0, uv_possible_blades * sizeof(void *));  	for_each_present_cpu(cpu) {  		int nid = cpu_to_node(cpu); @@ -370,14 +379,7 @@ static __init int uv_rtc_setup_clock(void)  	if (!is_uv_system())  		return -ENODEV; -	clocksource_uv.mult = clocksource_hz2mult(sn_rtc_cycles_per_second, -				clocksource_uv.shift); - -	/* If single blade, prefer tsc */ -	if (uv_num_possible_blades() == 1) -		clocksource_uv.rating = 250; - -	rc = clocksource_register(&clocksource_uv); +	rc = clocksource_register_hz(&clocksource_uv, sn_rtc_cycles_per_second);  	if (rc)  		printk(KERN_INFO "UV RTC clocksource failed rc %d\n", rc);  	else diff --git a/arch/x86/platform/visws/Makefile b/arch/x86/platform/visws/Makefile deleted file mode 100644 index 91bc17ab2fd..00000000000 --- a/arch/x86/platform/visws/Makefile +++ /dev/null @@ -1 +0,0 @@ -obj-$(CONFIG_X86_VISWS)	+= visws_quirks.o diff --git a/arch/x86/platform/visws/visws_quirks.c b/arch/x86/platform/visws/visws_quirks.c deleted file mode 100644 index 3371bd053b8..00000000000 --- a/arch/x86/platform/visws/visws_quirks.c +++ /dev/null @@ -1,614 +0,0 @@ -/* - *  SGI Visual Workstation support and quirks, unmaintained. - * - *  Split out from setup.c by davej@suse.de - * - *	Copyright (C) 1999 Bent Hagemark, Ingo Molnar - * - *  SGI Visual Workstation interrupt controller - * - *  The Cobalt system ASIC in the Visual Workstation contains a "Cobalt" APIC - *  which serves as the main interrupt controller in the system.  Non-legacy - *  hardware in the system uses this controller directly.  Legacy devices - *  are connected to the PIIX4 which in turn has its 8259(s) connected to - *  a of the Cobalt APIC entry. - * - *  09/02/2000 - Updated for 2.4 by jbarnes@sgi.com - * - *  25/11/2002 - Updated for 2.5 by Andrey Panin <pazke@orbita1.ru> - */ -#include <linux/interrupt.h> -#include <linux/module.h> -#include <linux/init.h> -#include <linux/smp.h> - -#include <asm/visws/cobalt.h> -#include <asm/visws/piix4.h> -#include <asm/io_apic.h> -#include <asm/fixmap.h> -#include <asm/reboot.h> -#include <asm/setup.h> -#include <asm/apic.h> -#include <asm/e820.h> -#include <asm/time.h> -#include <asm/io.h> - -#include <linux/kernel_stat.h> - -#include <asm/i8259.h> -#include <asm/irq_vectors.h> -#include <asm/visws/lithium.h> - -#include <linux/sched.h> -#include <linux/kernel.h> -#include <linux/pci.h> -#include <linux/pci_ids.h> - -extern int no_broadcast; - -char visws_board_type	= -1; -char visws_board_rev	= -1; - -static void __init visws_time_init(void) -{ -	printk(KERN_INFO "Starting Cobalt Timer system clock\n"); - -	/* Set the countdown value */ -	co_cpu_write(CO_CPU_TIMEVAL, CO_TIME_HZ/HZ); - -	/* Start the timer */ -	co_cpu_write(CO_CPU_CTRL, co_cpu_read(CO_CPU_CTRL) | CO_CTRL_TIMERUN); - -	/* Enable (unmask) the timer interrupt */ -	co_cpu_write(CO_CPU_CTRL, co_cpu_read(CO_CPU_CTRL) & ~CO_CTRL_TIMEMASK); - -	setup_default_timer_irq(); -} - -/* Replaces the default init_ISA_irqs in the generic setup */ -static void __init visws_pre_intr_init(void); - -/* Quirk for machine specific memory setup. */ - -#define MB (1024 * 1024) - -unsigned long sgivwfb_mem_phys; -unsigned long sgivwfb_mem_size; -EXPORT_SYMBOL(sgivwfb_mem_phys); -EXPORT_SYMBOL(sgivwfb_mem_size); - -long long mem_size __initdata = 0; - -static char * __init visws_memory_setup(void) -{ -	long long gfx_mem_size = 8 * MB; - -	mem_size = boot_params.alt_mem_k; - -	if (!mem_size) { -		printk(KERN_WARNING "Bootloader didn't set memory size, upgrade it !\n"); -		mem_size = 128 * MB; -	} - -	/* -	 * this hardcodes the graphics memory to 8 MB -	 * it really should be sized dynamically (or at least -	 * set as a boot param) -	 */ -	if (!sgivwfb_mem_size) { -		printk(KERN_WARNING "Defaulting to 8 MB framebuffer size\n"); -		sgivwfb_mem_size = 8 * MB; -	} - -	/* -	 * Trim to nearest MB -	 */ -	sgivwfb_mem_size &= ~((1 << 20) - 1); -	sgivwfb_mem_phys = mem_size - gfx_mem_size; - -	e820_add_region(0, LOWMEMSIZE(), E820_RAM); -	e820_add_region(HIGH_MEMORY, mem_size - sgivwfb_mem_size - HIGH_MEMORY, E820_RAM); -	e820_add_region(sgivwfb_mem_phys, sgivwfb_mem_size, E820_RESERVED); - -	return "PROM"; -} - -static void visws_machine_emergency_restart(void) -{ -	/* -	 * Visual Workstations restart after this -	 * register is poked on the PIIX4 -	 */ -	outb(PIIX4_RESET_VAL, PIIX4_RESET_PORT); -} - -static void visws_machine_power_off(void) -{ -	unsigned short pm_status; -/*	extern unsigned int pci_bus0; */ - -	while ((pm_status = inw(PMSTS_PORT)) & 0x100) -		outw(pm_status, PMSTS_PORT); - -	outw(PM_SUSPEND_ENABLE, PMCNTRL_PORT); - -	mdelay(10); - -#define PCI_CONF1_ADDRESS(bus, devfn, reg) \ -	(0x80000000 | (bus << 16) | (devfn << 8) | (reg & ~3)) - -/*	outl(PCI_CONF1_ADDRESS(pci_bus0, SPECIAL_DEV, SPECIAL_REG), 0xCF8); */ -	outl(PIIX_SPECIAL_STOP, 0xCFC); -} - -static void __init visws_get_smp_config(unsigned int early) -{ -} - -/* - * The Visual Workstation is Intel MP compliant in the hardware - * sense, but it doesn't have a BIOS(-configuration table). - * No problem for Linux. - */ - -static void __init MP_processor_info(struct mpc_cpu *m) -{ -	int ver, logical_apicid; -	physid_mask_t apic_cpus; - -	if (!(m->cpuflag & CPU_ENABLED)) -		return; - -	logical_apicid = m->apicid; -	printk(KERN_INFO "%sCPU #%d %u:%u APIC version %d\n", -	       m->cpuflag & CPU_BOOTPROCESSOR ? "Bootup " : "", -	       m->apicid, (m->cpufeature & CPU_FAMILY_MASK) >> 8, -	       (m->cpufeature & CPU_MODEL_MASK) >> 4, m->apicver); - -	if (m->cpuflag & CPU_BOOTPROCESSOR) -		boot_cpu_physical_apicid = m->apicid; - -	ver = m->apicver; -	if ((ver >= 0x14 && m->apicid >= 0xff) || m->apicid >= 0xf) { -		printk(KERN_ERR "Processor #%d INVALID. (Max ID: %d).\n", -			m->apicid, MAX_APICS); -		return; -	} - -	apic->apicid_to_cpu_present(m->apicid, &apic_cpus); -	physids_or(phys_cpu_present_map, phys_cpu_present_map, apic_cpus); -	/* -	 * Validate version -	 */ -	if (ver == 0x0) { -		printk(KERN_ERR "BIOS bug, APIC version is 0 for CPU#%d! " -			"fixing up to 0x10. (tell your hw vendor)\n", -			m->apicid); -		ver = 0x10; -	} -	apic_version[m->apicid] = ver; -} - -static void __init visws_find_smp_config(void) -{ -	struct mpc_cpu *mp = phys_to_virt(CO_CPU_TAB_PHYS); -	unsigned short ncpus = readw(phys_to_virt(CO_CPU_NUM_PHYS)); - -	if (ncpus > CO_CPU_MAX) { -		printk(KERN_WARNING "find_visws_smp: got cpu count of %d at %p\n", -			ncpus, mp); - -		ncpus = CO_CPU_MAX; -	} - -	if (ncpus > setup_max_cpus) -		ncpus = setup_max_cpus; - -#ifdef CONFIG_X86_LOCAL_APIC -	smp_found_config = 1; -#endif -	while (ncpus--) -		MP_processor_info(mp++); - -	mp_lapic_addr = APIC_DEFAULT_PHYS_BASE; -} - -static void visws_trap_init(void); - -void __init visws_early_detect(void) -{ -	int raw; - -	visws_board_type = (char)(inb_p(PIIX_GPI_BD_REG) & PIIX_GPI_BD_REG) -							 >> PIIX_GPI_BD_SHIFT; - -	if (visws_board_type < 0) -		return; - -	/* -	 * Override the default platform setup functions -	 */ -	x86_init.resources.memory_setup = visws_memory_setup; -	x86_init.mpparse.get_smp_config = visws_get_smp_config; -	x86_init.mpparse.find_smp_config = visws_find_smp_config; -	x86_init.irqs.pre_vector_init = visws_pre_intr_init; -	x86_init.irqs.trap_init = visws_trap_init; -	x86_init.timers.timer_init = visws_time_init; -	x86_init.pci.init = pci_visws_init; -	x86_init.pci.init_irq = x86_init_noop; - -	/* -	 * Install reboot quirks: -	 */ -	pm_power_off			= visws_machine_power_off; -	machine_ops.emergency_restart	= visws_machine_emergency_restart; - -	/* -	 * Do not use broadcast IPIs: -	 */ -	no_broadcast = 0; - -#ifdef CONFIG_X86_IO_APIC -	/* -	 * Turn off IO-APIC detection and initialization: -	 */ -	skip_ioapic_setup		= 1; -#endif - -	/* -	 * Get Board rev. -	 * First, we have to initialize the 307 part to allow us access -	 * to the GPIO registers.  Let's map them at 0x0fc0 which is right -	 * after the PIIX4 PM section. -	 */ -	outb_p(SIO_DEV_SEL, SIO_INDEX); -	outb_p(SIO_GP_DEV, SIO_DATA);	/* Talk to GPIO regs. */ - -	outb_p(SIO_DEV_MSB, SIO_INDEX); -	outb_p(SIO_GP_MSB, SIO_DATA);	/* MSB of GPIO base address */ - -	outb_p(SIO_DEV_LSB, SIO_INDEX); -	outb_p(SIO_GP_LSB, SIO_DATA);	/* LSB of GPIO base address */ - -	outb_p(SIO_DEV_ENB, SIO_INDEX); -	outb_p(1, SIO_DATA);		/* Enable GPIO registers. */ - -	/* -	 * Now, we have to map the power management section to write -	 * a bit which enables access to the GPIO registers. -	 * What lunatic came up with this shit? -	 */ -	outb_p(SIO_DEV_SEL, SIO_INDEX); -	outb_p(SIO_PM_DEV, SIO_DATA);	/* Talk to GPIO regs. */ - -	outb_p(SIO_DEV_MSB, SIO_INDEX); -	outb_p(SIO_PM_MSB, SIO_DATA);	/* MSB of PM base address */ - -	outb_p(SIO_DEV_LSB, SIO_INDEX); -	outb_p(SIO_PM_LSB, SIO_DATA);	/* LSB of PM base address */ - -	outb_p(SIO_DEV_ENB, SIO_INDEX); -	outb_p(1, SIO_DATA);		/* Enable PM registers. */ - -	/* -	 * Now, write the PM register which enables the GPIO registers. -	 */ -	outb_p(SIO_PM_FER2, SIO_PM_INDEX); -	outb_p(SIO_PM_GP_EN, SIO_PM_DATA); - -	/* -	 * Now, initialize the GPIO registers. -	 * We want them all to be inputs which is the -	 * power on default, so let's leave them alone. -	 * So, let's just read the board rev! -	 */ -	raw = inb_p(SIO_GP_DATA1); -	raw &= 0x7f;	/* 7 bits of valid board revision ID. */ - -	if (visws_board_type == VISWS_320) { -		if (raw < 0x6) { -			visws_board_rev = 4; -		} else if (raw < 0xc) { -			visws_board_rev = 5; -		} else { -			visws_board_rev = 6; -		} -	} else if (visws_board_type == VISWS_540) { -			visws_board_rev = 2; -		} else { -			visws_board_rev = raw; -		} - -	printk(KERN_INFO "Silicon Graphics Visual Workstation %s (rev %d) detected\n", -	       (visws_board_type == VISWS_320 ? "320" : -	       (visws_board_type == VISWS_540 ? "540" : -		"unknown")), visws_board_rev); -} - -#define A01234 (LI_INTA_0 | LI_INTA_1 | LI_INTA_2 | LI_INTA_3 | LI_INTA_4) -#define BCD (LI_INTB | LI_INTC | LI_INTD) -#define ALLDEVS (A01234 | BCD) - -static __init void lithium_init(void) -{ -	set_fixmap(FIX_LI_PCIA, LI_PCI_A_PHYS); -	set_fixmap(FIX_LI_PCIB, LI_PCI_B_PHYS); - -	if ((li_pcia_read16(PCI_VENDOR_ID) != PCI_VENDOR_ID_SGI) || -	    (li_pcia_read16(PCI_DEVICE_ID) != PCI_DEVICE_ID_SGI_LITHIUM)) { -		printk(KERN_EMERG "Lithium hostbridge %c not found\n", 'A'); -/*		panic("This machine is not SGI Visual Workstation 320/540"); */ -	} - -	if ((li_pcib_read16(PCI_VENDOR_ID) != PCI_VENDOR_ID_SGI) || -	    (li_pcib_read16(PCI_DEVICE_ID) != PCI_DEVICE_ID_SGI_LITHIUM)) { -		printk(KERN_EMERG "Lithium hostbridge %c not found\n", 'B'); -/*		panic("This machine is not SGI Visual Workstation 320/540"); */ -	} - -	li_pcia_write16(LI_PCI_INTEN, ALLDEVS); -	li_pcib_write16(LI_PCI_INTEN, ALLDEVS); -} - -static __init void cobalt_init(void) -{ -	/* -	 * On normal SMP PC this is used only with SMP, but we have to -	 * use it and set it up here to start the Cobalt clock -	 */ -	set_fixmap(FIX_APIC_BASE, APIC_DEFAULT_PHYS_BASE); -	setup_local_APIC(); -	printk(KERN_INFO "Local APIC Version %#x, ID %#x\n", -		(unsigned int)apic_read(APIC_LVR), -		(unsigned int)apic_read(APIC_ID)); - -	set_fixmap(FIX_CO_CPU, CO_CPU_PHYS); -	set_fixmap(FIX_CO_APIC, CO_APIC_PHYS); -	printk(KERN_INFO "Cobalt Revision %#lx, APIC ID %#lx\n", -		co_cpu_read(CO_CPU_REV), co_apic_read(CO_APIC_ID)); - -	/* Enable Cobalt APIC being careful to NOT change the ID! */ -	co_apic_write(CO_APIC_ID, co_apic_read(CO_APIC_ID) | CO_APIC_ENABLE); - -	printk(KERN_INFO "Cobalt APIC enabled: ID reg %#lx\n", -		co_apic_read(CO_APIC_ID)); -} - -static void __init visws_trap_init(void) -{ -	lithium_init(); -	cobalt_init(); -} - -/* - * IRQ controller / APIC support: - */ - -static DEFINE_SPINLOCK(cobalt_lock); - -/* - * Set the given Cobalt APIC Redirection Table entry to point - * to the given IDT vector/index. - */ -static inline void co_apic_set(int entry, int irq) -{ -	co_apic_write(CO_APIC_LO(entry), CO_APIC_LEVEL | (irq + FIRST_EXTERNAL_VECTOR)); -	co_apic_write(CO_APIC_HI(entry), 0); -} - -/* - * Cobalt (IO)-APIC functions to handle PCI devices. - */ -static inline int co_apic_ide0_hack(void) -{ -	extern char visws_board_type; -	extern char visws_board_rev; - -	if (visws_board_type == VISWS_320 && visws_board_rev == 5) -		return 5; -	return CO_APIC_IDE0; -} - -static int is_co_apic(unsigned int irq) -{ -	if (IS_CO_APIC(irq)) -		return CO_APIC(irq); - -	switch (irq) { -		case 0: return CO_APIC_CPU; -		case CO_IRQ_IDE0: return co_apic_ide0_hack(); -		case CO_IRQ_IDE1: return CO_APIC_IDE1; -		default: return -1; -	} -} - - -/* - * This is the SGI Cobalt (IO-)APIC: - */ -static void enable_cobalt_irq(struct irq_data *data) -{ -	co_apic_set(is_co_apic(data->irq), data->irq); -} - -static void disable_cobalt_irq(struct irq_data *data) -{ -	int entry = is_co_apic(data->irq); - -	co_apic_write(CO_APIC_LO(entry), CO_APIC_MASK); -	co_apic_read(CO_APIC_LO(entry)); -} - -static void ack_cobalt_irq(struct irq_data *data) -{ -	unsigned long flags; - -	spin_lock_irqsave(&cobalt_lock, flags); -	disable_cobalt_irq(data); -	apic_write(APIC_EOI, APIC_EIO_ACK); -	spin_unlock_irqrestore(&cobalt_lock, flags); -} - -static struct irq_chip cobalt_irq_type = { -	.name		= "Cobalt-APIC", -	.irq_enable	= enable_cobalt_irq, -	.irq_disable	= disable_cobalt_irq, -	.irq_ack	= ack_cobalt_irq, -}; - - -/* - * This is the PIIX4-based 8259 that is wired up indirectly to Cobalt - * -- not the manner expected by the code in i8259.c. - * - * there is a 'master' physical interrupt source that gets sent to - * the CPU. But in the chipset there are various 'virtual' interrupts - * waiting to be handled. We represent this to Linux through a 'master' - * interrupt controller type, and through a special virtual interrupt- - * controller. Device drivers only see the virtual interrupt sources. - */ -static unsigned int startup_piix4_master_irq(struct irq_data *data) -{ -	legacy_pic->init(0); -	enable_cobalt_irq(data); -} - -static void end_piix4_master_irq(struct irq_data *data) -{ -	unsigned long flags; - -	spin_lock_irqsave(&cobalt_lock, flags); -	enable_cobalt_irq(data); -	spin_unlock_irqrestore(&cobalt_lock, flags); -} - -static struct irq_chip piix4_master_irq_type = { -	.name		= "PIIX4-master", -	.irq_startup	= startup_piix4_master_irq, -	.irq_ack	= ack_cobalt_irq, -}; - -static void pii4_mask(struct irq_data *data) { } - -static struct irq_chip piix4_virtual_irq_type = { -	.name		= "PIIX4-virtual", -	.mask		= pii4_mask, -}; - -/* - * PIIX4-8259 master/virtual functions to handle interrupt requests - * from legacy devices: floppy, parallel, serial, rtc. - * - * None of these get Cobalt APIC entries, neither do they have IDT - * entries. These interrupts are purely virtual and distributed from - * the 'master' interrupt source: CO_IRQ_8259. - * - * When the 8259 interrupts its handler figures out which of these - * devices is interrupting and dispatches to its handler. - * - * CAREFUL: devices see the 'virtual' interrupt only. Thus disable/ - * enable_irq gets the right irq. This 'master' irq is never directly - * manipulated by any driver. - */ -static irqreturn_t piix4_master_intr(int irq, void *dev_id) -{ -	unsigned long flags; -	int realirq; - -	raw_spin_lock_irqsave(&i8259A_lock, flags); - -	/* Find out what's interrupting in the PIIX4 master 8259 */ -	outb(0x0c, 0x20);		/* OCW3 Poll command */ -	realirq = inb(0x20); - -	/* -	 * Bit 7 == 0 means invalid/spurious -	 */ -	if (unlikely(!(realirq & 0x80))) -		goto out_unlock; - -	realirq &= 7; - -	if (unlikely(realirq == 2)) { -		outb(0x0c, 0xa0); -		realirq = inb(0xa0); - -		if (unlikely(!(realirq & 0x80))) -			goto out_unlock; - -		realirq = (realirq & 7) + 8; -	} - -	/* mask and ack interrupt */ -	cached_irq_mask |= 1 << realirq; -	if (unlikely(realirq > 7)) { -		inb(0xa1); -		outb(cached_slave_mask, 0xa1); -		outb(0x60 + (realirq & 7), 0xa0); -		outb(0x60 + 2, 0x20); -	} else { -		inb(0x21); -		outb(cached_master_mask, 0x21); -		outb(0x60 + realirq, 0x20); -	} - -	raw_spin_unlock_irqrestore(&i8259A_lock, flags); - -	/* -	 * handle this 'virtual interrupt' as a Cobalt one now. -	 */ -	generic_handle_irq(realirq); - -	return IRQ_HANDLED; - -out_unlock: -	raw_spin_unlock_irqrestore(&i8259A_lock, flags); -	return IRQ_NONE; -} - -static struct irqaction master_action = { -	.handler =	piix4_master_intr, -	.name =		"PIIX4-8259", -}; - -static struct irqaction cascade_action = { -	.handler = 	no_action, -	.name =		"cascade", -}; - -static inline void set_piix4_virtual_irq_type(void) -{ -	piix4_virtual_irq_type.enable =	i8259A_chip.unmask; -	piix4_virtual_irq_type.disable = i8259A_chip.mask; -	piix4_virtual_irq_type.unmask =	i8259A_chip.unmask; -} - -static void __init visws_pre_intr_init(void) -{ -	int i; - -	set_piix4_virtual_irq_type(); - -	for (i = 0; i < CO_IRQ_APIC0 + CO_APIC_LAST + 1; i++) { -		struct irq_chip *chip = NULL; - -		if (i == 0) -			chip = &cobalt_irq_type; -		else if (i == CO_IRQ_IDE0) -			chip = &cobalt_irq_type; -		else if (i == CO_IRQ_IDE1) -			>chip = &cobalt_irq_type; -		else if (i == CO_IRQ_8259) -			chip = &piix4_master_irq_type; -		else if (i < CO_IRQ_APIC0) -			chip = &piix4_virtual_irq_type; -		else if (IS_CO_APIC(i)) -			chip = &cobalt_irq_type; - -		if (chip) -			set_irq_chip(i, chip); -	} - -	setup_irq(CO_IRQ_8259, &master_action); -	setup_irq(2, &cascade_action); -}  | 
