cleaning up olpc patch 1

SVN-Revision: 10060
This commit is contained in:
John Crispin 2007-12-30 17:38:08 +00:00
parent 6588e58b88
commit 42fc624cba
26 changed files with 11582 additions and 0 deletions

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View 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

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

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

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

View 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

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

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

View file

@ -0,0 +1,298 @@
/*
* olpcpci.c - Low-level PCI config space access for OLPC systems
* without the VSA PCI virtualization software.
*
* The AMD Geode chipset (GX2 processor, cs5536 I/O companion device)
* has some I/O functions (display, southbridge, sound, USB HCIs, etc)
* that more or less behave like PCI devices, but the hardware doesn't
* directly implement the PCI configuration space headers. AMD provides
* "VSA" (Virtual System Architecture) software that emulates PCI config
* space for these devices, by trapping I/O accesses to PCI config register
* (CF8/CFC) and running some code in System Management Mode interrupt state.
* On the OLPC platform, we don't want to use that VSA code because
* (a) it slows down suspend/resume, and (b) recompiling it requires special
* compilers that are hard to get. So instead of letting the complex VSA
* code simulate the PCI config registers for the on-chip devices, we
* just simulate them the easy way, by inserting the code into the
* pci_write_config and pci_read_config path. Most of the config registers
* are read-only anyway, so the bulk of the simulation is just table lookup.
*/
#include <linux/pci.h>
#include <linux/init.h>
#include <asm/olpc.h>
#include <asm/geode.h>
#include "pci.h"
static int is_lx;
/*
* In the tables below, the first two line (8 longwords) are the
* size masks that are used when the higher level PCI code determines
* the size of the region by writing ~0 to a base address register
* and reading back the result.
*
* The following lines are the values that are read during normal
* PCI config access cycles, i.e. not after just having written
* ~0 to a base address register.
*/
static const u32 lxnb_hdr[] = { /* dev 1 function 0 - devfn = 8 */
0x0 , 0x0 , 0x0 , 0x0 ,
0x0 , 0x0 , 0x0 , 0x0 ,
0x281022 , 0x2200005 , 0x6000021 , 0x80f808 , /* AMD Vendor ID */
0x0 , 0x0 , 0x0 , 0x0 , /* No virtual registers, hence no BAR for them */
0x0 , 0x0 , 0x0 , 0x28100b ,
0x0 , 0x0 , 0x0 , 0x0 ,
0x0 , 0x0 , 0x0 , 0x0 ,
0x0 , 0x0 , 0x0 , 0x0 ,
0x0 , 0x0 , 0x0 , 0x0 ,
};
static const u32 gxnb_hdr[] = { /* dev 1 function 0 - devfn = 8 */
0xfffffffd , 0x0 , 0x0 , 0x0 ,
0x0 , 0x0 , 0x0 , 0x0 ,
0x28100b , 0x2200005 , 0x6000021 , 0x80f808 , /* NSC Vendor ID */
0xac1d , 0x0 , 0x0 , 0x0 , /* I/O BAR - base of virtual registers */
0x0 , 0x0 , 0x0 , 0x28100b ,
0x0 , 0x0 , 0x0 , 0x0 ,
0x0 , 0x0 , 0x0 , 0x0 ,
0x0 , 0x0 , 0x0 , 0x0 ,
0x0 , 0x0 , 0x0 , 0x0 ,
};
static const u32 lxfb_hdr[] = { /* dev 1 function 1 - devfn = 9 */
0xff800008 , 0xffffc000 , 0xffffc000 , 0xffffc000 ,
0x0 , 0x0 , 0x0 , 0x0 ,
0x20811022 , 0x2200003 , 0x3000000 , 0x0 , /* AMD Vendor ID */
0xfd000000 , 0xfe000000 , 0xfe004000 , 0xfe008000 , /* FB, GP, VG, DF */
0xfe00c000 , 0x0 , 0x0 , 0x30100b , /* VIP */
0x0 , 0x0 , 0x0 , 0x10e , /* INTA, IRQ14 for graphics accel */
0x0 , 0x0 , 0x0 , 0x0 ,
0x3d0 , 0x3c0 , 0xa0000 , 0x0 , /* VG IO, VG IO, EGA FB, MONO FB */
0x0 , 0x0 , 0x0 , 0x0 ,
};
static const u32 gxfb_hdr[] = { /* dev 1 function 1 - devfn = 9 */
0xff800008 , 0xffffc000 , 0xffffc000 , 0xffffc000 ,
0x0 , 0x0 , 0x0 , 0x0 ,
0x30100b , 0x2200003 , 0x3000000 , 0x0 , /* NSC Vendor ID */
0xfd000000 , 0xfe000000 , 0xfe004000 , 0xfe008000 , /* FB, GP, VG, DF */
0x0 , 0x0 , 0x0 , 0x30100b ,
0x0 , 0x0 , 0x0 , 0x0 ,
0x0 , 0x0 , 0x0 , 0x0 ,
0x3d0 , 0x3c0 , 0xa0000 , 0x0 , /* VG IO, VG IO, EGA FB, MONO FB */
0x0 , 0x0 , 0x0 , 0x0 ,
};
static const u32 aes_hdr[] = { /* dev 1 function 2 - devfn = 0xa */
0xffffc000 , 0x0 , 0x0 , 0x0 ,
0x0 , 0x0 , 0x0 , 0x0 ,
0x20821022 , 0x2a00006 , 0x10100000 , 0x8 , /* NSC Vendor ID */
0xfe010000 , 0x0 , 0x0 , 0x0 , /* AES registers */
0x0 , 0x0 , 0x0 , 0x20821022 ,
0x0 , 0x0 , 0x0 , 0x0 ,
0x0 , 0x0 , 0x0 , 0x0 ,
0x0 , 0x0 , 0x0 , 0x0 ,
0x0 , 0x0 , 0x0 , 0x0 ,
};
static const u32 isa_hdr[] = { /* dev f function 0 - devfn = 78 */
0xfffffff9 , 0xffffff01 , 0xffffffc1 , 0xffffffe1 ,
0xffffff81 , 0xffffffc1 , 0x0 , 0x0 ,
0x20901022 , 0x2a00049 , 0x6010003 , 0x802000 ,
0x18b1 , 0x1001 , 0x1801 , 0x1881 , /* SMB-8 GPIO-256 MFGPT-64 IRQ-32 */
0x1401 , 0x1841 , 0x0 , 0x20901022 , /* PMS-128 ACPI-64 */
0x0 , 0x0 , 0x0 , 0x0 ,
0x0 , 0x0 , 0x0 , 0x0 ,
0x0 , 0x0 , 0x0 , 0xaa5b , /* interrupt steering */
0x0 , 0x0 , 0x0 , 0x0 ,
};
static const u32 ac97_hdr[] = { /* dev f function 3 - devfn = 7b */
0xffffff81 , 0x0 , 0x0 , 0x0 ,
0x0 , 0x0 , 0x0 , 0x0 ,
0x20931022 , 0x2a00041 , 0x4010001 , 0x0 ,
0x1481 , 0x0 , 0x0 , 0x0 , /* I/O BAR-128 */
0x0 , 0x0 , 0x0 , 0x20931022 ,
0x0 , 0x0 , 0x0 , 0x205 , /* IntB , IRQ5 */
0x0 , 0x0 , 0x0 , 0x0 ,
0x0 , 0x0 , 0x0 , 0x0 ,
0x0 , 0x0 , 0x0 , 0x0 ,
};
static const u32 ohci_hdr[] = { /* dev f function 4 - devfn = 7c */
0xfffff000 , 0x0 , 0x0 , 0x0 ,
0x0 , 0x0 , 0x0 , 0x0 ,
0x20941022 , 0x2300006 , 0xc031002 , 0x0 ,
0xfe01a000 , 0x0 , 0x0 , 0x0 , /* MEMBAR-1000 */
0x0 , 0x0 , 0x0 , 0x20941022 ,
0x0 , 0x40 , 0x0 , 0x40a , /* CapPtr INT-D, IRQ A */
0xc8020001 , 0x0 , 0x0 , 0x0 , /* Capabilities - 40 is R/O, 44 is mask 8103 (power control) */
0x0 , 0x0 , 0x0 , 0x0 ,
0x0 , 0x0 , 0x0 , 0x0 ,
};
static const u32 ehci_hdr[] = { /* dev f function 4 - devfn = 7d */
0xfffff000 , 0x0 , 0x0 , 0x0 ,
0x0 , 0x0 , 0x0 , 0x0 ,
0x20951022 , 0x2300006 , 0xc032002 , 0x0 ,
0xfe01b000 , 0x0 , 0x0 , 0x0 , /* MEMBAR-1000 */
0x0 , 0x0 , 0x0 , 0x20951022 ,
0x0 , 0x40 , 0x0 , 0x40a , /* CapPtr INT-D, IRQ A */
0xc8020001 , 0x0 , 0x0 , 0x0 , /* Capabilities - 40 is R/O, 44 is mask 8103 (power control) */
#if 0
0x1 , 0x40080000 , 0x0 , 0x0 , /* EECP - see section 2.1.7 of EHCI spec */
#endif
0x01000001 , 0x00000000 , 0x0 , 0x0 , /* EECP - see section 2.1.7 of EHCI spec */
0x2020 , 0x0 , 0x0 , 0x0 , /* (EHCI page 8) 60 SBRN (R/O), 61 FLADJ (R/W), PORTWAKECAP */
};
static u32 ff_loc = ~0;
static u32 zero_loc = 0;
static int bar_probing = 0; /* Set after a write of ~0 to a BAR */
#define NB_SLOT 0x1 /* Northbridge - GX chip - Device 1 */
#define SB_SLOT 0xf /* Southbridge - CS5536 chip - Device F */
#define SIMULATED(bus, devfn) (((bus) == 0) && ((PCI_SLOT(devfn) == NB_SLOT) || (PCI_SLOT(devfn) == SB_SLOT)))
static u32 *hdr_addr(const u32 *hdr, int reg)
{
u32 addr;
/*
* This is a little bit tricky. The header maps consist of
* 0x20 bytes of size masks, followed by 0x70 bytes of header data.
* In the normal case, when not probing a BAR's size, we want
* to access the header data, so we add 0x20 to the reg offset,
* thus skipping the size mask area.
* In the BAR probing case, we want to access the size mask for
* the BAR, so we subtract 0x10 (the config header offset for
* BAR0), and don't skip the size mask area.
*/
addr = (u32)hdr + reg + (bar_probing ? -0x10 : 0x20);
bar_probing = 0;
return (u32 *)addr;
}
static int pci_olpc_read(unsigned int seg, unsigned int bus,
unsigned int devfn, int reg, int len, u32 *value)
{
u32 *addr;
/* Use the hardware mechanism for non-simulated devices */
if (!SIMULATED(bus, devfn))
return pci_conf1_read(seg, bus, devfn, reg, len, value);
/*
* No device has config registers past 0x70, so we save table space
* by not storing entries for the nonexistent registers
*/
if (reg >= 0x70)
addr = &zero_loc;
else {
switch (devfn) {
case 0x8:
addr = hdr_addr(is_lx ? lxnb_hdr : gxnb_hdr, reg);
break;
case 0x9:
addr = hdr_addr(is_lx ? lxfb_hdr : gxfb_hdr, reg);
break;
case 0xa:
addr = is_lx ? hdr_addr(aes_hdr, reg) : &ff_loc;
break;
case 0x78:
addr = hdr_addr(isa_hdr, reg);
break;
case 0x7b:
addr = hdr_addr(ac97_hdr, reg);
break;
case 0x7c:
addr = hdr_addr(ohci_hdr, reg);
break;
case 0x7d:
addr = hdr_addr(ehci_hdr, reg);
break;
default:
addr = &ff_loc;
break;
}
}
switch (len) {
case 1:
*value = *(u8 *) addr;
break;
case 2:
*value = *(u16 *) addr;
break;
case 4:
*value = *addr;
break;
default:
BUG();
}
return 0;
}
static int pci_olpc_write(unsigned int seg, unsigned int bus,
unsigned int devfn, int reg, int len, u32 value)
{
/* Use the hardware mechanism for non-simulated devices */
if (!SIMULATED(bus, devfn))
return pci_conf1_write(seg, bus, devfn, reg, len, value);
/* XXX we may want to extend this to simulate EHCI power management */
/*
* Mostly we just discard writes, but if the write is a size probe
* (i.e. writing ~0 to a BAR), we remember it and arrange to return
* the appropriate size mask on the next read. This is cheating
* to some extent, because it depends on the fact that the next
* access after such a write will always be a read to the same BAR.
*/
if ((reg >= 0x10) && (reg < 0x2c)) {
/* Write is to a BAR */
if (value == ~0)
bar_probing = 1;
} else {
/*
* No warning on writes to ROM BAR, CMD, LATENCY_TIMER,
* CACHE_LINE_SIZE, or PM registers.
*/
if ((reg != 0x30) && (reg != 0x04) && (reg != 0x0d) &&
(reg != 0x0c) && (reg != 0x44))
printk(KERN_WARNING "OLPC PCI: Config write to devfn %x reg %x value %x\n", devfn, reg, value);
}
return 0;
}
static struct pci_raw_ops pci_olpc_conf = {
.read = pci_olpc_read,
.write = pci_olpc_write,
};
void __init pci_olpc_init(void)
{
if (!machine_is_olpc() || olpc_has_vsa())
return;
printk(KERN_INFO "PCI: Using configuration type OLPC\n");
raw_pci_ops = &pci_olpc_conf;
is_lx = is_geode_lx();
}

View file

@ -0,0 +1,554 @@
/*
* OLPC touchpad PS/2 mouse driver
*
* Copyright (c) 2006 One Laptop Per Child, inc.
* Authors Zephaniah E. Hull and Andres Salomon <dilinger@laptop.org>
*
* This driver is partly based on the ALPS driver, which is:
*
* Copyright (c) 2003 Neil Brown <neilb@cse.unsw.edu.au>
* Copyright (c) 2003-2005 Peter Osterlund <petero2@telia.com>
* Copyright (c) 2004 Dmitry Torokhov <dtor@mail.ru>
* Copyright (c) 2005 Vojtech Pavlik <vojtech@suse.cz>
*
* 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.
*/
/*
* The touchpad on the OLPC is fairly wide, with the entire area usable
* as a tablet ("PT mode"), and the center 1/3rd also usable as a touchpad
* ("GS mode").
*
* Earlier version of the device had simultaneous reporting; however, that
* was removed. Instead, the device now reports packets in one mode, and
* tells the driver when a mode switch needs to happen.
*/
#include <linux/input.h>
#include <linux/serio.h>
#include <linux/libps2.h>
#include <linux/delay.h>
#include <asm/olpc.h>
#include "psmouse.h"
#include "olpc.h"
static int tpdebug;
module_param(tpdebug, int, 0644);
#define OLPC_GS 1 /* The GS sensor. */
#define OLPC_PT 2 /* The PT sensor. */
static struct olpc_model_info olpc_model_data[] = {
{ { 0x67, 0x00, 0x00 }, OLPC_GS | OLPC_PT }, /* unknown ID */
{ { 0x67, 0x00, 0x0a }, OLPC_GS | OLPC_PT }, /* pre-B1 */
{ { 0x67, 0x00, 0x14 }, OLPC_GS }, /* B1.*/
{ { 0x67, 0x00, 0x28 }, OLPC_GS | OLPC_PT }, /* B2 */
{ { 0x67, 0x00, 0x3c }, OLPC_GS | OLPC_PT }, /* B2-2 */
{ { 0x67, 0x00, 0x50 }, OLPC_GS | OLPC_PT }, /* C1 */
};
#define OLPC_PKT_PT 0xcf
#define OLPC_PKT_GS 0xff
static int olpc_absolute_mode(struct psmouse *psmouse, int mode);
/*
* OLPC absolute Mode - single-mode format
*
* byte 0: 1 1 0 0 1 1 1 1
* byte 1: 0 x6 x5 x4 x3 x2 x1 x0
* byte 2(PT): 0 0 x9 x8 x7 ? pt-dsw gs-dsw
* byte 2(GS): 0 x10 x9 x8 x7 ? gs-dsw pt-dsw
* byte 3: 0 y9 y8 y7 1 0 swr swl
* byte 4: 0 y6 y5 y4 y3 y2 y1 y0
* byte 5: 0 z6 z5 z4 z3 z2 z1 z0
*
* ?'s are not defined in the protocol spec, may vary between models.
*
* swr/swl are the left/right buttons.
*
* pt-dsw/gs-dsw indicate that the pt/gs sensor is detecting a
* pen/finger
*/
static void olpc_process_packet_gspt(struct psmouse *psmouse)
{
struct olpc_data *priv = psmouse->private;
unsigned char *packet = psmouse->packet;
struct input_dev *dev = psmouse->dev;
struct input_dev *dev2 = priv->dev2;
int x, y, z, gs_down = 0, pt_down = 0, left, right;
struct timeval now_tv;
s64 now_ns;
left = packet[3] & 1;
right = packet[3] & 2;
x = packet[1] | ((packet[2] & 0x78) << 4);
y = packet[4] | ((packet[3] & 0x70) << 3);
z = packet[5];
if (psmouse->packet[0] == OLPC_PKT_GS) {
pt_down = !!(packet[2] & 1);
gs_down = !!(packet[2] & 2);
} else if (psmouse->packet[0] == OLPC_PKT_PT) {
gs_down = !!(packet[2] & 1);
pt_down = !!(packet[2] & 2);
}
/*
* XXX: Kludge.
* If it's been more than 30ms since the last packet,
* assume that there was a lift we were never told about.
*/
do_gettimeofday(&now_tv);
now_ns = timeval_to_ns (&now_tv);
if (now_ns >= priv->late) {
input_report_key(dev, BTN_TOUCH, 0);
input_report_key(dev, BTN_TOOL_PEN, 0);
input_report_key(dev2, BTN_TOUCH, 0);
input_report_key(dev2, BTN_TOOL_FINGER, 0);
input_sync(dev);
input_sync(dev2);
}
priv->late = now_ns + (30 * NSEC_PER_MSEC);
if (tpdebug) {
printk(KERN_DEBUG "%s %02x %02x %02x %02x %02x %02x\n",
__FUNCTION__, psmouse->packet[0], psmouse->packet[1],
psmouse->packet[2], psmouse->packet[3], psmouse->packet[4],
psmouse->packet[5]);
printk(KERN_DEBUG "l=%d r=%d p=%d g=%d x=%d y=%d z=%d\n",
left, right, pt_down, gs_down, x, y, z);
}
if (psmouse->packet[0] == OLPC_PKT_PT) {
input_report_key(dev, BTN_LEFT, left);
input_report_key(dev, BTN_RIGHT, right);
} else if (psmouse->packet[0] == OLPC_PKT_GS) {
input_report_key(dev, BTN_LEFT, left);
input_report_key(dev, BTN_RIGHT, right);
input_report_key(dev2, BTN_LEFT, left);
input_report_key(dev2, BTN_RIGHT, right);
}
input_report_key(dev, BTN_TOUCH, pt_down);
input_report_key(dev, BTN_TOOL_PEN, pt_down);
input_report_key(dev2, BTN_TOUCH, gs_down);
input_report_key(dev2, BTN_TOOL_FINGER, gs_down);
input_report_abs(dev2, ABS_PRESSURE, z);
if (psmouse->packet[0] == OLPC_PKT_PT && pt_down) {
input_report_abs(dev, ABS_X, x);
input_report_abs(dev, ABS_Y, y);
} else if (psmouse->packet[0] == OLPC_PKT_GS && gs_down) {
input_report_abs(dev2, ABS_X, x);
input_report_abs(dev2, ABS_Y, y);
}
input_sync(dev);
input_sync(dev2);
if (priv->pending_mode == OLPC_GS &&
psmouse->packet[0] == OLPC_PKT_PT && pt_down) {
priv->pending_mode = 0;
cancel_delayed_work(&priv->mode_switch);
}
if (priv->i->flags & (OLPC_PT|OLPC_GS)) {
int pending = 0;
if (psmouse->packet[0] == OLPC_PKT_PT && !pt_down)
pending = OLPC_GS;
else if (psmouse->packet[0] == OLPC_PKT_GS && pt_down)
pending = OLPC_PT;
if (priv->current_mode == pending) {
priv->pending_mode = 0;
pending = priv->current_mode;
}
else if (priv->pending_mode != pending) {
priv->pending_mode = pending;
if (tpdebug)
printk(KERN_WARNING "Scheduling mode switch to %s.\n",
pending == OLPC_GS ? "GS" : "PT");
/*
* Apply a de-bounce when switching from PT to GS, to allow for
* spurious PT-up packets.
*/
if (priv->pending_mode == OLPC_GS)
queue_delayed_work(kpsmoused_wq, &priv->mode_switch, msecs_to_jiffies(50));
else
queue_delayed_work(kpsmoused_wq, &priv->mode_switch, 0);
}
}
}
static psmouse_ret_t olpc_process_byte(struct psmouse *psmouse)
{
psmouse_ret_t ret = PSMOUSE_BAD_DATA;
if (psmouse->packet[0] != OLPC_PKT_PT &&
psmouse->packet[0] != OLPC_PKT_GS)
goto out;
/* Bytes 2 - 6 should have 0 in the highest bit */
if (psmouse->pktcnt >= 2 && psmouse->pktcnt <= 6 &&
(psmouse->packet[psmouse->pktcnt - 1] & 0x80))
goto out;
if (psmouse->pktcnt == 6) {
olpc_process_packet_gspt(psmouse);
ret = PSMOUSE_FULL_PACKET;
goto out;
}
ret = PSMOUSE_GOOD_DATA;
out:
if (ret != PSMOUSE_GOOD_DATA && ret != PSMOUSE_FULL_PACKET)
printk(KERN_DEBUG "%s: (%d) %02x %02x %02x %02x %02x %02x\n",
__FUNCTION__, psmouse->pktcnt, psmouse->packet[0],
psmouse->packet[1], psmouse->packet[2],
psmouse->packet[3], psmouse->packet[4],
psmouse->packet[5]);
return ret;
}
static struct olpc_model_info *olpc_get_model(struct psmouse *psmouse)
{
struct ps2dev *ps2dev = &psmouse->ps2dev;
unsigned char param[4];
int i;
/*
* Now try "E7 report". Allowed responses are in
* olpc_model_data[].signature
*/
if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21) ||
ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21) ||
ps2_command(ps2dev, NULL, PSMOUSE_CMD_SETSCALE21))
return NULL;
param[0] = param[1] = param[2] = 0xff;
if (ps2_command(ps2dev, param, PSMOUSE_CMD_GETINFO))
return NULL;
pr_debug("olpc.c(%d): E7 report: %2.2x %2.2x %2.2x",
__LINE__, param[0], param[1], param[2]);
for (i = 0; i < ARRAY_SIZE(olpc_model_data); i++) {
if (!memcmp(param, olpc_model_data[i].signature,
sizeof(olpc_model_data[i].signature))) {
printk(KERN_INFO __FILE__ ": OLPC touchpad revision 0x%x.\n", param[2]);
return olpc_model_data + i;
}
}
/*
* ALPS creates new IDs pretty frequently; rather than listing them
* all, just assume they support the defaults. We've set aside the
* first entry of olpc_model_data as the catch-all.
*/
if (!memcmp(param, olpc_model_data[0].signature, 2)) {
printk(KERN_INFO __FILE__ ": unknown ALPS revision %x, assuming default flags.\n", param[2]);
return &olpc_model_data[0];
}
return NULL;
}
static int olpc_find_mode(struct psmouse *psmouse)
{
struct olpc_data *priv = psmouse->private;
int mode = priv->i->flags;
if (mode & OLPC_GS)
mode = OLPC_GS;
else if (mode & OLPC_PT)
mode = OLPC_PT;
else
mode = -1;
return mode;
}
/*
* Touchpad should be disabled before calling this!
*/
static int olpc_new_mode(struct psmouse *psmouse, int mode)
{
struct ps2dev *ps2dev = &psmouse->ps2dev;
struct olpc_data *priv = psmouse->private;
unsigned char param;
int ret;
if (tpdebug)
printk(KERN_WARNING __FILE__ ": Switching to %d. [%lu]\n", mode, jiffies);
if ((ret = ps2_command(ps2dev, &param, 0x01F2)))
goto failed;
if ((ret = ps2_command(ps2dev, &param, 0x01F2)))
goto failed;
if ((ret = ps2_command(ps2dev, &param, 0x01F2)))
goto failed;
switch (mode) {
default:
printk(KERN_WARNING __FILE__ ": Invalid mode %d. Defaulting to OLPC_GS.\n", mode);
case OLPC_GS:
ret = ps2_command(ps2dev, NULL, 0xE6);
break;
case OLPC_PT:
ret = ps2_command(ps2dev, NULL, 0xE7);
break;
}
if (ret)
goto failed;
/* XXX: This is a bit hacky, make sure this isn't screwing stuff up. */
psmouse->pktcnt = psmouse->out_of_sync = 0;
psmouse->last = jiffies;
psmouse->state = PSMOUSE_ACTIVATED;
if ((ret = ps2_command(ps2dev, NULL, PSMOUSE_CMD_ENABLE)))
goto failed;
priv->current_mode = mode;
priv->pending_mode = 0;
if (tpdebug)
printk(KERN_WARNING __FILE__ ": Switched to mode %d successful.\n", mode);
failed:
if (ret)
printk(KERN_WARNING __FILE__ ": Mode switch to %d failed! (%d) [%lu]\n", mode, ret, jiffies);
return ret;
}
static int olpc_absolute_mode(struct psmouse *psmouse, int mode)
{
struct ps2dev *ps2dev = &psmouse->ps2dev;
/* Switch to 'Advanced mode.', four disables in a row. */
if (ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE) ||
ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE) ||
ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE) ||
ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE))
return -1;
return olpc_new_mode(psmouse, mode);
}
/*
* olpc_poll() - poll the touchpad for current motion packet.
* Used in resync.
* Note: We can't poll, so always return failure.
*/
static int olpc_poll(struct psmouse *psmouse)
{
return -1;
}
static int olpc_reconnect(struct psmouse *psmouse)
{
struct olpc_data *priv = psmouse->private;
int mode;
if (olpc_rev_after(OLPC_REV_B2))
if (psmouse->ps2dev.serio->dev.power.power_state.event != PM_EVENT_ON)
return 0;
psmouse_reset(psmouse);
if (!(priv->i = olpc_get_model(psmouse)))
return -1;
mode = olpc_find_mode(psmouse);
if (mode < 0)
return -1;
if (olpc_absolute_mode(psmouse, mode)) {
printk(KERN_ERR __FILE__ ": Failed to reenable absolute mode.\n");
return -1;
}
return 0;
}
static void olpc_disconnect(struct psmouse *psmouse)
{
struct olpc_data *priv = psmouse->private;
psmouse_reset(psmouse);
input_unregister_device(priv->dev2);
kfree(priv);
}
static void olpc_mode_switch(struct work_struct *w)
{
struct delayed_work *work = container_of(w, struct delayed_work, work);
struct olpc_data *priv = container_of(work, struct olpc_data, mode_switch);
struct psmouse *psmouse = priv->psmouse;
struct ps2dev *ps2dev = &psmouse->ps2dev;
int pending_mode, ret;
if (priv->pending_mode == priv->current_mode) {
priv->pending_mode = 0;
printk (KERN_DEBUG __FILE__ ": In switch_mode, no target mode.\n");
return;
}
if (tpdebug)
printk(KERN_WARNING __FILE__ ": Disable for switch to %d. [%lu]\n", priv->pending_mode, jiffies);
/* XXX: This is a bit hacky, make sure this isn't screwing stuff up. */
psmouse->state = PSMOUSE_INITIALIZING;
ret = ps2_command(ps2dev, NULL, PSMOUSE_CMD_DISABLE);
if (ret) {
/* XXX: if this ever fails, we need to do a full reset! */
printk(KERN_WARNING __FILE__ ": Disable failed for switch to %d. (%d) [%lu]\n", priv->pending_mode, ret, jiffies);
return;
}
/*
* ALPS tells us that it may take up to 20msec for the disable to
* take effect; however, ps2_command() will wait up to 200msec for
* the ACK to come back (and I'm assuming that by the time the
* hardware sends back its ACK, it has stopped sending bytes).
*/
pending_mode = priv->pending_mode;
if (olpc_new_mode(psmouse, priv->pending_mode))
goto bad;
/*
* Deal with a potential race condition.
*
* If there is a brief tap of a stylus or a fingernail that
* triggers a mode switch to PT mode, and the stylus/fingernail is
* lifted after the DISABLE above, but before we reenable in the new mode,
* then we can get stuck in PT mode.
*/
if (pending_mode == OLPC_PT) {
priv->pending_mode = OLPC_GS;
queue_delayed_work(kpsmoused_wq, &priv->mode_switch, msecs_to_jiffies(50));
}
return;
bad:
printk(KERN_WARNING __FILE__ ": Failure to switch modes, resetting device...\n");
olpc_reconnect(psmouse);
}
int olpc_init(struct psmouse *psmouse)
{
struct olpc_data *priv;
struct input_dev *dev = psmouse->dev;
struct input_dev *dev2;
int mode;
priv = kzalloc(sizeof(struct olpc_data), GFP_KERNEL);
dev2 = input_allocate_device();
if (!priv || !dev2)
goto init_fail;
psmouse->private = priv;
priv->dev2 = dev2;
priv->psmouse = psmouse;
psmouse_reset(psmouse);
if (!(priv->i = olpc_get_model(psmouse)))
goto init_fail;
mode = olpc_find_mode(psmouse);
if (mode < 0) {
printk(KERN_ERR __FILE__ ": Failed to identify proper mode\n");
goto init_fail;
}
if (olpc_absolute_mode(psmouse, mode)) {
printk(KERN_ERR __FILE__ ": Failed to enable absolute mode\n");
goto init_fail;
}
/*
* Unset some of the default bits for things we don't have.
*/
dev->evbit[LONG(EV_REL)] &= ~BIT(EV_REL);
dev->relbit[LONG(REL_X)] &= ~(BIT(REL_X) | BIT(REL_Y));
dev->keybit[LONG(BTN_MIDDLE)] &= ~BIT(BTN_MIDDLE);
dev->evbit[LONG(EV_KEY)] |= BIT(EV_KEY);
dev->keybit[LONG(BTN_TOUCH)] |= BIT(BTN_TOUCH);
dev->keybit[LONG(BTN_TOOL_PEN)] |= BIT(BTN_TOOL_PEN);
dev->keybit[LONG(BTN_LEFT)] |= BIT(BTN_LEFT) | BIT(BTN_RIGHT);
dev->evbit[LONG(EV_ABS)] |= BIT(EV_ABS);
input_set_abs_params(dev, ABS_X, 2, 1000, 0, 0);
input_set_abs_params(dev, ABS_Y, 0, 717, 0, 0);
snprintf(priv->phys, sizeof(priv->phys),
"%s/input1", psmouse->ps2dev.serio->phys);
dev2->phys = priv->phys;
dev2->name = "OLPC ALPS GlideSensor";
dev2->id.bustype = BUS_I8042;
dev2->id.vendor = 0x0002;
dev2->id.product = PSMOUSE_OLPC;
dev2->id.version = 0x0000;
dev2->evbit[LONG(EV_KEY)] |= BIT(EV_KEY);
dev2->keybit[LONG(BTN_TOUCH)] |= BIT(BTN_TOUCH);
dev2->keybit[LONG(BTN_TOOL_FINGER)] |= BIT(BTN_TOOL_FINGER);
dev2->keybit[LONG(BTN_LEFT)] |= BIT(BTN_LEFT) | BIT(BTN_RIGHT);
dev2->evbit[LONG(EV_ABS)] |= BIT(EV_ABS);
input_set_abs_params(dev2, ABS_X, 350, 512, 0, 0);
input_set_abs_params(dev2, ABS_Y, 70, 325, 0, 0);
input_set_abs_params(dev2, ABS_PRESSURE, 0, 63, 0, 0);
if (input_register_device(dev2)) {
printk(KERN_ERR __FILE__ ": Failed to register GlideSensor\n");
goto init_fail;
}
psmouse->protocol_handler = olpc_process_byte;
psmouse->poll = olpc_poll;
psmouse->disconnect = olpc_disconnect;
psmouse->reconnect = olpc_reconnect;
psmouse->pktsize = 6;
/* Disable the idle resync. */
psmouse->resync_time = 0;
/* Reset after a lot of bad bytes. */
psmouse->resetafter = 1024;
INIT_DELAYED_WORK(&priv->mode_switch, olpc_mode_switch);
return 0;
init_fail:
input_free_device(dev2);
kfree(priv);
return -1;
}
int olpc_detect(struct psmouse *psmouse, int set_properties)
{
if (!olpc_get_model(psmouse))
return -1;
if (set_properties) {
psmouse->vendor = "ALPS";
psmouse->name = "PenTablet";
psmouse->model = 0;
}
return 0;
}

View file

@ -0,0 +1,49 @@
/*
* OLPC touchpad PS/2 mouse driver
*
* Copyright (c) 2006 One Laptop Per Child, inc.
*
* This driver is partly based on the ALPS driver.
* Copyright (c) 2003 Peter Osterlund <petero2@telia.com>
* Copyright (c) 2005 Vojtech Pavlik <vojtech@suse.cz>
*
* 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.
*/
#ifndef _OLPC_H
#define _OLPC_H
struct olpc_model_info {
unsigned char signature[3];
unsigned char flags;
};
struct olpc_data {
struct input_dev *dev2; /* Relative device */
struct psmouse *psmouse;
char name[32]; /* Name */
char phys[32]; /* Phys */
struct olpc_model_info *i; /* Info */
int pending_mode;
int current_mode;
s64 late;
struct delayed_work mode_switch;
};
#ifdef CONFIG_MOUSE_PS2_OLPC
int olpc_detect(struct psmouse *psmouse, int set_properties);
int olpc_init(struct psmouse *psmouse);
#else
inline int olpc_detect(struct psmouse *psmouse, int set_properties)
{
return -ENOSYS;
}
inline int olpc_init(struct psmouse *psmouse)
{
return -ENOSYS;
}
#endif
#endif

View file

@ -0,0 +1,166 @@
/*
* Register definitions for the m88alp01 camera interface. Offsets in bytes
* as given in the spec.
*
* Copyright 2006 One Laptop Per Child Association, Inc.
*
* Written by Jonathan Corbet, corbet@lwn.net.
*
* This file may be distributed under the terms of the GNU General
* Public License, version 2.
*/
#define REG_Y0BAR 0x00
#define REG_Y1BAR 0x04
#define REG_Y2BAR 0x08
/* ... */
#define REG_IMGPITCH 0x24 /* Image pitch register */
#define IMGP_YP_SHFT 2 /* Y pitch params */
#define IMGP_YP_MASK 0x00003ffc /* Y pitch field */
#define IMGP_UVP_SHFT 18 /* UV pitch (planar) */
#define IMGP_UVP_MASK 0x3ffc0000
#define REG_IRQSTATRAW 0x28 /* RAW IRQ Status */
#define IRQ_EOF0 0x00000001 /* End of frame 0 */
#define IRQ_EOF1 0x00000002 /* End of frame 1 */
#define IRQ_EOF2 0x00000004 /* End of frame 2 */
#define IRQ_SOF0 0x00000008 /* Start of frame 0 */
#define IRQ_SOF1 0x00000010 /* Start of frame 1 */
#define IRQ_SOF2 0x00000020 /* Start of frame 2 */
#define IRQ_OVERFLOW 0x00000040 /* FIFO overflow */
#define IRQ_TWSIW 0x00010000 /* TWSI (smbus) write */
#define IRQ_TWSIR 0x00020000 /* TWSI read */
#define IRQ_TWSIE 0x00040000 /* TWSI error */
#define TWSIIRQS (IRQ_TWSIW|IRQ_TWSIR|IRQ_TWSIE)
#define FRAMEIRQS (IRQ_EOF0|IRQ_EOF1|IRQ_EOF2|IRQ_SOF0|IRQ_SOF1|IRQ_SOF2)
#define ALLIRQS (TWSIIRQS|FRAMEIRQS|IRQ_OVERFLOW)
#define REG_IRQMASK 0x2c /* IRQ mask - same bits as IRQSTAT */
#define REG_IRQSTAT 0x30 /* IRQ status / clear */
#define REG_IMGSIZE 0x34 /* Image size */
#define IMGSZ_V_MASK 0x1fff0000
#define IMGSZ_V_SHIFT 16
#define IMGSZ_H_MASK 0x00003fff
#define REG_IMGOFFSET 0x38 /* IMage offset */
#define REG_CTRL0 0x3c /* Control 0 */
#define C0_ENABLE 0x00000001 /* Makes the whole thing go */
/* Mask for all the format bits */
#define C0_DF_MASK 0x00fffffc /* Bits 2-23 */
/* RGB ordering */
#define C0_RGB4_RGBX 0x00000000
#define C0_RGB4_XRGB 0x00000004
#define C0_RGB4_BGRX 0x00000008
#define C0_RGB4_XBGR 0x0000000c
#define C0_RGB5_RGGB 0x00000000
#define C0_RGB5_GRBG 0x00000004
#define C0_RGB5_GBRG 0x00000008
#define C0_RGB5_BGGR 0x0000000c
/* Spec has two fields for DIN and DOUT, but they must match, so
combine them here. */
#define C0_DF_YUV 0x00000000 /* Data is YUV */
#define C0_DF_RGB 0x000000a0 /* ... RGB */
#define C0_DF_BAYER 0x00000140 /* ... Bayer */
/* 8-8-8 must be missing from the below - ask */
#define C0_RGBF_565 0x00000000
#define C0_RGBF_444 0x00000800
#define C0_RGB_BGR 0x00001000 /* Blue comes first */
#define C0_YUV_PLANAR 0x00000000 /* YUV 422 planar format */
#define C0_YUV_PACKED 0x00008000 /* YUV 422 packed */
#define C0_YUV_420PL 0x0000a000 /* YUV 420 planar */
/* Think that 420 packed must be 111 - ask */
#define C0_YUVE_YUYV 0x00000000 /* Y1CbY0Cr */
#define C0_YUVE_YVYU 0x00010000 /* Y1CrY0Cb */
#define C0_YUVE_VYUY 0x00020000 /* CrY1CbY0 */
#define C0_YUVE_UYVY 0x00030000 /* CbY1CrY0 */
#define C0_YUVE_XYUV 0x00000000 /* 420: .YUV */
#define C0_YUVE_XYVU 0x00010000 /* 420: .YVU */
#define C0_YUVE_XUVY 0x00020000 /* 420: .UVY */
#define C0_YUVE_XVUY 0x00030000 /* 420: .VUY */
/* Bayer bits 18,19 if needed */
#define C0_HPOL_LOW 0x01000000 /* HSYNC polarity active low */
#define C0_VPOL_LOW 0x02000000 /* VSYNC polarity active low */
#define C0_VCLK_LOW 0x04000000 /* VCLK on falling edge */
#define C0_DOWNSCALE 0x08000000 /* Enable downscaler */
#define C0_SIFM_MASK 0xc0000000 /* SIF mode bits */
#define C0_SIF_HVSYNC 0x00000000 /* Use H/VSYNC */
#define CO_SOF_NOSYNC 0x40000000 /* Use inband active signaling */
#define REG_CTRL1 0x40 /* Control 1 */
#define C1_444ALPHA 0x00f00000 /* Alpha field in RGB444 */
#define C1_ALPHA_SHFT 20
#define C1_DMAB32 0x00000000 /* 32-byte DMA burst */
#define C1_DMAB16 0x02000000 /* 16-byte DMA burst */
#define C1_DMAB64 0x04000000 /* 64-byte DMA burst */
#define C1_DMAB_MASK 0x06000000
#define C1_TWOBUFS 0x08000000 /* Use only two DMA buffers */
#define C1_PWRDWN 0x10000000 /* Power down */
#define REG_CLKCTRL 0x88 /* Clock control */
#define CLK_DIV_MASK 0x0000ffff /* Upper bits RW "reserved" */
#define REG_GPR 0xb4 /* General purpose register. This
controls inputs to the power and reset
pins on the OV7670 used with OLPC;
other deployments could differ. */
#define GPR_C1EN 0x00000020 /* Pad 1 (power down) enable */
#define GPR_C0EN 0x00000010 /* Pad 0 (reset) enable */
#define GPR_C1 0x00000002 /* Control 1 value */
/*
* Control 0 is wired to reset on OLPC machines. For ov7x sensors,
* it is active low, for 0v6x, instead, it's active high. What
* fun.
*/
#define GPR_C0 0x00000001 /* Control 0 value */
#define REG_TWSIC0 0xb8 /* TWSI (smbus) control 0 */
#define TWSIC0_EN 0x00000001 /* TWSI enable */
#define TWSIC0_MODE 0x00000002 /* 1 = 16-bit, 0 = 8-bit */
#define TWSIC0_SID 0x000003fc /* Slave ID */
#define TWSIC0_SID_SHIFT 2
#define TWSIC0_CLKDIV 0x0007fc00 /* Clock divider */
#define TWSIC0_MASKACK 0x00400000 /* Mask ack from sensor */
#define TWSIC0_OVMAGIC 0x00800000 /* Make it work on OV sensors */
#define REG_TWSIC1 0xbc /* TWSI control 1 */
#define TWSIC1_DATA 0x0000ffff /* Data to/from camchip */
#define TWSIC1_ADDR 0x00ff0000 /* Address (register) */
#define TWSIC1_ADDR_SHIFT 16
#define TWSIC1_READ 0x01000000 /* Set for read op */
#define TWSIC1_WSTAT 0x02000000 /* Write status */
#define TWSIC1_RVALID 0x04000000 /* Read data valid */
#define TWSIC1_ERROR 0x08000000 /* Something screwed up */
#define REG_UBAR 0xc4 /* Upper base address register */
/*
* Here's the weird global control registers which are said to live
* way up here.
*/
#define REG_GL_CSR 0x3004 /* Control/status register */
#define GCSR_SRS 0x00000001 /* SW Reset set */
#define GCSR_SRC 0x00000002 /* SW Reset clear */
#define GCSR_MRS 0x00000004 /* Master reset set */
#define GCSR_MRC 0x00000008 /* HW Reset clear */
#define GCSR_CCIC_EN 0x00004000 /* CCIC Clock enable */
#define REG_GL_IMASK 0x300c /* Interrupt mask register */
#define GIMSK_CCIC_EN 0x00000004 /* CCIC Interrupt enable */
#define REG_GL_FCR 0x3038 /* GPIO functional control register */
#define GFCR_GPIO_ON 0x08 /* Camera GPIO enabled */
#define REG_GL_GPIOR 0x315c /* GPIO register */
#define GGPIO_OUT 0x80000 /* GPIO output */
#define GGPIO_VAL 0x00008 /* Output pin value */
#define REG_LEN REG_GL_IMASK + 4
/*
* Useful stuff that probably belongs somewhere global.
*/
#define VGA_WIDTH 640
#define VGA_HEIGHT 480

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,11 @@
/* linux/drivers/mtd/mtdcore.h
*
* Header file for driver private mtdcore exports
*
*/
/* These are exported solely for the purpose of mtd_blkdevs.c. You
should not use them for _anything_ else */
extern struct mutex mtd_table_mutex;
extern struct mtd_info *mtd_table[MAX_MTD_DEVICES];

View file

@ -0,0 +1,365 @@
/*
* MTD Oops/Panic logger
*
* Copyright (C) 2007 Nokia Corporation. All rights reserved.
*
* Author: Richard Purdie <rpurdie@openedhand.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/console.h>
#include <linux/vmalloc.h>
#include <linux/workqueue.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/mtd/mtd.h>
#define OOPS_PAGE_SIZE 4096
static struct mtdoops_context {
int mtd_index;
struct work_struct work;
struct mtd_info *mtd;
int oops_pages;
int nextpage;
int nextcount;
void *oops_buf;
int ready;
int writecount;
} oops_cxt;
static void mtdoops_erase_callback(struct erase_info *done)
{
wait_queue_head_t *wait_q = (wait_queue_head_t *)done->priv;
wake_up(wait_q);
}
static int mtdoops_erase_block(struct mtd_info *mtd, int offset)
{
struct erase_info erase;
DECLARE_WAITQUEUE(wait, current);
wait_queue_head_t wait_q;
int ret;
init_waitqueue_head(&wait_q);
erase.mtd = mtd;
erase.callback = mtdoops_erase_callback;
erase.addr = offset;
if (mtd->erasesize < OOPS_PAGE_SIZE)
erase.len = OOPS_PAGE_SIZE;
else
erase.len = mtd->erasesize;
erase.priv = (u_long)&wait_q;
set_current_state(TASK_INTERRUPTIBLE);
add_wait_queue(&wait_q, &wait);
ret = mtd->erase(mtd, &erase);
if (ret) {
set_current_state(TASK_RUNNING);
remove_wait_queue(&wait_q, &wait);
printk (KERN_WARNING "mtdoops: erase of region [0x%x, 0x%x] "
"on \"%s\" failed\n",
erase.addr, erase.len, mtd->name);
return ret;
}
schedule(); /* Wait for erase to finish. */
remove_wait_queue(&wait_q, &wait);
return 0;
}
static int mtdoops_inc_counter(struct mtdoops_context *cxt)
{
struct mtd_info *mtd = cxt->mtd;
size_t retlen;
u32 count;
int ret;
cxt->nextpage++;
if (cxt->nextpage > cxt->oops_pages)
cxt->nextpage = 0;
cxt->nextcount++;
if (cxt->nextcount == 0xffffffff)
cxt->nextcount = 0;
ret = mtd->read(mtd, cxt->nextpage * OOPS_PAGE_SIZE, 4,
&retlen, (u_char *) &count);
if ((retlen != 4) || (ret < 0)) {
printk(KERN_ERR "mtdoops: Read failure at %d (%d of 4 read)"
", err %d.\n", cxt->nextpage * OOPS_PAGE_SIZE,
retlen, ret);
return 1;
}
/* See if we need to erase the next block */
if (count != 0xffffffff)
return 1;
printk(KERN_DEBUG "mtdoops: Ready %d, %d (no erase)\n",
cxt->nextpage, cxt->nextcount);
cxt->ready = 1;
return 0;
}
static void mtdoops_prepare(struct mtdoops_context *cxt)
{
struct mtd_info *mtd = cxt->mtd;
int i = 0, j, ret, mod;
/* We were unregistered */
if (!mtd)
return;
mod = (cxt->nextpage * OOPS_PAGE_SIZE) % mtd->erasesize;
if (mod != 0) {
cxt->nextpage = cxt->nextpage + ((mtd->erasesize - mod) / OOPS_PAGE_SIZE);
if (cxt->nextpage > cxt->oops_pages)
cxt->nextpage = 0;
}
while (mtd->block_isbad &&
mtd->block_isbad(mtd, cxt->nextpage * OOPS_PAGE_SIZE)) {
badblock:
printk(KERN_WARNING "mtdoops: Bad block at %08x\n",
cxt->nextpage * OOPS_PAGE_SIZE);
i++;
cxt->nextpage = cxt->nextpage + (mtd->erasesize / OOPS_PAGE_SIZE);
if (cxt->nextpage > cxt->oops_pages)
cxt->nextpage = 0;
if (i == (cxt->oops_pages / (mtd->erasesize / OOPS_PAGE_SIZE))) {
printk(KERN_ERR "mtdoops: All blocks bad!\n");
return;
}
}
for (j = 0, ret = -1; (j < 3) && (ret < 0); j++)
ret = mtdoops_erase_block(mtd, cxt->nextpage * OOPS_PAGE_SIZE);
if (ret < 0) {
if (mtd->block_markbad)
mtd->block_markbad(mtd, cxt->nextpage * OOPS_PAGE_SIZE);
goto badblock;
}
printk(KERN_DEBUG "mtdoops: Ready %d, %d \n", cxt->nextpage, cxt->nextcount);
cxt->ready = 1;
}
static void mtdoops_workfunc(struct work_struct *work)
{
struct mtdoops_context *cxt =
container_of(work, struct mtdoops_context, work);
mtdoops_prepare(cxt);
}
static int find_next_position(struct mtdoops_context *cxt)
{
struct mtd_info *mtd = cxt->mtd;
int page, maxpos = 0;
u32 count, maxcount = 0xffffffff;
size_t retlen;
for (page = 0; page < cxt->oops_pages; page++) {
mtd->read(mtd, page * OOPS_PAGE_SIZE, 4, &retlen, (u_char *) &count);
if (count == 0xffffffff)
continue;
if (maxcount == 0xffffffff) {
maxcount = count;
maxpos = page;
} else if ((count < 0x40000000) && (maxcount > 0xc0000000)) {
maxcount = count;
maxpos = page;
} else if ((count > maxcount) && (count < 0xc0000000)) {
maxcount = count;
maxpos = page;
} else if ((count > maxcount) && (count > 0xc0000000)
&& (maxcount > 0x80000000)) {
maxcount = count;
maxpos = page;
}
}
if (maxcount == 0xffffffff) {
cxt->nextpage = 0;
cxt->nextcount = 1;
cxt->ready = 1;
printk(KERN_DEBUG "mtdoops: Ready %d, %d (first init)\n",
cxt->nextpage, cxt->nextcount);
return 0;
}
cxt->nextpage = maxpos;
cxt->nextcount = maxcount;
return mtdoops_inc_counter(cxt);
}
static void mtdoops_notify_add(struct mtd_info *mtd)
{
struct mtdoops_context *cxt = &oops_cxt;
int ret;
if ((mtd->index != cxt->mtd_index) || cxt->mtd_index < 0)
return;
if (mtd->size < (mtd->erasesize * 2)) {
printk(KERN_ERR "MTD partition %d not big enough for mtdoops\n",
mtd->index);
return;
}
cxt->mtd = mtd;
cxt->oops_pages = mtd->size / OOPS_PAGE_SIZE;
ret = find_next_position(cxt);
if (ret == 1)
mtdoops_prepare(cxt);
printk(KERN_DEBUG "mtdoops: Attached to MTD device %d\n", mtd->index);
}
static void mtdoops_notify_remove(struct mtd_info *mtd)
{
struct mtdoops_context *cxt = &oops_cxt;
if ((mtd->index != cxt->mtd_index) || cxt->mtd_index < 0)
return;
cxt->mtd = NULL;
flush_scheduled_work();
}
static void
mtdoops_console_write(struct console *co, const char *s, unsigned int count)
{
struct mtdoops_context *cxt = co->data;
struct mtd_info *mtd = cxt->mtd;
int i, ret;
if (!cxt->ready || !mtd)
return;
if (!oops_in_progress && cxt->writecount != 0) {
size_t retlen;
if (cxt->writecount < OOPS_PAGE_SIZE)
memset(cxt->oops_buf + cxt->writecount, 0xff,
OOPS_PAGE_SIZE - cxt->writecount);
ret = mtd->write(mtd, cxt->nextpage * OOPS_PAGE_SIZE,
OOPS_PAGE_SIZE, &retlen, cxt->oops_buf);
cxt->ready = 0;
cxt->writecount = 0;
if ((retlen != OOPS_PAGE_SIZE) || (ret < 0))
printk(KERN_ERR "mtdoops: Write failure at %d (%d of %d"
" written), err %d.\n",
cxt->nextpage * OOPS_PAGE_SIZE, retlen,
OOPS_PAGE_SIZE, ret);
ret = mtdoops_inc_counter(cxt);
if (ret == 1)
schedule_work(&cxt->work);
}
if (!oops_in_progress)
return;
if (cxt->writecount == 0) {
u32 *stamp = cxt->oops_buf;
*stamp = cxt->nextcount;
cxt->writecount = 4;
}
if ((count + cxt->writecount) > OOPS_PAGE_SIZE)
count = OOPS_PAGE_SIZE - cxt->writecount;
for (i = 0; i < count; i++, s++)
*((char *)(cxt->oops_buf) + cxt->writecount + i) = *s;
cxt->writecount = cxt->writecount + count;
}
static int __init mtdoops_console_setup(struct console *co, char *options)
{
struct mtdoops_context *cxt = co->data;
if (cxt->mtd_index != -1)
return -EBUSY;
if (co->index == -1)
return -EINVAL;
cxt->mtd_index = co->index;
return 0;
}
static struct mtd_notifier mtdoops_notifier = {
.add = mtdoops_notify_add,
.remove = mtdoops_notify_remove,
};
static struct console mtdoops_console = {
.name = "ttyMTD",
.write = mtdoops_console_write,
.setup = mtdoops_console_setup,
.flags = CON_PRINTBUFFER,
.index = -1,
.data = &oops_cxt,
};
static int __init mtdoops_console_init(void)
{
struct mtdoops_context *cxt = &oops_cxt;
cxt->mtd_index = -1;
cxt->oops_buf = vmalloc(OOPS_PAGE_SIZE);
if (!cxt->oops_buf) {
printk(KERN_ERR "Failed to allocate oops buffer workspace\n");
return -ENOMEM;
}
INIT_WORK(&cxt->work, mtdoops_workfunc);
register_console(&mtdoops_console);
register_mtd_user(&mtdoops_notifier);
return 0;
}
static void __exit mtdoops_console_exit(void)
{
struct mtdoops_context *cxt = &oops_cxt;
unregister_mtd_user(&mtdoops_notifier);
unregister_console(&mtdoops_console);
vfree(cxt->oops_buf);
}
subsys_initcall(mtdoops_console_init);
module_exit(mtdoops_console_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Richard Purdie <rpurdie@openedhand.com>");
MODULE_DESCRIPTION("MTD Oops/Panic console logger/driver");

View file

@ -0,0 +1,495 @@
/*
* linux/drivers/mtd/onenand/onenand_sim.c
*
* The OneNAND simulator
*
* Copyright © 2005-2007 Samsung Electronics
* Kyungmin Park <kyungmin.park@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/vmalloc.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/mtd/onenand.h>
#include <linux/io.h>
#ifndef CONFIG_ONENAND_SIM_MANUFACTURER
#define CONFIG_ONENAND_SIM_MANUFACTURER 0xec
#endif
#ifndef CONFIG_ONENAND_SIM_DEVICE_ID
#define CONFIG_ONENAND_SIM_DEVICE_ID 0x04
#endif
#ifndef CONFIG_ONENAND_SIM_VERSION_ID
#define CONFIG_ONENAND_SIM_VERSION_ID 0x1e
#endif
static int manuf_id = CONFIG_ONENAND_SIM_MANUFACTURER;
static int device_id = CONFIG_ONENAND_SIM_DEVICE_ID;
static int version_id = CONFIG_ONENAND_SIM_VERSION_ID;
struct onenand_flash {
void __iomem *base;
void __iomem *data;
};
#define ONENAND_CORE(flash) (flash->data)
#define ONENAND_CORE_SPARE(flash, this, offset) \
((flash->data) + (this->chipsize) + (offset >> 5))
#define ONENAND_MAIN_AREA(this, offset) \
(this->base + ONENAND_DATARAM + offset)
#define ONENAND_SPARE_AREA(this, offset) \
(this->base + ONENAND_SPARERAM + offset)
#define ONENAND_GET_WP_STATUS(this) \
(readw(this->base + ONENAND_REG_WP_STATUS))
#define ONENAND_SET_WP_STATUS(v, this) \
(writew(v, this->base + ONENAND_REG_WP_STATUS))
/* It has all 0xff chars */
#define MAX_ONENAND_PAGESIZE (2048 + 64)
static unsigned char *ffchars;
static struct mtd_partition os_partitions[] = {
{
.name = "OneNAND simulator partition",
.offset = 0,
.size = MTDPART_SIZ_FULL,
},
};
/*
* OneNAND simulator mtd
*/
struct onenand_info {
struct mtd_info mtd;
struct mtd_partition *parts;
struct onenand_chip onenand;
struct onenand_flash flash;
};
struct onenand_info *info;
#define DPRINTK(format, args...) \
do { \
printk(KERN_DEBUG "%s[%d]: " format "\n", __func__, \
__LINE__, ##args); \
} while (0)
/**
* onenand_lock_handle - Handle Lock scheme
* @param this OneNAND device structure
* @param cmd The command to be sent
*
* Send lock command to OneNAND device.
* The lock scheme is depends on chip type.
*/
static void onenand_lock_handle(struct onenand_chip *this, int cmd)
{
int block_lock_scheme;
int status;
status = ONENAND_GET_WP_STATUS(this);
block_lock_scheme = !(this->options & ONENAND_HAS_CONT_LOCK);
switch (cmd) {
case ONENAND_CMD_UNLOCK:
if (block_lock_scheme)
ONENAND_SET_WP_STATUS(ONENAND_WP_US, this);
else
ONENAND_SET_WP_STATUS(status | ONENAND_WP_US, this);
break;
case ONENAND_CMD_LOCK:
if (block_lock_scheme)
ONENAND_SET_WP_STATUS(ONENAND_WP_LS, this);
else
ONENAND_SET_WP_STATUS(status | ONENAND_WP_LS, this);
break;
case ONENAND_CMD_LOCK_TIGHT:
if (block_lock_scheme)
ONENAND_SET_WP_STATUS(ONENAND_WP_LTS, this);
else
ONENAND_SET_WP_STATUS(status | ONENAND_WP_LTS, this);
break;
default:
break;
}
}
/**
* onenand_bootram_handle - Handle BootRAM area
* @param this OneNAND device structure
* @param cmd The command to be sent
*
* Emulate BootRAM area. It is possible to do basic operation using BootRAM.
*/
static void onenand_bootram_handle(struct onenand_chip *this, int cmd)
{
switch (cmd) {
case ONENAND_CMD_READID:
writew(manuf_id, this->base);
writew(device_id, this->base + 2);
writew(version_id, this->base + 4);
break;
default:
/* REVIST: Handle other commands */
break;
}
}
/**
* onenand_update_interrupt - Set interrupt register
* @param this OneNAND device structure
* @param cmd The command to be sent
*
* Update interrupt register. The status is depends on command.
*/
static void onenand_update_interrupt(struct onenand_chip *this, int cmd)
{
int interrupt = ONENAND_INT_MASTER;
switch (cmd) {
case ONENAND_CMD_READ:
case ONENAND_CMD_READOOB:
interrupt |= ONENAND_INT_READ;
break;
case ONENAND_CMD_PROG:
case ONENAND_CMD_PROGOOB:
interrupt |= ONENAND_INT_WRITE;
break;
case ONENAND_CMD_ERASE:
interrupt |= ONENAND_INT_ERASE;
break;
case ONENAND_CMD_RESET:
interrupt |= ONENAND_INT_RESET;
break;
default:
break;
}
writew(interrupt, this->base + ONENAND_REG_INTERRUPT);
}
/**
* onenand_check_overwrite - Check over-write if happend
* @param dest The destination pointer
* @param src The source pointer
* @param count The length to be check
* @return 0 on same, otherwise 1
*
* Compare the source with destination
*/
static int onenand_check_overwrite(void *dest, void *src, size_t count)
{
unsigned int *s = (unsigned int *) src;
unsigned int *d = (unsigned int *) dest;
int i;
count >>= 2;
for (i = 0; i < count; i++)
if ((*s++ ^ *d++) != 0)
return 1;
return 0;
}
/**
* onenand_data_handle - Handle OneNAND Core and DataRAM
* @param this OneNAND device structure
* @param cmd The command to be sent
* @param dataram Which dataram used
* @param offset The offset to OneNAND Core
*
* Copy data from OneNAND Core to DataRAM (read)
* Copy data from DataRAM to OneNAND Core (write)
* Erase the OneNAND Core (erase)
*/
static void onenand_data_handle(struct onenand_chip *this, int cmd,
int dataram, unsigned int offset)
{
struct mtd_info *mtd = &info->mtd;
struct onenand_flash *flash = this->priv;
int main_offset, spare_offset;
void __iomem *src;
void __iomem *dest;
unsigned int i;
if (dataram) {
main_offset = mtd->writesize;
spare_offset = mtd->oobsize;
} else {
main_offset = 0;
spare_offset = 0;
}
switch (cmd) {
case ONENAND_CMD_READ:
src = ONENAND_CORE(flash) + offset;
dest = ONENAND_MAIN_AREA(this, main_offset);
memcpy(dest, src, mtd->writesize);
/* Fall through */
case ONENAND_CMD_READOOB:
src = ONENAND_CORE_SPARE(flash, this, offset);
dest = ONENAND_SPARE_AREA(this, spare_offset);
memcpy(dest, src, mtd->oobsize);
break;
case ONENAND_CMD_PROG:
src = ONENAND_MAIN_AREA(this, main_offset);
dest = ONENAND_CORE(flash) + offset;
/* To handle partial write */
for (i = 0; i < (1 << mtd->subpage_sft); i++) {
int off = i * this->subpagesize;
if (!memcmp(src + off, ffchars, this->subpagesize))
continue;
if (memcmp(dest + off, ffchars, this->subpagesize) &&
onenand_check_overwrite(dest + off, src + off, this->subpagesize))
printk(KERN_ERR "over-write happend at 0x%08x\n", offset);
memcpy(dest + off, src + off, this->subpagesize);
}
/* Fall through */
case ONENAND_CMD_PROGOOB:
src = ONENAND_SPARE_AREA(this, spare_offset);
/* Check all data is 0xff chars */
if (!memcmp(src, ffchars, mtd->oobsize))
break;
dest = ONENAND_CORE_SPARE(flash, this, offset);
if (memcmp(dest, ffchars, mtd->oobsize) &&
onenand_check_overwrite(dest, src, mtd->oobsize))
printk(KERN_ERR "OOB: over-write happend at 0x%08x\n",
offset);
memcpy(dest, src, mtd->oobsize);
break;
case ONENAND_CMD_ERASE:
memset(ONENAND_CORE(flash) + offset, 0xff, mtd->erasesize);
memset(ONENAND_CORE_SPARE(flash, this, offset), 0xff,
(mtd->erasesize >> 5));
break;
default:
break;
}
}
/**
* onenand_command_handle - Handle command
* @param this OneNAND device structure
* @param cmd The command to be sent
*
* Emulate OneNAND command.
*/
static void onenand_command_handle(struct onenand_chip *this, int cmd)
{
unsigned long offset = 0;
int block = -1, page = -1, bufferram = -1;
int dataram = 0;
switch (cmd) {
case ONENAND_CMD_UNLOCK:
case ONENAND_CMD_LOCK:
case ONENAND_CMD_LOCK_TIGHT:
case ONENAND_CMD_UNLOCK_ALL:
onenand_lock_handle(this, cmd);
break;
case ONENAND_CMD_BUFFERRAM:
/* Do nothing */
return;
default:
block = (int) readw(this->base + ONENAND_REG_START_ADDRESS1);
if (block & (1 << ONENAND_DDP_SHIFT)) {
block &= ~(1 << ONENAND_DDP_SHIFT);
/* The half of chip block */
block += this->chipsize >> (this->erase_shift + 1);
}
if (cmd == ONENAND_CMD_ERASE)
break;
page = (int) readw(this->base + ONENAND_REG_START_ADDRESS8);
page = (page >> ONENAND_FPA_SHIFT);
bufferram = (int) readw(this->base + ONENAND_REG_START_BUFFER);
bufferram >>= ONENAND_BSA_SHIFT;
bufferram &= ONENAND_BSA_DATARAM1;
dataram = (bufferram == ONENAND_BSA_DATARAM1) ? 1 : 0;
break;
}
if (block != -1)
offset += block << this->erase_shift;
if (page != -1)
offset += page << this->page_shift;
onenand_data_handle(this, cmd, dataram, offset);
onenand_update_interrupt(this, cmd);
}
/**
* onenand_writew - [OneNAND Interface] Emulate write operation
* @param value value to write
* @param addr address to write
*
* Write OneNAND register with value
*/
static void onenand_writew(unsigned short value, void __iomem * addr)
{
struct onenand_chip *this = info->mtd.priv;
/* BootRAM handling */
if (addr < this->base + ONENAND_DATARAM) {
onenand_bootram_handle(this, value);
return;
}
/* Command handling */
if (addr == this->base + ONENAND_REG_COMMAND)
onenand_command_handle(this, value);
writew(value, addr);
}
/**
* flash_init - Initialize OneNAND simulator
* @param flash OneNAND simulaotr data strucutres
*
* Initialize OneNAND simulator.
*/
static int __init flash_init(struct onenand_flash *flash)
{
int density, size;
int buffer_size;
flash->base = kzalloc(131072, GFP_KERNEL);
if (!flash->base) {
printk(KERN_ERR "Unable to allocate base address.\n");
return -ENOMEM;
}
density = device_id >> ONENAND_DEVICE_DENSITY_SHIFT;
size = ((16 << 20) << density);
ONENAND_CORE(flash) = vmalloc(size + (size >> 5));
if (!ONENAND_CORE(flash)) {
printk(KERN_ERR "Unable to allocate nand core address.\n");
kfree(flash->base);
return -ENOMEM;
}
memset(ONENAND_CORE(flash), 0xff, size + (size >> 5));
/* Setup registers */
writew(manuf_id, flash->base + ONENAND_REG_MANUFACTURER_ID);
writew(device_id, flash->base + ONENAND_REG_DEVICE_ID);
writew(version_id, flash->base + ONENAND_REG_VERSION_ID);
if (density < 2)
buffer_size = 0x0400; /* 1KiB page */
else
buffer_size = 0x0800; /* 2KiB page */
writew(buffer_size, flash->base + ONENAND_REG_DATA_BUFFER_SIZE);
return 0;
}
/**
* flash_exit - Clean up OneNAND simulator
* @param flash OneNAND simulaotr data strucutres
*
* Clean up OneNAND simulator.
*/
static void flash_exit(struct onenand_flash *flash)
{
vfree(ONENAND_CORE(flash));
kfree(flash->base);
kfree(flash);
}
static int __init onenand_sim_init(void)
{
/* Allocate all 0xff chars pointer */
ffchars = kmalloc(MAX_ONENAND_PAGESIZE, GFP_KERNEL);
if (!ffchars) {
printk(KERN_ERR "Unable to allocate ff chars.\n");
return -ENOMEM;
}
memset(ffchars, 0xff, MAX_ONENAND_PAGESIZE);
/* Allocate OneNAND simulator mtd pointer */
info = kzalloc(sizeof(struct onenand_info), GFP_KERNEL);
if (!info) {
printk(KERN_ERR "Unable to allocate core structures.\n");
kfree(ffchars);
return -ENOMEM;
}
/* Override write_word function */
info->onenand.write_word = onenand_writew;
if (flash_init(&info->flash)) {
printk(KERN_ERR "Unable to allocat flash.\n");
kfree(ffchars);
kfree(info);
return -ENOMEM;
}
info->parts = os_partitions;
info->onenand.base = info->flash.base;
info->onenand.priv = &info->flash;
info->mtd.name = "OneNAND simulator";
info->mtd.priv = &info->onenand;
info->mtd.owner = THIS_MODULE;
if (onenand_scan(&info->mtd, 1)) {
flash_exit(&info->flash);
kfree(ffchars);
kfree(info);
return -ENXIO;
}
add_mtd_partitions(&info->mtd, info->parts, ARRAY_SIZE(os_partitions));
return 0;
}
static void __exit onenand_sim_exit(void)
{
struct onenand_chip *this = info->mtd.priv;
struct onenand_flash *flash = this->priv;
onenand_release(&info->mtd);
flash_exit(flash);
kfree(ffchars);
kfree(info);
}
module_init(onenand_sim_init);
module_exit(onenand_sim_exit);
MODULE_AUTHOR("Kyungmin Park <kyungmin.park@samsung.com>");
MODULE_DESCRIPTION("The OneNAND flash simulator");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,242 @@
/* This header file defines the registers and suspend/resume
structures for the Geode GX and LX. The lxfb driver defines
_GEODELX_ before including this file, which will unlock the
extra registers that are only valid for LX.
*/
#ifndef _GEODE_REGS_H_
#define _GEODE_REGS_H_
/* MSRs */
#define GX_VP_MSR_PAD_SELECT 0xC0002011
#define LX_VP_MSR_PAD_SELECT 0x48000011
#define GEODE_MSR_GLCP_DOTPLL 0x4c000015
#define GLCP_DOTPLL_RESET (1 << 0)
#define GLCP_DOTPLL_BYPASS (1 << 15)
#define GLCP_DOTPLL_HALFPIX (1 << 24)
#define GLCP_DOTPLL_LOCK (1 << 25)
/* Registers */
#define VP_FP_START 0x400
#ifdef _GEODELX_
#define GP_REG_SIZE 0x7C
#define DC_REG_SIZE 0xF0
#define VP_REG_SIZE 0x158
#define FP_REG_SIZE 0x70
#else
#define GP_REG_SIZE 0x50
#define DC_REG_SIZE 0x90
#define VP_REG_SIZE 0x138
#define FP_REG_SIZE 0x70
#endif
#define DC_PAL_SIZE 0x105
struct geoderegs {
struct {
u64 padsel;
u64 dotpll;
#ifdef _GEODELX_
u64 dfglcfg;
u64 dcspare;
#else
u64 rstpll;
#endif
} msr;
union {
unsigned char b[GP_REG_SIZE];
struct {
u32 dst_offset; /* 0x00 */
u32 src_offset; /* 0x04 */
u32 stride; /* 0x08 */
u32 wid_height; /* 0x0C */
u32 src_color_fg; /* 0x10 */
u32 src_color_bg; /* 0x14 */
u32 pat_color_0; /* 0x18 */
u32 pat_color_1; /* 0x1C */
u32 pat_color_2; /* 0x20 */
u32 pat_color_3; /* 0x24 */
u32 pat_color_4; /* 0x28 */
u32 pat_color_5; /* 0x2C */
u32 pat_data_0; /* 0x30 */
u32 pat_data_1; /* 0x34 */
u32 raster_mode; /* 0x38 */
u32 vector_mode; /* 0x3C */
u32 blt_mode; /* 0x40 */
u32 blit_status; /* 0x4C */
u32 hst_src; /* 0x48 */
u32 base_offset; /* 0x4C */
#ifdef _GEODELX_
u32 cmd_top; /* 0x50 */
u32 cmd_bot; /* 0x54 */
u32 cmd_read; /* 0x58 */
u32 cmd_write; /* 0x5C */
u32 ch3_offset; /* 0x60 */
u32 ch3_mode_str; /* 0x64 */
u32 ch3_width; /* 0x68 */
u32 ch3_hsrc; /* 0x6C */
u32 lut_index; /* 0x70 */
u32 lut_data; /* 0x74 */
u32 int_cntrl; /* 0x78 */
#endif
} r;
} gp;
union {
unsigned char b[DC_REG_SIZE];
struct {
u32 unlock; /* 0x00 */
u32 gcfg; /* 0x04 */
u32 dcfg; /* 0x08 */
u32 arb; /* 0x0C */
u32 fb_st_offset; /* 0x10 */
u32 cb_st_offset; /* 0x14 */
u32 curs_st_offset; /* 0x18 */
u32 icon_st_offset; /* 0x1C */
u32 vid_y_st_offset; /* 0x20 */
u32 vid_u_st_offset; /* 0x24 */
u32 vid_v_st_offset; /* 0x28 */
u32 dctop; /* 0x2c */
u32 line_size; /* 0x30 */
u32 gfx_pitch; /* 0x34 */
u32 vid_yuv_pitch; /* 0x38 */
u32 rsvd2; /* 0x3C */
u32 h_active_timing; /* 0x40 */
u32 h_blank_timing; /* 0x44 */
u32 h_sync_timing; /* 0x48 */
u32 rsvd3; /* 0x4C */
u32 v_active_timing; /* 0x50 */
u32 v_blank_timing; /* 0x54 */
u32 v_sync_timing; /* 0x58 */
u32 fbactive; /* 0x5C */
u32 dc_cursor_x; /* 0x60 */
u32 dc_cursor_y; /* 0x64 */
u32 dc_icon_x; /* 0x68 */
u32 dc_line_cnt; /* 0x6C */
u32 rsvd5; /* 0x70 - palette address */
u32 rsvd6; /* 0x74 - palette data */
u32 dfifo_diag; /* 0x78 */
u32 cfifo_diag; /* 0x7C */
u32 dc_vid_ds_delta; /* 0x80 */
u32 gliu0_mem_offset; /* 0x84 */
u32 dv_ctl; /* 0x88 - added by LX */
u32 dv_acc; /* 0x8C */
#ifdef _GEODELX_
u32 gfx_scale;
u32 irq_filt_ctl;
u32 filt_coeff1;
u32 filt_coeff2;
u32 vbi_event_ctl;
u32 vbi_odd_ctl;
u32 vbi_hor;
u32 vbi_ln_odd;
u32 vbi_ln_event;
u32 vbi_pitch;
u32 clr_key;
u32 clr_key_mask;
u32 clr_key_x;
u32 clr_key_y;
u32 irq;
u32 rsvd8;
u32 genlk_ctrl;
u32 vid_even_y_st_offset; /* 0xD8 */
u32 vid_even_u_st_offset; /* 0xDC */
u32 vid_even_v_st_offset; /* 0xE0 */
u32 v_active_even_timing; /* 0xE4 */
u32 v_blank_even_timing; /* 0xE8 */
u32 v_sync_even_timing; /* 0xEC */
#endif
} r;
} dc;
union {
unsigned char b[VP_REG_SIZE];
struct {
u64 vcfg; /* 0x00 */
u64 dcfg; /* 0x08 */
u64 vx; /* 0x10 */
u64 vy; /* 0x18 */
u64 vs; /* 0x20 */
u64 vck; /* 0x28 */
u64 vcm; /* 0x30 */
u64 rsvd1; /* 0x38 - Gamma address*/
u64 rsvd2; /* 0x40 - Gamma data*/
u64 rsvd3; /* 0x48 */
u64 misc; /* 0x50 */
u64 ccs; /* 0x58 */
u64 rsvd4[3]; /* 0x60-0x70 */
u64 vdc; /* 0x78 */
u64 vco; /* 0x80 */
u64 crc; /* 0x88 */
u64 crc32; /* 0x90 */
u64 vde; /* 0x98 */
u64 cck; /* 0xA0 */
u64 ccm; /* 0xA8 */
u64 cc1; /* 0xB0 */
u64 cc2; /* 0xB8 */
u64 a1x; /* 0xC0 */
u64 a1y; /* 0xC8 */
u64 a1c; /* 0xD0 */
u64 a1t; /* 0xD8 */
u64 a2x; /* 0xE0 */
u64 a2y; /* 0xE8 */
u64 a2c; /* 0xF0 */
u64 a2t; /* 0xF8 */
u64 a3x; /* 0x100 */
u64 a3y; /* 0x108 */
u64 a3c; /* 0x110 */
u64 a3t; /* 0x118 */
u64 vrr; /* 0x120 */
u64 awt; /* 0x128 */
u64 vtm; /* 0x130 */
#ifdef _GEODELX_
u64 vye; /* 0x138 */
u64 a1ye; /* 0x140 */
u32 a2ye; /* 0x148 */
u32 a3ye; /* 0x150 */
#endif
} r;
} vp;
union {
unsigned char b[FP_REG_SIZE];
struct {
u64 pt1; /* 0x400 */
u64 pt2; /* 0x408 */
u64 pm; /* 0x410 */
u64 dfc; /* 0x418 */
u64 blfsr; /* 0x420 */
u64 rlfsr; /* 0x428 */
u64 fmi; /* 0x430 */
u64 fmd; /* 0x438 */
u64 rsvd; /* 0x440 */
u64 dca; /* 0x448 */
u64 dmd; /* 0x450 */
u64 crc; /* 0x458 */
u64 fbb; /* 0x460 */
u64 crc32; /* 0x468 */
} r;
} fp;
u32 pal[DC_PAL_SIZE];
u32 gamma[256];
};
#endif

View file

@ -0,0 +1,272 @@
#include <linux/fb.h>
#include <asm/io.h>
#include <asm/msr.h>
#include "geodefb.h"
#include "video_gx.h"
void gx_set_dotpll(struct fb_info *info, struct geoderegs *regs)
{
int timeout = 1000;
u64 rstpll, dotpll;
rdmsrl(MSR_GLCP_SYS_RSTPLL, rstpll);
rdmsrl(MSR_GLCP_DOTPLL, dotpll);
dotpll &= 0x00000000ffffffffull;
dotpll |= regs->msr.dotpll & 0xffffffff00000000ull;
dotpll |= MSR_GLCP_DOTPLL_DOTRESET;
dotpll &= ~MSR_GLCP_DOTPLL_BYPASS;
wrmsrl(MSR_GLCP_DOTPLL, dotpll);
rstpll |= (regs->msr.rstpll &
( MSR_GLCP_SYS_RSTPLL_DOTPREDIV2 |
MSR_GLCP_SYS_RSTPLL_DOTPREMULT2 |
MSR_GLCP_SYS_RSTPLL_DOTPOSTDIV3));
wrmsrl(MSR_GLCP_SYS_RSTPLL, rstpll);
dotpll &= ~(MSR_GLCP_DOTPLL_DOTRESET);
wrmsrl(MSR_GLCP_DOTPLL, dotpll);
do {
rdmsrl(MSR_GLCP_DOTPLL, dotpll);
} while (timeout-- && !(dotpll & MSR_GLCP_DOTPLL_LOCK));
}
/* FIXME: Make sure nothing is read to clear */
void gx_save_regs(struct fb_info *info, struct geoderegs *regs)
{
struct geodefb_par *par = info->par;
int i;
/* Wait for the BLT engine to stop being busy */
while(readl(par->gp_regs + 0x44) & 0x05);
rdmsrl(GX_VP_MSR_PAD_SELECT, regs->msr.padsel);
rdmsrl(MSR_GLCP_DOTPLL, regs->msr.dotpll);
rdmsrl(MSR_GLCP_SYS_RSTPLL, regs->msr.rstpll);
writel(0x4758, par->dc_regs + 0x00);
memcpy(regs->gp.b, par->gp_regs, GP_REG_SIZE);
memcpy(regs->dc.b, par->dc_regs, DC_REG_SIZE);
memcpy(regs->vp.b, par->vid_regs, VP_REG_SIZE);
memcpy(regs->fp.b, par->vid_regs + 0x400, FP_REG_SIZE);
/* Save the palettes */
writel(0, par->dc_regs + 0x70);
for(i = 0; i < DC_PAL_SIZE; i++)
regs->pal[i] = readl(par->dc_regs + 0x74);
writel(0, par->vid_regs + 0x38);
for(i = 0; i < 0xFF; i++)
regs->gamma[i] = readl(par->vid_regs + 0x40);
}
void gx_restore_regs(struct fb_info *info, struct geoderegs *regs)
{
struct geodefb_par *par = info->par;
u32 val, i;
/* DOTPLL */
gx_set_dotpll(info, regs);
/* GP */
writel(regs->gp.r.dst_offset, par->gp_regs + 0x00);
writel(regs->gp.r.src_offset, par->gp_regs + 0x04);
writel(regs->gp.r.stride, par->gp_regs + 0x08);
writel(regs->gp.r.wid_height, par->gp_regs + 0x0C);
writel(regs->gp.r.src_color_fg, par->gp_regs + 0x10);
writel(regs->gp.r.src_color_bg, par->gp_regs + 0x14);
writel(regs->gp.r.pat_color_0, par->gp_regs + 0x18);
writel(regs->gp.r.pat_color_1, par->gp_regs + 0x1C);
writel(regs->gp.r.pat_color_2, par->gp_regs + 0x20);
writel(regs->gp.r.pat_color_3, par->gp_regs + 0x24);
writel(regs->gp.r.pat_color_4, par->gp_regs + 0x28);
writel(regs->gp.r.pat_color_5, par->gp_regs + 0x2C);
writel(regs->gp.r.pat_data_0, par->gp_regs + 0x30);
writel(regs->gp.r.pat_data_1, par->gp_regs + 0x34);
/* Don't write the raster / vector / blt mode regs */
/* status register is read only */
writel(regs->gp.r.hst_src, par->gp_regs + 0x48);
writel(regs->gp.r.base_offset, par->gp_regs + 0x4c);
/* DC */
/* Write the unlock value */
writel(0x4758, par->dc_regs + 0x00);
writel(0, par->dc_regs + 0x70);
for(i = 0; i < DC_PAL_SIZE; i++)
writel(regs->pal[i], par->dc_regs + 0x74);
/* Write the gcfg register without the enables */
writel(regs->dc.r.gcfg & ~0x0F, par->dc_regs + 0x04);
/* Write the vcfg register without the enables */
writel(regs->dc.r.dcfg & ~0x19, par->dc_regs + 0x08);
/* Write the rest of the active registers */
writel(regs->dc.r.fb_st_offset, par->dc_regs + 0x10);
writel(regs->dc.r.cb_st_offset, par->dc_regs + 0x14);
writel(regs->dc.r.curs_st_offset, par->dc_regs + 0x18);
writel(regs->dc.r.icon_st_offset, par->dc_regs + 0x1C);
writel(regs->dc.r.vid_y_st_offset, par->dc_regs + 0x20);
writel(regs->dc.r.vid_u_st_offset, par->dc_regs + 0x24);
writel(regs->dc.r.vid_v_st_offset, par->dc_regs + 0x28);
writel(regs->dc.r.line_size, par->dc_regs + 0x30);
writel(regs->dc.r.gfx_pitch, par->dc_regs + 0x34);
writel(regs->dc.r.vid_yuv_pitch, par->dc_regs + 0x38);
writel(regs->dc.r.h_active_timing, par->dc_regs + 0x40);
writel(regs->dc.r.h_blank_timing, par->dc_regs + 0x44);
writel(regs->dc.r.h_sync_timing, par->dc_regs + 0x48);
writel(regs->dc.r.v_active_timing, par->dc_regs + 0x50);
writel(regs->dc.r.v_blank_timing, par->dc_regs + 0x54);
writel(regs->dc.r.v_sync_timing, par->dc_regs + 0x58);
writel(regs->dc.r.dc_cursor_x, par->dc_regs + 0x60);
writel(regs->dc.r.dc_cursor_y, par->dc_regs + 0x64);
writel(regs->dc.r.dc_icon_x, par->dc_regs + 0x68);
/* Don't write the line_cnt or diag registers */
writel(regs->dc.r.dc_vid_ds_delta, par->dc_regs + 0x80);
writel(regs->dc.r.gliu0_mem_offset, par->dc_regs + 0x84);
writel(regs->dc.r.dv_acc, par->dc_regs + 0x8C);
/* VP */
/* MSR */
wrmsrl(GX_VP_MSR_PAD_SELECT, regs->msr.padsel);
writel(0, par->vid_regs + 0x38);
for(i = 0; i < 0xFF; i++)
writel((u32) regs->gamma[i], par->vid_regs + 0x40);
/* Don't enable video yet */
writel((u32) regs->vp.r.vcfg & ~0x01, par->vid_regs + 0x00);
/* Don't enable the CRT yet */
writel((u32) regs->vp.r.dcfg & ~0x0F, par->vid_regs + 0x08);
/* Write the rest of the VP registers */
writel((u32) regs->vp.r.vx, par->vid_regs + 0x10);
writel((u32) regs->vp.r.vy, par->vid_regs + 0x18);
writel((u32) regs->vp.r.vs, par->vid_regs + 0x20);
writel((u32) regs->vp.r.vck, par->vid_regs + 0x28);
writel((u32) regs->vp.r.vcm, par->vid_regs + 0x30);
writel((u32) regs->vp.r.misc, par->vid_regs + 0x50);
writel((u32) regs->vp.r.ccs, par->vid_regs + 0x58);
writel((u32) regs->vp.r.vdc, par->vid_regs + 0x78);
writel((u32) regs->vp.r.vco, par->vid_regs + 0x80);
writel((u32) regs->vp.r.crc, par->vid_regs + 0x88);
writel((u32) regs->vp.r.vde, par->vid_regs + 0x98);
writel((u32) regs->vp.r.cck, par->vid_regs + 0xA0);
writel((u32) regs->vp.r.ccm, par->vid_regs + 0xA8);
writel((u32) regs->vp.r.cc1, par->vid_regs + 0xB0);
writel((u32) regs->vp.r.cc2, par->vid_regs + 0xB8);
writel((u32) regs->vp.r.a1x, par->vid_regs + 0xC0);
writel((u32) regs->vp.r.a1y, par->vid_regs + 0xC8);
writel((u32) regs->vp.r.a1c, par->vid_regs + 0xD0);
writel((u32) regs->vp.r.a1t, par->vid_regs + 0xD8);
writel((u32) regs->vp.r.a2x, par->vid_regs + 0xE0);
writel((u32) regs->vp.r.a2y, par->vid_regs + 0xE8);
writel((u32) regs->vp.r.a2c, par->vid_regs + 0xF0);
writel((u32) regs->vp.r.a2t, par->vid_regs + 0xF8);
writel((u32) regs->vp.r.a3x, par->vid_regs + 0x100);
writel((u32) regs->vp.r.a3y, par->vid_regs + 0x108);
writel((u32) regs->vp.r.a3c, par->vid_regs + 0x110);
writel((u32) regs->vp.r.a3t, par->vid_regs + 0x118);
writel((u32) regs->vp.r.vrr, par->vid_regs + 0x120);
/* FP registers */
writel((u32) regs->fp.r.pt1, par->vid_regs + 0x400);
writel((u32) regs->fp.r.pt2, par->vid_regs + 0x408);
writel((u32) regs->fp.r.dfc, par->vid_regs + 0x418);
writel(regs->fp.r.blfsr, par->vid_regs + 0x420);
writel(regs->fp.r.rlfsr, par->vid_regs + 0x428);
writel(regs->fp.r.fmi, par->vid_regs + 0x430);
writel(regs->fp.r.fmd, par->vid_regs + 0x438);
writel(regs->fp.r.dca, par->vid_regs + 0x448);
writel(regs->fp.r.dmd, par->vid_regs + 0x450);
writel(regs->fp.r.crc, par->vid_regs + 0x458);
writel(regs->fp.r.fbb, par->vid_regs + 0x460);
/* Final enables */
val = readl(par->vid_regs + 0x410);
/* Control the panel */
if (regs->fp.r.pm & (1 << 24)) {
if (!(val & 0x09))
writel(regs->fp.r.pm, par->vid_regs + 0x410);
}
else {
if (!(val & 0x05))
writel(regs->fp.r.pm, par->vid_regs + 0x410);
}
/* Turn everything on */
writel(regs->dc.r.gcfg, par->dc_regs + 0x04);
writel((u32) regs->vp.r.vcfg, par->vid_regs + 0x00);
writel((u32) regs->vp.r.dcfg, par->vid_regs + 0x08);
writel(regs->dc.r.dcfg, par->dc_regs + 0x08);
}
#ifdef DEBUG
void dump_regs(struct fb_info *info, int mode) {
struct geodefb_par *par = info->par;
u32 val;
int i;
if (mode == 0) {
for(i = 0; i < GP_REG_SIZE; i += 4) {
val = readl(par->gp_regs + i);
}
}
if (mode == 1) {
writel(0x4758, par->dc_regs + 0x00);
for(i = 0; i < DC_REG_SIZE; i += 4) {
val = readl(par->dc_regs + i);
printk("DC%x: %x\n", i, val);
}
}
if (mode == 2) {
for(i = 0; i < VP_REG_SIZE; i += 8) {
val = readl(par->vid_regs + i);
printk("VP%x: %x\n", i, val);
}
}
if (mode == 3) {
for(i = 0; i < FP_REG_SIZE; i += 8) {
val = readl(par->vid_regs + 0x400 + i);
printk("FP%x: %x\n", i, val);
}
}
}
#endif

View file

@ -0,0 +1,824 @@
/*
* Mainly by David Woodhouse, somewhat modified by Jordan Crouse
*
* Copyright © 2006-2007 Red Hat, Inc.
* Copyright © 2006-2007 Advanced Micro Devices, Inc.
*
* This program is free software. You can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License as published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/fb.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>
#include <linux/i2c-id.h>
#include <linux/pci.h>
#include <linux/vt_kern.h>
#include <linux/pci_ids.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/backlight.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/ctype.h>
#include <linux/reboot.h>
#include <asm/tsc.h>
#include <asm/olpc.h>
#include "olpc_dcon.h"
/* Module definitions */
static int resumeline = 898;
module_param(resumeline, int, 0444);
static int noinit;
module_param(noinit, int, 0444);
/* Default off since it doesn't work on DCON ASIC in B-test OLPC board */
static int useaa = 1;
module_param(useaa, int, 0444);
/* I2C structures */
static struct i2c_driver dcon_driver;
static struct i2c_client *dcon_client;
/* Platform devices */
static struct platform_device *dcon_device;
/* Backlight device */
static struct backlight_device *dcon_bl_dev;
/* Base address of the GPIO registers */
static unsigned long gpio_base;
static struct fb_info *fbinfo;
/* Current source, initialized at probe time */
static int dcon_source;
/* Desired source */
static int dcon_pending;
/* Current output type */
static int dcon_output = DCON_OUTPUT_COLOR;
/* Current sleep status (not yet implemented) */
static int dcon_sleep_val = DCON_ACTIVE;
/* Shadow register for the DCON_REG_MODE register */
static unsigned short dcon_disp_mode;
/* Variables used during switches */
static int dcon_switched;
static DECLARE_WAIT_QUEUE_HEAD(dcon_wait_queue);
static unsigned short normal_i2c[] = { 0x0D, I2C_CLIENT_END };
I2C_CLIENT_INSMOD;
#define dcon_write(reg,val) i2c_smbus_write_word_data(dcon_client,reg,val)
#define dcon_read(reg) i2c_smbus_read_word_data(dcon_client,reg)
/* The current backlight value - this saves us some smbus traffic */
static int bl_val = -1;
/* ===== API functions - these are called by a variety of users ==== */
/* Backlight notes - turning off the backlight enable bit in the DCON
* doesn't save us any power over just pushing the BL to zero, so we
* don't use that bit in this code.
*/
static int dcon_get_backlight(void)
{
if (dcon_client == NULL)
return 0;
if (bl_val == -1)
bl_val = dcon_read(DCON_REG_BRIGHT) & 0x0F;
return bl_val;
}
static void dcon_set_backlight(int level)
{
if (dcon_client == NULL)
return;
if (bl_val == (level & 0x0F))
return;
bl_val = level & 0x0F;
dcon_write(DCON_REG_BRIGHT, bl_val);
/* Purposely turn off the backlight when we go to level 0 */
if (bl_val == 0) {
dcon_disp_mode &= ~MODE_BL_ENABLE;
dcon_write(DCON_REG_MODE, dcon_disp_mode);
}
else if (!(dcon_disp_mode & MODE_BL_ENABLE)) {
dcon_disp_mode |= MODE_BL_ENABLE;
dcon_write(DCON_REG_MODE, dcon_disp_mode);
}
}
/* Set the output type to either color or mono */
static int dcon_set_output(int arg)
{
if (dcon_output == arg)
return 0;
dcon_output = arg;
if (arg == DCON_OUTPUT_MONO) {
dcon_disp_mode &= ~(MODE_CSWIZZLE | MODE_COL_AA);
dcon_disp_mode |= MODE_MONO_LUMA;
}
else {
dcon_disp_mode &= ~(MODE_MONO_LUMA);
dcon_disp_mode |= MODE_CSWIZZLE;
if (useaa)
dcon_disp_mode |= MODE_COL_AA;
}
dcon_write(DCON_REG_MODE, dcon_disp_mode);
return 0;
}
/* For now, this will be really stupid - we need to address how
* DCONLOAD works in a sleep and account for it accordingly
*/
static void dcon_sleep(int state)
{
/* Turn off the backlight and put the DCON to sleep */
if (state == dcon_sleep_val)
return;
if (state == DCON_SLEEP) {
dcon_disp_mode &= ~MODE_BL_ENABLE;
dcon_disp_mode |= MODE_SLEEP;
}
else {
/* Only re-enable the backlight if the backlight value is set */
if (bl_val != 0)
dcon_disp_mode |= MODE_BL_ENABLE;
dcon_disp_mode &= ~MODE_SLEEP;
}
dcon_sleep_val = state;
dcon_write(DCON_REG_MODE, dcon_disp_mode);
/* We should turn off some stuff in the framebuffer - but what? */
}
/* Set the source of the display (CPU or DCON) */
static void dcon_source_switch(struct work_struct *work)
{
DECLARE_WAITQUEUE(wait, current);
int source = dcon_pending;
if (dcon_source == source)
return;
dcon_switched = 0;
switch (source) {
case DCON_SOURCE_CPU:
/* Enable the scanline interrupt bit */
if (dcon_write(DCON_REG_MODE, dcon_disp_mode | MODE_SCAN_INT))
printk(KERN_ERR "olpc-dcon: couldn't enable scanline interrupt!\n");
else {
/* Wait up to one second for the scanline interrupt */
wait_event_timeout(dcon_wait_queue, dcon_switched == 1, HZ);
}
if (!dcon_switched)
printk(KERN_ERR "olpc-dcon: Timeout entering CPU mode; expect a screen glitch.\n");
/*
* Ideally we'd like to disable interrupts here so that the
* fb_powerup and DCON turn on happen at a known time value;
* however, we can't do that right now with fb_set_suspend
* messing with semaphores.
*
* For now, we just hope..
*/
if (fb_powerup(fbinfo)) {
printk(KERN_ERR "olpc-dcon: Failed to enter CPU mode\n");
dcon_pending = DCON_SOURCE_DCON;
return;
}
/* And turn off the DCON */
outl(1<<11, gpio_base + GPIOx_OUT_VAL);
/* Turn off the scanline interrupt */
if (dcon_write(DCON_REG_MODE, dcon_disp_mode))
printk(KERN_ERR "olpc-dcon: couldn't disable scanline interrupt!\n");
printk(KERN_INFO "olpc-dcon: The CPU has control\n");
break;
case DCON_SOURCE_DCON:
{
int t;
add_wait_queue(&dcon_wait_queue, &wait);
set_current_state(TASK_UNINTERRUPTIBLE);
/* Clear GPIO11 (DCONLOAD) - this implies that the DCON is in
control */
outl(1 << (11 + 16), gpio_base + GPIOx_OUT_VAL);
t = schedule_timeout(HZ/2);
remove_wait_queue(&dcon_wait_queue, &wait);
set_current_state(TASK_RUNNING);
if (!dcon_switched)
printk(KERN_ERR "olpc-dcon: Timeout entering DCON mode; expect a screen glitch.\n");
/* Turn off the graphics engine completely */
fb_powerdown(fbinfo);
printk(KERN_INFO "olpc-dcon: The DCON has control\n");
break;
}
default:
BUG();
}
dcon_source = source;
}
static DECLARE_WORK(dcon_work, dcon_source_switch);
static int dcon_set_source(int arg)
{
if (arg != DCON_SOURCE_CPU && arg != DCON_SOURCE_DCON)
return -EINVAL;
if (dcon_pending == arg)
return 0;
dcon_pending = arg;
if ((dcon_source != arg) && !work_pending(&dcon_work))
schedule_work(&dcon_work);
return 0;
}
static int dcon_set_source_sync(int arg)
{
int ret = dcon_set_source(arg);
if (!ret)
flush_scheduled_work();
return ret;
}
static int dconbl_set(struct backlight_device *dev) {
int level = dev->props.brightness;
if (dev->props.power != FB_BLANK_UNBLANK)
level = 0;
dcon_set_backlight(level);
return 0;
}
static int dconbl_get(struct backlight_device *dev) {
return dcon_get_backlight();
}
static ssize_t dcon_mode_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%4.4X\n", dcon_disp_mode);
}
static ssize_t dcon_sleep_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", dcon_sleep_val);
}
static ssize_t /* __deprecated */ dcon_source_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
printk(KERN_WARNING "olpc-dcon: using deprecated sysfs 'source' interface; use 'freeze' instead!\n");
return sprintf(buf, "%d\n", dcon_source);
}
static ssize_t dcon_freeze_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", dcon_source == DCON_SOURCE_DCON ? 1 : 0);
}
static ssize_t dcon_output_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", dcon_output);
}
static ssize_t dcon_resumeline_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", resumeline);
}
static int _strtoul(const char *buf, int len, unsigned int *val)
{
char *endp;
unsigned int output = simple_strtoul(buf, &endp, 0);
int size = endp - buf;
if (*endp && isspace(*endp))
size++;
if (size != len)
return -EINVAL;
*val = output;
return 0;
}
static ssize_t dcon_output_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int output;
int rc = -EINVAL;
if (_strtoul(buf, count, &output))
return -EINVAL;
if (output == DCON_OUTPUT_COLOR || output == DCON_OUTPUT_MONO) {
dcon_set_output(output);
rc = count;
}
return rc;
}
static ssize_t /* __deprecated */ dcon_source_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int output;
int rc = -EINVAL;
printk(KERN_WARNING "olpc-dcon: using deprecated sysfs 'source' interface; use 'freeze' instead!\n");
if (_strtoul(buf, count, &output))
return -EINVAL;
dcon_set_source(output);
rc = count;
return rc;
}
static ssize_t dcon_freeze_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int output;
int rc = -EINVAL;
if (_strtoul(buf, count, &output))
return rc;
dcon_set_source(output ? DCON_SOURCE_DCON : DCON_SOURCE_CPU);
rc = count;
return rc;
}
static ssize_t dcon_resumeline_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int rl;
int rc = -EINVAL;
if (_strtoul(buf, count, &rl))
return rc;
resumeline = rl;
dcon_write(DCON_REG_SCAN_INT, resumeline);
rc = count;
return rc;
}
static ssize_t dcon_sleep_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int output;
if (_strtoul(buf, count, &output))
return -EINVAL;
dcon_sleep(output ? DCON_SLEEP : DCON_ACTIVE);
return count;
}
static struct device_attribute dcon_device_files[] = {
__ATTR(mode, 0444, dcon_mode_show, NULL),
__ATTR(sleep, 0644, dcon_sleep_show, dcon_sleep_store),
__ATTR(source, 0644, dcon_source_show, dcon_source_store),
__ATTR(freeze, 0644, dcon_freeze_show, dcon_freeze_store),
__ATTR(output, 0644, dcon_output_show, dcon_output_store),
__ATTR(resumeline, 0644, dcon_resumeline_show, dcon_resumeline_store),
};
static struct backlight_ops dcon_bl_ops = {
.get_brightness = dconbl_get,
.update_status = dconbl_set
};
/* List of GPIOs that we care about:
(in) GPIO12 -- DCONBLNK
(in) GPIO[56] -- DCONSTAT[01]
(out) GPIO11 -- DCONLOAD
*/
#define IN_GPIOS ((1<<5) | (1<<6) | (1<<7) | (1<<12))
#define OUT_GPIOS (1<<11)
static irqreturn_t dcon_interrupt(int, void *);
static int dcon_request_irq(void)
{
unsigned long lo, hi;
unsigned char lob;
rdmsr(MSR_LBAR_GPIO, lo, hi);
/* Check the mask and whether GPIO is enabled (sanity check) */
if (hi != 0x0000f001) {
printk(KERN_ERR "GPIO not enabled -- cannot use DCON\n");
return -ENODEV;
}
/* Mask off the IO base address */
gpio_base = lo & 0x0000ff00;
/* Turn off the event enable for GPIO7 just to be safe */
outl(1 << (16+7), gpio_base + GPIOx_EVNT_EN);
/* Set the directions for the GPIO pins */
outl(OUT_GPIOS | (IN_GPIOS << 16), gpio_base + GPIOx_OUT_EN);
outl(IN_GPIOS | (OUT_GPIOS << 16), gpio_base + GPIOx_IN_EN);
/* Set up the interrupt mappings */
/* Set the IRQ to pair 2 */
geode_gpio_event_irq(OLPC_GPIO_DCON_IRQ, 2);
/* Enable group 2 to trigger the DCON interrupt */
geode_gpio_set_irq(2, DCON_IRQ);
/* Select edge level for interrupt (in PIC) */
lob = inb(0x4d0);
lob &= ~(1 << DCON_IRQ);
outb(lob, 0x4d0);
/* Register the interupt handler */
if (request_irq(DCON_IRQ, &dcon_interrupt, 0, "DCON", &dcon_driver))
return -EIO;
/* Clear INV_EN for GPIO7 (DCONIRQ) */
outl((1<<(16+7)), gpio_base + GPIOx_INV_EN);
/* Enable filter for GPIO12 (DCONBLANK) */
outl(1<<(12), gpio_base + GPIOx_IN_FLTR_EN);
/* Disable filter for GPIO7 */
outl(1<<(16+7), gpio_base + GPIOx_IN_FLTR_EN);
/* Disable event counter for GPIO7 (DCONIRQ) and GPIO12 (DCONBLANK) */
outl(1<<(16+7), gpio_base + GPIOx_EVNTCNT_EN);
outl(1<<(16+12), gpio_base + GPIOx_EVNTCNT_EN);
/* Add GPIO12 to the Filter Event Pair #7 */
outb(12, gpio_base + GPIO_FE7_SEL);
/* Turn off negative Edge Enable for GPIO12 */
outl(1<<(16+12), gpio_base + GPIOx_NEGEDGE_EN);
/* Enable negative Edge Enable for GPIO7 */
outl(1<<7, gpio_base + GPIOx_NEGEDGE_EN);
/* Zero the filter amount for Filter Event Pair #7 */
outw(0, gpio_base + GPIO_FLT7_AMNT);
/* Clear the negative edge status for GPIO7 and GPIO12 */
outl((1<<7) | (1<<12), gpio_base+0x4c);
/* FIXME: Clear the posiitive status as well, just to be sure */
outl((1<<7) | (1<<12), gpio_base+0x48);
/* Enable events for GPIO7 (DCONIRQ) and GPIO12 (DCONBLANK) */
outl((1<<(7))|(1<<12), gpio_base + GPIOx_EVNT_EN);
/* Determine the current state by reading the GPIO bit */
/* Earlier stages of the boot process have established the state */
dcon_source = inl(gpio_base + GPIOx_OUT_VAL) & (1<<11)
? DCON_SOURCE_CPU
: DCON_SOURCE_DCON;
dcon_pending = dcon_source;
return 0;
}
static int dcon_reboot_notify(struct notifier_block *nb, unsigned long foo, void *bar)
{
if (dcon_client == NULL)
return 0;
/* Turn off the DCON. Entirely. */
dcon_write(DCON_REG_MODE, 0x39);
dcon_write(DCON_REG_MODE, 0x32);
return 0;
}
static int dcon_conswitch_notify(struct notifier_block *nb,
unsigned long mode, void *dummy)
{
if (mode == CONSOLE_EVENT_SWITCH_TEXT)
dcon_sleep(DCON_ACTIVE);
return 0;
}
static struct notifier_block dcon_nb = {
.notifier_call = dcon_reboot_notify,
.priority = -1,
};
static struct notifier_block dcon_console_nb = {
.notifier_call = dcon_conswitch_notify,
.priority = -1,
};
static int dcon_probe(struct i2c_adapter *adap, int addr, int kind)
{
struct i2c_client *client;
uint16_t ver;
int rc, i;
if (!olpc_has_dcon()) {
printk("olpc-dcon: No DCON is attached.\n");
return -ENODEV;
}
if (num_registered_fb >= 1)
fbinfo = registered_fb[0];
if (adap->id != I2C_HW_SMBUS_SCX200) {
printk(KERN_ERR "olpc-dcon: Invalid I2C bus (%d not %d)\n",
adap->id, I2C_HW_SMBUS_SCX200);
return -ENXIO;
}
client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
if (client == NULL)
return -ENOMEM;
strncpy(client->name, "OLPC-DCON", I2C_NAME_SIZE);
client->addr = addr;
client->adapter = adap;
client->driver = &dcon_driver;
if ((rc = i2c_attach_client(client)) != 0) {
printk(KERN_ERR "olpc-dcon: Unable to attach the I2C client.\n");
goto eclient;
}
ver = i2c_smbus_read_word_data(client, DCON_REG_ID);
if ((ver >> 8) != 0xDC) {
printk(KERN_ERR "olpc-dcon: DCON ID not 0xDCxx: 0x%04x instead.\n", ver);
rc = -ENXIO;
goto ei2c;
}
if ((rc = dcon_request_irq())) {
printk(KERN_ERR "olpc-dcon: Unable to grab IRQ.\n");
goto ei2c;
}
if (ver < 0xdc02 && !noinit) {
/* Initialize the DCON registers */
/* Start with work-arounds for DCON ASIC */
i2c_smbus_write_word_data(client, 0x4b, 0x00cc);
i2c_smbus_write_word_data(client, 0x4b, 0x00cc);
i2c_smbus_write_word_data(client, 0x4b, 0x00cc);
i2c_smbus_write_word_data(client, 0x0b, 0x007a);
i2c_smbus_write_word_data(client, 0x36, 0x025c);
i2c_smbus_write_word_data(client, 0x37, 0x025e);
/* Initialise SDRAM */
i2c_smbus_write_word_data(client, 0x3b, 0x002b);
i2c_smbus_write_word_data(client, 0x41, 0x0101);
i2c_smbus_write_word_data(client, 0x42, 0x0101);
}
/* Colour swizzle, AA, no passthrough, backlight */
dcon_disp_mode = MODE_PASSTHRU | MODE_BL_ENABLE | MODE_CSWIZZLE;
if (useaa)
dcon_disp_mode |= MODE_COL_AA;
i2c_smbus_write_word_data(client, DCON_REG_MODE, dcon_disp_mode);
/* Set the scanline to interrupt on during resume */
i2c_smbus_write_word_data(client, DCON_REG_SCAN_INT, resumeline);
/* Add the DCON device */
dcon_device = platform_device_alloc("dcon", -1);
if (dcon_device == NULL) {
printk(KERN_ERR "dcon: Unable to create the DCON device\n");
rc = -ENOMEM;
goto eirq;
}
if ((rc = platform_device_add(dcon_device))) {
printk(KERN_ERR "dcon: Unable to add the DCON device\n");
goto edev;
}
for(i = 0; i < ARRAY_SIZE(dcon_device_files); i++)
device_create_file(&dcon_device->dev, &dcon_device_files[i]);
/* Add the backlight device for the DCON */
dcon_client = client;
dcon_bl_dev = backlight_device_register("dcon-bl", &dcon_device->dev,
NULL, &dcon_bl_ops);
if (IS_ERR(dcon_bl_dev)) {
printk(KERN_INFO "Could not register the backlight device for the DCON (%ld)\n", PTR_ERR(dcon_bl_dev));
dcon_bl_dev = NULL;
}
else {
dcon_bl_dev->props.max_brightness = 15;
dcon_bl_dev->props.power = FB_BLANK_UNBLANK;
dcon_bl_dev->props.brightness = dcon_get_backlight();
backlight_update_status(dcon_bl_dev);
}
register_reboot_notifier(&dcon_nb);
console_event_register(&dcon_console_nb);
printk(KERN_INFO "olpc-dcon: Discovered DCON version %x\n", ver & 0xFF);
return 0;
edev:
platform_device_unregister(dcon_device);
dcon_device = NULL;
eirq:
free_irq(DCON_IRQ, &dcon_driver);
ei2c:
i2c_detach_client(client);
eclient:
kfree(client);
return rc;
}
static int dcon_attach(struct i2c_adapter *adap)
{
int ret;
ret = i2c_probe(adap, &addr_data, dcon_probe);
if (dcon_client == NULL)
printk(KERN_ERR "olpc-dcon: No DCON found on SMBus\n");
return ret;
}
static int dcon_detach(struct i2c_client *client)
{
int rc;
dcon_client = NULL;
unregister_reboot_notifier(&dcon_nb);
console_event_unregister(&dcon_console_nb);
free_irq(DCON_IRQ, &dcon_driver);
if ((rc = i2c_detach_client(client)) == 0)
kfree(i2c_get_clientdata(client));
if (dcon_bl_dev != NULL)
backlight_device_unregister(dcon_bl_dev);
if (dcon_device != NULL)
platform_device_unregister(dcon_device);
cancel_work_sync(&dcon_work);
return rc;
}
#ifdef CONFIG_PM
static int dcon_suspend(struct i2c_client *client, pm_message_t state)
{
if (dcon_sleep_val != DCON_ACTIVE)
return 0;
/* Set up the DCON to have the source */
return dcon_set_source_sync(DCON_SOURCE_DCON);
}
static int dcon_resume(struct i2c_client *client)
{
int x;
if (dcon_sleep_val != DCON_ACTIVE)
return 0;
/* HACK: ensure the bus is stable */
do {
x = dcon_read(DCON_REG_ID);
} while (x < 0);
return dcon_set_source(DCON_SOURCE_CPU);
}
#endif
static irqreturn_t dcon_interrupt(int irq, void *id)
{
int status = inl(gpio_base + GPIOx_READ_BACK) >> 5;
/* Clear the negative edge status for GPIO7 */
outl(1 << 7, gpio_base + GPIOx_NEGEDGE_STS);
switch (status & 3) {
case 3:
printk(KERN_DEBUG "olpc-dcon: DCONLOAD_MISSED interrupt\n");
break;
case 2: /* switch to DCON mode */
case 1: /* switch to CPU mode */
dcon_switched = 1;
wake_up(&dcon_wait_queue);
break;
case 0:
printk(KERN_DEBUG "olpc-dcon: scanline interrupt w/CPU\n");
}
return IRQ_HANDLED;
}
static struct i2c_driver dcon_driver = {
.driver = {
.name = "OLPC-DCON",
},
.id = I2C_DRIVERID_DCON,
.attach_adapter = dcon_attach,
.detach_client = dcon_detach,
#ifdef CONFIG_PM
.suspend = dcon_suspend,
.resume = dcon_resume,
#endif
};
static int __init olpc_dcon_init(void)
{
i2c_add_driver(&dcon_driver);
return 0;
}
static void __exit olpc_dcon_exit(void)
{
i2c_del_driver(&dcon_driver);
}
module_init(olpc_dcon_init);
module_exit(olpc_dcon_exit);
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,75 @@
#ifndef OLPC_DCON_H_
#define OLPC_DCON_H_
/* DCON registers */
#define DCON_REG_ID 0
#define DCON_REG_MODE 1
#define MODE_PASSTHRU (1<<0)
#define MODE_SLEEP (1<<1)
#define MODE_SLEEP_AUTO (1<<2)
#define MODE_BL_ENABLE (1<<3)
#define MODE_BLANK (1<<4)
#define MODE_CSWIZZLE (1<<5)
#define MODE_COL_AA (1<<6)
#define MODE_MONO_LUMA (1<<7)
#define MODE_SCAN_INT (1<<8)
#define MODE_CLOCKDIV (1<<9)
#define MODE_DEBUG (1<<14)
#define MODE_SELFTEST (1<<15)
#define DCON_REG_HRES 2
#define DCON_REG_HTOTAL 3
#define DCON_REG_HSYNC_WIDTH 4
#define DCON_REG_VRES 5
#define DCON_REG_VTOTAL 6
#define DCON_REG_VSYNC_WIDTH 7
#define DCON_REG_TIMEOUT 8
#define DCON_REG_SCAN_INT 9
#define DCON_REG_BRIGHT 10
/* GPIO registers (CS5536) */
#define MSR_LBAR_GPIO 0x5140000C
#define GPIOx_OUT_VAL 0x00
#define GPIOx_OUT_EN 0x04
#define GPIOx_IN_EN 0x20
#define GPIOx_INV_EN 0x24
#define GPIOx_IN_FLTR_EN 0x28
#define GPIOx_EVNTCNT_EN 0x2C
#define GPIOx_READ_BACK 0x30
#define GPIOx_EVNT_EN 0x38
#define GPIOx_NEGEDGE_EN 0x44
#define GPIOx_NEGEDGE_STS 0x4C
#define GPIO_FLT7_AMNT 0xD8
#define GPIO_MAP_X 0xE0
#define GPIO_MAP_Y 0xE4
#define GPIO_FE7_SEL 0xF7
/* Status values */
#define DCONSTAT_SCANINT 0
#define DCONSTAT_SCANINT_DCON 1
#define DCONSTAT_DISPLAYLOAD 2
#define DCONSTAT_MISSED 3
/* Source values */
#define DCON_SOURCE_DCON 0
#define DCON_SOURCE_CPU 1
/* Output values */
#define DCON_OUTPUT_COLOR 0
#define DCON_OUTPUT_MONO 1
/* Sleep values */
#define DCON_ACTIVE 0
#define DCON_SLEEP 1
/* Interrupt */
#define DCON_IRQ 6
#endif

View file

@ -0,0 +1,16 @@
/*
* Definitions for Open Firmware client interface on 32-bit system.
* OF Cell size is 4. Integer properties are encoded big endian,
* as with all OF implementations.
*
* 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.
*/
#ifndef _OFW_H
#define _OFW_H
extern int ofw(char *, int, int, ...);
#endif

View file

@ -0,0 +1,116 @@
/* OLPC machine specific definitions */
#ifndef ASM_OLPC_H_
#define ASM_OLPC_H_
#include <asm/geode.h>
struct olpc_platform_t {
int flags;
int boardrev;
int ecver;
};
#define OLPC_F_PRESENT 0x01
#define OLPC_F_DCON 0x02
#define OLPC_F_VSA 0x04
enum
{
OLPC_REV_A = 0,
OLPC_REV_PRE_B1,
OLPC_REV_B1,
OLPC_REV_PRE_B2,
OLPC_REV_B2,
OLPC_REV_PRE_B3,
OLPC_REV_B3,
OLPC_REV_B4,
OLPC_REV_C1,
OLPC_REV_R1,
OLPC_REV_UNKNOWN /* this should always come last */
};
#ifndef CONFIG_OLPC
static inline int machine_is_olpc(void) { return 0; }
static inline int olpc_has_dcon(void) { return 0; }
static inline int olpc_has_vsa(void) { return 0; }
static inline int olpc_get_rev(void) { return OLPC_REV_UNKNOWN; }
#else
extern struct olpc_platform_t olpc_platform_info;
static inline int
machine_is_olpc(void)
{
return (olpc_platform_info.flags & OLPC_F_PRESENT) ? 1 : 0;
}
static inline int
olpc_has_dcon(void)
{
return (olpc_platform_info.flags & OLPC_F_DCON) ? 1 : 0;
}
static inline int
olpc_has_vsa(void)
{
return (olpc_platform_info.flags & OLPC_F_VSA) ? 1 : 0;
}
static inline int
olpc_get_rev(void)
{
return olpc_platform_info.boardrev;
}
static inline int
olpc_rev_after(int rev)
{
return olpc_platform_info.boardrev > rev &&
olpc_platform_info.boardrev != OLPC_REV_UNKNOWN;
}
static inline int
olpc_rev_before(int rev)
{
return olpc_platform_info.boardrev < rev ||
olpc_platform_info.boardrev == OLPC_REV_UNKNOWN;
}
#endif
/* EC functions */
int olpc_ec_cmd(unsigned char cmd, unsigned char *inbuf, size_t inlen,
unsigned char *outbuf, size_t outlen);
void olpc_register_battery_callback(void (*f)(unsigned long));
void olpc_deregister_battery_callback(void);
/* EC commands and responses */
/* SCI source values */
#define EC_SCI_SRC_EMPTY 0x00
#define EC_SCI_SRC_GAME 0x01
#define EC_SCI_SRC_BATTERY 0x02
#define EC_SCI_SRC_BATSOC 0x04
#define EC_SCI_SRC_BATERR 0x08
#define EC_SCI_SRC_EBOOK 0x10
#define EC_SCI_SRC_WLAN 0x20
#define EC_SCI_SRC_ACPWR 0x40
/* GPIO assignments */
#define OLPC_GPIO_MIC_AC 1
#define OLPC_GPIO_DCON_IRQ 7
#define OLPC_GPIO_THRM_ALRM 10
#define OLPC_GPIO_WORKAUX 24
#define OLPC_GPIO_LID 26
#define OLPC_GPIO_ECSCI 27
#endif

View file

@ -0,0 +1,108 @@
#ifndef _I386_PROM_H
#define _I386_PROM_H
#ifdef __KERNEL__
/*
* Definitions for talking to the Open Firmware PROM on
* Power Macintosh computers.
*
* Copyright (C) 1996-2005 Paul Mackerras.
*
* Updates for PPC64 by Peter Bergner & David Engebretsen, IBM Corp.
* Updates for SPARC64 by David S. Miller
* Updates for i386/OLPC by Andres Salomon
*
* 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/types.h>
#include <linux/proc_fs.h>
#include <asm/atomic.h>
typedef u32 phandle;
typedef u32 ihandle;
struct property {
char *name;
int length;
void *value;
struct property *next;
};
struct device_node {
const char *name;
const char *type;
phandle node;
// phandle linux_phandle;
char *path_component_name;
char *full_name;
struct property *properties;
struct property *deadprops; /* removed properties */
struct device_node *parent;
struct device_node *child;
struct device_node *sibling;
struct device_node *next; /* next device of same type */
struct device_node *allnext; /* next in list of all nodes */
struct proc_dir_entry *pde; /* this node's proc directory */
struct kref kref;
unsigned long _flags;
void *data;
};
/* flag descriptions */
#define OF_DYNAMIC 1 /* node and properties were allocated via kmalloc */
#define OF_IS_DYNAMIC(x) test_bit(OF_DYNAMIC, &x->_flags)
#define OF_MARK_DYNAMIC(x) set_bit(OF_DYNAMIC, &x->_flags)
#define OF_BAD_ADDR ((u64)-1)
static inline void set_node_proc_entry(struct device_node *dn, struct proc_dir_entry *de)
{
dn->pde = de;
}
extern struct device_node *of_find_node_by_name(struct device_node *from,
const char *name);
#define for_each_node_by_name(dn, name) \
for (dn = of_find_node_by_name(NULL, name); dn; \
dn = of_find_node_by_name(dn, name))
extern struct device_node *of_find_node_by_type(struct device_node *from,
const char *type);
#define for_each_node_by_type(dn, type) \
for (dn = of_find_node_by_type(NULL, type); dn; \
dn = of_find_node_by_type(dn, type))
extern struct device_node *of_find_compatible_node(struct device_node *from,
const char *type, const char *compat);
extern struct device_node *of_find_node_by_path(const char *path);
extern struct device_node *of_find_node_by_phandle(phandle handle);
extern struct device_node *of_get_parent(const struct device_node *node);
extern struct device_node *of_get_next_child(const struct device_node *node,
struct device_node *prev);
extern struct property *of_find_property(const struct device_node *np,
const char *name,
int *lenp);
//extern struct device_node *of_node_get(struct device_node *node);
//extern void of_node_put(struct device_node *node);
extern int of_device_is_compatible(const struct device_node *device,
const char *);
extern const void *of_get_property(const struct device_node *node,
const char *name,
int *lenp);
#define get_property(node,name,lenp) of_get_property(node,name,lenp)
extern int of_set_property(struct device_node *node, const char *name, void *val, int len);
extern int of_getintprop_default(struct device_node *np,
const char *name,
int def);
extern int of_n_addr_cells(struct device_node *np);
extern int of_n_size_cells(struct device_node *np);
extern void prom_build_devicetree(void);
#endif /* __KERNEL__ */
#endif

View file

@ -0,0 +1,101 @@
/*
* Driver model for batteries
*
* © 2006 David Woodhouse <dwmw2@infradead.org>
*
* Based on LED Class support, by John Lenz and Richard Purdie:
*
* © 2005 John Lenz <lenz@cs.wisc.edu>
* © 2005-2006 Richard Purdie <rpurdie@openedhand.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
*/
#ifndef __LINUX_BATTERY_H__
#define __LINUX_BATTERY_H__
struct device;
struct class_device;
/*
* Battery Core
*/
#define PWRDEV_TYPE_BATTERY 0
#define PWRDEV_TYPE_AC 1
#define BAT_STAT_PRESENT (1<<0)
#define BAT_STAT_LOW (1<<1)
#define BAT_STAT_FULL (1<<2)
#define BAT_STAT_CHARGING (1<<3)
#define BAT_STAT_DISCHARGING (1<<4)
#define BAT_STAT_OVERTEMP (1<<5)
#define BAT_STAT_CRITICAL (1<<6)
#define BAT_STAT_FIRE (1<<7)
#define BAT_STAT_CHARGE_DONE (1<<8)
/* Thou shalt not export any attributes in sysfs except these, and
with these units: */
#define BAT_INFO_STATUS "status" /* Not free-form. Use
provided function */
#define BAT_INFO_TEMP1 "temp1" /* °C/1000 */
#define BAT_INFO_TEMP1_NAME "temp1_name" /* string */
#define BAT_INFO_TEMP2 "temp2" /* °C/1000 */
#define BAT_INFO_TEMP2_NAME "temp2_name" /* string */
#define BAT_INFO_VOLTAGE "voltage" /* mV */
#define BAT_INFO_VOLTAGE_DESIGN "voltage_design" /* mV */
#define BAT_INFO_CURRENT "current" /* mA */
#define BAT_INFO_CURRENT_NOW "current_now" /* mA */
#define BAT_INFO_POWER "power" /* mW */
#define BAT_INFO_POWER_NOW "power_now" /* mW */
/* The following capacity/charge properties are represented in either
mA or mW. The CAP_UNITS property MUST be provided if any of these are. */
#define BAT_INFO_RATE "rate" /* CAP_UNITS */
#define BAT_INFO_CAP_LEFT "capacity_left" /* CAP_UNITS*h */
#define BAT_INFO_CAP_DESIGN "capacity_design" /* CAP_UNITS*h */
#define BAT_INFO_CAP_LAST_FULL "capacity_last_full" /* CAP_UNITS*h */
#define BAT_INFO_CAP_LOW "capacity_low_thresh" /* CAP_UNITS*h */
#define BAT_INFO_CAP_WARN "capacity_warn_thresh" /* CAP_UNITS*h */
#define BAT_INFO_CAP_UNITS "capacity_units" /* string: must be
either mA or mW */
#define BAT_INFO_CAP_PCT "capacity_percentage" /* integer */
#define BAT_INFO_TIME_EMPTY "time_to_empty" /* seconds */
#define BAT_INFO_TIME_EMPTY_NOW "time_to_empty_now" /* seconds */
#define BAT_INFO_TIME_FULL "time_to_full" /* seconds */
#define BAT_INFO_TIME_FULL_NOW "time_to_full_now" /* seconds */
#define BAT_INFO_MANUFACTURER "manufacturer" /* string */
#define BAT_INFO_TECHNOLOGY "technology" /* string */
#define BAT_INFO_MODEL "model" /* string */
#define BAT_INFO_SERIAL "serial" /* string */
#define BAT_INFO_OEM_INFO "oem_info" /* string */
#define BAT_INFO_CYCLE_COUNT "cycle_count" /* integer */
#define BAT_INFO_DATE_MFR "date_manufactured" /* YYYY[-MM[-DD]] */
#define BAT_INFO_DATE_FIRST_USE "date_first_use" /* YYYY[-MM[-DD]] */
struct battery_dev {
int status_cap;
int id;
int type;
const char *name;
struct device *dev;
};
int battery_device_register(struct device *parent,
struct battery_dev *battery_cdev);
void battery_device_unregister(struct battery_dev *battery_cdev);
ssize_t battery_attribute_show_status(char *buf, unsigned long status);
ssize_t battery_attribute_show_ac_status(char *buf, unsigned long status);
#endif /* __LINUX_BATTERY_H__ */

View file

@ -0,0 +1,110 @@
#include <sound/driver.h>
#include <sound/core.h>
#include <sound/info.h>
#include <sound/control.h>
#include <sound/ac97_codec.h>
#include <asm/olpc.h>
#include "cs5535audio.h"
/*
* OLPC has an additional feature on top of the regular AD1888 codec features.
* It has an Analog Input mode that is switched into (after disabling the
* High Pass Filter) via GPIO. It is only supported on B2 and later models.
*/
int olpc_ai_enable(struct snd_ac97 *ac97, u8 val)
{
int err;
/*
* update the High Pass Filter (via AC97_AD_TEST2), and then set
* Analog Input mode through a GPIO.
*/
if (val) {
err = snd_ac97_update_bits(ac97, AC97_AD_TEST2,
1<<AC97_AD_HPFD_SHIFT, 1<<AC97_AD_HPFD_SHIFT);
geode_gpio_set(OLPC_GPIO_MIC_AC, GPIO_OUTPUT_VAL);
}
else {
err = snd_ac97_update_bits(ac97, AC97_AD_TEST2,
1<<AC97_AD_HPFD_SHIFT, 0);
geode_gpio_clear(OLPC_GPIO_MIC_AC, GPIO_OUTPUT_VAL);
}
if (err < 0)
snd_printk(KERN_ERR "Error updating AD_TEST2: %d\n", err);
return err;
}
EXPORT_SYMBOL_GPL(olpc_ai_enable);
static int snd_cs5535audio_ai_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
return 0;
}
static int snd_cs5535audio_ai_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.integer.value[0] = geode_gpio_isset(OLPC_GPIO_MIC_AC,
GPIO_OUTPUT_VAL);
return 0;
}
static int snd_cs5535audio_ai_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct cs5535audio *cs5535au = snd_kcontrol_chip(kcontrol);
struct snd_ac97 *ac97 = cs5535au->ac97;
olpc_ai_enable(ac97, ucontrol->value.integer.value[0]);
return 1;
}
static struct snd_kcontrol_new snd_cs5535audio_controls __devinitdata =
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "DC Mode Enable",
.info = snd_cs5535audio_ai_info,
.get = snd_cs5535audio_ai_get,
.put = snd_cs5535audio_ai_put,
.private_value = 0
};
void __devinit olpc_prequirks(struct snd_card *card,
struct snd_ac97_template *ac97)
{
/* Bail if this isn't an OLPC platform */
if (!machine_is_olpc())
return;
/* If on an OLPC B3 or higher, invert EAPD. */
if (olpc_rev_after(OLPC_REV_B2))
ac97->scaps |= AC97_SCAP_INV_EAPD;
}
int __devinit olpc_quirks(struct snd_card *card, struct snd_ac97 *ac97)
{
struct snd_ctl_elem_id elem;
/* Bail if this isn't an OLPC platform */
if (!machine_is_olpc())
return 0;
/* drop the original ad1888 HPF control */
memset(&elem, 0, sizeof(elem));
elem.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
strcpy(elem.name, "High Pass Filter Enable");
snd_ctl_remove_id(card, &elem);
/* add the override for OLPC's HPF */
return snd_ctl_add(card, snd_ctl_new1(&snd_cs5535audio_controls,
ac97->private_data));
}