2010-08-11 09:02:20 +08:00
|
|
|
/*
|
2016-03-06 23:27:48 +08:00
|
|
|
* RTC client/driver for the Maxim/Dallas DS3232/DS3234 Real-Time Clock
|
2010-08-11 09:02:20 +08:00
|
|
|
*
|
drivers/rtc/rtc-ds3232.c: fix time range difference between linux and RTC chip
In linux rtc_time struct, tm_mon range is 0~11, tm_wday range is 0~6,
while in RTC HW REG, month range is 1~12, day of the week range is 1~7,
this patch adjusts difference of them.
The efect of this bug was that most of month will be operated on as the
next month by the hardware (When in Jan it maybe even worse). For
example, if in May, software wrote 4 to the hardware, which handled it as
April. Then the logic would be different between software and hardware,
which would cause weird things to happen.
Signed-off-by: Lei Xu <B33228@freescale.com>
Cc: Alessandro Zummo <a.zummo@towertech.it>
Cc: john stultz <johnstul@us.ibm.com>
Cc: Jack Lan <jack.lan@freescale.com>
Cc: <stable@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2011-02-26 06:44:23 +08:00
|
|
|
* Copyright (C) 2009-2011 Freescale Semiconductor.
|
2010-10-28 06:33:12 +08:00
|
|
|
* Author: Jack Lan <jack.lan@freescale.com>
|
2016-03-06 23:27:48 +08:00
|
|
|
* Copyright (C) 2008 MIMOMax Wireless Ltd.
|
2010-08-11 09:02:20 +08:00
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2015-04-17 03:46:14 +08:00
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
|
2010-08-11 09:02:20 +08:00
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/interrupt.h>
|
|
|
|
#include <linux/i2c.h>
|
2016-03-06 23:27:48 +08:00
|
|
|
#include <linux/spi/spi.h>
|
2010-08-11 09:02:20 +08:00
|
|
|
#include <linux/rtc.h>
|
|
|
|
#include <linux/bcd.h>
|
|
|
|
#include <linux/workqueue.h>
|
|
|
|
#include <linux/slab.h>
|
2016-03-06 23:27:47 +08:00
|
|
|
#include <linux/regmap.h>
|
2010-08-11 09:02:20 +08:00
|
|
|
|
|
|
|
#define DS3232_REG_SECONDS 0x00
|
|
|
|
#define DS3232_REG_MINUTES 0x01
|
|
|
|
#define DS3232_REG_HOURS 0x02
|
|
|
|
#define DS3232_REG_AMPM 0x02
|
|
|
|
#define DS3232_REG_DAY 0x03
|
|
|
|
#define DS3232_REG_DATE 0x04
|
|
|
|
#define DS3232_REG_MONTH 0x05
|
|
|
|
#define DS3232_REG_CENTURY 0x05
|
|
|
|
#define DS3232_REG_YEAR 0x06
|
|
|
|
#define DS3232_REG_ALARM1 0x07 /* Alarm 1 BASE */
|
|
|
|
#define DS3232_REG_ALARM2 0x0B /* Alarm 2 BASE */
|
|
|
|
#define DS3232_REG_CR 0x0E /* Control register */
|
|
|
|
# define DS3232_REG_CR_nEOSC 0x80
|
|
|
|
# define DS3232_REG_CR_INTCN 0x04
|
|
|
|
# define DS3232_REG_CR_A2IE 0x02
|
|
|
|
# define DS3232_REG_CR_A1IE 0x01
|
|
|
|
|
|
|
|
#define DS3232_REG_SR 0x0F /* control/status register */
|
|
|
|
# define DS3232_REG_SR_OSF 0x80
|
|
|
|
# define DS3232_REG_SR_BSY 0x04
|
|
|
|
# define DS3232_REG_SR_A2F 0x02
|
|
|
|
# define DS3232_REG_SR_A1F 0x01
|
|
|
|
|
|
|
|
struct ds3232 {
|
2016-03-06 23:27:47 +08:00
|
|
|
struct device *dev;
|
|
|
|
struct regmap *regmap;
|
|
|
|
int irq;
|
2010-08-11 09:02:20 +08:00
|
|
|
struct rtc_device *rtc;
|
|
|
|
struct work_struct work;
|
|
|
|
|
|
|
|
/* The mutex protects alarm operations, and prevents a race
|
|
|
|
* between the enable_irq() in the workqueue and the free_irq()
|
|
|
|
* in the remove function.
|
|
|
|
*/
|
|
|
|
struct mutex mutex;
|
2014-04-04 05:50:08 +08:00
|
|
|
bool suspended;
|
2010-08-11 09:02:20 +08:00
|
|
|
int exiting;
|
|
|
|
};
|
|
|
|
|
2016-03-06 23:27:47 +08:00
|
|
|
static int ds3232_check_rtc_status(struct device *dev)
|
2010-08-11 09:02:20 +08:00
|
|
|
{
|
2016-03-06 23:27:47 +08:00
|
|
|
struct ds3232 *ds3232 = dev_get_drvdata(dev);
|
2010-08-11 09:02:20 +08:00
|
|
|
int ret = 0;
|
|
|
|
int control, stat;
|
|
|
|
|
2016-03-06 23:27:47 +08:00
|
|
|
ret = regmap_read(ds3232->regmap, DS3232_REG_SR, &stat);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
2010-08-11 09:02:20 +08:00
|
|
|
|
|
|
|
if (stat & DS3232_REG_SR_OSF)
|
2016-03-06 23:27:47 +08:00
|
|
|
dev_warn(dev,
|
2010-08-11 09:02:20 +08:00
|
|
|
"oscillator discontinuity flagged, "
|
|
|
|
"time unreliable\n");
|
|
|
|
|
|
|
|
stat &= ~(DS3232_REG_SR_OSF | DS3232_REG_SR_A1F | DS3232_REG_SR_A2F);
|
|
|
|
|
2016-03-06 23:27:47 +08:00
|
|
|
ret = regmap_write(ds3232->regmap, DS3232_REG_SR, stat);
|
|
|
|
if (ret)
|
2010-08-11 09:02:20 +08:00
|
|
|
return ret;
|
|
|
|
|
|
|
|
/* If the alarm is pending, clear it before requesting
|
|
|
|
* the interrupt, so an interrupt event isn't reported
|
|
|
|
* before everything is initialized.
|
|
|
|
*/
|
|
|
|
|
2016-03-06 23:27:47 +08:00
|
|
|
ret = regmap_read(ds3232->regmap, DS3232_REG_CR, &control);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
2010-08-11 09:02:20 +08:00
|
|
|
|
|
|
|
control &= ~(DS3232_REG_CR_A1IE | DS3232_REG_CR_A2IE);
|
|
|
|
control |= DS3232_REG_CR_INTCN;
|
|
|
|
|
2016-03-06 23:27:47 +08:00
|
|
|
return regmap_write(ds3232->regmap, DS3232_REG_CR, control);
|
2010-08-11 09:02:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int ds3232_read_time(struct device *dev, struct rtc_time *time)
|
|
|
|
{
|
2016-03-06 23:27:47 +08:00
|
|
|
struct ds3232 *ds3232 = dev_get_drvdata(dev);
|
2010-08-11 09:02:20 +08:00
|
|
|
int ret;
|
|
|
|
u8 buf[7];
|
|
|
|
unsigned int year, month, day, hour, minute, second;
|
|
|
|
unsigned int week, twelve_hr, am_pm;
|
|
|
|
unsigned int century, add_century = 0;
|
|
|
|
|
2016-03-06 23:27:47 +08:00
|
|
|
ret = regmap_bulk_read(ds3232->regmap, DS3232_REG_SECONDS, buf, 7);
|
|
|
|
if (ret)
|
2010-08-11 09:02:20 +08:00
|
|
|
return ret;
|
|
|
|
|
|
|
|
second = buf[0];
|
|
|
|
minute = buf[1];
|
|
|
|
hour = buf[2];
|
|
|
|
week = buf[3];
|
|
|
|
day = buf[4];
|
|
|
|
month = buf[5];
|
|
|
|
year = buf[6];
|
|
|
|
|
|
|
|
/* Extract additional information for AM/PM and century */
|
|
|
|
|
|
|
|
twelve_hr = hour & 0x40;
|
|
|
|
am_pm = hour & 0x20;
|
|
|
|
century = month & 0x80;
|
|
|
|
|
|
|
|
/* Write to rtc_time structure */
|
|
|
|
|
|
|
|
time->tm_sec = bcd2bin(second);
|
|
|
|
time->tm_min = bcd2bin(minute);
|
|
|
|
if (twelve_hr) {
|
|
|
|
/* Convert to 24 hr */
|
|
|
|
if (am_pm)
|
|
|
|
time->tm_hour = bcd2bin(hour & 0x1F) + 12;
|
|
|
|
else
|
|
|
|
time->tm_hour = bcd2bin(hour & 0x1F);
|
|
|
|
} else {
|
|
|
|
time->tm_hour = bcd2bin(hour);
|
|
|
|
}
|
|
|
|
|
drivers/rtc/rtc-ds3232.c: fix time range difference between linux and RTC chip
In linux rtc_time struct, tm_mon range is 0~11, tm_wday range is 0~6,
while in RTC HW REG, month range is 1~12, day of the week range is 1~7,
this patch adjusts difference of them.
The efect of this bug was that most of month will be operated on as the
next month by the hardware (When in Jan it maybe even worse). For
example, if in May, software wrote 4 to the hardware, which handled it as
April. Then the logic would be different between software and hardware,
which would cause weird things to happen.
Signed-off-by: Lei Xu <B33228@freescale.com>
Cc: Alessandro Zummo <a.zummo@towertech.it>
Cc: john stultz <johnstul@us.ibm.com>
Cc: Jack Lan <jack.lan@freescale.com>
Cc: <stable@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2011-02-26 06:44:23 +08:00
|
|
|
/* Day of the week in linux range is 0~6 while 1~7 in RTC chip */
|
|
|
|
time->tm_wday = bcd2bin(week) - 1;
|
2010-08-11 09:02:20 +08:00
|
|
|
time->tm_mday = bcd2bin(day);
|
drivers/rtc/rtc-ds3232.c: fix time range difference between linux and RTC chip
In linux rtc_time struct, tm_mon range is 0~11, tm_wday range is 0~6,
while in RTC HW REG, month range is 1~12, day of the week range is 1~7,
this patch adjusts difference of them.
The efect of this bug was that most of month will be operated on as the
next month by the hardware (When in Jan it maybe even worse). For
example, if in May, software wrote 4 to the hardware, which handled it as
April. Then the logic would be different between software and hardware,
which would cause weird things to happen.
Signed-off-by: Lei Xu <B33228@freescale.com>
Cc: Alessandro Zummo <a.zummo@towertech.it>
Cc: john stultz <johnstul@us.ibm.com>
Cc: Jack Lan <jack.lan@freescale.com>
Cc: <stable@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2011-02-26 06:44:23 +08:00
|
|
|
/* linux tm_mon range:0~11, while month range is 1~12 in RTC chip */
|
|
|
|
time->tm_mon = bcd2bin(month & 0x7F) - 1;
|
2010-08-11 09:02:20 +08:00
|
|
|
if (century)
|
|
|
|
add_century = 100;
|
|
|
|
|
|
|
|
time->tm_year = bcd2bin(year) + add_century;
|
|
|
|
|
|
|
|
return rtc_valid_tm(time);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ds3232_set_time(struct device *dev, struct rtc_time *time)
|
|
|
|
{
|
2016-03-06 23:27:47 +08:00
|
|
|
struct ds3232 *ds3232 = dev_get_drvdata(dev);
|
2010-08-11 09:02:20 +08:00
|
|
|
u8 buf[7];
|
|
|
|
|
|
|
|
/* Extract time from rtc_time and load into ds3232*/
|
|
|
|
|
|
|
|
buf[0] = bin2bcd(time->tm_sec);
|
|
|
|
buf[1] = bin2bcd(time->tm_min);
|
|
|
|
buf[2] = bin2bcd(time->tm_hour);
|
drivers/rtc/rtc-ds3232.c: fix time range difference between linux and RTC chip
In linux rtc_time struct, tm_mon range is 0~11, tm_wday range is 0~6,
while in RTC HW REG, month range is 1~12, day of the week range is 1~7,
this patch adjusts difference of them.
The efect of this bug was that most of month will be operated on as the
next month by the hardware (When in Jan it maybe even worse). For
example, if in May, software wrote 4 to the hardware, which handled it as
April. Then the logic would be different between software and hardware,
which would cause weird things to happen.
Signed-off-by: Lei Xu <B33228@freescale.com>
Cc: Alessandro Zummo <a.zummo@towertech.it>
Cc: john stultz <johnstul@us.ibm.com>
Cc: Jack Lan <jack.lan@freescale.com>
Cc: <stable@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2011-02-26 06:44:23 +08:00
|
|
|
/* Day of the week in linux range is 0~6 while 1~7 in RTC chip */
|
|
|
|
buf[3] = bin2bcd(time->tm_wday + 1);
|
2010-08-11 09:02:20 +08:00
|
|
|
buf[4] = bin2bcd(time->tm_mday); /* Date */
|
drivers/rtc/rtc-ds3232.c: fix time range difference between linux and RTC chip
In linux rtc_time struct, tm_mon range is 0~11, tm_wday range is 0~6,
while in RTC HW REG, month range is 1~12, day of the week range is 1~7,
this patch adjusts difference of them.
The efect of this bug was that most of month will be operated on as the
next month by the hardware (When in Jan it maybe even worse). For
example, if in May, software wrote 4 to the hardware, which handled it as
April. Then the logic would be different between software and hardware,
which would cause weird things to happen.
Signed-off-by: Lei Xu <B33228@freescale.com>
Cc: Alessandro Zummo <a.zummo@towertech.it>
Cc: john stultz <johnstul@us.ibm.com>
Cc: Jack Lan <jack.lan@freescale.com>
Cc: <stable@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2011-02-26 06:44:23 +08:00
|
|
|
/* linux tm_mon range:0~11, while month range is 1~12 in RTC chip */
|
|
|
|
buf[5] = bin2bcd(time->tm_mon + 1);
|
2010-08-11 09:02:20 +08:00
|
|
|
if (time->tm_year >= 100) {
|
|
|
|
buf[5] |= 0x80;
|
|
|
|
buf[6] = bin2bcd(time->tm_year - 100);
|
|
|
|
} else {
|
|
|
|
buf[6] = bin2bcd(time->tm_year);
|
|
|
|
}
|
|
|
|
|
2016-03-06 23:27:47 +08:00
|
|
|
return regmap_bulk_write(ds3232->regmap, DS3232_REG_SECONDS, buf, 7);
|
2010-08-11 09:02:20 +08:00
|
|
|
}
|
|
|
|
|
2010-10-28 06:33:12 +08:00
|
|
|
/*
|
|
|
|
* DS3232 has two alarm, we only use alarm1
|
|
|
|
* According to linux specification, only support one-shot alarm
|
|
|
|
* no periodic alarm mode
|
|
|
|
*/
|
|
|
|
static int ds3232_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
|
|
|
|
{
|
2016-03-06 23:27:47 +08:00
|
|
|
struct ds3232 *ds3232 = dev_get_drvdata(dev);
|
2010-10-28 06:33:12 +08:00
|
|
|
int control, stat;
|
|
|
|
int ret;
|
|
|
|
u8 buf[4];
|
|
|
|
|
|
|
|
mutex_lock(&ds3232->mutex);
|
|
|
|
|
2016-03-06 23:27:47 +08:00
|
|
|
ret = regmap_read(ds3232->regmap, DS3232_REG_SR, &stat);
|
|
|
|
if (ret)
|
2010-10-28 06:33:12 +08:00
|
|
|
goto out;
|
2016-03-06 23:27:47 +08:00
|
|
|
ret = regmap_read(ds3232->regmap, DS3232_REG_CR, &control);
|
|
|
|
if (ret)
|
2010-10-28 06:33:12 +08:00
|
|
|
goto out;
|
2016-03-06 23:27:47 +08:00
|
|
|
ret = regmap_bulk_read(ds3232->regmap, DS3232_REG_ALARM1, buf, 4);
|
|
|
|
if (ret)
|
2010-10-28 06:33:12 +08:00
|
|
|
goto out;
|
|
|
|
|
|
|
|
alarm->time.tm_sec = bcd2bin(buf[0] & 0x7F);
|
|
|
|
alarm->time.tm_min = bcd2bin(buf[1] & 0x7F);
|
|
|
|
alarm->time.tm_hour = bcd2bin(buf[2] & 0x7F);
|
|
|
|
alarm->time.tm_mday = bcd2bin(buf[3] & 0x7F);
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
alarm->enabled = !!(control & DS3232_REG_CR_A1IE);
|
|
|
|
alarm->pending = !!(stat & DS3232_REG_SR_A1F);
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
out:
|
|
|
|
mutex_unlock(&ds3232->mutex);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* linux rtc-module does not support wday alarm
|
|
|
|
* and only 24h time mode supported indeed
|
|
|
|
*/
|
|
|
|
static int ds3232_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
|
|
|
|
{
|
2016-03-06 23:27:47 +08:00
|
|
|
struct ds3232 *ds3232 = dev_get_drvdata(dev);
|
2010-10-28 06:33:12 +08:00
|
|
|
int control, stat;
|
|
|
|
int ret;
|
|
|
|
u8 buf[4];
|
|
|
|
|
2016-03-06 23:27:47 +08:00
|
|
|
if (ds3232->irq <= 0)
|
2010-10-28 06:33:12 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
mutex_lock(&ds3232->mutex);
|
|
|
|
|
|
|
|
buf[0] = bin2bcd(alarm->time.tm_sec);
|
|
|
|
buf[1] = bin2bcd(alarm->time.tm_min);
|
|
|
|
buf[2] = bin2bcd(alarm->time.tm_hour);
|
|
|
|
buf[3] = bin2bcd(alarm->time.tm_mday);
|
|
|
|
|
|
|
|
/* clear alarm interrupt enable bit */
|
2016-03-06 23:27:47 +08:00
|
|
|
ret = regmap_read(ds3232->regmap, DS3232_REG_CR, &control);
|
|
|
|
if (ret)
|
2010-10-28 06:33:12 +08:00
|
|
|
goto out;
|
|
|
|
control &= ~(DS3232_REG_CR_A1IE | DS3232_REG_CR_A2IE);
|
2016-03-06 23:27:47 +08:00
|
|
|
ret = regmap_write(ds3232->regmap, DS3232_REG_CR, control);
|
|
|
|
if (ret)
|
2010-10-28 06:33:12 +08:00
|
|
|
goto out;
|
|
|
|
|
|
|
|
/* clear any pending alarm flag */
|
2016-03-06 23:27:47 +08:00
|
|
|
ret = regmap_read(ds3232->regmap, DS3232_REG_SR, &stat);
|
|
|
|
if (ret)
|
2010-10-28 06:33:12 +08:00
|
|
|
goto out;
|
|
|
|
stat &= ~(DS3232_REG_SR_A1F | DS3232_REG_SR_A2F);
|
2016-03-06 23:27:47 +08:00
|
|
|
ret = regmap_write(ds3232->regmap, DS3232_REG_SR, stat);
|
|
|
|
if (ret)
|
2010-10-28 06:33:12 +08:00
|
|
|
goto out;
|
|
|
|
|
2016-03-06 23:27:47 +08:00
|
|
|
ret = regmap_bulk_write(ds3232->regmap, DS3232_REG_ALARM1, buf, 4);
|
2016-03-06 23:27:50 +08:00
|
|
|
if (ret)
|
|
|
|
goto out;
|
2010-10-28 06:33:12 +08:00
|
|
|
|
|
|
|
if (alarm->enabled) {
|
|
|
|
control |= DS3232_REG_CR_A1IE;
|
2016-03-06 23:27:47 +08:00
|
|
|
ret = regmap_write(ds3232->regmap, DS3232_REG_CR, control);
|
2010-10-28 06:33:12 +08:00
|
|
|
}
|
|
|
|
out:
|
|
|
|
mutex_unlock(&ds3232->mutex);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-03-06 23:27:50 +08:00
|
|
|
static int ds3232_update_alarm(struct device *dev, unsigned int enabled)
|
2010-10-28 06:33:12 +08:00
|
|
|
{
|
2016-03-06 23:27:47 +08:00
|
|
|
struct ds3232 *ds3232 = dev_get_drvdata(dev);
|
2010-10-28 06:33:12 +08:00
|
|
|
int control;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
mutex_lock(&ds3232->mutex);
|
|
|
|
|
2016-03-06 23:27:47 +08:00
|
|
|
ret = regmap_read(ds3232->regmap, DS3232_REG_CR, &control);
|
|
|
|
if (ret)
|
2010-10-28 06:33:12 +08:00
|
|
|
goto unlock;
|
|
|
|
|
2016-03-06 23:27:51 +08:00
|
|
|
if (enabled)
|
2010-10-28 06:33:12 +08:00
|
|
|
/* enable alarm1 interrupt */
|
|
|
|
control |= DS3232_REG_CR_A1IE;
|
|
|
|
else
|
|
|
|
/* disable alarm1 interrupt */
|
|
|
|
control &= ~(DS3232_REG_CR_A1IE);
|
2016-03-06 23:27:50 +08:00
|
|
|
ret = regmap_write(ds3232->regmap, DS3232_REG_CR, control);
|
2010-10-28 06:33:12 +08:00
|
|
|
|
|
|
|
unlock:
|
|
|
|
mutex_unlock(&ds3232->mutex);
|
2016-03-06 23:27:50 +08:00
|
|
|
|
|
|
|
return ret;
|
2010-10-28 06:33:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int ds3232_alarm_irq_enable(struct device *dev, unsigned int enabled)
|
|
|
|
{
|
2016-03-06 23:27:47 +08:00
|
|
|
struct ds3232 *ds3232 = dev_get_drvdata(dev);
|
2010-10-28 06:33:12 +08:00
|
|
|
|
2016-03-06 23:27:47 +08:00
|
|
|
if (ds3232->irq <= 0)
|
2010-10-28 06:33:12 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
2016-03-06 23:27:50 +08:00
|
|
|
return ds3232_update_alarm(dev, enabled);
|
2010-10-28 06:33:12 +08:00
|
|
|
}
|
|
|
|
|
2010-08-11 09:02:20 +08:00
|
|
|
static irqreturn_t ds3232_irq(int irq, void *dev_id)
|
|
|
|
{
|
2016-03-06 23:27:47 +08:00
|
|
|
struct device *dev = dev_id;
|
|
|
|
struct ds3232 *ds3232 = dev_get_drvdata(dev);
|
2010-08-11 09:02:20 +08:00
|
|
|
|
|
|
|
disable_irq_nosync(irq);
|
2014-04-04 05:50:08 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* If rtc as a wakeup source, can't schedule the work
|
|
|
|
* at system resume flow, because at this time the i2c bus
|
|
|
|
* has not been resumed.
|
|
|
|
*/
|
|
|
|
if (!ds3232->suspended)
|
|
|
|
schedule_work(&ds3232->work);
|
|
|
|
|
2010-08-11 09:02:20 +08:00
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ds3232_work(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct ds3232 *ds3232 = container_of(work, struct ds3232, work);
|
2016-03-06 23:27:47 +08:00
|
|
|
int ret;
|
2010-08-11 09:02:20 +08:00
|
|
|
int stat, control;
|
|
|
|
|
|
|
|
mutex_lock(&ds3232->mutex);
|
|
|
|
|
2016-03-06 23:27:47 +08:00
|
|
|
ret = regmap_read(ds3232->regmap, DS3232_REG_SR, &stat);
|
|
|
|
if (ret)
|
2010-08-11 09:02:20 +08:00
|
|
|
goto unlock;
|
|
|
|
|
|
|
|
if (stat & DS3232_REG_SR_A1F) {
|
2016-03-06 23:27:47 +08:00
|
|
|
ret = regmap_read(ds3232->regmap, DS3232_REG_CR, &control);
|
|
|
|
if (ret) {
|
2015-04-17 03:46:14 +08:00
|
|
|
pr_warn("Read Control Register error - Disable IRQ%d\n",
|
2016-03-06 23:27:47 +08:00
|
|
|
ds3232->irq);
|
2014-04-04 05:50:08 +08:00
|
|
|
} else {
|
|
|
|
/* disable alarm1 interrupt */
|
|
|
|
control &= ~(DS3232_REG_CR_A1IE);
|
2016-03-06 23:27:50 +08:00
|
|
|
ret = regmap_write(ds3232->regmap, DS3232_REG_CR,
|
|
|
|
control);
|
|
|
|
if (ret) {
|
|
|
|
dev_warn(ds3232->dev,
|
|
|
|
"Write Control Register error %d\n",
|
|
|
|
ret);
|
|
|
|
goto unlock;
|
|
|
|
}
|
2014-04-04 05:50:08 +08:00
|
|
|
|
|
|
|
/* clear the alarm pend flag */
|
|
|
|
stat &= ~DS3232_REG_SR_A1F;
|
2016-03-06 23:27:50 +08:00
|
|
|
ret = regmap_write(ds3232->regmap, DS3232_REG_SR, stat);
|
|
|
|
if (ret) {
|
|
|
|
dev_warn(ds3232->dev,
|
|
|
|
"Write Status Register error %d\n",
|
|
|
|
ret);
|
|
|
|
goto unlock;
|
|
|
|
}
|
2014-04-04 05:50:08 +08:00
|
|
|
|
|
|
|
rtc_update_irq(ds3232->rtc, 1, RTC_AF | RTC_IRQF);
|
|
|
|
|
|
|
|
if (!ds3232->exiting)
|
2016-03-06 23:27:47 +08:00
|
|
|
enable_irq(ds3232->irq);
|
2014-04-04 05:50:08 +08:00
|
|
|
}
|
2010-08-11 09:02:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
unlock:
|
|
|
|
mutex_unlock(&ds3232->mutex);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct rtc_class_ops ds3232_rtc_ops = {
|
|
|
|
.read_time = ds3232_read_time,
|
|
|
|
.set_time = ds3232_set_time,
|
2010-10-28 06:33:12 +08:00
|
|
|
.read_alarm = ds3232_read_alarm,
|
|
|
|
.set_alarm = ds3232_set_alarm,
|
|
|
|
.alarm_irq_enable = ds3232_alarm_irq_enable,
|
2010-08-11 09:02:20 +08:00
|
|
|
};
|
|
|
|
|
2016-03-06 23:27:47 +08:00
|
|
|
static int ds3232_probe(struct device *dev, struct regmap *regmap, int irq,
|
|
|
|
const char *name)
|
2010-08-11 09:02:20 +08:00
|
|
|
{
|
|
|
|
struct ds3232 *ds3232;
|
|
|
|
int ret;
|
|
|
|
|
2016-03-06 23:27:47 +08:00
|
|
|
ds3232 = devm_kzalloc(dev, sizeof(*ds3232), GFP_KERNEL);
|
2010-08-11 09:02:20 +08:00
|
|
|
if (!ds3232)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2016-03-06 23:27:47 +08:00
|
|
|
ds3232->regmap = regmap;
|
|
|
|
ds3232->irq = irq;
|
|
|
|
ds3232->dev = dev;
|
|
|
|
dev_set_drvdata(dev, ds3232);
|
2010-08-11 09:02:20 +08:00
|
|
|
|
|
|
|
INIT_WORK(&ds3232->work, ds3232_work);
|
|
|
|
mutex_init(&ds3232->mutex);
|
|
|
|
|
2016-03-06 23:27:47 +08:00
|
|
|
ret = ds3232_check_rtc_status(dev);
|
2010-08-11 09:02:20 +08:00
|
|
|
if (ret)
|
2013-04-30 07:20:31 +08:00
|
|
|
return ret;
|
2010-08-11 09:02:20 +08:00
|
|
|
|
2016-03-06 23:27:47 +08:00
|
|
|
if (ds3232->irq > 0) {
|
|
|
|
ret = devm_request_irq(dev, ds3232->irq, ds3232_irq,
|
|
|
|
IRQF_SHARED, name, dev);
|
2010-08-11 09:02:20 +08:00
|
|
|
if (ret) {
|
2016-03-06 23:27:47 +08:00
|
|
|
ds3232->irq = 0;
|
|
|
|
dev_err(dev, "unable to request IRQ\n");
|
|
|
|
} else
|
|
|
|
device_init_wakeup(dev, 1);
|
2010-08-11 09:02:20 +08:00
|
|
|
}
|
2016-03-06 23:27:47 +08:00
|
|
|
ds3232->rtc = devm_rtc_device_register(dev, name, &ds3232_rtc_ops,
|
|
|
|
THIS_MODULE);
|
|
|
|
|
2014-04-04 05:50:08 +08:00
|
|
|
return PTR_ERR_OR_ZERO(ds3232->rtc);
|
2010-08-11 09:02:20 +08:00
|
|
|
}
|
|
|
|
|
2016-03-06 23:27:47 +08:00
|
|
|
static int ds3232_remove(struct device *dev)
|
2010-08-11 09:02:20 +08:00
|
|
|
{
|
2016-03-06 23:27:47 +08:00
|
|
|
struct ds3232 *ds3232 = dev_get_drvdata(dev);
|
2010-08-11 09:02:20 +08:00
|
|
|
|
2016-03-06 23:27:47 +08:00
|
|
|
if (ds3232->irq > 0) {
|
2010-08-11 09:02:20 +08:00
|
|
|
mutex_lock(&ds3232->mutex);
|
|
|
|
ds3232->exiting = 1;
|
|
|
|
mutex_unlock(&ds3232->mutex);
|
|
|
|
|
2016-03-06 23:27:47 +08:00
|
|
|
devm_free_irq(dev, ds3232->irq, dev);
|
2010-12-24 23:00:17 +08:00
|
|
|
cancel_work_sync(&ds3232->work);
|
2010-08-11 09:02:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-04-04 05:50:08 +08:00
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
|
|
static int ds3232_suspend(struct device *dev)
|
|
|
|
{
|
|
|
|
struct ds3232 *ds3232 = dev_get_drvdata(dev);
|
|
|
|
|
|
|
|
if (device_can_wakeup(dev)) {
|
|
|
|
ds3232->suspended = true;
|
2016-03-06 23:27:47 +08:00
|
|
|
if (irq_set_irq_wake(ds3232->irq, 1)) {
|
2015-08-12 17:14:13 +08:00
|
|
|
dev_warn_once(dev, "Cannot set wakeup source\n");
|
|
|
|
ds3232->suspended = false;
|
|
|
|
}
|
2014-04-04 05:50:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ds3232_resume(struct device *dev)
|
|
|
|
{
|
|
|
|
struct ds3232 *ds3232 = dev_get_drvdata(dev);
|
|
|
|
|
|
|
|
if (ds3232->suspended) {
|
|
|
|
ds3232->suspended = false;
|
|
|
|
|
|
|
|
/* Clear the hardware alarm pend flag */
|
|
|
|
schedule_work(&ds3232->work);
|
|
|
|
|
2016-03-06 23:27:47 +08:00
|
|
|
irq_set_irq_wake(ds3232->irq, 0);
|
2014-04-04 05:50:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static const struct dev_pm_ops ds3232_pm_ops = {
|
|
|
|
SET_SYSTEM_SLEEP_PM_OPS(ds3232_suspend, ds3232_resume)
|
|
|
|
};
|
|
|
|
|
2016-03-06 23:27:48 +08:00
|
|
|
#if IS_ENABLED(CONFIG_I2C)
|
|
|
|
|
2016-03-06 23:27:47 +08:00
|
|
|
static int ds3232_i2c_probe(struct i2c_client *client,
|
|
|
|
const struct i2c_device_id *id)
|
|
|
|
{
|
|
|
|
struct regmap *regmap;
|
|
|
|
static const struct regmap_config config = {
|
|
|
|
.reg_bits = 8,
|
|
|
|
.val_bits = 8,
|
|
|
|
};
|
|
|
|
|
|
|
|
regmap = devm_regmap_init_i2c(client, &config);
|
|
|
|
if (IS_ERR(regmap)) {
|
|
|
|
dev_err(&client->dev, "%s: regmap allocation failed: %ld\n",
|
|
|
|
__func__, PTR_ERR(regmap));
|
|
|
|
return PTR_ERR(regmap);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ds3232_probe(&client->dev, regmap, client->irq, client->name);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ds3232_i2c_remove(struct i2c_client *client)
|
|
|
|
{
|
|
|
|
return ds3232_remove(&client->dev);
|
|
|
|
}
|
|
|
|
|
2010-08-11 09:02:20 +08:00
|
|
|
static const struct i2c_device_id ds3232_id[] = {
|
|
|
|
{ "ds3232", 0 },
|
|
|
|
{ }
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(i2c, ds3232_id);
|
|
|
|
|
|
|
|
static struct i2c_driver ds3232_driver = {
|
|
|
|
.driver = {
|
|
|
|
.name = "rtc-ds3232",
|
2014-04-04 05:50:08 +08:00
|
|
|
.pm = &ds3232_pm_ops,
|
2010-08-11 09:02:20 +08:00
|
|
|
},
|
2016-03-06 23:27:47 +08:00
|
|
|
.probe = ds3232_i2c_probe,
|
|
|
|
.remove = ds3232_i2c_remove,
|
2010-08-11 09:02:20 +08:00
|
|
|
.id_table = ds3232_id,
|
|
|
|
};
|
2016-03-06 23:27:48 +08:00
|
|
|
|
|
|
|
static int ds3232_register_driver(void)
|
|
|
|
{
|
|
|
|
return i2c_add_driver(&ds3232_driver);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ds3232_unregister_driver(void)
|
|
|
|
{
|
|
|
|
i2c_del_driver(&ds3232_driver);
|
|
|
|
}
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
static int ds3232_register_driver(void)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ds3232_unregister_driver(void)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if IS_ENABLED(CONFIG_SPI_MASTER)
|
|
|
|
|
|
|
|
static int ds3234_probe(struct spi_device *spi)
|
|
|
|
{
|
|
|
|
int res;
|
|
|
|
unsigned int tmp;
|
|
|
|
static const struct regmap_config config = {
|
|
|
|
.reg_bits = 8,
|
|
|
|
.val_bits = 8,
|
|
|
|
.write_flag_mask = 0x80,
|
|
|
|
};
|
|
|
|
struct regmap *regmap;
|
|
|
|
|
|
|
|
regmap = devm_regmap_init_spi(spi, &config);
|
|
|
|
if (IS_ERR(regmap)) {
|
|
|
|
dev_err(&spi->dev, "%s: regmap allocation failed: %ld\n",
|
|
|
|
__func__, PTR_ERR(regmap));
|
|
|
|
return PTR_ERR(regmap);
|
|
|
|
}
|
|
|
|
|
|
|
|
spi->mode = SPI_MODE_3;
|
|
|
|
spi->bits_per_word = 8;
|
|
|
|
spi_setup(spi);
|
|
|
|
|
|
|
|
res = regmap_read(regmap, DS3232_REG_SECONDS, &tmp);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
|
|
|
|
/* Control settings
|
|
|
|
*
|
|
|
|
* CONTROL_REG
|
|
|
|
* BIT 7 6 5 4 3 2 1 0
|
|
|
|
* EOSC BBSQW CONV RS2 RS1 INTCN A2IE A1IE
|
|
|
|
*
|
|
|
|
* 0 0 0 1 1 1 0 0
|
|
|
|
*
|
|
|
|
* CONTROL_STAT_REG
|
|
|
|
* BIT 7 6 5 4 3 2 1 0
|
|
|
|
* OSF BB32kHz CRATE1 CRATE0 EN32kHz BSY A2F A1F
|
|
|
|
*
|
|
|
|
* 1 0 0 0 1 0 0 0
|
|
|
|
*/
|
|
|
|
res = regmap_read(regmap, DS3232_REG_CR, &tmp);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
res = regmap_write(regmap, DS3232_REG_CR, tmp & 0x1c);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
|
|
|
|
res = regmap_read(regmap, DS3232_REG_SR, &tmp);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
res = regmap_write(regmap, DS3232_REG_SR, tmp & 0x88);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
|
|
|
|
/* Print our settings */
|
|
|
|
res = regmap_read(regmap, DS3232_REG_CR, &tmp);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
dev_info(&spi->dev, "Control Reg: 0x%02x\n", tmp);
|
|
|
|
|
|
|
|
res = regmap_read(regmap, DS3232_REG_SR, &tmp);
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
dev_info(&spi->dev, "Ctrl/Stat Reg: 0x%02x\n", tmp);
|
|
|
|
|
|
|
|
return ds3232_probe(&spi->dev, regmap, spi->irq, "ds3234");
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ds3234_remove(struct spi_device *spi)
|
|
|
|
{
|
|
|
|
return ds3232_remove(&spi->dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct spi_driver ds3234_driver = {
|
|
|
|
.driver = {
|
|
|
|
.name = "ds3234",
|
|
|
|
},
|
|
|
|
.probe = ds3234_probe,
|
|
|
|
.remove = ds3234_remove,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int ds3234_register_driver(void)
|
|
|
|
{
|
|
|
|
return spi_register_driver(&ds3234_driver);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ds3234_unregister_driver(void)
|
|
|
|
{
|
|
|
|
spi_unregister_driver(&ds3234_driver);
|
|
|
|
}
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
static int ds3234_register_driver(void)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ds3234_unregister_driver(void)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static int __init ds323x_init(void)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = ds3232_register_driver();
|
|
|
|
if (ret) {
|
|
|
|
pr_err("Failed to register ds3232 driver: %d\n", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = ds3234_register_driver();
|
|
|
|
if (ret) {
|
|
|
|
pr_err("Failed to register ds3234 driver: %d\n", ret);
|
|
|
|
ds3232_unregister_driver();
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
module_init(ds323x_init)
|
|
|
|
|
|
|
|
static void __exit ds323x_exit(void)
|
|
|
|
{
|
|
|
|
ds3234_unregister_driver();
|
|
|
|
ds3232_unregister_driver();
|
|
|
|
}
|
|
|
|
module_exit(ds323x_exit)
|
2010-08-11 09:02:20 +08:00
|
|
|
|
|
|
|
MODULE_AUTHOR("Srikanth Srinivasan <srikanth.srinivasan@freescale.com>");
|
2016-03-06 23:27:48 +08:00
|
|
|
MODULE_AUTHOR("Dennis Aberilla <denzzzhome@yahoo.com>");
|
|
|
|
MODULE_DESCRIPTION("Maxim/Dallas DS3232/DS3234 RTC Driver");
|
2010-08-11 09:02:20 +08:00
|
|
|
MODULE_LICENSE("GPL");
|
2016-03-06 23:27:48 +08:00
|
|
|
MODULE_ALIAS("spi:ds3234");
|