Creation of Cybook 2416 (actually Gen4) repository
This commit is contained in:
357
drivers/rtc/Kconfig
Normal file
357
drivers/rtc/Kconfig
Normal file
@@ -0,0 +1,357 @@
|
||||
#
|
||||
# RTC class/drivers configuration
|
||||
#
|
||||
|
||||
menu "Real Time Clock"
|
||||
|
||||
config RTC_LIB
|
||||
tristate
|
||||
|
||||
config RTC_CLASS
|
||||
tristate "RTC class"
|
||||
depends on EXPERIMENTAL
|
||||
default n
|
||||
select RTC_LIB
|
||||
help
|
||||
Generic RTC class support. If you say yes here, you will
|
||||
be allowed to plug one or more RTCs to your system. You will
|
||||
probably want to enable one or more of the interfaces below.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called rtc-class.
|
||||
|
||||
config RTC_HCTOSYS
|
||||
bool "Set system time from RTC on startup"
|
||||
depends on RTC_CLASS = y
|
||||
default y
|
||||
help
|
||||
If you say yes here, the system time will be set using
|
||||
the value read from the specified RTC device. This is useful
|
||||
in order to avoid unnecessary fsck runs.
|
||||
|
||||
config RTC_HCTOSYS_DEVICE
|
||||
string "The RTC to read the time from"
|
||||
depends on RTC_HCTOSYS = y
|
||||
default "rtc0"
|
||||
help
|
||||
The RTC device that will be used as the source for
|
||||
the system time, usually rtc0.
|
||||
|
||||
config RTC_DEBUG
|
||||
bool "RTC debug support"
|
||||
depends on RTC_CLASS = y
|
||||
help
|
||||
Say yes here to enable debugging support in the RTC framework
|
||||
and individual RTC drivers.
|
||||
|
||||
comment "RTC interfaces"
|
||||
depends on RTC_CLASS
|
||||
|
||||
config RTC_INTF_SYSFS
|
||||
tristate "sysfs"
|
||||
depends on RTC_CLASS && SYSFS
|
||||
default RTC_CLASS
|
||||
help
|
||||
Say yes here if you want to use your RTCs using sysfs interfaces,
|
||||
/sys/class/rtc/rtc0 through /sys/.../rtcN.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called rtc-sysfs.
|
||||
|
||||
config RTC_INTF_PROC
|
||||
tristate "proc"
|
||||
depends on RTC_CLASS && PROC_FS
|
||||
default RTC_CLASS
|
||||
help
|
||||
Say yes here if you want to use your first RTC through the proc
|
||||
interface, /proc/driver/rtc. Other RTCs will not be available
|
||||
through that API.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called rtc-proc.
|
||||
|
||||
config RTC_INTF_DEV
|
||||
tristate "dev"
|
||||
depends on RTC_CLASS
|
||||
default RTC_CLASS
|
||||
help
|
||||
Say yes here if you want to use your RTCs using the /dev
|
||||
interfaces, which "udev" sets up as /dev/rtc0 through
|
||||
/dev/rtcN. You may want to set up a symbolic link so one
|
||||
of these can be accessed as /dev/rtc, which is a name
|
||||
expected by "hwclock" and some other programs.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called rtc-dev.
|
||||
|
||||
config RTC_INTF_DEV_UIE_EMUL
|
||||
bool "RTC UIE emulation on dev interface"
|
||||
depends on RTC_INTF_DEV
|
||||
help
|
||||
Provides an emulation for RTC_UIE if the underlaying rtc chip
|
||||
driver does not expose RTC_UIE ioctls. Those requests generate
|
||||
once-per-second update interrupts, used for synchronization.
|
||||
|
||||
comment "RTC drivers"
|
||||
depends on RTC_CLASS
|
||||
|
||||
# this 'CMOS' RTC driver is arch dependent because <asm-generic/rtc.h>
|
||||
# requires <asm/mc146818rtc.h> defining CMOS_READ/CMOS_WRITE, and a
|
||||
# global rtc_lock ... it's not yet just another platform_device.
|
||||
|
||||
config RTC_DRV_CMOS
|
||||
tristate "PC-style 'CMOS' real time clock"
|
||||
depends on RTC_CLASS && (X86 || ALPHA || ARM26 || ARM \
|
||||
|| M32R || ATARI || POWERPC)
|
||||
help
|
||||
Say "yes" here to get direct support for the real time clock
|
||||
found in every PC or ACPI-based system, and some other boards.
|
||||
Specifically the original MC146818, compatibles like those in
|
||||
PC south bridges, the DS12887 or M48T86, some multifunction
|
||||
or LPC bus chips, and so on.
|
||||
|
||||
Your system will need to define the platform device used by
|
||||
this driver, otherwise it won't be accessible. This means
|
||||
you can safely enable this driver if you don't know whether
|
||||
or not your board has this kind of hardware.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called rtc-cmos.
|
||||
|
||||
config RTC_DRV_X1205
|
||||
tristate "Xicor/Intersil X1205"
|
||||
depends on RTC_CLASS && I2C
|
||||
help
|
||||
If you say yes here you get support for the
|
||||
Xicor/Intersil X1205 RTC chip.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called rtc-x1205.
|
||||
|
||||
config RTC_DRV_DS1307
|
||||
tristate "Dallas/Maxim DS1307 and similar I2C RTC chips"
|
||||
depends on RTC_CLASS && I2C
|
||||
help
|
||||
If you say yes here you get support for various compatible RTC
|
||||
chips (often with battery backup) connected with I2C. This driver
|
||||
should handle DS1307, DS1337, DS1338, DS1339, DS1340, ST M41T00,
|
||||
and probably other chips. In some cases the RTC must already
|
||||
have been initialized (by manufacturing or a bootloader).
|
||||
|
||||
The first seven registers on these chips hold an RTC, and other
|
||||
registers may add features such as NVRAM, a trickle charger for
|
||||
the RTC/NVRAM backup power, and alarms. This driver may not
|
||||
expose all those available chip features.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called rtc-ds1307.
|
||||
|
||||
config RTC_DRV_DS1553
|
||||
tristate "Dallas DS1553"
|
||||
depends on RTC_CLASS
|
||||
help
|
||||
If you say yes here you get support for the
|
||||
Dallas DS1553 timekeeping chip.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called rtc-ds1553.
|
||||
|
||||
config RTC_DRV_ISL1208
|
||||
tristate "Intersil 1208"
|
||||
depends on RTC_CLASS && I2C
|
||||
help
|
||||
If you say yes here you get support for the
|
||||
Intersil 1208 RTC chip.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called rtc-isl1208.
|
||||
|
||||
config RTC_DRV_DS1672
|
||||
tristate "Dallas/Maxim DS1672"
|
||||
depends on RTC_CLASS && I2C
|
||||
help
|
||||
If you say yes here you get support for the
|
||||
Dallas/Maxim DS1672 timekeeping chip.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called rtc-ds1672.
|
||||
|
||||
config RTC_DRV_DS1742
|
||||
tristate "Dallas DS1742/1743"
|
||||
depends on RTC_CLASS
|
||||
help
|
||||
If you say yes here you get support for the
|
||||
Dallas DS1742/1743 timekeeping chip.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called rtc-ds1742.
|
||||
|
||||
config RTC_DRV_OMAP
|
||||
tristate "TI OMAP1"
|
||||
depends on RTC_CLASS && ( \
|
||||
ARCH_OMAP15XX || ARCH_OMAP16XX || ARCH_OMAP730 )
|
||||
help
|
||||
Say "yes" here to support the real time clock on TI OMAP1 chips.
|
||||
This driver can also be built as a module called rtc-omap.
|
||||
|
||||
config RTC_DRV_PCF8563
|
||||
tristate "Philips PCF8563/Epson RTC8564"
|
||||
depends on RTC_CLASS && I2C
|
||||
help
|
||||
If you say yes here you get support for the
|
||||
Philips PCF8563 RTC chip. The Epson RTC8564
|
||||
should work as well.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called rtc-pcf8563.
|
||||
|
||||
config RTC_DRV_PCF8583
|
||||
tristate "Philips PCF8583"
|
||||
depends on RTC_CLASS && I2C && ARCH_RPC
|
||||
help
|
||||
If you say yes here you get support for the Philips PCF8583
|
||||
RTC chip found on Acorn RiscPCs. This driver supports the
|
||||
platform specific method of retrieving the current year from
|
||||
the RTC's SRAM.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called rtc-pcf8583.
|
||||
|
||||
config RTC_DRV_RS5C348
|
||||
tristate "Ricoh RS5C348A/B"
|
||||
depends on RTC_CLASS && SPI
|
||||
help
|
||||
If you say yes here you get support for the
|
||||
Ricoh RS5C348A and RS5C348B RTC chips.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called rtc-rs5c348.
|
||||
|
||||
config RTC_DRV_RS5C372
|
||||
tristate "Ricoh RS5C372A/B"
|
||||
depends on RTC_CLASS && I2C
|
||||
help
|
||||
If you say yes here you get support for the
|
||||
Ricoh RS5C372A and RS5C372B RTC chips.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called rtc-rs5c372.
|
||||
|
||||
config RTC_DRV_S3C
|
||||
tristate "Samsung S3C series SoC RTC"
|
||||
depends on RTC_CLASS && ARCH_S3C2410
|
||||
help
|
||||
RTC (Realtime Clock) driver for the clock inbuilt into the
|
||||
Samsung S3C24XX series of SoCs. This can provide periodic
|
||||
interrupt rates from 1Hz to 64Hz for user programs, and
|
||||
wakeup from Alarm.
|
||||
|
||||
The driver currently supports the common features on all the
|
||||
S3C24XX range, such as the S3C2410, S3C2412, S3C2413, S3C2440
|
||||
and S3C2442.
|
||||
|
||||
This driver can also be build as a module. If so, the module
|
||||
will be called rtc-s3c.
|
||||
|
||||
config RTC_DRV_M48T86
|
||||
tristate "ST M48T86/Dallas DS12887"
|
||||
depends on RTC_CLASS
|
||||
help
|
||||
If you say Y here you will get support for the
|
||||
ST M48T86 and Dallas DS12887 RTC chips.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called rtc-m48t86.
|
||||
|
||||
config RTC_DRV_EP93XX
|
||||
tristate "Cirrus Logic EP93XX"
|
||||
depends on RTC_CLASS && ARCH_EP93XX
|
||||
help
|
||||
If you say yes here you get support for the
|
||||
RTC embedded in the Cirrus Logic EP93XX processors.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called rtc-ep93xx.
|
||||
|
||||
config RTC_DRV_SA1100
|
||||
tristate "SA11x0/PXA2xx"
|
||||
depends on RTC_CLASS && (ARCH_SA1100 || ARCH_PXA)
|
||||
help
|
||||
If you say Y here you will get access to the real time clock
|
||||
built into your SA11x0 or PXA2xx CPU.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called rtc-sa1100.
|
||||
|
||||
config RTC_DRV_SH
|
||||
tristate "SuperH On-Chip RTC"
|
||||
depends on RTC_CLASS && SUPERH
|
||||
help
|
||||
Say Y here to enable support for the on-chip RTC found in
|
||||
most SuperH processors.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called rtc-sh.
|
||||
|
||||
config RTC_DRV_VR41XX
|
||||
tristate "NEC VR41XX"
|
||||
depends on RTC_CLASS && CPU_VR41XX
|
||||
help
|
||||
If you say Y here you will get access to the real time clock
|
||||
built into your NEC VR41XX CPU.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called rtc-vr41xx.
|
||||
|
||||
config RTC_DRV_PL031
|
||||
tristate "ARM AMBA PL031 RTC"
|
||||
depends on RTC_CLASS && ARM_AMBA
|
||||
help
|
||||
If you say Y here you will get access to ARM AMBA
|
||||
PrimeCell PL031 UART found on certain ARM SOCs.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called rtc-pl031.
|
||||
|
||||
config RTC_DRV_AT91RM9200
|
||||
tristate "AT91RM9200"
|
||||
depends on RTC_CLASS && ARCH_AT91RM9200
|
||||
help
|
||||
Driver for the Atmel AT91RM9200's internal RTC (Realtime Clock).
|
||||
|
||||
config RTC_DRV_TEST
|
||||
tristate "Test driver/device"
|
||||
depends on RTC_CLASS
|
||||
help
|
||||
If you say yes here you get support for the
|
||||
RTC test driver. It's a software RTC which can be
|
||||
used to test the RTC subsystem APIs. It gets
|
||||
the time from the system clock.
|
||||
You want this driver only if you are doing development
|
||||
on the RTC subsystem. Please read the source code
|
||||
for further details.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called rtc-test.
|
||||
|
||||
config RTC_DRV_MAX6902
|
||||
tristate "Maxim 6902"
|
||||
depends on RTC_CLASS && SPI
|
||||
help
|
||||
If you say yes here you will get support for the
|
||||
Maxim MAX6902 spi RTC chip.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called rtc-max6902.
|
||||
|
||||
config RTC_DRV_V3020
|
||||
tristate "EM Microelectronic V3020"
|
||||
depends on RTC_CLASS
|
||||
help
|
||||
If you say yes here you will get support for the
|
||||
EM Microelectronic v3020 RTC chip.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called rtc-v3020.
|
||||
|
||||
endmenu
|
||||
40
drivers/rtc/Makefile
Normal file
40
drivers/rtc/Makefile
Normal file
@@ -0,0 +1,40 @@
|
||||
#
|
||||
# Makefile for RTC class/drivers.
|
||||
#
|
||||
|
||||
ifeq ($(CONFIG_RTC_DEBUG),y)
|
||||
EXTRA_CFLAGS += -DDEBUG
|
||||
endif
|
||||
|
||||
obj-$(CONFIG_RTC_LIB) += rtc-lib.o
|
||||
obj-$(CONFIG_RTC_HCTOSYS) += hctosys.o
|
||||
obj-$(CONFIG_RTC_CLASS) += rtc-core.o
|
||||
rtc-core-y := class.o interface.o
|
||||
|
||||
obj-$(CONFIG_RTC_INTF_SYSFS) += rtc-sysfs.o
|
||||
obj-$(CONFIG_RTC_INTF_PROC) += rtc-proc.o
|
||||
obj-$(CONFIG_RTC_INTF_DEV) += rtc-dev.o
|
||||
|
||||
obj-$(CONFIG_RTC_DRV_CMOS) += rtc-cmos.o
|
||||
obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o
|
||||
obj-$(CONFIG_RTC_DRV_ISL1208) += rtc-isl1208.o
|
||||
obj-$(CONFIG_RTC_DRV_TEST) += rtc-test.o
|
||||
obj-$(CONFIG_RTC_DRV_DS1307) += rtc-ds1307.o
|
||||
obj-$(CONFIG_RTC_DRV_DS1672) += rtc-ds1672.o
|
||||
obj-$(CONFIG_RTC_DRV_DS1742) += rtc-ds1742.o
|
||||
obj-$(CONFIG_RTC_DRV_OMAP) += rtc-omap.o
|
||||
obj-$(CONFIG_RTC_DRV_PCF8563) += rtc-pcf8563.o
|
||||
obj-$(CONFIG_RTC_DRV_PCF8583) += rtc-pcf8583.o
|
||||
obj-$(CONFIG_RTC_DRV_RS5C372) += rtc-rs5c372.o
|
||||
obj-$(CONFIG_RTC_DRV_S3C) += rtc-s3c.o
|
||||
obj-$(CONFIG_RTC_DRV_RS5C348) += rtc-rs5c348.o
|
||||
obj-$(CONFIG_RTC_DRV_M48T86) += rtc-m48t86.o
|
||||
obj-$(CONFIG_RTC_DRV_DS1553) += rtc-ds1553.o
|
||||
obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o
|
||||
obj-$(CONFIG_RTC_DRV_SA1100) += rtc-sa1100.o
|
||||
obj-$(CONFIG_RTC_DRV_VR41XX) += rtc-vr41xx.o
|
||||
obj-$(CONFIG_RTC_DRV_PL031) += rtc-pl031.o
|
||||
obj-$(CONFIG_RTC_DRV_MAX6902) += rtc-max6902.o
|
||||
obj-$(CONFIG_RTC_DRV_V3020) += rtc-v3020.o
|
||||
obj-$(CONFIG_RTC_DRV_AT91RM9200)+= rtc-at91rm9200.o
|
||||
obj-$(CONFIG_RTC_DRV_SH) += rtc-sh.o
|
||||
156
drivers/rtc/class.c
Normal file
156
drivers/rtc/class.c
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* RTC subsystem, base class
|
||||
*
|
||||
* Copyright (C) 2005 Tower Technologies
|
||||
* Author: Alessandro Zummo <a.zummo@towertech.it>
|
||||
*
|
||||
* class skeleton from drivers/hwmon/hwmon.c
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/rtc.h>
|
||||
#include <linux/kdev_t.h>
|
||||
#include <linux/idr.h>
|
||||
|
||||
static DEFINE_IDR(rtc_idr);
|
||||
static DEFINE_MUTEX(idr_lock);
|
||||
struct class *rtc_class;
|
||||
|
||||
static void rtc_device_release(struct class_device *class_dev)
|
||||
{
|
||||
struct rtc_device *rtc = to_rtc_device(class_dev);
|
||||
mutex_lock(&idr_lock);
|
||||
idr_remove(&rtc_idr, rtc->id);
|
||||
mutex_unlock(&idr_lock);
|
||||
kfree(rtc);
|
||||
}
|
||||
|
||||
/**
|
||||
* rtc_device_register - register w/ RTC class
|
||||
* @dev: the device to register
|
||||
*
|
||||
* rtc_device_unregister() must be called when the class device is no
|
||||
* longer needed.
|
||||
*
|
||||
* Returns the pointer to the new struct class device.
|
||||
*/
|
||||
struct rtc_device *rtc_device_register(const char *name, struct device *dev,
|
||||
const struct rtc_class_ops *ops,
|
||||
struct module *owner)
|
||||
{
|
||||
struct rtc_device *rtc;
|
||||
int id, err;
|
||||
|
||||
if (idr_pre_get(&rtc_idr, GFP_KERNEL) == 0) {
|
||||
err = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
|
||||
mutex_lock(&idr_lock);
|
||||
err = idr_get_new(&rtc_idr, NULL, &id);
|
||||
mutex_unlock(&idr_lock);
|
||||
|
||||
if (err < 0)
|
||||
goto exit;
|
||||
|
||||
id = id & MAX_ID_MASK;
|
||||
|
||||
rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL);
|
||||
if (rtc == NULL) {
|
||||
err = -ENOMEM;
|
||||
goto exit_idr;
|
||||
}
|
||||
|
||||
rtc->id = id;
|
||||
rtc->ops = ops;
|
||||
rtc->owner = owner;
|
||||
rtc->max_user_freq = 64;
|
||||
rtc->class_dev.dev = dev;
|
||||
rtc->class_dev.class = rtc_class;
|
||||
rtc->class_dev.release = rtc_device_release;
|
||||
|
||||
mutex_init(&rtc->ops_lock);
|
||||
spin_lock_init(&rtc->irq_lock);
|
||||
spin_lock_init(&rtc->irq_task_lock);
|
||||
|
||||
strlcpy(rtc->name, name, RTC_DEVICE_NAME_SIZE);
|
||||
snprintf(rtc->class_dev.class_id, BUS_ID_SIZE, "rtc%d", id);
|
||||
|
||||
err = class_device_register(&rtc->class_dev);
|
||||
if (err)
|
||||
goto exit_kfree;
|
||||
|
||||
dev_info(dev, "rtc core: registered %s as %s\n",
|
||||
rtc->name, rtc->class_dev.class_id);
|
||||
|
||||
return rtc;
|
||||
|
||||
exit_kfree:
|
||||
kfree(rtc);
|
||||
|
||||
exit_idr:
|
||||
mutex_lock(&idr_lock);
|
||||
idr_remove(&rtc_idr, id);
|
||||
mutex_unlock(&idr_lock);
|
||||
|
||||
exit:
|
||||
dev_err(dev, "rtc core: unable to register %s, err = %d\n",
|
||||
name, err);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rtc_device_register);
|
||||
|
||||
|
||||
/**
|
||||
* rtc_device_unregister - removes the previously registered RTC class device
|
||||
*
|
||||
* @rtc: the RTC class device to destroy
|
||||
*/
|
||||
void rtc_device_unregister(struct rtc_device *rtc)
|
||||
{
|
||||
if (class_device_get(&rtc->class_dev) != NULL) {
|
||||
mutex_lock(&rtc->ops_lock);
|
||||
/* remove innards of this RTC, then disable it, before
|
||||
* letting any rtc_class_open() users access it again
|
||||
*/
|
||||
class_device_unregister(&rtc->class_dev);
|
||||
rtc->ops = NULL;
|
||||
mutex_unlock(&rtc->ops_lock);
|
||||
class_device_put(&rtc->class_dev);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rtc_device_unregister);
|
||||
|
||||
int rtc_interface_register(struct class_interface *intf)
|
||||
{
|
||||
intf->class = rtc_class;
|
||||
return class_interface_register(intf);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rtc_interface_register);
|
||||
|
||||
static int __init rtc_init(void)
|
||||
{
|
||||
rtc_class = class_create(THIS_MODULE, "rtc");
|
||||
if (IS_ERR(rtc_class)) {
|
||||
printk(KERN_ERR "%s: couldn't create class\n", __FILE__);
|
||||
return PTR_ERR(rtc_class);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit rtc_exit(void)
|
||||
{
|
||||
class_destroy(rtc_class);
|
||||
}
|
||||
|
||||
subsys_initcall(rtc_init);
|
||||
module_exit(rtc_exit);
|
||||
|
||||
MODULE_AUTHOR("Alessandro Zummo <a.zummo@towertech.it>");
|
||||
MODULE_DESCRIPTION("RTC class support");
|
||||
MODULE_LICENSE("GPL");
|
||||
69
drivers/rtc/hctosys.c
Normal file
69
drivers/rtc/hctosys.c
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* RTC subsystem, initialize system time on startup
|
||||
*
|
||||
* Copyright (C) 2005 Tower Technologies
|
||||
* Author: Alessandro Zummo <a.zummo@towertech.it>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/rtc.h>
|
||||
|
||||
/* IMPORTANT: the RTC only stores whole seconds. It is arbitrary
|
||||
* whether it stores the most close value or the value with partial
|
||||
* seconds truncated. However, it is important that we use it to store
|
||||
* the truncated value. This is because otherwise it is necessary,
|
||||
* in an rtc sync function, to read both xtime.tv_sec and
|
||||
* xtime.tv_nsec. On some processors (i.e. ARM), an atomic read
|
||||
* of >32bits is not possible. So storing the most close value would
|
||||
* slow down the sync API. So here we have the truncated value and
|
||||
* the best guess is to add 0.5s.
|
||||
*/
|
||||
|
||||
static int __init rtc_hctosys(void)
|
||||
{
|
||||
int err;
|
||||
struct rtc_time tm;
|
||||
struct class_device *class_dev = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE);
|
||||
|
||||
if (class_dev == NULL) {
|
||||
printk("%s: unable to open rtc device (%s)\n",
|
||||
__FILE__, CONFIG_RTC_HCTOSYS_DEVICE);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
err = rtc_read_time(class_dev, &tm);
|
||||
if (err == 0) {
|
||||
err = rtc_valid_tm(&tm);
|
||||
if (err == 0) {
|
||||
struct timespec tv;
|
||||
|
||||
tv.tv_nsec = NSEC_PER_SEC >> 1;
|
||||
|
||||
rtc_tm_to_time(&tm, &tv.tv_sec);
|
||||
|
||||
do_settimeofday(&tv);
|
||||
|
||||
dev_info(class_dev->dev,
|
||||
"setting the system clock to "
|
||||
"%d-%02d-%02d %02d:%02d:%02d (%u)\n",
|
||||
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
|
||||
tm.tm_hour, tm.tm_min, tm.tm_sec,
|
||||
(unsigned int) tv.tv_sec);
|
||||
}
|
||||
else
|
||||
dev_err(class_dev->dev,
|
||||
"hctosys: invalid date/time\n");
|
||||
}
|
||||
else
|
||||
dev_err(class_dev->dev,
|
||||
"hctosys: unable to read the hardware clock\n");
|
||||
|
||||
rtc_class_close(class_dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
late_initcall(rtc_hctosys);
|
||||
276
drivers/rtc/interface.c
Normal file
276
drivers/rtc/interface.c
Normal file
@@ -0,0 +1,276 @@
|
||||
/*
|
||||
* RTC subsystem, interface functions
|
||||
*
|
||||
* Copyright (C) 2005 Tower Technologies
|
||||
* Author: Alessandro Zummo <a.zummo@towertech.it>
|
||||
*
|
||||
* based on arch/arm/common/rtctime.c
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/rtc.h>
|
||||
|
||||
int rtc_read_time(struct class_device *class_dev, struct rtc_time *tm)
|
||||
{
|
||||
int err;
|
||||
struct rtc_device *rtc = to_rtc_device(class_dev);
|
||||
|
||||
err = mutex_lock_interruptible(&rtc->ops_lock);
|
||||
if (err)
|
||||
return -EBUSY;
|
||||
|
||||
if (!rtc->ops)
|
||||
err = -ENODEV;
|
||||
else if (!rtc->ops->read_time)
|
||||
err = -EINVAL;
|
||||
else {
|
||||
memset(tm, 0, sizeof(struct rtc_time));
|
||||
err = rtc->ops->read_time(class_dev->dev, tm);
|
||||
}
|
||||
|
||||
mutex_unlock(&rtc->ops_lock);
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rtc_read_time);
|
||||
|
||||
int rtc_set_time(struct class_device *class_dev, struct rtc_time *tm)
|
||||
{
|
||||
int err;
|
||||
struct rtc_device *rtc = to_rtc_device(class_dev);
|
||||
|
||||
err = rtc_valid_tm(tm);
|
||||
if (err != 0)
|
||||
return err;
|
||||
|
||||
err = mutex_lock_interruptible(&rtc->ops_lock);
|
||||
if (err)
|
||||
return -EBUSY;
|
||||
|
||||
if (!rtc->ops)
|
||||
err = -ENODEV;
|
||||
else if (!rtc->ops->set_time)
|
||||
err = -EINVAL;
|
||||
else
|
||||
err = rtc->ops->set_time(class_dev->dev, tm);
|
||||
|
||||
mutex_unlock(&rtc->ops_lock);
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rtc_set_time);
|
||||
|
||||
int rtc_set_mmss(struct class_device *class_dev, unsigned long secs)
|
||||
{
|
||||
int err;
|
||||
struct rtc_device *rtc = to_rtc_device(class_dev);
|
||||
|
||||
err = mutex_lock_interruptible(&rtc->ops_lock);
|
||||
if (err)
|
||||
return -EBUSY;
|
||||
|
||||
if (!rtc->ops)
|
||||
err = -ENODEV;
|
||||
else if (rtc->ops->set_mmss)
|
||||
err = rtc->ops->set_mmss(class_dev->dev, secs);
|
||||
else if (rtc->ops->read_time && rtc->ops->set_time) {
|
||||
struct rtc_time new, old;
|
||||
|
||||
err = rtc->ops->read_time(class_dev->dev, &old);
|
||||
if (err == 0) {
|
||||
rtc_time_to_tm(secs, &new);
|
||||
|
||||
/*
|
||||
* avoid writing when we're going to change the day of
|
||||
* the month. We will retry in the next minute. This
|
||||
* basically means that if the RTC must not drift
|
||||
* by more than 1 minute in 11 minutes.
|
||||
*/
|
||||
if (!((old.tm_hour == 23 && old.tm_min == 59) ||
|
||||
(new.tm_hour == 23 && new.tm_min == 59)))
|
||||
err = rtc->ops->set_time(class_dev->dev, &new);
|
||||
}
|
||||
}
|
||||
else
|
||||
err = -EINVAL;
|
||||
|
||||
mutex_unlock(&rtc->ops_lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rtc_set_mmss);
|
||||
|
||||
int rtc_read_alarm(struct class_device *class_dev, struct rtc_wkalrm *alarm)
|
||||
{
|
||||
int err;
|
||||
struct rtc_device *rtc = to_rtc_device(class_dev);
|
||||
|
||||
err = mutex_lock_interruptible(&rtc->ops_lock);
|
||||
if (err)
|
||||
return -EBUSY;
|
||||
|
||||
if (rtc->ops == NULL)
|
||||
err = -ENODEV;
|
||||
else if (!rtc->ops->read_alarm)
|
||||
err = -EINVAL;
|
||||
else {
|
||||
memset(alarm, 0, sizeof(struct rtc_wkalrm));
|
||||
err = rtc->ops->read_alarm(class_dev->dev, alarm);
|
||||
}
|
||||
|
||||
mutex_unlock(&rtc->ops_lock);
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rtc_read_alarm);
|
||||
|
||||
int rtc_set_alarm(struct class_device *class_dev, struct rtc_wkalrm *alarm)
|
||||
{
|
||||
int err;
|
||||
struct rtc_device *rtc = to_rtc_device(class_dev);
|
||||
|
||||
err = mutex_lock_interruptible(&rtc->ops_lock);
|
||||
if (err)
|
||||
return -EBUSY;
|
||||
|
||||
if (!rtc->ops)
|
||||
err = -ENODEV;
|
||||
else if (!rtc->ops->set_alarm)
|
||||
err = -EINVAL;
|
||||
else
|
||||
err = rtc->ops->set_alarm(class_dev->dev, alarm);
|
||||
|
||||
mutex_unlock(&rtc->ops_lock);
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rtc_set_alarm);
|
||||
|
||||
/**
|
||||
* rtc_update_irq - report RTC periodic, alarm, and/or update irqs
|
||||
* @class_dev: the rtc's class device
|
||||
* @num: how many irqs are being reported (usually one)
|
||||
* @events: mask of RTC_IRQF with one or more of RTC_PF, RTC_AF, RTC_UF
|
||||
* Context: in_interrupt(), irqs blocked
|
||||
*/
|
||||
void rtc_update_irq(struct class_device *class_dev,
|
||||
unsigned long num, unsigned long events)
|
||||
{
|
||||
struct rtc_device *rtc = to_rtc_device(class_dev);
|
||||
|
||||
spin_lock(&rtc->irq_lock);
|
||||
rtc->irq_data = (rtc->irq_data + (num << 8)) | events;
|
||||
spin_unlock(&rtc->irq_lock);
|
||||
|
||||
spin_lock(&rtc->irq_task_lock);
|
||||
if (rtc->irq_task)
|
||||
rtc->irq_task->func(rtc->irq_task->private_data);
|
||||
spin_unlock(&rtc->irq_task_lock);
|
||||
|
||||
wake_up_interruptible(&rtc->irq_queue);
|
||||
kill_fasync(&rtc->async_queue, SIGIO, POLL_IN);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rtc_update_irq);
|
||||
|
||||
struct class_device *rtc_class_open(char *name)
|
||||
{
|
||||
struct class_device *class_dev = NULL,
|
||||
*class_dev_tmp;
|
||||
|
||||
down(&rtc_class->sem);
|
||||
list_for_each_entry(class_dev_tmp, &rtc_class->children, node) {
|
||||
if (strncmp(class_dev_tmp->class_id, name, BUS_ID_SIZE) == 0) {
|
||||
class_dev = class_device_get(class_dev_tmp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (class_dev) {
|
||||
if (!try_module_get(to_rtc_device(class_dev)->owner))
|
||||
class_dev = NULL;
|
||||
}
|
||||
up(&rtc_class->sem);
|
||||
|
||||
return class_dev;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rtc_class_open);
|
||||
|
||||
void rtc_class_close(struct class_device *class_dev)
|
||||
{
|
||||
module_put(to_rtc_device(class_dev)->owner);
|
||||
class_device_put(class_dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rtc_class_close);
|
||||
|
||||
int rtc_irq_register(struct class_device *class_dev, struct rtc_task *task)
|
||||
{
|
||||
int retval = -EBUSY;
|
||||
struct rtc_device *rtc = to_rtc_device(class_dev);
|
||||
|
||||
if (task == NULL || task->func == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock_irq(&rtc->irq_task_lock);
|
||||
if (rtc->irq_task == NULL) {
|
||||
rtc->irq_task = task;
|
||||
retval = 0;
|
||||
}
|
||||
spin_unlock_irq(&rtc->irq_task_lock);
|
||||
|
||||
return retval;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rtc_irq_register);
|
||||
|
||||
void rtc_irq_unregister(struct class_device *class_dev, struct rtc_task *task)
|
||||
{
|
||||
struct rtc_device *rtc = to_rtc_device(class_dev);
|
||||
|
||||
spin_lock_irq(&rtc->irq_task_lock);
|
||||
if (rtc->irq_task == task)
|
||||
rtc->irq_task = NULL;
|
||||
spin_unlock_irq(&rtc->irq_task_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rtc_irq_unregister);
|
||||
|
||||
int rtc_irq_set_state(struct class_device *class_dev, struct rtc_task *task, int enabled)
|
||||
{
|
||||
int err = 0;
|
||||
unsigned long flags;
|
||||
struct rtc_device *rtc = to_rtc_device(class_dev);
|
||||
|
||||
if (rtc->ops->irq_set_state == NULL)
|
||||
return -ENXIO;
|
||||
|
||||
spin_lock_irqsave(&rtc->irq_task_lock, flags);
|
||||
if (rtc->irq_task != task)
|
||||
err = -ENXIO;
|
||||
spin_unlock_irqrestore(&rtc->irq_task_lock, flags);
|
||||
|
||||
if (err == 0)
|
||||
err = rtc->ops->irq_set_state(class_dev->dev, enabled);
|
||||
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rtc_irq_set_state);
|
||||
|
||||
int rtc_irq_set_freq(struct class_device *class_dev, struct rtc_task *task, int freq)
|
||||
{
|
||||
int err = 0;
|
||||
unsigned long flags;
|
||||
struct rtc_device *rtc = to_rtc_device(class_dev);
|
||||
|
||||
if (rtc->ops->irq_set_freq == NULL)
|
||||
return -ENXIO;
|
||||
|
||||
spin_lock_irqsave(&rtc->irq_task_lock, flags);
|
||||
if (rtc->irq_task != task)
|
||||
err = -ENXIO;
|
||||
spin_unlock_irqrestore(&rtc->irq_task_lock, flags);
|
||||
|
||||
if (err == 0) {
|
||||
err = rtc->ops->irq_set_freq(class_dev->dev, freq);
|
||||
if (err == 0)
|
||||
rtc->irq_freq = freq;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rtc_irq_set_freq);
|
||||
439
drivers/rtc/rtc-at91rm9200.c
Normal file
439
drivers/rtc/rtc-at91rm9200.c
Normal file
@@ -0,0 +1,439 @@
|
||||
/*
|
||||
* Real Time Clock interface for Linux on Atmel AT91RM9200
|
||||
*
|
||||
* Copyright (C) 2002 Rick Bronson
|
||||
*
|
||||
* Converted to RTC class model by Andrew Victor
|
||||
*
|
||||
* Ported to Linux 2.6 by Steven Scholz
|
||||
* Based on s3c2410-rtc.c Simtec Electronics
|
||||
*
|
||||
* Based on sa1100-rtc.c by Nils Faerber
|
||||
* Based on rtc.c 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/kernel.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/rtc.h>
|
||||
#include <linux/bcd.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/ioctl.h>
|
||||
#include <linux/completion.h>
|
||||
|
||||
#include <asm/uaccess.h>
|
||||
#include <asm/rtc.h>
|
||||
|
||||
#include <asm/mach/time.h>
|
||||
|
||||
#include <asm/arch/at91_rtc.h>
|
||||
|
||||
|
||||
#define AT91_RTC_FREQ 1
|
||||
#define AT91_RTC_EPOCH 1900UL /* just like arch/arm/common/rtctime.c */
|
||||
|
||||
static DECLARE_COMPLETION(at91_rtc_updated);
|
||||
static unsigned int at91_alarm_year = AT91_RTC_EPOCH;
|
||||
|
||||
/*
|
||||
* Decode time/date into rtc_time structure
|
||||
*/
|
||||
static void at91_rtc_decodetime(unsigned int timereg, unsigned int calreg,
|
||||
struct rtc_time *tm)
|
||||
{
|
||||
unsigned int time, date;
|
||||
|
||||
/* must read twice in case it changes */
|
||||
do {
|
||||
time = at91_sys_read(timereg);
|
||||
date = at91_sys_read(calreg);
|
||||
} while ((time != at91_sys_read(timereg)) ||
|
||||
(date != at91_sys_read(calreg)));
|
||||
|
||||
tm->tm_sec = BCD2BIN((time & AT91_RTC_SEC) >> 0);
|
||||
tm->tm_min = BCD2BIN((time & AT91_RTC_MIN) >> 8);
|
||||
tm->tm_hour = BCD2BIN((time & AT91_RTC_HOUR) >> 16);
|
||||
|
||||
/*
|
||||
* The Calendar Alarm register does not have a field for
|
||||
* the year - so these will return an invalid value. When an
|
||||
* alarm is set, at91_alarm_year wille store the current year.
|
||||
*/
|
||||
tm->tm_year = BCD2BIN(date & AT91_RTC_CENT) * 100; /* century */
|
||||
tm->tm_year += BCD2BIN((date & AT91_RTC_YEAR) >> 8); /* year */
|
||||
|
||||
tm->tm_wday = BCD2BIN((date & AT91_RTC_DAY) >> 21) - 1; /* day of the week [0-6], Sunday=0 */
|
||||
tm->tm_mon = BCD2BIN((date & AT91_RTC_MONTH) >> 16) - 1;
|
||||
tm->tm_mday = BCD2BIN((date & AT91_RTC_DATE) >> 24);
|
||||
}
|
||||
|
||||
/*
|
||||
* Read current time and date in RTC
|
||||
*/
|
||||
static int at91_rtc_readtime(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
at91_rtc_decodetime(AT91_RTC_TIMR, AT91_RTC_CALR, tm);
|
||||
tm->tm_yday = rtc_year_days(tm->tm_mday, tm->tm_mon, tm->tm_year);
|
||||
tm->tm_year = tm->tm_year - 1900;
|
||||
|
||||
pr_debug("%s(): %4d-%02d-%02d %02d:%02d:%02d\n", __FUNCTION__,
|
||||
1900 + tm->tm_year, tm->tm_mon, tm->tm_mday,
|
||||
tm->tm_hour, tm->tm_min, tm->tm_sec);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set current time and date in RTC
|
||||
*/
|
||||
static int at91_rtc_settime(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
unsigned long cr;
|
||||
|
||||
pr_debug("%s(): %4d-%02d-%02d %02d:%02d:%02d\n", __FUNCTION__,
|
||||
1900 + tm->tm_year, tm->tm_mon, tm->tm_mday,
|
||||
tm->tm_hour, tm->tm_min, tm->tm_sec);
|
||||
|
||||
/* Stop Time/Calendar from counting */
|
||||
cr = at91_sys_read(AT91_RTC_CR);
|
||||
at91_sys_write(AT91_RTC_CR, cr | AT91_RTC_UPDCAL | AT91_RTC_UPDTIM);
|
||||
|
||||
at91_sys_write(AT91_RTC_IER, AT91_RTC_ACKUPD);
|
||||
wait_for_completion(&at91_rtc_updated); /* wait for ACKUPD interrupt */
|
||||
at91_sys_write(AT91_RTC_IDR, AT91_RTC_ACKUPD);
|
||||
|
||||
at91_sys_write(AT91_RTC_TIMR,
|
||||
BIN2BCD(tm->tm_sec) << 0
|
||||
| BIN2BCD(tm->tm_min) << 8
|
||||
| BIN2BCD(tm->tm_hour) << 16);
|
||||
|
||||
at91_sys_write(AT91_RTC_CALR,
|
||||
BIN2BCD((tm->tm_year + 1900) / 100) /* century */
|
||||
| BIN2BCD(tm->tm_year % 100) << 8 /* year */
|
||||
| BIN2BCD(tm->tm_mon + 1) << 16 /* tm_mon starts at zero */
|
||||
| BIN2BCD(tm->tm_wday + 1) << 21 /* day of the week [0-6], Sunday=0 */
|
||||
| BIN2BCD(tm->tm_mday) << 24);
|
||||
|
||||
/* Restart Time/Calendar */
|
||||
cr = at91_sys_read(AT91_RTC_CR);
|
||||
at91_sys_write(AT91_RTC_CR, cr & ~(AT91_RTC_UPDCAL | AT91_RTC_UPDTIM));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read alarm time and date in RTC
|
||||
*/
|
||||
static int at91_rtc_readalarm(struct device *dev, struct rtc_wkalrm *alrm)
|
||||
{
|
||||
struct rtc_time *tm = &alrm->time;
|
||||
|
||||
at91_rtc_decodetime(AT91_RTC_TIMALR, AT91_RTC_CALALR, tm);
|
||||
tm->tm_yday = rtc_year_days(tm->tm_mday, tm->tm_mon, tm->tm_year);
|
||||
tm->tm_year = at91_alarm_year - 1900;
|
||||
|
||||
alrm->enabled = (at91_sys_read(AT91_RTC_IMR) & AT91_RTC_ALARM)
|
||||
? 1 : 0;
|
||||
|
||||
pr_debug("%s(): %4d-%02d-%02d %02d:%02d:%02d\n", __FUNCTION__,
|
||||
1900 + tm->tm_year, tm->tm_mon, tm->tm_mday,
|
||||
tm->tm_hour, tm->tm_min, tm->tm_sec);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set alarm time and date in RTC
|
||||
*/
|
||||
static int at91_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
|
||||
{
|
||||
struct rtc_time tm;
|
||||
|
||||
at91_rtc_decodetime(AT91_RTC_TIMR, AT91_RTC_CALR, &tm);
|
||||
|
||||
at91_alarm_year = tm.tm_year;
|
||||
|
||||
tm.tm_hour = alrm->time.tm_hour;
|
||||
tm.tm_min = alrm->time.tm_min;
|
||||
tm.tm_sec = alrm->time.tm_sec;
|
||||
|
||||
at91_sys_write(AT91_RTC_IDR, AT91_RTC_ALARM);
|
||||
at91_sys_write(AT91_RTC_TIMALR,
|
||||
BIN2BCD(tm.tm_sec) << 0
|
||||
| BIN2BCD(tm.tm_min) << 8
|
||||
| BIN2BCD(tm.tm_hour) << 16
|
||||
| AT91_RTC_HOUREN | AT91_RTC_MINEN | AT91_RTC_SECEN);
|
||||
at91_sys_write(AT91_RTC_CALALR,
|
||||
BIN2BCD(tm.tm_mon + 1) << 16 /* tm_mon starts at zero */
|
||||
| BIN2BCD(tm.tm_mday) << 24
|
||||
| AT91_RTC_DATEEN | AT91_RTC_MTHEN);
|
||||
|
||||
if (alrm->enabled)
|
||||
at91_sys_write(AT91_RTC_IER, AT91_RTC_ALARM);
|
||||
|
||||
pr_debug("%s(): %4d-%02d-%02d %02d:%02d:%02d\n", __FUNCTION__,
|
||||
at91_alarm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour,
|
||||
tm.tm_min, tm.tm_sec);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle commands from user-space
|
||||
*/
|
||||
static int at91_rtc_ioctl(struct device *dev, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
pr_debug("%s(): cmd=%08x, arg=%08lx.\n", __FUNCTION__, cmd, arg);
|
||||
|
||||
switch (cmd) {
|
||||
case RTC_AIE_OFF: /* alarm off */
|
||||
at91_sys_write(AT91_RTC_IDR, AT91_RTC_ALARM);
|
||||
break;
|
||||
case RTC_AIE_ON: /* alarm on */
|
||||
at91_sys_write(AT91_RTC_IER, AT91_RTC_ALARM);
|
||||
break;
|
||||
case RTC_UIE_OFF: /* update off */
|
||||
case RTC_PIE_OFF: /* periodic off */
|
||||
at91_sys_write(AT91_RTC_IDR, AT91_RTC_SECEV);
|
||||
break;
|
||||
case RTC_UIE_ON: /* update on */
|
||||
case RTC_PIE_ON: /* periodic on */
|
||||
at91_sys_write(AT91_RTC_IER, AT91_RTC_SECEV);
|
||||
break;
|
||||
case RTC_IRQP_READ: /* read periodic alarm frequency */
|
||||
ret = put_user(AT91_RTC_FREQ, (unsigned long *) arg);
|
||||
break;
|
||||
case RTC_IRQP_SET: /* set periodic alarm frequency */
|
||||
if (arg != AT91_RTC_FREQ)
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
default:
|
||||
ret = -ENOIOCTLCMD;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Provide additional RTC information in /proc/driver/rtc
|
||||
*/
|
||||
static int at91_rtc_proc(struct device *dev, struct seq_file *seq)
|
||||
{
|
||||
unsigned long imr = at91_sys_read(AT91_RTC_IMR);
|
||||
|
||||
seq_printf(seq, "update_IRQ\t: %s\n",
|
||||
(imr & AT91_RTC_ACKUPD) ? "yes" : "no");
|
||||
seq_printf(seq, "periodic_IRQ\t: %s\n",
|
||||
(imr & AT91_RTC_SECEV) ? "yes" : "no");
|
||||
seq_printf(seq, "periodic_freq\t: %ld\n",
|
||||
(unsigned long) AT91_RTC_FREQ);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* IRQ handler for the RTC
|
||||
*/
|
||||
static irqreturn_t at91_rtc_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct platform_device *pdev = dev_id;
|
||||
struct rtc_device *rtc = platform_get_drvdata(pdev);
|
||||
unsigned int rtsr;
|
||||
unsigned long events = 0;
|
||||
|
||||
rtsr = at91_sys_read(AT91_RTC_SR) & at91_sys_read(AT91_RTC_IMR);
|
||||
if (rtsr) { /* this interrupt is shared! Is it ours? */
|
||||
if (rtsr & AT91_RTC_ALARM)
|
||||
events |= (RTC_AF | RTC_IRQF);
|
||||
if (rtsr & AT91_RTC_SECEV)
|
||||
events |= (RTC_UF | RTC_IRQF);
|
||||
if (rtsr & AT91_RTC_ACKUPD)
|
||||
complete(&at91_rtc_updated);
|
||||
|
||||
at91_sys_write(AT91_RTC_SCCR, rtsr); /* clear status reg */
|
||||
|
||||
rtc_update_irq(&rtc->class_dev, 1, events);
|
||||
|
||||
pr_debug("%s(): num=%ld, events=0x%02lx\n", __FUNCTION__,
|
||||
events >> 8, events & 0x000000FF);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
return IRQ_NONE; /* not handled */
|
||||
}
|
||||
|
||||
static const struct rtc_class_ops at91_rtc_ops = {
|
||||
.ioctl = at91_rtc_ioctl,
|
||||
.read_time = at91_rtc_readtime,
|
||||
.set_time = at91_rtc_settime,
|
||||
.read_alarm = at91_rtc_readalarm,
|
||||
.set_alarm = at91_rtc_setalarm,
|
||||
.proc = at91_rtc_proc,
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialize and install RTC driver
|
||||
*/
|
||||
static int __init at91_rtc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct rtc_device *rtc;
|
||||
int ret;
|
||||
|
||||
at91_sys_write(AT91_RTC_CR, 0);
|
||||
at91_sys_write(AT91_RTC_MR, 0); /* 24 hour mode */
|
||||
|
||||
/* Disable all interrupts */
|
||||
at91_sys_write(AT91_RTC_IDR, AT91_RTC_ACKUPD | AT91_RTC_ALARM |
|
||||
AT91_RTC_SECEV | AT91_RTC_TIMEV |
|
||||
AT91_RTC_CALEV);
|
||||
|
||||
ret = request_irq(AT91_ID_SYS, at91_rtc_interrupt,
|
||||
IRQF_DISABLED | IRQF_SHARED,
|
||||
"at91_rtc", pdev);
|
||||
if (ret) {
|
||||
printk(KERN_ERR "at91_rtc: IRQ %d already in use.\n",
|
||||
AT91_ID_SYS);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* cpu init code should really have flagged this device as
|
||||
* being wake-capable; if it didn't, do that here.
|
||||
*/
|
||||
if (!device_can_wakeup(&pdev->dev))
|
||||
device_init_wakeup(&pdev->dev, 1);
|
||||
|
||||
rtc = rtc_device_register(pdev->name, &pdev->dev,
|
||||
&at91_rtc_ops, THIS_MODULE);
|
||||
if (IS_ERR(rtc)) {
|
||||
free_irq(AT91_ID_SYS, pdev);
|
||||
return PTR_ERR(rtc);
|
||||
}
|
||||
platform_set_drvdata(pdev, rtc);
|
||||
|
||||
printk(KERN_INFO "AT91 Real Time Clock driver.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable and remove the RTC driver
|
||||
*/
|
||||
static int __exit at91_rtc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct rtc_device *rtc = platform_get_drvdata(pdev);
|
||||
|
||||
/* Disable all interrupts */
|
||||
at91_sys_write(AT91_RTC_IDR, AT91_RTC_ACKUPD | AT91_RTC_ALARM |
|
||||
AT91_RTC_SECEV | AT91_RTC_TIMEV |
|
||||
AT91_RTC_CALEV);
|
||||
free_irq(AT91_ID_SYS, pdev);
|
||||
|
||||
rtc_device_unregister(rtc);
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
/* AT91RM9200 RTC Power management control */
|
||||
|
||||
static struct timespec at91_rtc_delta;
|
||||
static u32 at91_rtc_imr;
|
||||
|
||||
static int at91_rtc_suspend(struct platform_device *pdev, pm_message_t state)
|
||||
{
|
||||
struct rtc_time tm;
|
||||
struct timespec time;
|
||||
|
||||
time.tv_nsec = 0;
|
||||
|
||||
/* calculate time delta for suspend */
|
||||
at91_rtc_readtime(&pdev->dev, &tm);
|
||||
rtc_tm_to_time(&tm, &time.tv_sec);
|
||||
save_time_delta(&at91_rtc_delta, &time);
|
||||
|
||||
/* this IRQ is shared with DBGU and other hardware which isn't
|
||||
* necessarily doing PM like we are...
|
||||
*/
|
||||
at91_rtc_imr = at91_sys_read(AT91_RTC_IMR)
|
||||
& (AT91_RTC_ALARM|AT91_RTC_SECEV);
|
||||
if (at91_rtc_imr) {
|
||||
if (device_may_wakeup(&pdev->dev))
|
||||
enable_irq_wake(AT91_ID_SYS);
|
||||
else
|
||||
at91_sys_write(AT91_RTC_IDR, at91_rtc_imr);
|
||||
}
|
||||
|
||||
pr_debug("%s(): %4d-%02d-%02d %02d:%02d:%02d\n", __FUNCTION__,
|
||||
1900 + tm.tm_year, tm.tm_mon, tm.tm_mday,
|
||||
tm.tm_hour, tm.tm_min, tm.tm_sec);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int at91_rtc_resume(struct platform_device *pdev)
|
||||
{
|
||||
struct rtc_time tm;
|
||||
struct timespec time;
|
||||
|
||||
time.tv_nsec = 0;
|
||||
|
||||
at91_rtc_readtime(&pdev->dev, &tm);
|
||||
rtc_tm_to_time(&tm, &time.tv_sec);
|
||||
restore_time_delta(&at91_rtc_delta, &time);
|
||||
|
||||
if (at91_rtc_imr) {
|
||||
if (device_may_wakeup(&pdev->dev))
|
||||
disable_irq_wake(AT91_ID_SYS);
|
||||
else
|
||||
at91_sys_write(AT91_RTC_IER, at91_rtc_imr);
|
||||
}
|
||||
|
||||
pr_debug("%s(): %4d-%02d-%02d %02d:%02d:%02d\n", __FUNCTION__,
|
||||
1900 + tm.tm_year, tm.tm_mon, tm.tm_mday,
|
||||
tm.tm_hour, tm.tm_min, tm.tm_sec);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#define at91_rtc_suspend NULL
|
||||
#define at91_rtc_resume NULL
|
||||
#endif
|
||||
|
||||
static struct platform_driver at91_rtc_driver = {
|
||||
.remove = __exit_p(at91_rtc_remove),
|
||||
.suspend = at91_rtc_suspend,
|
||||
.resume = at91_rtc_resume,
|
||||
.driver = {
|
||||
.name = "at91_rtc",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init at91_rtc_init(void)
|
||||
{
|
||||
return platform_driver_probe(&at91_rtc_driver, at91_rtc_probe);
|
||||
}
|
||||
|
||||
static void __exit at91_rtc_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&at91_rtc_driver);
|
||||
}
|
||||
|
||||
module_init(at91_rtc_init);
|
||||
module_exit(at91_rtc_exit);
|
||||
|
||||
MODULE_AUTHOR("Rick Bronson");
|
||||
MODULE_DESCRIPTION("RTC driver for Atmel AT91RM9200");
|
||||
MODULE_LICENSE("GPL");
|
||||
741
drivers/rtc/rtc-cmos.c
Normal file
741
drivers/rtc/rtc-cmos.c
Normal file
@@ -0,0 +1,741 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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) {
|
||||
t->time.tm_mday = CMOS_READ(cmos->day_alrm);
|
||||
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->class_dev, 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->class_dev, 1, rtc_intr);
|
||||
}
|
||||
|
||||
spin_unlock_irq(&rtc_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmos_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 != 0) {
|
||||
if (f-- > 16 || freq != (1 << f))
|
||||
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;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_RTC_INTF_DEV) || defined(CONFIG_RTC_INTF_DEV_MODULE)
|
||||
|
||||
static int
|
||||
cmos_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->class_dev, 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
|
||||
#endif
|
||||
|
||||
static 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_set_freq,
|
||||
};
|
||||
|
||||
/*----------------------------------------------------------------*/
|
||||
|
||||
static struct cmos_rtc cmos_rtc;
|
||||
|
||||
static irqreturn_t cmos_interrupt(int irq, void *p)
|
||||
{
|
||||
u8 irqstat;
|
||||
|
||||
spin_lock(&rtc_lock);
|
||||
irqstat = CMOS_READ(RTC_INTR_FLAGS);
|
||||
irqstat &= (CMOS_READ(RTC_CONTROL) & RTC_IRQMASK) | RTC_IRQF;
|
||||
spin_unlock(&rtc_lock);
|
||||
|
||||
if (is_intr(irqstat)) {
|
||||
rtc_update_irq(p, 1, irqstat);
|
||||
return IRQ_HANDLED;
|
||||
} else
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PNPACPI
|
||||
#define is_pnpacpi() 1
|
||||
#define INITSECTION
|
||||
|
||||
#else
|
||||
#define is_pnpacpi() 0
|
||||
#define INITSECTION __init
|
||||
#endif
|
||||
|
||||
static int INITSECTION
|
||||
cmos_do_probe(struct device *dev, struct resource *ports, int rtc_irq)
|
||||
{
|
||||
struct cmos_rtc_board_info *info = dev->platform_data;
|
||||
int retval = 0;
|
||||
unsigned char rtc_control;
|
||||
|
||||
/* there can be only one ... */
|
||||
if (cmos_rtc.dev)
|
||||
return -EBUSY;
|
||||
|
||||
if (!ports)
|
||||
return -ENODEV;
|
||||
|
||||
cmos_rtc.irq = rtc_irq;
|
||||
cmos_rtc.iomem = ports;
|
||||
|
||||
/* For ACPI systems the info comes from the FADT. On others,
|
||||
* board specific setup provides it as appropriate.
|
||||
*/
|
||||
if (info) {
|
||||
cmos_rtc.day_alrm = info->rtc_day_alarm;
|
||||
cmos_rtc.mon_alrm = info->rtc_mon_alarm;
|
||||
cmos_rtc.century = info->rtc_century;
|
||||
}
|
||||
|
||||
cmos_rtc.rtc = rtc_device_register(driver_name, dev,
|
||||
&cmos_rtc_ops, THIS_MODULE);
|
||||
if (IS_ERR(cmos_rtc.rtc))
|
||||
return PTR_ERR(cmos_rtc.rtc);
|
||||
|
||||
cmos_rtc.dev = dev;
|
||||
dev_set_drvdata(dev, &cmos_rtc);
|
||||
|
||||
/* platform and pnp busses handle resources incompatibly.
|
||||
*
|
||||
* REVISIT for non-x86 systems we may need to handle io memory
|
||||
* resources: ioremap them, and request_mem_region().
|
||||
*/
|
||||
if (is_pnpacpi()) {
|
||||
retval = request_resource(&ioport_resource, ports);
|
||||
if (retval < 0) {
|
||||
dev_dbg(dev, "i/o registers already in use\n");
|
||||
goto cleanup0;
|
||||
}
|
||||
}
|
||||
rename_region(ports, cmos_rtc.rtc->class_dev.class_id);
|
||||
|
||||
spin_lock_irq(&rtc_lock);
|
||||
|
||||
/* force periodic irq to CMOS reset default of 1024Hz;
|
||||
*
|
||||
* REVISIT it's been reported that at least one x86_64 ALI mobo
|
||||
* doesn't use 32KHz here ... for portability we might need to
|
||||
* do something about other clock frequencies.
|
||||
*/
|
||||
CMOS_WRITE(RTC_REF_CLCK_32KHZ | 0x06, RTC_FREQ_SELECT);
|
||||
cmos_rtc.rtc->irq_freq = 1024;
|
||||
|
||||
/* disable irqs.
|
||||
*
|
||||
* NOTE after changing RTC_xIE bits we always read INTR_FLAGS;
|
||||
* allegedly some older rtcs need that to handle irqs properly
|
||||
*/
|
||||
rtc_control = CMOS_READ(RTC_CONTROL);
|
||||
rtc_control &= ~(RTC_PIE | RTC_AIE | RTC_UIE);
|
||||
CMOS_WRITE(rtc_control, RTC_CONTROL);
|
||||
CMOS_READ(RTC_INTR_FLAGS);
|
||||
|
||||
spin_unlock_irq(&rtc_lock);
|
||||
|
||||
/* FIXME teach the alarm code how to handle binary mode;
|
||||
* <asm-generic/rtc.h> doesn't know 12-hour mode either.
|
||||
*/
|
||||
if (!(rtc_control & RTC_24H) || (rtc_control & (RTC_DM_BINARY))) {
|
||||
dev_dbg(dev, "only 24-hr BCD mode supported\n");
|
||||
retval = -ENXIO;
|
||||
goto cleanup1;
|
||||
}
|
||||
|
||||
if (is_valid_irq(rtc_irq))
|
||||
retval = request_irq(rtc_irq, cmos_interrupt, IRQF_DISABLED,
|
||||
cmos_rtc.rtc->class_dev.class_id,
|
||||
&cmos_rtc.rtc->class_dev);
|
||||
if (retval < 0) {
|
||||
dev_dbg(dev, "IRQ %d is already in use\n", rtc_irq);
|
||||
goto cleanup1;
|
||||
}
|
||||
|
||||
/* REVISIT optionally make 50 or 114 bytes NVRAM available,
|
||||
* like rtc-ds1553, rtc-ds1742 ... this will often include
|
||||
* registers for century, and day/month alarm.
|
||||
*/
|
||||
|
||||
pr_info("%s: alarms up to one %s%s\n",
|
||||
cmos_rtc.rtc->class_dev.class_id,
|
||||
is_valid_irq(rtc_irq)
|
||||
? (cmos_rtc.mon_alrm
|
||||
? "year"
|
||||
: (cmos_rtc.day_alrm
|
||||
? "month" : "day"))
|
||||
: "no",
|
||||
cmos_rtc.century ? ", y3k" : ""
|
||||
);
|
||||
|
||||
return 0;
|
||||
|
||||
cleanup1:
|
||||
rename_region(ports, NULL);
|
||||
cleanup0:
|
||||
rtc_device_unregister(cmos_rtc.rtc);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void cmos_do_shutdown(void)
|
||||
{
|
||||
unsigned char rtc_control;
|
||||
|
||||
spin_lock_irq(&rtc_lock);
|
||||
rtc_control = CMOS_READ(RTC_CONTROL);
|
||||
rtc_control &= ~(RTC_PIE|RTC_AIE|RTC_UIE);
|
||||
CMOS_WRITE(rtc_control, RTC_CONTROL);
|
||||
CMOS_READ(RTC_INTR_FLAGS);
|
||||
spin_unlock_irq(&rtc_lock);
|
||||
}
|
||||
|
||||
static void __exit cmos_do_remove(struct device *dev)
|
||||
{
|
||||
struct cmos_rtc *cmos = dev_get_drvdata(dev);
|
||||
|
||||
cmos_do_shutdown();
|
||||
|
||||
if (is_pnpacpi())
|
||||
release_resource(cmos->iomem);
|
||||
rename_region(cmos->iomem, NULL);
|
||||
|
||||
if (is_valid_irq(cmos->irq))
|
||||
free_irq(cmos->irq, &cmos_rtc.rtc->class_dev);
|
||||
|
||||
rtc_device_unregister(cmos_rtc.rtc);
|
||||
|
||||
cmos_rtc.dev = NULL;
|
||||
dev_set_drvdata(dev, NULL);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
static int cmos_suspend(struct device *dev, pm_message_t mesg)
|
||||
{
|
||||
struct cmos_rtc *cmos = dev_get_drvdata(dev);
|
||||
int do_wake = device_may_wakeup(dev);
|
||||
unsigned char tmp;
|
||||
|
||||
/* only the alarm might be a wakeup event source */
|
||||
spin_lock_irq(&rtc_lock);
|
||||
cmos->suspend_ctrl = tmp = CMOS_READ(RTC_CONTROL);
|
||||
if (tmp & (RTC_PIE|RTC_AIE|RTC_UIE)) {
|
||||
unsigned char irqstat;
|
||||
|
||||
if (do_wake)
|
||||
tmp &= ~(RTC_PIE|RTC_UIE);
|
||||
else
|
||||
tmp &= ~(RTC_PIE|RTC_AIE|RTC_UIE);
|
||||
CMOS_WRITE(tmp, RTC_CONTROL);
|
||||
irqstat = CMOS_READ(RTC_INTR_FLAGS);
|
||||
irqstat &= (tmp & RTC_IRQMASK) | RTC_IRQF;
|
||||
if (is_intr(irqstat))
|
||||
rtc_update_irq(&cmos->rtc->class_dev, 1, irqstat);
|
||||
}
|
||||
spin_unlock_irq(&rtc_lock);
|
||||
|
||||
/* ACPI HOOK: enable ACPI_EVENT_RTC when (tmp & RTC_AIE)
|
||||
* ... it'd be best if we could do that under rtc_lock.
|
||||
*/
|
||||
|
||||
pr_debug("%s: suspend%s, ctrl %02x\n",
|
||||
cmos_rtc.rtc->class_dev.class_id,
|
||||
(tmp & RTC_AIE) ? ", alarm may wake" : "",
|
||||
tmp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmos_resume(struct device *dev)
|
||||
{
|
||||
struct cmos_rtc *cmos = dev_get_drvdata(dev);
|
||||
unsigned char tmp = cmos->suspend_ctrl;
|
||||
|
||||
/* REVISIT: a mechanism to resync the system clock (jiffies)
|
||||
* on resume should be portable between platforms ...
|
||||
*/
|
||||
|
||||
/* re-enable any irqs previously active */
|
||||
if (tmp & (RTC_PIE|RTC_AIE|RTC_UIE)) {
|
||||
|
||||
/* ACPI HOOK: disable ACPI_EVENT_RTC when (tmp & RTC_AIE) */
|
||||
|
||||
spin_lock_irq(&rtc_lock);
|
||||
CMOS_WRITE(tmp, RTC_CONTROL);
|
||||
tmp = CMOS_READ(RTC_INTR_FLAGS);
|
||||
tmp &= (cmos->suspend_ctrl & RTC_IRQMASK) | RTC_IRQF;
|
||||
if (is_intr(tmp))
|
||||
rtc_update_irq(&cmos->rtc->class_dev, 1, tmp);
|
||||
spin_unlock_irq(&rtc_lock);
|
||||
}
|
||||
|
||||
pr_debug("%s: resume, ctrl %02x\n",
|
||||
cmos_rtc.rtc->class_dev.class_id,
|
||||
cmos->suspend_ctrl);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
#define cmos_suspend NULL
|
||||
#define cmos_resume NULL
|
||||
#endif
|
||||
|
||||
/*----------------------------------------------------------------*/
|
||||
|
||||
/* The "CMOS" RTC normally lives on the platform_bus. On ACPI systems,
|
||||
* the device node will always be created as a PNPACPI device.
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_PNPACPI
|
||||
|
||||
#include <linux/pnp.h>
|
||||
|
||||
static int __devinit
|
||||
cmos_pnp_probe(struct pnp_dev *pnp, const struct pnp_device_id *id)
|
||||
{
|
||||
/* REVISIT paranoia argues for a shutdown notifier, since PNP
|
||||
* drivers can't provide shutdown() methods to disable IRQs.
|
||||
* Or better yet, fix PNP to allow those methods...
|
||||
*/
|
||||
return cmos_do_probe(&pnp->dev,
|
||||
&pnp->res.port_resource[0],
|
||||
pnp->res.irq_resource[0].start);
|
||||
}
|
||||
|
||||
static void __exit cmos_pnp_remove(struct pnp_dev *pnp)
|
||||
{
|
||||
cmos_do_remove(&pnp->dev);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
static int cmos_pnp_suspend(struct pnp_dev *pnp, pm_message_t mesg)
|
||||
{
|
||||
return cmos_suspend(&pnp->dev, mesg);
|
||||
}
|
||||
|
||||
static int cmos_pnp_resume(struct pnp_dev *pnp)
|
||||
{
|
||||
return cmos_resume(&pnp->dev);
|
||||
}
|
||||
|
||||
#else
|
||||
#define cmos_pnp_suspend NULL
|
||||
#define cmos_pnp_resume NULL
|
||||
#endif
|
||||
|
||||
|
||||
static const struct pnp_device_id rtc_ids[] = {
|
||||
{ .id = "PNP0b00", },
|
||||
{ .id = "PNP0b01", },
|
||||
{ .id = "PNP0b02", },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(pnp, rtc_ids);
|
||||
|
||||
static struct pnp_driver cmos_pnp_driver = {
|
||||
.name = (char *) driver_name,
|
||||
.id_table = rtc_ids,
|
||||
.probe = cmos_pnp_probe,
|
||||
.remove = __exit_p(cmos_pnp_remove),
|
||||
|
||||
/* flag ensures resume() gets called, and stops syslog spam */
|
||||
.flags = PNP_DRIVER_RES_DO_NOT_CHANGE,
|
||||
.suspend = cmos_pnp_suspend,
|
||||
.resume = cmos_pnp_resume,
|
||||
};
|
||||
|
||||
static int __init cmos_init(void)
|
||||
{
|
||||
return pnp_register_driver(&cmos_pnp_driver);
|
||||
}
|
||||
module_init(cmos_init);
|
||||
|
||||
static void __exit cmos_exit(void)
|
||||
{
|
||||
pnp_unregister_driver(&cmos_pnp_driver);
|
||||
}
|
||||
module_exit(cmos_exit);
|
||||
|
||||
#else /* no PNPACPI */
|
||||
|
||||
/*----------------------------------------------------------------*/
|
||||
|
||||
/* Platform setup should have set up an RTC device, when PNPACPI is
|
||||
* unavailable ... this could happen even on (older) PCs.
|
||||
*/
|
||||
|
||||
static int __init cmos_platform_probe(struct platform_device *pdev)
|
||||
{
|
||||
return cmos_do_probe(&pdev->dev,
|
||||
platform_get_resource(pdev, IORESOURCE_IO, 0),
|
||||
platform_get_irq(pdev, 0));
|
||||
}
|
||||
|
||||
static int __exit cmos_platform_remove(struct platform_device *pdev)
|
||||
{
|
||||
cmos_do_remove(&pdev->dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cmos_platform_shutdown(struct platform_device *pdev)
|
||||
{
|
||||
cmos_do_shutdown();
|
||||
}
|
||||
|
||||
static struct platform_driver cmos_platform_driver = {
|
||||
.remove = __exit_p(cmos_platform_remove),
|
||||
.shutdown = cmos_platform_shutdown,
|
||||
.driver = {
|
||||
.name = (char *) driver_name,
|
||||
.suspend = cmos_suspend,
|
||||
.resume = cmos_resume,
|
||||
}
|
||||
};
|
||||
|
||||
static int __init cmos_init(void)
|
||||
{
|
||||
return platform_driver_probe(&cmos_platform_driver,
|
||||
cmos_platform_probe);
|
||||
}
|
||||
module_init(cmos_init);
|
||||
|
||||
static void __exit cmos_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&cmos_platform_driver);
|
||||
}
|
||||
module_exit(cmos_exit);
|
||||
|
||||
|
||||
#endif /* !PNPACPI */
|
||||
|
||||
MODULE_AUTHOR("David Brownell");
|
||||
MODULE_DESCRIPTION("Driver for PC-style 'CMOS' RTCs");
|
||||
MODULE_LICENSE("GPL");
|
||||
517
drivers/rtc/rtc-dev.c
Normal file
517
drivers/rtc/rtc-dev.c
Normal file
@@ -0,0 +1,517 @@
|
||||
/*
|
||||
* RTC subsystem, dev interface
|
||||
*
|
||||
* Copyright (C) 2005 Tower Technologies
|
||||
* Author: Alessandro Zummo <a.zummo@towertech.it>
|
||||
*
|
||||
* based on arch/arm/common/rtctime.c
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/rtc.h>
|
||||
|
||||
static struct class *rtc_dev_class;
|
||||
static dev_t rtc_devt;
|
||||
|
||||
#define RTC_DEV_MAX 16 /* 16 RTCs should be enough for everyone... */
|
||||
|
||||
static int rtc_dev_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
int err;
|
||||
struct rtc_device *rtc = container_of(inode->i_cdev,
|
||||
struct rtc_device, char_dev);
|
||||
const struct rtc_class_ops *ops = rtc->ops;
|
||||
|
||||
/* We keep the lock as long as the device is in use
|
||||
* and return immediately if busy
|
||||
*/
|
||||
if (!(mutex_trylock(&rtc->char_lock)))
|
||||
return -EBUSY;
|
||||
|
||||
file->private_data = &rtc->class_dev;
|
||||
|
||||
err = ops->open ? ops->open(rtc->class_dev.dev) : 0;
|
||||
if (err == 0) {
|
||||
spin_lock_irq(&rtc->irq_lock);
|
||||
rtc->irq_data = 0;
|
||||
spin_unlock_irq(&rtc->irq_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* something has gone wrong, release the lock */
|
||||
mutex_unlock(&rtc->char_lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
|
||||
/*
|
||||
* Routine to poll RTC seconds field for change as often as possible,
|
||||
* after first RTC_UIE use timer to reduce polling
|
||||
*/
|
||||
static void rtc_uie_task(struct work_struct *work)
|
||||
{
|
||||
struct rtc_device *rtc =
|
||||
container_of(work, struct rtc_device, uie_task);
|
||||
struct rtc_time tm;
|
||||
int num = 0;
|
||||
int err;
|
||||
|
||||
err = rtc_read_time(&rtc->class_dev, &tm);
|
||||
|
||||
local_irq_disable();
|
||||
spin_lock(&rtc->irq_lock);
|
||||
if (rtc->stop_uie_polling || err) {
|
||||
rtc->uie_task_active = 0;
|
||||
} else if (rtc->oldsecs != tm.tm_sec) {
|
||||
num = (tm.tm_sec + 60 - rtc->oldsecs) % 60;
|
||||
rtc->oldsecs = tm.tm_sec;
|
||||
rtc->uie_timer.expires = jiffies + HZ - (HZ/10);
|
||||
rtc->uie_timer_active = 1;
|
||||
rtc->uie_task_active = 0;
|
||||
add_timer(&rtc->uie_timer);
|
||||
} else if (schedule_work(&rtc->uie_task) == 0) {
|
||||
rtc->uie_task_active = 0;
|
||||
}
|
||||
spin_unlock(&rtc->irq_lock);
|
||||
if (num)
|
||||
rtc_update_irq(&rtc->class_dev, num, RTC_UF | RTC_IRQF);
|
||||
local_irq_enable();
|
||||
}
|
||||
static void rtc_uie_timer(unsigned long data)
|
||||
{
|
||||
struct rtc_device *rtc = (struct rtc_device *)data;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&rtc->irq_lock, flags);
|
||||
rtc->uie_timer_active = 0;
|
||||
rtc->uie_task_active = 1;
|
||||
if ((schedule_work(&rtc->uie_task) == 0))
|
||||
rtc->uie_task_active = 0;
|
||||
spin_unlock_irqrestore(&rtc->irq_lock, flags);
|
||||
}
|
||||
|
||||
static void clear_uie(struct rtc_device *rtc)
|
||||
{
|
||||
spin_lock_irq(&rtc->irq_lock);
|
||||
if (rtc->irq_active) {
|
||||
rtc->stop_uie_polling = 1;
|
||||
if (rtc->uie_timer_active) {
|
||||
spin_unlock_irq(&rtc->irq_lock);
|
||||
del_timer_sync(&rtc->uie_timer);
|
||||
spin_lock_irq(&rtc->irq_lock);
|
||||
rtc->uie_timer_active = 0;
|
||||
}
|
||||
if (rtc->uie_task_active) {
|
||||
spin_unlock_irq(&rtc->irq_lock);
|
||||
flush_scheduled_work();
|
||||
spin_lock_irq(&rtc->irq_lock);
|
||||
}
|
||||
rtc->irq_active = 0;
|
||||
}
|
||||
spin_unlock_irq(&rtc->irq_lock);
|
||||
}
|
||||
|
||||
static int set_uie(struct rtc_device *rtc)
|
||||
{
|
||||
struct rtc_time tm;
|
||||
int err;
|
||||
|
||||
err = rtc_read_time(&rtc->class_dev, &tm);
|
||||
if (err)
|
||||
return err;
|
||||
spin_lock_irq(&rtc->irq_lock);
|
||||
if (!rtc->irq_active) {
|
||||
rtc->irq_active = 1;
|
||||
rtc->stop_uie_polling = 0;
|
||||
rtc->oldsecs = tm.tm_sec;
|
||||
rtc->uie_task_active = 1;
|
||||
if (schedule_work(&rtc->uie_task) == 0)
|
||||
rtc->uie_task_active = 0;
|
||||
}
|
||||
rtc->irq_data = 0;
|
||||
spin_unlock_irq(&rtc->irq_lock);
|
||||
return 0;
|
||||
}
|
||||
#endif /* CONFIG_RTC_INTF_DEV_UIE_EMUL */
|
||||
|
||||
static ssize_t
|
||||
rtc_dev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
|
||||
{
|
||||
struct rtc_device *rtc = to_rtc_device(file->private_data);
|
||||
|
||||
DECLARE_WAITQUEUE(wait, current);
|
||||
unsigned long data;
|
||||
ssize_t ret;
|
||||
|
||||
if (count != sizeof(unsigned int) && count < sizeof(unsigned long))
|
||||
return -EINVAL;
|
||||
|
||||
add_wait_queue(&rtc->irq_queue, &wait);
|
||||
do {
|
||||
__set_current_state(TASK_INTERRUPTIBLE);
|
||||
|
||||
spin_lock_irq(&rtc->irq_lock);
|
||||
data = rtc->irq_data;
|
||||
rtc->irq_data = 0;
|
||||
spin_unlock_irq(&rtc->irq_lock);
|
||||
|
||||
if (data != 0) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
if (file->f_flags & O_NONBLOCK) {
|
||||
ret = -EAGAIN;
|
||||
break;
|
||||
}
|
||||
if (signal_pending(current)) {
|
||||
ret = -ERESTARTSYS;
|
||||
break;
|
||||
}
|
||||
schedule();
|
||||
} while (1);
|
||||
set_current_state(TASK_RUNNING);
|
||||
remove_wait_queue(&rtc->irq_queue, &wait);
|
||||
|
||||
if (ret == 0) {
|
||||
/* Check for any data updates */
|
||||
if (rtc->ops->read_callback)
|
||||
data = rtc->ops->read_callback(rtc->class_dev.dev,
|
||||
data);
|
||||
|
||||
if (sizeof(int) != sizeof(long) &&
|
||||
count == sizeof(unsigned int))
|
||||
ret = put_user(data, (unsigned int __user *)buf) ?:
|
||||
sizeof(unsigned int);
|
||||
else
|
||||
ret = put_user(data, (unsigned long __user *)buf) ?:
|
||||
sizeof(unsigned long);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static unsigned int rtc_dev_poll(struct file *file, poll_table *wait)
|
||||
{
|
||||
struct rtc_device *rtc = to_rtc_device(file->private_data);
|
||||
unsigned long data;
|
||||
|
||||
poll_wait(file, &rtc->irq_queue, wait);
|
||||
|
||||
data = rtc->irq_data;
|
||||
|
||||
return (data != 0) ? (POLLIN | POLLRDNORM) : 0;
|
||||
}
|
||||
|
||||
static int rtc_dev_ioctl(struct inode *inode, struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
int err = 0;
|
||||
struct class_device *class_dev = file->private_data;
|
||||
struct rtc_device *rtc = to_rtc_device(class_dev);
|
||||
const struct rtc_class_ops *ops = rtc->ops;
|
||||
struct rtc_time tm;
|
||||
struct rtc_wkalrm alarm;
|
||||
void __user *uarg = (void __user *) arg;
|
||||
|
||||
/* check that the calling task has appropriate permissions
|
||||
* for certain ioctls. doing this check here is useful
|
||||
* to avoid duplicate code in each driver.
|
||||
*/
|
||||
switch (cmd) {
|
||||
case RTC_EPOCH_SET:
|
||||
case RTC_SET_TIME:
|
||||
if (!capable(CAP_SYS_TIME))
|
||||
return -EACCES;
|
||||
break;
|
||||
|
||||
case RTC_IRQP_SET:
|
||||
if (arg > rtc->max_user_freq && !capable(CAP_SYS_RESOURCE))
|
||||
return -EACCES;
|
||||
break;
|
||||
|
||||
case RTC_PIE_ON:
|
||||
if (!capable(CAP_SYS_RESOURCE))
|
||||
return -EACCES;
|
||||
break;
|
||||
}
|
||||
|
||||
/* avoid conflicting IRQ users */
|
||||
if (cmd == RTC_PIE_ON || cmd == RTC_PIE_OFF || cmd == RTC_IRQP_SET) {
|
||||
spin_lock_irq(&rtc->irq_task_lock);
|
||||
if (rtc->irq_task)
|
||||
err = -EBUSY;
|
||||
spin_unlock_irq(&rtc->irq_task_lock);
|
||||
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
/* try the driver's ioctl interface */
|
||||
if (ops->ioctl) {
|
||||
err = ops->ioctl(class_dev->dev, cmd, arg);
|
||||
if (err != -ENOIOCTLCMD)
|
||||
return err;
|
||||
}
|
||||
|
||||
/* if the driver does not provide the ioctl interface
|
||||
* or if that particular ioctl was not implemented
|
||||
* (-ENOIOCTLCMD), we will try to emulate here.
|
||||
*/
|
||||
|
||||
switch (cmd) {
|
||||
case RTC_ALM_READ:
|
||||
err = rtc_read_alarm(class_dev, &alarm);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (copy_to_user(uarg, &alarm.time, sizeof(tm)))
|
||||
return -EFAULT;
|
||||
break;
|
||||
|
||||
case RTC_ALM_SET:
|
||||
if (copy_from_user(&alarm.time, uarg, sizeof(tm)))
|
||||
return -EFAULT;
|
||||
|
||||
alarm.enabled = 0;
|
||||
alarm.pending = 0;
|
||||
alarm.time.tm_mday = -1;
|
||||
alarm.time.tm_mon = -1;
|
||||
alarm.time.tm_year = -1;
|
||||
alarm.time.tm_wday = -1;
|
||||
alarm.time.tm_yday = -1;
|
||||
alarm.time.tm_isdst = -1;
|
||||
err = rtc_set_alarm(class_dev, &alarm);
|
||||
break;
|
||||
|
||||
case RTC_RD_TIME:
|
||||
err = rtc_read_time(class_dev, &tm);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (copy_to_user(uarg, &tm, sizeof(tm)))
|
||||
return -EFAULT;
|
||||
break;
|
||||
|
||||
case RTC_SET_TIME:
|
||||
if (copy_from_user(&tm, uarg, sizeof(tm)))
|
||||
return -EFAULT;
|
||||
|
||||
err = rtc_set_time(class_dev, &tm);
|
||||
break;
|
||||
|
||||
case RTC_IRQP_READ:
|
||||
if (ops->irq_set_freq)
|
||||
err = put_user(rtc->irq_freq, (unsigned long __user *)uarg);
|
||||
break;
|
||||
|
||||
case RTC_IRQP_SET:
|
||||
if (ops->irq_set_freq)
|
||||
err = rtc_irq_set_freq(class_dev, rtc->irq_task, arg);
|
||||
break;
|
||||
|
||||
#if 0
|
||||
case RTC_EPOCH_SET:
|
||||
#ifndef rtc_epoch
|
||||
/*
|
||||
* There were no RTC clocks before 1900.
|
||||
*/
|
||||
if (arg < 1900) {
|
||||
err = -EINVAL;
|
||||
break;
|
||||
}
|
||||
rtc_epoch = arg;
|
||||
err = 0;
|
||||
#endif
|
||||
break;
|
||||
|
||||
case RTC_EPOCH_READ:
|
||||
err = put_user(rtc_epoch, (unsigned long __user *)uarg);
|
||||
break;
|
||||
#endif
|
||||
case RTC_WKALM_SET:
|
||||
if (copy_from_user(&alarm, uarg, sizeof(alarm)))
|
||||
return -EFAULT;
|
||||
|
||||
err = rtc_set_alarm(class_dev, &alarm);
|
||||
break;
|
||||
|
||||
case RTC_WKALM_RD:
|
||||
err = rtc_read_alarm(class_dev, &alarm);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (copy_to_user(uarg, &alarm, sizeof(alarm)))
|
||||
return -EFAULT;
|
||||
break;
|
||||
|
||||
#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
|
||||
case RTC_UIE_OFF:
|
||||
clear_uie(rtc);
|
||||
return 0;
|
||||
|
||||
case RTC_UIE_ON:
|
||||
return set_uie(rtc);
|
||||
#endif
|
||||
default:
|
||||
err = -ENOTTY;
|
||||
break;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int rtc_dev_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct rtc_device *rtc = to_rtc_device(file->private_data);
|
||||
|
||||
#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
|
||||
clear_uie(rtc);
|
||||
#endif
|
||||
if (rtc->ops->release)
|
||||
rtc->ops->release(rtc->class_dev.dev);
|
||||
|
||||
mutex_unlock(&rtc->char_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtc_dev_fasync(int fd, struct file *file, int on)
|
||||
{
|
||||
struct rtc_device *rtc = to_rtc_device(file->private_data);
|
||||
return fasync_helper(fd, file, on, &rtc->async_queue);
|
||||
}
|
||||
|
||||
static const struct file_operations rtc_dev_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.llseek = no_llseek,
|
||||
.read = rtc_dev_read,
|
||||
.poll = rtc_dev_poll,
|
||||
.ioctl = rtc_dev_ioctl,
|
||||
.open = rtc_dev_open,
|
||||
.release = rtc_dev_release,
|
||||
.fasync = rtc_dev_fasync,
|
||||
};
|
||||
|
||||
/* insertion/removal hooks */
|
||||
|
||||
static int rtc_dev_add_device(struct class_device *class_dev,
|
||||
struct class_interface *class_intf)
|
||||
{
|
||||
int err = 0;
|
||||
struct rtc_device *rtc = to_rtc_device(class_dev);
|
||||
|
||||
if (rtc->id >= RTC_DEV_MAX) {
|
||||
dev_err(class_dev->dev, "too many RTCs\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_init(&rtc->char_lock);
|
||||
spin_lock_init(&rtc->irq_lock);
|
||||
init_waitqueue_head(&rtc->irq_queue);
|
||||
#ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
|
||||
INIT_WORK(&rtc->uie_task, rtc_uie_task);
|
||||
setup_timer(&rtc->uie_timer, rtc_uie_timer, (unsigned long)rtc);
|
||||
#endif
|
||||
|
||||
cdev_init(&rtc->char_dev, &rtc_dev_fops);
|
||||
rtc->char_dev.owner = rtc->owner;
|
||||
|
||||
if (cdev_add(&rtc->char_dev, MKDEV(MAJOR(rtc_devt), rtc->id), 1)) {
|
||||
dev_err(class_dev->dev,
|
||||
"failed to add char device %d:%d\n",
|
||||
MAJOR(rtc_devt), rtc->id);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
rtc->rtc_dev = class_device_create(rtc_dev_class, NULL,
|
||||
MKDEV(MAJOR(rtc_devt), rtc->id),
|
||||
class_dev->dev, "rtc%d", rtc->id);
|
||||
if (IS_ERR(rtc->rtc_dev)) {
|
||||
dev_err(class_dev->dev, "cannot create rtc_dev device\n");
|
||||
err = PTR_ERR(rtc->rtc_dev);
|
||||
goto err_cdev_del;
|
||||
}
|
||||
|
||||
dev_dbg(class_dev->dev, "rtc intf: dev (%d:%d)\n",
|
||||
MAJOR(rtc->rtc_dev->devt),
|
||||
MINOR(rtc->rtc_dev->devt));
|
||||
|
||||
return 0;
|
||||
|
||||
err_cdev_del:
|
||||
|
||||
cdev_del(&rtc->char_dev);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void rtc_dev_remove_device(struct class_device *class_dev,
|
||||
struct class_interface *class_intf)
|
||||
{
|
||||
struct rtc_device *rtc = to_rtc_device(class_dev);
|
||||
|
||||
if (rtc->rtc_dev) {
|
||||
dev_dbg(class_dev->dev, "removing char %d:%d\n",
|
||||
MAJOR(rtc->rtc_dev->devt),
|
||||
MINOR(rtc->rtc_dev->devt));
|
||||
|
||||
class_device_unregister(rtc->rtc_dev);
|
||||
cdev_del(&rtc->char_dev);
|
||||
}
|
||||
}
|
||||
|
||||
/* interface registration */
|
||||
|
||||
static struct class_interface rtc_dev_interface = {
|
||||
.add = &rtc_dev_add_device,
|
||||
.remove = &rtc_dev_remove_device,
|
||||
};
|
||||
|
||||
static int __init rtc_dev_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
rtc_dev_class = class_create(THIS_MODULE, "rtc-dev");
|
||||
if (IS_ERR(rtc_dev_class))
|
||||
return PTR_ERR(rtc_dev_class);
|
||||
|
||||
err = alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc");
|
||||
if (err < 0) {
|
||||
printk(KERN_ERR "%s: failed to allocate char dev region\n",
|
||||
__FILE__);
|
||||
goto err_destroy_class;
|
||||
}
|
||||
|
||||
err = rtc_interface_register(&rtc_dev_interface);
|
||||
if (err < 0) {
|
||||
printk(KERN_ERR "%s: failed to register the interface\n",
|
||||
__FILE__);
|
||||
goto err_unregister_chrdev;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_unregister_chrdev:
|
||||
unregister_chrdev_region(rtc_devt, RTC_DEV_MAX);
|
||||
|
||||
err_destroy_class:
|
||||
class_destroy(rtc_dev_class);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit rtc_dev_exit(void)
|
||||
{
|
||||
class_interface_unregister(&rtc_dev_interface);
|
||||
class_destroy(rtc_dev_class);
|
||||
unregister_chrdev_region(rtc_devt, RTC_DEV_MAX);
|
||||
}
|
||||
|
||||
subsys_initcall(rtc_dev_init);
|
||||
module_exit(rtc_dev_exit);
|
||||
|
||||
MODULE_AUTHOR("Alessandro Zummo <a.zummo@towertech.it>");
|
||||
MODULE_DESCRIPTION("RTC class dev interface");
|
||||
MODULE_LICENSE("GPL");
|
||||
388
drivers/rtc/rtc-ds1307.c
Normal file
388
drivers/rtc/rtc-ds1307.c
Normal file
@@ -0,0 +1,388 @@
|
||||
/*
|
||||
* rtc-ds1307.c - RTC driver for some mostly-compatible I2C chips.
|
||||
*
|
||||
* Copyright (C) 2005 James Chapman (ds1337 core)
|
||||
* Copyright (C) 2006 David Brownell
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/rtc.h>
|
||||
#include <linux/bcd.h>
|
||||
|
||||
|
||||
|
||||
/* We can't determine type by probing, but if we expect pre-Linux code
|
||||
* to have set the chip up as a clock (turning on the oscillator and
|
||||
* setting the date and time), Linux can ignore the non-clock features.
|
||||
* That's a natural job for a factory or repair bench.
|
||||
*
|
||||
* If the I2C "force" mechanism is used, we assume the chip is a ds1337.
|
||||
* (Much better would be board-specific tables of I2C devices, along with
|
||||
* the platform_data drivers would use to sort such issues out.)
|
||||
*/
|
||||
enum ds_type {
|
||||
unknown = 0,
|
||||
ds_1307, /* or ds1338, ... */
|
||||
ds_1337, /* or ds1339, ... */
|
||||
ds_1340, /* or st m41t00, ... */
|
||||
// rs5c372 too? different address...
|
||||
};
|
||||
|
||||
static unsigned short normal_i2c[] = { 0x68, I2C_CLIENT_END };
|
||||
|
||||
I2C_CLIENT_INSMOD;
|
||||
|
||||
|
||||
|
||||
/* RTC registers don't differ much, except for the century flag */
|
||||
#define DS1307_REG_SECS 0x00 /* 00-59 */
|
||||
# define DS1307_BIT_CH 0x80
|
||||
#define DS1307_REG_MIN 0x01 /* 00-59 */
|
||||
#define DS1307_REG_HOUR 0x02 /* 00-23, or 1-12{am,pm} */
|
||||
# define DS1340_BIT_CENTURY_EN 0x80 /* in REG_HOUR */
|
||||
# define DS1340_BIT_CENTURY 0x40 /* in REG_HOUR */
|
||||
#define DS1307_REG_WDAY 0x03 /* 01-07 */
|
||||
#define DS1307_REG_MDAY 0x04 /* 01-31 */
|
||||
#define DS1307_REG_MONTH 0x05 /* 01-12 */
|
||||
# define DS1337_BIT_CENTURY 0x80 /* in REG_MONTH */
|
||||
#define DS1307_REG_YEAR 0x06 /* 00-99 */
|
||||
|
||||
/* Other registers (control, status, alarms, trickle charge, NVRAM, etc)
|
||||
* start at 7, and they differ a lot. Only control and status matter for RTC;
|
||||
* be careful using them.
|
||||
*/
|
||||
#define DS1307_REG_CONTROL 0x07
|
||||
# define DS1307_BIT_OUT 0x80
|
||||
# define DS1307_BIT_SQWE 0x10
|
||||
# define DS1307_BIT_RS1 0x02
|
||||
# define DS1307_BIT_RS0 0x01
|
||||
#define DS1337_REG_CONTROL 0x0e
|
||||
# define DS1337_BIT_nEOSC 0x80
|
||||
# define DS1337_BIT_RS2 0x10
|
||||
# define DS1337_BIT_RS1 0x08
|
||||
# define DS1337_BIT_INTCN 0x04
|
||||
# define DS1337_BIT_A2IE 0x02
|
||||
# define DS1337_BIT_A1IE 0x01
|
||||
#define DS1337_REG_STATUS 0x0f
|
||||
# define DS1337_BIT_OSF 0x80
|
||||
# define DS1337_BIT_A2I 0x02
|
||||
# define DS1337_BIT_A1I 0x01
|
||||
#define DS1339_REG_TRICKLE 0x10
|
||||
|
||||
|
||||
|
||||
struct ds1307 {
|
||||
u8 reg_addr;
|
||||
u8 regs[8];
|
||||
enum ds_type type;
|
||||
struct i2c_msg msg[2];
|
||||
struct i2c_client client;
|
||||
struct rtc_device *rtc;
|
||||
};
|
||||
|
||||
|
||||
static int ds1307_get_time(struct device *dev, struct rtc_time *t)
|
||||
{
|
||||
struct ds1307 *ds1307 = dev_get_drvdata(dev);
|
||||
int tmp;
|
||||
|
||||
/* read the RTC registers all at once */
|
||||
ds1307->msg[1].flags = I2C_M_RD;
|
||||
ds1307->msg[1].len = 7;
|
||||
|
||||
tmp = i2c_transfer(ds1307->client.adapter, ds1307->msg, 2);
|
||||
if (tmp != 2) {
|
||||
dev_err(dev, "%s error %d\n", "read", tmp);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
dev_dbg(dev, "%s: %02x %02x %02x %02x %02x %02x %02x\n",
|
||||
"read",
|
||||
ds1307->regs[0], ds1307->regs[1],
|
||||
ds1307->regs[2], ds1307->regs[3],
|
||||
ds1307->regs[4], ds1307->regs[5],
|
||||
ds1307->regs[6]);
|
||||
|
||||
t->tm_sec = BCD2BIN(ds1307->regs[DS1307_REG_SECS] & 0x7f);
|
||||
t->tm_min = BCD2BIN(ds1307->regs[DS1307_REG_MIN] & 0x7f);
|
||||
tmp = ds1307->regs[DS1307_REG_HOUR] & 0x3f;
|
||||
t->tm_hour = BCD2BIN(tmp);
|
||||
t->tm_wday = BCD2BIN(ds1307->regs[DS1307_REG_WDAY] & 0x07) - 1;
|
||||
t->tm_mday = BCD2BIN(ds1307->regs[DS1307_REG_MDAY] & 0x3f);
|
||||
tmp = ds1307->regs[DS1307_REG_MONTH] & 0x1f;
|
||||
t->tm_mon = BCD2BIN(tmp) - 1;
|
||||
|
||||
/* assume 20YY not 19YY, and ignore DS1337_BIT_CENTURY */
|
||||
t->tm_year = BCD2BIN(ds1307->regs[DS1307_REG_YEAR]) + 100;
|
||||
|
||||
dev_dbg(dev, "%s secs=%d, mins=%d, "
|
||||
"hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n",
|
||||
"read", t->tm_sec, t->tm_min,
|
||||
t->tm_hour, t->tm_mday,
|
||||
t->tm_mon, t->tm_year, t->tm_wday);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ds1307_set_time(struct device *dev, struct rtc_time *t)
|
||||
{
|
||||
struct ds1307 *ds1307 = dev_get_drvdata(dev);
|
||||
int result;
|
||||
int tmp;
|
||||
u8 *buf = ds1307->regs;
|
||||
|
||||
dev_dbg(dev, "%s secs=%d, mins=%d, "
|
||||
"hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n",
|
||||
"write", t->tm_sec, t->tm_min,
|
||||
t->tm_hour, t->tm_mday,
|
||||
t->tm_mon, t->tm_year, t->tm_wday);
|
||||
|
||||
*buf++ = 0; /* first register addr */
|
||||
buf[DS1307_REG_SECS] = BIN2BCD(t->tm_sec);
|
||||
buf[DS1307_REG_MIN] = BIN2BCD(t->tm_min);
|
||||
buf[DS1307_REG_HOUR] = BIN2BCD(t->tm_hour);
|
||||
buf[DS1307_REG_WDAY] = BIN2BCD(t->tm_wday + 1);
|
||||
buf[DS1307_REG_MDAY] = BIN2BCD(t->tm_mday);
|
||||
buf[DS1307_REG_MONTH] = BIN2BCD(t->tm_mon + 1);
|
||||
|
||||
/* assume 20YY not 19YY */
|
||||
tmp = t->tm_year - 100;
|
||||
buf[DS1307_REG_YEAR] = BIN2BCD(tmp);
|
||||
|
||||
if (ds1307->type == ds_1337)
|
||||
buf[DS1307_REG_MONTH] |= DS1337_BIT_CENTURY;
|
||||
else if (ds1307->type == ds_1340)
|
||||
buf[DS1307_REG_HOUR] |= DS1340_BIT_CENTURY_EN
|
||||
| DS1340_BIT_CENTURY;
|
||||
|
||||
ds1307->msg[1].flags = 0;
|
||||
ds1307->msg[1].len = 8;
|
||||
|
||||
dev_dbg(dev, "%s: %02x %02x %02x %02x %02x %02x %02x\n",
|
||||
"write", buf[0], buf[1], buf[2], buf[3],
|
||||
buf[4], buf[5], buf[6]);
|
||||
|
||||
result = i2c_transfer(ds1307->client.adapter, &ds1307->msg[1], 1);
|
||||
if (result != 1) {
|
||||
dev_err(dev, "%s error %d\n", "write", tmp);
|
||||
return -EIO;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct rtc_class_ops ds13xx_rtc_ops = {
|
||||
.read_time = ds1307_get_time,
|
||||
.set_time = ds1307_set_time,
|
||||
};
|
||||
|
||||
static struct i2c_driver ds1307_driver;
|
||||
|
||||
static int __devinit
|
||||
ds1307_detect(struct i2c_adapter *adapter, int address, int kind)
|
||||
{
|
||||
struct ds1307 *ds1307;
|
||||
int err = -ENODEV;
|
||||
struct i2c_client *client;
|
||||
int tmp;
|
||||
|
||||
if (!(ds1307 = kzalloc(sizeof(struct ds1307), GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
client = &ds1307->client;
|
||||
client->addr = address;
|
||||
client->adapter = adapter;
|
||||
client->driver = &ds1307_driver;
|
||||
client->flags = 0;
|
||||
|
||||
i2c_set_clientdata(client, ds1307);
|
||||
|
||||
ds1307->msg[0].addr = client->addr;
|
||||
ds1307->msg[0].flags = 0;
|
||||
ds1307->msg[0].len = 1;
|
||||
ds1307->msg[0].buf = &ds1307->reg_addr;
|
||||
|
||||
ds1307->msg[1].addr = client->addr;
|
||||
ds1307->msg[1].flags = I2C_M_RD;
|
||||
ds1307->msg[1].len = sizeof(ds1307->regs);
|
||||
ds1307->msg[1].buf = ds1307->regs;
|
||||
|
||||
/* HACK: "force" implies "needs ds1337-style-oscillator setup" */
|
||||
if (kind >= 0) {
|
||||
ds1307->type = ds_1337;
|
||||
|
||||
ds1307->reg_addr = DS1337_REG_CONTROL;
|
||||
ds1307->msg[1].len = 2;
|
||||
|
||||
tmp = i2c_transfer(client->adapter, ds1307->msg, 2);
|
||||
if (tmp != 2) {
|
||||
pr_debug("read error %d\n", tmp);
|
||||
err = -EIO;
|
||||
goto exit_free;
|
||||
}
|
||||
|
||||
ds1307->reg_addr = 0;
|
||||
ds1307->msg[1].len = sizeof(ds1307->regs);
|
||||
|
||||
/* oscillator is off; need to turn it on */
|
||||
if ((ds1307->regs[0] & DS1337_BIT_nEOSC)
|
||||
|| (ds1307->regs[1] & DS1337_BIT_OSF)) {
|
||||
printk(KERN_ERR "no ds1337 oscillator code\n");
|
||||
goto exit_free;
|
||||
}
|
||||
} else
|
||||
ds1307->type = ds_1307;
|
||||
|
||||
read_rtc:
|
||||
/* read RTC registers */
|
||||
|
||||
tmp = i2c_transfer(client->adapter, ds1307->msg, 2);
|
||||
if (tmp != 2) {
|
||||
pr_debug("read error %d\n", tmp);
|
||||
err = -EIO;
|
||||
goto exit_free;
|
||||
}
|
||||
|
||||
/* minimal sanity checking; some chips (like DS1340) don't
|
||||
* specify the extra bits as must-be-zero, but there are
|
||||
* still a few values that are clearly out-of-range.
|
||||
*/
|
||||
tmp = ds1307->regs[DS1307_REG_SECS];
|
||||
if (tmp & DS1307_BIT_CH) {
|
||||
if (ds1307->type && ds1307->type != ds_1307) {
|
||||
pr_debug("not a ds1307?\n");
|
||||
goto exit_free;
|
||||
}
|
||||
ds1307->type = ds_1307;
|
||||
|
||||
/* this partial initialization should work for ds1307,
|
||||
* ds1338, ds1340, st m41t00, and more.
|
||||
*/
|
||||
dev_warn(&client->dev, "oscillator started; SET TIME!\n");
|
||||
i2c_smbus_write_byte_data(client, 0, 0);
|
||||
goto read_rtc;
|
||||
}
|
||||
tmp = BCD2BIN(tmp & 0x7f);
|
||||
if (tmp > 60)
|
||||
goto exit_free;
|
||||
tmp = BCD2BIN(ds1307->regs[DS1307_REG_MIN] & 0x7f);
|
||||
if (tmp > 60)
|
||||
goto exit_free;
|
||||
|
||||
tmp = BCD2BIN(ds1307->regs[DS1307_REG_MDAY] & 0x3f);
|
||||
if (tmp == 0 || tmp > 31)
|
||||
goto exit_free;
|
||||
|
||||
tmp = BCD2BIN(ds1307->regs[DS1307_REG_MONTH] & 0x1f);
|
||||
if (tmp == 0 || tmp > 12)
|
||||
goto exit_free;
|
||||
|
||||
/* force into in 24 hour mode (most chips) or
|
||||
* disable century bit (ds1340)
|
||||
*/
|
||||
tmp = ds1307->regs[DS1307_REG_HOUR];
|
||||
if (tmp & (1 << 6)) {
|
||||
if (tmp & (1 << 5))
|
||||
tmp = BCD2BIN(tmp & 0x1f) + 12;
|
||||
else
|
||||
tmp = BCD2BIN(tmp);
|
||||
i2c_smbus_write_byte_data(client,
|
||||
DS1307_REG_HOUR,
|
||||
BIN2BCD(tmp));
|
||||
}
|
||||
|
||||
/* FIXME chips like 1337 can generate alarm irqs too; those are
|
||||
* worth exposing through the API (especially when the irq is
|
||||
* wakeup-capable).
|
||||
*/
|
||||
|
||||
switch (ds1307->type) {
|
||||
case unknown:
|
||||
strlcpy(client->name, "unknown", I2C_NAME_SIZE);
|
||||
break;
|
||||
case ds_1307:
|
||||
strlcpy(client->name, "ds1307", I2C_NAME_SIZE);
|
||||
break;
|
||||
case ds_1337:
|
||||
strlcpy(client->name, "ds1337", I2C_NAME_SIZE);
|
||||
break;
|
||||
case ds_1340:
|
||||
strlcpy(client->name, "ds1340", I2C_NAME_SIZE);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Tell the I2C layer a new client has arrived */
|
||||
if ((err = i2c_attach_client(client)))
|
||||
goto exit_free;
|
||||
|
||||
ds1307->rtc = rtc_device_register(client->name, &client->dev,
|
||||
&ds13xx_rtc_ops, THIS_MODULE);
|
||||
if (IS_ERR(ds1307->rtc)) {
|
||||
err = PTR_ERR(ds1307->rtc);
|
||||
dev_err(&client->dev,
|
||||
"unable to register the class device\n");
|
||||
goto exit_detach;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
exit_detach:
|
||||
i2c_detach_client(client);
|
||||
exit_free:
|
||||
kfree(ds1307);
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int __devinit
|
||||
ds1307_attach_adapter(struct i2c_adapter *adapter)
|
||||
{
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_I2C))
|
||||
return 0;
|
||||
return i2c_probe(adapter, &addr_data, ds1307_detect);
|
||||
}
|
||||
|
||||
static int __devexit ds1307_detach_client(struct i2c_client *client)
|
||||
{
|
||||
int err;
|
||||
struct ds1307 *ds1307 = i2c_get_clientdata(client);
|
||||
|
||||
rtc_device_unregister(ds1307->rtc);
|
||||
if ((err = i2c_detach_client(client)))
|
||||
return err;
|
||||
kfree(ds1307);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct i2c_driver ds1307_driver = {
|
||||
.driver = {
|
||||
.name = "ds1307",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.attach_adapter = ds1307_attach_adapter,
|
||||
.detach_client = __devexit_p(ds1307_detach_client),
|
||||
};
|
||||
|
||||
static int __init ds1307_init(void)
|
||||
{
|
||||
return i2c_add_driver(&ds1307_driver);
|
||||
}
|
||||
module_init(ds1307_init);
|
||||
|
||||
static void __exit ds1307_exit(void)
|
||||
{
|
||||
i2c_del_driver(&ds1307_driver);
|
||||
}
|
||||
module_exit(ds1307_exit);
|
||||
|
||||
MODULE_DESCRIPTION("RTC driver for DS1307 and similar chips");
|
||||
MODULE_LICENSE("GPL");
|
||||
418
drivers/rtc/rtc-ds1553.c
Normal file
418
drivers/rtc/rtc-ds1553.c
Normal file
@@ -0,0 +1,418 @@
|
||||
/*
|
||||
* An rtc driver for the Dallas DS1553
|
||||
*
|
||||
* Copyright (C) 2006 Atsushi Nemoto <anemo@mba.ocn.ne.jp>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/bcd.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/rtc.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/io.h>
|
||||
|
||||
#define DRV_VERSION "0.2"
|
||||
|
||||
#define RTC_REG_SIZE 0x2000
|
||||
#define RTC_OFFSET 0x1ff0
|
||||
|
||||
#define RTC_FLAGS (RTC_OFFSET + 0)
|
||||
#define RTC_SECONDS_ALARM (RTC_OFFSET + 2)
|
||||
#define RTC_MINUTES_ALARM (RTC_OFFSET + 3)
|
||||
#define RTC_HOURS_ALARM (RTC_OFFSET + 4)
|
||||
#define RTC_DATE_ALARM (RTC_OFFSET + 5)
|
||||
#define RTC_INTERRUPTS (RTC_OFFSET + 6)
|
||||
#define RTC_WATCHDOG (RTC_OFFSET + 7)
|
||||
#define RTC_CONTROL (RTC_OFFSET + 8)
|
||||
#define RTC_CENTURY (RTC_OFFSET + 8)
|
||||
#define RTC_SECONDS (RTC_OFFSET + 9)
|
||||
#define RTC_MINUTES (RTC_OFFSET + 10)
|
||||
#define RTC_HOURS (RTC_OFFSET + 11)
|
||||
#define RTC_DAY (RTC_OFFSET + 12)
|
||||
#define RTC_DATE (RTC_OFFSET + 13)
|
||||
#define RTC_MONTH (RTC_OFFSET + 14)
|
||||
#define RTC_YEAR (RTC_OFFSET + 15)
|
||||
|
||||
#define RTC_CENTURY_MASK 0x3f
|
||||
#define RTC_SECONDS_MASK 0x7f
|
||||
#define RTC_DAY_MASK 0x07
|
||||
|
||||
/* Bits in the Control/Century register */
|
||||
#define RTC_WRITE 0x80
|
||||
#define RTC_READ 0x40
|
||||
|
||||
/* Bits in the Seconds register */
|
||||
#define RTC_STOP 0x80
|
||||
|
||||
/* Bits in the Flags register */
|
||||
#define RTC_FLAGS_AF 0x40
|
||||
#define RTC_FLAGS_BLF 0x10
|
||||
|
||||
/* Bits in the Interrupts register */
|
||||
#define RTC_INTS_AE 0x80
|
||||
|
||||
struct rtc_plat_data {
|
||||
struct rtc_device *rtc;
|
||||
void __iomem *ioaddr;
|
||||
unsigned long baseaddr;
|
||||
unsigned long last_jiffies;
|
||||
int irq;
|
||||
unsigned int irqen;
|
||||
int alrm_sec;
|
||||
int alrm_min;
|
||||
int alrm_hour;
|
||||
int alrm_mday;
|
||||
};
|
||||
|
||||
static int ds1553_rtc_set_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
|
||||
void __iomem *ioaddr = pdata->ioaddr;
|
||||
u8 century;
|
||||
|
||||
century = BIN2BCD((tm->tm_year + 1900) / 100);
|
||||
|
||||
writeb(RTC_WRITE, pdata->ioaddr + RTC_CONTROL);
|
||||
|
||||
writeb(BIN2BCD(tm->tm_year % 100), ioaddr + RTC_YEAR);
|
||||
writeb(BIN2BCD(tm->tm_mon + 1), ioaddr + RTC_MONTH);
|
||||
writeb(BIN2BCD(tm->tm_wday) & RTC_DAY_MASK, ioaddr + RTC_DAY);
|
||||
writeb(BIN2BCD(tm->tm_mday), ioaddr + RTC_DATE);
|
||||
writeb(BIN2BCD(tm->tm_hour), ioaddr + RTC_HOURS);
|
||||
writeb(BIN2BCD(tm->tm_min), ioaddr + RTC_MINUTES);
|
||||
writeb(BIN2BCD(tm->tm_sec) & RTC_SECONDS_MASK, ioaddr + RTC_SECONDS);
|
||||
|
||||
/* RTC_CENTURY and RTC_CONTROL share same register */
|
||||
writeb(RTC_WRITE | (century & RTC_CENTURY_MASK), ioaddr + RTC_CENTURY);
|
||||
writeb(century & RTC_CENTURY_MASK, ioaddr + RTC_CONTROL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ds1553_rtc_read_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
|
||||
void __iomem *ioaddr = pdata->ioaddr;
|
||||
unsigned int year, month, day, hour, minute, second, week;
|
||||
unsigned int century;
|
||||
|
||||
/* give enough time to update RTC in case of continuous read */
|
||||
if (pdata->last_jiffies == jiffies)
|
||||
msleep(1);
|
||||
pdata->last_jiffies = jiffies;
|
||||
writeb(RTC_READ, ioaddr + RTC_CONTROL);
|
||||
second = readb(ioaddr + RTC_SECONDS) & RTC_SECONDS_MASK;
|
||||
minute = readb(ioaddr + RTC_MINUTES);
|
||||
hour = readb(ioaddr + RTC_HOURS);
|
||||
day = readb(ioaddr + RTC_DATE);
|
||||
week = readb(ioaddr + RTC_DAY) & RTC_DAY_MASK;
|
||||
month = readb(ioaddr + RTC_MONTH);
|
||||
year = readb(ioaddr + RTC_YEAR);
|
||||
century = readb(ioaddr + RTC_CENTURY) & RTC_CENTURY_MASK;
|
||||
writeb(0, ioaddr + RTC_CONTROL);
|
||||
tm->tm_sec = BCD2BIN(second);
|
||||
tm->tm_min = BCD2BIN(minute);
|
||||
tm->tm_hour = BCD2BIN(hour);
|
||||
tm->tm_mday = BCD2BIN(day);
|
||||
tm->tm_wday = BCD2BIN(week);
|
||||
tm->tm_mon = BCD2BIN(month) - 1;
|
||||
/* year is 1900 + tm->tm_year */
|
||||
tm->tm_year = BCD2BIN(year) + BCD2BIN(century) * 100 - 1900;
|
||||
|
||||
if (rtc_valid_tm(tm) < 0) {
|
||||
dev_err(dev, "retrieved date/time is not valid.\n");
|
||||
rtc_time_to_tm(0, tm);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ds1553_rtc_update_alarm(struct rtc_plat_data *pdata)
|
||||
{
|
||||
void __iomem *ioaddr = pdata->ioaddr;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&pdata->rtc->irq_lock, flags);
|
||||
writeb(pdata->alrm_mday < 0 || (pdata->irqen & RTC_UF) ?
|
||||
0x80 : BIN2BCD(pdata->alrm_mday),
|
||||
ioaddr + RTC_DATE_ALARM);
|
||||
writeb(pdata->alrm_hour < 0 || (pdata->irqen & RTC_UF) ?
|
||||
0x80 : BIN2BCD(pdata->alrm_hour),
|
||||
ioaddr + RTC_HOURS_ALARM);
|
||||
writeb(pdata->alrm_min < 0 || (pdata->irqen & RTC_UF) ?
|
||||
0x80 : BIN2BCD(pdata->alrm_min),
|
||||
ioaddr + RTC_MINUTES_ALARM);
|
||||
writeb(pdata->alrm_sec < 0 || (pdata->irqen & RTC_UF) ?
|
||||
0x80 : BIN2BCD(pdata->alrm_sec),
|
||||
ioaddr + RTC_SECONDS_ALARM);
|
||||
writeb(pdata->irqen ? RTC_INTS_AE : 0, ioaddr + RTC_INTERRUPTS);
|
||||
readb(ioaddr + RTC_FLAGS); /* clear interrupts */
|
||||
spin_unlock_irqrestore(&pdata->rtc->irq_lock, flags);
|
||||
}
|
||||
|
||||
static int ds1553_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
|
||||
|
||||
if (pdata->irq < 0)
|
||||
return -EINVAL;
|
||||
pdata->alrm_mday = alrm->time.tm_mday;
|
||||
pdata->alrm_hour = alrm->time.tm_hour;
|
||||
pdata->alrm_min = alrm->time.tm_min;
|
||||
pdata->alrm_sec = alrm->time.tm_sec;
|
||||
if (alrm->enabled)
|
||||
pdata->irqen |= RTC_AF;
|
||||
ds1553_rtc_update_alarm(pdata);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ds1553_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
|
||||
|
||||
if (pdata->irq < 0)
|
||||
return -EINVAL;
|
||||
alrm->time.tm_mday = pdata->alrm_mday < 0 ? 0 : pdata->alrm_mday;
|
||||
alrm->time.tm_hour = pdata->alrm_hour < 0 ? 0 : pdata->alrm_hour;
|
||||
alrm->time.tm_min = pdata->alrm_min < 0 ? 0 : pdata->alrm_min;
|
||||
alrm->time.tm_sec = pdata->alrm_sec < 0 ? 0 : pdata->alrm_sec;
|
||||
alrm->enabled = (pdata->irqen & RTC_AF) ? 1 : 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t ds1553_rtc_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct platform_device *pdev = dev_id;
|
||||
struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
|
||||
void __iomem *ioaddr = pdata->ioaddr;
|
||||
unsigned long events = RTC_IRQF;
|
||||
|
||||
/* read and clear interrupt */
|
||||
if (!(readb(ioaddr + RTC_FLAGS) & RTC_FLAGS_AF))
|
||||
return IRQ_NONE;
|
||||
if (readb(ioaddr + RTC_SECONDS_ALARM) & 0x80)
|
||||
events |= RTC_UF;
|
||||
else
|
||||
events |= RTC_AF;
|
||||
rtc_update_irq(&pdata->rtc->class_dev, 1, events);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void ds1553_rtc_release(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
|
||||
|
||||
if (pdata->irq >= 0) {
|
||||
pdata->irqen = 0;
|
||||
ds1553_rtc_update_alarm(pdata);
|
||||
}
|
||||
}
|
||||
|
||||
static int ds1553_rtc_ioctl(struct device *dev, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
|
||||
|
||||
if (pdata->irq < 0)
|
||||
return -ENOIOCTLCMD; /* fall back into rtc-dev's emulation */
|
||||
switch (cmd) {
|
||||
case RTC_AIE_OFF:
|
||||
pdata->irqen &= ~RTC_AF;
|
||||
ds1553_rtc_update_alarm(pdata);
|
||||
break;
|
||||
case RTC_AIE_ON:
|
||||
pdata->irqen |= RTC_AF;
|
||||
ds1553_rtc_update_alarm(pdata);
|
||||
break;
|
||||
case RTC_UIE_OFF:
|
||||
pdata->irqen &= ~RTC_UF;
|
||||
ds1553_rtc_update_alarm(pdata);
|
||||
break;
|
||||
case RTC_UIE_ON:
|
||||
pdata->irqen |= RTC_UF;
|
||||
ds1553_rtc_update_alarm(pdata);
|
||||
break;
|
||||
default:
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct rtc_class_ops ds1553_rtc_ops = {
|
||||
.read_time = ds1553_rtc_read_time,
|
||||
.set_time = ds1553_rtc_set_time,
|
||||
.read_alarm = ds1553_rtc_read_alarm,
|
||||
.set_alarm = ds1553_rtc_set_alarm,
|
||||
.release = ds1553_rtc_release,
|
||||
.ioctl = ds1553_rtc_ioctl,
|
||||
};
|
||||
|
||||
static ssize_t ds1553_nvram_read(struct kobject *kobj, char *buf,
|
||||
loff_t pos, size_t size)
|
||||
{
|
||||
struct platform_device *pdev =
|
||||
to_platform_device(container_of(kobj, struct device, kobj));
|
||||
struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
|
||||
void __iomem *ioaddr = pdata->ioaddr;
|
||||
ssize_t count;
|
||||
|
||||
for (count = 0; size > 0 && pos < RTC_OFFSET; count++, size--)
|
||||
*buf++ = readb(ioaddr + pos++);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t ds1553_nvram_write(struct kobject *kobj, char *buf,
|
||||
loff_t pos, size_t size)
|
||||
{
|
||||
struct platform_device *pdev =
|
||||
to_platform_device(container_of(kobj, struct device, kobj));
|
||||
struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
|
||||
void __iomem *ioaddr = pdata->ioaddr;
|
||||
ssize_t count;
|
||||
|
||||
for (count = 0; size > 0 && pos < RTC_OFFSET; count++, size--)
|
||||
writeb(*buf++, ioaddr + pos++);
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct bin_attribute ds1553_nvram_attr = {
|
||||
.attr = {
|
||||
.name = "nvram",
|
||||
.mode = S_IRUGO | S_IWUGO,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.size = RTC_OFFSET,
|
||||
.read = ds1553_nvram_read,
|
||||
.write = ds1553_nvram_write,
|
||||
};
|
||||
|
||||
static int __devinit ds1553_rtc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct rtc_device *rtc;
|
||||
struct resource *res;
|
||||
unsigned int cen, sec;
|
||||
struct rtc_plat_data *pdata = NULL;
|
||||
void __iomem *ioaddr = NULL;
|
||||
int ret = 0;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res)
|
||||
return -ENODEV;
|
||||
pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
|
||||
if (!pdata)
|
||||
return -ENOMEM;
|
||||
pdata->irq = -1;
|
||||
if (!request_mem_region(res->start, RTC_REG_SIZE, pdev->name)) {
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
pdata->baseaddr = res->start;
|
||||
ioaddr = ioremap(pdata->baseaddr, RTC_REG_SIZE);
|
||||
if (!ioaddr) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
pdata->ioaddr = ioaddr;
|
||||
pdata->irq = platform_get_irq(pdev, 0);
|
||||
|
||||
/* turn RTC on if it was not on */
|
||||
sec = readb(ioaddr + RTC_SECONDS);
|
||||
if (sec & RTC_STOP) {
|
||||
sec &= RTC_SECONDS_MASK;
|
||||
cen = readb(ioaddr + RTC_CENTURY) & RTC_CENTURY_MASK;
|
||||
writeb(RTC_WRITE, ioaddr + RTC_CONTROL);
|
||||
writeb(sec, ioaddr + RTC_SECONDS);
|
||||
writeb(cen & RTC_CENTURY_MASK, ioaddr + RTC_CONTROL);
|
||||
}
|
||||
if (readb(ioaddr + RTC_FLAGS) & RTC_FLAGS_BLF)
|
||||
dev_warn(&pdev->dev, "voltage-low detected.\n");
|
||||
|
||||
if (pdata->irq >= 0) {
|
||||
writeb(0, ioaddr + RTC_INTERRUPTS);
|
||||
if (request_irq(pdata->irq, ds1553_rtc_interrupt,
|
||||
IRQF_DISABLED | IRQF_SHARED,
|
||||
pdev->name, pdev) < 0) {
|
||||
dev_warn(&pdev->dev, "interrupt not available.\n");
|
||||
pdata->irq = -1;
|
||||
}
|
||||
}
|
||||
|
||||
rtc = rtc_device_register(pdev->name, &pdev->dev,
|
||||
&ds1553_rtc_ops, THIS_MODULE);
|
||||
if (IS_ERR(rtc)) {
|
||||
ret = PTR_ERR(rtc);
|
||||
goto out;
|
||||
}
|
||||
pdata->rtc = rtc;
|
||||
pdata->last_jiffies = jiffies;
|
||||
platform_set_drvdata(pdev, pdata);
|
||||
ret = sysfs_create_bin_file(&pdev->dev.kobj, &ds1553_nvram_attr);
|
||||
if (ret)
|
||||
goto out;
|
||||
return 0;
|
||||
out:
|
||||
if (pdata->rtc)
|
||||
rtc_device_unregister(pdata->rtc);
|
||||
if (pdata->irq >= 0)
|
||||
free_irq(pdata->irq, pdev);
|
||||
if (ioaddr)
|
||||
iounmap(ioaddr);
|
||||
if (pdata->baseaddr)
|
||||
release_mem_region(pdata->baseaddr, RTC_REG_SIZE);
|
||||
kfree(pdata);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devexit ds1553_rtc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
|
||||
|
||||
sysfs_remove_bin_file(&pdev->dev.kobj, &ds1553_nvram_attr);
|
||||
rtc_device_unregister(pdata->rtc);
|
||||
if (pdata->irq >= 0) {
|
||||
writeb(0, pdata->ioaddr + RTC_INTERRUPTS);
|
||||
free_irq(pdata->irq, pdev);
|
||||
}
|
||||
iounmap(pdata->ioaddr);
|
||||
release_mem_region(pdata->baseaddr, RTC_REG_SIZE);
|
||||
kfree(pdata);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver ds1553_rtc_driver = {
|
||||
.probe = ds1553_rtc_probe,
|
||||
.remove = __devexit_p(ds1553_rtc_remove),
|
||||
.driver = {
|
||||
.name = "ds1553",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static __init int ds1553_init(void)
|
||||
{
|
||||
return platform_driver_register(&ds1553_rtc_driver);
|
||||
}
|
||||
|
||||
static __exit void ds1553_exit(void)
|
||||
{
|
||||
return platform_driver_unregister(&ds1553_rtc_driver);
|
||||
}
|
||||
|
||||
module_init(ds1553_init);
|
||||
module_exit(ds1553_exit);
|
||||
|
||||
MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>");
|
||||
MODULE_DESCRIPTION("Dallas DS1553 RTC driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_VERSION(DRV_VERSION);
|
||||
282
drivers/rtc/rtc-ds1672.c
Normal file
282
drivers/rtc/rtc-ds1672.c
Normal file
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* An rtc/i2c driver for the Dallas DS1672
|
||||
* Copyright 2005-06 Tower Technologies
|
||||
*
|
||||
* Author: Alessandro Zummo <a.zummo@towertech.it>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/rtc.h>
|
||||
|
||||
#define DRV_VERSION "0.3"
|
||||
|
||||
/* Addresses to scan: none. This chip cannot be detected. */
|
||||
static unsigned short normal_i2c[] = { I2C_CLIENT_END };
|
||||
|
||||
/* Insmod parameters */
|
||||
I2C_CLIENT_INSMOD;
|
||||
|
||||
/* Registers */
|
||||
|
||||
#define DS1672_REG_CNT_BASE 0
|
||||
#define DS1672_REG_CONTROL 4
|
||||
#define DS1672_REG_TRICKLE 5
|
||||
|
||||
#define DS1672_REG_CONTROL_EOSC 0x80
|
||||
|
||||
/* Prototypes */
|
||||
static int ds1672_probe(struct i2c_adapter *adapter, int address, int kind);
|
||||
|
||||
/*
|
||||
* In the routines that deal directly with the ds1672 hardware, we use
|
||||
* rtc_time -- month 0-11, hour 0-23, yr = calendar year-epoch
|
||||
* Epoch is initialized as 2000. Time is set to UTC.
|
||||
*/
|
||||
static int ds1672_get_datetime(struct i2c_client *client, struct rtc_time *tm)
|
||||
{
|
||||
unsigned long time;
|
||||
unsigned char addr = DS1672_REG_CNT_BASE;
|
||||
unsigned char buf[4];
|
||||
|
||||
struct i2c_msg msgs[] = {
|
||||
{ client->addr, 0, 1, &addr }, /* setup read ptr */
|
||||
{ client->addr, I2C_M_RD, 4, buf }, /* read date */
|
||||
};
|
||||
|
||||
/* read date registers */
|
||||
if ((i2c_transfer(client->adapter, &msgs[0], 2)) != 2) {
|
||||
dev_err(&client->dev, "%s: read error\n", __FUNCTION__);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
dev_dbg(&client->dev,
|
||||
"%s: raw read data - counters=%02x,%02x,%02x,%02x\n",
|
||||
__FUNCTION__, buf[0], buf[1], buf[2], buf[3]);
|
||||
|
||||
time = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
|
||||
|
||||
rtc_time_to_tm(time, tm);
|
||||
|
||||
dev_dbg(&client->dev, "%s: tm is secs=%d, mins=%d, hours=%d, "
|
||||
"mday=%d, mon=%d, year=%d, wday=%d\n",
|
||||
__FUNCTION__, tm->tm_sec, tm->tm_min, tm->tm_hour,
|
||||
tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ds1672_set_mmss(struct i2c_client *client, unsigned long secs)
|
||||
{
|
||||
int xfer;
|
||||
unsigned char buf[6];
|
||||
|
||||
buf[0] = DS1672_REG_CNT_BASE;
|
||||
buf[1] = secs & 0x000000FF;
|
||||
buf[2] = (secs & 0x0000FF00) >> 8;
|
||||
buf[3] = (secs & 0x00FF0000) >> 16;
|
||||
buf[4] = (secs & 0xFF000000) >> 24;
|
||||
buf[5] = 0; /* set control reg to enable counting */
|
||||
|
||||
xfer = i2c_master_send(client, buf, 6);
|
||||
if (xfer != 6) {
|
||||
dev_err(&client->dev, "%s: send: %d\n", __FUNCTION__, xfer);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ds1672_set_datetime(struct i2c_client *client, struct rtc_time *tm)
|
||||
{
|
||||
unsigned long secs;
|
||||
|
||||
dev_dbg(&client->dev,
|
||||
"%s: secs=%d, mins=%d, hours=%d, "
|
||||
"mday=%d, mon=%d, year=%d, wday=%d\n",
|
||||
__FUNCTION__,
|
||||
tm->tm_sec, tm->tm_min, tm->tm_hour,
|
||||
tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday);
|
||||
|
||||
rtc_tm_to_time(tm, &secs);
|
||||
|
||||
return ds1672_set_mmss(client, secs);
|
||||
}
|
||||
|
||||
static int ds1672_rtc_read_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
return ds1672_get_datetime(to_i2c_client(dev), tm);
|
||||
}
|
||||
|
||||
static int ds1672_rtc_set_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
return ds1672_set_datetime(to_i2c_client(dev), tm);
|
||||
}
|
||||
|
||||
static int ds1672_rtc_set_mmss(struct device *dev, unsigned long secs)
|
||||
{
|
||||
return ds1672_set_mmss(to_i2c_client(dev), secs);
|
||||
}
|
||||
|
||||
static int ds1672_get_control(struct i2c_client *client, u8 *status)
|
||||
{
|
||||
unsigned char addr = DS1672_REG_CONTROL;
|
||||
|
||||
struct i2c_msg msgs[] = {
|
||||
{ client->addr, 0, 1, &addr }, /* setup read ptr */
|
||||
{ client->addr, I2C_M_RD, 1, status }, /* read control */
|
||||
};
|
||||
|
||||
/* read control register */
|
||||
if ((i2c_transfer(client->adapter, &msgs[0], 2)) != 2) {
|
||||
dev_err(&client->dev, "%s: read error\n", __FUNCTION__);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* following are the sysfs callback functions */
|
||||
static ssize_t show_control(struct device *dev, struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
u8 control;
|
||||
int err;
|
||||
|
||||
err = ds1672_get_control(client, &control);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return sprintf(buf, "%s\n", (control & DS1672_REG_CONTROL_EOSC)
|
||||
? "disabled" : "enabled");
|
||||
}
|
||||
static DEVICE_ATTR(control, S_IRUGO, show_control, NULL);
|
||||
|
||||
static const struct rtc_class_ops ds1672_rtc_ops = {
|
||||
.read_time = ds1672_rtc_read_time,
|
||||
.set_time = ds1672_rtc_set_time,
|
||||
.set_mmss = ds1672_rtc_set_mmss,
|
||||
};
|
||||
|
||||
static int ds1672_attach(struct i2c_adapter *adapter)
|
||||
{
|
||||
return i2c_probe(adapter, &addr_data, ds1672_probe);
|
||||
}
|
||||
|
||||
static int ds1672_detach(struct i2c_client *client)
|
||||
{
|
||||
int err;
|
||||
struct rtc_device *rtc = i2c_get_clientdata(client);
|
||||
|
||||
if (rtc)
|
||||
rtc_device_unregister(rtc);
|
||||
|
||||
if ((err = i2c_detach_client(client)))
|
||||
return err;
|
||||
|
||||
kfree(client);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct i2c_driver ds1672_driver = {
|
||||
.driver = {
|
||||
.name = "ds1672",
|
||||
},
|
||||
.id = I2C_DRIVERID_DS1672,
|
||||
.attach_adapter = &ds1672_attach,
|
||||
.detach_client = &ds1672_detach,
|
||||
};
|
||||
|
||||
static int ds1672_probe(struct i2c_adapter *adapter, int address, int kind)
|
||||
{
|
||||
int err = 0;
|
||||
u8 control;
|
||||
struct i2c_client *client;
|
||||
struct rtc_device *rtc;
|
||||
|
||||
dev_dbg(&adapter->dev, "%s\n", __FUNCTION__);
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) {
|
||||
err = -ENODEV;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!(client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* I2C client */
|
||||
client->addr = address;
|
||||
client->driver = &ds1672_driver;
|
||||
client->adapter = adapter;
|
||||
|
||||
strlcpy(client->name, ds1672_driver.driver.name, I2C_NAME_SIZE);
|
||||
|
||||
/* Inform the i2c layer */
|
||||
if ((err = i2c_attach_client(client)))
|
||||
goto exit_kfree;
|
||||
|
||||
dev_info(&client->dev, "chip found, driver version " DRV_VERSION "\n");
|
||||
|
||||
rtc = rtc_device_register(ds1672_driver.driver.name, &client->dev,
|
||||
&ds1672_rtc_ops, THIS_MODULE);
|
||||
|
||||
if (IS_ERR(rtc)) {
|
||||
err = PTR_ERR(rtc);
|
||||
goto exit_detach;
|
||||
}
|
||||
|
||||
i2c_set_clientdata(client, rtc);
|
||||
|
||||
/* read control register */
|
||||
err = ds1672_get_control(client, &control);
|
||||
if (err)
|
||||
goto exit_devreg;
|
||||
|
||||
if (control & DS1672_REG_CONTROL_EOSC)
|
||||
dev_warn(&client->dev, "Oscillator not enabled. "
|
||||
"Set time to enable.\n");
|
||||
|
||||
/* Register sysfs hooks */
|
||||
err = device_create_file(&client->dev, &dev_attr_control);
|
||||
if (err)
|
||||
goto exit_devreg;
|
||||
|
||||
return 0;
|
||||
|
||||
exit_devreg:
|
||||
rtc_device_unregister(rtc);
|
||||
|
||||
exit_detach:
|
||||
i2c_detach_client(client);
|
||||
|
||||
exit_kfree:
|
||||
kfree(client);
|
||||
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int __init ds1672_init(void)
|
||||
{
|
||||
return i2c_add_driver(&ds1672_driver);
|
||||
}
|
||||
|
||||
static void __exit ds1672_exit(void)
|
||||
{
|
||||
i2c_del_driver(&ds1672_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Alessandro Zummo <a.zummo@towertech.it>");
|
||||
MODULE_DESCRIPTION("Dallas/Maxim DS1672 timekeeper driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_VERSION(DRV_VERSION);
|
||||
|
||||
module_init(ds1672_init);
|
||||
module_exit(ds1672_exit);
|
||||
274
drivers/rtc/rtc-ds1742.c
Normal file
274
drivers/rtc/rtc-ds1742.c
Normal file
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* An rtc driver for the Dallas DS1742
|
||||
*
|
||||
* Copyright (C) 2006 Atsushi Nemoto <anemo@mba.ocn.ne.jp>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* Copyright (C) 2006 Torsten Ertbjerg Rasmussen <tr@newtec.dk>
|
||||
* - nvram size determined from resource
|
||||
* - this ds1742 driver now supports ds1743.
|
||||
*/
|
||||
|
||||
#include <linux/bcd.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/rtc.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/io.h>
|
||||
|
||||
#define DRV_VERSION "0.3"
|
||||
|
||||
#define RTC_SIZE 8
|
||||
|
||||
#define RTC_CONTROL 0
|
||||
#define RTC_CENTURY 0
|
||||
#define RTC_SECONDS 1
|
||||
#define RTC_MINUTES 2
|
||||
#define RTC_HOURS 3
|
||||
#define RTC_DAY 4
|
||||
#define RTC_DATE 5
|
||||
#define RTC_MONTH 6
|
||||
#define RTC_YEAR 7
|
||||
|
||||
#define RTC_CENTURY_MASK 0x3f
|
||||
#define RTC_SECONDS_MASK 0x7f
|
||||
#define RTC_DAY_MASK 0x07
|
||||
|
||||
/* Bits in the Control/Century register */
|
||||
#define RTC_WRITE 0x80
|
||||
#define RTC_READ 0x40
|
||||
|
||||
/* Bits in the Seconds register */
|
||||
#define RTC_STOP 0x80
|
||||
|
||||
/* Bits in the Day register */
|
||||
#define RTC_BATT_FLAG 0x80
|
||||
|
||||
struct rtc_plat_data {
|
||||
struct rtc_device *rtc;
|
||||
void __iomem *ioaddr_nvram;
|
||||
void __iomem *ioaddr_rtc;
|
||||
size_t size_nvram;
|
||||
size_t size;
|
||||
unsigned long baseaddr;
|
||||
unsigned long last_jiffies;
|
||||
};
|
||||
|
||||
static int ds1742_rtc_set_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
|
||||
void __iomem *ioaddr = pdata->ioaddr_rtc;
|
||||
u8 century;
|
||||
|
||||
century = BIN2BCD((tm->tm_year + 1900) / 100);
|
||||
|
||||
writeb(RTC_WRITE, ioaddr + RTC_CONTROL);
|
||||
|
||||
writeb(BIN2BCD(tm->tm_year % 100), ioaddr + RTC_YEAR);
|
||||
writeb(BIN2BCD(tm->tm_mon + 1), ioaddr + RTC_MONTH);
|
||||
writeb(BIN2BCD(tm->tm_wday) & RTC_DAY_MASK, ioaddr + RTC_DAY);
|
||||
writeb(BIN2BCD(tm->tm_mday), ioaddr + RTC_DATE);
|
||||
writeb(BIN2BCD(tm->tm_hour), ioaddr + RTC_HOURS);
|
||||
writeb(BIN2BCD(tm->tm_min), ioaddr + RTC_MINUTES);
|
||||
writeb(BIN2BCD(tm->tm_sec) & RTC_SECONDS_MASK, ioaddr + RTC_SECONDS);
|
||||
|
||||
/* RTC_CENTURY and RTC_CONTROL share same register */
|
||||
writeb(RTC_WRITE | (century & RTC_CENTURY_MASK), ioaddr + RTC_CENTURY);
|
||||
writeb(century & RTC_CENTURY_MASK, ioaddr + RTC_CONTROL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ds1742_rtc_read_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
|
||||
void __iomem *ioaddr = pdata->ioaddr_rtc;
|
||||
unsigned int year, month, day, hour, minute, second, week;
|
||||
unsigned int century;
|
||||
|
||||
/* give enough time to update RTC in case of continuous read */
|
||||
if (pdata->last_jiffies == jiffies)
|
||||
msleep(1);
|
||||
pdata->last_jiffies = jiffies;
|
||||
writeb(RTC_READ, ioaddr + RTC_CONTROL);
|
||||
second = readb(ioaddr + RTC_SECONDS) & RTC_SECONDS_MASK;
|
||||
minute = readb(ioaddr + RTC_MINUTES);
|
||||
hour = readb(ioaddr + RTC_HOURS);
|
||||
day = readb(ioaddr + RTC_DATE);
|
||||
week = readb(ioaddr + RTC_DAY) & RTC_DAY_MASK;
|
||||
month = readb(ioaddr + RTC_MONTH);
|
||||
year = readb(ioaddr + RTC_YEAR);
|
||||
century = readb(ioaddr + RTC_CENTURY) & RTC_CENTURY_MASK;
|
||||
writeb(0, ioaddr + RTC_CONTROL);
|
||||
tm->tm_sec = BCD2BIN(second);
|
||||
tm->tm_min = BCD2BIN(minute);
|
||||
tm->tm_hour = BCD2BIN(hour);
|
||||
tm->tm_mday = BCD2BIN(day);
|
||||
tm->tm_wday = BCD2BIN(week);
|
||||
tm->tm_mon = BCD2BIN(month) - 1;
|
||||
/* year is 1900 + tm->tm_year */
|
||||
tm->tm_year = BCD2BIN(year) + BCD2BIN(century) * 100 - 1900;
|
||||
|
||||
if (rtc_valid_tm(tm) < 0) {
|
||||
dev_err(dev, "retrieved date/time is not valid.\n");
|
||||
rtc_time_to_tm(0, tm);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct rtc_class_ops ds1742_rtc_ops = {
|
||||
.read_time = ds1742_rtc_read_time,
|
||||
.set_time = ds1742_rtc_set_time,
|
||||
};
|
||||
|
||||
static ssize_t ds1742_nvram_read(struct kobject *kobj, char *buf,
|
||||
loff_t pos, size_t size)
|
||||
{
|
||||
struct platform_device *pdev =
|
||||
to_platform_device(container_of(kobj, struct device, kobj));
|
||||
struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
|
||||
void __iomem *ioaddr = pdata->ioaddr_nvram;
|
||||
ssize_t count;
|
||||
|
||||
for (count = 0; size > 0 && pos < pdata->size_nvram; count++, size--)
|
||||
*buf++ = readb(ioaddr + pos++);
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t ds1742_nvram_write(struct kobject *kobj, char *buf,
|
||||
loff_t pos, size_t size)
|
||||
{
|
||||
struct platform_device *pdev =
|
||||
to_platform_device(container_of(kobj, struct device, kobj));
|
||||
struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
|
||||
void __iomem *ioaddr = pdata->ioaddr_nvram;
|
||||
ssize_t count;
|
||||
|
||||
for (count = 0; size > 0 && pos < pdata->size_nvram; count++, size--)
|
||||
writeb(*buf++, ioaddr + pos++);
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct bin_attribute ds1742_nvram_attr = {
|
||||
.attr = {
|
||||
.name = "nvram",
|
||||
.mode = S_IRUGO | S_IWUGO,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.read = ds1742_nvram_read,
|
||||
.write = ds1742_nvram_write,
|
||||
};
|
||||
|
||||
static int __devinit ds1742_rtc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct rtc_device *rtc;
|
||||
struct resource *res;
|
||||
unsigned int cen, sec;
|
||||
struct rtc_plat_data *pdata = NULL;
|
||||
void __iomem *ioaddr = NULL;
|
||||
int ret = 0;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res)
|
||||
return -ENODEV;
|
||||
pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
|
||||
if (!pdata)
|
||||
return -ENOMEM;
|
||||
pdata->size = res->end - res->start + 1;
|
||||
if (!request_mem_region(res->start, pdata->size, pdev->name)) {
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
pdata->baseaddr = res->start;
|
||||
ioaddr = ioremap(pdata->baseaddr, pdata->size);
|
||||
if (!ioaddr) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
pdata->ioaddr_nvram = ioaddr;
|
||||
pdata->size_nvram = pdata->size - RTC_SIZE;
|
||||
pdata->ioaddr_rtc = ioaddr + pdata->size_nvram;
|
||||
|
||||
/* turn RTC on if it was not on */
|
||||
ioaddr = pdata->ioaddr_rtc;
|
||||
sec = readb(ioaddr + RTC_SECONDS);
|
||||
if (sec & RTC_STOP) {
|
||||
sec &= RTC_SECONDS_MASK;
|
||||
cen = readb(ioaddr + RTC_CENTURY) & RTC_CENTURY_MASK;
|
||||
writeb(RTC_WRITE, ioaddr + RTC_CONTROL);
|
||||
writeb(sec, ioaddr + RTC_SECONDS);
|
||||
writeb(cen & RTC_CENTURY_MASK, ioaddr + RTC_CONTROL);
|
||||
}
|
||||
if (!(readb(ioaddr + RTC_DAY) & RTC_BATT_FLAG))
|
||||
dev_warn(&pdev->dev, "voltage-low detected.\n");
|
||||
|
||||
rtc = rtc_device_register(pdev->name, &pdev->dev,
|
||||
&ds1742_rtc_ops, THIS_MODULE);
|
||||
if (IS_ERR(rtc)) {
|
||||
ret = PTR_ERR(rtc);
|
||||
goto out;
|
||||
}
|
||||
pdata->rtc = rtc;
|
||||
pdata->last_jiffies = jiffies;
|
||||
platform_set_drvdata(pdev, pdata);
|
||||
ds1742_nvram_attr.size = max(ds1742_nvram_attr.size,
|
||||
pdata->size_nvram);
|
||||
ret = sysfs_create_bin_file(&pdev->dev.kobj, &ds1742_nvram_attr);
|
||||
if (ret)
|
||||
goto out;
|
||||
return 0;
|
||||
out:
|
||||
if (pdata->rtc)
|
||||
rtc_device_unregister(pdata->rtc);
|
||||
if (pdata->ioaddr_nvram)
|
||||
iounmap(pdata->ioaddr_nvram);
|
||||
if (pdata->baseaddr)
|
||||
release_mem_region(pdata->baseaddr, pdata->size);
|
||||
kfree(pdata);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devexit ds1742_rtc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
|
||||
|
||||
sysfs_remove_bin_file(&pdev->dev.kobj, &ds1742_nvram_attr);
|
||||
rtc_device_unregister(pdata->rtc);
|
||||
iounmap(pdata->ioaddr_nvram);
|
||||
release_mem_region(pdata->baseaddr, pdata->size);
|
||||
kfree(pdata);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver ds1742_rtc_driver = {
|
||||
.probe = ds1742_rtc_probe,
|
||||
.remove = __devexit_p(ds1742_rtc_remove),
|
||||
.driver = {
|
||||
.name = "ds1742",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static __init int ds1742_init(void)
|
||||
{
|
||||
return platform_driver_register(&ds1742_rtc_driver);
|
||||
}
|
||||
|
||||
static __exit void ds1742_exit(void)
|
||||
{
|
||||
return platform_driver_unregister(&ds1742_rtc_driver);
|
||||
}
|
||||
|
||||
module_init(ds1742_init);
|
||||
module_exit(ds1742_exit);
|
||||
|
||||
MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>");
|
||||
MODULE_DESCRIPTION("Dallas DS1742 RTC driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_VERSION(DRV_VERSION);
|
||||
160
drivers/rtc/rtc-ep93xx.c
Normal file
160
drivers/rtc/rtc-ep93xx.c
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* A driver for the RTC embedded in the Cirrus Logic EP93XX processors
|
||||
* Copyright (c) 2006 Tower Technologies
|
||||
*
|
||||
* Author: Alessandro Zummo <a.zummo@towertech.it>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/rtc.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <asm/hardware.h>
|
||||
|
||||
#define EP93XX_RTC_REG(x) (EP93XX_RTC_BASE + (x))
|
||||
#define EP93XX_RTC_DATA EP93XX_RTC_REG(0x0000)
|
||||
#define EP93XX_RTC_LOAD EP93XX_RTC_REG(0x000C)
|
||||
#define EP93XX_RTC_SWCOMP EP93XX_RTC_REG(0x0108)
|
||||
|
||||
#define DRV_VERSION "0.2"
|
||||
|
||||
static int ep93xx_get_swcomp(struct device *dev, unsigned short *preload,
|
||||
unsigned short *delete)
|
||||
{
|
||||
unsigned short comp = __raw_readl(EP93XX_RTC_SWCOMP);
|
||||
|
||||
if (preload)
|
||||
*preload = comp & 0xffff;
|
||||
|
||||
if (delete)
|
||||
*delete = (comp >> 16) & 0x1f;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ep93xx_rtc_read_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
unsigned long time = __raw_readl(EP93XX_RTC_DATA);
|
||||
|
||||
rtc_time_to_tm(time, tm);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ep93xx_rtc_set_mmss(struct device *dev, unsigned long secs)
|
||||
{
|
||||
__raw_writel(secs + 1, EP93XX_RTC_LOAD);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ep93xx_rtc_set_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
int err;
|
||||
unsigned long secs;
|
||||
|
||||
err = rtc_tm_to_time(tm, &secs);
|
||||
if (err != 0)
|
||||
return err;
|
||||
|
||||
return ep93xx_rtc_set_mmss(dev, secs);
|
||||
}
|
||||
|
||||
static int ep93xx_rtc_proc(struct device *dev, struct seq_file *seq)
|
||||
{
|
||||
unsigned short preload, delete;
|
||||
|
||||
ep93xx_get_swcomp(dev, &preload, &delete);
|
||||
|
||||
seq_printf(seq, "preload\t\t: %d\n", preload);
|
||||
seq_printf(seq, "delete\t\t: %d\n", delete);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct rtc_class_ops ep93xx_rtc_ops = {
|
||||
.read_time = ep93xx_rtc_read_time,
|
||||
.set_time = ep93xx_rtc_set_time,
|
||||
.set_mmss = ep93xx_rtc_set_mmss,
|
||||
.proc = ep93xx_rtc_proc,
|
||||
};
|
||||
|
||||
static ssize_t ep93xx_sysfs_show_comp_preload(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
unsigned short preload;
|
||||
|
||||
ep93xx_get_swcomp(dev, &preload, NULL);
|
||||
|
||||
return sprintf(buf, "%d\n", preload);
|
||||
}
|
||||
static DEVICE_ATTR(comp_preload, S_IRUGO, ep93xx_sysfs_show_comp_preload, NULL);
|
||||
|
||||
static ssize_t ep93xx_sysfs_show_comp_delete(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
unsigned short delete;
|
||||
|
||||
ep93xx_get_swcomp(dev, NULL, &delete);
|
||||
|
||||
return sprintf(buf, "%d\n", delete);
|
||||
}
|
||||
static DEVICE_ATTR(comp_delete, S_IRUGO, ep93xx_sysfs_show_comp_delete, NULL);
|
||||
|
||||
|
||||
static int __devinit ep93xx_rtc_probe(struct platform_device *dev)
|
||||
{
|
||||
struct rtc_device *rtc = rtc_device_register("ep93xx",
|
||||
&dev->dev, &ep93xx_rtc_ops, THIS_MODULE);
|
||||
|
||||
if (IS_ERR(rtc)) {
|
||||
return PTR_ERR(rtc);
|
||||
}
|
||||
|
||||
platform_set_drvdata(dev, rtc);
|
||||
|
||||
device_create_file(&dev->dev, &dev_attr_comp_preload);
|
||||
device_create_file(&dev->dev, &dev_attr_comp_delete);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devexit ep93xx_rtc_remove(struct platform_device *dev)
|
||||
{
|
||||
struct rtc_device *rtc = platform_get_drvdata(dev);
|
||||
|
||||
if (rtc)
|
||||
rtc_device_unregister(rtc);
|
||||
|
||||
platform_set_drvdata(dev, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver ep93xx_rtc_platform_driver = {
|
||||
.driver = {
|
||||
.name = "ep93xx-rtc",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = ep93xx_rtc_probe,
|
||||
.remove = __devexit_p(ep93xx_rtc_remove),
|
||||
};
|
||||
|
||||
static int __init ep93xx_rtc_init(void)
|
||||
{
|
||||
return platform_driver_register(&ep93xx_rtc_platform_driver);
|
||||
}
|
||||
|
||||
static void __exit ep93xx_rtc_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&ep93xx_rtc_platform_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Alessandro Zummo <a.zummo@towertech.it>");
|
||||
MODULE_DESCRIPTION("EP93XX RTC driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_VERSION(DRV_VERSION);
|
||||
|
||||
module_init(ep93xx_rtc_init);
|
||||
module_exit(ep93xx_rtc_exit);
|
||||
591
drivers/rtc/rtc-isl1208.c
Normal file
591
drivers/rtc/rtc-isl1208.c
Normal file
@@ -0,0 +1,591 @@
|
||||
/*
|
||||
* Intersil ISL1208 rtc class driver
|
||||
*
|
||||
* Copyright 2005,2006 Hebert Valerio Riedel <hvr@gnu.org>
|
||||
*
|
||||
* 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/i2c.h>
|
||||
#include <linux/bcd.h>
|
||||
#include <linux/rtc.h>
|
||||
|
||||
#define DRV_NAME "isl1208"
|
||||
#define DRV_VERSION "0.2"
|
||||
|
||||
/* Register map */
|
||||
/* rtc section */
|
||||
#define ISL1208_REG_SC 0x00
|
||||
#define ISL1208_REG_MN 0x01
|
||||
#define ISL1208_REG_HR 0x02
|
||||
#define ISL1208_REG_HR_MIL (1<<7) /* 24h/12h mode */
|
||||
#define ISL1208_REG_HR_PM (1<<5) /* PM/AM bit in 12h mode */
|
||||
#define ISL1208_REG_DT 0x03
|
||||
#define ISL1208_REG_MO 0x04
|
||||
#define ISL1208_REG_YR 0x05
|
||||
#define ISL1208_REG_DW 0x06
|
||||
#define ISL1208_RTC_SECTION_LEN 7
|
||||
|
||||
/* control/status section */
|
||||
#define ISL1208_REG_SR 0x07
|
||||
#define ISL1208_REG_SR_ARST (1<<7) /* auto reset */
|
||||
#define ISL1208_REG_SR_XTOSCB (1<<6) /* crystal oscillator */
|
||||
#define ISL1208_REG_SR_WRTC (1<<4) /* write rtc */
|
||||
#define ISL1208_REG_SR_ALM (1<<2) /* alarm */
|
||||
#define ISL1208_REG_SR_BAT (1<<1) /* battery */
|
||||
#define ISL1208_REG_SR_RTCF (1<<0) /* rtc fail */
|
||||
#define ISL1208_REG_INT 0x08
|
||||
#define ISL1208_REG_09 0x09 /* reserved */
|
||||
#define ISL1208_REG_ATR 0x0a
|
||||
#define ISL1208_REG_DTR 0x0b
|
||||
|
||||
/* alarm section */
|
||||
#define ISL1208_REG_SCA 0x0c
|
||||
#define ISL1208_REG_MNA 0x0d
|
||||
#define ISL1208_REG_HRA 0x0e
|
||||
#define ISL1208_REG_DTA 0x0f
|
||||
#define ISL1208_REG_MOA 0x10
|
||||
#define ISL1208_REG_DWA 0x11
|
||||
#define ISL1208_ALARM_SECTION_LEN 6
|
||||
|
||||
/* user section */
|
||||
#define ISL1208_REG_USR1 0x12
|
||||
#define ISL1208_REG_USR2 0x13
|
||||
#define ISL1208_USR_SECTION_LEN 2
|
||||
|
||||
/* i2c configuration */
|
||||
#define ISL1208_I2C_ADDR 0xde
|
||||
|
||||
static unsigned short normal_i2c[] = {
|
||||
ISL1208_I2C_ADDR>>1, I2C_CLIENT_END
|
||||
};
|
||||
I2C_CLIENT_INSMOD; /* defines addr_data */
|
||||
|
||||
static int isl1208_attach_adapter(struct i2c_adapter *adapter);
|
||||
static int isl1208_detach_client(struct i2c_client *client);
|
||||
|
||||
static struct i2c_driver isl1208_driver = {
|
||||
.driver = {
|
||||
.name = DRV_NAME,
|
||||
},
|
||||
.id = I2C_DRIVERID_ISL1208,
|
||||
.attach_adapter = &isl1208_attach_adapter,
|
||||
.detach_client = &isl1208_detach_client,
|
||||
};
|
||||
|
||||
/* block read */
|
||||
static int
|
||||
isl1208_i2c_read_regs(struct i2c_client *client, u8 reg, u8 buf[],
|
||||
unsigned len)
|
||||
{
|
||||
u8 reg_addr[1] = { reg };
|
||||
struct i2c_msg msgs[2] = {
|
||||
{ client->addr, client->flags, sizeof(reg_addr), reg_addr },
|
||||
{ client->addr, client->flags | I2C_M_RD, len, buf }
|
||||
};
|
||||
int ret;
|
||||
|
||||
BUG_ON(len == 0);
|
||||
BUG_ON(reg > ISL1208_REG_USR2);
|
||||
BUG_ON(reg + len > ISL1208_REG_USR2 + 1);
|
||||
|
||||
ret = i2c_transfer(client->adapter, msgs, 2);
|
||||
if (ret > 0)
|
||||
ret = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* block write */
|
||||
static int
|
||||
isl1208_i2c_set_regs(struct i2c_client *client, u8 reg, u8 const buf[],
|
||||
unsigned len)
|
||||
{
|
||||
u8 i2c_buf[ISL1208_REG_USR2 + 2];
|
||||
struct i2c_msg msgs[1] = {
|
||||
{ client->addr, client->flags, len + 1, i2c_buf }
|
||||
};
|
||||
int ret;
|
||||
|
||||
BUG_ON(len == 0);
|
||||
BUG_ON(reg > ISL1208_REG_USR2);
|
||||
BUG_ON(reg + len > ISL1208_REG_USR2 + 1);
|
||||
|
||||
i2c_buf[0] = reg;
|
||||
memcpy(&i2c_buf[1], &buf[0], len);
|
||||
|
||||
ret = i2c_transfer(client->adapter, msgs, 1);
|
||||
if (ret > 0)
|
||||
ret = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* simple check to see wether we have a isl1208 */
|
||||
static int isl1208_i2c_validate_client(struct i2c_client *client)
|
||||
{
|
||||
u8 regs[ISL1208_RTC_SECTION_LEN] = { 0, };
|
||||
u8 zero_mask[ISL1208_RTC_SECTION_LEN] = {
|
||||
0x80, 0x80, 0x40, 0xc0, 0xe0, 0x00, 0xf8
|
||||
};
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
ret = isl1208_i2c_read_regs(client, 0, regs, ISL1208_RTC_SECTION_LEN);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < ISL1208_RTC_SECTION_LEN; ++i) {
|
||||
if (regs[i] & zero_mask[i]) /* check if bits are cleared */
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isl1208_i2c_get_sr(struct i2c_client *client)
|
||||
{
|
||||
return i2c_smbus_read_byte_data(client, ISL1208_REG_SR) == -1 ? -EIO:0;
|
||||
}
|
||||
|
||||
static int isl1208_i2c_get_atr(struct i2c_client *client)
|
||||
{
|
||||
int atr = i2c_smbus_read_byte_data(client, ISL1208_REG_ATR);
|
||||
|
||||
if (atr < 0)
|
||||
return -EIO;
|
||||
|
||||
/* The 6bit value in the ATR register controls the load
|
||||
* capacitance C_load * in steps of 0.25pF
|
||||
*
|
||||
* bit (1<<5) of the ATR register is inverted
|
||||
*
|
||||
* C_load(ATR=0x20) = 4.50pF
|
||||
* C_load(ATR=0x00) = 12.50pF
|
||||
* C_load(ATR=0x1f) = 20.25pF
|
||||
*
|
||||
*/
|
||||
|
||||
atr &= 0x3f; /* mask out lsb */
|
||||
atr ^= 1<<5; /* invert 6th bit */
|
||||
atr += 2*9; /* add offset of 4.5pF; unit[atr] = 0.25pF */
|
||||
|
||||
return atr;
|
||||
}
|
||||
|
||||
static int isl1208_i2c_get_dtr(struct i2c_client *client)
|
||||
{
|
||||
int dtr = i2c_smbus_read_byte_data(client, ISL1208_REG_DTR);
|
||||
|
||||
if (dtr < 0)
|
||||
return -EIO;
|
||||
|
||||
/* dtr encodes adjustments of {-60,-40,-20,0,20,40,60} ppm */
|
||||
dtr = ((dtr & 0x3) * 20) * (dtr & (1<<2) ? -1 : 1);
|
||||
|
||||
return dtr;
|
||||
}
|
||||
|
||||
static int isl1208_i2c_get_usr(struct i2c_client *client)
|
||||
{
|
||||
u8 buf[ISL1208_USR_SECTION_LEN] = { 0, };
|
||||
int ret;
|
||||
|
||||
ret = isl1208_i2c_read_regs (client, ISL1208_REG_USR1, buf,
|
||||
ISL1208_USR_SECTION_LEN);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return (buf[1] << 8) | buf[0];
|
||||
}
|
||||
|
||||
static int isl1208_i2c_set_usr(struct i2c_client *client, u16 usr)
|
||||
{
|
||||
u8 buf[ISL1208_USR_SECTION_LEN];
|
||||
|
||||
buf[0] = usr & 0xff;
|
||||
buf[1] = (usr >> 8) & 0xff;
|
||||
|
||||
return isl1208_i2c_set_regs (client, ISL1208_REG_USR1, buf,
|
||||
ISL1208_USR_SECTION_LEN);
|
||||
}
|
||||
|
||||
static int isl1208_rtc_proc(struct device *dev, struct seq_file *seq)
|
||||
{
|
||||
struct i2c_client *const client = to_i2c_client(dev);
|
||||
int sr, dtr, atr, usr;
|
||||
|
||||
sr = isl1208_i2c_get_sr(client);
|
||||
if (sr < 0) {
|
||||
dev_err(&client->dev, "%s: reading SR failed\n", __func__);
|
||||
return sr;
|
||||
}
|
||||
|
||||
seq_printf(seq, "status_reg\t:%s%s%s%s%s%s (0x%.2x)\n",
|
||||
(sr & ISL1208_REG_SR_RTCF) ? " RTCF" : "",
|
||||
(sr & ISL1208_REG_SR_BAT) ? " BAT" : "",
|
||||
(sr & ISL1208_REG_SR_ALM) ? " ALM" : "",
|
||||
(sr & ISL1208_REG_SR_WRTC) ? " WRTC" : "",
|
||||
(sr & ISL1208_REG_SR_XTOSCB) ? " XTOSCB" : "",
|
||||
(sr & ISL1208_REG_SR_ARST) ? " ARST" : "",
|
||||
sr);
|
||||
|
||||
seq_printf(seq, "batt_status\t: %s\n",
|
||||
(sr & ISL1208_REG_SR_RTCF) ? "bad" : "okay");
|
||||
|
||||
dtr = isl1208_i2c_get_dtr(client);
|
||||
if (dtr >= 0 -1)
|
||||
seq_printf(seq, "digital_trim\t: %d ppm\n", dtr);
|
||||
|
||||
atr = isl1208_i2c_get_atr(client);
|
||||
if (atr >= 0)
|
||||
seq_printf(seq, "analog_trim\t: %d.%.2d pF\n",
|
||||
atr>>2, (atr&0x3)*25);
|
||||
|
||||
usr = isl1208_i2c_get_usr(client);
|
||||
if (usr >= 0)
|
||||
seq_printf(seq, "user_data\t: 0x%.4x\n", usr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int isl1208_i2c_read_time(struct i2c_client *client,
|
||||
struct rtc_time *tm)
|
||||
{
|
||||
int sr;
|
||||
u8 regs[ISL1208_RTC_SECTION_LEN] = { 0, };
|
||||
|
||||
sr = isl1208_i2c_get_sr(client);
|
||||
if (sr < 0) {
|
||||
dev_err(&client->dev, "%s: reading SR failed\n", __func__);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
sr = isl1208_i2c_read_regs(client, 0, regs, ISL1208_RTC_SECTION_LEN);
|
||||
if (sr < 0) {
|
||||
dev_err(&client->dev, "%s: reading RTC section failed\n",
|
||||
__func__);
|
||||
return sr;
|
||||
}
|
||||
|
||||
tm->tm_sec = BCD2BIN(regs[ISL1208_REG_SC]);
|
||||
tm->tm_min = BCD2BIN(regs[ISL1208_REG_MN]);
|
||||
{ /* HR field has a more complex interpretation */
|
||||
const u8 _hr = regs[ISL1208_REG_HR];
|
||||
if (_hr & ISL1208_REG_HR_MIL) /* 24h format */
|
||||
tm->tm_hour = BCD2BIN(_hr & 0x3f);
|
||||
else { // 12h format
|
||||
tm->tm_hour = BCD2BIN(_hr & 0x1f);
|
||||
if (_hr & ISL1208_REG_HR_PM) /* PM flag set */
|
||||
tm->tm_hour += 12;
|
||||
}
|
||||
}
|
||||
|
||||
tm->tm_mday = BCD2BIN(regs[ISL1208_REG_DT]);
|
||||
tm->tm_mon = BCD2BIN(regs[ISL1208_REG_MO]) - 1; /* rtc starts at 1 */
|
||||
tm->tm_year = BCD2BIN(regs[ISL1208_REG_YR]) + 100;
|
||||
tm->tm_wday = BCD2BIN(regs[ISL1208_REG_DW]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isl1208_i2c_read_alarm(struct i2c_client *client,
|
||||
struct rtc_wkalrm *alarm)
|
||||
{
|
||||
struct rtc_time *const tm = &alarm->time;
|
||||
u8 regs[ISL1208_ALARM_SECTION_LEN] = { 0, };
|
||||
int sr;
|
||||
|
||||
sr = isl1208_i2c_get_sr(client);
|
||||
if (sr < 0) {
|
||||
dev_err(&client->dev, "%s: reading SR failed\n", __func__);
|
||||
return sr;
|
||||
}
|
||||
|
||||
sr = isl1208_i2c_read_regs(client, ISL1208_REG_SCA, regs,
|
||||
ISL1208_ALARM_SECTION_LEN);
|
||||
if (sr < 0) {
|
||||
dev_err(&client->dev, "%s: reading alarm section failed\n",
|
||||
__func__);
|
||||
return sr;
|
||||
}
|
||||
|
||||
/* MSB of each alarm register is an enable bit */
|
||||
tm->tm_sec = BCD2BIN(regs[ISL1208_REG_SCA-ISL1208_REG_SCA] & 0x7f);
|
||||
tm->tm_min = BCD2BIN(regs[ISL1208_REG_MNA-ISL1208_REG_SCA] & 0x7f);
|
||||
tm->tm_hour = BCD2BIN(regs[ISL1208_REG_HRA-ISL1208_REG_SCA] & 0x3f);
|
||||
tm->tm_mday = BCD2BIN(regs[ISL1208_REG_DTA-ISL1208_REG_SCA] & 0x3f);
|
||||
tm->tm_mon = BCD2BIN(regs[ISL1208_REG_MOA-ISL1208_REG_SCA] & 0x1f)-1;
|
||||
tm->tm_wday = BCD2BIN(regs[ISL1208_REG_DWA-ISL1208_REG_SCA] & 0x03);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isl1208_rtc_read_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
return isl1208_i2c_read_time(to_i2c_client(dev), tm);
|
||||
}
|
||||
|
||||
static int isl1208_i2c_set_time(struct i2c_client *client,
|
||||
struct rtc_time const *tm)
|
||||
{
|
||||
int sr;
|
||||
u8 regs[ISL1208_RTC_SECTION_LEN] = { 0, };
|
||||
|
||||
regs[ISL1208_REG_SC] = BIN2BCD(tm->tm_sec);
|
||||
regs[ISL1208_REG_MN] = BIN2BCD(tm->tm_min);
|
||||
regs[ISL1208_REG_HR] = BIN2BCD(tm->tm_hour) | ISL1208_REG_HR_MIL;
|
||||
|
||||
regs[ISL1208_REG_DT] = BIN2BCD(tm->tm_mday);
|
||||
regs[ISL1208_REG_MO] = BIN2BCD(tm->tm_mon + 1);
|
||||
regs[ISL1208_REG_YR] = BIN2BCD(tm->tm_year - 100);
|
||||
|
||||
regs[ISL1208_REG_DW] = BIN2BCD(tm->tm_wday & 7);
|
||||
|
||||
sr = isl1208_i2c_get_sr(client);
|
||||
if (sr < 0) {
|
||||
dev_err(&client->dev, "%s: reading SR failed\n", __func__);
|
||||
return sr;
|
||||
}
|
||||
|
||||
/* set WRTC */
|
||||
sr = i2c_smbus_write_byte_data (client, ISL1208_REG_SR,
|
||||
sr | ISL1208_REG_SR_WRTC);
|
||||
if (sr < 0) {
|
||||
dev_err(&client->dev, "%s: writing SR failed\n", __func__);
|
||||
return sr;
|
||||
}
|
||||
|
||||
/* write RTC registers */
|
||||
sr = isl1208_i2c_set_regs(client, 0, regs, ISL1208_RTC_SECTION_LEN);
|
||||
if (sr < 0) {
|
||||
dev_err(&client->dev, "%s: writing RTC section failed\n",
|
||||
__func__);
|
||||
return sr;
|
||||
}
|
||||
|
||||
/* clear WRTC again */
|
||||
sr = i2c_smbus_write_byte_data (client, ISL1208_REG_SR,
|
||||
sr & ~ISL1208_REG_SR_WRTC);
|
||||
if (sr < 0) {
|
||||
dev_err(&client->dev, "%s: writing SR failed\n", __func__);
|
||||
return sr;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int isl1208_rtc_set_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
return isl1208_i2c_set_time(to_i2c_client(dev), tm);
|
||||
}
|
||||
|
||||
static int isl1208_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
|
||||
{
|
||||
return isl1208_i2c_read_alarm(to_i2c_client(dev), alarm);
|
||||
}
|
||||
|
||||
static const struct rtc_class_ops isl1208_rtc_ops = {
|
||||
.proc = isl1208_rtc_proc,
|
||||
.read_time = isl1208_rtc_read_time,
|
||||
.set_time = isl1208_rtc_set_time,
|
||||
.read_alarm = isl1208_rtc_read_alarm,
|
||||
//.set_alarm = isl1208_rtc_set_alarm,
|
||||
};
|
||||
|
||||
/* sysfs interface */
|
||||
|
||||
static ssize_t isl1208_sysfs_show_atrim(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
int atr;
|
||||
|
||||
atr = isl1208_i2c_get_atr(to_i2c_client(dev));
|
||||
if (atr < 0)
|
||||
return atr;
|
||||
|
||||
return sprintf(buf, "%d.%.2d pF\n", atr>>2, (atr&0x3)*25);
|
||||
}
|
||||
static DEVICE_ATTR(atrim, S_IRUGO, isl1208_sysfs_show_atrim, NULL);
|
||||
|
||||
static ssize_t isl1208_sysfs_show_dtrim(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
int dtr;
|
||||
|
||||
dtr = isl1208_i2c_get_dtr(to_i2c_client(dev));
|
||||
if (dtr < 0)
|
||||
return dtr;
|
||||
|
||||
return sprintf(buf, "%d ppm\n", dtr);
|
||||
}
|
||||
static DEVICE_ATTR(dtrim, S_IRUGO, isl1208_sysfs_show_dtrim, NULL);
|
||||
|
||||
static ssize_t isl1208_sysfs_show_usr(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
int usr;
|
||||
|
||||
usr = isl1208_i2c_get_usr(to_i2c_client(dev));
|
||||
if (usr < 0)
|
||||
return usr;
|
||||
|
||||
return sprintf(buf, "0x%.4x\n", usr);
|
||||
}
|
||||
|
||||
static ssize_t isl1208_sysfs_store_usr(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int usr = -1;
|
||||
|
||||
if (buf[0] == '0' && (buf[1] == 'x' || buf[1] == 'X')) {
|
||||
if (sscanf(buf, "%x", &usr) != 1)
|
||||
return -EINVAL;
|
||||
} else {
|
||||
if (sscanf(buf, "%d", &usr) != 1)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (usr < 0 || usr > 0xffff)
|
||||
return -EINVAL;
|
||||
|
||||
return isl1208_i2c_set_usr(to_i2c_client(dev), usr) ? -EIO : count;
|
||||
}
|
||||
static DEVICE_ATTR(usr, S_IRUGO | S_IWUSR, isl1208_sysfs_show_usr,
|
||||
isl1208_sysfs_store_usr);
|
||||
|
||||
static int
|
||||
isl1208_probe(struct i2c_adapter *adapter, int addr, int kind)
|
||||
{
|
||||
int rc = 0;
|
||||
struct i2c_client *new_client = NULL;
|
||||
struct rtc_device *rtc = NULL;
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) {
|
||||
rc = -ENODEV;
|
||||
goto failout;
|
||||
}
|
||||
|
||||
new_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
|
||||
if (new_client == NULL) {
|
||||
rc = -ENOMEM;
|
||||
goto failout;
|
||||
}
|
||||
|
||||
new_client->addr = addr;
|
||||
new_client->adapter = adapter;
|
||||
new_client->driver = &isl1208_driver;
|
||||
new_client->flags = 0;
|
||||
strcpy(new_client->name, DRV_NAME);
|
||||
|
||||
if (kind < 0) {
|
||||
rc = isl1208_i2c_validate_client(new_client);
|
||||
if (rc < 0)
|
||||
goto failout;
|
||||
}
|
||||
|
||||
rc = i2c_attach_client(new_client);
|
||||
if (rc < 0)
|
||||
goto failout;
|
||||
|
||||
dev_info(&new_client->dev,
|
||||
"chip found, driver version " DRV_VERSION "\n");
|
||||
|
||||
rtc = rtc_device_register(isl1208_driver.driver.name,
|
||||
&new_client->dev,
|
||||
&isl1208_rtc_ops, THIS_MODULE);
|
||||
|
||||
if (IS_ERR(rtc)) {
|
||||
rc = PTR_ERR(rtc);
|
||||
goto failout_detach;
|
||||
}
|
||||
|
||||
i2c_set_clientdata(new_client, rtc);
|
||||
|
||||
rc = isl1208_i2c_get_sr(new_client);
|
||||
if (rc < 0) {
|
||||
dev_err(&new_client->dev, "reading status failed\n");
|
||||
goto failout_unregister;
|
||||
}
|
||||
|
||||
if (rc & ISL1208_REG_SR_RTCF)
|
||||
dev_warn(&new_client->dev, "rtc power failure detected, "
|
||||
"please set clock.\n");
|
||||
|
||||
rc = device_create_file(&new_client->dev, &dev_attr_atrim);
|
||||
if (rc < 0)
|
||||
goto failout_unregister;
|
||||
rc = device_create_file(&new_client->dev, &dev_attr_dtrim);
|
||||
if (rc < 0)
|
||||
goto failout_atrim;
|
||||
rc = device_create_file(&new_client->dev, &dev_attr_usr);
|
||||
if (rc < 0)
|
||||
goto failout_dtrim;
|
||||
|
||||
return 0;
|
||||
|
||||
failout_dtrim:
|
||||
device_remove_file(&new_client->dev, &dev_attr_dtrim);
|
||||
failout_atrim:
|
||||
device_remove_file(&new_client->dev, &dev_attr_atrim);
|
||||
failout_unregister:
|
||||
rtc_device_unregister(rtc);
|
||||
failout_detach:
|
||||
i2c_detach_client(new_client);
|
||||
failout:
|
||||
kfree(new_client);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int
|
||||
isl1208_attach_adapter (struct i2c_adapter *adapter)
|
||||
{
|
||||
return i2c_probe(adapter, &addr_data, isl1208_probe);
|
||||
}
|
||||
|
||||
static int
|
||||
isl1208_detach_client(struct i2c_client *client)
|
||||
{
|
||||
int rc;
|
||||
struct rtc_device *const rtc = i2c_get_clientdata(client);
|
||||
|
||||
if (rtc)
|
||||
rtc_device_unregister(rtc); /* do we need to kfree? */
|
||||
|
||||
rc = i2c_detach_client(client);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
kfree(client);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* module management */
|
||||
|
||||
static int __init isl1208_init(void)
|
||||
{
|
||||
return i2c_add_driver(&isl1208_driver);
|
||||
}
|
||||
|
||||
static void __exit isl1208_exit(void)
|
||||
{
|
||||
i2c_del_driver(&isl1208_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Herbert Valerio Riedel <hvr@gnu.org>");
|
||||
MODULE_DESCRIPTION("Intersil ISL1208 RTC driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_VERSION(DRV_VERSION);
|
||||
|
||||
module_init(isl1208_init);
|
||||
module_exit(isl1208_exit);
|
||||
201
drivers/rtc/rtc-lib.c
Normal file
201
drivers/rtc/rtc-lib.c
Normal file
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* rtc and date/time utility functions
|
||||
*
|
||||
* Copyright (C) 2005-06 Tower Technologies
|
||||
* Author: Alessandro Zummo <a.zummo@towertech.it>
|
||||
*
|
||||
* based on arch/arm/common/rtctime.c and other bits
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/rtc.h>
|
||||
|
||||
static const unsigned char rtc_days_in_month[] = {
|
||||
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
|
||||
};
|
||||
|
||||
static const unsigned short rtc_ydays[2][13] = {
|
||||
/* Normal years */
|
||||
{ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
|
||||
/* Leap years */
|
||||
{ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
|
||||
};
|
||||
|
||||
#define LEAPS_THRU_END_OF(y) ((y)/4 - (y)/100 + (y)/400)
|
||||
#define LEAP_YEAR(year) ((!(year % 4) && (year % 100)) || !(year % 400))
|
||||
|
||||
/*
|
||||
* The number of days in the month.
|
||||
*/
|
||||
int rtc_month_days(unsigned int month, unsigned int year)
|
||||
{
|
||||
return rtc_days_in_month[month] + (LEAP_YEAR(year) && month == 1);
|
||||
}
|
||||
EXPORT_SYMBOL(rtc_month_days);
|
||||
|
||||
/*
|
||||
* The number of days since January 1. (0 to 365)
|
||||
*/
|
||||
int rtc_year_days(unsigned int day, unsigned int month, unsigned int year)
|
||||
{
|
||||
return rtc_ydays[LEAP_YEAR(year)][month] + day-1;
|
||||
}
|
||||
EXPORT_SYMBOL(rtc_year_days);
|
||||
|
||||
/*
|
||||
* Convert seconds since 01-01-1970 00:00:00 to Gregorian date.
|
||||
*/
|
||||
void rtc_time_to_tm(unsigned long time, struct rtc_time *tm)
|
||||
{
|
||||
register int days, month, year;
|
||||
|
||||
days = time / 86400;
|
||||
time -= days * 86400;
|
||||
|
||||
/* day of the week, 1970-01-01 was a Thursday */
|
||||
tm->tm_wday = (days + 4) % 7;
|
||||
|
||||
year = 1970 + days / 365;
|
||||
days -= (year - 1970) * 365
|
||||
+ LEAPS_THRU_END_OF(year - 1)
|
||||
- LEAPS_THRU_END_OF(1970 - 1);
|
||||
if (days < 0) {
|
||||
year -= 1;
|
||||
days += 365 + LEAP_YEAR(year);
|
||||
}
|
||||
tm->tm_year = year - 1900;
|
||||
tm->tm_yday = days + 1;
|
||||
|
||||
for (month = 0; month < 11; month++) {
|
||||
int newdays;
|
||||
|
||||
newdays = days - rtc_month_days(month, year);
|
||||
if (newdays < 0)
|
||||
break;
|
||||
days = newdays;
|
||||
}
|
||||
tm->tm_mon = month;
|
||||
tm->tm_mday = days + 1;
|
||||
|
||||
tm->tm_hour = time / 3600;
|
||||
time -= tm->tm_hour * 3600;
|
||||
tm->tm_min = time / 60;
|
||||
tm->tm_sec = time - tm->tm_min * 60;
|
||||
}
|
||||
EXPORT_SYMBOL(rtc_time_to_tm);
|
||||
|
||||
/*
|
||||
* Does the rtc_time represent a valid date/time?
|
||||
*/
|
||||
int rtc_valid_tm(struct rtc_time *tm)
|
||||
{
|
||||
if (tm->tm_year < 70
|
||||
|| ((unsigned)tm->tm_mon) >= 12
|
||||
|| tm->tm_mday < 1
|
||||
|| tm->tm_mday > rtc_month_days(tm->tm_mon, tm->tm_year + 1900)
|
||||
|| ((unsigned)tm->tm_hour) >= 24
|
||||
|| ((unsigned)tm->tm_min) >= 60
|
||||
|| ((unsigned)tm->tm_sec) >= 60)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(rtc_valid_tm);
|
||||
|
||||
/*
|
||||
* Convert Gregorian date to seconds since 01-01-1970 00:00:00.
|
||||
*/
|
||||
int rtc_tm_to_time(struct rtc_time *tm, unsigned long *time)
|
||||
{
|
||||
*time = mktime(tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
|
||||
tm->tm_hour, tm->tm_min, tm->tm_sec);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(rtc_tm_to_time);
|
||||
|
||||
|
||||
/* Merge the valid (i.e. non-negative) fields of alarm into the current
|
||||
* time. If the valid alarm fields are earlier than the equivalent
|
||||
* fields in the time, carry one into the least significant invalid
|
||||
* field, so that the alarm expiry is in the future. It assumes that the
|
||||
* least significant invalid field is more significant than the most
|
||||
* significant valid field, and that the seconds field is valid.
|
||||
*
|
||||
* This is used by alarms that take relative (rather than absolute)
|
||||
* times, and/or have a simple binary second counter instead of
|
||||
* day/hour/minute/sec registers.
|
||||
*/
|
||||
void rtc_merge_alarm(struct rtc_time *now, struct rtc_time *alarm)
|
||||
{
|
||||
int *alarmp = &alarm->tm_sec;
|
||||
int *timep = &now->tm_sec;
|
||||
int carry_into, i;
|
||||
|
||||
/* Ignore everything past the 6th element (tm_year). */
|
||||
for (i = 5; i > 0; i--) {
|
||||
if (alarmp[i] < 0)
|
||||
alarmp[i] = timep[i];
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
/* No carry needed if all fields are valid. */
|
||||
if (i == 5)
|
||||
return;
|
||||
|
||||
for (carry_into = i + 1; i >= 0; i--) {
|
||||
if (alarmp[i] < timep[i])
|
||||
break;
|
||||
|
||||
if (alarmp[i] > timep[i])
|
||||
return;
|
||||
}
|
||||
|
||||
switch (carry_into) {
|
||||
case 1:
|
||||
alarm->tm_min++;
|
||||
|
||||
if (alarm->tm_min < 60)
|
||||
return;
|
||||
|
||||
alarm->tm_min = 0;
|
||||
/* fall-through */
|
||||
|
||||
case 2:
|
||||
alarm->tm_hour++;
|
||||
|
||||
if (alarm->tm_hour < 60)
|
||||
return;
|
||||
|
||||
alarm->tm_hour = 0;
|
||||
/* fall-through */
|
||||
|
||||
case 3:
|
||||
alarm->tm_mday++;
|
||||
|
||||
if (alarm->tm_mday <= rtc_days_in_month[alarm->tm_mon])
|
||||
return;
|
||||
|
||||
alarm->tm_mday = 1;
|
||||
/* fall-through */
|
||||
|
||||
case 4:
|
||||
alarm->tm_mon++;
|
||||
|
||||
if (alarm->tm_mon <= 12)
|
||||
return;
|
||||
|
||||
alarm->tm_mon = 1;
|
||||
/* fall-through */
|
||||
|
||||
case 5:
|
||||
alarm->tm_year++;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(rtc_merge_alarm);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
204
drivers/rtc/rtc-m48t86.c
Normal file
204
drivers/rtc/rtc-m48t86.c
Normal file
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* ST M48T86 / Dallas DS12887 RTC driver
|
||||
* Copyright (c) 2006 Tower Technologies
|
||||
*
|
||||
* Author: Alessandro Zummo <a.zummo@towertech.it>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This drivers only supports the clock running in BCD and 24H mode.
|
||||
* If it will be ever adapted to binary and 12H mode, care must be taken
|
||||
* to not introduce bugs.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/rtc.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/m48t86.h>
|
||||
#include <linux/bcd.h>
|
||||
|
||||
#define M48T86_REG_SEC 0x00
|
||||
#define M48T86_REG_SECALRM 0x01
|
||||
#define M48T86_REG_MIN 0x02
|
||||
#define M48T86_REG_MINALRM 0x03
|
||||
#define M48T86_REG_HOUR 0x04
|
||||
#define M48T86_REG_HOURALRM 0x05
|
||||
#define M48T86_REG_DOW 0x06 /* 1 = sunday */
|
||||
#define M48T86_REG_DOM 0x07
|
||||
#define M48T86_REG_MONTH 0x08 /* 1 - 12 */
|
||||
#define M48T86_REG_YEAR 0x09 /* 0 - 99 */
|
||||
#define M48T86_REG_A 0x0A
|
||||
#define M48T86_REG_B 0x0B
|
||||
#define M48T86_REG_C 0x0C
|
||||
#define M48T86_REG_D 0x0D
|
||||
|
||||
#define M48T86_REG_B_H24 (1 << 1)
|
||||
#define M48T86_REG_B_DM (1 << 2)
|
||||
#define M48T86_REG_B_SET (1 << 7)
|
||||
#define M48T86_REG_D_VRT (1 << 7)
|
||||
|
||||
#define DRV_VERSION "0.1"
|
||||
|
||||
|
||||
static int m48t86_rtc_read_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
unsigned char reg;
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct m48t86_ops *ops = pdev->dev.platform_data;
|
||||
|
||||
reg = ops->readbyte(M48T86_REG_B);
|
||||
|
||||
if (reg & M48T86_REG_B_DM) {
|
||||
/* data (binary) mode */
|
||||
tm->tm_sec = ops->readbyte(M48T86_REG_SEC);
|
||||
tm->tm_min = ops->readbyte(M48T86_REG_MIN);
|
||||
tm->tm_hour = ops->readbyte(M48T86_REG_HOUR) & 0x3F;
|
||||
tm->tm_mday = ops->readbyte(M48T86_REG_DOM);
|
||||
/* tm_mon is 0-11 */
|
||||
tm->tm_mon = ops->readbyte(M48T86_REG_MONTH) - 1;
|
||||
tm->tm_year = ops->readbyte(M48T86_REG_YEAR) + 100;
|
||||
tm->tm_wday = ops->readbyte(M48T86_REG_DOW);
|
||||
} else {
|
||||
/* bcd mode */
|
||||
tm->tm_sec = BCD2BIN(ops->readbyte(M48T86_REG_SEC));
|
||||
tm->tm_min = BCD2BIN(ops->readbyte(M48T86_REG_MIN));
|
||||
tm->tm_hour = BCD2BIN(ops->readbyte(M48T86_REG_HOUR) & 0x3F);
|
||||
tm->tm_mday = BCD2BIN(ops->readbyte(M48T86_REG_DOM));
|
||||
/* tm_mon is 0-11 */
|
||||
tm->tm_mon = BCD2BIN(ops->readbyte(M48T86_REG_MONTH)) - 1;
|
||||
tm->tm_year = BCD2BIN(ops->readbyte(M48T86_REG_YEAR)) + 100;
|
||||
tm->tm_wday = BCD2BIN(ops->readbyte(M48T86_REG_DOW));
|
||||
}
|
||||
|
||||
/* correct the hour if the clock is in 12h mode */
|
||||
if (!(reg & M48T86_REG_B_H24))
|
||||
if (ops->readbyte(M48T86_REG_HOUR) & 0x80)
|
||||
tm->tm_hour += 12;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int m48t86_rtc_set_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
unsigned char reg;
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct m48t86_ops *ops = pdev->dev.platform_data;
|
||||
|
||||
reg = ops->readbyte(M48T86_REG_B);
|
||||
|
||||
/* update flag and 24h mode */
|
||||
reg |= M48T86_REG_B_SET | M48T86_REG_B_H24;
|
||||
ops->writebyte(reg, M48T86_REG_B);
|
||||
|
||||
if (reg & M48T86_REG_B_DM) {
|
||||
/* data (binary) mode */
|
||||
ops->writebyte(tm->tm_sec, M48T86_REG_SEC);
|
||||
ops->writebyte(tm->tm_min, M48T86_REG_MIN);
|
||||
ops->writebyte(tm->tm_hour, M48T86_REG_HOUR);
|
||||
ops->writebyte(tm->tm_mday, M48T86_REG_DOM);
|
||||
ops->writebyte(tm->tm_mon + 1, M48T86_REG_MONTH);
|
||||
ops->writebyte(tm->tm_year % 100, M48T86_REG_YEAR);
|
||||
ops->writebyte(tm->tm_wday, M48T86_REG_DOW);
|
||||
} else {
|
||||
/* bcd mode */
|
||||
ops->writebyte(BIN2BCD(tm->tm_sec), M48T86_REG_SEC);
|
||||
ops->writebyte(BIN2BCD(tm->tm_min), M48T86_REG_MIN);
|
||||
ops->writebyte(BIN2BCD(tm->tm_hour), M48T86_REG_HOUR);
|
||||
ops->writebyte(BIN2BCD(tm->tm_mday), M48T86_REG_DOM);
|
||||
ops->writebyte(BIN2BCD(tm->tm_mon + 1), M48T86_REG_MONTH);
|
||||
ops->writebyte(BIN2BCD(tm->tm_year % 100), M48T86_REG_YEAR);
|
||||
ops->writebyte(BIN2BCD(tm->tm_wday), M48T86_REG_DOW);
|
||||
}
|
||||
|
||||
/* update ended */
|
||||
reg &= ~M48T86_REG_B_SET;
|
||||
ops->writebyte(reg, M48T86_REG_B);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int m48t86_rtc_proc(struct device *dev, struct seq_file *seq)
|
||||
{
|
||||
unsigned char reg;
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct m48t86_ops *ops = pdev->dev.platform_data;
|
||||
|
||||
reg = ops->readbyte(M48T86_REG_B);
|
||||
|
||||
seq_printf(seq, "mode\t\t: %s\n",
|
||||
(reg & M48T86_REG_B_DM) ? "binary" : "bcd");
|
||||
|
||||
reg = ops->readbyte(M48T86_REG_D);
|
||||
|
||||
seq_printf(seq, "battery\t\t: %s\n",
|
||||
(reg & M48T86_REG_D_VRT) ? "ok" : "exhausted");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct rtc_class_ops m48t86_rtc_ops = {
|
||||
.read_time = m48t86_rtc_read_time,
|
||||
.set_time = m48t86_rtc_set_time,
|
||||
.proc = m48t86_rtc_proc,
|
||||
};
|
||||
|
||||
static int __devinit m48t86_rtc_probe(struct platform_device *dev)
|
||||
{
|
||||
unsigned char reg;
|
||||
struct m48t86_ops *ops = dev->dev.platform_data;
|
||||
struct rtc_device *rtc = rtc_device_register("m48t86",
|
||||
&dev->dev, &m48t86_rtc_ops, THIS_MODULE);
|
||||
|
||||
if (IS_ERR(rtc))
|
||||
return PTR_ERR(rtc);
|
||||
|
||||
platform_set_drvdata(dev, rtc);
|
||||
|
||||
/* read battery status */
|
||||
reg = ops->readbyte(M48T86_REG_D);
|
||||
dev_info(&dev->dev, "battery %s\n",
|
||||
(reg & M48T86_REG_D_VRT) ? "ok" : "exhausted");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devexit m48t86_rtc_remove(struct platform_device *dev)
|
||||
{
|
||||
struct rtc_device *rtc = platform_get_drvdata(dev);
|
||||
|
||||
if (rtc)
|
||||
rtc_device_unregister(rtc);
|
||||
|
||||
platform_set_drvdata(dev, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver m48t86_rtc_platform_driver = {
|
||||
.driver = {
|
||||
.name = "rtc-m48t86",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = m48t86_rtc_probe,
|
||||
.remove = __devexit_p(m48t86_rtc_remove),
|
||||
};
|
||||
|
||||
static int __init m48t86_rtc_init(void)
|
||||
{
|
||||
return platform_driver_register(&m48t86_rtc_platform_driver);
|
||||
}
|
||||
|
||||
static void __exit m48t86_rtc_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&m48t86_rtc_platform_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Alessandro Zummo <a.zummo@towertech.it>");
|
||||
MODULE_DESCRIPTION("M48T86 RTC driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_VERSION(DRV_VERSION);
|
||||
|
||||
module_init(m48t86_rtc_init);
|
||||
module_exit(m48t86_rtc_exit);
|
||||
285
drivers/rtc/rtc-max6902.c
Normal file
285
drivers/rtc/rtc-max6902.c
Normal file
@@ -0,0 +1,285 @@
|
||||
/* drivers/rtc/rtc-max6902.c
|
||||
*
|
||||
* Copyright (C) 2006 8D Technologies inc.
|
||||
* Copyright (C) 2004 Compulab Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* Driver for MAX6902 spi RTC
|
||||
*
|
||||
* Changelog:
|
||||
*
|
||||
* 24-May-2006: Raphael Assenat <raph@8d.com>
|
||||
* - Major rework
|
||||
* Converted to rtc_device and uses the SPI layer.
|
||||
*
|
||||
* ??-???-2005: Someone at Compulab
|
||||
* - Initial driver creation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/version.h>
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/rtc.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/bcd.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#define MAX6902_REG_SECONDS 0x01
|
||||
#define MAX6902_REG_MINUTES 0x03
|
||||
#define MAX6902_REG_HOURS 0x05
|
||||
#define MAX6902_REG_DATE 0x07
|
||||
#define MAX6902_REG_MONTH 0x09
|
||||
#define MAX6902_REG_DAY 0x0B
|
||||
#define MAX6902_REG_YEAR 0x0D
|
||||
#define MAX6902_REG_CONTROL 0x0F
|
||||
#define MAX6902_REG_CENTURY 0x13
|
||||
|
||||
#undef MAX6902_DEBUG
|
||||
|
||||
struct max6902 {
|
||||
struct rtc_device *rtc;
|
||||
u8 buf[9]; /* Burst read cmd + 8 registers */
|
||||
u8 tx_buf[2];
|
||||
u8 rx_buf[2];
|
||||
};
|
||||
|
||||
static void max6902_set_reg(struct device *dev, unsigned char address,
|
||||
unsigned char data)
|
||||
{
|
||||
struct spi_device *spi = to_spi_device(dev);
|
||||
unsigned char buf[2];
|
||||
|
||||
/* MSB must be '0' to write */
|
||||
buf[0] = address & 0x7f;
|
||||
buf[1] = data;
|
||||
|
||||
spi_write(spi, buf, 2);
|
||||
}
|
||||
|
||||
static int max6902_get_reg(struct device *dev, unsigned char address,
|
||||
unsigned char *data)
|
||||
{
|
||||
struct spi_device *spi = to_spi_device(dev);
|
||||
struct max6902 *chip = dev_get_drvdata(dev);
|
||||
struct spi_message message;
|
||||
struct spi_transfer xfer;
|
||||
int status;
|
||||
|
||||
if (!data)
|
||||
return -EINVAL;
|
||||
|
||||
/* Build our spi message */
|
||||
spi_message_init(&message);
|
||||
memset(&xfer, 0, sizeof(xfer));
|
||||
xfer.len = 2;
|
||||
/* Can tx_buf and rx_buf be equal? The doc in spi.h is not sure... */
|
||||
xfer.tx_buf = chip->tx_buf;
|
||||
xfer.rx_buf = chip->rx_buf;
|
||||
|
||||
/* Set MSB to indicate read */
|
||||
chip->tx_buf[0] = address | 0x80;
|
||||
|
||||
spi_message_add_tail(&xfer, &message);
|
||||
|
||||
/* do the i/o */
|
||||
status = spi_sync(spi, &message);
|
||||
if (status == 0)
|
||||
status = message.status;
|
||||
else
|
||||
return status;
|
||||
|
||||
*data = chip->rx_buf[1];
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static int max6902_get_datetime(struct device *dev, struct rtc_time *dt)
|
||||
{
|
||||
unsigned char tmp;
|
||||
int century;
|
||||
int err;
|
||||
struct spi_device *spi = to_spi_device(dev);
|
||||
struct max6902 *chip = dev_get_drvdata(dev);
|
||||
struct spi_message message;
|
||||
struct spi_transfer xfer;
|
||||
int status;
|
||||
|
||||
err = max6902_get_reg(dev, MAX6902_REG_CENTURY, &tmp);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* build the message */
|
||||
spi_message_init(&message);
|
||||
memset(&xfer, 0, sizeof(xfer));
|
||||
xfer.len = 1 + 7; /* Burst read command + 7 registers */
|
||||
xfer.tx_buf = chip->buf;
|
||||
xfer.rx_buf = chip->buf;
|
||||
chip->buf[0] = 0xbf; /* Burst read */
|
||||
spi_message_add_tail(&xfer, &message);
|
||||
|
||||
/* do the i/o */
|
||||
status = spi_sync(spi, &message);
|
||||
if (status == 0)
|
||||
status = message.status;
|
||||
else
|
||||
return status;
|
||||
|
||||
/* The chip sends data in this order:
|
||||
* Seconds, Minutes, Hours, Date, Month, Day, Year */
|
||||
dt->tm_sec = BCD2BIN(chip->buf[1]);
|
||||
dt->tm_min = BCD2BIN(chip->buf[2]);
|
||||
dt->tm_hour = BCD2BIN(chip->buf[3]);
|
||||
dt->tm_mday = BCD2BIN(chip->buf[4]);
|
||||
dt->tm_mon = BCD2BIN(chip->buf[5]) - 1;
|
||||
dt->tm_wday = BCD2BIN(chip->buf[6]);
|
||||
dt->tm_year = BCD2BIN(chip->buf[7]);
|
||||
|
||||
century = BCD2BIN(tmp) * 100;
|
||||
|
||||
dt->tm_year += century;
|
||||
dt->tm_year -= 1900;
|
||||
|
||||
#ifdef MAX6902_DEBUG
|
||||
printk("\n%s : Read RTC values\n",__FUNCTION__);
|
||||
printk("tm_hour: %i\n",dt->tm_hour);
|
||||
printk("tm_min : %i\n",dt->tm_min);
|
||||
printk("tm_sec : %i\n",dt->tm_sec);
|
||||
printk("tm_year: %i\n",dt->tm_year);
|
||||
printk("tm_mon : %i\n",dt->tm_mon);
|
||||
printk("tm_mday: %i\n",dt->tm_mday);
|
||||
printk("tm_wday: %i\n",dt->tm_wday);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max6902_set_datetime(struct device *dev, struct rtc_time *dt)
|
||||
{
|
||||
dt->tm_year = dt->tm_year+1900;
|
||||
|
||||
#ifdef MAX6902_DEBUG
|
||||
printk("\n%s : Setting RTC values\n",__FUNCTION__);
|
||||
printk("tm_sec : %i\n",dt->tm_sec);
|
||||
printk("tm_min : %i\n",dt->tm_min);
|
||||
printk("tm_hour: %i\n",dt->tm_hour);
|
||||
printk("tm_mday: %i\n",dt->tm_mday);
|
||||
printk("tm_wday: %i\n",dt->tm_wday);
|
||||
printk("tm_year: %i\n",dt->tm_year);
|
||||
#endif
|
||||
|
||||
/* Remove write protection */
|
||||
max6902_set_reg(dev, 0xF, 0);
|
||||
|
||||
max6902_set_reg(dev, 0x01, BIN2BCD(dt->tm_sec));
|
||||
max6902_set_reg(dev, 0x03, BIN2BCD(dt->tm_min));
|
||||
max6902_set_reg(dev, 0x05, BIN2BCD(dt->tm_hour));
|
||||
|
||||
max6902_set_reg(dev, 0x07, BIN2BCD(dt->tm_mday));
|
||||
max6902_set_reg(dev, 0x09, BIN2BCD(dt->tm_mon+1));
|
||||
max6902_set_reg(dev, 0x0B, BIN2BCD(dt->tm_wday));
|
||||
max6902_set_reg(dev, 0x0D, BIN2BCD(dt->tm_year%100));
|
||||
max6902_set_reg(dev, 0x13, BIN2BCD(dt->tm_year/100));
|
||||
|
||||
/* Compulab used a delay here. However, the datasheet
|
||||
* does not mention a delay being required anywhere... */
|
||||
/* delay(2000); */
|
||||
|
||||
/* Write protect */
|
||||
max6902_set_reg(dev, 0xF, 0x80);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max6902_read_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
return max6902_get_datetime(dev, tm);
|
||||
}
|
||||
|
||||
static int max6902_set_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
return max6902_set_datetime(dev, tm);
|
||||
}
|
||||
|
||||
static const struct rtc_class_ops max6902_rtc_ops = {
|
||||
.read_time = max6902_read_time,
|
||||
.set_time = max6902_set_time,
|
||||
};
|
||||
|
||||
static int __devinit max6902_probe(struct spi_device *spi)
|
||||
{
|
||||
struct rtc_device *rtc;
|
||||
unsigned char tmp;
|
||||
struct max6902 *chip;
|
||||
int res;
|
||||
|
||||
rtc = rtc_device_register("max6902",
|
||||
&spi->dev, &max6902_rtc_ops, THIS_MODULE);
|
||||
if (IS_ERR(rtc))
|
||||
return PTR_ERR(rtc);
|
||||
|
||||
spi->mode = SPI_MODE_3;
|
||||
spi->bits_per_word = 8;
|
||||
spi_setup(spi);
|
||||
|
||||
chip = kzalloc(sizeof *chip, GFP_KERNEL);
|
||||
if (!chip) {
|
||||
rtc_device_unregister(rtc);
|
||||
return -ENOMEM;
|
||||
}
|
||||
chip->rtc = rtc;
|
||||
dev_set_drvdata(&spi->dev, chip);
|
||||
|
||||
res = max6902_get_reg(&spi->dev, MAX6902_REG_SECONDS, &tmp);
|
||||
if (res) {
|
||||
rtc_device_unregister(rtc);
|
||||
return res;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devexit max6902_remove(struct spi_device *spi)
|
||||
{
|
||||
struct max6902 *chip = platform_get_drvdata(spi);
|
||||
struct rtc_device *rtc = chip->rtc;
|
||||
|
||||
if (rtc)
|
||||
rtc_device_unregister(rtc);
|
||||
|
||||
kfree(chip);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct spi_driver max6902_driver = {
|
||||
.driver = {
|
||||
.name = "max6902",
|
||||
.bus = &spi_bus_type,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = max6902_probe,
|
||||
.remove = __devexit_p(max6902_remove),
|
||||
};
|
||||
|
||||
static __init int max6902_init(void)
|
||||
{
|
||||
printk("max6902 spi driver\n");
|
||||
return spi_register_driver(&max6902_driver);
|
||||
}
|
||||
module_init(max6902_init);
|
||||
|
||||
static __exit void max6902_exit(void)
|
||||
{
|
||||
spi_unregister_driver(&max6902_driver);
|
||||
}
|
||||
module_exit(max6902_exit);
|
||||
|
||||
MODULE_DESCRIPTION ("max6902 spi RTC driver");
|
||||
MODULE_AUTHOR ("Raphael Assenat");
|
||||
MODULE_LICENSE ("GPL");
|
||||
571
drivers/rtc/rtc-omap.c
Normal file
571
drivers/rtc/rtc-omap.c
Normal file
@@ -0,0 +1,571 @@
|
||||
/*
|
||||
* TI OMAP1 Real Time Clock interface for Linux
|
||||
*
|
||||
* Copyright (C) 2003 MontaVista Software, Inc.
|
||||
* Author: George G. Davis <gdavis@mvista.com> or <source@mvista.com>
|
||||
*
|
||||
* Copyright (C) 2006 David Brownell (new RTC 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.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/rtc.h>
|
||||
#include <linux/bcd.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <asm/mach/time.h>
|
||||
|
||||
|
||||
/* The OMAP1 RTC is a year/month/day/hours/minutes/seconds BCD clock
|
||||
* with century-range alarm matching, driven by the 32kHz clock.
|
||||
*
|
||||
* The main user-visible ways it differs from PC RTCs are by omitting
|
||||
* "don't care" alarm fields and sub-second periodic IRQs, and having
|
||||
* an autoadjust mechanism to calibrate to the true oscillator rate.
|
||||
*
|
||||
* Board-specific wiring options include using split power mode with
|
||||
* RTC_OFF_NOFF used as the reset signal (so the RTC won't be reset),
|
||||
* and wiring RTC_WAKE_INT (so the RTC alarm can wake the system from
|
||||
* low power modes). See the BOARD-SPECIFIC CUSTOMIZATION comment.
|
||||
*/
|
||||
|
||||
#define OMAP_RTC_BASE 0xfffb4800
|
||||
|
||||
/* RTC registers */
|
||||
#define OMAP_RTC_SECONDS_REG 0x00
|
||||
#define OMAP_RTC_MINUTES_REG 0x04
|
||||
#define OMAP_RTC_HOURS_REG 0x08
|
||||
#define OMAP_RTC_DAYS_REG 0x0C
|
||||
#define OMAP_RTC_MONTHS_REG 0x10
|
||||
#define OMAP_RTC_YEARS_REG 0x14
|
||||
#define OMAP_RTC_WEEKS_REG 0x18
|
||||
|
||||
#define OMAP_RTC_ALARM_SECONDS_REG 0x20
|
||||
#define OMAP_RTC_ALARM_MINUTES_REG 0x24
|
||||
#define OMAP_RTC_ALARM_HOURS_REG 0x28
|
||||
#define OMAP_RTC_ALARM_DAYS_REG 0x2c
|
||||
#define OMAP_RTC_ALARM_MONTHS_REG 0x30
|
||||
#define OMAP_RTC_ALARM_YEARS_REG 0x34
|
||||
|
||||
#define OMAP_RTC_CTRL_REG 0x40
|
||||
#define OMAP_RTC_STATUS_REG 0x44
|
||||
#define OMAP_RTC_INTERRUPTS_REG 0x48
|
||||
|
||||
#define OMAP_RTC_COMP_LSB_REG 0x4c
|
||||
#define OMAP_RTC_COMP_MSB_REG 0x50
|
||||
#define OMAP_RTC_OSC_REG 0x54
|
||||
|
||||
/* OMAP_RTC_CTRL_REG bit fields: */
|
||||
#define OMAP_RTC_CTRL_SPLIT (1<<7)
|
||||
#define OMAP_RTC_CTRL_DISABLE (1<<6)
|
||||
#define OMAP_RTC_CTRL_SET_32_COUNTER (1<<5)
|
||||
#define OMAP_RTC_CTRL_TEST (1<<4)
|
||||
#define OMAP_RTC_CTRL_MODE_12_24 (1<<3)
|
||||
#define OMAP_RTC_CTRL_AUTO_COMP (1<<2)
|
||||
#define OMAP_RTC_CTRL_ROUND_30S (1<<1)
|
||||
#define OMAP_RTC_CTRL_STOP (1<<0)
|
||||
|
||||
/* OMAP_RTC_STATUS_REG bit fields: */
|
||||
#define OMAP_RTC_STATUS_POWER_UP (1<<7)
|
||||
#define OMAP_RTC_STATUS_ALARM (1<<6)
|
||||
#define OMAP_RTC_STATUS_1D_EVENT (1<<5)
|
||||
#define OMAP_RTC_STATUS_1H_EVENT (1<<4)
|
||||
#define OMAP_RTC_STATUS_1M_EVENT (1<<3)
|
||||
#define OMAP_RTC_STATUS_1S_EVENT (1<<2)
|
||||
#define OMAP_RTC_STATUS_RUN (1<<1)
|
||||
#define OMAP_RTC_STATUS_BUSY (1<<0)
|
||||
|
||||
/* OMAP_RTC_INTERRUPTS_REG bit fields: */
|
||||
#define OMAP_RTC_INTERRUPTS_IT_ALARM (1<<3)
|
||||
#define OMAP_RTC_INTERRUPTS_IT_TIMER (1<<2)
|
||||
|
||||
|
||||
#define rtc_read(addr) omap_readb(OMAP_RTC_BASE + (addr))
|
||||
#define rtc_write(val, addr) omap_writeb(val, OMAP_RTC_BASE + (addr))
|
||||
|
||||
|
||||
/* platform_bus isn't hotpluggable, so for static linkage it'd be safe
|
||||
* to get rid of probe() and remove() code ... too bad the driver struct
|
||||
* remembers probe(), that's about 25% of the runtime footprint!!
|
||||
*/
|
||||
#ifndef MODULE
|
||||
#undef __devexit
|
||||
#undef __devexit_p
|
||||
#define __devexit __exit
|
||||
#define __devexit_p __exit_p
|
||||
#endif
|
||||
|
||||
|
||||
/* we rely on the rtc framework to handle locking (rtc->ops_lock),
|
||||
* so the only other requirement is that register accesses which
|
||||
* require BUSY to be clear are made with IRQs locally disabled
|
||||
*/
|
||||
static void rtc_wait_not_busy(void)
|
||||
{
|
||||
int count = 0;
|
||||
u8 status;
|
||||
|
||||
/* BUSY may stay active for 1/32768 second (~30 usec) */
|
||||
for (count = 0; count < 50; count++) {
|
||||
status = rtc_read(OMAP_RTC_STATUS_REG);
|
||||
if ((status & (u8)OMAP_RTC_STATUS_BUSY) == 0)
|
||||
break;
|
||||
udelay(1);
|
||||
}
|
||||
/* now we have ~15 usec to read/write various registers */
|
||||
}
|
||||
|
||||
static irqreturn_t rtc_irq(int irq, void *class_dev)
|
||||
{
|
||||
unsigned long events = 0;
|
||||
u8 irq_data;
|
||||
|
||||
irq_data = rtc_read(OMAP_RTC_STATUS_REG);
|
||||
|
||||
/* alarm irq? */
|
||||
if (irq_data & OMAP_RTC_STATUS_ALARM) {
|
||||
rtc_write(OMAP_RTC_STATUS_ALARM, OMAP_RTC_STATUS_REG);
|
||||
events |= RTC_IRQF | RTC_AF;
|
||||
}
|
||||
|
||||
/* 1/sec periodic/update irq? */
|
||||
if (irq_data & OMAP_RTC_STATUS_1S_EVENT)
|
||||
events |= RTC_IRQF | RTC_UF;
|
||||
|
||||
rtc_update_irq(class_dev, 1, events);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_RTC_INTF_DEV
|
||||
|
||||
static int
|
||||
omap_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
u8 reg;
|
||||
|
||||
switch (cmd) {
|
||||
case RTC_AIE_OFF:
|
||||
case RTC_AIE_ON:
|
||||
case RTC_UIE_OFF:
|
||||
case RTC_UIE_ON:
|
||||
break;
|
||||
default:
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
|
||||
local_irq_disable();
|
||||
rtc_wait_not_busy();
|
||||
reg = rtc_read(OMAP_RTC_INTERRUPTS_REG);
|
||||
switch (cmd) {
|
||||
/* AIE = Alarm Interrupt Enable */
|
||||
case RTC_AIE_OFF:
|
||||
reg &= ~OMAP_RTC_INTERRUPTS_IT_ALARM;
|
||||
break;
|
||||
case RTC_AIE_ON:
|
||||
reg |= OMAP_RTC_INTERRUPTS_IT_ALARM;
|
||||
break;
|
||||
/* UIE = Update Interrupt Enable (1/second) */
|
||||
case RTC_UIE_OFF:
|
||||
reg &= ~OMAP_RTC_INTERRUPTS_IT_TIMER;
|
||||
break;
|
||||
case RTC_UIE_ON:
|
||||
reg |= OMAP_RTC_INTERRUPTS_IT_TIMER;
|
||||
break;
|
||||
}
|
||||
rtc_wait_not_busy();
|
||||
rtc_write(reg, OMAP_RTC_INTERRUPTS_REG);
|
||||
local_irq_enable();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
#define omap_rtc_ioctl NULL
|
||||
#endif
|
||||
|
||||
/* this hardware doesn't support "don't care" alarm fields */
|
||||
static int tm2bcd(struct rtc_time *tm)
|
||||
{
|
||||
if (rtc_valid_tm(tm) != 0)
|
||||
return -EINVAL;
|
||||
|
||||
tm->tm_sec = BIN2BCD(tm->tm_sec);
|
||||
tm->tm_min = BIN2BCD(tm->tm_min);
|
||||
tm->tm_hour = BIN2BCD(tm->tm_hour);
|
||||
tm->tm_mday = BIN2BCD(tm->tm_mday);
|
||||
|
||||
tm->tm_mon = BIN2BCD(tm->tm_mon + 1);
|
||||
|
||||
/* epoch == 1900 */
|
||||
if (tm->tm_year < 100 || tm->tm_year > 199)
|
||||
return -EINVAL;
|
||||
tm->tm_year = BIN2BCD(tm->tm_year - 100);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bcd2tm(struct rtc_time *tm)
|
||||
{
|
||||
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) - 1;
|
||||
/* epoch == 1900 */
|
||||
tm->tm_year = BCD2BIN(tm->tm_year) + 100;
|
||||
}
|
||||
|
||||
|
||||
static int omap_rtc_read_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
/* we don't report wday/yday/isdst ... */
|
||||
local_irq_disable();
|
||||
rtc_wait_not_busy();
|
||||
|
||||
tm->tm_sec = rtc_read(OMAP_RTC_SECONDS_REG);
|
||||
tm->tm_min = rtc_read(OMAP_RTC_MINUTES_REG);
|
||||
tm->tm_hour = rtc_read(OMAP_RTC_HOURS_REG);
|
||||
tm->tm_mday = rtc_read(OMAP_RTC_DAYS_REG);
|
||||
tm->tm_mon = rtc_read(OMAP_RTC_MONTHS_REG);
|
||||
tm->tm_year = rtc_read(OMAP_RTC_YEARS_REG);
|
||||
|
||||
local_irq_enable();
|
||||
|
||||
bcd2tm(tm);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap_rtc_set_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
if (tm2bcd(tm) < 0)
|
||||
return -EINVAL;
|
||||
local_irq_disable();
|
||||
rtc_wait_not_busy();
|
||||
|
||||
rtc_write(tm->tm_year, OMAP_RTC_YEARS_REG);
|
||||
rtc_write(tm->tm_mon, OMAP_RTC_MONTHS_REG);
|
||||
rtc_write(tm->tm_mday, OMAP_RTC_DAYS_REG);
|
||||
rtc_write(tm->tm_hour, OMAP_RTC_HOURS_REG);
|
||||
rtc_write(tm->tm_min, OMAP_RTC_MINUTES_REG);
|
||||
rtc_write(tm->tm_sec, OMAP_RTC_SECONDS_REG);
|
||||
|
||||
local_irq_enable();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm)
|
||||
{
|
||||
local_irq_disable();
|
||||
rtc_wait_not_busy();
|
||||
|
||||
alm->time.tm_sec = rtc_read(OMAP_RTC_ALARM_SECONDS_REG);
|
||||
alm->time.tm_min = rtc_read(OMAP_RTC_ALARM_MINUTES_REG);
|
||||
alm->time.tm_hour = rtc_read(OMAP_RTC_ALARM_HOURS_REG);
|
||||
alm->time.tm_mday = rtc_read(OMAP_RTC_ALARM_DAYS_REG);
|
||||
alm->time.tm_mon = rtc_read(OMAP_RTC_ALARM_MONTHS_REG);
|
||||
alm->time.tm_year = rtc_read(OMAP_RTC_ALARM_YEARS_REG);
|
||||
|
||||
local_irq_enable();
|
||||
|
||||
bcd2tm(&alm->time);
|
||||
alm->enabled = !!(rtc_read(OMAP_RTC_INTERRUPTS_REG)
|
||||
& OMAP_RTC_INTERRUPTS_IT_ALARM);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm)
|
||||
{
|
||||
u8 reg;
|
||||
|
||||
/* Much userspace code uses RTC_ALM_SET, thus "don't care" for
|
||||
* day/month/year specifies alarms up to 24 hours in the future.
|
||||
* So we need to handle that ... but let's ignore the "don't care"
|
||||
* values for hours/minutes/seconds.
|
||||
*/
|
||||
if (alm->time.tm_mday <= 0
|
||||
&& alm->time.tm_mon < 0
|
||||
&& alm->time.tm_year < 0) {
|
||||
struct rtc_time tm;
|
||||
unsigned long now, then;
|
||||
|
||||
omap_rtc_read_time(dev, &tm);
|
||||
rtc_tm_to_time(&tm, &now);
|
||||
|
||||
alm->time.tm_mday = tm.tm_mday;
|
||||
alm->time.tm_mon = tm.tm_mon;
|
||||
alm->time.tm_year = tm.tm_year;
|
||||
rtc_tm_to_time(&alm->time, &then);
|
||||
|
||||
/* sometimes the alarm wraps into tomorrow */
|
||||
if (then < now) {
|
||||
rtc_time_to_tm(now + 24 * 60 * 60, &tm);
|
||||
alm->time.tm_mday = tm.tm_mday;
|
||||
alm->time.tm_mon = tm.tm_mon;
|
||||
alm->time.tm_year = tm.tm_year;
|
||||
}
|
||||
}
|
||||
|
||||
if (tm2bcd(&alm->time) < 0)
|
||||
return -EINVAL;
|
||||
|
||||
local_irq_disable();
|
||||
rtc_wait_not_busy();
|
||||
|
||||
rtc_write(alm->time.tm_year, OMAP_RTC_ALARM_YEARS_REG);
|
||||
rtc_write(alm->time.tm_mon, OMAP_RTC_ALARM_MONTHS_REG);
|
||||
rtc_write(alm->time.tm_mday, OMAP_RTC_ALARM_DAYS_REG);
|
||||
rtc_write(alm->time.tm_hour, OMAP_RTC_ALARM_HOURS_REG);
|
||||
rtc_write(alm->time.tm_min, OMAP_RTC_ALARM_MINUTES_REG);
|
||||
rtc_write(alm->time.tm_sec, OMAP_RTC_ALARM_SECONDS_REG);
|
||||
|
||||
reg = rtc_read(OMAP_RTC_INTERRUPTS_REG);
|
||||
if (alm->enabled)
|
||||
reg |= OMAP_RTC_INTERRUPTS_IT_ALARM;
|
||||
else
|
||||
reg &= ~OMAP_RTC_INTERRUPTS_IT_ALARM;
|
||||
rtc_write(reg, OMAP_RTC_INTERRUPTS_REG);
|
||||
|
||||
local_irq_enable();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct rtc_class_ops omap_rtc_ops = {
|
||||
.ioctl = omap_rtc_ioctl,
|
||||
.read_time = omap_rtc_read_time,
|
||||
.set_time = omap_rtc_set_time,
|
||||
.read_alarm = omap_rtc_read_alarm,
|
||||
.set_alarm = omap_rtc_set_alarm,
|
||||
};
|
||||
|
||||
static int omap_rtc_alarm;
|
||||
static int omap_rtc_timer;
|
||||
|
||||
static int __devinit omap_rtc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *res, *mem;
|
||||
struct rtc_device *rtc;
|
||||
u8 reg, new_ctrl;
|
||||
|
||||
omap_rtc_timer = platform_get_irq(pdev, 0);
|
||||
if (omap_rtc_timer <= 0) {
|
||||
pr_debug("%s: no update irq?\n", pdev->name);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
omap_rtc_alarm = platform_get_irq(pdev, 1);
|
||||
if (omap_rtc_alarm <= 0) {
|
||||
pr_debug("%s: no alarm irq?\n", pdev->name);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
/* NOTE: using static mapping for RTC registers */
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (res && res->start != OMAP_RTC_BASE) {
|
||||
pr_debug("%s: RTC registers at %08x, expected %08x\n",
|
||||
pdev->name, (unsigned) res->start, OMAP_RTC_BASE);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
if (res)
|
||||
mem = request_mem_region(res->start,
|
||||
res->end - res->start + 1,
|
||||
pdev->name);
|
||||
else
|
||||
mem = NULL;
|
||||
if (!mem) {
|
||||
pr_debug("%s: RTC registers at %08x are not free\n",
|
||||
pdev->name, OMAP_RTC_BASE);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
rtc = rtc_device_register(pdev->name, &pdev->dev,
|
||||
&omap_rtc_ops, THIS_MODULE);
|
||||
if (IS_ERR(rtc)) {
|
||||
pr_debug("%s: can't register RTC device, err %ld\n",
|
||||
pdev->name, PTR_ERR(rtc));
|
||||
goto fail;
|
||||
}
|
||||
platform_set_drvdata(pdev, rtc);
|
||||
class_set_devdata(&rtc->class_dev, mem);
|
||||
|
||||
/* clear pending irqs, and set 1/second periodic,
|
||||
* which we'll use instead of update irqs
|
||||
*/
|
||||
rtc_write(0, OMAP_RTC_INTERRUPTS_REG);
|
||||
|
||||
/* clear old status */
|
||||
reg = rtc_read(OMAP_RTC_STATUS_REG);
|
||||
if (reg & (u8) OMAP_RTC_STATUS_POWER_UP) {
|
||||
pr_info("%s: RTC power up reset detected\n",
|
||||
pdev->name);
|
||||
rtc_write(OMAP_RTC_STATUS_POWER_UP, OMAP_RTC_STATUS_REG);
|
||||
}
|
||||
if (reg & (u8) OMAP_RTC_STATUS_ALARM)
|
||||
rtc_write(OMAP_RTC_STATUS_ALARM, OMAP_RTC_STATUS_REG);
|
||||
|
||||
/* handle periodic and alarm irqs */
|
||||
if (request_irq(omap_rtc_timer, rtc_irq, IRQF_DISABLED,
|
||||
rtc->class_dev.class_id, &rtc->class_dev)) {
|
||||
pr_debug("%s: RTC timer interrupt IRQ%d already claimed\n",
|
||||
pdev->name, omap_rtc_timer);
|
||||
goto fail0;
|
||||
}
|
||||
if (request_irq(omap_rtc_alarm, rtc_irq, IRQF_DISABLED,
|
||||
rtc->class_dev.class_id, &rtc->class_dev)) {
|
||||
pr_debug("%s: RTC alarm interrupt IRQ%d already claimed\n",
|
||||
pdev->name, omap_rtc_alarm);
|
||||
goto fail1;
|
||||
}
|
||||
|
||||
/* On boards with split power, RTC_ON_NOFF won't reset the RTC */
|
||||
reg = rtc_read(OMAP_RTC_CTRL_REG);
|
||||
if (reg & (u8) OMAP_RTC_CTRL_STOP)
|
||||
pr_info("%s: already running\n", pdev->name);
|
||||
|
||||
/* force to 24 hour mode */
|
||||
new_ctrl = reg & ~(OMAP_RTC_CTRL_SPLIT|OMAP_RTC_CTRL_AUTO_COMP);
|
||||
new_ctrl |= OMAP_RTC_CTRL_STOP;
|
||||
|
||||
/* BOARD-SPECIFIC CUSTOMIZATION CAN GO HERE:
|
||||
*
|
||||
* - Boards wired so that RTC_WAKE_INT does something, and muxed
|
||||
* right (W13_1610_RTC_WAKE_INT is the default after chip reset),
|
||||
* should initialize the device wakeup flag appropriately.
|
||||
*
|
||||
* - Boards wired so RTC_ON_nOFF is used as the reset signal,
|
||||
* rather than nPWRON_RESET, should forcibly enable split
|
||||
* power mode. (Some chip errata report that RTC_CTRL_SPLIT
|
||||
* is write-only, and always reads as zero...)
|
||||
*/
|
||||
device_init_wakeup(&pdev->dev, 0);
|
||||
|
||||
if (new_ctrl & (u8) OMAP_RTC_CTRL_SPLIT)
|
||||
pr_info("%s: split power mode\n", pdev->name);
|
||||
|
||||
if (reg != new_ctrl)
|
||||
rtc_write(new_ctrl, OMAP_RTC_CTRL_REG);
|
||||
|
||||
return 0;
|
||||
|
||||
fail1:
|
||||
free_irq(omap_rtc_timer, NULL);
|
||||
fail0:
|
||||
rtc_device_unregister(rtc);
|
||||
fail:
|
||||
release_resource(mem);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static int __devexit omap_rtc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct rtc_device *rtc = platform_get_drvdata(pdev);;
|
||||
|
||||
device_init_wakeup(&pdev->dev, 0);
|
||||
|
||||
/* leave rtc running, but disable irqs */
|
||||
rtc_write(0, OMAP_RTC_INTERRUPTS_REG);
|
||||
|
||||
free_irq(omap_rtc_timer, rtc);
|
||||
free_irq(omap_rtc_alarm, rtc);
|
||||
|
||||
release_resource(class_get_devdata(&rtc->class_dev));
|
||||
rtc_device_unregister(rtc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
static struct timespec rtc_delta;
|
||||
static u8 irqstat;
|
||||
|
||||
static int omap_rtc_suspend(struct platform_device *pdev, pm_message_t state)
|
||||
{
|
||||
struct rtc_time rtc_tm;
|
||||
struct timespec time;
|
||||
|
||||
time.tv_nsec = 0;
|
||||
omap_rtc_read_time(NULL, &rtc_tm);
|
||||
rtc_tm_to_time(&rtc_tm, &time.tv_sec);
|
||||
|
||||
save_time_delta(&rtc_delta, &time);
|
||||
irqstat = rtc_read(OMAP_RTC_INTERRUPTS_REG);
|
||||
|
||||
/* FIXME the RTC alarm is not currently acting as a wakeup event
|
||||
* source, and in fact this enable() call is just saving a flag
|
||||
* that's never used...
|
||||
*/
|
||||
if (device_may_wakeup(&pdev->dev))
|
||||
enable_irq_wake(omap_rtc_alarm);
|
||||
else
|
||||
rtc_write(0, OMAP_RTC_INTERRUPTS_REG);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap_rtc_resume(struct platform_device *pdev)
|
||||
{
|
||||
struct rtc_time rtc_tm;
|
||||
struct timespec time;
|
||||
|
||||
time.tv_nsec = 0;
|
||||
omap_rtc_read_time(NULL, &rtc_tm);
|
||||
rtc_tm_to_time(&rtc_tm, &time.tv_sec);
|
||||
|
||||
restore_time_delta(&rtc_delta, &time);
|
||||
if (device_may_wakeup(&pdev->dev))
|
||||
disable_irq_wake(omap_rtc_alarm);
|
||||
else
|
||||
rtc_write(irqstat, OMAP_RTC_INTERRUPTS_REG);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
#define omap_rtc_suspend NULL
|
||||
#define omap_rtc_resume NULL
|
||||
#endif
|
||||
|
||||
static void omap_rtc_shutdown(struct platform_device *pdev)
|
||||
{
|
||||
rtc_write(0, OMAP_RTC_INTERRUPTS_REG);
|
||||
}
|
||||
|
||||
MODULE_ALIAS("omap_rtc");
|
||||
static struct platform_driver omap_rtc_driver = {
|
||||
.probe = omap_rtc_probe,
|
||||
.remove = __devexit_p(omap_rtc_remove),
|
||||
.suspend = omap_rtc_suspend,
|
||||
.resume = omap_rtc_resume,
|
||||
.shutdown = omap_rtc_shutdown,
|
||||
.driver = {
|
||||
.name = "omap_rtc",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init rtc_init(void)
|
||||
{
|
||||
return platform_driver_register(&omap_rtc_driver);
|
||||
}
|
||||
module_init(rtc_init);
|
||||
|
||||
static void __exit rtc_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&omap_rtc_driver);
|
||||
}
|
||||
module_exit(rtc_exit);
|
||||
|
||||
MODULE_AUTHOR("George G. Davis (and others)");
|
||||
MODULE_LICENSE("GPL");
|
||||
370
drivers/rtc/rtc-pcf8563.c
Normal file
370
drivers/rtc/rtc-pcf8563.c
Normal file
@@ -0,0 +1,370 @@
|
||||
/*
|
||||
* An I2C driver for the Philips PCF8563 RTC
|
||||
* Copyright 2005-06 Tower Technologies
|
||||
*
|
||||
* Author: Alessandro Zummo <a.zummo@towertech.it>
|
||||
* Maintainers: http://www.nslu2-linux.org/
|
||||
*
|
||||
* based on the other drivers in this same directory.
|
||||
*
|
||||
* http://www.semiconductors.philips.com/acrobat/datasheets/PCF8563-04.pdf
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/bcd.h>
|
||||
#include <linux/rtc.h>
|
||||
|
||||
#define DRV_VERSION "0.4.2"
|
||||
|
||||
/* Addresses to scan: none
|
||||
* This chip cannot be reliably autodetected. An empty eeprom
|
||||
* located at 0x51 will pass the validation routine due to
|
||||
* the way the registers are implemented.
|
||||
*/
|
||||
static unsigned short normal_i2c[] = { I2C_CLIENT_END };
|
||||
|
||||
/* Module parameters */
|
||||
I2C_CLIENT_INSMOD;
|
||||
|
||||
#define PCF8563_REG_ST1 0x00 /* status */
|
||||
#define PCF8563_REG_ST2 0x01
|
||||
|
||||
#define PCF8563_REG_SC 0x02 /* datetime */
|
||||
#define PCF8563_REG_MN 0x03
|
||||
#define PCF8563_REG_HR 0x04
|
||||
#define PCF8563_REG_DM 0x05
|
||||
#define PCF8563_REG_DW 0x06
|
||||
#define PCF8563_REG_MO 0x07
|
||||
#define PCF8563_REG_YR 0x08
|
||||
|
||||
#define PCF8563_REG_AMN 0x09 /* alarm */
|
||||
#define PCF8563_REG_AHR 0x0A
|
||||
#define PCF8563_REG_ADM 0x0B
|
||||
#define PCF8563_REG_ADW 0x0C
|
||||
|
||||
#define PCF8563_REG_CLKO 0x0D /* clock out */
|
||||
#define PCF8563_REG_TMRC 0x0E /* timer control */
|
||||
#define PCF8563_REG_TMR 0x0F /* timer */
|
||||
|
||||
#define PCF8563_SC_LV 0x80 /* low voltage */
|
||||
#define PCF8563_MO_C 0x80 /* century */
|
||||
|
||||
struct pcf8563 {
|
||||
struct i2c_client client;
|
||||
/*
|
||||
* The meaning of MO_C bit varies by the chip type.
|
||||
* From PCF8563 datasheet: this bit is toggled when the years
|
||||
* register overflows from 99 to 00
|
||||
* 0 indicates the century is 20xx
|
||||
* 1 indicates the century is 19xx
|
||||
* From RTC8564 datasheet: this bit indicates change of
|
||||
* century. When the year digit data overflows from 99 to 00,
|
||||
* this bit is set. By presetting it to 0 while still in the
|
||||
* 20th century, it will be set in year 2000, ...
|
||||
* There seems no reliable way to know how the system use this
|
||||
* bit. So let's do it heuristically, assuming we are live in
|
||||
* 1970...2069.
|
||||
*/
|
||||
int c_polarity; /* 0: MO_C=1 means 19xx, otherwise MO_C=1 means 20xx */
|
||||
};
|
||||
|
||||
static int pcf8563_probe(struct i2c_adapter *adapter, int address, int kind);
|
||||
static int pcf8563_detach(struct i2c_client *client);
|
||||
|
||||
/*
|
||||
* In the routines that deal directly with the pcf8563 hardware, we use
|
||||
* rtc_time -- month 0-11, hour 0-23, yr = calendar year-epoch.
|
||||
*/
|
||||
static int pcf8563_get_datetime(struct i2c_client *client, struct rtc_time *tm)
|
||||
{
|
||||
struct pcf8563 *pcf8563 = container_of(client, struct pcf8563, client);
|
||||
unsigned char buf[13] = { PCF8563_REG_ST1 };
|
||||
|
||||
struct i2c_msg msgs[] = {
|
||||
{ client->addr, 0, 1, buf }, /* setup read ptr */
|
||||
{ client->addr, I2C_M_RD, 13, buf }, /* read status + date */
|
||||
};
|
||||
|
||||
/* read registers */
|
||||
if ((i2c_transfer(client->adapter, msgs, 2)) != 2) {
|
||||
dev_err(&client->dev, "%s: read error\n", __FUNCTION__);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (buf[PCF8563_REG_SC] & PCF8563_SC_LV)
|
||||
dev_info(&client->dev,
|
||||
"low voltage detected, date/time is not reliable.\n");
|
||||
|
||||
dev_dbg(&client->dev,
|
||||
"%s: raw data is st1=%02x, st2=%02x, sec=%02x, min=%02x, hr=%02x, "
|
||||
"mday=%02x, wday=%02x, mon=%02x, year=%02x\n",
|
||||
__FUNCTION__,
|
||||
buf[0], buf[1], buf[2], buf[3],
|
||||
buf[4], buf[5], buf[6], buf[7],
|
||||
buf[8]);
|
||||
|
||||
|
||||
tm->tm_sec = BCD2BIN(buf[PCF8563_REG_SC] & 0x7F);
|
||||
tm->tm_min = BCD2BIN(buf[PCF8563_REG_MN] & 0x7F);
|
||||
tm->tm_hour = BCD2BIN(buf[PCF8563_REG_HR] & 0x3F); /* rtc hr 0-23 */
|
||||
tm->tm_mday = BCD2BIN(buf[PCF8563_REG_DM] & 0x3F);
|
||||
tm->tm_wday = buf[PCF8563_REG_DW] & 0x07;
|
||||
tm->tm_mon = BCD2BIN(buf[PCF8563_REG_MO] & 0x1F) - 1; /* rtc mn 1-12 */
|
||||
tm->tm_year = BCD2BIN(buf[PCF8563_REG_YR]);
|
||||
if (tm->tm_year < 70)
|
||||
tm->tm_year += 100; /* assume we are in 1970...2069 */
|
||||
/* detect the polarity heuristically. see note above. */
|
||||
pcf8563->c_polarity = (buf[PCF8563_REG_MO] & PCF8563_MO_C) ?
|
||||
(tm->tm_year >= 100) : (tm->tm_year < 100);
|
||||
|
||||
dev_dbg(&client->dev, "%s: tm is secs=%d, mins=%d, hours=%d, "
|
||||
"mday=%d, mon=%d, year=%d, wday=%d\n",
|
||||
__FUNCTION__,
|
||||
tm->tm_sec, tm->tm_min, tm->tm_hour,
|
||||
tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday);
|
||||
|
||||
/* the clock can give out invalid datetime, but we cannot return
|
||||
* -EINVAL otherwise hwclock will refuse to set the time on bootup.
|
||||
*/
|
||||
if (rtc_valid_tm(tm) < 0)
|
||||
dev_err(&client->dev, "retrieved date/time is not valid.\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pcf8563_set_datetime(struct i2c_client *client, struct rtc_time *tm)
|
||||
{
|
||||
struct pcf8563 *pcf8563 = container_of(client, struct pcf8563, client);
|
||||
int i, err;
|
||||
unsigned char buf[9];
|
||||
|
||||
dev_dbg(&client->dev, "%s: secs=%d, mins=%d, hours=%d, "
|
||||
"mday=%d, mon=%d, year=%d, wday=%d\n",
|
||||
__FUNCTION__,
|
||||
tm->tm_sec, tm->tm_min, tm->tm_hour,
|
||||
tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday);
|
||||
|
||||
/* hours, minutes and seconds */
|
||||
buf[PCF8563_REG_SC] = BIN2BCD(tm->tm_sec);
|
||||
buf[PCF8563_REG_MN] = BIN2BCD(tm->tm_min);
|
||||
buf[PCF8563_REG_HR] = BIN2BCD(tm->tm_hour);
|
||||
|
||||
buf[PCF8563_REG_DM] = BIN2BCD(tm->tm_mday);
|
||||
|
||||
/* month, 1 - 12 */
|
||||
buf[PCF8563_REG_MO] = BIN2BCD(tm->tm_mon + 1);
|
||||
|
||||
/* year and century */
|
||||
buf[PCF8563_REG_YR] = BIN2BCD(tm->tm_year % 100);
|
||||
if (pcf8563->c_polarity ? (tm->tm_year >= 100) : (tm->tm_year < 100))
|
||||
buf[PCF8563_REG_MO] |= PCF8563_MO_C;
|
||||
|
||||
buf[PCF8563_REG_DW] = tm->tm_wday & 0x07;
|
||||
|
||||
/* write register's data */
|
||||
for (i = 0; i < 7; i++) {
|
||||
unsigned char data[2] = { PCF8563_REG_SC + i,
|
||||
buf[PCF8563_REG_SC + i] };
|
||||
|
||||
err = i2c_master_send(client, data, sizeof(data));
|
||||
if (err != sizeof(data)) {
|
||||
dev_err(&client->dev,
|
||||
"%s: err=%d addr=%02x, data=%02x\n",
|
||||
__FUNCTION__, err, data[0], data[1]);
|
||||
return -EIO;
|
||||
}
|
||||
};
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct pcf8563_limit
|
||||
{
|
||||
unsigned char reg;
|
||||
unsigned char mask;
|
||||
unsigned char min;
|
||||
unsigned char max;
|
||||
};
|
||||
|
||||
static int pcf8563_validate_client(struct i2c_client *client)
|
||||
{
|
||||
int i;
|
||||
|
||||
static const struct pcf8563_limit pattern[] = {
|
||||
/* register, mask, min, max */
|
||||
{ PCF8563_REG_SC, 0x7F, 0, 59 },
|
||||
{ PCF8563_REG_MN, 0x7F, 0, 59 },
|
||||
{ PCF8563_REG_HR, 0x3F, 0, 23 },
|
||||
{ PCF8563_REG_DM, 0x3F, 0, 31 },
|
||||
{ PCF8563_REG_MO, 0x1F, 0, 12 },
|
||||
};
|
||||
|
||||
/* check limits (only registers with bcd values) */
|
||||
for (i = 0; i < ARRAY_SIZE(pattern); i++) {
|
||||
int xfer;
|
||||
unsigned char value;
|
||||
unsigned char buf = pattern[i].reg;
|
||||
|
||||
struct i2c_msg msgs[] = {
|
||||
{ client->addr, 0, 1, &buf },
|
||||
{ client->addr, I2C_M_RD, 1, &buf },
|
||||
};
|
||||
|
||||
xfer = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
|
||||
|
||||
if (xfer != ARRAY_SIZE(msgs)) {
|
||||
dev_err(&client->dev,
|
||||
"%s: could not read register 0x%02X\n",
|
||||
__FUNCTION__, pattern[i].reg);
|
||||
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
value = BCD2BIN(buf & pattern[i].mask);
|
||||
|
||||
if (value > pattern[i].max ||
|
||||
value < pattern[i].min) {
|
||||
dev_dbg(&client->dev,
|
||||
"%s: pattern=%d, reg=%x, mask=0x%02x, min=%d, "
|
||||
"max=%d, value=%d, raw=0x%02X\n",
|
||||
__FUNCTION__, i, pattern[i].reg, pattern[i].mask,
|
||||
pattern[i].min, pattern[i].max,
|
||||
value, buf);
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pcf8563_rtc_read_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
return pcf8563_get_datetime(to_i2c_client(dev), tm);
|
||||
}
|
||||
|
||||
static int pcf8563_rtc_set_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
return pcf8563_set_datetime(to_i2c_client(dev), tm);
|
||||
}
|
||||
|
||||
static const struct rtc_class_ops pcf8563_rtc_ops = {
|
||||
.read_time = pcf8563_rtc_read_time,
|
||||
.set_time = pcf8563_rtc_set_time,
|
||||
};
|
||||
|
||||
static int pcf8563_attach(struct i2c_adapter *adapter)
|
||||
{
|
||||
return i2c_probe(adapter, &addr_data, pcf8563_probe);
|
||||
}
|
||||
|
||||
static struct i2c_driver pcf8563_driver = {
|
||||
.driver = {
|
||||
.name = "pcf8563",
|
||||
},
|
||||
.id = I2C_DRIVERID_PCF8563,
|
||||
.attach_adapter = &pcf8563_attach,
|
||||
.detach_client = &pcf8563_detach,
|
||||
};
|
||||
|
||||
static int pcf8563_probe(struct i2c_adapter *adapter, int address, int kind)
|
||||
{
|
||||
struct pcf8563 *pcf8563;
|
||||
struct i2c_client *client;
|
||||
struct rtc_device *rtc;
|
||||
|
||||
int err = 0;
|
||||
|
||||
dev_dbg(&adapter->dev, "%s\n", __FUNCTION__);
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) {
|
||||
err = -ENODEV;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!(pcf8563 = kzalloc(sizeof(struct pcf8563), GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
client = &pcf8563->client;
|
||||
client->addr = address;
|
||||
client->driver = &pcf8563_driver;
|
||||
client->adapter = adapter;
|
||||
|
||||
strlcpy(client->name, pcf8563_driver.driver.name, I2C_NAME_SIZE);
|
||||
|
||||
/* Verify the chip is really an PCF8563 */
|
||||
if (kind < 0) {
|
||||
if (pcf8563_validate_client(client) < 0) {
|
||||
err = -ENODEV;
|
||||
goto exit_kfree;
|
||||
}
|
||||
}
|
||||
|
||||
/* Inform the i2c layer */
|
||||
if ((err = i2c_attach_client(client)))
|
||||
goto exit_kfree;
|
||||
|
||||
dev_info(&client->dev, "chip found, driver version " DRV_VERSION "\n");
|
||||
|
||||
rtc = rtc_device_register(pcf8563_driver.driver.name, &client->dev,
|
||||
&pcf8563_rtc_ops, THIS_MODULE);
|
||||
|
||||
if (IS_ERR(rtc)) {
|
||||
err = PTR_ERR(rtc);
|
||||
goto exit_detach;
|
||||
}
|
||||
|
||||
i2c_set_clientdata(client, rtc);
|
||||
|
||||
return 0;
|
||||
|
||||
exit_detach:
|
||||
i2c_detach_client(client);
|
||||
|
||||
exit_kfree:
|
||||
kfree(pcf8563);
|
||||
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int pcf8563_detach(struct i2c_client *client)
|
||||
{
|
||||
struct pcf8563 *pcf8563 = container_of(client, struct pcf8563, client);
|
||||
int err;
|
||||
struct rtc_device *rtc = i2c_get_clientdata(client);
|
||||
|
||||
if (rtc)
|
||||
rtc_device_unregister(rtc);
|
||||
|
||||
if ((err = i2c_detach_client(client)))
|
||||
return err;
|
||||
|
||||
kfree(pcf8563);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init pcf8563_init(void)
|
||||
{
|
||||
return i2c_add_driver(&pcf8563_driver);
|
||||
}
|
||||
|
||||
static void __exit pcf8563_exit(void)
|
||||
{
|
||||
i2c_del_driver(&pcf8563_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Alessandro Zummo <a.zummo@towertech.it>");
|
||||
MODULE_DESCRIPTION("Philips PCF8563/Epson RTC8564 RTC driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_VERSION(DRV_VERSION);
|
||||
|
||||
module_init(pcf8563_init);
|
||||
module_exit(pcf8563_exit);
|
||||
395
drivers/rtc/rtc-pcf8583.c
Normal file
395
drivers/rtc/rtc-pcf8583.c
Normal file
@@ -0,0 +1,395 @@
|
||||
/*
|
||||
* drivers/rtc/rtc-pcf8583.c
|
||||
*
|
||||
* Copyright (C) 2000 Russell King
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* Driver for PCF8583 RTC & RAM chip
|
||||
*
|
||||
* Converted to the generic RTC susbsystem by G. Liakhovetski (2006)
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/mc146818rtc.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/bcd.h>
|
||||
|
||||
struct rtc_mem {
|
||||
unsigned int loc;
|
||||
unsigned int nr;
|
||||
unsigned char *data;
|
||||
};
|
||||
|
||||
struct pcf8583 {
|
||||
struct i2c_client client;
|
||||
struct rtc_device *rtc;
|
||||
unsigned char ctrl;
|
||||
};
|
||||
|
||||
#define CTRL_STOP 0x80
|
||||
#define CTRL_HOLD 0x40
|
||||
#define CTRL_32KHZ 0x00
|
||||
#define CTRL_MASK 0x08
|
||||
#define CTRL_ALARMEN 0x04
|
||||
#define CTRL_ALARM 0x02
|
||||
#define CTRL_TIMER 0x01
|
||||
|
||||
static unsigned short normal_i2c[] = { 0x50, I2C_CLIENT_END };
|
||||
|
||||
/* Module parameters */
|
||||
I2C_CLIENT_INSMOD;
|
||||
|
||||
static struct i2c_driver pcf8583_driver;
|
||||
|
||||
#define get_ctrl(x) ((struct pcf8583 *)i2c_get_clientdata(x))->ctrl
|
||||
#define set_ctrl(x, v) get_ctrl(x) = v
|
||||
|
||||
#define CMOS_YEAR (64 + 128)
|
||||
#define CMOS_CHECKSUM (63)
|
||||
|
||||
static int pcf8583_get_datetime(struct i2c_client *client, struct rtc_time *dt)
|
||||
{
|
||||
unsigned char buf[8], addr[1] = { 1 };
|
||||
struct i2c_msg msgs[2] = {
|
||||
{
|
||||
.addr = client->addr,
|
||||
.flags = 0,
|
||||
.len = 1,
|
||||
.buf = addr,
|
||||
}, {
|
||||
.addr = client->addr,
|
||||
.flags = I2C_M_RD,
|
||||
.len = 6,
|
||||
.buf = buf,
|
||||
}
|
||||
};
|
||||
int ret;
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
|
||||
ret = i2c_transfer(client->adapter, msgs, 2);
|
||||
if (ret == 2) {
|
||||
dt->tm_year = buf[4] >> 6;
|
||||
dt->tm_wday = buf[5] >> 5;
|
||||
|
||||
buf[4] &= 0x3f;
|
||||
buf[5] &= 0x1f;
|
||||
|
||||
dt->tm_sec = BCD2BIN(buf[1]);
|
||||
dt->tm_min = BCD2BIN(buf[2]);
|
||||
dt->tm_hour = BCD2BIN(buf[3]);
|
||||
dt->tm_mday = BCD2BIN(buf[4]);
|
||||
dt->tm_mon = BCD2BIN(buf[5]) - 1;
|
||||
}
|
||||
|
||||
return ret == 2 ? 0 : -EIO;
|
||||
}
|
||||
|
||||
static int pcf8583_set_datetime(struct i2c_client *client, struct rtc_time *dt, int datetoo)
|
||||
{
|
||||
unsigned char buf[8];
|
||||
int ret, len = 6;
|
||||
|
||||
buf[0] = 0;
|
||||
buf[1] = get_ctrl(client) | 0x80;
|
||||
buf[2] = 0;
|
||||
buf[3] = BIN2BCD(dt->tm_sec);
|
||||
buf[4] = BIN2BCD(dt->tm_min);
|
||||
buf[5] = BIN2BCD(dt->tm_hour);
|
||||
|
||||
if (datetoo) {
|
||||
len = 8;
|
||||
buf[6] = BIN2BCD(dt->tm_mday) | (dt->tm_year << 6);
|
||||
buf[7] = BIN2BCD(dt->tm_mon + 1) | (dt->tm_wday << 5);
|
||||
}
|
||||
|
||||
ret = i2c_master_send(client, (char *)buf, len);
|
||||
if (ret != len)
|
||||
return -EIO;
|
||||
|
||||
buf[1] = get_ctrl(client);
|
||||
ret = i2c_master_send(client, (char *)buf, 2);
|
||||
|
||||
return ret == 2 ? 0 : -EIO;
|
||||
}
|
||||
|
||||
static int pcf8583_get_ctrl(struct i2c_client *client, unsigned char *ctrl)
|
||||
{
|
||||
*ctrl = get_ctrl(client);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pcf8583_set_ctrl(struct i2c_client *client, unsigned char *ctrl)
|
||||
{
|
||||
unsigned char buf[2];
|
||||
|
||||
buf[0] = 0;
|
||||
buf[1] = *ctrl;
|
||||
set_ctrl(client, *ctrl);
|
||||
|
||||
return i2c_master_send(client, (char *)buf, 2);
|
||||
}
|
||||
|
||||
static int pcf8583_read_mem(struct i2c_client *client, struct rtc_mem *mem)
|
||||
{
|
||||
unsigned char addr[1];
|
||||
struct i2c_msg msgs[2] = {
|
||||
{
|
||||
.addr = client->addr,
|
||||
.flags = 0,
|
||||
.len = 1,
|
||||
.buf = addr,
|
||||
}, {
|
||||
.addr = client->addr,
|
||||
.flags = I2C_M_RD,
|
||||
.len = mem->nr,
|
||||
.buf = mem->data,
|
||||
}
|
||||
};
|
||||
|
||||
if (mem->loc < 8)
|
||||
return -EINVAL;
|
||||
|
||||
addr[0] = mem->loc;
|
||||
|
||||
return i2c_transfer(client->adapter, msgs, 2) == 2 ? 0 : -EIO;
|
||||
}
|
||||
|
||||
static int pcf8583_write_mem(struct i2c_client *client, struct rtc_mem *mem)
|
||||
{
|
||||
unsigned char addr[1];
|
||||
struct i2c_msg msgs[2] = {
|
||||
{
|
||||
.addr = client->addr,
|
||||
.flags = 0,
|
||||
.len = 1,
|
||||
.buf = addr,
|
||||
}, {
|
||||
.addr = client->addr,
|
||||
.flags = I2C_M_NOSTART,
|
||||
.len = mem->nr,
|
||||
.buf = mem->data,
|
||||
}
|
||||
};
|
||||
|
||||
if (mem->loc < 8)
|
||||
return -EINVAL;
|
||||
|
||||
addr[0] = mem->loc;
|
||||
|
||||
return i2c_transfer(client->adapter, msgs, 2) == 2 ? 0 : -EIO;
|
||||
}
|
||||
|
||||
static int pcf8583_rtc_read_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
unsigned char ctrl, year[2];
|
||||
struct rtc_mem mem = { CMOS_YEAR, sizeof(year), year };
|
||||
int real_year, year_offset, err;
|
||||
|
||||
/*
|
||||
* Ensure that the RTC is running.
|
||||
*/
|
||||
pcf8583_get_ctrl(client, &ctrl);
|
||||
if (ctrl & (CTRL_STOP | CTRL_HOLD)) {
|
||||
unsigned char new_ctrl = ctrl & ~(CTRL_STOP | CTRL_HOLD);
|
||||
|
||||
printk(KERN_WARNING "RTC: resetting control %02x -> %02x\n",
|
||||
ctrl, new_ctrl);
|
||||
|
||||
if ((err = pcf8583_set_ctrl(client, &new_ctrl)) < 0)
|
||||
return err;
|
||||
}
|
||||
|
||||
if (pcf8583_get_datetime(client, tm) ||
|
||||
pcf8583_read_mem(client, &mem))
|
||||
return -EIO;
|
||||
|
||||
real_year = year[0];
|
||||
|
||||
/*
|
||||
* The RTC year holds the LSB two bits of the current
|
||||
* year, which should reflect the LSB two bits of the
|
||||
* CMOS copy of the year. Any difference indicates
|
||||
* that we have to correct the CMOS version.
|
||||
*/
|
||||
year_offset = tm->tm_year - (real_year & 3);
|
||||
if (year_offset < 0)
|
||||
/*
|
||||
* RTC year wrapped. Adjust it appropriately.
|
||||
*/
|
||||
year_offset += 4;
|
||||
|
||||
tm->tm_year = (real_year + year_offset + year[1] * 100) - 1900;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pcf8583_rtc_set_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
unsigned char year[2], chk;
|
||||
struct rtc_mem cmos_year = { CMOS_YEAR, sizeof(year), year };
|
||||
struct rtc_mem cmos_check = { CMOS_CHECKSUM, 1, &chk };
|
||||
unsigned int proper_year = tm->tm_year + 1900;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* The RTC's own 2-bit year must reflect the least
|
||||
* significant two bits of the CMOS year.
|
||||
*/
|
||||
|
||||
ret = pcf8583_set_datetime(client, tm, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = pcf8583_read_mem(client, &cmos_check);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = pcf8583_read_mem(client, &cmos_year);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
chk -= year[1] + year[0];
|
||||
|
||||
year[1] = proper_year / 100;
|
||||
year[0] = proper_year % 100;
|
||||
|
||||
chk += year[1] + year[0];
|
||||
|
||||
ret = pcf8583_write_mem(client, &cmos_year);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = pcf8583_write_mem(client, &cmos_check);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct rtc_class_ops pcf8583_rtc_ops = {
|
||||
.read_time = pcf8583_rtc_read_time,
|
||||
.set_time = pcf8583_rtc_set_time,
|
||||
};
|
||||
|
||||
static int pcf8583_probe(struct i2c_adapter *adap, int addr, int kind);
|
||||
|
||||
static int pcf8583_attach(struct i2c_adapter *adap)
|
||||
{
|
||||
return i2c_probe(adap, &addr_data, pcf8583_probe);
|
||||
}
|
||||
|
||||
static int pcf8583_detach(struct i2c_client *client)
|
||||
{
|
||||
int err;
|
||||
struct pcf8583 *pcf = i2c_get_clientdata(client);
|
||||
struct rtc_device *rtc = pcf->rtc;
|
||||
|
||||
if (rtc)
|
||||
rtc_device_unregister(rtc);
|
||||
|
||||
if ((err = i2c_detach_client(client)))
|
||||
return err;
|
||||
|
||||
kfree(pcf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct i2c_driver pcf8583_driver = {
|
||||
.driver = {
|
||||
.name = "pcf8583",
|
||||
},
|
||||
.id = I2C_DRIVERID_PCF8583,
|
||||
.attach_adapter = pcf8583_attach,
|
||||
.detach_client = pcf8583_detach,
|
||||
};
|
||||
|
||||
static int pcf8583_probe(struct i2c_adapter *adap, int addr, int kind)
|
||||
{
|
||||
struct pcf8583 *pcf;
|
||||
struct i2c_client *client;
|
||||
struct rtc_device *rtc;
|
||||
unsigned char buf[1], ad[1] = { 0 };
|
||||
int err;
|
||||
struct i2c_msg msgs[2] = {
|
||||
{
|
||||
.addr = addr,
|
||||
.flags = 0,
|
||||
.len = 1,
|
||||
.buf = ad,
|
||||
}, {
|
||||
.addr = addr,
|
||||
.flags = I2C_M_RD,
|
||||
.len = 1,
|
||||
.buf = buf,
|
||||
}
|
||||
};
|
||||
|
||||
pcf = kzalloc(sizeof(*pcf), GFP_KERNEL);
|
||||
if (!pcf)
|
||||
return -ENOMEM;
|
||||
|
||||
client = &pcf->client;
|
||||
|
||||
client->addr = addr;
|
||||
client->adapter = adap;
|
||||
client->driver = &pcf8583_driver;
|
||||
|
||||
strlcpy(client->name, pcf8583_driver.driver.name, I2C_NAME_SIZE);
|
||||
|
||||
if (i2c_transfer(client->adapter, msgs, 2) != 2) {
|
||||
err = -EIO;
|
||||
goto exit_kfree;
|
||||
}
|
||||
|
||||
err = i2c_attach_client(client);
|
||||
|
||||
if (err)
|
||||
goto exit_kfree;
|
||||
|
||||
rtc = rtc_device_register(pcf8583_driver.driver.name, &client->dev,
|
||||
&pcf8583_rtc_ops, THIS_MODULE);
|
||||
|
||||
if (IS_ERR(rtc)) {
|
||||
err = PTR_ERR(rtc);
|
||||
goto exit_detach;
|
||||
}
|
||||
|
||||
pcf->rtc = rtc;
|
||||
i2c_set_clientdata(client, pcf);
|
||||
set_ctrl(client, buf[0]);
|
||||
|
||||
return 0;
|
||||
|
||||
exit_detach:
|
||||
i2c_detach_client(client);
|
||||
|
||||
exit_kfree:
|
||||
kfree(pcf);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static __init int pcf8583_init(void)
|
||||
{
|
||||
return i2c_add_driver(&pcf8583_driver);
|
||||
}
|
||||
|
||||
static __exit void pcf8583_exit(void)
|
||||
{
|
||||
i2c_del_driver(&pcf8583_driver);
|
||||
}
|
||||
|
||||
module_init(pcf8583_init);
|
||||
module_exit(pcf8583_exit);
|
||||
|
||||
MODULE_AUTHOR("Russell King");
|
||||
MODULE_DESCRIPTION("PCF8583 I2C RTC driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
233
drivers/rtc/rtc-pl031.c
Normal file
233
drivers/rtc/rtc-pl031.c
Normal file
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* drivers/rtc/rtc-pl031.c
|
||||
*
|
||||
* Real Time Clock interface for ARM AMBA PrimeCell 031 RTC
|
||||
*
|
||||
* Author: Deepak Saxena <dsaxena@plexity.net>
|
||||
*
|
||||
* Copyright 2006 (c) MontaVista Software, Inc.
|
||||
*
|
||||
* 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/platform_device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/rtc.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/pm.h>
|
||||
|
||||
#include <linux/amba/bus.h>
|
||||
|
||||
#include <asm/io.h>
|
||||
#include <asm/bitops.h>
|
||||
#include <asm/hardware.h>
|
||||
#include <asm/irq.h>
|
||||
#include <asm/rtc.h>
|
||||
|
||||
/*
|
||||
* Register definitions
|
||||
*/
|
||||
#define RTC_DR 0x00 /* Data read register */
|
||||
#define RTC_MR 0x04 /* Match register */
|
||||
#define RTC_LR 0x08 /* Data load register */
|
||||
#define RTC_CR 0x0c /* Control register */
|
||||
#define RTC_IMSC 0x10 /* Interrupt mask and set register */
|
||||
#define RTC_RIS 0x14 /* Raw interrupt status register */
|
||||
#define RTC_MIS 0x18 /* Masked interrupt status register */
|
||||
#define RTC_ICR 0x1c /* Interrupt clear register */
|
||||
|
||||
struct pl031_local {
|
||||
struct rtc_device *rtc;
|
||||
void __iomem *base;
|
||||
};
|
||||
|
||||
static irqreturn_t pl031_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct rtc_device *rtc = dev_id;
|
||||
|
||||
rtc_update_irq(&rtc->class_dev, 1, RTC_AF);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int pl031_open(struct device *dev)
|
||||
{
|
||||
/*
|
||||
* We request IRQ in pl031_probe, so nothing to do here...
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pl031_release(struct device *dev)
|
||||
{
|
||||
}
|
||||
|
||||
static int pl031_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct pl031_local *ldata = dev_get_drvdata(dev);
|
||||
|
||||
switch (cmd) {
|
||||
case RTC_AIE_OFF:
|
||||
__raw_writel(1, ldata->base + RTC_MIS);
|
||||
return 0;
|
||||
case RTC_AIE_ON:
|
||||
__raw_writel(0, ldata->base + RTC_MIS);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
|
||||
static int pl031_read_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
struct pl031_local *ldata = dev_get_drvdata(dev);
|
||||
|
||||
rtc_time_to_tm(__raw_readl(ldata->base + RTC_DR), tm);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pl031_set_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
unsigned long time;
|
||||
struct pl031_local *ldata = dev_get_drvdata(dev);
|
||||
|
||||
rtc_tm_to_time(tm, &time);
|
||||
__raw_writel(time, ldata->base + RTC_LR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pl031_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
|
||||
{
|
||||
struct pl031_local *ldata = dev_get_drvdata(dev);
|
||||
|
||||
rtc_time_to_tm(__raw_readl(ldata->base + RTC_MR), &alarm->time);
|
||||
alarm->pending = __raw_readl(ldata->base + RTC_RIS);
|
||||
alarm->enabled = __raw_readl(ldata->base + RTC_IMSC);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pl031_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
|
||||
{
|
||||
struct pl031_local *ldata = dev_get_drvdata(dev);
|
||||
unsigned long time;
|
||||
|
||||
rtc_tm_to_time(&alarm->time, &time);
|
||||
|
||||
__raw_writel(time, ldata->base + RTC_MR);
|
||||
__raw_writel(!alarm->enabled, ldata->base + RTC_MIS);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct rtc_class_ops pl031_ops = {
|
||||
.open = pl031_open,
|
||||
.release = pl031_release,
|
||||
.ioctl = pl031_ioctl,
|
||||
.read_time = pl031_read_time,
|
||||
.set_time = pl031_set_time,
|
||||
.read_alarm = pl031_read_alarm,
|
||||
.set_alarm = pl031_set_alarm,
|
||||
};
|
||||
|
||||
static int pl031_remove(struct amba_device *adev)
|
||||
{
|
||||
struct pl031_local *ldata = dev_get_drvdata(&adev->dev);
|
||||
|
||||
if (ldata) {
|
||||
dev_set_drvdata(&adev->dev, NULL);
|
||||
free_irq(adev->irq[0], ldata->rtc);
|
||||
rtc_device_unregister(ldata->rtc);
|
||||
iounmap(ldata->base);
|
||||
kfree(ldata);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pl031_probe(struct amba_device *adev, void *id)
|
||||
{
|
||||
int ret;
|
||||
struct pl031_local *ldata;
|
||||
|
||||
|
||||
ldata = kmalloc(sizeof(struct pl031_local), GFP_KERNEL);
|
||||
if (!ldata) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
dev_set_drvdata(&adev->dev, ldata);
|
||||
|
||||
ldata->base = ioremap(adev->res.start,
|
||||
adev->res.end - adev->res.start + 1);
|
||||
if (!ldata->base) {
|
||||
ret = -ENOMEM;
|
||||
goto out_no_remap;
|
||||
}
|
||||
|
||||
if (request_irq(adev->irq[0], pl031_interrupt, IRQF_DISABLED,
|
||||
"rtc-pl031", ldata->rtc)) {
|
||||
ret = -EIO;
|
||||
goto out_no_irq;
|
||||
}
|
||||
|
||||
ldata->rtc = rtc_device_register("pl031", &adev->dev, &pl031_ops,
|
||||
THIS_MODULE);
|
||||
if (IS_ERR(ldata->rtc)) {
|
||||
ret = PTR_ERR(ldata->rtc);
|
||||
goto out_no_rtc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_no_rtc:
|
||||
free_irq(adev->irq[0], ldata->rtc);
|
||||
out_no_irq:
|
||||
iounmap(ldata->base);
|
||||
out_no_remap:
|
||||
dev_set_drvdata(&adev->dev, NULL);
|
||||
kfree(ldata);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct amba_id pl031_ids[] __initdata = {
|
||||
{
|
||||
.id = 0x00041031,
|
||||
.mask = 0x000fffff, },
|
||||
{0, 0},
|
||||
};
|
||||
|
||||
static struct amba_driver pl031_driver = {
|
||||
.drv = {
|
||||
.name = "rtc-pl031",
|
||||
},
|
||||
.id_table = pl031_ids,
|
||||
.probe = pl031_probe,
|
||||
.remove = pl031_remove,
|
||||
};
|
||||
|
||||
static int __init pl031_init(void)
|
||||
{
|
||||
return amba_driver_register(&pl031_driver);
|
||||
}
|
||||
|
||||
static void __exit pl031_exit(void)
|
||||
{
|
||||
amba_driver_unregister(&pl031_driver);
|
||||
}
|
||||
|
||||
module_init(pl031_init);
|
||||
module_exit(pl031_exit);
|
||||
|
||||
MODULE_AUTHOR("Deepak Saxena <dsaxena@plexity.net");
|
||||
MODULE_DESCRIPTION("ARM AMBA PL031 RTC Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
164
drivers/rtc/rtc-proc.c
Normal file
164
drivers/rtc/rtc-proc.c
Normal file
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* RTC subsystem, proc interface
|
||||
*
|
||||
* Copyright (C) 2005-06 Tower Technologies
|
||||
* Author: Alessandro Zummo <a.zummo@towertech.it>
|
||||
*
|
||||
* based on arch/arm/common/rtctime.c
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/rtc.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/seq_file.h>
|
||||
|
||||
static struct class_device *rtc_dev = NULL;
|
||||
static DEFINE_MUTEX(rtc_lock);
|
||||
|
||||
static int rtc_proc_show(struct seq_file *seq, void *offset)
|
||||
{
|
||||
int err;
|
||||
struct class_device *class_dev = seq->private;
|
||||
const struct rtc_class_ops *ops = to_rtc_device(class_dev)->ops;
|
||||
struct rtc_wkalrm alrm;
|
||||
struct rtc_time tm;
|
||||
|
||||
err = rtc_read_time(class_dev, &tm);
|
||||
if (err == 0) {
|
||||
seq_printf(seq,
|
||||
"rtc_time\t: %02d:%02d:%02d\n"
|
||||
"rtc_date\t: %04d-%02d-%02d\n",
|
||||
tm.tm_hour, tm.tm_min, tm.tm_sec,
|
||||
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
|
||||
}
|
||||
|
||||
err = rtc_read_alarm(class_dev, &alrm);
|
||||
if (err == 0) {
|
||||
seq_printf(seq, "alrm_time\t: ");
|
||||
if ((unsigned int)alrm.time.tm_hour <= 24)
|
||||
seq_printf(seq, "%02d:", alrm.time.tm_hour);
|
||||
else
|
||||
seq_printf(seq, "**:");
|
||||
if ((unsigned int)alrm.time.tm_min <= 59)
|
||||
seq_printf(seq, "%02d:", alrm.time.tm_min);
|
||||
else
|
||||
seq_printf(seq, "**:");
|
||||
if ((unsigned int)alrm.time.tm_sec <= 59)
|
||||
seq_printf(seq, "%02d\n", alrm.time.tm_sec);
|
||||
else
|
||||
seq_printf(seq, "**\n");
|
||||
|
||||
seq_printf(seq, "alrm_date\t: ");
|
||||
if ((unsigned int)alrm.time.tm_year <= 200)
|
||||
seq_printf(seq, "%04d-", alrm.time.tm_year + 1900);
|
||||
else
|
||||
seq_printf(seq, "****-");
|
||||
if ((unsigned int)alrm.time.tm_mon <= 11)
|
||||
seq_printf(seq, "%02d-", alrm.time.tm_mon + 1);
|
||||
else
|
||||
seq_printf(seq, "**-");
|
||||
if (alrm.time.tm_mday && (unsigned int)alrm.time.tm_mday <= 31)
|
||||
seq_printf(seq, "%02d\n", alrm.time.tm_mday);
|
||||
else
|
||||
seq_printf(seq, "**\n");
|
||||
seq_printf(seq, "alarm_IRQ\t: %s\n",
|
||||
alrm.enabled ? "yes" : "no");
|
||||
seq_printf(seq, "alrm_pending\t: %s\n",
|
||||
alrm.pending ? "yes" : "no");
|
||||
}
|
||||
|
||||
seq_printf(seq, "24hr\t\t: yes\n");
|
||||
|
||||
if (ops->proc)
|
||||
ops->proc(class_dev->dev, seq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtc_proc_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct class_device *class_dev = PDE(inode)->data;
|
||||
|
||||
if (!try_module_get(THIS_MODULE))
|
||||
return -ENODEV;
|
||||
|
||||
return single_open(file, rtc_proc_show, class_dev);
|
||||
}
|
||||
|
||||
static int rtc_proc_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
int res = single_release(inode, file);
|
||||
module_put(THIS_MODULE);
|
||||
return res;
|
||||
}
|
||||
|
||||
static const struct file_operations rtc_proc_fops = {
|
||||
.open = rtc_proc_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = rtc_proc_release,
|
||||
};
|
||||
|
||||
static int rtc_proc_add_device(struct class_device *class_dev,
|
||||
struct class_interface *class_intf)
|
||||
{
|
||||
mutex_lock(&rtc_lock);
|
||||
if (rtc_dev == NULL) {
|
||||
struct proc_dir_entry *ent;
|
||||
|
||||
rtc_dev = class_dev;
|
||||
|
||||
ent = create_proc_entry("driver/rtc", 0, NULL);
|
||||
if (ent) {
|
||||
struct rtc_device *rtc = to_rtc_device(class_dev);
|
||||
|
||||
ent->proc_fops = &rtc_proc_fops;
|
||||
ent->owner = rtc->owner;
|
||||
ent->data = class_dev;
|
||||
|
||||
dev_dbg(class_dev->dev, "rtc intf: proc\n");
|
||||
}
|
||||
else
|
||||
rtc_dev = NULL;
|
||||
}
|
||||
mutex_unlock(&rtc_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rtc_proc_remove_device(struct class_device *class_dev,
|
||||
struct class_interface *class_intf)
|
||||
{
|
||||
mutex_lock(&rtc_lock);
|
||||
if (rtc_dev == class_dev) {
|
||||
remove_proc_entry("driver/rtc", NULL);
|
||||
rtc_dev = NULL;
|
||||
}
|
||||
mutex_unlock(&rtc_lock);
|
||||
}
|
||||
|
||||
static struct class_interface rtc_proc_interface = {
|
||||
.add = &rtc_proc_add_device,
|
||||
.remove = &rtc_proc_remove_device,
|
||||
};
|
||||
|
||||
static int __init rtc_proc_init(void)
|
||||
{
|
||||
return rtc_interface_register(&rtc_proc_interface);
|
||||
}
|
||||
|
||||
static void __exit rtc_proc_exit(void)
|
||||
{
|
||||
class_interface_unregister(&rtc_proc_interface);
|
||||
}
|
||||
|
||||
subsys_initcall(rtc_proc_init);
|
||||
module_exit(rtc_proc_exit);
|
||||
|
||||
MODULE_AUTHOR("Alessandro Zummo <a.zummo@towertech.it>");
|
||||
MODULE_DESCRIPTION("RTC class proc interface");
|
||||
MODULE_LICENSE("GPL");
|
||||
253
drivers/rtc/rtc-rs5c348.c
Normal file
253
drivers/rtc/rtc-rs5c348.c
Normal file
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
* A SPI driver for the Ricoh RS5C348 RTC
|
||||
*
|
||||
* Copyright (C) 2006 Atsushi Nemoto <anemo@mba.ocn.ne.jp>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* The board specific init code should provide characteristics of this
|
||||
* device:
|
||||
* Mode 1 (High-Active, Shift-Then-Sample), High Avtive CS
|
||||
*/
|
||||
|
||||
#include <linux/bcd.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/rtc.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
#define DRV_VERSION "0.2"
|
||||
|
||||
#define RS5C348_REG_SECS 0
|
||||
#define RS5C348_REG_MINS 1
|
||||
#define RS5C348_REG_HOURS 2
|
||||
#define RS5C348_REG_WDAY 3
|
||||
#define RS5C348_REG_DAY 4
|
||||
#define RS5C348_REG_MONTH 5
|
||||
#define RS5C348_REG_YEAR 6
|
||||
#define RS5C348_REG_CTL1 14
|
||||
#define RS5C348_REG_CTL2 15
|
||||
|
||||
#define RS5C348_SECS_MASK 0x7f
|
||||
#define RS5C348_MINS_MASK 0x7f
|
||||
#define RS5C348_HOURS_MASK 0x3f
|
||||
#define RS5C348_WDAY_MASK 0x03
|
||||
#define RS5C348_DAY_MASK 0x3f
|
||||
#define RS5C348_MONTH_MASK 0x1f
|
||||
|
||||
#define RS5C348_BIT_PM 0x20 /* REG_HOURS */
|
||||
#define RS5C348_BIT_Y2K 0x80 /* REG_MONTH */
|
||||
#define RS5C348_BIT_24H 0x20 /* REG_CTL1 */
|
||||
#define RS5C348_BIT_XSTP 0x10 /* REG_CTL2 */
|
||||
#define RS5C348_BIT_VDET 0x40 /* REG_CTL2 */
|
||||
|
||||
#define RS5C348_CMD_W(addr) (((addr) << 4) | 0x08) /* single write */
|
||||
#define RS5C348_CMD_R(addr) (((addr) << 4) | 0x0c) /* single read */
|
||||
#define RS5C348_CMD_MW(addr) (((addr) << 4) | 0x00) /* burst write */
|
||||
#define RS5C348_CMD_MR(addr) (((addr) << 4) | 0x04) /* burst read */
|
||||
|
||||
struct rs5c348_plat_data {
|
||||
struct rtc_device *rtc;
|
||||
int rtc_24h;
|
||||
};
|
||||
|
||||
static int
|
||||
rs5c348_rtc_set_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
struct spi_device *spi = to_spi_device(dev);
|
||||
struct rs5c348_plat_data *pdata = spi->dev.platform_data;
|
||||
u8 txbuf[5+7], *txp;
|
||||
int ret;
|
||||
|
||||
/* Transfer 5 bytes before writing SEC. This gives 31us for carry. */
|
||||
txp = txbuf;
|
||||
txbuf[0] = RS5C348_CMD_R(RS5C348_REG_CTL2); /* cmd, ctl2 */
|
||||
txbuf[1] = 0; /* dummy */
|
||||
txbuf[2] = RS5C348_CMD_R(RS5C348_REG_CTL2); /* cmd, ctl2 */
|
||||
txbuf[3] = 0; /* dummy */
|
||||
txbuf[4] = RS5C348_CMD_MW(RS5C348_REG_SECS); /* cmd, sec, ... */
|
||||
txp = &txbuf[5];
|
||||
txp[RS5C348_REG_SECS] = BIN2BCD(tm->tm_sec);
|
||||
txp[RS5C348_REG_MINS] = BIN2BCD(tm->tm_min);
|
||||
if (pdata->rtc_24h) {
|
||||
txp[RS5C348_REG_HOURS] = BIN2BCD(tm->tm_hour);
|
||||
} else {
|
||||
/* hour 0 is AM12, noon is PM12 */
|
||||
txp[RS5C348_REG_HOURS] = BIN2BCD((tm->tm_hour + 11) % 12 + 1) |
|
||||
(tm->tm_hour >= 12 ? RS5C348_BIT_PM : 0);
|
||||
}
|
||||
txp[RS5C348_REG_WDAY] = BIN2BCD(tm->tm_wday);
|
||||
txp[RS5C348_REG_DAY] = BIN2BCD(tm->tm_mday);
|
||||
txp[RS5C348_REG_MONTH] = BIN2BCD(tm->tm_mon + 1) |
|
||||
(tm->tm_year >= 100 ? RS5C348_BIT_Y2K : 0);
|
||||
txp[RS5C348_REG_YEAR] = BIN2BCD(tm->tm_year % 100);
|
||||
/* write in one transfer to avoid data inconsistency */
|
||||
ret = spi_write_then_read(spi, txbuf, sizeof(txbuf), NULL, 0);
|
||||
udelay(62); /* Tcsr 62us */
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
rs5c348_rtc_read_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
struct spi_device *spi = to_spi_device(dev);
|
||||
struct rs5c348_plat_data *pdata = spi->dev.platform_data;
|
||||
u8 txbuf[5], rxbuf[7];
|
||||
int ret;
|
||||
|
||||
/* Transfer 5 byte befores reading SEC. This gives 31us for carry. */
|
||||
txbuf[0] = RS5C348_CMD_R(RS5C348_REG_CTL2); /* cmd, ctl2 */
|
||||
txbuf[1] = 0; /* dummy */
|
||||
txbuf[2] = RS5C348_CMD_R(RS5C348_REG_CTL2); /* cmd, ctl2 */
|
||||
txbuf[3] = 0; /* dummy */
|
||||
txbuf[4] = RS5C348_CMD_MR(RS5C348_REG_SECS); /* cmd, sec, ... */
|
||||
|
||||
/* read in one transfer to avoid data inconsistency */
|
||||
ret = spi_write_then_read(spi, txbuf, sizeof(txbuf),
|
||||
rxbuf, sizeof(rxbuf));
|
||||
udelay(62); /* Tcsr 62us */
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
tm->tm_sec = BCD2BIN(rxbuf[RS5C348_REG_SECS] & RS5C348_SECS_MASK);
|
||||
tm->tm_min = BCD2BIN(rxbuf[RS5C348_REG_MINS] & RS5C348_MINS_MASK);
|
||||
tm->tm_hour = BCD2BIN(rxbuf[RS5C348_REG_HOURS] & RS5C348_HOURS_MASK);
|
||||
if (!pdata->rtc_24h) {
|
||||
tm->tm_hour %= 12;
|
||||
if (rxbuf[RS5C348_REG_HOURS] & RS5C348_BIT_PM)
|
||||
tm->tm_hour += 12;
|
||||
}
|
||||
tm->tm_wday = BCD2BIN(rxbuf[RS5C348_REG_WDAY] & RS5C348_WDAY_MASK);
|
||||
tm->tm_mday = BCD2BIN(rxbuf[RS5C348_REG_DAY] & RS5C348_DAY_MASK);
|
||||
tm->tm_mon =
|
||||
BCD2BIN(rxbuf[RS5C348_REG_MONTH] & RS5C348_MONTH_MASK) - 1;
|
||||
/* year is 1900 + tm->tm_year */
|
||||
tm->tm_year = BCD2BIN(rxbuf[RS5C348_REG_YEAR]) +
|
||||
((rxbuf[RS5C348_REG_MONTH] & RS5C348_BIT_Y2K) ? 100 : 0);
|
||||
|
||||
if (rtc_valid_tm(tm) < 0) {
|
||||
dev_err(&spi->dev, "retrieved date/time is not valid.\n");
|
||||
rtc_time_to_tm(0, tm);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct rtc_class_ops rs5c348_rtc_ops = {
|
||||
.read_time = rs5c348_rtc_read_time,
|
||||
.set_time = rs5c348_rtc_set_time,
|
||||
};
|
||||
|
||||
static struct spi_driver rs5c348_driver;
|
||||
|
||||
static int __devinit rs5c348_probe(struct spi_device *spi)
|
||||
{
|
||||
int ret;
|
||||
struct rtc_device *rtc;
|
||||
struct rs5c348_plat_data *pdata;
|
||||
|
||||
pdata = kzalloc(sizeof(struct rs5c348_plat_data), GFP_KERNEL);
|
||||
if (!pdata)
|
||||
return -ENOMEM;
|
||||
spi->dev.platform_data = pdata;
|
||||
|
||||
/* Check D7 of SECOND register */
|
||||
ret = spi_w8r8(spi, RS5C348_CMD_R(RS5C348_REG_SECS));
|
||||
if (ret < 0 || (ret & 0x80)) {
|
||||
dev_err(&spi->dev, "not found.\n");
|
||||
goto kfree_exit;
|
||||
}
|
||||
|
||||
dev_info(&spi->dev, "chip found, driver version " DRV_VERSION "\n");
|
||||
dev_info(&spi->dev, "spiclk %u KHz.\n",
|
||||
(spi->max_speed_hz + 500) / 1000);
|
||||
|
||||
/* turn RTC on if it was not on */
|
||||
ret = spi_w8r8(spi, RS5C348_CMD_R(RS5C348_REG_CTL2));
|
||||
if (ret < 0)
|
||||
goto kfree_exit;
|
||||
if (ret & (RS5C348_BIT_XSTP | RS5C348_BIT_VDET)) {
|
||||
u8 buf[2];
|
||||
struct rtc_time tm;
|
||||
if (ret & RS5C348_BIT_VDET)
|
||||
dev_warn(&spi->dev, "voltage-low detected.\n");
|
||||
if (ret & RS5C348_BIT_XSTP)
|
||||
dev_warn(&spi->dev, "oscillator-stop detected.\n");
|
||||
rtc_time_to_tm(0, &tm); /* 1970/1/1 */
|
||||
ret = rs5c348_rtc_set_time(&spi->dev, &tm);
|
||||
if (ret < 0)
|
||||
goto kfree_exit;
|
||||
buf[0] = RS5C348_CMD_W(RS5C348_REG_CTL2);
|
||||
buf[1] = 0;
|
||||
ret = spi_write_then_read(spi, buf, sizeof(buf), NULL, 0);
|
||||
if (ret < 0)
|
||||
goto kfree_exit;
|
||||
}
|
||||
|
||||
ret = spi_w8r8(spi, RS5C348_CMD_R(RS5C348_REG_CTL1));
|
||||
if (ret < 0)
|
||||
goto kfree_exit;
|
||||
if (ret & RS5C348_BIT_24H)
|
||||
pdata->rtc_24h = 1;
|
||||
|
||||
rtc = rtc_device_register(rs5c348_driver.driver.name, &spi->dev,
|
||||
&rs5c348_rtc_ops, THIS_MODULE);
|
||||
|
||||
if (IS_ERR(rtc)) {
|
||||
ret = PTR_ERR(rtc);
|
||||
goto kfree_exit;
|
||||
}
|
||||
|
||||
pdata->rtc = rtc;
|
||||
|
||||
return 0;
|
||||
kfree_exit:
|
||||
kfree(pdata);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devexit rs5c348_remove(struct spi_device *spi)
|
||||
{
|
||||
struct rs5c348_plat_data *pdata = spi->dev.platform_data;
|
||||
struct rtc_device *rtc = pdata->rtc;
|
||||
|
||||
if (rtc)
|
||||
rtc_device_unregister(rtc);
|
||||
kfree(pdata);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct spi_driver rs5c348_driver = {
|
||||
.driver = {
|
||||
.name = "rs5c348",
|
||||
.bus = &spi_bus_type,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = rs5c348_probe,
|
||||
.remove = __devexit_p(rs5c348_remove),
|
||||
};
|
||||
|
||||
static __init int rs5c348_init(void)
|
||||
{
|
||||
return spi_register_driver(&rs5c348_driver);
|
||||
}
|
||||
|
||||
static __exit void rs5c348_exit(void)
|
||||
{
|
||||
spi_unregister_driver(&rs5c348_driver);
|
||||
}
|
||||
|
||||
module_init(rs5c348_init);
|
||||
module_exit(rs5c348_exit);
|
||||
|
||||
MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>");
|
||||
MODULE_DESCRIPTION("Ricoh RS5C348 RTC driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_VERSION(DRV_VERSION);
|
||||
701
drivers/rtc/rtc-rs5c372.c
Normal file
701
drivers/rtc/rtc-rs5c372.c
Normal file
@@ -0,0 +1,701 @@
|
||||
/*
|
||||
* An I2C driver for Ricoh RS5C372 and RV5C38[67] RTCs
|
||||
*
|
||||
* Copyright (C) 2005 Pavel Mironchik <pmironchik@optifacio.net>
|
||||
* Copyright (C) 2006 Tower Technologies
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/rtc.h>
|
||||
#include <linux/bcd.h>
|
||||
|
||||
#define DRV_VERSION "0.4"
|
||||
|
||||
/* Addresses to scan */
|
||||
static unsigned short normal_i2c[] = { /* 0x32,*/ I2C_CLIENT_END };
|
||||
|
||||
/* Insmod parameters */
|
||||
I2C_CLIENT_INSMOD;
|
||||
|
||||
|
||||
/*
|
||||
* Ricoh has a family of I2C based RTCs, which differ only slightly from
|
||||
* each other. Differences center on pinout (e.g. how many interrupts,
|
||||
* output clock, etc) and how the control registers are used. The '372
|
||||
* is significant only because that's the one this driver first supported.
|
||||
*/
|
||||
#define RS5C372_REG_SECS 0
|
||||
#define RS5C372_REG_MINS 1
|
||||
#define RS5C372_REG_HOURS 2
|
||||
#define RS5C372_REG_WDAY 3
|
||||
#define RS5C372_REG_DAY 4
|
||||
#define RS5C372_REG_MONTH 5
|
||||
#define RS5C372_REG_YEAR 6
|
||||
#define RS5C372_REG_TRIM 7
|
||||
# define RS5C372_TRIM_XSL 0x80
|
||||
# define RS5C372_TRIM_MASK 0x7F
|
||||
|
||||
#define RS5C_REG_ALARM_A_MIN 8 /* or ALARM_W */
|
||||
#define RS5C_REG_ALARM_A_HOURS 9
|
||||
#define RS5C_REG_ALARM_A_WDAY 10
|
||||
|
||||
#define RS5C_REG_ALARM_B_MIN 11 /* or ALARM_D */
|
||||
#define RS5C_REG_ALARM_B_HOURS 12
|
||||
#define RS5C_REG_ALARM_B_WDAY 13 /* (ALARM_B only) */
|
||||
|
||||
#define RS5C_REG_CTRL1 14
|
||||
# define RS5C_CTRL1_AALE (1 << 7) /* or WALE */
|
||||
# define RS5C_CTRL1_BALE (1 << 6) /* or DALE */
|
||||
# define RV5C387_CTRL1_24 (1 << 5)
|
||||
# define RS5C372A_CTRL1_SL1 (1 << 5)
|
||||
# define RS5C_CTRL1_CT_MASK (7 << 0)
|
||||
# define RS5C_CTRL1_CT0 (0 << 0) /* no periodic irq */
|
||||
# define RS5C_CTRL1_CT4 (4 << 0) /* 1 Hz level irq */
|
||||
#define RS5C_REG_CTRL2 15
|
||||
# define RS5C372_CTRL2_24 (1 << 5)
|
||||
# define RS5C_CTRL2_XSTP (1 << 4)
|
||||
# define RS5C_CTRL2_CTFG (1 << 2)
|
||||
# define RS5C_CTRL2_AAFG (1 << 1) /* or WAFG */
|
||||
# define RS5C_CTRL2_BAFG (1 << 0) /* or DAFG */
|
||||
|
||||
|
||||
/* to read (style 1) or write registers starting at R */
|
||||
#define RS5C_ADDR(R) (((R) << 4) | 0)
|
||||
|
||||
|
||||
enum rtc_type {
|
||||
rtc_undef = 0,
|
||||
rtc_rs5c372a,
|
||||
rtc_rs5c372b,
|
||||
rtc_rv5c386,
|
||||
rtc_rv5c387a,
|
||||
};
|
||||
|
||||
/* REVISIT: this assumes that:
|
||||
* - we're in the 21st century, so it's safe to ignore the century
|
||||
* bit for rv5c38[67] (REG_MONTH bit 7);
|
||||
* - we should use ALARM_A not ALARM_B (may be wrong on some boards)
|
||||
*/
|
||||
struct rs5c372 {
|
||||
struct i2c_client *client;
|
||||
struct rtc_device *rtc;
|
||||
enum rtc_type type;
|
||||
unsigned time24:1;
|
||||
unsigned has_irq:1;
|
||||
char buf[17];
|
||||
char *regs;
|
||||
|
||||
/* on conversion to a "new style" i2c driver, this vanishes */
|
||||
struct i2c_client dev;
|
||||
};
|
||||
|
||||
static int rs5c_get_regs(struct rs5c372 *rs5c)
|
||||
{
|
||||
struct i2c_client *client = rs5c->client;
|
||||
struct i2c_msg msgs[] = {
|
||||
{ client->addr, I2C_M_RD, sizeof rs5c->buf, rs5c->buf },
|
||||
};
|
||||
|
||||
/* This implements the third reading method from the datasheet, using
|
||||
* an internal address that's reset after each transaction (by STOP)
|
||||
* to 0x0f ... so we read extra registers, and skip the first one.
|
||||
*
|
||||
* The first method doesn't work with the iop3xx adapter driver, on at
|
||||
* least 80219 chips; this works around that bug.
|
||||
*/
|
||||
if ((i2c_transfer(client->adapter, msgs, 1)) != 1) {
|
||||
pr_debug("%s: can't read registers\n", rs5c->rtc->name);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
dev_dbg(&client->dev,
|
||||
"%02x %02x %02x (%02x) %02x %02x %02x (%02x), "
|
||||
"%02x %02x %02x, %02x %02x %02x; %02x %02x\n",
|
||||
rs5c->regs[0], rs5c->regs[1], rs5c->regs[2], rs5c->regs[3],
|
||||
rs5c->regs[4], rs5c->regs[5], rs5c->regs[6], rs5c->regs[7],
|
||||
rs5c->regs[8], rs5c->regs[9], rs5c->regs[10], rs5c->regs[11],
|
||||
rs5c->regs[12], rs5c->regs[13], rs5c->regs[14], rs5c->regs[15]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned rs5c_reg2hr(struct rs5c372 *rs5c, unsigned reg)
|
||||
{
|
||||
unsigned hour;
|
||||
|
||||
if (rs5c->time24)
|
||||
return BCD2BIN(reg & 0x3f);
|
||||
|
||||
hour = BCD2BIN(reg & 0x1f);
|
||||
if (hour == 12)
|
||||
hour = 0;
|
||||
if (reg & 0x20)
|
||||
hour += 12;
|
||||
return hour;
|
||||
}
|
||||
|
||||
static unsigned rs5c_hr2reg(struct rs5c372 *rs5c, unsigned hour)
|
||||
{
|
||||
if (rs5c->time24)
|
||||
return BIN2BCD(hour);
|
||||
|
||||
if (hour > 12)
|
||||
return 0x20 | BIN2BCD(hour - 12);
|
||||
if (hour == 12)
|
||||
return 0x20 | BIN2BCD(12);
|
||||
if (hour == 0)
|
||||
return BIN2BCD(12);
|
||||
return BIN2BCD(hour);
|
||||
}
|
||||
|
||||
static int rs5c372_get_datetime(struct i2c_client *client, struct rtc_time *tm)
|
||||
{
|
||||
struct rs5c372 *rs5c = i2c_get_clientdata(client);
|
||||
int status = rs5c_get_regs(rs5c);
|
||||
|
||||
if (status < 0)
|
||||
return status;
|
||||
|
||||
tm->tm_sec = BCD2BIN(rs5c->regs[RS5C372_REG_SECS] & 0x7f);
|
||||
tm->tm_min = BCD2BIN(rs5c->regs[RS5C372_REG_MINS] & 0x7f);
|
||||
tm->tm_hour = rs5c_reg2hr(rs5c, rs5c->regs[RS5C372_REG_HOURS]);
|
||||
|
||||
tm->tm_wday = BCD2BIN(rs5c->regs[RS5C372_REG_WDAY] & 0x07);
|
||||
tm->tm_mday = BCD2BIN(rs5c->regs[RS5C372_REG_DAY] & 0x3f);
|
||||
|
||||
/* tm->tm_mon is zero-based */
|
||||
tm->tm_mon = BCD2BIN(rs5c->regs[RS5C372_REG_MONTH] & 0x1f) - 1;
|
||||
|
||||
/* year is 1900 + tm->tm_year */
|
||||
tm->tm_year = BCD2BIN(rs5c->regs[RS5C372_REG_YEAR]) + 100;
|
||||
|
||||
dev_dbg(&client->dev, "%s: tm is secs=%d, mins=%d, hours=%d, "
|
||||
"mday=%d, mon=%d, year=%d, wday=%d\n",
|
||||
__FUNCTION__,
|
||||
tm->tm_sec, tm->tm_min, tm->tm_hour,
|
||||
tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday);
|
||||
|
||||
/* rtc might need initialization */
|
||||
return rtc_valid_tm(tm);
|
||||
}
|
||||
|
||||
static int rs5c372_set_datetime(struct i2c_client *client, struct rtc_time *tm)
|
||||
{
|
||||
struct rs5c372 *rs5c = i2c_get_clientdata(client);
|
||||
unsigned char buf[8];
|
||||
|
||||
dev_dbg(&client->dev, "%s: tm is secs=%d, mins=%d, hours=%d "
|
||||
"mday=%d, mon=%d, year=%d, wday=%d\n",
|
||||
__FUNCTION__,
|
||||
tm->tm_sec, tm->tm_min, tm->tm_hour,
|
||||
tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday);
|
||||
|
||||
buf[0] = RS5C_ADDR(RS5C372_REG_SECS);
|
||||
buf[1] = BIN2BCD(tm->tm_sec);
|
||||
buf[2] = BIN2BCD(tm->tm_min);
|
||||
buf[3] = rs5c_hr2reg(rs5c, tm->tm_hour);
|
||||
buf[4] = BIN2BCD(tm->tm_wday);
|
||||
buf[5] = BIN2BCD(tm->tm_mday);
|
||||
buf[6] = BIN2BCD(tm->tm_mon + 1);
|
||||
buf[7] = BIN2BCD(tm->tm_year - 100);
|
||||
|
||||
if ((i2c_master_send(client, buf, 8)) != 8) {
|
||||
dev_err(&client->dev, "%s: write error\n", __FUNCTION__);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_RTC_INTF_PROC) || defined(CONFIG_RTC_INTF_PROC_MODULE)
|
||||
#define NEED_TRIM
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_RTC_INTF_SYSFS) || defined(CONFIG_RTC_INTF_SYSFS_MODULE)
|
||||
#define NEED_TRIM
|
||||
#endif
|
||||
|
||||
#ifdef NEED_TRIM
|
||||
static int rs5c372_get_trim(struct i2c_client *client, int *osc, int *trim)
|
||||
{
|
||||
struct rs5c372 *rs5c372 = i2c_get_clientdata(client);
|
||||
u8 tmp = rs5c372->regs[RS5C372_REG_TRIM];
|
||||
|
||||
if (osc)
|
||||
*osc = (tmp & RS5C372_TRIM_XSL) ? 32000 : 32768;
|
||||
|
||||
if (trim) {
|
||||
dev_dbg(&client->dev, "%s: raw trim=%x\n", __FUNCTION__, tmp);
|
||||
tmp &= RS5C372_TRIM_MASK;
|
||||
if (tmp & 0x3e) {
|
||||
int t = tmp & 0x3f;
|
||||
|
||||
if (tmp & 0x40)
|
||||
t = (~t | (s8)0xc0) + 1;
|
||||
else
|
||||
t = t - 1;
|
||||
|
||||
tmp = t * 2;
|
||||
} else
|
||||
tmp = 0;
|
||||
*trim = tmp;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int rs5c372_rtc_read_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
return rs5c372_get_datetime(to_i2c_client(dev), tm);
|
||||
}
|
||||
|
||||
static int rs5c372_rtc_set_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
return rs5c372_set_datetime(to_i2c_client(dev), tm);
|
||||
}
|
||||
|
||||
#if defined(CONFIG_RTC_INTF_DEV) || defined(CONFIG_RTC_INTF_DEV_MODULE)
|
||||
|
||||
static int
|
||||
rs5c_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct rs5c372 *rs5c = i2c_get_clientdata(client);
|
||||
unsigned char buf[2];
|
||||
int status;
|
||||
|
||||
buf[1] = rs5c->regs[RS5C_REG_CTRL1];
|
||||
switch (cmd) {
|
||||
case RTC_UIE_OFF:
|
||||
case RTC_UIE_ON:
|
||||
/* some 327a modes use a different IRQ pin for 1Hz irqs */
|
||||
if (rs5c->type == rtc_rs5c372a
|
||||
&& (buf[1] & RS5C372A_CTRL1_SL1))
|
||||
return -ENOIOCTLCMD;
|
||||
case RTC_AIE_OFF:
|
||||
case RTC_AIE_ON:
|
||||
/* these irq management calls only make sense for chips
|
||||
* which are wired up to an IRQ.
|
||||
*/
|
||||
if (!rs5c->has_irq)
|
||||
return -ENOIOCTLCMD;
|
||||
break;
|
||||
default:
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
|
||||
status = rs5c_get_regs(rs5c);
|
||||
if (status < 0)
|
||||
return status;
|
||||
|
||||
buf[0] = RS5C_ADDR(RS5C_REG_CTRL1);
|
||||
switch (cmd) {
|
||||
case RTC_AIE_OFF: /* alarm off */
|
||||
buf[1] &= ~RS5C_CTRL1_AALE;
|
||||
break;
|
||||
case RTC_AIE_ON: /* alarm on */
|
||||
buf[1] |= RS5C_CTRL1_AALE;
|
||||
break;
|
||||
case RTC_UIE_OFF: /* update off */
|
||||
buf[1] &= ~RS5C_CTRL1_CT_MASK;
|
||||
break;
|
||||
case RTC_UIE_ON: /* update on */
|
||||
buf[1] &= ~RS5C_CTRL1_CT_MASK;
|
||||
buf[1] |= RS5C_CTRL1_CT4;
|
||||
break;
|
||||
}
|
||||
if ((i2c_master_send(client, buf, 2)) != 2) {
|
||||
printk(KERN_WARNING "%s: can't update alarm\n",
|
||||
rs5c->rtc->name);
|
||||
status = -EIO;
|
||||
} else
|
||||
rs5c->regs[RS5C_REG_CTRL1] = buf[1];
|
||||
return status;
|
||||
}
|
||||
|
||||
#else
|
||||
#define rs5c_rtc_ioctl NULL
|
||||
#endif
|
||||
|
||||
|
||||
/* NOTE: Since RTC_WKALM_{RD,SET} were originally defined for EFI,
|
||||
* which only exposes a polled programming interface; and since
|
||||
* these calls map directly to those EFI requests; we don't demand
|
||||
* we have an IRQ for this chip when we go through this API.
|
||||
*
|
||||
* The older x86_pc derived RTC_ALM_{READ,SET} calls require irqs
|
||||
* though, managed through RTC_AIE_{ON,OFF} requests.
|
||||
*/
|
||||
|
||||
static int rs5c_read_alarm(struct device *dev, struct rtc_wkalrm *t)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct rs5c372 *rs5c = i2c_get_clientdata(client);
|
||||
int status;
|
||||
|
||||
status = rs5c_get_regs(rs5c);
|
||||
if (status < 0)
|
||||
return status;
|
||||
|
||||
/* report alarm time */
|
||||
t->time.tm_sec = 0;
|
||||
t->time.tm_min = BCD2BIN(rs5c->regs[RS5C_REG_ALARM_A_MIN] & 0x7f);
|
||||
t->time.tm_hour = rs5c_reg2hr(rs5c, rs5c->regs[RS5C_REG_ALARM_A_HOURS]);
|
||||
t->time.tm_mday = -1;
|
||||
t->time.tm_mon = -1;
|
||||
t->time.tm_year = -1;
|
||||
t->time.tm_wday = -1;
|
||||
t->time.tm_yday = -1;
|
||||
t->time.tm_isdst = -1;
|
||||
|
||||
/* ... and status */
|
||||
t->enabled = !!(rs5c->regs[RS5C_REG_CTRL1] & RS5C_CTRL1_AALE);
|
||||
t->pending = !!(rs5c->regs[RS5C_REG_CTRL2] & RS5C_CTRL2_AAFG);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rs5c_set_alarm(struct device *dev, struct rtc_wkalrm *t)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct rs5c372 *rs5c = i2c_get_clientdata(client);
|
||||
int status;
|
||||
unsigned char buf[4];
|
||||
|
||||
/* only handle up to 24 hours in the future, like RTC_ALM_SET */
|
||||
if (t->time.tm_mday != -1
|
||||
|| t->time.tm_mon != -1
|
||||
|| t->time.tm_year != -1)
|
||||
return -EINVAL;
|
||||
|
||||
/* REVISIT: round up tm_sec */
|
||||
|
||||
/* if needed, disable irq (clears pending status) */
|
||||
status = rs5c_get_regs(rs5c);
|
||||
if (status < 0)
|
||||
return status;
|
||||
if (rs5c->regs[RS5C_REG_CTRL1] & RS5C_CTRL1_AALE) {
|
||||
buf[0] = RS5C_ADDR(RS5C_REG_CTRL1);
|
||||
buf[1] = rs5c->regs[RS5C_REG_CTRL1] & ~RS5C_CTRL1_AALE;
|
||||
if (i2c_master_send(client, buf, 2) != 2) {
|
||||
pr_debug("%s: can't disable alarm\n", rs5c->rtc->name);
|
||||
return -EIO;
|
||||
}
|
||||
rs5c->regs[RS5C_REG_CTRL1] = buf[1];
|
||||
}
|
||||
|
||||
/* set alarm */
|
||||
buf[0] = RS5C_ADDR(RS5C_REG_ALARM_A_MIN);
|
||||
buf[1] = BIN2BCD(t->time.tm_min);
|
||||
buf[2] = rs5c_hr2reg(rs5c, t->time.tm_hour);
|
||||
buf[3] = 0x7f; /* any/all days */
|
||||
if ((i2c_master_send(client, buf, 4)) != 4) {
|
||||
pr_debug("%s: can't set alarm time\n", rs5c->rtc->name);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* ... and maybe enable its irq */
|
||||
if (t->enabled) {
|
||||
buf[0] = RS5C_ADDR(RS5C_REG_CTRL1);
|
||||
buf[1] = rs5c->regs[RS5C_REG_CTRL1] | RS5C_CTRL1_AALE;
|
||||
if ((i2c_master_send(client, buf, 2)) != 2)
|
||||
printk(KERN_WARNING "%s: can't enable alarm\n",
|
||||
rs5c->rtc->name);
|
||||
rs5c->regs[RS5C_REG_CTRL1] = buf[1];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_RTC_INTF_PROC) || defined(CONFIG_RTC_INTF_PROC_MODULE)
|
||||
|
||||
static int rs5c372_rtc_proc(struct device *dev, struct seq_file *seq)
|
||||
{
|
||||
int err, osc, trim;
|
||||
|
||||
err = rs5c372_get_trim(to_i2c_client(dev), &osc, &trim);
|
||||
if (err == 0) {
|
||||
seq_printf(seq, "crystal\t\t: %d.%03d KHz\n",
|
||||
osc / 1000, osc % 1000);
|
||||
seq_printf(seq, "trim\t\t: %d\n", trim);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
#define rs5c372_rtc_proc NULL
|
||||
#endif
|
||||
|
||||
static const struct rtc_class_ops rs5c372_rtc_ops = {
|
||||
.proc = rs5c372_rtc_proc,
|
||||
.ioctl = rs5c_rtc_ioctl,
|
||||
.read_time = rs5c372_rtc_read_time,
|
||||
.set_time = rs5c372_rtc_set_time,
|
||||
.read_alarm = rs5c_read_alarm,
|
||||
.set_alarm = rs5c_set_alarm,
|
||||
};
|
||||
|
||||
#if defined(CONFIG_RTC_INTF_SYSFS) || defined(CONFIG_RTC_INTF_SYSFS_MODULE)
|
||||
|
||||
static ssize_t rs5c372_sysfs_show_trim(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
int err, trim;
|
||||
|
||||
err = rs5c372_get_trim(to_i2c_client(dev), NULL, &trim);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return sprintf(buf, "%d\n", trim);
|
||||
}
|
||||
static DEVICE_ATTR(trim, S_IRUGO, rs5c372_sysfs_show_trim, NULL);
|
||||
|
||||
static ssize_t rs5c372_sysfs_show_osc(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
int err, osc;
|
||||
|
||||
err = rs5c372_get_trim(to_i2c_client(dev), &osc, NULL);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return sprintf(buf, "%d.%03d KHz\n", osc / 1000, osc % 1000);
|
||||
}
|
||||
static DEVICE_ATTR(osc, S_IRUGO, rs5c372_sysfs_show_osc, NULL);
|
||||
|
||||
static int rs5c_sysfs_register(struct device *dev)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = device_create_file(dev, &dev_attr_trim);
|
||||
if (err)
|
||||
return err;
|
||||
err = device_create_file(dev, &dev_attr_osc);
|
||||
if (err)
|
||||
device_remove_file(dev, &dev_attr_trim);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
#else
|
||||
static int rs5c_sysfs_register(struct device *dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif /* SYSFS */
|
||||
|
||||
static struct i2c_driver rs5c372_driver;
|
||||
|
||||
static int rs5c372_probe(struct i2c_adapter *adapter, int address, int kind)
|
||||
{
|
||||
int err = 0;
|
||||
struct i2c_client *client;
|
||||
struct rs5c372 *rs5c372;
|
||||
struct rtc_time tm;
|
||||
|
||||
dev_dbg(&adapter->dev, "%s\n", __FUNCTION__);
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) {
|
||||
err = -ENODEV;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!(rs5c372 = kzalloc(sizeof(struct rs5c372), GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* we read registers 0x0f then 0x00-0x0f; skip the first one */
|
||||
rs5c372->regs=&rs5c372->buf[1];
|
||||
|
||||
/* On conversion to a "new style" i2c driver, we'll be handed
|
||||
* the i2c_client (we won't create it)
|
||||
*/
|
||||
client = &rs5c372->dev;
|
||||
rs5c372->client = client;
|
||||
|
||||
/* I2C client */
|
||||
client->addr = address;
|
||||
client->driver = &rs5c372_driver;
|
||||
client->adapter = adapter;
|
||||
|
||||
strlcpy(client->name, rs5c372_driver.driver.name, I2C_NAME_SIZE);
|
||||
|
||||
i2c_set_clientdata(client, rs5c372);
|
||||
|
||||
/* Inform the i2c layer */
|
||||
if ((err = i2c_attach_client(client)))
|
||||
goto exit_kfree;
|
||||
|
||||
err = rs5c_get_regs(rs5c372);
|
||||
if (err < 0)
|
||||
goto exit_detach;
|
||||
|
||||
/* For "new style" drivers, irq is in i2c_client and chip type
|
||||
* info comes from i2c_client.dev.platform_data. Meanwhile:
|
||||
*
|
||||
* STICK BOARD-SPECIFIC SETUP CODE RIGHT HERE
|
||||
*/
|
||||
if (rs5c372->type == rtc_undef) {
|
||||
rs5c372->type = rtc_rs5c372b;
|
||||
dev_warn(&client->dev, "assuming rs5c372b\n");
|
||||
}
|
||||
|
||||
/* clock may be set for am/pm or 24 hr time */
|
||||
switch (rs5c372->type) {
|
||||
case rtc_rs5c372a:
|
||||
case rtc_rs5c372b:
|
||||
/* alarm uses ALARM_A; and nINTRA on 372a, nINTR on 372b.
|
||||
* so does periodic irq, except some 327a modes.
|
||||
*/
|
||||
if (rs5c372->regs[RS5C_REG_CTRL2] & RS5C372_CTRL2_24)
|
||||
rs5c372->time24 = 1;
|
||||
break;
|
||||
case rtc_rv5c386:
|
||||
case rtc_rv5c387a:
|
||||
if (rs5c372->regs[RS5C_REG_CTRL1] & RV5C387_CTRL1_24)
|
||||
rs5c372->time24 = 1;
|
||||
/* alarm uses ALARM_W; and nINTRB for alarm and periodic
|
||||
* irq, on both 386 and 387
|
||||
*/
|
||||
break;
|
||||
default:
|
||||
dev_err(&client->dev, "unknown RTC type\n");
|
||||
goto exit_detach;
|
||||
}
|
||||
|
||||
/* if the oscillator lost power and no other software (like
|
||||
* the bootloader) set it up, do it here.
|
||||
*/
|
||||
if (rs5c372->regs[RS5C_REG_CTRL2] & RS5C_CTRL2_XSTP) {
|
||||
unsigned char buf[3];
|
||||
|
||||
rs5c372->regs[RS5C_REG_CTRL2] &= ~RS5C_CTRL2_XSTP;
|
||||
|
||||
buf[0] = RS5C_ADDR(RS5C_REG_CTRL1);
|
||||
buf[1] = rs5c372->regs[RS5C_REG_CTRL1];
|
||||
buf[2] = rs5c372->regs[RS5C_REG_CTRL2];
|
||||
|
||||
/* use 24hr mode */
|
||||
switch (rs5c372->type) {
|
||||
case rtc_rs5c372a:
|
||||
case rtc_rs5c372b:
|
||||
buf[2] |= RS5C372_CTRL2_24;
|
||||
rs5c372->time24 = 1;
|
||||
break;
|
||||
case rtc_rv5c386:
|
||||
case rtc_rv5c387a:
|
||||
buf[1] |= RV5C387_CTRL1_24;
|
||||
rs5c372->time24 = 1;
|
||||
break;
|
||||
default:
|
||||
/* impossible */
|
||||
break;
|
||||
}
|
||||
|
||||
if ((i2c_master_send(client, buf, 3)) != 3) {
|
||||
dev_err(&client->dev, "setup error\n");
|
||||
goto exit_detach;
|
||||
}
|
||||
rs5c372->regs[RS5C_REG_CTRL1] = buf[1];
|
||||
rs5c372->regs[RS5C_REG_CTRL2] = buf[2];
|
||||
}
|
||||
|
||||
if (rs5c372_get_datetime(client, &tm) < 0)
|
||||
dev_warn(&client->dev, "clock needs to be set\n");
|
||||
|
||||
dev_info(&client->dev, "%s found, %s, driver version " DRV_VERSION "\n",
|
||||
({ char *s; switch (rs5c372->type) {
|
||||
case rtc_rs5c372a: s = "rs5c372a"; break;
|
||||
case rtc_rs5c372b: s = "rs5c372b"; break;
|
||||
case rtc_rv5c386: s = "rv5c386"; break;
|
||||
case rtc_rv5c387a: s = "rv5c387a"; break;
|
||||
default: s = "chip"; break;
|
||||
}; s;}),
|
||||
rs5c372->time24 ? "24hr" : "am/pm"
|
||||
);
|
||||
|
||||
/* FIXME when client->irq exists, use it to register alarm irq */
|
||||
|
||||
rs5c372->rtc = rtc_device_register(rs5c372_driver.driver.name,
|
||||
&client->dev, &rs5c372_rtc_ops, THIS_MODULE);
|
||||
|
||||
if (IS_ERR(rs5c372->rtc)) {
|
||||
err = PTR_ERR(rs5c372->rtc);
|
||||
goto exit_detach;
|
||||
}
|
||||
|
||||
err = rs5c_sysfs_register(&client->dev);
|
||||
if (err)
|
||||
goto exit_devreg;
|
||||
|
||||
return 0;
|
||||
|
||||
exit_devreg:
|
||||
rtc_device_unregister(rs5c372->rtc);
|
||||
|
||||
exit_detach:
|
||||
i2c_detach_client(client);
|
||||
|
||||
exit_kfree:
|
||||
kfree(rs5c372);
|
||||
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int rs5c372_attach(struct i2c_adapter *adapter)
|
||||
{
|
||||
return i2c_probe(adapter, &addr_data, rs5c372_probe);
|
||||
}
|
||||
|
||||
static int rs5c372_detach(struct i2c_client *client)
|
||||
{
|
||||
int err;
|
||||
struct rs5c372 *rs5c372 = i2c_get_clientdata(client);
|
||||
|
||||
if (rs5c372->rtc)
|
||||
rtc_device_unregister(rs5c372->rtc);
|
||||
|
||||
/* REVISIT properly destroy the sysfs files ... */
|
||||
|
||||
if ((err = i2c_detach_client(client)))
|
||||
return err;
|
||||
|
||||
kfree(rs5c372);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct i2c_driver rs5c372_driver = {
|
||||
.driver = {
|
||||
.name = "rtc-rs5c372",
|
||||
},
|
||||
.attach_adapter = &rs5c372_attach,
|
||||
.detach_client = &rs5c372_detach,
|
||||
};
|
||||
|
||||
static __init int rs5c372_init(void)
|
||||
{
|
||||
return i2c_add_driver(&rs5c372_driver);
|
||||
}
|
||||
|
||||
static __exit void rs5c372_exit(void)
|
||||
{
|
||||
i2c_del_driver(&rs5c372_driver);
|
||||
}
|
||||
|
||||
module_init(rs5c372_init);
|
||||
module_exit(rs5c372_exit);
|
||||
|
||||
MODULE_AUTHOR(
|
||||
"Pavel Mironchik <pmironchik@optifacio.net>, "
|
||||
"Alessandro Zummo <a.zummo@towertech.it>");
|
||||
MODULE_DESCRIPTION("Ricoh RS5C372 RTC driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_VERSION(DRV_VERSION);
|
||||
769
drivers/rtc/rtc-s3c.c
Normal file
769
drivers/rtc/rtc-s3c.c
Normal file
@@ -0,0 +1,769 @@
|
||||
/* drivers/rtc/rtc-s3c.c
|
||||
*
|
||||
* Copyright (c) 2004,2006 Simtec Electronics
|
||||
* Ben Dooks, <ben@simtec.co.uk>
|
||||
* http://armlinux.simtec.co.uk/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* S3C2410/S3C2440/S3C24XX Internal RTC Driver
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/rtc.h>
|
||||
#include <linux/bcd.h>
|
||||
#include <linux/clk.h>
|
||||
|
||||
#include <asm/hardware.h>
|
||||
#include <asm/uaccess.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/irq.h>
|
||||
#include <asm/rtc.h>
|
||||
|
||||
#include <asm/mach/time.h>
|
||||
|
||||
#include <asm/arch/regs-rtc.h>
|
||||
|
||||
/* I have yet to find an S3C implementation with more than one
|
||||
* of these rtc blocks in */
|
||||
|
||||
static struct resource *s3c_rtc_mem;
|
||||
|
||||
static void __iomem *s3c_rtc_base;
|
||||
static int s3c_rtc_alarmno = NO_IRQ;
|
||||
static int s3c_rtc_tickno = NO_IRQ;
|
||||
static int s3c_rtc_freq = 1;
|
||||
|
||||
static DEFINE_SPINLOCK(s3c_rtc_pie_lock);
|
||||
static unsigned int tick_count;
|
||||
|
||||
/* IRQ Handlers */
|
||||
|
||||
/* Qisda, ShiYong Lin, 2009/07/22, Implement function for RTC tick service {*/
|
||||
static unsigned long uiResolution = 0;
|
||||
extern void rtc_tick_keypad_message_to_ap (void);
|
||||
/* Qisda, ShiYong Lin, 2009/07/22, Implement function for RTC tick service }*/
|
||||
|
||||
static irqreturn_t s3c_rtc_alarmirq(int irq, void *id)
|
||||
{
|
||||
struct rtc_device *rdev = id;
|
||||
|
||||
rtc_update_irq(&rdev->class_dev, 1, RTC_AF | RTC_IRQF);
|
||||
|
||||
#if defined(CONFIG_CPU_S3C6400) || defined(CONFIG_CPU_S3C6410)
|
||||
writeb(S3C_INTP_ALM, s3c_rtc_base + S3C_INTP);
|
||||
#endif
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t s3c_rtc_tickirq(int irq, void *id)
|
||||
{
|
||||
struct rtc_device *rdev = id;
|
||||
|
||||
rtc_tick_keypad_message_to_ap();
|
||||
|
||||
rtc_update_irq(&rdev->class_dev, tick_count++, RTC_PF | RTC_IRQF);
|
||||
|
||||
#if defined(CONFIG_CPU_S3C6400) || defined(CONFIG_CPU_S3C6410)
|
||||
writeb(S3C_INTP_TIC, s3c_rtc_base + S3C_INTP);
|
||||
#endif
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/* Update control registers */
|
||||
static void s3c_rtc_setaie(int to)
|
||||
{
|
||||
unsigned int tmp;
|
||||
|
||||
pr_debug("%s: aie=%d\n", __FUNCTION__, to);
|
||||
|
||||
tmp = readb(s3c_rtc_base + S3C2410_RTCALM) & ~S3C2410_RTCALM_ALMEN;
|
||||
|
||||
if (to)
|
||||
tmp |= S3C2410_RTCALM_ALMEN;
|
||||
|
||||
writeb(tmp, s3c_rtc_base + S3C2410_RTCALM);
|
||||
|
||||
/* Qisda, ShiYong Lin, 2009/07/18, RTC debug message{*/
|
||||
#ifdef RTC_DEBUG
|
||||
tmp = readb(s3c_rtc_base + S3C2410_RTCALM);
|
||||
printk("s3c_rtc_setaie after read S3C2410_RTCALM = 0x%x\n", tmp);
|
||||
tmp = readb(s3c_rtc_base + S3C2410_TICNT);
|
||||
printk("s3c_rtc_setaie after read S3C2410_TICNT = 0x%x\n", tmp);
|
||||
tmp = readw(s3c_rtc_base + S3C2410_RTCCON);
|
||||
printk("s3c_rtc_setaie after read S3C2410_RTCCON = 0x%x\n", tmp);
|
||||
tmp = readl(s3c_rtc_base + 0x90);
|
||||
printk("s3c_rtc_setaie after read S3C2410_TICKCNT = 0x%x\n", tmp);
|
||||
#endif
|
||||
/* Qisda, ShiYong Lin, 2009/07/18, RTC debug message}*/
|
||||
|
||||
#if !defined(CONFIG_CPU_S3C6400) && !defined(CONFIG_CPU_S3C6410)
|
||||
if (readb(s3c_rtc_base + S3C2410_RTCALM) & S3C2410_RTCALM_ALMEN)
|
||||
#else
|
||||
if (1)
|
||||
#endif
|
||||
enable_irq_wake(s3c_rtc_alarmno);
|
||||
else
|
||||
disable_irq_wake(s3c_rtc_alarmno);
|
||||
}
|
||||
|
||||
static void s3c_rtc_setpie(int to)
|
||||
{
|
||||
unsigned int tmp;
|
||||
|
||||
pr_debug("%s: pie=%d\n", __FUNCTION__, to);
|
||||
|
||||
spin_lock_irq(&s3c_rtc_pie_lock);
|
||||
|
||||
#if !defined(CONFIG_CPU_S3C6400) && !defined(CONFIG_CPU_S3C6410)
|
||||
tmp = readb(s3c_rtc_base + S3C2410_TICNT) & ~S3C2410_TICNT_ENABLE;
|
||||
|
||||
if (to)
|
||||
tmp |= S3C2410_TICNT_ENABLE;
|
||||
|
||||
writeb(tmp, s3c_rtc_base + S3C2410_TICNT);
|
||||
#else
|
||||
tmp = readw(s3c_rtc_base + S3C2410_RTCCON) & ~S3C_RTCCON_TICEN;
|
||||
|
||||
if (to)
|
||||
tmp |= S3C_RTCCON_TICEN;
|
||||
|
||||
writew(tmp, s3c_rtc_base + S3C2410_RTCCON);
|
||||
|
||||
#endif
|
||||
spin_unlock_irq(&s3c_rtc_pie_lock);
|
||||
}
|
||||
|
||||
static void s3c_rtc_setfreq(int freq)
|
||||
{
|
||||
unsigned int tmp;
|
||||
|
||||
spin_lock_irq(&s3c_rtc_pie_lock);
|
||||
#if !defined(CONFIG_CPU_S3C6400) && !defined(CONFIG_CPU_S3C6410)
|
||||
|
||||
#if defined(CONFIG_CPU_S3C2450) || defined(CONFIG_CPU_S3C2416)
|
||||
tmp = readb(s3c_rtc_base + S3C2410_TICNT) & S3C2410_TICNT_ENABLE;
|
||||
#if 0
|
||||
writew(readw(s3c_rtc_base + S3C2410_RTCCON) & (~(1<<8)),s3c_rtc_base + S3C2410_RTCCON);
|
||||
#else
|
||||
writew((readw(s3c_rtc_base + S3C2410_RTCCON) & (~(1<<8)))| uiResolution,s3c_rtc_base + S3C2410_RTCCON);
|
||||
#endif
|
||||
#else
|
||||
tmp = readb(s3c_rtc_base + S3C2410_TICNT) & S3C2410_TICNT_ENABLE;
|
||||
#endif
|
||||
|
||||
s3c_rtc_freq = freq;
|
||||
|
||||
tmp |= (128 / freq)-1;
|
||||
writeb(tmp, s3c_rtc_base + S3C2410_TICNT);
|
||||
|
||||
#else
|
||||
tmp = readw(s3c_rtc_base + S3C2410_RTCCON) & (S3C_RTCCON_TICEN | S3C2410_RTCCON_RTCEN );
|
||||
writew(tmp, s3c_rtc_base + S3C2410_RTCCON);
|
||||
s3c_rtc_freq = freq;
|
||||
|
||||
tmp = (32768 / freq)-1;
|
||||
|
||||
#if defined(CONFIG_CPU_S3C6410)
|
||||
writel(tmp, s3c_rtc_base + S3C2410_TICNT);
|
||||
#elif defined(CONFIG_CPU_S3C6400)
|
||||
writew(tmp, s3c_rtc_base + S3C2410_TICNT);
|
||||
#endif
|
||||
#endif
|
||||
spin_unlock_irq(&s3c_rtc_pie_lock);
|
||||
}
|
||||
|
||||
/* Time read/write */
|
||||
|
||||
static int s3c_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
|
||||
{
|
||||
unsigned int have_retried = 0;
|
||||
void __iomem *base = s3c_rtc_base;
|
||||
|
||||
retry_get_time:
|
||||
rtc_tm->tm_min = readb(base + S3C2410_RTCMIN);
|
||||
rtc_tm->tm_hour = readb(base + S3C2410_RTCHOUR);
|
||||
rtc_tm->tm_mday = readb(base + S3C2410_RTCDATE);
|
||||
rtc_tm->tm_mon = readb(base + S3C2410_RTCMON);
|
||||
rtc_tm->tm_year = readb(base + S3C2410_RTCYEAR);
|
||||
rtc_tm->tm_sec = readb(base + S3C2410_RTCSEC);
|
||||
|
||||
/* the only way to work out wether the system was mid-update
|
||||
* when we read it is to check the second counter, and if it
|
||||
* is zero, then we re-try the entire read
|
||||
*/
|
||||
|
||||
if (rtc_tm->tm_sec == 0 && !have_retried) {
|
||||
have_retried = 1;
|
||||
goto retry_get_time;
|
||||
}
|
||||
|
||||
pr_debug("read time %02x.%02x.%02x %02x:%02x:%02x\n",
|
||||
rtc_tm->tm_year, rtc_tm->tm_mon, rtc_tm->tm_mday,
|
||||
rtc_tm->tm_hour, rtc_tm->tm_min, rtc_tm->tm_sec);
|
||||
|
||||
BCD_TO_BIN(rtc_tm->tm_sec);
|
||||
BCD_TO_BIN(rtc_tm->tm_min);
|
||||
BCD_TO_BIN(rtc_tm->tm_hour);
|
||||
BCD_TO_BIN(rtc_tm->tm_mday);
|
||||
BCD_TO_BIN(rtc_tm->tm_mon);
|
||||
BCD_TO_BIN(rtc_tm->tm_year);
|
||||
|
||||
rtc_tm->tm_year += 100;
|
||||
rtc_tm->tm_mon -= 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s3c_rtc_settime(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
void __iomem *base = s3c_rtc_base;
|
||||
int year = tm->tm_year - 100;
|
||||
|
||||
pr_debug("set time %02d.%02d.%02d %02d/%02d/%02d\n",
|
||||
tm->tm_year, tm->tm_mon, tm->tm_mday,
|
||||
tm->tm_hour, tm->tm_min, tm->tm_sec);
|
||||
|
||||
/* we get around y2k by simply not supporting it */
|
||||
|
||||
if (year < 0 || year >= 100) {
|
||||
dev_err(dev, "rtc only supports 100 years\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
writeb(BIN2BCD(tm->tm_sec), base + S3C2410_RTCSEC);
|
||||
writeb(BIN2BCD(tm->tm_min), base + S3C2410_RTCMIN);
|
||||
writeb(BIN2BCD(tm->tm_hour), base + S3C2410_RTCHOUR);
|
||||
writeb(BIN2BCD(tm->tm_mday), base + S3C2410_RTCDATE);
|
||||
writeb(BIN2BCD(tm->tm_mon + 1), base + S3C2410_RTCMON);
|
||||
writeb(BIN2BCD(year), base + S3C2410_RTCYEAR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s3c_rtc_getalarm(struct device *dev, struct rtc_wkalrm *alrm)
|
||||
{
|
||||
struct rtc_time *alm_tm = &alrm->time;
|
||||
void __iomem *base = s3c_rtc_base;
|
||||
unsigned int alm_en;
|
||||
|
||||
alm_tm->tm_sec = readb(base + S3C2410_ALMSEC);
|
||||
alm_tm->tm_min = readb(base + S3C2410_ALMMIN);
|
||||
alm_tm->tm_hour = readb(base + S3C2410_ALMHOUR);
|
||||
alm_tm->tm_mon = readb(base + S3C2410_ALMMON);
|
||||
alm_tm->tm_mday = readb(base + S3C2410_ALMDATE);
|
||||
alm_tm->tm_year = readb(base + S3C2410_ALMYEAR);
|
||||
|
||||
alm_en = readb(base + S3C2410_RTCALM);
|
||||
|
||||
alrm->enabled = (alm_en & S3C2410_RTCALM_ALMEN) ? 1 : 0;
|
||||
|
||||
pr_debug("read alarm %02x %02x.%02x.%02x %02x/%02x/%02x\n",
|
||||
alm_en,
|
||||
alm_tm->tm_year, alm_tm->tm_mon, alm_tm->tm_mday,
|
||||
alm_tm->tm_hour, alm_tm->tm_min, alm_tm->tm_sec);
|
||||
|
||||
|
||||
/* decode the alarm enable field */
|
||||
|
||||
if (alm_en & S3C2410_RTCALM_SECEN)
|
||||
BCD_TO_BIN(alm_tm->tm_sec);
|
||||
else
|
||||
alm_tm->tm_sec = 0xff;
|
||||
|
||||
if (alm_en & S3C2410_RTCALM_MINEN)
|
||||
BCD_TO_BIN(alm_tm->tm_min);
|
||||
else
|
||||
alm_tm->tm_min = 0xff;
|
||||
|
||||
if (alm_en & S3C2410_RTCALM_HOUREN)
|
||||
BCD_TO_BIN(alm_tm->tm_hour);
|
||||
else
|
||||
alm_tm->tm_hour = 0xff;
|
||||
|
||||
if (alm_en & S3C2410_RTCALM_DAYEN)
|
||||
BCD_TO_BIN(alm_tm->tm_mday);
|
||||
else
|
||||
alm_tm->tm_mday = 0xff;
|
||||
|
||||
if (alm_en & S3C2410_RTCALM_MONEN) {
|
||||
BCD_TO_BIN(alm_tm->tm_mon);
|
||||
alm_tm->tm_mon -= 1;
|
||||
} else {
|
||||
alm_tm->tm_mon = 0xff;
|
||||
}
|
||||
|
||||
if (alm_en & S3C2410_RTCALM_YEAREN)
|
||||
BCD_TO_BIN(alm_tm->tm_year);
|
||||
else
|
||||
alm_tm->tm_year = 0xffff;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s3c_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
|
||||
{
|
||||
struct rtc_time *tm = &alrm->time;
|
||||
void __iomem *base = s3c_rtc_base;
|
||||
unsigned int alrm_en;
|
||||
|
||||
pr_debug("s3c_rtc_setalarm: %d, %02x/%02x/%02x %02x.%02x.%02x\n",
|
||||
alrm->enabled,
|
||||
tm->tm_mday & 0xff, tm->tm_mon & 0xff, tm->tm_year & 0xff,
|
||||
tm->tm_hour & 0xff, tm->tm_min & 0xff, tm->tm_sec);
|
||||
|
||||
|
||||
alrm_en = readb(base + S3C2410_RTCALM) & S3C2410_RTCALM_ALMEN;
|
||||
writeb(0x00, base + S3C2410_RTCALM);
|
||||
|
||||
if (tm->tm_sec < 60 && tm->tm_sec >= 0) {
|
||||
alrm_en |= S3C2410_RTCALM_SECEN;
|
||||
writeb(BIN2BCD(tm->tm_sec), base + S3C2410_ALMSEC);
|
||||
}
|
||||
|
||||
if (tm->tm_min < 60 && tm->tm_min >= 0) {
|
||||
alrm_en |= S3C2410_RTCALM_MINEN;
|
||||
writeb(BIN2BCD(tm->tm_min), base + S3C2410_ALMMIN);
|
||||
}
|
||||
|
||||
if (tm->tm_hour < 24 && tm->tm_hour >= 0) {
|
||||
alrm_en |= S3C2410_RTCALM_HOUREN;
|
||||
writeb(BIN2BCD(tm->tm_hour), base + S3C2410_ALMHOUR);
|
||||
}
|
||||
|
||||
pr_debug("setting S3C2410_RTCALM to %08x\n", alrm_en);
|
||||
|
||||
writeb(alrm_en, base + S3C2410_RTCALM);
|
||||
|
||||
if (0) {
|
||||
alrm_en = readb(base + S3C2410_RTCALM);
|
||||
alrm_en &= ~S3C2410_RTCALM_ALMEN;
|
||||
writeb(alrm_en, base + S3C2410_RTCALM);
|
||||
disable_irq_wake(s3c_rtc_alarmno);
|
||||
}
|
||||
/*
|
||||
#if !defined(CONFIG_CPU_S3C6400) && !defined(CONFIG_CPU_S3C6410)
|
||||
if (alrm->enabled)
|
||||
#else
|
||||
if (1)
|
||||
#endif
|
||||
enable_irq_wake(s3c_rtc_alarmno);
|
||||
else
|
||||
disable_irq_wake(s3c_rtc_alarmno);
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s3c_rtc_ioctl(struct device *dev,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
unsigned int ret = -ENOIOCTLCMD;
|
||||
|
||||
switch (cmd) {
|
||||
case RTC_AIE_OFF:
|
||||
case RTC_AIE_ON:
|
||||
s3c_rtc_setaie((cmd == RTC_AIE_ON) ? 1 : 0);
|
||||
ret = 0;
|
||||
break;
|
||||
|
||||
case RTC_PIE_OFF:
|
||||
case RTC_PIE_ON:
|
||||
tick_count = 0;
|
||||
s3c_rtc_setpie((cmd == RTC_PIE_ON) ? 1 : 0);
|
||||
ret = 0;
|
||||
break;
|
||||
|
||||
case RTC_IRQP_READ:
|
||||
ret = put_user(s3c_rtc_freq, (unsigned long __user *)arg);
|
||||
break;
|
||||
|
||||
case RTC_IRQP_SET:
|
||||
/* check for power of 2 */
|
||||
printk("\nRTC_IRQP_SET arg = %d\n",(int)arg );
|
||||
if ((arg & (arg-1)) != 0 || arg < 1) {
|
||||
ret = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
s3c_rtc_setfreq(arg);
|
||||
ret = 0;
|
||||
break;
|
||||
|
||||
case RTC_UIE_ON:
|
||||
case RTC_UIE_OFF:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
|
||||
/* Qisda, ShiYong Lin, 2009/07/22, Implement function for RTC tick service {*/
|
||||
case RTC_SET_RESOLUTION:
|
||||
printk("RTC_SET_RESOLUTIONk = %d\n", (int)arg);
|
||||
if( arg > 14 || arg < 0)
|
||||
{
|
||||
printk("RTC_SET_RESOLUTION, Error\n");
|
||||
ret = -EINVAL;
|
||||
}
|
||||
else
|
||||
{
|
||||
uiResolution = (unsigned long) arg;
|
||||
uiResolution = uiResolution << 5;
|
||||
printk("RTC_SET_RESOLUTION, uiResolution = %d\n", (int)uiResolution);
|
||||
ret = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case RTC_GET_RESOLUTION:
|
||||
printk("RTC_GET_RESOLUTION = %d\n", (int)uiResolution);
|
||||
ret = put_user(uiResolution, (unsigned long __user *)arg);
|
||||
break;
|
||||
/* Qisda, ShiYong Lin, 2009/07/22, Implement function for RTC tick service }*/
|
||||
}
|
||||
|
||||
exit:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int s3c_rtc_proc(struct device *dev, struct seq_file *seq)
|
||||
{
|
||||
unsigned int ticnt = readb(s3c_rtc_base + S3C2410_TICNT);
|
||||
|
||||
seq_printf(seq, "periodic_IRQ\t: %s\n",
|
||||
(ticnt & S3C2410_TICNT_ENABLE) ? "yes" : "no" );
|
||||
|
||||
seq_printf(seq, "periodic_freq\t: %d\n", s3c_rtc_freq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s3c_rtc_open(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct rtc_device *rtc_dev = platform_get_drvdata(pdev);
|
||||
int ret;
|
||||
|
||||
ret = request_irq(s3c_rtc_alarmno, s3c_rtc_alarmirq,
|
||||
IRQF_DISABLED, "s3c2410-rtc alarm", rtc_dev);
|
||||
|
||||
if (ret) {
|
||||
dev_err(dev, "IRQ%d error %d\n", s3c_rtc_alarmno, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = request_irq(s3c_rtc_tickno, s3c_rtc_tickirq,
|
||||
IRQF_DISABLED, "s3c2410-rtc tick", rtc_dev);
|
||||
|
||||
if (ret) {
|
||||
dev_err(dev, "IRQ%d error %d\n", s3c_rtc_tickno, ret);
|
||||
goto tick_err;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
tick_err:
|
||||
free_irq(s3c_rtc_alarmno, rtc_dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void s3c_rtc_release(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct rtc_device *rtc_dev = platform_get_drvdata(pdev);
|
||||
|
||||
/* do not clear AIE here, it may be needed for wake */
|
||||
|
||||
s3c_rtc_setpie(0);
|
||||
free_irq(s3c_rtc_alarmno, rtc_dev);
|
||||
free_irq(s3c_rtc_tickno, rtc_dev);
|
||||
}
|
||||
|
||||
static const struct rtc_class_ops s3c_rtcops = {
|
||||
.open = s3c_rtc_open,
|
||||
.release = s3c_rtc_release,
|
||||
.ioctl = s3c_rtc_ioctl,
|
||||
.read_time = s3c_rtc_gettime,
|
||||
.set_time = s3c_rtc_settime,
|
||||
.read_alarm = s3c_rtc_getalarm,
|
||||
.set_alarm = s3c_rtc_setalarm,
|
||||
.proc = s3c_rtc_proc,
|
||||
};
|
||||
|
||||
static void s3c_rtc_enable(struct platform_device *pdev, int en)
|
||||
{
|
||||
void __iomem *base = s3c_rtc_base;
|
||||
unsigned int tmp;
|
||||
|
||||
if (s3c_rtc_base == NULL)
|
||||
return;
|
||||
|
||||
if (!en) {
|
||||
#if !defined(CONFIG_CPU_S3C6400) && !defined(CONFIG_CPU_S3C6410)
|
||||
tmp = readb(base + S3C2410_RTCCON);
|
||||
writeb(tmp & ~S3C2410_RTCCON_RTCEN, base + S3C2410_RTCCON);
|
||||
|
||||
tmp = readb(base + S3C2410_TICNT);
|
||||
writeb(tmp & ~S3C2410_TICNT_ENABLE, base + S3C2410_TICNT);
|
||||
#else
|
||||
tmp = readw(base + S3C2410_RTCCON);
|
||||
writew(tmp & ~ (S3C2410_RTCCON_RTCEN | S3C_RTCCON_TICEN), base + S3C2410_RTCCON);
|
||||
#endif
|
||||
} else {
|
||||
/* re-enable the device, and check it is ok */
|
||||
#if !defined(CONFIG_CPU_S3C6400) && !defined(CONFIG_CPU_S3C6410)
|
||||
if ((readb(base+S3C2410_RTCCON) & S3C2410_RTCCON_RTCEN) == 0){
|
||||
dev_info(&pdev->dev, "rtc disabled, re-enabling\n");
|
||||
|
||||
tmp = readb(base + S3C2410_RTCCON);
|
||||
writeb(tmp|S3C2410_RTCCON_RTCEN, base+S3C2410_RTCCON);
|
||||
}
|
||||
|
||||
if ((readb(base + S3C2410_RTCCON) & S3C2410_RTCCON_CNTSEL)){
|
||||
dev_info(&pdev->dev, "removing RTCCON_CNTSEL\n");
|
||||
|
||||
tmp = readb(base + S3C2410_RTCCON);
|
||||
writeb(tmp& ~S3C2410_RTCCON_CNTSEL, base+S3C2410_RTCCON);
|
||||
}
|
||||
|
||||
if ((readb(base + S3C2410_RTCCON) & S3C2410_RTCCON_CLKRST)){
|
||||
dev_info(&pdev->dev, "removing RTCCON_CLKRST\n");
|
||||
|
||||
tmp = readb(base + S3C2410_RTCCON);
|
||||
writeb(tmp & ~S3C2410_RTCCON_CLKRST, base+S3C2410_RTCCON);
|
||||
}
|
||||
#else
|
||||
if ((readw(base+S3C2410_RTCCON) & S3C2410_RTCCON_RTCEN) == 0){
|
||||
dev_info(&pdev->dev, "rtc disabled, re-enabling\n");
|
||||
|
||||
tmp = readw(base + S3C2410_RTCCON);
|
||||
writew(tmp|S3C2410_RTCCON_RTCEN, base+S3C2410_RTCCON);
|
||||
}
|
||||
|
||||
if ((readw(base + S3C2410_RTCCON) & S3C2410_RTCCON_CNTSEL)){
|
||||
dev_info(&pdev->dev, "removing RTCCON_CNTSEL\n");
|
||||
|
||||
tmp = readw(base + S3C2410_RTCCON);
|
||||
writew(tmp& ~S3C2410_RTCCON_CNTSEL, base+S3C2410_RTCCON);
|
||||
}
|
||||
|
||||
if ((readw(base + S3C2410_RTCCON) & S3C2410_RTCCON_CLKRST)){
|
||||
dev_info(&pdev->dev, "removing RTCCON_CLKRST\n");
|
||||
|
||||
tmp = readw(base + S3C2410_RTCCON);
|
||||
writew(tmp & ~S3C2410_RTCCON_CLKRST, base+S3C2410_RTCCON);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static int s3c_rtc_remove(struct platform_device *dev)
|
||||
{
|
||||
struct rtc_device *rtc = platform_get_drvdata(dev);
|
||||
|
||||
platform_set_drvdata(dev, NULL);
|
||||
rtc_device_unregister(rtc);
|
||||
|
||||
s3c_rtc_setpie(0);
|
||||
s3c_rtc_setaie(0);
|
||||
|
||||
iounmap(s3c_rtc_base);
|
||||
release_resource(s3c_rtc_mem);
|
||||
kfree(s3c_rtc_mem);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s3c_rtc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct rtc_device *rtc;
|
||||
struct resource *res;
|
||||
int ret;
|
||||
|
||||
/* Qisda, ShiYong Lin, 2009/07/18, Set system time when boot{*/
|
||||
struct rtc_time rtc_tm;
|
||||
/* Qisda, ShiYong Lin, 2009/07/18, Set system time when boot}*/
|
||||
|
||||
pr_debug("%s: probe=%p\n", __FUNCTION__, pdev);
|
||||
|
||||
/* find the IRQs */
|
||||
|
||||
s3c_rtc_tickno = platform_get_irq(pdev, 1);
|
||||
if (s3c_rtc_tickno < 0) {
|
||||
dev_err(&pdev->dev, "no irq for rtc tick\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
s3c_rtc_alarmno = platform_get_irq(pdev, 0);
|
||||
if (s3c_rtc_alarmno < 0) {
|
||||
dev_err(&pdev->dev, "no irq for alarm\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
pr_debug("s3c2410_rtc: tick irq %d, alarm irq %d\n",
|
||||
s3c_rtc_tickno, s3c_rtc_alarmno);
|
||||
|
||||
/* get the memory region */
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (res == NULL) {
|
||||
dev_err(&pdev->dev, "failed to get memory region resource\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
printk("res->start : %x res->end : %x",res->start,res->end);
|
||||
|
||||
s3c_rtc_mem = request_mem_region(res->start,
|
||||
res->end-res->start+1,
|
||||
pdev->name);
|
||||
|
||||
if (s3c_rtc_mem == NULL) {
|
||||
dev_err(&pdev->dev, "failed to reserve memory region\n");
|
||||
ret = -ENOENT;
|
||||
goto err_nores;
|
||||
}
|
||||
|
||||
s3c_rtc_base = ioremap(res->start, res->end - res->start + 1);
|
||||
if (s3c_rtc_base == NULL) {
|
||||
dev_err(&pdev->dev, "failed ioremap()\n");
|
||||
ret = -EINVAL;
|
||||
goto err_nomap;
|
||||
}
|
||||
|
||||
/* check to see if everything is setup correctly */
|
||||
|
||||
s3c_rtc_enable(pdev, 1);
|
||||
|
||||
pr_debug("s3c2410_rtc: RTCCON=%02x\n",
|
||||
readb(s3c_rtc_base + S3C2410_RTCCON));
|
||||
|
||||
s3c_rtc_setfreq(s3c_rtc_freq);
|
||||
|
||||
/* register RTC and exit */
|
||||
|
||||
rtc = rtc_device_register("s3c", &pdev->dev, &s3c_rtcops,
|
||||
THIS_MODULE);
|
||||
|
||||
if (IS_ERR(rtc)) {
|
||||
dev_err(&pdev->dev, "cannot attach rtc\n");
|
||||
ret = PTR_ERR(rtc);
|
||||
goto err_nortc;
|
||||
}
|
||||
#if !defined(CONFIG_CPU_S3C6400) && !defined(CONFIG_CPU_S3C6410)
|
||||
rtc->max_user_freq = 128;
|
||||
#else
|
||||
rtc->max_user_freq = 32768;
|
||||
#endif
|
||||
/* Qisda, ShiYong Lin, 2009/08/18, No need to set up RTC when boot {*/
|
||||
/*
|
||||
rtc_tm.tm_mday = 2;
|
||||
rtc_tm.tm_mon = 7;
|
||||
rtc_tm.tm_year = 9;
|
||||
rtc_tm.tm_hour = 17;
|
||||
rtc_tm.tm_min = 35;
|
||||
rtc_tm.tm_sec = 5;
|
||||
|
||||
writeb(BIN2BCD(rtc_tm.tm_sec), s3c_rtc_base + S3C2410_RTCSEC);
|
||||
writeb(BIN2BCD(rtc_tm.tm_min), s3c_rtc_base + S3C2410_RTCMIN);
|
||||
writeb(BIN2BCD(rtc_tm.tm_hour), s3c_rtc_base + S3C2410_RTCHOUR);
|
||||
writeb(BIN2BCD(rtc_tm.tm_mday), s3c_rtc_base + S3C2410_RTCDATE);
|
||||
writeb(BIN2BCD(rtc_tm.tm_mon + 1), s3c_rtc_base + S3C2410_RTCMON);
|
||||
writeb(BIN2BCD(rtc_tm.tm_year), s3c_rtc_base + S3C2410_RTCYEAR);
|
||||
*/
|
||||
/* } Qisda, ShiYong Lin, 2009/08/18, No need to set up RTC when boot */
|
||||
platform_set_drvdata(pdev, rtc);
|
||||
return 0;
|
||||
|
||||
err_nortc:
|
||||
s3c_rtc_enable(pdev, 0);
|
||||
iounmap(s3c_rtc_base);
|
||||
|
||||
err_nomap:
|
||||
release_resource(s3c_rtc_mem);
|
||||
|
||||
err_nores:
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
/* RTC Power management control */
|
||||
|
||||
static struct timespec s3c_rtc_delta;
|
||||
|
||||
static int ticnt_save;
|
||||
|
||||
static int s3c_rtc_suspend(struct platform_device *pdev, pm_message_t state)
|
||||
{
|
||||
struct rtc_time tm;
|
||||
struct timespec time;
|
||||
|
||||
time.tv_nsec = 0;
|
||||
|
||||
/* save TICNT for anyone using periodic interrupts */
|
||||
|
||||
ticnt_save = readb(s3c_rtc_base + S3C2410_TICNT);
|
||||
|
||||
/* calculate time delta for suspend */
|
||||
|
||||
s3c_rtc_gettime(&pdev->dev, &tm);
|
||||
rtc_tm_to_time(&tm, &time.tv_sec);
|
||||
save_time_delta(&s3c_rtc_delta, &time);
|
||||
s3c_rtc_enable(pdev, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s3c_rtc_resume(struct platform_device *pdev)
|
||||
{
|
||||
struct rtc_time tm;
|
||||
struct timespec time;
|
||||
|
||||
time.tv_nsec = 0;
|
||||
|
||||
s3c_rtc_enable(pdev, 1);
|
||||
s3c_rtc_gettime(&pdev->dev, &tm);
|
||||
rtc_tm_to_time(&tm, &time.tv_sec);
|
||||
restore_time_delta(&s3c_rtc_delta, &time);
|
||||
|
||||
writeb(ticnt_save, s3c_rtc_base + S3C2410_TICNT);
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#define s3c_rtc_suspend NULL
|
||||
#define s3c_rtc_resume NULL
|
||||
#endif
|
||||
|
||||
static struct platform_driver s3c2410_rtcdrv = {
|
||||
.probe = s3c_rtc_probe,
|
||||
.remove = s3c_rtc_remove,
|
||||
.suspend = s3c_rtc_suspend,
|
||||
.resume = s3c_rtc_resume,
|
||||
.driver = {
|
||||
.name = "s3c2410-rtc",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static char __initdata banner[] = "S3C24XX RTC, (c) 2004,2006 Simtec Electronics\n";
|
||||
|
||||
static int __init s3c_rtc_init(void)
|
||||
{
|
||||
printk(banner);
|
||||
return platform_driver_register(&s3c2410_rtcdrv);
|
||||
}
|
||||
|
||||
static void __exit s3c_rtc_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&s3c2410_rtcdrv);
|
||||
}
|
||||
|
||||
module_init(s3c_rtc_init);
|
||||
module_exit(s3c_rtc_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Samsung S3C RTC Driver");
|
||||
MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
|
||||
MODULE_LICENSE("GPL");
|
||||
378
drivers/rtc/rtc-sa1100.c
Normal file
378
drivers/rtc/rtc-sa1100.c
Normal file
@@ -0,0 +1,378 @@
|
||||
/*
|
||||
* Real Time Clock interface for StrongARM SA1x00 and XScale PXA2xx
|
||||
*
|
||||
* Copyright (c) 2000 Nils Faerber
|
||||
*
|
||||
* Based on rtc.c by Paul Gortmaker
|
||||
*
|
||||
* Original Driver by Nils Faerber <nils@kernelconcepts.de>
|
||||
*
|
||||
* Modifications from:
|
||||
* CIH <cih@coventive.com>
|
||||
* Nicolas Pitre <nico@cam.org>
|
||||
* Andrew Christian <andrew.christian@hp.com>
|
||||
*
|
||||
* Converted to the RTC subsystem and Driver Model
|
||||
* by Richard Purdie <rpurdie@rpsys.net>
|
||||
*
|
||||
* 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/platform_device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/rtc.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/pm.h>
|
||||
|
||||
#include <asm/bitops.h>
|
||||
#include <asm/hardware.h>
|
||||
#include <asm/irq.h>
|
||||
#include <asm/rtc.h>
|
||||
|
||||
#ifdef CONFIG_ARCH_PXA
|
||||
#include <asm/arch/pxa-regs.h>
|
||||
#endif
|
||||
|
||||
#define TIMER_FREQ CLOCK_TICK_RATE
|
||||
#define RTC_DEF_DIVIDER 32768 - 1
|
||||
#define RTC_DEF_TRIM 0
|
||||
|
||||
static unsigned long rtc_freq = 1024;
|
||||
static struct rtc_time rtc_alarm;
|
||||
static DEFINE_SPINLOCK(sa1100_rtc_lock);
|
||||
|
||||
static int rtc_update_alarm(struct rtc_time *alrm)
|
||||
{
|
||||
struct rtc_time alarm_tm, now_tm;
|
||||
unsigned long now, time;
|
||||
int ret;
|
||||
|
||||
do {
|
||||
now = RCNR;
|
||||
rtc_time_to_tm(now, &now_tm);
|
||||
rtc_next_alarm_time(&alarm_tm, &now_tm, alrm);
|
||||
ret = rtc_tm_to_time(&alarm_tm, &time);
|
||||
if (ret != 0)
|
||||
break;
|
||||
|
||||
RTSR = RTSR & (RTSR_HZE|RTSR_ALE|RTSR_AL);
|
||||
RTAR = time;
|
||||
} while (now != RCNR);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static irqreturn_t sa1100_rtc_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev_id);
|
||||
struct rtc_device *rtc = platform_get_drvdata(pdev);
|
||||
unsigned int rtsr;
|
||||
unsigned long events = 0;
|
||||
|
||||
spin_lock(&sa1100_rtc_lock);
|
||||
|
||||
rtsr = RTSR;
|
||||
/* clear interrupt sources */
|
||||
RTSR = 0;
|
||||
RTSR = (RTSR_AL | RTSR_HZ) & (rtsr >> 2);
|
||||
|
||||
/* clear alarm interrupt if it has occurred */
|
||||
if (rtsr & RTSR_AL)
|
||||
rtsr &= ~RTSR_ALE;
|
||||
RTSR = rtsr & (RTSR_ALE | RTSR_HZE);
|
||||
|
||||
/* update irq data & counter */
|
||||
if (rtsr & RTSR_AL)
|
||||
events |= RTC_AF | RTC_IRQF;
|
||||
if (rtsr & RTSR_HZ)
|
||||
events |= RTC_UF | RTC_IRQF;
|
||||
|
||||
rtc_update_irq(&rtc->class_dev, 1, events);
|
||||
|
||||
if (rtsr & RTSR_AL && rtc_periodic_alarm(&rtc_alarm))
|
||||
rtc_update_alarm(&rtc_alarm);
|
||||
|
||||
spin_unlock(&sa1100_rtc_lock);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int rtc_timer1_count;
|
||||
|
||||
static irqreturn_t timer1_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev_id);
|
||||
struct rtc_device *rtc = platform_get_drvdata(pdev);
|
||||
|
||||
/*
|
||||
* If we match for the first time, rtc_timer1_count will be 1.
|
||||
* Otherwise, we wrapped around (very unlikely but
|
||||
* still possible) so compute the amount of missed periods.
|
||||
* The match reg is updated only when the data is actually retrieved
|
||||
* to avoid unnecessary interrupts.
|
||||
*/
|
||||
OSSR = OSSR_M1; /* clear match on timer1 */
|
||||
|
||||
rtc_update_irq(&rtc->class_dev, rtc_timer1_count, RTC_PF | RTC_IRQF);
|
||||
|
||||
if (rtc_timer1_count == 1)
|
||||
rtc_timer1_count = (rtc_freq * ((1<<30)/(TIMER_FREQ>>2)));
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int sa1100_rtc_read_callback(struct device *dev, int data)
|
||||
{
|
||||
if (data & RTC_PF) {
|
||||
/* interpolate missed periods and set match for the next */
|
||||
unsigned long period = TIMER_FREQ/rtc_freq;
|
||||
unsigned long oscr = OSCR;
|
||||
unsigned long osmr1 = OSMR1;
|
||||
unsigned long missed = (oscr - osmr1)/period;
|
||||
data += missed << 8;
|
||||
OSSR = OSSR_M1; /* clear match on timer 1 */
|
||||
OSMR1 = osmr1 + (missed + 1)*period;
|
||||
/* Ensure we didn't miss another match in the mean time.
|
||||
* Here we compare (match - OSCR) 8 instead of 0 --
|
||||
* see comment in pxa_timer_interrupt() for explanation.
|
||||
*/
|
||||
while( (signed long)((osmr1 = OSMR1) - OSCR) <= 8 ) {
|
||||
data += 0x100;
|
||||
OSSR = OSSR_M1; /* clear match on timer 1 */
|
||||
OSMR1 = osmr1 + period;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
static int sa1100_rtc_open(struct device *dev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = request_irq(IRQ_RTC1Hz, sa1100_rtc_interrupt, IRQF_DISABLED,
|
||||
"rtc 1Hz", dev);
|
||||
if (ret) {
|
||||
dev_err(dev, "IRQ %d already in use.\n", IRQ_RTC1Hz);
|
||||
goto fail_ui;
|
||||
}
|
||||
ret = request_irq(IRQ_RTCAlrm, sa1100_rtc_interrupt, IRQF_DISABLED,
|
||||
"rtc Alrm", dev);
|
||||
if (ret) {
|
||||
dev_err(dev, "IRQ %d already in use.\n", IRQ_RTCAlrm);
|
||||
goto fail_ai;
|
||||
}
|
||||
ret = request_irq(IRQ_OST1, timer1_interrupt, IRQF_DISABLED,
|
||||
"rtc timer", dev);
|
||||
if (ret) {
|
||||
dev_err(dev, "IRQ %d already in use.\n", IRQ_OST1);
|
||||
goto fail_pi;
|
||||
}
|
||||
return 0;
|
||||
|
||||
fail_pi:
|
||||
free_irq(IRQ_RTCAlrm, dev);
|
||||
fail_ai:
|
||||
free_irq(IRQ_RTC1Hz, dev);
|
||||
fail_ui:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void sa1100_rtc_release(struct device *dev)
|
||||
{
|
||||
spin_lock_irq(&sa1100_rtc_lock);
|
||||
RTSR = 0;
|
||||
OIER &= ~OIER_E1;
|
||||
OSSR = OSSR_M1;
|
||||
spin_unlock_irq(&sa1100_rtc_lock);
|
||||
|
||||
free_irq(IRQ_OST1, dev);
|
||||
free_irq(IRQ_RTCAlrm, dev);
|
||||
free_irq(IRQ_RTC1Hz, dev);
|
||||
}
|
||||
|
||||
|
||||
static int sa1100_rtc_ioctl(struct device *dev, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
switch(cmd) {
|
||||
case RTC_AIE_OFF:
|
||||
spin_lock_irq(&sa1100_rtc_lock);
|
||||
RTSR &= ~RTSR_ALE;
|
||||
spin_unlock_irq(&sa1100_rtc_lock);
|
||||
return 0;
|
||||
case RTC_AIE_ON:
|
||||
spin_lock_irq(&sa1100_rtc_lock);
|
||||
RTSR |= RTSR_ALE;
|
||||
spin_unlock_irq(&sa1100_rtc_lock);
|
||||
return 0;
|
||||
case RTC_UIE_OFF:
|
||||
spin_lock_irq(&sa1100_rtc_lock);
|
||||
RTSR &= ~RTSR_HZE;
|
||||
spin_unlock_irq(&sa1100_rtc_lock);
|
||||
return 0;
|
||||
case RTC_UIE_ON:
|
||||
spin_lock_irq(&sa1100_rtc_lock);
|
||||
RTSR |= RTSR_HZE;
|
||||
spin_unlock_irq(&sa1100_rtc_lock);
|
||||
return 0;
|
||||
case RTC_PIE_OFF:
|
||||
spin_lock_irq(&sa1100_rtc_lock);
|
||||
OIER &= ~OIER_E1;
|
||||
spin_unlock_irq(&sa1100_rtc_lock);
|
||||
return 0;
|
||||
case RTC_PIE_ON:
|
||||
spin_lock_irq(&sa1100_rtc_lock);
|
||||
OSMR1 = TIMER_FREQ/rtc_freq + OSCR;
|
||||
OIER |= OIER_E1;
|
||||
rtc_timer1_count = 1;
|
||||
spin_unlock_irq(&sa1100_rtc_lock);
|
||||
return 0;
|
||||
case RTC_IRQP_READ:
|
||||
return put_user(rtc_freq, (unsigned long *)arg);
|
||||
case RTC_IRQP_SET:
|
||||
if (arg < 1 || arg > TIMER_FREQ)
|
||||
return -EINVAL;
|
||||
rtc_freq = arg;
|
||||
return 0;
|
||||
}
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
|
||||
static int sa1100_rtc_read_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
rtc_time_to_tm(RCNR, tm);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sa1100_rtc_set_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
unsigned long time;
|
||||
int ret;
|
||||
|
||||
ret = rtc_tm_to_time(tm, &time);
|
||||
if (ret == 0)
|
||||
RCNR = time;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sa1100_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
|
||||
{
|
||||
u32 rtsr;
|
||||
|
||||
memcpy(&alrm->time, &rtc_alarm, sizeof(struct rtc_time));
|
||||
rtsr = RTSR;
|
||||
alrm->enabled = (rtsr & RTSR_ALE) ? 1 : 0;
|
||||
alrm->pending = (rtsr & RTSR_AL) ? 1 : 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sa1100_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
|
||||
{
|
||||
int ret;
|
||||
|
||||
spin_lock_irq(&sa1100_rtc_lock);
|
||||
ret = rtc_update_alarm(&alrm->time);
|
||||
if (ret == 0) {
|
||||
if (alrm->enabled)
|
||||
RTSR |= RTSR_ALE;
|
||||
else
|
||||
RTSR &= ~RTSR_ALE;
|
||||
}
|
||||
spin_unlock_irq(&sa1100_rtc_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sa1100_rtc_proc(struct device *dev, struct seq_file *seq)
|
||||
{
|
||||
seq_printf(seq, "trim/divider\t: 0x%08x\n", (u32) RTTR);
|
||||
seq_printf(seq, "update_IRQ\t: %s\n",
|
||||
(RTSR & RTSR_HZE) ? "yes" : "no");
|
||||
seq_printf(seq, "periodic_IRQ\t: %s\n",
|
||||
(OIER & OIER_E1) ? "yes" : "no");
|
||||
seq_printf(seq, "periodic_freq\t: %ld\n", rtc_freq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct rtc_class_ops sa1100_rtc_ops = {
|
||||
.open = sa1100_rtc_open,
|
||||
.read_callback = sa1100_rtc_read_callback,
|
||||
.release = sa1100_rtc_release,
|
||||
.ioctl = sa1100_rtc_ioctl,
|
||||
.read_time = sa1100_rtc_read_time,
|
||||
.set_time = sa1100_rtc_set_time,
|
||||
.read_alarm = sa1100_rtc_read_alarm,
|
||||
.set_alarm = sa1100_rtc_set_alarm,
|
||||
.proc = sa1100_rtc_proc,
|
||||
};
|
||||
|
||||
static int sa1100_rtc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct rtc_device *rtc;
|
||||
|
||||
/*
|
||||
* According to the manual we should be able to let RTTR be zero
|
||||
* and then a default diviser for a 32.768KHz clock is used.
|
||||
* Apparently this doesn't work, at least for my SA1110 rev 5.
|
||||
* If the clock divider is uninitialized then reset it to the
|
||||
* default value to get the 1Hz clock.
|
||||
*/
|
||||
if (RTTR == 0) {
|
||||
RTTR = RTC_DEF_DIVIDER + (RTC_DEF_TRIM << 16);
|
||||
dev_warn(&pdev->dev, "warning: initializing default clock divider/trim value\n");
|
||||
/* The current RTC value probably doesn't make sense either */
|
||||
RCNR = 0;
|
||||
}
|
||||
|
||||
rtc = rtc_device_register(pdev->name, &pdev->dev, &sa1100_rtc_ops,
|
||||
THIS_MODULE);
|
||||
|
||||
if (IS_ERR(rtc))
|
||||
return PTR_ERR(rtc);
|
||||
|
||||
platform_set_drvdata(pdev, rtc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sa1100_rtc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct rtc_device *rtc = platform_get_drvdata(pdev);
|
||||
|
||||
if (rtc)
|
||||
rtc_device_unregister(rtc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver sa1100_rtc_driver = {
|
||||
.probe = sa1100_rtc_probe,
|
||||
.remove = sa1100_rtc_remove,
|
||||
.driver = {
|
||||
.name = "sa1100-rtc",
|
||||
},
|
||||
};
|
||||
|
||||
static int __init sa1100_rtc_init(void)
|
||||
{
|
||||
return platform_driver_register(&sa1100_rtc_driver);
|
||||
}
|
||||
|
||||
static void __exit sa1100_rtc_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&sa1100_rtc_driver);
|
||||
}
|
||||
|
||||
module_init(sa1100_rtc_init);
|
||||
module_exit(sa1100_rtc_exit);
|
||||
|
||||
MODULE_AUTHOR("Richard Purdie <rpurdie@rpsys.net>");
|
||||
MODULE_DESCRIPTION("SA11x0/PXA2xx Realtime Clock Driver (RTC)");
|
||||
MODULE_LICENSE("GPL");
|
||||
646
drivers/rtc/rtc-sh.c
Normal file
646
drivers/rtc/rtc-sh.c
Normal file
@@ -0,0 +1,646 @@
|
||||
/*
|
||||
* SuperH On-Chip RTC Support
|
||||
*
|
||||
* Copyright (C) 2006 Paul Mundt
|
||||
* Copyright (C) 2006 Jamie Lenehan
|
||||
*
|
||||
* Based on the old arch/sh/kernel/cpu/rtc.c by:
|
||||
*
|
||||
* Copyright (C) 2000 Philipp Rumpf <prumpf@tux.org>
|
||||
* Copyright (C) 1999 Tetsuya Okada & Niibe Yutaka
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details.
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/bcd.h>
|
||||
#include <linux/rtc.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/io.h>
|
||||
|
||||
#define DRV_NAME "sh-rtc"
|
||||
#define DRV_VERSION "0.1.2"
|
||||
|
||||
#ifdef CONFIG_CPU_SH3
|
||||
#define rtc_reg_size sizeof(u16)
|
||||
#define RTC_BIT_INVERTED 0 /* No bug on SH7708, SH7709A */
|
||||
#elif defined(CONFIG_CPU_SH4)
|
||||
#define rtc_reg_size sizeof(u32)
|
||||
#define RTC_BIT_INVERTED 0x40 /* bug on SH7750, SH7750S */
|
||||
#endif
|
||||
|
||||
#define RTC_REG(r) ((r) * rtc_reg_size)
|
||||
|
||||
#define R64CNT RTC_REG(0)
|
||||
|
||||
#define RSECCNT RTC_REG(1) /* RTC sec */
|
||||
#define RMINCNT RTC_REG(2) /* RTC min */
|
||||
#define RHRCNT RTC_REG(3) /* RTC hour */
|
||||
#define RWKCNT RTC_REG(4) /* RTC week */
|
||||
#define RDAYCNT RTC_REG(5) /* RTC day */
|
||||
#define RMONCNT RTC_REG(6) /* RTC month */
|
||||
#define RYRCNT RTC_REG(7) /* RTC year */
|
||||
#define RSECAR RTC_REG(8) /* ALARM sec */
|
||||
#define RMINAR RTC_REG(9) /* ALARM min */
|
||||
#define RHRAR RTC_REG(10) /* ALARM hour */
|
||||
#define RWKAR RTC_REG(11) /* ALARM week */
|
||||
#define RDAYAR RTC_REG(12) /* ALARM day */
|
||||
#define RMONAR RTC_REG(13) /* ALARM month */
|
||||
#define RCR1 RTC_REG(14) /* Control */
|
||||
#define RCR2 RTC_REG(15) /* Control */
|
||||
|
||||
/* ALARM Bits - or with BCD encoded value */
|
||||
#define AR_ENB 0x80 /* Enable for alarm cmp */
|
||||
|
||||
/* RCR1 Bits */
|
||||
#define RCR1_CF 0x80 /* Carry Flag */
|
||||
#define RCR1_CIE 0x10 /* Carry Interrupt Enable */
|
||||
#define RCR1_AIE 0x08 /* Alarm Interrupt Enable */
|
||||
#define RCR1_AF 0x01 /* Alarm Flag */
|
||||
|
||||
/* RCR2 Bits */
|
||||
#define RCR2_PEF 0x80 /* PEriodic interrupt Flag */
|
||||
#define RCR2_PESMASK 0x70 /* Periodic interrupt Set */
|
||||
#define RCR2_RTCEN 0x08 /* ENable RTC */
|
||||
#define RCR2_ADJ 0x04 /* ADJustment (30-second) */
|
||||
#define RCR2_RESET 0x02 /* Reset bit */
|
||||
#define RCR2_START 0x01 /* Start bit */
|
||||
|
||||
struct sh_rtc {
|
||||
void __iomem *regbase;
|
||||
unsigned long regsize;
|
||||
struct resource *res;
|
||||
unsigned int alarm_irq, periodic_irq, carry_irq;
|
||||
struct rtc_device *rtc_dev;
|
||||
spinlock_t lock;
|
||||
int rearm_aie;
|
||||
};
|
||||
|
||||
static irqreturn_t sh_rtc_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev_id);
|
||||
struct sh_rtc *rtc = platform_get_drvdata(pdev);
|
||||
unsigned int tmp, events = 0;
|
||||
|
||||
spin_lock(&rtc->lock);
|
||||
|
||||
tmp = readb(rtc->regbase + RCR1);
|
||||
tmp &= ~RCR1_CF;
|
||||
|
||||
if (rtc->rearm_aie) {
|
||||
if (tmp & RCR1_AF)
|
||||
tmp &= ~RCR1_AF; /* try to clear AF again */
|
||||
else {
|
||||
tmp |= RCR1_AIE; /* AF has cleared, rearm IRQ */
|
||||
rtc->rearm_aie = 0;
|
||||
}
|
||||
}
|
||||
|
||||
writeb(tmp, rtc->regbase + RCR1);
|
||||
|
||||
rtc_update_irq(&rtc->rtc_dev->class_dev, 1, events);
|
||||
|
||||
spin_unlock(&rtc->lock);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t sh_rtc_alarm(int irq, void *dev_id)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev_id);
|
||||
struct sh_rtc *rtc = platform_get_drvdata(pdev);
|
||||
unsigned int tmp, events = 0;
|
||||
|
||||
spin_lock(&rtc->lock);
|
||||
|
||||
tmp = readb(rtc->regbase + RCR1);
|
||||
|
||||
/*
|
||||
* If AF is set then the alarm has triggered. If we clear AF while
|
||||
* the alarm time still matches the RTC time then AF will
|
||||
* immediately be set again, and if AIE is enabled then the alarm
|
||||
* interrupt will immediately be retrigger. So we clear AIE here
|
||||
* and use rtc->rearm_aie so that the carry interrupt will keep
|
||||
* trying to clear AF and once it stays cleared it'll re-enable
|
||||
* AIE.
|
||||
*/
|
||||
if (tmp & RCR1_AF) {
|
||||
events |= RTC_AF | RTC_IRQF;
|
||||
|
||||
tmp &= ~(RCR1_AF|RCR1_AIE);
|
||||
|
||||
writeb(tmp, rtc->regbase + RCR1);
|
||||
|
||||
rtc->rearm_aie = 1;
|
||||
|
||||
rtc_update_irq(&rtc->rtc_dev->class_dev, 1, events);
|
||||
}
|
||||
|
||||
spin_unlock(&rtc->lock);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t sh_rtc_periodic(int irq, void *dev_id)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev_id);
|
||||
struct sh_rtc *rtc = platform_get_drvdata(pdev);
|
||||
|
||||
spin_lock(&rtc->lock);
|
||||
|
||||
rtc_update_irq(&rtc->rtc_dev->class_dev, 1, RTC_PF | RTC_IRQF);
|
||||
|
||||
spin_unlock(&rtc->lock);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static inline void sh_rtc_setpie(struct device *dev, unsigned int enable)
|
||||
{
|
||||
struct sh_rtc *rtc = dev_get_drvdata(dev);
|
||||
unsigned int tmp;
|
||||
|
||||
spin_lock_irq(&rtc->lock);
|
||||
|
||||
tmp = readb(rtc->regbase + RCR2);
|
||||
|
||||
if (enable) {
|
||||
tmp &= ~RCR2_PESMASK;
|
||||
tmp |= RCR2_PEF | (2 << 4);
|
||||
} else
|
||||
tmp &= ~(RCR2_PESMASK | RCR2_PEF);
|
||||
|
||||
writeb(tmp, rtc->regbase + RCR2);
|
||||
|
||||
spin_unlock_irq(&rtc->lock);
|
||||
}
|
||||
|
||||
static inline void sh_rtc_setaie(struct device *dev, unsigned int enable)
|
||||
{
|
||||
struct sh_rtc *rtc = dev_get_drvdata(dev);
|
||||
unsigned int tmp;
|
||||
|
||||
spin_lock_irq(&rtc->lock);
|
||||
|
||||
tmp = readb(rtc->regbase + RCR1);
|
||||
|
||||
if (!enable) {
|
||||
tmp &= ~RCR1_AIE;
|
||||
rtc->rearm_aie = 0;
|
||||
} else if (rtc->rearm_aie == 0)
|
||||
tmp |= RCR1_AIE;
|
||||
|
||||
writeb(tmp, rtc->regbase + RCR1);
|
||||
|
||||
spin_unlock_irq(&rtc->lock);
|
||||
}
|
||||
|
||||
static int sh_rtc_open(struct device *dev)
|
||||
{
|
||||
struct sh_rtc *rtc = dev_get_drvdata(dev);
|
||||
unsigned int tmp;
|
||||
int ret;
|
||||
|
||||
tmp = readb(rtc->regbase + RCR1);
|
||||
tmp &= ~RCR1_CF;
|
||||
tmp |= RCR1_CIE;
|
||||
writeb(tmp, rtc->regbase + RCR1);
|
||||
|
||||
ret = request_irq(rtc->periodic_irq, sh_rtc_periodic, IRQF_DISABLED,
|
||||
"sh-rtc period", dev);
|
||||
if (unlikely(ret)) {
|
||||
dev_err(dev, "request period IRQ failed with %d, IRQ %d\n",
|
||||
ret, rtc->periodic_irq);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = request_irq(rtc->carry_irq, sh_rtc_interrupt, IRQF_DISABLED,
|
||||
"sh-rtc carry", dev);
|
||||
if (unlikely(ret)) {
|
||||
dev_err(dev, "request carry IRQ failed with %d, IRQ %d\n",
|
||||
ret, rtc->carry_irq);
|
||||
free_irq(rtc->periodic_irq, dev);
|
||||
goto err_bad_carry;
|
||||
}
|
||||
|
||||
ret = request_irq(rtc->alarm_irq, sh_rtc_alarm, IRQF_DISABLED,
|
||||
"sh-rtc alarm", dev);
|
||||
if (unlikely(ret)) {
|
||||
dev_err(dev, "request alarm IRQ failed with %d, IRQ %d\n",
|
||||
ret, rtc->alarm_irq);
|
||||
goto err_bad_alarm;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_bad_alarm:
|
||||
free_irq(rtc->carry_irq, dev);
|
||||
err_bad_carry:
|
||||
free_irq(rtc->periodic_irq, dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void sh_rtc_release(struct device *dev)
|
||||
{
|
||||
struct sh_rtc *rtc = dev_get_drvdata(dev);
|
||||
|
||||
sh_rtc_setpie(dev, 0);
|
||||
sh_rtc_setaie(dev, 0);
|
||||
|
||||
free_irq(rtc->periodic_irq, dev);
|
||||
free_irq(rtc->carry_irq, dev);
|
||||
free_irq(rtc->alarm_irq, dev);
|
||||
}
|
||||
|
||||
static int sh_rtc_proc(struct device *dev, struct seq_file *seq)
|
||||
{
|
||||
struct sh_rtc *rtc = dev_get_drvdata(dev);
|
||||
unsigned int tmp;
|
||||
|
||||
tmp = readb(rtc->regbase + RCR1);
|
||||
seq_printf(seq, "carry_IRQ\t: %s\n",
|
||||
(tmp & RCR1_CIE) ? "yes" : "no");
|
||||
|
||||
tmp = readb(rtc->regbase + RCR2);
|
||||
seq_printf(seq, "periodic_IRQ\t: %s\n",
|
||||
(tmp & RCR2_PEF) ? "yes" : "no");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sh_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
unsigned int ret = -ENOIOCTLCMD;
|
||||
|
||||
switch (cmd) {
|
||||
case RTC_PIE_OFF:
|
||||
case RTC_PIE_ON:
|
||||
sh_rtc_setpie(dev, cmd == RTC_PIE_ON);
|
||||
ret = 0;
|
||||
break;
|
||||
case RTC_AIE_OFF:
|
||||
case RTC_AIE_ON:
|
||||
sh_rtc_setaie(dev, cmd == RTC_AIE_ON);
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sh_rtc_read_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct sh_rtc *rtc = platform_get_drvdata(pdev);
|
||||
unsigned int sec128, sec2, yr, yr100, cf_bit;
|
||||
|
||||
do {
|
||||
unsigned int tmp;
|
||||
|
||||
spin_lock_irq(&rtc->lock);
|
||||
|
||||
tmp = readb(rtc->regbase + RCR1);
|
||||
tmp &= ~RCR1_CF; /* Clear CF-bit */
|
||||
tmp |= RCR1_CIE;
|
||||
writeb(tmp, rtc->regbase + RCR1);
|
||||
|
||||
sec128 = readb(rtc->regbase + R64CNT);
|
||||
|
||||
tm->tm_sec = BCD2BIN(readb(rtc->regbase + RSECCNT));
|
||||
tm->tm_min = BCD2BIN(readb(rtc->regbase + RMINCNT));
|
||||
tm->tm_hour = BCD2BIN(readb(rtc->regbase + RHRCNT));
|
||||
tm->tm_wday = BCD2BIN(readb(rtc->regbase + RWKCNT));
|
||||
tm->tm_mday = BCD2BIN(readb(rtc->regbase + RDAYCNT));
|
||||
tm->tm_mon = BCD2BIN(readb(rtc->regbase + RMONCNT)) - 1;
|
||||
|
||||
#if defined(CONFIG_CPU_SH4)
|
||||
yr = readw(rtc->regbase + RYRCNT);
|
||||
yr100 = BCD2BIN(yr >> 8);
|
||||
yr &= 0xff;
|
||||
#else
|
||||
yr = readb(rtc->regbase + RYRCNT);
|
||||
yr100 = BCD2BIN((yr == 0x99) ? 0x19 : 0x20);
|
||||
#endif
|
||||
|
||||
tm->tm_year = (yr100 * 100 + BCD2BIN(yr)) - 1900;
|
||||
|
||||
sec2 = readb(rtc->regbase + R64CNT);
|
||||
cf_bit = readb(rtc->regbase + RCR1) & RCR1_CF;
|
||||
|
||||
spin_unlock_irq(&rtc->lock);
|
||||
} while (cf_bit != 0 || ((sec128 ^ sec2) & RTC_BIT_INVERTED) != 0);
|
||||
|
||||
#if RTC_BIT_INVERTED != 0
|
||||
if ((sec128 & RTC_BIT_INVERTED))
|
||||
tm->tm_sec--;
|
||||
#endif
|
||||
|
||||
dev_dbg(&dev, "%s: tm is secs=%d, mins=%d, hours=%d, "
|
||||
"mday=%d, mon=%d, year=%d, wday=%d\n",
|
||||
__FUNCTION__,
|
||||
tm->tm_sec, tm->tm_min, tm->tm_hour,
|
||||
tm->tm_mday, tm->tm_mon + 1, tm->tm_year, tm->tm_wday);
|
||||
|
||||
if (rtc_valid_tm(tm) < 0)
|
||||
dev_err(dev, "invalid date\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sh_rtc_set_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct sh_rtc *rtc = platform_get_drvdata(pdev);
|
||||
unsigned int tmp;
|
||||
int year;
|
||||
|
||||
spin_lock_irq(&rtc->lock);
|
||||
|
||||
/* Reset pre-scaler & stop RTC */
|
||||
tmp = readb(rtc->regbase + RCR2);
|
||||
tmp |= RCR2_RESET;
|
||||
writeb(tmp, rtc->regbase + RCR2);
|
||||
|
||||
writeb(BIN2BCD(tm->tm_sec), rtc->regbase + RSECCNT);
|
||||
writeb(BIN2BCD(tm->tm_min), rtc->regbase + RMINCNT);
|
||||
writeb(BIN2BCD(tm->tm_hour), rtc->regbase + RHRCNT);
|
||||
writeb(BIN2BCD(tm->tm_wday), rtc->regbase + RWKCNT);
|
||||
writeb(BIN2BCD(tm->tm_mday), rtc->regbase + RDAYCNT);
|
||||
writeb(BIN2BCD(tm->tm_mon + 1), rtc->regbase + RMONCNT);
|
||||
|
||||
#ifdef CONFIG_CPU_SH3
|
||||
year = tm->tm_year % 100;
|
||||
writeb(BIN2BCD(year), rtc->regbase + RYRCNT);
|
||||
#else
|
||||
year = (BIN2BCD((tm->tm_year + 1900) / 100) << 8) |
|
||||
BIN2BCD(tm->tm_year % 100);
|
||||
writew(year, rtc->regbase + RYRCNT);
|
||||
#endif
|
||||
|
||||
/* Start RTC */
|
||||
tmp = readb(rtc->regbase + RCR2);
|
||||
tmp &= ~RCR2_RESET;
|
||||
tmp |= RCR2_RTCEN | RCR2_START;
|
||||
writeb(tmp, rtc->regbase + RCR2);
|
||||
|
||||
spin_unlock_irq(&rtc->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int sh_rtc_read_alarm_value(struct sh_rtc *rtc, int reg_off)
|
||||
{
|
||||
unsigned int byte;
|
||||
int value = 0xff; /* return 0xff for ignored values */
|
||||
|
||||
byte = readb(rtc->regbase + reg_off);
|
||||
if (byte & AR_ENB) {
|
||||
byte &= ~AR_ENB; /* strip the enable bit */
|
||||
value = BCD2BIN(byte);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static int sh_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *wkalrm)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct sh_rtc *rtc = platform_get_drvdata(pdev);
|
||||
struct rtc_time* tm = &wkalrm->time;
|
||||
|
||||
spin_lock_irq(&rtc->lock);
|
||||
|
||||
tm->tm_sec = sh_rtc_read_alarm_value(rtc, RSECAR);
|
||||
tm->tm_min = sh_rtc_read_alarm_value(rtc, RMINAR);
|
||||
tm->tm_hour = sh_rtc_read_alarm_value(rtc, RHRAR);
|
||||
tm->tm_wday = sh_rtc_read_alarm_value(rtc, RWKAR);
|
||||
tm->tm_mday = sh_rtc_read_alarm_value(rtc, RDAYAR);
|
||||
tm->tm_mon = sh_rtc_read_alarm_value(rtc, RMONAR);
|
||||
if (tm->tm_mon > 0)
|
||||
tm->tm_mon -= 1; /* RTC is 1-12, tm_mon is 0-11 */
|
||||
tm->tm_year = 0xffff;
|
||||
|
||||
wkalrm->enabled = (readb(rtc->regbase + RCR1) & RCR1_AIE) ? 1 : 0;
|
||||
|
||||
spin_unlock_irq(&rtc->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void sh_rtc_write_alarm_value(struct sh_rtc *rtc,
|
||||
int value, int reg_off)
|
||||
{
|
||||
/* < 0 for a value that is ignored */
|
||||
if (value < 0)
|
||||
writeb(0, rtc->regbase + reg_off);
|
||||
else
|
||||
writeb(BIN2BCD(value) | AR_ENB, rtc->regbase + reg_off);
|
||||
}
|
||||
|
||||
static int sh_rtc_check_alarm(struct rtc_time* tm)
|
||||
{
|
||||
/*
|
||||
* The original rtc says anything > 0xc0 is "don't care" or "match
|
||||
* all" - most users use 0xff but rtc-dev uses -1 for the same thing.
|
||||
* The original rtc doesn't support years - some things use -1 and
|
||||
* some 0xffff. We use -1 to make out tests easier.
|
||||
*/
|
||||
if (tm->tm_year == 0xffff)
|
||||
tm->tm_year = -1;
|
||||
if (tm->tm_mon >= 0xff)
|
||||
tm->tm_mon = -1;
|
||||
if (tm->tm_mday >= 0xff)
|
||||
tm->tm_mday = -1;
|
||||
if (tm->tm_wday >= 0xff)
|
||||
tm->tm_wday = -1;
|
||||
if (tm->tm_hour >= 0xff)
|
||||
tm->tm_hour = -1;
|
||||
if (tm->tm_min >= 0xff)
|
||||
tm->tm_min = -1;
|
||||
if (tm->tm_sec >= 0xff)
|
||||
tm->tm_sec = -1;
|
||||
|
||||
if (tm->tm_year > 9999 ||
|
||||
tm->tm_mon >= 12 ||
|
||||
tm->tm_mday == 0 || tm->tm_mday >= 32 ||
|
||||
tm->tm_wday >= 7 ||
|
||||
tm->tm_hour >= 24 ||
|
||||
tm->tm_min >= 60 ||
|
||||
tm->tm_sec >= 60)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sh_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *wkalrm)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct sh_rtc *rtc = platform_get_drvdata(pdev);
|
||||
unsigned int rcr1;
|
||||
struct rtc_time *tm = &wkalrm->time;
|
||||
int mon, err;
|
||||
|
||||
err = sh_rtc_check_alarm(tm);
|
||||
if (unlikely(err < 0))
|
||||
return err;
|
||||
|
||||
spin_lock_irq(&rtc->lock);
|
||||
|
||||
/* disable alarm interrupt and clear the alarm flag */
|
||||
rcr1 = readb(rtc->regbase + RCR1);
|
||||
rcr1 &= ~(RCR1_AF|RCR1_AIE);
|
||||
writeb(rcr1, rtc->regbase + RCR1);
|
||||
|
||||
rtc->rearm_aie = 0;
|
||||
|
||||
/* set alarm time */
|
||||
sh_rtc_write_alarm_value(rtc, tm->tm_sec, RSECAR);
|
||||
sh_rtc_write_alarm_value(rtc, tm->tm_min, RMINAR);
|
||||
sh_rtc_write_alarm_value(rtc, tm->tm_hour, RHRAR);
|
||||
sh_rtc_write_alarm_value(rtc, tm->tm_wday, RWKAR);
|
||||
sh_rtc_write_alarm_value(rtc, tm->tm_mday, RDAYAR);
|
||||
mon = tm->tm_mon;
|
||||
if (mon >= 0)
|
||||
mon += 1;
|
||||
sh_rtc_write_alarm_value(rtc, mon, RMONAR);
|
||||
|
||||
if (wkalrm->enabled) {
|
||||
rcr1 |= RCR1_AIE;
|
||||
writeb(rcr1, rtc->regbase + RCR1);
|
||||
}
|
||||
|
||||
spin_unlock_irq(&rtc->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct rtc_class_ops sh_rtc_ops = {
|
||||
.open = sh_rtc_open,
|
||||
.release = sh_rtc_release,
|
||||
.ioctl = sh_rtc_ioctl,
|
||||
.read_time = sh_rtc_read_time,
|
||||
.set_time = sh_rtc_set_time,
|
||||
.read_alarm = sh_rtc_read_alarm,
|
||||
.set_alarm = sh_rtc_set_alarm,
|
||||
.proc = sh_rtc_proc,
|
||||
};
|
||||
|
||||
static int __devinit sh_rtc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct sh_rtc *rtc;
|
||||
struct resource *res;
|
||||
int ret = -ENOENT;
|
||||
|
||||
rtc = kzalloc(sizeof(struct sh_rtc), GFP_KERNEL);
|
||||
if (unlikely(!rtc))
|
||||
return -ENOMEM;
|
||||
|
||||
spin_lock_init(&rtc->lock);
|
||||
|
||||
rtc->periodic_irq = platform_get_irq(pdev, 0);
|
||||
if (unlikely(rtc->periodic_irq < 0)) {
|
||||
dev_err(&pdev->dev, "No IRQ for period\n");
|
||||
goto err_badres;
|
||||
}
|
||||
|
||||
rtc->carry_irq = platform_get_irq(pdev, 1);
|
||||
if (unlikely(rtc->carry_irq < 0)) {
|
||||
dev_err(&pdev->dev, "No IRQ for carry\n");
|
||||
goto err_badres;
|
||||
}
|
||||
|
||||
rtc->alarm_irq = platform_get_irq(pdev, 2);
|
||||
if (unlikely(rtc->alarm_irq < 0)) {
|
||||
dev_err(&pdev->dev, "No IRQ for alarm\n");
|
||||
goto err_badres;
|
||||
}
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||
if (unlikely(res == NULL)) {
|
||||
dev_err(&pdev->dev, "No IO resource\n");
|
||||
goto err_badres;
|
||||
}
|
||||
|
||||
rtc->regsize = res->end - res->start + 1;
|
||||
|
||||
rtc->res = request_mem_region(res->start, rtc->regsize, pdev->name);
|
||||
if (unlikely(!rtc->res)) {
|
||||
ret = -EBUSY;
|
||||
goto err_badres;
|
||||
}
|
||||
|
||||
rtc->regbase = (void __iomem *)rtc->res->start;
|
||||
if (unlikely(!rtc->regbase)) {
|
||||
ret = -EINVAL;
|
||||
goto err_badmap;
|
||||
}
|
||||
|
||||
rtc->rtc_dev = rtc_device_register("sh", &pdev->dev,
|
||||
&sh_rtc_ops, THIS_MODULE);
|
||||
if (IS_ERR(rtc)) {
|
||||
ret = PTR_ERR(rtc->rtc_dev);
|
||||
goto err_badmap;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, rtc);
|
||||
|
||||
return 0;
|
||||
|
||||
err_badmap:
|
||||
release_resource(rtc->res);
|
||||
err_badres:
|
||||
kfree(rtc);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devexit sh_rtc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct sh_rtc *rtc = platform_get_drvdata(pdev);
|
||||
|
||||
if (likely(rtc->rtc_dev))
|
||||
rtc_device_unregister(rtc->rtc_dev);
|
||||
|
||||
sh_rtc_setpie(&pdev->dev, 0);
|
||||
sh_rtc_setaie(&pdev->dev, 0);
|
||||
|
||||
release_resource(rtc->res);
|
||||
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
||||
kfree(rtc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
static struct platform_driver sh_rtc_platform_driver = {
|
||||
.driver = {
|
||||
.name = DRV_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = sh_rtc_probe,
|
||||
.remove = __devexit_p(sh_rtc_remove),
|
||||
};
|
||||
|
||||
static int __init sh_rtc_init(void)
|
||||
{
|
||||
return platform_driver_register(&sh_rtc_platform_driver);
|
||||
}
|
||||
|
||||
static void __exit sh_rtc_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&sh_rtc_platform_driver);
|
||||
}
|
||||
|
||||
module_init(sh_rtc_init);
|
||||
module_exit(sh_rtc_exit);
|
||||
|
||||
MODULE_DESCRIPTION("SuperH on-chip RTC driver");
|
||||
MODULE_VERSION(DRV_VERSION);
|
||||
MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>, Jamie Lenehan <lenehan@twibble.org>");
|
||||
MODULE_LICENSE("GPL");
|
||||
223
drivers/rtc/rtc-sysfs.c
Normal file
223
drivers/rtc/rtc-sysfs.c
Normal file
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* RTC subsystem, sysfs interface
|
||||
*
|
||||
* Copyright (C) 2005 Tower Technologies
|
||||
* Author: Alessandro Zummo <a.zummo@towertech.it>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/rtc.h>
|
||||
|
||||
/* device attributes */
|
||||
|
||||
static ssize_t rtc_sysfs_show_name(struct class_device *dev, char *buf)
|
||||
{
|
||||
return sprintf(buf, "%s\n", to_rtc_device(dev)->name);
|
||||
}
|
||||
static CLASS_DEVICE_ATTR(name, S_IRUGO, rtc_sysfs_show_name, NULL);
|
||||
|
||||
static ssize_t rtc_sysfs_show_date(struct class_device *dev, char *buf)
|
||||
{
|
||||
ssize_t retval;
|
||||
struct rtc_time tm;
|
||||
|
||||
retval = rtc_read_time(dev, &tm);
|
||||
if (retval == 0) {
|
||||
retval = sprintf(buf, "%04d-%02d-%02d\n",
|
||||
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
static CLASS_DEVICE_ATTR(date, S_IRUGO, rtc_sysfs_show_date, NULL);
|
||||
|
||||
static ssize_t rtc_sysfs_show_time(struct class_device *dev, char *buf)
|
||||
{
|
||||
ssize_t retval;
|
||||
struct rtc_time tm;
|
||||
|
||||
retval = rtc_read_time(dev, &tm);
|
||||
if (retval == 0) {
|
||||
retval = sprintf(buf, "%02d:%02d:%02d\n",
|
||||
tm.tm_hour, tm.tm_min, tm.tm_sec);
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
static CLASS_DEVICE_ATTR(time, S_IRUGO, rtc_sysfs_show_time, NULL);
|
||||
|
||||
static ssize_t rtc_sysfs_show_since_epoch(struct class_device *dev, char *buf)
|
||||
{
|
||||
ssize_t retval;
|
||||
struct rtc_time tm;
|
||||
|
||||
retval = rtc_read_time(dev, &tm);
|
||||
if (retval == 0) {
|
||||
unsigned long time;
|
||||
rtc_tm_to_time(&tm, &time);
|
||||
retval = sprintf(buf, "%lu\n", time);
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
static CLASS_DEVICE_ATTR(since_epoch, S_IRUGO, rtc_sysfs_show_since_epoch, NULL);
|
||||
|
||||
static struct attribute *rtc_attrs[] = {
|
||||
&class_device_attr_name.attr,
|
||||
&class_device_attr_date.attr,
|
||||
&class_device_attr_time.attr,
|
||||
&class_device_attr_since_epoch.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute_group rtc_attr_group = {
|
||||
.attrs = rtc_attrs,
|
||||
};
|
||||
|
||||
|
||||
static ssize_t
|
||||
rtc_sysfs_show_wakealarm(struct class_device *dev, char *buf)
|
||||
{
|
||||
ssize_t retval;
|
||||
unsigned long alarm;
|
||||
struct rtc_wkalrm alm;
|
||||
|
||||
/* Don't show disabled alarms; but the RTC could leave the
|
||||
* alarm enabled after it's already triggered. Alarms are
|
||||
* conceptually one-shot, even though some common hardware
|
||||
* (PCs) doesn't actually work that way.
|
||||
*
|
||||
* REVISIT maybe we should require RTC implementations to
|
||||
* disable the RTC alarm after it triggers, for uniformity.
|
||||
*/
|
||||
retval = rtc_read_alarm(dev, &alm);
|
||||
if (retval == 0 && alm.enabled) {
|
||||
rtc_tm_to_time(&alm.time, &alarm);
|
||||
retval = sprintf(buf, "%lu\n", alarm);
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
rtc_sysfs_set_wakealarm(struct class_device *dev, const char *buf, size_t n)
|
||||
{
|
||||
ssize_t retval;
|
||||
unsigned long now, alarm;
|
||||
struct rtc_wkalrm alm;
|
||||
|
||||
/* Only request alarms that trigger in the future. Disable them
|
||||
* by writing another time, e.g. 0 meaning Jan 1 1970 UTC.
|
||||
*/
|
||||
retval = rtc_read_time(dev, &alm.time);
|
||||
if (retval < 0)
|
||||
return retval;
|
||||
rtc_tm_to_time(&alm.time, &now);
|
||||
|
||||
alarm = simple_strtoul(buf, NULL, 0);
|
||||
if (alarm > now) {
|
||||
/* Avoid accidentally clobbering active alarms; we can't
|
||||
* entirely prevent that here, without even the minimal
|
||||
* locking from the /dev/rtcN api.
|
||||
*/
|
||||
retval = rtc_read_alarm(dev, &alm);
|
||||
if (retval < 0)
|
||||
return retval;
|
||||
if (alm.enabled)
|
||||
return -EBUSY;
|
||||
|
||||
alm.enabled = 1;
|
||||
} else {
|
||||
alm.enabled = 0;
|
||||
|
||||
/* Provide a valid future alarm time. Linux isn't EFI,
|
||||
* this time won't be ignored when disabling the alarm.
|
||||
*/
|
||||
alarm = now + 300;
|
||||
}
|
||||
rtc_time_to_tm(alarm, &alm.time);
|
||||
|
||||
retval = rtc_set_alarm(dev, &alm);
|
||||
return (retval < 0) ? retval : n;
|
||||
}
|
||||
static const CLASS_DEVICE_ATTR(wakealarm, S_IRUGO | S_IWUSR,
|
||||
rtc_sysfs_show_wakealarm, rtc_sysfs_set_wakealarm);
|
||||
|
||||
|
||||
/* The reason to trigger an alarm with no process watching it (via sysfs)
|
||||
* is its side effect: waking from a system state like suspend-to-RAM or
|
||||
* suspend-to-disk. So: no attribute unless that side effect is possible.
|
||||
* (Userspace may disable that mechanism later.)
|
||||
*/
|
||||
static inline int rtc_does_wakealarm(struct class_device *class_dev)
|
||||
{
|
||||
struct rtc_device *rtc;
|
||||
|
||||
if (!device_can_wakeup(class_dev->dev))
|
||||
return 0;
|
||||
rtc = to_rtc_device(class_dev);
|
||||
return rtc->ops->set_alarm != NULL;
|
||||
}
|
||||
|
||||
|
||||
static int rtc_sysfs_add_device(struct class_device *class_dev,
|
||||
struct class_interface *class_intf)
|
||||
{
|
||||
int err;
|
||||
|
||||
dev_dbg(class_dev->dev, "rtc intf: sysfs\n");
|
||||
|
||||
err = sysfs_create_group(&class_dev->kobj, &rtc_attr_group);
|
||||
if (err)
|
||||
dev_err(class_dev->dev, "failed to create %s\n",
|
||||
"sysfs attributes");
|
||||
else if (rtc_does_wakealarm(class_dev)) {
|
||||
/* not all RTCs support both alarms and wakeup */
|
||||
err = class_device_create_file(class_dev,
|
||||
&class_device_attr_wakealarm);
|
||||
if (err) {
|
||||
dev_err(class_dev->dev, "failed to create %s\n",
|
||||
"alarm attribute");
|
||||
sysfs_remove_group(&class_dev->kobj, &rtc_attr_group);
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void rtc_sysfs_remove_device(struct class_device *class_dev,
|
||||
struct class_interface *class_intf)
|
||||
{
|
||||
if (rtc_does_wakealarm(class_dev))
|
||||
class_device_remove_file(class_dev,
|
||||
&class_device_attr_wakealarm);
|
||||
sysfs_remove_group(&class_dev->kobj, &rtc_attr_group);
|
||||
}
|
||||
|
||||
/* interface registration */
|
||||
|
||||
static struct class_interface rtc_sysfs_interface = {
|
||||
.add = &rtc_sysfs_add_device,
|
||||
.remove = &rtc_sysfs_remove_device,
|
||||
};
|
||||
|
||||
static int __init rtc_sysfs_init(void)
|
||||
{
|
||||
return rtc_interface_register(&rtc_sysfs_interface);
|
||||
}
|
||||
|
||||
static void __exit rtc_sysfs_exit(void)
|
||||
{
|
||||
class_interface_unregister(&rtc_sysfs_interface);
|
||||
}
|
||||
|
||||
subsys_initcall(rtc_sysfs_init);
|
||||
module_exit(rtc_sysfs_exit);
|
||||
|
||||
MODULE_AUTHOR("Alessandro Zummo <a.zummo@towertech.it>");
|
||||
MODULE_DESCRIPTION("RTC class sysfs interface");
|
||||
MODULE_LICENSE("GPL");
|
||||
210
drivers/rtc/rtc-test.c
Normal file
210
drivers/rtc/rtc-test.c
Normal file
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* An RTC test device/driver
|
||||
* Copyright (C) 2005 Tower Technologies
|
||||
* Author: Alessandro Zummo <a.zummo@towertech.it>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/rtc.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
static struct platform_device *test0 = NULL, *test1 = NULL;
|
||||
|
||||
static int test_rtc_read_alarm(struct device *dev,
|
||||
struct rtc_wkalrm *alrm)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_rtc_set_alarm(struct device *dev,
|
||||
struct rtc_wkalrm *alrm)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_rtc_read_time(struct device *dev,
|
||||
struct rtc_time *tm)
|
||||
{
|
||||
rtc_time_to_tm(get_seconds(), tm);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_rtc_set_time(struct device *dev,
|
||||
struct rtc_time *tm)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_rtc_set_mmss(struct device *dev, unsigned long secs)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_rtc_proc(struct device *dev, struct seq_file *seq)
|
||||
{
|
||||
struct platform_device *plat_dev = to_platform_device(dev);
|
||||
|
||||
seq_printf(seq, "test\t\t: yes\n");
|
||||
seq_printf(seq, "id\t\t: %d\n", plat_dev->id);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_rtc_ioctl(struct device *dev, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
/* We do support interrupts, they're generated
|
||||
* using the sysfs interface.
|
||||
*/
|
||||
switch (cmd) {
|
||||
case RTC_PIE_ON:
|
||||
case RTC_PIE_OFF:
|
||||
case RTC_UIE_ON:
|
||||
case RTC_UIE_OFF:
|
||||
case RTC_AIE_ON:
|
||||
case RTC_AIE_OFF:
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct rtc_class_ops test_rtc_ops = {
|
||||
.proc = test_rtc_proc,
|
||||
.read_time = test_rtc_read_time,
|
||||
.set_time = test_rtc_set_time,
|
||||
.read_alarm = test_rtc_read_alarm,
|
||||
.set_alarm = test_rtc_set_alarm,
|
||||
.set_mmss = test_rtc_set_mmss,
|
||||
.ioctl = test_rtc_ioctl,
|
||||
};
|
||||
|
||||
static ssize_t test_irq_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
return sprintf(buf, "%d\n", 42);
|
||||
}
|
||||
static ssize_t test_irq_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int retval;
|
||||
struct platform_device *plat_dev = to_platform_device(dev);
|
||||
struct rtc_device *rtc = platform_get_drvdata(plat_dev);
|
||||
|
||||
retval = count;
|
||||
local_irq_disable();
|
||||
if (strncmp(buf, "tick", 4) == 0)
|
||||
rtc_update_irq(&rtc->class_dev, 1, RTC_PF | RTC_IRQF);
|
||||
else if (strncmp(buf, "alarm", 5) == 0)
|
||||
rtc_update_irq(&rtc->class_dev, 1, RTC_AF | RTC_IRQF);
|
||||
else if (strncmp(buf, "update", 6) == 0)
|
||||
rtc_update_irq(&rtc->class_dev, 1, RTC_UF | RTC_IRQF);
|
||||
else
|
||||
retval = -EINVAL;
|
||||
local_irq_enable();
|
||||
|
||||
return retval;
|
||||
}
|
||||
static DEVICE_ATTR(irq, S_IRUGO | S_IWUSR, test_irq_show, test_irq_store);
|
||||
|
||||
static int test_probe(struct platform_device *plat_dev)
|
||||
{
|
||||
int err;
|
||||
struct rtc_device *rtc = rtc_device_register("test", &plat_dev->dev,
|
||||
&test_rtc_ops, THIS_MODULE);
|
||||
if (IS_ERR(rtc)) {
|
||||
err = PTR_ERR(rtc);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = device_create_file(&plat_dev->dev, &dev_attr_irq);
|
||||
if (err)
|
||||
goto err;
|
||||
|
||||
platform_set_drvdata(plat_dev, rtc);
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
rtc_device_unregister(rtc);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int __devexit test_remove(struct platform_device *plat_dev)
|
||||
{
|
||||
struct rtc_device *rtc = platform_get_drvdata(plat_dev);
|
||||
|
||||
rtc_device_unregister(rtc);
|
||||
device_remove_file(&plat_dev->dev, &dev_attr_irq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver test_drv = {
|
||||
.probe = test_probe,
|
||||
.remove = __devexit_p(test_remove),
|
||||
.driver = {
|
||||
.name = "rtc-test",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init test_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
if ((err = platform_driver_register(&test_drv)))
|
||||
return err;
|
||||
|
||||
if ((test0 = platform_device_alloc("rtc-test", 0)) == NULL) {
|
||||
err = -ENOMEM;
|
||||
goto exit_driver_unregister;
|
||||
}
|
||||
|
||||
if ((test1 = platform_device_alloc("rtc-test", 1)) == NULL) {
|
||||
err = -ENOMEM;
|
||||
goto exit_free_test0;
|
||||
}
|
||||
|
||||
if ((err = platform_device_add(test0)))
|
||||
goto exit_free_test1;
|
||||
|
||||
if ((err = platform_device_add(test1)))
|
||||
goto exit_device_unregister;
|
||||
|
||||
return 0;
|
||||
|
||||
exit_device_unregister:
|
||||
platform_device_unregister(test0);
|
||||
|
||||
exit_free_test1:
|
||||
platform_device_put(test1);
|
||||
|
||||
exit_free_test0:
|
||||
platform_device_put(test0);
|
||||
|
||||
exit_driver_unregister:
|
||||
platform_driver_unregister(&test_drv);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit test_exit(void)
|
||||
{
|
||||
platform_device_unregister(test0);
|
||||
platform_device_unregister(test1);
|
||||
platform_driver_unregister(&test_drv);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Alessandro Zummo <a.zummo@towertech.it>");
|
||||
MODULE_DESCRIPTION("RTC test driver/device");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_init(test_init);
|
||||
module_exit(test_exit);
|
||||
261
drivers/rtc/rtc-v3020.c
Normal file
261
drivers/rtc/rtc-v3020.c
Normal file
@@ -0,0 +1,261 @@
|
||||
/* drivers/rtc/rtc-v3020.c
|
||||
*
|
||||
* Copyright (C) 2006 8D Technologies inc.
|
||||
* Copyright (C) 2004 Compulab Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* Driver for the V3020 RTC
|
||||
*
|
||||
* Changelog:
|
||||
*
|
||||
* 10-May-2006: Raphael Assenat <raph@8d.com>
|
||||
* - Converted to platform driver
|
||||
* - Use the generic rtc class
|
||||
*
|
||||
* ??-???-2004: Someone at Compulab
|
||||
* - Initial driver creation.
|
||||
*
|
||||
*/
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/rtc.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/bcd.h>
|
||||
#include <linux/rtc-v3020.h>
|
||||
|
||||
#include <asm/io.h>
|
||||
|
||||
#undef DEBUG
|
||||
|
||||
struct v3020 {
|
||||
void __iomem *ioaddress;
|
||||
int leftshift;
|
||||
struct rtc_device *rtc;
|
||||
};
|
||||
|
||||
static void v3020_set_reg(struct v3020 *chip, unsigned char address,
|
||||
unsigned char data)
|
||||
{
|
||||
int i;
|
||||
unsigned char tmp;
|
||||
|
||||
tmp = address;
|
||||
for (i = 0; i < 4; i++) {
|
||||
writel((tmp & 1) << chip->leftshift, chip->ioaddress);
|
||||
tmp >>= 1;
|
||||
}
|
||||
|
||||
/* Commands dont have data */
|
||||
if (!V3020_IS_COMMAND(address)) {
|
||||
for (i = 0; i < 8; i++) {
|
||||
writel((data & 1) << chip->leftshift, chip->ioaddress);
|
||||
data >>= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned char v3020_get_reg(struct v3020 *chip, unsigned char address)
|
||||
{
|
||||
unsigned int data=0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
writel((address & 1) << chip->leftshift, chip->ioaddress);
|
||||
address >>= 1;
|
||||
}
|
||||
|
||||
for (i = 0; i < 8; i++) {
|
||||
data >>= 1;
|
||||
if (readl(chip->ioaddress) & (1 << chip->leftshift))
|
||||
data |= 0x80;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static int v3020_read_time(struct device *dev, struct rtc_time *dt)
|
||||
{
|
||||
struct v3020 *chip = dev_get_drvdata(dev);
|
||||
int tmp;
|
||||
|
||||
/* Copy the current time to ram... */
|
||||
v3020_set_reg(chip, V3020_CMD_CLOCK2RAM, 0);
|
||||
|
||||
/* ...and then read constant values. */
|
||||
tmp = v3020_get_reg(chip, V3020_SECONDS);
|
||||
dt->tm_sec = BCD2BIN(tmp);
|
||||
tmp = v3020_get_reg(chip, V3020_MINUTES);
|
||||
dt->tm_min = BCD2BIN(tmp);
|
||||
tmp = v3020_get_reg(chip, V3020_HOURS);
|
||||
dt->tm_hour = BCD2BIN(tmp);
|
||||
tmp = v3020_get_reg(chip, V3020_MONTH_DAY);
|
||||
dt->tm_mday = BCD2BIN(tmp);
|
||||
tmp = v3020_get_reg(chip, V3020_MONTH);
|
||||
dt->tm_mon = BCD2BIN(tmp);
|
||||
tmp = v3020_get_reg(chip, V3020_WEEK_DAY);
|
||||
dt->tm_wday = BCD2BIN(tmp);
|
||||
tmp = v3020_get_reg(chip, V3020_YEAR);
|
||||
dt->tm_year = BCD2BIN(tmp)+100;
|
||||
|
||||
#ifdef DEBUG
|
||||
printk("\n%s : Read RTC values\n",__FUNCTION__);
|
||||
printk("tm_hour: %i\n",dt->tm_hour);
|
||||
printk("tm_min : %i\n",dt->tm_min);
|
||||
printk("tm_sec : %i\n",dt->tm_sec);
|
||||
printk("tm_year: %i\n",dt->tm_year);
|
||||
printk("tm_mon : %i\n",dt->tm_mon);
|
||||
printk("tm_mday: %i\n",dt->tm_mday);
|
||||
printk("tm_wday: %i\n",dt->tm_wday);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int v3020_set_time(struct device *dev, struct rtc_time *dt)
|
||||
{
|
||||
struct v3020 *chip = dev_get_drvdata(dev);
|
||||
|
||||
#ifdef DEBUG
|
||||
printk("\n%s : Setting RTC values\n",__FUNCTION__);
|
||||
printk("tm_sec : %i\n",dt->tm_sec);
|
||||
printk("tm_min : %i\n",dt->tm_min);
|
||||
printk("tm_hour: %i\n",dt->tm_hour);
|
||||
printk("tm_mday: %i\n",dt->tm_mday);
|
||||
printk("tm_wday: %i\n",dt->tm_wday);
|
||||
printk("tm_year: %i\n",dt->tm_year);
|
||||
#endif
|
||||
|
||||
/* Write all the values to ram... */
|
||||
v3020_set_reg(chip, V3020_SECONDS, BIN2BCD(dt->tm_sec));
|
||||
v3020_set_reg(chip, V3020_MINUTES, BIN2BCD(dt->tm_min));
|
||||
v3020_set_reg(chip, V3020_HOURS, BIN2BCD(dt->tm_hour));
|
||||
v3020_set_reg(chip, V3020_MONTH_DAY, BIN2BCD(dt->tm_mday));
|
||||
v3020_set_reg(chip, V3020_MONTH, BIN2BCD(dt->tm_mon));
|
||||
v3020_set_reg(chip, V3020_WEEK_DAY, BIN2BCD(dt->tm_wday));
|
||||
v3020_set_reg(chip, V3020_YEAR, BIN2BCD(dt->tm_year % 100));
|
||||
|
||||
/* ...and set the clock. */
|
||||
v3020_set_reg(chip, V3020_CMD_RAM2CLOCK, 0);
|
||||
|
||||
/* Compulab used this delay here. I dont know why,
|
||||
* the datasheet does not specify a delay. */
|
||||
/*mdelay(5);*/
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct rtc_class_ops v3020_rtc_ops = {
|
||||
.read_time = v3020_read_time,
|
||||
.set_time = v3020_set_time,
|
||||
};
|
||||
|
||||
static int rtc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct v3020_platform_data *pdata = pdev->dev.platform_data;
|
||||
struct v3020 *chip;
|
||||
struct rtc_device *rtc;
|
||||
int retval = -EBUSY;
|
||||
int i;
|
||||
int temp;
|
||||
|
||||
if (pdev->num_resources != 1)
|
||||
return -EBUSY;
|
||||
|
||||
if (pdev->resource[0].flags != IORESOURCE_MEM)
|
||||
return -EBUSY;
|
||||
|
||||
chip = kzalloc(sizeof *chip, GFP_KERNEL);
|
||||
if (!chip)
|
||||
return -ENOMEM;
|
||||
|
||||
chip->leftshift = pdata->leftshift;
|
||||
chip->ioaddress = ioremap(pdev->resource[0].start, 1);
|
||||
if (chip->ioaddress == NULL)
|
||||
goto err_chip;
|
||||
|
||||
/* Make sure the v3020 expects a communication cycle
|
||||
* by reading 8 times */
|
||||
for (i = 0; i < 8; i++)
|
||||
temp = readl(chip->ioaddress);
|
||||
|
||||
/* Test chip by doing a write/read sequence
|
||||
* to the chip ram */
|
||||
v3020_set_reg(chip, V3020_SECONDS, 0x33);
|
||||
if(v3020_get_reg(chip, V3020_SECONDS) != 0x33) {
|
||||
retval = -ENODEV;
|
||||
goto err_io;
|
||||
}
|
||||
|
||||
/* Make sure frequency measurment mode, test modes, and lock
|
||||
* are all disabled */
|
||||
v3020_set_reg(chip, V3020_STATUS_0, 0x0);
|
||||
|
||||
dev_info(&pdev->dev, "Chip available at physical address 0x%llx,"
|
||||
"data connected to D%d\n",
|
||||
(unsigned long long)pdev->resource[0].start,
|
||||
chip->leftshift);
|
||||
|
||||
platform_set_drvdata(pdev, chip);
|
||||
|
||||
rtc = rtc_device_register("v3020",
|
||||
&pdev->dev, &v3020_rtc_ops, THIS_MODULE);
|
||||
if (IS_ERR(rtc)) {
|
||||
retval = PTR_ERR(rtc);
|
||||
goto err_io;
|
||||
}
|
||||
chip->rtc = rtc;
|
||||
|
||||
return 0;
|
||||
|
||||
err_io:
|
||||
iounmap(chip->ioaddress);
|
||||
err_chip:
|
||||
kfree(chip);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int rtc_remove(struct platform_device *dev)
|
||||
{
|
||||
struct v3020 *chip = platform_get_drvdata(dev);
|
||||
struct rtc_device *rtc = chip->rtc;
|
||||
|
||||
if (rtc)
|
||||
rtc_device_unregister(rtc);
|
||||
|
||||
iounmap(chip->ioaddress);
|
||||
kfree(chip);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver rtc_device_driver = {
|
||||
.probe = rtc_probe,
|
||||
.remove = rtc_remove,
|
||||
.driver = {
|
||||
.name = "v3020",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static __init int v3020_init(void)
|
||||
{
|
||||
return platform_driver_register(&rtc_device_driver);
|
||||
}
|
||||
|
||||
static __exit void v3020_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&rtc_device_driver);
|
||||
}
|
||||
|
||||
module_init(v3020_init);
|
||||
module_exit(v3020_exit);
|
||||
|
||||
MODULE_DESCRIPTION("V3020 RTC");
|
||||
MODULE_AUTHOR("Raphael Assenat");
|
||||
MODULE_LICENSE("GPL");
|
||||
463
drivers/rtc/rtc-vr41xx.c
Normal file
463
drivers/rtc/rtc-vr41xx.c
Normal file
@@ -0,0 +1,463 @@
|
||||
/*
|
||||
* Driver for NEC VR4100 series Real Time Clock unit.
|
||||
*
|
||||
* Copyright (C) 2003-2006 Yoichi Yuasa <yoichi_yuasa@tripeaks.co.jp>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#include <linux/fs.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/rtc.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <asm/div64.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/uaccess.h>
|
||||
#include <asm/vr41xx/irq.h>
|
||||
|
||||
MODULE_AUTHOR("Yoichi Yuasa <yoichi_yuasa@tripeaks.co.jp>");
|
||||
MODULE_DESCRIPTION("NEC VR4100 series RTC driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
#define RTC1_TYPE1_START 0x0b0000c0UL
|
||||
#define RTC1_TYPE1_END 0x0b0000dfUL
|
||||
#define RTC2_TYPE1_START 0x0b0001c0UL
|
||||
#define RTC2_TYPE1_END 0x0b0001dfUL
|
||||
|
||||
#define RTC1_TYPE2_START 0x0f000100UL
|
||||
#define RTC1_TYPE2_END 0x0f00011fUL
|
||||
#define RTC2_TYPE2_START 0x0f000120UL
|
||||
#define RTC2_TYPE2_END 0x0f00013fUL
|
||||
|
||||
#define RTC1_SIZE 0x20
|
||||
#define RTC2_SIZE 0x20
|
||||
|
||||
/* RTC 1 registers */
|
||||
#define ETIMELREG 0x00
|
||||
#define ETIMEMREG 0x02
|
||||
#define ETIMEHREG 0x04
|
||||
/* RFU */
|
||||
#define ECMPLREG 0x08
|
||||
#define ECMPMREG 0x0a
|
||||
#define ECMPHREG 0x0c
|
||||
/* RFU */
|
||||
#define RTCL1LREG 0x10
|
||||
#define RTCL1HREG 0x12
|
||||
#define RTCL1CNTLREG 0x14
|
||||
#define RTCL1CNTHREG 0x16
|
||||
#define RTCL2LREG 0x18
|
||||
#define RTCL2HREG 0x1a
|
||||
#define RTCL2CNTLREG 0x1c
|
||||
#define RTCL2CNTHREG 0x1e
|
||||
|
||||
/* RTC 2 registers */
|
||||
#define TCLKLREG 0x00
|
||||
#define TCLKHREG 0x02
|
||||
#define TCLKCNTLREG 0x04
|
||||
#define TCLKCNTHREG 0x06
|
||||
/* RFU */
|
||||
#define RTCINTREG 0x1e
|
||||
#define TCLOCK_INT 0x08
|
||||
#define RTCLONG2_INT 0x04
|
||||
#define RTCLONG1_INT 0x02
|
||||
#define ELAPSEDTIME_INT 0x01
|
||||
|
||||
#define RTC_FREQUENCY 32768
|
||||
#define MAX_PERIODIC_RATE 6553
|
||||
|
||||
static void __iomem *rtc1_base;
|
||||
static void __iomem *rtc2_base;
|
||||
|
||||
#define rtc1_read(offset) readw(rtc1_base + (offset))
|
||||
#define rtc1_write(offset, value) writew((value), rtc1_base + (offset))
|
||||
|
||||
#define rtc2_read(offset) readw(rtc2_base + (offset))
|
||||
#define rtc2_write(offset, value) writew((value), rtc2_base + (offset))
|
||||
|
||||
static unsigned long epoch = 1970; /* Jan 1 1970 00:00:00 */
|
||||
|
||||
static DEFINE_SPINLOCK(rtc_lock);
|
||||
static char rtc_name[] = "RTC";
|
||||
static unsigned long periodic_frequency;
|
||||
static unsigned long periodic_count;
|
||||
|
||||
struct resource rtc_resource[2] = {
|
||||
{ .name = rtc_name,
|
||||
.flags = IORESOURCE_MEM, },
|
||||
{ .name = rtc_name,
|
||||
.flags = IORESOURCE_MEM, },
|
||||
};
|
||||
|
||||
static inline unsigned long read_elapsed_second(void)
|
||||
{
|
||||
|
||||
unsigned long first_low, first_mid, first_high;
|
||||
|
||||
unsigned long second_low, second_mid, second_high;
|
||||
|
||||
do {
|
||||
first_low = rtc1_read(ETIMELREG);
|
||||
first_mid = rtc1_read(ETIMEMREG);
|
||||
first_high = rtc1_read(ETIMEHREG);
|
||||
second_low = rtc1_read(ETIMELREG);
|
||||
second_mid = rtc1_read(ETIMEMREG);
|
||||
second_high = rtc1_read(ETIMEHREG);
|
||||
} while (first_low != second_low || first_mid != second_mid ||
|
||||
first_high != second_high);
|
||||
|
||||
return (first_high << 17) | (first_mid << 1) | (first_low >> 15);
|
||||
}
|
||||
|
||||
static inline void write_elapsed_second(unsigned long sec)
|
||||
{
|
||||
spin_lock_irq(&rtc_lock);
|
||||
|
||||
rtc1_write(ETIMELREG, (uint16_t)(sec << 15));
|
||||
rtc1_write(ETIMEMREG, (uint16_t)(sec >> 1));
|
||||
rtc1_write(ETIMEHREG, (uint16_t)(sec >> 17));
|
||||
|
||||
spin_unlock_irq(&rtc_lock);
|
||||
}
|
||||
|
||||
static void vr41xx_rtc_release(struct device *dev)
|
||||
{
|
||||
|
||||
spin_lock_irq(&rtc_lock);
|
||||
|
||||
rtc1_write(ECMPLREG, 0);
|
||||
rtc1_write(ECMPMREG, 0);
|
||||
rtc1_write(ECMPHREG, 0);
|
||||
rtc1_write(RTCL1LREG, 0);
|
||||
rtc1_write(RTCL1HREG, 0);
|
||||
|
||||
spin_unlock_irq(&rtc_lock);
|
||||
|
||||
disable_irq(ELAPSEDTIME_IRQ);
|
||||
disable_irq(RTCLONG1_IRQ);
|
||||
}
|
||||
|
||||
static int vr41xx_rtc_read_time(struct device *dev, struct rtc_time *time)
|
||||
{
|
||||
unsigned long epoch_sec, elapsed_sec;
|
||||
|
||||
epoch_sec = mktime(epoch, 1, 1, 0, 0, 0);
|
||||
elapsed_sec = read_elapsed_second();
|
||||
|
||||
rtc_time_to_tm(epoch_sec + elapsed_sec, time);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vr41xx_rtc_set_time(struct device *dev, struct rtc_time *time)
|
||||
{
|
||||
unsigned long epoch_sec, current_sec;
|
||||
|
||||
epoch_sec = mktime(epoch, 1, 1, 0, 0, 0);
|
||||
current_sec = mktime(time->tm_year + 1900, time->tm_mon + 1, time->tm_mday,
|
||||
time->tm_hour, time->tm_min, time->tm_sec);
|
||||
|
||||
write_elapsed_second(current_sec - epoch_sec);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vr41xx_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *wkalrm)
|
||||
{
|
||||
unsigned long low, mid, high;
|
||||
struct rtc_time *time = &wkalrm->time;
|
||||
|
||||
spin_lock_irq(&rtc_lock);
|
||||
|
||||
low = rtc1_read(ECMPLREG);
|
||||
mid = rtc1_read(ECMPMREG);
|
||||
high = rtc1_read(ECMPHREG);
|
||||
|
||||
spin_unlock_irq(&rtc_lock);
|
||||
|
||||
rtc_time_to_tm((high << 17) | (mid << 1) | (low >> 15), time);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vr41xx_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *wkalrm)
|
||||
{
|
||||
unsigned long alarm_sec;
|
||||
struct rtc_time *time = &wkalrm->time;
|
||||
|
||||
alarm_sec = mktime(time->tm_year + 1900, time->tm_mon + 1, time->tm_mday,
|
||||
time->tm_hour, time->tm_min, time->tm_sec);
|
||||
|
||||
spin_lock_irq(&rtc_lock);
|
||||
|
||||
rtc1_write(ECMPLREG, (uint16_t)(alarm_sec << 15));
|
||||
rtc1_write(ECMPMREG, (uint16_t)(alarm_sec >> 1));
|
||||
rtc1_write(ECMPHREG, (uint16_t)(alarm_sec >> 17));
|
||||
|
||||
spin_unlock_irq(&rtc_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vr41xx_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
unsigned long count;
|
||||
|
||||
switch (cmd) {
|
||||
case RTC_AIE_ON:
|
||||
enable_irq(ELAPSEDTIME_IRQ);
|
||||
break;
|
||||
case RTC_AIE_OFF:
|
||||
disable_irq(ELAPSEDTIME_IRQ);
|
||||
break;
|
||||
case RTC_PIE_ON:
|
||||
enable_irq(RTCLONG1_IRQ);
|
||||
break;
|
||||
case RTC_PIE_OFF:
|
||||
disable_irq(RTCLONG1_IRQ);
|
||||
break;
|
||||
case RTC_IRQP_READ:
|
||||
return put_user(periodic_frequency, (unsigned long __user *)arg);
|
||||
break;
|
||||
case RTC_IRQP_SET:
|
||||
if (arg > MAX_PERIODIC_RATE)
|
||||
return -EINVAL;
|
||||
|
||||
periodic_frequency = arg;
|
||||
|
||||
count = RTC_FREQUENCY;
|
||||
do_div(count, arg);
|
||||
|
||||
periodic_count = count;
|
||||
|
||||
spin_lock_irq(&rtc_lock);
|
||||
|
||||
rtc1_write(RTCL1LREG, count);
|
||||
rtc1_write(RTCL1HREG, count >> 16);
|
||||
|
||||
spin_unlock_irq(&rtc_lock);
|
||||
break;
|
||||
case RTC_EPOCH_READ:
|
||||
return put_user(epoch, (unsigned long __user *)arg);
|
||||
case RTC_EPOCH_SET:
|
||||
/* Doesn't support before 1900 */
|
||||
if (arg < 1900)
|
||||
return -EINVAL;
|
||||
epoch = arg;
|
||||
break;
|
||||
default:
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t elapsedtime_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct platform_device *pdev = (struct platform_device *)dev_id;
|
||||
struct rtc_device *rtc = platform_get_drvdata(pdev);
|
||||
|
||||
rtc2_write(RTCINTREG, ELAPSEDTIME_INT);
|
||||
|
||||
rtc_update_irq(&rtc->class_dev, 1, RTC_AF);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t rtclong1_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
struct platform_device *pdev = (struct platform_device *)dev_id;
|
||||
struct rtc_device *rtc = platform_get_drvdata(pdev);
|
||||
unsigned long count = periodic_count;
|
||||
|
||||
rtc2_write(RTCINTREG, RTCLONG1_INT);
|
||||
|
||||
rtc1_write(RTCL1LREG, count);
|
||||
rtc1_write(RTCL1HREG, count >> 16);
|
||||
|
||||
rtc_update_irq(&rtc->class_dev, 1, RTC_PF);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static const struct rtc_class_ops vr41xx_rtc_ops = {
|
||||
.release = vr41xx_rtc_release,
|
||||
.ioctl = vr41xx_rtc_ioctl,
|
||||
.read_time = vr41xx_rtc_read_time,
|
||||
.set_time = vr41xx_rtc_set_time,
|
||||
.read_alarm = vr41xx_rtc_read_alarm,
|
||||
.set_alarm = vr41xx_rtc_set_alarm,
|
||||
};
|
||||
|
||||
static int __devinit rtc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct rtc_device *rtc;
|
||||
unsigned int irq;
|
||||
int retval;
|
||||
|
||||
if (pdev->num_resources != 2)
|
||||
return -EBUSY;
|
||||
|
||||
rtc1_base = ioremap(pdev->resource[0].start, RTC1_SIZE);
|
||||
if (rtc1_base == NULL)
|
||||
return -EBUSY;
|
||||
|
||||
rtc2_base = ioremap(pdev->resource[1].start, RTC2_SIZE);
|
||||
if (rtc2_base == NULL) {
|
||||
iounmap(rtc1_base);
|
||||
rtc1_base = NULL;
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
rtc = rtc_device_register(rtc_name, &pdev->dev, &vr41xx_rtc_ops, THIS_MODULE);
|
||||
if (IS_ERR(rtc)) {
|
||||
iounmap(rtc1_base);
|
||||
iounmap(rtc2_base);
|
||||
rtc1_base = NULL;
|
||||
rtc2_base = NULL;
|
||||
return PTR_ERR(rtc);
|
||||
}
|
||||
|
||||
spin_lock_irq(&rtc_lock);
|
||||
|
||||
rtc1_write(ECMPLREG, 0);
|
||||
rtc1_write(ECMPMREG, 0);
|
||||
rtc1_write(ECMPHREG, 0);
|
||||
rtc1_write(RTCL1LREG, 0);
|
||||
rtc1_write(RTCL1HREG, 0);
|
||||
|
||||
spin_unlock_irq(&rtc_lock);
|
||||
|
||||
irq = ELAPSEDTIME_IRQ;
|
||||
retval = request_irq(irq, elapsedtime_interrupt, IRQF_DISABLED,
|
||||
"elapsed_time", pdev);
|
||||
if (retval == 0) {
|
||||
irq = RTCLONG1_IRQ;
|
||||
retval = request_irq(irq, rtclong1_interrupt, IRQF_DISABLED,
|
||||
"rtclong1", pdev);
|
||||
}
|
||||
|
||||
if (retval < 0) {
|
||||
printk(KERN_ERR "rtc: IRQ%d is busy\n", irq);
|
||||
rtc_device_unregister(rtc);
|
||||
if (irq == RTCLONG1_IRQ)
|
||||
free_irq(ELAPSEDTIME_IRQ, NULL);
|
||||
iounmap(rtc1_base);
|
||||
iounmap(rtc2_base);
|
||||
rtc1_base = NULL;
|
||||
rtc2_base = NULL;
|
||||
return retval;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, rtc);
|
||||
|
||||
disable_irq(ELAPSEDTIME_IRQ);
|
||||
disable_irq(RTCLONG1_IRQ);
|
||||
|
||||
printk(KERN_INFO "rtc: Real Time Clock of NEC VR4100 series\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devexit rtc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct rtc_device *rtc;
|
||||
|
||||
rtc = platform_get_drvdata(pdev);
|
||||
if (rtc != NULL)
|
||||
rtc_device_unregister(rtc);
|
||||
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
||||
free_irq(ELAPSEDTIME_IRQ, NULL);
|
||||
free_irq(RTCLONG1_IRQ, NULL);
|
||||
if (rtc1_base != NULL)
|
||||
iounmap(rtc1_base);
|
||||
if (rtc2_base != NULL)
|
||||
iounmap(rtc2_base);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_device *rtc_platform_device;
|
||||
|
||||
static struct platform_driver rtc_platform_driver = {
|
||||
.probe = rtc_probe,
|
||||
.remove = __devexit_p(rtc_remove),
|
||||
.driver = {
|
||||
.name = rtc_name,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init vr41xx_rtc_init(void)
|
||||
{
|
||||
int retval;
|
||||
|
||||
switch (current_cpu_data.cputype) {
|
||||
case CPU_VR4111:
|
||||
case CPU_VR4121:
|
||||
rtc_resource[0].start = RTC1_TYPE1_START;
|
||||
rtc_resource[0].end = RTC1_TYPE1_END;
|
||||
rtc_resource[1].start = RTC2_TYPE1_START;
|
||||
rtc_resource[1].end = RTC2_TYPE1_END;
|
||||
break;
|
||||
case CPU_VR4122:
|
||||
case CPU_VR4131:
|
||||
case CPU_VR4133:
|
||||
rtc_resource[0].start = RTC1_TYPE2_START;
|
||||
rtc_resource[0].end = RTC1_TYPE2_END;
|
||||
rtc_resource[1].start = RTC2_TYPE2_START;
|
||||
rtc_resource[1].end = RTC2_TYPE2_END;
|
||||
break;
|
||||
default:
|
||||
return -ENODEV;
|
||||
break;
|
||||
}
|
||||
|
||||
rtc_platform_device = platform_device_alloc("RTC", -1);
|
||||
if (rtc_platform_device == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
retval = platform_device_add_resources(rtc_platform_device,
|
||||
rtc_resource, ARRAY_SIZE(rtc_resource));
|
||||
|
||||
if (retval == 0)
|
||||
retval = platform_device_add(rtc_platform_device);
|
||||
|
||||
if (retval < 0) {
|
||||
platform_device_put(rtc_platform_device);
|
||||
return retval;
|
||||
}
|
||||
|
||||
retval = platform_driver_register(&rtc_platform_driver);
|
||||
if (retval < 0)
|
||||
platform_device_unregister(rtc_platform_device);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void __exit vr41xx_rtc_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&rtc_platform_driver);
|
||||
platform_device_unregister(rtc_platform_device);
|
||||
}
|
||||
|
||||
module_init(vr41xx_rtc_init);
|
||||
module_exit(vr41xx_rtc_exit);
|
||||
622
drivers/rtc/rtc-x1205.c
Normal file
622
drivers/rtc/rtc-x1205.c
Normal file
@@ -0,0 +1,622 @@
|
||||
/*
|
||||
* An i2c driver for the Xicor/Intersil X1205 RTC
|
||||
* Copyright 2004 Karen Spearel
|
||||
* Copyright 2005 Alessandro Zummo
|
||||
*
|
||||
* please send all reports to:
|
||||
* Karen Spearel <kas111 at gmail dot com>
|
||||
* Alessandro Zummo <a.zummo@towertech.it>
|
||||
*
|
||||
* based on a lot of other RTC drivers.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/bcd.h>
|
||||
#include <linux/rtc.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#define DRV_VERSION "1.0.7"
|
||||
|
||||
/* Addresses to scan: none. This chip is located at
|
||||
* 0x6f and uses a two bytes register addressing.
|
||||
* Two bytes need to be written to read a single register,
|
||||
* while most other chips just require one and take the second
|
||||
* one as the data to be written. To prevent corrupting
|
||||
* unknown chips, the user must explicitely set the probe parameter.
|
||||
*/
|
||||
|
||||
static unsigned short normal_i2c[] = { I2C_CLIENT_END };
|
||||
|
||||
/* Insmod parameters */
|
||||
I2C_CLIENT_INSMOD;
|
||||
|
||||
/* offsets into CCR area */
|
||||
|
||||
#define CCR_SEC 0
|
||||
#define CCR_MIN 1
|
||||
#define CCR_HOUR 2
|
||||
#define CCR_MDAY 3
|
||||
#define CCR_MONTH 4
|
||||
#define CCR_YEAR 5
|
||||
#define CCR_WDAY 6
|
||||
#define CCR_Y2K 7
|
||||
|
||||
#define X1205_REG_SR 0x3F /* status register */
|
||||
#define X1205_REG_Y2K 0x37
|
||||
#define X1205_REG_DW 0x36
|
||||
#define X1205_REG_YR 0x35
|
||||
#define X1205_REG_MO 0x34
|
||||
#define X1205_REG_DT 0x33
|
||||
#define X1205_REG_HR 0x32
|
||||
#define X1205_REG_MN 0x31
|
||||
#define X1205_REG_SC 0x30
|
||||
#define X1205_REG_DTR 0x13
|
||||
#define X1205_REG_ATR 0x12
|
||||
#define X1205_REG_INT 0x11
|
||||
#define X1205_REG_0 0x10
|
||||
#define X1205_REG_Y2K1 0x0F
|
||||
#define X1205_REG_DWA1 0x0E
|
||||
#define X1205_REG_YRA1 0x0D
|
||||
#define X1205_REG_MOA1 0x0C
|
||||
#define X1205_REG_DTA1 0x0B
|
||||
#define X1205_REG_HRA1 0x0A
|
||||
#define X1205_REG_MNA1 0x09
|
||||
#define X1205_REG_SCA1 0x08
|
||||
#define X1205_REG_Y2K0 0x07
|
||||
#define X1205_REG_DWA0 0x06
|
||||
#define X1205_REG_YRA0 0x05
|
||||
#define X1205_REG_MOA0 0x04
|
||||
#define X1205_REG_DTA0 0x03
|
||||
#define X1205_REG_HRA0 0x02
|
||||
#define X1205_REG_MNA0 0x01
|
||||
#define X1205_REG_SCA0 0x00
|
||||
|
||||
#define X1205_CCR_BASE 0x30 /* Base address of CCR */
|
||||
#define X1205_ALM0_BASE 0x00 /* Base address of ALARM0 */
|
||||
|
||||
#define X1205_SR_RTCF 0x01 /* Clock failure */
|
||||
#define X1205_SR_WEL 0x02 /* Write Enable Latch */
|
||||
#define X1205_SR_RWEL 0x04 /* Register Write Enable */
|
||||
|
||||
#define X1205_DTR_DTR0 0x01
|
||||
#define X1205_DTR_DTR1 0x02
|
||||
#define X1205_DTR_DTR2 0x04
|
||||
|
||||
#define X1205_HR_MIL 0x80 /* Set in ccr.hour for 24 hr mode */
|
||||
|
||||
/* Prototypes */
|
||||
static int x1205_attach(struct i2c_adapter *adapter);
|
||||
static int x1205_detach(struct i2c_client *client);
|
||||
static int x1205_probe(struct i2c_adapter *adapter, int address, int kind);
|
||||
|
||||
static struct i2c_driver x1205_driver = {
|
||||
.driver = {
|
||||
.name = "x1205",
|
||||
},
|
||||
.id = I2C_DRIVERID_X1205,
|
||||
.attach_adapter = &x1205_attach,
|
||||
.detach_client = &x1205_detach,
|
||||
};
|
||||
|
||||
/*
|
||||
* In the routines that deal directly with the x1205 hardware, we use
|
||||
* rtc_time -- month 0-11, hour 0-23, yr = calendar year-epoch
|
||||
* Epoch is initialized as 2000. Time is set to UTC.
|
||||
*/
|
||||
static int x1205_get_datetime(struct i2c_client *client, struct rtc_time *tm,
|
||||
unsigned char reg_base)
|
||||
{
|
||||
unsigned char dt_addr[2] = { 0, reg_base };
|
||||
|
||||
unsigned char buf[8];
|
||||
|
||||
struct i2c_msg msgs[] = {
|
||||
{ client->addr, 0, 2, dt_addr }, /* setup read ptr */
|
||||
{ client->addr, I2C_M_RD, 8, buf }, /* read date */
|
||||
};
|
||||
|
||||
/* read date registers */
|
||||
if ((i2c_transfer(client->adapter, &msgs[0], 2)) != 2) {
|
||||
dev_err(&client->dev, "%s: read error\n", __FUNCTION__);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
dev_dbg(&client->dev,
|
||||
"%s: raw read data - sec=%02x, min=%02x, hr=%02x, "
|
||||
"mday=%02x, mon=%02x, year=%02x, wday=%02x, y2k=%02x\n",
|
||||
__FUNCTION__,
|
||||
buf[0], buf[1], buf[2], buf[3],
|
||||
buf[4], buf[5], buf[6], buf[7]);
|
||||
|
||||
tm->tm_sec = BCD2BIN(buf[CCR_SEC]);
|
||||
tm->tm_min = BCD2BIN(buf[CCR_MIN]);
|
||||
tm->tm_hour = BCD2BIN(buf[CCR_HOUR] & 0x3F); /* hr is 0-23 */
|
||||
tm->tm_mday = BCD2BIN(buf[CCR_MDAY]);
|
||||
tm->tm_mon = BCD2BIN(buf[CCR_MONTH]) - 1; /* mon is 0-11 */
|
||||
tm->tm_year = BCD2BIN(buf[CCR_YEAR])
|
||||
+ (BCD2BIN(buf[CCR_Y2K]) * 100) - 1900;
|
||||
tm->tm_wday = buf[CCR_WDAY];
|
||||
|
||||
dev_dbg(&client->dev, "%s: tm is secs=%d, mins=%d, hours=%d, "
|
||||
"mday=%d, mon=%d, year=%d, wday=%d\n",
|
||||
__FUNCTION__,
|
||||
tm->tm_sec, tm->tm_min, tm->tm_hour,
|
||||
tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int x1205_get_status(struct i2c_client *client, unsigned char *sr)
|
||||
{
|
||||
static unsigned char sr_addr[2] = { 0, X1205_REG_SR };
|
||||
|
||||
struct i2c_msg msgs[] = {
|
||||
{ client->addr, 0, 2, sr_addr }, /* setup read ptr */
|
||||
{ client->addr, I2C_M_RD, 1, sr }, /* read status */
|
||||
};
|
||||
|
||||
/* read status register */
|
||||
if ((i2c_transfer(client->adapter, &msgs[0], 2)) != 2) {
|
||||
dev_err(&client->dev, "%s: read error\n", __FUNCTION__);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int x1205_set_datetime(struct i2c_client *client, struct rtc_time *tm,
|
||||
int datetoo, u8 reg_base)
|
||||
{
|
||||
int i, xfer;
|
||||
unsigned char buf[8];
|
||||
|
||||
static const unsigned char wel[3] = { 0, X1205_REG_SR,
|
||||
X1205_SR_WEL };
|
||||
|
||||
static const unsigned char rwel[3] = { 0, X1205_REG_SR,
|
||||
X1205_SR_WEL | X1205_SR_RWEL };
|
||||
|
||||
static const unsigned char diswe[3] = { 0, X1205_REG_SR, 0 };
|
||||
|
||||
dev_dbg(&client->dev,
|
||||
"%s: secs=%d, mins=%d, hours=%d\n",
|
||||
__FUNCTION__,
|
||||
tm->tm_sec, tm->tm_min, tm->tm_hour);
|
||||
|
||||
buf[CCR_SEC] = BIN2BCD(tm->tm_sec);
|
||||
buf[CCR_MIN] = BIN2BCD(tm->tm_min);
|
||||
|
||||
/* set hour and 24hr bit */
|
||||
buf[CCR_HOUR] = BIN2BCD(tm->tm_hour) | X1205_HR_MIL;
|
||||
|
||||
/* should we also set the date? */
|
||||
if (datetoo) {
|
||||
dev_dbg(&client->dev,
|
||||
"%s: mday=%d, mon=%d, year=%d, wday=%d\n",
|
||||
__FUNCTION__,
|
||||
tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday);
|
||||
|
||||
buf[CCR_MDAY] = BIN2BCD(tm->tm_mday);
|
||||
|
||||
/* month, 1 - 12 */
|
||||
buf[CCR_MONTH] = BIN2BCD(tm->tm_mon + 1);
|
||||
|
||||
/* year, since the rtc epoch*/
|
||||
buf[CCR_YEAR] = BIN2BCD(tm->tm_year % 100);
|
||||
buf[CCR_WDAY] = tm->tm_wday & 0x07;
|
||||
buf[CCR_Y2K] = BIN2BCD(tm->tm_year / 100);
|
||||
}
|
||||
|
||||
/* this sequence is required to unlock the chip */
|
||||
if ((xfer = i2c_master_send(client, wel, 3)) != 3) {
|
||||
dev_err(&client->dev, "%s: wel - %d\n", __FUNCTION__, xfer);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if ((xfer = i2c_master_send(client, rwel, 3)) != 3) {
|
||||
dev_err(&client->dev, "%s: rwel - %d\n", __FUNCTION__, xfer);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* write register's data */
|
||||
for (i = 0; i < (datetoo ? 8 : 3); i++) {
|
||||
unsigned char rdata[3] = { 0, reg_base + i, buf[i] };
|
||||
|
||||
xfer = i2c_master_send(client, rdata, 3);
|
||||
if (xfer != 3) {
|
||||
dev_err(&client->dev,
|
||||
"%s: xfer=%d addr=%02x, data=%02x\n",
|
||||
__FUNCTION__,
|
||||
xfer, rdata[1], rdata[2]);
|
||||
return -EIO;
|
||||
}
|
||||
};
|
||||
|
||||
/* disable further writes */
|
||||
if ((xfer = i2c_master_send(client, diswe, 3)) != 3) {
|
||||
dev_err(&client->dev, "%s: diswe - %d\n", __FUNCTION__, xfer);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int x1205_fix_osc(struct i2c_client *client)
|
||||
{
|
||||
int err;
|
||||
struct rtc_time tm;
|
||||
|
||||
tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
|
||||
|
||||
if ((err = x1205_set_datetime(client, &tm, 0, X1205_CCR_BASE)) < 0)
|
||||
dev_err(&client->dev,
|
||||
"unable to restart the oscillator\n");
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int x1205_get_dtrim(struct i2c_client *client, int *trim)
|
||||
{
|
||||
unsigned char dtr;
|
||||
static unsigned char dtr_addr[2] = { 0, X1205_REG_DTR };
|
||||
|
||||
struct i2c_msg msgs[] = {
|
||||
{ client->addr, 0, 2, dtr_addr }, /* setup read ptr */
|
||||
{ client->addr, I2C_M_RD, 1, &dtr }, /* read dtr */
|
||||
};
|
||||
|
||||
/* read dtr register */
|
||||
if ((i2c_transfer(client->adapter, &msgs[0], 2)) != 2) {
|
||||
dev_err(&client->dev, "%s: read error\n", __FUNCTION__);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
dev_dbg(&client->dev, "%s: raw dtr=%x\n", __FUNCTION__, dtr);
|
||||
|
||||
*trim = 0;
|
||||
|
||||
if (dtr & X1205_DTR_DTR0)
|
||||
*trim += 20;
|
||||
|
||||
if (dtr & X1205_DTR_DTR1)
|
||||
*trim += 10;
|
||||
|
||||
if (dtr & X1205_DTR_DTR2)
|
||||
*trim = -*trim;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int x1205_get_atrim(struct i2c_client *client, int *trim)
|
||||
{
|
||||
s8 atr;
|
||||
static unsigned char atr_addr[2] = { 0, X1205_REG_ATR };
|
||||
|
||||
struct i2c_msg msgs[] = {
|
||||
{ client->addr, 0, 2, atr_addr }, /* setup read ptr */
|
||||
{ client->addr, I2C_M_RD, 1, &atr }, /* read atr */
|
||||
};
|
||||
|
||||
/* read atr register */
|
||||
if ((i2c_transfer(client->adapter, &msgs[0], 2)) != 2) {
|
||||
dev_err(&client->dev, "%s: read error\n", __FUNCTION__);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
dev_dbg(&client->dev, "%s: raw atr=%x\n", __FUNCTION__, atr);
|
||||
|
||||
/* atr is a two's complement value on 6 bits,
|
||||
* perform sign extension. The formula is
|
||||
* Catr = (atr * 0.25pF) + 11.00pF.
|
||||
*/
|
||||
if (atr & 0x20)
|
||||
atr |= 0xC0;
|
||||
|
||||
dev_dbg(&client->dev, "%s: raw atr=%x (%d)\n", __FUNCTION__, atr, atr);
|
||||
|
||||
*trim = (atr * 250) + 11000;
|
||||
|
||||
dev_dbg(&client->dev, "%s: real=%d\n", __FUNCTION__, *trim);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct x1205_limit
|
||||
{
|
||||
unsigned char reg, mask, min, max;
|
||||
};
|
||||
|
||||
static int x1205_validate_client(struct i2c_client *client)
|
||||
{
|
||||
int i, xfer;
|
||||
|
||||
/* Probe array. We will read the register at the specified
|
||||
* address and check if the given bits are zero.
|
||||
*/
|
||||
static const unsigned char probe_zero_pattern[] = {
|
||||
/* register, mask */
|
||||
X1205_REG_SR, 0x18,
|
||||
X1205_REG_DTR, 0xF8,
|
||||
X1205_REG_ATR, 0xC0,
|
||||
X1205_REG_INT, 0x18,
|
||||
X1205_REG_0, 0xFF,
|
||||
};
|
||||
|
||||
static const struct x1205_limit probe_limits_pattern[] = {
|
||||
/* register, mask, min, max */
|
||||
{ X1205_REG_Y2K, 0xFF, 19, 20 },
|
||||
{ X1205_REG_DW, 0xFF, 0, 6 },
|
||||
{ X1205_REG_YR, 0xFF, 0, 99 },
|
||||
{ X1205_REG_MO, 0xFF, 0, 12 },
|
||||
{ X1205_REG_DT, 0xFF, 0, 31 },
|
||||
{ X1205_REG_HR, 0x7F, 0, 23 },
|
||||
{ X1205_REG_MN, 0xFF, 0, 59 },
|
||||
{ X1205_REG_SC, 0xFF, 0, 59 },
|
||||
{ X1205_REG_Y2K1, 0xFF, 19, 20 },
|
||||
{ X1205_REG_Y2K0, 0xFF, 19, 20 },
|
||||
};
|
||||
|
||||
/* check that registers have bits a 0 where expected */
|
||||
for (i = 0; i < ARRAY_SIZE(probe_zero_pattern); i += 2) {
|
||||
unsigned char buf;
|
||||
|
||||
unsigned char addr[2] = { 0, probe_zero_pattern[i] };
|
||||
|
||||
struct i2c_msg msgs[2] = {
|
||||
{ client->addr, 0, 2, addr },
|
||||
{ client->addr, I2C_M_RD, 1, &buf },
|
||||
};
|
||||
|
||||
if ((xfer = i2c_transfer(client->adapter, msgs, 2)) != 2) {
|
||||
dev_err(&client->dev,
|
||||
"%s: could not read register %x\n",
|
||||
__FUNCTION__, probe_zero_pattern[i]);
|
||||
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if ((buf & probe_zero_pattern[i+1]) != 0) {
|
||||
dev_err(&client->dev,
|
||||
"%s: register=%02x, zero pattern=%d, value=%x\n",
|
||||
__FUNCTION__, probe_zero_pattern[i], i, buf);
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
/* check limits (only registers with bcd values) */
|
||||
for (i = 0; i < ARRAY_SIZE(probe_limits_pattern); i++) {
|
||||
unsigned char reg, value;
|
||||
|
||||
unsigned char addr[2] = { 0, probe_limits_pattern[i].reg };
|
||||
|
||||
struct i2c_msg msgs[2] = {
|
||||
{ client->addr, 0, 2, addr },
|
||||
{ client->addr, I2C_M_RD, 1, ® },
|
||||
};
|
||||
|
||||
if ((xfer = i2c_transfer(client->adapter, msgs, 2)) != 2) {
|
||||
dev_err(&client->dev,
|
||||
"%s: could not read register %x\n",
|
||||
__FUNCTION__, probe_limits_pattern[i].reg);
|
||||
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
value = BCD2BIN(reg & probe_limits_pattern[i].mask);
|
||||
|
||||
if (value > probe_limits_pattern[i].max ||
|
||||
value < probe_limits_pattern[i].min) {
|
||||
dev_dbg(&client->dev,
|
||||
"%s: register=%x, lim pattern=%d, value=%d\n",
|
||||
__FUNCTION__, probe_limits_pattern[i].reg,
|
||||
i, value);
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int x1205_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
|
||||
{
|
||||
return x1205_get_datetime(to_i2c_client(dev),
|
||||
&alrm->time, X1205_ALM0_BASE);
|
||||
}
|
||||
|
||||
static int x1205_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
|
||||
{
|
||||
return x1205_set_datetime(to_i2c_client(dev),
|
||||
&alrm->time, 1, X1205_ALM0_BASE);
|
||||
}
|
||||
|
||||
static int x1205_rtc_read_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
return x1205_get_datetime(to_i2c_client(dev),
|
||||
tm, X1205_CCR_BASE);
|
||||
}
|
||||
|
||||
static int x1205_rtc_set_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
return x1205_set_datetime(to_i2c_client(dev),
|
||||
tm, 1, X1205_CCR_BASE);
|
||||
}
|
||||
|
||||
static int x1205_rtc_proc(struct device *dev, struct seq_file *seq)
|
||||
{
|
||||
int err, dtrim, atrim;
|
||||
|
||||
if ((err = x1205_get_dtrim(to_i2c_client(dev), &dtrim)) == 0)
|
||||
seq_printf(seq, "digital_trim\t: %d ppm\n", dtrim);
|
||||
|
||||
if ((err = x1205_get_atrim(to_i2c_client(dev), &atrim)) == 0)
|
||||
seq_printf(seq, "analog_trim\t: %d.%02d pF\n",
|
||||
atrim / 1000, atrim % 1000);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct rtc_class_ops x1205_rtc_ops = {
|
||||
.proc = x1205_rtc_proc,
|
||||
.read_time = x1205_rtc_read_time,
|
||||
.set_time = x1205_rtc_set_time,
|
||||
.read_alarm = x1205_rtc_read_alarm,
|
||||
.set_alarm = x1205_rtc_set_alarm,
|
||||
};
|
||||
|
||||
static ssize_t x1205_sysfs_show_atrim(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
int err, atrim;
|
||||
|
||||
err = x1205_get_atrim(to_i2c_client(dev), &atrim);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return sprintf(buf, "%d.%02d pF\n", atrim / 1000, atrim % 1000);
|
||||
}
|
||||
static DEVICE_ATTR(atrim, S_IRUGO, x1205_sysfs_show_atrim, NULL);
|
||||
|
||||
static ssize_t x1205_sysfs_show_dtrim(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
int err, dtrim;
|
||||
|
||||
err = x1205_get_dtrim(to_i2c_client(dev), &dtrim);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return sprintf(buf, "%d ppm\n", dtrim);
|
||||
}
|
||||
static DEVICE_ATTR(dtrim, S_IRUGO, x1205_sysfs_show_dtrim, NULL);
|
||||
|
||||
static int x1205_attach(struct i2c_adapter *adapter)
|
||||
{
|
||||
return i2c_probe(adapter, &addr_data, x1205_probe);
|
||||
}
|
||||
|
||||
static int x1205_probe(struct i2c_adapter *adapter, int address, int kind)
|
||||
{
|
||||
int err = 0;
|
||||
unsigned char sr;
|
||||
struct i2c_client *client;
|
||||
struct rtc_device *rtc;
|
||||
|
||||
dev_dbg(&adapter->dev, "%s\n", __FUNCTION__);
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) {
|
||||
err = -ENODEV;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (!(client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* I2C client */
|
||||
client->addr = address;
|
||||
client->driver = &x1205_driver;
|
||||
client->adapter = adapter;
|
||||
|
||||
strlcpy(client->name, x1205_driver.driver.name, I2C_NAME_SIZE);
|
||||
|
||||
/* Verify the chip is really an X1205 */
|
||||
if (kind < 0) {
|
||||
if (x1205_validate_client(client) < 0) {
|
||||
err = -ENODEV;
|
||||
goto exit_kfree;
|
||||
}
|
||||
}
|
||||
|
||||
/* Inform the i2c layer */
|
||||
if ((err = i2c_attach_client(client)))
|
||||
goto exit_kfree;
|
||||
|
||||
dev_info(&client->dev, "chip found, driver version " DRV_VERSION "\n");
|
||||
|
||||
rtc = rtc_device_register(x1205_driver.driver.name, &client->dev,
|
||||
&x1205_rtc_ops, THIS_MODULE);
|
||||
|
||||
if (IS_ERR(rtc)) {
|
||||
err = PTR_ERR(rtc);
|
||||
goto exit_detach;
|
||||
}
|
||||
|
||||
i2c_set_clientdata(client, rtc);
|
||||
|
||||
/* Check for power failures and eventualy enable the osc */
|
||||
if ((err = x1205_get_status(client, &sr)) == 0) {
|
||||
if (sr & X1205_SR_RTCF) {
|
||||
dev_err(&client->dev,
|
||||
"power failure detected, "
|
||||
"please set the clock\n");
|
||||
udelay(50);
|
||||
x1205_fix_osc(client);
|
||||
}
|
||||
}
|
||||
else
|
||||
dev_err(&client->dev, "couldn't read status\n");
|
||||
|
||||
err = device_create_file(&client->dev, &dev_attr_atrim);
|
||||
if (err) goto exit_devreg;
|
||||
err = device_create_file(&client->dev, &dev_attr_dtrim);
|
||||
if (err) goto exit_atrim;
|
||||
|
||||
return 0;
|
||||
|
||||
exit_atrim:
|
||||
device_remove_file(&client->dev, &dev_attr_atrim);
|
||||
|
||||
exit_devreg:
|
||||
rtc_device_unregister(rtc);
|
||||
|
||||
exit_detach:
|
||||
i2c_detach_client(client);
|
||||
|
||||
exit_kfree:
|
||||
kfree(client);
|
||||
|
||||
exit:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int x1205_detach(struct i2c_client *client)
|
||||
{
|
||||
int err;
|
||||
struct rtc_device *rtc = i2c_get_clientdata(client);
|
||||
|
||||
if (rtc)
|
||||
rtc_device_unregister(rtc);
|
||||
|
||||
if ((err = i2c_detach_client(client)))
|
||||
return err;
|
||||
|
||||
kfree(client);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init x1205_init(void)
|
||||
{
|
||||
return i2c_add_driver(&x1205_driver);
|
||||
}
|
||||
|
||||
static void __exit x1205_exit(void)
|
||||
{
|
||||
i2c_del_driver(&x1205_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR(
|
||||
"Karen Spearel <kas111 at gmail dot com>, "
|
||||
"Alessandro Zummo <a.zummo@towertech.it>");
|
||||
MODULE_DESCRIPTION("Xicor/Intersil X1205 RTC driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_VERSION(DRV_VERSION);
|
||||
|
||||
module_init(x1205_init);
|
||||
module_exit(x1205_exit);
|
||||
Reference in New Issue
Block a user