From 4b0eac56907e727d57464c0f9d312c2e3ea16a0f Mon Sep 17 00:00:00 2001 From: Victor Gallardo Date: Fri, 8 Oct 2010 10:25:27 +0000 Subject: [PATCH] --- yaml --- r: 229333 b: refs/heads/master c: d164f6d4f9108126f69ba2963cf6fb7ef4ba9232 h: refs/heads/master i: 229331: d26dbb9695f763e1ca6f2db3dc6a230384e8fc44 v: v3 --- [refs] | 2 +- .../powerpc/dts-bindings/4xx/cpm.txt | 52 ++ trunk/arch/powerpc/Kconfig | 13 +- trunk/arch/powerpc/include/asm/nvram.h | 52 +- trunk/arch/powerpc/kernel/nvram_64.c | 481 ++++++++++++------ trunk/arch/powerpc/mm/pgtable.c | 2 +- trunk/arch/powerpc/platforms/44x/Makefile | 5 +- trunk/arch/powerpc/platforms/chrp/time.c | 4 - trunk/arch/powerpc/platforms/pseries/nvram.c | 205 -------- trunk/arch/powerpc/sysdev/Makefile | 1 + trunk/arch/powerpc/sysdev/ppc4xx_cpm.c | 346 +++++++++++++ 11 files changed, 777 insertions(+), 386 deletions(-) create mode 100644 trunk/Documentation/powerpc/dts-bindings/4xx/cpm.txt create mode 100644 trunk/arch/powerpc/sysdev/ppc4xx_cpm.c diff --git a/[refs] b/[refs] index 39039386d96b..456bd449ba8e 100644 --- a/[refs] +++ b/[refs] @@ -1,2 +1,2 @@ --- -refs/heads/master: f4b98415953dcf85bac4ea0a2264a3ead4a7bcc4 +refs/heads/master: d164f6d4f9108126f69ba2963cf6fb7ef4ba9232 diff --git a/trunk/Documentation/powerpc/dts-bindings/4xx/cpm.txt b/trunk/Documentation/powerpc/dts-bindings/4xx/cpm.txt new file mode 100644 index 000000000000..ee459806d35e --- /dev/null +++ b/trunk/Documentation/powerpc/dts-bindings/4xx/cpm.txt @@ -0,0 +1,52 @@ +PPC4xx Clock Power Management (CPM) node + +Required properties: + - compatible : compatible list, currently only "ibm,cpm" + - dcr-access-method : "native" + - dcr-reg : < DCR register range > + +Optional properties: + - er-offset : All 4xx SoCs with a CPM controller have + one of two different order for the CPM + registers. Some have the CPM registers + in the following order (ER,FR,SR). The + others have them in the following order + (SR,ER,FR). For the second case set + er-offset = <1>. + - unused-units : specifier consist of one cell. For each + bit in the cell, the corresponding bit + in CPM will be set to turn off unused + devices. + - idle-doze : specifier consist of one cell. For each + bit in the cell, the corresponding bit + in CPM will be set to turn off unused + devices. This is usually just CPM[CPU]. + - standby : specifier consist of one cell. For each + bit in the cell, the corresponding bit + in CPM will be set on standby and + restored on resume. + - suspend : specifier consist of one cell. For each + bit in the cell, the corresponding bit + in CPM will be set on suspend (mem) and + restored on resume. Note, for standby + and suspend the corresponding bits can + be different or the same. Usually for + standby only class 2 and 3 units are set. + However, the interface does not care. + If they are the same, the additional + power saving will be seeing if support + is available to put the DDR in self + refresh mode and any additional power + saving techniques for the specific SoC. + +Example: + CPM0: cpm { + compatible = "ibm,cpm"; + dcr-access-method = "native"; + dcr-reg = <0x160 0x003>; + er-offset = <0>; + unused-units = <0x00000100>; + idle-doze = <0x02000000>; + standby = <0xfeff0000>; + suspend = <0xfeff791d>; +}; diff --git a/trunk/arch/powerpc/Kconfig b/trunk/arch/powerpc/Kconfig index 06d742c3fbcf..e16b4988f825 100644 --- a/trunk/arch/powerpc/Kconfig +++ b/trunk/arch/powerpc/Kconfig @@ -212,7 +212,7 @@ config ARCH_HIBERNATION_POSSIBLE config ARCH_SUSPEND_POSSIBLE def_bool y depends on ADB_PMU || PPC_EFIKA || PPC_LITE5200 || PPC_83xx || \ - PPC_85xx || PPC_86xx || PPC_PSERIES + PPC_85xx || PPC_86xx || PPC_PSERIES || 44x || 40x config PPC_DCR_NATIVE bool @@ -598,13 +598,11 @@ config EXTRA_TARGETS If unsure, leave blank -if !44x || BROKEN config ARCH_WANTS_FREEZER_CONTROL def_bool y depends on ADB_PMU source kernel/power/Kconfig -endif config SECCOMP bool "Enable seccomp to safely compute untrusted bytecode" @@ -685,6 +683,15 @@ config FSL_PMC Freescale MPC85xx/MPC86xx power management controller support (suspend/resume). For MPC83xx see platforms/83xx/suspend.c +config PPC4xx_CPM + bool + default y + depends on SUSPEND && (44x || 40x) + help + PPC4xx Clock Power Management (CPM) support (suspend/resume). + It also enables support for two different idle states (idle-wait + and idle-doze). + config 4xx_SOC bool diff --git a/trunk/arch/powerpc/include/asm/nvram.h b/trunk/arch/powerpc/include/asm/nvram.h index 92efe67d1c57..850b72f27445 100644 --- a/trunk/arch/powerpc/include/asm/nvram.h +++ b/trunk/arch/powerpc/include/asm/nvram.h @@ -10,7 +10,31 @@ #ifndef _ASM_POWERPC_NVRAM_H #define _ASM_POWERPC_NVRAM_H -/* Signatures for nvram partitions */ +#include + +#define NVRW_CNT 0x20 +#define NVRAM_HEADER_LEN 16 /* sizeof(struct nvram_header) */ +#define NVRAM_BLOCK_LEN 16 +#define NVRAM_MAX_REQ (2080/NVRAM_BLOCK_LEN) +#define NVRAM_MIN_REQ (1056/NVRAM_BLOCK_LEN) + +#define NVRAM_AS0 0x74 +#define NVRAM_AS1 0x75 +#define NVRAM_DATA 0x77 + + +/* RTC Offsets */ + +#define MOTO_RTC_SECONDS 0x1FF9 +#define MOTO_RTC_MINUTES 0x1FFA +#define MOTO_RTC_HOURS 0x1FFB +#define MOTO_RTC_DAY_OF_WEEK 0x1FFC +#define MOTO_RTC_DAY_OF_MONTH 0x1FFD +#define MOTO_RTC_MONTH 0x1FFE +#define MOTO_RTC_YEAR 0x1FFF +#define MOTO_RTC_CONTROLA 0x1FF8 +#define MOTO_RTC_CONTROLB 0x1FF9 + #define NVRAM_SIG_SP 0x02 /* support processor */ #define NVRAM_SIG_OF 0x50 /* open firmware config */ #define NVRAM_SIG_FW 0x51 /* general firmware */ @@ -25,19 +49,32 @@ #define NVRAM_SIG_OS 0xa0 /* OS defined */ #define NVRAM_SIG_PANIC 0xa1 /* Apple OSX "panic" */ +/* If change this size, then change the size of NVNAME_LEN */ +struct nvram_header { + unsigned char signature; + unsigned char checksum; + unsigned short length; + char name[12]; +}; + #ifdef __KERNEL__ -#include #include -#ifdef CONFIG_PPC_PSERIES +struct nvram_partition { + struct list_head partition; + struct nvram_header header; + unsigned int index; +}; + + extern int nvram_write_error_log(char * buff, int length, unsigned int err_type, unsigned int err_seq); extern int nvram_read_error_log(char * buff, int length, unsigned int * err_type, unsigned int *err_seq); extern int nvram_clear_error_log(void); + extern int pSeries_nvram_init(void); -#endif /* CONFIG_PPC_PSERIES */ #ifdef CONFIG_MMIO_NVRAM extern int mmio_nvram_init(void); @@ -48,13 +85,6 @@ static inline int mmio_nvram_init(void) } #endif -extern int __init nvram_scan_partitions(void); -extern loff_t nvram_create_partition(const char *name, int sig, - int req_size, int min_size); -extern int nvram_remove_partition(const char *name, int sig); -extern int nvram_get_partition_size(loff_t data_index); -extern loff_t nvram_find_partition(const char *name, int sig, int *out_size); - #endif /* __KERNEL__ */ /* PowerMac specific nvram stuffs */ diff --git a/trunk/arch/powerpc/kernel/nvram_64.c b/trunk/arch/powerpc/kernel/nvram_64.c index bb12b3248f13..9cf197f01e94 100644 --- a/trunk/arch/powerpc/kernel/nvram_64.c +++ b/trunk/arch/powerpc/kernel/nvram_64.c @@ -34,26 +34,15 @@ #undef DEBUG_NVRAM -#define NVRAM_HEADER_LEN sizeof(struct nvram_header) -#define NVRAM_BLOCK_LEN NVRAM_HEADER_LEN - -/* If change this size, then change the size of NVNAME_LEN */ -struct nvram_header { - unsigned char signature; - unsigned char checksum; - unsigned short length; - /* Terminating null required only for names < 12 chars. */ - char name[12]; -}; +static struct nvram_partition * nvram_part; +static long nvram_error_log_index = -1; +static long nvram_error_log_size = 0; -struct nvram_partition { - struct list_head partition; - struct nvram_header header; - unsigned int index; +struct err_log_info { + int error_type; + unsigned int seq_num; }; -static LIST_HEAD(nvram_partitions); - static loff_t dev_nvram_llseek(struct file *file, loff_t offset, int origin) { int size; @@ -197,12 +186,14 @@ static struct miscdevice nvram_dev = { #ifdef DEBUG_NVRAM static void __init nvram_print_partitions(char * label) { + struct list_head * p; struct nvram_partition * tmp_part; printk(KERN_WARNING "--------%s---------\n", label); printk(KERN_WARNING "indx\t\tsig\tchks\tlen\tname\n"); - list_for_each_entry(tmp_part, &nvram_partitions, partition) { - printk(KERN_WARNING "%4d \t%02x\t%02x\t%d\t%12s\n", + list_for_each(p, &nvram_part->partition) { + tmp_part = list_entry(p, struct nvram_partition, partition); + printk(KERN_WARNING "%4d \t%02x\t%02x\t%d\t%s\n", tmp_part->index, tmp_part->header.signature, tmp_part->header.checksum, tmp_part->header.length, tmp_part->header.name); @@ -237,113 +228,95 @@ static unsigned char __init nvram_checksum(struct nvram_header *p) return c_sum; } -/** - * nvram_remove_partition - Remove one or more partitions in nvram - * @name: name of the partition to remove, or NULL for a - * signature only match - * @sig: signature of the partition(s) to remove - */ - -int __init nvram_remove_partition(const char *name, int sig) +static int __init nvram_remove_os_partition(void) { - struct nvram_partition *part, *prev, *tmp; + struct list_head *i; + struct list_head *j; + struct nvram_partition * part; + struct nvram_partition * cur_part; int rc; - list_for_each_entry(part, &nvram_partitions, partition) { - if (part->header.signature != sig) - continue; - if (name && strncmp(name, part->header.name, 12)) + list_for_each(i, &nvram_part->partition) { + part = list_entry(i, struct nvram_partition, partition); + if (part->header.signature != NVRAM_SIG_OS) continue; - - /* Make partition a free partition */ + + /* Make os partition a free partition */ part->header.signature = NVRAM_SIG_FREE; - strncpy(part->header.name, "wwwwwwwwwwww", 12); + sprintf(part->header.name, "wwwwwwwwwwww"); part->header.checksum = nvram_checksum(&part->header); + + /* Merge contiguous free partitions backwards */ + list_for_each_prev(j, &part->partition) { + cur_part = list_entry(j, struct nvram_partition, partition); + if (cur_part == nvram_part || cur_part->header.signature != NVRAM_SIG_FREE) { + break; + } + + part->header.length += cur_part->header.length; + part->header.checksum = nvram_checksum(&part->header); + part->index = cur_part->index; + + list_del(&cur_part->partition); + kfree(cur_part); + j = &part->partition; /* fixup our loop */ + } + + /* Merge contiguous free partitions forwards */ + list_for_each(j, &part->partition) { + cur_part = list_entry(j, struct nvram_partition, partition); + if (cur_part == nvram_part || cur_part->header.signature != NVRAM_SIG_FREE) { + break; + } + + part->header.length += cur_part->header.length; + part->header.checksum = nvram_checksum(&part->header); + + list_del(&cur_part->partition); + kfree(cur_part); + j = &part->partition; /* fixup our loop */ + } + rc = nvram_write_header(part); if (rc <= 0) { - printk(KERN_ERR "nvram_remove_partition: nvram_write failed (%d)\n", rc); + printk(KERN_ERR "nvram_remove_os_partition: nvram_write failed (%d)\n", rc); return rc; } - } - /* Merge contiguous ones */ - prev = NULL; - list_for_each_entry_safe(part, tmp, &nvram_partitions, partition) { - if (part->header.signature != NVRAM_SIG_FREE) { - prev = NULL; - continue; - } - if (prev) { - prev->header.length += part->header.length; - prev->header.checksum = nvram_checksum(&part->header); - rc = nvram_write_header(part); - if (rc <= 0) { - printk(KERN_ERR "nvram_remove_partition: nvram_write failed (%d)\n", rc); - return rc; - } - list_del(&part->partition); - kfree(part); - } else - prev = part; } return 0; } -/** - * nvram_create_partition - Create a partition in nvram - * @name: name of the partition to create - * @sig: signature of the partition to create - * @req_size: size of data to allocate in bytes - * @min_size: minimum acceptable size (0 means req_size) +/* nvram_create_os_partition * - * Returns a negative error code or a positive nvram index - * of the beginning of the data area of the newly created - * partition. If you provided a min_size smaller than req_size - * you need to query for the actual size yourself after the - * call using nvram_partition_get_size(). + * Create a OS linux partition to buffer error logs. + * Will create a partition starting at the first free + * space found if space has enough room. */ -loff_t __init nvram_create_partition(const char *name, int sig, - int req_size, int min_size) +static int __init nvram_create_os_partition(void) { struct nvram_partition *part; struct nvram_partition *new_part; struct nvram_partition *free_part = NULL; - static char nv_init_vals[16]; + int seq_init[2] = { 0, 0 }; loff_t tmp_index; long size = 0; int rc; - - /* Convert sizes from bytes to blocks */ - req_size = _ALIGN_UP(req_size, NVRAM_BLOCK_LEN) / NVRAM_BLOCK_LEN; - min_size = _ALIGN_UP(min_size, NVRAM_BLOCK_LEN) / NVRAM_BLOCK_LEN; - - /* If no minimum size specified, make it the same as the - * requested size - */ - if (min_size == 0) - min_size = req_size; - if (min_size > req_size) - return -EINVAL; - - /* Now add one block to each for the header */ - req_size += 1; - min_size += 1; - + /* Find a free partition that will give us the maximum needed size If can't find one that will give us the minimum size needed */ - list_for_each_entry(part, &nvram_partitions, partition) { + list_for_each_entry(part, &nvram_part->partition, partition) { if (part->header.signature != NVRAM_SIG_FREE) continue; - if (part->header.length >= req_size) { - size = req_size; + if (part->header.length >= NVRAM_MAX_REQ) { + size = NVRAM_MAX_REQ; free_part = part; break; } - if (part->header.length > size && - part->header.length >= min_size) { - size = part->header.length; + if (!size && part->header.length >= NVRAM_MIN_REQ) { + size = NVRAM_MIN_REQ; free_part = part; } } @@ -353,95 +326,136 @@ loff_t __init nvram_create_partition(const char *name, int sig, /* Create our OS partition */ new_part = kmalloc(sizeof(*new_part), GFP_KERNEL); if (!new_part) { - pr_err("nvram_create_os_partition: kmalloc failed\n"); + printk(KERN_ERR "nvram_create_os_partition: kmalloc failed\n"); return -ENOMEM; } new_part->index = free_part->index; - new_part->header.signature = sig; + new_part->header.signature = NVRAM_SIG_OS; new_part->header.length = size; - strncpy(new_part->header.name, name, 12); + strcpy(new_part->header.name, "ppc64,linux"); new_part->header.checksum = nvram_checksum(&new_part->header); rc = nvram_write_header(new_part); if (rc <= 0) { - pr_err("nvram_create_os_partition: nvram_write_header " + printk(KERN_ERR "nvram_create_os_partition: nvram_write_header " + "failed (%d)\n", rc); + return rc; + } + + /* make sure and initialize to zero the sequence number and the error + type logged */ + tmp_index = new_part->index + NVRAM_HEADER_LEN; + rc = ppc_md.nvram_write((char *)&seq_init, sizeof(seq_init), &tmp_index); + if (rc <= 0) { + printk(KERN_ERR "nvram_create_os_partition: nvram_write " "failed (%d)\n", rc); return rc; } + + nvram_error_log_index = new_part->index + NVRAM_HEADER_LEN; + nvram_error_log_size = ((part->header.length - 1) * + NVRAM_BLOCK_LEN) - sizeof(struct err_log_info); + list_add_tail(&new_part->partition, &free_part->partition); - /* Adjust or remove the partition we stole the space from */ - if (free_part->header.length > size) { - free_part->index += size * NVRAM_BLOCK_LEN; - free_part->header.length -= size; - free_part->header.checksum = nvram_checksum(&free_part->header); - rc = nvram_write_header(free_part); - if (rc <= 0) { - pr_err("nvram_create_os_partition: nvram_write_header " - "failed (%d)\n", rc); - return rc; - } - } else { + if (free_part->header.length <= size) { list_del(&free_part->partition); kfree(free_part); + return 0; } - /* Clear the new partition */ - for (tmp_index = new_part->index + NVRAM_HEADER_LEN; - tmp_index < ((size - 1) * NVRAM_BLOCK_LEN); - tmp_index += NVRAM_BLOCK_LEN) { - rc = ppc_md.nvram_write(nv_init_vals, NVRAM_BLOCK_LEN, &tmp_index); - if (rc <= 0) { - pr_err("nvram_create_partition: nvram_write failed (%d)\n", rc); - return rc; - } - } - - return new_part->index + NVRAM_HEADER_LEN; -} - -/** - * nvram_get_partition_size - Get the data size of an nvram partition - * @data_index: This is the offset of the start of the data of - * the partition. The same value that is returned by - * nvram_create_partition(). - */ -int nvram_get_partition_size(loff_t data_index) -{ - struct nvram_partition *part; + /* Adjust the partition we stole the space from */ + free_part->index += size * NVRAM_BLOCK_LEN; + free_part->header.length -= size; + free_part->header.checksum = nvram_checksum(&free_part->header); - list_for_each_entry(part, &nvram_partitions, partition) { - if (part->index + NVRAM_HEADER_LEN == data_index) - return (part->header.length - 1) * NVRAM_BLOCK_LEN; + rc = nvram_write_header(free_part); + if (rc <= 0) { + printk(KERN_ERR "nvram_create_os_partition: nvram_write_header " + "failed (%d)\n", rc); + return rc; } - return -1; + + return 0; } -/** - * nvram_find_partition - Find an nvram partition by signature and name - * @name: Name of the partition or NULL for any name - * @sig: Signature to test against - * @out_size: if non-NULL, returns the size of the data part of the partition +/* nvram_setup_partition + * + * This will setup the partition we need for buffering the + * error logs and cleanup partitions if needed. + * + * The general strategy is the following: + * 1.) If there is ppc64,linux partition large enough then use it. + * 2.) If there is not a ppc64,linux partition large enough, search + * for a free partition that is large enough. + * 3.) If there is not a free partition large enough remove + * _all_ OS partitions and consolidate the space. + * 4.) Will first try getting a chunk that will satisfy the maximum + * error log size (NVRAM_MAX_REQ). + * 5.) If the max chunk cannot be allocated then try finding a chunk + * that will satisfy the minum needed (NVRAM_MIN_REQ). */ -loff_t nvram_find_partition(const char *name, int sig, int *out_size) +static int __init nvram_setup_partition(void) { - struct nvram_partition *p; - - list_for_each_entry(p, &nvram_partitions, partition) { - if (p->header.signature == sig && - (!name || !strncmp(p->header.name, name, 12))) { - if (out_size) - *out_size = (p->header.length - 1) * - NVRAM_BLOCK_LEN; - return p->index + NVRAM_HEADER_LEN; + struct list_head * p; + struct nvram_partition * part; + int rc; + + /* For now, we don't do any of this on pmac, until I + * have figured out if it's worth killing some unused stuffs + * in our nvram, as Apple defined partitions use pretty much + * all of the space + */ + if (machine_is(powermac)) + return -ENOSPC; + + /* see if we have an OS partition that meets our needs. + will try getting the max we need. If not we'll delete + partitions and try again. */ + list_for_each(p, &nvram_part->partition) { + part = list_entry(p, struct nvram_partition, partition); + if (part->header.signature != NVRAM_SIG_OS) + continue; + + if (strcmp(part->header.name, "ppc64,linux")) + continue; + + if (part->header.length >= NVRAM_MIN_REQ) { + /* found our partition */ + nvram_error_log_index = part->index + NVRAM_HEADER_LEN; + nvram_error_log_size = ((part->header.length - 1) * + NVRAM_BLOCK_LEN) - sizeof(struct err_log_info); + return 0; } } + + /* try creating a partition with the free space we have */ + rc = nvram_create_os_partition(); + if (!rc) { + return 0; + } + + /* need to free up some space */ + rc = nvram_remove_os_partition(); + if (rc) { + return rc; + } + + /* create a partition in this new space */ + rc = nvram_create_os_partition(); + if (rc) { + printk(KERN_ERR "nvram_create_os_partition: Could not find a " + "NVRAM partition large enough\n"); + return rc; + } + return 0; } -int __init nvram_scan_partitions(void) + +static int __init nvram_scan_partitions(void) { loff_t cur_index = 0; struct nvram_header phead; @@ -451,7 +465,7 @@ int __init nvram_scan_partitions(void) int total_size; int err; - if (ppc_md.nvram_size == NULL || ppc_md.nvram_size() <= 0) + if (ppc_md.nvram_size == NULL) return -ENODEV; total_size = ppc_md.nvram_size(); @@ -498,16 +512,12 @@ int __init nvram_scan_partitions(void) memcpy(&tmp_part->header, &phead, NVRAM_HEADER_LEN); tmp_part->index = cur_index; - list_add_tail(&tmp_part->partition, &nvram_partitions); + list_add_tail(&tmp_part->partition, &nvram_part->partition); cur_index += phead.length * NVRAM_BLOCK_LEN; } err = 0; -#ifdef DEBUG_NVRAM - nvram_print_partitions("NVRAM Partitions"); -#endif - out: kfree(header); return err; @@ -515,10 +525,9 @@ int __init nvram_scan_partitions(void) static int __init nvram_init(void) { + int error; int rc; - BUILD_BUG_ON(NVRAM_BLOCK_LEN != 16); - if (ppc_md.nvram_size == NULL || ppc_md.nvram_size() <= 0) return -ENODEV; @@ -528,6 +537,29 @@ static int __init nvram_init(void) return rc; } + /* initialize our anchor for the nvram partition list */ + nvram_part = kmalloc(sizeof(struct nvram_partition), GFP_KERNEL); + if (!nvram_part) { + printk(KERN_ERR "nvram_init: Failed kmalloc\n"); + return -ENOMEM; + } + INIT_LIST_HEAD(&nvram_part->partition); + + /* Get all the NVRAM partitions */ + error = nvram_scan_partitions(); + if (error) { + printk(KERN_ERR "nvram_init: Failed nvram_scan_partitions\n"); + return error; + } + + if(nvram_setup_partition()) + printk(KERN_WARNING "nvram_init: Could not find nvram partition" + " for nvram buffered error logging.\n"); + +#ifdef DEBUG_NVRAM + nvram_print_partitions("NVRAM Partitions"); +#endif + return rc; } @@ -536,6 +568,135 @@ void __exit nvram_cleanup(void) misc_deregister( &nvram_dev ); } + +#ifdef CONFIG_PPC_PSERIES + +/* nvram_write_error_log + * + * We need to buffer the error logs into nvram to ensure that we have + * the failure information to decode. If we have a severe error there + * is no way to guarantee that the OS or the machine is in a state to + * get back to user land and write the error to disk. For example if + * the SCSI device driver causes a Machine Check by writing to a bad + * IO address, there is no way of guaranteeing that the device driver + * is in any state that is would also be able to write the error data + * captured to disk, thus we buffer it in NVRAM for analysis on the + * next boot. + * + * In NVRAM the partition containing the error log buffer will looks like: + * Header (in bytes): + * +-----------+----------+--------+------------+------------------+ + * | signature | checksum | length | name | data | + * |0 |1 |2 3|4 15|16 length-1| + * +-----------+----------+--------+------------+------------------+ + * + * The 'data' section would look like (in bytes): + * +--------------+------------+-----------------------------------+ + * | event_logged | sequence # | error log | + * |0 3|4 7|8 nvram_error_log_size-1| + * +--------------+------------+-----------------------------------+ + * + * event_logged: 0 if event has not been logged to syslog, 1 if it has + * sequence #: The unique sequence # for each event. (until it wraps) + * error log: The error log from event_scan + */ +int nvram_write_error_log(char * buff, int length, + unsigned int err_type, unsigned int error_log_cnt) +{ + int rc; + loff_t tmp_index; + struct err_log_info info; + + if (nvram_error_log_index == -1) { + return -ESPIPE; + } + + if (length > nvram_error_log_size) { + length = nvram_error_log_size; + } + + info.error_type = err_type; + info.seq_num = error_log_cnt; + + tmp_index = nvram_error_log_index; + + rc = ppc_md.nvram_write((char *)&info, sizeof(struct err_log_info), &tmp_index); + if (rc <= 0) { + printk(KERN_ERR "nvram_write_error_log: Failed nvram_write (%d)\n", rc); + return rc; + } + + rc = ppc_md.nvram_write(buff, length, &tmp_index); + if (rc <= 0) { + printk(KERN_ERR "nvram_write_error_log: Failed nvram_write (%d)\n", rc); + return rc; + } + + return 0; +} + +/* nvram_read_error_log + * + * Reads nvram for error log for at most 'length' + */ +int nvram_read_error_log(char * buff, int length, + unsigned int * err_type, unsigned int * error_log_cnt) +{ + int rc; + loff_t tmp_index; + struct err_log_info info; + + if (nvram_error_log_index == -1) + return -1; + + if (length > nvram_error_log_size) + length = nvram_error_log_size; + + tmp_index = nvram_error_log_index; + + rc = ppc_md.nvram_read((char *)&info, sizeof(struct err_log_info), &tmp_index); + if (rc <= 0) { + printk(KERN_ERR "nvram_read_error_log: Failed nvram_read (%d)\n", rc); + return rc; + } + + rc = ppc_md.nvram_read(buff, length, &tmp_index); + if (rc <= 0) { + printk(KERN_ERR "nvram_read_error_log: Failed nvram_read (%d)\n", rc); + return rc; + } + + *error_log_cnt = info.seq_num; + *err_type = info.error_type; + + return 0; +} + +/* This doesn't actually zero anything, but it sets the event_logged + * word to tell that this event is safely in syslog. + */ +int nvram_clear_error_log(void) +{ + loff_t tmp_index; + int clear_word = ERR_FLAG_ALREADY_LOGGED; + int rc; + + if (nvram_error_log_index == -1) + return -1; + + tmp_index = nvram_error_log_index; + + rc = ppc_md.nvram_write((char *)&clear_word, sizeof(int), &tmp_index); + if (rc <= 0) { + printk(KERN_ERR "nvram_clear_error_log: Failed nvram_write (%d)\n", rc); + return rc; + } + + return 0; +} + +#endif /* CONFIG_PPC_PSERIES */ + module_init(nvram_init); module_exit(nvram_cleanup); MODULE_LICENSE("GPL"); diff --git a/trunk/arch/powerpc/mm/pgtable.c b/trunk/arch/powerpc/mm/pgtable.c index 6a3997f98dfb..2c7e801ab20b 100644 --- a/trunk/arch/powerpc/mm/pgtable.c +++ b/trunk/arch/powerpc/mm/pgtable.c @@ -92,7 +92,7 @@ static void pte_free_rcu_callback(struct rcu_head *head) static void pte_free_submit(struct pte_freelist_batch *batch) { - call_rcu_sched(&batch->rcu, pte_free_rcu_callback); + call_rcu(&batch->rcu, pte_free_rcu_callback); } void pgtable_free_tlb(struct mmu_gather *tlb, void *table, unsigned shift) diff --git a/trunk/arch/powerpc/platforms/44x/Makefile b/trunk/arch/powerpc/platforms/44x/Makefile index 82ff326e0795..c04d16df8488 100644 --- a/trunk/arch/powerpc/platforms/44x/Makefile +++ b/trunk/arch/powerpc/platforms/44x/Makefile @@ -1,4 +1,7 @@ -obj-$(CONFIG_44x) := misc_44x.o idle.o +obj-$(CONFIG_44x) += misc_44x.o +ifneq ($(CONFIG_PPC4xx_CPM),y) +obj-$(CONFIG_44x) += idle.o +endif obj-$(CONFIG_PPC44x_SIMPLE) += ppc44x_simple.o obj-$(CONFIG_EBONY) += ebony.o obj-$(CONFIG_SAM440EP) += sam440ep.o diff --git a/trunk/arch/powerpc/platforms/chrp/time.c b/trunk/arch/powerpc/platforms/chrp/time.c index f803f4b8ab6f..054dfe5b8e77 100644 --- a/trunk/arch/powerpc/platforms/chrp/time.c +++ b/trunk/arch/powerpc/platforms/chrp/time.c @@ -29,10 +29,6 @@ extern spinlock_t rtc_lock; -#define NVRAM_AS0 0x74 -#define NVRAM_AS1 0x75 -#define NVRAM_DATA 0x77 - static int nvram_as1 = NVRAM_AS1; static int nvram_as0 = NVRAM_AS0; static int nvram_data = NVRAM_DATA; diff --git a/trunk/arch/powerpc/platforms/pseries/nvram.c b/trunk/arch/powerpc/platforms/pseries/nvram.c index 7e828ba29bc3..bc3c7f2abd79 100644 --- a/trunk/arch/powerpc/platforms/pseries/nvram.c +++ b/trunk/arch/powerpc/platforms/pseries/nvram.c @@ -22,25 +22,11 @@ #include #include -/* Max bytes to read/write in one go */ -#define NVRW_CNT 0x20 - static unsigned int nvram_size; static int nvram_fetch, nvram_store; static char nvram_buf[NVRW_CNT]; /* assume this is in the first 4GB */ static DEFINE_SPINLOCK(nvram_lock); -static long nvram_error_log_index = -1; -static long nvram_error_log_size = 0; - -struct err_log_info { - int error_type; - unsigned int seq_num; -}; -#define NVRAM_MAX_REQ 2079 -#define NVRAM_MIN_REQ 1055 - -#define NVRAM_LOG_PART_NAME "ibm,rtas-log" static ssize_t pSeries_nvram_read(char *buf, size_t count, loff_t *index) { @@ -133,197 +119,6 @@ static ssize_t pSeries_nvram_get_size(void) return nvram_size ? nvram_size : -ENODEV; } - -/* nvram_write_error_log - * - * We need to buffer the error logs into nvram to ensure that we have - * the failure information to decode. If we have a severe error there - * is no way to guarantee that the OS or the machine is in a state to - * get back to user land and write the error to disk. For example if - * the SCSI device driver causes a Machine Check by writing to a bad - * IO address, there is no way of guaranteeing that the device driver - * is in any state that is would also be able to write the error data - * captured to disk, thus we buffer it in NVRAM for analysis on the - * next boot. - * - * In NVRAM the partition containing the error log buffer will looks like: - * Header (in bytes): - * +-----------+----------+--------+------------+------------------+ - * | signature | checksum | length | name | data | - * |0 |1 |2 3|4 15|16 length-1| - * +-----------+----------+--------+------------+------------------+ - * - * The 'data' section would look like (in bytes): - * +--------------+------------+-----------------------------------+ - * | event_logged | sequence # | error log | - * |0 3|4 7|8 nvram_error_log_size-1| - * +--------------+------------+-----------------------------------+ - * - * event_logged: 0 if event has not been logged to syslog, 1 if it has - * sequence #: The unique sequence # for each event. (until it wraps) - * error log: The error log from event_scan - */ -int nvram_write_error_log(char * buff, int length, - unsigned int err_type, unsigned int error_log_cnt) -{ - int rc; - loff_t tmp_index; - struct err_log_info info; - - if (nvram_error_log_index == -1) { - return -ESPIPE; - } - - if (length > nvram_error_log_size) { - length = nvram_error_log_size; - } - - info.error_type = err_type; - info.seq_num = error_log_cnt; - - tmp_index = nvram_error_log_index; - - rc = ppc_md.nvram_write((char *)&info, sizeof(struct err_log_info), &tmp_index); - if (rc <= 0) { - printk(KERN_ERR "nvram_write_error_log: Failed nvram_write (%d)\n", rc); - return rc; - } - - rc = ppc_md.nvram_write(buff, length, &tmp_index); - if (rc <= 0) { - printk(KERN_ERR "nvram_write_error_log: Failed nvram_write (%d)\n", rc); - return rc; - } - - return 0; -} - -/* nvram_read_error_log - * - * Reads nvram for error log for at most 'length' - */ -int nvram_read_error_log(char * buff, int length, - unsigned int * err_type, unsigned int * error_log_cnt) -{ - int rc; - loff_t tmp_index; - struct err_log_info info; - - if (nvram_error_log_index == -1) - return -1; - - if (length > nvram_error_log_size) - length = nvram_error_log_size; - - tmp_index = nvram_error_log_index; - - rc = ppc_md.nvram_read((char *)&info, sizeof(struct err_log_info), &tmp_index); - if (rc <= 0) { - printk(KERN_ERR "nvram_read_error_log: Failed nvram_read (%d)\n", rc); - return rc; - } - - rc = ppc_md.nvram_read(buff, length, &tmp_index); - if (rc <= 0) { - printk(KERN_ERR "nvram_read_error_log: Failed nvram_read (%d)\n", rc); - return rc; - } - - *error_log_cnt = info.seq_num; - *err_type = info.error_type; - - return 0; -} - -/* This doesn't actually zero anything, but it sets the event_logged - * word to tell that this event is safely in syslog. - */ -int nvram_clear_error_log(void) -{ - loff_t tmp_index; - int clear_word = ERR_FLAG_ALREADY_LOGGED; - int rc; - - if (nvram_error_log_index == -1) - return -1; - - tmp_index = nvram_error_log_index; - - rc = ppc_md.nvram_write((char *)&clear_word, sizeof(int), &tmp_index); - if (rc <= 0) { - printk(KERN_ERR "nvram_clear_error_log: Failed nvram_write (%d)\n", rc); - return rc; - } - - return 0; -} - -/* pseries_nvram_init_log_partition - * - * This will setup the partition we need for buffering the - * error logs and cleanup partitions if needed. - * - * The general strategy is the following: - * 1.) If there is log partition large enough then use it. - * 2.) If there is none large enough, search - * for a free partition that is large enough. - * 3.) If there is not a free partition large enough remove - * _all_ OS partitions and consolidate the space. - * 4.) Will first try getting a chunk that will satisfy the maximum - * error log size (NVRAM_MAX_REQ). - * 5.) If the max chunk cannot be allocated then try finding a chunk - * that will satisfy the minum needed (NVRAM_MIN_REQ). - */ -static int __init pseries_nvram_init_log_partition(void) -{ - loff_t p; - int size; - - /* Scan nvram for partitions */ - nvram_scan_partitions(); - - /* Lookg for ours */ - p = nvram_find_partition(NVRAM_LOG_PART_NAME, NVRAM_SIG_OS, &size); - - /* Found one but too small, remove it */ - if (p && size < NVRAM_MIN_REQ) { - pr_info("nvram: Found too small "NVRAM_LOG_PART_NAME" partition" - ",removing it..."); - nvram_remove_partition(NVRAM_LOG_PART_NAME, NVRAM_SIG_OS); - p = 0; - } - - /* Create one if we didn't find */ - if (!p) { - p = nvram_create_partition(NVRAM_LOG_PART_NAME, NVRAM_SIG_OS, - NVRAM_MAX_REQ, NVRAM_MIN_REQ); - /* No room for it, try to get rid of any OS partition - * and try again - */ - if (p == -ENOSPC) { - pr_info("nvram: No room to create "NVRAM_LOG_PART_NAME - " partition, deleting all OS partitions..."); - nvram_remove_partition(NULL, NVRAM_SIG_OS); - p = nvram_create_partition(NVRAM_LOG_PART_NAME, - NVRAM_SIG_OS, NVRAM_MAX_REQ, - NVRAM_MIN_REQ); - } - } - - if (p <= 0) { - pr_err("nvram: Failed to find or create "NVRAM_LOG_PART_NAME - " partition, err %d\n", (int)p); - return 0; - } - - nvram_error_log_index = p; - nvram_error_log_size = nvram_get_partition_size(p) - - sizeof(struct err_log_info); - - return 0; -} -machine_arch_initcall(pseries, pseries_nvram_init_log_partition); - int __init pSeries_nvram_init(void) { struct device_node *nvram; diff --git a/trunk/arch/powerpc/sysdev/Makefile b/trunk/arch/powerpc/sysdev/Makefile index 0bef9dacb64e..9c2973479142 100644 --- a/trunk/arch/powerpc/sysdev/Makefile +++ b/trunk/arch/powerpc/sysdev/Makefile @@ -41,6 +41,7 @@ obj-$(CONFIG_OF_RTC) += of_rtc.o ifeq ($(CONFIG_PCI),y) obj-$(CONFIG_4xx) += ppc4xx_pci.o endif +obj-$(CONFIG_PPC4xx_CPM) += ppc4xx_cpm.o obj-$(CONFIG_PPC4xx_GPIO) += ppc4xx_gpio.o obj-$(CONFIG_CPM) += cpm_common.o diff --git a/trunk/arch/powerpc/sysdev/ppc4xx_cpm.c b/trunk/arch/powerpc/sysdev/ppc4xx_cpm.c new file mode 100644 index 000000000000..73b86cc5ea74 --- /dev/null +++ b/trunk/arch/powerpc/sysdev/ppc4xx_cpm.c @@ -0,0 +1,346 @@ +/* + * PowerPC 4xx Clock and Power Management + * + * Copyright (C) 2010, Applied Micro Circuits Corporation + * Victor Gallardo (vgallardo@apm.com) + * + * Based on arch/powerpc/platforms/44x/idle.c: + * Jerone Young + * Copyright 2008 IBM Corp. + * + * Based on arch/powerpc/sysdev/fsl_pmc.c: + * Anton Vorontsov + * Copyright 2009 MontaVista Software, Inc. + * + * See file CREDITS for list of people who contributed to this + * project. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define CPM_ER 0 +#define CPM_FR 1 +#define CPM_SR 2 + +#define CPM_IDLE_WAIT 0 +#define CPM_IDLE_DOZE 1 + +struct cpm { + dcr_host_t dcr_host; + unsigned int dcr_offset[3]; + unsigned int powersave_off; + unsigned int unused; + unsigned int idle_doze; + unsigned int standby; + unsigned int suspend; +}; + +static struct cpm cpm; + +struct cpm_idle_mode { + unsigned int enabled; + const char *name; +}; + +static struct cpm_idle_mode idle_mode[] = { + [CPM_IDLE_WAIT] = { 1, "wait" }, /* default */ + [CPM_IDLE_DOZE] = { 0, "doze" }, +}; + +static unsigned int cpm_set(unsigned int cpm_reg, unsigned int mask) +{ + unsigned int value; + + /* CPM controller supports 3 different types of sleep interface + * known as class 1, 2 and 3. For class 1 units, they are + * unconditionally put to sleep when the corresponding CPM bit is + * set. For class 2 and 3 units this is not case; if they can be + * put to to sleep, they will. Here we do not verify, we just + * set them and expect them to eventually go off when they can. + */ + value = dcr_read(cpm.dcr_host, cpm.dcr_offset[cpm_reg]); + dcr_write(cpm.dcr_host, cpm.dcr_offset[cpm_reg], value | mask); + + /* return old state, to restore later if needed */ + return value; +} + +static void cpm_idle_wait(void) +{ + unsigned long msr_save; + + /* save off initial state */ + msr_save = mfmsr(); + /* sync required when CPM0_ER[CPU] is set */ + mb(); + /* set wait state MSR */ + mtmsr(msr_save|MSR_WE|MSR_EE|MSR_CE|MSR_DE); + isync(); + /* return to initial state */ + mtmsr(msr_save); + isync(); +} + +static void cpm_idle_sleep(unsigned int mask) +{ + unsigned int er_save; + + /* update CPM_ER state */ + er_save = cpm_set(CPM_ER, mask); + + /* go to wait state so that CPM0_ER[CPU] can take effect */ + cpm_idle_wait(); + + /* restore CPM_ER state */ + dcr_write(cpm.dcr_host, cpm.dcr_offset[CPM_ER], er_save); +} + +static void cpm_idle_doze(void) +{ + cpm_idle_sleep(cpm.idle_doze); +} + +static void cpm_idle_config(int mode) +{ + int i; + + if (idle_mode[mode].enabled) + return; + + for (i = 0; i < ARRAY_SIZE(idle_mode); i++) + idle_mode[i].enabled = 0; + + idle_mode[mode].enabled = 1; +} + +static ssize_t cpm_idle_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + char *s = buf; + int i; + + for (i = 0; i < ARRAY_SIZE(idle_mode); i++) { + if (idle_mode[i].enabled) + s += sprintf(s, "[%s] ", idle_mode[i].name); + else + s += sprintf(s, "%s ", idle_mode[i].name); + } + + *(s-1) = '\n'; /* convert the last space to a newline */ + + return s - buf; +} + +static ssize_t cpm_idle_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t n) +{ + int i; + char *p; + int len; + + p = memchr(buf, '\n', n); + len = p ? p - buf : n; + + for (i = 0; i < ARRAY_SIZE(idle_mode); i++) { + if (strncmp(buf, idle_mode[i].name, len) == 0) { + cpm_idle_config(i); + return n; + } + } + + return -EINVAL; +} + +static struct kobj_attribute cpm_idle_attr = + __ATTR(idle, 0644, cpm_idle_show, cpm_idle_store); + +static void cpm_idle_config_sysfs(void) +{ + struct sys_device *sys_dev; + unsigned long ret; + + sys_dev = get_cpu_sysdev(0); + + ret = sysfs_create_file(&sys_dev->kobj, + &cpm_idle_attr.attr); + if (ret) + printk(KERN_WARNING + "cpm: failed to create idle sysfs entry\n"); +} + +static void cpm_idle(void) +{ + if (idle_mode[CPM_IDLE_DOZE].enabled) + cpm_idle_doze(); + else + cpm_idle_wait(); +} + +static int cpm_suspend_valid(suspend_state_t state) +{ + switch (state) { + case PM_SUSPEND_STANDBY: + return !!cpm.standby; + case PM_SUSPEND_MEM: + return !!cpm.suspend; + default: + return 0; + } +} + +static void cpm_suspend_standby(unsigned int mask) +{ + unsigned long tcr_save; + + /* disable decrement interrupt */ + tcr_save = mfspr(SPRN_TCR); + mtspr(SPRN_TCR, tcr_save & ~TCR_DIE); + + /* go to sleep state */ + cpm_idle_sleep(mask); + + /* restore decrement interrupt */ + mtspr(SPRN_TCR, tcr_save); +} + +static int cpm_suspend_enter(suspend_state_t state) +{ + switch (state) { + case PM_SUSPEND_STANDBY: + cpm_suspend_standby(cpm.standby); + break; + case PM_SUSPEND_MEM: + cpm_suspend_standby(cpm.suspend); + break; + } + + return 0; +} + +static struct platform_suspend_ops cpm_suspend_ops = { + .valid = cpm_suspend_valid, + .enter = cpm_suspend_enter, +}; + +static int cpm_get_uint_property(struct device_node *np, + const char *name) +{ + int len; + const unsigned int *prop = of_get_property(np, name, &len); + + if (prop == NULL || len < sizeof(u32)) + return 0; + + return *prop; +} + +static int __init cpm_init(void) +{ + struct device_node *np; + int dcr_base, dcr_len; + int ret = 0; + + if (!cpm.powersave_off) { + cpm_idle_config(CPM_IDLE_WAIT); + ppc_md.power_save = &cpm_idle; + } + + np = of_find_compatible_node(NULL, NULL, "ibm,cpm"); + if (!np) { + ret = -EINVAL; + goto out; + } + + dcr_base = dcr_resource_start(np, 0); + dcr_len = dcr_resource_len(np, 0); + + if (dcr_base == 0 || dcr_len == 0) { + printk(KERN_ERR "cpm: could not parse dcr property for %s\n", + np->full_name); + ret = -EINVAL; + goto out; + } + + cpm.dcr_host = dcr_map(np, dcr_base, dcr_len); + + if (!DCR_MAP_OK(cpm.dcr_host)) { + printk(KERN_ERR "cpm: failed to map dcr property for %s\n", + np->full_name); + ret = -EINVAL; + goto out; + } + + /* All 4xx SoCs with a CPM controller have one of two + * different order for the CPM registers. Some have the + * CPM registers in the following order (ER,FR,SR). The + * others have them in the following order (SR,ER,FR). + */ + + if (cpm_get_uint_property(np, "er-offset") == 0) { + cpm.dcr_offset[CPM_ER] = 0; + cpm.dcr_offset[CPM_FR] = 1; + cpm.dcr_offset[CPM_SR] = 2; + } else { + cpm.dcr_offset[CPM_ER] = 1; + cpm.dcr_offset[CPM_FR] = 2; + cpm.dcr_offset[CPM_SR] = 0; + } + + /* Now let's see what IPs to turn off for the following modes */ + + cpm.unused = cpm_get_uint_property(np, "unused-units"); + cpm.idle_doze = cpm_get_uint_property(np, "idle-doze"); + cpm.standby = cpm_get_uint_property(np, "standby"); + cpm.suspend = cpm_get_uint_property(np, "suspend"); + + /* If some IPs are unused let's turn them off now */ + + if (cpm.unused) { + cpm_set(CPM_ER, cpm.unused); + cpm_set(CPM_FR, cpm.unused); + } + + /* Now let's export interfaces */ + + if (!cpm.powersave_off && cpm.idle_doze) + cpm_idle_config_sysfs(); + + if (cpm.standby || cpm.suspend) + suspend_set_ops(&cpm_suspend_ops); +out: + if (np) + of_node_put(np); + return ret; +} + +late_initcall(cpm_init); + +static int __init cpm_powersave_off(char *arg) +{ + cpm.powersave_off = 1; + return 0; +} +__setup("powersave=off", cpm_powersave_off);