Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

View file

@ -0,0 +1,12 @@
# Platform specific code goes here
obj-y += ce4100/
obj-y += efi/
obj-y += geode/
obj-y += goldfish/
obj-y += iris/
obj-y += intel-mid/
obj-y += olpc/
obj-y += scx200/
obj-y += sfi/
obj-y += ts5500/
obj-y += uv/

View file

@ -0,0 +1 @@
obj-$(CONFIG_X86_INTEL_CE) += ce4100.o

View file

@ -0,0 +1,168 @@
/*
* Intel CE4100 platform specific setup code
*
* (C) Copyright 2010 Intel Corporation
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/reboot.h>
#include <linux/serial_reg.h>
#include <linux/serial_8250.h>
#include <linux/reboot.h>
#include <asm/ce4100.h>
#include <asm/prom.h>
#include <asm/setup.h>
#include <asm/i8259.h>
#include <asm/io.h>
#include <asm/io_apic.h>
#include <asm/emergency-restart.h>
static int ce4100_i8042_detect(void)
{
return 0;
}
/*
* The CE4100 platform has an internal 8051 Microcontroller which is
* responsible for signaling to the external Power Management Unit the
* intention to reset, reboot or power off the system. This 8051 device has
* its command register mapped at I/O port 0xcf9 and the value 0x4 is used
* to power off the system.
*/
static void ce4100_power_off(void)
{
outb(0x4, 0xcf9);
}
#ifdef CONFIG_SERIAL_8250
static unsigned int mem_serial_in(struct uart_port *p, int offset)
{
offset = offset << p->regshift;
return readl(p->membase + offset);
}
/*
* The UART Tx interrupts are not set under some conditions and therefore serial
* transmission hangs. This is a silicon issue and has not been root caused. The
* workaround for this silicon issue checks UART_LSR_THRE bit and UART_LSR_TEMT
* bit of LSR register in interrupt handler to see whether at least one of these
* two bits is set, if so then process the transmit request. If this workaround
* is not applied, then the serial transmission may hang. This workaround is for
* errata number 9 in Errata - B step.
*/
static unsigned int ce4100_mem_serial_in(struct uart_port *p, int offset)
{
unsigned int ret, ier, lsr;
if (offset == UART_IIR) {
offset = offset << p->regshift;
ret = readl(p->membase + offset);
if (ret & UART_IIR_NO_INT) {
/* see if the TX interrupt should have really set */
ier = mem_serial_in(p, UART_IER);
/* see if the UART's XMIT interrupt is enabled */
if (ier & UART_IER_THRI) {
lsr = mem_serial_in(p, UART_LSR);
/* now check to see if the UART should be
generating an interrupt (but isn't) */
if (lsr & (UART_LSR_THRE | UART_LSR_TEMT))
ret &= ~UART_IIR_NO_INT;
}
}
} else
ret = mem_serial_in(p, offset);
return ret;
}
static void ce4100_mem_serial_out(struct uart_port *p, int offset, int value)
{
offset = offset << p->regshift;
writel(value, p->membase + offset);
}
static void ce4100_serial_fixup(int port, struct uart_port *up,
unsigned short *capabilites)
{
#ifdef CONFIG_EARLY_PRINTK
/*
* Over ride the legacy port configuration that comes from
* asm/serial.h. Using the ioport driver then switching to the
* PCI memmaped driver hangs the IOAPIC
*/
if (up->iotype != UPIO_MEM32) {
up->uartclk = 14745600;
up->mapbase = 0xdffe0200;
set_fixmap_nocache(FIX_EARLYCON_MEM_BASE,
up->mapbase & PAGE_MASK);
up->membase =
(void __iomem *)__fix_to_virt(FIX_EARLYCON_MEM_BASE);
up->membase += up->mapbase & ~PAGE_MASK;
up->mapbase += port * 0x100;
up->membase += port * 0x100;
up->iotype = UPIO_MEM32;
up->regshift = 2;
up->irq = 4;
}
#endif
up->iobase = 0;
up->serial_in = ce4100_mem_serial_in;
up->serial_out = ce4100_mem_serial_out;
*capabilites |= (1 << 12);
}
static __init void sdv_serial_fixup(void)
{
serial8250_set_isa_configurator(ce4100_serial_fixup);
}
#else
static inline void sdv_serial_fixup(void) {};
#endif
static void __init sdv_arch_setup(void)
{
sdv_serial_fixup();
}
static void sdv_pci_init(void)
{
x86_of_pci_init();
}
/*
* CE4100 specific x86_init function overrides and early setup
* calls.
*/
void __init x86_ce4100_early_setup(void)
{
x86_init.oem.arch_setup = sdv_arch_setup;
x86_platform.i8042_detect = ce4100_i8042_detect;
x86_init.resources.probe_roms = x86_init_noop;
x86_init.mpparse.get_smp_config = x86_init_uint_noop;
x86_init.mpparse.find_smp_config = x86_init_noop;
x86_init.mpparse.setup_ioapic_ids = setup_ioapic_ids_from_mpc_nocheck;
x86_init.pci.init = ce4100_pci_init;
x86_init.pci.init_irq = sdv_pci_init;
/*
* By default, the reboot method is ACPI which is supported by the
* CE4100 bootloader CEFDK using FADT.ResetReg Address and ResetValue
* the bootloader will however issue a system power off instead of
* reboot. By using BOOT_KBD we ensure proper system reboot as
* expected.
*/
reboot_type = BOOT_KBD;
pm_power_off = ce4100_power_off;
}

View 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>;
};
};
};
};
};

View file

@ -0,0 +1,4 @@
obj-$(CONFIG_EFI) += quirks.o efi.o efi_$(BITS).o efi_stub_$(BITS).o
obj-$(CONFIG_ACPI_BGRT) += efi-bgrt.o
obj-$(CONFIG_EARLY_PRINTK_EFI) += early_printk.o
obj-$(CONFIG_EFI_MIXED) += efi_thunk_$(BITS).o

View file

@ -0,0 +1,236 @@
/*
* Copyright (C) 2013 Intel Corporation; author Matt Fleming
*
* This file is part of the Linux kernel, and is made available under
* the terms of the GNU General Public License version 2.
*/
#include <linux/console.h>
#include <linux/efi.h>
#include <linux/font.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <asm/setup.h>
static const struct font_desc *font;
static u32 efi_x, efi_y;
static void *efi_fb;
static bool early_efi_keep;
/*
* efi earlyprintk need use early_ioremap to map the framebuffer.
* But early_ioremap is not usable for earlyprintk=efi,keep, ioremap should
* be used instead. ioremap will be available after paging_init() which is
* earlier than initcall callbacks. Thus adding this early initcall function
* early_efi_map_fb to map the whole efi framebuffer.
*/
static __init int early_efi_map_fb(void)
{
unsigned long base, size;
if (!early_efi_keep)
return 0;
base = boot_params.screen_info.lfb_base;
size = boot_params.screen_info.lfb_size;
efi_fb = ioremap(base, size);
return efi_fb ? 0 : -ENOMEM;
}
early_initcall(early_efi_map_fb);
/*
* early_efi_map maps efi framebuffer region [start, start + len -1]
* In case earlyprintk=efi,keep we have the whole framebuffer mapped already
* so just return the offset efi_fb + start.
*/
static __init_refok void *early_efi_map(unsigned long start, unsigned long len)
{
unsigned long base;
base = boot_params.screen_info.lfb_base;
if (efi_fb)
return (efi_fb + start);
else
return early_ioremap(base + start, len);
}
static __init_refok void early_efi_unmap(void *addr, unsigned long len)
{
if (!efi_fb)
early_iounmap(addr, len);
}
static void early_efi_clear_scanline(unsigned int y)
{
unsigned long *dst;
u16 len;
len = boot_params.screen_info.lfb_linelength;
dst = early_efi_map(y*len, len);
if (!dst)
return;
memset(dst, 0, len);
early_efi_unmap(dst, len);
}
static void early_efi_scroll_up(void)
{
unsigned long *dst, *src;
u16 len;
u32 i, height;
len = boot_params.screen_info.lfb_linelength;
height = boot_params.screen_info.lfb_height;
for (i = 0; i < height - font->height; i++) {
dst = early_efi_map(i*len, len);
if (!dst)
return;
src = early_efi_map((i + font->height) * len, len);
if (!src) {
early_efi_unmap(dst, len);
return;
}
memmove(dst, src, len);
early_efi_unmap(src, len);
early_efi_unmap(dst, len);
}
}
static void early_efi_write_char(u32 *dst, unsigned char c, unsigned int h)
{
const u32 color_black = 0x00000000;
const u32 color_white = 0x00ffffff;
const u8 *src;
u8 s8;
int m;
src = font->data + c * font->height;
s8 = *(src + h);
for (m = 0; m < 8; m++) {
if ((s8 >> (7 - m)) & 1)
*dst = color_white;
else
*dst = color_black;
dst++;
}
}
static void
early_efi_write(struct console *con, const char *str, unsigned int num)
{
struct screen_info *si;
unsigned int len;
const char *s;
void *dst;
si = &boot_params.screen_info;
len = si->lfb_linelength;
while (num) {
unsigned int linemax;
unsigned int h, count = 0;
for (s = str; *s && *s != '\n'; s++) {
if (count == num)
break;
count++;
}
linemax = (si->lfb_width - efi_x) / font->width;
if (count > linemax)
count = linemax;
for (h = 0; h < font->height; h++) {
unsigned int n, x;
dst = early_efi_map((efi_y + h) * len, len);
if (!dst)
return;
s = str;
n = count;
x = efi_x;
while (n-- > 0) {
early_efi_write_char(dst + x*4, *s, h);
x += font->width;
s++;
}
early_efi_unmap(dst, len);
}
num -= count;
efi_x += count * font->width;
str += count;
if (num > 0 && *s == '\n') {
efi_x = 0;
efi_y += font->height;
str++;
num--;
}
if (efi_x >= si->lfb_width) {
efi_x = 0;
efi_y += font->height;
}
if (efi_y + font->height > si->lfb_height) {
u32 i;
efi_y -= font->height;
early_efi_scroll_up();
for (i = 0; i < font->height; i++)
early_efi_clear_scanline(efi_y + i);
}
}
}
static __init int early_efi_setup(struct console *con, char *options)
{
struct screen_info *si;
u16 xres, yres;
u32 i;
si = &boot_params.screen_info;
xres = si->lfb_width;
yres = si->lfb_height;
/*
* early_efi_write_char() implicitly assumes a framebuffer with
* 32-bits per pixel.
*/
if (si->lfb_depth != 32)
return -ENODEV;
font = get_default_font(xres, yres, -1, -1);
if (!font)
return -ENODEV;
efi_y = rounddown(yres, font->height) - font->height;
for (i = 0; i < (yres - efi_y) / font->height; i++)
early_efi_scroll_up();
/* early_console_register will unset CON_BOOT in case ,keep */
if (!(con->flags & CON_BOOT))
early_efi_keep = true;
return 0;
}
struct console early_efi_console = {
.name = "earlyefi",
.write = early_efi_write,
.setup = early_efi_setup,
.flags = CON_PRINTBUFFER,
.index = -1,
};

View file

@ -0,0 +1,105 @@
/*
* Copyright 2012 Intel Corporation
* Author: Josh Triplett <josh@joshtriplett.org>
*
* Based on the bgrt driver:
* Copyright 2012 Red Hat, Inc <mjg@redhat.com>
* Author: Matthew Garrett
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/acpi.h>
#include <linux/efi.h>
#include <linux/efi-bgrt.h>
struct acpi_table_bgrt *bgrt_tab;
void *__initdata bgrt_image;
size_t __initdata bgrt_image_size;
struct bmp_header {
u16 id;
u32 size;
} __packed;
void __init efi_bgrt_init(void)
{
acpi_status status;
void __iomem *image;
bool ioremapped = false;
struct bmp_header bmp_header;
if (acpi_disabled)
return;
status = acpi_get_table("BGRT", 0,
(struct acpi_table_header **)&bgrt_tab);
if (ACPI_FAILURE(status))
return;
if (bgrt_tab->header.length < sizeof(*bgrt_tab)) {
pr_err("Ignoring BGRT: invalid length %u (expected %zu)\n",
bgrt_tab->header.length, sizeof(*bgrt_tab));
return;
}
if (bgrt_tab->version != 1) {
pr_err("Ignoring BGRT: invalid version %u (expected 1)\n",
bgrt_tab->version);
return;
}
if (bgrt_tab->status != 1) {
pr_err("Ignoring BGRT: invalid status %u (expected 1)\n",
bgrt_tab->status);
return;
}
if (bgrt_tab->image_type != 0) {
pr_err("Ignoring BGRT: invalid image type %u (expected 0)\n",
bgrt_tab->image_type);
return;
}
if (!bgrt_tab->image_address) {
pr_err("Ignoring BGRT: null image address\n");
return;
}
image = efi_lookup_mapped_addr(bgrt_tab->image_address);
if (!image) {
image = early_memremap(bgrt_tab->image_address,
sizeof(bmp_header));
ioremapped = true;
if (!image) {
pr_err("Ignoring BGRT: failed to map image header memory\n");
return;
}
}
memcpy_fromio(&bmp_header, image, sizeof(bmp_header));
if (ioremapped)
early_iounmap(image, sizeof(bmp_header));
bgrt_image_size = bmp_header.size;
bgrt_image = kmalloc(bgrt_image_size, GFP_KERNEL | __GFP_NOWARN);
if (!bgrt_image) {
pr_err("Ignoring BGRT: failed to allocate memory for image (wanted %zu bytes)\n",
bgrt_image_size);
return;
}
if (ioremapped) {
image = early_memremap(bgrt_tab->image_address,
bmp_header.size);
if (!image) {
pr_err("Ignoring BGRT: failed to map image memory\n");
kfree(bgrt_image);
bgrt_image = NULL;
return;
}
}
memcpy_fromio(bgrt_image, image, bgrt_image_size);
if (ioremapped)
early_iounmap(image, bmp_header.size);
}

945
arch/x86/platform/efi/efi.c Normal file
View file

@ -0,0 +1,945 @@
/*
* Common EFI (Extensible Firmware Interface) support functions
* Based on Extensible Firmware Interface Specification version 1.0
*
* Copyright (C) 1999 VA Linux Systems
* Copyright (C) 1999 Walt Drummond <drummond@valinux.com>
* Copyright (C) 1999-2002 Hewlett-Packard Co.
* David Mosberger-Tang <davidm@hpl.hp.com>
* Stephane Eranian <eranian@hpl.hp.com>
* Copyright (C) 2005-2008 Intel Co.
* Fenghua Yu <fenghua.yu@intel.com>
* Bibo Mao <bibo.mao@intel.com>
* Chandramouli Narayanan <mouli@linux.intel.com>
* Huang Ying <ying.huang@intel.com>
* Copyright (C) 2013 SuSE Labs
* Borislav Petkov <bp@suse.de> - runtime services VA mapping
*
* Copied from efi_32.c to eliminate the duplicated code between EFI
* 32/64 support code. --ying 2007-10-26
*
* All EFI Runtime Services are not implemented yet as EFI only
* supports physical mode addressing on SoftSDV. This is to be fixed
* in a future version. --drummond 1999-07-20
*
* Implemented EFI runtime services and virtual mode calls. --davidm
*
* Goutham Rao: <goutham.rao@intel.com>
* Skip non-WB memory and ignore empty memory ranges.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/efi.h>
#include <linux/efi-bgrt.h>
#include <linux/export.h>
#include <linux/bootmem.h>
#include <linux/slab.h>
#include <linux/memblock.h>
#include <linux/spinlock.h>
#include <linux/uaccess.h>
#include <linux/time.h>
#include <linux/io.h>
#include <linux/reboot.h>
#include <linux/bcd.h>
#include <asm/setup.h>
#include <asm/efi.h>
#include <asm/time.h>
#include <asm/cacheflush.h>
#include <asm/tlbflush.h>
#include <asm/x86_init.h>
#include <asm/rtc.h>
#include <asm/uv/uv.h>
#define EFI_DEBUG
struct efi_memory_map memmap;
static struct efi efi_phys __initdata;
static efi_system_table_t efi_systab __initdata;
static efi_config_table_type_t arch_tables[] __initdata = {
#ifdef CONFIG_X86_UV
{UV_SYSTEM_TABLE_GUID, "UVsystab", &efi.uv_systab},
#endif
{NULL_GUID, NULL, NULL},
};
u64 efi_setup; /* efi setup_data physical address */
static int add_efi_memmap __initdata;
static int __init setup_add_efi_memmap(char *arg)
{
add_efi_memmap = 1;
return 0;
}
early_param("add_efi_memmap", setup_add_efi_memmap);
static efi_status_t __init phys_efi_set_virtual_address_map(
unsigned long memory_map_size,
unsigned long descriptor_size,
u32 descriptor_version,
efi_memory_desc_t *virtual_map)
{
efi_status_t status;
efi_call_phys_prolog();
status = efi_call_phys(efi_phys.set_virtual_address_map,
memory_map_size, descriptor_size,
descriptor_version, virtual_map);
efi_call_phys_epilog();
return status;
}
void efi_get_time(struct timespec *now)
{
efi_status_t status;
efi_time_t eft;
efi_time_cap_t cap;
status = efi.get_time(&eft, &cap);
if (status != EFI_SUCCESS)
pr_err("Oops: efitime: can't read time!\n");
now->tv_sec = mktime(eft.year, eft.month, eft.day, eft.hour,
eft.minute, eft.second);
now->tv_nsec = 0;
}
/*
* Tell the kernel about the EFI memory map. This might include
* more than the max 128 entries that can fit in the e820 legacy
* (zeropage) memory map.
*/
static void __init do_add_efi_memmap(void)
{
void *p;
for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) {
efi_memory_desc_t *md = p;
unsigned long long start = md->phys_addr;
unsigned long long size = md->num_pages << EFI_PAGE_SHIFT;
int e820_type;
switch (md->type) {
case EFI_LOADER_CODE:
case EFI_LOADER_DATA:
case EFI_BOOT_SERVICES_CODE:
case EFI_BOOT_SERVICES_DATA:
case EFI_CONVENTIONAL_MEMORY:
if (md->attribute & EFI_MEMORY_WB)
e820_type = E820_RAM;
else
e820_type = E820_RESERVED;
break;
case EFI_ACPI_RECLAIM_MEMORY:
e820_type = E820_ACPI;
break;
case EFI_ACPI_MEMORY_NVS:
e820_type = E820_NVS;
break;
case EFI_UNUSABLE_MEMORY:
e820_type = E820_UNUSABLE;
break;
default:
/*
* EFI_RESERVED_TYPE EFI_RUNTIME_SERVICES_CODE
* EFI_RUNTIME_SERVICES_DATA EFI_MEMORY_MAPPED_IO
* EFI_MEMORY_MAPPED_IO_PORT_SPACE EFI_PAL_CODE
*/
e820_type = E820_RESERVED;
break;
}
e820_add_region(start, size, e820_type);
}
sanitize_e820_map(e820.map, ARRAY_SIZE(e820.map), &e820.nr_map);
}
int __init efi_memblock_x86_reserve_range(void)
{
struct efi_info *e = &boot_params.efi_info;
unsigned long pmap;
if (efi_enabled(EFI_PARAVIRT))
return 0;
#ifdef CONFIG_X86_32
/* Can't handle data above 4GB at this time */
if (e->efi_memmap_hi) {
pr_err("Memory map is above 4GB, disabling EFI.\n");
return -EINVAL;
}
pmap = e->efi_memmap;
#else
pmap = (e->efi_memmap | ((__u64)e->efi_memmap_hi << 32));
#endif
memmap.phys_map = (void *)pmap;
memmap.nr_map = e->efi_memmap_size /
e->efi_memdesc_size;
memmap.desc_size = e->efi_memdesc_size;
memmap.desc_version = e->efi_memdesc_version;
memblock_reserve(pmap, memmap.nr_map * memmap.desc_size);
efi.memmap = &memmap;
return 0;
}
static void __init print_efi_memmap(void)
{
#ifdef EFI_DEBUG
efi_memory_desc_t *md;
void *p;
int i;
for (p = memmap.map, i = 0;
p < memmap.map_end;
p += memmap.desc_size, i++) {
char buf[64];
md = p;
pr_info("mem%02u: %s range=[0x%016llx-0x%016llx) (%lluMB)\n",
i, efi_md_typeattr_format(buf, sizeof(buf), md),
md->phys_addr,
md->phys_addr + (md->num_pages << EFI_PAGE_SHIFT),
(md->num_pages >> (20 - EFI_PAGE_SHIFT)));
}
#endif /* EFI_DEBUG */
}
void __init efi_unmap_memmap(void)
{
clear_bit(EFI_MEMMAP, &efi.flags);
if (memmap.map) {
early_memunmap(memmap.map, memmap.nr_map * memmap.desc_size);
memmap.map = NULL;
}
}
static int __init efi_systab_init(void *phys)
{
if (efi_enabled(EFI_64BIT)) {
efi_system_table_64_t *systab64;
struct efi_setup_data *data = NULL;
u64 tmp = 0;
if (efi_setup) {
data = early_memremap(efi_setup, sizeof(*data));
if (!data)
return -ENOMEM;
}
systab64 = early_memremap((unsigned long)phys,
sizeof(*systab64));
if (systab64 == NULL) {
pr_err("Couldn't map the system table!\n");
if (data)
early_memunmap(data, sizeof(*data));
return -ENOMEM;
}
efi_systab.hdr = systab64->hdr;
efi_systab.fw_vendor = data ? (unsigned long)data->fw_vendor :
systab64->fw_vendor;
tmp |= data ? data->fw_vendor : systab64->fw_vendor;
efi_systab.fw_revision = systab64->fw_revision;
efi_systab.con_in_handle = systab64->con_in_handle;
tmp |= systab64->con_in_handle;
efi_systab.con_in = systab64->con_in;
tmp |= systab64->con_in;
efi_systab.con_out_handle = systab64->con_out_handle;
tmp |= systab64->con_out_handle;
efi_systab.con_out = systab64->con_out;
tmp |= systab64->con_out;
efi_systab.stderr_handle = systab64->stderr_handle;
tmp |= systab64->stderr_handle;
efi_systab.stderr = systab64->stderr;
tmp |= systab64->stderr;
efi_systab.runtime = data ?
(void *)(unsigned long)data->runtime :
(void *)(unsigned long)systab64->runtime;
tmp |= data ? data->runtime : systab64->runtime;
efi_systab.boottime = (void *)(unsigned long)systab64->boottime;
tmp |= systab64->boottime;
efi_systab.nr_tables = systab64->nr_tables;
efi_systab.tables = data ? (unsigned long)data->tables :
systab64->tables;
tmp |= data ? data->tables : systab64->tables;
early_memunmap(systab64, sizeof(*systab64));
if (data)
early_memunmap(data, sizeof(*data));
#ifdef CONFIG_X86_32
if (tmp >> 32) {
pr_err("EFI data located above 4GB, disabling EFI.\n");
return -EINVAL;
}
#endif
} else {
efi_system_table_32_t *systab32;
systab32 = early_memremap((unsigned long)phys,
sizeof(*systab32));
if (systab32 == NULL) {
pr_err("Couldn't map the system table!\n");
return -ENOMEM;
}
efi_systab.hdr = systab32->hdr;
efi_systab.fw_vendor = systab32->fw_vendor;
efi_systab.fw_revision = systab32->fw_revision;
efi_systab.con_in_handle = systab32->con_in_handle;
efi_systab.con_in = systab32->con_in;
efi_systab.con_out_handle = systab32->con_out_handle;
efi_systab.con_out = systab32->con_out;
efi_systab.stderr_handle = systab32->stderr_handle;
efi_systab.stderr = systab32->stderr;
efi_systab.runtime = (void *)(unsigned long)systab32->runtime;
efi_systab.boottime = (void *)(unsigned long)systab32->boottime;
efi_systab.nr_tables = systab32->nr_tables;
efi_systab.tables = systab32->tables;
early_memunmap(systab32, sizeof(*systab32));
}
efi.systab = &efi_systab;
/*
* Verify the EFI Table
*/
if (efi.systab->hdr.signature != EFI_SYSTEM_TABLE_SIGNATURE) {
pr_err("System table signature incorrect!\n");
return -EINVAL;
}
if ((efi.systab->hdr.revision >> 16) == 0)
pr_err("Warning: System table version %d.%02d, expected 1.00 or greater!\n",
efi.systab->hdr.revision >> 16,
efi.systab->hdr.revision & 0xffff);
set_bit(EFI_SYSTEM_TABLES, &efi.flags);
return 0;
}
static int __init efi_runtime_init32(void)
{
efi_runtime_services_32_t *runtime;
runtime = early_memremap((unsigned long)efi.systab->runtime,
sizeof(efi_runtime_services_32_t));
if (!runtime) {
pr_err("Could not map the runtime service table!\n");
return -ENOMEM;
}
/*
* We will only need *early* access to the SetVirtualAddressMap
* EFI runtime service. All other runtime services will be called
* via the virtual mapping.
*/
efi_phys.set_virtual_address_map =
(efi_set_virtual_address_map_t *)
(unsigned long)runtime->set_virtual_address_map;
early_memunmap(runtime, sizeof(efi_runtime_services_32_t));
return 0;
}
static int __init efi_runtime_init64(void)
{
efi_runtime_services_64_t *runtime;
runtime = early_memremap((unsigned long)efi.systab->runtime,
sizeof(efi_runtime_services_64_t));
if (!runtime) {
pr_err("Could not map the runtime service table!\n");
return -ENOMEM;
}
/*
* We will only need *early* access to the SetVirtualAddressMap
* EFI runtime service. All other runtime services will be called
* via the virtual mapping.
*/
efi_phys.set_virtual_address_map =
(efi_set_virtual_address_map_t *)
(unsigned long)runtime->set_virtual_address_map;
early_memunmap(runtime, sizeof(efi_runtime_services_64_t));
return 0;
}
static int __init efi_runtime_init(void)
{
int rv;
/*
* Check out the runtime services table. We need to map
* the runtime services table so that we can grab the physical
* address of several of the EFI runtime functions, needed to
* set the firmware into virtual mode.
*
* When EFI_PARAVIRT is in force then we could not map runtime
* service memory region because we do not have direct access to it.
* However, runtime services are available through proxy functions
* (e.g. in case of Xen dom0 EFI implementation they call special
* hypercall which executes relevant EFI functions) and that is why
* they are always enabled.
*/
if (!efi_enabled(EFI_PARAVIRT)) {
if (efi_enabled(EFI_64BIT))
rv = efi_runtime_init64();
else
rv = efi_runtime_init32();
if (rv)
return rv;
}
set_bit(EFI_RUNTIME_SERVICES, &efi.flags);
return 0;
}
static int __init efi_memmap_init(void)
{
if (efi_enabled(EFI_PARAVIRT))
return 0;
/* Map the EFI memory map */
memmap.map = early_memremap((unsigned long)memmap.phys_map,
memmap.nr_map * memmap.desc_size);
if (memmap.map == NULL) {
pr_err("Could not map the memory map!\n");
return -ENOMEM;
}
memmap.map_end = memmap.map + (memmap.nr_map * memmap.desc_size);
if (add_efi_memmap)
do_add_efi_memmap();
set_bit(EFI_MEMMAP, &efi.flags);
return 0;
}
void __init efi_init(void)
{
efi_char16_t *c16;
char vendor[100] = "unknown";
int i = 0;
void *tmp;
#ifdef CONFIG_X86_32
if (boot_params.efi_info.efi_systab_hi ||
boot_params.efi_info.efi_memmap_hi) {
pr_info("Table located above 4GB, disabling EFI.\n");
return;
}
efi_phys.systab = (efi_system_table_t *)boot_params.efi_info.efi_systab;
#else
efi_phys.systab = (efi_system_table_t *)
(boot_params.efi_info.efi_systab |
((__u64)boot_params.efi_info.efi_systab_hi<<32));
#endif
if (efi_systab_init(efi_phys.systab))
return;
efi.config_table = (unsigned long)efi.systab->tables;
efi.fw_vendor = (unsigned long)efi.systab->fw_vendor;
efi.runtime = (unsigned long)efi.systab->runtime;
/*
* Show what we know for posterity
*/
c16 = tmp = early_memremap(efi.systab->fw_vendor, 2);
if (c16) {
for (i = 0; i < sizeof(vendor) - 1 && *c16; ++i)
vendor[i] = *c16++;
vendor[i] = '\0';
} else
pr_err("Could not map the firmware vendor!\n");
early_memunmap(tmp, 2);
pr_info("EFI v%u.%.02u by %s\n",
efi.systab->hdr.revision >> 16,
efi.systab->hdr.revision & 0xffff, vendor);
if (efi_reuse_config(efi.systab->tables, efi.systab->nr_tables))
return;
if (efi_config_init(arch_tables))
return;
/*
* Note: We currently don't support runtime services on an EFI
* that doesn't match the kernel 32/64-bit mode.
*/
if (!efi_runtime_supported())
pr_info("No EFI runtime due to 32/64-bit mismatch with kernel\n");
else {
if (efi_runtime_disabled() || efi_runtime_init())
return;
}
if (efi_memmap_init())
return;
print_efi_memmap();
}
void __init efi_late_init(void)
{
efi_bgrt_init();
}
void __init efi_set_executable(efi_memory_desc_t *md, bool executable)
{
u64 addr, npages;
addr = md->virt_addr;
npages = md->num_pages;
memrange_efi_to_native(&addr, &npages);
if (executable)
set_memory_x(addr, npages);
else
set_memory_nx(addr, npages);
}
void __init runtime_code_page_mkexec(void)
{
efi_memory_desc_t *md;
void *p;
/* Make EFI runtime service code area executable */
for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) {
md = p;
if (md->type != EFI_RUNTIME_SERVICES_CODE)
continue;
efi_set_executable(md, true);
}
}
void __init efi_memory_uc(u64 addr, unsigned long size)
{
unsigned long page_shift = 1UL << EFI_PAGE_SHIFT;
u64 npages;
npages = round_up(size, page_shift) / page_shift;
memrange_efi_to_native(&addr, &npages);
set_memory_uc(addr, npages);
}
void __init old_map_region(efi_memory_desc_t *md)
{
u64 start_pfn, end_pfn, end;
unsigned long size;
void *va;
start_pfn = PFN_DOWN(md->phys_addr);
size = md->num_pages << PAGE_SHIFT;
end = md->phys_addr + size;
end_pfn = PFN_UP(end);
if (pfn_range_is_mapped(start_pfn, end_pfn)) {
va = __va(md->phys_addr);
if (!(md->attribute & EFI_MEMORY_WB))
efi_memory_uc((u64)(unsigned long)va, size);
} else
va = efi_ioremap(md->phys_addr, size,
md->type, md->attribute);
md->virt_addr = (u64) (unsigned long) va;
if (!va)
pr_err("ioremap of 0x%llX failed!\n",
(unsigned long long)md->phys_addr);
}
/* Merge contiguous regions of the same type and attribute */
static void __init efi_merge_regions(void)
{
void *p;
efi_memory_desc_t *md, *prev_md = NULL;
for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) {
u64 prev_size;
md = p;
if (!prev_md) {
prev_md = md;
continue;
}
if (prev_md->type != md->type ||
prev_md->attribute != md->attribute) {
prev_md = md;
continue;
}
prev_size = prev_md->num_pages << EFI_PAGE_SHIFT;
if (md->phys_addr == (prev_md->phys_addr + prev_size)) {
prev_md->num_pages += md->num_pages;
md->type = EFI_RESERVED_TYPE;
md->attribute = 0;
continue;
}
prev_md = md;
}
}
static void __init get_systab_virt_addr(efi_memory_desc_t *md)
{
unsigned long size;
u64 end, systab;
size = md->num_pages << EFI_PAGE_SHIFT;
end = md->phys_addr + size;
systab = (u64)(unsigned long)efi_phys.systab;
if (md->phys_addr <= systab && systab < end) {
systab += md->virt_addr - md->phys_addr;
efi.systab = (efi_system_table_t *)(unsigned long)systab;
}
}
static void __init save_runtime_map(void)
{
#ifdef CONFIG_KEXEC
efi_memory_desc_t *md;
void *tmp, *p, *q = NULL;
int count = 0;
if (efi_enabled(EFI_OLD_MEMMAP))
return;
for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) {
md = p;
if (!(md->attribute & EFI_MEMORY_RUNTIME) ||
(md->type == EFI_BOOT_SERVICES_CODE) ||
(md->type == EFI_BOOT_SERVICES_DATA))
continue;
tmp = krealloc(q, (count + 1) * memmap.desc_size, GFP_KERNEL);
if (!tmp)
goto out;
q = tmp;
memcpy(q + count * memmap.desc_size, md, memmap.desc_size);
count++;
}
efi_runtime_map_setup(q, count, memmap.desc_size);
return;
out:
kfree(q);
pr_err("Error saving runtime map, efi runtime on kexec non-functional!!\n");
#endif
}
static void *realloc_pages(void *old_memmap, int old_shift)
{
void *ret;
ret = (void *)__get_free_pages(GFP_KERNEL, old_shift + 1);
if (!ret)
goto out;
/*
* A first-time allocation doesn't have anything to copy.
*/
if (!old_memmap)
return ret;
memcpy(ret, old_memmap, PAGE_SIZE << old_shift);
out:
free_pages((unsigned long)old_memmap, old_shift);
return ret;
}
/*
* Map the efi memory ranges of the runtime services and update new_mmap with
* virtual addresses.
*/
static void * __init efi_map_regions(int *count, int *pg_shift)
{
void *p, *new_memmap = NULL;
unsigned long left = 0;
efi_memory_desc_t *md;
for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) {
md = p;
if (!(md->attribute & EFI_MEMORY_RUNTIME)) {
#ifdef CONFIG_X86_64
if (md->type != EFI_BOOT_SERVICES_CODE &&
md->type != EFI_BOOT_SERVICES_DATA)
#endif
continue;
}
efi_map_region(md);
get_systab_virt_addr(md);
if (left < memmap.desc_size) {
new_memmap = realloc_pages(new_memmap, *pg_shift);
if (!new_memmap)
return NULL;
left += PAGE_SIZE << *pg_shift;
(*pg_shift)++;
}
memcpy(new_memmap + (*count * memmap.desc_size), md,
memmap.desc_size);
left -= memmap.desc_size;
(*count)++;
}
return new_memmap;
}
static void __init kexec_enter_virtual_mode(void)
{
#ifdef CONFIG_KEXEC
efi_memory_desc_t *md;
void *p;
efi.systab = NULL;
/*
* We don't do virtual mode, since we don't do runtime services, on
* non-native EFI
*/
if (!efi_is_native()) {
efi_unmap_memmap();
clear_bit(EFI_RUNTIME_SERVICES, &efi.flags);
return;
}
/*
* Map efi regions which were passed via setup_data. The virt_addr is a
* fixed addr which was used in first kernel of a kexec boot.
*/
for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) {
md = p;
efi_map_region_fixed(md); /* FIXME: add error handling */
get_systab_virt_addr(md);
}
save_runtime_map();
BUG_ON(!efi.systab);
efi_sync_low_kernel_mappings();
/*
* Now that EFI is in virtual mode, update the function
* pointers in the runtime service table to the new virtual addresses.
*
* Call EFI services through wrapper functions.
*/
efi.runtime_version = efi_systab.hdr.revision;
efi_native_runtime_setup();
efi.set_virtual_address_map = NULL;
if (efi_enabled(EFI_OLD_MEMMAP) && (__supported_pte_mask & _PAGE_NX))
runtime_code_page_mkexec();
/* clean DUMMY object */
efi_delete_dummy_variable();
#endif
}
/*
* This function will switch the EFI runtime services to virtual mode.
* Essentially, we look through the EFI memmap and map every region that
* has the runtime attribute bit set in its memory descriptor into the
* ->trampoline_pgd page table using a top-down VA allocation scheme.
*
* The old method which used to update that memory descriptor with the
* virtual address obtained from ioremap() is still supported when the
* kernel is booted with efi=old_map on its command line. Same old
* method enabled the runtime services to be called without having to
* thunk back into physical mode for every invocation.
*
* The new method does a pagetable switch in a preemption-safe manner
* so that we're in a different address space when calling a runtime
* function. For function arguments passing we do copy the PGDs of the
* kernel page table into ->trampoline_pgd prior to each call.
*
* Specially for kexec boot, efi runtime maps in previous kernel should
* be passed in via setup_data. In that case runtime ranges will be mapped
* to the same virtual addresses as the first kernel, see
* kexec_enter_virtual_mode().
*/
static void __init __efi_enter_virtual_mode(void)
{
int count = 0, pg_shift = 0;
void *new_memmap = NULL;
efi_status_t status;
efi.systab = NULL;
efi_merge_regions();
new_memmap = efi_map_regions(&count, &pg_shift);
if (!new_memmap) {
pr_err("Error reallocating memory, EFI runtime non-functional!\n");
clear_bit(EFI_RUNTIME_SERVICES, &efi.flags);
return;
}
save_runtime_map();
BUG_ON(!efi.systab);
if (efi_setup_page_tables(__pa(new_memmap), 1 << pg_shift)) {
clear_bit(EFI_RUNTIME_SERVICES, &efi.flags);
return;
}
efi_sync_low_kernel_mappings();
efi_dump_pagetable();
if (efi_is_native()) {
status = phys_efi_set_virtual_address_map(
memmap.desc_size * count,
memmap.desc_size,
memmap.desc_version,
(efi_memory_desc_t *)__pa(new_memmap));
} else {
status = efi_thunk_set_virtual_address_map(
efi_phys.set_virtual_address_map,
memmap.desc_size * count,
memmap.desc_size,
memmap.desc_version,
(efi_memory_desc_t *)__pa(new_memmap));
}
if (status != EFI_SUCCESS) {
pr_alert("Unable to switch EFI into virtual mode (status=%lx)!\n",
status);
panic("EFI call to SetVirtualAddressMap() failed!");
}
/*
* Now that EFI is in virtual mode, update the function
* pointers in the runtime service table to the new virtual addresses.
*
* Call EFI services through wrapper functions.
*/
efi.runtime_version = efi_systab.hdr.revision;
if (efi_is_native())
efi_native_runtime_setup();
else
efi_thunk_runtime_setup();
efi.set_virtual_address_map = NULL;
efi_runtime_mkexec();
/*
* We mapped the descriptor array into the EFI pagetable above but we're
* not unmapping it here. Here's why:
*
* We're copying select PGDs from the kernel page table to the EFI page
* table and when we do so and make changes to those PGDs like unmapping
* stuff from them, those changes appear in the kernel page table and we
* go boom.
*
* From setup_real_mode():
*
* ...
* trampoline_pgd[0] = init_level4_pgt[pgd_index(__PAGE_OFFSET)].pgd;
*
* In this particular case, our allocation is in PGD 0 of the EFI page
* table but we've copied that PGD from PGD[272] of the EFI page table:
*
* pgd_index(__PAGE_OFFSET = 0xffff880000000000) = 272
*
* where the direct memory mapping in kernel space is.
*
* new_memmap's VA comes from that direct mapping and thus clearing it,
* it would get cleared in the kernel page table too.
*
* efi_cleanup_page_tables(__pa(new_memmap), 1 << pg_shift);
*/
free_pages((unsigned long)new_memmap, pg_shift);
/* clean DUMMY object */
efi_delete_dummy_variable();
}
void __init efi_enter_virtual_mode(void)
{
if (efi_enabled(EFI_PARAVIRT))
return;
if (efi_setup)
kexec_enter_virtual_mode();
else
__efi_enter_virtual_mode();
}
/*
* Convenience functions to obtain memory types and attributes
*/
u32 efi_mem_type(unsigned long phys_addr)
{
efi_memory_desc_t *md;
void *p;
if (!efi_enabled(EFI_MEMMAP))
return 0;
for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) {
md = p;
if ((md->phys_addr <= phys_addr) &&
(phys_addr < (md->phys_addr +
(md->num_pages << EFI_PAGE_SHIFT))))
return md->type;
}
return 0;
}
u64 efi_mem_attributes(unsigned long phys_addr)
{
efi_memory_desc_t *md;
void *p;
if (!efi_enabled(EFI_MEMMAP))
return 0;
for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) {
md = p;
if ((md->phys_addr <= phys_addr) &&
(phys_addr < (md->phys_addr +
(md->num_pages << EFI_PAGE_SHIFT))))
return md->attribute;
}
return 0;
}
static int __init arch_parse_efi_cmdline(char *str)
{
if (parse_option_str(str, "old_map"))
set_bit(EFI_OLD_MEMMAP, &efi.flags);
return 0;
}
early_param("efi", arch_parse_efi_cmdline);

View file

@ -0,0 +1,92 @@
/*
* Extensible Firmware Interface
*
* Based on Extensible Firmware Interface Specification version 1.0
*
* Copyright (C) 1999 VA Linux Systems
* Copyright (C) 1999 Walt Drummond <drummond@valinux.com>
* Copyright (C) 1999-2002 Hewlett-Packard Co.
* David Mosberger-Tang <davidm@hpl.hp.com>
* Stephane Eranian <eranian@hpl.hp.com>
*
* All EFI Runtime Services are not implemented yet as EFI only
* supports physical mode addressing on SoftSDV. This is to be fixed
* in a future version. --drummond 1999-07-20
*
* Implemented EFI runtime services and virtual mode calls. --davidm
*
* Goutham Rao: <goutham.rao@intel.com>
* Skip non-WB memory and ignore empty memory ranges.
*/
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/ioport.h>
#include <linux/efi.h>
#include <asm/io.h>
#include <asm/desc.h>
#include <asm/page.h>
#include <asm/pgtable.h>
#include <asm/tlbflush.h>
#include <asm/efi.h>
/*
* To make EFI call EFI runtime service in physical addressing mode we need
* prolog/epilog before/after the invocation to disable interrupt, to
* claim EFI runtime service handler exclusively and to duplicate a memory in
* low memory space say 0 - 3G.
*/
static unsigned long efi_rt_eflags;
void efi_sync_low_kernel_mappings(void) {}
void __init efi_dump_pagetable(void) {}
int __init efi_setup_page_tables(unsigned long pa_memmap, unsigned num_pages)
{
return 0;
}
void __init efi_cleanup_page_tables(unsigned long pa_memmap, unsigned num_pages)
{
}
void __init efi_map_region(efi_memory_desc_t *md)
{
old_map_region(md);
}
void __init efi_map_region_fixed(efi_memory_desc_t *md) {}
void __init parse_efi_setup(u64 phys_addr, u32 data_len) {}
void __init efi_call_phys_prolog(void)
{
struct desc_ptr gdt_descr;
local_irq_save(efi_rt_eflags);
load_cr3(initial_page_table);
__flush_tlb_all();
gdt_descr.address = __pa(get_cpu_gdt_table(0));
gdt_descr.size = GDT_SIZE - 1;
load_gdt(&gdt_descr);
}
void __init efi_call_phys_epilog(void)
{
struct desc_ptr gdt_descr;
gdt_descr.address = (unsigned long)get_cpu_gdt_table(0);
gdt_descr.size = GDT_SIZE - 1;
load_gdt(&gdt_descr);
load_cr3(swapper_pg_dir);
__flush_tlb_all();
local_irq_restore(efi_rt_eflags);
}
void __init efi_runtime_mkexec(void)
{
if (__supported_pte_mask & _PAGE_NX)
runtime_code_page_mkexec();
}

View file

@ -0,0 +1,604 @@
/*
* x86_64 specific EFI support functions
* Based on Extensible Firmware Interface Specification version 1.0
*
* Copyright (C) 2005-2008 Intel Co.
* Fenghua Yu <fenghua.yu@intel.com>
* Bibo Mao <bibo.mao@intel.com>
* Chandramouli Narayanan <mouli@linux.intel.com>
* Huang Ying <ying.huang@intel.com>
*
* Code to convert EFI to E820 map has been implemented in elilo bootloader
* based on a EFI patch by Edgar Hucek. Based on the E820 map, the page table
* is setup appropriately for EFI runtime code.
* - mouli 06/14/2007.
*
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/types.h>
#include <linux/spinlock.h>
#include <linux/bootmem.h>
#include <linux/ioport.h>
#include <linux/module.h>
#include <linux/efi.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/reboot.h>
#include <linux/slab.h>
#include <asm/setup.h>
#include <asm/page.h>
#include <asm/e820.h>
#include <asm/pgtable.h>
#include <asm/tlbflush.h>
#include <asm/proto.h>
#include <asm/efi.h>
#include <asm/cacheflush.h>
#include <asm/fixmap.h>
#include <asm/realmode.h>
#include <asm/time.h>
static pgd_t *save_pgd __initdata;
static unsigned long efi_flags __initdata;
/*
* We allocate runtime services regions bottom-up, starting from -4G, i.e.
* 0xffff_ffff_0000_0000 and limit EFI VA mapping space to 64G.
*/
static u64 efi_va = -4 * (1UL << 30);
#define EFI_VA_END (-68 * (1UL << 30))
/*
* Scratch space used for switching the pagetable in the EFI stub
*/
struct efi_scratch {
u64 r15;
u64 prev_cr3;
pgd_t *efi_pgt;
bool use_pgd;
u64 phys_stack;
} __packed;
static void __init early_code_mapping_set_exec(int executable)
{
efi_memory_desc_t *md;
void *p;
if (!(__supported_pte_mask & _PAGE_NX))
return;
/* Make EFI service code area executable */
for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) {
md = p;
if (md->type == EFI_RUNTIME_SERVICES_CODE ||
md->type == EFI_BOOT_SERVICES_CODE)
efi_set_executable(md, executable);
}
}
void __init efi_call_phys_prolog(void)
{
unsigned long vaddress;
int pgd;
int n_pgds;
if (!efi_enabled(EFI_OLD_MEMMAP))
return;
early_code_mapping_set_exec(1);
local_irq_save(efi_flags);
n_pgds = DIV_ROUND_UP((max_pfn << PAGE_SHIFT), PGDIR_SIZE);
save_pgd = kmalloc(n_pgds * sizeof(pgd_t), GFP_KERNEL);
for (pgd = 0; pgd < n_pgds; pgd++) {
save_pgd[pgd] = *pgd_offset_k(pgd * PGDIR_SIZE);
vaddress = (unsigned long)__va(pgd * PGDIR_SIZE);
set_pgd(pgd_offset_k(pgd * PGDIR_SIZE), *pgd_offset_k(vaddress));
}
__flush_tlb_all();
}
void __init efi_call_phys_epilog(void)
{
/*
* After the lock is released, the original page table is restored.
*/
int pgd;
int n_pgds = DIV_ROUND_UP((max_pfn << PAGE_SHIFT) , PGDIR_SIZE);
if (!efi_enabled(EFI_OLD_MEMMAP))
return;
for (pgd = 0; pgd < n_pgds; pgd++)
set_pgd(pgd_offset_k(pgd * PGDIR_SIZE), save_pgd[pgd]);
kfree(save_pgd);
__flush_tlb_all();
local_irq_restore(efi_flags);
early_code_mapping_set_exec(0);
}
/*
* Add low kernel mappings for passing arguments to EFI functions.
*/
void efi_sync_low_kernel_mappings(void)
{
unsigned num_pgds;
pgd_t *pgd = (pgd_t *)__va(real_mode_header->trampoline_pgd);
if (efi_enabled(EFI_OLD_MEMMAP))
return;
num_pgds = pgd_index(MODULES_END - 1) - pgd_index(PAGE_OFFSET);
memcpy(pgd + pgd_index(PAGE_OFFSET),
init_mm.pgd + pgd_index(PAGE_OFFSET),
sizeof(pgd_t) * num_pgds);
}
int __init efi_setup_page_tables(unsigned long pa_memmap, unsigned num_pages)
{
unsigned long text;
struct page *page;
unsigned npages;
pgd_t *pgd;
if (efi_enabled(EFI_OLD_MEMMAP))
return 0;
efi_scratch.efi_pgt = (pgd_t *)(unsigned long)real_mode_header->trampoline_pgd;
pgd = __va(efi_scratch.efi_pgt);
/*
* It can happen that the physical address of new_memmap lands in memory
* which is not mapped in the EFI page table. Therefore we need to go
* and ident-map those pages containing the map before calling
* phys_efi_set_virtual_address_map().
*/
if (kernel_map_pages_in_pgd(pgd, pa_memmap, pa_memmap, num_pages, _PAGE_NX)) {
pr_err("Error ident-mapping new memmap (0x%lx)!\n", pa_memmap);
return 1;
}
efi_scratch.use_pgd = true;
/*
* When making calls to the firmware everything needs to be 1:1
* mapped and addressable with 32-bit pointers. Map the kernel
* text and allocate a new stack because we can't rely on the
* stack pointer being < 4GB.
*/
if (!IS_ENABLED(CONFIG_EFI_MIXED))
return 0;
page = alloc_page(GFP_KERNEL|__GFP_DMA32);
if (!page)
panic("Unable to allocate EFI runtime stack < 4GB\n");
efi_scratch.phys_stack = virt_to_phys(page_address(page));
efi_scratch.phys_stack += PAGE_SIZE; /* stack grows down */
npages = (_end - _text) >> PAGE_SHIFT;
text = __pa(_text);
if (kernel_map_pages_in_pgd(pgd, text >> PAGE_SHIFT, text, npages, 0)) {
pr_err("Failed to map kernel text 1:1\n");
return 1;
}
return 0;
}
void __init efi_cleanup_page_tables(unsigned long pa_memmap, unsigned num_pages)
{
pgd_t *pgd = (pgd_t *)__va(real_mode_header->trampoline_pgd);
kernel_unmap_pages_in_pgd(pgd, pa_memmap, num_pages);
}
static void __init __map_region(efi_memory_desc_t *md, u64 va)
{
pgd_t *pgd = (pgd_t *)__va(real_mode_header->trampoline_pgd);
unsigned long pf = 0;
if (!(md->attribute & EFI_MEMORY_WB))
pf |= _PAGE_PCD;
if (kernel_map_pages_in_pgd(pgd, md->phys_addr, va, md->num_pages, pf))
pr_warn("Error mapping PA 0x%llx -> VA 0x%llx!\n",
md->phys_addr, va);
}
void __init efi_map_region(efi_memory_desc_t *md)
{
unsigned long size = md->num_pages << PAGE_SHIFT;
u64 pa = md->phys_addr;
if (efi_enabled(EFI_OLD_MEMMAP))
return old_map_region(md);
/*
* Make sure the 1:1 mappings are present as a catch-all for b0rked
* firmware which doesn't update all internal pointers after switching
* to virtual mode and would otherwise crap on us.
*/
__map_region(md, md->phys_addr);
/*
* Enforce the 1:1 mapping as the default virtual address when
* booting in EFI mixed mode, because even though we may be
* running a 64-bit kernel, the firmware may only be 32-bit.
*/
if (!efi_is_native () && IS_ENABLED(CONFIG_EFI_MIXED)) {
md->virt_addr = md->phys_addr;
return;
}
efi_va -= size;
/* Is PA 2M-aligned? */
if (!(pa & (PMD_SIZE - 1))) {
efi_va &= PMD_MASK;
} else {
u64 pa_offset = pa & (PMD_SIZE - 1);
u64 prev_va = efi_va;
/* get us the same offset within this 2M page */
efi_va = (efi_va & PMD_MASK) + pa_offset;
if (efi_va > prev_va)
efi_va -= PMD_SIZE;
}
if (efi_va < EFI_VA_END) {
pr_warn(FW_WARN "VA address range overflow!\n");
return;
}
/* Do the VA map */
__map_region(md, efi_va);
md->virt_addr = efi_va;
}
/*
* kexec kernel will use efi_map_region_fixed to map efi runtime memory ranges.
* md->virt_addr is the original virtual address which had been mapped in kexec
* 1st kernel.
*/
void __init efi_map_region_fixed(efi_memory_desc_t *md)
{
__map_region(md, md->virt_addr);
}
void __iomem *__init efi_ioremap(unsigned long phys_addr, unsigned long size,
u32 type, u64 attribute)
{
unsigned long last_map_pfn;
if (type == EFI_MEMORY_MAPPED_IO)
return ioremap(phys_addr, size);
last_map_pfn = init_memory_mapping(phys_addr, phys_addr + size);
if ((last_map_pfn << PAGE_SHIFT) < phys_addr + size) {
unsigned long top = last_map_pfn << PAGE_SHIFT;
efi_ioremap(top, size - (top - phys_addr), type, attribute);
}
if (!(attribute & EFI_MEMORY_WB))
efi_memory_uc((u64)(unsigned long)__va(phys_addr), size);
return (void __iomem *)__va(phys_addr);
}
void __init parse_efi_setup(u64 phys_addr, u32 data_len)
{
efi_setup = phys_addr + sizeof(struct setup_data);
}
void __init efi_runtime_mkexec(void)
{
if (!efi_enabled(EFI_OLD_MEMMAP))
return;
if (__supported_pte_mask & _PAGE_NX)
runtime_code_page_mkexec();
}
void __init efi_dump_pagetable(void)
{
#ifdef CONFIG_EFI_PGT_DUMP
pgd_t *pgd = (pgd_t *)__va(real_mode_header->trampoline_pgd);
ptdump_walk_pgd_level(NULL, pgd);
#endif
}
#ifdef CONFIG_EFI_MIXED
extern efi_status_t efi64_thunk(u32, ...);
#define runtime_service32(func) \
({ \
u32 table = (u32)(unsigned long)efi.systab; \
u32 *rt, *___f; \
\
rt = (u32 *)(table + offsetof(efi_system_table_32_t, runtime)); \
___f = (u32 *)(*rt + offsetof(efi_runtime_services_32_t, func)); \
*___f; \
})
/*
* Switch to the EFI page tables early so that we can access the 1:1
* runtime services mappings which are not mapped in any other page
* tables. This function must be called before runtime_service32().
*
* Also, disable interrupts because the IDT points to 64-bit handlers,
* which aren't going to function correctly when we switch to 32-bit.
*/
#define efi_thunk(f, ...) \
({ \
efi_status_t __s; \
unsigned long flags; \
u32 func; \
\
efi_sync_low_kernel_mappings(); \
local_irq_save(flags); \
\
efi_scratch.prev_cr3 = read_cr3(); \
write_cr3((unsigned long)efi_scratch.efi_pgt); \
__flush_tlb_all(); \
\
func = runtime_service32(f); \
__s = efi64_thunk(func, __VA_ARGS__); \
\
write_cr3(efi_scratch.prev_cr3); \
__flush_tlb_all(); \
local_irq_restore(flags); \
\
__s; \
})
efi_status_t efi_thunk_set_virtual_address_map(
void *phys_set_virtual_address_map,
unsigned long memory_map_size,
unsigned long descriptor_size,
u32 descriptor_version,
efi_memory_desc_t *virtual_map)
{
efi_status_t status;
unsigned long flags;
u32 func;
efi_sync_low_kernel_mappings();
local_irq_save(flags);
efi_scratch.prev_cr3 = read_cr3();
write_cr3((unsigned long)efi_scratch.efi_pgt);
__flush_tlb_all();
func = (u32)(unsigned long)phys_set_virtual_address_map;
status = efi64_thunk(func, memory_map_size, descriptor_size,
descriptor_version, virtual_map);
write_cr3(efi_scratch.prev_cr3);
__flush_tlb_all();
local_irq_restore(flags);
return status;
}
static efi_status_t efi_thunk_get_time(efi_time_t *tm, efi_time_cap_t *tc)
{
efi_status_t status;
u32 phys_tm, phys_tc;
spin_lock(&rtc_lock);
phys_tm = virt_to_phys(tm);
phys_tc = virt_to_phys(tc);
status = efi_thunk(get_time, phys_tm, phys_tc);
spin_unlock(&rtc_lock);
return status;
}
static efi_status_t efi_thunk_set_time(efi_time_t *tm)
{
efi_status_t status;
u32 phys_tm;
spin_lock(&rtc_lock);
phys_tm = virt_to_phys(tm);
status = efi_thunk(set_time, phys_tm);
spin_unlock(&rtc_lock);
return status;
}
static efi_status_t
efi_thunk_get_wakeup_time(efi_bool_t *enabled, efi_bool_t *pending,
efi_time_t *tm)
{
efi_status_t status;
u32 phys_enabled, phys_pending, phys_tm;
spin_lock(&rtc_lock);
phys_enabled = virt_to_phys(enabled);
phys_pending = virt_to_phys(pending);
phys_tm = virt_to_phys(tm);
status = efi_thunk(get_wakeup_time, phys_enabled,
phys_pending, phys_tm);
spin_unlock(&rtc_lock);
return status;
}
static efi_status_t
efi_thunk_set_wakeup_time(efi_bool_t enabled, efi_time_t *tm)
{
efi_status_t status;
u32 phys_tm;
spin_lock(&rtc_lock);
phys_tm = virt_to_phys(tm);
status = efi_thunk(set_wakeup_time, enabled, phys_tm);
spin_unlock(&rtc_lock);
return status;
}
static efi_status_t
efi_thunk_get_variable(efi_char16_t *name, efi_guid_t *vendor,
u32 *attr, unsigned long *data_size, void *data)
{
efi_status_t status;
u32 phys_name, phys_vendor, phys_attr;
u32 phys_data_size, phys_data;
phys_data_size = virt_to_phys(data_size);
phys_vendor = virt_to_phys(vendor);
phys_name = virt_to_phys(name);
phys_attr = virt_to_phys(attr);
phys_data = virt_to_phys(data);
status = efi_thunk(get_variable, phys_name, phys_vendor,
phys_attr, phys_data_size, phys_data);
return status;
}
static efi_status_t
efi_thunk_set_variable(efi_char16_t *name, efi_guid_t *vendor,
u32 attr, unsigned long data_size, void *data)
{
u32 phys_name, phys_vendor, phys_data;
efi_status_t status;
phys_name = virt_to_phys(name);
phys_vendor = virt_to_phys(vendor);
phys_data = virt_to_phys(data);
/* If data_size is > sizeof(u32) we've got problems */
status = efi_thunk(set_variable, phys_name, phys_vendor,
attr, data_size, phys_data);
return status;
}
static efi_status_t
efi_thunk_get_next_variable(unsigned long *name_size,
efi_char16_t *name,
efi_guid_t *vendor)
{
efi_status_t status;
u32 phys_name_size, phys_name, phys_vendor;
phys_name_size = virt_to_phys(name_size);
phys_vendor = virt_to_phys(vendor);
phys_name = virt_to_phys(name);
status = efi_thunk(get_next_variable, phys_name_size,
phys_name, phys_vendor);
return status;
}
static efi_status_t
efi_thunk_get_next_high_mono_count(u32 *count)
{
efi_status_t status;
u32 phys_count;
phys_count = virt_to_phys(count);
status = efi_thunk(get_next_high_mono_count, phys_count);
return status;
}
static void
efi_thunk_reset_system(int reset_type, efi_status_t status,
unsigned long data_size, efi_char16_t *data)
{
u32 phys_data;
phys_data = virt_to_phys(data);
efi_thunk(reset_system, reset_type, status, data_size, phys_data);
}
static efi_status_t
efi_thunk_update_capsule(efi_capsule_header_t **capsules,
unsigned long count, unsigned long sg_list)
{
/*
* To properly support this function we would need to repackage
* 'capsules' because the firmware doesn't understand 64-bit
* pointers.
*/
return EFI_UNSUPPORTED;
}
static efi_status_t
efi_thunk_query_variable_info(u32 attr, u64 *storage_space,
u64 *remaining_space,
u64 *max_variable_size)
{
efi_status_t status;
u32 phys_storage, phys_remaining, phys_max;
if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION)
return EFI_UNSUPPORTED;
phys_storage = virt_to_phys(storage_space);
phys_remaining = virt_to_phys(remaining_space);
phys_max = virt_to_phys(max_variable_size);
status = efi_thunk(query_variable_info, attr, phys_storage,
phys_remaining, phys_max);
return status;
}
static efi_status_t
efi_thunk_query_capsule_caps(efi_capsule_header_t **capsules,
unsigned long count, u64 *max_size,
int *reset_type)
{
/*
* To properly support this function we would need to repackage
* 'capsules' because the firmware doesn't understand 64-bit
* pointers.
*/
return EFI_UNSUPPORTED;
}
void efi_thunk_runtime_setup(void)
{
efi.get_time = efi_thunk_get_time;
efi.set_time = efi_thunk_set_time;
efi.get_wakeup_time = efi_thunk_get_wakeup_time;
efi.set_wakeup_time = efi_thunk_set_wakeup_time;
efi.get_variable = efi_thunk_get_variable;
efi.get_next_variable = efi_thunk_get_next_variable;
efi.set_variable = efi_thunk_set_variable;
efi.get_next_high_mono_count = efi_thunk_get_next_high_mono_count;
efi.reset_system = efi_thunk_reset_system;
efi.query_variable_info = efi_thunk_query_variable_info;
efi.update_capsule = efi_thunk_update_capsule;
efi.query_capsule_caps = efi_thunk_query_capsule_caps;
}
#endif /* CONFIG_EFI_MIXED */

View file

@ -0,0 +1,123 @@
/*
* EFI call stub for IA32.
*
* This stub allows us to make EFI calls in physical mode with interrupts
* turned off.
*/
#include <linux/linkage.h>
#include <asm/page_types.h>
/*
* efi_call_phys(void *, ...) is a function with variable parameters.
* All the callers of this function assure that all the parameters are 4-bytes.
*/
/*
* In gcc calling convention, EBX, ESP, EBP, ESI and EDI are all callee save.
* So we'd better save all of them at the beginning of this function and restore
* at the end no matter how many we use, because we can not assure EFI runtime
* service functions will comply with gcc calling convention, too.
*/
.text
ENTRY(efi_call_phys)
/*
* 0. The function can only be called in Linux kernel. So CS has been
* set to 0x0010, DS and SS have been set to 0x0018. In EFI, I found
* the values of these registers are the same. And, the corresponding
* GDT entries are identical. So I will do nothing about segment reg
* and GDT, but change GDT base register in prolog and epilog.
*/
/*
* 1. Now I am running with EIP = <physical address> + PAGE_OFFSET.
* But to make it smoothly switch from virtual mode to flat mode.
* The mapping of lower virtual memory has been created in prolog and
* epilog.
*/
movl $1f, %edx
subl $__PAGE_OFFSET, %edx
jmp *%edx
1:
/*
* 2. Now on the top of stack is the return
* address in the caller of efi_call_phys(), then parameter 1,
* parameter 2, ..., param n. To make things easy, we save the return
* address of efi_call_phys in a global variable.
*/
popl %edx
movl %edx, saved_return_addr
/* get the function pointer into ECX*/
popl %ecx
movl %ecx, efi_rt_function_ptr
movl $2f, %edx
subl $__PAGE_OFFSET, %edx
pushl %edx
/*
* 3. Clear PG bit in %CR0.
*/
movl %cr0, %edx
andl $0x7fffffff, %edx
movl %edx, %cr0
jmp 1f
1:
/*
* 4. Adjust stack pointer.
*/
subl $__PAGE_OFFSET, %esp
/*
* 5. Call the physical function.
*/
jmp *%ecx
2:
/*
* 6. After EFI runtime service returns, control will return to
* following instruction. We'd better readjust stack pointer first.
*/
addl $__PAGE_OFFSET, %esp
/*
* 7. Restore PG bit
*/
movl %cr0, %edx
orl $0x80000000, %edx
movl %edx, %cr0
jmp 1f
1:
/*
* 8. Now restore the virtual mode from flat mode by
* adding EIP with PAGE_OFFSET.
*/
movl $1f, %edx
jmp *%edx
1:
/*
* 9. Balance the stack. And because EAX contain the return value,
* we'd better not clobber it.
*/
leal efi_rt_function_ptr, %edx
movl (%edx), %ecx
pushl %ecx
/*
* 10. Push the saved return address onto the stack and return.
*/
leal saved_return_addr, %edx
movl (%edx), %ecx
pushl %ecx
ret
ENDPROC(efi_call_phys)
.previous
.data
saved_return_addr:
.long 0
efi_rt_function_ptr:
.long 0

View file

@ -0,0 +1,98 @@
/*
* Function calling ABI conversion from Linux to EFI for x86_64
*
* Copyright (C) 2007 Intel Corp
* Bibo Mao <bibo.mao@intel.com>
* Huang Ying <ying.huang@intel.com>
*/
#include <linux/linkage.h>
#include <asm/segment.h>
#include <asm/msr.h>
#include <asm/processor-flags.h>
#include <asm/page_types.h>
#define SAVE_XMM \
mov %rsp, %rax; \
subq $0x70, %rsp; \
and $~0xf, %rsp; \
mov %rax, (%rsp); \
mov %cr0, %rax; \
clts; \
mov %rax, 0x8(%rsp); \
movaps %xmm0, 0x60(%rsp); \
movaps %xmm1, 0x50(%rsp); \
movaps %xmm2, 0x40(%rsp); \
movaps %xmm3, 0x30(%rsp); \
movaps %xmm4, 0x20(%rsp); \
movaps %xmm5, 0x10(%rsp)
#define RESTORE_XMM \
movaps 0x60(%rsp), %xmm0; \
movaps 0x50(%rsp), %xmm1; \
movaps 0x40(%rsp), %xmm2; \
movaps 0x30(%rsp), %xmm3; \
movaps 0x20(%rsp), %xmm4; \
movaps 0x10(%rsp), %xmm5; \
mov 0x8(%rsp), %rsi; \
mov %rsi, %cr0; \
mov (%rsp), %rsp
/* stolen from gcc */
.macro FLUSH_TLB_ALL
movq %r15, efi_scratch(%rip)
movq %r14, efi_scratch+8(%rip)
movq %cr4, %r15
movq %r15, %r14
andb $0x7f, %r14b
movq %r14, %cr4
movq %r15, %cr4
movq efi_scratch+8(%rip), %r14
movq efi_scratch(%rip), %r15
.endm
.macro SWITCH_PGT
cmpb $0, efi_scratch+24(%rip)
je 1f
movq %r15, efi_scratch(%rip) # r15
# save previous CR3
movq %cr3, %r15
movq %r15, efi_scratch+8(%rip) # prev_cr3
movq efi_scratch+16(%rip), %r15 # EFI pgt
movq %r15, %cr3
1:
.endm
.macro RESTORE_PGT
cmpb $0, efi_scratch+24(%rip)
je 2f
movq efi_scratch+8(%rip), %r15
movq %r15, %cr3
movq efi_scratch(%rip), %r15
FLUSH_TLB_ALL
2:
.endm
ENTRY(efi_call)
SAVE_XMM
mov (%rsp), %rax
mov 8(%rax), %rax
subq $48, %rsp
mov %r9, 32(%rsp)
mov %rax, 40(%rsp)
mov %r8, %r9
mov %rcx, %r8
mov %rsi, %rcx
SWITCH_PGT
call *%rdi
RESTORE_PGT
addq $48, %rsp
RESTORE_XMM
ret
ENDPROC(efi_call)
.data
ENTRY(efi_scratch)
.fill 3,8,0
.byte 0
.quad 0

View file

@ -0,0 +1,152 @@
/*
* Copyright (C) 2014 Intel Corporation; author Matt Fleming
*
* Support for invoking 32-bit EFI runtime services from a 64-bit
* kernel.
*
* The below thunking functions are only used after ExitBootServices()
* has been called. This simplifies things considerably as compared with
* the early EFI thunking because we can leave all the kernel state
* intact (GDT, IDT, etc) and simply invoke the the 32-bit EFI runtime
* services from __KERNEL32_CS. This means we can continue to service
* interrupts across an EFI mixed mode call.
*
* We do however, need to handle the fact that we're running in a full
* 64-bit virtual address space. Things like the stack and instruction
* addresses need to be accessible by the 32-bit firmware, so we rely on
* using the identity mappings in the EFI page table to access the stack
* and kernel text (see efi_setup_page_tables()).
*/
#include <linux/linkage.h>
#include <asm/page_types.h>
#include <asm/segment.h>
.text
.code64
ENTRY(efi64_thunk)
push %rbp
push %rbx
/*
* Switch to 1:1 mapped 32-bit stack pointer.
*/
movq %rsp, efi_saved_sp(%rip)
movq efi_scratch+25(%rip), %rsp
/*
* Calculate the physical address of the kernel text.
*/
movq $__START_KERNEL_map, %rax
subq phys_base(%rip), %rax
/*
* Push some physical addresses onto the stack. This is easier
* to do now in a code64 section while the assembler can address
* 64-bit values. Note that all the addresses on the stack are
* 32-bit.
*/
subq $16, %rsp
leaq efi_exit32(%rip), %rbx
subq %rax, %rbx
movl %ebx, 8(%rsp)
leaq __efi64_thunk(%rip), %rbx
subq %rax, %rbx
call *%rbx
movq efi_saved_sp(%rip), %rsp
pop %rbx
pop %rbp
retq
ENDPROC(efi64_thunk)
/*
* We run this function from the 1:1 mapping.
*
* This function must be invoked with a 1:1 mapped stack.
*/
ENTRY(__efi64_thunk)
movl %ds, %eax
push %rax
movl %es, %eax
push %rax
movl %ss, %eax
push %rax
subq $32, %rsp
movl %esi, 0x0(%rsp)
movl %edx, 0x4(%rsp)
movl %ecx, 0x8(%rsp)
movq %r8, %rsi
movl %esi, 0xc(%rsp)
movq %r9, %rsi
movl %esi, 0x10(%rsp)
leaq 1f(%rip), %rbx
movq %rbx, func_rt_ptr(%rip)
/* Switch to 32-bit descriptor */
pushq $__KERNEL32_CS
leaq efi_enter32(%rip), %rax
pushq %rax
lretq
1: addq $32, %rsp
pop %rbx
movl %ebx, %ss
pop %rbx
movl %ebx, %es
pop %rbx
movl %ebx, %ds
/*
* Convert 32-bit status code into 64-bit.
*/
test %rax, %rax
jz 1f
movl %eax, %ecx
andl $0x0fffffff, %ecx
andl $0xf0000000, %eax
shl $32, %rax
or %rcx, %rax
1:
ret
ENDPROC(__efi64_thunk)
ENTRY(efi_exit32)
movq func_rt_ptr(%rip), %rax
push %rax
mov %rdi, %rax
ret
ENDPROC(efi_exit32)
.code32
/*
* EFI service pointer must be in %edi.
*
* The stack should represent the 32-bit calling convention.
*/
ENTRY(efi_enter32)
movl $__KERNEL_DS, %eax
movl %eax, %ds
movl %eax, %es
movl %eax, %ss
call *%edi
/* We must preserve return value */
movl %eax, %edi
movl 72(%esp), %eax
pushl $__KERNEL_CS
pushl %eax
lret
ENDPROC(efi_enter32)
.data
.balign 8
func_rt_ptr: .quad 0
efi_saved_sp: .quad 0

View file

@ -0,0 +1,290 @@
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/time.h>
#include <linux/types.h>
#include <linux/efi.h>
#include <linux/slab.h>
#include <linux/memblock.h>
#include <linux/bootmem.h>
#include <linux/acpi.h>
#include <asm/efi.h>
#include <asm/uv/uv.h>
#define EFI_MIN_RESERVE 5120
#define EFI_DUMMY_GUID \
EFI_GUID(0x4424ac57, 0xbe4b, 0x47dd, 0x9e, 0x97, 0xed, 0x50, 0xf0, 0x9f, 0x92, 0xa9)
static efi_char16_t efi_dummy_name[6] = { 'D', 'U', 'M', 'M', 'Y', 0 };
static bool efi_no_storage_paranoia;
/*
* Some firmware implementations refuse to boot if there's insufficient
* space in the variable store. The implementation of garbage collection
* in some FW versions causes stale (deleted) variables to take up space
* longer than intended and space is only freed once the store becomes
* almost completely full.
*
* Enabling this option disables the space checks in
* efi_query_variable_store() and forces garbage collection.
*
* Only enable this option if deleting EFI variables does not free up
* space in your variable store, e.g. if despite deleting variables
* you're unable to create new ones.
*/
static int __init setup_storage_paranoia(char *arg)
{
efi_no_storage_paranoia = true;
return 0;
}
early_param("efi_no_storage_paranoia", setup_storage_paranoia);
/*
* Deleting the dummy variable which kicks off garbage collection
*/
void efi_delete_dummy_variable(void)
{
efi.set_variable(efi_dummy_name, &EFI_DUMMY_GUID,
EFI_VARIABLE_NON_VOLATILE |
EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS,
0, NULL);
}
/*
* Some firmware implementations refuse to boot if there's insufficient space
* in the variable store. Ensure that we never use more than a safe limit.
*
* Return EFI_SUCCESS if it is safe to write 'size' bytes to the variable
* store.
*/
efi_status_t efi_query_variable_store(u32 attributes, unsigned long size)
{
efi_status_t status;
u64 storage_size, remaining_size, max_size;
if (!(attributes & EFI_VARIABLE_NON_VOLATILE))
return 0;
status = efi.query_variable_info(attributes, &storage_size,
&remaining_size, &max_size);
if (status != EFI_SUCCESS)
return status;
/*
* We account for that by refusing the write if permitting it would
* reduce the available space to under 5KB. This figure was provided by
* Samsung, so should be safe.
*/
if ((remaining_size - size < EFI_MIN_RESERVE) &&
!efi_no_storage_paranoia) {
/*
* Triggering garbage collection may require that the firmware
* generate a real EFI_OUT_OF_RESOURCES error. We can force
* that by attempting to use more space than is available.
*/
unsigned long dummy_size = remaining_size + 1024;
void *dummy = kzalloc(dummy_size, GFP_ATOMIC);
if (!dummy)
return EFI_OUT_OF_RESOURCES;
status = efi.set_variable(efi_dummy_name, &EFI_DUMMY_GUID,
EFI_VARIABLE_NON_VOLATILE |
EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS,
dummy_size, dummy);
if (status == EFI_SUCCESS) {
/*
* This should have failed, so if it didn't make sure
* that we delete it...
*/
efi_delete_dummy_variable();
}
kfree(dummy);
/*
* The runtime code may now have triggered a garbage collection
* run, so check the variable info again
*/
status = efi.query_variable_info(attributes, &storage_size,
&remaining_size, &max_size);
if (status != EFI_SUCCESS)
return status;
/*
* There still isn't enough room, so return an error
*/
if (remaining_size - size < EFI_MIN_RESERVE)
return EFI_OUT_OF_RESOURCES;
}
return EFI_SUCCESS;
}
EXPORT_SYMBOL_GPL(efi_query_variable_store);
/*
* The UEFI specification makes it clear that the operating system is free to do
* whatever it wants with boot services code after ExitBootServices() has been
* called. Ignoring this recommendation a significant bunch of EFI implementations
* continue calling into boot services code (SetVirtualAddressMap). In order to
* work around such buggy implementations we reserve boot services region during
* EFI init and make sure it stays executable. Then, after SetVirtualAddressMap(), it
* is discarded.
*/
void __init efi_reserve_boot_services(void)
{
void *p;
for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) {
efi_memory_desc_t *md = p;
u64 start = md->phys_addr;
u64 size = md->num_pages << EFI_PAGE_SHIFT;
if (md->type != EFI_BOOT_SERVICES_CODE &&
md->type != EFI_BOOT_SERVICES_DATA)
continue;
/* Only reserve where possible:
* - Not within any already allocated areas
* - Not over any memory area (really needed, if above?)
* - Not within any part of the kernel
* - Not the bios reserved area
*/
if ((start + size > __pa_symbol(_text)
&& start <= __pa_symbol(_end)) ||
!e820_all_mapped(start, start+size, E820_RAM) ||
memblock_is_region_reserved(start, size)) {
/* Could not reserve, skip it */
md->num_pages = 0;
memblock_dbg("Could not reserve boot range [0x%010llx-0x%010llx]\n",
start, start+size-1);
} else
memblock_reserve(start, size);
}
}
void __init efi_free_boot_services(void)
{
void *p;
for (p = memmap.map; p < memmap.map_end; p += memmap.desc_size) {
efi_memory_desc_t *md = p;
unsigned long long start = md->phys_addr;
unsigned long long size = md->num_pages << EFI_PAGE_SHIFT;
if (md->type != EFI_BOOT_SERVICES_CODE &&
md->type != EFI_BOOT_SERVICES_DATA)
continue;
/* Could not reserve boot area */
if (!size)
continue;
free_bootmem_late(start, size);
}
efi_unmap_memmap();
}
/*
* A number of config table entries get remapped to virtual addresses
* after entering EFI virtual mode. However, the kexec kernel requires
* their physical addresses therefore we pass them via setup_data and
* correct those entries to their respective physical addresses here.
*
* Currently only handles smbios which is necessary for some firmware
* implementation.
*/
int __init efi_reuse_config(u64 tables, int nr_tables)
{
int i, sz, ret = 0;
void *p, *tablep;
struct efi_setup_data *data;
if (!efi_setup)
return 0;
if (!efi_enabled(EFI_64BIT))
return 0;
data = early_memremap(efi_setup, sizeof(*data));
if (!data) {
ret = -ENOMEM;
goto out;
}
if (!data->smbios)
goto out_memremap;
sz = sizeof(efi_config_table_64_t);
p = tablep = early_memremap(tables, nr_tables * sz);
if (!p) {
pr_err("Could not map Configuration table!\n");
ret = -ENOMEM;
goto out_memremap;
}
for (i = 0; i < efi.systab->nr_tables; i++) {
efi_guid_t guid;
guid = ((efi_config_table_64_t *)p)->guid;
if (!efi_guidcmp(guid, SMBIOS_TABLE_GUID))
((efi_config_table_64_t *)p)->table = data->smbios;
p += sz;
}
early_memunmap(tablep, nr_tables * sz);
out_memremap:
early_memunmap(data, sizeof(*data));
out:
return ret;
}
void __init efi_apply_memmap_quirks(void)
{
/*
* Once setup is done earlier, unmap the EFI memory map on mismatched
* firmware/kernel architectures since there is no support for runtime
* services.
*/
if (!efi_runtime_supported()) {
pr_info("efi: Setup done, disabling due to 32/64-bit mismatch\n");
efi_unmap_memmap();
}
/*
* UV doesn't support the new EFI pagetable mapping yet.
*/
if (is_uv_system())
set_bit(EFI_OLD_MEMMAP, &efi.flags);
}
/*
* For most modern platforms the preferred method of powering off is via
* ACPI. However, there are some that are known to require the use of
* EFI runtime services and for which ACPI does not work at all.
*
* Using EFI is a last resort, to be used only if no other option
* exists.
*/
bool efi_reboot_required(void)
{
if (!acpi_gbl_reduced_hardware)
return false;
efi_reboot_quirk_mode = EFI_RESET_WARM;
return true;
}
bool efi_poweroff_required(void)
{
return !!acpi_gbl_reduced_hardware;
}

View file

@ -0,0 +1,3 @@
obj-$(CONFIG_ALIX) += alix.o
obj-$(CONFIG_NET5501) += net5501.o
obj-$(CONFIG_GEOS) += geos.o

View file

@ -0,0 +1,200 @@
/*
* System Specific setup for PCEngines ALIX.
* At the moment this means setup of GPIO control of LEDs
* on Alix.2/3/6 boards.
*
*
* Copyright (C) 2008 Constantin Baranov <const@mimas.ru>
* Copyright (C) 2011 Ed Wildgoose <kernel@wildgooses.com>
* and Philip Prindeville <philipp@redfish-solutions.com>
*
* TODO: There are large similarities with leds-net5501.c
* by Alessandro Zummo <a.zummo@towertech.it>
* In the future leds-net5501.c should be migrated over to platform
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/string.h>
#include <linux/module.h>
#include <linux/leds.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/gpio_keys.h>
#include <linux/dmi.h>
#include <asm/geode.h>
#define BIOS_SIGNATURE_TINYBIOS 0xf0000
#define BIOS_SIGNATURE_COREBOOT 0x500
#define BIOS_REGION_SIZE 0x10000
static bool force = 0;
module_param(force, bool, 0444);
/* FIXME: Award bios is not automatically detected as Alix platform */
MODULE_PARM_DESC(force, "Force detection as ALIX.2/ALIX.3 platform");
static struct gpio_keys_button alix_gpio_buttons[] = {
{
.code = KEY_RESTART,
.gpio = 24,
.active_low = 1,
.desc = "Reset button",
.type = EV_KEY,
.wakeup = 0,
.debounce_interval = 100,
.can_disable = 0,
}
};
static struct gpio_keys_platform_data alix_buttons_data = {
.buttons = alix_gpio_buttons,
.nbuttons = ARRAY_SIZE(alix_gpio_buttons),
.poll_interval = 20,
};
static struct platform_device alix_buttons_dev = {
.name = "gpio-keys-polled",
.id = 1,
.dev = {
.platform_data = &alix_buttons_data,
}
};
static struct gpio_led alix_leds[] = {
{
.name = "alix:1",
.gpio = 6,
.default_trigger = "default-on",
.active_low = 1,
},
{
.name = "alix:2",
.gpio = 25,
.default_trigger = "default-off",
.active_low = 1,
},
{
.name = "alix:3",
.gpio = 27,
.default_trigger = "default-off",
.active_low = 1,
},
};
static struct gpio_led_platform_data alix_leds_data = {
.num_leds = ARRAY_SIZE(alix_leds),
.leds = alix_leds,
};
static struct platform_device alix_leds_dev = {
.name = "leds-gpio",
.id = -1,
.dev.platform_data = &alix_leds_data,
};
static struct platform_device *alix_devs[] __initdata = {
&alix_buttons_dev,
&alix_leds_dev,
};
static void __init register_alix(void)
{
/* Setup LED control through leds-gpio driver */
platform_add_devices(alix_devs, ARRAY_SIZE(alix_devs));
}
static bool __init alix_present(unsigned long bios_phys,
const char *alix_sig,
size_t alix_sig_len)
{
const size_t bios_len = BIOS_REGION_SIZE;
const char *bios_virt;
const char *scan_end;
const char *p;
char name[64];
if (force) {
printk(KERN_NOTICE "%s: forced to skip BIOS test, "
"assume system is ALIX.2/ALIX.3\n",
KBUILD_MODNAME);
return true;
}
bios_virt = phys_to_virt(bios_phys);
scan_end = bios_virt + bios_len - (alix_sig_len + 2);
for (p = bios_virt; p < scan_end; p++) {
const char *tail;
char *a;
if (memcmp(p, alix_sig, alix_sig_len) != 0)
continue;
memcpy(name, p, sizeof(name));
/* remove the first \0 character from string */
a = strchr(name, '\0');
if (a)
*a = ' ';
/* cut the string at a newline */
a = strchr(name, '\r');
if (a)
*a = '\0';
tail = p + alix_sig_len;
if ((tail[0] == '2' || tail[0] == '3' || tail[0] == '6')) {
printk(KERN_INFO
"%s: system is recognized as \"%s\"\n",
KBUILD_MODNAME, name);
return true;
}
}
return false;
}
static bool __init alix_present_dmi(void)
{
const char *vendor, *product;
vendor = dmi_get_system_info(DMI_SYS_VENDOR);
if (!vendor || strcmp(vendor, "PC Engines"))
return false;
product = dmi_get_system_info(DMI_PRODUCT_NAME);
if (!product || (strcmp(product, "ALIX.2D") && strcmp(product, "ALIX.6")))
return false;
printk(KERN_INFO "%s: system is recognized as \"%s %s\"\n",
KBUILD_MODNAME, vendor, product);
return true;
}
static int __init alix_init(void)
{
const char tinybios_sig[] = "PC Engines ALIX.";
const char coreboot_sig[] = "PC Engines\0ALIX.";
if (!is_geode())
return 0;
if (alix_present(BIOS_SIGNATURE_TINYBIOS, tinybios_sig, sizeof(tinybios_sig) - 1) ||
alix_present(BIOS_SIGNATURE_COREBOOT, coreboot_sig, sizeof(coreboot_sig) - 1) ||
alix_present_dmi())
register_alix();
return 0;
}
module_init(alix_init);
MODULE_AUTHOR("Ed Wildgoose <kernel@wildgooses.com>");
MODULE_DESCRIPTION("PCEngines ALIX System Setup");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,128 @@
/*
* System Specific setup for Traverse Technologies GEOS.
* At the moment this means setup of GPIO control of LEDs.
*
* Copyright (C) 2008 Constantin Baranov <const@mimas.ru>
* Copyright (C) 2011 Ed Wildgoose <kernel@wildgooses.com>
* and Philip Prindeville <philipp@redfish-solutions.com>
*
* TODO: There are large similarities with leds-net5501.c
* by Alessandro Zummo <a.zummo@towertech.it>
* In the future leds-net5501.c should be migrated over to platform
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/string.h>
#include <linux/module.h>
#include <linux/leds.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/gpio_keys.h>
#include <linux/dmi.h>
#include <asm/geode.h>
static struct gpio_keys_button geos_gpio_buttons[] = {
{
.code = KEY_RESTART,
.gpio = 3,
.active_low = 1,
.desc = "Reset button",
.type = EV_KEY,
.wakeup = 0,
.debounce_interval = 100,
.can_disable = 0,
}
};
static struct gpio_keys_platform_data geos_buttons_data = {
.buttons = geos_gpio_buttons,
.nbuttons = ARRAY_SIZE(geos_gpio_buttons),
.poll_interval = 20,
};
static struct platform_device geos_buttons_dev = {
.name = "gpio-keys-polled",
.id = 1,
.dev = {
.platform_data = &geos_buttons_data,
}
};
static struct gpio_led geos_leds[] = {
{
.name = "geos:1",
.gpio = 6,
.default_trigger = "default-on",
.active_low = 1,
},
{
.name = "geos:2",
.gpio = 25,
.default_trigger = "default-off",
.active_low = 1,
},
{
.name = "geos:3",
.gpio = 27,
.default_trigger = "default-off",
.active_low = 1,
},
};
static struct gpio_led_platform_data geos_leds_data = {
.num_leds = ARRAY_SIZE(geos_leds),
.leds = geos_leds,
};
static struct platform_device geos_leds_dev = {
.name = "leds-gpio",
.id = -1,
.dev.platform_data = &geos_leds_data,
};
static struct platform_device *geos_devs[] __initdata = {
&geos_buttons_dev,
&geos_leds_dev,
};
static void __init register_geos(void)
{
/* Setup LED control through leds-gpio driver */
platform_add_devices(geos_devs, ARRAY_SIZE(geos_devs));
}
static int __init geos_init(void)
{
const char *vendor, *product;
if (!is_geode())
return 0;
vendor = dmi_get_system_info(DMI_SYS_VENDOR);
if (!vendor || strcmp(vendor, "Traverse Technologies"))
return 0;
product = dmi_get_system_info(DMI_PRODUCT_NAME);
if (!product || strcmp(product, "Geos"))
return 0;
printk(KERN_INFO "%s: system is recognized as \"%s %s\"\n",
KBUILD_MODNAME, vendor, product);
register_geos();
return 0;
}
module_init(geos_init);
MODULE_AUTHOR("Philip Prindeville <philipp@redfish-solutions.com>");
MODULE_DESCRIPTION("Traverse Technologies Geos System Setup");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,154 @@
/*
* System Specific setup for Soekris net5501
* At the moment this means setup of GPIO control of LEDs and buttons
* on net5501 boards.
*
*
* Copyright (C) 2008-2009 Tower Technologies
* Written by Alessandro Zummo <a.zummo@towertech.it>
*
* Copyright (C) 2008 Constantin Baranov <const@mimas.ru>
* Copyright (C) 2011 Ed Wildgoose <kernel@wildgooses.com>
* and Philip Prindeville <philipp@redfish-solutions.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/string.h>
#include <linux/module.h>
#include <linux/leds.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/gpio_keys.h>
#include <asm/geode.h>
#define BIOS_REGION_BASE 0xffff0000
#define BIOS_REGION_SIZE 0x00010000
static struct gpio_keys_button net5501_gpio_buttons[] = {
{
.code = KEY_RESTART,
.gpio = 24,
.active_low = 1,
.desc = "Reset button",
.type = EV_KEY,
.wakeup = 0,
.debounce_interval = 100,
.can_disable = 0,
}
};
static struct gpio_keys_platform_data net5501_buttons_data = {
.buttons = net5501_gpio_buttons,
.nbuttons = ARRAY_SIZE(net5501_gpio_buttons),
.poll_interval = 20,
};
static struct platform_device net5501_buttons_dev = {
.name = "gpio-keys-polled",
.id = 1,
.dev = {
.platform_data = &net5501_buttons_data,
}
};
static struct gpio_led net5501_leds[] = {
{
.name = "net5501:1",
.gpio = 6,
.default_trigger = "default-on",
.active_low = 0,
},
};
static struct gpio_led_platform_data net5501_leds_data = {
.num_leds = ARRAY_SIZE(net5501_leds),
.leds = net5501_leds,
};
static struct platform_device net5501_leds_dev = {
.name = "leds-gpio",
.id = -1,
.dev.platform_data = &net5501_leds_data,
};
static struct platform_device *net5501_devs[] __initdata = {
&net5501_buttons_dev,
&net5501_leds_dev,
};
static void __init register_net5501(void)
{
/* Setup LED control through leds-gpio driver */
platform_add_devices(net5501_devs, ARRAY_SIZE(net5501_devs));
}
struct net5501_board {
u16 offset;
u16 len;
char *sig;
};
static struct net5501_board __initdata boards[] = {
{ 0xb7b, 7, "net5501" }, /* net5501 v1.33/1.33c */
{ 0xb1f, 7, "net5501" }, /* net5501 v1.32i */
};
static bool __init net5501_present(void)
{
int i;
unsigned char *rombase, *bios;
bool found = false;
rombase = ioremap(BIOS_REGION_BASE, BIOS_REGION_SIZE - 1);
if (!rombase) {
printk(KERN_ERR "%s: failed to get rombase\n", KBUILD_MODNAME);
return found;
}
bios = rombase + 0x20; /* null terminated */
if (memcmp(bios, "comBIOS", 7))
goto unmap;
for (i = 0; i < ARRAY_SIZE(boards); i++) {
unsigned char *model = rombase + boards[i].offset;
if (!memcmp(model, boards[i].sig, boards[i].len)) {
printk(KERN_INFO "%s: system is recognized as \"%s\"\n",
KBUILD_MODNAME, model);
found = true;
break;
}
}
unmap:
iounmap(rombase);
return found;
}
static int __init net5501_init(void)
{
if (!is_geode())
return 0;
if (!net5501_present())
return 0;
register_net5501();
return 0;
}
module_init(net5501_init);
MODULE_AUTHOR("Philip Prindeville <philipp@redfish-solutions.com>");
MODULE_DESCRIPTION("Soekris net5501 System Setup");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1 @@
obj-$(CONFIG_GOLDFISH) += goldfish.o

View 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);

View file

@ -0,0 +1,7 @@
obj-$(CONFIG_X86_INTEL_MID) += intel-mid.o intel_mid_vrtc.o mfld.o mrfl.o
obj-$(CONFIG_EARLY_PRINTK_INTEL_MID) += early_printk_intel_mid.o
# SFI specific code
ifdef CONFIG_X86_INTEL_MID
obj-$(CONFIG_SFI) += sfi.o device_libs/
endif

View file

@ -0,0 +1,23 @@
# IPC Devices
obj-y += platform_ipc.o
obj-$(subst m,y,$(CONFIG_MFD_INTEL_MSIC)) += platform_msic.o
obj-$(subst m,y,$(CONFIG_SND_MFLD_MACHINE)) += platform_msic_audio.o
obj-$(subst m,y,$(CONFIG_GPIO_MSIC)) += platform_msic_gpio.o
obj-$(subst m,y,$(CONFIG_MFD_INTEL_MSIC)) += platform_msic_ocd.o
obj-$(subst m,y,$(CONFIG_MFD_INTEL_MSIC)) += platform_msic_battery.o
obj-$(subst m,y,$(CONFIG_INTEL_MID_POWER_BUTTON)) += platform_msic_power_btn.o
obj-$(subst m,y,$(CONFIG_GPIO_INTEL_PMIC)) += platform_pmic_gpio.o
obj-$(subst m,y,$(CONFIG_INTEL_MFLD_THERMAL)) += platform_msic_thermal.o
# I2C Devices
obj-$(subst m,y,$(CONFIG_SENSORS_EMC1403)) += platform_emc1403.o
obj-$(subst m,y,$(CONFIG_SENSORS_LIS3LV02D)) += platform_lis331.o
obj-$(subst m,y,$(CONFIG_GPIO_PCA953X)) += platform_max7315.o
obj-$(subst m,y,$(CONFIG_INPUT_MPU3050)) += platform_mpu3050.o
obj-$(subst m,y,$(CONFIG_INPUT_BMA150)) += platform_bma023.o
obj-$(subst m,y,$(CONFIG_GPIO_PCA953X)) += platform_tca6416.o
obj-$(subst m,y,$(CONFIG_DRM_MEDFIELD)) += platform_tc35876x.o
# SPI Devices
obj-$(subst m,y,$(CONFIG_SERIAL_MRST_MAX3110)) += platform_max3111.o
# MISC Devices
obj-$(subst m,y,$(CONFIG_KEYBOARD_GPIO)) += platform_gpio_keys.o
obj-$(subst m,y,$(CONFIG_INTEL_MID_WATCHDOG)) += platform_wdt.o

View file

@ -0,0 +1,20 @@
/*
* platform_bma023.c: bma023 platform data initilization file
*
* (C) Copyright 2013 Intel Corporation
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#include <asm/intel-mid.h>
static const struct devs_id bma023_dev_id __initconst = {
.name = "bma023",
.type = SFI_DEV_TYPE_I2C,
.delay = 1,
};
sfi_device(bma023_dev_id);

View file

@ -0,0 +1,43 @@
/*
* platform_emc1403.c: emc1403 platform data initilization file
*
* (C) Copyright 2013 Intel Corporation
* Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#include <linux/init.h>
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <asm/intel-mid.h>
static void __init *emc1403_platform_data(void *info)
{
static short intr2nd_pdata;
struct i2c_board_info *i2c_info = info;
int intr = get_gpio_by_name("thermal_int");
int intr2nd = get_gpio_by_name("thermal_alert");
if (intr < 0)
return NULL;
if (intr2nd < 0)
return NULL;
i2c_info->irq = intr + INTEL_MID_IRQ_OFFSET;
intr2nd_pdata = intr2nd + INTEL_MID_IRQ_OFFSET;
return &intr2nd_pdata;
}
static const struct devs_id emc1403_dev_id __initconst = {
.name = "emc1403",
.type = SFI_DEV_TYPE_I2C,
.delay = 1,
.get_platform_data = &emc1403_platform_data,
};
sfi_device(emc1403_dev_id);

View file

@ -0,0 +1,83 @@
/*
* platform_gpio_keys.c: gpio_keys platform data initilization file
*
* (C) Copyright 2013 Intel Corporation
* Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#include <linux/input.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/gpio.h>
#include <linux/gpio_keys.h>
#include <linux/platform_device.h>
#include <asm/intel-mid.h>
#define DEVICE_NAME "gpio-keys"
/*
* we will search these buttons in SFI GPIO table (by name)
* and register them dynamically. Please add all possible
* buttons here, we will shrink them if no GPIO found.
*/
static struct gpio_keys_button gpio_button[] = {
{KEY_POWER, -1, 1, "power_btn", EV_KEY, 0, 3000},
{KEY_PROG1, -1, 1, "prog_btn1", EV_KEY, 0, 20},
{KEY_PROG2, -1, 1, "prog_btn2", EV_KEY, 0, 20},
{SW_LID, -1, 1, "lid_switch", EV_SW, 0, 20},
{KEY_VOLUMEUP, -1, 1, "vol_up", EV_KEY, 0, 20},
{KEY_VOLUMEDOWN, -1, 1, "vol_down", EV_KEY, 0, 20},
{KEY_CAMERA, -1, 1, "camera_full", EV_KEY, 0, 20},
{KEY_CAMERA_FOCUS, -1, 1, "camera_half", EV_KEY, 0, 20},
{SW_KEYPAD_SLIDE, -1, 1, "MagSw1", EV_SW, 0, 20},
{SW_KEYPAD_SLIDE, -1, 1, "MagSw2", EV_SW, 0, 20},
};
static struct gpio_keys_platform_data gpio_keys = {
.buttons = gpio_button,
.rep = 1,
.nbuttons = -1, /* will fill it after search */
};
static struct platform_device pb_device = {
.name = DEVICE_NAME,
.id = -1,
.dev = {
.platform_data = &gpio_keys,
},
};
/*
* Shrink the non-existent buttons, register the gpio button
* device if there is some
*/
static int __init pb_keys_init(void)
{
struct gpio_keys_button *gb = gpio_button;
int i, num, good = 0;
num = sizeof(gpio_button) / sizeof(struct gpio_keys_button);
for (i = 0; i < num; i++) {
gb[i].gpio = get_gpio_by_name(gb[i].desc);
pr_debug("info[%2d]: name = %s, gpio = %d\n", i, gb[i].desc,
gb[i].gpio);
if (gb[i].gpio < 0)
continue;
if (i != good)
gb[good] = gb[i];
good++;
}
if (good) {
gpio_keys.nbuttons = good;
return platform_device_register(&pb_device);
}
return 0;
}
late_initcall(pb_keys_init);

View file

@ -0,0 +1,68 @@
/*
* platform_ipc.c: IPC platform library file
*
* (C) Copyright 2013 Intel Corporation
* Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/sfi.h>
#include <linux/gpio.h>
#include <asm/intel-mid.h>
#include "platform_ipc.h"
void __init ipc_device_handler(struct sfi_device_table_entry *pentry,
struct devs_id *dev)
{
struct platform_device *pdev;
void *pdata = NULL;
static struct resource res __initdata = {
.name = "IRQ",
.flags = IORESOURCE_IRQ,
};
pr_debug("IPC bus, name = %16.16s, irq = 0x%2x\n",
pentry->name, pentry->irq);
/*
* We need to call platform init of IPC devices to fill misc_pdata
* structure. It will be used in msic_init for initialization.
*/
if (dev != NULL)
pdata = dev->get_platform_data(pentry);
/*
* On Medfield the platform device creation is handled by the MSIC
* MFD driver so we don't need to do it here.
*/
if (intel_mid_has_msic())
return;
pdev = platform_device_alloc(pentry->name, 0);
if (pdev == NULL) {
pr_err("out of memory for SFI platform device '%s'.\n",
pentry->name);
return;
}
res.start = pentry->irq;
platform_device_add_resources(pdev, &res, 1);
pdev->dev.platform_data = pdata;
intel_scu_device_register(pdev);
}
static const struct devs_id pmic_audio_dev_id __initconst = {
.name = "pmic_audio",
.type = SFI_DEV_TYPE_IPC,
.delay = 1,
.device_handler = &ipc_device_handler,
};
sfi_device(pmic_audio_dev_id);

View file

@ -0,0 +1,18 @@
/*
* platform_ipc.h: IPC platform library header file
*
* (C) Copyright 2013 Intel Corporation
* Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#ifndef _PLATFORM_IPC_H_
#define _PLATFORM_IPC_H_
void __init
ipc_device_handler(struct sfi_device_table_entry *pentry, struct devs_id *dev);
#endif

View file

@ -0,0 +1,41 @@
/*
* platform_lis331.c: lis331 platform data initilization file
*
* (C) Copyright 2013 Intel Corporation
* Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#include <linux/i2c.h>
#include <linux/gpio.h>
#include <asm/intel-mid.h>
static void __init *lis331dl_platform_data(void *info)
{
static short intr2nd_pdata;
struct i2c_board_info *i2c_info = info;
int intr = get_gpio_by_name("accel_int");
int intr2nd = get_gpio_by_name("accel_2");
if (intr < 0)
return NULL;
if (intr2nd < 0)
return NULL;
i2c_info->irq = intr + INTEL_MID_IRQ_OFFSET;
intr2nd_pdata = intr2nd + INTEL_MID_IRQ_OFFSET;
return &intr2nd_pdata;
}
static const struct devs_id lis331dl_dev_id __initconst = {
.name = "i2c_accel",
.type = SFI_DEV_TYPE_I2C,
.get_platform_data = &lis331dl_platform_data,
};
sfi_device(lis331dl_dev_id);

View file

@ -0,0 +1,35 @@
/*
* platform_max3111.c: max3111 platform data initilization file
*
* (C) Copyright 2013 Intel Corporation
* Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#include <linux/gpio.h>
#include <linux/spi/spi.h>
#include <asm/intel-mid.h>
static void __init *max3111_platform_data(void *info)
{
struct spi_board_info *spi_info = info;
int intr = get_gpio_by_name("max3111_int");
spi_info->mode = SPI_MODE_0;
if (intr == -1)
return NULL;
spi_info->irq = intr + INTEL_MID_IRQ_OFFSET;
return NULL;
}
static const struct devs_id max3111_dev_id __initconst = {
.name = "spi_max3111",
.type = SFI_DEV_TYPE_SPI,
.get_platform_data = &max3111_platform_data,
};
sfi_device(max3111_dev_id);

View file

@ -0,0 +1,79 @@
/*
* platform_max7315.c: max7315 platform data initilization file
*
* (C) Copyright 2013 Intel Corporation
* Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#include <linux/init.h>
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/platform_data/pca953x.h>
#include <asm/intel-mid.h>
#define MAX7315_NUM 2
static void __init *max7315_platform_data(void *info)
{
static struct pca953x_platform_data max7315_pdata[MAX7315_NUM];
static int nr;
struct pca953x_platform_data *max7315 = &max7315_pdata[nr];
struct i2c_board_info *i2c_info = info;
int gpio_base, intr;
char base_pin_name[SFI_NAME_LEN + 1];
char intr_pin_name[SFI_NAME_LEN + 1];
if (nr == MAX7315_NUM) {
pr_err("too many max7315s, we only support %d\n",
MAX7315_NUM);
return NULL;
}
/* we have several max7315 on the board, we only need load several
* instances of the same pca953x driver to cover them
*/
strcpy(i2c_info->type, "max7315");
if (nr++) {
sprintf(base_pin_name, "max7315_%d_base", nr);
sprintf(intr_pin_name, "max7315_%d_int", nr);
} else {
strcpy(base_pin_name, "max7315_base");
strcpy(intr_pin_name, "max7315_int");
}
gpio_base = get_gpio_by_name(base_pin_name);
intr = get_gpio_by_name(intr_pin_name);
if (gpio_base < 0)
return NULL;
max7315->gpio_base = gpio_base;
if (intr != -1) {
i2c_info->irq = intr + INTEL_MID_IRQ_OFFSET;
max7315->irq_base = gpio_base + INTEL_MID_IRQ_OFFSET;
} else {
i2c_info->irq = -1;
max7315->irq_base = -1;
}
return max7315;
}
static const struct devs_id max7315_dev_id __initconst = {
.name = "i2c_max7315",
.type = SFI_DEV_TYPE_I2C,
.delay = 1,
.get_platform_data = &max7315_platform_data,
};
static const struct devs_id max7315_2_dev_id __initconst = {
.name = "i2c_max7315_2",
.type = SFI_DEV_TYPE_I2C,
.delay = 1,
.get_platform_data = &max7315_platform_data,
};
sfi_device(max7315_dev_id);
sfi_device(max7315_2_dev_id);

View file

@ -0,0 +1,36 @@
/*
* platform_mpu3050.c: mpu3050 platform data initilization file
*
* (C) Copyright 2013 Intel Corporation
* Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <asm/intel-mid.h>
static void *mpu3050_platform_data(void *info)
{
struct i2c_board_info *i2c_info = info;
int intr = get_gpio_by_name("mpu3050_int");
if (intr < 0)
return NULL;
i2c_info->irq = intr + INTEL_MID_IRQ_OFFSET;
return NULL;
}
static const struct devs_id mpu3050_dev_id __initconst = {
.name = "mpu3050",
.type = SFI_DEV_TYPE_I2C,
.delay = 1,
.get_platform_data = &mpu3050_platform_data,
};
sfi_device(mpu3050_dev_id);

View file

@ -0,0 +1,87 @@
/*
* platform_msic.c: MSIC platform data initilization file
*
* (C) Copyright 2013 Intel Corporation
* Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/scatterlist.h>
#include <linux/init.h>
#include <linux/sfi.h>
#include <linux/mfd/intel_msic.h>
#include <asm/intel_scu_ipc.h>
#include <asm/intel-mid.h>
#include "platform_msic.h"
struct intel_msic_platform_data msic_pdata;
static struct resource msic_resources[] = {
{
.start = INTEL_MSIC_IRQ_PHYS_BASE,
.end = INTEL_MSIC_IRQ_PHYS_BASE + 64 - 1,
.flags = IORESOURCE_MEM,
},
};
static struct platform_device msic_device = {
.name = "intel_msic",
.id = -1,
.dev = {
.platform_data = &msic_pdata,
},
.num_resources = ARRAY_SIZE(msic_resources),
.resource = msic_resources,
};
static int msic_scu_status_change(struct notifier_block *nb,
unsigned long code, void *data)
{
if (code == SCU_DOWN) {
platform_device_unregister(&msic_device);
return 0;
}
return platform_device_register(&msic_device);
}
static int __init msic_init(void)
{
static struct notifier_block msic_scu_notifier = {
.notifier_call = msic_scu_status_change,
};
/*
* We need to be sure that the SCU IPC is ready before MSIC device
* can be registered.
*/
if (intel_mid_has_msic())
intel_scu_notifier_add(&msic_scu_notifier);
return 0;
}
arch_initcall(msic_init);
/*
* msic_generic_platform_data - sets generic platform data for the block
* @info: pointer to the SFI device table entry for this block
* @block: MSIC block
*
* Function sets IRQ number from the SFI table entry for given device to
* the MSIC platform data.
*/
void *msic_generic_platform_data(void *info, enum intel_msic_block block)
{
struct sfi_device_table_entry *entry = info;
BUG_ON(block < 0 || block >= INTEL_MSIC_BLOCK_LAST);
msic_pdata.irq[block] = entry->irq;
return NULL;
}

View file

@ -0,0 +1,19 @@
/*
* platform_msic.h: MSIC platform data header file
*
* (C) Copyright 2013 Intel Corporation
* Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#ifndef _PLATFORM_MSIC_H_
#define _PLATFORM_MSIC_H_
extern struct intel_msic_platform_data msic_pdata;
void *msic_generic_platform_data(void *info, enum intel_msic_block block);
#endif

View file

@ -0,0 +1,47 @@
/*
* platform_msic_audio.c: MSIC audio platform data initilization file
*
* (C) Copyright 2013 Intel Corporation
* Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/scatterlist.h>
#include <linux/init.h>
#include <linux/sfi.h>
#include <linux/platform_device.h>
#include <linux/mfd/intel_msic.h>
#include <asm/intel-mid.h>
#include "platform_msic.h"
#include "platform_ipc.h"
static void *msic_audio_platform_data(void *info)
{
struct platform_device *pdev;
pdev = platform_device_register_simple("sst-platform", -1, NULL, 0);
if (IS_ERR(pdev)) {
pr_err("failed to create audio platform device\n");
return NULL;
}
return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_AUDIO);
}
static const struct devs_id msic_audio_dev_id __initconst = {
.name = "msic_audio",
.type = SFI_DEV_TYPE_IPC,
.delay = 1,
.get_platform_data = &msic_audio_platform_data,
.device_handler = &ipc_device_handler,
};
sfi_device(msic_audio_dev_id);

View file

@ -0,0 +1,37 @@
/*
* platform_msic_battery.c: MSIC battery platform data initilization file
*
* (C) Copyright 2013 Intel Corporation
* Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/scatterlist.h>
#include <linux/init.h>
#include <linux/sfi.h>
#include <linux/mfd/intel_msic.h>
#include <asm/intel-mid.h>
#include "platform_msic.h"
#include "platform_ipc.h"
static void __init *msic_battery_platform_data(void *info)
{
return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_BATTERY);
}
static const struct devs_id msic_battery_dev_id __initconst = {
.name = "msic_battery",
.type = SFI_DEV_TYPE_IPC,
.delay = 1,
.get_platform_data = &msic_battery_platform_data,
.device_handler = &ipc_device_handler,
};
sfi_device(msic_battery_dev_id);

View file

@ -0,0 +1,48 @@
/*
* platform_msic_gpio.c: MSIC GPIO platform data initilization file
*
* (C) Copyright 2013 Intel Corporation
* Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/scatterlist.h>
#include <linux/sfi.h>
#include <linux/init.h>
#include <linux/gpio.h>
#include <linux/mfd/intel_msic.h>
#include <asm/intel-mid.h>
#include "platform_msic.h"
#include "platform_ipc.h"
static void __init *msic_gpio_platform_data(void *info)
{
static struct intel_msic_gpio_pdata msic_gpio_pdata;
int gpio = get_gpio_by_name("msic_gpio_base");
if (gpio < 0)
return NULL;
msic_gpio_pdata.gpio_base = gpio;
msic_pdata.gpio = &msic_gpio_pdata;
return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_GPIO);
}
static const struct devs_id msic_gpio_dev_id __initconst = {
.name = "msic_gpio",
.type = SFI_DEV_TYPE_IPC,
.delay = 1,
.get_platform_data = &msic_gpio_platform_data,
.device_handler = &ipc_device_handler,
};
sfi_device(msic_gpio_dev_id);

View file

@ -0,0 +1,49 @@
/*
* platform_msic_ocd.c: MSIC OCD platform data initilization file
*
* (C) Copyright 2013 Intel Corporation
* Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/scatterlist.h>
#include <linux/sfi.h>
#include <linux/init.h>
#include <linux/gpio.h>
#include <linux/mfd/intel_msic.h>
#include <asm/intel-mid.h>
#include "platform_msic.h"
#include "platform_ipc.h"
static void __init *msic_ocd_platform_data(void *info)
{
static struct intel_msic_ocd_pdata msic_ocd_pdata;
int gpio;
gpio = get_gpio_by_name("ocd_gpio");
if (gpio < 0)
return NULL;
msic_ocd_pdata.gpio = gpio;
msic_pdata.ocd = &msic_ocd_pdata;
return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_OCD);
}
static const struct devs_id msic_ocd_dev_id __initconst = {
.name = "msic_ocd",
.type = SFI_DEV_TYPE_IPC,
.delay = 1,
.get_platform_data = &msic_ocd_platform_data,
.device_handler = &ipc_device_handler,
};
sfi_device(msic_ocd_dev_id);

View file

@ -0,0 +1,36 @@
/*
* platform_msic_power_btn.c: MSIC power btn platform data initilization file
*
* (C) Copyright 2013 Intel Corporation
* Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/scatterlist.h>
#include <linux/sfi.h>
#include <linux/init.h>
#include <linux/mfd/intel_msic.h>
#include <asm/intel-mid.h>
#include "platform_msic.h"
#include "platform_ipc.h"
static void __init *msic_power_btn_platform_data(void *info)
{
return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_POWER_BTN);
}
static const struct devs_id msic_power_btn_dev_id __initconst = {
.name = "msic_power_btn",
.type = SFI_DEV_TYPE_IPC,
.delay = 1,
.get_platform_data = &msic_power_btn_platform_data,
.device_handler = &ipc_device_handler,
};
sfi_device(msic_power_btn_dev_id);

View file

@ -0,0 +1,37 @@
/*
* platform_msic_thermal.c: msic_thermal platform data initilization file
*
* (C) Copyright 2013 Intel Corporation
* Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#include <linux/input.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/gpio.h>
#include <linux/platform_device.h>
#include <linux/mfd/intel_msic.h>
#include <asm/intel-mid.h>
#include "platform_msic.h"
#include "platform_ipc.h"
static void __init *msic_thermal_platform_data(void *info)
{
return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_THERMAL);
}
static const struct devs_id msic_thermal_dev_id __initconst = {
.name = "msic_thermal",
.type = SFI_DEV_TYPE_IPC,
.delay = 1,
.get_platform_data = &msic_thermal_platform_data,
.device_handler = &ipc_device_handler,
};
sfi_device(msic_thermal_dev_id);

View file

@ -0,0 +1,54 @@
/*
* platform_pmic_gpio.c: PMIC GPIO platform data initilization file
*
* (C) Copyright 2013 Intel Corporation
* Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/scatterlist.h>
#include <linux/gpio.h>
#include <linux/init.h>
#include <linux/sfi.h>
#include <linux/intel_pmic_gpio.h>
#include <asm/intel-mid.h>
#include "platform_ipc.h"
static void __init *pmic_gpio_platform_data(void *info)
{
static struct intel_pmic_gpio_platform_data pmic_gpio_pdata;
int gpio_base = get_gpio_by_name("pmic_gpio_base");
if (gpio_base < 0)
gpio_base = 64;
pmic_gpio_pdata.gpio_base = gpio_base;
pmic_gpio_pdata.irq_base = gpio_base + INTEL_MID_IRQ_OFFSET;
pmic_gpio_pdata.gpiointr = 0xffffeff8;
return &pmic_gpio_pdata;
}
static const struct devs_id pmic_gpio_spi_dev_id __initconst = {
.name = "pmic_gpio",
.type = SFI_DEV_TYPE_SPI,
.delay = 1,
.get_platform_data = &pmic_gpio_platform_data,
};
static const struct devs_id pmic_gpio_ipc_dev_id __initconst = {
.name = "pmic_gpio",
.type = SFI_DEV_TYPE_IPC,
.delay = 1,
.get_platform_data = &pmic_gpio_platform_data,
.device_handler = &ipc_device_handler
};
sfi_device(pmic_gpio_spi_dev_id);
sfi_device(pmic_gpio_ipc_dev_id);

View file

@ -0,0 +1,36 @@
/*
* platform_tc35876x.c: tc35876x platform data initilization file
*
* (C) Copyright 2013 Intel Corporation
* Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#include <linux/gpio.h>
#include <linux/i2c/tc35876x.h>
#include <asm/intel-mid.h>
/*tc35876x DSI_LVDS bridge chip and panel platform data*/
static void *tc35876x_platform_data(void *data)
{
static struct tc35876x_platform_data pdata;
/* gpio pins set to -1 will not be used by the driver */
pdata.gpio_bridge_reset = get_gpio_by_name("LCMB_RXEN");
pdata.gpio_panel_bl_en = get_gpio_by_name("6S6P_BL_EN");
pdata.gpio_panel_vadd = get_gpio_by_name("EN_VREG_LCD_V3P3");
return &pdata;
}
static const struct devs_id tc35876x_dev_id __initconst = {
.name = "i2c_disp_brig",
.type = SFI_DEV_TYPE_I2C,
.get_platform_data = &tc35876x_platform_data,
};
sfi_device(tc35876x_dev_id);

View file

@ -0,0 +1,57 @@
/*
* platform_tca6416.c: tca6416 platform data initilization file
*
* (C) Copyright 2013 Intel Corporation
* Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#include <linux/platform_data/pca953x.h>
#include <linux/i2c.h>
#include <linux/gpio.h>
#include <asm/intel-mid.h>
#define TCA6416_NAME "tca6416"
#define TCA6416_BASE "tca6416_base"
#define TCA6416_INTR "tca6416_int"
static void *tca6416_platform_data(void *info)
{
static struct pca953x_platform_data tca6416;
struct i2c_board_info *i2c_info = info;
int gpio_base, intr;
char base_pin_name[SFI_NAME_LEN + 1];
char intr_pin_name[SFI_NAME_LEN + 1];
strcpy(i2c_info->type, TCA6416_NAME);
strcpy(base_pin_name, TCA6416_BASE);
strcpy(intr_pin_name, TCA6416_INTR);
gpio_base = get_gpio_by_name(base_pin_name);
intr = get_gpio_by_name(intr_pin_name);
if (gpio_base < 0)
return NULL;
tca6416.gpio_base = gpio_base;
if (intr >= 0) {
i2c_info->irq = intr + INTEL_MID_IRQ_OFFSET;
tca6416.irq_base = gpio_base + INTEL_MID_IRQ_OFFSET;
} else {
i2c_info->irq = -1;
tca6416.irq_base = -1;
}
return &tca6416;
}
static const struct devs_id tca6416_dev_id __initconst = {
.name = "tca6416",
.type = SFI_DEV_TYPE_I2C,
.delay = 1,
.get_platform_data = &tca6416_platform_data,
};
sfi_device(tca6416_dev_id);

View file

@ -0,0 +1,62 @@
/*
* platform_wdt.c: Watchdog platform library file
*
* (C) Copyright 2014 Intel Corporation
* Author: David Cohen <david.a.cohen@linux.intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/platform_data/intel-mid_wdt.h>
#include <asm/intel-mid.h>
#include <asm/io_apic.h>
#define TANGIER_EXT_TIMER0_MSI 15
static struct platform_device wdt_dev = {
.name = "intel_mid_wdt",
.id = -1,
};
static int tangier_probe(struct platform_device *pdev)
{
int gsi;
struct intel_mid_wdt_pdata *pdata = pdev->dev.platform_data;
if (!pdata)
return -EINVAL;
/* IOAPIC builds identity mapping between GSI and IRQ on MID */
gsi = pdata->irq;
if (mp_set_gsi_attr(gsi, 1, 0, cpu_to_node(0)) ||
mp_map_gsi_to_irq(gsi, IOAPIC_MAP_ALLOC) <= 0) {
dev_warn(&pdev->dev, "cannot find interrupt %d in ioapic\n",
gsi);
return -EINVAL;
}
return 0;
}
static struct intel_mid_wdt_pdata tangier_pdata = {
.irq = TANGIER_EXT_TIMER0_MSI,
.probe = tangier_probe,
};
static int __init register_mid_wdt(void)
{
if (intel_mid_identify_cpu() == INTEL_MID_CPU_CHIP_TANGIER) {
wdt_dev.dev.platform_data = &tangier_pdata;
return platform_device_register(&wdt_dev);
}
return -ENODEV;
}
rootfs_initcall(register_mid_wdt);

View file

@ -0,0 +1,324 @@
/*
* early_printk_intel_mid.c - early consoles for Intel MID platforms
*
* Copyright (c) 2008-2010, Intel Corporation
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
/*
* This file implements two early consoles named mrst and hsu.
* mrst is based on Maxim3110 spi-uart device, it exists in both
* Moorestown and Medfield platforms, while hsu is based on a High
* Speed UART device which only exists in the Medfield platform
*/
#include <linux/serial_reg.h>
#include <linux/serial_mfd.h>
#include <linux/kmsg_dump.h>
#include <linux/console.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <asm/fixmap.h>
#include <asm/pgtable.h>
#include <asm/intel-mid.h>
#define MRST_SPI_TIMEOUT 0x200000
#define MRST_REGBASE_SPI0 0xff128000
#define MRST_REGBASE_SPI1 0xff128400
#define MRST_CLK_SPI0_REG 0xff11d86c
/* Bit fields in CTRLR0 */
#define SPI_DFS_OFFSET 0
#define SPI_FRF_OFFSET 4
#define SPI_FRF_SPI 0x0
#define SPI_FRF_SSP 0x1
#define SPI_FRF_MICROWIRE 0x2
#define SPI_FRF_RESV 0x3
#define SPI_MODE_OFFSET 6
#define SPI_SCPH_OFFSET 6
#define SPI_SCOL_OFFSET 7
#define SPI_TMOD_OFFSET 8
#define SPI_TMOD_TR 0x0 /* xmit & recv */
#define SPI_TMOD_TO 0x1 /* xmit only */
#define SPI_TMOD_RO 0x2 /* recv only */
#define SPI_TMOD_EPROMREAD 0x3 /* eeprom read mode */
#define SPI_SLVOE_OFFSET 10
#define SPI_SRL_OFFSET 11
#define SPI_CFS_OFFSET 12
/* Bit fields in SR, 7 bits */
#define SR_MASK 0x7f /* cover 7 bits */
#define SR_BUSY (1 << 0)
#define SR_TF_NOT_FULL (1 << 1)
#define SR_TF_EMPT (1 << 2)
#define SR_RF_NOT_EMPT (1 << 3)
#define SR_RF_FULL (1 << 4)
#define SR_TX_ERR (1 << 5)
#define SR_DCOL (1 << 6)
struct dw_spi_reg {
u32 ctrl0;
u32 ctrl1;
u32 ssienr;
u32 mwcr;
u32 ser;
u32 baudr;
u32 txfltr;
u32 rxfltr;
u32 txflr;
u32 rxflr;
u32 sr;
u32 imr;
u32 isr;
u32 risr;
u32 txoicr;
u32 rxoicr;
u32 rxuicr;
u32 msticr;
u32 icr;
u32 dmacr;
u32 dmatdlr;
u32 dmardlr;
u32 idr;
u32 version;
/* Currently operates as 32 bits, though only the low 16 bits matter */
u32 dr;
} __packed;
#define dw_readl(dw, name) __raw_readl(&(dw)->name)
#define dw_writel(dw, name, val) __raw_writel((val), &(dw)->name)
/* Default use SPI0 register for mrst, we will detect Penwell and use SPI1 */
static unsigned long mrst_spi_paddr = MRST_REGBASE_SPI0;
static u32 *pclk_spi0;
/* Always contains an accessible address, start with 0 */
static struct dw_spi_reg *pspi;
static struct kmsg_dumper dw_dumper;
static int dumper_registered;
static void dw_kmsg_dump(struct kmsg_dumper *dumper,
enum kmsg_dump_reason reason)
{
static char line[1024];
size_t len;
/* When run to this, we'd better re-init the HW */
mrst_early_console_init();
while (kmsg_dump_get_line(dumper, true, line, sizeof(line), &len))
early_mrst_console.write(&early_mrst_console, line, len);
}
/* Set the ratio rate to 115200, 8n1, IRQ disabled */
static void max3110_write_config(void)
{
u16 config;
config = 0xc001;
dw_writel(pspi, dr, config);
}
/* Translate char to a eligible word and send to max3110 */
static void max3110_write_data(char c)
{
u16 data;
data = 0x8000 | c;
dw_writel(pspi, dr, data);
}
void mrst_early_console_init(void)
{
u32 ctrlr0 = 0;
u32 spi0_cdiv;
u32 freq; /* Freqency info only need be searched once */
/* Base clk is 100 MHz, the actual clk = 100M / (clk_divider + 1) */
pclk_spi0 = (void *)set_fixmap_offset_nocache(FIX_EARLYCON_MEM_BASE,
MRST_CLK_SPI0_REG);
spi0_cdiv = ((*pclk_spi0) & 0xe00) >> 9;
freq = 100000000 / (spi0_cdiv + 1);
if (intel_mid_identify_cpu() == INTEL_MID_CPU_CHIP_PENWELL)
mrst_spi_paddr = MRST_REGBASE_SPI1;
pspi = (void *)set_fixmap_offset_nocache(FIX_EARLYCON_MEM_BASE,
mrst_spi_paddr);
/* Disable SPI controller */
dw_writel(pspi, ssienr, 0);
/* Set control param, 8 bits, transmit only mode */
ctrlr0 = dw_readl(pspi, ctrl0);
ctrlr0 &= 0xfcc0;
ctrlr0 |= 0xf | (SPI_FRF_SPI << SPI_FRF_OFFSET)
| (SPI_TMOD_TO << SPI_TMOD_OFFSET);
dw_writel(pspi, ctrl0, ctrlr0);
/*
* Change the spi0 clk to comply with 115200 bps, use 100000 to
* calculate the clk dividor to make the clock a little slower
* than real baud rate.
*/
dw_writel(pspi, baudr, freq/100000);
/* Disable all INT for early phase */
dw_writel(pspi, imr, 0x0);
/* Set the cs to spi-uart */
dw_writel(pspi, ser, 0x2);
/* Enable the HW, the last step for HW init */
dw_writel(pspi, ssienr, 0x1);
/* Set the default configuration */
max3110_write_config();
/* Register the kmsg dumper */
if (!dumper_registered) {
dw_dumper.dump = dw_kmsg_dump;
kmsg_dump_register(&dw_dumper);
dumper_registered = 1;
}
}
/* Slave select should be called in the read/write function */
static void early_mrst_spi_putc(char c)
{
unsigned int timeout;
u32 sr;
timeout = MRST_SPI_TIMEOUT;
/* Early putc needs to make sure the TX FIFO is not full */
while (--timeout) {
sr = dw_readl(pspi, sr);
if (!(sr & SR_TF_NOT_FULL))
cpu_relax();
else
break;
}
if (!timeout)
pr_warn("MRST earlycon: timed out\n");
else
max3110_write_data(c);
}
/* Early SPI only uses polling mode */
static void early_mrst_spi_write(struct console *con, const char *str,
unsigned n)
{
int i;
for (i = 0; i < n && *str; i++) {
if (*str == '\n')
early_mrst_spi_putc('\r');
early_mrst_spi_putc(*str);
str++;
}
}
struct console early_mrst_console = {
.name = "earlymrst",
.write = early_mrst_spi_write,
.flags = CON_PRINTBUFFER,
.index = -1,
};
/*
* Following is the early console based on Medfield HSU (High
* Speed UART) device.
*/
#define HSU_PORT_BASE 0xffa28080
static void __iomem *phsu;
void hsu_early_console_init(const char *s)
{
unsigned long paddr, port = 0;
u8 lcr;
/*
* Select the early HSU console port if specified by user in the
* kernel command line.
*/
if (*s && !kstrtoul(s, 10, &port))
port = clamp_val(port, 0, 2);
paddr = HSU_PORT_BASE + port * 0x80;
phsu = (void *)set_fixmap_offset_nocache(FIX_EARLYCON_MEM_BASE, paddr);
/* Disable FIFO */
writeb(0x0, phsu + UART_FCR);
/* Set to default 115200 bps, 8n1 */
lcr = readb(phsu + UART_LCR);
writeb((0x80 | lcr), phsu + UART_LCR);
writeb(0x18, phsu + UART_DLL);
writeb(lcr, phsu + UART_LCR);
writel(0x3600, phsu + UART_MUL*4);
writeb(0x8, phsu + UART_MCR);
writeb(0x7, phsu + UART_FCR);
writeb(0x3, phsu + UART_LCR);
/* Clear IRQ status */
readb(phsu + UART_LSR);
readb(phsu + UART_RX);
readb(phsu + UART_IIR);
readb(phsu + UART_MSR);
/* Enable FIFO */
writeb(0x7, phsu + UART_FCR);
}
#define BOTH_EMPTY (UART_LSR_TEMT | UART_LSR_THRE)
static void early_hsu_putc(char ch)
{
unsigned int timeout = 10000; /* 10ms */
u8 status;
while (--timeout) {
status = readb(phsu + UART_LSR);
if (status & BOTH_EMPTY)
break;
udelay(1);
}
/* Only write the char when there was no timeout */
if (timeout)
writeb(ch, phsu + UART_TX);
}
static void early_hsu_write(struct console *con, const char *str, unsigned n)
{
int i;
for (i = 0; i < n && *str; i++) {
if (*str == '\n')
early_hsu_putc('\r');
early_hsu_putc(*str);
str++;
}
}
struct console early_hsu_console = {
.name = "earlyhsu",
.write = early_hsu_write,
.flags = CON_PRINTBUFFER,
.index = -1,
};

View file

@ -0,0 +1,217 @@
/*
* intel-mid.c: Intel MID platform setup code
*
* (C) Copyright 2008, 2012 Intel Corporation
* Author: Jacob Pan (jacob.jun.pan@intel.com)
* Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#define pr_fmt(fmt) "intel_mid: " fmt
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/scatterlist.h>
#include <linux/sfi.h>
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <asm/setup.h>
#include <asm/mpspec_def.h>
#include <asm/hw_irq.h>
#include <asm/apic.h>
#include <asm/io_apic.h>
#include <asm/intel-mid.h>
#include <asm/intel_mid_vrtc.h>
#include <asm/io.h>
#include <asm/i8259.h>
#include <asm/intel_scu_ipc.h>
#include <asm/apb_timer.h>
#include <asm/reboot.h>
#include "intel_mid_weak_decls.h"
/*
* the clockevent devices on Moorestown/Medfield can be APBT or LAPIC clock,
* cmdline option x86_intel_mid_timer can be used to override the configuration
* to prefer one or the other.
* at runtime, there are basically three timer configurations:
* 1. per cpu apbt clock only
* 2. per cpu always-on lapic clocks only, this is Penwell/Medfield only
* 3. per cpu lapic clock (C3STOP) and one apbt clock, with broadcast.
*
* by default (without cmdline option), platform code first detects cpu type
* to see if we are on lincroft or penwell, then set up both lapic or apbt
* clocks accordingly.
* i.e. by default, medfield uses configuration #2, moorestown uses #1.
* config #3 is supported but not recommended on medfield.
*
* rating and feature summary:
* lapic (with C3STOP) --------- 100
* apbt (always-on) ------------ 110
* lapic (always-on,ARAT) ------ 150
*/
enum intel_mid_timer_options intel_mid_timer_options;
/* intel_mid_ops to store sub arch ops */
struct intel_mid_ops *intel_mid_ops;
/* getter function for sub arch ops*/
static void *(*get_intel_mid_ops[])(void) = INTEL_MID_OPS_INIT;
enum intel_mid_cpu_type __intel_mid_cpu_chip;
EXPORT_SYMBOL_GPL(__intel_mid_cpu_chip);
static void intel_mid_power_off(void)
{
};
static void intel_mid_reboot(void)
{
intel_scu_ipc_simple_command(IPCMSG_COLD_BOOT, 0);
}
static unsigned long __init intel_mid_calibrate_tsc(void)
{
return 0;
}
static void __init intel_mid_time_init(void)
{
sfi_table_parse(SFI_SIG_MTMR, NULL, NULL, sfi_parse_mtmr);
switch (intel_mid_timer_options) {
case INTEL_MID_TIMER_APBT_ONLY:
break;
case INTEL_MID_TIMER_LAPIC_APBT:
x86_init.timers.setup_percpu_clockev = setup_boot_APIC_clock;
x86_cpuinit.setup_percpu_clockev = setup_secondary_APIC_clock;
break;
default:
if (!boot_cpu_has(X86_FEATURE_ARAT))
break;
x86_init.timers.setup_percpu_clockev = setup_boot_APIC_clock;
x86_cpuinit.setup_percpu_clockev = setup_secondary_APIC_clock;
return;
}
/* we need at least one APB timer */
pre_init_apic_IRQ0();
apbt_time_init();
}
static void intel_mid_arch_setup(void)
{
if (boot_cpu_data.x86 != 6) {
pr_err("Unknown Intel MID CPU (%d:%d), default to Penwell\n",
boot_cpu_data.x86, boot_cpu_data.x86_model);
__intel_mid_cpu_chip = INTEL_MID_CPU_CHIP_PENWELL;
goto out;
}
switch (boot_cpu_data.x86_model) {
case 0x35:
__intel_mid_cpu_chip = INTEL_MID_CPU_CHIP_CLOVERVIEW;
break;
case 0x3C:
case 0x4A:
__intel_mid_cpu_chip = INTEL_MID_CPU_CHIP_TANGIER;
break;
case 0x27:
default:
__intel_mid_cpu_chip = INTEL_MID_CPU_CHIP_PENWELL;
break;
}
if (__intel_mid_cpu_chip < MAX_CPU_OPS(get_intel_mid_ops))
intel_mid_ops = get_intel_mid_ops[__intel_mid_cpu_chip]();
else {
intel_mid_ops = get_intel_mid_ops[INTEL_MID_CPU_CHIP_PENWELL]();
pr_info("ARCH: Uknown SoC, assuming PENWELL!\n");
}
out:
if (intel_mid_ops->arch_setup)
intel_mid_ops->arch_setup();
}
/* MID systems don't have i8042 controller */
static int intel_mid_i8042_detect(void)
{
return 0;
}
/*
* Moorestown does not have external NMI source nor port 0x61 to report
* NMI status. The possible NMI sources are from pmu as a result of NMI
* watchdog or lock debug. Reading io port 0x61 results in 0xff which
* misled NMI handler.
*/
static unsigned char intel_mid_get_nmi_reason(void)
{
return 0;
}
/*
* Moorestown specific x86_init function overrides and early setup
* calls.
*/
void __init x86_intel_mid_early_setup(void)
{
x86_init.resources.probe_roms = x86_init_noop;
x86_init.resources.reserve_resources = x86_init_noop;
x86_init.timers.timer_init = intel_mid_time_init;
x86_init.timers.setup_percpu_clockev = x86_init_noop;
x86_init.irqs.pre_vector_init = x86_init_noop;
x86_init.oem.arch_setup = intel_mid_arch_setup;
x86_cpuinit.setup_percpu_clockev = apbt_setup_secondary_clock;
x86_platform.calibrate_tsc = intel_mid_calibrate_tsc;
x86_platform.i8042_detect = intel_mid_i8042_detect;
x86_init.timers.wallclock_init = intel_mid_rtc_init;
x86_platform.get_nmi_reason = intel_mid_get_nmi_reason;
x86_init.pci.init = intel_mid_pci_init;
x86_init.pci.fixup_irqs = x86_init_noop;
legacy_pic = &null_legacy_pic;
pm_power_off = intel_mid_power_off;
machine_ops.emergency_restart = intel_mid_reboot;
/* Avoid searching for BIOS MP tables */
x86_init.mpparse.find_smp_config = x86_init_noop;
x86_init.mpparse.get_smp_config = x86_init_uint_noop;
set_bit(MP_BUS_ISA, mp_bus_not_pci);
}
/*
* if user does not want to use per CPU apb timer, just give it a lower rating
* than local apic timer and skip the late per cpu timer init.
*/
static inline int __init setup_x86_intel_mid_timer(char *arg)
{
if (!arg)
return -EINVAL;
if (strcmp("apbt_only", arg) == 0)
intel_mid_timer_options = INTEL_MID_TIMER_APBT_ONLY;
else if (strcmp("lapic_and_apbt", arg) == 0)
intel_mid_timer_options = INTEL_MID_TIMER_LAPIC_APBT;
else {
pr_warn("X86 INTEL_MID timer option %s not recognised"
" use x86_intel_mid_timer=apbt_only or lapic_and_apbt\n",
arg);
return -EINVAL;
}
return 0;
}
__setup("x86_intel_mid_timer=", setup_x86_intel_mid_timer);

View file

@ -0,0 +1,177 @@
/*
* intel_mid_vrtc.c: Driver for virtual RTC device on Intel MID platform
*
* (C) Copyright 2009 Intel Corporation
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*
* Note:
* VRTC is emulated by system controller firmware, the real HW
* RTC is located in the PMIC device. SCU FW shadows PMIC RTC
* in a memory mapped IO space that is visible to the host IA
* processor.
*
* This driver is based on RTC CMOS driver.
*/
#include <linux/kernel.h>
#include <linux/export.h>
#include <linux/init.h>
#include <linux/sfi.h>
#include <linux/platform_device.h>
#include <asm/intel-mid.h>
#include <asm/intel_mid_vrtc.h>
#include <asm/time.h>
#include <asm/fixmap.h>
static unsigned char __iomem *vrtc_virt_base;
unsigned char vrtc_cmos_read(unsigned char reg)
{
unsigned char retval;
/* vRTC's registers range from 0x0 to 0xD */
if (reg > 0xd || !vrtc_virt_base)
return 0xff;
lock_cmos_prefix(reg);
retval = __raw_readb(vrtc_virt_base + (reg << 2));
lock_cmos_suffix(reg);
return retval;
}
EXPORT_SYMBOL_GPL(vrtc_cmos_read);
void vrtc_cmos_write(unsigned char val, unsigned char reg)
{
if (reg > 0xd || !vrtc_virt_base)
return;
lock_cmos_prefix(reg);
__raw_writeb(val, vrtc_virt_base + (reg << 2));
lock_cmos_suffix(reg);
}
EXPORT_SYMBOL_GPL(vrtc_cmos_write);
void vrtc_get_time(struct timespec *now)
{
u8 sec, min, hour, mday, mon;
unsigned long flags;
u32 year;
spin_lock_irqsave(&rtc_lock, flags);
while ((vrtc_cmos_read(RTC_FREQ_SELECT) & RTC_UIP))
cpu_relax();
sec = vrtc_cmos_read(RTC_SECONDS);
min = vrtc_cmos_read(RTC_MINUTES);
hour = vrtc_cmos_read(RTC_HOURS);
mday = vrtc_cmos_read(RTC_DAY_OF_MONTH);
mon = vrtc_cmos_read(RTC_MONTH);
year = vrtc_cmos_read(RTC_YEAR);
spin_unlock_irqrestore(&rtc_lock, flags);
/* vRTC YEAR reg contains the offset to 1972 */
year += 1972;
pr_info("vRTC: sec: %d min: %d hour: %d day: %d "
"mon: %d year: %d\n", sec, min, hour, mday, mon, year);
now->tv_sec = mktime(year, mon, mday, hour, min, sec);
now->tv_nsec = 0;
}
int vrtc_set_mmss(const struct timespec *now)
{
unsigned long flags;
struct rtc_time tm;
int year;
int retval = 0;
rtc_time_to_tm(now->tv_sec, &tm);
if (!rtc_valid_tm(&tm) && tm.tm_year >= 72) {
/*
* tm.year is the number of years since 1900, and the
* vrtc need the years since 1972.
*/
year = tm.tm_year - 72;
spin_lock_irqsave(&rtc_lock, flags);
vrtc_cmos_write(year, RTC_YEAR);
vrtc_cmos_write(tm.tm_mon, RTC_MONTH);
vrtc_cmos_write(tm.tm_mday, RTC_DAY_OF_MONTH);
vrtc_cmos_write(tm.tm_hour, RTC_HOURS);
vrtc_cmos_write(tm.tm_min, RTC_MINUTES);
vrtc_cmos_write(tm.tm_sec, RTC_SECONDS);
spin_unlock_irqrestore(&rtc_lock, flags);
} else {
pr_err("%s: Invalid vRTC value: write of %lx to vRTC failed\n",
__FUNCTION__, now->tv_sec);
retval = -EINVAL;
}
return retval;
}
void __init intel_mid_rtc_init(void)
{
unsigned long vrtc_paddr;
sfi_table_parse(SFI_SIG_MRTC, NULL, NULL, sfi_parse_mrtc);
vrtc_paddr = sfi_mrtc_array[0].phys_addr;
if (!sfi_mrtc_num || !vrtc_paddr)
return;
vrtc_virt_base = (void __iomem *)set_fixmap_offset_nocache(FIX_LNW_VRTC,
vrtc_paddr);
x86_platform.get_wallclock = vrtc_get_time;
x86_platform.set_wallclock = vrtc_set_mmss;
}
/*
* The Moorestown platform has a memory mapped virtual RTC device that emulates
* the programming interface of the RTC.
*/
static struct resource vrtc_resources[] = {
[0] = {
.flags = IORESOURCE_MEM,
},
[1] = {
.flags = IORESOURCE_IRQ,
}
};
static struct platform_device vrtc_device = {
.name = "rtc_mrst",
.id = -1,
.resource = vrtc_resources,
.num_resources = ARRAY_SIZE(vrtc_resources),
};
/* Register the RTC device if appropriate */
static int __init intel_mid_device_create(void)
{
/* No Moorestown, no device */
if (!intel_mid_identify_cpu())
return -ENODEV;
/* No timer, no device */
if (!sfi_mrtc_num)
return -ENODEV;
/* iomem resource */
vrtc_resources[0].start = sfi_mrtc_array[0].phys_addr;
vrtc_resources[0].end = sfi_mrtc_array[0].phys_addr +
MRST_VRTC_MAP_SZ;
/* irq resource */
vrtc_resources[1].start = sfi_mrtc_array[0].irq;
vrtc_resources[1].end = sfi_mrtc_array[0].irq;
return platform_device_register(&vrtc_device);
}
module_init(intel_mid_device_create);

View file

@ -0,0 +1,18 @@
/*
* intel_mid_weak_decls.h: Weak declarations of intel-mid.c
*
* (C) Copyright 2013 Intel Corporation
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
/* For every CPU addition a new get_<cpuname>_ops interface needs
* to be added.
*/
extern void *get_penwell_ops(void);
extern void *get_cloverview_ops(void);
extern void *get_tangier_ops(void);

View file

@ -0,0 +1,75 @@
/*
* mfld.c: Intel Medfield platform setup code
*
* (C) Copyright 2013 Intel Corporation
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#include <linux/init.h>
#include <asm/apic.h>
#include <asm/intel-mid.h>
#include <asm/intel_mid_vrtc.h>
#include "intel_mid_weak_decls.h"
static void penwell_arch_setup(void);
/* penwell arch ops */
static struct intel_mid_ops penwell_ops = {
.arch_setup = penwell_arch_setup,
};
static void mfld_power_off(void)
{
}
static unsigned long __init mfld_calibrate_tsc(void)
{
unsigned long fast_calibrate;
u32 lo, hi, ratio, fsb;
rdmsr(MSR_IA32_PERF_STATUS, lo, hi);
pr_debug("IA32 perf status is 0x%x, 0x%0x\n", lo, hi);
ratio = (hi >> 8) & 0x1f;
pr_debug("ratio is %d\n", ratio);
if (!ratio) {
pr_err("read a zero ratio, should be incorrect!\n");
pr_err("force tsc ratio to 16 ...\n");
ratio = 16;
}
rdmsr(MSR_FSB_FREQ, lo, hi);
if ((lo & 0x7) == 0x7)
fsb = FSB_FREQ_83SKU;
else
fsb = FSB_FREQ_100SKU;
fast_calibrate = ratio * fsb;
pr_debug("read penwell tsc %lu khz\n", fast_calibrate);
lapic_timer_frequency = fsb * 1000 / HZ;
/* mark tsc clocksource as reliable */
set_cpu_cap(&boot_cpu_data, X86_FEATURE_TSC_RELIABLE);
if (fast_calibrate)
return fast_calibrate;
return 0;
}
static void __init penwell_arch_setup(void)
{
x86_platform.calibrate_tsc = mfld_calibrate_tsc;
pm_power_off = mfld_power_off;
}
void *get_penwell_ops(void)
{
return &penwell_ops;
}
void *get_cloverview_ops(void)
{
return &penwell_ops;
}

View file

@ -0,0 +1,103 @@
/*
* mrfl.c: Intel Merrifield platform specific setup code
*
* (C) Copyright 2013 Intel Corporation
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#include <linux/init.h>
#include <asm/apic.h>
#include <asm/intel-mid.h>
#include "intel_mid_weak_decls.h"
static unsigned long __init tangier_calibrate_tsc(void)
{
unsigned long fast_calibrate;
u32 lo, hi, ratio, fsb, bus_freq;
/* *********************** */
/* Compute TSC:Ratio * FSB */
/* *********************** */
/* Compute Ratio */
rdmsr(MSR_PLATFORM_INFO, lo, hi);
pr_debug("IA32 PLATFORM_INFO is 0x%x : %x\n", hi, lo);
ratio = (lo >> 8) & 0xFF;
pr_debug("ratio is %d\n", ratio);
if (!ratio) {
pr_err("Read a zero ratio, force tsc ratio to 4 ...\n");
ratio = 4;
}
/* Compute FSB */
rdmsr(MSR_FSB_FREQ, lo, hi);
pr_debug("Actual FSB frequency detected by SOC 0x%x : %x\n",
hi, lo);
bus_freq = lo & 0x7;
pr_debug("bus_freq = 0x%x\n", bus_freq);
if (bus_freq == 0)
fsb = FSB_FREQ_100SKU;
else if (bus_freq == 1)
fsb = FSB_FREQ_100SKU;
else if (bus_freq == 2)
fsb = FSB_FREQ_133SKU;
else if (bus_freq == 3)
fsb = FSB_FREQ_167SKU;
else if (bus_freq == 4)
fsb = FSB_FREQ_83SKU;
else if (bus_freq == 5)
fsb = FSB_FREQ_400SKU;
else if (bus_freq == 6)
fsb = FSB_FREQ_267SKU;
else if (bus_freq == 7)
fsb = FSB_FREQ_333SKU;
else {
BUG();
pr_err("Invalid bus_freq! Setting to minimal value!\n");
fsb = FSB_FREQ_100SKU;
}
/* TSC = FSB Freq * Resolved HFM Ratio */
fast_calibrate = ratio * fsb;
pr_debug("calculate tangier tsc %lu KHz\n", fast_calibrate);
/* ************************************ */
/* Calculate Local APIC Timer Frequency */
/* ************************************ */
lapic_timer_frequency = (fsb * 1000) / HZ;
pr_debug("Setting lapic_timer_frequency = %d\n",
lapic_timer_frequency);
/* mark tsc clocksource as reliable */
set_cpu_cap(&boot_cpu_data, X86_FEATURE_TSC_RELIABLE);
if (fast_calibrate)
return fast_calibrate;
return 0;
}
static void __init tangier_arch_setup(void)
{
x86_platform.calibrate_tsc = tangier_calibrate_tsc;
}
/* tangier arch ops */
static struct intel_mid_ops tangier_ops = {
.arch_setup = tangier_arch_setup,
};
void *get_tangier_ops(void)
{
return &tangier_ops;
}

View file

@ -0,0 +1,512 @@
/*
* intel_mid_sfi.c: Intel MID SFI initialization code
*
* (C) Copyright 2013 Intel Corporation
* Author: Sathyanarayanan Kuppuswamy <sathyanarayanan.kuppuswamy@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/scatterlist.h>
#include <linux/sfi.h>
#include <linux/intel_pmic_gpio.h>
#include <linux/spi/spi.h>
#include <linux/i2c.h>
#include <linux/skbuff.h>
#include <linux/gpio.h>
#include <linux/gpio_keys.h>
#include <linux/input.h>
#include <linux/platform_device.h>
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/mmc/core.h>
#include <linux/mmc/card.h>
#include <linux/blkdev.h>
#include <asm/setup.h>
#include <asm/mpspec_def.h>
#include <asm/hw_irq.h>
#include <asm/apic.h>
#include <asm/io_apic.h>
#include <asm/intel-mid.h>
#include <asm/intel_mid_vrtc.h>
#include <asm/io.h>
#include <asm/i8259.h>
#include <asm/intel_scu_ipc.h>
#include <asm/apb_timer.h>
#include <asm/reboot.h>
#define SFI_SIG_OEM0 "OEM0"
#define MAX_IPCDEVS 24
#define MAX_SCU_SPI 24
#define MAX_SCU_I2C 24
static struct platform_device *ipc_devs[MAX_IPCDEVS];
static struct spi_board_info *spi_devs[MAX_SCU_SPI];
static struct i2c_board_info *i2c_devs[MAX_SCU_I2C];
static struct sfi_gpio_table_entry *gpio_table;
static struct sfi_timer_table_entry sfi_mtimer_array[SFI_MTMR_MAX_NUM];
static int ipc_next_dev;
static int spi_next_dev;
static int i2c_next_dev;
static int i2c_bus[MAX_SCU_I2C];
static int gpio_num_entry;
static u32 sfi_mtimer_usage[SFI_MTMR_MAX_NUM];
int sfi_mrtc_num;
int sfi_mtimer_num;
struct sfi_rtc_table_entry sfi_mrtc_array[SFI_MRTC_MAX];
EXPORT_SYMBOL_GPL(sfi_mrtc_array);
struct blocking_notifier_head intel_scu_notifier =
BLOCKING_NOTIFIER_INIT(intel_scu_notifier);
EXPORT_SYMBOL_GPL(intel_scu_notifier);
#define intel_mid_sfi_get_pdata(dev, priv) \
((dev)->get_platform_data ? (dev)->get_platform_data(priv) : NULL)
/* parse all the mtimer info to a static mtimer array */
int __init sfi_parse_mtmr(struct sfi_table_header *table)
{
struct sfi_table_simple *sb;
struct sfi_timer_table_entry *pentry;
struct mpc_intsrc mp_irq;
int totallen;
sb = (struct sfi_table_simple *)table;
if (!sfi_mtimer_num) {
sfi_mtimer_num = SFI_GET_NUM_ENTRIES(sb,
struct sfi_timer_table_entry);
pentry = (struct sfi_timer_table_entry *) sb->pentry;
totallen = sfi_mtimer_num * sizeof(*pentry);
memcpy(sfi_mtimer_array, pentry, totallen);
}
pr_debug("SFI MTIMER info (num = %d):\n", sfi_mtimer_num);
pentry = sfi_mtimer_array;
for (totallen = 0; totallen < sfi_mtimer_num; totallen++, pentry++) {
pr_debug("timer[%d]: paddr = 0x%08x, freq = %dHz, irq = %d\n",
totallen, (u32)pentry->phys_addr,
pentry->freq_hz, pentry->irq);
if (!pentry->irq)
continue;
mp_irq.type = MP_INTSRC;
mp_irq.irqtype = mp_INT;
/* triggering mode edge bit 2-3, active high polarity bit 0-1 */
mp_irq.irqflag = 5;
mp_irq.srcbus = MP_BUS_ISA;
mp_irq.srcbusirq = pentry->irq; /* IRQ */
mp_irq.dstapic = MP_APIC_ALL;
mp_irq.dstirq = pentry->irq;
mp_save_irq(&mp_irq);
mp_map_gsi_to_irq(pentry->irq, IOAPIC_MAP_ALLOC);
}
return 0;
}
struct sfi_timer_table_entry *sfi_get_mtmr(int hint)
{
int i;
if (hint < sfi_mtimer_num) {
if (!sfi_mtimer_usage[hint]) {
pr_debug("hint taken for timer %d irq %d\n",
hint, sfi_mtimer_array[hint].irq);
sfi_mtimer_usage[hint] = 1;
return &sfi_mtimer_array[hint];
}
}
/* take the first timer available */
for (i = 0; i < sfi_mtimer_num;) {
if (!sfi_mtimer_usage[i]) {
sfi_mtimer_usage[i] = 1;
return &sfi_mtimer_array[i];
}
i++;
}
return NULL;
}
void sfi_free_mtmr(struct sfi_timer_table_entry *mtmr)
{
int i;
for (i = 0; i < sfi_mtimer_num;) {
if (mtmr->irq == sfi_mtimer_array[i].irq) {
sfi_mtimer_usage[i] = 0;
return;
}
i++;
}
}
/* parse all the mrtc info to a global mrtc array */
int __init sfi_parse_mrtc(struct sfi_table_header *table)
{
struct sfi_table_simple *sb;
struct sfi_rtc_table_entry *pentry;
struct mpc_intsrc mp_irq;
int totallen;
sb = (struct sfi_table_simple *)table;
if (!sfi_mrtc_num) {
sfi_mrtc_num = SFI_GET_NUM_ENTRIES(sb,
struct sfi_rtc_table_entry);
pentry = (struct sfi_rtc_table_entry *)sb->pentry;
totallen = sfi_mrtc_num * sizeof(*pentry);
memcpy(sfi_mrtc_array, pentry, totallen);
}
pr_debug("SFI RTC info (num = %d):\n", sfi_mrtc_num);
pentry = sfi_mrtc_array;
for (totallen = 0; totallen < sfi_mrtc_num; totallen++, pentry++) {
pr_debug("RTC[%d]: paddr = 0x%08x, irq = %d\n",
totallen, (u32)pentry->phys_addr, pentry->irq);
mp_irq.type = MP_INTSRC;
mp_irq.irqtype = mp_INT;
mp_irq.irqflag = 0xf; /* level trigger and active low */
mp_irq.srcbus = MP_BUS_ISA;
mp_irq.srcbusirq = pentry->irq; /* IRQ */
mp_irq.dstapic = MP_APIC_ALL;
mp_irq.dstirq = pentry->irq;
mp_save_irq(&mp_irq);
mp_map_gsi_to_irq(pentry->irq, IOAPIC_MAP_ALLOC);
}
return 0;
}
/*
* Parsing GPIO table first, since the DEVS table will need this table
* to map the pin name to the actual pin.
*/
static int __init sfi_parse_gpio(struct sfi_table_header *table)
{
struct sfi_table_simple *sb;
struct sfi_gpio_table_entry *pentry;
int num, i;
if (gpio_table)
return 0;
sb = (struct sfi_table_simple *)table;
num = SFI_GET_NUM_ENTRIES(sb, struct sfi_gpio_table_entry);
pentry = (struct sfi_gpio_table_entry *)sb->pentry;
gpio_table = kmalloc(num * sizeof(*pentry), GFP_KERNEL);
if (!gpio_table)
return -1;
memcpy(gpio_table, pentry, num * sizeof(*pentry));
gpio_num_entry = num;
pr_debug("GPIO pin info:\n");
for (i = 0; i < num; i++, pentry++)
pr_debug("info[%2d]: controller = %16.16s, pin_name = %16.16s,"
" pin = %d\n", i,
pentry->controller_name,
pentry->pin_name,
pentry->pin_no);
return 0;
}
int get_gpio_by_name(const char *name)
{
struct sfi_gpio_table_entry *pentry = gpio_table;
int i;
if (!pentry)
return -1;
for (i = 0; i < gpio_num_entry; i++, pentry++) {
if (!strncmp(name, pentry->pin_name, SFI_NAME_LEN))
return pentry->pin_no;
}
return -EINVAL;
}
void __init intel_scu_device_register(struct platform_device *pdev)
{
if (ipc_next_dev == MAX_IPCDEVS)
pr_err("too many SCU IPC devices");
else
ipc_devs[ipc_next_dev++] = pdev;
}
static void __init intel_scu_spi_device_register(struct spi_board_info *sdev)
{
struct spi_board_info *new_dev;
if (spi_next_dev == MAX_SCU_SPI) {
pr_err("too many SCU SPI devices");
return;
}
new_dev = kzalloc(sizeof(*sdev), GFP_KERNEL);
if (!new_dev) {
pr_err("failed to alloc mem for delayed spi dev %s\n",
sdev->modalias);
return;
}
*new_dev = *sdev;
spi_devs[spi_next_dev++] = new_dev;
}
static void __init intel_scu_i2c_device_register(int bus,
struct i2c_board_info *idev)
{
struct i2c_board_info *new_dev;
if (i2c_next_dev == MAX_SCU_I2C) {
pr_err("too many SCU I2C devices");
return;
}
new_dev = kzalloc(sizeof(*idev), GFP_KERNEL);
if (!new_dev) {
pr_err("failed to alloc mem for delayed i2c dev %s\n",
idev->type);
return;
}
*new_dev = *idev;
i2c_bus[i2c_next_dev] = bus;
i2c_devs[i2c_next_dev++] = new_dev;
}
/* Called by IPC driver */
void intel_scu_devices_create(void)
{
int i;
for (i = 0; i < ipc_next_dev; i++)
platform_device_add(ipc_devs[i]);
for (i = 0; i < spi_next_dev; i++)
spi_register_board_info(spi_devs[i], 1);
for (i = 0; i < i2c_next_dev; i++) {
struct i2c_adapter *adapter;
struct i2c_client *client;
adapter = i2c_get_adapter(i2c_bus[i]);
if (adapter) {
client = i2c_new_device(adapter, i2c_devs[i]);
if (!client)
pr_err("can't create i2c device %s\n",
i2c_devs[i]->type);
} else
i2c_register_board_info(i2c_bus[i], i2c_devs[i], 1);
}
intel_scu_notifier_post(SCU_AVAILABLE, NULL);
}
EXPORT_SYMBOL_GPL(intel_scu_devices_create);
/* Called by IPC driver */
void intel_scu_devices_destroy(void)
{
int i;
intel_scu_notifier_post(SCU_DOWN, NULL);
for (i = 0; i < ipc_next_dev; i++)
platform_device_del(ipc_devs[i]);
}
EXPORT_SYMBOL_GPL(intel_scu_devices_destroy);
static void __init install_irq_resource(struct platform_device *pdev, int irq)
{
/* Single threaded */
static struct resource res __initdata = {
.name = "IRQ",
.flags = IORESOURCE_IRQ,
};
res.start = irq;
platform_device_add_resources(pdev, &res, 1);
}
static void __init sfi_handle_ipc_dev(struct sfi_device_table_entry *pentry,
struct devs_id *dev)
{
struct platform_device *pdev;
void *pdata = NULL;
pr_debug("IPC bus, name = %16.16s, irq = 0x%2x\n",
pentry->name, pentry->irq);
pdata = intel_mid_sfi_get_pdata(dev, pentry);
if (IS_ERR(pdata))
return;
pdev = platform_device_alloc(pentry->name, 0);
if (pdev == NULL) {
pr_err("out of memory for SFI platform device '%s'.\n",
pentry->name);
return;
}
install_irq_resource(pdev, pentry->irq);
pdev->dev.platform_data = pdata;
platform_device_add(pdev);
}
static void __init sfi_handle_spi_dev(struct sfi_device_table_entry *pentry,
struct devs_id *dev)
{
struct spi_board_info spi_info;
void *pdata = NULL;
memset(&spi_info, 0, sizeof(spi_info));
strncpy(spi_info.modalias, pentry->name, SFI_NAME_LEN);
spi_info.irq = ((pentry->irq == (u8)0xff) ? 0 : pentry->irq);
spi_info.bus_num = pentry->host_num;
spi_info.chip_select = pentry->addr;
spi_info.max_speed_hz = pentry->max_freq;
pr_debug("SPI bus=%d, name=%16.16s, irq=0x%2x, max_freq=%d, cs=%d\n",
spi_info.bus_num,
spi_info.modalias,
spi_info.irq,
spi_info.max_speed_hz,
spi_info.chip_select);
pdata = intel_mid_sfi_get_pdata(dev, &spi_info);
if (IS_ERR(pdata))
return;
spi_info.platform_data = pdata;
if (dev->delay)
intel_scu_spi_device_register(&spi_info);
else
spi_register_board_info(&spi_info, 1);
}
static void __init sfi_handle_i2c_dev(struct sfi_device_table_entry *pentry,
struct devs_id *dev)
{
struct i2c_board_info i2c_info;
void *pdata = NULL;
memset(&i2c_info, 0, sizeof(i2c_info));
strncpy(i2c_info.type, pentry->name, SFI_NAME_LEN);
i2c_info.irq = ((pentry->irq == (u8)0xff) ? 0 : pentry->irq);
i2c_info.addr = pentry->addr;
pr_debug("I2C bus = %d, name = %16.16s, irq = 0x%2x, addr = 0x%x\n",
pentry->host_num,
i2c_info.type,
i2c_info.irq,
i2c_info.addr);
pdata = intel_mid_sfi_get_pdata(dev, &i2c_info);
i2c_info.platform_data = pdata;
if (IS_ERR(pdata))
return;
if (dev->delay)
intel_scu_i2c_device_register(pentry->host_num, &i2c_info);
else
i2c_register_board_info(pentry->host_num, &i2c_info, 1);
}
extern struct devs_id *const __x86_intel_mid_dev_start[],
*const __x86_intel_mid_dev_end[];
static struct devs_id __init *get_device_id(u8 type, char *name)
{
struct devs_id *const *dev_table;
for (dev_table = __x86_intel_mid_dev_start;
dev_table < __x86_intel_mid_dev_end; dev_table++) {
struct devs_id *dev = *dev_table;
if (dev->type == type &&
!strncmp(dev->name, name, SFI_NAME_LEN)) {
return dev;
}
}
return NULL;
}
static int __init sfi_parse_devs(struct sfi_table_header *table)
{
struct sfi_table_simple *sb;
struct sfi_device_table_entry *pentry;
struct devs_id *dev = NULL;
int num, i, ret;
int polarity;
sb = (struct sfi_table_simple *)table;
num = SFI_GET_NUM_ENTRIES(sb, struct sfi_device_table_entry);
pentry = (struct sfi_device_table_entry *)sb->pentry;
for (i = 0; i < num; i++, pentry++) {
int irq = pentry->irq;
if (irq != (u8)0xff) { /* native RTE case */
/* these SPI2 devices are not exposed to system as PCI
* devices, but they have separate RTE entry in IOAPIC
* so we have to enable them one by one here
*/
if (intel_mid_identify_cpu() ==
INTEL_MID_CPU_CHIP_TANGIER) {
if (!strncmp(pentry->name, "r69001-ts-i2c", 13))
/* active low */
polarity = 1;
else if (!strncmp(pentry->name,
"synaptics_3202", 14))
/* active low */
polarity = 1;
else if (irq == 41)
/* fast_int_1 */
polarity = 1;
else
/* active high */
polarity = 0;
} else {
/* PNW and CLV go with active low */
polarity = 1;
}
ret = mp_set_gsi_attr(irq, 1, polarity, NUMA_NO_NODE);
if (ret == 0)
ret = mp_map_gsi_to_irq(irq, IOAPIC_MAP_ALLOC);
WARN_ON(ret < 0);
}
dev = get_device_id(pentry->type, pentry->name);
if (!dev)
continue;
if (dev->device_handler) {
dev->device_handler(pentry, dev);
} else {
switch (pentry->type) {
case SFI_DEV_TYPE_IPC:
sfi_handle_ipc_dev(pentry, dev);
break;
case SFI_DEV_TYPE_SPI:
sfi_handle_spi_dev(pentry, dev);
break;
case SFI_DEV_TYPE_I2C:
sfi_handle_i2c_dev(pentry, dev);
break;
case SFI_DEV_TYPE_UART:
case SFI_DEV_TYPE_HSI:
default:
break;
}
}
}
return 0;
}
static int __init intel_mid_platform_init(void)
{
sfi_table_parse(SFI_SIG_GPIO, NULL, NULL, sfi_parse_gpio);
sfi_table_parse(SFI_SIG_DEVS, NULL, NULL, sfi_parse_devs);
return 0;
}
arch_initcall(intel_mid_platform_init);

View file

@ -0,0 +1 @@
obj-$(CONFIG_X86_32_IRIS) += iris.o

View file

@ -0,0 +1,137 @@
/*
* Eurobraille/Iris power off support.
*
* Eurobraille's Iris machine is a PC with no APM or ACPI support.
* It is shutdown by a special I/O sequence which this module provides.
*
* Copyright (C) Shérab <Sebastien.Hinderer@ens-lyon.org>
*
* This program is free software ; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with the program ; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/moduleparam.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <asm/io.h>
#define IRIS_GIO_BASE 0x340
#define IRIS_GIO_INPUT IRIS_GIO_BASE
#define IRIS_GIO_OUTPUT (IRIS_GIO_BASE + 1)
#define IRIS_GIO_PULSE 0x80 /* First byte to send */
#define IRIS_GIO_REST 0x00 /* Second byte to send */
#define IRIS_GIO_NODEV 0xff /* Likely not an Iris */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Sébastien Hinderer <Sebastien.Hinderer@ens-lyon.org>");
MODULE_DESCRIPTION("A power_off handler for Iris devices from EuroBraille");
MODULE_SUPPORTED_DEVICE("Eurobraille/Iris");
static bool force;
module_param(force, bool, 0);
MODULE_PARM_DESC(force, "Set to one to force poweroff handler installation.");
static void (*old_pm_power_off)(void);
static void iris_power_off(void)
{
outb(IRIS_GIO_PULSE, IRIS_GIO_OUTPUT);
msleep(850);
outb(IRIS_GIO_REST, IRIS_GIO_OUTPUT);
}
/*
* Before installing the power_off handler, try to make sure the OS is
* running on an Iris. Since Iris does not support DMI, this is done
* by reading its input port and seeing whether the read value is
* meaningful.
*/
static int iris_probe(struct platform_device *pdev)
{
unsigned char status = inb(IRIS_GIO_INPUT);
if (status == IRIS_GIO_NODEV) {
printk(KERN_ERR "This machine does not seem to be an Iris. "
"Power off handler not installed.\n");
return -ENODEV;
}
old_pm_power_off = pm_power_off;
pm_power_off = &iris_power_off;
printk(KERN_INFO "Iris power_off handler installed.\n");
return 0;
}
static int iris_remove(struct platform_device *pdev)
{
pm_power_off = old_pm_power_off;
printk(KERN_INFO "Iris power_off handler uninstalled.\n");
return 0;
}
static struct platform_driver iris_driver = {
.driver = {
.name = "iris",
.owner = THIS_MODULE,
},
.probe = iris_probe,
.remove = iris_remove,
};
static struct resource iris_resources[] = {
{
.start = IRIS_GIO_BASE,
.end = IRIS_GIO_OUTPUT,
.flags = IORESOURCE_IO,
.name = "address"
}
};
static struct platform_device *iris_device;
static int iris_init(void)
{
int ret;
if (force != 1) {
printk(KERN_ERR "The force parameter has not been set to 1."
" The Iris poweroff handler will not be installed.\n");
return -ENODEV;
}
ret = platform_driver_register(&iris_driver);
if (ret < 0) {
printk(KERN_ERR "Failed to register iris platform driver: %d\n",
ret);
return ret;
}
iris_device = platform_device_register_simple("iris", (-1),
iris_resources, ARRAY_SIZE(iris_resources));
if (IS_ERR(iris_device)) {
printk(KERN_ERR "Failed to register iris platform device\n");
platform_driver_unregister(&iris_driver);
return PTR_ERR(iris_device);
}
return 0;
}
static void iris_exit(void)
{
platform_device_unregister(iris_device);
platform_driver_unregister(&iris_driver);
}
module_init(iris_init);
module_exit(iris_exit);

View 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

View file

@ -0,0 +1,202 @@
/*
* Support for power management features of the OLPC XO-1 laptop
*
* Copyright (C) 2010 Andres Salomon <dilinger@queued.net>
* Copyright (C) 2010 One Laptop per Child
* Copyright (C) 2006 Red Hat, Inc.
* Copyright (C) 2006 Advanced Micro Devices, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/cs5535.h>
#include <linux/platform_device.h>
#include <linux/export.h>
#include <linux/pm.h>
#include <linux/mfd/core.h>
#include <linux/suspend.h>
#include <linux/olpc-ec.h>
#include <asm/io.h>
#include <asm/olpc.h>
#define DRV_NAME "olpc-xo1-pm"
static unsigned long acpi_base;
static unsigned long pms_base;
static u16 wakeup_mask = CS5536_PM_PWRBTN;
static struct {
unsigned long address;
unsigned short segment;
} ofw_bios_entry = { 0xF0000 + PAGE_OFFSET, __KERNEL_CS };
/* Set bits in the wakeup mask */
void olpc_xo1_pm_wakeup_set(u16 value)
{
wakeup_mask |= value;
}
EXPORT_SYMBOL_GPL(olpc_xo1_pm_wakeup_set);
/* Clear bits in the wakeup mask */
void olpc_xo1_pm_wakeup_clear(u16 value)
{
wakeup_mask &= ~value;
}
EXPORT_SYMBOL_GPL(olpc_xo1_pm_wakeup_clear);
static int xo1_power_state_enter(suspend_state_t pm_state)
{
unsigned long saved_sci_mask;
/* Only STR is supported */
if (pm_state != PM_SUSPEND_MEM)
return -EINVAL;
/*
* Save SCI mask (this gets lost since PM1_EN is used as a mask for
* wakeup events, which is not necessarily the same event set)
*/
saved_sci_mask = inl(acpi_base + CS5536_PM1_STS);
saved_sci_mask &= 0xffff0000;
/* Save CPU state */
do_olpc_suspend_lowlevel();
/* Resume path starts here */
/* Restore SCI mask (using dword access to CS5536_PM1_EN) */
outl(saved_sci_mask, acpi_base + CS5536_PM1_STS);
return 0;
}
asmlinkage __visible int xo1_do_sleep(u8 sleep_state)
{
void *pgd_addr = __va(read_cr3());
/* Program wakeup mask (using dword access to CS5536_PM1_EN) */
outl(wakeup_mask << 16, acpi_base + CS5536_PM1_STS);
__asm__("movl %0,%%eax" : : "r" (pgd_addr));
__asm__("call *(%%edi); cld"
: : "D" (&ofw_bios_entry));
__asm__("movb $0x34, %al\n\t"
"outb %al, $0x70\n\t"
"movb $0x30, %al\n\t"
"outb %al, $0x71\n\t");
return 0;
}
static void xo1_power_off(void)
{
printk(KERN_INFO "OLPC XO-1 power off sequence...\n");
/* Enable all of these controls with 0 delay */
outl(0x40000000, pms_base + CS5536_PM_SCLK);
outl(0x40000000, pms_base + CS5536_PM_IN_SLPCTL);
outl(0x40000000, pms_base + CS5536_PM_WKXD);
outl(0x40000000, pms_base + CS5536_PM_WKD);
/* Clear status bits (possibly unnecessary) */
outl(0x0002ffff, pms_base + CS5536_PM_SSC);
outl(0xffffffff, acpi_base + CS5536_PM_GPE0_STS);
/* Write SLP_EN bit to start the machinery */
outl(0x00002000, acpi_base + CS5536_PM1_CNT);
}
static int xo1_power_state_valid(suspend_state_t pm_state)
{
/* suspend-to-RAM only */
return pm_state == PM_SUSPEND_MEM;
}
static const struct platform_suspend_ops xo1_suspend_ops = {
.valid = xo1_power_state_valid,
.enter = xo1_power_state_enter,
};
static int xo1_pm_probe(struct platform_device *pdev)
{
struct resource *res;
int err;
/* don't run on non-XOs */
if (!machine_is_olpc())
return -ENODEV;
err = mfd_cell_enable(pdev);
if (err)
return err;
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
if (!res) {
dev_err(&pdev->dev, "can't fetch device resource info\n");
return -EIO;
}
if (strcmp(pdev->name, "cs5535-pms") == 0)
pms_base = res->start;
else if (strcmp(pdev->name, "olpc-xo1-pm-acpi") == 0)
acpi_base = res->start;
/* If we have both addresses, we can override the poweroff hook */
if (pms_base && acpi_base) {
suspend_set_ops(&xo1_suspend_ops);
pm_power_off = xo1_power_off;
printk(KERN_INFO "OLPC XO-1 support registered\n");
}
return 0;
}
static int xo1_pm_remove(struct platform_device *pdev)
{
mfd_cell_disable(pdev);
if (strcmp(pdev->name, "cs5535-pms") == 0)
pms_base = 0;
else if (strcmp(pdev->name, "olpc-xo1-pm-acpi") == 0)
acpi_base = 0;
pm_power_off = NULL;
return 0;
}
static struct platform_driver cs5535_pms_driver = {
.driver = {
.name = "cs5535-pms",
.owner = THIS_MODULE,
},
.probe = xo1_pm_probe,
.remove = xo1_pm_remove,
};
static struct platform_driver cs5535_acpi_driver = {
.driver = {
.name = "olpc-xo1-pm-acpi",
.owner = THIS_MODULE,
},
.probe = xo1_pm_probe,
.remove = xo1_pm_remove,
};
static int __init xo1_pm_init(void)
{
int r;
r = platform_driver_register(&cs5535_pms_driver);
if (r)
return r;
r = platform_driver_register(&cs5535_acpi_driver);
if (r)
platform_driver_unregister(&cs5535_pms_driver);
return r;
}
arch_initcall(xo1_pm_init);

View 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);

View 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);

View file

@ -0,0 +1,233 @@
/*
* Support for OLPC XO-1.5 System Control Interrupts (SCI)
*
* Copyright (C) 2009-2010 One Laptop per Child
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/power_supply.h>
#include <linux/olpc-ec.h>
#include <linux/acpi.h>
#include <asm/olpc.h>
#define DRV_NAME "olpc-xo15-sci"
#define PFX DRV_NAME ": "
#define XO15_SCI_CLASS DRV_NAME
#define XO15_SCI_DEVICE_NAME "OLPC XO-1.5 SCI"
static unsigned long xo15_sci_gpe;
static bool lid_wake_on_close;
/*
* The normal ACPI LID wakeup behavior is wake-on-open, but not
* wake-on-close. This is implemented as standard by the XO-1.5 DSDT.
*
* We provide here a sysfs attribute that will additionally enable
* wake-on-close behavior. This is useful (e.g.) when we oportunistically
* suspend with the display running; if the lid is then closed, we want to
* wake up to turn the display off.
*
* This is controlled through a custom method in the XO-1.5 DSDT.
*/
static int set_lid_wake_behavior(bool wake_on_close)
{
acpi_status status;
status = acpi_execute_simple_method(NULL, "\\_SB.PCI0.LID.LIDW", wake_on_close);
if (ACPI_FAILURE(status)) {
pr_warning(PFX "failed to set lid behavior\n");
return 1;
}
lid_wake_on_close = wake_on_close;
return 0;
}
static ssize_t
lid_wake_on_close_show(struct kobject *s, struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%u\n", lid_wake_on_close);
}
static ssize_t lid_wake_on_close_store(struct kobject *s,
struct kobj_attribute *attr,
const char *buf, size_t n)
{
unsigned int val;
if (sscanf(buf, "%u", &val) != 1)
return -EINVAL;
set_lid_wake_behavior(!!val);
return n;
}
static struct kobj_attribute lid_wake_on_close_attr =
__ATTR(lid_wake_on_close, 0644,
lid_wake_on_close_show,
lid_wake_on_close_store);
static void battery_status_changed(void)
{
struct power_supply *psy = power_supply_get_by_name("olpc-battery");
if (psy) {
power_supply_changed(psy);
put_device(psy->dev);
}
}
static void ac_status_changed(void)
{
struct power_supply *psy = power_supply_get_by_name("olpc-ac");
if (psy) {
power_supply_changed(psy);
put_device(psy->dev);
}
}
static void process_sci_queue(void)
{
u16 data;
int r;
do {
r = olpc_ec_sci_query(&data);
if (r || !data)
break;
pr_debug(PFX "SCI 0x%x received\n", data);
switch (data) {
case EC_SCI_SRC_BATERR:
case EC_SCI_SRC_BATSOC:
case EC_SCI_SRC_BATTERY:
case EC_SCI_SRC_BATCRIT:
battery_status_changed();
break;
case EC_SCI_SRC_ACPWR:
ac_status_changed();
break;
}
} while (data);
if (r)
pr_err(PFX "Failed to clear SCI queue");
}
static void process_sci_queue_work(struct work_struct *work)
{
process_sci_queue();
}
static DECLARE_WORK(sci_work, process_sci_queue_work);
static u32 xo15_sci_gpe_handler(acpi_handle gpe_device, u32 gpe, void *context)
{
schedule_work(&sci_work);
return ACPI_INTERRUPT_HANDLED | ACPI_REENABLE_GPE;
}
static int xo15_sci_add(struct acpi_device *device)
{
unsigned long long tmp;
acpi_status status;
int r;
if (!device)
return -EINVAL;
strcpy(acpi_device_name(device), XO15_SCI_DEVICE_NAME);
strcpy(acpi_device_class(device), XO15_SCI_CLASS);
/* Get GPE bit assignment (EC events). */
status = acpi_evaluate_integer(device->handle, "_GPE", NULL, &tmp);
if (ACPI_FAILURE(status))
return -EINVAL;
xo15_sci_gpe = tmp;
status = acpi_install_gpe_handler(NULL, xo15_sci_gpe,
ACPI_GPE_EDGE_TRIGGERED,
xo15_sci_gpe_handler, device);
if (ACPI_FAILURE(status))
return -ENODEV;
dev_info(&device->dev, "Initialized, GPE = 0x%lx\n", xo15_sci_gpe);
r = sysfs_create_file(&device->dev.kobj, &lid_wake_on_close_attr.attr);
if (r)
goto err_sysfs;
/* Flush queue, and enable all SCI events */
process_sci_queue();
olpc_ec_mask_write(EC_SCI_SRC_ALL);
acpi_enable_gpe(NULL, xo15_sci_gpe);
/* Enable wake-on-EC */
if (device->wakeup.flags.valid)
device_init_wakeup(&device->dev, true);
return 0;
err_sysfs:
acpi_remove_gpe_handler(NULL, xo15_sci_gpe, xo15_sci_gpe_handler);
cancel_work_sync(&sci_work);
return r;
}
static int xo15_sci_remove(struct acpi_device *device)
{
acpi_disable_gpe(NULL, xo15_sci_gpe);
acpi_remove_gpe_handler(NULL, xo15_sci_gpe, xo15_sci_gpe_handler);
cancel_work_sync(&sci_work);
sysfs_remove_file(&device->dev.kobj, &lid_wake_on_close_attr.attr);
return 0;
}
static int xo15_sci_resume(struct device *dev)
{
/* Enable all EC events */
olpc_ec_mask_write(EC_SCI_SRC_ALL);
/* Power/battery status might have changed */
battery_status_changed();
ac_status_changed();
return 0;
}
static SIMPLE_DEV_PM_OPS(xo15_sci_pm, NULL, xo15_sci_resume);
static const struct acpi_device_id xo15_sci_device_ids[] = {
{"XO15EC", 0},
{"", 0},
};
static struct acpi_driver xo15_sci_drv = {
.name = DRV_NAME,
.class = XO15_SCI_CLASS,
.ids = xo15_sci_device_ids,
.ops = {
.add = xo15_sci_add,
.remove = xo15_sci_remove,
},
.drv.pm = &xo15_sci_pm,
};
static int __init xo15_sci_init(void)
{
return acpi_bus_register_driver(&xo15_sci_drv);
}
device_initcall(xo15_sci_init);

View 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);

View 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);

View 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;
}

View 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

View file

@ -0,0 +1,2 @@
obj-$(CONFIG_SCx200) += scx200.o
scx200-y += scx200_32.o

View 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);

View file

@ -0,0 +1 @@
obj-$(CONFIG_SFI) += sfi.o

117
arch/x86/platform/sfi/sfi.c Normal file
View file

@ -0,0 +1,117 @@
/*
* sfi.c - x86 architecture SFI support.
*
* Copyright (c) 2009, Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#define KMSG_COMPONENT "SFI"
#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
#include <linux/acpi.h>
#include <linux/init.h>
#include <linux/sfi.h>
#include <linux/io.h>
#include <linux/irqdomain.h>
#include <asm/io_apic.h>
#include <asm/mpspec.h>
#include <asm/setup.h>
#include <asm/apic.h>
#ifdef CONFIG_X86_LOCAL_APIC
static unsigned long sfi_lapic_addr __initdata = APIC_DEFAULT_PHYS_BASE;
/* All CPUs enumerated by SFI must be present and enabled */
static void __init mp_sfi_register_lapic(u8 id)
{
if (MAX_LOCAL_APIC - id <= 0) {
pr_warning("Processor #%d invalid (max %d)\n",
id, MAX_LOCAL_APIC);
return;
}
pr_info("registering lapic[%d]\n", id);
generic_processor_info(id, GET_APIC_VERSION(apic_read(APIC_LVR)));
}
static int __init sfi_parse_cpus(struct sfi_table_header *table)
{
struct sfi_table_simple *sb;
struct sfi_cpu_table_entry *pentry;
int i;
int cpu_num;
sb = (struct sfi_table_simple *)table;
cpu_num = SFI_GET_NUM_ENTRIES(sb, struct sfi_cpu_table_entry);
pentry = (struct sfi_cpu_table_entry *)sb->pentry;
for (i = 0; i < cpu_num; i++) {
mp_sfi_register_lapic(pentry->apic_id);
pentry++;
}
smp_found_config = 1;
return 0;
}
#endif /* CONFIG_X86_LOCAL_APIC */
#ifdef CONFIG_X86_IO_APIC
static struct irq_domain_ops sfi_ioapic_irqdomain_ops = {
.map = mp_irqdomain_map,
};
static int __init sfi_parse_ioapic(struct sfi_table_header *table)
{
struct sfi_table_simple *sb;
struct sfi_apic_table_entry *pentry;
int i, num;
struct ioapic_domain_cfg cfg = {
.type = IOAPIC_DOMAIN_STRICT,
.ops = &sfi_ioapic_irqdomain_ops,
};
sb = (struct sfi_table_simple *)table;
num = SFI_GET_NUM_ENTRIES(sb, struct sfi_apic_table_entry);
pentry = (struct sfi_apic_table_entry *)sb->pentry;
for (i = 0; i < num; i++) {
mp_register_ioapic(i, pentry->phys_addr, gsi_top, &cfg);
pentry++;
}
WARN(pic_mode, KERN_WARNING
"SFI: pic_mod shouldn't be 1 when IOAPIC table is present\n");
pic_mode = 0;
return 0;
}
#endif /* CONFIG_X86_IO_APIC */
/*
* sfi_platform_init(): register lapics & io-apics
*/
int __init sfi_platform_init(void)
{
#ifdef CONFIG_X86_LOCAL_APIC
register_lapic_address(sfi_lapic_addr);
sfi_table_parse(SFI_SIG_CPUS, NULL, NULL, sfi_parse_cpus);
#endif
#ifdef CONFIG_X86_IO_APIC
sfi_table_parse(SFI_SIG_APIC, NULL, NULL, sfi_parse_ioapic);
#endif
return 0;
}

View file

@ -0,0 +1 @@
obj-$(CONFIG_TS5500) += ts5500.o

View file

@ -0,0 +1,351 @@
/*
* Technologic Systems TS-5500 Single Board Computer support
*
* Copyright (C) 2013-2014 Savoir-faire Linux Inc.
* Vivien Didelot <vivien.didelot@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version.
*
*
* This driver registers the Technologic Systems TS-5500 Single Board Computer
* (SBC) and its devices, and exposes information to userspace such as jumpers'
* state or available options. For further information about sysfs entries, see
* Documentation/ABI/testing/sysfs-platform-ts5500.
*
* This code may be extended to support similar x86-based platforms.
* Actually, the TS-5500 and TS-5400 are supported.
*/
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/platform_data/gpio-ts5500.h>
#include <linux/platform_data/max197.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
/* Product code register */
#define TS5500_PRODUCT_CODE_ADDR 0x74
#define TS5500_PRODUCT_CODE 0x60 /* TS-5500 product code */
#define TS5400_PRODUCT_CODE 0x40 /* TS-5400 product code */
/* SRAM/RS-485/ADC options, and RS-485 RTS/Automatic RS-485 flags register */
#define TS5500_SRAM_RS485_ADC_ADDR 0x75
#define TS5500_SRAM BIT(0) /* SRAM option */
#define TS5500_RS485 BIT(1) /* RS-485 option */
#define TS5500_ADC BIT(2) /* A/D converter option */
#define TS5500_RS485_RTS BIT(6) /* RTS for RS-485 */
#define TS5500_RS485_AUTO BIT(7) /* Automatic RS-485 */
/* External Reset/Industrial Temperature Range options register */
#define TS5500_ERESET_ITR_ADDR 0x76
#define TS5500_ERESET BIT(0) /* External Reset option */
#define TS5500_ITR BIT(1) /* Indust. Temp. Range option */
/* LED/Jumpers register */
#define TS5500_LED_JP_ADDR 0x77
#define TS5500_LED BIT(0) /* LED flag */
#define TS5500_JP1 BIT(1) /* Automatic CMOS */
#define TS5500_JP2 BIT(2) /* Enable Serial Console */
#define TS5500_JP3 BIT(3) /* Write Enable Drive A */
#define TS5500_JP4 BIT(4) /* Fast Console (115K baud) */
#define TS5500_JP5 BIT(5) /* User Jumper */
#define TS5500_JP6 BIT(6) /* Console on COM1 (req. JP2) */
#define TS5500_JP7 BIT(7) /* Undocumented (Unused) */
/* A/D Converter registers */
#define TS5500_ADC_CONV_BUSY_ADDR 0x195 /* Conversion state register */
#define TS5500_ADC_CONV_BUSY BIT(0)
#define TS5500_ADC_CONV_INIT_LSB_ADDR 0x196 /* Start conv. / LSB register */
#define TS5500_ADC_CONV_MSB_ADDR 0x197 /* MSB register */
#define TS5500_ADC_CONV_DELAY 12 /* usec */
/**
* struct ts5500_sbc - TS-5500 board description
* @name: Board model name.
* @id: Board product ID.
* @sram: Flag for SRAM option.
* @rs485: Flag for RS-485 option.
* @adc: Flag for Analog/Digital converter option.
* @ereset: Flag for External Reset option.
* @itr: Flag for Industrial Temperature Range option.
* @jumpers: Bitfield for jumpers' state.
*/
struct ts5500_sbc {
const char *name;
int id;
bool sram;
bool rs485;
bool adc;
bool ereset;
bool itr;
u8 jumpers;
};
/* Board signatures in BIOS shadow RAM */
static const struct {
const char * const string;
const ssize_t offset;
} ts5500_signatures[] __initconst = {
{ "TS-5x00 AMD Elan", 0xb14 },
};
static int __init ts5500_check_signature(void)
{
void __iomem *bios;
int i, ret = -ENODEV;
bios = ioremap(0xf0000, 0x10000);
if (!bios)
return -ENOMEM;
for (i = 0; i < ARRAY_SIZE(ts5500_signatures); i++) {
if (check_signature(bios + ts5500_signatures[i].offset,
ts5500_signatures[i].string,
strlen(ts5500_signatures[i].string))) {
ret = 0;
break;
}
}
iounmap(bios);
return ret;
}
static int __init ts5500_detect_config(struct ts5500_sbc *sbc)
{
u8 tmp;
int ret = 0;
if (!request_region(TS5500_PRODUCT_CODE_ADDR, 4, "ts5500"))
return -EBUSY;
sbc->id = inb(TS5500_PRODUCT_CODE_ADDR);
if (sbc->id == TS5500_PRODUCT_CODE) {
sbc->name = "TS-5500";
} else if (sbc->id == TS5400_PRODUCT_CODE) {
sbc->name = "TS-5400";
} else {
pr_err("ts5500: unknown product code 0x%x\n", sbc->id);
ret = -ENODEV;
goto cleanup;
}
tmp = inb(TS5500_SRAM_RS485_ADC_ADDR);
sbc->sram = tmp & TS5500_SRAM;
sbc->rs485 = tmp & TS5500_RS485;
sbc->adc = tmp & TS5500_ADC;
tmp = inb(TS5500_ERESET_ITR_ADDR);
sbc->ereset = tmp & TS5500_ERESET;
sbc->itr = tmp & TS5500_ITR;
tmp = inb(TS5500_LED_JP_ADDR);
sbc->jumpers = tmp & ~TS5500_LED;
cleanup:
release_region(TS5500_PRODUCT_CODE_ADDR, 4);
return ret;
}
static ssize_t name_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct ts5500_sbc *sbc = dev_get_drvdata(dev);
return sprintf(buf, "%s\n", sbc->name);
}
static DEVICE_ATTR_RO(name);
static ssize_t id_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct ts5500_sbc *sbc = dev_get_drvdata(dev);
return sprintf(buf, "0x%.2x\n", sbc->id);
}
static DEVICE_ATTR_RO(id);
static ssize_t jumpers_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct ts5500_sbc *sbc = dev_get_drvdata(dev);
return sprintf(buf, "0x%.2x\n", sbc->jumpers >> 1);
}
static DEVICE_ATTR_RO(jumpers);
#define TS5500_ATTR_BOOL(_field) \
static ssize_t _field##_show(struct device *dev, \
struct device_attribute *attr, char *buf) \
{ \
struct ts5500_sbc *sbc = dev_get_drvdata(dev); \
\
return sprintf(buf, "%d\n", sbc->_field); \
} \
static DEVICE_ATTR_RO(_field)
TS5500_ATTR_BOOL(sram);
TS5500_ATTR_BOOL(rs485);
TS5500_ATTR_BOOL(adc);
TS5500_ATTR_BOOL(ereset);
TS5500_ATTR_BOOL(itr);
static struct attribute *ts5500_attributes[] = {
&dev_attr_id.attr,
&dev_attr_name.attr,
&dev_attr_jumpers.attr,
&dev_attr_sram.attr,
&dev_attr_rs485.attr,
&dev_attr_adc.attr,
&dev_attr_ereset.attr,
&dev_attr_itr.attr,
NULL
};
static const struct attribute_group ts5500_attr_group = {
.attrs = ts5500_attributes,
};
static struct resource ts5500_dio1_resource[] = {
DEFINE_RES_IRQ_NAMED(7, "DIO1 interrupt"),
};
static struct platform_device ts5500_dio1_pdev = {
.name = "ts5500-dio1",
.id = -1,
.resource = ts5500_dio1_resource,
.num_resources = 1,
};
static struct resource ts5500_dio2_resource[] = {
DEFINE_RES_IRQ_NAMED(6, "DIO2 interrupt"),
};
static struct platform_device ts5500_dio2_pdev = {
.name = "ts5500-dio2",
.id = -1,
.resource = ts5500_dio2_resource,
.num_resources = 1,
};
static void ts5500_led_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
outb(!!brightness, TS5500_LED_JP_ADDR);
}
static enum led_brightness ts5500_led_get(struct led_classdev *led_cdev)
{
return (inb(TS5500_LED_JP_ADDR) & TS5500_LED) ? LED_FULL : LED_OFF;
}
static struct led_classdev ts5500_led_cdev = {
.name = "ts5500:green:",
.brightness_set = ts5500_led_set,
.brightness_get = ts5500_led_get,
};
static int ts5500_adc_convert(u8 ctrl)
{
u8 lsb, msb;
/* Start conversion (ensure the 3 MSB are set to 0) */
outb(ctrl & 0x1f, TS5500_ADC_CONV_INIT_LSB_ADDR);
/*
* The platform has CPLD logic driving the A/D converter.
* The conversion must complete within 11 microseconds,
* otherwise we have to re-initiate a conversion.
*/
udelay(TS5500_ADC_CONV_DELAY);
if (inb(TS5500_ADC_CONV_BUSY_ADDR) & TS5500_ADC_CONV_BUSY)
return -EBUSY;
/* Read the raw data */
lsb = inb(TS5500_ADC_CONV_INIT_LSB_ADDR);
msb = inb(TS5500_ADC_CONV_MSB_ADDR);
return (msb << 8) | lsb;
}
static struct max197_platform_data ts5500_adc_pdata = {
.convert = ts5500_adc_convert,
};
static struct platform_device ts5500_adc_pdev = {
.name = "max197",
.id = -1,
.dev = {
.platform_data = &ts5500_adc_pdata,
},
};
static int __init ts5500_init(void)
{
struct platform_device *pdev;
struct ts5500_sbc *sbc;
int err;
/*
* There is no DMI available or PCI bridge subvendor info,
* only the BIOS provides a 16-bit identification call.
* It is safer to find a signature in the BIOS shadow RAM.
*/
err = ts5500_check_signature();
if (err)
return err;
pdev = platform_device_register_simple("ts5500", -1, NULL, 0);
if (IS_ERR(pdev))
return PTR_ERR(pdev);
sbc = devm_kzalloc(&pdev->dev, sizeof(struct ts5500_sbc), GFP_KERNEL);
if (!sbc) {
err = -ENOMEM;
goto error;
}
err = ts5500_detect_config(sbc);
if (err)
goto error;
platform_set_drvdata(pdev, sbc);
err = sysfs_create_group(&pdev->dev.kobj, &ts5500_attr_group);
if (err)
goto error;
if (sbc->id == TS5500_PRODUCT_CODE) {
ts5500_dio1_pdev.dev.parent = &pdev->dev;
if (platform_device_register(&ts5500_dio1_pdev))
dev_warn(&pdev->dev, "DIO1 block registration failed\n");
ts5500_dio2_pdev.dev.parent = &pdev->dev;
if (platform_device_register(&ts5500_dio2_pdev))
dev_warn(&pdev->dev, "DIO2 block registration failed\n");
}
if (led_classdev_register(&pdev->dev, &ts5500_led_cdev))
dev_warn(&pdev->dev, "LED registration failed\n");
if (sbc->adc) {
ts5500_adc_pdev.dev.parent = &pdev->dev;
if (platform_device_register(&ts5500_adc_pdev))
dev_warn(&pdev->dev, "ADC registration failed\n");
}
return 0;
error:
platform_device_unregister(pdev);
return err;
}
device_initcall(ts5500_init);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Savoir-faire Linux Inc. <kernel@savoirfairelinux.com>");
MODULE_DESCRIPTION("Technologic Systems TS-5500 platform driver");

View file

@ -0,0 +1 @@
obj-$(CONFIG_X86_UV) += tlb_uv.o bios_uv.o uv_irq.o uv_sysfs.o uv_time.o uv_nmi.o

View file

@ -0,0 +1,216 @@
/*
* BIOS run time interface routines.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Copyright (c) 2008-2009 Silicon Graphics, Inc. All Rights Reserved.
* Copyright (c) Russ Anderson <rja@sgi.com>
*/
#include <linux/efi.h>
#include <linux/export.h>
#include <asm/efi.h>
#include <linux/io.h>
#include <asm/uv/bios.h>
#include <asm/uv/uv_hub.h>
static struct uv_systab uv_systab;
s64 uv_bios_call(enum uv_bios_cmd which, u64 a1, u64 a2, u64 a3, u64 a4, u64 a5)
{
struct uv_systab *tab = &uv_systab;
s64 ret;
if (!tab->function)
/*
* BIOS does not support UV systab
*/
return BIOS_STATUS_UNIMPLEMENTED;
ret = efi_call((void *)__va(tab->function), (u64)which,
a1, a2, a3, a4, a5);
return ret;
}
EXPORT_SYMBOL_GPL(uv_bios_call);
s64 uv_bios_call_irqsave(enum uv_bios_cmd which, u64 a1, u64 a2, u64 a3,
u64 a4, u64 a5)
{
unsigned long bios_flags;
s64 ret;
local_irq_save(bios_flags);
ret = uv_bios_call(which, a1, a2, a3, a4, a5);
local_irq_restore(bios_flags);
return ret;
}
s64 uv_bios_call_reentrant(enum uv_bios_cmd which, u64 a1, u64 a2, u64 a3,
u64 a4, u64 a5)
{
s64 ret;
preempt_disable();
ret = uv_bios_call(which, a1, a2, a3, a4, a5);
preempt_enable();
return ret;
}
long sn_partition_id;
EXPORT_SYMBOL_GPL(sn_partition_id);
long sn_coherency_id;
EXPORT_SYMBOL_GPL(sn_coherency_id);
long sn_region_size;
EXPORT_SYMBOL_GPL(sn_region_size);
long system_serial_number;
EXPORT_SYMBOL_GPL(system_serial_number);
int uv_type;
EXPORT_SYMBOL_GPL(uv_type);
s64 uv_bios_get_sn_info(int fc, int *uvtype, long *partid, long *coher,
long *region, long *ssn)
{
s64 ret;
u64 v0, v1;
union partition_info_u part;
ret = uv_bios_call_irqsave(UV_BIOS_GET_SN_INFO, fc,
(u64)(&v0), (u64)(&v1), 0, 0);
if (ret != BIOS_STATUS_SUCCESS)
return ret;
part.val = v0;
if (uvtype)
*uvtype = part.hub_version;
if (partid)
*partid = part.partition_id;
if (coher)
*coher = part.coherence_id;
if (region)
*region = part.region_size;
if (ssn)
*ssn = v1;
return ret;
}
EXPORT_SYMBOL_GPL(uv_bios_get_sn_info);
int
uv_bios_mq_watchlist_alloc(unsigned long addr, unsigned int mq_size,
unsigned long *intr_mmr_offset)
{
u64 watchlist;
s64 ret;
/*
* bios returns watchlist number or negative error number.
*/
ret = (int)uv_bios_call_irqsave(UV_BIOS_WATCHLIST_ALLOC, addr,
mq_size, (u64)intr_mmr_offset,
(u64)&watchlist, 0);
if (ret < BIOS_STATUS_SUCCESS)
return ret;
return watchlist;
}
EXPORT_SYMBOL_GPL(uv_bios_mq_watchlist_alloc);
int
uv_bios_mq_watchlist_free(int blade, int watchlist_num)
{
return (int)uv_bios_call_irqsave(UV_BIOS_WATCHLIST_FREE,
blade, watchlist_num, 0, 0, 0);
}
EXPORT_SYMBOL_GPL(uv_bios_mq_watchlist_free);
s64
uv_bios_change_memprotect(u64 paddr, u64 len, enum uv_memprotect perms)
{
return uv_bios_call_irqsave(UV_BIOS_MEMPROTECT, paddr, len,
perms, 0, 0);
}
EXPORT_SYMBOL_GPL(uv_bios_change_memprotect);
s64
uv_bios_reserved_page_pa(u64 buf, u64 *cookie, u64 *addr, u64 *len)
{
s64 ret;
ret = uv_bios_call_irqsave(UV_BIOS_GET_PARTITION_ADDR, (u64)cookie,
(u64)addr, buf, (u64)len, 0);
return ret;
}
EXPORT_SYMBOL_GPL(uv_bios_reserved_page_pa);
s64 uv_bios_freq_base(u64 clock_type, u64 *ticks_per_second)
{
return uv_bios_call(UV_BIOS_FREQ_BASE, clock_type,
(u64)ticks_per_second, 0, 0, 0);
}
EXPORT_SYMBOL_GPL(uv_bios_freq_base);
/*
* uv_bios_set_legacy_vga_target - Set Legacy VGA I/O Target
* @decode: true to enable target, false to disable target
* @domain: PCI domain number
* @bus: PCI bus number
*
* Returns:
* 0: Success
* -EINVAL: Invalid domain or bus number
* -ENOSYS: Capability not available
* -EBUSY: Legacy VGA I/O cannot be retargeted at this time
*/
int uv_bios_set_legacy_vga_target(bool decode, int domain, int bus)
{
return uv_bios_call(UV_BIOS_SET_LEGACY_VGA_TARGET,
(u64)decode, (u64)domain, (u64)bus, 0, 0);
}
EXPORT_SYMBOL_GPL(uv_bios_set_legacy_vga_target);
#ifdef CONFIG_EFI
void uv_bios_init(void)
{
struct uv_systab *tab;
if ((efi.uv_systab == EFI_INVALID_TABLE_ADDR) ||
(efi.uv_systab == (unsigned long)NULL)) {
printk(KERN_CRIT "No EFI UV System Table.\n");
uv_systab.function = (unsigned long)NULL;
return;
}
tab = (struct uv_systab *)ioremap(efi.uv_systab,
sizeof(struct uv_systab));
if (strncmp(tab->signature, "UVST", 4) != 0)
printk(KERN_ERR "bad signature in UV system table!");
/*
* Copy table to permanent spot for later use.
*/
memcpy(&uv_systab, tab, sizeof(struct uv_systab));
iounmap(tab);
printk(KERN_INFO "EFI UV System Table Revision %d\n",
uv_systab.revision);
}
#else /* !CONFIG_EFI */
void uv_bios_init(void) { }
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,288 @@
/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* SGI UV IRQ functions
*
* Copyright (C) 2008 Silicon Graphics, Inc. All rights reserved.
*/
#include <linux/module.h>
#include <linux/rbtree.h>
#include <linux/slab.h>
#include <linux/irq.h>
#include <asm/apic.h>
#include <asm/uv/uv_irq.h>
#include <asm/uv/uv_hub.h>
/* MMR offset and pnode of hub sourcing interrupts for a given irq */
struct uv_irq_2_mmr_pnode{
struct rb_node list;
unsigned long offset;
int pnode;
int irq;
};
static DEFINE_SPINLOCK(uv_irq_lock);
static struct rb_root uv_irq_root;
static int uv_set_irq_affinity(struct irq_data *, const struct cpumask *, bool);
static void uv_noop(struct irq_data *data) { }
static void uv_ack_apic(struct irq_data *data)
{
ack_APIC_irq();
}
static struct irq_chip uv_irq_chip = {
.name = "UV-CORE",
.irq_mask = uv_noop,
.irq_unmask = uv_noop,
.irq_eoi = uv_ack_apic,
.irq_set_affinity = uv_set_irq_affinity,
};
/*
* Add offset and pnode information of the hub sourcing interrupts to the
* rb tree for a specific irq.
*/
static int uv_set_irq_2_mmr_info(int irq, unsigned long offset, unsigned blade)
{
struct rb_node **link = &uv_irq_root.rb_node;
struct rb_node *parent = NULL;
struct uv_irq_2_mmr_pnode *n;
struct uv_irq_2_mmr_pnode *e;
unsigned long irqflags;
n = kmalloc_node(sizeof(struct uv_irq_2_mmr_pnode), GFP_KERNEL,
uv_blade_to_memory_nid(blade));
if (!n)
return -ENOMEM;
n->irq = irq;
n->offset = offset;
n->pnode = uv_blade_to_pnode(blade);
spin_lock_irqsave(&uv_irq_lock, irqflags);
/* Find the right place in the rbtree: */
while (*link) {
parent = *link;
e = rb_entry(parent, struct uv_irq_2_mmr_pnode, list);
if (unlikely(irq == e->irq)) {
/* irq entry exists */
e->pnode = uv_blade_to_pnode(blade);
e->offset = offset;
spin_unlock_irqrestore(&uv_irq_lock, irqflags);
kfree(n);
return 0;
}
if (irq < e->irq)
link = &(*link)->rb_left;
else
link = &(*link)->rb_right;
}
/* Insert the node into the rbtree. */
rb_link_node(&n->list, parent, link);
rb_insert_color(&n->list, &uv_irq_root);
spin_unlock_irqrestore(&uv_irq_lock, irqflags);
return 0;
}
/* Retrieve offset and pnode information from the rb tree for a specific irq */
int uv_irq_2_mmr_info(int irq, unsigned long *offset, int *pnode)
{
struct uv_irq_2_mmr_pnode *e;
struct rb_node *n;
unsigned long irqflags;
spin_lock_irqsave(&uv_irq_lock, irqflags);
n = uv_irq_root.rb_node;
while (n) {
e = rb_entry(n, struct uv_irq_2_mmr_pnode, list);
if (e->irq == irq) {
*offset = e->offset;
*pnode = e->pnode;
spin_unlock_irqrestore(&uv_irq_lock, irqflags);
return 0;
}
if (irq < e->irq)
n = n->rb_left;
else
n = n->rb_right;
}
spin_unlock_irqrestore(&uv_irq_lock, irqflags);
return -1;
}
/*
* Re-target the irq to the specified CPU and enable the specified MMR located
* on the specified blade to allow the sending of MSIs to the specified CPU.
*/
static int
arch_enable_uv_irq(char *irq_name, unsigned int irq, int cpu, int mmr_blade,
unsigned long mmr_offset, int limit)
{
const struct cpumask *eligible_cpu = cpumask_of(cpu);
struct irq_cfg *cfg = irq_get_chip_data(irq);
unsigned long mmr_value;
struct uv_IO_APIC_route_entry *entry;
int mmr_pnode, err;
unsigned int dest;
BUILD_BUG_ON(sizeof(struct uv_IO_APIC_route_entry) !=
sizeof(unsigned long));
err = assign_irq_vector(irq, cfg, eligible_cpu);
if (err != 0)
return err;
err = apic->cpu_mask_to_apicid_and(eligible_cpu, eligible_cpu, &dest);
if (err != 0)
return err;
if (limit == UV_AFFINITY_CPU)
irq_set_status_flags(irq, IRQ_NO_BALANCING);
else
irq_set_status_flags(irq, IRQ_MOVE_PCNTXT);
irq_set_chip_and_handler_name(irq, &uv_irq_chip, handle_percpu_irq,
irq_name);
mmr_value = 0;
entry = (struct uv_IO_APIC_route_entry *)&mmr_value;
entry->vector = cfg->vector;
entry->delivery_mode = apic->irq_delivery_mode;
entry->dest_mode = apic->irq_dest_mode;
entry->polarity = 0;
entry->trigger = 0;
entry->mask = 0;
entry->dest = dest;
mmr_pnode = uv_blade_to_pnode(mmr_blade);
uv_write_global_mmr64(mmr_pnode, mmr_offset, mmr_value);
if (cfg->move_in_progress)
send_cleanup_vector(cfg);
return irq;
}
/*
* Disable the specified MMR located on the specified blade so that MSIs are
* longer allowed to be sent.
*/
static void arch_disable_uv_irq(int mmr_pnode, unsigned long mmr_offset)
{
unsigned long mmr_value;
struct uv_IO_APIC_route_entry *entry;
BUILD_BUG_ON(sizeof(struct uv_IO_APIC_route_entry) !=
sizeof(unsigned long));
mmr_value = 0;
entry = (struct uv_IO_APIC_route_entry *)&mmr_value;
entry->mask = 1;
uv_write_global_mmr64(mmr_pnode, mmr_offset, mmr_value);
}
static int
uv_set_irq_affinity(struct irq_data *data, const struct cpumask *mask,
bool force)
{
struct irq_cfg *cfg = data->chip_data;
unsigned int dest;
unsigned long mmr_value, mmr_offset;
struct uv_IO_APIC_route_entry *entry;
int mmr_pnode;
if (__ioapic_set_affinity(data, mask, &dest))
return -1;
mmr_value = 0;
entry = (struct uv_IO_APIC_route_entry *)&mmr_value;
entry->vector = cfg->vector;
entry->delivery_mode = apic->irq_delivery_mode;
entry->dest_mode = apic->irq_dest_mode;
entry->polarity = 0;
entry->trigger = 0;
entry->mask = 0;
entry->dest = dest;
/* Get previously stored MMR and pnode of hub sourcing interrupts */
if (uv_irq_2_mmr_info(data->irq, &mmr_offset, &mmr_pnode))
return -1;
uv_write_global_mmr64(mmr_pnode, mmr_offset, mmr_value);
if (cfg->move_in_progress)
send_cleanup_vector(cfg);
return IRQ_SET_MASK_OK_NOCOPY;
}
/*
* Set up a mapping of an available irq and vector, and enable the specified
* MMR that defines the MSI that is to be sent to the specified CPU when an
* interrupt is raised.
*/
int uv_setup_irq(char *irq_name, int cpu, int mmr_blade,
unsigned long mmr_offset, int limit)
{
int ret, irq = irq_alloc_hwirq(uv_blade_to_memory_nid(mmr_blade));
if (!irq)
return -EBUSY;
ret = arch_enable_uv_irq(irq_name, irq, cpu, mmr_blade, mmr_offset,
limit);
if (ret == irq)
uv_set_irq_2_mmr_info(irq, mmr_offset, mmr_blade);
else
irq_free_hwirq(irq);
return ret;
}
EXPORT_SYMBOL_GPL(uv_setup_irq);
/*
* Tear down a mapping of an irq and vector, and disable the specified MMR that
* defined the MSI that was to be sent to the specified CPU when an interrupt
* was raised.
*
* Set mmr_blade and mmr_offset to what was passed in on uv_setup_irq().
*/
void uv_teardown_irq(unsigned int irq)
{
struct uv_irq_2_mmr_pnode *e;
struct rb_node *n;
unsigned long irqflags;
spin_lock_irqsave(&uv_irq_lock, irqflags);
n = uv_irq_root.rb_node;
while (n) {
e = rb_entry(n, struct uv_irq_2_mmr_pnode, list);
if (e->irq == irq) {
arch_disable_uv_irq(e->pnode, e->offset);
rb_erase(n, &uv_irq_root);
kfree(e);
break;
}
if (irq < e->irq)
n = n->rb_left;
else
n = n->rb_right;
}
spin_unlock_irqrestore(&uv_irq_lock, irqflags);
irq_free_hwirq(irq);
}
EXPORT_SYMBOL_GPL(uv_teardown_irq);

View file

@ -0,0 +1,727 @@
/*
* SGI NMI support routines
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Copyright (c) 2009-2013 Silicon Graphics, Inc. All Rights Reserved.
* Copyright (c) Mike Travis
*/
#include <linux/cpu.h>
#include <linux/delay.h>
#include <linux/kdb.h>
#include <linux/kexec.h>
#include <linux/kgdb.h>
#include <linux/module.h>
#include <linux/nmi.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <asm/apic.h>
#include <asm/current.h>
#include <asm/kdebug.h>
#include <asm/local64.h>
#include <asm/nmi.h>
#include <asm/traps.h>
#include <asm/uv/uv.h>
#include <asm/uv/uv_hub.h>
#include <asm/uv/uv_mmrs.h>
/*
* UV handler for NMI
*
* Handle system-wide NMI events generated by the global 'power nmi' command.
*
* Basic operation is to field the NMI interrupt on each cpu and wait
* until all cpus have arrived into the nmi handler. If some cpus do not
* make it into the handler, try and force them in with the IPI(NMI) signal.
*
* We also have to lessen UV Hub MMR accesses as much as possible as this
* disrupts the UV Hub's primary mission of directing NumaLink traffic and
* can cause system problems to occur.
*
* To do this we register our primary NMI notifier on the NMI_UNKNOWN
* chain. This reduces the number of false NMI calls when the perf
* tools are running which generate an enormous number of NMIs per
* second (~4M/s for 1024 cpu threads). Our secondary NMI handler is
* very short as it only checks that if it has been "pinged" with the
* IPI(NMI) signal as mentioned above, and does not read the UV Hub's MMR.
*
*/
static struct uv_hub_nmi_s **uv_hub_nmi_list;
DEFINE_PER_CPU(struct uv_cpu_nmi_s, uv_cpu_nmi);
EXPORT_PER_CPU_SYMBOL_GPL(uv_cpu_nmi);
static unsigned long nmi_mmr;
static unsigned long nmi_mmr_clear;
static unsigned long nmi_mmr_pending;
static atomic_t uv_in_nmi;
static atomic_t uv_nmi_cpu = ATOMIC_INIT(-1);
static atomic_t uv_nmi_cpus_in_nmi = ATOMIC_INIT(-1);
static atomic_t uv_nmi_slave_continue;
static cpumask_var_t uv_nmi_cpu_mask;
/* Values for uv_nmi_slave_continue */
#define SLAVE_CLEAR 0
#define SLAVE_CONTINUE 1
#define SLAVE_EXIT 2
/*
* Default is all stack dumps go to the console and buffer.
* Lower level to send to log buffer only.
*/
static int uv_nmi_loglevel = CONSOLE_LOGLEVEL_DEFAULT;
module_param_named(dump_loglevel, uv_nmi_loglevel, int, 0644);
/*
* The following values show statistics on how perf events are affecting
* this system.
*/
static int param_get_local64(char *buffer, const struct kernel_param *kp)
{
return sprintf(buffer, "%lu\n", local64_read((local64_t *)kp->arg));
}
static int param_set_local64(const char *val, const struct kernel_param *kp)
{
/* clear on any write */
local64_set((local64_t *)kp->arg, 0);
return 0;
}
static struct kernel_param_ops param_ops_local64 = {
.get = param_get_local64,
.set = param_set_local64,
};
#define param_check_local64(name, p) __param_check(name, p, local64_t)
static local64_t uv_nmi_count;
module_param_named(nmi_count, uv_nmi_count, local64, 0644);
static local64_t uv_nmi_misses;
module_param_named(nmi_misses, uv_nmi_misses, local64, 0644);
static local64_t uv_nmi_ping_count;
module_param_named(ping_count, uv_nmi_ping_count, local64, 0644);
static local64_t uv_nmi_ping_misses;
module_param_named(ping_misses, uv_nmi_ping_misses, local64, 0644);
/*
* Following values allow tuning for large systems under heavy loading
*/
static int uv_nmi_initial_delay = 100;
module_param_named(initial_delay, uv_nmi_initial_delay, int, 0644);
static int uv_nmi_slave_delay = 100;
module_param_named(slave_delay, uv_nmi_slave_delay, int, 0644);
static int uv_nmi_loop_delay = 100;
module_param_named(loop_delay, uv_nmi_loop_delay, int, 0644);
static int uv_nmi_trigger_delay = 10000;
module_param_named(trigger_delay, uv_nmi_trigger_delay, int, 0644);
static int uv_nmi_wait_count = 100;
module_param_named(wait_count, uv_nmi_wait_count, int, 0644);
static int uv_nmi_retry_count = 500;
module_param_named(retry_count, uv_nmi_retry_count, int, 0644);
/*
* Valid NMI Actions:
* "dump" - dump process stack for each cpu
* "ips" - dump IP info for each cpu
* "kdump" - do crash dump
* "kdb" - enter KDB (default)
* "kgdb" - enter KGDB
*/
static char uv_nmi_action[8] = "kdb";
module_param_string(action, uv_nmi_action, sizeof(uv_nmi_action), 0644);
static inline bool uv_nmi_action_is(const char *action)
{
return (strncmp(uv_nmi_action, action, strlen(action)) == 0);
}
/* Setup which NMI support is present in system */
static void uv_nmi_setup_mmrs(void)
{
if (uv_read_local_mmr(UVH_NMI_MMRX_SUPPORTED)) {
uv_write_local_mmr(UVH_NMI_MMRX_REQ,
1UL << UVH_NMI_MMRX_REQ_SHIFT);
nmi_mmr = UVH_NMI_MMRX;
nmi_mmr_clear = UVH_NMI_MMRX_CLEAR;
nmi_mmr_pending = 1UL << UVH_NMI_MMRX_SHIFT;
pr_info("UV: SMI NMI support: %s\n", UVH_NMI_MMRX_TYPE);
} else {
nmi_mmr = UVH_NMI_MMR;
nmi_mmr_clear = UVH_NMI_MMR_CLEAR;
nmi_mmr_pending = 1UL << UVH_NMI_MMR_SHIFT;
pr_info("UV: SMI NMI support: %s\n", UVH_NMI_MMR_TYPE);
}
}
/* Read NMI MMR and check if NMI flag was set by BMC. */
static inline int uv_nmi_test_mmr(struct uv_hub_nmi_s *hub_nmi)
{
hub_nmi->nmi_value = uv_read_local_mmr(nmi_mmr);
atomic_inc(&hub_nmi->read_mmr_count);
return !!(hub_nmi->nmi_value & nmi_mmr_pending);
}
static inline void uv_local_mmr_clear_nmi(void)
{
uv_write_local_mmr(nmi_mmr_clear, nmi_mmr_pending);
}
/*
* If first cpu in on this hub, set hub_nmi "in_nmi" and "owner" values and
* return true. If first cpu in on the system, set global "in_nmi" flag.
*/
static int uv_set_in_nmi(int cpu, struct uv_hub_nmi_s *hub_nmi)
{
int first = atomic_add_unless(&hub_nmi->in_nmi, 1, 1);
if (first) {
atomic_set(&hub_nmi->cpu_owner, cpu);
if (atomic_add_unless(&uv_in_nmi, 1, 1))
atomic_set(&uv_nmi_cpu, cpu);
atomic_inc(&hub_nmi->nmi_count);
}
return first;
}
/* Check if this is a system NMI event */
static int uv_check_nmi(struct uv_hub_nmi_s *hub_nmi)
{
int cpu = smp_processor_id();
int nmi = 0;
local64_inc(&uv_nmi_count);
this_cpu_inc(uv_cpu_nmi.queries);
do {
nmi = atomic_read(&hub_nmi->in_nmi);
if (nmi)
break;
if (raw_spin_trylock(&hub_nmi->nmi_lock)) {
/* check hub MMR NMI flag */
if (uv_nmi_test_mmr(hub_nmi)) {
uv_set_in_nmi(cpu, hub_nmi);
nmi = 1;
break;
}
/* MMR NMI flag is clear */
raw_spin_unlock(&hub_nmi->nmi_lock);
} else {
/* wait a moment for the hub nmi locker to set flag */
cpu_relax();
udelay(uv_nmi_slave_delay);
/* re-check hub in_nmi flag */
nmi = atomic_read(&hub_nmi->in_nmi);
if (nmi)
break;
}
/* check if this BMC missed setting the MMR NMI flag */
if (!nmi) {
nmi = atomic_read(&uv_in_nmi);
if (nmi)
uv_set_in_nmi(cpu, hub_nmi);
}
} while (0);
if (!nmi)
local64_inc(&uv_nmi_misses);
return nmi;
}
/* Need to reset the NMI MMR register, but only once per hub. */
static inline void uv_clear_nmi(int cpu)
{
struct uv_hub_nmi_s *hub_nmi = uv_hub_nmi;
if (cpu == atomic_read(&hub_nmi->cpu_owner)) {
atomic_set(&hub_nmi->cpu_owner, -1);
atomic_set(&hub_nmi->in_nmi, 0);
uv_local_mmr_clear_nmi();
raw_spin_unlock(&hub_nmi->nmi_lock);
}
}
/* Print non-responding cpus */
static void uv_nmi_nr_cpus_pr(char *fmt)
{
static char cpu_list[1024];
int len = sizeof(cpu_list);
int c = cpumask_weight(uv_nmi_cpu_mask);
int n = cpulist_scnprintf(cpu_list, len, uv_nmi_cpu_mask);
if (n >= len-1)
strcpy(&cpu_list[len - 6], "...\n");
printk(fmt, c, cpu_list);
}
/* Ping non-responding cpus attemping to force them into the NMI handler */
static void uv_nmi_nr_cpus_ping(void)
{
int cpu;
for_each_cpu(cpu, uv_nmi_cpu_mask)
uv_cpu_nmi_per(cpu).pinging = 1;
apic->send_IPI_mask(uv_nmi_cpu_mask, APIC_DM_NMI);
}
/* Clean up flags for cpus that ignored both NMI and ping */
static void uv_nmi_cleanup_mask(void)
{
int cpu;
for_each_cpu(cpu, uv_nmi_cpu_mask) {
uv_cpu_nmi_per(cpu).pinging = 0;
uv_cpu_nmi_per(cpu).state = UV_NMI_STATE_OUT;
cpumask_clear_cpu(cpu, uv_nmi_cpu_mask);
}
}
/* Loop waiting as cpus enter nmi handler */
static int uv_nmi_wait_cpus(int first)
{
int i, j, k, n = num_online_cpus();
int last_k = 0, waiting = 0;
if (first) {
cpumask_copy(uv_nmi_cpu_mask, cpu_online_mask);
k = 0;
} else {
k = n - cpumask_weight(uv_nmi_cpu_mask);
}
udelay(uv_nmi_initial_delay);
for (i = 0; i < uv_nmi_retry_count; i++) {
int loop_delay = uv_nmi_loop_delay;
for_each_cpu(j, uv_nmi_cpu_mask) {
if (uv_cpu_nmi_per(j).state) {
cpumask_clear_cpu(j, uv_nmi_cpu_mask);
if (++k >= n)
break;
}
}
if (k >= n) { /* all in? */
k = n;
break;
}
if (last_k != k) { /* abort if no new cpus coming in */
last_k = k;
waiting = 0;
} else if (++waiting > uv_nmi_wait_count)
break;
/* extend delay if waiting only for cpu 0 */
if (waiting && (n - k) == 1 &&
cpumask_test_cpu(0, uv_nmi_cpu_mask))
loop_delay *= 100;
udelay(loop_delay);
}
atomic_set(&uv_nmi_cpus_in_nmi, k);
return n - k;
}
/* Wait until all slave cpus have entered UV NMI handler */
static void uv_nmi_wait(int master)
{
/* indicate this cpu is in */
this_cpu_write(uv_cpu_nmi.state, UV_NMI_STATE_IN);
/* if not the first cpu in (the master), then we are a slave cpu */
if (!master)
return;
do {
/* wait for all other cpus to gather here */
if (!uv_nmi_wait_cpus(1))
break;
/* if not all made it in, send IPI NMI to them */
uv_nmi_nr_cpus_pr(KERN_ALERT
"UV: Sending NMI IPI to %d non-responding CPUs: %s\n");
uv_nmi_nr_cpus_ping();
/* if all cpus are in, then done */
if (!uv_nmi_wait_cpus(0))
break;
uv_nmi_nr_cpus_pr(KERN_ALERT
"UV: %d CPUs not in NMI loop: %s\n");
} while (0);
pr_alert("UV: %d of %d CPUs in NMI\n",
atomic_read(&uv_nmi_cpus_in_nmi), num_online_cpus());
}
static void uv_nmi_dump_cpu_ip_hdr(void)
{
printk(KERN_DEFAULT
"\nUV: %4s %6s %-32s %s (Note: PID 0 not listed)\n",
"CPU", "PID", "COMMAND", "IP");
}
static void uv_nmi_dump_cpu_ip(int cpu, struct pt_regs *regs)
{
printk(KERN_DEFAULT "UV: %4d %6d %-32.32s ",
cpu, current->pid, current->comm);
printk_address(regs->ip);
}
/* Dump this cpu's state */
static void uv_nmi_dump_state_cpu(int cpu, struct pt_regs *regs)
{
const char *dots = " ................................. ";
if (uv_nmi_action_is("ips")) {
if (cpu == 0)
uv_nmi_dump_cpu_ip_hdr();
if (current->pid != 0)
uv_nmi_dump_cpu_ip(cpu, regs);
} else if (uv_nmi_action_is("dump")) {
printk(KERN_DEFAULT
"UV:%sNMI process trace for CPU %d\n", dots, cpu);
show_regs(regs);
}
this_cpu_write(uv_cpu_nmi.state, UV_NMI_STATE_DUMP_DONE);
}
/* Trigger a slave cpu to dump it's state */
static void uv_nmi_trigger_dump(int cpu)
{
int retry = uv_nmi_trigger_delay;
if (uv_cpu_nmi_per(cpu).state != UV_NMI_STATE_IN)
return;
uv_cpu_nmi_per(cpu).state = UV_NMI_STATE_DUMP;
do {
cpu_relax();
udelay(10);
if (uv_cpu_nmi_per(cpu).state
!= UV_NMI_STATE_DUMP)
return;
} while (--retry > 0);
pr_crit("UV: CPU %d stuck in process dump function\n", cpu);
uv_cpu_nmi_per(cpu).state = UV_NMI_STATE_DUMP_DONE;
}
/* Wait until all cpus ready to exit */
static void uv_nmi_sync_exit(int master)
{
atomic_dec(&uv_nmi_cpus_in_nmi);
if (master) {
while (atomic_read(&uv_nmi_cpus_in_nmi) > 0)
cpu_relax();
atomic_set(&uv_nmi_slave_continue, SLAVE_CLEAR);
} else {
while (atomic_read(&uv_nmi_slave_continue))
cpu_relax();
}
}
/* Walk through cpu list and dump state of each */
static void uv_nmi_dump_state(int cpu, struct pt_regs *regs, int master)
{
if (master) {
int tcpu;
int ignored = 0;
int saved_console_loglevel = console_loglevel;
pr_alert("UV: tracing %s for %d CPUs from CPU %d\n",
uv_nmi_action_is("ips") ? "IPs" : "processes",
atomic_read(&uv_nmi_cpus_in_nmi), cpu);
console_loglevel = uv_nmi_loglevel;
atomic_set(&uv_nmi_slave_continue, SLAVE_EXIT);
for_each_online_cpu(tcpu) {
if (cpumask_test_cpu(tcpu, uv_nmi_cpu_mask))
ignored++;
else if (tcpu == cpu)
uv_nmi_dump_state_cpu(tcpu, regs);
else
uv_nmi_trigger_dump(tcpu);
}
if (ignored)
printk(KERN_DEFAULT "UV: %d CPUs ignored NMI\n",
ignored);
console_loglevel = saved_console_loglevel;
pr_alert("UV: process trace complete\n");
} else {
while (!atomic_read(&uv_nmi_slave_continue))
cpu_relax();
while (this_cpu_read(uv_cpu_nmi.state) != UV_NMI_STATE_DUMP)
cpu_relax();
uv_nmi_dump_state_cpu(cpu, regs);
}
uv_nmi_sync_exit(master);
}
static void uv_nmi_touch_watchdogs(void)
{
touch_softlockup_watchdog_sync();
clocksource_touch_watchdog();
rcu_cpu_stall_reset();
touch_nmi_watchdog();
}
#if defined(CONFIG_KEXEC)
static atomic_t uv_nmi_kexec_failed;
static void uv_nmi_kdump(int cpu, int master, struct pt_regs *regs)
{
/* Call crash to dump system state */
if (master) {
pr_emerg("UV: NMI executing crash_kexec on CPU%d\n", cpu);
crash_kexec(regs);
pr_emerg("UV: crash_kexec unexpectedly returned, ");
if (!kexec_crash_image) {
pr_cont("crash kernel not loaded\n");
atomic_set(&uv_nmi_kexec_failed, 1);
uv_nmi_sync_exit(1);
return;
}
pr_cont("kexec busy, stalling cpus while waiting\n");
}
/* If crash exec fails the slaves should return, otherwise stall */
while (atomic_read(&uv_nmi_kexec_failed) == 0)
mdelay(10);
/* Crash kernel most likely not loaded, return in an orderly fashion */
uv_nmi_sync_exit(0);
}
#else /* !CONFIG_KEXEC */
static inline void uv_nmi_kdump(int cpu, int master, struct pt_regs *regs)
{
if (master)
pr_err("UV: NMI kdump: KEXEC not supported in this kernel\n");
}
#endif /* !CONFIG_KEXEC */
#ifdef CONFIG_KGDB
#ifdef CONFIG_KGDB_KDB
static inline int uv_nmi_kdb_reason(void)
{
return KDB_REASON_SYSTEM_NMI;
}
#else /* !CONFIG_KGDB_KDB */
static inline int uv_nmi_kdb_reason(void)
{
/* Insure user is expecting to attach gdb remote */
if (uv_nmi_action_is("kgdb"))
return 0;
pr_err("UV: NMI error: KDB is not enabled in this kernel\n");
return -1;
}
#endif /* CONFIG_KGDB_KDB */
/*
* Call KGDB/KDB from NMI handler
*
* Note that if both KGDB and KDB are configured, then the action of 'kgdb' or
* 'kdb' has no affect on which is used. See the KGDB documention for further
* information.
*/
static void uv_call_kgdb_kdb(int cpu, struct pt_regs *regs, int master)
{
if (master) {
int reason = uv_nmi_kdb_reason();
int ret;
if (reason < 0)
return;
/* call KGDB NMI handler as MASTER */
ret = kgdb_nmicallin(cpu, X86_TRAP_NMI, regs, reason,
&uv_nmi_slave_continue);
if (ret) {
pr_alert("KGDB returned error, is kgdboc set?\n");
atomic_set(&uv_nmi_slave_continue, SLAVE_EXIT);
}
} else {
/* wait for KGDB signal that it's ready for slaves to enter */
int sig;
do {
cpu_relax();
sig = atomic_read(&uv_nmi_slave_continue);
} while (!sig);
/* call KGDB as slave */
if (sig == SLAVE_CONTINUE)
kgdb_nmicallback(cpu, regs);
}
uv_nmi_sync_exit(master);
}
#else /* !CONFIG_KGDB */
static inline void uv_call_kgdb_kdb(int cpu, struct pt_regs *regs, int master)
{
pr_err("UV: NMI error: KGDB is not enabled in this kernel\n");
}
#endif /* !CONFIG_KGDB */
/*
* UV NMI handler
*/
int uv_handle_nmi(unsigned int reason, struct pt_regs *regs)
{
struct uv_hub_nmi_s *hub_nmi = uv_hub_nmi;
int cpu = smp_processor_id();
int master = 0;
unsigned long flags;
local_irq_save(flags);
/* If not a UV System NMI, ignore */
if (!this_cpu_read(uv_cpu_nmi.pinging) && !uv_check_nmi(hub_nmi)) {
local_irq_restore(flags);
return NMI_DONE;
}
/* Indicate we are the first CPU into the NMI handler */
master = (atomic_read(&uv_nmi_cpu) == cpu);
/* If NMI action is "kdump", then attempt to do it */
if (uv_nmi_action_is("kdump"))
uv_nmi_kdump(cpu, master, regs);
/* Pause as all cpus enter the NMI handler */
uv_nmi_wait(master);
/* Dump state of each cpu */
if (uv_nmi_action_is("ips") || uv_nmi_action_is("dump"))
uv_nmi_dump_state(cpu, regs, master);
/* Call KGDB/KDB if enabled */
else if (uv_nmi_action_is("kdb") || uv_nmi_action_is("kgdb"))
uv_call_kgdb_kdb(cpu, regs, master);
/* Clear per_cpu "in nmi" flag */
this_cpu_write(uv_cpu_nmi.state, UV_NMI_STATE_OUT);
/* Clear MMR NMI flag on each hub */
uv_clear_nmi(cpu);
/* Clear global flags */
if (master) {
if (cpumask_weight(uv_nmi_cpu_mask))
uv_nmi_cleanup_mask();
atomic_set(&uv_nmi_cpus_in_nmi, -1);
atomic_set(&uv_nmi_cpu, -1);
atomic_set(&uv_in_nmi, 0);
}
uv_nmi_touch_watchdogs();
local_irq_restore(flags);
return NMI_HANDLED;
}
/*
* NMI handler for pulling in CPUs when perf events are grabbing our NMI
*/
static int uv_handle_nmi_ping(unsigned int reason, struct pt_regs *regs)
{
int ret;
this_cpu_inc(uv_cpu_nmi.queries);
if (!this_cpu_read(uv_cpu_nmi.pinging)) {
local64_inc(&uv_nmi_ping_misses);
return NMI_DONE;
}
this_cpu_inc(uv_cpu_nmi.pings);
local64_inc(&uv_nmi_ping_count);
ret = uv_handle_nmi(reason, regs);
this_cpu_write(uv_cpu_nmi.pinging, 0);
return ret;
}
static void uv_register_nmi_notifier(void)
{
if (register_nmi_handler(NMI_UNKNOWN, uv_handle_nmi, 0, "uv"))
pr_warn("UV: NMI handler failed to register\n");
if (register_nmi_handler(NMI_LOCAL, uv_handle_nmi_ping, 0, "uvping"))
pr_warn("UV: PING NMI handler failed to register\n");
}
void uv_nmi_init(void)
{
unsigned int value;
/*
* Unmask NMI on all cpus
*/
value = apic_read(APIC_LVT1) | APIC_DM_NMI;
value &= ~APIC_LVT_MASKED;
apic_write(APIC_LVT1, value);
}
void uv_nmi_setup(void)
{
int size = sizeof(void *) * (1 << NODES_SHIFT);
int cpu, nid;
/* Setup hub nmi info */
uv_nmi_setup_mmrs();
uv_hub_nmi_list = kzalloc(size, GFP_KERNEL);
pr_info("UV: NMI hub list @ 0x%p (%d)\n", uv_hub_nmi_list, size);
BUG_ON(!uv_hub_nmi_list);
size = sizeof(struct uv_hub_nmi_s);
for_each_present_cpu(cpu) {
nid = cpu_to_node(cpu);
if (uv_hub_nmi_list[nid] == NULL) {
uv_hub_nmi_list[nid] = kzalloc_node(size,
GFP_KERNEL, nid);
BUG_ON(!uv_hub_nmi_list[nid]);
raw_spin_lock_init(&(uv_hub_nmi_list[nid]->nmi_lock));
atomic_set(&uv_hub_nmi_list[nid]->cpu_owner, -1);
}
uv_hub_nmi_per(cpu) = uv_hub_nmi_list[nid];
}
BUG_ON(!alloc_cpumask_var(&uv_nmi_cpu_mask, GFP_KERNEL));
uv_register_nmi_notifier();
}

View 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);

View file

@ -0,0 +1,425 @@
/*
* SGI RTC clock/timer routines.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Copyright (c) 2009-2013 Silicon Graphics, Inc. All Rights Reserved.
* Copyright (c) Dimitri Sivanich
*/
#include <linux/clockchips.h>
#include <linux/slab.h>
#include <asm/uv/uv_mmrs.h>
#include <asm/uv/uv_hub.h>
#include <asm/uv/bios.h>
#include <asm/uv/uv.h>
#include <asm/apic.h>
#include <asm/cpu.h>
#define RTC_NAME "sgi_rtc"
static cycle_t uv_read_rtc(struct clocksource *cs);
static int uv_rtc_next_event(unsigned long, struct clock_event_device *);
static void uv_rtc_timer_setup(enum clock_event_mode,
struct clock_event_device *);
static struct clocksource clocksource_uv = {
.name = RTC_NAME,
.rating = 299,
.read = uv_read_rtc,
.mask = (cycle_t)UVH_RTC_REAL_TIME_CLOCK_MASK,
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
};
static struct clock_event_device clock_event_device_uv = {
.name = RTC_NAME,
.features = CLOCK_EVT_FEAT_ONESHOT,
.shift = 20,
.rating = 400,
.irq = -1,
.set_next_event = uv_rtc_next_event,
.set_mode = uv_rtc_timer_setup,
.event_handler = NULL,
};
static DEFINE_PER_CPU(struct clock_event_device, cpu_ced);
/* There is one of these allocated per node */
struct uv_rtc_timer_head {
spinlock_t lock;
/* next cpu waiting for timer, local node relative: */
int next_cpu;
/* number of cpus on this node: */
int ncpus;
struct {
int lcpu; /* systemwide logical cpu number */
u64 expires; /* next timer expiration for this cpu */
} cpu[1];
};
/*
* Access to uv_rtc_timer_head via blade id.
*/
static struct uv_rtc_timer_head **blade_info __read_mostly;
static int uv_rtc_evt_enable;
/*
* Hardware interface routines
*/
/* Send IPIs to another node */
static void uv_rtc_send_IPI(int cpu)
{
unsigned long apicid, val;
int pnode;
apicid = cpu_physical_id(cpu);
pnode = uv_apicid_to_pnode(apicid);
apicid |= uv_apicid_hibits;
val = (1UL << UVH_IPI_INT_SEND_SHFT) |
(apicid << UVH_IPI_INT_APIC_ID_SHFT) |
(X86_PLATFORM_IPI_VECTOR << UVH_IPI_INT_VECTOR_SHFT);
uv_write_global_mmr64(pnode, UVH_IPI_INT, val);
}
/* Check for an RTC interrupt pending */
static int uv_intr_pending(int pnode)
{
if (is_uv1_hub())
return uv_read_global_mmr64(pnode, UVH_EVENT_OCCURRED0) &
UV1H_EVENT_OCCURRED0_RTC1_MASK;
else if (is_uvx_hub())
return uv_read_global_mmr64(pnode, UVXH_EVENT_OCCURRED2) &
UVXH_EVENT_OCCURRED2_RTC_1_MASK;
return 0;
}
/* Setup interrupt and return non-zero if early expiration occurred. */
static int uv_setup_intr(int cpu, u64 expires)
{
u64 val;
unsigned long apicid = cpu_physical_id(cpu) | uv_apicid_hibits;
int pnode = uv_cpu_to_pnode(cpu);
uv_write_global_mmr64(pnode, UVH_RTC1_INT_CONFIG,
UVH_RTC1_INT_CONFIG_M_MASK);
uv_write_global_mmr64(pnode, UVH_INT_CMPB, -1L);
if (is_uv1_hub())
uv_write_global_mmr64(pnode, UVH_EVENT_OCCURRED0_ALIAS,
UV1H_EVENT_OCCURRED0_RTC1_MASK);
else
uv_write_global_mmr64(pnode, UVXH_EVENT_OCCURRED2_ALIAS,
UVXH_EVENT_OCCURRED2_RTC_1_MASK);
val = (X86_PLATFORM_IPI_VECTOR << UVH_RTC1_INT_CONFIG_VECTOR_SHFT) |
((u64)apicid << UVH_RTC1_INT_CONFIG_APIC_ID_SHFT);
/* Set configuration */
uv_write_global_mmr64(pnode, UVH_RTC1_INT_CONFIG, val);
/* Initialize comparator value */
uv_write_global_mmr64(pnode, UVH_INT_CMPB, expires);
if (uv_read_rtc(NULL) <= expires)
return 0;
return !uv_intr_pending(pnode);
}
/*
* Per-cpu timer tracking routines
*/
static __init void uv_rtc_deallocate_timers(void)
{
int bid;
for_each_possible_blade(bid) {
kfree(blade_info[bid]);
}
kfree(blade_info);
}
/* Allocate per-node list of cpu timer expiration times. */
static __init int uv_rtc_allocate_timers(void)
{
int cpu;
blade_info = kzalloc(uv_possible_blades * sizeof(void *), GFP_KERNEL);
if (!blade_info)
return -ENOMEM;
for_each_present_cpu(cpu) {
int nid = cpu_to_node(cpu);
int bid = uv_cpu_to_blade_id(cpu);
int bcpu = uv_cpu_hub_info(cpu)->blade_processor_id;
struct uv_rtc_timer_head *head = blade_info[bid];
if (!head) {
head = kmalloc_node(sizeof(struct uv_rtc_timer_head) +
(uv_blade_nr_possible_cpus(bid) *
2 * sizeof(u64)),
GFP_KERNEL, nid);
if (!head) {
uv_rtc_deallocate_timers();
return -ENOMEM;
}
spin_lock_init(&head->lock);
head->ncpus = uv_blade_nr_possible_cpus(bid);
head->next_cpu = -1;
blade_info[bid] = head;
}
head->cpu[bcpu].lcpu = cpu;
head->cpu[bcpu].expires = ULLONG_MAX;
}
return 0;
}
/* Find and set the next expiring timer. */
static void uv_rtc_find_next_timer(struct uv_rtc_timer_head *head, int pnode)
{
u64 lowest = ULLONG_MAX;
int c, bcpu = -1;
head->next_cpu = -1;
for (c = 0; c < head->ncpus; c++) {
u64 exp = head->cpu[c].expires;
if (exp < lowest) {
bcpu = c;
lowest = exp;
}
}
if (bcpu >= 0) {
head->next_cpu = bcpu;
c = head->cpu[bcpu].lcpu;
if (uv_setup_intr(c, lowest))
/* If we didn't set it up in time, trigger */
uv_rtc_send_IPI(c);
} else {
uv_write_global_mmr64(pnode, UVH_RTC1_INT_CONFIG,
UVH_RTC1_INT_CONFIG_M_MASK);
}
}
/*
* Set expiration time for current cpu.
*
* Returns 1 if we missed the expiration time.
*/
static int uv_rtc_set_timer(int cpu, u64 expires)
{
int pnode = uv_cpu_to_pnode(cpu);
int bid = uv_cpu_to_blade_id(cpu);
struct uv_rtc_timer_head *head = blade_info[bid];
int bcpu = uv_cpu_hub_info(cpu)->blade_processor_id;
u64 *t = &head->cpu[bcpu].expires;
unsigned long flags;
int next_cpu;
spin_lock_irqsave(&head->lock, flags);
next_cpu = head->next_cpu;
*t = expires;
/* Will this one be next to go off? */
if (next_cpu < 0 || bcpu == next_cpu ||
expires < head->cpu[next_cpu].expires) {
head->next_cpu = bcpu;
if (uv_setup_intr(cpu, expires)) {
*t = ULLONG_MAX;
uv_rtc_find_next_timer(head, pnode);
spin_unlock_irqrestore(&head->lock, flags);
return -ETIME;
}
}
spin_unlock_irqrestore(&head->lock, flags);
return 0;
}
/*
* Unset expiration time for current cpu.
*
* Returns 1 if this timer was pending.
*/
static int uv_rtc_unset_timer(int cpu, int force)
{
int pnode = uv_cpu_to_pnode(cpu);
int bid = uv_cpu_to_blade_id(cpu);
struct uv_rtc_timer_head *head = blade_info[bid];
int bcpu = uv_cpu_hub_info(cpu)->blade_processor_id;
u64 *t = &head->cpu[bcpu].expires;
unsigned long flags;
int rc = 0;
spin_lock_irqsave(&head->lock, flags);
if ((head->next_cpu == bcpu && uv_read_rtc(NULL) >= *t) || force)
rc = 1;
if (rc) {
*t = ULLONG_MAX;
/* Was the hardware setup for this timer? */
if (head->next_cpu == bcpu)
uv_rtc_find_next_timer(head, pnode);
}
spin_unlock_irqrestore(&head->lock, flags);
return rc;
}
/*
* Kernel interface routines.
*/
/*
* Read the RTC.
*
* Starting with HUB rev 2.0, the UV RTC register is replicated across all
* cachelines of it's own page. This allows faster simultaneous reads
* from a given socket.
*/
static cycle_t uv_read_rtc(struct clocksource *cs)
{
unsigned long offset;
if (uv_get_min_hub_revision_id() == 1)
offset = 0;
else
offset = (uv_blade_processor_id() * L1_CACHE_BYTES) % PAGE_SIZE;
return (cycle_t)uv_read_local_mmr(UVH_RTC | offset);
}
/*
* Program the next event, relative to now
*/
static int uv_rtc_next_event(unsigned long delta,
struct clock_event_device *ced)
{
int ced_cpu = cpumask_first(ced->cpumask);
return uv_rtc_set_timer(ced_cpu, delta + uv_read_rtc(NULL));
}
/*
* Setup the RTC timer in oneshot mode
*/
static void uv_rtc_timer_setup(enum clock_event_mode mode,
struct clock_event_device *evt)
{
int ced_cpu = cpumask_first(evt->cpumask);
switch (mode) {
case CLOCK_EVT_MODE_PERIODIC:
case CLOCK_EVT_MODE_ONESHOT:
case CLOCK_EVT_MODE_RESUME:
/* Nothing to do here yet */
break;
case CLOCK_EVT_MODE_UNUSED:
case CLOCK_EVT_MODE_SHUTDOWN:
uv_rtc_unset_timer(ced_cpu, 1);
break;
}
}
static void uv_rtc_interrupt(void)
{
int cpu = smp_processor_id();
struct clock_event_device *ced = &per_cpu(cpu_ced, cpu);
if (!ced || !ced->event_handler)
return;
if (uv_rtc_unset_timer(cpu, 0) != 1)
return;
ced->event_handler(ced);
}
static int __init uv_enable_evt_rtc(char *str)
{
uv_rtc_evt_enable = 1;
return 1;
}
__setup("uvrtcevt", uv_enable_evt_rtc);
static __init void uv_rtc_register_clockevents(struct work_struct *dummy)
{
struct clock_event_device *ced = this_cpu_ptr(&cpu_ced);
*ced = clock_event_device_uv;
ced->cpumask = cpumask_of(smp_processor_id());
clockevents_register_device(ced);
}
static __init int uv_rtc_setup_clock(void)
{
int rc;
if (!is_uv_system())
return -ENODEV;
rc = clocksource_register_hz(&clocksource_uv, sn_rtc_cycles_per_second);
if (rc)
printk(KERN_INFO "UV RTC clocksource failed rc %d\n", rc);
else
printk(KERN_INFO "UV RTC clocksource registered freq %lu MHz\n",
sn_rtc_cycles_per_second/(unsigned long)1E6);
if (rc || !uv_rtc_evt_enable || x86_platform_ipi_callback)
return rc;
/* Setup and register clockevents */
rc = uv_rtc_allocate_timers();
if (rc)
goto error;
x86_platform_ipi_callback = uv_rtc_interrupt;
clock_event_device_uv.mult = div_sc(sn_rtc_cycles_per_second,
NSEC_PER_SEC, clock_event_device_uv.shift);
clock_event_device_uv.min_delta_ns = NSEC_PER_SEC /
sn_rtc_cycles_per_second;
clock_event_device_uv.max_delta_ns = clocksource_uv.mask *
(NSEC_PER_SEC / sn_rtc_cycles_per_second);
rc = schedule_on_each_cpu(uv_rtc_register_clockevents);
if (rc) {
x86_platform_ipi_callback = NULL;
uv_rtc_deallocate_timers();
goto error;
}
printk(KERN_INFO "UV RTC clockevents registered\n");
return 0;
error:
clocksource_unregister(&clocksource_uv);
printk(KERN_INFO "UV RTC clockevents failed rc %d\n", rc);
return rc;
}
arch_initcall(uv_rtc_setup_clock);