diff options
| -rw-r--r-- | drivers/rtc/Kconfig | 9 | ||||
| -rw-r--r-- | drivers/rtc/Makefile | 1 | ||||
| -rw-r--r-- | drivers/rtc/rtc-m48t35.c | 234 | 
3 files changed, 244 insertions, 0 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 24217008aa3..f3d7fd3406a 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -410,6 +410,15 @@ config RTC_DRV_M48T86  	  This driver can also be built as a module. If so, the module  	  will be called rtc-m48t86. +config RTC_DRV_M48T35 +	tristate "ST M48T35" +	help +	  If you say Y here you will get support for the +	  ST M48T35 RTC chip. + +	  This driver can also be built as a module, if so, the module +	  will be called "rtc-m48t35". +  config RTC_DRV_M48T59  	tristate "ST M48T59/M48T08/M48T02"  	help diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index a3208082565..37a71b72726 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -37,6 +37,7 @@ obj-$(CONFIG_RTC_DRV_FM3130)	+= rtc-fm3130.o  obj-$(CONFIG_RTC_DRV_ISL1208)	+= rtc-isl1208.o  obj-$(CONFIG_RTC_DRV_M41T80)	+= rtc-m41t80.o  obj-$(CONFIG_RTC_DRV_M41T94)	+= rtc-m41t94.o +obj-$(CONFIG_RTC_DRV_M48T35)	+= rtc-m48t35.o  obj-$(CONFIG_RTC_DRV_M48T59)	+= rtc-m48t59.o  obj-$(CONFIG_RTC_DRV_M48T86)	+= rtc-m48t86.o  obj-$(CONFIG_RTC_DRV_BQ4802)	+= rtc-bq4802.o diff --git a/drivers/rtc/rtc-m48t35.c b/drivers/rtc/rtc-m48t35.c new file mode 100644 index 00000000000..b9c1fe4a198 --- /dev/null +++ b/drivers/rtc/rtc-m48t35.c @@ -0,0 +1,234 @@ +/* + * Driver for the SGS-Thomson M48T35 Timekeeper RAM chip + * + * Copyright (C) 2000 Silicon Graphics, Inc. + * Written by Ulf Carlsson (ulfc@engr.sgi.com) + * + * Copyright (C) 2008 Thomas Bogendoerfer + * + * Based on code written by Paul Gortmaker. + * + * 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/module.h> +#include <linux/rtc.h> +#include <linux/platform_device.h> +#include <linux/bcd.h> + +#define DRV_VERSION		"1.0" + +struct m48t35_rtc { +	u8	pad[0x7ff8];    /* starts at 0x7ff8 */ +	u8	control; +	u8	sec; +	u8	min; +	u8	hour; +	u8	day; +	u8	date; +	u8	month; +	u8	year; +}; + +#define M48T35_RTC_SET		0x80 +#define M48T35_RTC_READ		0x40 + +struct m48t35_priv { +	struct rtc_device *rtc; +	struct m48t35_rtc __iomem *reg; +	size_t size; +	unsigned long baseaddr; +	spinlock_t lock; +}; + +static int m48t35_read_time(struct device *dev, struct rtc_time *tm) +{ +	struct m48t35_priv *priv = dev_get_drvdata(dev); +	u8 control; + +	/* +	 * Only the values that we read from the RTC are set. We leave +	 * tm_wday, tm_yday and tm_isdst untouched. Even though the +	 * 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. +	 */ +	spin_lock_irq(&priv->lock); +	control = readb(&priv->reg->control); +	writeb(control | M48T35_RTC_READ, &priv->reg->control); +	tm->tm_sec = readb(&priv->reg->sec); +	tm->tm_min = readb(&priv->reg->min); +	tm->tm_hour = readb(&priv->reg->hour); +	tm->tm_mday = readb(&priv->reg->date); +	tm->tm_mon = readb(&priv->reg->month); +	tm->tm_year = readb(&priv->reg->year); +	writeb(control, &priv->reg->control); +	spin_unlock_irq(&priv->lock); + +	tm->tm_sec = bcd2bin(tm->tm_sec); +	tm->tm_min = bcd2bin(tm->tm_min); +	tm->tm_hour = bcd2bin(tm->tm_hour); +	tm->tm_mday = bcd2bin(tm->tm_mday); +	tm->tm_mon = bcd2bin(tm->tm_mon); +	tm->tm_year = bcd2bin(tm->tm_year); + +	/* +	 * Account for differences between how the RTC uses the values +	 * and how they are defined in a struct rtc_time; +	 */ +	tm->tm_year += 70; +	if (tm->tm_year <= 69) +		tm->tm_year += 100; + +	tm->tm_mon--; +	return rtc_valid_tm(tm); +} + +static int m48t35_set_time(struct device *dev, struct rtc_time *tm) +{ +	struct m48t35_priv *priv = dev_get_drvdata(dev); +	unsigned char mon, day, hrs, min, sec; +	unsigned int yrs; +	u8 control; + +	yrs = tm->tm_year + 1900; +	mon = tm->tm_mon + 1;   /* tm_mon starts at zero */ +	day = tm->tm_mday; +	hrs = tm->tm_hour; +	min = tm->tm_min; +	sec = tm->tm_sec; + +	if (yrs < 1970) +		return -EINVAL; + +	yrs -= 1970; +	if (yrs > 255)    /* They are unsigned */ +		return -EINVAL; + +	if (yrs > 169) +		return -EINVAL; + +	if (yrs >= 100) +		yrs -= 100; + +	sec = bin2bcd(sec); +	min = bin2bcd(min); +	hrs = bin2bcd(hrs); +	day = bin2bcd(day); +	mon = bin2bcd(mon); +	yrs = bin2bcd(yrs); + +	spin_lock_irq(&priv->lock); +	control = readb(&priv->reg->control); +	writeb(control | M48T35_RTC_SET, &priv->reg->control); +	writeb(yrs, &priv->reg->year); +	writeb(mon, &priv->reg->month); +	writeb(day, &priv->reg->date); +	writeb(hrs, &priv->reg->hour); +	writeb(min, &priv->reg->min); +	writeb(sec, &priv->reg->sec); +	writeb(control, &priv->reg->control); +	spin_unlock_irq(&priv->lock); +	return 0; +} + +static const struct rtc_class_ops m48t35_ops = { +	.read_time	= m48t35_read_time, +	.set_time	= m48t35_set_time, +}; + +static int __devinit m48t35_probe(struct platform_device *pdev) +{ +	struct rtc_device *rtc; +	struct resource *res; +	struct m48t35_priv *priv; +	int ret = 0; + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	if (!res) +		return -ENODEV; +	priv = kzalloc(sizeof(struct m48t35_priv), GFP_KERNEL); +	if (!priv) +		return -ENOMEM; + +	priv->size = res->end - res->start + 1; +	/* +	 * kludge: remove the #ifndef after ioc3 resource +	 * conflicts are resolved +	 */ +#ifndef CONFIG_SGI_IP27 +	if (!request_mem_region(res->start, priv->size, pdev->name)) { +		ret = -EBUSY; +		goto out; +	} +#endif +	priv->baseaddr = res->start; +	priv->reg = ioremap(priv->baseaddr, priv->size); +	if (!priv->reg) { +		ret = -ENOMEM; +		goto out; +	} +	spin_lock_init(&priv->lock); +	rtc = rtc_device_register("m48t35", &pdev->dev, +				  &m48t35_ops, THIS_MODULE); +	if (IS_ERR(rtc)) { +		ret = PTR_ERR(rtc); +		goto out; +	} +	priv->rtc = rtc; +	platform_set_drvdata(pdev, priv); +	return 0; + +out: +	if (priv->rtc) +		rtc_device_unregister(priv->rtc); +	if (priv->reg) +		iounmap(priv->reg); +	if (priv->baseaddr) +		release_mem_region(priv->baseaddr, priv->size); +	kfree(priv); +	return ret; +} + +static int __devexit m48t35_remove(struct platform_device *pdev) +{ +	struct m48t35_priv *priv = platform_get_drvdata(pdev); + +	rtc_device_unregister(priv->rtc); +	iounmap(priv->reg); +#ifndef CONFIG_SGI_IP27 +	release_mem_region(priv->baseaddr, priv->size); +#endif +	kfree(priv); +	return 0; +} + +static struct platform_driver m48t35_platform_driver = { +	.driver		= { +		.name	= "rtc-m48t35", +		.owner	= THIS_MODULE, +	}, +	.probe		= m48t35_probe, +	.remove		= __devexit_p(m48t35_remove), +}; + +static int __init m48t35_init(void) +{ +	return platform_driver_register(&m48t35_platform_driver); +} + +static void __exit m48t35_exit(void) +{ +	platform_driver_unregister(&m48t35_platform_driver); +} + +MODULE_AUTHOR("Thomas Bogendoerfer <tsbogend@alpha.franken.de>"); +MODULE_DESCRIPTION("M48T35 RTC driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRV_VERSION); +MODULE_ALIAS("platform:rtc-m48t35"); + +module_init(m48t35_init); +module_exit(m48t35_exit);  |