⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 rtc-cmos.c

📁 linux 内核源代码
💻 C
📖 第 1 页 / 共 2 页
字号:
/* * RTC class driver for "CMOS RTC":  PCs, ACPI, etc * * Copyright (C) 1996 Paul Gortmaker (drivers/char/rtc.c) * Copyright (C) 2006 David Brownell (convert to new framework) * * 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. *//* * The original "cmos clock" chip was an MC146818 chip, now obsolete. * That defined the register interface now provided by all PCs, some * non-PC systems, and incorporated into ACPI.  Modern PC chipsets * integrate an MC146818 clone in their southbridge, and boards use * that instead of discrete clones like the DS12887 or M48T86.  There * are also clones that connect using the LPC bus. * * That register API is also used directly by various other drivers * (notably for integrated NVRAM), infrastructure (x86 has code to * bypass the RTC framework, directly reading the RTC during boot * and updating minutes/seconds for systems using NTP synch) and * utilities (like userspace 'hwclock', if no /dev node exists). * * So **ALL** calls to CMOS_READ and CMOS_WRITE must be done with * interrupts disabled, holding the global rtc_lock, to exclude those * other drivers and utilities on correctly configured systems. */#include <linux/kernel.h>#include <linux/module.h>#include <linux/init.h>#include <linux/interrupt.h>#include <linux/spinlock.h>#include <linux/platform_device.h>#include <linux/mod_devicetable.h>/* this is for "generic access to PC-style RTC" using CMOS_READ/CMOS_WRITE */#include <asm-generic/rtc.h>struct cmos_rtc {	struct rtc_device	*rtc;	struct device		*dev;	int			irq;	struct resource		*iomem;	void			(*wake_on)(struct device *);	void			(*wake_off)(struct device *);	u8			enabled_wake;	u8			suspend_ctrl;	/* newer hardware extends the original register set */	u8			day_alrm;	u8			mon_alrm;	u8			century;};/* both platform and pnp busses use negative numbers for invalid irqs */#define is_valid_irq(n)		((n) >= 0)static const char driver_name[] = "rtc_cmos";/* The RTC_INTR register may have e.g. RTC_PF set even if RTC_PIE is clear; * always mask it against the irq enable bits in RTC_CONTROL.  Bit values * are the same: PF==PIE, AF=AIE, UF=UIE; so RTC_IRQMASK works with both. */#define	RTC_IRQMASK	(RTC_PF | RTC_AF | RTC_UF)static inline int is_intr(u8 rtc_intr){	if (!(rtc_intr & RTC_IRQF))		return 0;	return rtc_intr & RTC_IRQMASK;}/*----------------------------------------------------------------*/static int cmos_read_time(struct device *dev, struct rtc_time *t){	/* REVISIT:  if the clock has a "century" register, use	 * that instead of the heuristic in get_rtc_time().	 * That'll make Y3K compatility (year > 2070) easy!	 */	get_rtc_time(t);	return 0;}static int cmos_set_time(struct device *dev, struct rtc_time *t){	/* REVISIT:  set the "century" register if available	 *	 * NOTE: this ignores the issue whereby updating the seconds	 * takes effect exactly 500ms after we write the register.	 * (Also queueing and other delays before we get this far.)	 */	return set_rtc_time(t);}static int cmos_read_alarm(struct device *dev, struct rtc_wkalrm *t){	struct cmos_rtc	*cmos = dev_get_drvdata(dev);	unsigned char	rtc_control;	if (!is_valid_irq(cmos->irq))		return -EIO;	/* Basic alarms only support hour, minute, and seconds fields.	 * Some also support day and month, for alarms up to a year in	 * the future.	 */	t->time.tm_mday = -1;	t->time.tm_mon = -1;	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);	/* REVISIT this assumes PC style usage:  always BCD */	if (((unsigned)t->time.tm_sec) < 0x60)		t->time.tm_sec = BCD2BIN(t->time.tm_sec);	else		t->time.tm_sec = -1;	if (((unsigned)t->time.tm_min) < 0x60)		t->time.tm_min = BCD2BIN(t->time.tm_min);	else		t->time.tm_min = -1;	if (((unsigned)t->time.tm_hour) < 0x24)		t->time.tm_hour = BCD2BIN(t->time.tm_hour);	else		t->time.tm_hour = -1;	if (cmos->day_alrm) {		if (((unsigned)t->time.tm_mday) <= 0x31)			t->time.tm_mday = BCD2BIN(t->time.tm_mday);		else			t->time.tm_mday = -1;		if (cmos->mon_alrm) {			if (((unsigned)t->time.tm_mon) <= 0x12)				t->time.tm_mon = BCD2BIN(t->time.tm_mon) - 1;			else				t->time.tm_mon = -1;		}	}	t->time.tm_year = -1;	t->enabled = !!(rtc_control & RTC_AIE);	t->pending = 0;	return 0;}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;	unsigned char	rtc_control, rtc_intr;	if (!is_valid_irq(cmos->irq))		return -EIO;	/* REVISIT this assumes PC style usage:  always BCD */	/* Writing 0xff means "don't care" or "match all".  */	mon = t->time.tm_mon;	mon = (mon < 12) ? BIN2BCD(mon) : 0xff;	mon++;	mday = t->time.tm_mday;	mday = (mday >= 1 && mday <= 31) ? BIN2BCD(mday) : 0xff;	hrs = t->time.tm_hour;	hrs = (hrs < 24) ? BIN2BCD(hrs) : 0xff;	min = t->time.tm_min;	min = (min < 60) ? BIN2BCD(min) : 0xff;	sec = t->time.tm_sec;	sec = (sec < 60) ? BIN2BCD(sec) : 0xff;	spin_lock_irq(&rtc_lock);	/* next rtc irq must not be from previous alarm setting */	rtc_control = CMOS_READ(RTC_CONTROL);	rtc_control &= ~RTC_AIE;	CMOS_WRITE(rtc_control, RTC_CONTROL);	rtc_intr = CMOS_READ(RTC_INTR_FLAGS);	rtc_intr &= (rtc_control & RTC_IRQMASK) | RTC_IRQF;	if (is_intr(rtc_intr))		rtc_update_irq(cmos->rtc, 1, rtc_intr);	/* 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);	}	if (t->enabled) {		rtc_control |= RTC_AIE;		CMOS_WRITE(rtc_control, RTC_CONTROL);		rtc_intr = CMOS_READ(RTC_INTR_FLAGS);		rtc_intr &= (rtc_control & RTC_IRQMASK) | RTC_IRQF;		if (is_intr(rtc_intr))			rtc_update_irq(cmos->rtc, 1, rtc_intr);	}	spin_unlock_irq(&rtc_lock);	return 0;}static int cmos_irq_set_freq(struct device *dev, int freq){	struct cmos_rtc	*cmos = dev_get_drvdata(dev);	int		f;	unsigned long	flags;	if (!is_valid_irq(cmos->irq))		return -ENXIO;	/* 0 = no irqs; 1 = 2^15 Hz ... 15 = 2^0 Hz */	f = ffs(freq);	if (f-- > 16)		return -EINVAL;	f = 16 - f;	spin_lock_irqsave(&rtc_lock, flags);	CMOS_WRITE(RTC_REF_CLCK_32KHZ | f, RTC_FREQ_SELECT);	spin_unlock_irqrestore(&rtc_lock, flags);	return 0;}static int cmos_irq_set_state(struct device *dev, int enabled){	struct cmos_rtc	*cmos = dev_get_drvdata(dev);	unsigned char	rtc_control, rtc_intr;	unsigned long	flags;	if (!is_valid_irq(cmos->irq))		return -ENXIO;	spin_lock_irqsave(&rtc_lock, flags);	rtc_control = CMOS_READ(RTC_CONTROL);	if (enabled)		rtc_control |= RTC_PIE;	else		rtc_control &= ~RTC_PIE;	CMOS_WRITE(rtc_control, RTC_CONTROL);	rtc_intr = CMOS_READ(RTC_INTR_FLAGS);	rtc_intr &= (rtc_control & RTC_IRQMASK) | RTC_IRQF;	if (is_intr(rtc_intr))		rtc_update_irq(cmos->rtc, 1, rtc_intr);	spin_unlock_irqrestore(&rtc_lock, flags);	return 0;}#if defined(CONFIG_RTC_INTF_DEV) || defined(CONFIG_RTC_INTF_DEV_MODULE)static intcmos_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg){	struct cmos_rtc	*cmos = dev_get_drvdata(dev);	unsigned char	rtc_control, rtc_intr;	unsigned long	flags;	switch (cmd) {	case RTC_AIE_OFF:	case RTC_AIE_ON:	case RTC_UIE_OFF:	case RTC_UIE_ON:	case RTC_PIE_OFF:	case RTC_PIE_ON:		if (!is_valid_irq(cmos->irq))			return -EINVAL;		break;	default:		return -ENOIOCTLCMD;	}	spin_lock_irqsave(&rtc_lock, flags);	rtc_control = CMOS_READ(RTC_CONTROL);	switch (cmd) {	case RTC_AIE_OFF:	/* alarm off */		rtc_control &= ~RTC_AIE;		break;	case RTC_AIE_ON:	/* alarm on */		rtc_control |= RTC_AIE;		break;	case RTC_UIE_OFF:	/* update off */		rtc_control &= ~RTC_UIE;		break;	case RTC_UIE_ON:	/* update on */		rtc_control |= RTC_UIE;		break;	case RTC_PIE_OFF:	/* periodic off */		rtc_control &= ~RTC_PIE;		break;	case RTC_PIE_ON:	/* periodic on */		rtc_control |= RTC_PIE;		break;	}	CMOS_WRITE(rtc_control, RTC_CONTROL);	rtc_intr = CMOS_READ(RTC_INTR_FLAGS);	rtc_intr &= (rtc_control & RTC_IRQMASK) | RTC_IRQF;	if (is_intr(rtc_intr))		rtc_update_irq(cmos->rtc, 1, rtc_intr);	spin_unlock_irqrestore(&rtc_lock, flags);	return 0;}#else#define	cmos_rtc_ioctl	NULL#endif#if defined(CONFIG_RTC_INTF_PROC) || defined(CONFIG_RTC_INTF_PROC_MODULE)static int cmos_procfs(struct device *dev, struct seq_file *seq){	struct cmos_rtc	*cmos = dev_get_drvdata(dev);	unsigned char	rtc_control, valid;	spin_lock_irq(&rtc_lock);	rtc_control = CMOS_READ(RTC_CONTROL);	valid = CMOS_READ(RTC_VALID);	spin_unlock_irq(&rtc_lock);	/* NOTE:  at least ICH6 reports battery status using a different	 * (non-RTC) bit; and SQWE is ignored on many current systems.	 */	return seq_printf(seq,			"periodic_IRQ\t: %s\n"			"update_IRQ\t: %s\n"			// "square_wave\t: %s\n"			// "BCD\t\t: %s\n"			"DST_enable\t: %s\n"			"periodic_freq\t: %d\n"			"batt_status\t: %s\n",			(rtc_control & RTC_PIE) ? "yes" : "no",			(rtc_control & RTC_UIE) ? "yes" : "no",			// (rtc_control & RTC_SQWE) ? "yes" : "no",			// (rtc_control & RTC_DM_BINARY) ? "no" : "yes",			(rtc_control & RTC_DST_EN) ? "yes" : "no",			cmos->rtc->irq_freq,			(valid & RTC_VRT) ? "okay" : "dead");}#else#define	cmos_procfs	NULL#endifstatic const struct rtc_class_ops cmos_rtc_ops = {	.ioctl		= cmos_rtc_ioctl,	.read_time	= cmos_read_time,	.set_time	= cmos_set_time,	.read_alarm	= cmos_read_alarm,	.set_alarm	= cmos_set_alarm,	.proc		= cmos_procfs,	.irq_set_freq	= cmos_irq_set_freq,	.irq_set_state	= cmos_irq_set_state,};/*----------------------------------------------------------------*/static struct cmos_rtc	cmos_rtc;static irqreturn_t cmos_interrupt(int irq, void *p){	u8		irqstat;

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -