/* drivers/rtc/rtc-s3c.c
*
* Copyright (c) 2010 Samsung Electronics Co., Ltd.
* http://www.samsung.com/
*
* 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 <linux/log2.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <asm/irq.h>
#include "rtc-s3c.h"
struct s3c_rtc {
struct device *dev;
struct rtc_device *rtc;
void __iomem *base;
struct clk *rtc_clk;
struct clk *rtc_src_clk;
bool enabled;
struct s3c_rtc_data *data;
int irq_alarm;
int irq_tick;
spinlock_t pie_lock;
spinlock_t alarm_clk_lock;
int ticnt_save, ticnt_en_save;
bool wake_en;
};
struct s3c_rtc_data {
int max_user_freq;
bool needs_src_clk;
void (*irq_handler) (struct s3c_rtc *info, int mask);
void (*set_freq) (struct s3c_rtc *info, int freq);
void (*enable_tick) (struct s3c_rtc *info, struct seq_file *seq);
void (*select_tick_clk) (struct s3c_rtc *info);
void (*save_tick_cnt) (struct s3c_rtc *info);
void (*restore_tick_cnt) (struct s3c_rtc *info);
void (*enable) (struct s3c_rtc *info);
void (*disable) (struct s3c_rtc *info);
};
static void s3c_rtc_alarm_clk_enable(struct s3c_rtc *info, bool enable)
{
unsigned long irq_flags;
spin_lock_irqsave(&info->alarm_clk_lock, irq_flags);
if (enable) {
if (!info->enabled) {
clk_enable(info->rtc_clk);
if (info->data->needs_src_clk)
clk_enable(info->rtc_src_clk);
info->enabled = true;
}
} else {
if (info->enabled) {
if (info->data->needs_src_clk)
clk_disable(info->rtc_src_clk);
clk_disable(info->rtc_clk);
info->enabled = false;
}
}
spin_unlock_irqrestore(&info->alarm_clk_lock, irq_flags);
}
/* IRQ Handlers */
static irqreturn_t s3c_rtc_tickirq(int irq, void *id)
{
struct s3c_rtc *info = (struct s3c_rtc *)id;
if (info->data->irq_handler)
info->data->irq_handler(info, S3C2410_INTP_TIC);
return IRQ_HANDLED;
}
static irqreturn_t s3c_rtc_alarmirq(int irq, void *id)
{
struct s3c_rtc *info = (struct s3c_rtc *)id;
if (info->data->irq_handler)
info->data->irq_handler(info, S3C2410_INTP_ALM);
return IRQ_HANDLED;
}
/* Update control registers */
static int s3c_rtc_setaie(struct device *dev, unsigned int enabled)
{
struct s3c_rtc *info = dev_get_drvdata(dev);
unsigned int tmp;
dev_dbg(info->dev, "%s: aie=%d\n", __func__, enabled);
clk_enable(info->rtc_clk);
if (info->data->needs_src_clk)
clk_enable(info->rtc_src_clk);
tmp = readb(info->base + S3C2410_RTCALM) & ~S3C2410_RTCALM_ALMEN;
if (enabled)
tmp |= S3C2410_RTCALM_ALMEN;
writeb(tmp, info->base + S3C2410_RTCALM);
if (info->data->needs_src_clk)
clk_disable(info->rtc_src_clk);
clk_disable(info->rtc_clk);
s3c_rtc_alarm_clk_enable(info, enabled);
return 0;
}
/* Set RTC frequency */
static int s3c_rtc_setfreq(struct s3c_rtc *info, int freq)
{
if (!is_power_of_2(freq))
return -EINVAL;
clk_enable(info->rtc_clk);
if (info->data->needs_src_clk)
clk_enable(info->rtc_src_clk);
spin_lock_irq(&info->pie_lock);
if (info->data->set_freq)
info->data->set_freq(info, freq);
spin_unlock_irq(&info->pie_lock);
if (info->data->needs_src_clk)
clk_disable(info->rtc_src_clk);
clk_disable(info->rtc_clk);
return 0;
}
/* Time read/write */
static int s3c_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
{
struct s3c_rtc *info = dev_get_drvdata(dev);
unsigned int have_retried = 0;
clk_enable(info->rtc_clk);
if (info->data->needs_src_clk