mirror of
git://projects.qi-hardware.com/openwrt-xburst.git
synced 2025-04-21 12:27:27 +03:00
Adding .24 support for olpc
kernel compiles fine, but graphic support is broken to test new kernel version, change target/linux/olpc/Makefile from .23.16 to .24 git-svn-id: svn://svn.openwrt.org/openwrt/trunk@10493 3c298f89-4303-0410-b956-a3cf2f4a3e73
This commit is contained in:
354
target/linux/olpc/files-2.6.23/arch/i386/kernel/mfgpt.c
Normal file
354
target/linux/olpc/files-2.6.23/arch/i386/kernel/mfgpt.c
Normal file
@@ -0,0 +1,354 @@
|
||||
/* Driver/API for AMD Geode Multi-Function General Purpose Timers (MFGPT)
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* The MFPGT timers on the CS5536 provide us with suitable timers to use
|
||||
* as clock event sources - not as good as a HPET or APIC, but certainly
|
||||
* better then the PIT. This isn't a general purpose MFGPT driver, but
|
||||
* a simplified one designed specifically to act as a clock event source.
|
||||
* For full details about the MFGPT, please consult the CS5536 data sheet.
|
||||
*/
|
||||
|
||||
/* We are using the 32Khz input clock - its the only one that has the
|
||||
* ranges we find desirable. The following table lists the suitable
|
||||
* divisors and the associated hz, minimum interval
|
||||
* and the maximum interval:
|
||||
|
||||
Divisor Hz Min Delta (S) Max Delta (S)
|
||||
1 32000 .0005 2.048
|
||||
2 16000 .001 4.096
|
||||
4 8000 .002 8.192
|
||||
8 4000 .004 16.384
|
||||
16 2000 .008 32.768
|
||||
32 1000 .016 65.536
|
||||
64 500 .032 131.072
|
||||
128 250 .064 262.144
|
||||
256 125 .128 524.288
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/clocksource.h>
|
||||
#include <linux/clockchips.h>
|
||||
#include <asm/geode.h>
|
||||
|
||||
#include "do_timer.h"
|
||||
|
||||
#define MFGPT_MAX_TIMERS 8
|
||||
|
||||
#define F_AVAIL 0x01
|
||||
|
||||
static struct mfgpt_timer_t {
|
||||
int flags;
|
||||
struct module *owner;
|
||||
} mfgpt_timers[MFGPT_MAX_TIMERS];
|
||||
|
||||
/* Selected from the table above */
|
||||
|
||||
#define MFGPT_DIVISOR 16
|
||||
#define MFGPT_SCALE 4 /* divisor = 2^(scale) */
|
||||
#define MFGPT_HZ (32000 / MFGPT_DIVISOR)
|
||||
#define MFGPT_PERIODIC (MFGPT_HZ / HZ)
|
||||
|
||||
#ifdef CONFIG_GEODE_MFGPT_TIMER
|
||||
static int __init mfgpt_timer_setup(void);
|
||||
#else
|
||||
#define mfgpt_timer_setup() (0)
|
||||
#endif
|
||||
|
||||
/* Allow for disabling of MFGPTs */
|
||||
static int disable = 0;
|
||||
static int __init mfgpt_disable(char *s)
|
||||
{
|
||||
disable = 1;
|
||||
return 1;
|
||||
}
|
||||
__setup("nomfgpt", mfgpt_disable);
|
||||
|
||||
/*
|
||||
* Check whether any MFGPTs are available for the kernel to use. In most
|
||||
* cases, firmware that uses AMD's VSA code will claim all timers during
|
||||
* bootup; we certainly don't want to take them if they're already in use.
|
||||
* In other cases (such as with VSAless OpenFirmware), the system firmware
|
||||
* leaves timers available for us to use.
|
||||
*/
|
||||
int __init geode_mfgpt_detect(void)
|
||||
{
|
||||
int count = 0, i;
|
||||
u16 val;
|
||||
|
||||
if (disable) {
|
||||
printk(KERN_INFO "geode-mfgpt: Skipping MFGPT setup\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < MFGPT_MAX_TIMERS; i++) {
|
||||
val = geode_mfgpt_read(i, MFGPT_REG_SETUP);
|
||||
if (!(val & MFGPT_SETUP_SETUP)) {
|
||||
mfgpt_timers[i].flags = F_AVAIL;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
/* set up clock event device, if desired */
|
||||
i = mfgpt_timer_setup();
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
int geode_mfgpt_toggle_event(int timer, int cmp, int event, int enable)
|
||||
{
|
||||
u32 msr, mask, value, dummy;
|
||||
int shift = (cmp == MFGPT_CMP1) ? 0 : 8;
|
||||
|
||||
if (timer < 0 || timer >= MFGPT_MAX_TIMERS)
|
||||
return -EIO;
|
||||
|
||||
/*
|
||||
* The register maps for these are described in sections 6.17.1.x of
|
||||
* the AMD Geode CS5536 Companion Device Data Book.
|
||||
*/
|
||||
switch(event) {
|
||||
case MFGPT_EVENT_RESET:
|
||||
/* XXX: According to the docs, we cannot reset timers above
|
||||
* 6; that is, resets for 7 and 8 will be ignored. Is this
|
||||
* a problem? */
|
||||
msr = MFGPT_NR_MSR;
|
||||
mask = 1 << (timer + 24);
|
||||
break;
|
||||
|
||||
case MFGPT_EVENT_NMI:
|
||||
msr = MFGPT_NR_MSR;
|
||||
mask = 1 << (timer + shift);
|
||||
break;
|
||||
|
||||
case MFGPT_EVENT_IRQ:
|
||||
msr = MFGPT_IRQ_MSR;
|
||||
mask = 1 << (timer + shift);
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
rdmsr(msr, value, dummy);
|
||||
|
||||
if (enable)
|
||||
value |= mask;
|
||||
else
|
||||
value &= ~mask;
|
||||
|
||||
wrmsr(msr, value, dummy);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(geode_mfgpt_toggle_event);
|
||||
|
||||
int geode_mfgpt_set_irq(int timer, int cmp, int irq, int enable)
|
||||
{
|
||||
u32 val, dummy;
|
||||
int offset;
|
||||
|
||||
if (timer < 0 || timer >= MFGPT_MAX_TIMERS)
|
||||
return -EIO;
|
||||
|
||||
if (geode_mfgpt_toggle_event(timer, cmp, MFGPT_EVENT_IRQ, enable))
|
||||
return -EIO;
|
||||
|
||||
rdmsr(0x51400022, val, dummy);
|
||||
|
||||
offset = (timer % 4) * 4;
|
||||
|
||||
val &= ~((0xF << offset) | (0xF << (offset + 16)));
|
||||
|
||||
if (enable) {
|
||||
val |= (irq & 0x0F) << (offset);
|
||||
val |= (irq & 0x0F) << (offset + 16);
|
||||
}
|
||||
|
||||
wrmsr(0x51400022, val, dummy);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(geode_mfgpt_set_irq);
|
||||
|
||||
static int mfgpt_get(int timer, struct module *owner)
|
||||
{
|
||||
mfgpt_timers[timer].flags &= ~F_AVAIL;
|
||||
mfgpt_timers[timer].owner = owner;
|
||||
printk(KERN_INFO "geode-mfgpt: Registered timer %d\n", timer);
|
||||
return timer;
|
||||
}
|
||||
|
||||
int geode_mfgpt_alloc_timer(int timer, int domain, struct module *owner)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!geode_get_dev_base(GEODE_DEV_MFGPT))
|
||||
return -ENODEV;
|
||||
if (timer >= MFGPT_MAX_TIMERS)
|
||||
return -EIO;
|
||||
|
||||
if (timer < 0) {
|
||||
/* Try to find an available timer */
|
||||
for (i = 0; i < MFGPT_MAX_TIMERS; i++) {
|
||||
if (mfgpt_timers[i].flags & F_AVAIL)
|
||||
return mfgpt_get(i, owner);
|
||||
|
||||
if (i == 5 && domain == MFGPT_DOMAIN_WORKING)
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* If they requested a specific timer, try to honor that */
|
||||
if (mfgpt_timers[timer].flags & F_AVAIL)
|
||||
return mfgpt_get(timer, owner);
|
||||
}
|
||||
|
||||
/* No timers available - too bad */
|
||||
return -1;
|
||||
}
|
||||
EXPORT_SYMBOL(geode_mfgpt_alloc_timer);
|
||||
|
||||
#ifdef CONFIG_GEODE_MFGPT_TIMER
|
||||
|
||||
static unsigned int mfgpt_tick_mode = CLOCK_EVT_MODE_SHUTDOWN;
|
||||
static u16 mfgpt_event_clock;
|
||||
|
||||
static int irq = 7;
|
||||
static int __init mfgpt_setup(char *str)
|
||||
{
|
||||
get_option(&str, &irq);
|
||||
return 1;
|
||||
}
|
||||
__setup("mfgpt_irq=", mfgpt_setup);
|
||||
|
||||
static inline void mfgpt_disable_timer(u16 clock)
|
||||
{
|
||||
u16 val = geode_mfgpt_read(clock, MFGPT_REG_SETUP);
|
||||
geode_mfgpt_write(clock, MFGPT_REG_SETUP, val & ~MFGPT_SETUP_CNTEN);
|
||||
}
|
||||
|
||||
static int mfgpt_next_event(unsigned long, struct clock_event_device *);
|
||||
static void mfgpt_set_mode(enum clock_event_mode, struct clock_event_device *);
|
||||
|
||||
static struct clock_event_device mfgpt_clockevent = {
|
||||
.name = "mfgpt-timer",
|
||||
.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
|
||||
.set_mode = mfgpt_set_mode,
|
||||
.set_next_event = mfgpt_next_event,
|
||||
.rating = 250,
|
||||
.cpumask = CPU_MASK_ALL,
|
||||
.shift = 32
|
||||
};
|
||||
|
||||
static inline void mfgpt_start_timer(u16 clock, u16 delta)
|
||||
{
|
||||
geode_mfgpt_write(mfgpt_event_clock, MFGPT_REG_CMP2, (u16) delta);
|
||||
geode_mfgpt_write(mfgpt_event_clock, MFGPT_REG_COUNTER, 0);
|
||||
|
||||
geode_mfgpt_write(mfgpt_event_clock, MFGPT_REG_SETUP,
|
||||
MFGPT_SETUP_CNTEN | MFGPT_SETUP_CMP2);
|
||||
}
|
||||
|
||||
static void mfgpt_set_mode(enum clock_event_mode mode,
|
||||
struct clock_event_device *evt)
|
||||
{
|
||||
mfgpt_disable_timer(mfgpt_event_clock);
|
||||
|
||||
if (mode == CLOCK_EVT_MODE_PERIODIC)
|
||||
mfgpt_start_timer(mfgpt_event_clock, MFGPT_PERIODIC);
|
||||
|
||||
mfgpt_tick_mode = mode;
|
||||
}
|
||||
|
||||
static int mfgpt_next_event(unsigned long delta, struct clock_event_device *evt)
|
||||
{
|
||||
mfgpt_start_timer(mfgpt_event_clock, delta);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Assume (foolishly?), that this interrupt was due to our tick */
|
||||
|
||||
static irqreturn_t mfgpt_tick(int irq, void *dev_id)
|
||||
{
|
||||
if (mfgpt_tick_mode == CLOCK_EVT_MODE_SHUTDOWN)
|
||||
return IRQ_HANDLED;
|
||||
|
||||
/* Turn off the clock */
|
||||
mfgpt_disable_timer(mfgpt_event_clock);
|
||||
|
||||
/* Clear the counter */
|
||||
geode_mfgpt_write(mfgpt_event_clock, MFGPT_REG_COUNTER, 0);
|
||||
|
||||
/* Restart the clock in periodic mode */
|
||||
|
||||
if (mfgpt_tick_mode == CLOCK_EVT_MODE_PERIODIC) {
|
||||
geode_mfgpt_write(mfgpt_event_clock, MFGPT_REG_SETUP,
|
||||
MFGPT_SETUP_CNTEN | MFGPT_SETUP_CMP2);
|
||||
}
|
||||
|
||||
mfgpt_clockevent.event_handler(&mfgpt_clockevent);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static struct irqaction mfgptirq = {
|
||||
.handler = mfgpt_tick,
|
||||
.flags = IRQF_DISABLED | IRQF_NOBALANCING,
|
||||
.mask = CPU_MASK_NONE,
|
||||
.name = "mfgpt-timer"
|
||||
};
|
||||
|
||||
static int __init mfgpt_timer_setup(void)
|
||||
{
|
||||
int timer, ret;
|
||||
u16 val;
|
||||
|
||||
timer = geode_mfgpt_alloc_timer(MFGPT_TIMER_ANY, MFGPT_DOMAIN_WORKING, THIS_MODULE);
|
||||
if (timer < 0) {
|
||||
printk(KERN_ERR "mfgpt-timer: Could not allocate a MFPGT timer\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
mfgpt_event_clock = timer;
|
||||
/* Set the clock scale and enable the event mode for CMP2 */
|
||||
val = MFGPT_SCALE | (3 << 8);
|
||||
|
||||
geode_mfgpt_write(mfgpt_event_clock, MFGPT_REG_SETUP, val);
|
||||
|
||||
/* Set up the IRQ on the MFGPT side */
|
||||
if (geode_mfgpt_setup_irq(mfgpt_event_clock, MFGPT_CMP2, irq)) {
|
||||
printk(KERN_ERR "mfgpt-timer: Could not set up IRQ %d\n", irq);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* And register it with the kernel */
|
||||
ret = setup_irq(irq, &mfgptirq);
|
||||
|
||||
if (ret) {
|
||||
printk(KERN_ERR "mfgpt-timer: Unable to set up the interrupt.\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Set up the clock event */
|
||||
mfgpt_clockevent.mult = div_sc(MFGPT_HZ, NSEC_PER_SEC, 32);
|
||||
mfgpt_clockevent.min_delta_ns = clockevent_delta2ns(0xF, &mfgpt_clockevent);
|
||||
mfgpt_clockevent.max_delta_ns = clockevent_delta2ns(0xFFFE, &mfgpt_clockevent);
|
||||
|
||||
printk("mfgpt-timer: registering the MFGT timer as a clock event.\n");
|
||||
clockevents_register_device(&mfgpt_clockevent);
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
geode_mfgpt_release_irq(mfgpt_event_clock, MFGPT_CMP2, irq);
|
||||
printk(KERN_ERR "mfgpt-timer: Unable to set up the MFGPT clock source\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
#endif
|
||||
100
target/linux/olpc/files-2.6.23/arch/i386/kernel/ofw.c
Normal file
100
target/linux/olpc/files-2.6.23/arch/i386/kernel/ofw.c
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* ofw.c - Open Firmware client interface for 32-bit systems.
|
||||
* This code is intended to be portable to any 32-bit Open Firmware
|
||||
* implementation with a standard client interface that can be
|
||||
* called when Linux is running.
|
||||
*
|
||||
* Copyright (C) 2007 Mitch Bradley <wmb@firmworks.com>
|
||||
* Copyright (C) 2007 Andres Salomon <dilinger@debian.org>
|
||||
*/
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/module.h>
|
||||
#include <asm/ofw.h>
|
||||
|
||||
|
||||
int (*call_firmware)(int *);
|
||||
|
||||
static DEFINE_SPINLOCK(prom_lock);
|
||||
|
||||
#define MAXARGS 20
|
||||
|
||||
/*
|
||||
* The return value from ofw() in all cases is 0 if the attempt to call the
|
||||
* function succeeded, <0 otherwise. That return value is from the
|
||||
* gateway function only. Any results from the called function are returned
|
||||
* via output argument pointers.
|
||||
*
|
||||
* Here are call templates for all the standard OFW client services:
|
||||
*
|
||||
* ofw("test", 1, 1, namestr, &missing);
|
||||
* ofw("peer", 1, 1, phandle, &sibling_phandle);
|
||||
* ofw("child", 1, 1, phandle, &child_phandle);
|
||||
* ofw("parent", 1, 1, phandle, &parent_phandle);
|
||||
* ofw("instance_to_package", 1, 1, ihandle, &phandle);
|
||||
* ofw("getproplen", 2, 1, phandle, namestr, &proplen);
|
||||
* ofw("getprop", 4, 1, phandle, namestr, bufaddr, buflen, &size);
|
||||
* ofw("nextprop", 3, 1, phandle, previousstr, bufaddr, &flag);
|
||||
* ofw("setprop", 4, 1, phandle, namestr, bufaddr, len, &size);
|
||||
* ofw("canon", 3, 1, devspecstr, bufaddr, buflen, &length);
|
||||
* ofw("finddevice", 1, 1, devspecstr, &phandle);
|
||||
* ofw("instance-to-path", 3, 1, ihandle, bufaddr, buflen, &length);
|
||||
* ofw("package-to-path", 3, 1, phandle, bufaddr, buflen, &length);
|
||||
* ofw("call_method", numin, numout, in0, in1, ..., &out0, &out1, ...);
|
||||
* ofw("open", 1, 1, devspecstr, &ihandle);
|
||||
* ofw("close", 1, 0, ihandle);
|
||||
* ofw("read", 3, 1, ihandle, addr, len, &actual);
|
||||
* ofw("write", 3, 1, ihandle, addr, len, &actual);
|
||||
* ofw("seek", 3, 1, ihandle, pos_hi, pos_lo, &status);
|
||||
* ofw("claim", 3, 1, virtaddr, size, align, &baseaddr);
|
||||
* ofw("release", 2, 0, virtaddr, size);
|
||||
* ofw("boot", 1, 0, bootspecstr);
|
||||
* ofw("enter", 0, 0);
|
||||
* ofw("exit", 0, 0);
|
||||
* ofw("chain", 5, 0, virtaddr, size, entryaddr, argsaddr, len);
|
||||
* ofw("interpret", numin+1, numout+1, cmdstr, in0, ..., &catchres, &out0, ...);
|
||||
* ofw("set-callback", 1, 1, newfuncaddr, &oldfuncaddr);
|
||||
* ofw("set-symbol-lookup", 2, 0, symtovaladdr, valtosymaddr);
|
||||
* ofw("milliseconds", 0, 1, &ms);
|
||||
*/
|
||||
|
||||
int ofw(char *name, int numargs, int numres, ...)
|
||||
{
|
||||
va_list ap;
|
||||
int argarray[MAXARGS+3];
|
||||
int argnum = 3;
|
||||
int retval;
|
||||
int *intp;
|
||||
unsigned long flags;
|
||||
|
||||
if (!call_firmware)
|
||||
return -1;
|
||||
if ((numargs + numres) > MAXARGS)
|
||||
return -1; /* spit out an error? */
|
||||
|
||||
argarray[0] = (int) name;
|
||||
argarray[1] = numargs;
|
||||
argarray[2] = numres;
|
||||
|
||||
va_start(ap, numres);
|
||||
while (numargs) {
|
||||
argarray[argnum++] = va_arg(ap, int);
|
||||
numargs--;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&prom_lock, flags);
|
||||
retval = call_firmware(argarray);
|
||||
spin_unlock_irqrestore(&prom_lock, flags);
|
||||
|
||||
if (retval == 0) {
|
||||
while (numres) {
|
||||
intp = va_arg(ap, int *);
|
||||
*intp = argarray[argnum++];
|
||||
numres--;
|
||||
}
|
||||
}
|
||||
va_end(ap);
|
||||
return retval;
|
||||
}
|
||||
EXPORT_SYMBOL(ofw);
|
||||
785
target/linux/olpc/files-2.6.23/arch/i386/kernel/olpc-pm.c
Normal file
785
target/linux/olpc/files-2.6.23/arch/i386/kernel/olpc-pm.c
Normal file
@@ -0,0 +1,785 @@
|
||||
/* olpc-pm.c
|
||||
* © 2006 Red Hat, Inc.
|
||||
* Portions also copyright 2006 Advanced Micro Devices, Inc.
|
||||
* GPLv2
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/suspend.h>
|
||||
#include <linux/bootmem.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/rtc.h>
|
||||
#include <linux/mc146818rtc.h>
|
||||
#include <asm/io.h>
|
||||
|
||||
#include <asm/olpc.h>
|
||||
|
||||
/* A few words about accessing the ACPI and PM registers. Long story short,
|
||||
byte and word accesses of the ACPI and PM registers is broken. The only
|
||||
way to do it really correctly is to use dword accesses, which we do
|
||||
throughout this code. For more details, please consult Eratta 17 and 18
|
||||
here:
|
||||
|
||||
http://www.amd.com/files/connectivitysolutions/geode/geode_gx/34472D_CS5536_B1_specupdate.pdf
|
||||
*/
|
||||
|
||||
#define PM_IRQ 3
|
||||
|
||||
#define CS5536_PM_PWRBTN (1 << 8)
|
||||
#define CS5536_PM_RTC (1 << 10)
|
||||
|
||||
#define GPIO_WAKEUP_EC (1 << 31)
|
||||
#define GPIO_WAKEUP_LID (1 << 30)
|
||||
|
||||
#define PM_MODE_NORMAL 0
|
||||
#define PM_MODE_TEST 1
|
||||
#define PM_MODE_MAX 2
|
||||
|
||||
/* These, and the battery EC commands, should be in an olpc.h. */
|
||||
#define EC_WRITE_SCI_MASK 0x1b
|
||||
#define EC_READ_SCI_MASK 0x1c
|
||||
|
||||
extern void do_olpc_suspend_lowlevel(void);
|
||||
|
||||
static struct {
|
||||
unsigned long address;
|
||||
unsigned short segment;
|
||||
} ofw_bios_entry = { 0, __KERNEL_CS };
|
||||
|
||||
static int olpc_pm_mode = PM_MODE_NORMAL;
|
||||
static unsigned long acpi_base;
|
||||
static unsigned long pms_base;
|
||||
static int sci_irq;
|
||||
static int olpc_lid_flag;
|
||||
|
||||
static struct input_dev *pm_inputdev;
|
||||
static struct input_dev *lid_inputdev;
|
||||
static struct input_dev *ebook_inputdev;
|
||||
static struct pm_ops olpc_pm_ops;
|
||||
|
||||
static int gpio_wake_events = 0;
|
||||
static int ebook_state = -1;
|
||||
static u16 olpc_wakeup_mask = 0;
|
||||
|
||||
struct platform_device olpc_powerbutton_dev = {
|
||||
.name = "powerbutton",
|
||||
.id = -1,
|
||||
};
|
||||
|
||||
struct platform_device olpc_lid_dev = {
|
||||
.name = "lid",
|
||||
.id = -1,
|
||||
};
|
||||
|
||||
static void __init init_ebook_state(void)
|
||||
{
|
||||
if (olpc_ec_cmd(0x2a, NULL, 0, (unsigned char *) &ebook_state, 1)) {
|
||||
printk(KERN_WARNING "olpc-pm: failed to get EBOOK state!\n");
|
||||
ebook_state = 0;
|
||||
}
|
||||
ebook_state &= 1;
|
||||
|
||||
/* the input layer needs to know what value to default to as well */
|
||||
input_report_switch(ebook_inputdev, SW_TABLET_MODE, ebook_state);
|
||||
input_sync(ebook_inputdev);
|
||||
}
|
||||
|
||||
static void (*battery_callback)(unsigned long);
|
||||
static DEFINE_SPINLOCK(battery_callback_lock);
|
||||
|
||||
/* propagate_events is non-NULL if run from workqueue,
|
||||
NULL when called at init time to flush SCI queue */
|
||||
static void process_sci_queue(struct work_struct *propagate_events)
|
||||
{
|
||||
unsigned char data = 0;
|
||||
unsigned char battery_events = 0;
|
||||
int ret;
|
||||
|
||||
do {
|
||||
ret = olpc_ec_cmd(0x84, NULL, 0, &data, 1);
|
||||
if (!ret) {
|
||||
printk(KERN_DEBUG "olpc-pm: SCI 0x%x received\n",
|
||||
data);
|
||||
|
||||
switch (data) {
|
||||
case EC_SCI_SRC_EMPTY:
|
||||
case EC_SCI_SRC_GAME:
|
||||
case EC_SCI_SRC_WLAN:
|
||||
/* we ignore these for now */
|
||||
break;
|
||||
case EC_SCI_SRC_BATERR:
|
||||
printk(KERN_ERR "olpc-pm: Battery Management System detected an error! Remove turnip from battery slot.\n");
|
||||
case EC_SCI_SRC_BATSOC:
|
||||
case EC_SCI_SRC_BATTERY:
|
||||
case EC_SCI_SRC_ACPWR:
|
||||
battery_events |= data;
|
||||
break;
|
||||
case EC_SCI_SRC_EBOOK:
|
||||
ebook_state = !ebook_state;
|
||||
if (propagate_events) {
|
||||
input_report_switch(ebook_inputdev,
|
||||
SW_TABLET_MODE, ebook_state);
|
||||
input_sync(ebook_inputdev);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
printk(KERN_ERR "olpc-pm: Unknown SCI event 0x%x occurred!\n", data);
|
||||
}
|
||||
}
|
||||
} while (data && !ret);
|
||||
|
||||
if (battery_events && battery_callback && propagate_events) {
|
||||
void (*cbk)(unsigned long);
|
||||
|
||||
/* Older EC versions didn't distinguish between AC and battery
|
||||
events */
|
||||
if (olpc_platform_info.ecver < 0x45)
|
||||
battery_events = EC_SCI_SRC_BATTERY | EC_SCI_SRC_ACPWR;
|
||||
|
||||
spin_lock(&battery_callback_lock);
|
||||
cbk = battery_callback;
|
||||
spin_unlock(&battery_callback_lock);
|
||||
|
||||
cbk(battery_events);
|
||||
}
|
||||
|
||||
if (ret)
|
||||
printk(KERN_WARNING "Failed to clear SCI queue!\n");
|
||||
}
|
||||
|
||||
static DECLARE_WORK(sci_work, process_sci_queue);
|
||||
|
||||
void olpc_register_battery_callback(void (*f)(unsigned long))
|
||||
{
|
||||
spin_lock(&battery_callback_lock);
|
||||
battery_callback = f;
|
||||
spin_unlock(&battery_callback_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(olpc_register_battery_callback);
|
||||
|
||||
void olpc_deregister_battery_callback(void)
|
||||
{
|
||||
spin_lock(&battery_callback_lock);
|
||||
battery_callback = NULL;
|
||||
spin_unlock(&battery_callback_lock);
|
||||
cancel_work_sync(&sci_work);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(olpc_deregister_battery_callback);
|
||||
|
||||
|
||||
static int olpc_pm_interrupt(int irq, void *id)
|
||||
{
|
||||
uint32_t sts, gpe = 0;
|
||||
|
||||
sts = inl(acpi_base + PM1_STS);
|
||||
outl(sts | 0xFFFF, acpi_base + PM1_STS);
|
||||
|
||||
if (olpc_get_rev() >= OLPC_REV_B2) {
|
||||
gpe = inl(acpi_base + PM_GPE0_STS);
|
||||
outl(0xFFFFFFFF, acpi_base + PM_GPE0_STS);
|
||||
}
|
||||
|
||||
if (sts & CS5536_PM_PWRBTN) {
|
||||
input_report_key(pm_inputdev, KEY_POWER, 1);
|
||||
input_sync(pm_inputdev);
|
||||
printk(KERN_DEBUG "olpm-pm: PM_PWRBTN event received\n");
|
||||
/* Do we need to delay this (and hence schedule_work)? */
|
||||
input_report_key(pm_inputdev, KEY_POWER, 0);
|
||||
input_sync(pm_inputdev);
|
||||
}
|
||||
|
||||
if (gpe & GPIO_WAKEUP_EC) {
|
||||
geode_gpio_clear(OLPC_GPIO_ECSCI, GPIO_NEGATIVE_EDGE_STS);
|
||||
schedule_work(&sci_work);
|
||||
}
|
||||
|
||||
if (gpe & GPIO_WAKEUP_LID) {
|
||||
/* Disable events */
|
||||
geode_gpio_clear(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE);
|
||||
|
||||
/* Clear the edge */
|
||||
|
||||
if (olpc_lid_flag)
|
||||
geode_gpio_clear(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_EN);
|
||||
else
|
||||
geode_gpio_clear(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_EN);
|
||||
|
||||
/* Clear the status too */
|
||||
geode_gpio_set(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_STS);
|
||||
geode_gpio_set(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_STS);
|
||||
|
||||
/* The line is high when the LID is open, but SW_LID
|
||||
* should be high when the LID is closed, so we pass the old
|
||||
* value of olpc_lid_flag
|
||||
*/
|
||||
|
||||
input_report_switch(lid_inputdev, SW_LID, olpc_lid_flag);
|
||||
input_sync(lid_inputdev);
|
||||
|
||||
/* Swap the status */
|
||||
olpc_lid_flag = !olpc_lid_flag;
|
||||
|
||||
if (olpc_lid_flag)
|
||||
geode_gpio_set(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_EN);
|
||||
else
|
||||
geode_gpio_set(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_EN);
|
||||
|
||||
/* re-enable the event */
|
||||
geode_gpio_set(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/*
|
||||
* For now, only support STR. We also don't support suspending on
|
||||
* B1s, due to difficulties with the cafe FPGA.
|
||||
*/
|
||||
static int olpc_pm_state_valid(suspend_state_t pm_state)
|
||||
{
|
||||
if (pm_state == PM_SUSPEND_MEM && olpc_rev_after(OLPC_REV_B1))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* This is a catchall function for operations that just don't belong
|
||||
* anywhere else. Later we will evaluate if these belong in the
|
||||
* individual device drivers or the firmware.
|
||||
* If you add something to this function, please explain yourself with
|
||||
* a comment.
|
||||
*/
|
||||
|
||||
extern void gxfb_flatpanel_control(int state);
|
||||
|
||||
static u32 gpio_wakeup[2];
|
||||
static u64 irq_sources[4];
|
||||
static u64 mfgpt_irq_msr, mfgpt_nr_msr;
|
||||
|
||||
void olpc_fixup_wakeup(void)
|
||||
{
|
||||
u32 base = geode_gpio_base();
|
||||
int i;
|
||||
|
||||
/* This clears any pending events from the status register -
|
||||
* the firmware also does this, but its possible that it tries
|
||||
* it too early before the key has a chance to debounce
|
||||
*/
|
||||
|
||||
outl((CS5536_PM_PWRBTN << 16) | 0xFFFF, acpi_base + PM1_STS);
|
||||
|
||||
/* Enable the flatpanel sequencing as early as possible, because
|
||||
it takes ~64ms to resume. This probably belongs in the firmware */
|
||||
|
||||
//gxfb_flatpanel_control(1);
|
||||
|
||||
/* Restore the interrupt sources */
|
||||
wrmsrl(MSR_PIC_YSEL_LOW, irq_sources[0]);
|
||||
wrmsrl(MSR_PIC_ZSEL_LOW, irq_sources[1]);
|
||||
wrmsrl(MSR_PIC_YSEL_HIGH, irq_sources[2]);
|
||||
wrmsrl(MSR_PIC_ZSEL_HIGH, irq_sources[3]);
|
||||
|
||||
/* Restore the X and Y sources for GPIO */
|
||||
outl(gpio_wakeup[0], base + GPIO_MAP_X);
|
||||
outl(gpio_wakeup[1], base + GPIO_MAP_Y);
|
||||
|
||||
/* Resture the MFGPT MSRs */
|
||||
wrmsrl(MFGPT_IRQ_MSR, mfgpt_irq_msr);
|
||||
wrmsrl(MFGPT_NR_MSR, mfgpt_nr_msr);
|
||||
|
||||
for (i=0;i<2;i++) {
|
||||
/* tell the wireless module to restart USB communication */
|
||||
olpc_ec_cmd(0x24, NULL, 0, NULL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void olpc_fixup_sleep(void)
|
||||
{
|
||||
u32 base = geode_gpio_base();
|
||||
int i;
|
||||
|
||||
/* Save the X and Y sources for GPIO */
|
||||
gpio_wakeup[0] = inl(base + GPIO_MAP_X);
|
||||
gpio_wakeup[1] = inl(base + GPIO_MAP_Y);
|
||||
|
||||
/* Save the Y and Z unrestricted sources */
|
||||
|
||||
rdmsrl(MSR_PIC_YSEL_LOW, irq_sources[0]);
|
||||
rdmsrl(MSR_PIC_ZSEL_LOW, irq_sources[1]);
|
||||
rdmsrl(MSR_PIC_YSEL_HIGH, irq_sources[2]);
|
||||
rdmsrl(MSR_PIC_ZSEL_HIGH, irq_sources[3]);
|
||||
|
||||
/* Turn off the MFGPT timers on the way down */
|
||||
|
||||
for(i = 0; i < 8; i++) {
|
||||
u32 val = geode_mfgpt_read(i, MFGPT_REG_SETUP);
|
||||
|
||||
if (val & MFGPT_SETUP_SETUP) {
|
||||
val &= ~MFGPT_SETUP_CNTEN;
|
||||
geode_mfgpt_write(i, MFGPT_REG_SETUP, val);
|
||||
}
|
||||
}
|
||||
|
||||
/* Save the MFGPT MSRs */
|
||||
rdmsrl(MFGPT_IRQ_MSR, mfgpt_irq_msr);
|
||||
rdmsrl(MFGPT_NR_MSR, mfgpt_nr_msr);
|
||||
|
||||
if (device_may_wakeup(&olpc_powerbutton_dev.dev))
|
||||
olpc_wakeup_mask |= CS5536_PM_PWRBTN;
|
||||
else
|
||||
olpc_wakeup_mask &= ~(CS5536_PM_PWRBTN);
|
||||
|
||||
if (device_may_wakeup(&olpc_lid_dev.dev)) {
|
||||
geode_gpio_set(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE);
|
||||
gpio_wake_events |= GPIO_WAKEUP_LID;
|
||||
} else {
|
||||
geode_gpio_clear(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE);
|
||||
gpio_wake_events &= ~(GPIO_WAKEUP_LID);
|
||||
}
|
||||
}
|
||||
|
||||
static int olpc_pm_enter(suspend_state_t pm_state)
|
||||
{
|
||||
/* Only STR is supported */
|
||||
if (pm_state != PM_SUSPEND_MEM)
|
||||
return -EINVAL;
|
||||
|
||||
olpc_fixup_sleep();
|
||||
|
||||
/* Set the GPIO wakeup bits */
|
||||
outl(gpio_wake_events, acpi_base + PM_GPE0_EN);
|
||||
outl(0xFFFFFFFF, acpi_base + PM_GPE0_STS);
|
||||
|
||||
/* Save CPU state */
|
||||
do_olpc_suspend_lowlevel();
|
||||
|
||||
olpc_fixup_wakeup();
|
||||
|
||||
/* Restore the SCI wakeup events */
|
||||
outl(gpio_wake_events, acpi_base + PM_GPE0_EN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int asmlinkage olpc_do_sleep(u8 sleep_state)
|
||||
{
|
||||
void *pgd_addr = __va(read_cr3());
|
||||
printk(KERN_ERR "olpc_do_sleep!\n"); /* this needs to remain here so
|
||||
* that gcc doesn't optimize
|
||||
* away our __va! */
|
||||
/* FIXME: Set the SCI bits we want to wake up on here */
|
||||
|
||||
/* FIXME: Set any other SCI events that we might want here */
|
||||
|
||||
outl((olpc_wakeup_mask << 16) | 0xFFFF, acpi_base + PM1_STS);
|
||||
|
||||
/* If we are in test mode, then just return (simulate a successful
|
||||
suspend/resume). Otherwise, if we are doing the real thing,
|
||||
then go for the gusto */
|
||||
|
||||
if (olpc_pm_mode != PM_MODE_TEST) {
|
||||
__asm__ __volatile__("movl %0,%%eax" : : "r" (pgd_addr));
|
||||
__asm__("call *(%%edi); cld"
|
||||
: : "D" (&ofw_bios_entry));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* This code will slowly disappear as we fixup the issues in the BIOS */
|
||||
|
||||
static void __init olpc_fixup_bios(void)
|
||||
{
|
||||
unsigned long hi, lo;
|
||||
|
||||
if (olpc_has_vsa()) {
|
||||
/* The VSA aggressively sets up the ACPI and PM register for
|
||||
* trapping - its not enough to force these values in the BIOS -
|
||||
* they seem to be changed during PCI init as well.
|
||||
*/
|
||||
|
||||
/* Change the PM registers to decode to the DD */
|
||||
|
||||
rdmsr(0x510100e2, lo, hi);
|
||||
hi |= 0x80000000;
|
||||
wrmsr(0x510100e2, lo, hi);
|
||||
|
||||
/* Change the ACPI registers to decode to the DD */
|
||||
|
||||
rdmsr(0x510100e3, lo, hi);
|
||||
hi |= 0x80000000;
|
||||
wrmsr(0x510100e3, lo, hi);
|
||||
}
|
||||
|
||||
/* GPIO24 controls WORK_AUX */
|
||||
|
||||
geode_gpio_set(OLPC_GPIO_WORKAUX, GPIO_OUTPUT_ENABLE);
|
||||
geode_gpio_set(OLPC_GPIO_WORKAUX, GPIO_OUTPUT_AUX1);
|
||||
|
||||
if (olpc_get_rev() >= OLPC_REV_B2) {
|
||||
/* GPIO10 is connected to the thermal alarm */
|
||||
geode_gpio_set(OLPC_GPIO_THRM_ALRM, GPIO_INPUT_ENABLE);
|
||||
geode_gpio_set(OLPC_GPIO_THRM_ALRM, GPIO_INPUT_AUX1);
|
||||
|
||||
/* Set up to get LID events */
|
||||
geode_gpio_set(OLPC_GPIO_LID, GPIO_INPUT_ENABLE);
|
||||
|
||||
/* Clear edge detection and event enable for now */
|
||||
geode_gpio_clear(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE);
|
||||
geode_gpio_clear(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_EN);
|
||||
geode_gpio_clear(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_EN);
|
||||
|
||||
geode_gpio_set(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_STS);
|
||||
geode_gpio_set(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_STS);
|
||||
|
||||
/* Set the LID to cause an PME event on group 6 */
|
||||
geode_gpio_event_pme(OLPC_GPIO_LID, 6);
|
||||
|
||||
/* Set PME group 6 to fire the SCI interrupt */
|
||||
geode_gpio_set_irq(6, sci_irq);
|
||||
}
|
||||
|
||||
geode_gpio_set(OLPC_GPIO_ECSCI, GPIO_INPUT_ENABLE);
|
||||
|
||||
/* Clear pending events */
|
||||
|
||||
geode_gpio_set(OLPC_GPIO_ECSCI, GPIO_NEGATIVE_EDGE_STS);
|
||||
geode_gpio_set(OLPC_GPIO_ECSCI, GPIO_POSITIVE_EDGE_STS);
|
||||
|
||||
//geode_gpio_set(OLPC_GPIO_ECSCI, GPIO_NEGATIVE_EDGE_EN);
|
||||
geode_gpio_set(OLPC_GPIO_ECSCI, GPIO_EVENTS_ENABLE);
|
||||
|
||||
/* Set the SCI to cause a PME event on group 7 */
|
||||
geode_gpio_event_pme(OLPC_GPIO_ECSCI, 7);
|
||||
|
||||
/* And have group 6 also fire the SCI interrupt */
|
||||
geode_gpio_set_irq(7, sci_irq);
|
||||
}
|
||||
|
||||
/* This provides a control file for setting up testing of the
|
||||
power management system. For now, there is just one setting:
|
||||
"test" which means that we don't actually enter the power
|
||||
off routine.
|
||||
*/
|
||||
|
||||
static const char * const pm_states[] = {
|
||||
[PM_MODE_NORMAL] = "normal",
|
||||
[PM_MODE_TEST] = "test",
|
||||
};
|
||||
|
||||
extern struct mutex pm_mutex;
|
||||
extern struct kset power_subsys;
|
||||
|
||||
static ssize_t control_show(struct kset *s, char *buf)
|
||||
{
|
||||
return sprintf(buf, "%s\n", pm_states[olpc_pm_mode]);
|
||||
}
|
||||
|
||||
static ssize_t control_store(struct kset *s, const char *buf, size_t n)
|
||||
{
|
||||
int i, len;
|
||||
char *p;
|
||||
|
||||
p = memchr(buf, '\n', n);
|
||||
len = p ? p - buf : n;
|
||||
|
||||
/* Grab the mutex */
|
||||
mutex_lock(&pm_mutex);
|
||||
|
||||
for(i = 0; i < PM_MODE_MAX; i++) {
|
||||
if (!strncmp(buf, pm_states[i], len)) {
|
||||
olpc_pm_mode = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&pm_mutex);
|
||||
|
||||
return (i == PM_MODE_MAX) ? -EINVAL : n;
|
||||
}
|
||||
|
||||
static struct subsys_attribute control_attr = {
|
||||
.attr = {
|
||||
.name = "olpc-pm",
|
||||
.mode = 0644,
|
||||
},
|
||||
.show = control_show,
|
||||
.store = control_store,
|
||||
};
|
||||
|
||||
static struct attribute * olpc_attributes[] = {
|
||||
&control_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct attribute_group olpc_attrs = {
|
||||
.attrs = olpc_attributes,
|
||||
};
|
||||
|
||||
static int __init alloc_inputdevs(void)
|
||||
{
|
||||
int ret = -ENOMEM;
|
||||
|
||||
pm_inputdev = input_allocate_device();
|
||||
if (!pm_inputdev)
|
||||
goto err;
|
||||
|
||||
pm_inputdev->name = "OLPC PM";
|
||||
pm_inputdev->phys = "olpc_pm/input0";
|
||||
set_bit(EV_KEY, pm_inputdev->evbit);
|
||||
set_bit(KEY_POWER, pm_inputdev->keybit);
|
||||
|
||||
ret = input_register_device(pm_inputdev);
|
||||
if (ret) {
|
||||
printk(KERN_ERR "olpc-pm: failed to register PM input device: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
lid_inputdev = input_allocate_device();
|
||||
if (!lid_inputdev)
|
||||
goto err;
|
||||
|
||||
lid_inputdev->name = "OLPC lid switch";
|
||||
lid_inputdev->phys = "olpc_pm/input1";
|
||||
set_bit(EV_SW, lid_inputdev->evbit);
|
||||
set_bit(SW_LID, lid_inputdev->swbit);
|
||||
|
||||
ret = input_register_device(lid_inputdev);
|
||||
if (ret) {
|
||||
printk(KERN_ERR "olpc-pm: failed to register lid input device: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
ebook_inputdev = input_allocate_device();
|
||||
if (!ebook_inputdev)
|
||||
goto err;
|
||||
|
||||
ebook_inputdev->name = "OLPC ebook switch";
|
||||
ebook_inputdev->phys = "olpc_pm/input2";
|
||||
set_bit(EV_SW, ebook_inputdev->evbit);
|
||||
set_bit(SW_TABLET_MODE, ebook_inputdev->swbit);
|
||||
|
||||
ret = input_register_device(ebook_inputdev);
|
||||
if (ret) {
|
||||
printk(KERN_ERR "olpc-pm: failed to register ebook input device: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
return ret;
|
||||
err:
|
||||
if (ebook_inputdev) {
|
||||
input_unregister_device(ebook_inputdev);
|
||||
ebook_inputdev = NULL;
|
||||
}
|
||||
if (lid_inputdev) {
|
||||
input_unregister_device(lid_inputdev);
|
||||
lid_inputdev = NULL;
|
||||
}
|
||||
if (pm_inputdev) {
|
||||
input_unregister_device(pm_inputdev);
|
||||
pm_inputdev = NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __init olpc_pm_init(void)
|
||||
{
|
||||
uint32_t lo, hi;
|
||||
int ret;
|
||||
uint8_t ec_byte;
|
||||
|
||||
if (!machine_is_olpc())
|
||||
return -ENODEV;
|
||||
|
||||
acpi_base = geode_acpi_base();
|
||||
pms_base = geode_pms_base();
|
||||
|
||||
if (!acpi_base || !pms_base)
|
||||
return -ENODEV;
|
||||
|
||||
ret = alloc_inputdevs();
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
rdmsr(0x51400020, lo, hi);
|
||||
sci_irq = (lo >> 20) & 15;
|
||||
|
||||
if (sci_irq) {
|
||||
printk(KERN_INFO "SCI is mapped to IRQ %d\n", sci_irq);
|
||||
} else {
|
||||
/* Zero doesn't mean zero -- it means masked */
|
||||
printk(KERN_INFO "SCI unmapped. Mapping to IRQ 3\n");
|
||||
sci_irq = 3;
|
||||
lo |= 0x00300000;
|
||||
wrmsrl(0x51400020, lo);
|
||||
}
|
||||
|
||||
olpc_fixup_bios();
|
||||
|
||||
lo = inl(pms_base + PM_FSD);
|
||||
|
||||
/* Lock, enable failsafe, 4 seconds */
|
||||
outl(0xc001f400, pms_base + PM_FSD);
|
||||
|
||||
/* Here we set up the SCI events we're interested in during
|
||||
* real-time. We have no sleep button, and the RTC doesn't make
|
||||
* sense, so set up the power button
|
||||
*/
|
||||
|
||||
outl(inl(acpi_base) | ((CS5536_PM_PWRBTN) << 16), acpi_base);
|
||||
|
||||
if (olpc_get_rev() >= OLPC_REV_B2) {
|
||||
gpio_wake_events |= GPIO_WAKEUP_LID;
|
||||
|
||||
/* Get the current value of the GPIO, and set up the edges */
|
||||
olpc_lid_flag = geode_gpio_isset(OLPC_GPIO_LID, GPIO_READ_BACK);
|
||||
|
||||
/* Watch for the opposite edge */
|
||||
|
||||
if (olpc_lid_flag)
|
||||
geode_gpio_set(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_EN);
|
||||
else
|
||||
geode_gpio_set(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_EN);
|
||||
|
||||
/* Enable the event */
|
||||
geode_gpio_set(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE);
|
||||
}
|
||||
|
||||
/* Set up the mask for wakeups the EC will generate SCIs on */
|
||||
|
||||
ret = olpc_ec_cmd(EC_READ_SCI_MASK, NULL, 0, &ec_byte, 1);
|
||||
if (ret)
|
||||
printk(KERN_ERR "Error getting the EC SCI mask: %d\n", ret);
|
||||
|
||||
/* Disable battery 1% charge wakeups */
|
||||
ec_byte &= ~EC_SCI_SRC_BATSOC;
|
||||
|
||||
ret = olpc_ec_cmd(EC_WRITE_SCI_MASK, &ec_byte, 1, NULL, 0);
|
||||
if (ret)
|
||||
printk(KERN_ERR "Error setting the EC SCI mask: %d\n", ret);
|
||||
|
||||
/* Set up the EC SCI */
|
||||
|
||||
gpio_wake_events |= GPIO_WAKEUP_EC;
|
||||
|
||||
outl(gpio_wake_events, acpi_base + PM_GPE0_EN);
|
||||
outl(0xFFFFFFFF, acpi_base + PM_GPE0_STS);
|
||||
|
||||
/* Select level triggered in PIC */
|
||||
|
||||
if (sci_irq < 8) {
|
||||
lo = inb(0x4d0);
|
||||
lo |= 1 << sci_irq;
|
||||
outb(lo, 0x4d0);
|
||||
} else {
|
||||
lo = inb(0x4d1);
|
||||
lo |= 1 << (sci_irq - 8);
|
||||
outb(lo, 0x4d1);
|
||||
}
|
||||
/* Clear pending interrupt */
|
||||
outl(inl(acpi_base) | 0xFFFF, acpi_base);
|
||||
process_sci_queue(0); /* we just want to flush the queue here */
|
||||
init_ebook_state();
|
||||
|
||||
/* Enable the interrupt */
|
||||
|
||||
ret = request_irq(sci_irq, &olpc_pm_interrupt, 0, "SCI", &acpi_base);
|
||||
|
||||
if (ret) {
|
||||
printk(KERN_ERR "Error registering SCI: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ofw_bios_entry.address = 0xF0000 + PAGE_OFFSET;
|
||||
pm_set_ops(&olpc_pm_ops);
|
||||
|
||||
sysfs_create_group(&power_subsys.kobj, &olpc_attrs);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#if defined (CONFIG_RTC_DRV_CMOS) || defined (CONFIG_RTC_DRV_CMOS_MODULE)
|
||||
struct resource rtc_platform_resource[2] = {
|
||||
{
|
||||
.flags = IORESOURCE_IO,
|
||||
.start = RTC_PORT(0),
|
||||
.end = RTC_PORT(0) + RTC_IO_EXTENT
|
||||
},
|
||||
{
|
||||
.flags = IORESOURCE_IRQ,
|
||||
.start = 8,
|
||||
.end = 8,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
static void rtc_wake_on(struct device *dev)
|
||||
{
|
||||
olpc_wakeup_mask |= CS5536_PM_RTC;
|
||||
}
|
||||
|
||||
static void rtc_wake_off(struct device *dev)
|
||||
{
|
||||
olpc_wakeup_mask &= ~(CS5536_PM_RTC);
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
struct platform_device olpc_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 olpc_platform_init(void)
|
||||
{
|
||||
(void)platform_device_register(&olpc_rtc_device);
|
||||
device_init_wakeup(&olpc_rtc_device.dev, 1);
|
||||
|
||||
(void)platform_device_register(&olpc_powerbutton_dev);
|
||||
device_init_wakeup(&olpc_powerbutton_dev.dev, 1);
|
||||
|
||||
(void)platform_device_register(&olpc_lid_dev);
|
||||
device_init_wakeup(&olpc_lid_dev.dev, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
arch_initcall(olpc_platform_init);
|
||||
#endif /* CONFIG_RTC_DRV_CMOS */
|
||||
|
||||
static void olpc_pm_exit(void)
|
||||
{
|
||||
/* Clear any pending events, and disable them */
|
||||
outl(0xFFFF, acpi_base+2);
|
||||
|
||||
free_irq(sci_irq, &acpi_base);
|
||||
input_unregister_device(pm_inputdev);
|
||||
input_unregister_device(lid_inputdev);
|
||||
input_unregister_device(ebook_inputdev);
|
||||
}
|
||||
|
||||
static struct pm_ops olpc_pm_ops = {
|
||||
.valid = olpc_pm_state_valid,
|
||||
.enter = olpc_pm_enter,
|
||||
};
|
||||
|
||||
module_init(olpc_pm_init);
|
||||
module_exit(olpc_pm_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
|
||||
MODULE_DESCRIPTION("AMD Geode power management for OLPC CL1");
|
||||
39
target/linux/olpc/files-2.6.23/arch/i386/kernel/olpc-sleep.S
Normal file
39
target/linux/olpc/files-2.6.23/arch/i386/kernel/olpc-sleep.S
Normal file
@@ -0,0 +1,39 @@
|
||||
.text
|
||||
|
||||
ENTRY(olpc_sleep_asm)
|
||||
olpc_sleep:
|
||||
;; Get the value of PM1_CNT and store it off
|
||||
|
||||
add 08h, ax
|
||||
mov bx,dx
|
||||
in dx,eax
|
||||
or 2000h, ax
|
||||
mov ax,di
|
||||
|
||||
;; flush the cache
|
||||
wbinvd
|
||||
|
||||
;; GX2 must disable refresh before going into self-refresh
|
||||
mov 2000000180xh, ecx
|
||||
rdmsr
|
||||
mov eax, esi
|
||||
and 0FF0000FFh, eax
|
||||
wrmsr
|
||||
|
||||
;; Now, put the memory into self refresh
|
||||
mov 2004, cx
|
||||
xor edx, edx
|
||||
xor eax, eax
|
||||
mov 04h, al
|
||||
wrmsr
|
||||
|
||||
;; Thats all she wrote - time to go to sleep
|
||||
|
||||
mov bx, dx
|
||||
movzx di, eax
|
||||
out eax, dx
|
||||
|
||||
;;
|
||||
|
||||
|
||||
|
||||
122
target/linux/olpc/files-2.6.23/arch/i386/kernel/olpc-wakeup.S
Normal file
122
target/linux/olpc/files-2.6.23/arch/i386/kernel/olpc-wakeup.S
Normal file
@@ -0,0 +1,122 @@
|
||||
.text
|
||||
#include <linux/linkage.h>
|
||||
#include <asm/segment.h>
|
||||
#include <asm/page.h>
|
||||
|
||||
ALIGN
|
||||
.align 4096
|
||||
|
||||
wakeup_start:
|
||||
# jmp wakeup_start
|
||||
|
||||
cli
|
||||
cld
|
||||
|
||||
# Clear any dangerous flags
|
||||
|
||||
pushl $0
|
||||
popfl
|
||||
|
||||
# Set up %cr3
|
||||
movl $swsusp_pg_dir - __PAGE_OFFSET, %eax
|
||||
movl %eax, %cr3
|
||||
|
||||
movl saved_cr4, %eax
|
||||
movl %eax, %cr4
|
||||
|
||||
movl saved_cr0, %eax
|
||||
movl %eax, %cr0
|
||||
|
||||
jmp 1f
|
||||
1:
|
||||
ljmpl $__KERNEL_CS,$wakeup_return
|
||||
|
||||
|
||||
.org 0x1000
|
||||
|
||||
wakeup_return:
|
||||
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 olpc_do_sleep
|
||||
|
||||
jmp wakeup_start
|
||||
.p2align 4,,7
|
||||
ret_point:
|
||||
movl saved_context_esp, %esp
|
||||
|
||||
call restore_registers
|
||||
call restore_processor_state
|
||||
ret
|
||||
|
||||
.data
|
||||
ALIGN
|
||||
|
||||
saved_gdt: .long 0,0
|
||||
saved_idt: .long 0,0
|
||||
saved_ldt: .long 0
|
||||
saved_cr4: .long 0
|
||||
saved_cr0: .long 0
|
||||
|
||||
320
target/linux/olpc/files-2.6.23/arch/i386/kernel/olpc.c
Normal file
320
target/linux/olpc/files-2.6.23/arch/i386/kernel/olpc.c
Normal file
@@ -0,0 +1,320 @@
|
||||
/* Support for the OLPC DCON and OLPC EC access
|
||||
* 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/autoconf.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/mc146818rtc.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
#include <asm/olpc.h>
|
||||
#include <asm/ofw.h>
|
||||
|
||||
/* This is our new multi-purpose structure used to contain the
|
||||
* information about the platform that we detect
|
||||
*/
|
||||
|
||||
struct olpc_platform_t olpc_platform_info;
|
||||
EXPORT_SYMBOL_GPL(olpc_platform_info);
|
||||
|
||||
/*********************************************************************
|
||||
* EC locking and access
|
||||
*********************************************************************/
|
||||
|
||||
static DEFINE_SPINLOCK(ec_lock);
|
||||
|
||||
/* what the timeout *should* be (in ms) */
|
||||
#define EC_BASE_TIMEOUT 20
|
||||
|
||||
/* the timeout that bugs in the EC might force us to actually use */
|
||||
static int ec_timeout = EC_BASE_TIMEOUT;
|
||||
|
||||
static int __init olpc_ec_timeout_set(char *str)
|
||||
{
|
||||
if (get_option(&str, &ec_timeout) != 1) {
|
||||
ec_timeout = EC_BASE_TIMEOUT;
|
||||
printk(KERN_ERR "olpc-ec: invalid argument to "
|
||||
"'olpc_ec_timeout=', ignoring!\n");
|
||||
}
|
||||
printk(KERN_DEBUG "olpc-ec: using %d ms delay for EC commands.\n",
|
||||
ec_timeout);
|
||||
return 1;
|
||||
}
|
||||
__setup("olpc_ec_timeout=", olpc_ec_timeout_set);
|
||||
|
||||
/*
|
||||
* These *bf_status functions return whether the buffers are full or not.
|
||||
*/
|
||||
|
||||
static inline unsigned int ibf_status(unsigned int port)
|
||||
{
|
||||
return inb(port) & 0x02;
|
||||
}
|
||||
|
||||
static inline unsigned int obf_status(unsigned int port)
|
||||
{
|
||||
return inb(port) & 0x01;
|
||||
}
|
||||
|
||||
#define wait_on_ibf(p, d) __wait_on_ibf(__LINE__, (p), (d))
|
||||
static int __wait_on_ibf(unsigned int line, unsigned int port, int desired)
|
||||
{
|
||||
unsigned int timeo;
|
||||
int state = ibf_status(port);
|
||||
|
||||
for (timeo = ec_timeout; state != desired && timeo; timeo--) {
|
||||
mdelay(1);
|
||||
state = ibf_status(port);
|
||||
}
|
||||
|
||||
if ((state == desired) && (ec_timeout > EC_BASE_TIMEOUT) &&
|
||||
timeo < (ec_timeout - EC_BASE_TIMEOUT)) {
|
||||
printk(KERN_WARNING "olpc-ec: waited %u ms for IBF (%d)!\n",
|
||||
EC_BASE_TIMEOUT-timeo, line);
|
||||
}
|
||||
|
||||
return !(state == desired);
|
||||
}
|
||||
|
||||
#define wait_on_obf(p, d) __wait_on_obf(__LINE__, (p), (d))
|
||||
static int __wait_on_obf(unsigned int line, unsigned int port, int desired)
|
||||
{
|
||||
unsigned int timeo;
|
||||
int state = obf_status(port);
|
||||
|
||||
for (timeo = ec_timeout; state != desired && timeo; timeo--) {
|
||||
mdelay(1);
|
||||
state = obf_status(port);
|
||||
}
|
||||
|
||||
if ((state == desired) && (ec_timeout > EC_BASE_TIMEOUT) &&
|
||||
timeo < (ec_timeout - EC_BASE_TIMEOUT)) {
|
||||
printk(KERN_WARNING "olpc-ec: waited %u ms for OBF (%d)!\n",
|
||||
EC_BASE_TIMEOUT-timeo, line);
|
||||
}
|
||||
|
||||
return !(state == desired);
|
||||
}
|
||||
|
||||
int olpc_ec_cmd(unsigned char cmd, unsigned char *inbuf, size_t inlen,
|
||||
unsigned char *outbuf, size_t outlen)
|
||||
{
|
||||
unsigned long flags;
|
||||
int ret = -EIO;
|
||||
int i;
|
||||
|
||||
spin_lock_irqsave(&ec_lock, flags);
|
||||
|
||||
if (wait_on_ibf(0x6c, 0)) {
|
||||
printk(KERN_ERR "olpc-ec: timeout waiting for EC to "
|
||||
"quiesce!\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
restart:
|
||||
/*
|
||||
* Note that if we time out during any IBF checks, that's a failure;
|
||||
* we have to return. There's no way for the kernel to clear that.
|
||||
*
|
||||
* If we time out during an OBF check, we can restart the command;
|
||||
* reissuing it will clear the OBF flag, and we should be alright.
|
||||
* The OBF flag will sometimes misbehave due to what we believe
|
||||
* is a hardware quirk..
|
||||
*/
|
||||
printk(KERN_DEBUG "olpc-ec: running cmd 0x%x\n", cmd);
|
||||
outb(cmd, 0x6c);
|
||||
|
||||
if (wait_on_ibf(0x6c, 0)) {
|
||||
printk(KERN_ERR "olpc-ec: timeout waiting for EC to read "
|
||||
"command!\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (inbuf && inlen) {
|
||||
/* write data to EC */
|
||||
for (i = 0; i < inlen; i++) {
|
||||
if (wait_on_ibf(0x6c, 0)) {
|
||||
printk(KERN_ERR "olpc-ec: timeout waiting for"
|
||||
" EC accept data!\n");
|
||||
goto err;
|
||||
}
|
||||
printk(KERN_DEBUG "olpc-ec: sending cmd arg 0x%x\n",
|
||||
inbuf[i]);
|
||||
outb(inbuf[i], 0x68);
|
||||
}
|
||||
}
|
||||
if (outbuf && outlen) {
|
||||
/* read data from EC */
|
||||
for (i = 0; i < outlen; i++) {
|
||||
if (wait_on_obf(0x6c, 1)) {
|
||||
printk(KERN_ERR "olpc-ec: timeout waiting for"
|
||||
" EC to provide data!\n");
|
||||
goto restart;
|
||||
}
|
||||
outbuf[i] = inb(0x68);
|
||||
printk(KERN_DEBUG "olpc-ec: received 0x%x\n",
|
||||
outbuf[i]);
|
||||
}
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
err:
|
||||
spin_unlock_irqrestore(&ec_lock, flags);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(olpc_ec_cmd);
|
||||
|
||||
/*********************************************************************
|
||||
* DCON stuff
|
||||
*********************************************************************/
|
||||
|
||||
static void olpc_power_off(void)
|
||||
{
|
||||
printk(KERN_INFO "OLPC power off sequence...\n");
|
||||
outb(0xff, 0x381);
|
||||
outb(0x14, 0x382);
|
||||
outb(0x01, 0x383);
|
||||
outb(0xff, 0x381);
|
||||
outb(0x14, 0x382);
|
||||
outb(0x00, 0x383);
|
||||
}
|
||||
|
||||
static void __init
|
||||
ec_detect(void)
|
||||
{
|
||||
olpc_ec_cmd(0x08, NULL, 0, (unsigned char *) &olpc_platform_info.ecver, 1);
|
||||
}
|
||||
|
||||
/* Check to see if this version of the OLPC board has VSA built
|
||||
* in, and set a flag
|
||||
*/
|
||||
|
||||
static void __init vsa_detect(void)
|
||||
{
|
||||
u16 rev;
|
||||
|
||||
outw(0xFC53, 0xAC1C);
|
||||
outw(0x0003, 0xAC1C);
|
||||
|
||||
rev = inw(0xAC1E);
|
||||
|
||||
if (rev == 0x4132)
|
||||
olpc_platform_info.flags |= OLPC_F_VSA;
|
||||
}
|
||||
|
||||
/* Map OFW revisions to what OLPC_REV_* */
|
||||
static const char __initdata *olpc_boardrev_str[] = {
|
||||
"A1",
|
||||
"preB1",
|
||||
"B1",
|
||||
"preB2",
|
||||
"B2",
|
||||
"preB3",
|
||||
"B3",
|
||||
"B4",
|
||||
"C1",
|
||||
"R1",
|
||||
};
|
||||
|
||||
static void __init platform_detect(char *revision, size_t len)
|
||||
{
|
||||
size_t propsize;
|
||||
int i;
|
||||
|
||||
BUG_ON(ARRAY_SIZE(olpc_boardrev_str) != OLPC_REV_UNKNOWN);
|
||||
|
||||
if (ofw("getprop", 4, 1, NULL, "model", revision, len, &propsize)) {
|
||||
printk(KERN_ERR "ofw: getprop call failed!\n");
|
||||
goto failed;
|
||||
}
|
||||
if (len < propsize) {
|
||||
printk(KERN_ERR "ofw: revision string is too long!\n");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
for (i=0; i < ARRAY_SIZE(olpc_boardrev_str); i++) {
|
||||
if (strcmp(revision, olpc_boardrev_str[i]) == 0) {
|
||||
olpc_platform_info.boardrev = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
failed:
|
||||
strncpy(revision, "Unknown", len);
|
||||
olpc_platform_info.boardrev = OLPC_REV_UNKNOWN;
|
||||
}
|
||||
|
||||
static int olpc_dcon_present = -1;
|
||||
module_param(olpc_dcon_present, int, 0444);
|
||||
|
||||
/* REV_A CMOS map:
|
||||
* bit 440; DCON present bit
|
||||
*/
|
||||
|
||||
#define OLPC_CMOS_DCON_OFFSET (440 / 8)
|
||||
#define OLPC_CMOS_DCON_MASK 0x01
|
||||
|
||||
static int __init olpc_init(void)
|
||||
{
|
||||
unsigned char *romsig;
|
||||
char revision[10];
|
||||
|
||||
spin_lock_init(&ec_lock);
|
||||
|
||||
romsig = ioremap(0xffffffc0, 16);
|
||||
|
||||
if (!romsig)
|
||||
return 0;
|
||||
|
||||
if (strncmp(romsig, "CL1 Q", 7))
|
||||
goto unmap;
|
||||
if (strncmp(romsig+6, romsig+13, 3)) {
|
||||
printk(KERN_INFO "OLPC BIOS signature looks invalid. Assuming not OLPC\n");
|
||||
goto unmap;
|
||||
}
|
||||
printk(KERN_INFO "OLPC board with OpenFirmware: %.16s\n", romsig);
|
||||
|
||||
olpc_platform_info.flags |= OLPC_F_PRESENT;
|
||||
|
||||
pm_power_off = olpc_power_off;
|
||||
|
||||
/* Get the platform revision */
|
||||
platform_detect(revision, sizeof(revision));
|
||||
|
||||
/* If olpc_dcon_present isn't set by the command line, then
|
||||
* "detect" it
|
||||
*/
|
||||
|
||||
if (olpc_dcon_present == -1) {
|
||||
/* B1 and greater always has a DCON */
|
||||
if (olpc_platform_info.boardrev >= OLPC_REV_B1 &&
|
||||
olpc_platform_info.boardrev < OLPC_REV_UNKNOWN)
|
||||
olpc_dcon_present = 1;
|
||||
}
|
||||
|
||||
if (olpc_dcon_present)
|
||||
olpc_platform_info.flags |= OLPC_F_DCON;
|
||||
|
||||
/* Get the EC revision */
|
||||
ec_detect();
|
||||
|
||||
/* Check to see if the VSA exists */
|
||||
vsa_detect();
|
||||
|
||||
printk(KERN_INFO "OLPC board revision: %s (EC=%x)\n", revision,
|
||||
olpc_platform_info.ecver);
|
||||
|
||||
unmap:
|
||||
iounmap(romsig);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
postcore_initcall(olpc_init);
|
||||
478
target/linux/olpc/files-2.6.23/arch/i386/kernel/prom.c
Normal file
478
target/linux/olpc/files-2.6.23/arch/i386/kernel/prom.c
Normal file
@@ -0,0 +1,478 @@
|
||||
/*
|
||||
* Procedures for creating, accessing and interpreting the device tree.
|
||||
*
|
||||
* Paul Mackerras August 1996.
|
||||
* Copyright (C) 1996-2005 Paul Mackerras.
|
||||
*
|
||||
* Adapted for 64bit PowerPC by Dave Engebretsen and Peter Bergner.
|
||||
* {engebret|bergner}@us.ibm.com
|
||||
*
|
||||
* Adapted for sparc64 by David S. Miller davem@davemloft.net
|
||||
*
|
||||
* Adapter for i386/OLPC by Andres Salomon <dilinger@debian.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version
|
||||
* 2 of the License, or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/bootmem.h>
|
||||
#include <linux/module.h>
|
||||
#include <asm/prom.h>
|
||||
#include <asm/ofw.h>
|
||||
|
||||
/*
|
||||
* XXX: This is very much a stub; right now we're keeping 2 device trees
|
||||
* in memory (one for promfs, and one here). That will not remain
|
||||
* for long!
|
||||
*/
|
||||
|
||||
static struct device_node *allnodes;
|
||||
|
||||
/* use when traversing tree through the allnext, child, sibling,
|
||||
* or parent members of struct device_node.
|
||||
*/
|
||||
static DEFINE_RWLOCK(devtree_lock);
|
||||
|
||||
int of_device_is_compatible(const struct device_node *device,
|
||||
const char *compat)
|
||||
{
|
||||
const char* cp;
|
||||
int cplen, l;
|
||||
|
||||
cp = of_get_property(device, "compatible", &cplen);
|
||||
if (cp == NULL)
|
||||
return 0;
|
||||
while (cplen > 0) {
|
||||
if (strncmp(cp, compat, strlen(compat)) == 0)
|
||||
return 1;
|
||||
l = strlen(cp) + 1;
|
||||
cp += l;
|
||||
cplen -= l;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(of_device_is_compatible);
|
||||
|
||||
struct device_node *of_get_parent(const struct device_node *node)
|
||||
{
|
||||
struct device_node *np;
|
||||
|
||||
if (!node)
|
||||
return NULL;
|
||||
|
||||
np = node->parent;
|
||||
|
||||
return np;
|
||||
}
|
||||
EXPORT_SYMBOL(of_get_parent);
|
||||
|
||||
struct device_node *of_get_next_child(const struct device_node *node,
|
||||
struct device_node *prev)
|
||||
{
|
||||
struct device_node *next;
|
||||
|
||||
next = prev ? prev->sibling : node->child;
|
||||
for (; next != 0; next = next->sibling) {
|
||||
break;
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
EXPORT_SYMBOL(of_get_next_child);
|
||||
|
||||
struct device_node *of_find_node_by_path(const char *path)
|
||||
{
|
||||
struct device_node *np = allnodes;
|
||||
|
||||
for (; np != 0; np = np->allnext) {
|
||||
if (np->full_name != 0 && strcmp(np->full_name, path) == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
return np;
|
||||
}
|
||||
EXPORT_SYMBOL(of_find_node_by_path);
|
||||
|
||||
struct device_node *of_find_node_by_phandle(phandle handle)
|
||||
{
|
||||
struct device_node *np;
|
||||
|
||||
for (np = allnodes; np != 0; np = np->allnext)
|
||||
if (np->node == handle)
|
||||
break;
|
||||
|
||||
return np;
|
||||
}
|
||||
EXPORT_SYMBOL(of_find_node_by_phandle);
|
||||
|
||||
struct device_node *of_find_node_by_name(struct device_node *from,
|
||||
const char *name)
|
||||
{
|
||||
struct device_node *np;
|
||||
|
||||
np = from ? from->allnext : allnodes;
|
||||
for (; np != NULL; np = np->allnext)
|
||||
if (np->name != NULL && strcmp(np->name, name) == 0)
|
||||
break;
|
||||
|
||||
return np;
|
||||
}
|
||||
EXPORT_SYMBOL(of_find_node_by_name);
|
||||
|
||||
struct device_node *of_find_node_by_type(struct device_node *from,
|
||||
const char *type)
|
||||
{
|
||||
struct device_node *np;
|
||||
|
||||
np = from ? from->allnext : allnodes;
|
||||
for (; np != 0; np = np->allnext)
|
||||
if (np->type != 0 && strcmp(np->type, type) == 0)
|
||||
break;
|
||||
|
||||
return np;
|
||||
}
|
||||
EXPORT_SYMBOL(of_find_node_by_type);
|
||||
|
||||
struct device_node *of_find_compatible_node(struct device_node *from,
|
||||
const char *type, const char *compatible)
|
||||
{
|
||||
struct device_node *np;
|
||||
|
||||
np = from ? from->allnext : allnodes;
|
||||
for (; np != 0; np = np->allnext) {
|
||||
if (type != NULL
|
||||
&& !(np->type != 0 && strcmp(np->type, type) == 0))
|
||||
continue;
|
||||
if (of_device_is_compatible(np, compatible))
|
||||
break;
|
||||
}
|
||||
|
||||
return np;
|
||||
}
|
||||
EXPORT_SYMBOL(of_find_compatible_node);
|
||||
|
||||
struct property *of_find_property(const struct device_node *np,
|
||||
const char *name,
|
||||
int *lenp)
|
||||
{
|
||||
struct property *pp;
|
||||
|
||||
for (pp = np->properties; pp != 0; pp = pp->next) {
|
||||
if (strcasecmp(pp->name, name) == 0) {
|
||||
if (lenp != 0)
|
||||
*lenp = pp->length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return pp;
|
||||
}
|
||||
EXPORT_SYMBOL(of_find_property);
|
||||
|
||||
/*
|
||||
* Find a property with a given name for a given node
|
||||
* and return the value.
|
||||
*/
|
||||
const void *of_get_property(const struct device_node *np, const char *name,
|
||||
int *lenp)
|
||||
{
|
||||
struct property *pp = of_find_property(np,name,lenp);
|
||||
return pp ? pp->value : NULL;
|
||||
}
|
||||
EXPORT_SYMBOL(of_get_property);
|
||||
|
||||
int of_getintprop_default(struct device_node *np, const char *name, int def)
|
||||
{
|
||||
struct property *prop;
|
||||
int len;
|
||||
|
||||
prop = of_find_property(np, name, &len);
|
||||
if (!prop || len != 4)
|
||||
return def;
|
||||
|
||||
return *(int *) prop->value;
|
||||
}
|
||||
EXPORT_SYMBOL(of_getintprop_default);
|
||||
|
||||
int of_n_addr_cells(struct device_node *np)
|
||||
{
|
||||
const int* ip;
|
||||
do {
|
||||
if (np->parent)
|
||||
np = np->parent;
|
||||
ip = of_get_property(np, "#address-cells", NULL);
|
||||
if (ip != NULL)
|
||||
return *ip;
|
||||
} while (np->parent);
|
||||
/* No #address-cells property for the root node, default to 2 */
|
||||
return 2;
|
||||
}
|
||||
EXPORT_SYMBOL(of_n_addr_cells);
|
||||
|
||||
int of_n_size_cells(struct device_node *np)
|
||||
{
|
||||
const int* ip;
|
||||
do {
|
||||
if (np->parent)
|
||||
np = np->parent;
|
||||
ip = of_get_property(np, "#size-cells", NULL);
|
||||
if (ip != NULL)
|
||||
return *ip;
|
||||
} while (np->parent);
|
||||
/* No #size-cells property for the root node, default to 1 */
|
||||
return 1;
|
||||
}
|
||||
EXPORT_SYMBOL(of_n_size_cells);
|
||||
|
||||
int of_set_property(struct device_node *dp, const char *name, void *val, int len)
|
||||
{
|
||||
return -EIO;
|
||||
}
|
||||
EXPORT_SYMBOL(of_set_property);
|
||||
|
||||
static unsigned int prom_early_allocated;
|
||||
|
||||
static void * __init prom_early_alloc(unsigned long size)
|
||||
{
|
||||
void *ret;
|
||||
|
||||
ret = kmalloc(size, GFP_KERNEL);
|
||||
if (ret != NULL)
|
||||
memset(ret, 0, size);
|
||||
else
|
||||
printk(KERN_ERR "ACK! couldn't allocate prom memory!\n");
|
||||
|
||||
prom_early_allocated += size;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int is_root_node(const struct device_node *dp)
|
||||
{
|
||||
if (!dp)
|
||||
return 0;
|
||||
|
||||
return (dp->parent == NULL);
|
||||
}
|
||||
|
||||
static char * __init build_path_component(struct device_node *dp)
|
||||
{
|
||||
int pathlen;
|
||||
char *n, *i;
|
||||
|
||||
if (ofw("package-to-path", 3, 1, dp->node, NULL, 0, &pathlen)) {
|
||||
printk(KERN_ERR "PROM: unable to get path name from OFW!\n");
|
||||
return "ERROR";
|
||||
}
|
||||
n = prom_early_alloc(pathlen + 1);
|
||||
if (ofw("package-to-path", 3, 1, dp->node, n, pathlen+1, &pathlen))
|
||||
printk(KERN_ERR "PROM: unable to get path name from OFW\n");
|
||||
|
||||
if ((i = strrchr(n, '/')))
|
||||
n = ++i; /* we only want the file name */
|
||||
return n;
|
||||
}
|
||||
|
||||
static char * __init build_full_name(struct device_node *dp)
|
||||
{
|
||||
int len, ourlen, plen;
|
||||
char *n;
|
||||
|
||||
plen = strlen(dp->parent->full_name);
|
||||
ourlen = strlen(dp->path_component_name);
|
||||
len = ourlen + plen + 2;
|
||||
|
||||
n = prom_early_alloc(len);
|
||||
strcpy(n, dp->parent->full_name);
|
||||
if (!is_root_node(dp->parent)) {
|
||||
strcpy(n + plen, "/");
|
||||
plen++;
|
||||
}
|
||||
strcpy(n + plen, dp->path_component_name);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
static struct property * __init build_one_prop(phandle node, char *prev, char *special_name, void *special_val, int special_len)
|
||||
{
|
||||
static struct property *tmp = NULL;
|
||||
struct property *p;
|
||||
|
||||
if (tmp) {
|
||||
p = tmp;
|
||||
memset(p, 0, sizeof(*p) + 32);
|
||||
tmp = NULL;
|
||||
} else {
|
||||
p = prom_early_alloc(sizeof(struct property) + 32);
|
||||
}
|
||||
|
||||
p->name = (char *) (p + 1);
|
||||
if (special_name) {
|
||||
strcpy(p->name, special_name);
|
||||
p->length = special_len;
|
||||
p->value = prom_early_alloc(special_len);
|
||||
memcpy(p->value, special_val, special_len);
|
||||
} else {
|
||||
int fl;
|
||||
if (prev == NULL) {
|
||||
if (ofw("nextprop", 3, 1, node, "", p->name, &fl)) {
|
||||
printk(KERN_ERR "PROM: %s: nextprop failed!\n", __func__);
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
if (ofw("nextprop", 3, 1, node, prev, p->name, &fl)) {
|
||||
printk(KERN_ERR "PROM: %s: nextprop failed!\n", __func__);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if (strlen(p->name) == 0 || fl != 1) {
|
||||
tmp = p;
|
||||
return NULL;
|
||||
}
|
||||
if (ofw("getproplen", 2, 1, node, p->name, &p->length)) {
|
||||
printk(KERN_ERR "PROM: %s: getproplen failed!\n", __func__);
|
||||
return NULL;
|
||||
}
|
||||
if (p->length <= 0) {
|
||||
p->length = 0;
|
||||
} else {
|
||||
p->value = prom_early_alloc(p->length + 1);
|
||||
if (ofw("getprop", 4, 1, node, p->name, p->value, p->length, &p->length)) {
|
||||
printk(KERN_ERR "PROM: %s: getprop failed!\n", __func__);
|
||||
return NULL;
|
||||
}
|
||||
((unsigned char *)p->value)[p->length] = '\0';
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static struct property * __init build_prop_list(phandle node)
|
||||
{
|
||||
struct property *head, *tail;
|
||||
|
||||
head = tail = build_one_prop(node, NULL,
|
||||
".node", &node, sizeof(node));
|
||||
|
||||
tail->next = build_one_prop(node, NULL, NULL, NULL, 0);
|
||||
tail = tail->next;
|
||||
while(tail) {
|
||||
tail->next = build_one_prop(node, tail->name,
|
||||
NULL, NULL, 0);
|
||||
tail = tail->next;
|
||||
}
|
||||
|
||||
return head;
|
||||
}
|
||||
|
||||
static char * __init get_one_property(phandle node, const char *name)
|
||||
{
|
||||
char *buf = "<NULL>";
|
||||
int len;
|
||||
|
||||
if (ofw("getproplen", 2, 1, node, name, &len)) {
|
||||
printk(KERN_ERR "PROM: %s: getproplen failed!\n", __func__);
|
||||
return NULL;
|
||||
}
|
||||
if (len > 0) {
|
||||
buf = prom_early_alloc(len);
|
||||
if (ofw("getprop", 4, 1, node, name, buf, len, &len)) {
|
||||
printk(KERN_ERR "PROM: %s: getprop failed!\n", __func__);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static struct device_node * __init create_node(phandle node, struct device_node *parent)
|
||||
{
|
||||
struct device_node *dp;
|
||||
|
||||
if (!node)
|
||||
return NULL;
|
||||
|
||||
dp = prom_early_alloc(sizeof(*dp));
|
||||
dp->parent = parent;
|
||||
|
||||
kref_init(&dp->kref);
|
||||
|
||||
dp->name = get_one_property(node, "name");
|
||||
dp->type = get_one_property(node, "device_type");
|
||||
dp->node = node;
|
||||
|
||||
dp->properties = build_prop_list(node);
|
||||
|
||||
return dp;
|
||||
}
|
||||
|
||||
static struct device_node * __init build_tree(struct device_node *parent, phandle node, struct device_node ***nextp)
|
||||
{
|
||||
struct device_node *ret = NULL, *prev_sibling = NULL;
|
||||
struct device_node *dp;
|
||||
u32 child;
|
||||
|
||||
while (1) {
|
||||
dp = create_node(node, parent);
|
||||
if (!dp)
|
||||
break;
|
||||
|
||||
if (prev_sibling)
|
||||
prev_sibling->sibling = dp;
|
||||
|
||||
if (!ret)
|
||||
ret = dp;
|
||||
prev_sibling = dp;
|
||||
|
||||
*(*nextp) = dp;
|
||||
*nextp = &dp->allnext;
|
||||
|
||||
dp->path_component_name = build_path_component(dp);
|
||||
dp->full_name = build_full_name(dp);
|
||||
|
||||
if (ofw("child", 1, 1, node, &child)) {
|
||||
printk(KERN_ERR "PROM: %s: fetching child failed!\n", __func__);
|
||||
return NULL;
|
||||
}
|
||||
dp->child = build_tree(dp, child, nextp);
|
||||
|
||||
if (ofw("peer", 1, 1, node, &node)) {
|
||||
printk(KERN_ERR "PROM: %s: fetching peer failed!\n", __func__);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static phandle root_node;
|
||||
|
||||
void __init prom_build_devicetree(void)
|
||||
{
|
||||
struct device_node **nextp;
|
||||
u32 child;
|
||||
|
||||
if (ofw("peer", 1, 1, 0, &root_node)) {
|
||||
printk(KERN_ERR "PROM: unable to get root node from OFW!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
allnodes = create_node(root_node, NULL);
|
||||
allnodes->path_component_name = "";
|
||||
allnodes->full_name = "/";
|
||||
|
||||
nextp = &allnodes->allnext;
|
||||
if (ofw("child", 1, 1, allnodes->node, &child)) {
|
||||
printk(KERN_ERR "PROM: unable to get child node from OFW!\n");
|
||||
return;
|
||||
}
|
||||
allnodes->child = build_tree(allnodes, child, &nextp);
|
||||
printk("PROM: Built device tree with %u bytes of memory.\n",
|
||||
prom_early_allocated);
|
||||
}
|
||||
Reference in New Issue
Block a user