Fixed MTP to work with TWRP

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

View file

@ -0,0 +1,5 @@
obj-$(CONFIG_OLPC) += olpc.o olpc_ofw.o olpc_dt.o
obj-$(CONFIG_OLPC_XO1_PM) += olpc-xo1-pm.o xo1-wakeup.o
obj-$(CONFIG_OLPC_XO1_RTC) += olpc-xo1-rtc.o
obj-$(CONFIG_OLPC_XO1_SCI) += olpc-xo1-sci.o
obj-$(CONFIG_OLPC_XO15_SCI) += olpc-xo15-sci.o

View file

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

View file

@ -0,0 +1,81 @@
/*
* Support for OLPC XO-1 Real Time Clock (RTC)
*
* Copyright (C) 2011 One Laptop per Child
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/mc146818rtc.h>
#include <linux/platform_device.h>
#include <linux/rtc.h>
#include <linux/of.h>
#include <asm/msr.h>
#include <asm/olpc.h>
static void rtc_wake_on(struct device *dev)
{
olpc_xo1_pm_wakeup_set(CS5536_PM_RTC);
}
static void rtc_wake_off(struct device *dev)
{
olpc_xo1_pm_wakeup_clear(CS5536_PM_RTC);
}
static struct resource rtc_platform_resource[] = {
[0] = {
.start = RTC_PORT(0),
.end = RTC_PORT(1),
.flags = IORESOURCE_IO,
},
[1] = {
.start = RTC_IRQ,
.end = RTC_IRQ,
.flags = IORESOURCE_IRQ,
}
};
static struct cmos_rtc_board_info rtc_info = {
.rtc_day_alarm = 0,
.rtc_mon_alarm = 0,
.rtc_century = 0,
.wake_on = rtc_wake_on,
.wake_off = rtc_wake_off,
};
static struct platform_device xo1_rtc_device = {
.name = "rtc_cmos",
.id = -1,
.num_resources = ARRAY_SIZE(rtc_platform_resource),
.dev.platform_data = &rtc_info,
.resource = rtc_platform_resource,
};
static int __init xo1_rtc_init(void)
{
int r;
struct device_node *node;
node = of_find_compatible_node(NULL, NULL, "olpc,xo1-rtc");
if (!node)
return 0;
of_node_put(node);
pr_info("olpc-xo1-rtc: Initializing OLPC XO-1 RTC\n");
rdmsrl(MSR_RTC_DOMA_OFFSET, rtc_info.rtc_day_alarm);
rdmsrl(MSR_RTC_MONA_OFFSET, rtc_info.rtc_mon_alarm);
rdmsrl(MSR_RTC_CEN_OFFSET, rtc_info.rtc_century);
r = platform_device_register(&xo1_rtc_device);
if (r)
return r;
device_init_wakeup(&xo1_rtc_device.dev, 1);
return 0;
}
arch_initcall(xo1_rtc_init);

View file

@ -0,0 +1,642 @@
/*
* Support for OLPC XO-1 System Control Interrupts (SCI)
*
* Copyright (C) 2010 One Laptop per Child
* Copyright (C) 2006 Red Hat, Inc.
* Copyright (C) 2006 Advanced Micro Devices, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/cs5535.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/pm_wakeup.h>
#include <linux/mfd/core.h>
#include <linux/power_supply.h>
#include <linux/suspend.h>
#include <linux/workqueue.h>
#include <linux/olpc-ec.h>
#include <asm/io.h>
#include <asm/msr.h>
#include <asm/olpc.h>
#define DRV_NAME "olpc-xo1-sci"
#define PFX DRV_NAME ": "
static unsigned long acpi_base;
static struct input_dev *power_button_idev;
static struct input_dev *ebook_switch_idev;
static struct input_dev *lid_switch_idev;
static int sci_irq;
static bool lid_open;
static bool lid_inverted;
static int lid_wake_mode;
enum lid_wake_modes {
LID_WAKE_ALWAYS,
LID_WAKE_OPEN,
LID_WAKE_CLOSE,
};
static const char * const lid_wake_mode_names[] = {
[LID_WAKE_ALWAYS] = "always",
[LID_WAKE_OPEN] = "open",
[LID_WAKE_CLOSE] = "close",
};
static void battery_status_changed(void)
{
struct power_supply *psy = power_supply_get_by_name("olpc-battery");
if (psy) {
power_supply_changed(psy);
put_device(psy->dev);
}
}
static void ac_status_changed(void)
{
struct power_supply *psy = power_supply_get_by_name("olpc-ac");
if (psy) {
power_supply_changed(psy);
put_device(psy->dev);
}
}
/* Report current ebook switch state through input layer */
static void send_ebook_state(void)
{
unsigned char state;
if (olpc_ec_cmd(EC_READ_EB_MODE, NULL, 0, &state, 1)) {
pr_err(PFX "failed to get ebook state\n");
return;
}
if (!!test_bit(SW_TABLET_MODE, ebook_switch_idev->sw) == state)
return; /* Nothing new to report. */
input_report_switch(ebook_switch_idev, SW_TABLET_MODE, state);
input_sync(ebook_switch_idev);
pm_wakeup_event(&ebook_switch_idev->dev, 0);
}
static void flip_lid_inverter(void)
{
/* gpio is high; invert so we'll get l->h event interrupt */
if (lid_inverted)
cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_INPUT_INVERT);
else
cs5535_gpio_set(OLPC_GPIO_LID, GPIO_INPUT_INVERT);
lid_inverted = !lid_inverted;
}
static void detect_lid_state(void)
{
/*
* the edge detector hookup on the gpio inputs on the geode is
* odd, to say the least. See http://dev.laptop.org/ticket/5703
* for details, but in a nutshell: we don't use the edge
* detectors. instead, we make use of an anomoly: with the both
* edge detectors turned off, we still get an edge event on a
* positive edge transition. to take advantage of this, we use the
* front-end inverter to ensure that that's the edge we're always
* going to see next.
*/
int state;
state = cs5535_gpio_isset(OLPC_GPIO_LID, GPIO_READ_BACK);
lid_open = !state ^ !lid_inverted; /* x ^^ y */
if (!state)
return;
flip_lid_inverter();
}
/* Report current lid switch state through input layer */
static void send_lid_state(void)
{
if (!!test_bit(SW_LID, lid_switch_idev->sw) == !lid_open)
return; /* Nothing new to report. */
input_report_switch(lid_switch_idev, SW_LID, !lid_open);
input_sync(lid_switch_idev);
pm_wakeup_event(&lid_switch_idev->dev, 0);
}
static ssize_t lid_wake_mode_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
const char *mode = lid_wake_mode_names[lid_wake_mode];
return sprintf(buf, "%s\n", mode);
}
static ssize_t lid_wake_mode_set(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int i;
for (i = 0; i < ARRAY_SIZE(lid_wake_mode_names); i++) {
const char *mode = lid_wake_mode_names[i];
if (strlen(mode) != count || strncasecmp(mode, buf, count))
continue;
lid_wake_mode = i;
return count;
}
return -EINVAL;
}
static DEVICE_ATTR(lid_wake_mode, S_IWUSR | S_IRUGO, lid_wake_mode_show,
lid_wake_mode_set);
/*
* Process all items in the EC's SCI queue.
*
* This is handled in a workqueue because olpc_ec_cmd can be slow (and
* can even timeout).
*
* If propagate_events is false, the queue is drained without events being
* generated for the interrupts.
*/
static void process_sci_queue(bool propagate_events)
{
int r;
u16 data;
do {
r = olpc_ec_sci_query(&data);
if (r || !data)
break;
pr_debug(PFX "SCI 0x%x received\n", data);
switch (data) {
case EC_SCI_SRC_BATERR:
case EC_SCI_SRC_BATSOC:
case EC_SCI_SRC_BATTERY:
case EC_SCI_SRC_BATCRIT:
battery_status_changed();
break;
case EC_SCI_SRC_ACPWR:
ac_status_changed();
break;
}
if (data == EC_SCI_SRC_EBOOK && propagate_events)
send_ebook_state();
} while (data);
if (r)
pr_err(PFX "Failed to clear SCI queue");
}
static void process_sci_queue_work(struct work_struct *work)
{
process_sci_queue(true);
}
static DECLARE_WORK(sci_work, process_sci_queue_work);
static irqreturn_t xo1_sci_intr(int irq, void *dev_id)
{
struct platform_device *pdev = dev_id;
u32 sts;
u32 gpe;
sts = inl(acpi_base + CS5536_PM1_STS);
outl(sts | 0xffff, acpi_base + CS5536_PM1_STS);
gpe = inl(acpi_base + CS5536_PM_GPE0_STS);
outl(0xffffffff, acpi_base + CS5536_PM_GPE0_STS);
dev_dbg(&pdev->dev, "sts %x gpe %x\n", sts, gpe);
if (sts & CS5536_PWRBTN_FLAG) {
if (!(sts & CS5536_WAK_FLAG)) {
/* Only report power button input when it was pressed
* during regular operation (as opposed to when it
* was used to wake the system). */
input_report_key(power_button_idev, KEY_POWER, 1);
input_sync(power_button_idev);
input_report_key(power_button_idev, KEY_POWER, 0);
input_sync(power_button_idev);
}
/* Report the wakeup event in all cases. */
pm_wakeup_event(&power_button_idev->dev, 0);
}
if ((sts & (CS5536_RTC_FLAG | CS5536_WAK_FLAG)) ==
(CS5536_RTC_FLAG | CS5536_WAK_FLAG)) {
/* When the system is woken by the RTC alarm, report the
* event on the rtc device. */
struct device *rtc = bus_find_device_by_name(
&platform_bus_type, NULL, "rtc_cmos");
if (rtc) {
pm_wakeup_event(rtc, 0);
put_device(rtc);
}
}
if (gpe & CS5536_GPIOM7_PME_FLAG) { /* EC GPIO */
cs5535_gpio_set(OLPC_GPIO_ECSCI, GPIO_NEGATIVE_EDGE_STS);
schedule_work(&sci_work);
}
cs5535_gpio_set(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_STS);
cs5535_gpio_set(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_STS);
detect_lid_state();
send_lid_state();
return IRQ_HANDLED;
}
static int xo1_sci_suspend(struct platform_device *pdev, pm_message_t state)
{
if (device_may_wakeup(&power_button_idev->dev))
olpc_xo1_pm_wakeup_set(CS5536_PM_PWRBTN);
else
olpc_xo1_pm_wakeup_clear(CS5536_PM_PWRBTN);
if (device_may_wakeup(&ebook_switch_idev->dev))
olpc_ec_wakeup_set(EC_SCI_SRC_EBOOK);
else
olpc_ec_wakeup_clear(EC_SCI_SRC_EBOOK);
if (!device_may_wakeup(&lid_switch_idev->dev)) {
cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE);
} else if ((lid_open && lid_wake_mode == LID_WAKE_OPEN) ||
(!lid_open && lid_wake_mode == LID_WAKE_CLOSE)) {
flip_lid_inverter();
/* we may have just caused an event */
cs5535_gpio_set(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_STS);
cs5535_gpio_set(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_STS);
cs5535_gpio_set(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE);
}
return 0;
}
static int xo1_sci_resume(struct platform_device *pdev)
{
/*
* We don't know what may have happened while we were asleep.
* Reestablish our lid setup so we're sure to catch all transitions.
*/
detect_lid_state();
send_lid_state();
cs5535_gpio_set(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE);
/* Enable all EC events */
olpc_ec_mask_write(EC_SCI_SRC_ALL);
/* Power/battery status might have changed too */
battery_status_changed();
ac_status_changed();
return 0;
}
static int setup_sci_interrupt(struct platform_device *pdev)
{
u32 lo, hi;
u32 sts;
int r;
rdmsr(0x51400020, lo, hi);
sci_irq = (lo >> 20) & 15;
if (sci_irq) {
dev_info(&pdev->dev, "SCI is mapped to IRQ %d\n", sci_irq);
} else {
/* Zero means masked */
dev_info(&pdev->dev, "SCI unmapped. Mapping to IRQ 3\n");
sci_irq = 3;
lo |= 0x00300000;
wrmsrl(0x51400020, lo);
}
/* Select level triggered in PIC */
if (sci_irq < 8) {
lo = inb(CS5536_PIC_INT_SEL1);
lo |= 1 << sci_irq;
outb(lo, CS5536_PIC_INT_SEL1);
} else {
lo = inb(CS5536_PIC_INT_SEL2);
lo |= 1 << (sci_irq - 8);
outb(lo, CS5536_PIC_INT_SEL2);
}
/* Enable interesting SCI events, and clear pending interrupts */
sts = inl(acpi_base + CS5536_PM1_STS);
outl(((CS5536_PM_PWRBTN | CS5536_PM_RTC) << 16) | 0xffff,
acpi_base + CS5536_PM1_STS);
r = request_irq(sci_irq, xo1_sci_intr, 0, DRV_NAME, pdev);
if (r)
dev_err(&pdev->dev, "can't request interrupt\n");
return r;
}
static int setup_ec_sci(void)
{
int r;
r = gpio_request(OLPC_GPIO_ECSCI, "OLPC-ECSCI");
if (r)
return r;
gpio_direction_input(OLPC_GPIO_ECSCI);
/* Clear pending EC SCI events */
cs5535_gpio_set(OLPC_GPIO_ECSCI, GPIO_NEGATIVE_EDGE_STS);
cs5535_gpio_set(OLPC_GPIO_ECSCI, GPIO_POSITIVE_EDGE_STS);
/*
* Enable EC SCI events, and map them to both a PME and the SCI
* interrupt.
*
* Ordinarily, in addition to functioning as GPIOs, Geode GPIOs can
* be mapped to regular interrupts *or* Geode-specific Power
* Management Events (PMEs) - events that bring the system out of
* suspend. In this case, we want both of those things - the system
* wakeup, *and* the ability to get an interrupt when an event occurs.
*
* To achieve this, we map the GPIO to a PME, and then we use one
* of the many generic knobs on the CS5535 PIC to additionally map the
* PME to the regular SCI interrupt line.
*/
cs5535_gpio_set(OLPC_GPIO_ECSCI, GPIO_EVENTS_ENABLE);
/* Set the SCI to cause a PME event on group 7 */
cs5535_gpio_setup_event(OLPC_GPIO_ECSCI, 7, 1);
/* And have group 7 also fire the SCI interrupt */
cs5535_pic_unreqz_select_high(7, sci_irq);
return 0;
}
static void free_ec_sci(void)
{
gpio_free(OLPC_GPIO_ECSCI);
}
static int setup_lid_events(void)
{
int r;
r = gpio_request(OLPC_GPIO_LID, "OLPC-LID");
if (r)
return r;
gpio_direction_input(OLPC_GPIO_LID);
cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_INPUT_INVERT);
lid_inverted = 0;
/* Clear edge detection and event enable for now */
cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE);
cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_EN);
cs5535_gpio_clear(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_EN);
cs5535_gpio_set(OLPC_GPIO_LID, GPIO_NEGATIVE_EDGE_STS);
cs5535_gpio_set(OLPC_GPIO_LID, GPIO_POSITIVE_EDGE_STS);
/* Set the LID to cause an PME event on group 6 */
cs5535_gpio_setup_event(OLPC_GPIO_LID, 6, 1);
/* Set PME group 6 to fire the SCI interrupt */
cs5535_gpio_set_irq(6, sci_irq);
/* Enable the event */
cs5535_gpio_set(OLPC_GPIO_LID, GPIO_EVENTS_ENABLE);
return 0;
}
static void free_lid_events(void)
{
gpio_free(OLPC_GPIO_LID);
}
static int setup_power_button(struct platform_device *pdev)
{
int r;
power_button_idev = input_allocate_device();
if (!power_button_idev)
return -ENOMEM;
power_button_idev->name = "Power Button";
power_button_idev->phys = DRV_NAME "/input0";
set_bit(EV_KEY, power_button_idev->evbit);
set_bit(KEY_POWER, power_button_idev->keybit);
power_button_idev->dev.parent = &pdev->dev;
device_init_wakeup(&power_button_idev->dev, 1);
r = input_register_device(power_button_idev);
if (r) {
dev_err(&pdev->dev, "failed to register power button: %d\n", r);
input_free_device(power_button_idev);
}
return r;
}
static void free_power_button(void)
{
input_unregister_device(power_button_idev);
}
static int setup_ebook_switch(struct platform_device *pdev)
{
int r;
ebook_switch_idev = input_allocate_device();
if (!ebook_switch_idev)
return -ENOMEM;
ebook_switch_idev->name = "EBook Switch";
ebook_switch_idev->phys = DRV_NAME "/input1";
set_bit(EV_SW, ebook_switch_idev->evbit);
set_bit(SW_TABLET_MODE, ebook_switch_idev->swbit);
ebook_switch_idev->dev.parent = &pdev->dev;
device_set_wakeup_capable(&ebook_switch_idev->dev, true);
r = input_register_device(ebook_switch_idev);
if (r) {
dev_err(&pdev->dev, "failed to register ebook switch: %d\n", r);
input_free_device(ebook_switch_idev);
}
return r;
}
static void free_ebook_switch(void)
{
input_unregister_device(ebook_switch_idev);
}
static int setup_lid_switch(struct platform_device *pdev)
{
int r;
lid_switch_idev = input_allocate_device();
if (!lid_switch_idev)
return -ENOMEM;
lid_switch_idev->name = "Lid Switch";
lid_switch_idev->phys = DRV_NAME "/input2";
set_bit(EV_SW, lid_switch_idev->evbit);
set_bit(SW_LID, lid_switch_idev->swbit);
lid_switch_idev->dev.parent = &pdev->dev;
device_set_wakeup_capable(&lid_switch_idev->dev, true);
r = input_register_device(lid_switch_idev);
if (r) {
dev_err(&pdev->dev, "failed to register lid switch: %d\n", r);
goto err_register;
}
r = device_create_file(&lid_switch_idev->dev, &dev_attr_lid_wake_mode);
if (r) {
dev_err(&pdev->dev, "failed to create wake mode attr: %d\n", r);
goto err_create_attr;
}
return 0;
err_create_attr:
input_unregister_device(lid_switch_idev);
lid_switch_idev = NULL;
err_register:
input_free_device(lid_switch_idev);
return r;
}
static void free_lid_switch(void)
{
device_remove_file(&lid_switch_idev->dev, &dev_attr_lid_wake_mode);
input_unregister_device(lid_switch_idev);
}
static int xo1_sci_probe(struct platform_device *pdev)
{
struct resource *res;
int r;
/* don't run on non-XOs */
if (!machine_is_olpc())
return -ENODEV;
r = mfd_cell_enable(pdev);
if (r)
return r;
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
if (!res) {
dev_err(&pdev->dev, "can't fetch device resource info\n");
return -EIO;
}
acpi_base = res->start;
r = setup_power_button(pdev);
if (r)
return r;
r = setup_ebook_switch(pdev);
if (r)
goto err_ebook;
r = setup_lid_switch(pdev);
if (r)
goto err_lid;
r = setup_lid_events();
if (r)
goto err_lidevt;
r = setup_ec_sci();
if (r)
goto err_ecsci;
/* Enable PME generation for EC-generated events */
outl(CS5536_GPIOM6_PME_EN | CS5536_GPIOM7_PME_EN,
acpi_base + CS5536_PM_GPE0_EN);
/* Clear pending events */
outl(0xffffffff, acpi_base + CS5536_PM_GPE0_STS);
process_sci_queue(false);
/* Initial sync */
send_ebook_state();
detect_lid_state();
send_lid_state();
r = setup_sci_interrupt(pdev);
if (r)
goto err_sci;
/* Enable all EC events */
olpc_ec_mask_write(EC_SCI_SRC_ALL);
return r;
err_sci:
free_ec_sci();
err_ecsci:
free_lid_events();
err_lidevt:
free_lid_switch();
err_lid:
free_ebook_switch();
err_ebook:
free_power_button();
return r;
}
static int xo1_sci_remove(struct platform_device *pdev)
{
mfd_cell_disable(pdev);
free_irq(sci_irq, pdev);
cancel_work_sync(&sci_work);
free_ec_sci();
free_lid_events();
free_lid_switch();
free_ebook_switch();
free_power_button();
acpi_base = 0;
return 0;
}
static struct platform_driver xo1_sci_driver = {
.driver = {
.name = "olpc-xo1-sci-acpi",
},
.probe = xo1_sci_probe,
.remove = xo1_sci_remove,
.suspend = xo1_sci_suspend,
.resume = xo1_sci_resume,
};
static int __init xo1_sci_init(void)
{
return platform_driver_register(&xo1_sci_driver);
}
arch_initcall(xo1_sci_init);

View file

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

View file

@ -0,0 +1,410 @@
/*
* Support for the OLPC DCON and OLPC EC access
*
* Copyright © 2006 Advanced Micro Devices, Inc.
* Copyright © 2007-2008 Andres Salomon <dilinger@debian.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/string.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/syscore_ops.h>
#include <linux/mutex.h>
#include <linux/olpc-ec.h>
#include <asm/geode.h>
#include <asm/setup.h>
#include <asm/olpc.h>
#include <asm/olpc_ofw.h>
struct olpc_platform_t olpc_platform_info;
EXPORT_SYMBOL_GPL(olpc_platform_info);
/* EC event mask to be applied during suspend (defining wakeup sources). */
static u16 ec_wakeup_mask;
/* what the timeout *should* be (in ms) */
#define EC_BASE_TIMEOUT 20
/* the timeout that bugs in the EC might force us to actually use */
static int ec_timeout = EC_BASE_TIMEOUT;
static int __init olpc_ec_timeout_set(char *str)
{
if (get_option(&str, &ec_timeout) != 1) {
ec_timeout = EC_BASE_TIMEOUT;
printk(KERN_ERR "olpc-ec: invalid argument to "
"'olpc_ec_timeout=', ignoring!\n");
}
printk(KERN_DEBUG "olpc-ec: using %d ms delay for EC commands.\n",
ec_timeout);
return 1;
}
__setup("olpc_ec_timeout=", olpc_ec_timeout_set);
/*
* These {i,o}bf_status functions return whether the buffers are full or not.
*/
static inline unsigned int ibf_status(unsigned int port)
{
return !!(inb(port) & 0x02);
}
static inline unsigned int obf_status(unsigned int port)
{
return inb(port) & 0x01;
}
#define wait_on_ibf(p, d) __wait_on_ibf(__LINE__, (p), (d))
static int __wait_on_ibf(unsigned int line, unsigned int port, int desired)
{
unsigned int timeo;
int state = ibf_status(port);
for (timeo = ec_timeout; state != desired && timeo; timeo--) {
mdelay(1);
state = ibf_status(port);
}
if ((state == desired) && (ec_timeout > EC_BASE_TIMEOUT) &&
timeo < (ec_timeout - EC_BASE_TIMEOUT)) {
printk(KERN_WARNING "olpc-ec: %d: waited %u ms for IBF!\n",
line, ec_timeout - timeo);
}
return !(state == desired);
}
#define wait_on_obf(p, d) __wait_on_obf(__LINE__, (p), (d))
static int __wait_on_obf(unsigned int line, unsigned int port, int desired)
{
unsigned int timeo;
int state = obf_status(port);
for (timeo = ec_timeout; state != desired && timeo; timeo--) {
mdelay(1);
state = obf_status(port);
}
if ((state == desired) && (ec_timeout > EC_BASE_TIMEOUT) &&
timeo < (ec_timeout - EC_BASE_TIMEOUT)) {
printk(KERN_WARNING "olpc-ec: %d: waited %u ms for OBF!\n",
line, ec_timeout - timeo);
}
return !(state == desired);
}
/*
* This allows the kernel to run Embedded Controller commands. The EC is
* documented at <http://wiki.laptop.org/go/Embedded_controller>, and the
* available EC commands are here:
* <http://wiki.laptop.org/go/Ec_specification>. Unfortunately, while
* OpenFirmware's source is available, the EC's is not.
*/
static int olpc_xo1_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *outbuf,
size_t outlen, void *arg)
{
int ret = -EIO;
int i;
int restarts = 0;
/* Clear OBF */
for (i = 0; i < 10 && (obf_status(0x6c) == 1); i++)
inb(0x68);
if (i == 10) {
printk(KERN_ERR "olpc-ec: timeout while attempting to "
"clear OBF flag!\n");
goto err;
}
if (wait_on_ibf(0x6c, 0)) {
printk(KERN_ERR "olpc-ec: timeout waiting for EC to "
"quiesce!\n");
goto err;
}
restart:
/*
* Note that if we time out during any IBF checks, that's a failure;
* we have to return. There's no way for the kernel to clear that.
*
* If we time out during an OBF check, we can restart the command;
* reissuing it will clear the OBF flag, and we should be alright.
* The OBF flag will sometimes misbehave due to what we believe
* is a hardware quirk..
*/
pr_devel("olpc-ec: running cmd 0x%x\n", cmd);
outb(cmd, 0x6c);
if (wait_on_ibf(0x6c, 0)) {
printk(KERN_ERR "olpc-ec: timeout waiting for EC to read "
"command!\n");
goto err;
}
if (inbuf && inlen) {
/* write data to EC */
for (i = 0; i < inlen; i++) {
pr_devel("olpc-ec: sending cmd arg 0x%x\n", inbuf[i]);
outb(inbuf[i], 0x68);
if (wait_on_ibf(0x6c, 0)) {
printk(KERN_ERR "olpc-ec: timeout waiting for"
" EC accept data!\n");
goto err;
}
}
}
if (outbuf && outlen) {
/* read data from EC */
for (i = 0; i < outlen; i++) {
if (wait_on_obf(0x6c, 1)) {
printk(KERN_ERR "olpc-ec: timeout waiting for"
" EC to provide data!\n");
if (restarts++ < 10)
goto restart;
goto err;
}
outbuf[i] = inb(0x68);
pr_devel("olpc-ec: received 0x%x\n", outbuf[i]);
}
}
ret = 0;
err:
return ret;
}
void olpc_ec_wakeup_set(u16 value)
{
ec_wakeup_mask |= value;
}
EXPORT_SYMBOL_GPL(olpc_ec_wakeup_set);
void olpc_ec_wakeup_clear(u16 value)
{
ec_wakeup_mask &= ~value;
}
EXPORT_SYMBOL_GPL(olpc_ec_wakeup_clear);
/*
* Returns true if the compile and runtime configurations allow for EC events
* to wake the system.
*/
bool olpc_ec_wakeup_available(void)
{
if (!machine_is_olpc())
return false;
/*
* XO-1 EC wakeups are available when olpc-xo1-sci driver is
* compiled in
*/
#ifdef CONFIG_OLPC_XO1_SCI
if (olpc_platform_info.boardrev < olpc_board_pre(0xd0)) /* XO-1 */
return true;
#endif
/*
* XO-1.5 EC wakeups are available when olpc-xo15-sci driver is
* compiled in
*/
#ifdef CONFIG_OLPC_XO15_SCI
if (olpc_platform_info.boardrev >= olpc_board_pre(0xd0)) /* XO-1.5 */
return true;
#endif
return false;
}
EXPORT_SYMBOL_GPL(olpc_ec_wakeup_available);
int olpc_ec_mask_write(u16 bits)
{
if (olpc_platform_info.flags & OLPC_F_EC_WIDE_SCI) {
__be16 ec_word = cpu_to_be16(bits);
return olpc_ec_cmd(EC_WRITE_EXT_SCI_MASK, (void *) &ec_word, 2,
NULL, 0);
} else {
unsigned char ec_byte = bits & 0xff;
return olpc_ec_cmd(EC_WRITE_SCI_MASK, &ec_byte, 1, NULL, 0);
}
}
EXPORT_SYMBOL_GPL(olpc_ec_mask_write);
int olpc_ec_sci_query(u16 *sci_value)
{
int ret;
if (olpc_platform_info.flags & OLPC_F_EC_WIDE_SCI) {
__be16 ec_word;
ret = olpc_ec_cmd(EC_EXT_SCI_QUERY,
NULL, 0, (void *) &ec_word, 2);
if (ret == 0)
*sci_value = be16_to_cpu(ec_word);
} else {
unsigned char ec_byte;
ret = olpc_ec_cmd(EC_SCI_QUERY, NULL, 0, &ec_byte, 1);
if (ret == 0)
*sci_value = ec_byte;
}
return ret;
}
EXPORT_SYMBOL_GPL(olpc_ec_sci_query);
static bool __init check_ofw_architecture(struct device_node *root)
{
const char *olpc_arch;
int propsize;
olpc_arch = of_get_property(root, "architecture", &propsize);
return propsize == 5 && strncmp("OLPC", olpc_arch, 5) == 0;
}
static u32 __init get_board_revision(struct device_node *root)
{
int propsize;
const __be32 *rev;
rev = of_get_property(root, "board-revision-int", &propsize);
if (propsize != 4)
return 0;
return be32_to_cpu(*rev);
}
static bool __init platform_detect(void)
{
struct device_node *root = of_find_node_by_path("/");
bool success;
if (!root)
return false;
success = check_ofw_architecture(root);
if (success) {
olpc_platform_info.boardrev = get_board_revision(root);
olpc_platform_info.flags |= OLPC_F_PRESENT;
}
of_node_put(root);
return success;
}
static int __init add_xo1_platform_devices(void)
{
struct platform_device *pdev;
pdev = platform_device_register_simple("xo1-rfkill", -1, NULL, 0);
if (IS_ERR(pdev))
return PTR_ERR(pdev);
pdev = platform_device_register_simple("olpc-xo1", -1, NULL, 0);
if (IS_ERR(pdev))
return PTR_ERR(pdev);
return 0;
}
static int olpc_xo1_ec_probe(struct platform_device *pdev)
{
/* get the EC revision */
olpc_ec_cmd(EC_FIRMWARE_REV, NULL, 0,
(unsigned char *) &olpc_platform_info.ecver, 1);
/* EC version 0x5f adds support for wide SCI mask */
if (olpc_platform_info.ecver >= 0x5f)
olpc_platform_info.flags |= OLPC_F_EC_WIDE_SCI;
pr_info("OLPC board revision %s%X (EC=%x)\n",
((olpc_platform_info.boardrev & 0xf) < 8) ? "pre" : "",
olpc_platform_info.boardrev >> 4,
olpc_platform_info.ecver);
return 0;
}
static int olpc_xo1_ec_suspend(struct platform_device *pdev)
{
olpc_ec_mask_write(ec_wakeup_mask);
/*
* Squelch SCIs while suspended. This is a fix for
* <http://dev.laptop.org/ticket/1835>.
*/
return olpc_ec_cmd(EC_SET_SCI_INHIBIT, NULL, 0, NULL, 0);
}
static int olpc_xo1_ec_resume(struct platform_device *pdev)
{
/* Tell the EC to stop inhibiting SCIs */
olpc_ec_cmd(EC_SET_SCI_INHIBIT_RELEASE, NULL, 0, NULL, 0);
/*
* Tell the wireless module to restart USB communication.
* Must be done twice.
*/
olpc_ec_cmd(EC_WAKE_UP_WLAN, NULL, 0, NULL, 0);
olpc_ec_cmd(EC_WAKE_UP_WLAN, NULL, 0, NULL, 0);
return 0;
}
static struct olpc_ec_driver ec_xo1_driver = {
.probe = olpc_xo1_ec_probe,
.suspend = olpc_xo1_ec_suspend,
.resume = olpc_xo1_ec_resume,
.ec_cmd = olpc_xo1_ec_cmd,
};
static struct olpc_ec_driver ec_xo1_5_driver = {
.probe = olpc_xo1_ec_probe,
.ec_cmd = olpc_xo1_ec_cmd,
};
static int __init olpc_init(void)
{
int r = 0;
if (!olpc_ofw_present() || !platform_detect())
return 0;
/* register the XO-1 and 1.5-specific EC handler */
if (olpc_platform_info.boardrev < olpc_board_pre(0xd0)) /* XO-1 */
olpc_ec_driver_register(&ec_xo1_driver, NULL);
else
olpc_ec_driver_register(&ec_xo1_5_driver, NULL);
platform_device_register_simple("olpc-ec", -1, NULL, 0);
/* assume B1 and above models always have a DCON */
if (olpc_board_at_least(olpc_board(0xb1)))
olpc_platform_info.flags |= OLPC_F_DCON;
#ifdef CONFIG_PCI_OLPC
/* If the VSA exists let it emulate PCI, if not emulate in kernel.
* XO-1 only. */
if (olpc_platform_info.boardrev < olpc_board_pre(0xd0) &&
!cs5535_has_vsa2())
x86_init.pci.arch_init = pci_olpc_init;
#endif
if (olpc_platform_info.boardrev < olpc_board_pre(0xd0)) { /* XO-1 */
r = add_xo1_platform_devices();
if (r)
return r;
}
return 0;
}
postcore_initcall(olpc_init);

View file

@ -0,0 +1,304 @@
/*
* OLPC-specific OFW device tree support code.
*
* Paul Mackerras August 1996.
* Copyright (C) 1996-2005 Paul Mackerras.
*
* Adapted for 64bit PowerPC by Dave Engebretsen and Peter Bergner.
* {engebret|bergner}@us.ibm.com
*
* Adapted for sparc by David S. Miller davem@davemloft.net
* Adapted for x86/OLPC by Andres Salomon <dilinger@queued.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <linux/kernel.h>
#include <linux/bootmem.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_pdt.h>
#include <asm/olpc.h>
#include <asm/olpc_ofw.h>
static phandle __init olpc_dt_getsibling(phandle node)
{
const void *args[] = { (void *)node };
void *res[] = { &node };
if ((s32)node == -1)
return 0;
if (olpc_ofw("peer", args, res) || (s32)node == -1)
return 0;
return node;
}
static phandle __init olpc_dt_getchild(phandle node)
{
const void *args[] = { (void *)node };
void *res[] = { &node };
if ((s32)node == -1)
return 0;
if (olpc_ofw("child", args, res) || (s32)node == -1) {
pr_err("PROM: %s: fetching child failed!\n", __func__);
return 0;
}
return node;
}
static int __init olpc_dt_getproplen(phandle node, const char *prop)
{
const void *args[] = { (void *)node, prop };
int len;
void *res[] = { &len };
if ((s32)node == -1)
return -1;
if (olpc_ofw("getproplen", args, res)) {
pr_err("PROM: %s: getproplen failed!\n", __func__);
return -1;
}
return len;
}
static int __init olpc_dt_getproperty(phandle node, const char *prop,
char *buf, int bufsize)
{
int plen;
plen = olpc_dt_getproplen(node, prop);
if (plen > bufsize || plen < 1) {
return -1;
} else {
const void *args[] = { (void *)node, prop, buf, (void *)plen };
void *res[] = { &plen };
if (olpc_ofw("getprop", args, res)) {
pr_err("PROM: %s: getprop failed!\n", __func__);
return -1;
}
}
return plen;
}
static int __init olpc_dt_nextprop(phandle node, char *prev, char *buf)
{
const void *args[] = { (void *)node, prev, buf };
int success;
void *res[] = { &success };
buf[0] = '\0';
if ((s32)node == -1)
return -1;
if (olpc_ofw("nextprop", args, res) || success != 1)
return -1;
return 0;
}
static int __init olpc_dt_pkg2path(phandle node, char *buf,
const int buflen, int *len)
{
const void *args[] = { (void *)node, buf, (void *)buflen };
void *res[] = { len };
if ((s32)node == -1)
return -1;
if (olpc_ofw("package-to-path", args, res) || *len < 1)
return -1;
return 0;
}
static unsigned int prom_early_allocated __initdata;
void * __init prom_early_alloc(unsigned long size)
{
static u8 *mem;
static size_t free_mem;
void *res;
if (free_mem < size) {
const size_t chunk_size = max(PAGE_SIZE, size);
/*
* To mimimize the number of allocations, grab at least
* PAGE_SIZE of memory (that's an arbitrary choice that's
* fast enough on the platforms we care about while minimizing
* wasted bootmem) and hand off chunks of it to callers.
*/
res = alloc_bootmem(chunk_size);
BUG_ON(!res);
prom_early_allocated += chunk_size;
memset(res, 0, chunk_size);
free_mem = chunk_size;
mem = res;
}
/* allocate from the local cache */
free_mem -= size;
res = mem;
mem += size;
return res;
}
static struct of_pdt_ops prom_olpc_ops __initdata = {
.nextprop = olpc_dt_nextprop,
.getproplen = olpc_dt_getproplen,
.getproperty = olpc_dt_getproperty,
.getchild = olpc_dt_getchild,
.getsibling = olpc_dt_getsibling,
.pkg2path = olpc_dt_pkg2path,
};
static phandle __init olpc_dt_finddevice(const char *path)
{
phandle node;
const void *args[] = { path };
void *res[] = { &node };
if (olpc_ofw("finddevice", args, res)) {
pr_err("olpc_dt: finddevice failed!\n");
return 0;
}
if ((s32) node == -1)
return 0;
return node;
}
static int __init olpc_dt_interpret(const char *words)
{
int result;
const void *args[] = { words };
void *res[] = { &result };
if (olpc_ofw("interpret", args, res)) {
pr_err("olpc_dt: interpret failed!\n");
return -1;
}
return result;
}
/*
* Extract board revision directly from OFW device tree.
* We can't use olpc_platform_info because that hasn't been set up yet.
*/
static u32 __init olpc_dt_get_board_revision(void)
{
phandle node;
__be32 rev;
int r;
node = olpc_dt_finddevice("/");
if (!node)
return 0;
r = olpc_dt_getproperty(node, "board-revision-int",
(char *) &rev, sizeof(rev));
if (r < 0)
return 0;
return be32_to_cpu(rev);
}
void __init olpc_dt_fixup(void)
{
int r;
char buf[64];
phandle node;
u32 board_rev;
node = olpc_dt_finddevice("/battery@0");
if (!node)
return;
/*
* If the battery node has a compatible property, we are running a new
* enough firmware and don't have fixups to make.
*/
r = olpc_dt_getproperty(node, "compatible", buf, sizeof(buf));
if (r > 0)
return;
pr_info("PROM DT: Old firmware detected, applying fixes\n");
/* Add olpc,xo1-battery compatible marker to battery node */
olpc_dt_interpret("\" /battery@0\" find-device"
" \" olpc,xo1-battery\" +compatible"
" device-end");
board_rev = olpc_dt_get_board_revision();
if (!board_rev)
return;
if (board_rev >= olpc_board_pre(0xd0)) {
/* XO-1.5: add dcon device */
olpc_dt_interpret("\" /pci/display@1\" find-device"
" new-device"
" \" dcon\" device-name \" olpc,xo1-dcon\" +compatible"
" finish-device device-end");
} else {
/* XO-1: add dcon device, mark RTC as olpc,xo1-rtc */
olpc_dt_interpret("\" /pci/display@1,1\" find-device"
" new-device"
" \" dcon\" device-name \" olpc,xo1-dcon\" +compatible"
" finish-device device-end"
" \" /rtc\" find-device"
" \" olpc,xo1-rtc\" +compatible"
" device-end");
}
}
void __init olpc_dt_build_devicetree(void)
{
phandle root;
if (!olpc_ofw_is_installed())
return;
olpc_dt_fixup();
root = olpc_dt_getsibling(0);
if (!root) {
pr_err("PROM: unable to get root node from OFW!\n");
return;
}
of_pdt_build_devicetree(root, &prom_olpc_ops);
pr_info("PROM DT: Built device tree with %u bytes of memory.\n",
prom_early_allocated);
}
/* A list of DT node/bus matches that we want to expose as platform devices */
static struct of_device_id __initdata of_ids[] = {
{ .compatible = "olpc,xo1-battery" },
{ .compatible = "olpc,xo1-dcon" },
{ .compatible = "olpc,xo1-rtc" },
{},
};
static int __init olpc_create_platform_devices(void)
{
if (machine_is_olpc())
return of_platform_bus_probe(NULL, of_ids, NULL);
else
return 0;
}
device_initcall(olpc_create_platform_devices);

View file

@ -0,0 +1,117 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <asm/page.h>
#include <asm/setup.h>
#include <asm/io.h>
#include <asm/pgtable.h>
#include <asm/olpc_ofw.h>
/* address of OFW callback interface; will be NULL if OFW isn't found */
static int (*olpc_ofw_cif)(int *);
/* page dir entry containing OFW's pgdir table; filled in by head_32.S */
u32 olpc_ofw_pgd __initdata;
static DEFINE_SPINLOCK(ofw_lock);
#define MAXARGS 10
void __init setup_olpc_ofw_pgd(void)
{
pgd_t *base, *ofw_pde;
if (!olpc_ofw_cif)
return;
/* fetch OFW's PDE */
base = early_ioremap(olpc_ofw_pgd, sizeof(olpc_ofw_pgd) * PTRS_PER_PGD);
if (!base) {
printk(KERN_ERR "failed to remap OFW's pgd - disabling OFW!\n");
olpc_ofw_cif = NULL;
return;
}
ofw_pde = &base[OLPC_OFW_PDE_NR];
/* install OFW's PDE permanently into the kernel's pgtable */
set_pgd(&swapper_pg_dir[OLPC_OFW_PDE_NR], *ofw_pde);
/* implicit optimization barrier here due to uninline function return */
early_iounmap(base, sizeof(olpc_ofw_pgd) * PTRS_PER_PGD);
}
int __olpc_ofw(const char *name, int nr_args, const void **args, int nr_res,
void **res)
{
int ofw_args[MAXARGS + 3];
unsigned long flags;
int ret, i, *p;
BUG_ON(nr_args + nr_res > MAXARGS);
if (!olpc_ofw_cif)
return -EIO;
ofw_args[0] = (int)name;
ofw_args[1] = nr_args;
ofw_args[2] = nr_res;
p = &ofw_args[3];
for (i = 0; i < nr_args; i++, p++)
*p = (int)args[i];
/* call into ofw */
spin_lock_irqsave(&ofw_lock, flags);
ret = olpc_ofw_cif(ofw_args);
spin_unlock_irqrestore(&ofw_lock, flags);
if (!ret) {
for (i = 0; i < nr_res; i++, p++)
*((int *)res[i]) = *p;
}
return ret;
}
EXPORT_SYMBOL_GPL(__olpc_ofw);
bool olpc_ofw_present(void)
{
return olpc_ofw_cif != NULL;
}
EXPORT_SYMBOL_GPL(olpc_ofw_present);
/* OFW cif _should_ be above this address */
#define OFW_MIN 0xff000000
/* OFW starts on a 1MB boundary */
#define OFW_BOUND (1<<20)
void __init olpc_ofw_detect(void)
{
struct olpc_ofw_header *hdr = &boot_params.olpc_ofw_header;
unsigned long start;
/* ensure OFW booted us by checking for "OFW " string */
if (hdr->ofw_magic != OLPC_OFW_SIG)
return;
olpc_ofw_cif = (int (*)(int *))hdr->cif_handler;
if ((unsigned long)olpc_ofw_cif < OFW_MIN) {
printk(KERN_ERR "OFW detected, but cif has invalid address 0x%lx - disabling.\n",
(unsigned long)olpc_ofw_cif);
olpc_ofw_cif = NULL;
return;
}
/* determine where OFW starts in memory */
start = round_down((unsigned long)olpc_ofw_cif, OFW_BOUND);
printk(KERN_INFO "OFW detected in memory, cif @ 0x%lx (reserving top %ldMB)\n",
(unsigned long)olpc_ofw_cif, (-start) >> 20);
reserve_top_address(-start);
}
bool __init olpc_ofw_is_installed(void)
{
return olpc_ofw_cif != NULL;
}

View file

@ -0,0 +1,124 @@
.text
#include <linux/linkage.h>
#include <asm/segment.h>
#include <asm/page.h>
#include <asm/pgtable_32.h>
.macro writepost,value
movb $0x34, %al
outb %al, $0x70
movb $\value, %al
outb %al, $0x71
.endm
wakeup_start:
# OFW lands us here, running in protected mode, with a
# kernel-compatible GDT already setup.
# Clear any dangerous flags
pushl $0
popfl
writepost 0x31
# Set up %cr3
movl $initial_page_table - __PAGE_OFFSET, %eax
movl %eax, %cr3
movl saved_cr4, %eax
movl %eax, %cr4
movl saved_cr0, %eax
movl %eax, %cr0
# Control registers were modified, pipeline resync is needed
jmp 1f
1:
movw $__KERNEL_DS, %ax
movw %ax, %ss
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
lgdt saved_gdt
lidt saved_idt
lldt saved_ldt
ljmp $(__KERNEL_CS),$1f
1:
movl %cr3, %eax
movl %eax, %cr3
wbinvd
# Go back to the return point
jmp ret_point
save_registers:
sgdt saved_gdt
sidt saved_idt
sldt saved_ldt
pushl %edx
movl %cr4, %edx
movl %edx, saved_cr4
movl %cr0, %edx
movl %edx, saved_cr0
popl %edx
movl %ebx, saved_context_ebx
movl %ebp, saved_context_ebp
movl %esi, saved_context_esi
movl %edi, saved_context_edi
pushfl
popl saved_context_eflags
ret
restore_registers:
movl saved_context_ebp, %ebp
movl saved_context_ebx, %ebx
movl saved_context_esi, %esi
movl saved_context_edi, %edi
pushl saved_context_eflags
popfl
ret
ENTRY(do_olpc_suspend_lowlevel)
call save_processor_state
call save_registers
# This is the stack context we want to remember
movl %esp, saved_context_esp
pushl $3
call xo1_do_sleep
jmp wakeup_start
.p2align 4,,7
ret_point:
movl saved_context_esp, %esp
writepost 0x32
call restore_registers
call restore_processor_state
ret
.data
saved_gdt: .long 0,0
saved_idt: .long 0,0
saved_ldt: .long 0
saved_cr4: .long 0
saved_cr0: .long 0
saved_context_esp: .long 0
saved_context_edi: .long 0
saved_context_esi: .long 0
saved_context_ebx: .long 0
saved_context_ebp: .long 0
saved_context_eflags: .long 0