Initial commit
This commit is contained in:
commit
169c65d57e
51358 changed files with 23120455 additions and 0 deletions
13
arch/x86/platform/Makefile
Normal file
13
arch/x86/platform/Makefile
Normal file
|
@ -0,0 +1,13 @@
|
|||
# Platform specific code goes here
|
||||
obj-y += ce4100/
|
||||
obj-y += efi/
|
||||
obj-y += geode/
|
||||
obj-y += goldfish/
|
||||
obj-y += iris/
|
||||
obj-y += mrst/
|
||||
obj-y += olpc/
|
||||
obj-y += scx200/
|
||||
obj-y += sfi/
|
||||
obj-y += ts5500/
|
||||
obj-y += visws/
|
||||
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
|
173
arch/x86/platform/ce4100/ce4100.c
Normal file
173
arch/x86/platform/ce4100/ce4100.c
Normal file
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* 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/serial_reg.h>
|
||||
#include <linux/serial_8250.h>
|
||||
|
||||
#include <asm/ce4100.h>
|
||||
#include <asm/prom.h>
|
||||
#include <asm/setup.h>
|
||||
#include <asm/i8259.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/io_apic.h>
|
||||
#include <asm/emergency-restart.h>
|
||||
|
||||
static int ce4100_i8042_detect(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The CE4100 platform has an internal 8051 Microcontroller which is
|
||||
* responsible for signaling to the external Power Management Unit the
|
||||
* intention to reset, reboot or power off the system. This 8051 device has
|
||||
* its command register mapped at I/O port 0xcf9 and the value 0x4 is used
|
||||
* to power off the system.
|
||||
*/
|
||||
static void ce4100_power_off(void)
|
||||
{
|
||||
outb(0x4, 0xcf9);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SERIAL_8250
|
||||
|
||||
static unsigned int mem_serial_in(struct uart_port *p, int offset)
|
||||
{
|
||||
offset = offset << p->regshift;
|
||||
return readl(p->membase + offset);
|
||||
}
|
||||
|
||||
/*
|
||||
* The UART Tx interrupts are not set under some conditions and therefore serial
|
||||
* transmission hangs. This is a silicon issue and has not been root caused. The
|
||||
* workaround for this silicon issue checks UART_LSR_THRE bit and UART_LSR_TEMT
|
||||
* bit of LSR register in interrupt handler to see whether at least one of these
|
||||
* two bits is set, if so then process the transmit request. If this workaround
|
||||
* is not applied, then the serial transmission may hang. This workaround is for
|
||||
* errata number 9 in Errata - B step.
|
||||
*/
|
||||
|
||||
static unsigned int ce4100_mem_serial_in(struct uart_port *p, int offset)
|
||||
{
|
||||
unsigned int ret, ier, lsr;
|
||||
|
||||
if (offset == UART_IIR) {
|
||||
offset = offset << p->regshift;
|
||||
ret = readl(p->membase + offset);
|
||||
if (ret & UART_IIR_NO_INT) {
|
||||
/* see if the TX interrupt should have really set */
|
||||
ier = mem_serial_in(p, UART_IER);
|
||||
/* see if the UART's XMIT interrupt is enabled */
|
||||
if (ier & UART_IER_THRI) {
|
||||
lsr = mem_serial_in(p, UART_LSR);
|
||||
/* now check to see if the UART should be
|
||||
generating an interrupt (but isn't) */
|
||||
if (lsr & (UART_LSR_THRE | UART_LSR_TEMT))
|
||||
ret &= ~UART_IIR_NO_INT;
|
||||
}
|
||||
}
|
||||
} else
|
||||
ret = mem_serial_in(p, offset);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ce4100_mem_serial_out(struct uart_port *p, int offset, int value)
|
||||
{
|
||||
offset = offset << p->regshift;
|
||||
writel(value, p->membase + offset);
|
||||
}
|
||||
|
||||
static void ce4100_serial_fixup(int port, struct uart_port *up,
|
||||
unsigned short *capabilites)
|
||||
{
|
||||
#ifdef CONFIG_EARLY_PRINTK
|
||||
/*
|
||||
* Over ride the legacy port configuration that comes from
|
||||
* asm/serial.h. Using the ioport driver then switching to the
|
||||
* PCI memmaped driver hangs the IOAPIC
|
||||
*/
|
||||
if (up->iotype != UPIO_MEM32) {
|
||||
up->uartclk = 14745600;
|
||||
up->mapbase = 0xdffe0200;
|
||||
set_fixmap_nocache(FIX_EARLYCON_MEM_BASE,
|
||||
up->mapbase & PAGE_MASK);
|
||||
up->membase =
|
||||
(void __iomem *)__fix_to_virt(FIX_EARLYCON_MEM_BASE);
|
||||
up->membase += up->mapbase & ~PAGE_MASK;
|
||||
up->mapbase += port * 0x100;
|
||||
up->membase += port * 0x100;
|
||||
up->iotype = UPIO_MEM32;
|
||||
up->regshift = 2;
|
||||
up->irq = 4;
|
||||
}
|
||||
#endif
|
||||
up->iobase = 0;
|
||||
up->serial_in = ce4100_mem_serial_in;
|
||||
up->serial_out = ce4100_mem_serial_out;
|
||||
|
||||
*capabilites |= (1 << 12);
|
||||
}
|
||||
|
||||
static __init void sdv_serial_fixup(void)
|
||||
{
|
||||
serial8250_set_isa_configurator(ce4100_serial_fixup);
|
||||
}
|
||||
|
||||
#else
|
||||
static inline void sdv_serial_fixup(void) {};
|
||||
#endif
|
||||
|
||||
static void __init sdv_arch_setup(void)
|
||||
{
|
||||
sdv_serial_fixup();
|
||||
}
|
||||
|
||||
#ifdef CONFIG_X86_IO_APIC
|
||||
static void __cpuinit sdv_pci_init(void)
|
||||
{
|
||||
x86_of_pci_init();
|
||||
/* We can't set this earlier, because we need to calibrate the timer */
|
||||
legacy_pic = &null_legacy_pic;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* CE4100 specific x86_init function overrides and early setup
|
||||
* calls.
|
||||
*/
|
||||
void __init x86_ce4100_early_setup(void)
|
||||
{
|
||||
x86_init.oem.arch_setup = sdv_arch_setup;
|
||||
x86_platform.i8042_detect = ce4100_i8042_detect;
|
||||
x86_init.resources.probe_roms = x86_init_noop;
|
||||
x86_init.mpparse.get_smp_config = x86_init_uint_noop;
|
||||
x86_init.mpparse.find_smp_config = x86_init_noop;
|
||||
x86_init.pci.init = ce4100_pci_init;
|
||||
|
||||
/*
|
||||
* By default, the reboot method is ACPI which is supported by the
|
||||
* CE4100 bootloader CEFDK using FADT.ResetReg Address and ResetValue
|
||||
* the bootloader will however issue a system power off instead of
|
||||
* reboot. By using BOOT_KBD we ensure proper system reboot as
|
||||
* expected.
|
||||
*/
|
||||
reboot_type = BOOT_KBD;
|
||||
|
||||
#ifdef CONFIG_X86_IO_APIC
|
||||
x86_init.pci.init_irq = sdv_pci_init;
|
||||
x86_init.mpparse.setup_ioapic_ids = setup_ioapic_ids_from_mpc_nocheck;
|
||||
#endif
|
||||
|
||||
pm_power_off = ce4100_power_off;
|
||||
}
|
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>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
2
arch/x86/platform/efi/Makefile
Normal file
2
arch/x86/platform/efi/Makefile
Normal file
|
@ -0,0 +1,2 @@
|
|||
obj-$(CONFIG_EFI) += efi.o efi_$(BITS).o efi_stub_$(BITS).o
|
||||
obj-$(CONFIG_ACPI_BGRT) += efi-bgrt.o
|
79
arch/x86/platform/efi/efi-bgrt.c
Normal file
79
arch/x86/platform/efi/efi-bgrt.c
Normal file
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright 2012 Intel Corporation
|
||||
* Author: Josh Triplett <josh@joshtriplett.org>
|
||||
*
|
||||
* Based on the bgrt driver:
|
||||
* Copyright 2012 Red Hat, Inc <mjg@redhat.com>
|
||||
* Author: Matthew Garrett
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/efi.h>
|
||||
#include <linux/efi-bgrt.h>
|
||||
|
||||
struct acpi_table_bgrt *bgrt_tab;
|
||||
void *__initdata bgrt_image;
|
||||
size_t __initdata bgrt_image_size;
|
||||
|
||||
struct bmp_header {
|
||||
u16 id;
|
||||
u32 size;
|
||||
} __packed;
|
||||
|
||||
void __init efi_bgrt_init(void)
|
||||
{
|
||||
acpi_status status;
|
||||
void __iomem *image;
|
||||
bool ioremapped = false;
|
||||
struct bmp_header bmp_header;
|
||||
|
||||
if (acpi_disabled)
|
||||
return;
|
||||
|
||||
status = acpi_get_table("BGRT", 0,
|
||||
(struct acpi_table_header **)&bgrt_tab);
|
||||
if (ACPI_FAILURE(status))
|
||||
return;
|
||||
|
||||
if (bgrt_tab->header.length < sizeof(*bgrt_tab))
|
||||
return;
|
||||
if (bgrt_tab->version != 1)
|
||||
return;
|
||||
if (bgrt_tab->image_type != 0 || !bgrt_tab->image_address)
|
||||
return;
|
||||
|
||||
image = efi_lookup_mapped_addr(bgrt_tab->image_address);
|
||||
if (!image) {
|
||||
image = ioremap(bgrt_tab->image_address, sizeof(bmp_header));
|
||||
ioremapped = true;
|
||||
if (!image)
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy_fromio(&bmp_header, image, sizeof(bmp_header));
|
||||
if (ioremapped)
|
||||
iounmap(image);
|
||||
bgrt_image_size = bmp_header.size;
|
||||
|
||||
bgrt_image = kmalloc(bgrt_image_size, GFP_KERNEL);
|
||||
if (!bgrt_image)
|
||||
return;
|
||||
|
||||
if (ioremapped) {
|
||||
image = ioremap(bgrt_tab->image_address, bmp_header.size);
|
||||
if (!image) {
|
||||
kfree(bgrt_image);
|
||||
bgrt_image = NULL;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
memcpy_fromio(bgrt_image, image, bgrt_image_size);
|
||||
if (ioremapped)
|
||||
iounmap(image);
|
||||
}
|
1001
arch/x86/platform/efi/efi.c
Normal file
1001
arch/x86/platform/efi/efi.c
Normal file
File diff suppressed because it is too large
Load diff
69
arch/x86/platform/efi/efi_32.c
Normal file
69
arch/x86/platform/efi/efi_32.c
Normal file
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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
|
||||
* prelog/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_call_phys_prelog(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 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);
|
||||
}
|
115
arch/x86/platform/efi/efi_64.c
Normal file
115
arch/x86/platform/efi/efi_64.c
Normal file
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* 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>
|
||||
|
||||
static pgd_t *save_pgd __initdata;
|
||||
static unsigned long efi_flags __initdata;
|
||||
|
||||
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_prelog(void)
|
||||
{
|
||||
unsigned long vaddress;
|
||||
int pgd;
|
||||
int n_pgds;
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
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 prelog 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 prelog 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
|
116
arch/x86/platform/efi/efi_stub_64.S
Normal file
116
arch/x86/platform/efi/efi_stub_64.S
Normal file
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* 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>
|
||||
|
||||
#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
|
||||
|
||||
ENTRY(efi_call0)
|
||||
SAVE_XMM
|
||||
subq $32, %rsp
|
||||
call *%rdi
|
||||
addq $32, %rsp
|
||||
RESTORE_XMM
|
||||
ret
|
||||
ENDPROC(efi_call0)
|
||||
|
||||
ENTRY(efi_call1)
|
||||
SAVE_XMM
|
||||
subq $32, %rsp
|
||||
mov %rsi, %rcx
|
||||
call *%rdi
|
||||
addq $32, %rsp
|
||||
RESTORE_XMM
|
||||
ret
|
||||
ENDPROC(efi_call1)
|
||||
|
||||
ENTRY(efi_call2)
|
||||
SAVE_XMM
|
||||
subq $32, %rsp
|
||||
mov %rsi, %rcx
|
||||
call *%rdi
|
||||
addq $32, %rsp
|
||||
RESTORE_XMM
|
||||
ret
|
||||
ENDPROC(efi_call2)
|
||||
|
||||
ENTRY(efi_call3)
|
||||
SAVE_XMM
|
||||
subq $32, %rsp
|
||||
mov %rcx, %r8
|
||||
mov %rsi, %rcx
|
||||
call *%rdi
|
||||
addq $32, %rsp
|
||||
RESTORE_XMM
|
||||
ret
|
||||
ENDPROC(efi_call3)
|
||||
|
||||
ENTRY(efi_call4)
|
||||
SAVE_XMM
|
||||
subq $32, %rsp
|
||||
mov %r8, %r9
|
||||
mov %rcx, %r8
|
||||
mov %rsi, %rcx
|
||||
call *%rdi
|
||||
addq $32, %rsp
|
||||
RESTORE_XMM
|
||||
ret
|
||||
ENDPROC(efi_call4)
|
||||
|
||||
ENTRY(efi_call5)
|
||||
SAVE_XMM
|
||||
subq $48, %rsp
|
||||
mov %r9, 32(%rsp)
|
||||
mov %r8, %r9
|
||||
mov %rcx, %r8
|
||||
mov %rsi, %rcx
|
||||
call *%rdi
|
||||
addq $48, %rsp
|
||||
RESTORE_XMM
|
||||
ret
|
||||
ENDPROC(efi_call5)
|
||||
|
||||
ENTRY(efi_call6)
|
||||
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
|
||||
call *%rdi
|
||||
addq $48, %rsp
|
||||
RESTORE_XMM
|
||||
ret
|
||||
ENDPROC(efi_call6)
|
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 __initdata platform_device *alix_devs[] = {
|
||||
&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 __initdata platform_device *geos_devs[] = {
|
||||
&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 __initdata platform_device *net5501_devs[] = {
|
||||
&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);
|
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
|
138
arch/x86/platform/iris/iris.c
Normal file
138
arch/x86/platform/iris/iris.c
Normal file
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* 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/init.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);
|
3
arch/x86/platform/mrst/Makefile
Normal file
3
arch/x86/platform/mrst/Makefile
Normal file
|
@ -0,0 +1,3 @@
|
|||
obj-$(CONFIG_X86_INTEL_MID) += mrst.o
|
||||
obj-$(CONFIG_X86_INTEL_MID) += vrtc.o
|
||||
obj-$(CONFIG_EARLY_PRINTK_INTEL_MID) += early_printk_mrst.o
|
324
arch/x86/platform/mrst/early_printk_mrst.c
Normal file
324
arch/x86/platform/mrst/early_printk_mrst.c
Normal file
|
@ -0,0 +1,324 @@
|
|||
/*
|
||||
* early_printk_mrst.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/init.h>
|
||||
#include <linux/io.h>
|
||||
|
||||
#include <asm/fixmap.h>
|
||||
#include <asm/pgtable.h>
|
||||
#include <asm/mrst.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 (mrst_identify_cpu() == MRST_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_warning("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,
|
||||
};
|
1052
arch/x86/platform/mrst/mrst.c
Normal file
1052
arch/x86/platform/mrst/mrst.c
Normal file
File diff suppressed because it is too large
Load diff
177
arch/x86/platform/mrst/vrtc.c
Normal file
177
arch/x86/platform/mrst/vrtc.c
Normal file
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
* 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/mrst.h>
|
||||
#include <asm/mrst-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);
|
||||
|
||||
unsigned long vrtc_get_time(void)
|
||||
{
|
||||
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;
|
||||
|
||||
printk(KERN_INFO "vRTC: sec: %d min: %d hour: %d day: %d "
|
||||
"mon: %d year: %d\n", sec, min, hour, mday, mon, year);
|
||||
|
||||
return mktime(year, mon, mday, hour, min, sec);
|
||||
}
|
||||
|
||||
int vrtc_set_mmss(unsigned long nowtime)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct rtc_time tm;
|
||||
int year;
|
||||
int retval = 0;
|
||||
|
||||
rtc_time_to_tm(nowtime, &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 {
|
||||
printk(KERN_ERR
|
||||
"%s: Invalid vRTC value: write of %lx to vRTC failed\n",
|
||||
__FUNCTION__, nowtime);
|
||||
retval = -EINVAL;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
void __init mrst_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 mrst_device_create(void)
|
||||
{
|
||||
/* No Moorestown, no device */
|
||||
if (!mrst_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(mrst_device_create);
|
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 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);
|
241
arch/x86/platform/olpc/olpc-xo15-sci.c
Normal file
241
arch/x86/platform/olpc/olpc-xo15-sci.c
Normal file
|
@ -0,0 +1,241 @@
|
|||
/*
|
||||
* 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 <acpi/acpi_bus.h>
|
||||
#include <acpi/acpi_drivers.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)
|
||||
{
|
||||
struct acpi_object_list arg_list;
|
||||
union acpi_object arg;
|
||||
acpi_status status;
|
||||
|
||||
arg_list.count = 1;
|
||||
arg_list.pointer = &arg;
|
||||
arg.type = ACPI_TYPE_INTEGER;
|
||||
arg.integer.value = wake_on_close;
|
||||
|
||||
status = acpi_evaluate_object(NULL, "\\_SB.PCI0.LID.LIDW", &arg_list, NULL);
|
||||
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
|
109
arch/x86/platform/sfi/sfi.c
Normal file
109
arch/x86/platform/sfi/sfi.c
Normal file
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* 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 <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 int __init sfi_parse_ioapic(struct sfi_table_header *table)
|
||||
{
|
||||
struct sfi_table_simple *sb;
|
||||
struct sfi_apic_table_entry *pentry;
|
||||
int i, num;
|
||||
|
||||
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);
|
||||
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
|
339
arch/x86/platform/ts5500/ts5500.c
Normal file
339
arch/x86/platform/ts5500/ts5500.c
Normal file
|
@ -0,0 +1,339 @@
|
|||
/*
|
||||
* Technologic Systems TS-5500 Single Board Computer support
|
||||
*
|
||||
* Copyright (C) 2013 Savoir-faire Linux Inc.
|
||||
* Vivien Didelot <vivien.didelot@savoirfairelinux.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation; either version 2 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
*
|
||||
* This driver registers the Technologic Systems TS-5500 Single Board Computer
|
||||
* (SBC) and its devices, and exposes information to userspace such as jumpers'
|
||||
* state or available options. For further information about sysfs entries, see
|
||||
* Documentation/ABI/testing/sysfs-platform-ts5500.
|
||||
*
|
||||
* This code actually supports the TS-5500 platform, but it may be extended to
|
||||
* support similar Technologic Systems x86-based platforms, such as the TS-5600.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_data/gpio-ts5500.h>
|
||||
#include <linux/platform_data/max197.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
/* Product code register */
|
||||
#define TS5500_PRODUCT_CODE_ADDR 0x74
|
||||
#define TS5500_PRODUCT_CODE 0x60 /* TS-5500 product code */
|
||||
|
||||
/* SRAM/RS-485/ADC options, and RS-485 RTS/Automatic RS-485 flags register */
|
||||
#define TS5500_SRAM_RS485_ADC_ADDR 0x75
|
||||
#define TS5500_SRAM BIT(0) /* SRAM option */
|
||||
#define TS5500_RS485 BIT(1) /* RS-485 option */
|
||||
#define TS5500_ADC BIT(2) /* A/D converter option */
|
||||
#define TS5500_RS485_RTS BIT(6) /* RTS for RS-485 */
|
||||
#define TS5500_RS485_AUTO BIT(7) /* Automatic RS-485 */
|
||||
|
||||
/* External Reset/Industrial Temperature Range options register */
|
||||
#define TS5500_ERESET_ITR_ADDR 0x76
|
||||
#define TS5500_ERESET BIT(0) /* External Reset option */
|
||||
#define TS5500_ITR BIT(1) /* Indust. Temp. Range option */
|
||||
|
||||
/* LED/Jumpers register */
|
||||
#define TS5500_LED_JP_ADDR 0x77
|
||||
#define TS5500_LED BIT(0) /* LED flag */
|
||||
#define TS5500_JP1 BIT(1) /* Automatic CMOS */
|
||||
#define TS5500_JP2 BIT(2) /* Enable Serial Console */
|
||||
#define TS5500_JP3 BIT(3) /* Write Enable Drive A */
|
||||
#define TS5500_JP4 BIT(4) /* Fast Console (115K baud) */
|
||||
#define TS5500_JP5 BIT(5) /* User Jumper */
|
||||
#define TS5500_JP6 BIT(6) /* Console on COM1 (req. JP2) */
|
||||
#define TS5500_JP7 BIT(7) /* Undocumented (Unused) */
|
||||
|
||||
/* A/D Converter registers */
|
||||
#define TS5500_ADC_CONV_BUSY_ADDR 0x195 /* Conversion state register */
|
||||
#define TS5500_ADC_CONV_BUSY BIT(0)
|
||||
#define TS5500_ADC_CONV_INIT_LSB_ADDR 0x196 /* Start conv. / LSB register */
|
||||
#define TS5500_ADC_CONV_MSB_ADDR 0x197 /* MSB register */
|
||||
#define TS5500_ADC_CONV_DELAY 12 /* usec */
|
||||
|
||||
/**
|
||||
* struct ts5500_sbc - TS-5500 board description
|
||||
* @id: Board product ID.
|
||||
* @sram: Flag for SRAM option.
|
||||
* @rs485: Flag for RS-485 option.
|
||||
* @adc: Flag for Analog/Digital converter option.
|
||||
* @ereset: Flag for External Reset option.
|
||||
* @itr: Flag for Industrial Temperature Range option.
|
||||
* @jumpers: Bitfield for jumpers' state.
|
||||
*/
|
||||
struct ts5500_sbc {
|
||||
int id;
|
||||
bool sram;
|
||||
bool rs485;
|
||||
bool adc;
|
||||
bool ereset;
|
||||
bool itr;
|
||||
u8 jumpers;
|
||||
};
|
||||
|
||||
/* Board signatures in BIOS shadow RAM */
|
||||
static const struct {
|
||||
const char * const string;
|
||||
const ssize_t offset;
|
||||
} ts5500_signatures[] __initdata = {
|
||||
{ "TS-5x00 AMD Elan", 0xb14 },
|
||||
};
|
||||
|
||||
static int __init ts5500_check_signature(void)
|
||||
{
|
||||
void __iomem *bios;
|
||||
int i, ret = -ENODEV;
|
||||
|
||||
bios = ioremap(0xf0000, 0x10000);
|
||||
if (!bios)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(ts5500_signatures); i++) {
|
||||
if (check_signature(bios + ts5500_signatures[i].offset,
|
||||
ts5500_signatures[i].string,
|
||||
strlen(ts5500_signatures[i].string))) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
iounmap(bios);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __init ts5500_detect_config(struct ts5500_sbc *sbc)
|
||||
{
|
||||
u8 tmp;
|
||||
int ret = 0;
|
||||
|
||||
if (!request_region(TS5500_PRODUCT_CODE_ADDR, 4, "ts5500"))
|
||||
return -EBUSY;
|
||||
|
||||
tmp = inb(TS5500_PRODUCT_CODE_ADDR);
|
||||
if (tmp != TS5500_PRODUCT_CODE) {
|
||||
pr_err("This platform is not a TS-5500 (found ID 0x%x)\n", tmp);
|
||||
ret = -ENODEV;
|
||||
goto cleanup;
|
||||
}
|
||||
sbc->id = tmp;
|
||||
|
||||
tmp = inb(TS5500_SRAM_RS485_ADC_ADDR);
|
||||
sbc->sram = tmp & TS5500_SRAM;
|
||||
sbc->rs485 = tmp & TS5500_RS485;
|
||||
sbc->adc = tmp & TS5500_ADC;
|
||||
|
||||
tmp = inb(TS5500_ERESET_ITR_ADDR);
|
||||
sbc->ereset = tmp & TS5500_ERESET;
|
||||
sbc->itr = tmp & TS5500_ITR;
|
||||
|
||||
tmp = inb(TS5500_LED_JP_ADDR);
|
||||
sbc->jumpers = tmp & ~TS5500_LED;
|
||||
|
||||
cleanup:
|
||||
release_region(TS5500_PRODUCT_CODE_ADDR, 4);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t ts5500_show_id(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct ts5500_sbc *sbc = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "0x%.2x\n", sbc->id);
|
||||
}
|
||||
|
||||
static ssize_t ts5500_show_jumpers(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct ts5500_sbc *sbc = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "0x%.2x\n", sbc->jumpers >> 1);
|
||||
}
|
||||
|
||||
#define TS5500_SHOW(field) \
|
||||
static ssize_t ts5500_show_##field(struct device *dev, \
|
||||
struct device_attribute *attr, \
|
||||
char *buf) \
|
||||
{ \
|
||||
struct ts5500_sbc *sbc = dev_get_drvdata(dev); \
|
||||
return sprintf(buf, "%d\n", sbc->field); \
|
||||
}
|
||||
|
||||
TS5500_SHOW(sram)
|
||||
TS5500_SHOW(rs485)
|
||||
TS5500_SHOW(adc)
|
||||
TS5500_SHOW(ereset)
|
||||
TS5500_SHOW(itr)
|
||||
|
||||
static DEVICE_ATTR(id, S_IRUGO, ts5500_show_id, NULL);
|
||||
static DEVICE_ATTR(jumpers, S_IRUGO, ts5500_show_jumpers, NULL);
|
||||
static DEVICE_ATTR(sram, S_IRUGO, ts5500_show_sram, NULL);
|
||||
static DEVICE_ATTR(rs485, S_IRUGO, ts5500_show_rs485, NULL);
|
||||
static DEVICE_ATTR(adc, S_IRUGO, ts5500_show_adc, NULL);
|
||||
static DEVICE_ATTR(ereset, S_IRUGO, ts5500_show_ereset, NULL);
|
||||
static DEVICE_ATTR(itr, S_IRUGO, ts5500_show_itr, NULL);
|
||||
|
||||
static struct attribute *ts5500_attributes[] = {
|
||||
&dev_attr_id.attr,
|
||||
&dev_attr_jumpers.attr,
|
||||
&dev_attr_sram.attr,
|
||||
&dev_attr_rs485.attr,
|
||||
&dev_attr_adc.attr,
|
||||
&dev_attr_ereset.attr,
|
||||
&dev_attr_itr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group ts5500_attr_group = {
|
||||
.attrs = ts5500_attributes,
|
||||
};
|
||||
|
||||
static struct resource ts5500_dio1_resource[] = {
|
||||
DEFINE_RES_IRQ_NAMED(7, "DIO1 interrupt"),
|
||||
};
|
||||
|
||||
static struct platform_device ts5500_dio1_pdev = {
|
||||
.name = "ts5500-dio1",
|
||||
.id = -1,
|
||||
.resource = ts5500_dio1_resource,
|
||||
.num_resources = 1,
|
||||
};
|
||||
|
||||
static struct resource ts5500_dio2_resource[] = {
|
||||
DEFINE_RES_IRQ_NAMED(6, "DIO2 interrupt"),
|
||||
};
|
||||
|
||||
static struct platform_device ts5500_dio2_pdev = {
|
||||
.name = "ts5500-dio2",
|
||||
.id = -1,
|
||||
.resource = ts5500_dio2_resource,
|
||||
.num_resources = 1,
|
||||
};
|
||||
|
||||
static void ts5500_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
outb(!!brightness, TS5500_LED_JP_ADDR);
|
||||
}
|
||||
|
||||
static enum led_brightness ts5500_led_get(struct led_classdev *led_cdev)
|
||||
{
|
||||
return (inb(TS5500_LED_JP_ADDR) & TS5500_LED) ? LED_FULL : LED_OFF;
|
||||
}
|
||||
|
||||
static struct led_classdev ts5500_led_cdev = {
|
||||
.name = "ts5500:green:",
|
||||
.brightness_set = ts5500_led_set,
|
||||
.brightness_get = ts5500_led_get,
|
||||
};
|
||||
|
||||
static int ts5500_adc_convert(u8 ctrl)
|
||||
{
|
||||
u8 lsb, msb;
|
||||
|
||||
/* Start conversion (ensure the 3 MSB are set to 0) */
|
||||
outb(ctrl & 0x1f, TS5500_ADC_CONV_INIT_LSB_ADDR);
|
||||
|
||||
/*
|
||||
* The platform has CPLD logic driving the A/D converter.
|
||||
* The conversion must complete within 11 microseconds,
|
||||
* otherwise we have to re-initiate a conversion.
|
||||
*/
|
||||
udelay(TS5500_ADC_CONV_DELAY);
|
||||
if (inb(TS5500_ADC_CONV_BUSY_ADDR) & TS5500_ADC_CONV_BUSY)
|
||||
return -EBUSY;
|
||||
|
||||
/* Read the raw data */
|
||||
lsb = inb(TS5500_ADC_CONV_INIT_LSB_ADDR);
|
||||
msb = inb(TS5500_ADC_CONV_MSB_ADDR);
|
||||
|
||||
return (msb << 8) | lsb;
|
||||
}
|
||||
|
||||
static struct max197_platform_data ts5500_adc_pdata = {
|
||||
.convert = ts5500_adc_convert,
|
||||
};
|
||||
|
||||
static struct platform_device ts5500_adc_pdev = {
|
||||
.name = "max197",
|
||||
.id = -1,
|
||||
.dev = {
|
||||
.platform_data = &ts5500_adc_pdata,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init ts5500_init(void)
|
||||
{
|
||||
struct platform_device *pdev;
|
||||
struct ts5500_sbc *sbc;
|
||||
int err;
|
||||
|
||||
/*
|
||||
* There is no DMI available or PCI bridge subvendor info,
|
||||
* only the BIOS provides a 16-bit identification call.
|
||||
* It is safer to find a signature in the BIOS shadow RAM.
|
||||
*/
|
||||
err = ts5500_check_signature();
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
pdev = platform_device_register_simple("ts5500", -1, NULL, 0);
|
||||
if (IS_ERR(pdev))
|
||||
return PTR_ERR(pdev);
|
||||
|
||||
sbc = devm_kzalloc(&pdev->dev, sizeof(struct ts5500_sbc), GFP_KERNEL);
|
||||
if (!sbc) {
|
||||
err = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
|
||||
err = ts5500_detect_config(sbc);
|
||||
if (err)
|
||||
goto error;
|
||||
|
||||
platform_set_drvdata(pdev, sbc);
|
||||
|
||||
err = sysfs_create_group(&pdev->dev.kobj, &ts5500_attr_group);
|
||||
if (err)
|
||||
goto error;
|
||||
|
||||
ts5500_dio1_pdev.dev.parent = &pdev->dev;
|
||||
if (platform_device_register(&ts5500_dio1_pdev))
|
||||
dev_warn(&pdev->dev, "DIO1 block registration failed\n");
|
||||
ts5500_dio2_pdev.dev.parent = &pdev->dev;
|
||||
if (platform_device_register(&ts5500_dio2_pdev))
|
||||
dev_warn(&pdev->dev, "DIO2 block registration failed\n");
|
||||
|
||||
if (led_classdev_register(&pdev->dev, &ts5500_led_cdev))
|
||||
dev_warn(&pdev->dev, "LED registration failed\n");
|
||||
|
||||
if (sbc->adc) {
|
||||
ts5500_adc_pdev.dev.parent = &pdev->dev;
|
||||
if (platform_device_register(&ts5500_adc_pdev))
|
||||
dev_warn(&pdev->dev, "ADC registration failed\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
platform_device_unregister(pdev);
|
||||
return err;
|
||||
}
|
||||
device_initcall(ts5500_init);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Savoir-faire Linux Inc. <kernel@savoirfairelinux.com>");
|
||||
MODULE_DESCRIPTION("Technologic Systems TS-5500 platform driver");
|
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
|
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_call6((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
|
2149
arch/x86/platform/uv/tlb_uv.c
Normal file
2149
arch/x86/platform/uv/tlb_uv.c
Normal file
File diff suppressed because it is too large
Load diff
290
arch/x86/platform/uv/uv_irq.c
Normal file
290
arch/x86/platform/uv/uv_irq.c
Normal file
|
@ -0,0 +1,290 @@
|
|||
/*
|
||||
* 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 irq, ret;
|
||||
|
||||
irq = create_irq_nr(NR_IRQS_LEGACY, uv_blade_to_memory_nid(mmr_blade));
|
||||
|
||||
if (irq <= 0)
|
||||
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
|
||||
destroy_irq(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);
|
||||
destroy_irq(irq);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(uv_teardown_irq);
|
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 = &__get_cpu_var(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);
|
1
arch/x86/platform/visws/Makefile
Normal file
1
arch/x86/platform/visws/Makefile
Normal file
|
@ -0,0 +1 @@
|
|||
obj-$(CONFIG_X86_VISWS) += visws_quirks.o
|
608
arch/x86/platform/visws/visws_quirks.c
Normal file
608
arch/x86/platform/visws/visws_quirks.c
Normal file
|
@ -0,0 +1,608 @@
|
|||
/*
|
||||
* SGI Visual Workstation support and quirks, unmaintained.
|
||||
*
|
||||
* Split out from setup.c by davej@suse.de
|
||||
*
|
||||
* Copyright (C) 1999 Bent Hagemark, Ingo Molnar
|
||||
*
|
||||
* SGI Visual Workstation interrupt controller
|
||||
*
|
||||
* The Cobalt system ASIC in the Visual Workstation contains a "Cobalt" APIC
|
||||
* which serves as the main interrupt controller in the system. Non-legacy
|
||||
* hardware in the system uses this controller directly. Legacy devices
|
||||
* are connected to the PIIX4 which in turn has its 8259(s) connected to
|
||||
* a of the Cobalt APIC entry.
|
||||
*
|
||||
* 09/02/2000 - Updated for 2.4 by jbarnes@sgi.com
|
||||
*
|
||||
* 25/11/2002 - Updated for 2.5 by Andrey Panin <pazke@orbita1.ru>
|
||||
*/
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/smp.h>
|
||||
|
||||
#include <asm/visws/cobalt.h>
|
||||
#include <asm/visws/piix4.h>
|
||||
#include <asm/io_apic.h>
|
||||
#include <asm/fixmap.h>
|
||||
#include <asm/reboot.h>
|
||||
#include <asm/setup.h>
|
||||
#include <asm/apic.h>
|
||||
#include <asm/e820.h>
|
||||
#include <asm/time.h>
|
||||
#include <asm/io.h>
|
||||
|
||||
#include <linux/kernel_stat.h>
|
||||
|
||||
#include <asm/i8259.h>
|
||||
#include <asm/irq_vectors.h>
|
||||
#include <asm/visws/lithium.h>
|
||||
|
||||
#include <linux/sched.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/pci_ids.h>
|
||||
|
||||
extern int no_broadcast;
|
||||
|
||||
char visws_board_type = -1;
|
||||
char visws_board_rev = -1;
|
||||
|
||||
static void __init visws_time_init(void)
|
||||
{
|
||||
printk(KERN_INFO "Starting Cobalt Timer system clock\n");
|
||||
|
||||
/* Set the countdown value */
|
||||
co_cpu_write(CO_CPU_TIMEVAL, CO_TIME_HZ/HZ);
|
||||
|
||||
/* Start the timer */
|
||||
co_cpu_write(CO_CPU_CTRL, co_cpu_read(CO_CPU_CTRL) | CO_CTRL_TIMERUN);
|
||||
|
||||
/* Enable (unmask) the timer interrupt */
|
||||
co_cpu_write(CO_CPU_CTRL, co_cpu_read(CO_CPU_CTRL) & ~CO_CTRL_TIMEMASK);
|
||||
|
||||
setup_default_timer_irq();
|
||||
}
|
||||
|
||||
/* Replaces the default init_ISA_irqs in the generic setup */
|
||||
static void __init visws_pre_intr_init(void);
|
||||
|
||||
/* Quirk for machine specific memory setup. */
|
||||
|
||||
#define MB (1024 * 1024)
|
||||
|
||||
unsigned long sgivwfb_mem_phys;
|
||||
unsigned long sgivwfb_mem_size;
|
||||
EXPORT_SYMBOL(sgivwfb_mem_phys);
|
||||
EXPORT_SYMBOL(sgivwfb_mem_size);
|
||||
|
||||
long long mem_size __initdata = 0;
|
||||
|
||||
static char * __init visws_memory_setup(void)
|
||||
{
|
||||
long long gfx_mem_size = 8 * MB;
|
||||
|
||||
mem_size = boot_params.alt_mem_k;
|
||||
|
||||
if (!mem_size) {
|
||||
printk(KERN_WARNING "Bootloader didn't set memory size, upgrade it !\n");
|
||||
mem_size = 128 * MB;
|
||||
}
|
||||
|
||||
/*
|
||||
* this hardcodes the graphics memory to 8 MB
|
||||
* it really should be sized dynamically (or at least
|
||||
* set as a boot param)
|
||||
*/
|
||||
if (!sgivwfb_mem_size) {
|
||||
printk(KERN_WARNING "Defaulting to 8 MB framebuffer size\n");
|
||||
sgivwfb_mem_size = 8 * MB;
|
||||
}
|
||||
|
||||
/*
|
||||
* Trim to nearest MB
|
||||
*/
|
||||
sgivwfb_mem_size &= ~((1 << 20) - 1);
|
||||
sgivwfb_mem_phys = mem_size - gfx_mem_size;
|
||||
|
||||
e820_add_region(0, LOWMEMSIZE(), E820_RAM);
|
||||
e820_add_region(HIGH_MEMORY, mem_size - sgivwfb_mem_size - HIGH_MEMORY, E820_RAM);
|
||||
e820_add_region(sgivwfb_mem_phys, sgivwfb_mem_size, E820_RESERVED);
|
||||
|
||||
return "PROM";
|
||||
}
|
||||
|
||||
static void visws_machine_emergency_restart(void)
|
||||
{
|
||||
/*
|
||||
* Visual Workstations restart after this
|
||||
* register is poked on the PIIX4
|
||||
*/
|
||||
outb(PIIX4_RESET_VAL, PIIX4_RESET_PORT);
|
||||
}
|
||||
|
||||
static void visws_machine_power_off(void)
|
||||
{
|
||||
unsigned short pm_status;
|
||||
/* extern unsigned int pci_bus0; */
|
||||
|
||||
while ((pm_status = inw(PMSTS_PORT)) & 0x100)
|
||||
outw(pm_status, PMSTS_PORT);
|
||||
|
||||
outw(PM_SUSPEND_ENABLE, PMCNTRL_PORT);
|
||||
|
||||
mdelay(10);
|
||||
|
||||
#define PCI_CONF1_ADDRESS(bus, devfn, reg) \
|
||||
(0x80000000 | (bus << 16) | (devfn << 8) | (reg & ~3))
|
||||
|
||||
/* outl(PCI_CONF1_ADDRESS(pci_bus0, SPECIAL_DEV, SPECIAL_REG), 0xCF8); */
|
||||
outl(PIIX_SPECIAL_STOP, 0xCFC);
|
||||
}
|
||||
|
||||
static void __init visws_get_smp_config(unsigned int early)
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
* The Visual Workstation is Intel MP compliant in the hardware
|
||||
* sense, but it doesn't have a BIOS(-configuration table).
|
||||
* No problem for Linux.
|
||||
*/
|
||||
|
||||
static void __init MP_processor_info(struct mpc_cpu *m)
|
||||
{
|
||||
int ver, logical_apicid;
|
||||
physid_mask_t apic_cpus;
|
||||
|
||||
if (!(m->cpuflag & CPU_ENABLED))
|
||||
return;
|
||||
|
||||
logical_apicid = m->apicid;
|
||||
printk(KERN_INFO "%sCPU #%d %u:%u APIC version %d\n",
|
||||
m->cpuflag & CPU_BOOTPROCESSOR ? "Bootup " : "",
|
||||
m->apicid, (m->cpufeature & CPU_FAMILY_MASK) >> 8,
|
||||
(m->cpufeature & CPU_MODEL_MASK) >> 4, m->apicver);
|
||||
|
||||
if (m->cpuflag & CPU_BOOTPROCESSOR)
|
||||
boot_cpu_physical_apicid = m->apicid;
|
||||
|
||||
ver = m->apicver;
|
||||
if ((ver >= 0x14 && m->apicid >= 0xff) || m->apicid >= 0xf) {
|
||||
printk(KERN_ERR "Processor #%d INVALID. (Max ID: %d).\n",
|
||||
m->apicid, MAX_LOCAL_APIC);
|
||||
return;
|
||||
}
|
||||
|
||||
apic->apicid_to_cpu_present(m->apicid, &apic_cpus);
|
||||
physids_or(phys_cpu_present_map, phys_cpu_present_map, apic_cpus);
|
||||
/*
|
||||
* Validate version
|
||||
*/
|
||||
if (ver == 0x0) {
|
||||
printk(KERN_ERR "BIOS bug, APIC version is 0 for CPU#%d! "
|
||||
"fixing up to 0x10. (tell your hw vendor)\n",
|
||||
m->apicid);
|
||||
ver = 0x10;
|
||||
}
|
||||
apic_version[m->apicid] = ver;
|
||||
}
|
||||
|
||||
static void __init visws_find_smp_config(void)
|
||||
{
|
||||
struct mpc_cpu *mp = phys_to_virt(CO_CPU_TAB_PHYS);
|
||||
unsigned short ncpus = readw(phys_to_virt(CO_CPU_NUM_PHYS));
|
||||
|
||||
if (ncpus > CO_CPU_MAX) {
|
||||
printk(KERN_WARNING "find_visws_smp: got cpu count of %d at %p\n",
|
||||
ncpus, mp);
|
||||
|
||||
ncpus = CO_CPU_MAX;
|
||||
}
|
||||
|
||||
if (ncpus > setup_max_cpus)
|
||||
ncpus = setup_max_cpus;
|
||||
|
||||
#ifdef CONFIG_X86_LOCAL_APIC
|
||||
smp_found_config = 1;
|
||||
#endif
|
||||
while (ncpus--)
|
||||
MP_processor_info(mp++);
|
||||
|
||||
mp_lapic_addr = APIC_DEFAULT_PHYS_BASE;
|
||||
}
|
||||
|
||||
static void visws_trap_init(void);
|
||||
|
||||
void __init visws_early_detect(void)
|
||||
{
|
||||
int raw;
|
||||
|
||||
visws_board_type = (char)(inb_p(PIIX_GPI_BD_REG) & PIIX_GPI_BD_REG)
|
||||
>> PIIX_GPI_BD_SHIFT;
|
||||
|
||||
if (visws_board_type < 0)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Override the default platform setup functions
|
||||
*/
|
||||
x86_init.resources.memory_setup = visws_memory_setup;
|
||||
x86_init.mpparse.get_smp_config = visws_get_smp_config;
|
||||
x86_init.mpparse.find_smp_config = visws_find_smp_config;
|
||||
x86_init.irqs.pre_vector_init = visws_pre_intr_init;
|
||||
x86_init.irqs.trap_init = visws_trap_init;
|
||||
x86_init.timers.timer_init = visws_time_init;
|
||||
x86_init.pci.init = pci_visws_init;
|
||||
x86_init.pci.init_irq = x86_init_noop;
|
||||
|
||||
/*
|
||||
* Install reboot quirks:
|
||||
*/
|
||||
pm_power_off = visws_machine_power_off;
|
||||
machine_ops.emergency_restart = visws_machine_emergency_restart;
|
||||
|
||||
/*
|
||||
* Do not use broadcast IPIs:
|
||||
*/
|
||||
no_broadcast = 0;
|
||||
|
||||
#ifdef CONFIG_X86_IO_APIC
|
||||
/*
|
||||
* Turn off IO-APIC detection and initialization:
|
||||
*/
|
||||
skip_ioapic_setup = 1;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Get Board rev.
|
||||
* First, we have to initialize the 307 part to allow us access
|
||||
* to the GPIO registers. Let's map them at 0x0fc0 which is right
|
||||
* after the PIIX4 PM section.
|
||||
*/
|
||||
outb_p(SIO_DEV_SEL, SIO_INDEX);
|
||||
outb_p(SIO_GP_DEV, SIO_DATA); /* Talk to GPIO regs. */
|
||||
|
||||
outb_p(SIO_DEV_MSB, SIO_INDEX);
|
||||
outb_p(SIO_GP_MSB, SIO_DATA); /* MSB of GPIO base address */
|
||||
|
||||
outb_p(SIO_DEV_LSB, SIO_INDEX);
|
||||
outb_p(SIO_GP_LSB, SIO_DATA); /* LSB of GPIO base address */
|
||||
|
||||
outb_p(SIO_DEV_ENB, SIO_INDEX);
|
||||
outb_p(1, SIO_DATA); /* Enable GPIO registers. */
|
||||
|
||||
/*
|
||||
* Now, we have to map the power management section to write
|
||||
* a bit which enables access to the GPIO registers.
|
||||
* What lunatic came up with this shit?
|
||||
*/
|
||||
outb_p(SIO_DEV_SEL, SIO_INDEX);
|
||||
outb_p(SIO_PM_DEV, SIO_DATA); /* Talk to GPIO regs. */
|
||||
|
||||
outb_p(SIO_DEV_MSB, SIO_INDEX);
|
||||
outb_p(SIO_PM_MSB, SIO_DATA); /* MSB of PM base address */
|
||||
|
||||
outb_p(SIO_DEV_LSB, SIO_INDEX);
|
||||
outb_p(SIO_PM_LSB, SIO_DATA); /* LSB of PM base address */
|
||||
|
||||
outb_p(SIO_DEV_ENB, SIO_INDEX);
|
||||
outb_p(1, SIO_DATA); /* Enable PM registers. */
|
||||
|
||||
/*
|
||||
* Now, write the PM register which enables the GPIO registers.
|
||||
*/
|
||||
outb_p(SIO_PM_FER2, SIO_PM_INDEX);
|
||||
outb_p(SIO_PM_GP_EN, SIO_PM_DATA);
|
||||
|
||||
/*
|
||||
* Now, initialize the GPIO registers.
|
||||
* We want them all to be inputs which is the
|
||||
* power on default, so let's leave them alone.
|
||||
* So, let's just read the board rev!
|
||||
*/
|
||||
raw = inb_p(SIO_GP_DATA1);
|
||||
raw &= 0x7f; /* 7 bits of valid board revision ID. */
|
||||
|
||||
if (visws_board_type == VISWS_320) {
|
||||
if (raw < 0x6) {
|
||||
visws_board_rev = 4;
|
||||
} else if (raw < 0xc) {
|
||||
visws_board_rev = 5;
|
||||
} else {
|
||||
visws_board_rev = 6;
|
||||
}
|
||||
} else if (visws_board_type == VISWS_540) {
|
||||
visws_board_rev = 2;
|
||||
} else {
|
||||
visws_board_rev = raw;
|
||||
}
|
||||
|
||||
printk(KERN_INFO "Silicon Graphics Visual Workstation %s (rev %d) detected\n",
|
||||
(visws_board_type == VISWS_320 ? "320" :
|
||||
(visws_board_type == VISWS_540 ? "540" :
|
||||
"unknown")), visws_board_rev);
|
||||
}
|
||||
|
||||
#define A01234 (LI_INTA_0 | LI_INTA_1 | LI_INTA_2 | LI_INTA_3 | LI_INTA_4)
|
||||
#define BCD (LI_INTB | LI_INTC | LI_INTD)
|
||||
#define ALLDEVS (A01234 | BCD)
|
||||
|
||||
static __init void lithium_init(void)
|
||||
{
|
||||
set_fixmap(FIX_LI_PCIA, LI_PCI_A_PHYS);
|
||||
set_fixmap(FIX_LI_PCIB, LI_PCI_B_PHYS);
|
||||
|
||||
if ((li_pcia_read16(PCI_VENDOR_ID) != PCI_VENDOR_ID_SGI) ||
|
||||
(li_pcia_read16(PCI_DEVICE_ID) != PCI_DEVICE_ID_SGI_LITHIUM)) {
|
||||
printk(KERN_EMERG "Lithium hostbridge %c not found\n", 'A');
|
||||
/* panic("This machine is not SGI Visual Workstation 320/540"); */
|
||||
}
|
||||
|
||||
if ((li_pcib_read16(PCI_VENDOR_ID) != PCI_VENDOR_ID_SGI) ||
|
||||
(li_pcib_read16(PCI_DEVICE_ID) != PCI_DEVICE_ID_SGI_LITHIUM)) {
|
||||
printk(KERN_EMERG "Lithium hostbridge %c not found\n", 'B');
|
||||
/* panic("This machine is not SGI Visual Workstation 320/540"); */
|
||||
}
|
||||
|
||||
li_pcia_write16(LI_PCI_INTEN, ALLDEVS);
|
||||
li_pcib_write16(LI_PCI_INTEN, ALLDEVS);
|
||||
}
|
||||
|
||||
static __init void cobalt_init(void)
|
||||
{
|
||||
/*
|
||||
* On normal SMP PC this is used only with SMP, but we have to
|
||||
* use it and set it up here to start the Cobalt clock
|
||||
*/
|
||||
set_fixmap(FIX_APIC_BASE, APIC_DEFAULT_PHYS_BASE);
|
||||
setup_local_APIC();
|
||||
printk(KERN_INFO "Local APIC Version %#x, ID %#x\n",
|
||||
(unsigned int)apic_read(APIC_LVR),
|
||||
(unsigned int)apic_read(APIC_ID));
|
||||
|
||||
set_fixmap(FIX_CO_CPU, CO_CPU_PHYS);
|
||||
set_fixmap(FIX_CO_APIC, CO_APIC_PHYS);
|
||||
printk(KERN_INFO "Cobalt Revision %#lx, APIC ID %#lx\n",
|
||||
co_cpu_read(CO_CPU_REV), co_apic_read(CO_APIC_ID));
|
||||
|
||||
/* Enable Cobalt APIC being careful to NOT change the ID! */
|
||||
co_apic_write(CO_APIC_ID, co_apic_read(CO_APIC_ID) | CO_APIC_ENABLE);
|
||||
|
||||
printk(KERN_INFO "Cobalt APIC enabled: ID reg %#lx\n",
|
||||
co_apic_read(CO_APIC_ID));
|
||||
}
|
||||
|
||||
static void __init visws_trap_init(void)
|
||||
{
|
||||
lithium_init();
|
||||
cobalt_init();
|
||||
}
|
||||
|
||||
/*
|
||||
* IRQ controller / APIC support:
|
||||
*/
|
||||
|
||||
static DEFINE_SPINLOCK(cobalt_lock);
|
||||
|
||||
/*
|
||||
* Set the given Cobalt APIC Redirection Table entry to point
|
||||
* to the given IDT vector/index.
|
||||
*/
|
||||
static inline void co_apic_set(int entry, int irq)
|
||||
{
|
||||
co_apic_write(CO_APIC_LO(entry), CO_APIC_LEVEL | (irq + FIRST_EXTERNAL_VECTOR));
|
||||
co_apic_write(CO_APIC_HI(entry), 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Cobalt (IO)-APIC functions to handle PCI devices.
|
||||
*/
|
||||
static inline int co_apic_ide0_hack(void)
|
||||
{
|
||||
extern char visws_board_type;
|
||||
extern char visws_board_rev;
|
||||
|
||||
if (visws_board_type == VISWS_320 && visws_board_rev == 5)
|
||||
return 5;
|
||||
return CO_APIC_IDE0;
|
||||
}
|
||||
|
||||
static int is_co_apic(unsigned int irq)
|
||||
{
|
||||
if (IS_CO_APIC(irq))
|
||||
return CO_APIC(irq);
|
||||
|
||||
switch (irq) {
|
||||
case 0: return CO_APIC_CPU;
|
||||
case CO_IRQ_IDE0: return co_apic_ide0_hack();
|
||||
case CO_IRQ_IDE1: return CO_APIC_IDE1;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This is the SGI Cobalt (IO-)APIC:
|
||||
*/
|
||||
static void enable_cobalt_irq(struct irq_data *data)
|
||||
{
|
||||
co_apic_set(is_co_apic(data->irq), data->irq);
|
||||
}
|
||||
|
||||
static void disable_cobalt_irq(struct irq_data *data)
|
||||
{
|
||||
int entry = is_co_apic(data->irq);
|
||||
|
||||
co_apic_write(CO_APIC_LO(entry), CO_APIC_MASK);
|
||||
co_apic_read(CO_APIC_LO(entry));
|
||||
}
|
||||
|
||||
static void ack_cobalt_irq(struct irq_data *data)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&cobalt_lock, flags);
|
||||
disable_cobalt_irq(data);
|
||||
apic_write(APIC_EOI, APIC_EOI_ACK);
|
||||
spin_unlock_irqrestore(&cobalt_lock, flags);
|
||||
}
|
||||
|
||||
static struct irq_chip cobalt_irq_type = {
|
||||
.name = "Cobalt-APIC",
|
||||
.irq_enable = enable_cobalt_irq,
|
||||
.irq_disable = disable_cobalt_irq,
|
||||
.irq_ack = ack_cobalt_irq,
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* This is the PIIX4-based 8259 that is wired up indirectly to Cobalt
|
||||
* -- not the manner expected by the code in i8259.c.
|
||||
*
|
||||
* there is a 'master' physical interrupt source that gets sent to
|
||||
* the CPU. But in the chipset there are various 'virtual' interrupts
|
||||
* waiting to be handled. We represent this to Linux through a 'master'
|
||||
* interrupt controller type, and through a special virtual interrupt-
|
||||
* controller. Device drivers only see the virtual interrupt sources.
|
||||
*/
|
||||
static unsigned int startup_piix4_master_irq(struct irq_data *data)
|
||||
{
|
||||
legacy_pic->init(0);
|
||||
enable_cobalt_irq(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct irq_chip piix4_master_irq_type = {
|
||||
.name = "PIIX4-master",
|
||||
.irq_startup = startup_piix4_master_irq,
|
||||
.irq_ack = ack_cobalt_irq,
|
||||
};
|
||||
|
||||
static void pii4_mask(struct irq_data *data) { }
|
||||
|
||||
static struct irq_chip piix4_virtual_irq_type = {
|
||||
.name = "PIIX4-virtual",
|
||||
.irq_mask = pii4_mask,
|
||||
};
|
||||
|
||||
/*
|
||||
* PIIX4-8259 master/virtual functions to handle interrupt requests
|
||||
* from legacy devices: floppy, parallel, serial, rtc.
|
||||
*
|
||||
* None of these get Cobalt APIC entries, neither do they have IDT
|
||||
* entries. These interrupts are purely virtual and distributed from
|
||||
* the 'master' interrupt source: CO_IRQ_8259.
|
||||
*
|
||||
* When the 8259 interrupts its handler figures out which of these
|
||||
* devices is interrupting and dispatches to its handler.
|
||||
*
|
||||
* CAREFUL: devices see the 'virtual' interrupt only. Thus disable/
|
||||
* enable_irq gets the right irq. This 'master' irq is never directly
|
||||
* manipulated by any driver.
|
||||
*/
|
||||
static irqreturn_t piix4_master_intr(int irq, void *dev_id)
|
||||
{
|
||||
unsigned long flags;
|
||||
int realirq;
|
||||
|
||||
raw_spin_lock_irqsave(&i8259A_lock, flags);
|
||||
|
||||
/* Find out what's interrupting in the PIIX4 master 8259 */
|
||||
outb(0x0c, 0x20); /* OCW3 Poll command */
|
||||
realirq = inb(0x20);
|
||||
|
||||
/*
|
||||
* Bit 7 == 0 means invalid/spurious
|
||||
*/
|
||||
if (unlikely(!(realirq & 0x80)))
|
||||
goto out_unlock;
|
||||
|
||||
realirq &= 7;
|
||||
|
||||
if (unlikely(realirq == 2)) {
|
||||
outb(0x0c, 0xa0);
|
||||
realirq = inb(0xa0);
|
||||
|
||||
if (unlikely(!(realirq & 0x80)))
|
||||
goto out_unlock;
|
||||
|
||||
realirq = (realirq & 7) + 8;
|
||||
}
|
||||
|
||||
/* mask and ack interrupt */
|
||||
cached_irq_mask |= 1 << realirq;
|
||||
if (unlikely(realirq > 7)) {
|
||||
inb(0xa1);
|
||||
outb(cached_slave_mask, 0xa1);
|
||||
outb(0x60 + (realirq & 7), 0xa0);
|
||||
outb(0x60 + 2, 0x20);
|
||||
} else {
|
||||
inb(0x21);
|
||||
outb(cached_master_mask, 0x21);
|
||||
outb(0x60 + realirq, 0x20);
|
||||
}
|
||||
|
||||
raw_spin_unlock_irqrestore(&i8259A_lock, flags);
|
||||
|
||||
/*
|
||||
* handle this 'virtual interrupt' as a Cobalt one now.
|
||||
*/
|
||||
generic_handle_irq(realirq);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
|
||||
out_unlock:
|
||||
raw_spin_unlock_irqrestore(&i8259A_lock, flags);
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
static struct irqaction master_action = {
|
||||
.handler = piix4_master_intr,
|
||||
.name = "PIIX4-8259",
|
||||
.flags = IRQF_NO_THREAD,
|
||||
};
|
||||
|
||||
static struct irqaction cascade_action = {
|
||||
.handler = no_action,
|
||||
.name = "cascade",
|
||||
.flags = IRQF_NO_THREAD,
|
||||
};
|
||||
|
||||
static inline void set_piix4_virtual_irq_type(void)
|
||||
{
|
||||
piix4_virtual_irq_type.irq_enable = i8259A_chip.irq_unmask;
|
||||
piix4_virtual_irq_type.irq_disable = i8259A_chip.irq_mask;
|
||||
piix4_virtual_irq_type.irq_unmask = i8259A_chip.irq_unmask;
|
||||
}
|
||||
|
||||
static void __init visws_pre_intr_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
set_piix4_virtual_irq_type();
|
||||
|
||||
for (i = 0; i < CO_IRQ_APIC0 + CO_APIC_LAST + 1; i++) {
|
||||
struct irq_chip *chip = NULL;
|
||||
|
||||
if (i == 0)
|
||||
chip = &cobalt_irq_type;
|
||||
else if (i == CO_IRQ_IDE0)
|
||||
chip = &cobalt_irq_type;
|
||||
else if (i == CO_IRQ_IDE1)
|
||||
chip = &cobalt_irq_type;
|
||||
else if (i == CO_IRQ_8259)
|
||||
chip = &piix4_master_irq_type;
|
||||
else if (i < CO_IRQ_APIC0)
|
||||
chip = &piix4_virtual_irq_type;
|
||||
else if (IS_CO_APIC(i))
|
||||
chip = &cobalt_irq_type;
|
||||
|
||||
if (chip)
|
||||
irq_set_chip(i, chip);
|
||||
}
|
||||
|
||||
setup_irq(CO_IRQ_8259, &master_action);
|
||||
setup_irq(2, &cascade_action);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue