aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/rtc/rtc-s5m.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/rtc/rtc-s5m.c')
-rw-r--r--drivers/rtc/rtc-s5m.c268
1 files changed, 267 insertions, 1 deletions
diff --git a/drivers/rtc/rtc-s5m.c b/drivers/rtc/rtc-s5m.c
index 22c4ff7..fdea136 100644
--- a/drivers/rtc/rtc-s5m.c
+++ b/drivers/rtc/rtc-s5m.c
@@ -29,6 +29,9 @@ struct s5m_rtc_info {
struct rtc_device *rtc_dev;
struct mutex lock;
int irq;
+#if defined(CONFIG_RTC_POWER_OFF)
+ int irq2;
+#endif
int device_type;
int rtc_24hr_mode;
bool wtsr_smpl;
@@ -322,7 +325,7 @@ static int s5m_rtc_stop_alarm(struct s5m_rtc_info *info)
data[i] &= ~ALARM_ENABLE_MASK;
ret = s5m_bulk_write(info->rtc, S5M87XX_ALARM0_SEC, 8, data);
- if (ret <0)
+ if (ret < 0)
return ret;
ret = s5m8767_rtc_set_alarm_reg(info);
@@ -336,6 +339,50 @@ static int s5m_rtc_stop_alarm(struct s5m_rtc_info *info)
return ret;
}
+#if defined(CONFIG_RTC_POWER_OFF)
+static int s5m_rtc_stop_alarm_poweroff(struct s5m_rtc_info *info)
+{
+ u8 data[8];
+ int ret, i;
+ struct rtc_time tm;
+
+ if (!mutex_is_locked(&info->lock))
+ dev_warn(info->dev, "%s: should have mutex locked\n", __func__);
+
+ ret = s5m_bulk_read(info->rtc, S5M87XX_ALARM1_SEC, 8, data);
+ if (ret < 0)
+ return ret;
+
+ s5m8767_data_to_tm(data, &tm, info->rtc_24hr_mode);
+ printk(KERN_INFO "%s: %d/%d/%d %d:%d:%d(%d)\n", __func__,
+ 1900 + tm.tm_year, 1 + tm.tm_mon, tm.tm_mday,
+ tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_wday);
+
+ switch (info->device_type) {
+ case S5M8763X:
+ ret = s5m_reg_write(info->rtc, S5M87XX_ALARM1_CONF, 0);
+ break;
+
+ case S5M8767X:
+ for (i = 0; i < 7; i++)
+ data[i] &= ~ALARM_ENABLE_MASK;
+
+ ret = s5m_bulk_write(info->rtc, S5M87XX_ALARM1_SEC, 8, data);
+ if (ret < 0)
+ return ret;
+
+ ret = s5m8767_rtc_set_alarm_reg(info);
+
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+#endif
+
static int s5m_rtc_start_alarm(struct s5m_rtc_info *info)
{
int ret;
@@ -387,6 +434,60 @@ static int s5m_rtc_start_alarm(struct s5m_rtc_info *info)
return ret;
}
+#if defined(CONFIG_RTC_POWER_OFF)
+static int s5m_rtc_start_alarm_poweroff(struct s5m_rtc_info *info)
+{
+ int ret;
+ u8 data[8];
+ u8 alarm0_conf;
+ struct rtc_time tm;
+
+ if (!mutex_is_locked(&info->lock))
+ dev_warn(info->dev, "%s: should have mutex locked\n", __func__);
+
+ ret = s5m_bulk_read(info->rtc, S5M87XX_ALARM1_SEC, 8, data);
+ if (ret < 0)
+ return ret;
+
+ s5m8767_data_to_tm(data, &tm, info->rtc_24hr_mode);
+ printk(KERN_INFO "%s: %d/%d/%d %d:%d:%d(%d)\n", __func__,
+ 1900 + tm.tm_year, 1 + tm.tm_mon, tm.tm_mday,
+ tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_wday);
+
+ switch (info->device_type) {
+ case S5M8763X:
+ alarm0_conf = 0x77;
+ ret = s5m_reg_write(info->rtc, S5M87XX_ALARM1_CONF,
+ alarm0_conf);
+ break;
+
+ case S5M8767X:
+ data[RTC_SEC] |= ALARM_ENABLE_MASK;
+ data[RTC_MIN] |= ALARM_ENABLE_MASK;
+ data[RTC_HOUR] |= ALARM_ENABLE_MASK;
+ data[RTC_WEEKDAY] &= ~ALARM_ENABLE_MASK;
+ if (data[RTC_DATE] & 0x1f)
+ data[RTC_DATE] |= ALARM_ENABLE_MASK;
+ if (data[RTC_MONTH] & 0xf)
+ data[RTC_MONTH] |= ALARM_ENABLE_MASK;
+ if (data[RTC_YEAR1] & 0x7f)
+ data[RTC_YEAR1] |= ALARM_ENABLE_MASK;
+
+ ret = s5m_bulk_write(info->rtc, S5M87XX_ALARM1_SEC, 8, data);
+ if (ret < 0)
+ return ret;
+ ret = s5m8767_rtc_set_alarm_reg(info);
+
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+#endif
+
static int s5m_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
{
struct s5m_rtc_info *info = dev_get_drvdata(dev);
@@ -432,6 +533,97 @@ out:
return ret;
}
+#if defined(CONFIG_RTC_POWER_OFF)
+static int s5m_rtc_set_alarm_poweroff(struct device *dev,
+ struct rtc_wkalrm *alrm)
+{
+ struct s5m_rtc_info *info = dev_get_drvdata(dev);
+ u8 data[8];
+ int ret;
+
+ switch (info->device_type) {
+ case S5M8763X:
+ if (alrm->enabled) {
+ data[RTC_SEC] = bin2bcd(alrm->time.tm_sec);
+ data[RTC_MIN] = bin2bcd(alrm->time.tm_min);
+ data[RTC_HOUR] = bin2bcd(alrm->time.tm_hour);
+ data[RTC_WEEKDAY] = 0;
+ data[RTC_DATE] = bin2bcd(alrm->time.tm_mday);
+ data[RTC_MONTH] = bin2bcd(alrm->time.tm_mon);
+ data[RTC_YEAR1] = bin2bcd(alrm->time.tm_year % 100);
+ data[RTC_YEAR2] =
+ bin2bcd((alrm->time.tm_year + 1900) / 100);
+ } else {
+ data[RTC_SEC] = 0;
+ data[RTC_MIN] = 0;
+ data[RTC_HOUR] = 0;
+ data[RTC_WEEKDAY] = 0;
+ data[RTC_DATE] = 1;
+ data[RTC_MONTH] = 0;
+ data[RTC_YEAR1] = 0;
+ data[RTC_YEAR2] = 0;
+ }
+ break;
+
+ case S5M8767X:
+ if (alrm->enabled) {
+ data[RTC_SEC] = alrm->time.tm_sec;
+ data[RTC_MIN] = alrm->time.tm_min;
+ if (alrm->time.tm_hour >= 12)
+ data[RTC_HOUR] =
+ alrm->time.tm_hour | HOUR_PM_MASK;
+ else
+ data[RTC_HOUR] =
+ alrm->time.tm_hour & ~HOUR_PM_MASK;
+ data[RTC_WEEKDAY] = 1 << alrm->time.tm_wday;
+ data[RTC_DATE] = alrm->time.tm_mday;
+ data[RTC_MONTH] = alrm->time.tm_mon + 1;
+ data[RTC_YEAR1] = alrm->time.tm_year % 100;
+ data[RTC_YEAR2] =
+ bin2bcd((alrm->time.tm_year + 1900) / 100);
+ } else {
+ data[RTC_SEC] = 0;
+ data[RTC_MIN] = 0;
+ data[RTC_HOUR] = 0;
+ data[RTC_WEEKDAY] = 0;
+ data[RTC_DATE] = 1;
+ data[RTC_MONTH] = 0;
+ data[RTC_YEAR1] = 0;
+ data[RTC_YEAR2] = 0;
+ }
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ printk(KERN_INFO "%s: %d/%d/%d %d:%d:%d(%d)\n", __func__,
+ 1900 + alrm->time.tm_year, 1 + alrm->time.tm_mon,
+ alrm->time.tm_mday, alrm->time.tm_hour, alrm->time.tm_min,
+ alrm->time.tm_sec, alrm->time.tm_wday);
+
+ mutex_lock(&info->lock);
+
+ ret = s5m_rtc_stop_alarm_poweroff(info);
+ if (ret < 0)
+ goto out;
+
+ ret = s5m_bulk_write(info->rtc, S5M87XX_ALARM1_SEC, 8, data);
+ if (ret < 0)
+ goto out;
+
+ ret = s5m8767_rtc_set_alarm_reg(info);
+ if (ret < 0)
+ goto out;
+
+ if (alrm->enabled)
+ ret = s5m_rtc_start_alarm_poweroff(info);
+out:
+ mutex_unlock(&info->lock);
+ return ret;
+}
+#endif
+
static int s5m_rtc_alarm_irq_enable(struct device *dev,
unsigned int enabled)
{
@@ -458,11 +650,27 @@ static irqreturn_t s5m_rtc_alarm_irq(int irq, void *data)
return IRQ_HANDLED;
}
+#if defined(CONFIG_RTC_POWER_OFF)
+static irqreturn_t s5m_rtc_alarm2_irq(int irq, void *data)
+{
+ struct s5m_rtc_info *info = data;
+
+ rtc_update_irq(info->rtc_dev, 1, RTC_IRQF | RTC_AF);
+
+ pr_info("%s called", __func__);
+
+ return IRQ_HANDLED;
+}
+#endif
+
static const struct rtc_class_ops s5m_rtc_ops = {
.read_time = s5m_rtc_read_time,
.set_time = s5m_rtc_set_time,
.read_alarm = s5m_rtc_read_alarm,
.set_alarm = s5m_rtc_set_alarm,
+#if defined(CONFIG_RTC_POWER_OFF)
+ .set_alarm_poweroff = s5m_rtc_set_alarm_poweroff,
+#endif
.alarm_irq_enable = s5m_rtc_alarm_irq_enable,
};
@@ -487,6 +695,7 @@ static void s5m_rtc_enable_wtsr(struct s5m_rtc_info *info, bool enable)
__func__, ret);
return;
}
+ ret = s5m8767_rtc_set_alarm_reg(info);
}
static void s5m_rtc_enable_smpl(struct s5m_rtc_info *info, bool enable)
@@ -510,6 +719,7 @@ static void s5m_rtc_enable_smpl(struct s5m_rtc_info *info, bool enable)
__func__, ret);
return;
}
+ ret = s5m8767_rtc_set_alarm_reg(info);
val = 0;
s5m_reg_read(info->rtc, S5M87XX_WTSR_SMPL_CNTL, &val);
@@ -521,7 +731,16 @@ static int s5m8767_rtc_init_reg(struct s5m_rtc_info *info)
u8 data[2], tp_read;
int ret;
struct rtc_time tm;
+#if defined(CONFIG_RTC_POWER_OFF)
+ u8 data_alm2[8];
+ ret = s5m_bulk_read(info->rtc, S5M87XX_ALARM1_SEC, 8, data_alm2);
+ if (ret < 0) {
+ dev_err(info->dev, "%s: fail to read control reg(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+#endif
ret = s5m_reg_read(info->rtc, S5M87XX_RTC_UDR_CON, &tp_read);
if (ret < 0) {
dev_err(info->dev, "%s: fail to read control reg(%d)\n",
@@ -588,10 +807,16 @@ static int __devinit s5m_rtc_probe(struct platform_device *pdev)
switch (pdata->device_type) {
case S5M8763X:
info->irq = s5m87xx->irq_base + S5M8763_IRQ_ALARM0;
+#if defined(CONFIG_RTC_POWER_OFF)
+ info->irq2 = s5m87xx->irq_base + S5M8763_IRQ_ALARM1;
+#endif
break;
case S5M8767X:
info->irq = s5m87xx->irq_base + S5M8767_IRQ_RTCA1;
+#if defined(CONFIG_RTC_POWER_OFF)
+ info->irq2 = s5m87xx->irq_base + S5M8767_IRQ_RTCA2;
+#endif
break;
default:
@@ -626,7 +851,14 @@ static int __devinit s5m_rtc_probe(struct platform_device *pdev)
if (ret < 0)
dev_err(&pdev->dev, "Failed to request alarm IRQ: %d: %d\n",
info->irq, ret);
+#if defined(CONFIG_RTC_POWER_OFF)
+ ret = request_threaded_irq(info->irq2, NULL, s5m_rtc_alarm2_irq, 0,
+ "rtc-alarm0", info);
+ if (ret < 0)
+ dev_err(&pdev->dev, "Failed to request alarm2 IRQ: %d: %d\n",
+ info->irq, ret);
+#endif
dev_info(&pdev->dev, "RTC CHIP NAME: %s\n", pdev->id_entry->name);
return 0;
@@ -643,6 +875,9 @@ static int __devexit s5m_rtc_remove(struct platform_device *pdev)
if (info) {
free_irq(info->irq, info);
+#if defined(CONFIG_RTC_POWER_OFF)
+ free_irq(info->irq2, info);
+#endif
rtc_device_unregister(info->rtc_dev);
kfree(info);
}
@@ -676,10 +911,41 @@ static const struct platform_device_id s5m_rtc_id[] = {
{ "s5m-rtc", 0 },
};
+#if defined(CONFIG_RTC_POWER_OFF)
+extern bool fake_shut_down;
+
+static int s5m_rtc_resume(struct device *dev)
+{
+ struct s5m_rtc_info *info = dev_get_drvdata(dev);
+ int ret;
+ ret = s5m_rtc_stop_alarm_poweroff(info);
+
+ return ret;
+}
+
+static int s5m_rtc_suspend(struct device *dev)
+{
+ struct s5m_rtc_info *info = dev_get_drvdata(dev);
+
+ if (fake_shut_down)
+ disable_irq(info->irq);
+
+ return 0;
+}
+
+static const struct dev_pm_ops s5m_rtc_pm_ops = {
+ .resume = s5m_rtc_resume,
+ .suspend = s5m_rtc_suspend,
+};
+#endif
+
static struct platform_driver s5m_rtc_driver = {
.driver = {
.name = "s5m-rtc",
.owner = THIS_MODULE,
+#if defined(CONFIG_RTC_POWER_OFF)
+ .pm = &s5m_rtc_pm_ops,
+#endif
},
.probe = s5m_rtc_probe,
.remove = __devexit_p(s5m_rtc_remove),