diff options
Diffstat (limited to 'drivers/rtc/rtc-cmos.c')
| -rw-r--r-- | drivers/rtc/rtc-cmos.c | 201 | 
1 files changed, 130 insertions, 71 deletions
diff --git a/drivers/rtc/rtc-cmos.c b/drivers/rtc/rtc-cmos.c index 4eb53412b808..7c006c2b125f 100644 --- a/drivers/rtc/rtc-cmos.c +++ b/drivers/rtc/rtc-cmos.c @@ -222,6 +222,8 @@ static inline void cmos_write_bank2(unsigned char val, unsigned char addr)  static int cmos_read_time(struct device *dev, struct rtc_time *t)  { +	int ret; +  	/*  	 * If pm_trace abused the RTC for storage, set the timespec to 0,  	 * which tells the caller that this RTC value is unusable. @@ -229,7 +231,12 @@ static int cmos_read_time(struct device *dev, struct rtc_time *t)  	if (!pm_trace_rtc_valid())  		return -EIO; -	mc146818_get_time(t); +	ret = mc146818_get_time(t); +	if (ret < 0) { +		dev_err_ratelimited(dev, "unable to read current time\n"); +		return ret; +	} +  	return 0;  } @@ -242,10 +249,46 @@ static int cmos_set_time(struct device *dev, struct rtc_time *t)  	return mc146818_set_time(t);  } +struct cmos_read_alarm_callback_param { +	struct cmos_rtc *cmos; +	struct rtc_time *time; +	unsigned char	rtc_control; +}; + +static void cmos_read_alarm_callback(unsigned char __always_unused seconds, +				     void *param_in) +{ +	struct cmos_read_alarm_callback_param *p = +		(struct cmos_read_alarm_callback_param *)param_in; +	struct rtc_time *time = p->time; + +	time->tm_sec = CMOS_READ(RTC_SECONDS_ALARM); +	time->tm_min = CMOS_READ(RTC_MINUTES_ALARM); +	time->tm_hour = CMOS_READ(RTC_HOURS_ALARM); + +	if (p->cmos->day_alrm) { +		/* ignore upper bits on readback per ACPI spec */ +		time->tm_mday = CMOS_READ(p->cmos->day_alrm) & 0x3f; +		if (!time->tm_mday) +			time->tm_mday = -1; + +		if (p->cmos->mon_alrm) { +			time->tm_mon = CMOS_READ(p->cmos->mon_alrm); +			if (!time->tm_mon) +				time->tm_mon = -1; +		} +	} + +	p->rtc_control = CMOS_READ(RTC_CONTROL); +} +  static int cmos_read_alarm(struct device *dev, struct rtc_wkalrm *t)  {  	struct cmos_rtc	*cmos = dev_get_drvdata(dev); -	unsigned char	rtc_control; +	struct cmos_read_alarm_callback_param p = { +		.cmos = cmos, +		.time = &t->time, +	};  	/* This not only a rtc_op, but also called directly */  	if (!is_valid_irq(cmos->irq)) @@ -256,28 +299,18 @@ static int cmos_read_alarm(struct device *dev, struct rtc_wkalrm *t)  	 * the future.  	 */ -	spin_lock_irq(&rtc_lock); -	t->time.tm_sec = CMOS_READ(RTC_SECONDS_ALARM); -	t->time.tm_min = CMOS_READ(RTC_MINUTES_ALARM); -	t->time.tm_hour = CMOS_READ(RTC_HOURS_ALARM); - -	if (cmos->day_alrm) { -		/* ignore upper bits on readback per ACPI spec */ -		t->time.tm_mday = CMOS_READ(cmos->day_alrm) & 0x3f; -		if (!t->time.tm_mday) -			t->time.tm_mday = -1; - -		if (cmos->mon_alrm) { -			t->time.tm_mon = CMOS_READ(cmos->mon_alrm); -			if (!t->time.tm_mon) -				t->time.tm_mon = -1; -		} -	} - -	rtc_control = CMOS_READ(RTC_CONTROL); -	spin_unlock_irq(&rtc_lock); +	/* Some Intel chipsets disconnect the alarm registers when the clock +	 * update is in progress - during this time reads return bogus values +	 * and writes may fail silently. See for example "7th Generation IntelĀ® +	 * Processor Family I/O for U/Y Platforms [...] Datasheet", section +	 * 27.7.1 +	 * +	 * Use the mc146818_avoid_UIP() function to avoid this. +	 */ +	if (!mc146818_avoid_UIP(cmos_read_alarm_callback, &p)) +		return -EIO; -	if (!(rtc_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD) { +	if (!(p.rtc_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD) {  		if (((unsigned)t->time.tm_sec) < 0x60)  			t->time.tm_sec = bcd2bin(t->time.tm_sec);  		else @@ -306,7 +339,7 @@ static int cmos_read_alarm(struct device *dev, struct rtc_wkalrm *t)  		}  	} -	t->enabled = !!(rtc_control & RTC_AIE); +	t->enabled = !!(p.rtc_control & RTC_AIE);  	t->pending = 0;  	return 0; @@ -437,10 +470,57 @@ static int cmos_validate_alarm(struct device *dev, struct rtc_wkalrm *t)  	return 0;  } +struct cmos_set_alarm_callback_param { +	struct cmos_rtc *cmos; +	unsigned char mon, mday, hrs, min, sec; +	struct rtc_wkalrm *t; +}; + +/* Note: this function may be executed by mc146818_avoid_UIP() more then + *	 once + */ +static void cmos_set_alarm_callback(unsigned char __always_unused seconds, +				    void *param_in) +{ +	struct cmos_set_alarm_callback_param *p = +		(struct cmos_set_alarm_callback_param *)param_in; + +	/* next rtc irq must not be from previous alarm setting */ +	cmos_irq_disable(p->cmos, RTC_AIE); + +	/* update alarm */ +	CMOS_WRITE(p->hrs, RTC_HOURS_ALARM); +	CMOS_WRITE(p->min, RTC_MINUTES_ALARM); +	CMOS_WRITE(p->sec, RTC_SECONDS_ALARM); + +	/* the system may support an "enhanced" alarm */ +	if (p->cmos->day_alrm) { +		CMOS_WRITE(p->mday, p->cmos->day_alrm); +		if (p->cmos->mon_alrm) +			CMOS_WRITE(p->mon, p->cmos->mon_alrm); +	} + +	if (use_hpet_alarm()) { +		/* +		 * FIXME the HPET alarm glue currently ignores day_alrm +		 * and mon_alrm ... +		 */ +		hpet_set_alarm_time(p->t->time.tm_hour, p->t->time.tm_min, +				    p->t->time.tm_sec); +	} + +	if (p->t->enabled) +		cmos_irq_enable(p->cmos, RTC_AIE); +} +  static int cmos_set_alarm(struct device *dev, struct rtc_wkalrm *t)  {  	struct cmos_rtc	*cmos = dev_get_drvdata(dev); -	unsigned char mon, mday, hrs, min, sec, rtc_control; +	struct cmos_set_alarm_callback_param p = { +		.cmos = cmos, +		.t = t +	}; +	unsigned char rtc_control;  	int ret;  	/* This not only a rtc_op, but also called directly */ @@ -451,52 +531,33 @@ static int cmos_set_alarm(struct device *dev, struct rtc_wkalrm *t)  	if (ret < 0)  		return ret; -	mon = t->time.tm_mon + 1; -	mday = t->time.tm_mday; -	hrs = t->time.tm_hour; -	min = t->time.tm_min; -	sec = t->time.tm_sec; +	p.mon = t->time.tm_mon + 1; +	p.mday = t->time.tm_mday; +	p.hrs = t->time.tm_hour; +	p.min = t->time.tm_min; +	p.sec = t->time.tm_sec; +	spin_lock_irq(&rtc_lock);  	rtc_control = CMOS_READ(RTC_CONTROL); +	spin_unlock_irq(&rtc_lock); +  	if (!(rtc_control & RTC_DM_BINARY) || RTC_ALWAYS_BCD) {  		/* Writing 0xff means "don't care" or "match all".  */ -		mon = (mon <= 12) ? bin2bcd(mon) : 0xff; -		mday = (mday >= 1 && mday <= 31) ? bin2bcd(mday) : 0xff; -		hrs = (hrs < 24) ? bin2bcd(hrs) : 0xff; -		min = (min < 60) ? bin2bcd(min) : 0xff; -		sec = (sec < 60) ? bin2bcd(sec) : 0xff; -	} - -	spin_lock_irq(&rtc_lock); - -	/* next rtc irq must not be from previous alarm setting */ -	cmos_irq_disable(cmos, RTC_AIE); - -	/* update alarm */ -	CMOS_WRITE(hrs, RTC_HOURS_ALARM); -	CMOS_WRITE(min, RTC_MINUTES_ALARM); -	CMOS_WRITE(sec, RTC_SECONDS_ALARM); - -	/* the system may support an "enhanced" alarm */ -	if (cmos->day_alrm) { -		CMOS_WRITE(mday, cmos->day_alrm); -		if (cmos->mon_alrm) -			CMOS_WRITE(mon, cmos->mon_alrm); +		p.mon = (p.mon <= 12) ? bin2bcd(p.mon) : 0xff; +		p.mday = (p.mday >= 1 && p.mday <= 31) ? bin2bcd(p.mday) : 0xff; +		p.hrs = (p.hrs < 24) ? bin2bcd(p.hrs) : 0xff; +		p.min = (p.min < 60) ? bin2bcd(p.min) : 0xff; +		p.sec = (p.sec < 60) ? bin2bcd(p.sec) : 0xff;  	} -	if (use_hpet_alarm()) { -		/* -		 * FIXME the HPET alarm glue currently ignores day_alrm -		 * and mon_alrm ... -		 */ -		hpet_set_alarm_time(t->time.tm_hour, t->time.tm_min, -				    t->time.tm_sec); -	} - -	if (t->enabled) -		cmos_irq_enable(cmos, RTC_AIE); - -	spin_unlock_irq(&rtc_lock); +	/* +	 * Some Intel chipsets disconnect the alarm registers when the clock +	 * update is in progress - during this time writes fail silently. +	 * +	 * Use mc146818_avoid_UIP() to avoid this. +	 */ +	if (!mc146818_avoid_UIP(cmos_set_alarm_callback, &p)) +		return -EIO;  	cmos->alarm_expires = rtc_tm_to_time64(&t->time); @@ -790,16 +851,14 @@ cmos_do_probe(struct device *dev, struct resource *ports, int rtc_irq)  	rename_region(ports, dev_name(&cmos_rtc.rtc->dev)); -	spin_lock_irq(&rtc_lock); - -	/* Ensure that the RTC is accessible. Bit 6 must be 0! */ -	if ((CMOS_READ(RTC_VALID) & 0x40) != 0) { -		spin_unlock_irq(&rtc_lock); -		dev_warn(dev, "not accessible\n"); +	if (!mc146818_does_rtc_work()) { +		dev_warn(dev, "broken or not accessible\n");  		retval = -ENXIO;  		goto cleanup1;  	} +	spin_lock_irq(&rtc_lock); +  	if (!(flags & CMOS_RTC_FLAGS_NOFREQ)) {  		/* force periodic irq to CMOS reset default of 1024Hz;  		 *  |