diff options
author | Tejun Heo <tj@kernel.org> | 2011-11-28 09:46:22 -0800 |
---|---|---|
committer | Tejun Heo <tj@kernel.org> | 2011-11-28 09:46:22 -0800 |
commit | d4bbf7e7759afc172e2bfbc5c416324590049cdd (patch) | |
tree | 7eab5ee5481cd3dcf1162329fec827177640018a /arch/x86/platform | |
parent | a150439c4a97db379f0ed6faa46fbbb6e7bf3cb2 (diff) | |
parent | 401d0069cb344f401bc9d264c31db55876ff78c0 (diff) | |
download | linux-d4bbf7e7759afc172e2bfbc5c416324590049cdd.tar.bz2 |
Merge branch 'master' into x86/memblock
Conflicts & resolutions:
* arch/x86/xen/setup.c
dc91c728fd "xen: allow extra memory to be in multiple regions"
24aa07882b "memblock, x86: Replace memblock_x86_reserve/free..."
conflicted on xen_add_extra_mem() updates. The resolution is
trivial as the latter just want to replace
memblock_x86_reserve_range() with memblock_reserve().
* drivers/pci/intel-iommu.c
166e9278a3f "x86/ia64: intel-iommu: move to drivers/iommu/"
5dfe8660a3d "bootmem: Replace work_with_active_regions() with..."
conflicted as the former moved the file under drivers/iommu/.
Resolved by applying the chnages from the latter on the moved
file.
* mm/Kconfig
6661672053a "memblock: add NO_BOOTMEM config symbol"
c378ddd53f9 "memblock, x86: Make ARCH_DISCARD_MEMBLOCK a config option"
conflicted trivially. Both added config options. Just
letting both add their own options resolves the conflict.
* mm/memblock.c
d1f0ece6cdc "mm/memblock.c: small function definition fixes"
ed7b56a799c "memblock: Remove memblock_memory_can_coalesce()"
confliected. The former updates function removed by the
latter. Resolution is trivial.
Signed-off-by: Tejun Heo <tj@kernel.org>
Diffstat (limited to 'arch/x86/platform')
-rw-r--r-- | arch/x86/platform/Makefile | 1 | ||||
-rw-r--r-- | arch/x86/platform/ce4100/ce4100.c | 2 | ||||
-rw-r--r-- | arch/x86/platform/efi/efi.c | 91 | ||||
-rw-r--r-- | arch/x86/platform/efi/efi_32.c | 1 | ||||
-rw-r--r-- | arch/x86/platform/geode/Makefile | 1 | ||||
-rw-r--r-- | arch/x86/platform/geode/alix.c | 142 | ||||
-rw-r--r-- | arch/x86/platform/mrst/Makefile | 1 | ||||
-rw-r--r-- | arch/x86/platform/mrst/mrst.c | 242 | ||||
-rw-r--r-- | arch/x86/platform/mrst/pmu.c | 817 | ||||
-rw-r--r-- | arch/x86/platform/mrst/pmu.h | 234 | ||||
-rw-r--r-- | arch/x86/platform/mrst/vrtc.c | 14 | ||||
-rw-r--r-- | arch/x86/platform/olpc/Makefile | 5 | ||||
-rw-r--r-- | arch/x86/platform/olpc/olpc-xo1-pm.c | 216 | ||||
-rw-r--r-- | arch/x86/platform/olpc/olpc-xo1-rtc.c | 81 | ||||
-rw-r--r-- | arch/x86/platform/olpc/olpc-xo1-sci.c | 614 | ||||
-rw-r--r-- | arch/x86/platform/olpc/olpc-xo1.c | 146 | ||||
-rw-r--r-- | arch/x86/platform/olpc/olpc-xo15-sci.c | 168 | ||||
-rw-r--r-- | arch/x86/platform/olpc/olpc.c | 99 | ||||
-rw-r--r-- | arch/x86/platform/olpc/olpc_dt.c | 103 | ||||
-rw-r--r-- | arch/x86/platform/olpc/xo1-wakeup.S | 124 | ||||
-rw-r--r-- | arch/x86/platform/uv/bios_uv.c | 1 | ||||
-rw-r--r-- | arch/x86/platform/uv/tlb_uv.c | 86 |
22 files changed, 2970 insertions, 219 deletions
diff --git a/arch/x86/platform/Makefile b/arch/x86/platform/Makefile index 021eee91c056..8d874396cb29 100644 --- a/arch/x86/platform/Makefile +++ b/arch/x86/platform/Makefile @@ -1,6 +1,7 @@ # Platform specific code goes here obj-y += ce4100/ obj-y += efi/ +obj-y += geode/ obj-y += iris/ obj-y += mrst/ obj-y += olpc/ diff --git a/arch/x86/platform/ce4100/ce4100.c b/arch/x86/platform/ce4100/ce4100.c index 28071bb31db7..4c61b52191eb 100644 --- a/arch/x86/platform/ce4100/ce4100.c +++ b/arch/x86/platform/ce4100/ce4100.c @@ -109,7 +109,7 @@ static __init void sdv_serial_fixup(void) } #else -static inline void sdv_serial_fixup(void); +static inline void sdv_serial_fixup(void) {}; #endif static void __init sdv_arch_setup(void) diff --git a/arch/x86/platform/efi/efi.c b/arch/x86/platform/efi/efi.c index 3b4e86bda3cb..4a01967f02e7 100644 --- a/arch/x86/platform/efi/efi.c +++ b/arch/x86/platform/efi/efi.c @@ -29,6 +29,7 @@ #include <linux/kernel.h> #include <linux/init.h> #include <linux/efi.h> +#include <linux/export.h> #include <linux/bootmem.h> #include <linux/memblock.h> #include <linux/spinlock.h> @@ -51,7 +52,17 @@ int efi_enabled; EXPORT_SYMBOL(efi_enabled); -struct efi efi; +struct efi __read_mostly efi = { + .mps = EFI_INVALID_TABLE_ADDR, + .acpi = EFI_INVALID_TABLE_ADDR, + .acpi20 = EFI_INVALID_TABLE_ADDR, + .smbios = EFI_INVALID_TABLE_ADDR, + .sal_systab = EFI_INVALID_TABLE_ADDR, + .boot_info = EFI_INVALID_TABLE_ADDR, + .hcdp = EFI_INVALID_TABLE_ADDR, + .uga = EFI_INVALID_TABLE_ADDR, + .uv_systab = EFI_INVALID_TABLE_ADDR, +}; EXPORT_SYMBOL(efi); struct efi_memory_map memmap; @@ -79,26 +90,50 @@ early_param("add_efi_memmap", setup_add_efi_memmap); static efi_status_t virt_efi_get_time(efi_time_t *tm, efi_time_cap_t *tc) { - return efi_call_virt2(get_time, tm, tc); + unsigned long flags; + efi_status_t status; + + spin_lock_irqsave(&rtc_lock, flags); + status = efi_call_virt2(get_time, tm, tc); + spin_unlock_irqrestore(&rtc_lock, flags); + return status; } static efi_status_t virt_efi_set_time(efi_time_t *tm) { - return efi_call_virt1(set_time, tm); + unsigned long flags; + efi_status_t status; + + spin_lock_irqsave(&rtc_lock, flags); + status = efi_call_virt1(set_time, tm); + spin_unlock_irqrestore(&rtc_lock, flags); + return status; } static efi_status_t virt_efi_get_wakeup_time(efi_bool_t *enabled, efi_bool_t *pending, efi_time_t *tm) { - return efi_call_virt3(get_wakeup_time, - enabled, pending, tm); + unsigned long flags; + efi_status_t status; + + spin_lock_irqsave(&rtc_lock, flags); + status = efi_call_virt3(get_wakeup_time, + enabled, pending, tm); + spin_unlock_irqrestore(&rtc_lock, flags); + return status; } static efi_status_t virt_efi_set_wakeup_time(efi_bool_t enabled, efi_time_t *tm) { - return efi_call_virt2(set_wakeup_time, - enabled, tm); + unsigned long flags; + efi_status_t status; + + spin_lock_irqsave(&rtc_lock, flags); + status = efi_call_virt2(set_wakeup_time, + enabled, tm); + spin_unlock_irqrestore(&rtc_lock, flags); + return status; } static efi_status_t virt_efi_get_variable(efi_char16_t *name, @@ -122,7 +157,7 @@ static efi_status_t virt_efi_get_next_variable(unsigned long *name_size, static efi_status_t virt_efi_set_variable(efi_char16_t *name, efi_guid_t *vendor, - unsigned long attr, + u32 attr, unsigned long data_size, void *data) { @@ -131,6 +166,18 @@ static efi_status_t virt_efi_set_variable(efi_char16_t *name, data_size, data); } +static efi_status_t virt_efi_query_variable_info(u32 attr, + u64 *storage_space, + u64 *remaining_space, + u64 *max_variable_size) +{ + if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION) + return EFI_UNSUPPORTED; + + return efi_call_virt4(query_variable_info, attr, storage_space, + remaining_space, max_variable_size); +} + static efi_status_t virt_efi_get_next_high_mono_count(u32 *count) { return efi_call_virt1(get_next_high_mono_count, count); @@ -145,6 +192,28 @@ static void virt_efi_reset_system(int reset_type, data_size, data); } +static efi_status_t virt_efi_update_capsule(efi_capsule_header_t **capsules, + unsigned long count, + unsigned long sg_list) +{ + if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION) + return EFI_UNSUPPORTED; + + return efi_call_virt3(update_capsule, capsules, count, sg_list); +} + +static efi_status_t virt_efi_query_capsule_caps(efi_capsule_header_t **capsules, + unsigned long count, + u64 *max_size, + int *reset_type) +{ + if (efi.runtime_version < EFI_2_00_SYSTEM_TABLE_REVISION) + return EFI_UNSUPPORTED; + + return efi_call_virt4(query_capsule_caps, capsules, count, max_size, + reset_type); +} + static efi_status_t __init phys_efi_set_virtual_address_map( unsigned long memory_map_size, unsigned long descriptor_size, @@ -164,11 +233,14 @@ static efi_status_t __init phys_efi_set_virtual_address_map( static efi_status_t __init phys_efi_get_time(efi_time_t *tm, efi_time_cap_t *tc) { + unsigned long flags; efi_status_t status; + spin_lock_irqsave(&rtc_lock, flags); efi_call_phys_prelog(); status = efi_call_phys2(efi_phys.get_time, tm, tc); efi_call_phys_epilog(); + spin_unlock_irqrestore(&rtc_lock, flags); return status; } @@ -666,6 +738,9 @@ void __init efi_enter_virtual_mode(void) efi.get_next_high_mono_count = virt_efi_get_next_high_mono_count; efi.reset_system = virt_efi_reset_system; efi.set_virtual_address_map = NULL; + efi.query_variable_info = virt_efi_query_variable_info; + efi.update_capsule = virt_efi_update_capsule; + efi.query_capsule_caps = virt_efi_query_capsule_caps; if (__supported_pte_mask & _PAGE_NX) runtime_code_page_mkexec(); early_iounmap(memmap.map, memmap.nr_map * memmap.desc_size); diff --git a/arch/x86/platform/efi/efi_32.c b/arch/x86/platform/efi/efi_32.c index 5cab48ee61a4..e36bf714cb77 100644 --- a/arch/x86/platform/efi/efi_32.c +++ b/arch/x86/platform/efi/efi_32.c @@ -25,6 +25,7 @@ #include <linux/efi.h> #include <asm/io.h> +#include <asm/desc.h> #include <asm/page.h> #include <asm/pgtable.h> #include <asm/tlbflush.h> diff --git a/arch/x86/platform/geode/Makefile b/arch/x86/platform/geode/Makefile new file mode 100644 index 000000000000..07c9cd05021a --- /dev/null +++ b/arch/x86/platform/geode/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_ALIX) += alix.o diff --git a/arch/x86/platform/geode/alix.c b/arch/x86/platform/geode/alix.c new file mode 100644 index 000000000000..ca1973699d3d --- /dev/null +++ b/arch/x86/platform/geode/alix.c @@ -0,0 +1,142 @@ +/* + * 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> + * + * 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 <asm/geode.h> + +static int 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_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 void __init register_alix(void) +{ + /* Setup LED control through leds-gpio driver */ + platform_device_register(&alix_leds_dev); +} + +static int __init alix_present(unsigned long bios_phys, + const char *alix_sig, + size_t alix_sig_len) +{ + const size_t bios_len = 0x00010000; + 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 1; + } + + 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')) { + printk(KERN_INFO + "%s: system is recognized as \"%s\"\n", + KBUILD_MODNAME, name); + return 1; + } + } + + return 0; +} + +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(0xf0000, tinybios_sig, sizeof(tinybios_sig) - 1) || + alix_present(0x500, coreboot_sig, sizeof(coreboot_sig) - 1)) + register_alix(); + + return 0; +} + +module_init(alix_init); + +MODULE_AUTHOR("Ed Wildgoose <kernel@wildgooses.com>"); +MODULE_DESCRIPTION("PCEngines ALIX System Setup"); +MODULE_LICENSE("GPL"); diff --git a/arch/x86/platform/mrst/Makefile b/arch/x86/platform/mrst/Makefile index f61ccdd49341..1ea38775a6d3 100644 --- a/arch/x86/platform/mrst/Makefile +++ b/arch/x86/platform/mrst/Makefile @@ -1,3 +1,4 @@ obj-$(CONFIG_X86_MRST) += mrst.o obj-$(CONFIG_X86_MRST) += vrtc.o obj-$(CONFIG_EARLY_PRINTK_MRST) += early_printk_mrst.o +obj-$(CONFIG_X86_MRST) += pmu.o diff --git a/arch/x86/platform/mrst/mrst.c b/arch/x86/platform/mrst/mrst.c index 7000e74b3087..b1489a06a49d 100644 --- a/arch/x86/platform/mrst/mrst.c +++ b/arch/x86/platform/mrst/mrst.c @@ -14,6 +14,8 @@ #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> @@ -24,6 +26,8 @@ #include <linux/platform_device.h> #include <linux/irq.h> #include <linux/module.h> +#include <linux/notifier.h> +#include <linux/mfd/intel_msic.h> #include <asm/setup.h> #include <asm/mpspec_def.h> @@ -183,11 +187,34 @@ int __init sfi_parse_mrtc(struct sfi_table_header *table) static unsigned long __init mrst_calibrate_tsc(void) { unsigned long flags, fast_calibrate; - - local_irq_save(flags); - fast_calibrate = apbt_quick_calibrate(); - local_irq_restore(flags); - + if (__mrst_cpu_chip == MRST_CPU_CHIP_PENWELL) { + 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 = PENWELL_FSB_FREQ_83SKU; + else + fsb = PENWELL_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); + } else { + local_irq_save(flags); + fast_calibrate = apbt_quick_calibrate(); + local_irq_restore(flags); + } + if (fast_calibrate) return fast_calibrate; @@ -250,6 +277,17 @@ static void mrst_reboot(void) } /* + * 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 mrst_get_nmi_reason(void) +{ + return 0; +} + +/* * Moorestown specific x86_init function overrides and early setup * calls. */ @@ -270,6 +308,8 @@ void __init x86_mrst_early_setup(void) x86_platform.calibrate_tsc = mrst_calibrate_tsc; x86_platform.i8042_detect = mrst_i8042_detect; x86_init.timers.wallclock_init = mrst_rtc_init; + x86_platform.get_nmi_reason = mrst_get_nmi_reason; + x86_init.pci.init = pci_mrst_init; x86_init.pci.fixup_irqs = x86_init_noop; @@ -392,6 +432,7 @@ 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 + MRST_IRQ_OFFSET; @@ -480,7 +521,130 @@ static void __init *no_platform_data(void *info) return NULL; } +static struct resource msic_resources[] = { + { + .start = INTEL_MSIC_IRQ_PHYS_BASE, + .end = INTEL_MSIC_IRQ_PHYS_BASE + 64 - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct intel_msic_platform_data msic_pdata; + +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 inline bool mrst_has_msic(void) +{ + return mrst_identify_cpu() == MRST_CPU_CHIP_PENWELL; +} + +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 (mrst_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. + */ +static 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 no_platform_data(info); +} + +static void *msic_battery_platform_data(void *info) +{ + return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_BATTERY); +} + +static void *msic_gpio_platform_data(void *info) +{ + static struct intel_msic_gpio_pdata pdata; + int gpio = get_gpio_by_name("msic_gpio_base"); + + if (gpio < 0) + return NULL; + + pdata.gpio_base = gpio; + msic_pdata.gpio = &pdata; + + return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_GPIO); +} + +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 void *msic_power_btn_platform_data(void *info) +{ + return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_POWER_BTN); +} + +static void *msic_ocd_platform_data(void *info) +{ + static struct intel_msic_ocd_pdata pdata; + int gpio = get_gpio_by_name("ocd_gpio"); + + if (gpio < 0) + return NULL; + + pdata.gpio = gpio; + msic_pdata.ocd = &pdata; + + return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_OCD); +} + static const struct devs_id __initconst device_ids[] = { + {"bma023", SFI_DEV_TYPE_I2C, 1, &no_platform_data}, {"pmic_gpio", SFI_DEV_TYPE_SPI, 1, &pmic_gpio_platform_data}, {"spi_max3111", SFI_DEV_TYPE_SPI, 0, &max3111_platform_data}, {"i2c_max7315", SFI_DEV_TYPE_I2C, 1, &max7315_platform_data}, @@ -488,7 +652,14 @@ static const struct devs_id __initconst device_ids[] = { {"emc1403", SFI_DEV_TYPE_I2C, 1, &emc1403_platform_data}, {"i2c_accel", SFI_DEV_TYPE_I2C, 0, &lis331dl_platform_data}, {"pmic_audio", SFI_DEV_TYPE_IPC, 1, &no_platform_data}, - {"msic_audio", SFI_DEV_TYPE_IPC, 1, &no_platform_data}, + + /* MSIC subdevices */ + {"msic_battery", SFI_DEV_TYPE_IPC, 1, &msic_battery_platform_data}, + {"msic_gpio", SFI_DEV_TYPE_IPC, 1, &msic_gpio_platform_data}, + {"msic_audio", SFI_DEV_TYPE_IPC, 1, &msic_audio_platform_data}, + {"msic_power_btn", SFI_DEV_TYPE_IPC, 1, &msic_power_btn_platform_data}, + {"msic_ocd", SFI_DEV_TYPE_IPC, 1, &msic_ocd_platform_data}, + {}, }; @@ -555,6 +726,9 @@ static void __init intel_scu_i2c_device_register(int bus, i2c_devs[i2c_next_dev++] = new_dev; } +BLOCKING_NOTIFIER_HEAD(intel_scu_notifier); +EXPORT_SYMBOL_GPL(intel_scu_notifier); + /* Called by IPC driver */ void intel_scu_devices_create(void) { @@ -579,6 +753,7 @@ void intel_scu_devices_create(void) } else i2c_register_board_info(i2c_bus[i], i2c_devs[i], 1); } + intel_scu_notifier_post(SCU_AVAILABLE, 0L); } EXPORT_SYMBOL_GPL(intel_scu_devices_create); @@ -587,6 +762,8 @@ void intel_scu_devices_destroy(void) { int i; + intel_scu_notifier_post(SCU_DOWN, 0L); + for (i = 0; i < ipc_next_dev; i++) platform_device_del(ipc_devs[i]); } @@ -603,19 +780,37 @@ static void __init install_irq_resource(struct platform_device *pdev, int irq) platform_device_add_resources(pdev, &res, 1); } -static void __init sfi_handle_ipc_dev(struct platform_device *pdev) +static void __init sfi_handle_ipc_dev(struct sfi_device_table_entry *entry) { const struct devs_id *dev = device_ids; + struct platform_device *pdev; void *pdata = NULL; while (dev->name[0]) { if (dev->type == SFI_DEV_TYPE_IPC && - !strncmp(dev->name, pdev->name, SFI_NAME_LEN)) { - pdata = dev->get_platform_data(pdev); + !strncmp(dev->name, entry->name, SFI_NAME_LEN)) { + pdata = dev->get_platform_data(entry); break; } dev++; } + + /* + * On Medfield the platform device creation is handled by the MSIC + * MFD driver so we don't need to do it here. + */ + if (mrst_has_msic()) + return; + + /* ID as IRQ is a hack that will go away */ + pdev = platform_device_alloc(entry->name, entry->irq); + if (pdev == NULL) { + pr_err("out of memory for SFI platform device '%s'.\n", + entry->name); + return; + } + install_irq_resource(pdev, entry->irq); + pdev->dev.platform_data = pdata; intel_scu_device_register(pdev); } @@ -668,7 +863,6 @@ static int __init sfi_parse_devs(struct sfi_table_header *table) struct sfi_device_table_entry *pentry; struct spi_board_info spi_info; struct i2c_board_info i2c_info; - struct platform_device *pdev; int num, i, bus; int ioapic; struct io_apic_irq_attr irq_attr; @@ -678,36 +872,32 @@ static int __init sfi_parse_devs(struct sfi_table_header *table) pentry = (struct sfi_device_table_entry *)sb->pentry; for (i = 0; i < num; i++, pentry++) { - if (pentry->irq != (u8)0xff) { /* native RTE case */ + int irq = pentry->irq; + + if (irq != (u8)0xff) { /* native RTE case */ /* these SPI2 devices are not exposed to system as PCI * devices, but they have separate RTE entry in IOAPIC * so we have to enable them one by one here */ - ioapic = mp_find_ioapic(pentry->irq); + ioapic = mp_find_ioapic(irq); irq_attr.ioapic = ioapic; - irq_attr.ioapic_pin = pentry->irq; + irq_attr.ioapic_pin = irq; irq_attr.trigger = 1; irq_attr.polarity = 1; - io_apic_set_pci_routing(NULL, pentry->irq, &irq_attr); - } + io_apic_set_pci_routing(NULL, irq, &irq_attr); + } else + irq = 0; /* No irq */ + switch (pentry->type) { case SFI_DEV_TYPE_IPC: - /* ID as IRQ is a hack that will go away */ - pdev = platform_device_alloc(pentry->name, pentry->irq); - if (pdev == NULL) { - pr_err("out of memory for SFI platform device '%s'.\n", - pentry->name); - continue; - } - install_irq_resource(pdev, pentry->irq); pr_debug("info[%2d]: IPC bus, name = %16.16s, " "irq = 0x%2x\n", i, pentry->name, pentry->irq); - sfi_handle_ipc_dev(pdev); + sfi_handle_ipc_dev(pentry); break; case SFI_DEV_TYPE_SPI: memset(&spi_info, 0, sizeof(spi_info)); strncpy(spi_info.modalias, pentry->name, SFI_NAME_LEN); - spi_info.irq = pentry->irq; + spi_info.irq = irq; spi_info.bus_num = pentry->host_num; spi_info.chip_select = pentry->addr; spi_info.max_speed_hz = pentry->max_freq; @@ -724,7 +914,7 @@ static int __init sfi_parse_devs(struct sfi_table_header *table) memset(&i2c_info, 0, sizeof(i2c_info)); bus = pentry->host_num; strncpy(i2c_info.type, pentry->name, SFI_NAME_LEN); - i2c_info.irq = pentry->irq; + i2c_info.irq = irq; i2c_info.addr = pentry->addr; pr_debug("info[%2d]: I2C bus = %d, name = %16.16s, " "irq = 0x%2x, addr = 0x%x\n", i, bus, diff --git a/arch/x86/platform/mrst/pmu.c b/arch/x86/platform/mrst/pmu.c new file mode 100644 index 000000000000..c0ac06da57ac --- /dev/null +++ b/arch/x86/platform/mrst/pmu.c @@ -0,0 +1,817 @@ +/* + * mrst/pmu.c - driver for MRST Power Management Unit + * + * Copyright (c) 2011, 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. + */ + +#include <linux/cpuidle.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/seq_file.h> +#include <linux/sfi.h> +#include <asm/intel_scu_ipc.h> +#include "pmu.h" + +#define IPCMSG_FW_REVISION 0xF4 + +struct mrst_device { + u16 pci_dev_num; /* DEBUG only */ + u16 lss; + u16 latest_request; + unsigned int pci_state_counts[PCI_D3cold + 1]; /* DEBUG only */ +}; + +/* + * comlete list of MRST PCI devices + */ +static struct mrst_device mrst_devs[] = { +/* 0 */ { 0x0800, LSS_SPI0 }, /* Moorestown SPI Ctrl 0 */ +/* 1 */ { 0x0801, LSS_SPI1 }, /* Moorestown SPI Ctrl 1 */ +/* 2 */ { 0x0802, LSS_I2C0 }, /* Moorestown I2C 0 */ +/* 3 */ { 0x0803, LSS_I2C1 }, /* Moorestown I2C 1 */ +/* 4 */ { 0x0804, LSS_I2C2 }, /* Moorestown I2C 2 */ +/* 5 */ { 0x0805, LSS_KBD }, /* Moorestown Keyboard Ctrl */ +/* 6 */ { 0x0806, LSS_USB_HC }, /* Moorestown USB Ctrl */ +/* 7 */ { 0x0807, LSS_SD_HC0 }, /* Moorestown SD Host Ctrl 0 */ +/* 8 */ { 0x0808, LSS_SD_HC1 }, /* Moorestown SD Host Ctrl 1 */ +/* 9 */ { 0x0809, LSS_NAND }, /* Moorestown NAND Ctrl */ +/* 10 */ { 0x080a, LSS_AUDIO }, /* Moorestown Audio Ctrl */ +/* 11 */ { 0x080b, LSS_IMAGING }, /* Moorestown ISP */ +/* 12 */ { 0x080c, LSS_SECURITY }, /* Moorestown Security Controller */ +/* 13 */ { 0x080d, LSS_DISPLAY }, /* Moorestown External Displays */ +/* 14 */ { 0x080e, 0 }, /* Moorestown SCU IPC */ +/* 15 */ { 0x080f, LSS_GPIO }, /* Moorestown GPIO Controller */ +/* 16 */ { 0x0810, 0 }, /* Moorestown Power Management Unit */ +/* 17 */ { 0x0811, LSS_USB_OTG }, /* Moorestown OTG Ctrl */ +/* 18 */ { 0x0812, LSS_SPI2 }, /* Moorestown SPI Ctrl 2 */ +/* 19 */ { 0x0813, 0 }, /* Moorestown SC DMA */ +/* 20 */ { 0x0814, LSS_AUDIO_LPE }, /* Moorestown LPE DMA */ +/* 21 */ { 0x0815, LSS_AUDIO_SSP }, /* Moorestown SSP0 */ + +/* 22 */ { 0x084F, LSS_SD_HC2 }, /* Moorestown SD Host Ctrl 2 */ + +/* 23 */ { 0x4102, 0 }, /* Lincroft */ +/* 24 */ { 0x4110, 0 }, /* Lincroft */ +}; + +/* n.b. We ignore PCI-id 0x815 in LSS9 b/c Linux has no driver for it */ +static u16 mrst_lss9_pci_ids[] = {0x080a, 0x0814, 0}; +static u16 mrst_lss10_pci_ids[] = {0x0800, 0x0801, 0x0802, 0x0803, + 0x0804, 0x0805, 0x080f, 0}; + +/* handle concurrent SMP invokations of pmu_pci_set_power_state() */ +static spinlock_t mrst_pmu_power_state_lock; + +static unsigned int wake_counters[MRST_NUM_LSS]; /* DEBUG only */ +static unsigned int pmu_irq_stats[INT_INVALID + 1]; /* DEBUG only */ + +static int graphics_is_off; +static int lss_s0i3_enabled; +static bool mrst_pmu_s0i3_enable; + +/* debug counters */ +static u32 pmu_wait_ready_calls; +static u32 pmu_wait_ready_udelays; +static u32 pmu_wait_ready_udelays_max; +static u32 pmu_wait_done_calls; +static u32 pmu_wait_done_udelays; +static u32 pmu_wait_done_udelays_max; +static u32 pmu_set_power_state_entry; +static u32 pmu_set_power_state_send_cmd; + +static struct mrst_device *pci_id_2_mrst_dev(u16 pci_dev_num) +{ + int index = 0; + + if ((pci_dev_num >= 0x0800) && (pci_dev_num <= 0x815)) + index = pci_dev_num - 0x800; + else if (pci_dev_num == 0x084F) + index = 22; + else if (pci_dev_num == 0x4102) + index = 23; + else if (pci_dev_num == 0x4110) + index = 24; + + if (pci_dev_num != mrst_devs[index].pci_dev_num) { + WARN_ONCE(1, FW_BUG "Unknown PCI device 0x%04X\n", pci_dev_num); + return 0; + } + + return &mrst_devs[index]; +} + +/** + * mrst_pmu_validate_cstates + * @dev: cpuidle_device + * + * Certain states are not appropriate for governor to pick in some cases. + * This function will be called as cpuidle_device's prepare callback and + * thus tells governor to ignore such states when selecting the next state + * to enter. + */ + +#define IDLE_STATE4_IS_C6 4 +#define IDLE_STATE5_IS_S0I3 5 + +int mrst_pmu_invalid_cstates(void) +{ + int cpu = smp_processor_id(); + + /* + * Demote to C4 if the PMU is busy. + * Since LSS changes leave the busy bit clear... + * busy means either the PMU is waiting for an ACK-C6 that + * isn't coming due to an MWAIT that returned immediately; + * or we returned from S0i3 successfully, and the PMU + * is not done sending us interrupts. + */ + if (pmu_read_busy_status()) + return 1 << IDLE_STATE4_IS_C6 | 1 << IDLE_STATE5_IS_S0I3; + + /* + * Disallow S0i3 if: PMU is not initialized, or CPU1 is active, + * or if device LSS is insufficient, or the GPU is active, + * or if it has been explicitly disabled. + */ + if (!pmu_reg || !cpumask_equal(cpu_online_mask, cpumask_of(cpu)) || + !lss_s0i3_enabled || !graphics_is_off || !mrst_pmu_s0i3_enable) + return 1 << IDLE_STATE5_IS_S0I3; + else + return 0; +} + +/* + * pmu_update_wake_counters(): read PM_WKS, update wake_counters[] + * DEBUG only. + */ +static void pmu_update_wake_counters(void) +{ + int lss; + u32 wake_status; + + wake_status = pmu_read_wks(); + + for (lss = 0; lss < MRST_NUM_LSS; ++lss) { + if (wake_status & (1 << lss)) + wake_counters[lss]++; + } +} + +int mrst_pmu_s0i3_entry(void) +{ + int status; + + /* Clear any possible error conditions */ + pmu_write_ics(0x300); + + /* set wake control to current D-states */ + pmu_write_wssc(S0I3_SSS_TARGET); + + status = mrst_s0i3_entry(PM_S0I3_COMMAND, &pmu_reg->pm_cmd); + pmu_update_wake_counters(); + return status; +} + +/* poll for maximum of 5ms for busy bit to clear */ +static int pmu_wait_ready(void) +{ + int udelays; + + pmu_wait_ready_calls++; + + for (udelays = 0; udelays < 500; ++udelays) { + if (udelays > pmu_wait_ready_udelays_max) + pmu_wait_ready_udelays_max = udelays; + + if (pmu_read_busy_status() == 0) + return 0; + + udelay(10); + pmu_wait_ready_udelays++; + } + + /* + * if this fires, observe + * /sys/kernel/debug/mrst_pmu_wait_ready_calls + * /sys/kernel/debug/mrst_pmu_wait_ready_udelays + */ + WARN_ONCE(1, "SCU not ready for 5ms"); + return -EBUSY; +} +/* poll for maximum of 50ms us for busy bit to clear */ +static int pmu_wait_done(void) +{ + int udelays; + + pmu_wait_done_calls++; + + for (udelays = 0; udelays < 500; ++udelays) { + if (udelays > pmu_wait_done_udelays_max) + pmu_wait_done_udelays_max = udelays; + + if (pmu_read_busy_status() == 0) + return 0; + + udelay(100); + pmu_wait_done_udelays++; + } + + /* + * if this fires, observe + * /sys/kernel/debug/mrst_pmu_wait_done_calls + * /sys/kernel/debug/mrst_pmu_wait_done_udelays + */ + WARN_ONCE(1, "SCU not done for 50ms"); + return -EBUSY; +} + +u32 mrst_pmu_msi_is_disabled(void) +{ + return pmu_msi_is_disabled(); +} + +void mrst_pmu_enable_msi(void) +{ + pmu_msi_enable(); +} + +/** + * pmu_irq - pmu driver interrupt handler + * Context: interrupt context + */ +static irqreturn_t pmu_irq(int irq, void *dummy) +{ + union pmu_pm_ics pmu_ics; + + pmu_ics.value = pmu_read_ics(); + + if (!pmu_ics.bits.pending) + return IRQ_NONE; + + switch (pmu_ics.bits.cause) { + case INT_SPURIOUS: + case INT_CMD_DONE: + case INT_CMD_ERR: + case INT_WAKE_RX: + case INT_SS_ERROR: + case INT_S0IX_MISS: + case INT_NO_ACKC6: + pmu_irq_stats[pmu_ics.bits.cause]++; + break; + default: + pmu_irq_stats[INT_INVALID]++; + } + + pmu_write_ics(pmu_ics.value); /* Clear pending interrupt */ + + return IRQ_HANDLED; +} + +/* + * Translate PCI power management to MRST LSS D-states + */ +static int pci_2_mrst_state(int lss, pci_power_t pci_state) +{ + switch (pci_state) { + case PCI_D0: + if (SSMSK(D0i1, lss) & D0I1_ACG_SSS_TARGET) + return D0i1; + else + return D0; + case PCI_D1: + return D0i1; + case PCI_D2: + return D0i2; + case PCI_D3hot: + case PCI_D3cold: + return D0i3; + default: + WARN(1, "pci_state %d\n", pci_state); + return 0; + } +} + +static int pmu_issue_command(u32 pm_ssc) +{ + union pmu_pm_set_cfg_cmd_t command; + + if (pmu_read_busy_status()) { + pr_debug("pmu is busy, Operation not permitted\n"); + return -1; + } + + /* + * enable interrupts in PMU so that interrupts are + * propagated when ioc bit for a particular set + * command is set + */ + + pmu_irq_enable(); + + /* Configure the sub systems for pmu2 */ + + pmu_write_ssc(pm_ssc); + + /* + * Send the set config command for pmu its configured + * for mode CM_IMMEDIATE & hence with No Trigger + */ + + command.pmu2_params.d_param.cfg_mode = CM_IMMEDIATE; + command.pmu2_params.d_param.cfg_delay = 0; + command.pmu2_params.d_param.rsvd = 0; + + /* construct the command to send SET_CFG to particular PMU */ + command.pmu2_params.d_param.cmd = SET_CFG_CMD; + command.pmu2_params.d_param.ioc = 0; + command.pmu2_params.d_param.mode_id = 0; + command.pmu2_params.d_param.sys_state = SYS_STATE_S0I0; + + /* write the value of PM_CMD into particular PMU */ + pr_debug("pmu command being written %x\n", + command.pmu_pm_set_cfg_cmd_value); + + pmu_write_cmd(command.pmu_pm_set_cfg_cmd_value); + + return 0; +} + +static u16 pmu_min_lss_pci_req(u16 *ids, u16 pci_state) +{ + u16 existing_request; + int i; + + for (i = 0; ids[i]; ++i) { + struct mrst_device *mrst_dev; + + mrst_dev = pci_id_2_mrst_dev(ids[i]); + if (unlikely(!mrst_dev)) + continue; + + existing_request = mrst_dev->latest_request; + if (existing_request < pci_state) + pci_state = existing_request; + } + return pci_state; +} + +/** + * pmu_pci_set_power_state - Callback function is used by all the PCI devices + * for a platform specific device power on/shutdown. + */ + +int pmu_pci_set_power_state(struct pci_dev *pdev, pci_power_t pci_state) +{ + u32 old_sss, new_sss; + int status = 0; + struct mrst_device *mrst_dev; + + pmu_set_power_state_entry++; + + BUG_ON(pdev->vendor != PCI_VENDOR_ID_INTEL); + BUG_ON(pci_state < PCI_D0 || pci_state > PCI_D3cold); + + mrst_dev = pci_id_2_mrst_dev(pdev->device); + if (unlikely(!mrst_dev)) + return -ENODEV; + + mrst_dev->pci_state_counts[pci_state]++; /* count invocations */ + + /* PMU driver calls self as part of PCI initialization, ignore */ + if (pdev->device == PCI_DEV_ID_MRST_PMU) + return 0; + + BUG_ON(!pmu_reg); /* SW bug if called before initialized */ + + spin_lock(&mrst_pmu_power_state_lock); + + if (pdev->d3_delay) { + dev_dbg(&pdev->dev, "d3_delay %d, should be 0\n", + pdev->d3_delay); + pdev->d3_delay = 0; + } + /* + * If Lincroft graphics, simply remember state + */ + if ((pdev->class >> 16) == PCI_BASE_CLASS_DISPLAY + && !((pdev->class & PCI_SUB_CLASS_MASK) >> 8)) { + if (pci_state == PCI_D0) + graphics_is_off = 0; + else + graphics_is_off = 1; + goto ret; + } + + if (!mrst_dev->lss) + goto ret; /* device with no LSS */ + + if (mrst_dev->latest_request == pci_state) + goto ret; /* no change */ + + mrst_dev->latest_request = pci_state; /* record latest request */ + + /* + * LSS9 and LSS10 contain multiple PCI devices. + * Use the lowest numbered (highest power) state in the LSS + */ + if (mrst_dev->lss == 9) + pci_state = pmu_min_lss_pci_req(mrst_lss9_pci_ids, pci_state); + else if (mrst_dev->lss == 10) + pci_state = pmu_min_lss_pci_req(mrst_lss10_pci_ids, pci_state); + + status = pmu_wait_ready(); + if (status) + goto ret; + + old_sss = pmu_read_sss(); + new_sss = old_sss & ~SSMSK(3, mrst_dev->lss); + new_sss |= SSMSK(pci_2_mrst_state(mrst_dev->lss, pci_state), + mrst_dev->lss); + + if (new_sss == old_sss) + goto ret; /* nothing to do */ + + pmu_set_power_state_send_cmd++; + + status = pmu_issue_command(new_sss); + + if (unlikely(status != 0)) { + dev_err(&pdev->dev, "Failed to Issue a PM command\n"); + goto ret; + } + + if (pmu_wait_done()) + goto ret; + + lss_s0i3_enabled = + ((pmu_read_sss() & S0I3_SSS_TARGET) == S0I3_SSS_TARGET); +ret: + spin_unlock(&mrst_pmu_power_state_lock); + return status; +} + +#ifdef CONFIG_DEBUG_FS +static char *d0ix_names[] = {"D0", "D0i1", "D0i2", "D0i3"}; + +static inline const char *d0ix_name(int state) +{ + return d0ix_names[(int) state]; +} + +static int debug_mrst_pmu_show(struct seq_file *s, void *unused) +{ + struct pci_dev *pdev = NULL; + u32 cur_pmsss; + int lss; + + seq_printf(s, "0x%08X D0I1_ACG_SSS_TARGET\n", D0I1_ACG_SSS_TARGET); + + cur_pmsss = pmu_read_sss(); + + seq_printf(s, "0x%08X S0I3_SSS_TARGET\n", S0I3_SSS_TARGET); + + seq_printf(s, "0x%08X Current SSS ", cur_pmsss); + seq_printf(s, lss_s0i3_enabled ? "\n" : "[BLOCKS s0i3]\n"); + + if (cpumask_equal(cpu_online_mask, cpumask_of(0))) + seq_printf(s, "cpu0 is only cpu online\n"); + else + seq_printf(s, "cpu0 is NOT only cpu online [BLOCKS S0i3]\n"); + + seq_printf(s, "GFX: %s\n", graphics_is_off ? "" : "[BLOCKS s0i3]"); + + + for_each_pci_dev(pdev) { + int pos; + u16 pmcsr; + struct mrst_device *mrst_dev; + int i; + + mrst_dev = pci_id_2_mrst_dev(pdev->device); + + seq_printf(s, "%s %04x/%04X %-16.16s ", + dev_name(&pdev->dev), + pdev->vendor, pdev->device, + dev_driver_string(&pdev->dev)); + + if (unlikely (!mrst_dev)) { + seq_printf(s, " UNKNOWN\n"); + continue; + } + + if (mrst_dev->lss) + seq_printf(s, "LSS %2d %-4s ", mrst_dev->lss, + d0ix_name(((cur_pmsss >> + (mrst_dev->lss * 2)) & 0x3))); + else + seq_printf(s, " "); + + /* PCI PM config space setting */ + pos = pci_find_capability(pdev, PCI_CAP_ID_PM); + if (pos != 0) { + pci_read_config_word(pdev, pos + PCI_PM_CTRL, &pmcsr); + seq_printf(s, "PCI-%-4s", + pci_power_name(pmcsr & PCI_PM_CTRL_STATE_MASK)); + } else { + seq_printf(s, " "); + } + + seq_printf(s, " %s ", pci_power_name(mrst_dev->latest_request)); + for (i = 0; i <= PCI_D3cold; ++i) + seq_printf(s, "%d ", mrst_dev->pci_state_counts[i]); + + if (mrst_dev->lss) { + unsigned int lssmask; + + lssmask = SSMSK(D0i3, mrst_dev->lss); + + if ((lssmask & S0I3_SSS_TARGET) && + ((lssmask & cur_pmsss) != + (lssmask & S0I3_SSS_TARGET))) + seq_printf(s , "[BLOCKS s0i3]"); + } + + seq_printf(s, "\n"); + } + seq_printf(s, "Wake Counters:\n"); + for (lss = 0; lss < MRST_NUM_LSS; ++lss) + seq_printf(s, "LSS%d %d\n", lss, wake_counters[lss]); + + seq_printf(s, "Interrupt Counters:\n"); + seq_printf(s, + "INT_SPURIOUS \t%8u\n" "INT_CMD_DONE \t%8u\n" + "INT_CMD_ERR \t%8u\n" "INT_WAKE_RX \t%8u\n" + "INT_SS_ERROR \t%8u\n" "INT_S0IX_MISS\t%8u\n" + "INT_NO_ACKC6 \t%8u\n" "INT_INVALID \t%8u\n", + pmu_irq_stats[INT_SPURIOUS], pmu_irq_stats[INT_CMD_DONE], + pmu_irq_stats[INT_CMD_ERR], pmu_irq_stats[INT_WAKE_RX], + pmu_irq_stats[INT_SS_ERROR], pmu_irq_stats[INT_S0IX_MISS], + pmu_irq_stats[INT_NO_ACKC6], pmu_irq_stats[INT_INVALID]); + + seq_printf(s, "mrst_pmu_wait_ready_calls %8d\n", + pmu_wait_ready_calls); + seq_printf(s, "mrst_pmu_wait_ready_udelays %8d\n", + pmu_wait_ready_udelays); + seq_printf(s, "mrst_pmu_wait_ready_udelays_max %8d\n", + pmu_wait_ready_udelays_max); + seq_printf(s, "mrst_pmu_wait_done_calls %8d\n", + pmu_wait_done_calls); + seq_printf(s, "mrst_pmu_wait_done_udelays %8d\n", + pmu_wait_done_udelays); + seq_printf(s, "mrst_pmu_wait_done_udelays_max %8d\n", + pmu_wait_done_udelays_max); + seq_printf(s, "mrst_pmu_set_power_state_entry %8d\n", + pmu_set_power_state_entry); + seq_printf(s, "mrst_pmu_set_power_state_send_cmd %8d\n", + pmu_set_power_state_send_cmd); + seq_printf(s, "SCU busy: %d\n", pmu_read_busy_status()); + + return 0; +} + +static int debug_mrst_pmu_open(struct inode *inode, struct file *file) +{ + return single_open(file, debug_mrst_pmu_show, NULL); +} + +static const struct file_operations devices_state_operations = { + .open = debug_mrst_pmu_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; +#endif /* DEBUG_FS */ + +/* + * Validate SCU PCI shim PCI vendor capability byte + * against LSS hard-coded in mrst_devs[] above. + * DEBUG only. + */ +static void pmu_scu_firmware_debug(void) +{ + struct pci_dev *pdev = NULL; + + for_each_pci_dev(pdev) { + struct mrst_device *mrst_dev; + u8 pci_config_lss; + int pos; + + mrst_dev = pci_id_2_mrst_dev(pdev->device); + if (unlikely(!mrst_dev)) { + printk(KERN_ERR FW_BUG "pmu: Unknown " + "PCI device 0x%04X\n", pdev->device); + continue; + } + + if (mrst_dev->lss == 0) + continue; /* no LSS in our table */ + + pos = pci_find_capability(pdev, PCI_CAP_ID_VNDR); + if (!pos != 0) { + printk(KERN_ERR FW_BUG "pmu: 0x%04X " + "missing PCI Vendor Capability\n", + pdev->device); + continue; + } + pci_read_config_byte(pdev, pos + 4, &pci_config_lss); + if (!(pci_config_lss & PCI_VENDOR_CAP_LOG_SS_MASK)) { + printk(KERN_ERR FW_BUG "pmu: 0x%04X " + "invalid PCI Vendor Capability 0x%x " + " expected LSS 0x%X\n", + pdev->device, pci_config_lss, mrst_dev->lss); + continue; + } + pci_config_lss &= PCI_VENDOR_CAP_LOG_ID_MASK; + + if (mrst_dev->lss == pci_config_lss) + continue; + + printk(KERN_ERR FW_BUG "pmu: 0x%04X LSS = %d, expected %d\n", + pdev->device, pci_config_lss, mrst_dev->lss); + } +} + +/** + * pmu_probe + */ +static int __devinit pmu_probe(struct pci_dev *pdev, + const struct pci_device_id *pci_id) +{ + int ret; + struct mrst_pmu_reg *pmu; + + /* Init the device */ + ret = pci_enable_device(pdev); + if (ret) { + dev_err(&pdev->dev, "Unable to Enable PCI device\n"); + return ret; + } + + ret = pci_request_regions(pdev, MRST_PMU_DRV_NAME); + if (ret < 0) { + dev_err(&pdev->dev, "Cannot obtain PCI resources, aborting\n"); + goto out_err1; + } + + /* Map the memory of PMU reg base */ + pmu = pci_iomap(pdev, 0, 0); + if (!pmu) { + dev_err(&pdev->dev, "Unable to map the PMU address space\n"); + ret = -ENOMEM; + goto out_err2; + } + +#ifdef CONFIG_DEBUG_FS + /* /sys/kernel/debug/mrst_pmu */ + (void) debugfs_create_file("mrst_pmu", S_IFREG | S_IRUGO, + NULL, NULL, &devices_state_operations); +#endif + pmu_reg = pmu; /* success */ + + if (request_irq(pdev->irq, pmu_irq, 0, MRST_PMU_DRV_NAME, NULL)) { + dev_err(&pdev->dev, "Registering isr has failed\n"); + ret = -1; + goto out_err3; + } + + pmu_scu_firmware_debug(); + + pmu_write_wkc(S0I3_WAKE_SOURCES); /* Enable S0i3 wakeup sources */ + + pmu_wait_ready(); + + pmu_write_ssc(D0I1_ACG_SSS_TARGET); /* Enable Auto-Clock_Gating */ + pmu_write_cmd(0x201); + + spin_lock_init(&mrst_pmu_power_state_lock); + + /* Enable the hardware interrupt */ + pmu_irq_enable(); + return 0; + +out_err3: + free_irq(pdev->irq, NULL); + pci_iounmap(pdev, pmu_reg); + pmu_reg = NULL; +out_err2: + pci_release_region(pdev, 0); +out_err1: + pci_disable_device(pdev); + return ret; +} + +static void __devexit pmu_remove(struct pci_dev *pdev) +{ + dev_err(&pdev->dev, "Mid PM pmu_remove called\n"); + + /* Freeing up the irq */ + free_irq(pdev->irq, NULL); + + pci_iounmap(pdev, pmu_reg); + pmu_reg = NULL; + + /* disable the current PCI device */ + pci_release_region(pdev, 0); + pci_disable_device(pdev); +} + +static DEFINE_PCI_DEVICE_TABLE(pmu_pci_ids) = { + { PCI_VDEVICE(INTEL, PCI_DEV_ID_MRST_PMU), 0 }, + { } +}; + +MODULE_DEVICE_TABLE(pci, pmu_pci_ids); + +static struct pci_driver driver = { + .name = MRST_PMU_DRV_NAME, + .id_table = pmu_pci_ids, + .probe = pmu_probe, + .remove = __devexit_p(pmu_remove), +}; + +/** + * pmu_pci_register - register the PMU driver as PCI device + */ +static int __init pmu_pci_register(void) +{ + return pci_register_driver(&driver); +} + +/* Register and probe via fs_initcall() to preceed device_initcall() */ +fs_initcall(pmu_pci_register); + +static void __exit mid_pci_cleanup(void) +{ + pci_unregister_driver(&driver); +} + +static int ia_major; +static int ia_minor; + +static int pmu_sfi_parse_oem(struct sfi_table_header *table) +{ + struct sfi_table_simple *sb; + + sb = (struct sfi_table_simple *)table; + ia_major = (sb->pentry[1] >> 0) & 0xFFFF; + ia_minor = (sb->pentry[1] >> 16) & 0xFFFF; + printk(KERN_INFO "mrst_pmu: IA FW version v%x.%x\n", + ia_major, ia_minor); + + return 0; +} + +static int __init scu_fw_check(void) +{ + int ret; + u32 fw_version; + + if (!pmu_reg) + return 0; /* this driver didn't probe-out */ + + sfi_table_parse("OEMB", NULL, NULL, pmu_sfi_parse_oem); + + if (ia_major < 0x6005 || ia_minor < 0x1525) { + WARN(1, "mrst_pmu: IA FW version too old\n"); + return -1; + } + + ret = intel_scu_ipc_command(IPCMSG_FW_REVISION, 0, NULL, 0, + &fw_version, 1); + + if (ret) { + WARN(1, "mrst_pmu: IPC FW version? %d\n", ret); + } else { + int scu_major = (fw_version >> 8) & 0xFF; + int scu_minor = (fw_version >> 0) & 0xFF; + + printk(KERN_INFO "mrst_pmu: firmware v%x\n", fw_version); + + if ((scu_major >= 0xC0) && (scu_minor >= 0x49)) { + printk(KERN_INFO "mrst_pmu: enabling S0i3\n"); + mrst_pmu_s0i3_enable = true; + } else { + WARN(1, "mrst_pmu: S0i3 disabled, old firmware %X.%X", + scu_major, scu_minor); + } + } + return 0; +} +late_initcall(scu_fw_check); +module_exit(mid_pci_cleanup); diff --git a/arch/x86/platform/mrst/pmu.h b/arch/x86/platform/mrst/pmu.h new file mode 100644 index 000000000000..bfbfe64b167b --- /dev/null +++ b/arch/x86/platform/mrst/pmu.h @@ -0,0 +1,234 @@ +/* + * mrst/pmu.h - private definitions for MRST Power Management Unit mrst/pmu.c + * + * Copyright (c) 2011, 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. + */ + +#ifndef _MRST_PMU_H_ +#define _MRST_PMU_H_ + +#define PCI_DEV_ID_MRST_PMU 0x0810 +#define MRST_PMU_DRV_NAME "mrst_pmu" +#define PCI_SUB_CLASS_MASK 0xFF00 + +#define PCI_VENDOR_CAP_LOG_ID_MASK 0x7F +#define PCI_VENDOR_CAP_LOG_SS_MASK 0x80 + +#define SUB_SYS_ALL_D0I1 0x01155555 +#define S0I3_WAKE_SOURCES 0x00001FFF + +#define PM_S0I3_COMMAND \ + ((0 << 31) | /* Reserved */ \ + (0 << 30) | /* Core must be idle */ \ + (0xc2 << 22) | /* ACK C6 trigger */ \ + (3 << 19) | /* Trigger on DMI message */ \ + (3 << 16) | /* Enter S0i3 */ \ + (0 << 13) | /* Numeric mode ID (sw) */ \ + (3 << 9) | /* Trigger mode */ \ + (0 << 8) | /* Do not interrupt */ \ + (1 << 0)) /* Set configuration */ + +#define LSS_DMI 0 +#define LSS_SD_HC0 1 +#define LSS_SD_HC1 2 +#define LSS_NAND 3 +#define LSS_IMAGING 4 +#define LSS_SECURITY 5 +#define LSS_DISPLAY 6 +#define LSS_USB_HC 7 +#define LSS_USB_OTG 8 +#define LSS_AUDIO 9 +#define LSS_AUDIO_LPE 9 +#define LSS_AUDIO_SSP 9 +#define LSS_I2C0 10 +#define LSS_I2C1 10 +#define LSS_I2C2 10 +#define LSS_KBD 10 +#define LSS_SPI0 10 +#define LSS_SPI1 10 +#define LSS_SPI2 10 +#define LSS_GPIO 10 +#define LSS_SRAM 11 /* used by SCU, do not touch */ +#define LSS_SD_HC2 12 +/* LSS hardware bits 15,14,13 are hardwired to 0, thus unusable */ +#define MRST_NUM_LSS 13 + +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) + +#define SSMSK(mask, lss) ((mask) << ((lss) * 2)) +#define D0 0 +#define D0i1 1 +#define D0i2 2 +#define D0i3 3 + +#define S0I3_SSS_TARGET ( \ + SSMSK(D0i1, LSS_DMI) | \ + SSMSK(D0i3, LSS_SD_HC0) | \ + SSMSK(D0i3, LSS_SD_HC1) | \ + SSMSK(D0i3, LSS_NAND) | \ + SSMSK(D0i3, LSS_SD_HC2) | \ + SSMSK(D0i3, LSS_IMAGING) | \ + SSMSK(D0i3, LSS_SECURITY) | \ + SSMSK(D0i3, LSS_DISPLAY) | \ + SSMSK(D0i3, LSS_USB_HC) | \ + SSMSK(D0i3, LSS_USB_OTG) | \ + SSMSK(D0i3, LSS_AUDIO) | \ + SSMSK(D0i1, LSS_I2C0)) + +/* + * D0i1 on Langwell is Autonomous Clock Gating (ACG). + * Enable ACG on every LSS except camera and audio + */ +#define D0I1_ACG_SSS_TARGET \ + (SUB_SYS_ALL_D0I1 & ~SSMSK(D0i1, LSS_IMAGING) & ~SSMSK(D0i1, LSS_AUDIO)) + +enum cm_mode { + CM_NOP, /* ignore the config mode value */ + CM_IMMEDIATE, + CM_DELAY, + CM_TRIGGER, + CM_INVALID +}; + +enum sys_state { + SYS_STATE_S0I0, + SYS_STATE_S0I1, + SYS_STATE_S0I2, + SYS_STATE_S0I3, + SYS_STATE_S3, + SYS_STATE_S5 +}; + +#define SET_CFG_CMD 1 + +enum int_status { + INT_SPURIOUS = 0, + INT_CMD_DONE = 1, + INT_CMD_ERR = 2, + INT_WAKE_RX = 3, + INT_SS_ERROR = 4, + INT_S0IX_MISS = 5, + INT_NO_ACKC6 = 6, + INT_INVALID = 7, +}; + +/* PMU register interface */ +static struct mrst_pmu_reg { + u32 pm_sts; /* 0x00 */ + u32 pm_cmd; /* 0x04 */ + u32 pm_ics; /* 0x08 */ + u32 _resv1; /* 0x0C */ + u32 pm_wkc[2]; /* 0x10 */ + u32 pm_wks[2]; /* 0x18 */ + u32 pm_ssc[4]; /* 0x20 */ + u32 pm_sss[4]; /* 0x30 */ + u32 pm_wssc[4]; /* 0x40 */ + u32 pm_c3c4; /* 0x50 */ + u32 pm_c5c6; /* 0x54 */ + u32 pm_msi_disable; /* 0x58 */ +} *pmu_reg; + +static inline u32 pmu_read_sts(void) { return readl(&pmu_reg->pm_sts); } +static inline u32 pmu_read_ics(void) { return readl(&pmu_reg->pm_ics); } +static inline u32 pmu_read_wks(void) { return readl(&pmu_reg->pm_wks[0]); } +static inline u32 pmu_read_sss(void) { return readl(&pmu_reg->pm_sss[0]); } + +static inline void pmu_write_cmd(u32 arg) { writel(arg, &pmu_reg->pm_cmd); } +static inline void pmu_write_ics(u32 arg) { writel(arg, &pmu_reg->pm_ics); } +static inline void pmu_write_wkc(u32 arg) { writel(arg, &pmu_reg->pm_wkc[0]); } +static inline void pmu_write_ssc(u32 arg) { writel(arg, &pmu_reg->pm_ssc[0]); } +static inline void pmu_write_wssc(u32 arg) + { writel(arg, &pmu_reg->pm_wssc[0]); } + +static inline void pmu_msi_enable(void) { writel(0, &pmu_reg->pm_msi_disable); } +static inline u32 pmu_msi_is_disabled(void) + { return readl(&pmu_reg->pm_msi_disable); } + +union pmu_pm_ics { + struct { + u32 cause:8; + u32 enable:1; + u32 pending:1; + u32 reserved:22; + } bits; + u32 value; +}; + +static inline void pmu_irq_enable(void) +{ + union pmu_pm_ics pmu_ics; + + pmu_ics.value = pmu_read_ics(); + pmu_ics.bits.enable = 1; + pmu_write_ics(pmu_ics.value); +} + +union pmu_pm_status { + struct { + u32 pmu_rev:8; + u32 pmu_busy:1; + u32 mode_id:4; + u32 Reserved:19; + } pmu_status_parts; + u32 pmu_status_value; +}; + +static inline int pmu_read_busy_status(void) +{ + union pmu_pm_status result; + + result.pmu_status_value = pmu_read_sts(); + + return result.pmu_status_parts.pmu_busy; +} + +/* pmu set config parameters */ +struct cfg_delay_param_t { + u32 cmd:8; + u32 ioc:1; + u32 cfg_mode:4; + u32 mode_id:3; + u32 sys_state:3; + u32 cfg_delay:8; + u32 rsvd:5; +}; + +struct cfg_trig_param_t { + u32 cmd:8; + u32 ioc:1; + u32 cfg_mode:4; + u32 mode_id:3; + u32 sys_state:3; + u32 cfg_trig_type:3; + u32 cfg_trig_val:8; + u32 cmbi:1; + u32 rsvd1:1; +}; + +union pmu_pm_set_cfg_cmd_t { + union { + struct cfg_delay_param_t d_param; + struct cfg_trig_param_t t_param; + } pmu2_params; + u32 pmu_pm_set_cfg_cmd_value; +}; + +#ifdef FUTURE_PATCH +extern int mrst_s0i3_entry(u32 regval, u32 *regaddr); +#else +static inline int mrst_s0i3_entry(u32 regval, u32 *regaddr) { return -1; } +#endif +#endif diff --git a/arch/x86/platform/mrst/vrtc.c b/arch/x86/platform/mrst/vrtc.c index 73d70d65e76e..225bd0f0f675 100644 --- a/arch/x86/platform/mrst/vrtc.c +++ b/arch/x86/platform/mrst/vrtc.c @@ -18,6 +18,7 @@ */ #include <linux/kernel.h> +#include <linux/export.h> #include <linux/init.h> #include <linux/sfi.h> #include <linux/platform_device.h> @@ -58,8 +59,11 @@ EXPORT_SYMBOL_GPL(vrtc_cmos_write); unsigned long vrtc_get_time(void) { u8 sec, min, hour, mday, mon; + unsigned long flags; u32 year; + spin_lock_irqsave(&rtc_lock, flags); + while ((vrtc_cmos_read(RTC_FREQ_SELECT) & RTC_UIP)) cpu_relax(); @@ -70,8 +74,10 @@ unsigned long vrtc_get_time(void) mon = vrtc_cmos_read(RTC_MONTH); year = vrtc_cmos_read(RTC_YEAR); - /* vRTC YEAR reg contains the offset to 1960 */ - year += 1960; + spin_unlock_irqrestore(&rtc_lock, flags); + + /* vRTC YEAR reg contains the offset to 1972 */ + year += 1972; printk(KERN_INFO "vRTC: sec: %d min: %d hour: %d day: %d " "mon: %d year: %d\n", sec, min, hour, mday, mon, year); @@ -83,8 +89,10 @@ unsigned long vrtc_get_time(void) int vrtc_set_mmss(unsigned long nowtime) { int real_sec, real_min; + unsigned long flags; int vrtc_min; + spin_lock_irqsave(&rtc_lock, flags); vrtc_min = vrtc_cmos_read(RTC_MINUTES); real_sec = nowtime % 60; @@ -95,6 +103,8 @@ int vrtc_set_mmss(unsigned long nowtime) vrtc_cmos_write(real_sec, RTC_SECONDS); vrtc_cmos_write(real_min, RTC_MINUTES); + spin_unlock_irqrestore(&rtc_lock, flags); + return 0; } diff --git a/arch/x86/platform/olpc/Makefile b/arch/x86/platform/olpc/Makefile index 81c5e2165c24..fd332c533947 100644 --- a/arch/x86/platform/olpc/Makefile +++ b/arch/x86/platform/olpc/Makefile @@ -1,2 +1,5 @@ obj-$(CONFIG_OLPC) += olpc.o olpc_ofw.o olpc_dt.o -obj-$(CONFIG_OLPC_XO1) += olpc-xo1.o +obj-$(CONFIG_OLPC_XO1_PM) += olpc-xo1-pm.o xo1-wakeup.o +obj-$(CONFIG_OLPC_XO1_RTC) += olpc-xo1-rtc.o +obj-$(CONFIG_OLPC_XO1_SCI) += olpc-xo1-sci.o +obj-$(CONFIG_OLPC_XO15_SCI) += olpc-xo15-sci.o diff --git a/arch/x86/platform/olpc/olpc-xo1-pm.c b/arch/x86/platform/olpc/olpc-xo1-pm.c new file mode 100644 index 000000000000..0ce8616c88ae --- /dev/null +++ b/arch/x86/platform/olpc/olpc-xo1-pm.c @@ -0,0 +1,216 @@ +/* + * 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 <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; + int r; + + /* Only STR is supported */ + if (pm_state != PM_SUSPEND_MEM) + return -EINVAL; + + r = olpc_ec_cmd(EC_SET_SCI_INHIBIT, NULL, 0, NULL, 0); + if (r) + return r; + + /* + * 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); + + /* 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; +} + +asmlinkage int xo1_do_sleep(u8 sleep_state) +{ + void *pgd_addr = __va(read_cr3()); + + /* Program wakeup mask (using dword access to CS5536_PM1_EN) */ + outl(wakeup_mask << 16, acpi_base + CS5536_PM1_STS); + + __asm__("movl %0,%%eax" : : "r" (pgd_addr)); + __asm__("call *(%%edi); cld" + : : "D" (&ofw_bios_entry)); + __asm__("movb $0x34, %al\n\t" + "outb %al, $0x70\n\t" + "movb $0x30, %al\n\t" + "outb %al, $0x71\n\t"); + return 0; +} + +static void xo1_power_off(void) +{ + printk(KERN_INFO "OLPC XO-1 power off sequence...\n"); + + /* Enable all of these controls with 0 delay */ + outl(0x40000000, pms_base + CS5536_PM_SCLK); + outl(0x40000000, pms_base + CS5536_PM_IN_SLPCTL); + outl(0x40000000, pms_base + CS5536_PM_WKXD); + outl(0x40000000, pms_base + CS5536_PM_WKD); + + /* Clear status bits (possibly unnecessary) */ + outl(0x0002ffff, pms_base + CS5536_PM_SSC); + outl(0xffffffff, acpi_base + CS5536_PM_GPE0_STS); + + /* Write SLP_EN bit to start the machinery */ + outl(0x00002000, acpi_base + CS5536_PM1_CNT); +} + +static int xo1_power_state_valid(suspend_state_t pm_state) +{ + /* suspend-to-RAM only */ + return pm_state == PM_SUSPEND_MEM; +} + +static const struct platform_suspend_ops xo1_suspend_ops = { + .valid = xo1_power_state_valid, + .enter = xo1_power_state_enter, +}; + +static int __devinit 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 __devexit 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 = __devexit_p(xo1_pm_remove), +}; + +static struct platform_driver cs5535_acpi_driver = { + .driver = { + .name = "olpc-xo1-pm-acpi", + .owner = THIS_MODULE, + }, + .probe = xo1_pm_probe, + .remove = __devexit_p(xo1_pm_remove), +}; + +static int __init xo1_pm_init(void) +{ + int r; + + r = platform_driver_register(&cs5535_pms_driver); + if (r) + return r; + + r = platform_driver_register(&cs5535_acpi_driver); + if (r) + platform_driver_unregister(&cs5535_pms_driver); + + return r; +} +arch_initcall(xo1_pm_init); diff --git a/arch/x86/platform/olpc/olpc-xo1-rtc.c b/arch/x86/platform/olpc/olpc-xo1-rtc.c new file mode 100644 index 000000000000..a2b4efddd61a --- /dev/null +++ b/arch/x86/platform/olpc/olpc-xo1-rtc.c @@ -0,0 +1,81 @@ +/* + * Support for OLPC XO-1 Real Time Clock (RTC) + * + * Copyright (C) 2011 One Laptop per Child + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/mc146818rtc.h> +#include <linux/platform_device.h> +#include <linux/rtc.h> +#include <linux/of.h> + +#include <asm/msr.h> +#include <asm/olpc.h> + +static void rtc_wake_on(struct device *dev) +{ + olpc_xo1_pm_wakeup_set(CS5536_PM_RTC); +} + +static void rtc_wake_off(struct device *dev) +{ + olpc_xo1_pm_wakeup_clear(CS5536_PM_RTC); +} + +static struct resource rtc_platform_resource[] = { + [0] = { + .start = RTC_PORT(0), + .end = RTC_PORT(1), + .flags = IORESOURCE_IO, + }, + [1] = { + .start = RTC_IRQ, + .end = RTC_IRQ, + .flags = IORESOURCE_IRQ, + } +}; + +static struct cmos_rtc_board_info rtc_info = { + .rtc_day_alarm = 0, + .rtc_mon_alarm = 0, + .rtc_century = 0, + .wake_on = rtc_wake_on, + .wake_off = rtc_wake_off, +}; + +static struct platform_device xo1_rtc_device = { + .name = "rtc_cmos", + .id = -1, + .num_resources = ARRAY_SIZE(rtc_platform_resource), + .dev.platform_data = &rtc_info, + .resource = rtc_platform_resource, +}; + +static int __init xo1_rtc_init(void) +{ + int r; + struct device_node *node; + + node = of_find_compatible_node(NULL, NULL, "olpc,xo1-rtc"); + if (!node) + return 0; + of_node_put(node); + + pr_info("olpc-xo1-rtc: Initializing OLPC XO-1 RTC\n"); + rdmsrl(MSR_RTC_DOMA_OFFSET, rtc_info.rtc_day_alarm); + rdmsrl(MSR_RTC_MONA_OFFSET, rtc_info.rtc_mon_alarm); + rdmsrl(MSR_RTC_CEN_OFFSET, rtc_info.rtc_century); + + r = platform_device_register(&xo1_rtc_device); + if (r) + return r; + + device_init_wakeup(&xo1_rtc_device.dev, 1); + return 0; +} +arch_initcall(xo1_rtc_init); diff --git a/arch/x86/platform/olpc/olpc-xo1-sci.c b/arch/x86/platform/olpc/olpc-xo1-sci.c new file mode 100644 index 000000000000..1d4c783d7325 --- /dev/null +++ b/arch/x86/platform/olpc/olpc-xo1-sci.c @@ -0,0 +1,614 @@ +/* + * 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/mfd/core.h> +#include <linux/power_supply.h> +#include <linux/suspend.h> +#include <linux/workqueue.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; + } + + input_report_switch(ebook_switch_idev, SW_TABLET_MODE, state); + input_sync(ebook_switch_idev); +} + +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) +{ + input_report_switch(lid_switch_idev, SW_LID, !lid_open); + input_sync(lid_switch_idev); +} + +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 && !(sts & CS5536_WAK_FLAG)) { + 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); + } + + 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 __devinit 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 SCI from power button, and clear pending interrupts */ + sts = inl(acpi_base + CS5536_PM1_STS); + outl((CS5536_PM_PWRBTN << 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 __devinit 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 __devinit 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 __devinit 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); + input_free_device(power_button_idev); +} + +static int __devinit 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); + input_free_device(ebook_switch_idev); +} + +static int __devinit 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); +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); + input_free_device(lid_switch_idev); +} + +static int __devinit 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 __devexit 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 = __devexit_p(xo1_sci_remove), + .suspend = xo1_sci_suspend, + .resume = xo1_sci_resume, +}; + +static int __init xo1_sci_init(void) +{ + return platform_driver_register(&xo1_sci_driver); +} +arch_initcall(xo1_sci_init); diff --git a/arch/x86/platform/olpc/olpc-xo1.c b/arch/x86/platform/olpc/olpc-xo1.c deleted file mode 100644 index ab81fb271760..000000000000 --- a/arch/x86/platform/olpc/olpc-xo1.c +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Support for 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/module.h> -#include <linux/platform_device.h> -#include <linux/pm.h> -#include <linux/mfd/core.h> - -#include <asm/io.h> -#include <asm/olpc.h> - -#define DRV_NAME "olpc-xo1" - -/* PMC registers (PMS block) */ -#define PM_SCLK 0x10 -#define PM_IN_SLPCTL 0x20 -#define PM_WKXD 0x34 -#define PM_WKD 0x30 -#define PM_SSC 0x54 - -/* PM registers (ACPI block) */ -#define PM1_CNT 0x08 -#define PM_GPE0_STS 0x18 - -static unsigned long acpi_base; -static unsigned long pms_base; - -static void xo1_power_off(void) -{ - printk(KERN_INFO "OLPC XO-1 power off sequence...\n"); - - /* Enable all of these controls with 0 delay */ - outl(0x40000000, pms_base + PM_SCLK); - outl(0x40000000, pms_base + PM_IN_SLPCTL); - outl(0x40000000, pms_base + PM_WKXD); - outl(0x40000000, pms_base + PM_WKD); - - /* Clear status bits (possibly unnecessary) */ - outl(0x0002ffff, pms_base + PM_SSC); - outl(0xffffffff, acpi_base + PM_GPE0_STS); - - /* Write SLP_EN bit to start the machinery */ - outl(0x00002000, acpi_base + PM1_CNT); -} - -static int __devinit olpc_xo1_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) { - pm_power_off = xo1_power_off; - printk(KERN_INFO "OLPC XO-1 support registered\n"); - } - - return 0; -} - -static int __devexit olpc_xo1_remove(struct platform_device *pdev) -{ - 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_drv = { - .driver = { - .name = "cs5535-pms", - .owner = THIS_MODULE, - }, - .probe = olpc_xo1_probe, - .remove = __devexit_p(olpc_xo1_remove), -}; - -static struct platform_driver cs5535_acpi_drv = { - .driver = { - .name = "olpc-xo1-pm-acpi", - .owner = THIS_MODULE, - }, - .probe = olpc_xo1_probe, - .remove = __devexit_p(olpc_xo1_remove), -}; - -static int __init olpc_xo1_init(void) -{ - int r; - - r = platform_driver_register(&cs5535_pms_drv); - if (r) - return r; - - r = platform_driver_register(&cs5535_acpi_drv); - if (r) - platform_driver_unregister(&cs5535_pms_drv); - - return r; -} - -static void __exit olpc_xo1_exit(void) -{ - platform_driver_unregister(&cs5535_acpi_drv); - platform_driver_unregister(&cs5535_pms_drv); -} - -MODULE_AUTHOR("Daniel Drake <dsd@laptop.org>"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:cs5535-pms"); - -module_init(olpc_xo1_init); -module_exit(olpc_xo1_exit); diff --git a/arch/x86/platform/olpc/olpc-xo15-sci.c b/arch/x86/platform/olpc/olpc-xo15-sci.c new file mode 100644 index 000000000000..2b235b77d9ab --- /dev/null +++ b/arch/x86/platform/olpc/olpc-xo15-sci.c @@ -0,0 +1,168 @@ +/* + * 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 <acpi/acpi_bus.h> +#include <acpi/acpi_drivers.h> +#include <asm/olpc.h> + +#define DRV_NAME "olpc-xo15-sci" +#define PFX DRV_NAME ": " +#define XO15_SCI_CLASS DRV_NAME +#define XO15_SCI_DEVICE_NAME "OLPC XO-1.5 SCI" + +static unsigned long xo15_sci_gpe; + +static 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; + + 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); + + /* 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; +} + +static int xo15_sci_remove(struct acpi_device *device, int type) +{ + acpi_disable_gpe(NULL, xo15_sci_gpe); + acpi_remove_gpe_handler(NULL, xo15_sci_gpe, xo15_sci_gpe_handler); + cancel_work_sync(&sci_work); + return 0; +} + +static int xo15_sci_resume(struct acpi_device *device) +{ + /* 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 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, + .resume = xo15_sci_resume, + }, +}; + +static int __init xo15_sci_init(void) +{ + return acpi_bus_register_driver(&xo15_sci_drv); +} +device_initcall(xo15_sci_init); diff --git a/arch/x86/platform/olpc/olpc.c b/arch/x86/platform/olpc/olpc.c index 0060fd59ea00..7cce722667b8 100644 --- a/arch/x86/platform/olpc/olpc.c +++ b/arch/x86/platform/olpc/olpc.c @@ -19,6 +19,7 @@ #include <linux/string.h> #include <linux/platform_device.h> #include <linux/of.h> +#include <linux/syscore_ops.h> #include <asm/geode.h> #include <asm/setup.h> @@ -30,6 +31,9 @@ EXPORT_SYMBOL_GPL(olpc_platform_info); static DEFINE_SPINLOCK(ec_lock); +/* EC event mask to be applied during suspend (defining wakeup sources). */ +static u16 ec_wakeup_mask; + /* what the timeout *should* be (in ms) */ #define EC_BASE_TIMEOUT 20 @@ -157,13 +161,13 @@ restart: if (inbuf && inlen) { /* write data to EC */ for (i = 0; i < inlen; i++) { + pr_devel("olpc-ec: sending cmd arg 0x%x\n", inbuf[i]); + outb(inbuf[i], 0x68); if (wait_on_ibf(0x6c, 0)) { printk(KERN_ERR "olpc-ec: timeout waiting for" " EC accept data!\n"); goto err; } - pr_devel("olpc-ec: sending cmd arg 0x%x\n", inbuf[i]); - outb(inbuf[i], 0x68); } } if (outbuf && outlen) { @@ -188,6 +192,88 @@ err: } EXPORT_SYMBOL_GPL(olpc_ec_cmd); +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 int olpc_ec_suspend(void) +{ + return olpc_ec_mask_write(ec_wakeup_mask); +} + static bool __init check_ofw_architecture(struct device_node *root) { const char *olpc_arch; @@ -242,6 +328,10 @@ static int __init add_xo1_platform_devices(void) return 0; } +static struct syscore_ops olpc_syscore_ops = { + .suspend = olpc_ec_suspend, +}; + static int __init olpc_init(void) { int r = 0; @@ -266,6 +356,9 @@ static int __init olpc_init(void) !cs5535_has_vsa2()) x86_init.pci.arch_init = pci_olpc_init; #endif + /* EC version 0x5f adds support for wide SCI mask */ + if (olpc_platform_info.ecver >= 0x5f) + olpc_platform_info.flags |= OLPC_F_EC_WIDE_SCI; printk(KERN_INFO "OLPC board revision %s%X (EC=%x)\n", ((olpc_platform_info.boardrev & 0xf) < 8) ? "pre" : "", @@ -278,6 +371,8 @@ static int __init olpc_init(void) return r; } + register_syscore_ops(&olpc_syscore_ops); + return 0; } diff --git a/arch/x86/platform/olpc/olpc_dt.c b/arch/x86/platform/olpc/olpc_dt.c index d39f63d017d2..d6ee92986920 100644 --- a/arch/x86/platform/olpc/olpc_dt.c +++ b/arch/x86/platform/olpc/olpc_dt.c @@ -165,6 +165,107 @@ static struct of_pdt_ops prom_olpc_ops __initdata = { .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; @@ -172,6 +273,8 @@ void __init olpc_dt_build_devicetree(void) 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"); diff --git a/arch/x86/platform/olpc/xo1-wakeup.S b/arch/x86/platform/olpc/xo1-wakeup.S new file mode 100644 index 000000000000..948deb289753 --- /dev/null +++ b/arch/x86/platform/olpc/xo1-wakeup.S @@ -0,0 +1,124 @@ +.text +#include <linux/linkage.h> +#include <asm/segment.h> +#include <asm/page.h> +#include <asm/pgtable_32.h> + + .macro writepost,value + movb $0x34, %al + outb %al, $0x70 + movb $\value, %al + outb %al, $0x71 + .endm + +wakeup_start: + # OFW lands us here, running in protected mode, with a + # kernel-compatible GDT already setup. + + # Clear any dangerous flags + pushl $0 + popfl + + writepost 0x31 + + # Set up %cr3 + movl $initial_page_table - __PAGE_OFFSET, %eax + movl %eax, %cr3 + + movl saved_cr4, %eax + movl %eax, %cr4 + + movl saved_cr0, %eax + movl %eax, %cr0 + + # Control registers were modified, pipeline resync is needed + jmp 1f +1: + + movw $__KERNEL_DS, %ax + movw %ax, %ss + movw %ax, %ds + movw %ax, %es + movw %ax, %fs + movw %ax, %gs + + lgdt saved_gdt + lidt saved_idt + lldt saved_ldt + ljmp $(__KERNEL_CS),$1f +1: + movl %cr3, %eax + movl %eax, %cr3 + wbinvd + + # Go back to the return point + jmp ret_point + +save_registers: + sgdt saved_gdt + sidt saved_idt + sldt saved_ldt + + pushl %edx + movl %cr4, %edx + movl %edx, saved_cr4 + + movl %cr0, %edx + movl %edx, saved_cr0 + + popl %edx + + movl %ebx, saved_context_ebx + movl %ebp, saved_context_ebp + movl %esi, saved_context_esi + movl %edi, saved_context_edi + + pushfl + popl saved_context_eflags + + ret + +restore_registers: + movl saved_context_ebp, %ebp + movl saved_context_ebx, %ebx + movl saved_context_esi, %esi + movl saved_context_edi, %edi + + pushl saved_context_eflags + popfl + + ret + +ENTRY(do_olpc_suspend_lowlevel) + call save_processor_state + call save_registers + + # This is the stack context we want to remember + movl %esp, saved_context_esp + + pushl $3 + call xo1_do_sleep + + jmp wakeup_start + .p2align 4,,7 +ret_point: + movl saved_context_esp, %esp + + writepost 0x32 + + call restore_registers + call restore_processor_state + ret + +.data +saved_gdt: .long 0,0 +saved_idt: .long 0,0 +saved_ldt: .long 0 +saved_cr4: .long 0 +saved_cr0: .long 0 +saved_context_esp: .long 0 +saved_context_edi: .long 0 +saved_context_esi: .long 0 +saved_context_ebx: .long 0 +saved_context_ebp: .long 0 +saved_context_eflags: .long 0 diff --git a/arch/x86/platform/uv/bios_uv.c b/arch/x86/platform/uv/bios_uv.c index 8bc57baaa9ad..766612137a62 100644 --- a/arch/x86/platform/uv/bios_uv.c +++ b/arch/x86/platform/uv/bios_uv.c @@ -20,6 +20,7 @@ */ #include <linux/efi.h> +#include <linux/export.h> #include <asm/efi.h> #include <linux/io.h> #include <asm/uv/bios.h> diff --git a/arch/x86/platform/uv/tlb_uv.c b/arch/x86/platform/uv/tlb_uv.c index 68e467f69fec..5b552198f774 100644 --- a/arch/x86/platform/uv/tlb_uv.c +++ b/arch/x86/platform/uv/tlb_uv.c @@ -115,9 +115,6 @@ early_param("nobau", setup_nobau); /* base pnode in this partition */ static int uv_base_pnode __read_mostly; -/* position of pnode (which is nasid>>1): */ -static int uv_nshift __read_mostly; -static unsigned long uv_mmask __read_mostly; static DEFINE_PER_CPU(struct ptc_stats, ptcstats); static DEFINE_PER_CPU(struct bau_control, bau_control); @@ -296,14 +293,18 @@ static void bau_process_message(struct msg_desc *mdp, } /* - * Determine the first cpu on a uvhub. + * Determine the first cpu on a pnode. */ -static int uvhub_to_first_cpu(int uvhub) +static int pnode_to_first_cpu(int pnode, struct bau_control *smaster) { int cpu; - for_each_present_cpu(cpu) - if (uvhub == uv_cpu_to_blade_id(cpu)) + struct hub_and_pnode *hpp; + + for_each_present_cpu(cpu) { + hpp = &smaster->thp[cpu]; + if (pnode == hpp->pnode) return cpu; + } return -1; } @@ -366,28 +367,32 @@ static void do_reset(void *ptr) * Use IPI to get all target uvhubs to release resources held by * a given sending cpu number. */ -static void reset_with_ipi(struct bau_targ_hubmask *distribution, int sender) +static void reset_with_ipi(struct pnmask *distribution, struct bau_control *bcp) { - int uvhub; + int pnode; + int apnode; int maskbits; - cpumask_t mask; + int sender = bcp->cpu; + cpumask_t *mask = bcp->uvhub_master->cpumask; + struct bau_control *smaster = bcp->socket_master; struct reset_args reset_args; reset_args.sender = sender; - cpus_clear(mask); + cpus_clear(*mask); /* find a single cpu for each uvhub in this distribution mask */ - maskbits = sizeof(struct bau_targ_hubmask) * BITSPERBYTE; - for (uvhub = 0; uvhub < maskbits; uvhub++) { + maskbits = sizeof(struct pnmask) * BITSPERBYTE; + /* each bit is a pnode relative to the partition base pnode */ + for (pnode = 0; pnode < maskbits; pnode++) { int cpu; - if (!bau_uvhub_isset(uvhub, distribution)) + if (!bau_uvhub_isset(pnode, distribution)) continue; - /* find a cpu for this uvhub */ - cpu = uvhub_to_first_cpu(uvhub); - cpu_set(cpu, mask); + apnode = pnode + bcp->partition_base_pnode; + cpu = pnode_to_first_cpu(apnode, smaster); + cpu_set(cpu, *mask); } /* IPI all cpus; preemption is already disabled */ - smp_call_function_many(&mask, do_reset, (void *)&reset_args, 1); + smp_call_function_many(mask, do_reset, (void *)&reset_args, 1); return; } @@ -604,7 +609,7 @@ static void destination_plugged(struct bau_desc *bau_desc, quiesce_local_uvhub(hmaster); spin_lock(&hmaster->queue_lock); - reset_with_ipi(&bau_desc->distribution, bcp->cpu); + reset_with_ipi(&bau_desc->distribution, bcp); spin_unlock(&hmaster->queue_lock); end_uvhub_quiesce(hmaster); @@ -626,7 +631,7 @@ static void destination_timeout(struct bau_desc *bau_desc, quiesce_local_uvhub(hmaster); spin_lock(&hmaster->queue_lock); - reset_with_ipi(&bau_desc->distribution, bcp->cpu); + reset_with_ipi(&bau_desc->distribution, bcp); spin_unlock(&hmaster->queue_lock); end_uvhub_quiesce(hmaster); @@ -1334,9 +1339,10 @@ static ssize_t tunables_write(struct file *file, const char __user *user, instr[count] = '\0'; - bcp = &per_cpu(bau_control, smp_processor_id()); - + cpu = get_cpu(); + bcp = &per_cpu(bau_control, cpu); ret = parse_tunables_write(bcp, instr, count); + put_cpu(); if (ret) return ret; @@ -1426,7 +1432,7 @@ static void activation_descriptor_init(int node, int pnode, int base_pnode) { int i; int cpu; - unsigned long pa; + unsigned long gpa; unsigned long m; unsigned long n; size_t dsize; @@ -1442,9 +1448,9 @@ static void activation_descriptor_init(int node, int pnode, int base_pnode) bau_desc = kmalloc_node(dsize, GFP_KERNEL, node); BUG_ON(!bau_desc); - pa = uv_gpa(bau_desc); /* need the real nasid*/ - n = pa >> uv_nshift; - m = pa & uv_mmask; + gpa = uv_gpa(bau_desc); + n = uv_gpa_to_gnode(gpa); + m = uv_gpa_to_offset(gpa); /* the 14-bit pnode */ write_mmr_descriptor_base(pnode, (n << UV_DESC_PSHIFT | m)); @@ -1516,9 +1522,9 @@ static void pq_init(int node, int pnode) bcp->queue_last = pqp + (DEST_Q_SIZE - 1); } /* - * need the pnode of where the memory was really allocated + * need the gnode of where the memory was really allocated */ - pn = uv_gpa(pqp) >> uv_nshift; + pn = uv_gpa_to_gnode(uv_gpa(pqp)); first = uv_physnodeaddr(pqp); pn_first = ((unsigned long)pn << UV_PAYLOADQ_PNODE_SHIFT) | first; last = uv_physnodeaddr(pqp + (DEST_Q_SIZE - 1)); @@ -1687,6 +1693,16 @@ static void make_per_cpu_thp(struct bau_control *smaster) } /* + * Each uvhub is to get a local cpumask. + */ +static void make_per_hub_cpumask(struct bau_control *hmaster) +{ + int sz = sizeof(cpumask_t); + + hmaster->cpumask = kzalloc_node(sz, GFP_KERNEL, hmaster->osnode); +} + +/* * Initialize all the per_cpu information for the cpu's on a given socket, * given what has been gathered into the socket_desc struct. * And reports the chosen hub and socket masters back to the caller. @@ -1751,11 +1767,12 @@ static int __init summarize_uvhub_sockets(int nuvhubs, sdp = &bdp->socket[socket]; if (scan_sock(sdp, bdp, &smaster, &hmaster)) return 1; + make_per_cpu_thp(smaster); } socket++; socket_mask = (socket_mask >> 1); - make_per_cpu_thp(smaster); } + make_per_hub_cpumask(hmaster); } return 0; } @@ -1777,15 +1794,20 @@ static int __init init_per_cpu(int nuvhubs, int base_part_pnode) uvhub_mask = kzalloc((nuvhubs+7)/8, GFP_KERNEL); if (get_cpu_topology(base_part_pnode, uvhub_descs, uvhub_mask)) - return 1; + goto fail; if (summarize_uvhub_sockets(nuvhubs, uvhub_descs, uvhub_mask)) - return 1; + goto fail; kfree(uvhub_descs); kfree(uvhub_mask); init_per_cpu_tunables(); return 0; + +fail: + kfree(uvhub_descs); + kfree(uvhub_mask); + return 1; } /* @@ -1812,8 +1834,6 @@ static int __init uv_bau_init(void) zalloc_cpumask_var_node(mask, GFP_KERNEL, cpu_to_node(cur_cpu)); } - uv_nshift = uv_hub_info->m_val; - uv_mmask = (1UL << uv_hub_info->m_val) - 1; nuvhubs = uv_num_possible_blades(); spin_lock_init(&disable_lock); congested_cycles = usec_2_cycles(congested_respns_us); |