diff options
Diffstat (limited to 'drivers/rtc/rtc-mc146818-lib.c')
| -rw-r--r-- | drivers/rtc/rtc-mc146818-lib.c | 182 | 
1 files changed, 119 insertions, 63 deletions
diff --git a/drivers/rtc/rtc-mc146818-lib.c b/drivers/rtc/rtc-mc146818-lib.c index dcfaf09946ee..ae9f131b43c0 100644 --- a/drivers/rtc/rtc-mc146818-lib.c +++ b/drivers/rtc/rtc-mc146818-lib.c @@ -8,48 +8,100 @@  #include <linux/acpi.h>  #endif -unsigned int mc146818_get_time(struct rtc_time *time) +/* + * Execute a function while the UIP (Update-in-progress) bit of the RTC is + * unset. + * + * Warning: callback may be executed more then once. + */ +bool mc146818_avoid_UIP(void (*callback)(unsigned char seconds, void *param), +			void *param)  { -	unsigned char ctrl; +	int i;  	unsigned long flags; -	unsigned char century = 0; -	bool retry; +	unsigned char seconds; -#ifdef CONFIG_MACH_DECSTATION -	unsigned int real_year; -#endif +	for (i = 0; i < 10; i++) { +		spin_lock_irqsave(&rtc_lock, flags); -again: -	spin_lock_irqsave(&rtc_lock, flags); -	/* Ensure that the RTC is accessible. Bit 6 must be 0! */ -	if (WARN_ON_ONCE((CMOS_READ(RTC_VALID) & 0x40) != 0)) { -		spin_unlock_irqrestore(&rtc_lock, flags); -		memset(time, 0xff, sizeof(*time)); -		return 0; -	} +		/* +		 * Check whether there is an update in progress during which the +		 * readout is unspecified. The maximum update time is ~2ms. Poll +		 * every msec for completion. +		 * +		 * Store the second value before checking UIP so a long lasting +		 * NMI which happens to hit after the UIP check cannot make +		 * an update cycle invisible. +		 */ +		seconds = CMOS_READ(RTC_SECONDS); -	/* -	 * Check whether there is an update in progress during which the -	 * readout is unspecified. The maximum update time is ~2ms. Poll -	 * every msec for completion. -	 * -	 * Store the second value before checking UIP so a long lasting NMI -	 * which happens to hit after the UIP check cannot make an update -	 * cycle invisible. -	 */ -	time->tm_sec = CMOS_READ(RTC_SECONDS); +		if (CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP) { +			spin_unlock_irqrestore(&rtc_lock, flags); +			mdelay(1); +			continue; +		} -	if (CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP) { -		spin_unlock_irqrestore(&rtc_lock, flags); -		mdelay(1); -		goto again; -	} +		/* Revalidate the above readout */ +		if (seconds != CMOS_READ(RTC_SECONDS)) { +			spin_unlock_irqrestore(&rtc_lock, flags); +			continue; +		} -	/* Revalidate the above readout */ -	if (time->tm_sec != CMOS_READ(RTC_SECONDS)) { +		if (callback) +			callback(seconds, param); + +		/* +		 * Check for the UIP bit again. If it is set now then +		 * the above values may contain garbage. +		 */ +		if (CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP) { +			spin_unlock_irqrestore(&rtc_lock, flags); +			mdelay(1); +			continue; +		} + +		/* +		 * A NMI might have interrupted the above sequence so check +		 * whether the seconds value has changed which indicates that +		 * the NMI took longer than the UIP bit was set. Unlikely, but +		 * possible and there is also virt... +		 */ +		if (seconds != CMOS_READ(RTC_SECONDS)) { +			spin_unlock_irqrestore(&rtc_lock, flags); +			continue; +		}  		spin_unlock_irqrestore(&rtc_lock, flags); -		goto again; + +		return true;  	} +	return false; +} +EXPORT_SYMBOL_GPL(mc146818_avoid_UIP); + +/* + * If the UIP (Update-in-progress) bit of the RTC is set for more then + * 10ms, the RTC is apparently broken or not present. + */ +bool mc146818_does_rtc_work(void) +{ +	return mc146818_avoid_UIP(NULL, NULL); +} +EXPORT_SYMBOL_GPL(mc146818_does_rtc_work); + +struct mc146818_get_time_callback_param { +	struct rtc_time *time; +	unsigned char ctrl; +#ifdef CONFIG_ACPI +	unsigned char century; +#endif +#ifdef CONFIG_MACH_DECSTATION +	unsigned int real_year; +#endif +}; + +static void mc146818_get_time_callback(unsigned char seconds, void *param_in) +{ +	struct mc146818_get_time_callback_param *p = param_in;  	/*  	 * Only the values that we read from the RTC are set. We leave @@ -57,39 +109,39 @@ again:  	 * RTC has RTC_DAY_OF_WEEK, we ignore it, as it is only updated  	 * by the RTC when initially set to a non-zero value.  	 */ -	time->tm_min = CMOS_READ(RTC_MINUTES); -	time->tm_hour = CMOS_READ(RTC_HOURS); -	time->tm_mday = CMOS_READ(RTC_DAY_OF_MONTH); -	time->tm_mon = CMOS_READ(RTC_MONTH); -	time->tm_year = CMOS_READ(RTC_YEAR); +	p->time->tm_sec = seconds; +	p->time->tm_min = CMOS_READ(RTC_MINUTES); +	p->time->tm_hour = CMOS_READ(RTC_HOURS); +	p->time->tm_mday = CMOS_READ(RTC_DAY_OF_MONTH); +	p->time->tm_mon = CMOS_READ(RTC_MONTH); +	p->time->tm_year = CMOS_READ(RTC_YEAR);  #ifdef CONFIG_MACH_DECSTATION -	real_year = CMOS_READ(RTC_DEC_YEAR); +	p->real_year = CMOS_READ(RTC_DEC_YEAR);  #endif  #ifdef CONFIG_ACPI  	if (acpi_gbl_FADT.header.revision >= FADT2_REVISION_ID && -	    acpi_gbl_FADT.century) -		century = CMOS_READ(acpi_gbl_FADT.century); +	    acpi_gbl_FADT.century) { +		p->century = CMOS_READ(acpi_gbl_FADT.century); +	} else { +		p->century = 0; +	}  #endif -	ctrl = CMOS_READ(RTC_CONTROL); -	/* -	 * Check for the UIP bit again. If it is set now then -	 * the above values may contain garbage. -	 */ -	retry = CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP; -	/* -	 * A NMI might have interrupted the above sequence so check whether -	 * the seconds value has changed which indicates that the NMI took -	 * longer than the UIP bit was set. Unlikely, but possible and -	 * there is also virt... -	 */ -	retry |= time->tm_sec != CMOS_READ(RTC_SECONDS); -	spin_unlock_irqrestore(&rtc_lock, flags); +	p->ctrl = CMOS_READ(RTC_CONTROL); +} -	if (retry) -		goto again; +int mc146818_get_time(struct rtc_time *time) +{ +	struct mc146818_get_time_callback_param p = { +		.time = time +	}; + +	if (!mc146818_avoid_UIP(mc146818_get_time_callback, &p)) { +		memset(time, 0, sizeof(*time)); +		return -EIO; +	} -	if (!(ctrl & RTC_DM_BINARY) || RTC_ALWAYS_BCD) +	if (!(p.ctrl & RTC_DM_BINARY) || RTC_ALWAYS_BCD)  	{  		time->tm_sec = bcd2bin(time->tm_sec);  		time->tm_min = bcd2bin(time->tm_min); @@ -97,15 +149,19 @@ again:  		time->tm_mday = bcd2bin(time->tm_mday);  		time->tm_mon = bcd2bin(time->tm_mon);  		time->tm_year = bcd2bin(time->tm_year); -		century = bcd2bin(century); +#ifdef CONFIG_ACPI +		p.century = bcd2bin(p.century); +#endif  	}  #ifdef CONFIG_MACH_DECSTATION -	time->tm_year += real_year - 72; +	time->tm_year += p.real_year - 72;  #endif -	if (century > 20) -		time->tm_year += (century - 19) * 100; +#ifdef CONFIG_ACPI +	if (p.century > 19) +		time->tm_year += (p.century - 19) * 100; +#endif  	/*  	 * Account for differences between how the RTC uses the values @@ -116,7 +172,7 @@ again:  	time->tm_mon--; -	return RTC_24H; +	return 0;  }  EXPORT_SYMBOL_GPL(mc146818_get_time);  |