add idl4k kernel firmware version 1.13.0.105

This commit is contained in:
Jaroslav Kysela
2015-03-26 17:22:37 +01:00
parent 5194d2792e
commit e9070cdc77
31064 changed files with 12769984 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
config PPC_PMAC
bool "Apple PowerMac based machines"
depends on PPC_BOOK3S
select MPIC
select PCI
select PPC_INDIRECT_PCI if PPC32
select PPC_MPC106 if PPC32
select PPC_NATIVE
default y
config PPC_PMAC64
bool
depends on PPC_PMAC && POWER4
select MPIC
select U3_DART
select MPIC_U3_HT_IRQS
select GENERIC_TBSYNC
select PPC_970_NAP
default y

View File

@@ -0,0 +1,21 @@
CFLAGS_bootx_init.o += -fPIC
ifdef CONFIG_FUNCTION_TRACER
# Do not trace early boot code
CFLAGS_REMOVE_bootx_init.o = -pg -mno-sched-epilog
endif
obj-y += pic.o setup.o time.o feature.o pci.o \
sleep.o low_i2c.o cache.o pfunc_core.o \
pfunc_base.o udbg_scc.o udbg_adb.o
obj-$(CONFIG_PMAC_BACKLIGHT) += backlight.o
obj-$(CONFIG_CPU_FREQ_PMAC) += cpufreq_32.o
obj-$(CONFIG_CPU_FREQ_PMAC64) += cpufreq_64.o
# CONFIG_NVRAM is an arch. independant tristate symbol, for pmac32 we really
# need this to be a bool. Cheat here and pretend CONFIG_NVRAM=m is really
# CONFIG_NVRAM=y
obj-$(CONFIG_NVRAM:m=y) += nvram.o
# ppc64 pmac doesn't define CONFIG_NVRAM but needs nvram stuff
obj-$(CONFIG_PPC64) += nvram.o
obj-$(CONFIG_PPC32) += bootx_init.o
obj-$(CONFIG_SMP) += smp.o

View File

@@ -0,0 +1,219 @@
/*
* Miscellaneous procedures for dealing with the PowerMac hardware.
* Contains support for the backlight.
*
* Copyright (C) 2000 Benjamin Herrenschmidt
* Copyright (C) 2006 Michael Hanselmann <linux-kernel@hansmi.ch>
*
*/
#include <linux/kernel.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/adb.h>
#include <linux/pmu.h>
#include <asm/atomic.h>
#include <asm/prom.h>
#include <asm/backlight.h>
#define OLD_BACKLIGHT_MAX 15
static void pmac_backlight_key_worker(struct work_struct *work);
static void pmac_backlight_set_legacy_worker(struct work_struct *work);
static DECLARE_WORK(pmac_backlight_key_work, pmac_backlight_key_worker);
static DECLARE_WORK(pmac_backlight_set_legacy_work, pmac_backlight_set_legacy_worker);
/* Although these variables are used in interrupt context, it makes no sense to
* protect them. No user is able to produce enough key events per second and
* notice the errors that might happen.
*/
static int pmac_backlight_key_queued;
static int pmac_backlight_set_legacy_queued;
/* The via-pmu code allows the backlight to be grabbed, in which case the
* in-kernel control of the brightness needs to be disabled. This should
* only be used by really old PowerBooks.
*/
static atomic_t kernel_backlight_disabled = ATOMIC_INIT(0);
/* Protect the pmac_backlight variable below.
You should hold this lock when using the pmac_backlight pointer to
prevent its potential removal. */
DEFINE_MUTEX(pmac_backlight_mutex);
/* Main backlight storage
*
* Backlight drivers in this variable are required to have the "ops"
* attribute set and to have an update_status function.
*
* We can only store one backlight here, but since Apple laptops have only one
* internal display, it doesn't matter. Other backlight drivers can be used
* independently.
*
*/
struct backlight_device *pmac_backlight;
int pmac_has_backlight_type(const char *type)
{
struct device_node* bk_node = of_find_node_by_name(NULL, "backlight");
if (bk_node) {
const char *prop = of_get_property(bk_node,
"backlight-control", NULL);
if (prop && strncmp(prop, type, strlen(type)) == 0) {
of_node_put(bk_node);
return 1;
}
of_node_put(bk_node);
}
return 0;
}
int pmac_backlight_curve_lookup(struct fb_info *info, int value)
{
int level = (FB_BACKLIGHT_LEVELS - 1);
if (info && info->bl_dev) {
int i, max = 0;
/* Look for biggest value */
for (i = 0; i < FB_BACKLIGHT_LEVELS; i++)
max = max((int)info->bl_curve[i], max);
/* Look for nearest value */
for (i = 0; i < FB_BACKLIGHT_LEVELS; i++) {
int diff = abs(info->bl_curve[i] - value);
if (diff < max) {
max = diff;
level = i;
}
}
}
return level;
}
static void pmac_backlight_key_worker(struct work_struct *work)
{
if (atomic_read(&kernel_backlight_disabled))
return;
mutex_lock(&pmac_backlight_mutex);
if (pmac_backlight) {
struct backlight_properties *props;
int brightness;
props = &pmac_backlight->props;
brightness = props->brightness +
((pmac_backlight_key_queued?-1:1) *
(props->max_brightness / 15));
if (brightness < 0)
brightness = 0;
else if (brightness > props->max_brightness)
brightness = props->max_brightness;
props->brightness = brightness;
backlight_update_status(pmac_backlight);
}
mutex_unlock(&pmac_backlight_mutex);
}
/* This function is called in interrupt context */
void pmac_backlight_key(int direction)
{
if (atomic_read(&kernel_backlight_disabled))
return;
/* we can receive multiple interrupts here, but the scheduled work
* will run only once, with the last value
*/
pmac_backlight_key_queued = direction;
schedule_work(&pmac_backlight_key_work);
}
static int __pmac_backlight_set_legacy_brightness(int brightness)
{
int error = -ENXIO;
mutex_lock(&pmac_backlight_mutex);
if (pmac_backlight) {
struct backlight_properties *props;
props = &pmac_backlight->props;
props->brightness = brightness *
(props->max_brightness + 1) /
(OLD_BACKLIGHT_MAX + 1);
if (props->brightness > props->max_brightness)
props->brightness = props->max_brightness;
else if (props->brightness < 0)
props->brightness = 0;
backlight_update_status(pmac_backlight);
error = 0;
}
mutex_unlock(&pmac_backlight_mutex);
return error;
}
static void pmac_backlight_set_legacy_worker(struct work_struct *work)
{
if (atomic_read(&kernel_backlight_disabled))
return;
__pmac_backlight_set_legacy_brightness(pmac_backlight_set_legacy_queued);
}
/* This function is called in interrupt context */
void pmac_backlight_set_legacy_brightness_pmu(int brightness) {
if (atomic_read(&kernel_backlight_disabled))
return;
pmac_backlight_set_legacy_queued = brightness;
schedule_work(&pmac_backlight_set_legacy_work);
}
int pmac_backlight_set_legacy_brightness(int brightness)
{
return __pmac_backlight_set_legacy_brightness(brightness);
}
int pmac_backlight_get_legacy_brightness()
{
int result = -ENXIO;
mutex_lock(&pmac_backlight_mutex);
if (pmac_backlight) {
struct backlight_properties *props;
props = &pmac_backlight->props;
result = props->brightness *
(OLD_BACKLIGHT_MAX + 1) /
(props->max_brightness + 1);
}
mutex_unlock(&pmac_backlight_mutex);
return result;
}
void pmac_backlight_disable()
{
atomic_inc(&kernel_backlight_disabled);
}
void pmac_backlight_enable()
{
atomic_dec(&kernel_backlight_disabled);
}
EXPORT_SYMBOL_GPL(pmac_backlight);
EXPORT_SYMBOL_GPL(pmac_backlight_mutex);
EXPORT_SYMBOL_GPL(pmac_has_backlight_type);

View File

@@ -0,0 +1,594 @@
/*
* Early boot support code for BootX bootloader
*
* Copyright (C) 2005 Ben. Herrenschmidt (benh@kernel.crashing.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/string.h>
#include <linux/init.h>
#include <linux/utsrelease.h>
#include <asm/sections.h>
#include <asm/prom.h>
#include <asm/page.h>
#include <asm/bootx.h>
#include <asm/btext.h>
#include <asm/io.h>
#undef DEBUG
#define SET_BOOT_BAT
#ifdef DEBUG
#define DBG(fmt...) do { bootx_printf(fmt); } while(0)
#else
#define DBG(fmt...) do { } while(0)
#endif
extern void __start(unsigned long r3, unsigned long r4, unsigned long r5);
static unsigned long __initdata bootx_dt_strbase;
static unsigned long __initdata bootx_dt_strend;
static unsigned long __initdata bootx_node_chosen;
static boot_infos_t * __initdata bootx_info;
static char __initdata bootx_disp_path[256];
/* Is boot-info compatible ? */
#define BOOT_INFO_IS_COMPATIBLE(bi) \
((bi)->compatible_version <= BOOT_INFO_VERSION)
#define BOOT_INFO_IS_V2_COMPATIBLE(bi) ((bi)->version >= 2)
#define BOOT_INFO_IS_V4_COMPATIBLE(bi) ((bi)->version >= 4)
#ifdef CONFIG_BOOTX_TEXT
static void __init bootx_printf(const char *format, ...)
{
const char *p, *q, *s;
va_list args;
unsigned long v;
va_start(args, format);
for (p = format; *p != 0; p = q) {
for (q = p; *q != 0 && *q != '\n' && *q != '%'; ++q)
;
if (q > p)
btext_drawtext(p, q - p);
if (*q == 0)
break;
if (*q == '\n') {
++q;
btext_flushline();
btext_drawstring("\r\n");
btext_flushline();
continue;
}
++q;
if (*q == 0)
break;
switch (*q) {
case 's':
++q;
s = va_arg(args, const char *);
if (s == NULL)
s = "<NULL>";
btext_drawstring(s);
break;
case 'x':
++q;
v = va_arg(args, unsigned long);
btext_drawhex(v);
break;
}
}
}
#else /* CONFIG_BOOTX_TEXT */
static void __init bootx_printf(const char *format, ...) {}
#endif /* CONFIG_BOOTX_TEXT */
static void * __init bootx_early_getprop(unsigned long base,
unsigned long node,
char *prop)
{
struct bootx_dt_node *np = (struct bootx_dt_node *)(base + node);
u32 *ppp = &np->properties;
while(*ppp) {
struct bootx_dt_prop *pp =
(struct bootx_dt_prop *)(base + *ppp);
if (strcmp((char *)((unsigned long)pp->name + base),
prop) == 0) {
return (void *)((unsigned long)pp->value + base);
}
ppp = &pp->next;
}
return NULL;
}
#define dt_push_token(token, mem) \
do { \
*(mem) = _ALIGN_UP(*(mem),4); \
*((u32 *)*(mem)) = token; \
*(mem) += 4; \
} while(0)
static unsigned long __init bootx_dt_find_string(char *str)
{
char *s, *os;
s = os = (char *)bootx_dt_strbase;
s += 4;
while (s < (char *)bootx_dt_strend) {
if (strcmp(s, str) == 0)
return s - os;
s += strlen(s) + 1;
}
return 0;
}
static void __init bootx_dt_add_prop(char *name, void *data, int size,
unsigned long *mem_end)
{
unsigned long soff = bootx_dt_find_string(name);
if (data == NULL)
size = 0;
if (soff == 0) {
bootx_printf("WARNING: Can't find string index for <%s>\n",
name);
return;
}
if (size > 0x20000) {
bootx_printf("WARNING: ignoring large property ");
bootx_printf("%s length 0x%x\n", name, size);
return;
}
dt_push_token(OF_DT_PROP, mem_end);
dt_push_token(size, mem_end);
dt_push_token(soff, mem_end);
/* push property content */
if (size && data) {
memcpy((void *)*mem_end, data, size);
*mem_end = _ALIGN_UP(*mem_end + size, 4);
}
}
static void __init bootx_add_chosen_props(unsigned long base,
unsigned long *mem_end)
{
u32 val;
bootx_dt_add_prop("linux,bootx", NULL, 0, mem_end);
if (bootx_info->kernelParamsOffset) {
char *args = (char *)((unsigned long)bootx_info) +
bootx_info->kernelParamsOffset;
bootx_dt_add_prop("bootargs", args, strlen(args) + 1, mem_end);
}
if (bootx_info->ramDisk) {
val = ((unsigned long)bootx_info) + bootx_info->ramDisk;
bootx_dt_add_prop("linux,initrd-start", &val, 4, mem_end);
val += bootx_info->ramDiskSize;
bootx_dt_add_prop("linux,initrd-end", &val, 4, mem_end);
}
if (strlen(bootx_disp_path))
bootx_dt_add_prop("linux,stdout-path", bootx_disp_path,
strlen(bootx_disp_path) + 1, mem_end);
}
static void __init bootx_add_display_props(unsigned long base,
unsigned long *mem_end,
int has_real_node)
{
boot_infos_t *bi = bootx_info;
u32 tmp;
if (has_real_node) {
bootx_dt_add_prop("linux,boot-display", NULL, 0, mem_end);
bootx_dt_add_prop("linux,opened", NULL, 0, mem_end);
} else
bootx_dt_add_prop("linux,bootx-noscreen", NULL, 0, mem_end);
tmp = bi->dispDeviceDepth;
bootx_dt_add_prop("linux,bootx-depth", &tmp, 4, mem_end);
tmp = bi->dispDeviceRect[2] - bi->dispDeviceRect[0];
bootx_dt_add_prop("linux,bootx-width", &tmp, 4, mem_end);
tmp = bi->dispDeviceRect[3] - bi->dispDeviceRect[1];
bootx_dt_add_prop("linux,bootx-height", &tmp, 4, mem_end);
tmp = bi->dispDeviceRowBytes;
bootx_dt_add_prop("linux,bootx-linebytes", &tmp, 4, mem_end);
tmp = (u32)bi->dispDeviceBase;
if (tmp == 0)
tmp = (u32)bi->logicalDisplayBase;
tmp += bi->dispDeviceRect[1] * bi->dispDeviceRowBytes;
tmp += bi->dispDeviceRect[0] * ((bi->dispDeviceDepth + 7) / 8);
bootx_dt_add_prop("linux,bootx-addr", &tmp, 4, mem_end);
}
static void __init bootx_dt_add_string(char *s, unsigned long *mem_end)
{
unsigned int l = strlen(s) + 1;
memcpy((void *)*mem_end, s, l);
bootx_dt_strend = *mem_end = *mem_end + l;
}
static void __init bootx_scan_dt_build_strings(unsigned long base,
unsigned long node,
unsigned long *mem_end)
{
struct bootx_dt_node *np = (struct bootx_dt_node *)(base + node);
u32 *cpp, *ppp = &np->properties;
unsigned long soff;
char *namep;
/* Keep refs to known nodes */
namep = np->full_name ? (char *)(base + np->full_name) : NULL;
if (namep == NULL) {
bootx_printf("Node without a full name !\n");
namep = "";
}
DBG("* strings: %s\n", namep);
if (!strcmp(namep, "/chosen")) {
DBG(" detected /chosen ! adding properties names !\n");
bootx_dt_add_string("linux,bootx", mem_end);
bootx_dt_add_string("linux,stdout-path", mem_end);
bootx_dt_add_string("linux,initrd-start", mem_end);
bootx_dt_add_string("linux,initrd-end", mem_end);
bootx_dt_add_string("bootargs", mem_end);
bootx_node_chosen = node;
}
if (node == bootx_info->dispDeviceRegEntryOffset) {
DBG(" detected display ! adding properties names !\n");
bootx_dt_add_string("linux,boot-display", mem_end);
bootx_dt_add_string("linux,opened", mem_end);
strncpy(bootx_disp_path, namep, 255);
}
/* get and store all property names */
while (*ppp) {
struct bootx_dt_prop *pp =
(struct bootx_dt_prop *)(base + *ppp);
namep = pp->name ? (char *)(base + pp->name) : NULL;
if (namep == NULL || strcmp(namep, "name") == 0)
goto next;
/* get/create string entry */
soff = bootx_dt_find_string(namep);
if (soff == 0)
bootx_dt_add_string(namep, mem_end);
next:
ppp = &pp->next;
}
/* do all our children */
cpp = &np->child;
while(*cpp) {
np = (struct bootx_dt_node *)(base + *cpp);
bootx_scan_dt_build_strings(base, *cpp, mem_end);
cpp = &np->sibling;
}
}
static void __init bootx_scan_dt_build_struct(unsigned long base,
unsigned long node,
unsigned long *mem_end)
{
struct bootx_dt_node *np = (struct bootx_dt_node *)(base + node);
u32 *cpp, *ppp = &np->properties;
char *namep, *p, *ep, *lp;
int l;
dt_push_token(OF_DT_BEGIN_NODE, mem_end);
/* get the node's full name */
namep = np->full_name ? (char *)(base + np->full_name) : NULL;
if (namep == NULL)
namep = "";
l = strlen(namep);
DBG("* struct: %s\n", namep);
/* Fixup an Apple bug where they have bogus \0 chars in the
* middle of the path in some properties, and extract
* the unit name (everything after the last '/').
*/
memcpy((void *)*mem_end, namep, l + 1);
namep = (char *)*mem_end;
for (lp = p = namep, ep = namep + l; p < ep; p++) {
if (*p == '/')
lp = namep;
else if (*p != 0)
*lp++ = *p;
}
*lp = 0;
*mem_end = _ALIGN_UP((unsigned long)lp + 1, 4);
/* get and store all properties */
while (*ppp) {
struct bootx_dt_prop *pp =
(struct bootx_dt_prop *)(base + *ppp);
namep = pp->name ? (char *)(base + pp->name) : NULL;
/* Skip "name" */
if (namep == NULL || !strcmp(namep, "name"))
goto next;
/* Skip "bootargs" in /chosen too as we replace it */
if (node == bootx_node_chosen && !strcmp(namep, "bootargs"))
goto next;
/* push property head */
bootx_dt_add_prop(namep,
pp->value ? (void *)(base + pp->value): NULL,
pp->length, mem_end);
next:
ppp = &pp->next;
}
if (node == bootx_node_chosen) {
bootx_add_chosen_props(base, mem_end);
if (bootx_info->dispDeviceRegEntryOffset == 0)
bootx_add_display_props(base, mem_end, 0);
}
else if (node == bootx_info->dispDeviceRegEntryOffset)
bootx_add_display_props(base, mem_end, 1);
/* do all our children */
cpp = &np->child;
while(*cpp) {
np = (struct bootx_dt_node *)(base + *cpp);
bootx_scan_dt_build_struct(base, *cpp, mem_end);
cpp = &np->sibling;
}
dt_push_token(OF_DT_END_NODE, mem_end);
}
static unsigned long __init bootx_flatten_dt(unsigned long start)
{
boot_infos_t *bi = bootx_info;
unsigned long mem_start, mem_end;
struct boot_param_header *hdr;
unsigned long base;
u64 *rsvmap;
/* Start using memory after the big blob passed by BootX, get
* some space for the header
*/
mem_start = mem_end = _ALIGN_UP(((unsigned long)bi) + start, 4);
DBG("Boot params header at: %x\n", mem_start);
hdr = (struct boot_param_header *)mem_start;
mem_end += sizeof(struct boot_param_header);
rsvmap = (u64 *)(_ALIGN_UP(mem_end, 8));
hdr->off_mem_rsvmap = ((unsigned long)rsvmap) - mem_start;
mem_end = ((unsigned long)rsvmap) + 8 * sizeof(u64);
/* Get base of tree */
base = ((unsigned long)bi) + bi->deviceTreeOffset;
/* Build string array */
DBG("Building string array at: %x\n", mem_end);
DBG("Device Tree Base=%x\n", base);
bootx_dt_strbase = mem_end;
mem_end += 4;
bootx_dt_strend = mem_end;
bootx_scan_dt_build_strings(base, 4, &mem_end);
/* Add some strings */
bootx_dt_add_string("linux,bootx-noscreen", &mem_end);
bootx_dt_add_string("linux,bootx-depth", &mem_end);
bootx_dt_add_string("linux,bootx-width", &mem_end);
bootx_dt_add_string("linux,bootx-height", &mem_end);
bootx_dt_add_string("linux,bootx-linebytes", &mem_end);
bootx_dt_add_string("linux,bootx-addr", &mem_end);
/* Wrap up strings */
hdr->off_dt_strings = bootx_dt_strbase - mem_start;
hdr->dt_strings_size = bootx_dt_strend - bootx_dt_strbase;
/* Build structure */
mem_end = _ALIGN(mem_end, 16);
DBG("Building device tree structure at: %x\n", mem_end);
hdr->off_dt_struct = mem_end - mem_start;
bootx_scan_dt_build_struct(base, 4, &mem_end);
dt_push_token(OF_DT_END, &mem_end);
/* Finish header */
hdr->boot_cpuid_phys = 0;
hdr->magic = OF_DT_HEADER;
hdr->totalsize = mem_end - mem_start;
hdr->version = OF_DT_VERSION;
/* Version 16 is not backward compatible */
hdr->last_comp_version = 0x10;
/* Reserve the whole thing and copy the reserve map in, we
* also bump mem_reserve_cnt to cause further reservations to
* fail since it's too late.
*/
mem_end = _ALIGN(mem_end, PAGE_SIZE);
DBG("End of boot params: %x\n", mem_end);
rsvmap[0] = mem_start;
rsvmap[1] = mem_end;
if (bootx_info->ramDisk) {
rsvmap[2] = ((unsigned long)bootx_info) + bootx_info->ramDisk;
rsvmap[3] = rsvmap[2] + bootx_info->ramDiskSize;
rsvmap[4] = 0;
rsvmap[5] = 0;
} else {
rsvmap[2] = 0;
rsvmap[3] = 0;
}
return (unsigned long)hdr;
}
#ifdef CONFIG_BOOTX_TEXT
static void __init btext_welcome(boot_infos_t *bi)
{
unsigned long flags;
unsigned long pvr;
bootx_printf("Welcome to Linux, kernel " UTS_RELEASE "\n");
bootx_printf("\nlinked at : 0x%x", KERNELBASE);
bootx_printf("\nframe buffer at : 0x%x", bi->dispDeviceBase);
bootx_printf(" (phys), 0x%x", bi->logicalDisplayBase);
bootx_printf(" (log)");
bootx_printf("\nklimit : 0x%x",(unsigned long)klimit);
bootx_printf("\nboot_info at : 0x%x", bi);
__asm__ __volatile__ ("mfmsr %0" : "=r" (flags));
bootx_printf("\nMSR : 0x%x", flags);
__asm__ __volatile__ ("mfspr %0, 287" : "=r" (pvr));
bootx_printf("\nPVR : 0x%x", pvr);
pvr >>= 16;
if (pvr > 1) {
__asm__ __volatile__ ("mfspr %0, 1008" : "=r" (flags));
bootx_printf("\nHID0 : 0x%x", flags);
}
if (pvr == 8 || pvr == 12 || pvr == 0x800c) {
__asm__ __volatile__ ("mfspr %0, 1019" : "=r" (flags));
bootx_printf("\nICTC : 0x%x", flags);
}
#ifdef DEBUG
bootx_printf("\n\n");
bootx_printf("bi->deviceTreeOffset : 0x%x\n",
bi->deviceTreeOffset);
bootx_printf("bi->deviceTreeSize : 0x%x\n",
bi->deviceTreeSize);
#endif
bootx_printf("\n\n");
}
#endif /* CONFIG_BOOTX_TEXT */
void __init bootx_init(unsigned long r3, unsigned long r4)
{
boot_infos_t *bi = (boot_infos_t *) r4;
unsigned long hdr;
unsigned long space;
unsigned long ptr, x;
char *model;
unsigned long offset = reloc_offset();
reloc_got2(offset);
bootx_info = bi;
/* We haven't cleared any bss at this point, make sure
* what we need is initialized
*/
bootx_dt_strbase = bootx_dt_strend = 0;
bootx_node_chosen = 0;
bootx_disp_path[0] = 0;
if (!BOOT_INFO_IS_V2_COMPATIBLE(bi))
bi->logicalDisplayBase = bi->dispDeviceBase;
/* Fixup depth 16 -> 15 as that's what MacOS calls 16bpp */
if (bi->dispDeviceDepth == 16)
bi->dispDeviceDepth = 15;
#ifdef CONFIG_BOOTX_TEXT
ptr = (unsigned long)bi->logicalDisplayBase;
ptr += bi->dispDeviceRect[1] * bi->dispDeviceRowBytes;
ptr += bi->dispDeviceRect[0] * ((bi->dispDeviceDepth + 7) / 8);
btext_setup_display(bi->dispDeviceRect[2] - bi->dispDeviceRect[0],
bi->dispDeviceRect[3] - bi->dispDeviceRect[1],
bi->dispDeviceDepth, bi->dispDeviceRowBytes,
(unsigned long)bi->logicalDisplayBase);
btext_clearscreen();
btext_flushscreen();
#endif /* CONFIG_BOOTX_TEXT */
/*
* Test if boot-info is compatible. Done only in config
* CONFIG_BOOTX_TEXT since there is nothing much we can do
* with an incompatible version, except display a message
* and eventually hang the processor...
*
* I'll try to keep enough of boot-info compatible in the
* future to always allow display of this message;
*/
if (!BOOT_INFO_IS_COMPATIBLE(bi)) {
bootx_printf(" !!! WARNING - Incompatible version"
" of BootX !!!\n\n\n");
for (;;)
;
}
if (bi->architecture != BOOT_ARCH_PCI) {
bootx_printf(" !!! WARNING - Usupported machine"
" architecture !\n");
for (;;)
;
}
#ifdef CONFIG_BOOTX_TEXT
btext_welcome(bi);
#endif
/* New BootX enters kernel with MMU off, i/os are not allowed
* here. This hack will have been done by the boostrap anyway.
*/
if (bi->version < 4) {
/*
* XXX If this is an iMac, turn off the USB controller.
*/
model = (char *) bootx_early_getprop(r4 + bi->deviceTreeOffset,
4, "model");
if (model
&& (strcmp(model, "iMac,1") == 0
|| strcmp(model, "PowerMac1,1") == 0)) {
bootx_printf("iMac,1 detected, shutting down USB \n");
out_le32((unsigned __iomem *)0x80880008, 1); /* XXX */
}
}
/* Get a pointer that points above the device tree, args, ramdisk,
* etc... to use for generating the flattened tree
*/
if (bi->version < 5) {
space = bi->deviceTreeOffset + bi->deviceTreeSize;
if (bi->ramDisk >= space)
space = bi->ramDisk + bi->ramDiskSize;
} else
space = bi->totalParamsSize;
bootx_printf("Total space used by parameters & ramdisk: 0x%x \n", space);
/* New BootX will have flushed all TLBs and enters kernel with
* MMU switched OFF, so this should not be useful anymore.
*/
if (bi->version < 4) {
bootx_printf("Touching pages...\n");
/*
* Touch each page to make sure the PTEs for them
* are in the hash table - the aim is to try to avoid
* getting DSI exceptions while copying the kernel image.
*/
for (ptr = ((unsigned long) &_stext) & PAGE_MASK;
ptr < (unsigned long)bi + space; ptr += PAGE_SIZE)
x = *(volatile unsigned long *)ptr;
}
/* Ok, now we need to generate a flattened device-tree to pass
* to the kernel
*/
bootx_printf("Preparing boot params...\n");
hdr = bootx_flatten_dt(space);
#ifdef CONFIG_BOOTX_TEXT
#ifdef SET_BOOT_BAT
bootx_printf("Preparing BAT...\n");
btext_prepare_BAT();
#else
btext_unmap();
#endif
#endif
reloc_got2(-offset);
__start(hdr, KERNELBASE + offset, 0);
}

View File

@@ -0,0 +1,358 @@
/*
* This file contains low-level cache management functions
* used for sleep and CPU speed changes on Apple machines.
* (In fact the only thing that is Apple-specific is that we assume
* that we can read from ROM at physical address 0xfff00000.)
*
* Copyright (C) 2004 Paul Mackerras (paulus@samba.org) and
* Benjamin Herrenschmidt (benh@kernel.crashing.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 <asm/processor.h>
#include <asm/ppc_asm.h>
#include <asm/cputable.h>
/*
* Flush and disable all data caches (dL1, L2, L3). This is used
* when going to sleep, when doing a PMU based cpufreq transition,
* or when "offlining" a CPU on SMP machines. This code is over
* paranoid, but I've had enough issues with various CPU revs and
* bugs that I decided it was worth beeing over cautious
*/
_GLOBAL(flush_disable_caches)
#ifndef CONFIG_6xx
blr
#else
BEGIN_FTR_SECTION
b flush_disable_745x
END_FTR_SECTION_IFSET(CPU_FTR_SPEC7450)
BEGIN_FTR_SECTION
b flush_disable_75x
END_FTR_SECTION_IFSET(CPU_FTR_L2CR)
b __flush_disable_L1
/* This is the code for G3 and 74[01]0 */
flush_disable_75x:
mflr r10
/* Turn off EE and DR in MSR */
mfmsr r11
rlwinm r0,r11,0,~MSR_EE
rlwinm r0,r0,0,~MSR_DR
sync
mtmsr r0
isync
/* Stop DST streams */
BEGIN_FTR_SECTION
DSSALL
sync
END_FTR_SECTION_IFSET(CPU_FTR_ALTIVEC)
/* Stop DPM */
mfspr r8,SPRN_HID0 /* Save SPRN_HID0 in r8 */
rlwinm r4,r8,0,12,10 /* Turn off HID0[DPM] */
sync
mtspr SPRN_HID0,r4 /* Disable DPM */
sync
/* Disp-flush L1. We have a weird problem here that I never
* totally figured out. On 750FX, using the ROM for the flush
* results in a non-working flush. We use that workaround for
* now until I finally understand what's going on. --BenH
*/
/* ROM base by default */
lis r4,0xfff0
mfpvr r3
srwi r3,r3,16
cmplwi cr0,r3,0x7000
bne+ 1f
/* RAM base on 750FX */
li r4,0
1: li r4,0x4000
mtctr r4
1: lwz r0,0(r4)
addi r4,r4,32
bdnz 1b
sync
isync
/* Disable / invalidate / enable L1 data */
mfspr r3,SPRN_HID0
rlwinm r3,r3,0,~(HID0_DCE | HID0_ICE)
mtspr SPRN_HID0,r3
sync
isync
ori r3,r3,(HID0_DCE|HID0_DCI|HID0_ICE|HID0_ICFI)
sync
isync
mtspr SPRN_HID0,r3
xori r3,r3,(HID0_DCI|HID0_ICFI)
mtspr SPRN_HID0,r3
sync
/* Get the current enable bit of the L2CR into r4 */
mfspr r5,SPRN_L2CR
/* Set to data-only (pre-745x bit) */
oris r3,r5,L2CR_L2DO@h
b 2f
/* When disabling L2, code must be in L1 */
.balign 32
1: mtspr SPRN_L2CR,r3
3: sync
isync
b 1f
2: b 3f
3: sync
isync
b 1b
1: /* disp-flush L2. The interesting thing here is that the L2 can be
* up to 2Mb ... so using the ROM, we'll end up wrapping back to memory
* but that is probbaly fine. We disp-flush over 4Mb to be safe
*/
lis r4,2
mtctr r4
lis r4,0xfff0
1: lwz r0,0(r4)
addi r4,r4,32
bdnz 1b
sync
isync
lis r4,2
mtctr r4
lis r4,0xfff0
1: dcbf 0,r4
addi r4,r4,32
bdnz 1b
sync
isync
/* now disable L2 */
rlwinm r5,r5,0,~L2CR_L2E
b 2f
/* When disabling L2, code must be in L1 */
.balign 32
1: mtspr SPRN_L2CR,r5
3: sync
isync
b 1f
2: b 3f
3: sync
isync
b 1b
1: sync
isync
/* Invalidate L2. This is pre-745x, we clear the L2I bit ourselves */
oris r4,r5,L2CR_L2I@h
mtspr SPRN_L2CR,r4
sync
isync
/* Wait for the invalidation to complete */
1: mfspr r3,SPRN_L2CR
rlwinm. r0,r3,0,31,31
bne 1b
/* Clear L2I */
xoris r4,r4,L2CR_L2I@h
sync
mtspr SPRN_L2CR,r4
sync
/* now disable the L1 data cache */
mfspr r0,SPRN_HID0
rlwinm r0,r0,0,~(HID0_DCE|HID0_ICE)
mtspr SPRN_HID0,r0
sync
isync
/* Restore HID0[DPM] to whatever it was before */
sync
mfspr r0,SPRN_HID0
rlwimi r0,r8,0,11,11 /* Turn back HID0[DPM] */
mtspr SPRN_HID0,r0
sync
/* restore DR and EE */
sync
mtmsr r11
isync
mtlr r10
blr
/* This code is for 745x processors */
flush_disable_745x:
/* Turn off EE and DR in MSR */
mfmsr r11
rlwinm r0,r11,0,~MSR_EE
rlwinm r0,r0,0,~MSR_DR
sync
mtmsr r0
isync
/* Stop prefetch streams */
DSSALL
sync
/* Disable L2 prefetching */
mfspr r0,SPRN_MSSCR0
rlwinm r0,r0,0,0,29
mtspr SPRN_MSSCR0,r0
sync
isync
lis r4,0
dcbf 0,r4
dcbf 0,r4
dcbf 0,r4
dcbf 0,r4
dcbf 0,r4
dcbf 0,r4
dcbf 0,r4
dcbf 0,r4
/* Due to a bug with the HW flush on some CPU revs, we occasionally
* experience data corruption. I'm adding a displacement flush along
* with a dcbf loop over a few Mb to "help". The problem isn't totally
* fixed by this in theory, but at least, in practice, I couldn't reproduce
* it even with a big hammer...
*/
lis r4,0x0002
mtctr r4
li r4,0
1:
lwz r0,0(r4)
addi r4,r4,32 /* Go to start of next cache line */
bdnz 1b
isync
/* Now, flush the first 4MB of memory */
lis r4,0x0002
mtctr r4
li r4,0
sync
1:
dcbf 0,r4
addi r4,r4,32 /* Go to start of next cache line */
bdnz 1b
/* Flush and disable the L1 data cache */
mfspr r6,SPRN_LDSTCR
lis r3,0xfff0 /* read from ROM for displacement flush */
li r4,0xfe /* start with only way 0 unlocked */
li r5,128 /* 128 lines in each way */
1: mtctr r5
rlwimi r6,r4,0,24,31
mtspr SPRN_LDSTCR,r6
sync
isync
2: lwz r0,0(r3) /* touch each cache line */
addi r3,r3,32
bdnz 2b
rlwinm r4,r4,1,24,30 /* move on to the next way */
ori r4,r4,1
cmpwi r4,0xff /* all done? */
bne 1b
/* now unlock the L1 data cache */
li r4,0
rlwimi r6,r4,0,24,31
sync
mtspr SPRN_LDSTCR,r6
sync
isync
/* Flush the L2 cache using the hardware assist */
mfspr r3,SPRN_L2CR
cmpwi r3,0 /* check if it is enabled first */
bge 4f
oris r0,r3,(L2CR_L2IO_745x|L2CR_L2DO_745x)@h
b 2f
/* When disabling/locking L2, code must be in L1 */
.balign 32
1: mtspr SPRN_L2CR,r0 /* lock the L2 cache */
3: sync
isync
b 1f
2: b 3f
3: sync
isync
b 1b
1: sync
isync
ori r0,r3,L2CR_L2HWF_745x
sync
mtspr SPRN_L2CR,r0 /* set the hardware flush bit */
3: mfspr r0,SPRN_L2CR /* wait for it to go to 0 */
andi. r0,r0,L2CR_L2HWF_745x
bne 3b
sync
rlwinm r3,r3,0,~L2CR_L2E
b 2f
/* When disabling L2, code must be in L1 */
.balign 32
1: mtspr SPRN_L2CR,r3 /* disable the L2 cache */
3: sync
isync
b 1f
2: b 3f
3: sync
isync
b 1b
1: sync
isync
oris r4,r3,L2CR_L2I@h
mtspr SPRN_L2CR,r4
sync
isync
1: mfspr r4,SPRN_L2CR
andis. r0,r4,L2CR_L2I@h
bne 1b
sync
BEGIN_FTR_SECTION
/* Flush the L3 cache using the hardware assist */
4: mfspr r3,SPRN_L3CR
cmpwi r3,0 /* check if it is enabled */
bge 6f
oris r0,r3,L3CR_L3IO@h
ori r0,r0,L3CR_L3DO
sync
mtspr SPRN_L3CR,r0 /* lock the L3 cache */
sync
isync
ori r0,r0,L3CR_L3HWF
sync
mtspr SPRN_L3CR,r0 /* set the hardware flush bit */
5: mfspr r0,SPRN_L3CR /* wait for it to go to zero */
andi. r0,r0,L3CR_L3HWF
bne 5b
rlwinm r3,r3,0,~L3CR_L3E
sync
mtspr SPRN_L3CR,r3 /* disable the L3 cache */
sync
ori r4,r3,L3CR_L3I
mtspr SPRN_L3CR,r4
1: mfspr r4,SPRN_L3CR
andi. r0,r4,L3CR_L3I
bne 1b
sync
END_FTR_SECTION_IFSET(CPU_FTR_L3CR)
6: mfspr r0,SPRN_HID0 /* now disable the L1 data cache */
rlwinm r0,r0,0,~HID0_DCE
mtspr SPRN_HID0,r0
sync
isync
mtmsr r11 /* restore DR and EE */
isync
blr
#endif /* CONFIG_6xx */

View File

@@ -0,0 +1,715 @@
/*
* Copyright (C) 2002 - 2005 Benjamin Herrenschmidt <benh@kernel.crashing.org>
* Copyright (C) 2004 John Steele Scott <toojays@toojays.net>
*
* 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.
*
* TODO: Need a big cleanup here. Basically, we need to have different
* cpufreq_driver structures for the different type of HW instead of the
* current mess. We also need to better deal with the detection of the
* type of machine.
*
*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/adb.h>
#include <linux/pmu.h>
#include <linux/slab.h>
#include <linux/cpufreq.h>
#include <linux/init.h>
#include <linux/sysdev.h>
#include <linux/hardirq.h>
#include <asm/prom.h>
#include <asm/machdep.h>
#include <asm/irq.h>
#include <asm/pmac_feature.h>
#include <asm/mmu_context.h>
#include <asm/sections.h>
#include <asm/cputable.h>
#include <asm/time.h>
#include <asm/system.h>
#include <asm/mpic.h>
#include <asm/keylargo.h>
/* WARNING !!! This will cause calibrate_delay() to be called,
* but this is an __init function ! So you MUST go edit
* init/main.c to make it non-init before enabling DEBUG_FREQ
*/
#undef DEBUG_FREQ
extern void low_choose_7447a_dfs(int dfs);
extern void low_choose_750fx_pll(int pll);
extern void low_sleep_handler(void);
/*
* Currently, PowerMac cpufreq supports only high & low frequencies
* that are set by the firmware
*/
static unsigned int low_freq;
static unsigned int hi_freq;
static unsigned int cur_freq;
static unsigned int sleep_freq;
/*
* Different models uses different mechanisms to switch the frequency
*/
static int (*set_speed_proc)(int low_speed);
static unsigned int (*get_speed_proc)(void);
/*
* Some definitions used by the various speedprocs
*/
static u32 voltage_gpio;
static u32 frequency_gpio;
static u32 slew_done_gpio;
static int no_schedule;
static int has_cpu_l2lve;
static int is_pmu_based;
/* There are only two frequency states for each processor. Values
* are in kHz for the time being.
*/
#define CPUFREQ_HIGH 0
#define CPUFREQ_LOW 1
static struct cpufreq_frequency_table pmac_cpu_freqs[] = {
{CPUFREQ_HIGH, 0},
{CPUFREQ_LOW, 0},
{0, CPUFREQ_TABLE_END},
};
static struct freq_attr* pmac_cpu_freqs_attr[] = {
&cpufreq_freq_attr_scaling_available_freqs,
NULL,
};
static inline void local_delay(unsigned long ms)
{
if (no_schedule)
mdelay(ms);
else
msleep(ms);
}
#ifdef DEBUG_FREQ
static inline void debug_calc_bogomips(void)
{
/* This will cause a recalc of bogomips and display the
* result. We backup/restore the value to avoid affecting the
* core cpufreq framework's own calculation.
*/
unsigned long save_lpj = loops_per_jiffy;
calibrate_delay();
loops_per_jiffy = save_lpj;
}
#endif /* DEBUG_FREQ */
/* Switch CPU speed under 750FX CPU control
*/
static int cpu_750fx_cpu_speed(int low_speed)
{
u32 hid2;
if (low_speed == 0) {
/* ramping up, set voltage first */
pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, voltage_gpio, 0x05);
/* Make sure we sleep for at least 1ms */
local_delay(10);
/* tweak L2 for high voltage */
if (has_cpu_l2lve) {
hid2 = mfspr(SPRN_HID2);
hid2 &= ~0x2000;
mtspr(SPRN_HID2, hid2);
}
}
#ifdef CONFIG_6xx
low_choose_750fx_pll(low_speed);
#endif
if (low_speed == 1) {
/* tweak L2 for low voltage */
if (has_cpu_l2lve) {
hid2 = mfspr(SPRN_HID2);
hid2 |= 0x2000;
mtspr(SPRN_HID2, hid2);
}
/* ramping down, set voltage last */
pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, voltage_gpio, 0x04);
local_delay(10);
}
return 0;
}
static unsigned int cpu_750fx_get_cpu_speed(void)
{
if (mfspr(SPRN_HID1) & HID1_PS)
return low_freq;
else
return hi_freq;
}
/* Switch CPU speed using DFS */
static int dfs_set_cpu_speed(int low_speed)
{
if (low_speed == 0) {
/* ramping up, set voltage first */
pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, voltage_gpio, 0x05);
/* Make sure we sleep for at least 1ms */
local_delay(1);
}
/* set frequency */
#ifdef CONFIG_6xx
low_choose_7447a_dfs(low_speed);
#endif
udelay(100);
if (low_speed == 1) {
/* ramping down, set voltage last */
pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, voltage_gpio, 0x04);
local_delay(1);
}
return 0;
}
static unsigned int dfs_get_cpu_speed(void)
{
if (mfspr(SPRN_HID1) & HID1_DFS)
return low_freq;
else
return hi_freq;
}
/* Switch CPU speed using slewing GPIOs
*/
static int gpios_set_cpu_speed(int low_speed)
{
int gpio, timeout = 0;
/* If ramping up, set voltage first */
if (low_speed == 0) {
pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, voltage_gpio, 0x05);
/* Delay is way too big but it's ok, we schedule */
local_delay(10);
}
/* Set frequency */
gpio = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, frequency_gpio, 0);
if (low_speed == ((gpio & 0x01) == 0))
goto skip;
pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, frequency_gpio,
low_speed ? 0x04 : 0x05);
udelay(200);
do {
if (++timeout > 100)
break;
local_delay(1);
gpio = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, slew_done_gpio, 0);
} while((gpio & 0x02) == 0);
skip:
/* If ramping down, set voltage last */
if (low_speed == 1) {
pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, voltage_gpio, 0x04);
/* Delay is way too big but it's ok, we schedule */
local_delay(10);
}
#ifdef DEBUG_FREQ
debug_calc_bogomips();
#endif
return 0;
}
/* Switch CPU speed under PMU control
*/
static int pmu_set_cpu_speed(int low_speed)
{
struct adb_request req;
unsigned long save_l2cr;
unsigned long save_l3cr;
unsigned int pic_prio;
unsigned long flags;
preempt_disable();
#ifdef DEBUG_FREQ
printk(KERN_DEBUG "HID1, before: %x\n", mfspr(SPRN_HID1));
#endif
pmu_suspend();
/* Disable all interrupt sources on openpic */
pic_prio = mpic_cpu_get_priority();
mpic_cpu_set_priority(0xf);
/* Make sure the decrementer won't interrupt us */
asm volatile("mtdec %0" : : "r" (0x7fffffff));
/* Make sure any pending DEC interrupt occurring while we did
* the above didn't re-enable the DEC */
mb();
asm volatile("mtdec %0" : : "r" (0x7fffffff));
/* We can now disable MSR_EE */
local_irq_save(flags);
/* Giveup the FPU & vec */
enable_kernel_fp();
#ifdef CONFIG_ALTIVEC
if (cpu_has_feature(CPU_FTR_ALTIVEC))
enable_kernel_altivec();
#endif /* CONFIG_ALTIVEC */
/* Save & disable L2 and L3 caches */
save_l3cr = _get_L3CR(); /* (returns -1 if not available) */
save_l2cr = _get_L2CR(); /* (returns -1 if not available) */
/* Send the new speed command. My assumption is that this command
* will cause PLL_CFG[0..3] to be changed next time CPU goes to sleep
*/
pmu_request(&req, NULL, 6, PMU_CPU_SPEED, 'W', 'O', 'O', 'F', low_speed);
while (!req.complete)
pmu_poll();
/* Prepare the northbridge for the speed transition */
pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,1,1);
/* Call low level code to backup CPU state and recover from
* hardware reset
*/
low_sleep_handler();
/* Restore the northbridge */
pmac_call_feature(PMAC_FTR_SLEEP_STATE,NULL,1,0);
/* Restore L2 cache */
if (save_l2cr != 0xffffffff && (save_l2cr & L2CR_L2E) != 0)
_set_L2CR(save_l2cr);
/* Restore L3 cache */
if (save_l3cr != 0xffffffff && (save_l3cr & L3CR_L3E) != 0)
_set_L3CR(save_l3cr);
/* Restore userland MMU context */
switch_mmu_context(NULL, current->active_mm);
#ifdef DEBUG_FREQ
printk(KERN_DEBUG "HID1, after: %x\n", mfspr(SPRN_HID1));
#endif
/* Restore low level PMU operations */
pmu_unlock();
/* Restore decrementer */
wakeup_decrementer();
/* Restore interrupts */
mpic_cpu_set_priority(pic_prio);
/* Let interrupts flow again ... */
local_irq_restore(flags);
#ifdef DEBUG_FREQ
debug_calc_bogomips();
#endif
pmu_resume();
preempt_enable();
return 0;
}
static int do_set_cpu_speed(int speed_mode, int notify)
{
struct cpufreq_freqs freqs;
unsigned long l3cr;
static unsigned long prev_l3cr;
freqs.old = cur_freq;
freqs.new = (speed_mode == CPUFREQ_HIGH) ? hi_freq : low_freq;
freqs.cpu = smp_processor_id();
if (freqs.old == freqs.new)
return 0;
if (notify)
cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
if (speed_mode == CPUFREQ_LOW &&
cpu_has_feature(CPU_FTR_L3CR)) {
l3cr = _get_L3CR();
if (l3cr & L3CR_L3E) {
prev_l3cr = l3cr;
_set_L3CR(0);
}
}
set_speed_proc(speed_mode == CPUFREQ_LOW);
if (speed_mode == CPUFREQ_HIGH &&
cpu_has_feature(CPU_FTR_L3CR)) {
l3cr = _get_L3CR();
if ((prev_l3cr & L3CR_L3E) && l3cr != prev_l3cr)
_set_L3CR(prev_l3cr);
}
if (notify)
cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
cur_freq = (speed_mode == CPUFREQ_HIGH) ? hi_freq : low_freq;
return 0;
}
static unsigned int pmac_cpufreq_get_speed(unsigned int cpu)
{
return cur_freq;
}
static int pmac_cpufreq_verify(struct cpufreq_policy *policy)
{
return cpufreq_frequency_table_verify(policy, pmac_cpu_freqs);
}
static int pmac_cpufreq_target( struct cpufreq_policy *policy,
unsigned int target_freq,
unsigned int relation)
{
unsigned int newstate = 0;
int rc;
if (cpufreq_frequency_table_target(policy, pmac_cpu_freqs,
target_freq, relation, &newstate))
return -EINVAL;
rc = do_set_cpu_speed(newstate, 1);
ppc_proc_freq = cur_freq * 1000ul;
return rc;
}
static int pmac_cpufreq_cpu_init(struct cpufreq_policy *policy)
{
if (policy->cpu != 0)
return -ENODEV;
policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL;
policy->cur = cur_freq;
cpufreq_frequency_table_get_attr(pmac_cpu_freqs, policy->cpu);
return cpufreq_frequency_table_cpuinfo(policy, pmac_cpu_freqs);
}
static u32 read_gpio(struct device_node *np)
{
const u32 *reg = of_get_property(np, "reg", NULL);
u32 offset;
if (reg == NULL)
return 0;
/* That works for all keylargos but shall be fixed properly
* some day... The problem is that it seems we can't rely
* on the "reg" property of the GPIO nodes, they are either
* relative to the base of KeyLargo or to the base of the
* GPIO space, and the device-tree doesn't help.
*/
offset = *reg;
if (offset < KEYLARGO_GPIO_LEVELS0)
offset += KEYLARGO_GPIO_LEVELS0;
return offset;
}
static int pmac_cpufreq_suspend(struct cpufreq_policy *policy, pm_message_t pmsg)
{
/* Ok, this could be made a bit smarter, but let's be robust for now. We
* always force a speed change to high speed before sleep, to make sure
* we have appropriate voltage and/or bus speed for the wakeup process,
* and to make sure our loops_per_jiffies are "good enough", that is will
* not cause too short delays if we sleep in low speed and wake in high
* speed..
*/
no_schedule = 1;
sleep_freq = cur_freq;
if (cur_freq == low_freq && !is_pmu_based)
do_set_cpu_speed(CPUFREQ_HIGH, 0);
return 0;
}
static int pmac_cpufreq_resume(struct cpufreq_policy *policy)
{
/* If we resume, first check if we have a get() function */
if (get_speed_proc)
cur_freq = get_speed_proc();
else
cur_freq = 0;
/* We don't, hrm... we don't really know our speed here, best
* is that we force a switch to whatever it was, which is
* probably high speed due to our suspend() routine
*/
do_set_cpu_speed(sleep_freq == low_freq ?
CPUFREQ_LOW : CPUFREQ_HIGH, 0);
ppc_proc_freq = cur_freq * 1000ul;
no_schedule = 0;
return 0;
}
static struct cpufreq_driver pmac_cpufreq_driver = {
.verify = pmac_cpufreq_verify,
.target = pmac_cpufreq_target,
.get = pmac_cpufreq_get_speed,
.init = pmac_cpufreq_cpu_init,
.suspend = pmac_cpufreq_suspend,
.resume = pmac_cpufreq_resume,
.flags = CPUFREQ_PM_NO_WARN,
.attr = pmac_cpu_freqs_attr,
.name = "powermac",
.owner = THIS_MODULE,
};
static int pmac_cpufreq_init_MacRISC3(struct device_node *cpunode)
{
struct device_node *volt_gpio_np = of_find_node_by_name(NULL,
"voltage-gpio");
struct device_node *freq_gpio_np = of_find_node_by_name(NULL,
"frequency-gpio");
struct device_node *slew_done_gpio_np = of_find_node_by_name(NULL,
"slewing-done");
const u32 *value;
/*
* Check to see if it's GPIO driven or PMU only
*
* The way we extract the GPIO address is slightly hackish, but it
* works well enough for now. We need to abstract the whole GPIO
* stuff sooner or later anyway
*/
if (volt_gpio_np)
voltage_gpio = read_gpio(volt_gpio_np);
if (freq_gpio_np)
frequency_gpio = read_gpio(freq_gpio_np);
if (slew_done_gpio_np)
slew_done_gpio = read_gpio(slew_done_gpio_np);
/* If we use the frequency GPIOs, calculate the min/max speeds based
* on the bus frequencies
*/
if (frequency_gpio && slew_done_gpio) {
int lenp, rc;
const u32 *freqs, *ratio;
freqs = of_get_property(cpunode, "bus-frequencies", &lenp);
lenp /= sizeof(u32);
if (freqs == NULL || lenp != 2) {
printk(KERN_ERR "cpufreq: bus-frequencies incorrect or missing\n");
return 1;
}
ratio = of_get_property(cpunode, "processor-to-bus-ratio*2",
NULL);
if (ratio == NULL) {
printk(KERN_ERR "cpufreq: processor-to-bus-ratio*2 missing\n");
return 1;
}
/* Get the min/max bus frequencies */
low_freq = min(freqs[0], freqs[1]);
hi_freq = max(freqs[0], freqs[1]);
/* Grrrr.. It _seems_ that the device-tree is lying on the low bus
* frequency, it claims it to be around 84Mhz on some models while
* it appears to be approx. 101Mhz on all. Let's hack around here...
* fortunately, we don't need to be too precise
*/
if (low_freq < 98000000)
low_freq = 101000000;
/* Convert those to CPU core clocks */
low_freq = (low_freq * (*ratio)) / 2000;
hi_freq = (hi_freq * (*ratio)) / 2000;
/* Now we get the frequencies, we read the GPIO to see what is out current
* speed
*/
rc = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, frequency_gpio, 0);
cur_freq = (rc & 0x01) ? hi_freq : low_freq;
set_speed_proc = gpios_set_cpu_speed;
return 1;
}
/* If we use the PMU, look for the min & max frequencies in the
* device-tree
*/
value = of_get_property(cpunode, "min-clock-frequency", NULL);
if (!value)
return 1;
low_freq = (*value) / 1000;
/* The PowerBook G4 12" (PowerBook6,1) has an error in the device-tree
* here */
if (low_freq < 100000)
low_freq *= 10;
value = of_get_property(cpunode, "max-clock-frequency", NULL);
if (!value)
return 1;
hi_freq = (*value) / 1000;
set_speed_proc = pmu_set_cpu_speed;
is_pmu_based = 1;
return 0;
}
static int pmac_cpufreq_init_7447A(struct device_node *cpunode)
{
struct device_node *volt_gpio_np;
if (of_get_property(cpunode, "dynamic-power-step", NULL) == NULL)
return 1;
volt_gpio_np = of_find_node_by_name(NULL, "cpu-vcore-select");
if (volt_gpio_np)
voltage_gpio = read_gpio(volt_gpio_np);
if (!voltage_gpio){
printk(KERN_ERR "cpufreq: missing cpu-vcore-select gpio\n");
return 1;
}
/* OF only reports the high frequency */
hi_freq = cur_freq;
low_freq = cur_freq/2;
/* Read actual frequency from CPU */
cur_freq = dfs_get_cpu_speed();
set_speed_proc = dfs_set_cpu_speed;
get_speed_proc = dfs_get_cpu_speed;
return 0;
}
static int pmac_cpufreq_init_750FX(struct device_node *cpunode)
{
struct device_node *volt_gpio_np;
u32 pvr;
const u32 *value;
if (of_get_property(cpunode, "dynamic-power-step", NULL) == NULL)
return 1;
hi_freq = cur_freq;
value = of_get_property(cpunode, "reduced-clock-frequency", NULL);
if (!value)
return 1;
low_freq = (*value) / 1000;
volt_gpio_np = of_find_node_by_name(NULL, "cpu-vcore-select");
if (volt_gpio_np)
voltage_gpio = read_gpio(volt_gpio_np);
pvr = mfspr(SPRN_PVR);
has_cpu_l2lve = !((pvr & 0xf00) == 0x100);
set_speed_proc = cpu_750fx_cpu_speed;
get_speed_proc = cpu_750fx_get_cpu_speed;
cur_freq = cpu_750fx_get_cpu_speed();
return 0;
}
/* Currently, we support the following machines:
*
* - Titanium PowerBook 1Ghz (PMU based, 667Mhz & 1Ghz)
* - Titanium PowerBook 800 (PMU based, 667Mhz & 800Mhz)
* - Titanium PowerBook 400 (PMU based, 300Mhz & 400Mhz)
* - Titanium PowerBook 500 (PMU based, 300Mhz & 500Mhz)
* - iBook2 500/600 (PMU based, 400Mhz & 500/600Mhz)
* - iBook2 700 (CPU based, 400Mhz & 700Mhz, support low voltage)
* - Recent MacRISC3 laptops
* - All new machines with 7447A CPUs
*/
static int __init pmac_cpufreq_setup(void)
{
struct device_node *cpunode;
const u32 *value;
if (strstr(cmd_line, "nocpufreq"))
return 0;
/* Assume only one CPU */
cpunode = of_find_node_by_type(NULL, "cpu");
if (!cpunode)
goto out;
/* Get current cpu clock freq */
value = of_get_property(cpunode, "clock-frequency", NULL);
if (!value)
goto out;
cur_freq = (*value) / 1000;
/* Check for 7447A based MacRISC3 */
if (machine_is_compatible("MacRISC3") &&
of_get_property(cpunode, "dynamic-power-step", NULL) &&
PVR_VER(mfspr(SPRN_PVR)) == 0x8003) {
pmac_cpufreq_init_7447A(cpunode);
/* Check for other MacRISC3 machines */
} else if (machine_is_compatible("PowerBook3,4") ||
machine_is_compatible("PowerBook3,5") ||
machine_is_compatible("MacRISC3")) {
pmac_cpufreq_init_MacRISC3(cpunode);
/* Else check for iBook2 500/600 */
} else if (machine_is_compatible("PowerBook4,1")) {
hi_freq = cur_freq;
low_freq = 400000;
set_speed_proc = pmu_set_cpu_speed;
is_pmu_based = 1;
}
/* Else check for TiPb 550 */
else if (machine_is_compatible("PowerBook3,3") && cur_freq == 550000) {
hi_freq = cur_freq;
low_freq = 500000;
set_speed_proc = pmu_set_cpu_speed;
is_pmu_based = 1;
}
/* Else check for TiPb 400 & 500 */
else if (machine_is_compatible("PowerBook3,2")) {
/* We only know about the 400 MHz and the 500Mhz model
* they both have 300 MHz as low frequency
*/
if (cur_freq < 350000 || cur_freq > 550000)
goto out;
hi_freq = cur_freq;
low_freq = 300000;
set_speed_proc = pmu_set_cpu_speed;
is_pmu_based = 1;
}
/* Else check for 750FX */
else if (PVR_VER(mfspr(SPRN_PVR)) == 0x7000)
pmac_cpufreq_init_750FX(cpunode);
out:
of_node_put(cpunode);
if (set_speed_proc == NULL)
return -ENODEV;
pmac_cpu_freqs[CPUFREQ_LOW].frequency = low_freq;
pmac_cpu_freqs[CPUFREQ_HIGH].frequency = hi_freq;
ppc_proc_freq = cur_freq * 1000ul;
printk(KERN_INFO "Registering PowerMac CPU frequency driver\n");
printk(KERN_INFO "Low: %d Mhz, High: %d Mhz, Boot: %d Mhz\n",
low_freq/1000, hi_freq/1000, cur_freq/1000);
return cpufreq_register_driver(&pmac_cpufreq_driver);
}
module_init(pmac_cpufreq_setup);

View File

@@ -0,0 +1,748 @@
/*
* Copyright (C) 2002 - 2005 Benjamin Herrenschmidt <benh@kernel.crashing.org>
* and Markus Demleitner <msdemlei@cl.uni-heidelberg.de>
*
* 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 driver adds basic cpufreq support for SMU & 970FX based G5 Macs,
* that is iMac G5 and latest single CPU desktop.
*/
#undef DEBUG
#include <linux/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/cpufreq.h>
#include <linux/init.h>
#include <linux/completion.h>
#include <linux/mutex.h>
#include <asm/prom.h>
#include <asm/machdep.h>
#include <asm/irq.h>
#include <asm/sections.h>
#include <asm/cputable.h>
#include <asm/time.h>
#include <asm/smu.h>
#include <asm/pmac_pfunc.h>
#define DBG(fmt...) pr_debug(fmt)
/* see 970FX user manual */
#define SCOM_PCR 0x0aa001 /* PCR scom addr */
#define PCR_HILO_SELECT 0x80000000U /* 1 = PCR, 0 = PCRH */
#define PCR_SPEED_FULL 0x00000000U /* 1:1 speed value */
#define PCR_SPEED_HALF 0x00020000U /* 1:2 speed value */
#define PCR_SPEED_QUARTER 0x00040000U /* 1:4 speed value */
#define PCR_SPEED_MASK 0x000e0000U /* speed mask */
#define PCR_SPEED_SHIFT 17
#define PCR_FREQ_REQ_VALID 0x00010000U /* freq request valid */
#define PCR_VOLT_REQ_VALID 0x00008000U /* volt request valid */
#define PCR_TARGET_TIME_MASK 0x00006000U /* target time */
#define PCR_STATLAT_MASK 0x00001f00U /* STATLAT value */
#define PCR_SNOOPLAT_MASK 0x000000f0U /* SNOOPLAT value */
#define PCR_SNOOPACC_MASK 0x0000000fU /* SNOOPACC value */
#define SCOM_PSR 0x408001 /* PSR scom addr */
/* warning: PSR is a 64 bits register */
#define PSR_CMD_RECEIVED 0x2000000000000000U /* command received */
#define PSR_CMD_COMPLETED 0x1000000000000000U /* command completed */
#define PSR_CUR_SPEED_MASK 0x0300000000000000U /* current speed */
#define PSR_CUR_SPEED_SHIFT (56)
/*
* The G5 only supports two frequencies (Quarter speed is not supported)
*/
#define CPUFREQ_HIGH 0
#define CPUFREQ_LOW 1
static struct cpufreq_frequency_table g5_cpu_freqs[] = {
{CPUFREQ_HIGH, 0},
{CPUFREQ_LOW, 0},
{0, CPUFREQ_TABLE_END},
};
static struct freq_attr* g5_cpu_freqs_attr[] = {
&cpufreq_freq_attr_scaling_available_freqs,
NULL,
};
/* Power mode data is an array of the 32 bits PCR values to use for
* the various frequencies, retrieved from the device-tree
*/
static int g5_pmode_cur;
static void (*g5_switch_volt)(int speed_mode);
static int (*g5_switch_freq)(int speed_mode);
static int (*g5_query_freq)(void);
static DEFINE_MUTEX(g5_switch_mutex);
static unsigned long transition_latency;
#ifdef CONFIG_PMAC_SMU
static const u32 *g5_pmode_data;
static int g5_pmode_max;
static struct smu_sdbp_fvt *g5_fvt_table; /* table of op. points */
static int g5_fvt_count; /* number of op. points */
static int g5_fvt_cur; /* current op. point */
/*
* SMU based voltage switching for Neo2 platforms
*/
static void g5_smu_switch_volt(int speed_mode)
{
struct smu_simple_cmd cmd;
DECLARE_COMPLETION_ONSTACK(comp);
smu_queue_simple(&cmd, SMU_CMD_POWER_COMMAND, 8, smu_done_complete,
&comp, 'V', 'S', 'L', 'E', 'W',
0xff, g5_fvt_cur+1, speed_mode);
wait_for_completion(&comp);
}
/*
* Platform function based voltage/vdnap switching for Neo2
*/
static struct pmf_function *pfunc_set_vdnap0;
static struct pmf_function *pfunc_vdnap0_complete;
static void g5_vdnap_switch_volt(int speed_mode)
{
struct pmf_args args;
u32 slew, done = 0;
unsigned long timeout;
slew = (speed_mode == CPUFREQ_LOW) ? 1 : 0;
args.count = 1;
args.u[0].p = &slew;
pmf_call_one(pfunc_set_vdnap0, &args);
/* It's an irq GPIO so we should be able to just block here,
* I'll do that later after I've properly tested the IRQ code for
* platform functions
*/
timeout = jiffies + HZ/10;
while(!time_after(jiffies, timeout)) {
args.count = 1;
args.u[0].p = &done;
pmf_call_one(pfunc_vdnap0_complete, &args);
if (done)
break;
msleep(1);
}
if (done == 0)
printk(KERN_WARNING "cpufreq: Timeout in clock slewing !\n");
}
/*
* SCOM based frequency switching for 970FX rev3
*/
static int g5_scom_switch_freq(int speed_mode)
{
unsigned long flags;
int to;
/* If frequency is going up, first ramp up the voltage */
if (speed_mode < g5_pmode_cur)
g5_switch_volt(speed_mode);
local_irq_save(flags);
/* Clear PCR high */
scom970_write(SCOM_PCR, 0);
/* Clear PCR low */
scom970_write(SCOM_PCR, PCR_HILO_SELECT | 0);
/* Set PCR low */
scom970_write(SCOM_PCR, PCR_HILO_SELECT |
g5_pmode_data[speed_mode]);
/* Wait for completion */
for (to = 0; to < 10; to++) {
unsigned long psr = scom970_read(SCOM_PSR);
if ((psr & PSR_CMD_RECEIVED) == 0 &&
(((psr >> PSR_CUR_SPEED_SHIFT) ^
(g5_pmode_data[speed_mode] >> PCR_SPEED_SHIFT)) & 0x3)
== 0)
break;
if (psr & PSR_CMD_COMPLETED)
break;
udelay(100);
}
local_irq_restore(flags);
/* If frequency is going down, last ramp the voltage */
if (speed_mode > g5_pmode_cur)
g5_switch_volt(speed_mode);
g5_pmode_cur = speed_mode;
ppc_proc_freq = g5_cpu_freqs[speed_mode].frequency * 1000ul;
return 0;
}
static int g5_scom_query_freq(void)
{
unsigned long psr = scom970_read(SCOM_PSR);
int i;
for (i = 0; i <= g5_pmode_max; i++)
if ((((psr >> PSR_CUR_SPEED_SHIFT) ^
(g5_pmode_data[i] >> PCR_SPEED_SHIFT)) & 0x3) == 0)
break;
return i;
}
/*
* Fake voltage switching for platforms with missing support
*/
static void g5_dummy_switch_volt(int speed_mode)
{
}
#endif /* CONFIG_PMAC_SMU */
/*
* Platform function based voltage switching for PowerMac7,2 & 7,3
*/
static struct pmf_function *pfunc_cpu0_volt_high;
static struct pmf_function *pfunc_cpu0_volt_low;
static struct pmf_function *pfunc_cpu1_volt_high;
static struct pmf_function *pfunc_cpu1_volt_low;
static void g5_pfunc_switch_volt(int speed_mode)
{
if (speed_mode == CPUFREQ_HIGH) {
if (pfunc_cpu0_volt_high)
pmf_call_one(pfunc_cpu0_volt_high, NULL);
if (pfunc_cpu1_volt_high)
pmf_call_one(pfunc_cpu1_volt_high, NULL);
} else {
if (pfunc_cpu0_volt_low)
pmf_call_one(pfunc_cpu0_volt_low, NULL);
if (pfunc_cpu1_volt_low)
pmf_call_one(pfunc_cpu1_volt_low, NULL);
}
msleep(10); /* should be faster , to fix */
}
/*
* Platform function based frequency switching for PowerMac7,2 & 7,3
*/
static struct pmf_function *pfunc_cpu_setfreq_high;
static struct pmf_function *pfunc_cpu_setfreq_low;
static struct pmf_function *pfunc_cpu_getfreq;
static struct pmf_function *pfunc_slewing_done;
static int g5_pfunc_switch_freq(int speed_mode)
{
struct pmf_args args;
u32 done = 0;
unsigned long timeout;
int rc;
DBG("g5_pfunc_switch_freq(%d)\n", speed_mode);
/* If frequency is going up, first ramp up the voltage */
if (speed_mode < g5_pmode_cur)
g5_switch_volt(speed_mode);
/* Do it */
if (speed_mode == CPUFREQ_HIGH)
rc = pmf_call_one(pfunc_cpu_setfreq_high, NULL);
else
rc = pmf_call_one(pfunc_cpu_setfreq_low, NULL);
if (rc)
printk(KERN_WARNING "cpufreq: pfunc switch error %d\n", rc);
/* It's an irq GPIO so we should be able to just block here,
* I'll do that later after I've properly tested the IRQ code for
* platform functions
*/
timeout = jiffies + HZ/10;
while(!time_after(jiffies, timeout)) {
args.count = 1;
args.u[0].p = &done;
pmf_call_one(pfunc_slewing_done, &args);
if (done)
break;
msleep(1);
}
if (done == 0)
printk(KERN_WARNING "cpufreq: Timeout in clock slewing !\n");
/* If frequency is going down, last ramp the voltage */
if (speed_mode > g5_pmode_cur)
g5_switch_volt(speed_mode);
g5_pmode_cur = speed_mode;
ppc_proc_freq = g5_cpu_freqs[speed_mode].frequency * 1000ul;
return 0;
}
static int g5_pfunc_query_freq(void)
{
struct pmf_args args;
u32 val = 0;
args.count = 1;
args.u[0].p = &val;
pmf_call_one(pfunc_cpu_getfreq, &args);
return val ? CPUFREQ_HIGH : CPUFREQ_LOW;
}
/*
* Common interface to the cpufreq core
*/
static int g5_cpufreq_verify(struct cpufreq_policy *policy)
{
return cpufreq_frequency_table_verify(policy, g5_cpu_freqs);
}
static int g5_cpufreq_target(struct cpufreq_policy *policy,
unsigned int target_freq, unsigned int relation)
{
unsigned int newstate = 0;
struct cpufreq_freqs freqs;
int rc;
if (cpufreq_frequency_table_target(policy, g5_cpu_freqs,
target_freq, relation, &newstate))
return -EINVAL;
if (g5_pmode_cur == newstate)
return 0;
mutex_lock(&g5_switch_mutex);
freqs.old = g5_cpu_freqs[g5_pmode_cur].frequency;
freqs.new = g5_cpu_freqs[newstate].frequency;
freqs.cpu = 0;
cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
rc = g5_switch_freq(newstate);
cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
mutex_unlock(&g5_switch_mutex);
return rc;
}
static unsigned int g5_cpufreq_get_speed(unsigned int cpu)
{
return g5_cpu_freqs[g5_pmode_cur].frequency;
}
static int g5_cpufreq_cpu_init(struct cpufreq_policy *policy)
{
policy->cpuinfo.transition_latency = transition_latency;
policy->cur = g5_cpu_freqs[g5_query_freq()].frequency;
/* secondary CPUs are tied to the primary one by the
* cpufreq core if in the secondary policy we tell it that
* it actually must be one policy together with all others. */
cpumask_copy(policy->cpus, &cpu_online_map);
cpufreq_frequency_table_get_attr(g5_cpu_freqs, policy->cpu);
return cpufreq_frequency_table_cpuinfo(policy,
g5_cpu_freqs);
}
static struct cpufreq_driver g5_cpufreq_driver = {
.name = "powermac",
.owner = THIS_MODULE,
.flags = CPUFREQ_CONST_LOOPS,
.init = g5_cpufreq_cpu_init,
.verify = g5_cpufreq_verify,
.target = g5_cpufreq_target,
.get = g5_cpufreq_get_speed,
.attr = g5_cpu_freqs_attr,
};
#ifdef CONFIG_PMAC_SMU
static int __init g5_neo2_cpufreq_init(struct device_node *cpus)
{
struct device_node *cpunode;
unsigned int psize, ssize;
unsigned long max_freq;
char *freq_method, *volt_method;
const u32 *valp;
u32 pvr_hi;
int use_volts_vdnap = 0;
int use_volts_smu = 0;
int rc = -ENODEV;
/* Check supported platforms */
if (machine_is_compatible("PowerMac8,1") ||
machine_is_compatible("PowerMac8,2") ||
machine_is_compatible("PowerMac9,1"))
use_volts_smu = 1;
else if (machine_is_compatible("PowerMac11,2"))
use_volts_vdnap = 1;
else
return -ENODEV;
/* Get first CPU node */
for (cpunode = NULL;
(cpunode = of_get_next_child(cpus, cpunode)) != NULL;) {
const u32 *reg = of_get_property(cpunode, "reg", NULL);
if (reg == NULL || (*reg) != 0)
continue;
if (!strcmp(cpunode->type, "cpu"))
break;
}
if (cpunode == NULL) {
printk(KERN_ERR "cpufreq: Can't find any CPU 0 node\n");
return -ENODEV;
}
/* Check 970FX for now */
valp = of_get_property(cpunode, "cpu-version", NULL);
if (!valp) {
DBG("No cpu-version property !\n");
goto bail_noprops;
}
pvr_hi = (*valp) >> 16;
if (pvr_hi != 0x3c && pvr_hi != 0x44) {
printk(KERN_ERR "cpufreq: Unsupported CPU version\n");
goto bail_noprops;
}
/* Look for the powertune data in the device-tree */
g5_pmode_data = of_get_property(cpunode, "power-mode-data",&psize);
if (!g5_pmode_data) {
DBG("No power-mode-data !\n");
goto bail_noprops;
}
g5_pmode_max = psize / sizeof(u32) - 1;
if (use_volts_smu) {
const struct smu_sdbp_header *shdr;
/* Look for the FVT table */
shdr = smu_get_sdb_partition(SMU_SDB_FVT_ID, NULL);
if (!shdr)
goto bail_noprops;
g5_fvt_table = (struct smu_sdbp_fvt *)&shdr[1];
ssize = (shdr->len * sizeof(u32)) -
sizeof(struct smu_sdbp_header);
g5_fvt_count = ssize / sizeof(struct smu_sdbp_fvt);
g5_fvt_cur = 0;
/* Sanity checking */
if (g5_fvt_count < 1 || g5_pmode_max < 1)
goto bail_noprops;
g5_switch_volt = g5_smu_switch_volt;
volt_method = "SMU";
} else if (use_volts_vdnap) {
struct device_node *root;
root = of_find_node_by_path("/");
if (root == NULL) {
printk(KERN_ERR "cpufreq: Can't find root of "
"device tree\n");
goto bail_noprops;
}
pfunc_set_vdnap0 = pmf_find_function(root, "set-vdnap0");
pfunc_vdnap0_complete =
pmf_find_function(root, "slewing-done");
if (pfunc_set_vdnap0 == NULL ||
pfunc_vdnap0_complete == NULL) {
printk(KERN_ERR "cpufreq: Can't find required "
"platform function\n");
goto bail_noprops;
}
g5_switch_volt = g5_vdnap_switch_volt;
volt_method = "GPIO";
} else {
g5_switch_volt = g5_dummy_switch_volt;
volt_method = "none";
}
/*
* From what I see, clock-frequency is always the maximal frequency.
* The current driver can not slew sysclk yet, so we really only deal
* with powertune steps for now. We also only implement full freq and
* half freq in this version. So far, I haven't yet seen a machine
* supporting anything else.
*/
valp = of_get_property(cpunode, "clock-frequency", NULL);
if (!valp)
return -ENODEV;
max_freq = (*valp)/1000;
g5_cpu_freqs[0].frequency = max_freq;
g5_cpu_freqs[1].frequency = max_freq/2;
/* Set callbacks */
transition_latency = 12000;
g5_switch_freq = g5_scom_switch_freq;
g5_query_freq = g5_scom_query_freq;
freq_method = "SCOM";
/* Force apply current frequency to make sure everything is in
* sync (voltage is right for example). Firmware may leave us with
* a strange setting ...
*/
g5_switch_volt(CPUFREQ_HIGH);
msleep(10);
g5_pmode_cur = -1;
g5_switch_freq(g5_query_freq());
printk(KERN_INFO "Registering G5 CPU frequency driver\n");
printk(KERN_INFO "Frequency method: %s, Voltage method: %s\n",
freq_method, volt_method);
printk(KERN_INFO "Low: %d Mhz, High: %d Mhz, Cur: %d MHz\n",
g5_cpu_freqs[1].frequency/1000,
g5_cpu_freqs[0].frequency/1000,
g5_cpu_freqs[g5_pmode_cur].frequency/1000);
rc = cpufreq_register_driver(&g5_cpufreq_driver);
/* We keep the CPU node on hold... hopefully, Apple G5 don't have
* hotplug CPU with a dynamic device-tree ...
*/
return rc;
bail_noprops:
of_node_put(cpunode);
return rc;
}
#endif /* CONFIG_PMAC_SMU */
static int __init g5_pm72_cpufreq_init(struct device_node *cpus)
{
struct device_node *cpuid = NULL, *hwclock = NULL, *cpunode = NULL;
const u8 *eeprom = NULL;
const u32 *valp;
u64 max_freq, min_freq, ih, il;
int has_volt = 1, rc = 0;
DBG("cpufreq: Initializing for PowerMac7,2, PowerMac7,3 and"
" RackMac3,1...\n");
/* Get first CPU node */
for (cpunode = NULL;
(cpunode = of_get_next_child(cpus, cpunode)) != NULL;) {
if (!strcmp(cpunode->type, "cpu"))
break;
}
if (cpunode == NULL) {
printk(KERN_ERR "cpufreq: Can't find any CPU node\n");
return -ENODEV;
}
/* Lookup the cpuid eeprom node */
cpuid = of_find_node_by_path("/u3@0,f8000000/i2c@f8001000/cpuid@a0");
if (cpuid != NULL)
eeprom = of_get_property(cpuid, "cpuid", NULL);
if (eeprom == NULL) {
printk(KERN_ERR "cpufreq: Can't find cpuid EEPROM !\n");
rc = -ENODEV;
goto bail;
}
/* Lookup the i2c hwclock */
for (hwclock = NULL;
(hwclock = of_find_node_by_name(hwclock, "i2c-hwclock")) != NULL;){
const char *loc = of_get_property(hwclock,
"hwctrl-location", NULL);
if (loc == NULL)
continue;
if (strcmp(loc, "CPU CLOCK"))
continue;
if (!of_get_property(hwclock, "platform-get-frequency", NULL))
continue;
break;
}
if (hwclock == NULL) {
printk(KERN_ERR "cpufreq: Can't find i2c clock chip !\n");
rc = -ENODEV;
goto bail;
}
DBG("cpufreq: i2c clock chip found: %s\n", hwclock->full_name);
/* Now get all the platform functions */
pfunc_cpu_getfreq =
pmf_find_function(hwclock, "get-frequency");
pfunc_cpu_setfreq_high =
pmf_find_function(hwclock, "set-frequency-high");
pfunc_cpu_setfreq_low =
pmf_find_function(hwclock, "set-frequency-low");
pfunc_slewing_done =
pmf_find_function(hwclock, "slewing-done");
pfunc_cpu0_volt_high =
pmf_find_function(hwclock, "set-voltage-high-0");
pfunc_cpu0_volt_low =
pmf_find_function(hwclock, "set-voltage-low-0");
pfunc_cpu1_volt_high =
pmf_find_function(hwclock, "set-voltage-high-1");
pfunc_cpu1_volt_low =
pmf_find_function(hwclock, "set-voltage-low-1");
/* Check we have minimum requirements */
if (pfunc_cpu_getfreq == NULL || pfunc_cpu_setfreq_high == NULL ||
pfunc_cpu_setfreq_low == NULL || pfunc_slewing_done == NULL) {
printk(KERN_ERR "cpufreq: Can't find platform functions !\n");
rc = -ENODEV;
goto bail;
}
/* Check that we have complete sets */
if (pfunc_cpu0_volt_high == NULL || pfunc_cpu0_volt_low == NULL) {
pmf_put_function(pfunc_cpu0_volt_high);
pmf_put_function(pfunc_cpu0_volt_low);
pfunc_cpu0_volt_high = pfunc_cpu0_volt_low = NULL;
has_volt = 0;
}
if (!has_volt ||
pfunc_cpu1_volt_high == NULL || pfunc_cpu1_volt_low == NULL) {
pmf_put_function(pfunc_cpu1_volt_high);
pmf_put_function(pfunc_cpu1_volt_low);
pfunc_cpu1_volt_high = pfunc_cpu1_volt_low = NULL;
}
/* Note: The device tree also contains a "platform-set-values"
* function for which I haven't quite figured out the usage. It
* might have to be called on init and/or wakeup, I'm not too sure
* but things seem to work fine without it so far ...
*/
/* Get max frequency from device-tree */
valp = of_get_property(cpunode, "clock-frequency", NULL);
if (!valp) {
printk(KERN_ERR "cpufreq: Can't find CPU frequency !\n");
rc = -ENODEV;
goto bail;
}
max_freq = (*valp)/1000;
/* Now calculate reduced frequency by using the cpuid input freq
* ratio. This requires 64 bits math unless we are willing to lose
* some precision
*/
ih = *((u32 *)(eeprom + 0x10));
il = *((u32 *)(eeprom + 0x20));
/* Check for machines with no useful settings */
if (il == ih) {
printk(KERN_WARNING "cpufreq: No low frequency mode available"
" on this model !\n");
rc = -ENODEV;
goto bail;
}
min_freq = 0;
if (ih != 0 && il != 0)
min_freq = (max_freq * il) / ih;
/* Sanity check */
if (min_freq >= max_freq || min_freq < 1000) {
printk(KERN_ERR "cpufreq: Can't calculate low frequency !\n");
rc = -ENXIO;
goto bail;
}
g5_cpu_freqs[0].frequency = max_freq;
g5_cpu_freqs[1].frequency = min_freq;
/* Set callbacks */
transition_latency = CPUFREQ_ETERNAL;
g5_switch_volt = g5_pfunc_switch_volt;
g5_switch_freq = g5_pfunc_switch_freq;
g5_query_freq = g5_pfunc_query_freq;
/* Force apply current frequency to make sure everything is in
* sync (voltage is right for example). Firmware may leave us with
* a strange setting ...
*/
g5_switch_volt(CPUFREQ_HIGH);
msleep(10);
g5_pmode_cur = -1;
g5_switch_freq(g5_query_freq());
printk(KERN_INFO "Registering G5 CPU frequency driver\n");
printk(KERN_INFO "Frequency method: i2c/pfunc, "
"Voltage method: %s\n", has_volt ? "i2c/pfunc" : "none");
printk(KERN_INFO "Low: %d Mhz, High: %d Mhz, Cur: %d MHz\n",
g5_cpu_freqs[1].frequency/1000,
g5_cpu_freqs[0].frequency/1000,
g5_cpu_freqs[g5_pmode_cur].frequency/1000);
rc = cpufreq_register_driver(&g5_cpufreq_driver);
bail:
if (rc != 0) {
pmf_put_function(pfunc_cpu_getfreq);
pmf_put_function(pfunc_cpu_setfreq_high);
pmf_put_function(pfunc_cpu_setfreq_low);
pmf_put_function(pfunc_slewing_done);
pmf_put_function(pfunc_cpu0_volt_high);
pmf_put_function(pfunc_cpu0_volt_low);
pmf_put_function(pfunc_cpu1_volt_high);
pmf_put_function(pfunc_cpu1_volt_low);
}
of_node_put(hwclock);
of_node_put(cpuid);
of_node_put(cpunode);
return rc;
}
static int __init g5_cpufreq_init(void)
{
struct device_node *cpus;
int rc = 0;
cpus = of_find_node_by_path("/cpus");
if (cpus == NULL) {
DBG("No /cpus node !\n");
return -ENODEV;
}
if (machine_is_compatible("PowerMac7,2") ||
machine_is_compatible("PowerMac7,3") ||
machine_is_compatible("RackMac3,1"))
rc = g5_pm72_cpufreq_init(cpus);
#ifdef CONFIG_PMAC_SMU
else
rc = g5_neo2_cpufreq_init(cpus);
#endif /* CONFIG_PMAC_SMU */
of_node_put(cpus);
return rc;
}
module_init(g5_cpufreq_init);
MODULE_LICENSE("GPL");

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,658 @@
/*
* Copyright (C) 2002 Benjamin Herrenschmidt (benh@kernel.crashing.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.
*
* Todo: - add support for the OF persistent properties
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/stddef.h>
#include <linux/string.h>
#include <linux/nvram.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/adb.h>
#include <linux/pmu.h>
#include <linux/bootmem.h>
#include <linux/completion.h>
#include <linux/spinlock.h>
#include <asm/sections.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/prom.h>
#include <asm/machdep.h>
#include <asm/nvram.h>
#include "pmac.h"
#define DEBUG
#ifdef DEBUG
#define DBG(x...) printk(x)
#else
#define DBG(x...)
#endif
#define NVRAM_SIZE 0x2000 /* 8kB of non-volatile RAM */
#define CORE99_SIGNATURE 0x5a
#define CORE99_ADLER_START 0x14
/* On Core99, nvram is either a sharp, a micron or an AMD flash */
#define SM_FLASH_STATUS_DONE 0x80
#define SM_FLASH_STATUS_ERR 0x38
#define SM_FLASH_CMD_ERASE_CONFIRM 0xd0
#define SM_FLASH_CMD_ERASE_SETUP 0x20
#define SM_FLASH_CMD_RESET 0xff
#define SM_FLASH_CMD_WRITE_SETUP 0x40
#define SM_FLASH_CMD_CLEAR_STATUS 0x50
#define SM_FLASH_CMD_READ_STATUS 0x70
/* CHRP NVRAM header */
struct chrp_header {
u8 signature;
u8 cksum;
u16 len;
char name[12];
u8 data[0];
};
struct core99_header {
struct chrp_header hdr;
u32 adler;
u32 generation;
u32 reserved[2];
};
/*
* Read and write the non-volatile RAM on PowerMacs and CHRP machines.
*/
static int nvram_naddrs;
static volatile unsigned char __iomem *nvram_data;
static int is_core_99;
static int core99_bank = 0;
static int nvram_partitions[3];
// XXX Turn that into a sem
static DEFINE_SPINLOCK(nv_lock);
static int (*core99_write_bank)(int bank, u8* datas);
static int (*core99_erase_bank)(int bank);
static char *nvram_image;
static unsigned char core99_nvram_read_byte(int addr)
{
if (nvram_image == NULL)
return 0xff;
return nvram_image[addr];
}
static void core99_nvram_write_byte(int addr, unsigned char val)
{
if (nvram_image == NULL)
return;
nvram_image[addr] = val;
}
static ssize_t core99_nvram_read(char *buf, size_t count, loff_t *index)
{
int i;
if (nvram_image == NULL)
return -ENODEV;
if (*index > NVRAM_SIZE)
return 0;
i = *index;
if (i + count > NVRAM_SIZE)
count = NVRAM_SIZE - i;
memcpy(buf, &nvram_image[i], count);
*index = i + count;
return count;
}
static ssize_t core99_nvram_write(char *buf, size_t count, loff_t *index)
{
int i;
if (nvram_image == NULL)
return -ENODEV;
if (*index > NVRAM_SIZE)
return 0;
i = *index;
if (i + count > NVRAM_SIZE)
count = NVRAM_SIZE - i;
memcpy(&nvram_image[i], buf, count);
*index = i + count;
return count;
}
static ssize_t core99_nvram_size(void)
{
if (nvram_image == NULL)
return -ENODEV;
return NVRAM_SIZE;
}
#ifdef CONFIG_PPC32
static volatile unsigned char __iomem *nvram_addr;
static int nvram_mult;
static unsigned char direct_nvram_read_byte(int addr)
{
return in_8(&nvram_data[(addr & (NVRAM_SIZE - 1)) * nvram_mult]);
}
static void direct_nvram_write_byte(int addr, unsigned char val)
{
out_8(&nvram_data[(addr & (NVRAM_SIZE - 1)) * nvram_mult], val);
}
static unsigned char indirect_nvram_read_byte(int addr)
{
unsigned char val;
unsigned long flags;
spin_lock_irqsave(&nv_lock, flags);
out_8(nvram_addr, addr >> 5);
val = in_8(&nvram_data[(addr & 0x1f) << 4]);
spin_unlock_irqrestore(&nv_lock, flags);
return val;
}
static void indirect_nvram_write_byte(int addr, unsigned char val)
{
unsigned long flags;
spin_lock_irqsave(&nv_lock, flags);
out_8(nvram_addr, addr >> 5);
out_8(&nvram_data[(addr & 0x1f) << 4], val);
spin_unlock_irqrestore(&nv_lock, flags);
}
#ifdef CONFIG_ADB_PMU
static void pmu_nvram_complete(struct adb_request *req)
{
if (req->arg)
complete((struct completion *)req->arg);
}
static unsigned char pmu_nvram_read_byte(int addr)
{
struct adb_request req;
DECLARE_COMPLETION_ONSTACK(req_complete);
req.arg = system_state == SYSTEM_RUNNING ? &req_complete : NULL;
if (pmu_request(&req, pmu_nvram_complete, 3, PMU_READ_NVRAM,
(addr >> 8) & 0xff, addr & 0xff))
return 0xff;
if (system_state == SYSTEM_RUNNING)
wait_for_completion(&req_complete);
while (!req.complete)
pmu_poll();
return req.reply[0];
}
static void pmu_nvram_write_byte(int addr, unsigned char val)
{
struct adb_request req;
DECLARE_COMPLETION_ONSTACK(req_complete);
req.arg = system_state == SYSTEM_RUNNING ? &req_complete : NULL;
if (pmu_request(&req, pmu_nvram_complete, 4, PMU_WRITE_NVRAM,
(addr >> 8) & 0xff, addr & 0xff, val))
return;
if (system_state == SYSTEM_RUNNING)
wait_for_completion(&req_complete);
while (!req.complete)
pmu_poll();
}
#endif /* CONFIG_ADB_PMU */
#endif /* CONFIG_PPC32 */
static u8 chrp_checksum(struct chrp_header* hdr)
{
u8 *ptr;
u16 sum = hdr->signature;
for (ptr = (u8 *)&hdr->len; ptr < hdr->data; ptr++)
sum += *ptr;
while (sum > 0xFF)
sum = (sum & 0xFF) + (sum>>8);
return sum;
}
static u32 core99_calc_adler(u8 *buffer)
{
int cnt;
u32 low, high;
buffer += CORE99_ADLER_START;
low = 1;
high = 0;
for (cnt=0; cnt<(NVRAM_SIZE-CORE99_ADLER_START); cnt++) {
if ((cnt % 5000) == 0) {
high %= 65521UL;
high %= 65521UL;
}
low += buffer[cnt];
high += low;
}
low %= 65521UL;
high %= 65521UL;
return (high << 16) | low;
}
static u32 core99_check(u8* datas)
{
struct core99_header* hdr99 = (struct core99_header*)datas;
if (hdr99->hdr.signature != CORE99_SIGNATURE) {
DBG("Invalid signature\n");
return 0;
}
if (hdr99->hdr.cksum != chrp_checksum(&hdr99->hdr)) {
DBG("Invalid checksum\n");
return 0;
}
if (hdr99->adler != core99_calc_adler(datas)) {
DBG("Invalid adler\n");
return 0;
}
return hdr99->generation;
}
static int sm_erase_bank(int bank)
{
int stat, i;
unsigned long timeout;
u8 __iomem *base = (u8 __iomem *)nvram_data + core99_bank*NVRAM_SIZE;
DBG("nvram: Sharp/Micron Erasing bank %d...\n", bank);
out_8(base, SM_FLASH_CMD_ERASE_SETUP);
out_8(base, SM_FLASH_CMD_ERASE_CONFIRM);
timeout = 0;
do {
if (++timeout > 1000000) {
printk(KERN_ERR "nvram: Sharp/Micron flash erase timeout !\n");
break;
}
out_8(base, SM_FLASH_CMD_READ_STATUS);
stat = in_8(base);
} while (!(stat & SM_FLASH_STATUS_DONE));
out_8(base, SM_FLASH_CMD_CLEAR_STATUS);
out_8(base, SM_FLASH_CMD_RESET);
for (i=0; i<NVRAM_SIZE; i++)
if (base[i] != 0xff) {
printk(KERN_ERR "nvram: Sharp/Micron flash erase failed !\n");
return -ENXIO;
}
return 0;
}
static int sm_write_bank(int bank, u8* datas)
{
int i, stat = 0;
unsigned long timeout;
u8 __iomem *base = (u8 __iomem *)nvram_data + core99_bank*NVRAM_SIZE;
DBG("nvram: Sharp/Micron Writing bank %d...\n", bank);
for (i=0; i<NVRAM_SIZE; i++) {
out_8(base+i, SM_FLASH_CMD_WRITE_SETUP);
udelay(1);
out_8(base+i, datas[i]);
timeout = 0;
do {
if (++timeout > 1000000) {
printk(KERN_ERR "nvram: Sharp/Micron flash write timeout !\n");
break;
}
out_8(base, SM_FLASH_CMD_READ_STATUS);
stat = in_8(base);
} while (!(stat & SM_FLASH_STATUS_DONE));
if (!(stat & SM_FLASH_STATUS_DONE))
break;
}
out_8(base, SM_FLASH_CMD_CLEAR_STATUS);
out_8(base, SM_FLASH_CMD_RESET);
for (i=0; i<NVRAM_SIZE; i++)
if (base[i] != datas[i]) {
printk(KERN_ERR "nvram: Sharp/Micron flash write failed !\n");
return -ENXIO;
}
return 0;
}
static int amd_erase_bank(int bank)
{
int i, stat = 0;
unsigned long timeout;
u8 __iomem *base = (u8 __iomem *)nvram_data + core99_bank*NVRAM_SIZE;
DBG("nvram: AMD Erasing bank %d...\n", bank);
/* Unlock 1 */
out_8(base+0x555, 0xaa);
udelay(1);
/* Unlock 2 */
out_8(base+0x2aa, 0x55);
udelay(1);
/* Sector-Erase */
out_8(base+0x555, 0x80);
udelay(1);
out_8(base+0x555, 0xaa);
udelay(1);
out_8(base+0x2aa, 0x55);
udelay(1);
out_8(base, 0x30);
udelay(1);
timeout = 0;
do {
if (++timeout > 1000000) {
printk(KERN_ERR "nvram: AMD flash erase timeout !\n");
break;
}
stat = in_8(base) ^ in_8(base);
} while (stat != 0);
/* Reset */
out_8(base, 0xf0);
udelay(1);
for (i=0; i<NVRAM_SIZE; i++)
if (base[i] != 0xff) {
printk(KERN_ERR "nvram: AMD flash erase failed !\n");
return -ENXIO;
}
return 0;
}
static int amd_write_bank(int bank, u8* datas)
{
int i, stat = 0;
unsigned long timeout;
u8 __iomem *base = (u8 __iomem *)nvram_data + core99_bank*NVRAM_SIZE;
DBG("nvram: AMD Writing bank %d...\n", bank);
for (i=0; i<NVRAM_SIZE; i++) {
/* Unlock 1 */
out_8(base+0x555, 0xaa);
udelay(1);
/* Unlock 2 */
out_8(base+0x2aa, 0x55);
udelay(1);
/* Write single word */
out_8(base+0x555, 0xa0);
udelay(1);
out_8(base+i, datas[i]);
timeout = 0;
do {
if (++timeout > 1000000) {
printk(KERN_ERR "nvram: AMD flash write timeout !\n");
break;
}
stat = in_8(base) ^ in_8(base);
} while (stat != 0);
if (stat != 0)
break;
}
/* Reset */
out_8(base, 0xf0);
udelay(1);
for (i=0; i<NVRAM_SIZE; i++)
if (base[i] != datas[i]) {
printk(KERN_ERR "nvram: AMD flash write failed !\n");
return -ENXIO;
}
return 0;
}
static void __init lookup_partitions(void)
{
u8 buffer[17];
int i, offset;
struct chrp_header* hdr;
if (pmac_newworld) {
nvram_partitions[pmac_nvram_OF] = -1;
nvram_partitions[pmac_nvram_XPRAM] = -1;
nvram_partitions[pmac_nvram_NR] = -1;
hdr = (struct chrp_header *)buffer;
offset = 0;
buffer[16] = 0;
do {
for (i=0;i<16;i++)
buffer[i] = ppc_md.nvram_read_val(offset+i);
if (!strcmp(hdr->name, "common"))
nvram_partitions[pmac_nvram_OF] = offset + 0x10;
if (!strcmp(hdr->name, "APL,MacOS75")) {
nvram_partitions[pmac_nvram_XPRAM] = offset + 0x10;
nvram_partitions[pmac_nvram_NR] = offset + 0x110;
}
offset += (hdr->len * 0x10);
} while(offset < NVRAM_SIZE);
} else {
nvram_partitions[pmac_nvram_OF] = 0x1800;
nvram_partitions[pmac_nvram_XPRAM] = 0x1300;
nvram_partitions[pmac_nvram_NR] = 0x1400;
}
DBG("nvram: OF partition at 0x%x\n", nvram_partitions[pmac_nvram_OF]);
DBG("nvram: XP partition at 0x%x\n", nvram_partitions[pmac_nvram_XPRAM]);
DBG("nvram: NR partition at 0x%x\n", nvram_partitions[pmac_nvram_NR]);
}
static void core99_nvram_sync(void)
{
struct core99_header* hdr99;
unsigned long flags;
if (!is_core_99 || !nvram_data || !nvram_image)
return;
spin_lock_irqsave(&nv_lock, flags);
if (!memcmp(nvram_image, (u8*)nvram_data + core99_bank*NVRAM_SIZE,
NVRAM_SIZE))
goto bail;
DBG("Updating nvram...\n");
hdr99 = (struct core99_header*)nvram_image;
hdr99->generation++;
hdr99->hdr.signature = CORE99_SIGNATURE;
hdr99->hdr.cksum = chrp_checksum(&hdr99->hdr);
hdr99->adler = core99_calc_adler(nvram_image);
core99_bank = core99_bank ? 0 : 1;
if (core99_erase_bank)
if (core99_erase_bank(core99_bank)) {
printk("nvram: Error erasing bank %d\n", core99_bank);
goto bail;
}
if (core99_write_bank)
if (core99_write_bank(core99_bank, nvram_image))
printk("nvram: Error writing bank %d\n", core99_bank);
bail:
spin_unlock_irqrestore(&nv_lock, flags);
#ifdef DEBUG
mdelay(2000);
#endif
}
static int __init core99_nvram_setup(struct device_node *dp, unsigned long addr)
{
int i;
u32 gen_bank0, gen_bank1;
if (nvram_naddrs < 1) {
printk(KERN_ERR "nvram: no address\n");
return -EINVAL;
}
nvram_image = alloc_bootmem(NVRAM_SIZE);
if (nvram_image == NULL) {
printk(KERN_ERR "nvram: can't allocate ram image\n");
return -ENOMEM;
}
nvram_data = ioremap(addr, NVRAM_SIZE*2);
nvram_naddrs = 1; /* Make sure we get the correct case */
DBG("nvram: Checking bank 0...\n");
gen_bank0 = core99_check((u8 *)nvram_data);
gen_bank1 = core99_check((u8 *)nvram_data + NVRAM_SIZE);
core99_bank = (gen_bank0 < gen_bank1) ? 1 : 0;
DBG("nvram: gen0=%d, gen1=%d\n", gen_bank0, gen_bank1);
DBG("nvram: Active bank is: %d\n", core99_bank);
for (i=0; i<NVRAM_SIZE; i++)
nvram_image[i] = nvram_data[i + core99_bank*NVRAM_SIZE];
ppc_md.nvram_read_val = core99_nvram_read_byte;
ppc_md.nvram_write_val = core99_nvram_write_byte;
ppc_md.nvram_read = core99_nvram_read;
ppc_md.nvram_write = core99_nvram_write;
ppc_md.nvram_size = core99_nvram_size;
ppc_md.nvram_sync = core99_nvram_sync;
ppc_md.machine_shutdown = core99_nvram_sync;
/*
* Maybe we could be smarter here though making an exclusive list
* of known flash chips is a bit nasty as older OF didn't provide us
* with a useful "compatible" entry. A solution would be to really
* identify the chip using flash id commands and base ourselves on
* a list of known chips IDs
*/
if (of_device_is_compatible(dp, "amd-0137")) {
core99_erase_bank = amd_erase_bank;
core99_write_bank = amd_write_bank;
} else {
core99_erase_bank = sm_erase_bank;
core99_write_bank = sm_write_bank;
}
return 0;
}
int __init pmac_nvram_init(void)
{
struct device_node *dp;
struct resource r1, r2;
unsigned int s1 = 0, s2 = 0;
int err = 0;
nvram_naddrs = 0;
dp = of_find_node_by_name(NULL, "nvram");
if (dp == NULL) {
printk(KERN_ERR "Can't find NVRAM device\n");
return -ENODEV;
}
/* Try to obtain an address */
if (of_address_to_resource(dp, 0, &r1) == 0) {
nvram_naddrs = 1;
s1 = (r1.end - r1.start) + 1;
if (of_address_to_resource(dp, 1, &r2) == 0) {
nvram_naddrs = 2;
s2 = (r2.end - r2.start) + 1;
}
}
is_core_99 = of_device_is_compatible(dp, "nvram,flash");
if (is_core_99) {
err = core99_nvram_setup(dp, r1.start);
goto bail;
}
#ifdef CONFIG_PPC32
if (machine_is(chrp) && nvram_naddrs == 1) {
nvram_data = ioremap(r1.start, s1);
nvram_mult = 1;
ppc_md.nvram_read_val = direct_nvram_read_byte;
ppc_md.nvram_write_val = direct_nvram_write_byte;
} else if (nvram_naddrs == 1) {
nvram_data = ioremap(r1.start, s1);
nvram_mult = (s1 + NVRAM_SIZE - 1) / NVRAM_SIZE;
ppc_md.nvram_read_val = direct_nvram_read_byte;
ppc_md.nvram_write_val = direct_nvram_write_byte;
} else if (nvram_naddrs == 2) {
nvram_addr = ioremap(r1.start, s1);
nvram_data = ioremap(r2.start, s2);
ppc_md.nvram_read_val = indirect_nvram_read_byte;
ppc_md.nvram_write_val = indirect_nvram_write_byte;
} else if (nvram_naddrs == 0 && sys_ctrler == SYS_CTRLER_PMU) {
#ifdef CONFIG_ADB_PMU
nvram_naddrs = -1;
ppc_md.nvram_read_val = pmu_nvram_read_byte;
ppc_md.nvram_write_val = pmu_nvram_write_byte;
#endif /* CONFIG_ADB_PMU */
} else {
printk(KERN_ERR "Incompatible type of NVRAM\n");
err = -ENXIO;
}
#endif /* CONFIG_PPC32 */
bail:
of_node_put(dp);
if (err == 0)
lookup_partitions();
return err;
}
int pmac_get_partition(int partition)
{
return nvram_partitions[partition];
}
u8 pmac_xpram_read(int xpaddr)
{
int offset = pmac_get_partition(pmac_nvram_XPRAM);
if (offset < 0 || xpaddr < 0 || xpaddr > 0x100)
return 0xff;
return ppc_md.nvram_read_val(xpaddr + offset);
}
void pmac_xpram_write(int xpaddr, u8 data)
{
int offset = pmac_get_partition(pmac_nvram_XPRAM);
if (offset < 0 || xpaddr < 0 || xpaddr > 0x100)
return;
ppc_md.nvram_write_val(xpaddr + offset, data);
}
EXPORT_SYMBOL(pmac_get_partition);
EXPORT_SYMBOL(pmac_xpram_read);
EXPORT_SYMBOL(pmac_xpram_write);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,409 @@
#include <linux/types.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <asm/pmac_feature.h>
#include <asm/pmac_pfunc.h>
#undef DEBUG
#ifdef DEBUG
#define DBG(fmt...) printk(fmt)
#else
#define DBG(fmt...)
#endif
static irqreturn_t macio_gpio_irq(int irq, void *data)
{
pmf_do_irq(data);
return IRQ_HANDLED;
}
static int macio_do_gpio_irq_enable(struct pmf_function *func)
{
unsigned int irq = irq_of_parse_and_map(func->node, 0);
if (irq == NO_IRQ)
return -EINVAL;
return request_irq(irq, macio_gpio_irq, 0, func->node->name, func);
}
static int macio_do_gpio_irq_disable(struct pmf_function *func)
{
unsigned int irq = irq_of_parse_and_map(func->node, 0);
if (irq == NO_IRQ)
return -EINVAL;
free_irq(irq, func);
return 0;
}
static int macio_do_gpio_write(PMF_STD_ARGS, u8 value, u8 mask)
{
u8 __iomem *addr = (u8 __iomem *)func->driver_data;
unsigned long flags;
u8 tmp;
/* Check polarity */
if (args && args->count && !args->u[0].v)
value = ~value;
/* Toggle the GPIO */
spin_lock_irqsave(&feature_lock, flags);
tmp = readb(addr);
tmp = (tmp & ~mask) | (value & mask);
DBG("Do write 0x%02x to GPIO %s (%p)\n",
tmp, func->node->full_name, addr);
writeb(tmp, addr);
spin_unlock_irqrestore(&feature_lock, flags);
return 0;
}
static int macio_do_gpio_read(PMF_STD_ARGS, u8 mask, int rshift, u8 xor)
{
u8 __iomem *addr = (u8 __iomem *)func->driver_data;
u32 value;
/* Check if we have room for reply */
if (args == NULL || args->count == 0 || args->u[0].p == NULL)
return -EINVAL;
value = readb(addr);
*args->u[0].p = ((value & mask) >> rshift) ^ xor;
return 0;
}
static int macio_do_delay(PMF_STD_ARGS, u32 duration)
{
/* assume we can sleep ! */
msleep((duration + 999) / 1000);
return 0;
}
static struct pmf_handlers macio_gpio_handlers = {
.irq_enable = macio_do_gpio_irq_enable,
.irq_disable = macio_do_gpio_irq_disable,
.write_gpio = macio_do_gpio_write,
.read_gpio = macio_do_gpio_read,
.delay = macio_do_delay,
};
static void macio_gpio_init_one(struct macio_chip *macio)
{
struct device_node *gparent, *gp;
/*
* Find the "gpio" parent node
*/
for (gparent = NULL;
(gparent = of_get_next_child(macio->of_node, gparent)) != NULL;)
if (strcmp(gparent->name, "gpio") == 0)
break;
if (gparent == NULL)
return;
DBG("Installing GPIO functions for macio %s\n",
macio->of_node->full_name);
/*
* Ok, got one, we dont need anything special to track them down, so
* we just create them all
*/
for (gp = NULL; (gp = of_get_next_child(gparent, gp)) != NULL;) {
const u32 *reg = of_get_property(gp, "reg", NULL);
unsigned long offset;
if (reg == NULL)
continue;
offset = *reg;
/* Deal with old style device-tree. We can safely hard code the
* offset for now too even if it's a bit gross ...
*/
if (offset < 0x50)
offset += 0x50;
offset += (unsigned long)macio->base;
pmf_register_driver(gp, &macio_gpio_handlers, (void *)offset);
}
DBG("Calling initial GPIO functions for macio %s\n",
macio->of_node->full_name);
/* And now we run all the init ones */
for (gp = NULL; (gp = of_get_next_child(gparent, gp)) != NULL;)
pmf_do_functions(gp, NULL, 0, PMF_FLAGS_ON_INIT, NULL);
/* Note: We do not at this point implement the "at sleep" or "at wake"
* functions. I yet to find any for GPIOs anyway
*/
}
static int macio_do_write_reg32(PMF_STD_ARGS, u32 offset, u32 value, u32 mask)
{
struct macio_chip *macio = func->driver_data;
unsigned long flags;
spin_lock_irqsave(&feature_lock, flags);
MACIO_OUT32(offset, (MACIO_IN32(offset) & ~mask) | (value & mask));
spin_unlock_irqrestore(&feature_lock, flags);
return 0;
}
static int macio_do_read_reg32(PMF_STD_ARGS, u32 offset)
{
struct macio_chip *macio = func->driver_data;
/* Check if we have room for reply */
if (args == NULL || args->count == 0 || args->u[0].p == NULL)
return -EINVAL;
*args->u[0].p = MACIO_IN32(offset);
return 0;
}
static int macio_do_write_reg8(PMF_STD_ARGS, u32 offset, u8 value, u8 mask)
{
struct macio_chip *macio = func->driver_data;
unsigned long flags;
spin_lock_irqsave(&feature_lock, flags);
MACIO_OUT8(offset, (MACIO_IN8(offset) & ~mask) | (value & mask));
spin_unlock_irqrestore(&feature_lock, flags);
return 0;
}
static int macio_do_read_reg8(PMF_STD_ARGS, u32 offset)
{
struct macio_chip *macio = func->driver_data;
/* Check if we have room for reply */
if (args == NULL || args->count == 0 || args->u[0].p == NULL)
return -EINVAL;
*((u8 *)(args->u[0].p)) = MACIO_IN8(offset);
return 0;
}
static int macio_do_read_reg32_msrx(PMF_STD_ARGS, u32 offset, u32 mask,
u32 shift, u32 xor)
{
struct macio_chip *macio = func->driver_data;
/* Check if we have room for reply */
if (args == NULL || args->count == 0 || args->u[0].p == NULL)
return -EINVAL;
*args->u[0].p = ((MACIO_IN32(offset) & mask) >> shift) ^ xor;
return 0;
}
static int macio_do_read_reg8_msrx(PMF_STD_ARGS, u32 offset, u32 mask,
u32 shift, u32 xor)
{
struct macio_chip *macio = func->driver_data;
/* Check if we have room for reply */
if (args == NULL || args->count == 0 || args->u[0].p == NULL)
return -EINVAL;
*((u8 *)(args->u[0].p)) = ((MACIO_IN8(offset) & mask) >> shift) ^ xor;
return 0;
}
static int macio_do_write_reg32_slm(PMF_STD_ARGS, u32 offset, u32 shift,
u32 mask)
{
struct macio_chip *macio = func->driver_data;
unsigned long flags;
u32 tmp, val;
/* Check args */
if (args == NULL || args->count == 0)
return -EINVAL;
spin_lock_irqsave(&feature_lock, flags);
tmp = MACIO_IN32(offset);
val = args->u[0].v << shift;
tmp = (tmp & ~mask) | (val & mask);
MACIO_OUT32(offset, tmp);
spin_unlock_irqrestore(&feature_lock, flags);
return 0;
}
static int macio_do_write_reg8_slm(PMF_STD_ARGS, u32 offset, u32 shift,
u32 mask)
{
struct macio_chip *macio = func->driver_data;
unsigned long flags;
u32 tmp, val;
/* Check args */
if (args == NULL || args->count == 0)
return -EINVAL;
spin_lock_irqsave(&feature_lock, flags);
tmp = MACIO_IN8(offset);
val = args->u[0].v << shift;
tmp = (tmp & ~mask) | (val & mask);
MACIO_OUT8(offset, tmp);
spin_unlock_irqrestore(&feature_lock, flags);
return 0;
}
static struct pmf_handlers macio_mmio_handlers = {
.write_reg32 = macio_do_write_reg32,
.read_reg32 = macio_do_read_reg32,
.write_reg8 = macio_do_write_reg8,
.read_reg8 = macio_do_read_reg8,
.read_reg32_msrx = macio_do_read_reg32_msrx,
.read_reg8_msrx = macio_do_read_reg8_msrx,
.write_reg32_slm = macio_do_write_reg32_slm,
.write_reg8_slm = macio_do_write_reg8_slm,
.delay = macio_do_delay,
};
static void macio_mmio_init_one(struct macio_chip *macio)
{
DBG("Installing MMIO functions for macio %s\n",
macio->of_node->full_name);
pmf_register_driver(macio->of_node, &macio_mmio_handlers, macio);
}
static struct device_node *unin_hwclock;
static int unin_do_write_reg32(PMF_STD_ARGS, u32 offset, u32 value, u32 mask)
{
unsigned long flags;
spin_lock_irqsave(&feature_lock, flags);
/* This is fairly bogus in darwin, but it should work for our needs
* implemeted that way:
*/
UN_OUT(offset, (UN_IN(offset) & ~mask) | (value & mask));
spin_unlock_irqrestore(&feature_lock, flags);
return 0;
}
static struct pmf_handlers unin_mmio_handlers = {
.write_reg32 = unin_do_write_reg32,
.delay = macio_do_delay,
};
static void uninorth_install_pfunc(void)
{
struct device_node *np;
DBG("Installing functions for UniN %s\n",
uninorth_node->full_name);
/*
* Install handlers for the bridge itself
*/
pmf_register_driver(uninorth_node, &unin_mmio_handlers, NULL);
pmf_do_functions(uninorth_node, NULL, 0, PMF_FLAGS_ON_INIT, NULL);
/*
* Install handlers for the hwclock child if any
*/
for (np = NULL; (np = of_get_next_child(uninorth_node, np)) != NULL;)
if (strcmp(np->name, "hw-clock") == 0) {
unin_hwclock = np;
break;
}
if (unin_hwclock) {
DBG("Installing functions for UniN clock %s\n",
unin_hwclock->full_name);
pmf_register_driver(unin_hwclock, &unin_mmio_handlers, NULL);
pmf_do_functions(unin_hwclock, NULL, 0, PMF_FLAGS_ON_INIT,
NULL);
}
}
/* We export this as the SMP code might init us early */
int __init pmac_pfunc_base_install(void)
{
static int pfbase_inited;
int i;
if (pfbase_inited)
return 0;
pfbase_inited = 1;
if (!machine_is(powermac))
return 0;
DBG("Installing base platform functions...\n");
/*
* Locate mac-io chips and install handlers
*/
for (i = 0 ; i < MAX_MACIO_CHIPS; i++) {
if (macio_chips[i].of_node) {
macio_mmio_init_one(&macio_chips[i]);
macio_gpio_init_one(&macio_chips[i]);
}
}
/*
* Install handlers for northbridge and direct mapped hwclock
* if any. We do not implement the config space access callback
* which is only ever used for functions that we do not call in
* the current driver (enabling/disabling cells in U2, mostly used
* to restore the PCI settings, we do that differently)
*/
if (uninorth_node && uninorth_base)
uninorth_install_pfunc();
DBG("All base functions installed\n");
return 0;
}
machine_arch_initcall(powermac, pmac_pfunc_base_install);
#ifdef CONFIG_PM
/* Those can be called by pmac_feature. Ultimately, I should use a sysdev
* or a device, but for now, that's good enough until I sort out some
* ordering issues. Also, we do not bother with GPIOs, as so far I yet have
* to see a case where a GPIO function has the on-suspend or on-resume bit
*/
void pmac_pfunc_base_suspend(void)
{
int i;
for (i = 0 ; i < MAX_MACIO_CHIPS; i++) {
if (macio_chips[i].of_node)
pmf_do_functions(macio_chips[i].of_node, NULL, 0,
PMF_FLAGS_ON_SLEEP, NULL);
}
if (uninorth_node)
pmf_do_functions(uninorth_node, NULL, 0,
PMF_FLAGS_ON_SLEEP, NULL);
if (unin_hwclock)
pmf_do_functions(unin_hwclock, NULL, 0,
PMF_FLAGS_ON_SLEEP, NULL);
}
void pmac_pfunc_base_resume(void)
{
int i;
if (unin_hwclock)
pmf_do_functions(unin_hwclock, NULL, 0,
PMF_FLAGS_ON_WAKE, NULL);
if (uninorth_node)
pmf_do_functions(uninorth_node, NULL, 0,
PMF_FLAGS_ON_WAKE, NULL);
for (i = 0 ; i < MAX_MACIO_CHIPS; i++) {
if (macio_chips[i].of_node)
pmf_do_functions(macio_chips[i].of_node, NULL, 0,
PMF_FLAGS_ON_WAKE, NULL);
}
}
#endif /* CONFIG_PM */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,686 @@
/*
* Support for the interrupt controllers found on Power Macintosh,
* currently Apple's "Grand Central" interrupt controller in all
* it's incarnations. OpenPIC support used on newer machines is
* in a separate file
*
* Copyright (C) 1997 Paul Mackerras (paulus@samba.org)
* Copyright (C) 2005 Benjamin Herrenschmidt (benh@kernel.crashing.org)
* IBM, Corp.
*
* 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/stddef.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/signal.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/sysdev.h>
#include <linux/adb.h>
#include <linux/pmu.h>
#include <linux/module.h>
#include <asm/sections.h>
#include <asm/io.h>
#include <asm/smp.h>
#include <asm/prom.h>
#include <asm/pci-bridge.h>
#include <asm/time.h>
#include <asm/pmac_feature.h>
#include <asm/mpic.h>
#include <asm/xmon.h>
#include "pmac.h"
#ifdef CONFIG_PPC32
struct pmac_irq_hw {
unsigned int event;
unsigned int enable;
unsigned int ack;
unsigned int level;
};
/* Default addresses */
static volatile struct pmac_irq_hw __iomem *pmac_irq_hw[4];
#define GC_LEVEL_MASK 0x3ff00000
#define OHARE_LEVEL_MASK 0x1ff00000
#define HEATHROW_LEVEL_MASK 0x1ff00000
static int max_irqs;
static int max_real_irqs;
static u32 level_mask[4];
static DEFINE_SPINLOCK(pmac_pic_lock);
#define NR_MASK_WORDS ((NR_IRQS + 31) / 32)
static unsigned long ppc_lost_interrupts[NR_MASK_WORDS];
static unsigned long ppc_cached_irq_mask[NR_MASK_WORDS];
static int pmac_irq_cascade = -1;
static struct irq_host *pmac_pic_host;
static void __pmac_retrigger(unsigned int irq_nr)
{
if (irq_nr >= max_real_irqs && pmac_irq_cascade > 0) {
__set_bit(irq_nr, ppc_lost_interrupts);
irq_nr = pmac_irq_cascade;
mb();
}
if (!__test_and_set_bit(irq_nr, ppc_lost_interrupts)) {
atomic_inc(&ppc_n_lost_interrupts);
set_dec(1);
}
}
static void pmac_mask_and_ack_irq(unsigned int virq)
{
unsigned int src = irq_map[virq].hwirq;
unsigned long bit = 1UL << (src & 0x1f);
int i = src >> 5;
unsigned long flags;
spin_lock_irqsave(&pmac_pic_lock, flags);
__clear_bit(src, ppc_cached_irq_mask);
if (__test_and_clear_bit(src, ppc_lost_interrupts))
atomic_dec(&ppc_n_lost_interrupts);
out_le32(&pmac_irq_hw[i]->enable, ppc_cached_irq_mask[i]);
out_le32(&pmac_irq_hw[i]->ack, bit);
do {
/* make sure ack gets to controller before we enable
interrupts */
mb();
} while((in_le32(&pmac_irq_hw[i]->enable) & bit)
!= (ppc_cached_irq_mask[i] & bit));
spin_unlock_irqrestore(&pmac_pic_lock, flags);
}
static void pmac_ack_irq(unsigned int virq)
{
unsigned int src = irq_map[virq].hwirq;
unsigned long bit = 1UL << (src & 0x1f);
int i = src >> 5;
unsigned long flags;
spin_lock_irqsave(&pmac_pic_lock, flags);
if (__test_and_clear_bit(src, ppc_lost_interrupts))
atomic_dec(&ppc_n_lost_interrupts);
out_le32(&pmac_irq_hw[i]->ack, bit);
(void)in_le32(&pmac_irq_hw[i]->ack);
spin_unlock_irqrestore(&pmac_pic_lock, flags);
}
static void __pmac_set_irq_mask(unsigned int irq_nr, int nokicklost)
{
unsigned long bit = 1UL << (irq_nr & 0x1f);
int i = irq_nr >> 5;
if ((unsigned)irq_nr >= max_irqs)
return;
/* enable unmasked interrupts */
out_le32(&pmac_irq_hw[i]->enable, ppc_cached_irq_mask[i]);
do {
/* make sure mask gets to controller before we
return to user */
mb();
} while((in_le32(&pmac_irq_hw[i]->enable) & bit)
!= (ppc_cached_irq_mask[i] & bit));
/*
* Unfortunately, setting the bit in the enable register
* when the device interrupt is already on *doesn't* set
* the bit in the flag register or request another interrupt.
*/
if (bit & ppc_cached_irq_mask[i] & in_le32(&pmac_irq_hw[i]->level))
__pmac_retrigger(irq_nr);
}
/* When an irq gets requested for the first client, if it's an
* edge interrupt, we clear any previous one on the controller
*/
static unsigned int pmac_startup_irq(unsigned int virq)
{
unsigned long flags;
unsigned int src = irq_map[virq].hwirq;
unsigned long bit = 1UL << (src & 0x1f);
int i = src >> 5;
spin_lock_irqsave(&pmac_pic_lock, flags);
if ((irq_desc[virq].status & IRQ_LEVEL) == 0)
out_le32(&pmac_irq_hw[i]->ack, bit);
__set_bit(src, ppc_cached_irq_mask);
__pmac_set_irq_mask(src, 0);
spin_unlock_irqrestore(&pmac_pic_lock, flags);
return 0;
}
static void pmac_mask_irq(unsigned int virq)
{
unsigned long flags;
unsigned int src = irq_map[virq].hwirq;
spin_lock_irqsave(&pmac_pic_lock, flags);
__clear_bit(src, ppc_cached_irq_mask);
__pmac_set_irq_mask(src, 1);
spin_unlock_irqrestore(&pmac_pic_lock, flags);
}
static void pmac_unmask_irq(unsigned int virq)
{
unsigned long flags;
unsigned int src = irq_map[virq].hwirq;
spin_lock_irqsave(&pmac_pic_lock, flags);
__set_bit(src, ppc_cached_irq_mask);
__pmac_set_irq_mask(src, 0);
spin_unlock_irqrestore(&pmac_pic_lock, flags);
}
static int pmac_retrigger(unsigned int virq)
{
unsigned long flags;
spin_lock_irqsave(&pmac_pic_lock, flags);
__pmac_retrigger(irq_map[virq].hwirq);
spin_unlock_irqrestore(&pmac_pic_lock, flags);
return 1;
}
static struct irq_chip pmac_pic = {
.typename = " PMAC-PIC ",
.startup = pmac_startup_irq,
.mask = pmac_mask_irq,
.ack = pmac_ack_irq,
.mask_ack = pmac_mask_and_ack_irq,
.unmask = pmac_unmask_irq,
.retrigger = pmac_retrigger,
};
static irqreturn_t gatwick_action(int cpl, void *dev_id)
{
unsigned long flags;
int irq, bits;
int rc = IRQ_NONE;
spin_lock_irqsave(&pmac_pic_lock, flags);
for (irq = max_irqs; (irq -= 32) >= max_real_irqs; ) {
int i = irq >> 5;
bits = in_le32(&pmac_irq_hw[i]->event) | ppc_lost_interrupts[i];
/* We must read level interrupts from the level register */
bits |= (in_le32(&pmac_irq_hw[i]->level) & level_mask[i]);
bits &= ppc_cached_irq_mask[i];
if (bits == 0)
continue;
irq += __ilog2(bits);
spin_unlock_irqrestore(&pmac_pic_lock, flags);
generic_handle_irq(irq);
spin_lock_irqsave(&pmac_pic_lock, flags);
rc = IRQ_HANDLED;
}
spin_unlock_irqrestore(&pmac_pic_lock, flags);
return rc;
}
static unsigned int pmac_pic_get_irq(void)
{
int irq;
unsigned long bits = 0;
unsigned long flags;
#ifdef CONFIG_SMP
void psurge_smp_message_recv(void);
/* IPI's are a hack on the powersurge -- Cort */
if ( smp_processor_id() != 0 ) {
psurge_smp_message_recv();
return NO_IRQ_IGNORE; /* ignore, already handled */
}
#endif /* CONFIG_SMP */
spin_lock_irqsave(&pmac_pic_lock, flags);
for (irq = max_real_irqs; (irq -= 32) >= 0; ) {
int i = irq >> 5;
bits = in_le32(&pmac_irq_hw[i]->event) | ppc_lost_interrupts[i];
/* We must read level interrupts from the level register */
bits |= (in_le32(&pmac_irq_hw[i]->level) & level_mask[i]);
bits &= ppc_cached_irq_mask[i];
if (bits == 0)
continue;
irq += __ilog2(bits);
break;
}
spin_unlock_irqrestore(&pmac_pic_lock, flags);
if (unlikely(irq < 0))
return NO_IRQ;
return irq_linear_revmap(pmac_pic_host, irq);
}
#ifdef CONFIG_XMON
static struct irqaction xmon_action = {
.handler = xmon_irq,
.flags = 0,
.name = "NMI - XMON"
};
#endif
static struct irqaction gatwick_cascade_action = {
.handler = gatwick_action,
.flags = IRQF_DISABLED,
.name = "cascade",
};
static int pmac_pic_host_match(struct irq_host *h, struct device_node *node)
{
/* We match all, we don't always have a node anyway */
return 1;
}
static int pmac_pic_host_map(struct irq_host *h, unsigned int virq,
irq_hw_number_t hw)
{
struct irq_desc *desc = get_irq_desc(virq);
int level;
if (hw >= max_irqs)
return -EINVAL;
/* Mark level interrupts, set delayed disable for edge ones and set
* handlers
*/
level = !!(level_mask[hw >> 5] & (1UL << (hw & 0x1f)));
if (level)
desc->status |= IRQ_LEVEL;
set_irq_chip_and_handler(virq, &pmac_pic, level ?
handle_level_irq : handle_edge_irq);
return 0;
}
static int pmac_pic_host_xlate(struct irq_host *h, struct device_node *ct,
u32 *intspec, unsigned int intsize,
irq_hw_number_t *out_hwirq,
unsigned int *out_flags)
{
*out_flags = IRQ_TYPE_NONE;
*out_hwirq = *intspec;
return 0;
}
static struct irq_host_ops pmac_pic_host_ops = {
.match = pmac_pic_host_match,
.map = pmac_pic_host_map,
.xlate = pmac_pic_host_xlate,
};
static void __init pmac_pic_probe_oldstyle(void)
{
int i;
struct device_node *master = NULL;
struct device_node *slave = NULL;
u8 __iomem *addr;
struct resource r;
/* Set our get_irq function */
ppc_md.get_irq = pmac_pic_get_irq;
/*
* Find the interrupt controller type & node
*/
if ((master = of_find_node_by_name(NULL, "gc")) != NULL) {
max_irqs = max_real_irqs = 32;
level_mask[0] = GC_LEVEL_MASK;
} else if ((master = of_find_node_by_name(NULL, "ohare")) != NULL) {
max_irqs = max_real_irqs = 32;
level_mask[0] = OHARE_LEVEL_MASK;
/* We might have a second cascaded ohare */
slave = of_find_node_by_name(NULL, "pci106b,7");
if (slave) {
max_irqs = 64;
level_mask[1] = OHARE_LEVEL_MASK;
}
} else if ((master = of_find_node_by_name(NULL, "mac-io")) != NULL) {
max_irqs = max_real_irqs = 64;
level_mask[0] = HEATHROW_LEVEL_MASK;
level_mask[1] = 0;
/* We might have a second cascaded heathrow */
slave = of_find_node_by_name(master, "mac-io");
/* Check ordering of master & slave */
if (of_device_is_compatible(master, "gatwick")) {
struct device_node *tmp;
BUG_ON(slave == NULL);
tmp = master;
master = slave;
slave = tmp;
}
/* We found a slave */
if (slave) {
max_irqs = 128;
level_mask[2] = HEATHROW_LEVEL_MASK;
level_mask[3] = 0;
}
}
BUG_ON(master == NULL);
/*
* Allocate an irq host
*/
pmac_pic_host = irq_alloc_host(master, IRQ_HOST_MAP_LINEAR, max_irqs,
&pmac_pic_host_ops,
max_irqs);
BUG_ON(pmac_pic_host == NULL);
irq_set_default_host(pmac_pic_host);
/* Get addresses of first controller if we have a node for it */
BUG_ON(of_address_to_resource(master, 0, &r));
/* Map interrupts of primary controller */
addr = (u8 __iomem *) ioremap(r.start, 0x40);
i = 0;
pmac_irq_hw[i++] = (volatile struct pmac_irq_hw __iomem *)
(addr + 0x20);
if (max_real_irqs > 32)
pmac_irq_hw[i++] = (volatile struct pmac_irq_hw __iomem *)
(addr + 0x10);
of_node_put(master);
printk(KERN_INFO "irq: Found primary Apple PIC %s for %d irqs\n",
master->full_name, max_real_irqs);
/* Map interrupts of cascaded controller */
if (slave && !of_address_to_resource(slave, 0, &r)) {
addr = (u8 __iomem *)ioremap(r.start, 0x40);
pmac_irq_hw[i++] = (volatile struct pmac_irq_hw __iomem *)
(addr + 0x20);
if (max_irqs > 64)
pmac_irq_hw[i++] =
(volatile struct pmac_irq_hw __iomem *)
(addr + 0x10);
pmac_irq_cascade = irq_of_parse_and_map(slave, 0);
printk(KERN_INFO "irq: Found slave Apple PIC %s for %d irqs"
" cascade: %d\n", slave->full_name,
max_irqs - max_real_irqs, pmac_irq_cascade);
}
of_node_put(slave);
/* Disable all interrupts in all controllers */
for (i = 0; i * 32 < max_irqs; ++i)
out_le32(&pmac_irq_hw[i]->enable, 0);
/* Hookup cascade irq */
if (slave && pmac_irq_cascade != NO_IRQ)
setup_irq(pmac_irq_cascade, &gatwick_cascade_action);
printk(KERN_INFO "irq: System has %d possible interrupts\n", max_irqs);
#ifdef CONFIG_XMON
setup_irq(irq_create_mapping(NULL, 20), &xmon_action);
#endif
}
#endif /* CONFIG_PPC32 */
static void pmac_u3_cascade(unsigned int irq, struct irq_desc *desc)
{
struct mpic *mpic = desc->handler_data;
unsigned int cascade_irq = mpic_get_one_irq(mpic);
if (cascade_irq != NO_IRQ)
generic_handle_irq(cascade_irq);
desc->chip->eoi(irq);
}
static void __init pmac_pic_setup_mpic_nmi(struct mpic *mpic)
{
#if defined(CONFIG_XMON) && defined(CONFIG_PPC32)
struct device_node* pswitch;
int nmi_irq;
pswitch = of_find_node_by_name(NULL, "programmer-switch");
if (pswitch) {
nmi_irq = irq_of_parse_and_map(pswitch, 0);
if (nmi_irq != NO_IRQ) {
mpic_irq_set_priority(nmi_irq, 9);
setup_irq(nmi_irq, &xmon_action);
}
of_node_put(pswitch);
}
#endif /* defined(CONFIG_XMON) && defined(CONFIG_PPC32) */
}
static struct mpic * __init pmac_setup_one_mpic(struct device_node *np,
int master)
{
const char *name = master ? " MPIC 1 " : " MPIC 2 ";
struct resource r;
struct mpic *mpic;
unsigned int flags = master ? MPIC_PRIMARY : 0;
int rc;
rc = of_address_to_resource(np, 0, &r);
if (rc)
return NULL;
pmac_call_feature(PMAC_FTR_ENABLE_MPIC, np, 0, 0);
flags |= MPIC_WANTS_RESET;
if (of_get_property(np, "big-endian", NULL))
flags |= MPIC_BIG_ENDIAN;
/* Primary Big Endian means HT interrupts. This is quite dodgy
* but works until I find a better way
*/
if (master && (flags & MPIC_BIG_ENDIAN))
flags |= MPIC_U3_HT_IRQS;
mpic = mpic_alloc(np, r.start, flags, 0, 0, name);
if (mpic == NULL)
return NULL;
mpic_init(mpic);
return mpic;
}
static int __init pmac_pic_probe_mpic(void)
{
struct mpic *mpic1, *mpic2;
struct device_node *np, *master = NULL, *slave = NULL;
unsigned int cascade;
/* We can have up to 2 MPICs cascaded */
for (np = NULL; (np = of_find_node_by_type(np, "open-pic"))
!= NULL;) {
if (master == NULL &&
of_get_property(np, "interrupts", NULL) == NULL)
master = of_node_get(np);
else if (slave == NULL)
slave = of_node_get(np);
if (master && slave)
break;
}
/* Check for bogus setups */
if (master == NULL && slave != NULL) {
master = slave;
slave = NULL;
}
/* Not found, default to good old pmac pic */
if (master == NULL)
return -ENODEV;
/* Set master handler */
ppc_md.get_irq = mpic_get_irq;
/* Setup master */
mpic1 = pmac_setup_one_mpic(master, 1);
BUG_ON(mpic1 == NULL);
/* Install NMI if any */
pmac_pic_setup_mpic_nmi(mpic1);
of_node_put(master);
/* No slave, let's go out */
if (slave == NULL)
return 0;
/* Get/Map slave interrupt */
cascade = irq_of_parse_and_map(slave, 0);
if (cascade == NO_IRQ) {
printk(KERN_ERR "Failed to map cascade IRQ\n");
return 0;
}
mpic2 = pmac_setup_one_mpic(slave, 0);
if (mpic2 == NULL) {
printk(KERN_ERR "Failed to setup slave MPIC\n");
of_node_put(slave);
return 0;
}
set_irq_data(cascade, mpic2);
set_irq_chained_handler(cascade, pmac_u3_cascade);
of_node_put(slave);
return 0;
}
void __init pmac_pic_init(void)
{
unsigned int flags = 0;
/* We configure the OF parsing based on our oldworld vs. newworld
* platform type and wether we were booted by BootX.
*/
#ifdef CONFIG_PPC32
if (!pmac_newworld)
flags |= OF_IMAP_OLDWORLD_MAC;
if (of_get_property(of_chosen, "linux,bootx", NULL) != NULL)
flags |= OF_IMAP_NO_PHANDLE;
#endif /* CONFIG_PPC_32 */
of_irq_map_init(flags);
/* We first try to detect Apple's new Core99 chipset, since mac-io
* is quite different on those machines and contains an IBM MPIC2.
*/
if (pmac_pic_probe_mpic() == 0)
return;
#ifdef CONFIG_PPC32
pmac_pic_probe_oldstyle();
#endif
}
#if defined(CONFIG_PM) && defined(CONFIG_PPC32)
/*
* These procedures are used in implementing sleep on the powerbooks.
* sleep_save_intrs() saves the states of all interrupt enables
* and disables all interrupts except for the nominated one.
* sleep_restore_intrs() restores the states of all interrupt enables.
*/
unsigned long sleep_save_mask[2];
/* This used to be passed by the PMU driver but that link got
* broken with the new driver model. We use this tweak for now...
* We really want to do things differently though...
*/
static int pmacpic_find_viaint(void)
{
int viaint = -1;
#ifdef CONFIG_ADB_PMU
struct device_node *np;
if (pmu_get_model() != PMU_OHARE_BASED)
goto not_found;
np = of_find_node_by_name(NULL, "via-pmu");
if (np == NULL)
goto not_found;
viaint = irq_of_parse_and_map(np, 0);
not_found:
#endif /* CONFIG_ADB_PMU */
return viaint;
}
static int pmacpic_suspend(struct sys_device *sysdev, pm_message_t state)
{
int viaint = pmacpic_find_viaint();
sleep_save_mask[0] = ppc_cached_irq_mask[0];
sleep_save_mask[1] = ppc_cached_irq_mask[1];
ppc_cached_irq_mask[0] = 0;
ppc_cached_irq_mask[1] = 0;
if (viaint > 0)
set_bit(viaint, ppc_cached_irq_mask);
out_le32(&pmac_irq_hw[0]->enable, ppc_cached_irq_mask[0]);
if (max_real_irqs > 32)
out_le32(&pmac_irq_hw[1]->enable, ppc_cached_irq_mask[1]);
(void)in_le32(&pmac_irq_hw[0]->event);
/* make sure mask gets to controller before we return to caller */
mb();
(void)in_le32(&pmac_irq_hw[0]->enable);
return 0;
}
static int pmacpic_resume(struct sys_device *sysdev)
{
int i;
out_le32(&pmac_irq_hw[0]->enable, 0);
if (max_real_irqs > 32)
out_le32(&pmac_irq_hw[1]->enable, 0);
mb();
for (i = 0; i < max_real_irqs; ++i)
if (test_bit(i, sleep_save_mask))
pmac_unmask_irq(i);
return 0;
}
#endif /* CONFIG_PM && CONFIG_PPC32 */
static struct sysdev_class pmacpic_sysclass = {
.name = "pmac_pic",
};
static struct sys_device device_pmacpic = {
.id = 0,
.cls = &pmacpic_sysclass,
};
static struct sysdev_driver driver_pmacpic = {
#if defined(CONFIG_PM) && defined(CONFIG_PPC32)
.suspend = &pmacpic_suspend,
.resume = &pmacpic_resume,
#endif /* CONFIG_PM && CONFIG_PPC32 */
};
static int __init init_pmacpic_sysfs(void)
{
#ifdef CONFIG_PPC32
if (max_irqs == 0)
return -ENODEV;
#endif
printk(KERN_DEBUG "Registering pmac pic with sysfs...\n");
sysdev_class_register(&pmacpic_sysclass);
sysdev_register(&device_pmacpic);
sysdev_driver_register(&pmacpic_sysclass, &driver_pmacpic);
return 0;
}
machine_subsys_initcall(powermac, init_pmacpic_sysfs);

View File

@@ -0,0 +1,11 @@
#ifndef __PPC_PLATFORMS_PMAC_PIC_H
#define __PPC_PLATFORMS_PMAC_PIC_H
#include <linux/irq.h>
extern struct irq_chip pmac_pic;
extern void pmac_pic_init(void);
extern int pmac_get_irq(void);
#endif /* __PPC_PLATFORMS_PMAC_PIC_H */

View File

@@ -0,0 +1,40 @@
#ifndef __PMAC_H__
#define __PMAC_H__
#include <linux/pci.h>
#include <linux/irq.h>
/*
* Declaration for the various functions exported by the
* pmac_* files. Mostly for use by pmac_setup
*/
struct rtc_time;
extern int pmac_newworld;
extern long pmac_time_init(void);
extern unsigned long pmac_get_boot_time(void);
extern void pmac_get_rtc_time(struct rtc_time *);
extern int pmac_set_rtc_time(struct rtc_time *);
extern void pmac_read_rtc_time(void);
extern void pmac_calibrate_decr(void);
extern void pmac_pci_irq_fixup(struct pci_dev *);
extern void pmac_pci_init(void);
extern void pmac_nvram_update(void);
extern unsigned char pmac_nvram_read_byte(int addr);
extern void pmac_nvram_write_byte(int addr, unsigned char val);
extern int pmac_pci_enable_device_hook(struct pci_dev *dev);
extern void pmac_pcibios_after_init(void);
extern int of_show_percpuinfo(struct seq_file *m, int i);
extern void pmac_setup_pci_dma(void);
extern void pmac_check_ht_link(void);
extern void pmac_setup_smp(void);
extern int pmac_nvram_init(void);
extern void pmac_pic_init(void);
#endif /* __PMAC_H__ */

View File

@@ -0,0 +1,727 @@
/*
* Powermac setup and early boot code plus other random bits.
*
* PowerPC version
* Copyright (C) 1995-1996 Gary Thomas (gdt@linuxppc.org)
*
* Adapted for Power Macintosh by Paul Mackerras
* Copyright (C) 1996 Paul Mackerras (paulus@samba.org)
*
* Derived from "arch/alpha/kernel/setup.c"
* Copyright (C) 1995 Linus Torvalds
*
* Maintained by Benjamin Herrenschmidt (benh@kernel.crashing.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.
*
*/
/*
* bootup setup stuff..
*/
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/stddef.h>
#include <linux/unistd.h>
#include <linux/ptrace.h>
#include <linux/slab.h>
#include <linux/user.h>
#include <linux/tty.h>
#include <linux/string.h>
#include <linux/delay.h>
#include <linux/ioport.h>
#include <linux/major.h>
#include <linux/initrd.h>
#include <linux/vt_kern.h>
#include <linux/console.h>
#include <linux/pci.h>
#include <linux/adb.h>
#include <linux/cuda.h>
#include <linux/pmu.h>
#include <linux/irq.h>
#include <linux/seq_file.h>
#include <linux/root_dev.h>
#include <linux/bitops.h>
#include <linux/suspend.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#include <linux/lmb.h>
#include <asm/reg.h>
#include <asm/sections.h>
#include <asm/prom.h>
#include <asm/system.h>
#include <asm/pgtable.h>
#include <asm/io.h>
#include <asm/pci-bridge.h>
#include <asm/ohare.h>
#include <asm/mediabay.h>
#include <asm/machdep.h>
#include <asm/dma.h>
#include <asm/cputable.h>
#include <asm/btext.h>
#include <asm/pmac_feature.h>
#include <asm/time.h>
#include <asm/mmu_context.h>
#include <asm/iommu.h>
#include <asm/smu.h>
#include <asm/pmc.h>
#include <asm/udbg.h>
#include "pmac.h"
#undef SHOW_GATWICK_IRQS
int ppc_override_l2cr = 0;
int ppc_override_l2cr_value;
int has_l2cache = 0;
int pmac_newworld;
static int current_root_goodness = -1;
extern struct machdep_calls pmac_md;
#define DEFAULT_ROOT_DEVICE Root_SDA1 /* sda1 - slightly silly choice */
#ifdef CONFIG_PPC64
int sccdbg;
#endif
sys_ctrler_t sys_ctrler = SYS_CTRLER_UNKNOWN;
EXPORT_SYMBOL(sys_ctrler);
#ifdef CONFIG_PMAC_SMU
unsigned long smu_cmdbuf_abs;
EXPORT_SYMBOL(smu_cmdbuf_abs);
#endif
static void pmac_show_cpuinfo(struct seq_file *m)
{
struct device_node *np;
const char *pp;
int plen;
int mbmodel;
unsigned int mbflags;
char* mbname;
mbmodel = pmac_call_feature(PMAC_FTR_GET_MB_INFO, NULL,
PMAC_MB_INFO_MODEL, 0);
mbflags = pmac_call_feature(PMAC_FTR_GET_MB_INFO, NULL,
PMAC_MB_INFO_FLAGS, 0);
if (pmac_call_feature(PMAC_FTR_GET_MB_INFO, NULL, PMAC_MB_INFO_NAME,
(long) &mbname) != 0)
mbname = "Unknown";
/* find motherboard type */
seq_printf(m, "machine\t\t: ");
np = of_find_node_by_path("/");
if (np != NULL) {
pp = of_get_property(np, "model", NULL);
if (pp != NULL)
seq_printf(m, "%s\n", pp);
else
seq_printf(m, "PowerMac\n");
pp = of_get_property(np, "compatible", &plen);
if (pp != NULL) {
seq_printf(m, "motherboard\t:");
while (plen > 0) {
int l = strlen(pp) + 1;
seq_printf(m, " %s", pp);
plen -= l;
pp += l;
}
seq_printf(m, "\n");
}
of_node_put(np);
} else
seq_printf(m, "PowerMac\n");
/* print parsed model */
seq_printf(m, "detected as\t: %d (%s)\n", mbmodel, mbname);
seq_printf(m, "pmac flags\t: %08x\n", mbflags);
/* find l2 cache info */
np = of_find_node_by_name(NULL, "l2-cache");
if (np == NULL)
np = of_find_node_by_type(NULL, "cache");
if (np != NULL) {
const unsigned int *ic =
of_get_property(np, "i-cache-size", NULL);
const unsigned int *dc =
of_get_property(np, "d-cache-size", NULL);
seq_printf(m, "L2 cache\t:");
has_l2cache = 1;
if (of_get_property(np, "cache-unified", NULL) != 0 && dc) {
seq_printf(m, " %dK unified", *dc / 1024);
} else {
if (ic)
seq_printf(m, " %dK instruction", *ic / 1024);
if (dc)
seq_printf(m, "%s %dK data",
(ic? " +": ""), *dc / 1024);
}
pp = of_get_property(np, "ram-type", NULL);
if (pp)
seq_printf(m, " %s", pp);
seq_printf(m, "\n");
of_node_put(np);
}
/* Indicate newworld/oldworld */
seq_printf(m, "pmac-generation\t: %s\n",
pmac_newworld ? "NewWorld" : "OldWorld");
}
#ifndef CONFIG_ADB_CUDA
int find_via_cuda(void)
{
struct device_node *dn = of_find_node_by_name(NULL, "via-cuda");
if (!dn)
return 0;
of_node_put(dn);
printk("WARNING ! Your machine is CUDA-based but your kernel\n");
printk(" wasn't compiled with CONFIG_ADB_CUDA option !\n");
return 0;
}
#endif
#ifndef CONFIG_ADB_PMU
int find_via_pmu(void)
{
struct device_node *dn = of_find_node_by_name(NULL, "via-pmu");
if (!dn)
return 0;
of_node_put(dn);
printk("WARNING ! Your machine is PMU-based but your kernel\n");
printk(" wasn't compiled with CONFIG_ADB_PMU option !\n");
return 0;
}
#endif
#ifndef CONFIG_PMAC_SMU
int smu_init(void)
{
/* should check and warn if SMU is present */
return 0;
}
#endif
#ifdef CONFIG_PPC32
static volatile u32 *sysctrl_regs;
static void __init ohare_init(void)
{
struct device_node *dn;
/* this area has the CPU identification register
and some registers used by smp boards */
sysctrl_regs = (volatile u32 *) ioremap(0xf8000000, 0x1000);
/*
* Turn on the L2 cache.
* We assume that we have a PSX memory controller iff
* we have an ohare I/O controller.
*/
dn = of_find_node_by_name(NULL, "ohare");
if (dn) {
of_node_put(dn);
if (((sysctrl_regs[2] >> 24) & 0xf) >= 3) {
if (sysctrl_regs[4] & 0x10)
sysctrl_regs[4] |= 0x04000020;
else
sysctrl_regs[4] |= 0x04000000;
if(has_l2cache)
printk(KERN_INFO "Level 2 cache enabled\n");
}
}
}
static void __init l2cr_init(void)
{
/* Checks "l2cr-value" property in the registry */
if (cpu_has_feature(CPU_FTR_L2CR)) {
struct device_node *np = of_find_node_by_name(NULL, "cpus");
if (np == 0)
np = of_find_node_by_type(NULL, "cpu");
if (np != 0) {
const unsigned int *l2cr =
of_get_property(np, "l2cr-value", NULL);
if (l2cr != 0) {
ppc_override_l2cr = 1;
ppc_override_l2cr_value = *l2cr;
_set_L2CR(0);
_set_L2CR(ppc_override_l2cr_value);
}
of_node_put(np);
}
}
if (ppc_override_l2cr)
printk(KERN_INFO "L2CR overridden (0x%x), "
"backside cache is %s\n",
ppc_override_l2cr_value,
(ppc_override_l2cr_value & 0x80000000)
? "enabled" : "disabled");
}
#endif
static void __init pmac_setup_arch(void)
{
struct device_node *cpu, *ic;
const int *fp;
unsigned long pvr;
pvr = PVR_VER(mfspr(SPRN_PVR));
/* Set loops_per_jiffy to a half-way reasonable value,
for use until calibrate_delay gets called. */
loops_per_jiffy = 50000000 / HZ;
cpu = of_find_node_by_type(NULL, "cpu");
if (cpu != NULL) {
fp = of_get_property(cpu, "clock-frequency", NULL);
if (fp != NULL) {
if (pvr >= 0x30 && pvr < 0x80)
/* PPC970 etc. */
loops_per_jiffy = *fp / (3 * HZ);
else if (pvr == 4 || pvr >= 8)
/* 604, G3, G4 etc. */
loops_per_jiffy = *fp / HZ;
else
/* 601, 603, etc. */
loops_per_jiffy = *fp / (2 * HZ);
}
of_node_put(cpu);
}
/* See if newworld or oldworld */
ic = of_find_node_with_property(NULL, "interrupt-controller");
if (ic) {
pmac_newworld = 1;
of_node_put(ic);
}
/* Lookup PCI hosts */
pmac_pci_init();
#ifdef CONFIG_PPC32
ohare_init();
l2cr_init();
#endif /* CONFIG_PPC32 */
find_via_cuda();
find_via_pmu();
smu_init();
#if defined(CONFIG_NVRAM) || defined(CONFIG_NVRAM_MODULE) || \
defined(CONFIG_PPC64)
pmac_nvram_init();
#endif
#ifdef CONFIG_PPC32
#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start)
ROOT_DEV = Root_RAM0;
else
#endif
ROOT_DEV = DEFAULT_ROOT_DEVICE;
#endif
#ifdef CONFIG_ADB
if (strstr(cmd_line, "adb_sync")) {
extern int __adb_probe_sync;
__adb_probe_sync = 1;
}
#endif /* CONFIG_ADB */
}
#ifdef CONFIG_SCSI
void note_scsi_host(struct device_node *node, void *host)
{
}
EXPORT_SYMBOL(note_scsi_host);
#endif
static int initializing = 1;
static int pmac_late_init(void)
{
initializing = 0;
/* this is udbg (which is __init) and we can later use it during
* cpu hotplug (in smp_core99_kick_cpu) */
ppc_md.progress = NULL;
return 0;
}
machine_late_initcall(powermac, pmac_late_init);
/*
* This is __init_refok because we check for "initializing" before
* touching any of the __init sensitive things and "initializing"
* will be false after __init time. This can't be __init because it
* can be called whenever a disk is first accessed.
*/
void __init_refok note_bootable_part(dev_t dev, int part, int goodness)
{
char *p;
if (!initializing)
return;
if ((goodness <= current_root_goodness) &&
ROOT_DEV != DEFAULT_ROOT_DEVICE)
return;
p = strstr(boot_command_line, "root=");
if (p != NULL && (p == boot_command_line || p[-1] == ' '))
return;
ROOT_DEV = dev + part;
current_root_goodness = goodness;
}
#ifdef CONFIG_ADB_CUDA
static void cuda_restart(void)
{
struct adb_request req;
cuda_request(&req, NULL, 2, CUDA_PACKET, CUDA_RESET_SYSTEM);
for (;;)
cuda_poll();
}
static void cuda_shutdown(void)
{
struct adb_request req;
cuda_request(&req, NULL, 2, CUDA_PACKET, CUDA_POWERDOWN);
for (;;)
cuda_poll();
}
#else
#define cuda_restart()
#define cuda_shutdown()
#endif
#ifndef CONFIG_ADB_PMU
#define pmu_restart()
#define pmu_shutdown()
#endif
#ifndef CONFIG_PMAC_SMU
#define smu_restart()
#define smu_shutdown()
#endif
static void pmac_restart(char *cmd)
{
switch (sys_ctrler) {
case SYS_CTRLER_CUDA:
cuda_restart();
break;
case SYS_CTRLER_PMU:
pmu_restart();
break;
case SYS_CTRLER_SMU:
smu_restart();
break;
default: ;
}
}
static void pmac_power_off(void)
{
switch (sys_ctrler) {
case SYS_CTRLER_CUDA:
cuda_shutdown();
break;
case SYS_CTRLER_PMU:
pmu_shutdown();
break;
case SYS_CTRLER_SMU:
smu_shutdown();
break;
default: ;
}
}
static void
pmac_halt(void)
{
pmac_power_off();
}
/*
* Early initialization.
*/
static void __init pmac_init_early(void)
{
/* Enable early btext debug if requested */
if (strstr(cmd_line, "btextdbg")) {
udbg_adb_init_early();
register_early_udbg_console();
}
/* Probe motherboard chipset */
pmac_feature_init();
/* Initialize debug stuff */
udbg_scc_init(!!strstr(cmd_line, "sccdbg"));
udbg_adb_init(!!strstr(cmd_line, "btextdbg"));
#ifdef CONFIG_PPC64
iommu_init_early_dart();
#endif
/* SMP Init has to be done early as we need to patch up
* cpu_possible_map before interrupt stacks are allocated
* or kaboom...
*/
#ifdef CONFIG_SMP
pmac_setup_smp();
#endif
}
static int __init pmac_declare_of_platform_devices(void)
{
struct device_node *np;
if (machine_is(chrp))
return -1;
np = of_find_node_by_name(NULL, "valkyrie");
if (np)
of_platform_device_create(np, "valkyrie", NULL);
np = of_find_node_by_name(NULL, "platinum");
if (np)
of_platform_device_create(np, "platinum", NULL);
np = of_find_node_by_type(NULL, "smu");
if (np) {
of_platform_device_create(np, "smu", NULL);
of_node_put(np);
}
return 0;
}
machine_device_initcall(powermac, pmac_declare_of_platform_devices);
#ifdef CONFIG_SERIAL_PMACZILOG_CONSOLE
/*
* This is called very early, as part of console_init() (typically just after
* time_init()). This function is respondible for trying to find a good
* default console on serial ports. It tries to match the open firmware
* default output with one of the available serial console drivers.
*/
static int __init check_pmac_serial_console(void)
{
struct device_node *prom_stdout = NULL;
int offset = 0;
const char *name;
#ifdef CONFIG_SERIAL_PMACZILOG_TTYS
char *devname = "ttyS";
#else
char *devname = "ttyPZ";
#endif
pr_debug(" -> check_pmac_serial_console()\n");
/* The user has requested a console so this is already set up. */
if (strstr(boot_command_line, "console=")) {
pr_debug(" console was specified !\n");
return -EBUSY;
}
if (!of_chosen) {
pr_debug(" of_chosen is NULL !\n");
return -ENODEV;
}
/* We are getting a weird phandle from OF ... */
/* ... So use the full path instead */
name = of_get_property(of_chosen, "linux,stdout-path", NULL);
if (name == NULL) {
pr_debug(" no linux,stdout-path !\n");
return -ENODEV;
}
prom_stdout = of_find_node_by_path(name);
if (!prom_stdout) {
pr_debug(" can't find stdout package %s !\n", name);
return -ENODEV;
}
pr_debug("stdout is %s\n", prom_stdout->full_name);
name = of_get_property(prom_stdout, "name", NULL);
if (!name) {
pr_debug(" stdout package has no name !\n");
goto not_found;
}
if (strcmp(name, "ch-a") == 0)
offset = 0;
else if (strcmp(name, "ch-b") == 0)
offset = 1;
else
goto not_found;
of_node_put(prom_stdout);
pr_debug("Found serial console at %s%d\n", devname, offset);
return add_preferred_console(devname, offset, NULL);
not_found:
pr_debug("No preferred console found !\n");
of_node_put(prom_stdout);
return -ENODEV;
}
console_initcall(check_pmac_serial_console);
#endif /* CONFIG_SERIAL_PMACZILOG_CONSOLE */
/*
* Called very early, MMU is off, device-tree isn't unflattened
*/
static int __init pmac_probe(void)
{
unsigned long root = of_get_flat_dt_root();
if (!of_flat_dt_is_compatible(root, "Power Macintosh") &&
!of_flat_dt_is_compatible(root, "MacRISC"))
return 0;
#ifdef CONFIG_PPC64
/*
* On U3, the DART (iommu) must be allocated now since it
* has an impact on htab_initialize (due to the large page it
* occupies having to be broken up so the DART itself is not
* part of the cacheable linar mapping
*/
alloc_dart_table();
hpte_init_native();
#endif
#ifdef CONFIG_PPC32
/* isa_io_base gets set in pmac_pci_init */
ISA_DMA_THRESHOLD = ~0L;
DMA_MODE_READ = 1;
DMA_MODE_WRITE = 2;
#endif /* CONFIG_PPC32 */
#ifdef CONFIG_PMAC_SMU
/*
* SMU based G5s need some memory below 2Gb, at least the current
* driver needs that. We have to allocate it now. We allocate 4k
* (1 small page) for now.
*/
smu_cmdbuf_abs = lmb_alloc_base(4096, 4096, 0x80000000UL);
#endif /* CONFIG_PMAC_SMU */
return 1;
}
#ifdef CONFIG_PPC64
/* Move that to pci.c */
static int pmac_pci_probe_mode(struct pci_bus *bus)
{
struct device_node *node = pci_bus_to_OF_node(bus);
/* We need to use normal PCI probing for the AGP bus,
* since the device for the AGP bridge isn't in the tree.
* Same for the PCIe host on U4 and the HT host bridge.
*/
if (bus->self == NULL && (of_device_is_compatible(node, "u3-agp") ||
of_device_is_compatible(node, "u4-pcie") ||
of_device_is_compatible(node, "u3-ht")))
return PCI_PROBE_NORMAL;
return PCI_PROBE_DEVTREE;
}
#ifdef CONFIG_HOTPLUG_CPU
/* access per cpu vars from generic smp.c */
DECLARE_PER_CPU(int, cpu_state);
static void pmac_cpu_die(void)
{
/*
* turn off as much as possible, we'll be
* kicked out as this will only be invoked
* on core99 platforms for now ...
*/
printk(KERN_INFO "CPU#%d offline\n", smp_processor_id());
__get_cpu_var(cpu_state) = CPU_DEAD;
smp_wmb();
/*
* during the path that leads here preemption is disabled,
* reenable it now so that when coming up preempt count is
* zero correctly
*/
preempt_enable();
/*
* hard-disable interrupts for the non-NAP case, the NAP code
* needs to re-enable interrupts (but soft-disables them)
*/
hard_irq_disable();
while (1) {
/* let's not take timer interrupts too often ... */
set_dec(0x7fffffff);
/* should always be true at this point */
if (cpu_has_feature(CPU_FTR_CAN_NAP))
power4_cpu_offline_powersave();
else {
HMT_low();
HMT_very_low();
}
}
}
#endif /* CONFIG_HOTPLUG_CPU */
#endif /* CONFIG_PPC64 */
define_machine(powermac) {
.name = "PowerMac",
.probe = pmac_probe,
.setup_arch = pmac_setup_arch,
.init_early = pmac_init_early,
.show_cpuinfo = pmac_show_cpuinfo,
.init_IRQ = pmac_pic_init,
.get_irq = NULL, /* changed later */
.pci_irq_fixup = pmac_pci_irq_fixup,
.restart = pmac_restart,
.power_off = pmac_power_off,
.halt = pmac_halt,
.time_init = pmac_time_init,
.get_boot_time = pmac_get_boot_time,
.set_rtc_time = pmac_set_rtc_time,
.get_rtc_time = pmac_get_rtc_time,
.calibrate_decr = pmac_calibrate_decr,
.feature_call = pmac_do_feature_call,
.progress = udbg_progress,
#ifdef CONFIG_PPC64
.pci_probe_mode = pmac_pci_probe_mode,
.power_save = power4_idle,
.enable_pmcs = power4_enable_pmcs,
#endif /* CONFIG_PPC64 */
#ifdef CONFIG_PPC32
.pcibios_enable_device_hook = pmac_pci_enable_device_hook,
.pcibios_after_init = pmac_pcibios_after_init,
.phys_mem_access_prot = pci_phys_mem_access_prot,
#endif
#if defined(CONFIG_HOTPLUG_CPU) && defined(CONFIG_PPC64)
.cpu_die = pmac_cpu_die,
#endif
#if defined(CONFIG_HOTPLUG_CPU) && defined(CONFIG_PPC32)
.cpu_die = generic_mach_cpu_die,
#endif
};

View File

@@ -0,0 +1,397 @@
/*
* This file contains sleep low-level functions for PowerBook G3.
* Copyright (C) 1999 Benjamin Herrenschmidt (benh@kernel.crashing.org)
* and Paul Mackerras (paulus@samba.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 <asm/processor.h>
#include <asm/page.h>
#include <asm/ppc_asm.h>
#include <asm/cputable.h>
#include <asm/cache.h>
#include <asm/thread_info.h>
#include <asm/asm-offsets.h>
#include <asm/mmu.h>
#define MAGIC 0x4c617273 /* 'Lars' */
/*
* Structure for storing CPU registers on the stack.
*/
#define SL_SP 0
#define SL_PC 4
#define SL_MSR 8
#define SL_SDR1 0xc
#define SL_SPRG0 0x10 /* 4 sprg's */
#define SL_DBAT0 0x20
#define SL_IBAT0 0x28
#define SL_DBAT1 0x30
#define SL_IBAT1 0x38
#define SL_DBAT2 0x40
#define SL_IBAT2 0x48
#define SL_DBAT3 0x50
#define SL_IBAT3 0x58
#define SL_TB 0x60
#define SL_R2 0x68
#define SL_CR 0x6c
#define SL_R12 0x70 /* r12 to r31 */
#define SL_SIZE (SL_R12 + 80)
.section .text
.align 5
#if defined(CONFIG_PM) || defined(CONFIG_CPU_FREQ_PMAC) || \
(defined(CONFIG_HOTPLUG_CPU) && defined(CONFIG_PPC32))
/* This gets called by via-pmu.c late during the sleep process.
* The PMU was already send the sleep command and will shut us down
* soon. We need to save all that is needed and setup the wakeup
* vector that will be called by the ROM on wakeup
*/
_GLOBAL(low_sleep_handler)
#ifndef CONFIG_6xx
blr
#else
mflr r0
stw r0,4(r1)
stwu r1,-SL_SIZE(r1)
mfcr r0
stw r0,SL_CR(r1)
stw r2,SL_R2(r1)
stmw r12,SL_R12(r1)
/* Save MSR & SDR1 */
mfmsr r4
stw r4,SL_MSR(r1)
mfsdr1 r4
stw r4,SL_SDR1(r1)
/* Get a stable timebase and save it */
1: mftbu r4
stw r4,SL_TB(r1)
mftb r5
stw r5,SL_TB+4(r1)
mftbu r3
cmpw r3,r4
bne 1b
/* Save SPRGs */
mfsprg r4,0
stw r4,SL_SPRG0(r1)
mfsprg r4,1
stw r4,SL_SPRG0+4(r1)
mfsprg r4,2
stw r4,SL_SPRG0+8(r1)
mfsprg r4,3
stw r4,SL_SPRG0+12(r1)
/* Save BATs */
mfdbatu r4,0
stw r4,SL_DBAT0(r1)
mfdbatl r4,0
stw r4,SL_DBAT0+4(r1)
mfdbatu r4,1
stw r4,SL_DBAT1(r1)
mfdbatl r4,1
stw r4,SL_DBAT1+4(r1)
mfdbatu r4,2
stw r4,SL_DBAT2(r1)
mfdbatl r4,2
stw r4,SL_DBAT2+4(r1)
mfdbatu r4,3
stw r4,SL_DBAT3(r1)
mfdbatl r4,3
stw r4,SL_DBAT3+4(r1)
mfibatu r4,0
stw r4,SL_IBAT0(r1)
mfibatl r4,0
stw r4,SL_IBAT0+4(r1)
mfibatu r4,1
stw r4,SL_IBAT1(r1)
mfibatl r4,1
stw r4,SL_IBAT1+4(r1)
mfibatu r4,2
stw r4,SL_IBAT2(r1)
mfibatl r4,2
stw r4,SL_IBAT2+4(r1)
mfibatu r4,3
stw r4,SL_IBAT3(r1)
mfibatl r4,3
stw r4,SL_IBAT3+4(r1)
/* Backup various CPU config stuffs */
bl __save_cpu_setup
/* The ROM can wake us up via 2 different vectors:
* - On wallstreet & lombard, we must write a magic
* value 'Lars' at address 4 and a pointer to a
* memory location containing the PC to resume from
* at address 0.
* - On Core99, we must store the wakeup vector at
* address 0x80 and eventually it's parameters
* at address 0x84. I've have some trouble with those
* parameters however and I no longer use them.
*/
lis r5,grackle_wake_up@ha
addi r5,r5,grackle_wake_up@l
tophys(r5,r5)
stw r5,SL_PC(r1)
lis r4,KERNELBASE@h
tophys(r5,r1)
addi r5,r5,SL_PC
lis r6,MAGIC@ha
addi r6,r6,MAGIC@l
stw r5,0(r4)
stw r6,4(r4)
/* Setup stuffs at 0x80-0x84 for Core99 */
lis r3,core99_wake_up@ha
addi r3,r3,core99_wake_up@l
tophys(r3,r3)
stw r3,0x80(r4)
stw r5,0x84(r4)
/* Store a pointer to our backup storage into
* a kernel global
*/
lis r3,sleep_storage@ha
addi r3,r3,sleep_storage@l
stw r5,0(r3)
.globl low_cpu_die
low_cpu_die:
/* Flush & disable all caches */
bl flush_disable_caches
/* Turn off data relocation. */
mfmsr r3 /* Save MSR in r7 */
rlwinm r3,r3,0,28,26 /* Turn off DR bit */
sync
mtmsr r3
isync
BEGIN_FTR_SECTION
/* Flush any pending L2 data prefetches to work around HW bug */
sync
lis r3,0xfff0
lwz r0,0(r3) /* perform cache-inhibited load to ROM */
sync /* (caches are disabled at this point) */
END_FTR_SECTION_IFSET(CPU_FTR_SPEC7450)
/*
* Set the HID0 and MSR for sleep.
*/
mfspr r2,SPRN_HID0
rlwinm r2,r2,0,10,7 /* clear doze, nap */
oris r2,r2,HID0_SLEEP@h
sync
isync
mtspr SPRN_HID0,r2
sync
/* This loop puts us back to sleep in case we have a spurrious
* wakeup so that the host bridge properly stays asleep. The
* CPU will be turned off, either after a known time (about 1
* second) on wallstreet & lombard, or as soon as the CPU enters
* SLEEP mode on core99
*/
mfmsr r2
oris r2,r2,MSR_POW@h
1: sync
mtmsr r2
isync
b 1b
/*
* Here is the resume code.
*/
/*
* Core99 machines resume here
* r4 has the physical address of SL_PC(sp) (unused)
*/
_GLOBAL(core99_wake_up)
/* Make sure HID0 no longer contains any sleep bit and that data cache
* is disabled
*/
mfspr r3,SPRN_HID0
rlwinm r3,r3,0,11,7 /* clear SLEEP, NAP, DOZE bits */
rlwinm 3,r3,0,18,15 /* clear DCE, ICE */
mtspr SPRN_HID0,r3
sync
isync
/* sanitize MSR */
mfmsr r3
ori r3,r3,MSR_EE|MSR_IP
xori r3,r3,MSR_EE|MSR_IP
sync
isync
mtmsr r3
sync
isync
/* Recover sleep storage */
lis r3,sleep_storage@ha
addi r3,r3,sleep_storage@l
tophys(r3,r3)
lwz r1,0(r3)
/* Pass thru to older resume code ... */
/*
* Here is the resume code for older machines.
* r1 has the physical address of SL_PC(sp).
*/
grackle_wake_up:
/* Restore the kernel's segment registers before
* we do any r1 memory access as we are not sure they
* are in a sane state above the first 256Mb region
*/
li r0,16 /* load up segment register values */
mtctr r0 /* for context 0 */
lis r3,0x2000 /* Ku = 1, VSID = 0 */
li r4,0
3: mtsrin r3,r4
addi r3,r3,0x111 /* increment VSID */
addis r4,r4,0x1000 /* address of next segment */
bdnz 3b
sync
isync
subi r1,r1,SL_PC
/* Restore various CPU config stuffs */
bl __restore_cpu_setup
/* Make sure all FPRs have been initialized */
bl reloc_offset
bl __init_fpu_registers
/* Invalidate & enable L1 cache, we don't care about
* whatever the ROM may have tried to write to memory
*/
bl __inval_enable_L1
/* Restore the BATs, and SDR1. Then we can turn on the MMU. */
lwz r4,SL_SDR1(r1)
mtsdr1 r4
lwz r4,SL_SPRG0(r1)
mtsprg 0,r4
lwz r4,SL_SPRG0+4(r1)
mtsprg 1,r4
lwz r4,SL_SPRG0+8(r1)
mtsprg 2,r4
lwz r4,SL_SPRG0+12(r1)
mtsprg 3,r4
lwz r4,SL_DBAT0(r1)
mtdbatu 0,r4
lwz r4,SL_DBAT0+4(r1)
mtdbatl 0,r4
lwz r4,SL_DBAT1(r1)
mtdbatu 1,r4
lwz r4,SL_DBAT1+4(r1)
mtdbatl 1,r4
lwz r4,SL_DBAT2(r1)
mtdbatu 2,r4
lwz r4,SL_DBAT2+4(r1)
mtdbatl 2,r4
lwz r4,SL_DBAT3(r1)
mtdbatu 3,r4
lwz r4,SL_DBAT3+4(r1)
mtdbatl 3,r4
lwz r4,SL_IBAT0(r1)
mtibatu 0,r4
lwz r4,SL_IBAT0+4(r1)
mtibatl 0,r4
lwz r4,SL_IBAT1(r1)
mtibatu 1,r4
lwz r4,SL_IBAT1+4(r1)
mtibatl 1,r4
lwz r4,SL_IBAT2(r1)
mtibatu 2,r4
lwz r4,SL_IBAT2+4(r1)
mtibatl 2,r4
lwz r4,SL_IBAT3(r1)
mtibatu 3,r4
lwz r4,SL_IBAT3+4(r1)
mtibatl 3,r4
BEGIN_MMU_FTR_SECTION
li r4,0
mtspr SPRN_DBAT4U,r4
mtspr SPRN_DBAT4L,r4
mtspr SPRN_DBAT5U,r4
mtspr SPRN_DBAT5L,r4
mtspr SPRN_DBAT6U,r4
mtspr SPRN_DBAT6L,r4
mtspr SPRN_DBAT7U,r4
mtspr SPRN_DBAT7L,r4
mtspr SPRN_IBAT4U,r4
mtspr SPRN_IBAT4L,r4
mtspr SPRN_IBAT5U,r4
mtspr SPRN_IBAT5L,r4
mtspr SPRN_IBAT6U,r4
mtspr SPRN_IBAT6L,r4
mtspr SPRN_IBAT7U,r4
mtspr SPRN_IBAT7L,r4
END_MMU_FTR_SECTION_IFSET(MMU_FTR_USE_HIGH_BATS)
/* Flush all TLBs */
lis r4,0x1000
1: addic. r4,r4,-0x1000
tlbie r4
blt 1b
sync
/* restore the MSR and turn on the MMU */
lwz r3,SL_MSR(r1)
bl turn_on_mmu
/* get back the stack pointer */
tovirt(r1,r1)
/* Restore TB */
li r3,0
mttbl r3
lwz r3,SL_TB(r1)
lwz r4,SL_TB+4(r1)
mttbu r3
mttbl r4
/* Restore the callee-saved registers and return */
lwz r0,SL_CR(r1)
mtcr r0
lwz r2,SL_R2(r1)
lmw r12,SL_R12(r1)
addi r1,r1,SL_SIZE
lwz r0,4(r1)
mtlr r0
blr
turn_on_mmu:
mflr r4
tovirt(r4,r4)
mtsrr0 r4
mtsrr1 r3
sync
isync
rfi
#endif /* defined(CONFIG_PM) || defined(CONFIG_CPU_FREQ) */
.section .data
.balign L1_CACHE_BYTES
sleep_storage:
.long 0
.balign L1_CACHE_BYTES, 0
#endif /* CONFIG_6xx */
.section .text

View File

@@ -0,0 +1,960 @@
/*
* SMP support for power macintosh.
*
* We support both the old "powersurge" SMP architecture
* and the current Core99 (G4 PowerMac) machines.
*
* Note that we don't support the very first rev. of
* Apple/DayStar 2 CPUs board, the one with the funky
* watchdog. Hopefully, none of these should be there except
* maybe internally to Apple. I should probably still add some
* code to detect this card though and disable SMP. --BenH.
*
* Support Macintosh G4 SMP by Troy Benjegerdes (hozer@drgw.net)
* and Ben Herrenschmidt <benh@kernel.crashing.org>.
*
* Support for DayStar quad CPU cards
* Copyright (C) XLR8, Inc. 1994-2000
*
* 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/sched.h>
#include <linux/smp.h>
#include <linux/interrupt.h>
#include <linux/kernel_stat.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/spinlock.h>
#include <linux/errno.h>
#include <linux/hardirq.h>
#include <linux/cpu.h>
#include <linux/compiler.h>
#include <asm/ptrace.h>
#include <asm/atomic.h>
#include <asm/code-patching.h>
#include <asm/irq.h>
#include <asm/page.h>
#include <asm/pgtable.h>
#include <asm/sections.h>
#include <asm/io.h>
#include <asm/prom.h>
#include <asm/smp.h>
#include <asm/machdep.h>
#include <asm/pmac_feature.h>
#include <asm/time.h>
#include <asm/mpic.h>
#include <asm/cacheflush.h>
#include <asm/keylargo.h>
#include <asm/pmac_low_i2c.h>
#include <asm/pmac_pfunc.h>
#undef DEBUG
#ifdef DEBUG
#define DBG(fmt...) udbg_printf(fmt)
#else
#define DBG(fmt...)
#endif
extern void __secondary_start_pmac_0(void);
extern int pmac_pfunc_base_install(void);
static void (*pmac_tb_freeze)(int freeze);
static u64 timebase;
static int tb_req;
#ifdef CONFIG_PPC32
/*
* Powersurge (old powermac SMP) support.
*/
/* Addresses for powersurge registers */
#define HAMMERHEAD_BASE 0xf8000000
#define HHEAD_CONFIG 0x90
#define HHEAD_SEC_INTR 0xc0
/* register for interrupting the primary processor on the powersurge */
/* N.B. this is actually the ethernet ROM! */
#define PSURGE_PRI_INTR 0xf3019000
/* register for storing the start address for the secondary processor */
/* N.B. this is the PCI config space address register for the 1st bridge */
#define PSURGE_START 0xf2800000
/* Daystar/XLR8 4-CPU card */
#define PSURGE_QUAD_REG_ADDR 0xf8800000
#define PSURGE_QUAD_IRQ_SET 0
#define PSURGE_QUAD_IRQ_CLR 1
#define PSURGE_QUAD_IRQ_PRIMARY 2
#define PSURGE_QUAD_CKSTOP_CTL 3
#define PSURGE_QUAD_PRIMARY_ARB 4
#define PSURGE_QUAD_BOARD_ID 6
#define PSURGE_QUAD_WHICH_CPU 7
#define PSURGE_QUAD_CKSTOP_RDBK 8
#define PSURGE_QUAD_RESET_CTL 11
#define PSURGE_QUAD_OUT(r, v) (out_8(quad_base + ((r) << 4) + 4, (v)))
#define PSURGE_QUAD_IN(r) (in_8(quad_base + ((r) << 4) + 4) & 0x0f)
#define PSURGE_QUAD_BIS(r, v) (PSURGE_QUAD_OUT((r), PSURGE_QUAD_IN(r) | (v)))
#define PSURGE_QUAD_BIC(r, v) (PSURGE_QUAD_OUT((r), PSURGE_QUAD_IN(r) & ~(v)))
/* virtual addresses for the above */
static volatile u8 __iomem *hhead_base;
static volatile u8 __iomem *quad_base;
static volatile u32 __iomem *psurge_pri_intr;
static volatile u8 __iomem *psurge_sec_intr;
static volatile u32 __iomem *psurge_start;
/* values for psurge_type */
#define PSURGE_NONE -1
#define PSURGE_DUAL 0
#define PSURGE_QUAD_OKEE 1
#define PSURGE_QUAD_COTTON 2
#define PSURGE_QUAD_ICEGRASS 3
/* what sort of powersurge board we have */
static int psurge_type = PSURGE_NONE;
/*
* Set and clear IPIs for powersurge.
*/
static inline void psurge_set_ipi(int cpu)
{
if (psurge_type == PSURGE_NONE)
return;
if (cpu == 0)
in_be32(psurge_pri_intr);
else if (psurge_type == PSURGE_DUAL)
out_8(psurge_sec_intr, 0);
else
PSURGE_QUAD_OUT(PSURGE_QUAD_IRQ_SET, 1 << cpu);
}
static inline void psurge_clr_ipi(int cpu)
{
if (cpu > 0) {
switch(psurge_type) {
case PSURGE_DUAL:
out_8(psurge_sec_intr, ~0);
case PSURGE_NONE:
break;
default:
PSURGE_QUAD_OUT(PSURGE_QUAD_IRQ_CLR, 1 << cpu);
}
}
}
/*
* On powersurge (old SMP powermac architecture) we don't have
* separate IPIs for separate messages like openpic does. Instead
* we have a bitmap for each processor, where a 1 bit means that
* the corresponding message is pending for that processor.
* Ideally each cpu's entry would be in a different cache line.
* -- paulus.
*/
static unsigned long psurge_smp_message[NR_CPUS];
void psurge_smp_message_recv(void)
{
int cpu = smp_processor_id();
int msg;
/* clear interrupt */
psurge_clr_ipi(cpu);
if (num_online_cpus() < 2)
return;
/* make sure there is a message there */
for (msg = 0; msg < 4; msg++)
if (test_and_clear_bit(msg, &psurge_smp_message[cpu]))
smp_message_recv(msg);
}
irqreturn_t psurge_primary_intr(int irq, void *d)
{
psurge_smp_message_recv();
return IRQ_HANDLED;
}
static void smp_psurge_message_pass(int target, int msg)
{
int i;
if (num_online_cpus() < 2)
return;
for_each_online_cpu(i) {
if (target == MSG_ALL
|| (target == MSG_ALL_BUT_SELF && i != smp_processor_id())
|| target == i) {
set_bit(msg, &psurge_smp_message[i]);
psurge_set_ipi(i);
}
}
}
/*
* Determine a quad card presence. We read the board ID register, we
* force the data bus to change to something else, and we read it again.
* It it's stable, then the register probably exist (ugh !)
*/
static int __init psurge_quad_probe(void)
{
int type;
unsigned int i;
type = PSURGE_QUAD_IN(PSURGE_QUAD_BOARD_ID);
if (type < PSURGE_QUAD_OKEE || type > PSURGE_QUAD_ICEGRASS
|| type != PSURGE_QUAD_IN(PSURGE_QUAD_BOARD_ID))
return PSURGE_DUAL;
/* looks OK, try a slightly more rigorous test */
/* bogus is not necessarily cacheline-aligned,
though I don't suppose that really matters. -- paulus */
for (i = 0; i < 100; i++) {
volatile u32 bogus[8];
bogus[(0+i)%8] = 0x00000000;
bogus[(1+i)%8] = 0x55555555;
bogus[(2+i)%8] = 0xFFFFFFFF;
bogus[(3+i)%8] = 0xAAAAAAAA;
bogus[(4+i)%8] = 0x33333333;
bogus[(5+i)%8] = 0xCCCCCCCC;
bogus[(6+i)%8] = 0xCCCCCCCC;
bogus[(7+i)%8] = 0x33333333;
wmb();
asm volatile("dcbf 0,%0" : : "r" (bogus) : "memory");
mb();
if (type != PSURGE_QUAD_IN(PSURGE_QUAD_BOARD_ID))
return PSURGE_DUAL;
}
return type;
}
static void __init psurge_quad_init(void)
{
int procbits;
if (ppc_md.progress) ppc_md.progress("psurge_quad_init", 0x351);
procbits = ~PSURGE_QUAD_IN(PSURGE_QUAD_WHICH_CPU);
if (psurge_type == PSURGE_QUAD_ICEGRASS)
PSURGE_QUAD_BIS(PSURGE_QUAD_RESET_CTL, procbits);
else
PSURGE_QUAD_BIC(PSURGE_QUAD_CKSTOP_CTL, procbits);
mdelay(33);
out_8(psurge_sec_intr, ~0);
PSURGE_QUAD_OUT(PSURGE_QUAD_IRQ_CLR, procbits);
PSURGE_QUAD_BIS(PSURGE_QUAD_RESET_CTL, procbits);
if (psurge_type != PSURGE_QUAD_ICEGRASS)
PSURGE_QUAD_BIS(PSURGE_QUAD_CKSTOP_CTL, procbits);
PSURGE_QUAD_BIC(PSURGE_QUAD_PRIMARY_ARB, procbits);
mdelay(33);
PSURGE_QUAD_BIC(PSURGE_QUAD_RESET_CTL, procbits);
mdelay(33);
PSURGE_QUAD_BIS(PSURGE_QUAD_PRIMARY_ARB, procbits);
mdelay(33);
}
static int __init smp_psurge_probe(void)
{
int i, ncpus;
struct device_node *dn;
/* We don't do SMP on the PPC601 -- paulus */
if (PVR_VER(mfspr(SPRN_PVR)) == 1)
return 1;
/*
* The powersurge cpu board can be used in the generation
* of powermacs that have a socket for an upgradeable cpu card,
* including the 7500, 8500, 9500, 9600.
* The device tree doesn't tell you if you have 2 cpus because
* OF doesn't know anything about the 2nd processor.
* Instead we look for magic bits in magic registers,
* in the hammerhead memory controller in the case of the
* dual-cpu powersurge board. -- paulus.
*/
dn = of_find_node_by_name(NULL, "hammerhead");
if (dn == NULL)
return 1;
of_node_put(dn);
hhead_base = ioremap(HAMMERHEAD_BASE, 0x800);
quad_base = ioremap(PSURGE_QUAD_REG_ADDR, 1024);
psurge_sec_intr = hhead_base + HHEAD_SEC_INTR;
psurge_type = psurge_quad_probe();
if (psurge_type != PSURGE_DUAL) {
psurge_quad_init();
/* All released cards using this HW design have 4 CPUs */
ncpus = 4;
/* No sure how timebase sync works on those, let's use SW */
smp_ops->give_timebase = smp_generic_give_timebase;
smp_ops->take_timebase = smp_generic_take_timebase;
} else {
iounmap(quad_base);
if ((in_8(hhead_base + HHEAD_CONFIG) & 0x02) == 0) {
/* not a dual-cpu card */
iounmap(hhead_base);
psurge_type = PSURGE_NONE;
return 1;
}
ncpus = 2;
}
psurge_start = ioremap(PSURGE_START, 4);
psurge_pri_intr = ioremap(PSURGE_PRI_INTR, 4);
/* This is necessary because OF doesn't know about the
* secondary cpu(s), and thus there aren't nodes in the
* device tree for them, and smp_setup_cpu_maps hasn't
* set their bits in cpu_present_map.
*/
if (ncpus > NR_CPUS)
ncpus = NR_CPUS;
for (i = 1; i < ncpus ; ++i)
set_cpu_present(i, true);
if (ppc_md.progress) ppc_md.progress("smp_psurge_probe - done", 0x352);
return ncpus;
}
static void __init smp_psurge_kick_cpu(int nr)
{
unsigned long start = __pa(__secondary_start_pmac_0) + nr * 8;
unsigned long a, flags;
int i, j;
/* Defining this here is evil ... but I prefer hiding that
* crap to avoid giving people ideas that they can do the
* same.
*/
extern volatile unsigned int cpu_callin_map[NR_CPUS];
/* may need to flush here if secondary bats aren't setup */
for (a = KERNELBASE; a < KERNELBASE + 0x800000; a += 32)
asm volatile("dcbf 0,%0" : : "r" (a) : "memory");
asm volatile("sync");
if (ppc_md.progress) ppc_md.progress("smp_psurge_kick_cpu", 0x353);
/* This is going to freeze the timeebase, we disable interrupts */
local_irq_save(flags);
out_be32(psurge_start, start);
mb();
psurge_set_ipi(nr);
/*
* We can't use udelay here because the timebase is now frozen.
*/
for (i = 0; i < 2000; ++i)
asm volatile("nop" : : : "memory");
psurge_clr_ipi(nr);
/*
* Also, because the timebase is frozen, we must not return to the
* caller which will try to do udelay's etc... Instead, we wait -here-
* for the CPU to callin.
*/
for (i = 0; i < 100000 && !cpu_callin_map[nr]; ++i) {
for (j = 1; j < 10000; j++)
asm volatile("nop" : : : "memory");
asm volatile("sync" : : : "memory");
}
if (!cpu_callin_map[nr])
goto stuck;
/* And we do the TB sync here too for standard dual CPU cards */
if (psurge_type == PSURGE_DUAL) {
while(!tb_req)
barrier();
tb_req = 0;
mb();
timebase = get_tb();
mb();
while (timebase)
barrier();
mb();
}
stuck:
/* now interrupt the secondary, restarting both TBs */
if (psurge_type == PSURGE_DUAL)
psurge_set_ipi(1);
if (ppc_md.progress) ppc_md.progress("smp_psurge_kick_cpu - done", 0x354);
}
static struct irqaction psurge_irqaction = {
.handler = psurge_primary_intr,
.flags = IRQF_DISABLED,
.name = "primary IPI",
};
static void __init smp_psurge_setup_cpu(int cpu_nr)
{
if (cpu_nr != 0)
return;
/* reset the entry point so if we get another intr we won't
* try to startup again */
out_be32(psurge_start, 0x100);
if (setup_irq(irq_create_mapping(NULL, 30), &psurge_irqaction))
printk(KERN_ERR "Couldn't get primary IPI interrupt");
}
void __init smp_psurge_take_timebase(void)
{
if (psurge_type != PSURGE_DUAL)
return;
tb_req = 1;
mb();
while (!timebase)
barrier();
mb();
set_tb(timebase >> 32, timebase & 0xffffffff);
timebase = 0;
mb();
set_dec(tb_ticks_per_jiffy/2);
}
void __init smp_psurge_give_timebase(void)
{
/* Nothing to do here */
}
/* PowerSurge-style Macs */
struct smp_ops_t psurge_smp_ops = {
.message_pass = smp_psurge_message_pass,
.probe = smp_psurge_probe,
.kick_cpu = smp_psurge_kick_cpu,
.setup_cpu = smp_psurge_setup_cpu,
.give_timebase = smp_psurge_give_timebase,
.take_timebase = smp_psurge_take_timebase,
};
#endif /* CONFIG_PPC32 - actually powersurge support */
/*
* Core 99 and later support
*/
static void smp_core99_give_timebase(void)
{
unsigned long flags;
local_irq_save(flags);
while(!tb_req)
barrier();
tb_req = 0;
(*pmac_tb_freeze)(1);
mb();
timebase = get_tb();
mb();
while (timebase)
barrier();
mb();
(*pmac_tb_freeze)(0);
mb();
local_irq_restore(flags);
}
static void __devinit smp_core99_take_timebase(void)
{
unsigned long flags;
local_irq_save(flags);
tb_req = 1;
mb();
while (!timebase)
barrier();
mb();
set_tb(timebase >> 32, timebase & 0xffffffff);
timebase = 0;
mb();
local_irq_restore(flags);
}
#ifdef CONFIG_PPC64
/*
* G5s enable/disable the timebase via an i2c-connected clock chip.
*/
static struct pmac_i2c_bus *pmac_tb_clock_chip_host;
static u8 pmac_tb_pulsar_addr;
static void smp_core99_cypress_tb_freeze(int freeze)
{
u8 data;
int rc;
/* Strangely, the device-tree says address is 0xd2, but darwin
* accesses 0xd0 ...
*/
pmac_i2c_setmode(pmac_tb_clock_chip_host,
pmac_i2c_mode_combined);
rc = pmac_i2c_xfer(pmac_tb_clock_chip_host,
0xd0 | pmac_i2c_read,
1, 0x81, &data, 1);
if (rc != 0)
goto bail;
data = (data & 0xf3) | (freeze ? 0x00 : 0x0c);
pmac_i2c_setmode(pmac_tb_clock_chip_host, pmac_i2c_mode_stdsub);
rc = pmac_i2c_xfer(pmac_tb_clock_chip_host,
0xd0 | pmac_i2c_write,
1, 0x81, &data, 1);
bail:
if (rc != 0) {
printk("Cypress Timebase %s rc: %d\n",
freeze ? "freeze" : "unfreeze", rc);
panic("Timebase freeze failed !\n");
}
}
static void smp_core99_pulsar_tb_freeze(int freeze)
{
u8 data;
int rc;
pmac_i2c_setmode(pmac_tb_clock_chip_host,
pmac_i2c_mode_combined);
rc = pmac_i2c_xfer(pmac_tb_clock_chip_host,
pmac_tb_pulsar_addr | pmac_i2c_read,
1, 0x2e, &data, 1);
if (rc != 0)
goto bail;
data = (data & 0x88) | (freeze ? 0x11 : 0x22);
pmac_i2c_setmode(pmac_tb_clock_chip_host, pmac_i2c_mode_stdsub);
rc = pmac_i2c_xfer(pmac_tb_clock_chip_host,
pmac_tb_pulsar_addr | pmac_i2c_write,
1, 0x2e, &data, 1);
bail:
if (rc != 0) {
printk(KERN_ERR "Pulsar Timebase %s rc: %d\n",
freeze ? "freeze" : "unfreeze", rc);
panic("Timebase freeze failed !\n");
}
}
static void __init smp_core99_setup_i2c_hwsync(int ncpus)
{
struct device_node *cc = NULL;
struct device_node *p;
const char *name = NULL;
const u32 *reg;
int ok;
/* Look for the clock chip */
while ((cc = of_find_node_by_name(cc, "i2c-hwclock")) != NULL) {
p = of_get_parent(cc);
ok = p && of_device_is_compatible(p, "uni-n-i2c");
of_node_put(p);
if (!ok)
continue;
pmac_tb_clock_chip_host = pmac_i2c_find_bus(cc);
if (pmac_tb_clock_chip_host == NULL)
continue;
reg = of_get_property(cc, "reg", NULL);
if (reg == NULL)
continue;
switch (*reg) {
case 0xd2:
if (of_device_is_compatible(cc,"pulsar-legacy-slewing")) {
pmac_tb_freeze = smp_core99_pulsar_tb_freeze;
pmac_tb_pulsar_addr = 0xd2;
name = "Pulsar";
} else if (of_device_is_compatible(cc, "cy28508")) {
pmac_tb_freeze = smp_core99_cypress_tb_freeze;
name = "Cypress";
}
break;
case 0xd4:
pmac_tb_freeze = smp_core99_pulsar_tb_freeze;
pmac_tb_pulsar_addr = 0xd4;
name = "Pulsar";
break;
}
if (pmac_tb_freeze != NULL)
break;
}
if (pmac_tb_freeze != NULL) {
/* Open i2c bus for synchronous access */
if (pmac_i2c_open(pmac_tb_clock_chip_host, 1)) {
printk(KERN_ERR "Failed top open i2c bus for clock"
" sync, fallback to software sync !\n");
goto no_i2c_sync;
}
printk(KERN_INFO "Processor timebase sync using %s i2c clock\n",
name);
return;
}
no_i2c_sync:
pmac_tb_freeze = NULL;
pmac_tb_clock_chip_host = NULL;
}
/*
* Newer G5s uses a platform function
*/
static void smp_core99_pfunc_tb_freeze(int freeze)
{
struct device_node *cpus;
struct pmf_args args;
cpus = of_find_node_by_path("/cpus");
BUG_ON(cpus == NULL);
args.count = 1;
args.u[0].v = !freeze;
pmf_call_function(cpus, "cpu-timebase", &args);
of_node_put(cpus);
}
#else /* CONFIG_PPC64 */
/*
* SMP G4 use a GPIO to enable/disable the timebase.
*/
static unsigned int core99_tb_gpio; /* Timebase freeze GPIO */
static void smp_core99_gpio_tb_freeze(int freeze)
{
if (freeze)
pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, core99_tb_gpio, 4);
else
pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, core99_tb_gpio, 0);
pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, core99_tb_gpio, 0);
}
#endif /* !CONFIG_PPC64 */
/* L2 and L3 cache settings to pass from CPU0 to CPU1 on G4 cpus */
volatile static long int core99_l2_cache;
volatile static long int core99_l3_cache;
static void __devinit core99_init_caches(int cpu)
{
#ifndef CONFIG_PPC64
if (!cpu_has_feature(CPU_FTR_L2CR))
return;
if (cpu == 0) {
core99_l2_cache = _get_L2CR();
printk("CPU0: L2CR is %lx\n", core99_l2_cache);
} else {
printk("CPU%d: L2CR was %lx\n", cpu, _get_L2CR());
_set_L2CR(0);
_set_L2CR(core99_l2_cache);
printk("CPU%d: L2CR set to %lx\n", cpu, core99_l2_cache);
}
if (!cpu_has_feature(CPU_FTR_L3CR))
return;
if (cpu == 0){
core99_l3_cache = _get_L3CR();
printk("CPU0: L3CR is %lx\n", core99_l3_cache);
} else {
printk("CPU%d: L3CR was %lx\n", cpu, _get_L3CR());
_set_L3CR(0);
_set_L3CR(core99_l3_cache);
printk("CPU%d: L3CR set to %lx\n", cpu, core99_l3_cache);
}
#endif /* !CONFIG_PPC64 */
}
static void __init smp_core99_setup(int ncpus)
{
#ifdef CONFIG_PPC64
/* i2c based HW sync on some G5s */
if (machine_is_compatible("PowerMac7,2") ||
machine_is_compatible("PowerMac7,3") ||
machine_is_compatible("RackMac3,1"))
smp_core99_setup_i2c_hwsync(ncpus);
/* pfunc based HW sync on recent G5s */
if (pmac_tb_freeze == NULL) {
struct device_node *cpus =
of_find_node_by_path("/cpus");
if (cpus &&
of_get_property(cpus, "platform-cpu-timebase", NULL)) {
pmac_tb_freeze = smp_core99_pfunc_tb_freeze;
printk(KERN_INFO "Processor timebase sync using"
" platform function\n");
}
}
#else /* CONFIG_PPC64 */
/* GPIO based HW sync on ppc32 Core99 */
if (pmac_tb_freeze == NULL && !machine_is_compatible("MacRISC4")) {
struct device_node *cpu;
const u32 *tbprop = NULL;
core99_tb_gpio = KL_GPIO_TB_ENABLE; /* default value */
cpu = of_find_node_by_type(NULL, "cpu");
if (cpu != NULL) {
tbprop = of_get_property(cpu, "timebase-enable", NULL);
if (tbprop)
core99_tb_gpio = *tbprop;
of_node_put(cpu);
}
pmac_tb_freeze = smp_core99_gpio_tb_freeze;
printk(KERN_INFO "Processor timebase sync using"
" GPIO 0x%02x\n", core99_tb_gpio);
}
#endif /* CONFIG_PPC64 */
/* No timebase sync, fallback to software */
if (pmac_tb_freeze == NULL) {
smp_ops->give_timebase = smp_generic_give_timebase;
smp_ops->take_timebase = smp_generic_take_timebase;
printk(KERN_INFO "Processor timebase sync using software\n");
}
#ifndef CONFIG_PPC64
{
int i;
/* XXX should get this from reg properties */
for (i = 1; i < ncpus; ++i)
set_hard_smp_processor_id(i, i);
}
#endif
/* 32 bits SMP can't NAP */
if (!machine_is_compatible("MacRISC4"))
powersave_nap = 0;
}
static int __init smp_core99_probe(void)
{
struct device_node *cpus;
int ncpus = 0;
if (ppc_md.progress) ppc_md.progress("smp_core99_probe", 0x345);
/* Count CPUs in the device-tree */
for (cpus = NULL; (cpus = of_find_node_by_type(cpus, "cpu")) != NULL;)
++ncpus;
printk(KERN_INFO "PowerMac SMP probe found %d cpus\n", ncpus);
/* Nothing more to do if less than 2 of them */
if (ncpus <= 1)
return 1;
/* We need to perform some early initialisations before we can start
* setting up SMP as we are running before initcalls
*/
pmac_pfunc_base_install();
pmac_i2c_init();
/* Setup various bits like timebase sync method, ability to nap, ... */
smp_core99_setup(ncpus);
/* Install IPIs */
mpic_request_ipis();
/* Collect l2cr and l3cr values from CPU 0 */
core99_init_caches(0);
return ncpus;
}
static void __devinit smp_core99_kick_cpu(int nr)
{
unsigned int save_vector;
unsigned long target, flags;
unsigned int *vector = (unsigned int *)(PAGE_OFFSET+0x100);
if (nr < 0 || nr > 3)
return;
if (ppc_md.progress)
ppc_md.progress("smp_core99_kick_cpu", 0x346);
local_irq_save(flags);
/* Save reset vector */
save_vector = *vector;
/* Setup fake reset vector that does
* b __secondary_start_pmac_0 + nr*8
*/
target = (unsigned long) __secondary_start_pmac_0 + nr * 8;
patch_branch(vector, target, BRANCH_SET_LINK);
/* Put some life in our friend */
pmac_call_feature(PMAC_FTR_RESET_CPU, NULL, nr, 0);
/* FIXME: We wait a bit for the CPU to take the exception, I should
* instead wait for the entry code to set something for me. Well,
* ideally, all that crap will be done in prom.c and the CPU left
* in a RAM-based wait loop like CHRP.
*/
mdelay(1);
/* Restore our exception vector */
*vector = save_vector;
flush_icache_range((unsigned long) vector, (unsigned long) vector + 4);
local_irq_restore(flags);
if (ppc_md.progress) ppc_md.progress("smp_core99_kick_cpu done", 0x347);
}
static void __devinit smp_core99_setup_cpu(int cpu_nr)
{
/* Setup L2/L3 */
if (cpu_nr != 0)
core99_init_caches(cpu_nr);
/* Setup openpic */
mpic_setup_this_cpu();
if (cpu_nr == 0) {
#ifdef CONFIG_PPC64
extern void g5_phy_disable_cpu1(void);
/* Close i2c bus if it was used for tb sync */
if (pmac_tb_clock_chip_host) {
pmac_i2c_close(pmac_tb_clock_chip_host);
pmac_tb_clock_chip_host = NULL;
}
/* If we didn't start the second CPU, we must take
* it off the bus
*/
if (machine_is_compatible("MacRISC4") &&
num_online_cpus() < 2)
g5_phy_disable_cpu1();
#endif /* CONFIG_PPC64 */
if (ppc_md.progress)
ppc_md.progress("core99_setup_cpu 0 done", 0x349);
}
}
#if defined(CONFIG_HOTPLUG_CPU) && defined(CONFIG_PPC32)
int smp_core99_cpu_disable(void)
{
set_cpu_online(smp_processor_id(), false);
/* XXX reset cpu affinity here */
mpic_cpu_set_priority(0xf);
asm volatile("mtdec %0" : : "r" (0x7fffffff));
mb();
udelay(20);
asm volatile("mtdec %0" : : "r" (0x7fffffff));
return 0;
}
extern void low_cpu_die(void) __attribute__((noreturn)); /* in sleep.S */
static int cpu_dead[NR_CPUS];
void cpu_die(void)
{
local_irq_disable();
cpu_dead[smp_processor_id()] = 1;
mb();
low_cpu_die();
}
void smp_core99_cpu_die(unsigned int cpu)
{
int timeout;
timeout = 1000;
while (!cpu_dead[cpu]) {
if (--timeout == 0) {
printk("CPU %u refused to die!\n", cpu);
break;
}
msleep(1);
}
cpu_dead[cpu] = 0;
}
#endif /* CONFIG_HOTPLUG_CPU && CONFIG_PP32 */
/* Core99 Macs (dual G4s and G5s) */
struct smp_ops_t core99_smp_ops = {
.message_pass = smp_mpic_message_pass,
.probe = smp_core99_probe,
.kick_cpu = smp_core99_kick_cpu,
.setup_cpu = smp_core99_setup_cpu,
.give_timebase = smp_core99_give_timebase,
.take_timebase = smp_core99_take_timebase,
#if defined(CONFIG_HOTPLUG_CPU)
# if defined(CONFIG_PPC32)
.cpu_disable = smp_core99_cpu_disable,
.cpu_die = smp_core99_cpu_die,
# endif
# if defined(CONFIG_PPC64)
.cpu_disable = generic_cpu_disable,
.cpu_die = generic_cpu_die,
/* intentionally do *NOT* assign cpu_enable,
* the generic code will use kick_cpu then! */
# endif
#endif
};
void __init pmac_setup_smp(void)
{
struct device_node *np;
/* Check for Core99 */
np = of_find_node_by_name(NULL, "uni-n");
if (!np)
np = of_find_node_by_name(NULL, "u3");
if (!np)
np = of_find_node_by_name(NULL, "u4");
if (np) {
of_node_put(np);
smp_ops = &core99_smp_ops;
}
#ifdef CONFIG_PPC32
else {
/* We have to set bits in cpu_possible_map here since the
* secondary CPU(s) aren't in the device tree. Various
* things won't be initialized for CPUs not in the possible
* map, so we really need to fix it up here.
*/
int cpu;
for (cpu = 1; cpu < 4 && cpu < NR_CPUS; ++cpu)
set_cpu_possible(cpu, true);
smp_ops = &psurge_smp_ops;
}
#endif /* CONFIG_PPC32 */
}

View File

@@ -0,0 +1,335 @@
/*
* Support for periodic interrupts (100 per second) and for getting
* the current time from the RTC on Power Macintoshes.
*
* We use the decrementer register for our periodic interrupts.
*
* Paul Mackerras August 1996.
* Copyright (C) 1996 Paul Mackerras.
* Copyright (C) 2003-2005 Benjamin Herrenschmidt.
*
*/
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/param.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/init.h>
#include <linux/time.h>
#include <linux/adb.h>
#include <linux/cuda.h>
#include <linux/pmu.h>
#include <linux/interrupt.h>
#include <linux/hardirq.h>
#include <linux/rtc.h>
#include <asm/sections.h>
#include <asm/prom.h>
#include <asm/system.h>
#include <asm/io.h>
#include <asm/pgtable.h>
#include <asm/machdep.h>
#include <asm/time.h>
#include <asm/nvram.h>
#include <asm/smu.h>
#undef DEBUG
#ifdef DEBUG
#define DBG(x...) printk(x)
#else
#define DBG(x...)
#endif
/* Apparently the RTC stores seconds since 1 Jan 1904 */
#define RTC_OFFSET 2082844800
/*
* Calibrate the decrementer frequency with the VIA timer 1.
*/
#define VIA_TIMER_FREQ_6 4700000 /* time 1 frequency * 6 */
/* VIA registers */
#define RS 0x200 /* skip between registers */
#define T1CL (4*RS) /* Timer 1 ctr/latch (low 8 bits) */
#define T1CH (5*RS) /* Timer 1 counter (high 8 bits) */
#define T1LL (6*RS) /* Timer 1 latch (low 8 bits) */
#define T1LH (7*RS) /* Timer 1 latch (high 8 bits) */
#define ACR (11*RS) /* Auxiliary control register */
#define IFR (13*RS) /* Interrupt flag register */
/* Bits in ACR */
#define T1MODE 0xc0 /* Timer 1 mode */
#define T1MODE_CONT 0x40 /* continuous interrupts */
/* Bits in IFR and IER */
#define T1_INT 0x40 /* Timer 1 interrupt */
long __init pmac_time_init(void)
{
s32 delta = 0;
#ifdef CONFIG_NVRAM
int dst;
delta = ((s32)pmac_xpram_read(PMAC_XPRAM_MACHINE_LOC + 0x9)) << 16;
delta |= ((s32)pmac_xpram_read(PMAC_XPRAM_MACHINE_LOC + 0xa)) << 8;
delta |= pmac_xpram_read(PMAC_XPRAM_MACHINE_LOC + 0xb);
if (delta & 0x00800000UL)
delta |= 0xFF000000UL;
dst = ((pmac_xpram_read(PMAC_XPRAM_MACHINE_LOC + 0x8) & 0x80) != 0);
printk("GMT Delta read from XPRAM: %d minutes, DST: %s\n", delta/60,
dst ? "on" : "off");
#endif
return delta;
}
#if defined(CONFIG_ADB_CUDA) || defined(CONFIG_ADB_PMU)
static void to_rtc_time(unsigned long now, struct rtc_time *tm)
{
to_tm(now, tm);
tm->tm_year -= 1900;
tm->tm_mon -= 1;
}
#endif
#if defined(CONFIG_ADB_CUDA) || defined(CONFIG_ADB_PMU) || \
defined(CONFIG_PMAC_SMU)
static unsigned long from_rtc_time(struct rtc_time *tm)
{
return mktime(tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec);
}
#endif
#ifdef CONFIG_ADB_CUDA
static unsigned long cuda_get_time(void)
{
struct adb_request req;
unsigned int now;
if (cuda_request(&req, NULL, 2, CUDA_PACKET, CUDA_GET_TIME) < 0)
return 0;
while (!req.complete)
cuda_poll();
if (req.reply_len != 7)
printk(KERN_ERR "cuda_get_time: got %d byte reply\n",
req.reply_len);
now = (req.reply[3] << 24) + (req.reply[4] << 16)
+ (req.reply[5] << 8) + req.reply[6];
return ((unsigned long)now) - RTC_OFFSET;
}
#define cuda_get_rtc_time(tm) to_rtc_time(cuda_get_time(), (tm))
static int cuda_set_rtc_time(struct rtc_time *tm)
{
unsigned int nowtime;
struct adb_request req;
nowtime = from_rtc_time(tm) + RTC_OFFSET;
if (cuda_request(&req, NULL, 6, CUDA_PACKET, CUDA_SET_TIME,
nowtime >> 24, nowtime >> 16, nowtime >> 8,
nowtime) < 0)
return -ENXIO;
while (!req.complete)
cuda_poll();
if ((req.reply_len != 3) && (req.reply_len != 7))
printk(KERN_ERR "cuda_set_rtc_time: got %d byte reply\n",
req.reply_len);
return 0;
}
#else
#define cuda_get_time() 0
#define cuda_get_rtc_time(tm)
#define cuda_set_rtc_time(tm) 0
#endif
#ifdef CONFIG_ADB_PMU
static unsigned long pmu_get_time(void)
{
struct adb_request req;
unsigned int now;
if (pmu_request(&req, NULL, 1, PMU_READ_RTC) < 0)
return 0;
pmu_wait_complete(&req);
if (req.reply_len != 4)
printk(KERN_ERR "pmu_get_time: got %d byte reply from PMU\n",
req.reply_len);
now = (req.reply[0] << 24) + (req.reply[1] << 16)
+ (req.reply[2] << 8) + req.reply[3];
return ((unsigned long)now) - RTC_OFFSET;
}
#define pmu_get_rtc_time(tm) to_rtc_time(pmu_get_time(), (tm))
static int pmu_set_rtc_time(struct rtc_time *tm)
{
unsigned int nowtime;
struct adb_request req;
nowtime = from_rtc_time(tm) + RTC_OFFSET;
if (pmu_request(&req, NULL, 5, PMU_SET_RTC, nowtime >> 24,
nowtime >> 16, nowtime >> 8, nowtime) < 0)
return -ENXIO;
pmu_wait_complete(&req);
if (req.reply_len != 0)
printk(KERN_ERR "pmu_set_rtc_time: %d byte reply from PMU\n",
req.reply_len);
return 0;
}
#else
#define pmu_get_time() 0
#define pmu_get_rtc_time(tm)
#define pmu_set_rtc_time(tm) 0
#endif
#ifdef CONFIG_PMAC_SMU
static unsigned long smu_get_time(void)
{
struct rtc_time tm;
if (smu_get_rtc_time(&tm, 1))
return 0;
return from_rtc_time(&tm);
}
#else
#define smu_get_time() 0
#define smu_get_rtc_time(tm, spin)
#define smu_set_rtc_time(tm, spin) 0
#endif
/* Can't be __init, it's called when suspending and resuming */
unsigned long pmac_get_boot_time(void)
{
/* Get the time from the RTC, used only at boot time */
switch (sys_ctrler) {
case SYS_CTRLER_CUDA:
return cuda_get_time();
case SYS_CTRLER_PMU:
return pmu_get_time();
case SYS_CTRLER_SMU:
return smu_get_time();
default:
return 0;
}
}
void pmac_get_rtc_time(struct rtc_time *tm)
{
/* Get the time from the RTC, used only at boot time */
switch (sys_ctrler) {
case SYS_CTRLER_CUDA:
cuda_get_rtc_time(tm);
break;
case SYS_CTRLER_PMU:
pmu_get_rtc_time(tm);
break;
case SYS_CTRLER_SMU:
smu_get_rtc_time(tm, 1);
break;
default:
;
}
}
int pmac_set_rtc_time(struct rtc_time *tm)
{
switch (sys_ctrler) {
case SYS_CTRLER_CUDA:
return cuda_set_rtc_time(tm);
case SYS_CTRLER_PMU:
return pmu_set_rtc_time(tm);
case SYS_CTRLER_SMU:
return smu_set_rtc_time(tm, 1);
default:
return -ENODEV;
}
}
#ifdef CONFIG_PPC32
/*
* Calibrate the decrementer register using VIA timer 1.
* This is used both on powermacs and CHRP machines.
*/
int __init via_calibrate_decr(void)
{
struct device_node *vias;
volatile unsigned char __iomem *via;
int count = VIA_TIMER_FREQ_6 / 100;
unsigned int dstart, dend;
struct resource rsrc;
vias = of_find_node_by_name(NULL, "via-cuda");
if (vias == NULL)
vias = of_find_node_by_name(NULL, "via-pmu");
if (vias == NULL)
vias = of_find_node_by_name(NULL, "via");
if (vias == NULL || of_address_to_resource(vias, 0, &rsrc)) {
of_node_put(vias);
return 0;
}
of_node_put(vias);
via = ioremap(rsrc.start, rsrc.end - rsrc.start + 1);
if (via == NULL) {
printk(KERN_ERR "Failed to map VIA for timer calibration !\n");
return 0;
}
/* set timer 1 for continuous interrupts */
out_8(&via[ACR], (via[ACR] & ~T1MODE) | T1MODE_CONT);
/* set the counter to a small value */
out_8(&via[T1CH], 2);
/* set the latch to `count' */
out_8(&via[T1LL], count);
out_8(&via[T1LH], count >> 8);
/* wait until it hits 0 */
while ((in_8(&via[IFR]) & T1_INT) == 0)
;
dstart = get_dec();
/* clear the interrupt & wait until it hits 0 again */
in_8(&via[T1CL]);
while ((in_8(&via[IFR]) & T1_INT) == 0)
;
dend = get_dec();
ppc_tb_freq = (dstart - dend) * 100 / 6;
iounmap(via);
return 1;
}
#endif
/*
* Query the OF and get the decr frequency.
*/
void __init pmac_calibrate_decr(void)
{
generic_calibrate_decr();
#ifdef CONFIG_PPC32
/* We assume MacRISC2 machines have correct device-tree
* calibration. That's better since the VIA itself seems
* to be slightly off. --BenH
*/
if (!machine_is_compatible("MacRISC2") &&
!machine_is_compatible("MacRISC3") &&
!machine_is_compatible("MacRISC4"))
if (via_calibrate_decr())
return;
/* Special case: QuickSilver G4s seem to have a badly calibrated
* timebase-frequency in OF, VIA is much better on these. We should
* probably implement calibration based on the KL timer on these
* machines anyway... -BenH
*/
if (machine_is_compatible("PowerMac3,5"))
if (via_calibrate_decr())
return;
#endif
}

View File

@@ -0,0 +1,219 @@
#include <linux/string.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/bitops.h>
#include <linux/ptrace.h>
#include <linux/adb.h>
#include <linux/pmu.h>
#include <linux/cuda.h>
#include <asm/machdep.h>
#include <asm/io.h>
#include <asm/page.h>
#include <asm/xmon.h>
#include <asm/prom.h>
#include <asm/bootx.h>
#include <asm/errno.h>
#include <asm/pmac_feature.h>
#include <asm/processor.h>
#include <asm/delay.h>
#include <asm/btext.h>
#include <asm/time.h>
#include <asm/udbg.h>
/*
* This implementation is "special", it can "patch" the current
* udbg implementation and work on top of it. It must thus be
* initialized last
*/
static void (*udbg_adb_old_putc)(char c);
static int (*udbg_adb_old_getc)(void);
static int (*udbg_adb_old_getc_poll)(void);
static enum {
input_adb_none,
input_adb_pmu,
input_adb_cuda,
} input_type = input_adb_none;
int xmon_wants_key, xmon_adb_keycode;
static inline void udbg_adb_poll(void)
{
#ifdef CONFIG_ADB_PMU
if (input_type == input_adb_pmu)
pmu_poll_adb();
#endif /* CONFIG_ADB_PMU */
#ifdef CONFIG_ADB_CUDA
if (input_type == input_adb_cuda)
cuda_poll();
#endif /* CONFIG_ADB_CUDA */
}
#ifdef CONFIG_BOOTX_TEXT
static int udbg_adb_use_btext;
static int xmon_adb_shiftstate;
static unsigned char xmon_keytab[128] =
"asdfhgzxcv\000bqwer" /* 0x00 - 0x0f */
"yt123465=97-80]o" /* 0x10 - 0x1f */
"u[ip\rlj'k;\\,/nm." /* 0x20 - 0x2f */
"\t `\177\0\033\0\0\0\0\0\0\0\0\0\0" /* 0x30 - 0x3f */
"\0.\0*\0+\0\0\0\0\0/\r\0-\0" /* 0x40 - 0x4f */
"\0\0000123456789\0\0\0"; /* 0x50 - 0x5f */
static unsigned char xmon_shift_keytab[128] =
"ASDFHGZXCV\000BQWER" /* 0x00 - 0x0f */
"YT!@#$^%+(&_*)}O" /* 0x10 - 0x1f */
"U{IP\rLJ\"K:|<?NM>" /* 0x20 - 0x2f */
"\t ~\177\0\033\0\0\0\0\0\0\0\0\0\0" /* 0x30 - 0x3f */
"\0.\0*\0+\0\0\0\0\0/\r\0-\0" /* 0x40 - 0x4f */
"\0\0000123456789\0\0\0"; /* 0x50 - 0x5f */
static int udbg_adb_local_getc(void)
{
int k, t, on;
xmon_wants_key = 1;
for (;;) {
xmon_adb_keycode = -1;
t = 0;
on = 0;
k = -1;
do {
if (--t < 0) {
on = 1 - on;
btext_drawchar(on? 0xdb: 0x20);
btext_drawchar('\b');
t = 200000;
}
udbg_adb_poll();
if (udbg_adb_old_getc_poll)
k = udbg_adb_old_getc_poll();
} while (k == -1 && xmon_adb_keycode == -1);
if (on)
btext_drawstring(" \b");
if (k != -1)
return k;
k = xmon_adb_keycode;
/* test for shift keys */
if ((k & 0x7f) == 0x38 || (k & 0x7f) == 0x7b) {
xmon_adb_shiftstate = (k & 0x80) == 0;
continue;
}
if (k >= 0x80)
continue; /* ignore up transitions */
k = (xmon_adb_shiftstate? xmon_shift_keytab: xmon_keytab)[k];
if (k != 0)
break;
}
xmon_wants_key = 0;
return k;
}
#endif /* CONFIG_BOOTX_TEXT */
static int udbg_adb_getc(void)
{
#ifdef CONFIG_BOOTX_TEXT
if (udbg_adb_use_btext && input_type != input_adb_none)
return udbg_adb_local_getc();
#endif
if (udbg_adb_old_getc)
return udbg_adb_old_getc();
return -1;
}
/* getc_poll() is not really used, unless you have the xmon-over modem
* hack that doesn't quite concern us here, thus we just poll the low level
* ADB driver to prevent it from timing out and call back the original poll
* routine.
*/
static int udbg_adb_getc_poll(void)
{
udbg_adb_poll();
if (udbg_adb_old_getc_poll)
return udbg_adb_old_getc_poll();
return -1;
}
static void udbg_adb_putc(char c)
{
#ifdef CONFIG_BOOTX_TEXT
if (udbg_adb_use_btext)
btext_drawchar(c);
#endif
if (udbg_adb_old_putc)
return udbg_adb_old_putc(c);
}
void __init udbg_adb_init_early(void)
{
#ifdef CONFIG_BOOTX_TEXT
if (btext_find_display(1) == 0) {
udbg_adb_use_btext = 1;
udbg_putc = udbg_adb_putc;
}
#endif
}
int __init udbg_adb_init(int force_btext)
{
struct device_node *np;
/* Capture existing callbacks */
udbg_adb_old_putc = udbg_putc;
udbg_adb_old_getc = udbg_getc;
udbg_adb_old_getc_poll = udbg_getc_poll;
/* Check if our early init was already called */
if (udbg_adb_old_putc == udbg_adb_putc)
udbg_adb_old_putc = NULL;
#ifdef CONFIG_BOOTX_TEXT
if (udbg_adb_old_putc == btext_drawchar)
udbg_adb_old_putc = NULL;
#endif
/* Set ours as output */
udbg_putc = udbg_adb_putc;
udbg_getc = udbg_adb_getc;
udbg_getc_poll = udbg_adb_getc_poll;
#ifdef CONFIG_BOOTX_TEXT
/* Check if we should use btext output */
if (btext_find_display(force_btext) == 0)
udbg_adb_use_btext = 1;
#endif
/* See if there is a keyboard in the device tree with a parent
* of type "adb". If not, we return a failure, but we keep the
* bext output set for now
*/
for (np = NULL; (np = of_find_node_by_name(np, "keyboard")) != NULL;) {
struct device_node *parent = of_get_parent(np);
int found = (parent && strcmp(parent->type, "adb") == 0);
of_node_put(parent);
if (found)
break;
}
if (np == NULL)
return -ENODEV;
of_node_put(np);
#ifdef CONFIG_ADB_PMU
if (find_via_pmu())
input_type = input_adb_pmu;
#endif
#ifdef CONFIG_ADB_CUDA
if (find_via_cuda())
input_type = input_adb_cuda;
#endif
/* Same as above: nothing found, keep btext set for output */
if (input_type == input_adb_none)
return -ENODEV;
return 0;
}

View File

@@ -0,0 +1,184 @@
/*
* udbg for zilog scc ports as found on Apple PowerMacs
*
* Copyright (C) 2001-2005 PPC 64 Team, IBM Corp
*
* 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 <asm/udbg.h>
#include <asm/processor.h>
#include <asm/io.h>
#include <asm/prom.h>
#include <asm/pmac_feature.h>
extern u8 real_readb(volatile u8 __iomem *addr);
extern void real_writeb(u8 data, volatile u8 __iomem *addr);
#define SCC_TXRDY 4
#define SCC_RXRDY 1
static volatile u8 __iomem *sccc;
static volatile u8 __iomem *sccd;
static void udbg_scc_putc(char c)
{
if (sccc) {
while ((in_8(sccc) & SCC_TXRDY) == 0)
;
out_8(sccd, c);
if (c == '\n')
udbg_scc_putc('\r');
}
}
static int udbg_scc_getc_poll(void)
{
if (sccc) {
if ((in_8(sccc) & SCC_RXRDY) != 0)
return in_8(sccd);
else
return -1;
}
return -1;
}
static int udbg_scc_getc(void)
{
if (sccc) {
while ((in_8(sccc) & SCC_RXRDY) == 0)
;
return in_8(sccd);
}
return -1;
}
static unsigned char scc_inittab[] = {
13, 0, /* set baud rate divisor */
12, 0,
14, 1, /* baud rate gen enable, src=rtxc */
11, 0x50, /* clocks = br gen */
5, 0xea, /* tx 8 bits, assert DTR & RTS */
4, 0x46, /* x16 clock, 1 stop */
3, 0xc1, /* rx enable, 8 bits */
};
void udbg_scc_init(int force_scc)
{
const u32 *reg;
unsigned long addr;
struct device_node *stdout = NULL, *escc = NULL, *macio = NULL;
struct device_node *ch, *ch_def = NULL, *ch_a = NULL;
const char *path;
int i, x;
escc = of_find_node_by_name(NULL, "escc");
if (escc == NULL)
goto bail;
macio = of_get_parent(escc);
if (macio == NULL)
goto bail;
path = of_get_property(of_chosen, "linux,stdout-path", NULL);
if (path != NULL)
stdout = of_find_node_by_path(path);
for (ch = NULL; (ch = of_get_next_child(escc, ch)) != NULL;) {
if (ch == stdout)
ch_def = of_node_get(ch);
if (strcmp(ch->name, "ch-a") == 0)
ch_a = of_node_get(ch);
}
if (ch_def == NULL && !force_scc)
goto bail;
ch = ch_def ? ch_def : ch_a;
/* Get address within mac-io ASIC */
reg = of_get_property(escc, "reg", NULL);
if (reg == NULL)
goto bail;
addr = reg[0];
/* Get address of mac-io PCI itself */
reg = of_get_property(macio, "assigned-addresses", NULL);
if (reg == NULL)
goto bail;
addr += reg[2];
/* Lock the serial port */
pmac_call_feature(PMAC_FTR_SCC_ENABLE, ch,
PMAC_SCC_ASYNC | PMAC_SCC_FLAG_XMON, 1);
if (ch == ch_a)
addr += 0x20;
sccc = ioremap(addr & PAGE_MASK, PAGE_SIZE) ;
sccc += addr & ~PAGE_MASK;
sccd = sccc + 0x10;
mb();
for (i = 20000; i != 0; --i)
x = in_8(sccc);
out_8(sccc, 0x09); /* reset A or B side */
out_8(sccc, 0xc0);
/* If SCC was the OF output port, read the BRG value, else
* Setup for 38400 or 57600 8N1 depending on the machine
*/
if (ch_def != NULL) {
out_8(sccc, 13);
scc_inittab[1] = in_8(sccc);
out_8(sccc, 12);
scc_inittab[3] = in_8(sccc);
} else if (machine_is_compatible("RackMac1,1")
|| machine_is_compatible("RackMac1,2")
|| machine_is_compatible("MacRISC4")) {
/* Xserves and G5s default to 57600 */
scc_inittab[1] = 0;
scc_inittab[3] = 0;
} else {
/* Others default to 38400 */
scc_inittab[1] = 0;
scc_inittab[3] = 1;
}
for (i = 0; i < sizeof(scc_inittab); ++i)
out_8(sccc, scc_inittab[i]);
udbg_putc = udbg_scc_putc;
udbg_getc = udbg_scc_getc;
udbg_getc_poll = udbg_scc_getc_poll;
udbg_puts("Hello World !\n");
bail:
of_node_put(macio);
of_node_put(escc);
of_node_put(stdout);
of_node_put(ch_def);
of_node_put(ch_a);
}
#ifdef CONFIG_PPC64
static void udbg_real_scc_putc(char c)
{
while ((real_readb(sccc) & SCC_TXRDY) == 0)
;
real_writeb(c, sccd);
if (c == '\n')
udbg_real_scc_putc('\r');
}
void __init udbg_init_pmac_realmode(void)
{
sccc = (volatile u8 __iomem *)0x80013020ul;
sccd = (volatile u8 __iomem *)0x80013030ul;
udbg_putc = udbg_real_scc_putc;
udbg_getc = NULL;
udbg_getc_poll = NULL;
}
#endif /* CONFIG_PPC64 */