mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-08 17:18:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
12
arch/x86/platform/Makefile
Normal file
12
arch/x86/platform/Makefile
Normal file
|
@ -0,0 +1,12 @@
|
|||
# Platform specific code goes here
|
||||
obj-y += ce4100/
|
||||
obj-y += efi/
|
||||
obj-y += geode/
|
||||
obj-y += goldfish/
|
||||
obj-y += iris/
|
||||
obj-y += intel-mid/
|
||||
obj-y += olpc/
|
||||
obj-y += scx200/
|
||||
obj-y += sfi/
|
||||
obj-y += ts5500/
|
||||
obj-y += uv/
|
1
arch/x86/platform/ce4100/Makefile
Normal file
1
arch/x86/platform/ce4100/Makefile
Normal file
|
@ -0,0 +1 @@
|
|||
obj-$(CONFIG_X86_INTEL_CE) += ce4100.o
|
168
arch/x86/platform/ce4100/ce4100.c
Normal file
168
arch/x86/platform/ce4100/ce4100.c
Normal file
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* 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();
|
||||
}
|
||||
|
||||
static void sdv_pci_init(void)
|
||||
{
|
||||
x86_of_pci_init();
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.mpparse.setup_ioapic_ids = setup_ioapic_ids_from_mpc_nocheck;
|
||||
x86_init.pci.init = ce4100_pci_init;
|
||||
x86_init.pci.init_irq = sdv_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;
|
||||
|
||||
pm_power_off = ce4100_power_off;
|
||||
}
|
433
arch/x86/platform/ce4100/falconfalls.dts
Normal file
433
arch/x86/platform/ce4100/falconfalls.dts
Normal file
|
@ -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>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
4
arch/x86/platform/efi/Makefile
Normal file
4
arch/x86/platform/efi/Makefile
Normal file
|
@ -0,0 +1,4 @@
|
|||
obj-$(CONFIG_EFI) += quirks.o 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
|
236
arch/x86/platform/efi/early_printk.c
Normal file
236
arch/x86/platform/efi/early_printk.c
Normal file
|
@ -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,
|
||||
};
|
105
arch/x86/platform/efi/efi-bgrt.c
Normal file
105
arch/x86/platform/efi/efi-bgrt.c
Normal file
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* 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)) {
|
||||
pr_err("Ignoring BGRT: invalid length %u (expected %zu)\n",
|
||||
bgrt_tab->header.length, sizeof(*bgrt_tab));
|
||||
return;
|
||||
}
|
||||
if (bgrt_tab->version != 1) {
|
||||
pr_err("Ignoring BGRT: invalid version %u (expected 1)\n",
|
||||
bgrt_tab->version);
|
||||
return;
|
||||
}
|
||||
if (bgrt_tab->status != 1) {
|
||||
pr_err("Ignoring BGRT: invalid status %u (expected 1)\n",
|
||||
bgrt_tab->status);
|
||||
return;
|
||||
}
|
||||
if (bgrt_tab->image_type != 0) {
|
||||
pr_err("Ignoring BGRT: invalid image type %u (expected 0)\n",
|
||||
bgrt_tab->image_type);
|
||||
return;
|
||||
}
|
||||
if (!bgrt_tab->image_address) {
|
||||
pr_err("Ignoring BGRT: null image address\n");
|
||||
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) {
|
||||
pr_err("Ignoring BGRT: failed to map image header memory\n");
|
||||
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 | __GFP_NOWARN);
|
||||
if (!bgrt_image) {
|
||||
pr_err("Ignoring BGRT: failed to allocate memory for image (wanted %zu bytes)\n",
|
||||
bgrt_image_size);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ioremapped) {
|
||||
image = early_memremap(bgrt_tab->image_address,
|
||||
bmp_header.size);
|
||||
if (!image) {
|
||||
pr_err("Ignoring BGRT: failed to map image memory\n");
|
||||
kfree(bgrt_image);
|
||||
bgrt_image = NULL;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
memcpy_fromio(bgrt_image, image, bgrt_image_size);
|
||||
if (ioremapped)
|
||||
early_iounmap(image, bmp_header.size);
|
||||
}
|
945
arch/x86/platform/efi/efi.c
Normal file
945
arch/x86/platform/efi/efi.c
Normal file
|
@ -0,0 +1,945 @@
|
|||
/*
|
||||
* Common EFI (Extensible Firmware Interface) support functions
|
||||
* Based on Extensible Firmware Interface Specification version 1.0
|
||||
*
|
||||
* Copyright (C) 1999 VA Linux Systems
|
||||
* Copyright (C) 1999 Walt Drummond <drummond@valinux.com>
|
||||
* Copyright (C) 1999-2002 Hewlett-Packard Co.
|
||||
* David Mosberger-Tang <davidm@hpl.hp.com>
|
||||
* Stephane Eranian <eranian@hpl.hp.com>
|
||||
* Copyright (C) 2005-2008 Intel Co.
|
||||
* Fenghua Yu <fenghua.yu@intel.com>
|
||||
* 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
|
||||
*
|
||||
* All EFI Runtime Services are not implemented yet as EFI only
|
||||
* supports physical mode addressing on SoftSDV. This is to be fixed
|
||||
* in a future version. --drummond 1999-07-20
|
||||
*
|
||||
* Implemented EFI runtime services and virtual mode calls. --davidm
|
||||
*
|
||||
* Goutham Rao: <goutham.rao@intel.com>
|
||||
* 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>
|
||||
#include <linux/time.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/bcd.h>
|
||||
|
||||
#include <asm/setup.h>
|
||||
#include <asm/efi.h>
|
||||
#include <asm/time.h>
|
||||
#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
|
||||
|
||||
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 int add_efi_memmap __initdata;
|
||||
static int __init setup_add_efi_memmap(char *arg)
|
||||
{
|
||||
add_efi_memmap = 1;
|
||||
return 0;
|
||||
}
|
||||
early_param("add_efi_memmap", setup_add_efi_memmap);
|
||||
|
||||
static efi_status_t __init phys_efi_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;
|
||||
|
||||
efi_call_phys_prolog();
|
||||
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;
|
||||
}
|
||||
|
||||
void efi_get_time(struct timespec *now)
|
||||
{
|
||||
efi_status_t status;
|
||||
efi_time_t eft;
|
||||
efi_time_cap_t cap;
|
||||
|
||||
status = efi.get_time(&eft, &cap);
|
||||
if (status != EFI_SUCCESS)
|
||||
pr_err("Oops: efitime: can't read time!\n");
|
||||
|
||||
now->tv_sec = mktime(eft.year, eft.month, eft.day, eft.hour,
|
||||
eft.minute, eft.second);
|
||||
now->tv_nsec = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Tell the kernel about the EFI memory map. This might include
|
||||
* more than the max 128 entries that can fit in the e820 legacy
|
||||
* (zeropage) memory map.
|
||||
*/
|
||||
|
||||
static void __init do_add_efi_memmap(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;
|
||||
int e820_type;
|
||||
|
||||
switch (md->type) {
|
||||
case EFI_LOADER_CODE:
|
||||
case EFI_LOADER_DATA:
|
||||
case EFI_BOOT_SERVICES_CODE:
|
||||
case EFI_BOOT_SERVICES_DATA:
|
||||
case EFI_CONVENTIONAL_MEMORY:
|
||||
if (md->attribute & EFI_MEMORY_WB)
|
||||
e820_type = E820_RAM;
|
||||
else
|
||||
e820_type = E820_RESERVED;
|
||||
break;
|
||||
case EFI_ACPI_RECLAIM_MEMORY:
|
||||
e820_type = E820_ACPI;
|
||||
break;
|
||||
case EFI_ACPI_MEMORY_NVS:
|
||||
e820_type = E820_NVS;
|
||||
break;
|
||||
case EFI_UNUSABLE_MEMORY:
|
||||
e820_type = E820_UNUSABLE;
|
||||
break;
|
||||
default:
|
||||
/*
|
||||
* EFI_RESERVED_TYPE EFI_RUNTIME_SERVICES_CODE
|
||||
* EFI_RUNTIME_SERVICES_DATA EFI_MEMORY_MAPPED_IO
|
||||
* EFI_MEMORY_MAPPED_IO_PORT_SPACE EFI_PAL_CODE
|
||||
*/
|
||||
e820_type = E820_RESERVED;
|
||||
break;
|
||||
}
|
||||
e820_add_region(start, size, e820_type);
|
||||
}
|
||||
sanitize_e820_map(e820.map, ARRAY_SIZE(e820.map), &e820.nr_map);
|
||||
}
|
||||
|
||||
int __init efi_memblock_x86_reserve_range(void)
|
||||
{
|
||||
struct efi_info *e = &boot_params.efi_info;
|
||||
unsigned long pmap;
|
||||
|
||||
if (efi_enabled(EFI_PARAVIRT))
|
||||
return 0;
|
||||
|
||||
#ifdef CONFIG_X86_32
|
||||
/* 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 = (e->efi_memmap | ((__u64)e->efi_memmap_hi << 32));
|
||||
#endif
|
||||
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;
|
||||
}
|
||||
|
||||
static void __init print_efi_memmap(void)
|
||||
{
|
||||
#ifdef EFI_DEBUG
|
||||
efi_memory_desc_t *md;
|
||||
void *p;
|
||||
int i;
|
||||
|
||||
for (p = memmap.map, i = 0;
|
||||
p < memmap.map_end;
|
||||
p += memmap.desc_size, i++) {
|
||||
char buf[64];
|
||||
|
||||
md = p;
|
||||
pr_info("mem%02u: %s range=[0x%016llx-0x%016llx) (%lluMB)\n",
|
||||
i, efi_md_typeattr_format(buf, sizeof(buf), md),
|
||||
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_unmap_memmap(void)
|
||||
{
|
||||
clear_bit(EFI_MEMMAP, &efi.flags);
|
||||
if (memmap.map) {
|
||||
early_memunmap(memmap.map, memmap.nr_map * memmap.desc_size);
|
||||
memmap.map = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
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_memremap((unsigned long)phys,
|
||||
sizeof(*systab64));
|
||||
if (systab64 == NULL) {
|
||||
pr_err("Couldn't map the system table!\n");
|
||||
if (data)
|
||||
early_memunmap(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_memunmap(systab64, sizeof(*systab64));
|
||||
if (data)
|
||||
early_memunmap(data, sizeof(*data));
|
||||
#ifdef CONFIG_X86_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_memremap((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_memunmap(systab32, sizeof(*systab32));
|
||||
}
|
||||
|
||||
efi.systab = &efi_systab;
|
||||
|
||||
/*
|
||||
* Verify the EFI Table
|
||||
*/
|
||||
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)
|
||||
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_memremap((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;
|
||||
}
|
||||
|
||||
/*
|
||||
* We will only need *early* access to the SetVirtualAddressMap
|
||||
* EFI runtime service. All other runtime services will be called
|
||||
* via the virtual mapping.
|
||||
*/
|
||||
efi_phys.set_virtual_address_map =
|
||||
(efi_set_virtual_address_map_t *)
|
||||
(unsigned long)runtime->set_virtual_address_map;
|
||||
early_memunmap(runtime, sizeof(efi_runtime_services_32_t));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init efi_runtime_init64(void)
|
||||
{
|
||||
efi_runtime_services_64_t *runtime;
|
||||
|
||||
runtime = early_memremap((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;
|
||||
}
|
||||
|
||||
/*
|
||||
* We will only need *early* access to the SetVirtualAddressMap
|
||||
* EFI runtime service. All other runtime services will be called
|
||||
* via the virtual mapping.
|
||||
*/
|
||||
efi_phys.set_virtual_address_map =
|
||||
(efi_set_virtual_address_map_t *)
|
||||
(unsigned long)runtime->set_virtual_address_map;
|
||||
early_memunmap(runtime, sizeof(efi_runtime_services_64_t));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init efi_runtime_init(void)
|
||||
{
|
||||
int rv;
|
||||
|
||||
/*
|
||||
* Check out the runtime services table. We need to map
|
||||
* the runtime services table so that we can grab the physical
|
||||
* address of several of the EFI runtime functions, needed to
|
||||
* set the firmware into virtual mode.
|
||||
*
|
||||
* When EFI_PARAVIRT is in force then we could not map runtime
|
||||
* service memory region because we do not have direct access to it.
|
||||
* However, runtime services are available through proxy functions
|
||||
* (e.g. in case of Xen dom0 EFI implementation they call special
|
||||
* hypercall which executes relevant EFI functions) and that is why
|
||||
* they are always enabled.
|
||||
*/
|
||||
|
||||
if (!efi_enabled(EFI_PARAVIRT)) {
|
||||
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)
|
||||
{
|
||||
if (efi_enabled(EFI_PARAVIRT))
|
||||
return 0;
|
||||
|
||||
/* Map the EFI memory map */
|
||||
memmap.map = early_memremap((unsigned long)memmap.phys_map,
|
||||
memmap.nr_map * memmap.desc_size);
|
||||
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 (add_efi_memmap)
|
||||
do_add_efi_memmap();
|
||||
|
||||
set_bit(EFI_MEMMAP, &efi.flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void __init efi_init(void)
|
||||
{
|
||||
efi_char16_t *c16;
|
||||
char vendor[100] = "unknown";
|
||||
int i = 0;
|
||||
void *tmp;
|
||||
|
||||
#ifdef CONFIG_X86_32
|
||||
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
|
||||
|
||||
if (efi_systab_init(efi_phys.systab))
|
||||
return;
|
||||
|
||||
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_memremap(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_memunmap(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 (efi_runtime_disabled() || efi_runtime_init())
|
||||
return;
|
||||
}
|
||||
if (efi_memmap_init())
|
||||
return;
|
||||
|
||||
print_efi_memmap();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/* Make EFI runtime 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)
|
||||
continue;
|
||||
|
||||
efi_set_executable(md, true);
|
||||
}
|
||||
}
|
||||
|
||||
void __init efi_memory_uc(u64 addr, unsigned long size)
|
||||
{
|
||||
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;
|
||||
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);
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
||||
for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) {
|
||||
u64 prev_size;
|
||||
md = p;
|
||||
|
||||
if (!prev_md) {
|
||||
prev_md = md;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prev_md->type != md->type ||
|
||||
prev_md->attribute != md->attribute) {
|
||||
prev_md = md;
|
||||
continue;
|
||||
}
|
||||
|
||||
prev_size = prev_md->num_pages << EFI_PAGE_SHIFT;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
clear_bit(EFI_RUNTIME_SERVICES, &efi.flags);
|
||||
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);
|
||||
|
||||
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;
|
||||
|
||||
efi_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_delete_dummy_variable();
|
||||
#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");
|
||||
clear_bit(EFI_RUNTIME_SERVICES, &efi.flags);
|
||||
return;
|
||||
}
|
||||
|
||||
save_runtime_map();
|
||||
|
||||
BUG_ON(!efi.systab);
|
||||
|
||||
if (efi_setup_page_tables(__pa(new_memmap), 1 << pg_shift)) {
|
||||
clear_bit(EFI_RUNTIME_SERVICES, &efi.flags);
|
||||
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) {
|
||||
pr_alert("Unable to switch EFI into virtual mode (status=%lx)!\n",
|
||||
status);
|
||||
panic("EFI call to SetVirtualAddressMap() failed!");
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
if (efi_is_native())
|
||||
efi_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_delete_dummy_variable();
|
||||
}
|
||||
|
||||
void __init efi_enter_virtual_mode(void)
|
||||
{
|
||||
if (efi_enabled(EFI_PARAVIRT))
|
||||
return;
|
||||
|
||||
if (efi_setup)
|
||||
kexec_enter_virtual_mode();
|
||||
else
|
||||
__efi_enter_virtual_mode();
|
||||
}
|
||||
|
||||
/*
|
||||
* Convenience functions to obtain memory types and attributes
|
||||
*/
|
||||
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) &&
|
||||
(phys_addr < (md->phys_addr +
|
||||
(md->num_pages << EFI_PAGE_SHIFT))))
|
||||
return md->type;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
u64 efi_mem_attributes(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) &&
|
||||
(phys_addr < (md->phys_addr +
|
||||
(md->num_pages << EFI_PAGE_SHIFT))))
|
||||
return md->attribute;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init arch_parse_efi_cmdline(char *str)
|
||||
{
|
||||
if (parse_option_str(str, "old_map"))
|
||||
set_bit(EFI_OLD_MEMMAP, &efi.flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
early_param("efi", arch_parse_efi_cmdline);
|
92
arch/x86/platform/efi/efi_32.c
Normal file
92
arch/x86/platform/efi/efi_32.c
Normal file
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Extensible Firmware Interface
|
||||
*
|
||||
* Based on Extensible Firmware Interface Specification version 1.0
|
||||
*
|
||||
* Copyright (C) 1999 VA Linux Systems
|
||||
* Copyright (C) 1999 Walt Drummond <drummond@valinux.com>
|
||||
* Copyright (C) 1999-2002 Hewlett-Packard Co.
|
||||
* David Mosberger-Tang <davidm@hpl.hp.com>
|
||||
* Stephane Eranian <eranian@hpl.hp.com>
|
||||
*
|
||||
* All EFI Runtime Services are not implemented yet as EFI only
|
||||
* supports physical mode addressing on SoftSDV. This is to be fixed
|
||||
* in a future version. --drummond 1999-07-20
|
||||
*
|
||||
* Implemented EFI runtime services and virtual mode calls. --davidm
|
||||
*
|
||||
* Goutham Rao: <goutham.rao@intel.com>
|
||||
* Skip non-WB memory and ignore empty memory ranges.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/efi.h>
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <asm/desc.h>
|
||||
#include <asm/page.h>
|
||||
#include <asm/pgtable.h>
|
||||
#include <asm/tlbflush.h>
|
||||
#include <asm/efi.h>
|
||||
|
||||
/*
|
||||
* To make EFI call EFI runtime service in physical addressing mode we need
|
||||
* prolog/epilog before/after the invocation to disable interrupt, to
|
||||
* claim EFI runtime service handler exclusively and to duplicate a memory in
|
||||
* low memory space say 0 - 3G.
|
||||
*/
|
||||
static unsigned long efi_rt_eflags;
|
||||
|
||||
void efi_sync_low_kernel_mappings(void) {}
|
||||
void __init efi_dump_pagetable(void) {}
|
||||
int __init efi_setup_page_tables(unsigned long pa_memmap, unsigned num_pages)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
void __init 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 __init efi_call_phys_prolog(void)
|
||||
{
|
||||
struct desc_ptr gdt_descr;
|
||||
|
||||
local_irq_save(efi_rt_eflags);
|
||||
|
||||
load_cr3(initial_page_table);
|
||||
__flush_tlb_all();
|
||||
|
||||
gdt_descr.address = __pa(get_cpu_gdt_table(0));
|
||||
gdt_descr.size = GDT_SIZE - 1;
|
||||
load_gdt(&gdt_descr);
|
||||
}
|
||||
|
||||
void __init efi_call_phys_epilog(void)
|
||||
{
|
||||
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);
|
||||
|
||||
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();
|
||||
}
|
604
arch/x86/platform/efi/efi_64.c
Normal file
604
arch/x86/platform/efi/efi_64.c
Normal file
|
@ -0,0 +1,604 @@
|
|||
/*
|
||||
* x86_64 specific EFI support functions
|
||||
* Based on Extensible Firmware Interface Specification version 1.0
|
||||
*
|
||||
* Copyright (C) 2005-2008 Intel Co.
|
||||
* Fenghua Yu <fenghua.yu@intel.com>
|
||||
* Bibo Mao <bibo.mao@intel.com>
|
||||
* Chandramouli Narayanan <mouli@linux.intel.com>
|
||||
* Huang Ying <ying.huang@intel.com>
|
||||
*
|
||||
* Code to convert EFI to E820 map has been implemented in elilo bootloader
|
||||
* based on a EFI patch by Edgar Hucek. Based on the E820 map, the page table
|
||||
* is setup appropriately for EFI runtime code.
|
||||
* - mouli 06/14/2007.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/bootmem.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/efi.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <asm/setup.h>
|
||||
#include <asm/page.h>
|
||||
#include <asm/e820.h>
|
||||
#include <asm/pgtable.h>
|
||||
#include <asm/tlbflush.h>
|
||||
#include <asm/proto.h>
|
||||
#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 unsigned long efi_flags __initdata;
|
||||
|
||||
/*
|
||||
* 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))
|
||||
|
||||
/*
|
||||
* 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_code_mapping_set_exec(int executable)
|
||||
{
|
||||
efi_memory_desc_t *md;
|
||||
void *p;
|
||||
|
||||
if (!(__supported_pte_mask & _PAGE_NX))
|
||||
return;
|
||||
|
||||
/* 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 ||
|
||||
md->type == EFI_BOOT_SERVICES_CODE)
|
||||
efi_set_executable(md, executable);
|
||||
}
|
||||
}
|
||||
|
||||
void __init efi_call_phys_prolog(void)
|
||||
{
|
||||
unsigned long vaddress;
|
||||
int pgd;
|
||||
int n_pgds;
|
||||
|
||||
if (!efi_enabled(EFI_OLD_MEMMAP))
|
||||
return;
|
||||
|
||||
early_code_mapping_set_exec(1);
|
||||
local_irq_save(efi_flags);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
void __init efi_call_phys_epilog(void)
|
||||
{
|
||||
/*
|
||||
* After the lock is released, the original page table is restored.
|
||||
*/
|
||||
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_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 __init 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 __init 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, u64 attribute)
|
||||
{
|
||||
unsigned long last_map_pfn;
|
||||
|
||||
if (type == EFI_MEMORY_MAPPED_IO)
|
||||
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) {
|
||||
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 */
|
123
arch/x86/platform/efi/efi_stub_32.S
Normal file
123
arch/x86/platform/efi/efi_stub_32.S
Normal file
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* EFI call stub for IA32.
|
||||
*
|
||||
* This stub allows us to make EFI calls in physical mode with interrupts
|
||||
* turned off.
|
||||
*/
|
||||
|
||||
#include <linux/linkage.h>
|
||||
#include <asm/page_types.h>
|
||||
|
||||
/*
|
||||
* efi_call_phys(void *, ...) is a function with variable parameters.
|
||||
* All the callers of this function assure that all the parameters are 4-bytes.
|
||||
*/
|
||||
|
||||
/*
|
||||
* In gcc calling convention, EBX, ESP, EBP, ESI and EDI are all callee save.
|
||||
* So we'd better save all of them at the beginning of this function and restore
|
||||
* at the end no matter how many we use, because we can not assure EFI runtime
|
||||
* service functions will comply with gcc calling convention, too.
|
||||
*/
|
||||
|
||||
.text
|
||||
ENTRY(efi_call_phys)
|
||||
/*
|
||||
* 0. The function can only be called in Linux kernel. So CS has been
|
||||
* set to 0x0010, DS and SS have been set to 0x0018. In EFI, I found
|
||||
* the values of these registers are the same. And, the corresponding
|
||||
* GDT entries are identical. So I will do nothing about segment reg
|
||||
* and GDT, but change GDT base register in prolog and epilog.
|
||||
*/
|
||||
|
||||
/*
|
||||
* 1. Now I am running with EIP = <physical address> + PAGE_OFFSET.
|
||||
* But to make it smoothly switch from virtual mode to flat mode.
|
||||
* The mapping of lower virtual memory has been created in prolog and
|
||||
* epilog.
|
||||
*/
|
||||
movl $1f, %edx
|
||||
subl $__PAGE_OFFSET, %edx
|
||||
jmp *%edx
|
||||
1:
|
||||
|
||||
/*
|
||||
* 2. Now on the top of stack is the return
|
||||
* address in the caller of efi_call_phys(), then parameter 1,
|
||||
* parameter 2, ..., param n. To make things easy, we save the return
|
||||
* address of efi_call_phys in a global variable.
|
||||
*/
|
||||
popl %edx
|
||||
movl %edx, saved_return_addr
|
||||
/* get the function pointer into ECX*/
|
||||
popl %ecx
|
||||
movl %ecx, efi_rt_function_ptr
|
||||
movl $2f, %edx
|
||||
subl $__PAGE_OFFSET, %edx
|
||||
pushl %edx
|
||||
|
||||
/*
|
||||
* 3. Clear PG bit in %CR0.
|
||||
*/
|
||||
movl %cr0, %edx
|
||||
andl $0x7fffffff, %edx
|
||||
movl %edx, %cr0
|
||||
jmp 1f
|
||||
1:
|
||||
|
||||
/*
|
||||
* 4. Adjust stack pointer.
|
||||
*/
|
||||
subl $__PAGE_OFFSET, %esp
|
||||
|
||||
/*
|
||||
* 5. Call the physical function.
|
||||
*/
|
||||
jmp *%ecx
|
||||
|
||||
2:
|
||||
/*
|
||||
* 6. After EFI runtime service returns, control will return to
|
||||
* following instruction. We'd better readjust stack pointer first.
|
||||
*/
|
||||
addl $__PAGE_OFFSET, %esp
|
||||
|
||||
/*
|
||||
* 7. Restore PG bit
|
||||
*/
|
||||
movl %cr0, %edx
|
||||
orl $0x80000000, %edx
|
||||
movl %edx, %cr0
|
||||
jmp 1f
|
||||
1:
|
||||
/*
|
||||
* 8. Now restore the virtual mode from flat mode by
|
||||
* adding EIP with PAGE_OFFSET.
|
||||
*/
|
||||
movl $1f, %edx
|
||||
jmp *%edx
|
||||
1:
|
||||
|
||||
/*
|
||||
* 9. Balance the stack. And because EAX contain the return value,
|
||||
* we'd better not clobber it.
|
||||
*/
|
||||
leal efi_rt_function_ptr, %edx
|
||||
movl (%edx), %ecx
|
||||
pushl %ecx
|
||||
|
||||
/*
|
||||
* 10. Push the saved return address onto the stack and return.
|
||||
*/
|
||||
leal saved_return_addr, %edx
|
||||
movl (%edx), %ecx
|
||||
pushl %ecx
|
||||
ret
|
||||
ENDPROC(efi_call_phys)
|
||||
.previous
|
||||
|
||||
.data
|
||||
saved_return_addr:
|
||||
.long 0
|
||||
efi_rt_function_ptr:
|
||||
.long 0
|
98
arch/x86/platform/efi/efi_stub_64.S
Normal file
98
arch/x86/platform/efi/efi_stub_64.S
Normal file
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Function calling ABI conversion from Linux to EFI for x86_64
|
||||
*
|
||||
* Copyright (C) 2007 Intel Corp
|
||||
* Bibo Mao <bibo.mao@intel.com>
|
||||
* Huang Ying <ying.huang@intel.com>
|
||||
*/
|
||||
|
||||
#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; \
|
||||
subq $0x70, %rsp; \
|
||||
and $~0xf, %rsp; \
|
||||
mov %rax, (%rsp); \
|
||||
mov %cr0, %rax; \
|
||||
clts; \
|
||||
mov %rax, 0x8(%rsp); \
|
||||
movaps %xmm0, 0x60(%rsp); \
|
||||
movaps %xmm1, 0x50(%rsp); \
|
||||
movaps %xmm2, 0x40(%rsp); \
|
||||
movaps %xmm3, 0x30(%rsp); \
|
||||
movaps %xmm4, 0x20(%rsp); \
|
||||
movaps %xmm5, 0x10(%rsp)
|
||||
|
||||
#define RESTORE_XMM \
|
||||
movaps 0x60(%rsp), %xmm0; \
|
||||
movaps 0x50(%rsp), %xmm1; \
|
||||
movaps 0x40(%rsp), %xmm2; \
|
||||
movaps 0x30(%rsp), %xmm3; \
|
||||
movaps 0x20(%rsp), %xmm4; \
|
||||
movaps 0x10(%rsp), %xmm5; \
|
||||
mov 0x8(%rsp), %rsi; \
|
||||
mov %rsi, %cr0; \
|
||||
mov (%rsp), %rsp
|
||||
|
||||
/* 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
|
||||
|
||||
.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
|
||||
|
||||
.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_call)
|
||||
SAVE_XMM
|
||||
mov (%rsp), %rax
|
||||
mov 8(%rax), %rax
|
||||
subq $48, %rsp
|
||||
mov %r9, 32(%rsp)
|
||||
mov %rax, 40(%rsp)
|
||||
mov %r8, %r9
|
||||
mov %rcx, %r8
|
||||
mov %rsi, %rcx
|
||||
SWITCH_PGT
|
||||
call *%rdi
|
||||
RESTORE_PGT
|
||||
addq $48, %rsp
|
||||
RESTORE_XMM
|
||||
ret
|
||||
ENDPROC(efi_call)
|
||||
|
||||
.data
|
||||
ENTRY(efi_scratch)
|
||||
.fill 3,8,0
|
||||
.byte 0
|
||||
.quad 0
|
152
arch/x86/platform/efi/efi_thunk_64.S
Normal file
152
arch/x86/platform/efi/efi_thunk_64.S
Normal file
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
* Copyright (C) 2014 Intel Corporation; author Matt Fleming
|
||||
*
|
||||
* Support for invoking 32-bit EFI runtime services from a 64-bit
|
||||
* kernel.
|
||||
*
|
||||
* The below thunking functions are only used after ExitBootServices()
|
||||
* has been called. This simplifies things considerably as compared with
|
||||
* the early EFI thunking because we can leave all the kernel state
|
||||
* intact (GDT, IDT, etc) and simply invoke the the 32-bit EFI runtime
|
||||
* services from __KERNEL32_CS. This means we can continue to service
|
||||
* interrupts across an EFI mixed mode call.
|
||||
*
|
||||
* We do however, need to handle the fact that we're running in a full
|
||||
* 64-bit virtual address space. Things like the stack and instruction
|
||||
* addresses need to be accessible by the 32-bit firmware, so we rely on
|
||||
* using the identity mappings in the EFI page table to access the stack
|
||||
* and kernel text (see efi_setup_page_tables()).
|
||||
*/
|
||||
|
||||
#include <linux/linkage.h>
|
||||
#include <asm/page_types.h>
|
||||
#include <asm/segment.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 __efi64_thunk(%rip), %rbx
|
||||
subq %rax, %rbx
|
||||
call *%rbx
|
||||
|
||||
movq efi_saved_sp(%rip), %rsp
|
||||
pop %rbx
|
||||
pop %rbp
|
||||
retq
|
||||
ENDPROC(efi64_thunk)
|
||||
|
||||
/*
|
||||
* 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)
|
||||
|
||||
leaq 1f(%rip), %rbx
|
||||
movq %rbx, func_rt_ptr(%rip)
|
||||
|
||||
/* Switch to 32-bit descriptor */
|
||||
pushq $__KERNEL32_CS
|
||||
leaq efi_enter32(%rip), %rax
|
||||
pushq %rax
|
||||
lretq
|
||||
|
||||
1: addq $32, %rsp
|
||||
|
||||
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
|
||||
|
||||
call *%edi
|
||||
|
||||
/* We must preserve return value */
|
||||
movl %eax, %edi
|
||||
|
||||
movl 72(%esp), %eax
|
||||
pushl $__KERNEL_CS
|
||||
pushl %eax
|
||||
|
||||
lret
|
||||
ENDPROC(efi_enter32)
|
||||
|
||||
.data
|
||||
.balign 8
|
||||
func_rt_ptr: .quad 0
|
||||
efi_saved_sp: .quad 0
|
290
arch/x86/platform/efi/quirks.c
Normal file
290
arch/x86/platform/efi/quirks.c
Normal file
|
@ -0,0 +1,290 @@
|
|||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/efi.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/memblock.h>
|
||||
#include <linux/bootmem.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <asm/efi.h>
|
||||
#include <asm/uv/uv.h>
|
||||
|
||||
#define EFI_MIN_RESERVE 5120
|
||||
|
||||
#define EFI_DUMMY_GUID \
|
||||
EFI_GUID(0x4424ac57, 0xbe4b, 0x47dd, 0x9e, 0x97, 0xed, 0x50, 0xf0, 0x9f, 0x92, 0xa9)
|
||||
|
||||
static efi_char16_t efi_dummy_name[6] = { 'D', 'U', 'M', 'M', 'Y', 0 };
|
||||
|
||||
static bool efi_no_storage_paranoia;
|
||||
|
||||
/*
|
||||
* Some firmware implementations refuse to boot if there's insufficient
|
||||
* space in the variable store. The implementation of garbage collection
|
||||
* in some FW versions causes stale (deleted) variables to take up space
|
||||
* longer than intended and space is only freed once the store becomes
|
||||
* almost completely full.
|
||||
*
|
||||
* Enabling this option disables the space checks in
|
||||
* efi_query_variable_store() and forces garbage collection.
|
||||
*
|
||||
* Only enable this option if deleting EFI variables does not free up
|
||||
* space in your variable store, e.g. if despite deleting variables
|
||||
* you're unable to create new ones.
|
||||
*/
|
||||
static int __init setup_storage_paranoia(char *arg)
|
||||
{
|
||||
efi_no_storage_paranoia = true;
|
||||
return 0;
|
||||
}
|
||||
early_param("efi_no_storage_paranoia", setup_storage_paranoia);
|
||||
|
||||
/*
|
||||
* Deleting the dummy variable which kicks off garbage collection
|
||||
*/
|
||||
void efi_delete_dummy_variable(void)
|
||||
{
|
||||
efi.set_variable(efi_dummy_name, &EFI_DUMMY_GUID,
|
||||
EFI_VARIABLE_NON_VOLATILE |
|
||||
EFI_VARIABLE_BOOTSERVICE_ACCESS |
|
||||
EFI_VARIABLE_RUNTIME_ACCESS,
|
||||
0, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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_delete_dummy_variable();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
/*
|
||||
* The UEFI specification makes it clear that the operating system is free to do
|
||||
* whatever it wants with boot services code after ExitBootServices() has been
|
||||
* called. Ignoring this recommendation a significant bunch of EFI implementations
|
||||
* continue calling into boot services code (SetVirtualAddressMap). In order to
|
||||
* work around such buggy implementations we reserve boot services region during
|
||||
* EFI init and make sure it stays executable. Then, after SetVirtualAddressMap(), it
|
||||
* is discarded.
|
||||
*/
|
||||
void __init efi_reserve_boot_services(void)
|
||||
{
|
||||
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_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();
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
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_memunmap(tablep, nr_tables * sz);
|
||||
|
||||
out_memremap:
|
||||
early_memunmap(data, sizeof(*data));
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
* For most modern platforms the preferred method of powering off is via
|
||||
* ACPI. However, there are some that are known to require the use of
|
||||
* EFI runtime services and for which ACPI does not work at all.
|
||||
*
|
||||
* Using EFI is a last resort, to be used only if no other option
|
||||
* exists.
|
||||
*/
|
||||
bool efi_reboot_required(void)
|
||||
{
|
||||
if (!acpi_gbl_reduced_hardware)
|
||||
return false;
|
||||
|
||||
efi_reboot_quirk_mode = EFI_RESET_WARM;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool efi_poweroff_required(void)
|
||||
{
|
||||
return !!acpi_gbl_reduced_hardware;
|
||||
}
|
3
arch/x86/platform/geode/Makefile
Normal file
3
arch/x86/platform/geode/Makefile
Normal file
|
@ -0,0 +1,3 @@
|
|||
obj-$(CONFIG_ALIX) += alix.o
|
||||
obj-$(CONFIG_NET5501) += net5501.o
|
||||
obj-$(CONFIG_GEOS) += geos.o
|
200
arch/x86/platform/geode/alix.c
Normal file
200
arch/x86/platform/geode/alix.c
Normal file
|
@ -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");
|
128
arch/x86/platform/geode/geos.c
Normal file
128
arch/x86/platform/geode/geos.c
Normal file
|
@ -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");
|
154
arch/x86/platform/geode/net5501.c
Normal file
154
arch/x86/platform/geode/net5501.c
Normal file
|
@ -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");
|
1
arch/x86/platform/goldfish/Makefile
Normal file
1
arch/x86/platform/goldfish/Makefile
Normal file
|
@ -0,0 +1 @@
|
|||
obj-$(CONFIG_GOLDFISH) += goldfish.o
|
51
arch/x86/platform/goldfish/goldfish.c
Normal file
51
arch/x86/platform/goldfish/goldfish.c
Normal file
|
@ -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);
|
7
arch/x86/platform/intel-mid/Makefile
Normal file
7
arch/x86/platform/intel-mid/Makefile
Normal file
|
@ -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
|
23
arch/x86/platform/intel-mid/device_libs/Makefile
Normal file
23
arch/x86/platform/intel-mid/device_libs/Makefile
Normal file
|
@ -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
|
20
arch/x86/platform/intel-mid/device_libs/platform_bma023.c
Normal file
20
arch/x86/platform/intel-mid/device_libs/platform_bma023.c
Normal file
|
@ -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);
|
43
arch/x86/platform/intel-mid/device_libs/platform_emc1403.c
Normal file
43
arch/x86/platform/intel-mid/device_libs/platform_emc1403.c
Normal file
|
@ -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);
|
83
arch/x86/platform/intel-mid/device_libs/platform_gpio_keys.c
Normal file
83
arch/x86/platform/intel-mid/device_libs/platform_gpio_keys.c
Normal file
|
@ -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);
|
68
arch/x86/platform/intel-mid/device_libs/platform_ipc.c
Normal file
68
arch/x86/platform/intel-mid/device_libs/platform_ipc.c
Normal file
|
@ -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);
|
18
arch/x86/platform/intel-mid/device_libs/platform_ipc.h
Normal file
18
arch/x86/platform/intel-mid/device_libs/platform_ipc.h
Normal file
|
@ -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
|
41
arch/x86/platform/intel-mid/device_libs/platform_lis331.c
Normal file
41
arch/x86/platform/intel-mid/device_libs/platform_lis331.c
Normal file
|
@ -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);
|
35
arch/x86/platform/intel-mid/device_libs/platform_max3111.c
Normal file
35
arch/x86/platform/intel-mid/device_libs/platform_max3111.c
Normal file
|
@ -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);
|
79
arch/x86/platform/intel-mid/device_libs/platform_max7315.c
Normal file
79
arch/x86/platform/intel-mid/device_libs/platform_max7315.c
Normal file
|
@ -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);
|
36
arch/x86/platform/intel-mid/device_libs/platform_mpu3050.c
Normal file
36
arch/x86/platform/intel-mid/device_libs/platform_mpu3050.c
Normal file
|
@ -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);
|
87
arch/x86/platform/intel-mid/device_libs/platform_msic.c
Normal file
87
arch/x86/platform/intel-mid/device_libs/platform_msic.c
Normal file
|
@ -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;
|
||||
}
|
19
arch/x86/platform/intel-mid/device_libs/platform_msic.h
Normal file
19
arch/x86/platform/intel-mid/device_libs/platform_msic.h
Normal file
|
@ -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
|
|
@ -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);
|
|
@ -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);
|
48
arch/x86/platform/intel-mid/device_libs/platform_msic_gpio.c
Normal file
48
arch/x86/platform/intel-mid/device_libs/platform_msic_gpio.c
Normal file
|
@ -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);
|
49
arch/x86/platform/intel-mid/device_libs/platform_msic_ocd.c
Normal file
49
arch/x86/platform/intel-mid/device_libs/platform_msic_ocd.c
Normal file
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
54
arch/x86/platform/intel-mid/device_libs/platform_pmic_gpio.c
Normal file
54
arch/x86/platform/intel-mid/device_libs/platform_pmic_gpio.c
Normal file
|
@ -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);
|
36
arch/x86/platform/intel-mid/device_libs/platform_tc35876x.c
Normal file
36
arch/x86/platform/intel-mid/device_libs/platform_tc35876x.c
Normal file
|
@ -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);
|
57
arch/x86/platform/intel-mid/device_libs/platform_tca6416.c
Normal file
57
arch/x86/platform/intel-mid/device_libs/platform_tca6416.c
Normal file
|
@ -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);
|
62
arch/x86/platform/intel-mid/device_libs/platform_wdt.c
Normal file
62
arch/x86/platform/intel-mid/device_libs/platform_wdt.c
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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 gsi;
|
||||
struct intel_mid_wdt_pdata *pdata = pdev->dev.platform_data;
|
||||
|
||||
if (!pdata)
|
||||
return -EINVAL;
|
||||
|
||||
/* IOAPIC builds identity mapping between GSI and IRQ on MID */
|
||||
gsi = pdata->irq;
|
||||
if (mp_set_gsi_attr(gsi, 1, 0, cpu_to_node(0)) ||
|
||||
mp_map_gsi_to_irq(gsi, IOAPIC_MAP_ALLOC) <= 0) {
|
||||
dev_warn(&pdev->dev, "cannot find interrupt %d in ioapic\n",
|
||||
gsi);
|
||||
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);
|
324
arch/x86/platform/intel-mid/early_printk_intel_mid.c
Normal file
324
arch/x86/platform/intel-mid/early_printk_intel_mid.c
Normal file
|
@ -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,
|
||||
};
|
217
arch/x86/platform/intel-mid/intel-mid.c
Normal file
217
arch/x86/platform/intel-mid/intel-mid.c
Normal file
|
@ -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);
|
||||
|
177
arch/x86/platform/intel-mid/intel_mid_vrtc.c
Normal file
177
arch/x86/platform/intel-mid/intel_mid_vrtc.c
Normal file
|
@ -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);
|
18
arch/x86/platform/intel-mid/intel_mid_weak_decls.h
Normal file
18
arch/x86/platform/intel-mid/intel_mid_weak_decls.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
|
||||
/* For every CPU addition a new get_<cpuname>_ops interface needs
|
||||
* to be added.
|
||||
*/
|
||||
extern void *get_penwell_ops(void);
|
||||
extern void *get_cloverview_ops(void);
|
||||
extern void *get_tangier_ops(void);
|
75
arch/x86/platform/intel-mid/mfld.c
Normal file
75
arch/x86/platform/intel-mid/mfld.c
Normal file
|
@ -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;
|
||||
}
|
103
arch/x86/platform/intel-mid/mrfl.c
Normal file
103
arch/x86/platform/intel-mid/mrfl.c
Normal file
|
@ -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;
|
||||
}
|
512
arch/x86/platform/intel-mid/sfi.c
Normal file
512
arch/x86/platform/intel-mid/sfi.c
Normal file
|
@ -0,0 +1,512 @@
|
|||
/*
|
||||
* 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);
|
||||
mp_map_gsi_to_irq(pentry->irq, IOAPIC_MAP_ALLOC);
|
||||
}
|
||||
|
||||
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);
|
||||
mp_map_gsi_to_irq(pentry->irq, IOAPIC_MAP_ALLOC);
|
||||
}
|
||||
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, ret;
|
||||
int polarity;
|
||||
|
||||
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
|
||||
*/
|
||||
if (intel_mid_identify_cpu() ==
|
||||
INTEL_MID_CPU_CHIP_TANGIER) {
|
||||
if (!strncmp(pentry->name, "r69001-ts-i2c", 13))
|
||||
/* active low */
|
||||
polarity = 1;
|
||||
else if (!strncmp(pentry->name,
|
||||
"synaptics_3202", 14))
|
||||
/* active low */
|
||||
polarity = 1;
|
||||
else if (irq == 41)
|
||||
/* fast_int_1 */
|
||||
polarity = 1;
|
||||
else
|
||||
/* active high */
|
||||
polarity = 0;
|
||||
} else {
|
||||
/* PNW and CLV go with active low */
|
||||
polarity = 1;
|
||||
}
|
||||
|
||||
ret = mp_set_gsi_attr(irq, 1, polarity, NUMA_NO_NODE);
|
||||
if (ret == 0)
|
||||
ret = mp_map_gsi_to_irq(irq, IOAPIC_MAP_ALLOC);
|
||||
WARN_ON(ret < 0);
|
||||
}
|
||||
|
||||
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);
|
1
arch/x86/platform/iris/Makefile
Normal file
1
arch/x86/platform/iris/Makefile
Normal file
|
@ -0,0 +1 @@
|
|||
obj-$(CONFIG_X86_32_IRIS) += iris.o
|
137
arch/x86/platform/iris/iris.c
Normal file
137
arch/x86/platform/iris/iris.c
Normal file
|
@ -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);
|
5
arch/x86/platform/olpc/Makefile
Normal file
5
arch/x86/platform/olpc/Makefile
Normal file
|
@ -0,0 +1,5 @@
|
|||
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
|
202
arch/x86/platform/olpc/olpc-xo1-pm.c
Normal file
202
arch/x86/platform/olpc/olpc-xo1-pm.c
Normal file
|
@ -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);
|
81
arch/x86/platform/olpc/olpc-xo1-rtc.c
Normal file
81
arch/x86/platform/olpc/olpc-xo1-rtc.c
Normal file
|
@ -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);
|
642
arch/x86/platform/olpc/olpc-xo1-sci.c
Normal file
642
arch/x86/platform/olpc/olpc-xo1-sci.c
Normal file
|
@ -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);
|
233
arch/x86/platform/olpc/olpc-xo15-sci.c
Normal file
233
arch/x86/platform/olpc/olpc-xo15-sci.c
Normal file
|
@ -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);
|
410
arch/x86/platform/olpc/olpc.c
Normal file
410
arch/x86/platform/olpc/olpc.c
Normal file
|
@ -0,0 +1,410 @@
|
|||
/*
|
||||
* Support for the OLPC DCON and OLPC EC access
|
||||
*
|
||||
* Copyright © 2006 Advanced Micro Devices, Inc.
|
||||
* Copyright © 2007-2008 Andres Salomon <dilinger@debian.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.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/delay.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>
|
||||
#include <asm/olpc.h>
|
||||
#include <asm/olpc_ofw.h>
|
||||
|
||||
struct olpc_platform_t olpc_platform_info;
|
||||
EXPORT_SYMBOL_GPL(olpc_platform_info);
|
||||
|
||||
/* 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
|
||||
|
||||
/* the timeout that bugs in the EC might force us to actually use */
|
||||
static int ec_timeout = EC_BASE_TIMEOUT;
|
||||
|
||||
static int __init olpc_ec_timeout_set(char *str)
|
||||
{
|
||||
if (get_option(&str, &ec_timeout) != 1) {
|
||||
ec_timeout = EC_BASE_TIMEOUT;
|
||||
printk(KERN_ERR "olpc-ec: invalid argument to "
|
||||
"'olpc_ec_timeout=', ignoring!\n");
|
||||
}
|
||||
printk(KERN_DEBUG "olpc-ec: using %d ms delay for EC commands.\n",
|
||||
ec_timeout);
|
||||
return 1;
|
||||
}
|
||||
__setup("olpc_ec_timeout=", olpc_ec_timeout_set);
|
||||
|
||||
/*
|
||||
* These {i,o}bf_status functions return whether the buffers are full or not.
|
||||
*/
|
||||
|
||||
static inline unsigned int ibf_status(unsigned int port)
|
||||
{
|
||||
return !!(inb(port) & 0x02);
|
||||
}
|
||||
|
||||
static inline unsigned int obf_status(unsigned int port)
|
||||
{
|
||||
return inb(port) & 0x01;
|
||||
}
|
||||
|
||||
#define wait_on_ibf(p, d) __wait_on_ibf(__LINE__, (p), (d))
|
||||
static int __wait_on_ibf(unsigned int line, unsigned int port, int desired)
|
||||
{
|
||||
unsigned int timeo;
|
||||
int state = ibf_status(port);
|
||||
|
||||
for (timeo = ec_timeout; state != desired && timeo; timeo--) {
|
||||
mdelay(1);
|
||||
state = ibf_status(port);
|
||||
}
|
||||
|
||||
if ((state == desired) && (ec_timeout > EC_BASE_TIMEOUT) &&
|
||||
timeo < (ec_timeout - EC_BASE_TIMEOUT)) {
|
||||
printk(KERN_WARNING "olpc-ec: %d: waited %u ms for IBF!\n",
|
||||
line, ec_timeout - timeo);
|
||||
}
|
||||
|
||||
return !(state == desired);
|
||||
}
|
||||
|
||||
#define wait_on_obf(p, d) __wait_on_obf(__LINE__, (p), (d))
|
||||
static int __wait_on_obf(unsigned int line, unsigned int port, int desired)
|
||||
{
|
||||
unsigned int timeo;
|
||||
int state = obf_status(port);
|
||||
|
||||
for (timeo = ec_timeout; state != desired && timeo; timeo--) {
|
||||
mdelay(1);
|
||||
state = obf_status(port);
|
||||
}
|
||||
|
||||
if ((state == desired) && (ec_timeout > EC_BASE_TIMEOUT) &&
|
||||
timeo < (ec_timeout - EC_BASE_TIMEOUT)) {
|
||||
printk(KERN_WARNING "olpc-ec: %d: waited %u ms for OBF!\n",
|
||||
line, ec_timeout - timeo);
|
||||
}
|
||||
|
||||
return !(state == desired);
|
||||
}
|
||||
|
||||
/*
|
||||
* This allows the kernel to run Embedded Controller commands. The EC is
|
||||
* documented at <http://wiki.laptop.org/go/Embedded_controller>, and the
|
||||
* available EC commands are here:
|
||||
* <http://wiki.laptop.org/go/Ec_specification>. Unfortunately, while
|
||||
* OpenFirmware's source is available, the EC's is not.
|
||||
*/
|
||||
static int olpc_xo1_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *outbuf,
|
||||
size_t outlen, void *arg)
|
||||
{
|
||||
int ret = -EIO;
|
||||
int i;
|
||||
int restarts = 0;
|
||||
|
||||
/* Clear OBF */
|
||||
for (i = 0; i < 10 && (obf_status(0x6c) == 1); i++)
|
||||
inb(0x68);
|
||||
if (i == 10) {
|
||||
printk(KERN_ERR "olpc-ec: timeout while attempting to "
|
||||
"clear OBF flag!\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (wait_on_ibf(0x6c, 0)) {
|
||||
printk(KERN_ERR "olpc-ec: timeout waiting for EC to "
|
||||
"quiesce!\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
restart:
|
||||
/*
|
||||
* Note that if we time out during any IBF checks, that's a failure;
|
||||
* we have to return. There's no way for the kernel to clear that.
|
||||
*
|
||||
* If we time out during an OBF check, we can restart the command;
|
||||
* reissuing it will clear the OBF flag, and we should be alright.
|
||||
* The OBF flag will sometimes misbehave due to what we believe
|
||||
* is a hardware quirk..
|
||||
*/
|
||||
pr_devel("olpc-ec: running cmd 0x%x\n", cmd);
|
||||
outb(cmd, 0x6c);
|
||||
|
||||
if (wait_on_ibf(0x6c, 0)) {
|
||||
printk(KERN_ERR "olpc-ec: timeout waiting for EC to read "
|
||||
"command!\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (outbuf && outlen) {
|
||||
/* read data from EC */
|
||||
for (i = 0; i < outlen; i++) {
|
||||
if (wait_on_obf(0x6c, 1)) {
|
||||
printk(KERN_ERR "olpc-ec: timeout waiting for"
|
||||
" EC to provide data!\n");
|
||||
if (restarts++ < 10)
|
||||
goto restart;
|
||||
goto err;
|
||||
}
|
||||
outbuf[i] = inb(0x68);
|
||||
pr_devel("olpc-ec: received 0x%x\n", outbuf[i]);
|
||||
}
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
void olpc_ec_wakeup_set(u16 value)
|
||||
{
|
||||
ec_wakeup_mask |= value;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(olpc_ec_wakeup_set);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(olpc_ec_mask_write);
|
||||
|
||||
int olpc_ec_sci_query(u16 *sci_value)
|
||||
{
|
||||
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 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)
|
||||
{
|
||||
struct device_node *root = of_find_node_by_path("/");
|
||||
bool success;
|
||||
|
||||
if (!root)
|
||||
return false;
|
||||
|
||||
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)
|
||||
{
|
||||
struct platform_device *pdev;
|
||||
|
||||
pdev = platform_device_register_simple("xo1-rfkill", -1, NULL, 0);
|
||||
if (IS_ERR(pdev))
|
||||
return PTR_ERR(pdev);
|
||||
|
||||
pdev = platform_device_register_simple("olpc-xo1", -1, NULL, 0);
|
||||
if (IS_ERR(pdev))
|
||||
return PTR_ERR(pdev);
|
||||
|
||||
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;
|
||||
|
||||
if (!olpc_ofw_present() || !platform_detect())
|
||||
return 0;
|
||||
|
||||
/* 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;
|
||||
|
||||
#ifdef CONFIG_PCI_OLPC
|
||||
/* If the VSA exists let it emulate PCI, if not emulate in kernel.
|
||||
* XO-1 only. */
|
||||
if (olpc_platform_info.boardrev < olpc_board_pre(0xd0) &&
|
||||
!cs5535_has_vsa2())
|
||||
x86_init.pci.arch_init = pci_olpc_init;
|
||||
#endif
|
||||
|
||||
if (olpc_platform_info.boardrev < olpc_board_pre(0xd0)) { /* XO-1 */
|
||||
r = add_xo1_platform_devices();
|
||||
if (r)
|
||||
return r;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
postcore_initcall(olpc_init);
|
304
arch/x86/platform/olpc/olpc_dt.c
Normal file
304
arch/x86/platform/olpc/olpc_dt.c
Normal file
|
@ -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);
|
117
arch/x86/platform/olpc/olpc_ofw.c
Normal file
117
arch/x86/platform/olpc/olpc_ofw.c
Normal file
|
@ -0,0 +1,117 @@
|
|||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <asm/page.h>
|
||||
#include <asm/setup.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/pgtable.h>
|
||||
#include <asm/olpc_ofw.h>
|
||||
|
||||
/* address of OFW callback interface; will be NULL if OFW isn't found */
|
||||
static int (*olpc_ofw_cif)(int *);
|
||||
|
||||
/* page dir entry containing OFW's pgdir table; filled in by head_32.S */
|
||||
u32 olpc_ofw_pgd __initdata;
|
||||
|
||||
static DEFINE_SPINLOCK(ofw_lock);
|
||||
|
||||
#define MAXARGS 10
|
||||
|
||||
void __init setup_olpc_ofw_pgd(void)
|
||||
{
|
||||
pgd_t *base, *ofw_pde;
|
||||
|
||||
if (!olpc_ofw_cif)
|
||||
return;
|
||||
|
||||
/* fetch OFW's PDE */
|
||||
base = early_ioremap(olpc_ofw_pgd, sizeof(olpc_ofw_pgd) * PTRS_PER_PGD);
|
||||
if (!base) {
|
||||
printk(KERN_ERR "failed to remap OFW's pgd - disabling OFW!\n");
|
||||
olpc_ofw_cif = NULL;
|
||||
return;
|
||||
}
|
||||
ofw_pde = &base[OLPC_OFW_PDE_NR];
|
||||
|
||||
/* install OFW's PDE permanently into the kernel's pgtable */
|
||||
set_pgd(&swapper_pg_dir[OLPC_OFW_PDE_NR], *ofw_pde);
|
||||
/* implicit optimization barrier here due to uninline function return */
|
||||
|
||||
early_iounmap(base, sizeof(olpc_ofw_pgd) * PTRS_PER_PGD);
|
||||
}
|
||||
|
||||
int __olpc_ofw(const char *name, int nr_args, const void **args, int nr_res,
|
||||
void **res)
|
||||
{
|
||||
int ofw_args[MAXARGS + 3];
|
||||
unsigned long flags;
|
||||
int ret, i, *p;
|
||||
|
||||
BUG_ON(nr_args + nr_res > MAXARGS);
|
||||
|
||||
if (!olpc_ofw_cif)
|
||||
return -EIO;
|
||||
|
||||
ofw_args[0] = (int)name;
|
||||
ofw_args[1] = nr_args;
|
||||
ofw_args[2] = nr_res;
|
||||
|
||||
p = &ofw_args[3];
|
||||
for (i = 0; i < nr_args; i++, p++)
|
||||
*p = (int)args[i];
|
||||
|
||||
/* call into ofw */
|
||||
spin_lock_irqsave(&ofw_lock, flags);
|
||||
ret = olpc_ofw_cif(ofw_args);
|
||||
spin_unlock_irqrestore(&ofw_lock, flags);
|
||||
|
||||
if (!ret) {
|
||||
for (i = 0; i < nr_res; i++, p++)
|
||||
*((int *)res[i]) = *p;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__olpc_ofw);
|
||||
|
||||
bool olpc_ofw_present(void)
|
||||
{
|
||||
return olpc_ofw_cif != NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(olpc_ofw_present);
|
||||
|
||||
/* OFW cif _should_ be above this address */
|
||||
#define OFW_MIN 0xff000000
|
||||
|
||||
/* OFW starts on a 1MB boundary */
|
||||
#define OFW_BOUND (1<<20)
|
||||
|
||||
void __init olpc_ofw_detect(void)
|
||||
{
|
||||
struct olpc_ofw_header *hdr = &boot_params.olpc_ofw_header;
|
||||
unsigned long start;
|
||||
|
||||
/* ensure OFW booted us by checking for "OFW " string */
|
||||
if (hdr->ofw_magic != OLPC_OFW_SIG)
|
||||
return;
|
||||
|
||||
olpc_ofw_cif = (int (*)(int *))hdr->cif_handler;
|
||||
|
||||
if ((unsigned long)olpc_ofw_cif < OFW_MIN) {
|
||||
printk(KERN_ERR "OFW detected, but cif has invalid address 0x%lx - disabling.\n",
|
||||
(unsigned long)olpc_ofw_cif);
|
||||
olpc_ofw_cif = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
/* determine where OFW starts in memory */
|
||||
start = round_down((unsigned long)olpc_ofw_cif, OFW_BOUND);
|
||||
printk(KERN_INFO "OFW detected in memory, cif @ 0x%lx (reserving top %ldMB)\n",
|
||||
(unsigned long)olpc_ofw_cif, (-start) >> 20);
|
||||
reserve_top_address(-start);
|
||||
}
|
||||
|
||||
bool __init olpc_ofw_is_installed(void)
|
||||
{
|
||||
return olpc_ofw_cif != NULL;
|
||||
}
|
124
arch/x86/platform/olpc/xo1-wakeup.S
Normal file
124
arch/x86/platform/olpc/xo1-wakeup.S
Normal file
|
@ -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
|
2
arch/x86/platform/scx200/Makefile
Normal file
2
arch/x86/platform/scx200/Makefile
Normal file
|
@ -0,0 +1,2 @@
|
|||
obj-$(CONFIG_SCx200) += scx200.o
|
||||
scx200-y += scx200_32.o
|
129
arch/x86/platform/scx200/scx200_32.c
Normal file
129
arch/x86/platform/scx200/scx200_32.c
Normal file
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com>
|
||||
*
|
||||
* National Semiconductor SCx200 support.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/pci.h>
|
||||
|
||||
#include <linux/scx200.h>
|
||||
#include <linux/scx200_gpio.h>
|
||||
|
||||
/* Verify that the configuration block really is there */
|
||||
#define scx200_cb_probe(base) (inw((base) + SCx200_CBA) == (base))
|
||||
|
||||
MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>");
|
||||
MODULE_DESCRIPTION("NatSemi SCx200 Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
unsigned scx200_gpio_base = 0;
|
||||
unsigned long scx200_gpio_shadow[2];
|
||||
|
||||
unsigned scx200_cb_base = 0;
|
||||
|
||||
static struct pci_device_id scx200_tbl[] = {
|
||||
{ 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 scx200_probe(struct pci_dev *, const struct pci_device_id *);
|
||||
|
||||
static struct pci_driver scx200_pci_driver = {
|
||||
.name = "scx200",
|
||||
.id_table = scx200_tbl,
|
||||
.probe = scx200_probe,
|
||||
};
|
||||
|
||||
static DEFINE_MUTEX(scx200_gpio_config_lock);
|
||||
|
||||
static void scx200_init_shadow(void)
|
||||
{
|
||||
int bank;
|
||||
|
||||
/* read the current values driven on the GPIO signals */
|
||||
for (bank = 0; bank < 2; ++bank)
|
||||
scx200_gpio_shadow[bank] = inl(scx200_gpio_base + 0x10 * bank);
|
||||
}
|
||||
|
||||
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);
|
||||
pr_info("GPIO base 0x%x\n", base);
|
||||
|
||||
if (!request_region(base, SCx200_GPIO_SIZE,
|
||||
"NatSemi SCx200 GPIO")) {
|
||||
pr_err("can't allocate I/O for GPIOs\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
scx200_gpio_base = base;
|
||||
scx200_init_shadow();
|
||||
|
||||
} else {
|
||||
/* find the base of the Configuration Block */
|
||||
if (scx200_cb_probe(SCx200_CB_BASE_FIXED)) {
|
||||
scx200_cb_base = SCx200_CB_BASE_FIXED;
|
||||
} else {
|
||||
pci_read_config_dword(pdev, SCx200_CBA_SCRATCH, &base);
|
||||
if (scx200_cb_probe(base)) {
|
||||
scx200_cb_base = base;
|
||||
} else {
|
||||
pr_warn("Configuration Block not found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
pr_info("Configuration Block base 0x%x\n", scx200_cb_base);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 scx200_gpio_configure(unsigned index, u32 mask, u32 bits)
|
||||
{
|
||||
u32 config, new_config;
|
||||
|
||||
mutex_lock(&scx200_gpio_config_lock);
|
||||
|
||||
outl(index, scx200_gpio_base + 0x20);
|
||||
config = inl(scx200_gpio_base + 0x24);
|
||||
|
||||
new_config = (config & mask) | bits;
|
||||
outl(new_config, scx200_gpio_base + 0x24);
|
||||
|
||||
mutex_unlock(&scx200_gpio_config_lock);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
static int __init scx200_init(void)
|
||||
{
|
||||
pr_info("NatSemi SCx200 Driver\n");
|
||||
return pci_register_driver(&scx200_pci_driver);
|
||||
}
|
||||
|
||||
static void __exit scx200_cleanup(void)
|
||||
{
|
||||
pci_unregister_driver(&scx200_pci_driver);
|
||||
release_region(scx200_gpio_base, SCx200_GPIO_SIZE);
|
||||
}
|
||||
|
||||
module_init(scx200_init);
|
||||
module_exit(scx200_cleanup);
|
||||
|
||||
EXPORT_SYMBOL(scx200_gpio_base);
|
||||
EXPORT_SYMBOL(scx200_gpio_shadow);
|
||||
EXPORT_SYMBOL(scx200_gpio_configure);
|
||||
EXPORT_SYMBOL(scx200_cb_base);
|
1
arch/x86/platform/sfi/Makefile
Normal file
1
arch/x86/platform/sfi/Makefile
Normal file
|
@ -0,0 +1 @@
|
|||
obj-$(CONFIG_SFI) += sfi.o
|
117
arch/x86/platform/sfi/sfi.c
Normal file
117
arch/x86/platform/sfi/sfi.c
Normal file
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* sfi.c - x86 architecture SFI support.
|
||||
*
|
||||
* Copyright (c) 2009, Intel Corporation.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope 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.,
|
||||
* 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#define KMSG_COMPONENT "SFI"
|
||||
#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/sfi.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/irqdomain.h>
|
||||
|
||||
#include <asm/io_apic.h>
|
||||
#include <asm/mpspec.h>
|
||||
#include <asm/setup.h>
|
||||
#include <asm/apic.h>
|
||||
|
||||
#ifdef CONFIG_X86_LOCAL_APIC
|
||||
static unsigned long sfi_lapic_addr __initdata = APIC_DEFAULT_PHYS_BASE;
|
||||
|
||||
/* All CPUs enumerated by SFI must be present and enabled */
|
||||
static void __init mp_sfi_register_lapic(u8 id)
|
||||
{
|
||||
if (MAX_LOCAL_APIC - id <= 0) {
|
||||
pr_warning("Processor #%d invalid (max %d)\n",
|
||||
id, MAX_LOCAL_APIC);
|
||||
return;
|
||||
}
|
||||
|
||||
pr_info("registering lapic[%d]\n", id);
|
||||
|
||||
generic_processor_info(id, GET_APIC_VERSION(apic_read(APIC_LVR)));
|
||||
}
|
||||
|
||||
static int __init sfi_parse_cpus(struct sfi_table_header *table)
|
||||
{
|
||||
struct sfi_table_simple *sb;
|
||||
struct sfi_cpu_table_entry *pentry;
|
||||
int i;
|
||||
int cpu_num;
|
||||
|
||||
sb = (struct sfi_table_simple *)table;
|
||||
cpu_num = SFI_GET_NUM_ENTRIES(sb, struct sfi_cpu_table_entry);
|
||||
pentry = (struct sfi_cpu_table_entry *)sb->pentry;
|
||||
|
||||
for (i = 0; i < cpu_num; i++) {
|
||||
mp_sfi_register_lapic(pentry->apic_id);
|
||||
pentry++;
|
||||
}
|
||||
|
||||
smp_found_config = 1;
|
||||
return 0;
|
||||
}
|
||||
#endif /* CONFIG_X86_LOCAL_APIC */
|
||||
|
||||
#ifdef CONFIG_X86_IO_APIC
|
||||
static struct irq_domain_ops sfi_ioapic_irqdomain_ops = {
|
||||
.map = mp_irqdomain_map,
|
||||
};
|
||||
|
||||
static int __init sfi_parse_ioapic(struct sfi_table_header *table)
|
||||
{
|
||||
struct sfi_table_simple *sb;
|
||||
struct sfi_apic_table_entry *pentry;
|
||||
int i, num;
|
||||
struct ioapic_domain_cfg cfg = {
|
||||
.type = IOAPIC_DOMAIN_STRICT,
|
||||
.ops = &sfi_ioapic_irqdomain_ops,
|
||||
};
|
||||
|
||||
sb = (struct sfi_table_simple *)table;
|
||||
num = SFI_GET_NUM_ENTRIES(sb, struct sfi_apic_table_entry);
|
||||
pentry = (struct sfi_apic_table_entry *)sb->pentry;
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
mp_register_ioapic(i, pentry->phys_addr, gsi_top, &cfg);
|
||||
pentry++;
|
||||
}
|
||||
|
||||
WARN(pic_mode, KERN_WARNING
|
||||
"SFI: pic_mod shouldn't be 1 when IOAPIC table is present\n");
|
||||
pic_mode = 0;
|
||||
return 0;
|
||||
}
|
||||
#endif /* CONFIG_X86_IO_APIC */
|
||||
|
||||
/*
|
||||
* sfi_platform_init(): register lapics & io-apics
|
||||
*/
|
||||
int __init sfi_platform_init(void)
|
||||
{
|
||||
#ifdef CONFIG_X86_LOCAL_APIC
|
||||
register_lapic_address(sfi_lapic_addr);
|
||||
sfi_table_parse(SFI_SIG_CPUS, NULL, NULL, sfi_parse_cpus);
|
||||
#endif
|
||||
#ifdef CONFIG_X86_IO_APIC
|
||||
sfi_table_parse(SFI_SIG_APIC, NULL, NULL, sfi_parse_ioapic);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
1
arch/x86/platform/ts5500/Makefile
Normal file
1
arch/x86/platform/ts5500/Makefile
Normal file
|
@ -0,0 +1 @@
|
|||
obj-$(CONFIG_TS5500) += ts5500.o
|
351
arch/x86/platform/ts5500/ts5500.c
Normal file
351
arch/x86/platform/ts5500/ts5500.c
Normal file
|
@ -0,0 +1,351 @@
|
|||
/*
|
||||
* Technologic Systems TS-5500 Single Board Computer support
|
||||
*
|
||||
* Copyright (C) 2013-2014 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 may be extended to support similar x86-based platforms.
|
||||
* Actually, the TS-5500 and TS-5400 are supported.
|
||||
*/
|
||||
|
||||
#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 */
|
||||
#define TS5400_PRODUCT_CODE 0x40 /* TS-5400 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
|
||||
* @name: Board model name.
|
||||
* @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 {
|
||||
const char *name;
|
||||
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;
|
||||
|
||||
sbc->id = inb(TS5500_PRODUCT_CODE_ADDR);
|
||||
if (sbc->id == TS5500_PRODUCT_CODE) {
|
||||
sbc->name = "TS-5500";
|
||||
} else if (sbc->id == TS5400_PRODUCT_CODE) {
|
||||
sbc->name = "TS-5400";
|
||||
} else {
|
||||
pr_err("ts5500: unknown product code 0x%x\n", sbc->id);
|
||||
ret = -ENODEV;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
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 name_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct ts5500_sbc *sbc = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "%s\n", sbc->name);
|
||||
}
|
||||
static DEVICE_ATTR_RO(name);
|
||||
|
||||
static ssize_t id_show(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 DEVICE_ATTR_RO(id);
|
||||
|
||||
static ssize_t jumpers_show(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);
|
||||
}
|
||||
static DEVICE_ATTR_RO(jumpers);
|
||||
|
||||
#define TS5500_ATTR_BOOL(_field) \
|
||||
static ssize_t _field##_show(struct device *dev, \
|
||||
struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct ts5500_sbc *sbc = dev_get_drvdata(dev); \
|
||||
\
|
||||
return sprintf(buf, "%d\n", sbc->_field); \
|
||||
} \
|
||||
static DEVICE_ATTR_RO(_field)
|
||||
|
||||
TS5500_ATTR_BOOL(sram);
|
||||
TS5500_ATTR_BOOL(rs485);
|
||||
TS5500_ATTR_BOOL(adc);
|
||||
TS5500_ATTR_BOOL(ereset);
|
||||
TS5500_ATTR_BOOL(itr);
|
||||
|
||||
static struct attribute *ts5500_attributes[] = {
|
||||
&dev_attr_id.attr,
|
||||
&dev_attr_name.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;
|
||||
|
||||
if (sbc->id == TS5500_PRODUCT_CODE) {
|
||||
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");
|
1
arch/x86/platform/uv/Makefile
Normal file
1
arch/x86/platform/uv/Makefile
Normal file
|
@ -0,0 +1 @@
|
|||
obj-$(CONFIG_X86_UV) += tlb_uv.o bios_uv.o uv_irq.o uv_sysfs.o uv_time.o uv_nmi.o
|
216
arch/x86/platform/uv/bios_uv.c
Normal file
216
arch/x86/platform/uv/bios_uv.c
Normal file
|
@ -0,0 +1,216 @@
|
|||
/*
|
||||
* BIOS run time interface 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) 2008-2009 Silicon Graphics, Inc. All Rights Reserved.
|
||||
* Copyright (c) Russ Anderson <rja@sgi.com>
|
||||
*/
|
||||
|
||||
#include <linux/efi.h>
|
||||
#include <linux/export.h>
|
||||
#include <asm/efi.h>
|
||||
#include <linux/io.h>
|
||||
#include <asm/uv/bios.h>
|
||||
#include <asm/uv/uv_hub.h>
|
||||
|
||||
static struct uv_systab uv_systab;
|
||||
|
||||
s64 uv_bios_call(enum uv_bios_cmd which, u64 a1, u64 a2, u64 a3, u64 a4, u64 a5)
|
||||
{
|
||||
struct uv_systab *tab = &uv_systab;
|
||||
s64 ret;
|
||||
|
||||
if (!tab->function)
|
||||
/*
|
||||
* BIOS does not support UV systab
|
||||
*/
|
||||
return BIOS_STATUS_UNIMPLEMENTED;
|
||||
|
||||
ret = efi_call((void *)__va(tab->function), (u64)which,
|
||||
a1, a2, a3, a4, a5);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uv_bios_call);
|
||||
|
||||
s64 uv_bios_call_irqsave(enum uv_bios_cmd which, u64 a1, u64 a2, u64 a3,
|
||||
u64 a4, u64 a5)
|
||||
{
|
||||
unsigned long bios_flags;
|
||||
s64 ret;
|
||||
|
||||
local_irq_save(bios_flags);
|
||||
ret = uv_bios_call(which, a1, a2, a3, a4, a5);
|
||||
local_irq_restore(bios_flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
s64 uv_bios_call_reentrant(enum uv_bios_cmd which, u64 a1, u64 a2, u64 a3,
|
||||
u64 a4, u64 a5)
|
||||
{
|
||||
s64 ret;
|
||||
|
||||
preempt_disable();
|
||||
ret = uv_bios_call(which, a1, a2, a3, a4, a5);
|
||||
preempt_enable();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
long sn_partition_id;
|
||||
EXPORT_SYMBOL_GPL(sn_partition_id);
|
||||
long sn_coherency_id;
|
||||
EXPORT_SYMBOL_GPL(sn_coherency_id);
|
||||
long sn_region_size;
|
||||
EXPORT_SYMBOL_GPL(sn_region_size);
|
||||
long system_serial_number;
|
||||
EXPORT_SYMBOL_GPL(system_serial_number);
|
||||
int uv_type;
|
||||
EXPORT_SYMBOL_GPL(uv_type);
|
||||
|
||||
|
||||
s64 uv_bios_get_sn_info(int fc, int *uvtype, long *partid, long *coher,
|
||||
long *region, long *ssn)
|
||||
{
|
||||
s64 ret;
|
||||
u64 v0, v1;
|
||||
union partition_info_u part;
|
||||
|
||||
ret = uv_bios_call_irqsave(UV_BIOS_GET_SN_INFO, fc,
|
||||
(u64)(&v0), (u64)(&v1), 0, 0);
|
||||
if (ret != BIOS_STATUS_SUCCESS)
|
||||
return ret;
|
||||
|
||||
part.val = v0;
|
||||
if (uvtype)
|
||||
*uvtype = part.hub_version;
|
||||
if (partid)
|
||||
*partid = part.partition_id;
|
||||
if (coher)
|
||||
*coher = part.coherence_id;
|
||||
if (region)
|
||||
*region = part.region_size;
|
||||
if (ssn)
|
||||
*ssn = v1;
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uv_bios_get_sn_info);
|
||||
|
||||
int
|
||||
uv_bios_mq_watchlist_alloc(unsigned long addr, unsigned int mq_size,
|
||||
unsigned long *intr_mmr_offset)
|
||||
{
|
||||
u64 watchlist;
|
||||
s64 ret;
|
||||
|
||||
/*
|
||||
* bios returns watchlist number or negative error number.
|
||||
*/
|
||||
ret = (int)uv_bios_call_irqsave(UV_BIOS_WATCHLIST_ALLOC, addr,
|
||||
mq_size, (u64)intr_mmr_offset,
|
||||
(u64)&watchlist, 0);
|
||||
if (ret < BIOS_STATUS_SUCCESS)
|
||||
return ret;
|
||||
|
||||
return watchlist;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uv_bios_mq_watchlist_alloc);
|
||||
|
||||
int
|
||||
uv_bios_mq_watchlist_free(int blade, int watchlist_num)
|
||||
{
|
||||
return (int)uv_bios_call_irqsave(UV_BIOS_WATCHLIST_FREE,
|
||||
blade, watchlist_num, 0, 0, 0);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uv_bios_mq_watchlist_free);
|
||||
|
||||
s64
|
||||
uv_bios_change_memprotect(u64 paddr, u64 len, enum uv_memprotect perms)
|
||||
{
|
||||
return uv_bios_call_irqsave(UV_BIOS_MEMPROTECT, paddr, len,
|
||||
perms, 0, 0);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uv_bios_change_memprotect);
|
||||
|
||||
s64
|
||||
uv_bios_reserved_page_pa(u64 buf, u64 *cookie, u64 *addr, u64 *len)
|
||||
{
|
||||
s64 ret;
|
||||
|
||||
ret = uv_bios_call_irqsave(UV_BIOS_GET_PARTITION_ADDR, (u64)cookie,
|
||||
(u64)addr, buf, (u64)len, 0);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uv_bios_reserved_page_pa);
|
||||
|
||||
s64 uv_bios_freq_base(u64 clock_type, u64 *ticks_per_second)
|
||||
{
|
||||
return uv_bios_call(UV_BIOS_FREQ_BASE, clock_type,
|
||||
(u64)ticks_per_second, 0, 0, 0);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uv_bios_freq_base);
|
||||
|
||||
/*
|
||||
* uv_bios_set_legacy_vga_target - Set Legacy VGA I/O Target
|
||||
* @decode: true to enable target, false to disable target
|
||||
* @domain: PCI domain number
|
||||
* @bus: PCI bus number
|
||||
*
|
||||
* Returns:
|
||||
* 0: Success
|
||||
* -EINVAL: Invalid domain or bus number
|
||||
* -ENOSYS: Capability not available
|
||||
* -EBUSY: Legacy VGA I/O cannot be retargeted at this time
|
||||
*/
|
||||
int uv_bios_set_legacy_vga_target(bool decode, int domain, int bus)
|
||||
{
|
||||
return uv_bios_call(UV_BIOS_SET_LEGACY_VGA_TARGET,
|
||||
(u64)decode, (u64)domain, (u64)bus, 0, 0);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uv_bios_set_legacy_vga_target);
|
||||
|
||||
|
||||
#ifdef CONFIG_EFI
|
||||
void uv_bios_init(void)
|
||||
{
|
||||
struct uv_systab *tab;
|
||||
|
||||
if ((efi.uv_systab == EFI_INVALID_TABLE_ADDR) ||
|
||||
(efi.uv_systab == (unsigned long)NULL)) {
|
||||
printk(KERN_CRIT "No EFI UV System Table.\n");
|
||||
uv_systab.function = (unsigned long)NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
tab = (struct uv_systab *)ioremap(efi.uv_systab,
|
||||
sizeof(struct uv_systab));
|
||||
if (strncmp(tab->signature, "UVST", 4) != 0)
|
||||
printk(KERN_ERR "bad signature in UV system table!");
|
||||
|
||||
/*
|
||||
* Copy table to permanent spot for later use.
|
||||
*/
|
||||
memcpy(&uv_systab, tab, sizeof(struct uv_systab));
|
||||
iounmap(tab);
|
||||
|
||||
printk(KERN_INFO "EFI UV System Table Revision %d\n",
|
||||
uv_systab.revision);
|
||||
}
|
||||
#else /* !CONFIG_EFI */
|
||||
|
||||
void uv_bios_init(void) { }
|
||||
#endif
|
2171
arch/x86/platform/uv/tlb_uv.c
Normal file
2171
arch/x86/platform/uv/tlb_uv.c
Normal file
File diff suppressed because it is too large
Load diff
288
arch/x86/platform/uv/uv_irq.c
Normal file
288
arch/x86/platform/uv/uv_irq.c
Normal file
|
@ -0,0 +1,288 @@
|
|||
/*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details.
|
||||
*
|
||||
* SGI UV IRQ functions
|
||||
*
|
||||
* Copyright (C) 2008 Silicon Graphics, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/rbtree.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/irq.h>
|
||||
|
||||
#include <asm/apic.h>
|
||||
#include <asm/uv/uv_irq.h>
|
||||
#include <asm/uv/uv_hub.h>
|
||||
|
||||
/* MMR offset and pnode of hub sourcing interrupts for a given irq */
|
||||
struct uv_irq_2_mmr_pnode{
|
||||
struct rb_node list;
|
||||
unsigned long offset;
|
||||
int pnode;
|
||||
int irq;
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
static void uv_noop(struct irq_data *data) { }
|
||||
|
||||
static void uv_ack_apic(struct irq_data *data)
|
||||
{
|
||||
ack_APIC_irq();
|
||||
}
|
||||
|
||||
static struct irq_chip uv_irq_chip = {
|
||||
.name = "UV-CORE",
|
||||
.irq_mask = uv_noop,
|
||||
.irq_unmask = uv_noop,
|
||||
.irq_eoi = uv_ack_apic,
|
||||
.irq_set_affinity = uv_set_irq_affinity,
|
||||
};
|
||||
|
||||
/*
|
||||
* Add offset and pnode information of the hub sourcing interrupts to the
|
||||
* rb tree for a specific irq.
|
||||
*/
|
||||
static int uv_set_irq_2_mmr_info(int irq, unsigned long offset, unsigned blade)
|
||||
{
|
||||
struct rb_node **link = &uv_irq_root.rb_node;
|
||||
struct rb_node *parent = NULL;
|
||||
struct uv_irq_2_mmr_pnode *n;
|
||||
struct uv_irq_2_mmr_pnode *e;
|
||||
unsigned long irqflags;
|
||||
|
||||
n = kmalloc_node(sizeof(struct uv_irq_2_mmr_pnode), GFP_KERNEL,
|
||||
uv_blade_to_memory_nid(blade));
|
||||
if (!n)
|
||||
return -ENOMEM;
|
||||
|
||||
n->irq = irq;
|
||||
n->offset = offset;
|
||||
n->pnode = uv_blade_to_pnode(blade);
|
||||
spin_lock_irqsave(&uv_irq_lock, irqflags);
|
||||
/* Find the right place in the rbtree: */
|
||||
while (*link) {
|
||||
parent = *link;
|
||||
e = rb_entry(parent, struct uv_irq_2_mmr_pnode, list);
|
||||
|
||||
if (unlikely(irq == e->irq)) {
|
||||
/* irq entry exists */
|
||||
e->pnode = uv_blade_to_pnode(blade);
|
||||
e->offset = offset;
|
||||
spin_unlock_irqrestore(&uv_irq_lock, irqflags);
|
||||
kfree(n);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (irq < e->irq)
|
||||
link = &(*link)->rb_left;
|
||||
else
|
||||
link = &(*link)->rb_right;
|
||||
}
|
||||
|
||||
/* Insert the node into the rbtree. */
|
||||
rb_link_node(&n->list, parent, link);
|
||||
rb_insert_color(&n->list, &uv_irq_root);
|
||||
|
||||
spin_unlock_irqrestore(&uv_irq_lock, irqflags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Retrieve offset and pnode information from the rb tree for a specific irq */
|
||||
int uv_irq_2_mmr_info(int irq, unsigned long *offset, int *pnode)
|
||||
{
|
||||
struct uv_irq_2_mmr_pnode *e;
|
||||
struct rb_node *n;
|
||||
unsigned long irqflags;
|
||||
|
||||
spin_lock_irqsave(&uv_irq_lock, irqflags);
|
||||
n = uv_irq_root.rb_node;
|
||||
while (n) {
|
||||
e = rb_entry(n, struct uv_irq_2_mmr_pnode, list);
|
||||
|
||||
if (e->irq == irq) {
|
||||
*offset = e->offset;
|
||||
*pnode = e->pnode;
|
||||
spin_unlock_irqrestore(&uv_irq_lock, irqflags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (irq < e->irq)
|
||||
n = n->rb_left;
|
||||
else
|
||||
n = n->rb_right;
|
||||
}
|
||||
spin_unlock_irqrestore(&uv_irq_lock, irqflags);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Re-target the irq to the specified CPU and enable the specified MMR located
|
||||
* on the specified blade to allow the sending of MSIs to the specified CPU.
|
||||
*/
|
||||
static int
|
||||
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 = 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));
|
||||
|
||||
err = assign_irq_vector(irq, cfg, eligible_cpu);
|
||||
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);
|
||||
|
||||
irq_set_chip_and_handler_name(irq, &uv_irq_chip, handle_percpu_irq,
|
||||
irq_name);
|
||||
|
||||
mmr_value = 0;
|
||||
entry = (struct uv_IO_APIC_route_entry *)&mmr_value;
|
||||
entry->vector = cfg->vector;
|
||||
entry->delivery_mode = apic->irq_delivery_mode;
|
||||
entry->dest_mode = apic->irq_dest_mode;
|
||||
entry->polarity = 0;
|
||||
entry->trigger = 0;
|
||||
entry->mask = 0;
|
||||
entry->dest = dest;
|
||||
|
||||
mmr_pnode = uv_blade_to_pnode(mmr_blade);
|
||||
uv_write_global_mmr64(mmr_pnode, mmr_offset, mmr_value);
|
||||
|
||||
if (cfg->move_in_progress)
|
||||
send_cleanup_vector(cfg);
|
||||
|
||||
return irq;
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable the specified MMR located on the specified blade so that MSIs are
|
||||
* longer allowed to be sent.
|
||||
*/
|
||||
static void arch_disable_uv_irq(int mmr_pnode, unsigned long mmr_offset)
|
||||
{
|
||||
unsigned long mmr_value;
|
||||
struct uv_IO_APIC_route_entry *entry;
|
||||
|
||||
BUILD_BUG_ON(sizeof(struct uv_IO_APIC_route_entry) !=
|
||||
sizeof(unsigned long));
|
||||
|
||||
mmr_value = 0;
|
||||
entry = (struct uv_IO_APIC_route_entry *)&mmr_value;
|
||||
entry->mask = 1;
|
||||
|
||||
uv_write_global_mmr64(mmr_pnode, mmr_offset, mmr_value);
|
||||
}
|
||||
|
||||
static int
|
||||
uv_set_irq_affinity(struct irq_data *data, const struct cpumask *mask,
|
||||
bool force)
|
||||
{
|
||||
struct irq_cfg *cfg = data->chip_data;
|
||||
unsigned int dest;
|
||||
unsigned long mmr_value, mmr_offset;
|
||||
struct uv_IO_APIC_route_entry *entry;
|
||||
int mmr_pnode;
|
||||
|
||||
if (__ioapic_set_affinity(data, mask, &dest))
|
||||
return -1;
|
||||
|
||||
mmr_value = 0;
|
||||
entry = (struct uv_IO_APIC_route_entry *)&mmr_value;
|
||||
|
||||
entry->vector = cfg->vector;
|
||||
entry->delivery_mode = apic->irq_delivery_mode;
|
||||
entry->dest_mode = apic->irq_dest_mode;
|
||||
entry->polarity = 0;
|
||||
entry->trigger = 0;
|
||||
entry->mask = 0;
|
||||
entry->dest = dest;
|
||||
|
||||
/* Get previously stored MMR and pnode of hub sourcing interrupts */
|
||||
if (uv_irq_2_mmr_info(data->irq, &mmr_offset, &mmr_pnode))
|
||||
return -1;
|
||||
|
||||
uv_write_global_mmr64(mmr_pnode, mmr_offset, mmr_value);
|
||||
|
||||
if (cfg->move_in_progress)
|
||||
send_cleanup_vector(cfg);
|
||||
|
||||
return IRQ_SET_MASK_OK_NOCOPY;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set up a mapping of an available irq and vector, and enable the specified
|
||||
* MMR that defines the MSI that is to be sent to the specified CPU when an
|
||||
* interrupt is raised.
|
||||
*/
|
||||
int uv_setup_irq(char *irq_name, int cpu, int mmr_blade,
|
||||
unsigned long mmr_offset, int limit)
|
||||
{
|
||||
int ret, irq = irq_alloc_hwirq(uv_blade_to_memory_nid(mmr_blade));
|
||||
|
||||
if (!irq)
|
||||
return -EBUSY;
|
||||
|
||||
ret = arch_enable_uv_irq(irq_name, irq, cpu, mmr_blade, mmr_offset,
|
||||
limit);
|
||||
if (ret == irq)
|
||||
uv_set_irq_2_mmr_info(irq, mmr_offset, mmr_blade);
|
||||
else
|
||||
irq_free_hwirq(irq);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uv_setup_irq);
|
||||
|
||||
/*
|
||||
* Tear down a mapping of an irq and vector, and disable the specified MMR that
|
||||
* defined the MSI that was to be sent to the specified CPU when an interrupt
|
||||
* was raised.
|
||||
*
|
||||
* Set mmr_blade and mmr_offset to what was passed in on uv_setup_irq().
|
||||
*/
|
||||
void uv_teardown_irq(unsigned int irq)
|
||||
{
|
||||
struct uv_irq_2_mmr_pnode *e;
|
||||
struct rb_node *n;
|
||||
unsigned long irqflags;
|
||||
|
||||
spin_lock_irqsave(&uv_irq_lock, irqflags);
|
||||
n = uv_irq_root.rb_node;
|
||||
while (n) {
|
||||
e = rb_entry(n, struct uv_irq_2_mmr_pnode, list);
|
||||
if (e->irq == irq) {
|
||||
arch_disable_uv_irq(e->pnode, e->offset);
|
||||
rb_erase(n, &uv_irq_root);
|
||||
kfree(e);
|
||||
break;
|
||||
}
|
||||
if (irq < e->irq)
|
||||
n = n->rb_left;
|
||||
else
|
||||
n = n->rb_right;
|
||||
}
|
||||
spin_unlock_irqrestore(&uv_irq_lock, irqflags);
|
||||
irq_free_hwirq(irq);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uv_teardown_irq);
|
727
arch/x86/platform/uv/uv_nmi.c
Normal file
727
arch/x86/platform/uv/uv_nmi.c
Normal file
|
@ -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);
|
||||
this_cpu_inc(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)
|
||||
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) {
|
||||
uv_cpu_nmi_per(cpu).pinging = 0;
|
||||
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 (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 */
|
||||
this_cpu_write(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);
|
||||
}
|
||||
this_cpu_write(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 (uv_cpu_nmi_per(cpu).state != UV_NMI_STATE_IN)
|
||||
return;
|
||||
|
||||
uv_cpu_nmi_per(cpu).state = UV_NMI_STATE_DUMP;
|
||||
do {
|
||||
cpu_relax();
|
||||
udelay(10);
|
||||
if (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);
|
||||
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 (this_cpu_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 (!this_cpu_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 */
|
||||
this_cpu_write(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;
|
||||
|
||||
this_cpu_inc(uv_cpu_nmi.queries);
|
||||
if (!this_cpu_read(uv_cpu_nmi.pinging)) {
|
||||
local64_inc(&uv_nmi_ping_misses);
|
||||
return NMI_DONE;
|
||||
}
|
||||
|
||||
this_cpu_inc(uv_cpu_nmi.pings);
|
||||
local64_inc(&uv_nmi_ping_count);
|
||||
ret = uv_handle_nmi(reason, regs);
|
||||
this_cpu_write(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();
|
||||
}
|
76
arch/x86/platform/uv/uv_sysfs.c
Normal file
76
arch/x86/platform/uv/uv_sysfs.c
Normal file
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* This file supports the /sys/firmware/sgi_uv interfaces for SGI UV.
|
||||
*
|
||||
* 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) 2008 Silicon Graphics, Inc. All Rights Reserved.
|
||||
* Copyright (c) Russ Anderson
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <asm/uv/bios.h>
|
||||
#include <asm/uv/uv.h>
|
||||
|
||||
struct kobject *sgi_uv_kobj;
|
||||
|
||||
static ssize_t partition_id_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
return snprintf(buf, PAGE_SIZE, "%ld\n", sn_partition_id);
|
||||
}
|
||||
|
||||
static ssize_t coherence_id_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
return snprintf(buf, PAGE_SIZE, "%ld\n", partition_coherence_id());
|
||||
}
|
||||
|
||||
static struct kobj_attribute partition_id_attr =
|
||||
__ATTR(partition_id, S_IRUGO, partition_id_show, NULL);
|
||||
|
||||
static struct kobj_attribute coherence_id_attr =
|
||||
__ATTR(coherence_id, S_IRUGO, coherence_id_show, NULL);
|
||||
|
||||
|
||||
static int __init sgi_uv_sysfs_init(void)
|
||||
{
|
||||
unsigned long ret;
|
||||
|
||||
if (!is_uv_system())
|
||||
return -ENODEV;
|
||||
|
||||
if (!sgi_uv_kobj)
|
||||
sgi_uv_kobj = kobject_create_and_add("sgi_uv", firmware_kobj);
|
||||
if (!sgi_uv_kobj) {
|
||||
printk(KERN_WARNING "kobject_create_and_add sgi_uv failed\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = sysfs_create_file(sgi_uv_kobj, &partition_id_attr.attr);
|
||||
if (ret) {
|
||||
printk(KERN_WARNING "sysfs_create_file partition_id failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = sysfs_create_file(sgi_uv_kobj, &coherence_id_attr.attr);
|
||||
if (ret) {
|
||||
printk(KERN_WARNING "sysfs_create_file coherence_id failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
device_initcall(sgi_uv_sysfs_init);
|
425
arch/x86/platform/uv/uv_time.c
Normal file
425
arch/x86/platform/uv/uv_time.c
Normal file
|
@ -0,0 +1,425 @@
|
|||
/*
|
||||
* SGI RTC clock/timer 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) Dimitri Sivanich
|
||||
*/
|
||||
#include <linux/clockchips.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <asm/uv/uv_mmrs.h>
|
||||
#include <asm/uv/uv_hub.h>
|
||||
#include <asm/uv/bios.h>
|
||||
#include <asm/uv/uv.h>
|
||||
#include <asm/apic.h>
|
||||
#include <asm/cpu.h>
|
||||
|
||||
#define RTC_NAME "sgi_rtc"
|
||||
|
||||
static cycle_t uv_read_rtc(struct clocksource *cs);
|
||||
static int uv_rtc_next_event(unsigned long, struct clock_event_device *);
|
||||
static void uv_rtc_timer_setup(enum clock_event_mode,
|
||||
struct clock_event_device *);
|
||||
|
||||
static struct clocksource clocksource_uv = {
|
||||
.name = RTC_NAME,
|
||||
.rating = 299,
|
||||
.read = uv_read_rtc,
|
||||
.mask = (cycle_t)UVH_RTC_REAL_TIME_CLOCK_MASK,
|
||||
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
|
||||
};
|
||||
|
||||
static struct clock_event_device clock_event_device_uv = {
|
||||
.name = RTC_NAME,
|
||||
.features = CLOCK_EVT_FEAT_ONESHOT,
|
||||
.shift = 20,
|
||||
.rating = 400,
|
||||
.irq = -1,
|
||||
.set_next_event = uv_rtc_next_event,
|
||||
.set_mode = uv_rtc_timer_setup,
|
||||
.event_handler = NULL,
|
||||
};
|
||||
|
||||
static DEFINE_PER_CPU(struct clock_event_device, cpu_ced);
|
||||
|
||||
/* There is one of these allocated per node */
|
||||
struct uv_rtc_timer_head {
|
||||
spinlock_t lock;
|
||||
/* next cpu waiting for timer, local node relative: */
|
||||
int next_cpu;
|
||||
/* number of cpus on this node: */
|
||||
int ncpus;
|
||||
struct {
|
||||
int lcpu; /* systemwide logical cpu number */
|
||||
u64 expires; /* next timer expiration for this cpu */
|
||||
} cpu[1];
|
||||
};
|
||||
|
||||
/*
|
||||
* Access to uv_rtc_timer_head via blade id.
|
||||
*/
|
||||
static struct uv_rtc_timer_head **blade_info __read_mostly;
|
||||
|
||||
static int uv_rtc_evt_enable;
|
||||
|
||||
/*
|
||||
* Hardware interface routines
|
||||
*/
|
||||
|
||||
/* Send IPIs to another node */
|
||||
static void uv_rtc_send_IPI(int cpu)
|
||||
{
|
||||
unsigned long apicid, val;
|
||||
int pnode;
|
||||
|
||||
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);
|
||||
|
||||
uv_write_global_mmr64(pnode, UVH_IPI_INT, val);
|
||||
}
|
||||
|
||||
/* Check for an RTC interrupt pending */
|
||||
static int uv_intr_pending(int pnode)
|
||||
{
|
||||
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);
|
||||
|
||||
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)apicid << UVH_RTC1_INT_CONFIG_APIC_ID_SHFT);
|
||||
|
||||
/* Set configuration */
|
||||
uv_write_global_mmr64(pnode, UVH_RTC1_INT_CONFIG, val);
|
||||
/* Initialize comparator value */
|
||||
uv_write_global_mmr64(pnode, UVH_INT_CMPB, expires);
|
||||
|
||||
if (uv_read_rtc(NULL) <= expires)
|
||||
return 0;
|
||||
|
||||
return !uv_intr_pending(pnode);
|
||||
}
|
||||
|
||||
/*
|
||||
* Per-cpu timer tracking routines
|
||||
*/
|
||||
|
||||
static __init void uv_rtc_deallocate_timers(void)
|
||||
{
|
||||
int bid;
|
||||
|
||||
for_each_possible_blade(bid) {
|
||||
kfree(blade_info[bid]);
|
||||
}
|
||||
kfree(blade_info);
|
||||
}
|
||||
|
||||
/* Allocate per-node list of cpu timer expiration times. */
|
||||
static __init int uv_rtc_allocate_timers(void)
|
||||
{
|
||||
int cpu;
|
||||
|
||||
blade_info = kzalloc(uv_possible_blades * sizeof(void *), GFP_KERNEL);
|
||||
if (!blade_info)
|
||||
return -ENOMEM;
|
||||
|
||||
for_each_present_cpu(cpu) {
|
||||
int nid = cpu_to_node(cpu);
|
||||
int bid = uv_cpu_to_blade_id(cpu);
|
||||
int bcpu = uv_cpu_hub_info(cpu)->blade_processor_id;
|
||||
struct uv_rtc_timer_head *head = blade_info[bid];
|
||||
|
||||
if (!head) {
|
||||
head = kmalloc_node(sizeof(struct uv_rtc_timer_head) +
|
||||
(uv_blade_nr_possible_cpus(bid) *
|
||||
2 * sizeof(u64)),
|
||||
GFP_KERNEL, nid);
|
||||
if (!head) {
|
||||
uv_rtc_deallocate_timers();
|
||||
return -ENOMEM;
|
||||
}
|
||||
spin_lock_init(&head->lock);
|
||||
head->ncpus = uv_blade_nr_possible_cpus(bid);
|
||||
head->next_cpu = -1;
|
||||
blade_info[bid] = head;
|
||||
}
|
||||
|
||||
head->cpu[bcpu].lcpu = cpu;
|
||||
head->cpu[bcpu].expires = ULLONG_MAX;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Find and set the next expiring timer. */
|
||||
static void uv_rtc_find_next_timer(struct uv_rtc_timer_head *head, int pnode)
|
||||
{
|
||||
u64 lowest = ULLONG_MAX;
|
||||
int c, bcpu = -1;
|
||||
|
||||
head->next_cpu = -1;
|
||||
for (c = 0; c < head->ncpus; c++) {
|
||||
u64 exp = head->cpu[c].expires;
|
||||
if (exp < lowest) {
|
||||
bcpu = c;
|
||||
lowest = exp;
|
||||
}
|
||||
}
|
||||
if (bcpu >= 0) {
|
||||
head->next_cpu = bcpu;
|
||||
c = head->cpu[bcpu].lcpu;
|
||||
if (uv_setup_intr(c, lowest))
|
||||
/* If we didn't set it up in time, trigger */
|
||||
uv_rtc_send_IPI(c);
|
||||
} else {
|
||||
uv_write_global_mmr64(pnode, UVH_RTC1_INT_CONFIG,
|
||||
UVH_RTC1_INT_CONFIG_M_MASK);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Set expiration time for current cpu.
|
||||
*
|
||||
* Returns 1 if we missed the expiration time.
|
||||
*/
|
||||
static int uv_rtc_set_timer(int cpu, u64 expires)
|
||||
{
|
||||
int pnode = uv_cpu_to_pnode(cpu);
|
||||
int bid = uv_cpu_to_blade_id(cpu);
|
||||
struct uv_rtc_timer_head *head = blade_info[bid];
|
||||
int bcpu = uv_cpu_hub_info(cpu)->blade_processor_id;
|
||||
u64 *t = &head->cpu[bcpu].expires;
|
||||
unsigned long flags;
|
||||
int next_cpu;
|
||||
|
||||
spin_lock_irqsave(&head->lock, flags);
|
||||
|
||||
next_cpu = head->next_cpu;
|
||||
*t = expires;
|
||||
|
||||
/* Will this one be next to go off? */
|
||||
if (next_cpu < 0 || bcpu == next_cpu ||
|
||||
expires < head->cpu[next_cpu].expires) {
|
||||
head->next_cpu = bcpu;
|
||||
if (uv_setup_intr(cpu, expires)) {
|
||||
*t = ULLONG_MAX;
|
||||
uv_rtc_find_next_timer(head, pnode);
|
||||
spin_unlock_irqrestore(&head->lock, flags);
|
||||
return -ETIME;
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&head->lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unset expiration time for current cpu.
|
||||
*
|
||||
* Returns 1 if this timer was pending.
|
||||
*/
|
||||
static int uv_rtc_unset_timer(int cpu, int force)
|
||||
{
|
||||
int pnode = uv_cpu_to_pnode(cpu);
|
||||
int bid = uv_cpu_to_blade_id(cpu);
|
||||
struct uv_rtc_timer_head *head = blade_info[bid];
|
||||
int bcpu = uv_cpu_hub_info(cpu)->blade_processor_id;
|
||||
u64 *t = &head->cpu[bcpu].expires;
|
||||
unsigned long flags;
|
||||
int rc = 0;
|
||||
|
||||
spin_lock_irqsave(&head->lock, flags);
|
||||
|
||||
if ((head->next_cpu == bcpu && uv_read_rtc(NULL) >= *t) || force)
|
||||
rc = 1;
|
||||
|
||||
if (rc) {
|
||||
*t = ULLONG_MAX;
|
||||
/* Was the hardware setup for this timer? */
|
||||
if (head->next_cpu == bcpu)
|
||||
uv_rtc_find_next_timer(head, pnode);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&head->lock, flags);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Kernel interface routines.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Read the RTC.
|
||||
*
|
||||
* Starting with HUB rev 2.0, the UV RTC register is replicated across all
|
||||
* cachelines of it's own page. This allows faster simultaneous reads
|
||||
* from a given socket.
|
||||
*/
|
||||
static cycle_t uv_read_rtc(struct clocksource *cs)
|
||||
{
|
||||
unsigned long offset;
|
||||
|
||||
if (uv_get_min_hub_revision_id() == 1)
|
||||
offset = 0;
|
||||
else
|
||||
offset = (uv_blade_processor_id() * L1_CACHE_BYTES) % PAGE_SIZE;
|
||||
|
||||
return (cycle_t)uv_read_local_mmr(UVH_RTC | offset);
|
||||
}
|
||||
|
||||
/*
|
||||
* Program the next event, relative to now
|
||||
*/
|
||||
static int uv_rtc_next_event(unsigned long delta,
|
||||
struct clock_event_device *ced)
|
||||
{
|
||||
int ced_cpu = cpumask_first(ced->cpumask);
|
||||
|
||||
return uv_rtc_set_timer(ced_cpu, delta + uv_read_rtc(NULL));
|
||||
}
|
||||
|
||||
/*
|
||||
* Setup the RTC timer in oneshot mode
|
||||
*/
|
||||
static void uv_rtc_timer_setup(enum clock_event_mode mode,
|
||||
struct clock_event_device *evt)
|
||||
{
|
||||
int ced_cpu = cpumask_first(evt->cpumask);
|
||||
|
||||
switch (mode) {
|
||||
case CLOCK_EVT_MODE_PERIODIC:
|
||||
case CLOCK_EVT_MODE_ONESHOT:
|
||||
case CLOCK_EVT_MODE_RESUME:
|
||||
/* Nothing to do here yet */
|
||||
break;
|
||||
case CLOCK_EVT_MODE_UNUSED:
|
||||
case CLOCK_EVT_MODE_SHUTDOWN:
|
||||
uv_rtc_unset_timer(ced_cpu, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void uv_rtc_interrupt(void)
|
||||
{
|
||||
int cpu = smp_processor_id();
|
||||
struct clock_event_device *ced = &per_cpu(cpu_ced, cpu);
|
||||
|
||||
if (!ced || !ced->event_handler)
|
||||
return;
|
||||
|
||||
if (uv_rtc_unset_timer(cpu, 0) != 1)
|
||||
return;
|
||||
|
||||
ced->event_handler(ced);
|
||||
}
|
||||
|
||||
static int __init uv_enable_evt_rtc(char *str)
|
||||
{
|
||||
uv_rtc_evt_enable = 1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
__setup("uvrtcevt", uv_enable_evt_rtc);
|
||||
|
||||
static __init void uv_rtc_register_clockevents(struct work_struct *dummy)
|
||||
{
|
||||
struct clock_event_device *ced = this_cpu_ptr(&cpu_ced);
|
||||
|
||||
*ced = clock_event_device_uv;
|
||||
ced->cpumask = cpumask_of(smp_processor_id());
|
||||
clockevents_register_device(ced);
|
||||
}
|
||||
|
||||
static __init int uv_rtc_setup_clock(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (!is_uv_system())
|
||||
return -ENODEV;
|
||||
|
||||
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
|
||||
printk(KERN_INFO "UV RTC clocksource registered freq %lu MHz\n",
|
||||
sn_rtc_cycles_per_second/(unsigned long)1E6);
|
||||
|
||||
if (rc || !uv_rtc_evt_enable || x86_platform_ipi_callback)
|
||||
return rc;
|
||||
|
||||
/* Setup and register clockevents */
|
||||
rc = uv_rtc_allocate_timers();
|
||||
if (rc)
|
||||
goto error;
|
||||
|
||||
x86_platform_ipi_callback = uv_rtc_interrupt;
|
||||
|
||||
clock_event_device_uv.mult = div_sc(sn_rtc_cycles_per_second,
|
||||
NSEC_PER_SEC, clock_event_device_uv.shift);
|
||||
|
||||
clock_event_device_uv.min_delta_ns = NSEC_PER_SEC /
|
||||
sn_rtc_cycles_per_second;
|
||||
|
||||
clock_event_device_uv.max_delta_ns = clocksource_uv.mask *
|
||||
(NSEC_PER_SEC / sn_rtc_cycles_per_second);
|
||||
|
||||
rc = schedule_on_each_cpu(uv_rtc_register_clockevents);
|
||||
if (rc) {
|
||||
x86_platform_ipi_callback = NULL;
|
||||
uv_rtc_deallocate_timers();
|
||||
goto error;
|
||||
}
|
||||
|
||||
printk(KERN_INFO "UV RTC clockevents registered\n");
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
clocksource_unregister(&clocksource_uv);
|
||||
printk(KERN_INFO "UV RTC clockevents failed rc %d\n", rc);
|
||||
|
||||
return rc;
|
||||
}
|
||||
arch_initcall(uv_rtc_setup_clock);
|
Loading…
Add table
Add a link
Reference in a new issue