forked from mirrors/linux
		
	powerpc/powernv: Add RTC and NVRAM support plus RTAS fallbacks
Implements OPAL RTC and NVRAM support and wire all that up to the powernv platform. We use RTAS for RTC as a fallback if available. Using RTAS for nvram is not supported yet, pending some rework/cleanup and generalization of the pSeries & CHRP code. We also use RTAS fallbacks for power off and reboot Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
This commit is contained in:
		
							parent
							
								
									ec27329ffb
								
							
						
					
					
						commit
						628daa8d5a
					
				
					 5 changed files with 230 additions and 22 deletions
				
			
		|  | @ -430,6 +430,12 @@ extern int opal_put_chars(uint32_t vtermno, const char *buf, int total_len); | |||
| 
 | ||||
| extern void hvc_opal_init_early(void); | ||||
| 
 | ||||
| struct rtc_time; | ||||
| extern int opal_set_rtc_time(struct rtc_time *tm); | ||||
| extern void opal_get_rtc_time(struct rtc_time *tm); | ||||
| extern unsigned long opal_get_boot_time(void); | ||||
| extern void opal_nvram_init(void); | ||||
| 
 | ||||
| #endif /* __ASSEMBLY__ */ | ||||
| 
 | ||||
| #endif /* __OPAL_H */ | ||||
|  |  | |||
|  | @ -1,2 +1,4 @@ | |||
| obj-y			+= setup.o opal-takeover.o opal-wrappers.o opal.o | ||||
| obj-y			+= opal-rtc.o opal-nvram.o | ||||
| 
 | ||||
| obj-$(CONFIG_SMP)	+= smp.o | ||||
|  |  | |||
							
								
								
									
										88
									
								
								arch/powerpc/platforms/powernv/opal-nvram.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								arch/powerpc/platforms/powernv/opal-nvram.c
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,88 @@ | |||
| /*
 | ||||
|  * PowerNV nvram code. | ||||
|  * | ||||
|  * Copyright 2011 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. | ||||
|  */ | ||||
| 
 | ||||
| #define DEBUG | ||||
| 
 | ||||
| #include <linux/kernel.h> | ||||
| #include <linux/init.h> | ||||
| #include <linux/of.h> | ||||
| 
 | ||||
| #include <asm/opal.h> | ||||
| #include <asm/machdep.h> | ||||
| 
 | ||||
| static unsigned int nvram_size; | ||||
| 
 | ||||
| static ssize_t opal_nvram_size(void) | ||||
| { | ||||
| 	return nvram_size; | ||||
| } | ||||
| 
 | ||||
| static ssize_t opal_nvram_read(char *buf, size_t count, loff_t *index) | ||||
| { | ||||
| 	s64 rc; | ||||
| 	int off; | ||||
| 
 | ||||
| 	if (*index >= nvram_size) | ||||
| 		return 0; | ||||
| 	off = *index; | ||||
| 	if ((off + count) > nvram_size) | ||||
| 		count = nvram_size - off; | ||||
| 	rc = opal_read_nvram(__pa(buf), count, off); | ||||
| 	if (rc != OPAL_SUCCESS) | ||||
| 		return -EIO; | ||||
| 	*index += count; | ||||
| 	return count; | ||||
| } | ||||
| 
 | ||||
| static ssize_t opal_nvram_write(char *buf, size_t count, loff_t *index) | ||||
| { | ||||
| 	s64 rc = OPAL_BUSY; | ||||
| 	int off; | ||||
| 
 | ||||
| 	if (*index >= nvram_size) | ||||
| 		return 0; | ||||
| 	off = *index; | ||||
| 	if ((off + count) > nvram_size) | ||||
| 		count = nvram_size - off; | ||||
| 
 | ||||
| 	while (rc == OPAL_BUSY || rc == OPAL_BUSY_EVENT) { | ||||
| 		rc = opal_write_nvram(__pa(buf), count, off); | ||||
| 		if (rc == OPAL_BUSY_EVENT) | ||||
| 			opal_poll_events(NULL); | ||||
| 	} | ||||
| 	*index += count; | ||||
| 	return count; | ||||
| } | ||||
| 
 | ||||
| void __init opal_nvram_init(void) | ||||
| { | ||||
| 	struct device_node *np; | ||||
| 	const u32 *nbytes_p; | ||||
| 
 | ||||
| 	np = of_find_compatible_node(NULL, NULL, "ibm,opal-nvram"); | ||||
| 	if (np == NULL) | ||||
| 		return; | ||||
| 
 | ||||
| 	nbytes_p = of_get_property(np, "#bytes", NULL); | ||||
| 	if (!nbytes_p) { | ||||
| 		of_node_put(np); | ||||
| 		return; | ||||
| 	} | ||||
| 	nvram_size = *nbytes_p; | ||||
| 
 | ||||
| 	printk(KERN_INFO "OPAL nvram setup, %u bytes\n", nvram_size); | ||||
| 	of_node_put(np); | ||||
| 
 | ||||
| 	ppc_md.nvram_read = opal_nvram_read; | ||||
| 	ppc_md.nvram_write = opal_nvram_write; | ||||
| 	ppc_md.nvram_size = opal_nvram_size; | ||||
| } | ||||
| 
 | ||||
							
								
								
									
										97
									
								
								arch/powerpc/platforms/powernv/opal-rtc.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								arch/powerpc/platforms/powernv/opal-rtc.c
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,97 @@ | |||
| /*
 | ||||
|  * PowerNV Real Time Clock. | ||||
|  * | ||||
|  * Copyright 2011 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/kernel.h> | ||||
| #include <linux/time.h> | ||||
| #include <linux/bcd.h> | ||||
| #include <linux/rtc.h> | ||||
| #include <linux/delay.h> | ||||
| 
 | ||||
| #include <asm/opal.h> | ||||
| #include <asm/firmware.h> | ||||
| 
 | ||||
| static void opal_to_tm(u32 y_m_d, u64 h_m_s_ms, struct rtc_time *tm) | ||||
| { | ||||
| 	tm->tm_year	= ((bcd2bin(y_m_d >> 24) * 100) + | ||||
| 			   bcd2bin((y_m_d >> 16) & 0xff)) - 1900; | ||||
| 	tm->tm_mon	= bcd2bin((y_m_d >> 8) & 0xff) - 1; | ||||
| 	tm->tm_mday	= bcd2bin(y_m_d & 0xff); | ||||
| 	tm->tm_hour	= bcd2bin((h_m_s_ms >> 56) & 0xff); | ||||
| 	tm->tm_min	= bcd2bin((h_m_s_ms >> 48) & 0xff); | ||||
| 	tm->tm_sec	= bcd2bin((h_m_s_ms >> 40) & 0xff); | ||||
| 
 | ||||
|         GregorianDay(tm); | ||||
| } | ||||
| 
 | ||||
| unsigned long __init opal_get_boot_time(void) | ||||
| { | ||||
| 	struct rtc_time tm; | ||||
| 	u32 y_m_d; | ||||
| 	u64 h_m_s_ms; | ||||
| 	long rc = OPAL_BUSY; | ||||
| 
 | ||||
| 	while (rc == OPAL_BUSY || rc == OPAL_BUSY_EVENT) { | ||||
| 		rc = opal_rtc_read(&y_m_d, &h_m_s_ms); | ||||
| 		if (rc == OPAL_BUSY_EVENT) | ||||
| 			opal_poll_events(NULL); | ||||
| 		else | ||||
| 			mdelay(10); | ||||
| 	} | ||||
| 	if (rc != OPAL_SUCCESS) | ||||
| 		return 0; | ||||
| 	opal_to_tm(y_m_d, h_m_s_ms, &tm); | ||||
| 	return mktime(tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, | ||||
| 		      tm.tm_hour, tm.tm_min, tm.tm_sec); | ||||
| } | ||||
| 
 | ||||
| void opal_get_rtc_time(struct rtc_time *tm) | ||||
| { | ||||
| 	long rc = OPAL_BUSY; | ||||
| 	u32 y_m_d; | ||||
| 	u64 h_m_s_ms; | ||||
| 
 | ||||
| 	while (rc == OPAL_BUSY || rc == OPAL_BUSY_EVENT) { | ||||
| 		rc = opal_rtc_read(&y_m_d, &h_m_s_ms); | ||||
| 		if (rc == OPAL_BUSY_EVENT) | ||||
| 			opal_poll_events(NULL); | ||||
| 		else | ||||
| 			mdelay(10); | ||||
| 	} | ||||
| 	if (rc != OPAL_SUCCESS) | ||||
| 		return; | ||||
| 	opal_to_tm(y_m_d, h_m_s_ms, tm); | ||||
| } | ||||
| 
 | ||||
| int opal_set_rtc_time(struct rtc_time *tm) | ||||
| { | ||||
| 	long rc = OPAL_BUSY; | ||||
| 	u32 y_m_d = 0; | ||||
| 	u64 h_m_s_ms = 0; | ||||
| 
 | ||||
| 	y_m_d |= ((u32)bin2bcd((tm->tm_year + 1900) / 100)) << 24; | ||||
| 	y_m_d |= ((u32)bin2bcd((tm->tm_year + 1900) % 100)) << 16; | ||||
| 	y_m_d |= ((u32)bin2bcd((tm->tm_mon + 1))) << 8; | ||||
| 	y_m_d |= ((u32)bin2bcd(tm->tm_mday)); | ||||
| 
 | ||||
| 	h_m_s_ms |= ((u64)bin2bcd(tm->tm_hour)) << 56; | ||||
| 	h_m_s_ms |= ((u64)bin2bcd(tm->tm_min)) << 48; | ||||
| 	h_m_s_ms |= ((u64)bin2bcd(tm->tm_sec)) << 40; | ||||
| 
 | ||||
| 	while (rc == OPAL_BUSY || rc == OPAL_BUSY_EVENT) { | ||||
| 		rc = opal_rtc_write(y_m_d, h_m_s_ms); | ||||
| 		if (rc == OPAL_BUSY_EVENT) | ||||
| 			opal_poll_events(NULL); | ||||
| 		else | ||||
| 			mdelay(10); | ||||
| 	} | ||||
| 	return rc == OPAL_SUCCESS ? 0 : -EIO; | ||||
| } | ||||
|  | @ -29,7 +29,9 @@ | |||
| #include <asm/machdep.h> | ||||
| #include <asm/firmware.h> | ||||
| #include <asm/xics.h> | ||||
| #include <asm/rtas.h> | ||||
| #include <asm/opal.h> | ||||
| #include <asm/xics.h> | ||||
| 
 | ||||
| #include "powernv.h" | ||||
| 
 | ||||
|  | @ -40,7 +42,9 @@ static void __init pnv_setup_arch(void) | |||
| 
 | ||||
| 	/* XXX PCI */ | ||||
| 
 | ||||
| 	/* XXX NVRAM */ | ||||
| 	/* Setup RTC and NVRAM callbacks */ | ||||
| 	if (firmware_has_feature(FW_FEATURE_OPAL)) | ||||
| 		opal_nvram_init(); | ||||
| 
 | ||||
| 	/* Enable NAP mode */ | ||||
| 	powersave_nap = 1; | ||||
|  | @ -118,20 +122,6 @@ static void __noreturn pnv_halt(void) | |||
| 	pnv_power_off(); | ||||
| } | ||||
| 
 | ||||
| static unsigned long __init pnv_get_boot_time(void) | ||||
| { | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void pnv_get_rtc_time(struct rtc_time *rtc_tm) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| static int pnv_set_rtc_time(struct rtc_time *tm) | ||||
| { | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void pnv_progress(char *s, unsigned short hex) | ||||
| { | ||||
| } | ||||
|  | @ -143,6 +133,30 @@ static void pnv_kexec_cpu_down(int crash_shutdown, int secondary) | |||
| } | ||||
| #endif /* CONFIG_KEXEC */ | ||||
| 
 | ||||
| static void __init pnv_setup_machdep_opal(void) | ||||
| { | ||||
| 	ppc_md.get_boot_time = opal_get_boot_time; | ||||
| 	ppc_md.get_rtc_time = opal_get_rtc_time; | ||||
| 	ppc_md.set_rtc_time = opal_set_rtc_time; | ||||
| 	ppc_md.restart = pnv_restart; | ||||
| 	ppc_md.power_off = pnv_power_off; | ||||
| 	ppc_md.halt = pnv_halt; | ||||
| } | ||||
| 
 | ||||
| #ifdef CONFIG_PPC_POWERNV_RTAS | ||||
| static void __init pnv_setup_machdep_rtas(void) | ||||
| { | ||||
| 	if (rtas_token("get-time-of-day") != RTAS_UNKNOWN_SERVICE) { | ||||
| 		ppc_md.get_boot_time = rtas_get_boot_time; | ||||
| 		ppc_md.get_rtc_time = rtas_get_rtc_time; | ||||
| 		ppc_md.set_rtc_time = rtas_set_rtc_time; | ||||
| 	} | ||||
| 	ppc_md.restart = rtas_restart; | ||||
| 	ppc_md.power_off = rtas_power_off; | ||||
| 	ppc_md.halt = rtas_halt; | ||||
| } | ||||
| #endif /* CONFIG_PPC_POWERNV_RTAS */ | ||||
| 
 | ||||
| static int __init pnv_probe(void) | ||||
| { | ||||
| 	unsigned long root = of_get_flat_dt_root(); | ||||
|  | @ -152,6 +166,13 @@ static int __init pnv_probe(void) | |||
| 
 | ||||
| 	hpte_init_native(); | ||||
| 
 | ||||
| 	if (firmware_has_feature(FW_FEATURE_OPAL)) | ||||
| 		pnv_setup_machdep_opal(); | ||||
| #ifdef CONFIG_PPC_POWERNV_RTAS | ||||
| 	else if (rtas.base) | ||||
| 		pnv_setup_machdep_rtas(); | ||||
| #endif /* CONFIG_PPC_POWERNV_RTAS */ | ||||
| 
 | ||||
| 	pr_debug("PowerNV detected !\n"); | ||||
| 
 | ||||
| 	return 1; | ||||
|  | @ -160,16 +181,10 @@ static int __init pnv_probe(void) | |||
| define_machine(powernv) { | ||||
| 	.name			= "PowerNV", | ||||
| 	.probe			= pnv_probe, | ||||
| 	.setup_arch		= pnv_setup_arch, | ||||
| 	.init_early		= pnv_init_early, | ||||
| 	.setup_arch		= pnv_setup_arch, | ||||
| 	.init_IRQ		= pnv_init_IRQ, | ||||
| 	.show_cpuinfo		= pnv_show_cpuinfo, | ||||
| 	.restart		= pnv_restart, | ||||
| 	.power_off		= pnv_power_off, | ||||
| 	.halt			= pnv_halt, | ||||
| 	.get_boot_time		= pnv_get_boot_time, | ||||
| 	.get_rtc_time		= pnv_get_rtc_time, | ||||
| 	.set_rtc_time		= pnv_set_rtc_time, | ||||
| 	.progress		= pnv_progress, | ||||
| 	.power_save             = power7_idle, | ||||
| 	.calibrate_decr		= generic_calibrate_decr, | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Benjamin Herrenschmidt
						Benjamin Herrenschmidt