/*
* linux/drivers/serial/tcc-rtc.c
*
* Author:
* Created: Feb 10, 2009
* Description: RTC driver for Telechips TCC Series
*
* Copyright (C) 2009 Telechips
*
* 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, see the file COPYING, or write
* to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "tcc/tca_alarm.h"
//#define printk printk
#define DRV_NAME "tcc-rtc"
#define RTCCON 0x00
#define INTCON 0x04
#define RTCALM 0x08
#define ALMSEC 0x0C
#define ALMMIN 0x10
#define ALMHOUR 0x14
#define ALMDATE 0x18
#define ALMDAY 0x1C
#define ALMMON 0x20
#define ALMYEAR 0x24
#define BCDSEC 0x28
#define BCDMIN 0x2C
#define BCDHOUR 0x30
#define BCDDATE 0x34
#define BCDDAY 0x38
#define BCDMON 0x3C
#define BCDYEAR 0x40
#define RTCIM 0x44
#define RTCPEND 0x48
#if 0
#pragma pack(push, 4)
struct tcc_rtc_regs {
volatile unsigned long RTCCON, INTCON, RTCALM,
ALMSEC, ALMMIN, ALMHOUR, ALMDATE, ALMDAY, ALMMON, ALMYEAR,
BCDSEC, BCDMIN, BCDHOUR, BCDDATE, BCDDAY, BCDMON, BCDYEAR,
RTCIM, RTCPEND;
};
#pragma pack(pop)
volatile struct tcc_rtc_regs *rtc_regs;
#endif
static void __iomem *rtc_base;
static void __iomem *pic_base;
static int tcc_rtc_alarmno = NO_IRQ;
/* IRQ Handlers */
static irqreturn_t tcc_rtc_alarmirq(int irq, void *id)
{
printk("[IRQ OK]--------------------------------------------------------------\n");
tca_alarm_setint((unsigned int)rtc_base);
rtc_update_irq(id, 1, RTC_AF | RTC_IRQF);
return IRQ_HANDLED;
}
/* Update control registers */
static void tcc_rtc_setaie(int to)
{
unsigned int tmp;
printk("%s: aie=%d\n", __func__, to);
tcc_writel( tcc_readl(rtc_base + RTCCON) | Hw1, rtc_base + RTCCON);
tcc_writel( tcc_readl(rtc_base + INTCON) | Hw0, rtc_base + INTCON);
tmp = tcc_readl(rtc_base + RTCALM) & ~Hw7;
if (to){
tmp |= Hw7;
tca_alarm_setpmwkup((unsigned int)rtc_base, (unsigned int)pic_base);
}
tcc_writel(tmp, rtc_base + RTCALM);
//tcc_writel( tcc_readl(rtc_base + INTCON) & ~Hw0, rtc_base + INTCON);
tcc_writel( tcc_readl(rtc_base + RTCCON) & ~Hw1, rtc_base + RTCCON);
}
/* Time read/write */
static int tcc_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)
{
rtctime pTime;
local_irq_disable();
tca_rtc_gettime((unsigned int)rtc_base, &pTime);
rtc_tm->tm_sec = pTime.wSecond;
rtc_tm->tm_min = pTime.wMinute;
rtc_tm->tm_hour = pTime.wHour;
rtc_tm->tm_mday = pTime.wDay;
rtc_tm->tm_mon = pTime.wMonth - 1;
rtc_tm->tm_year = pTime.wYear - 1900;
printk("read time %02d.%02d.%02d %02d/%02d/%02d\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);
local_irq_enable();
return 0;
}
static int tcc_rtc_settime(struct device *dev, struct rtc_time *tm)
{
rtctime pTime;
printk("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);
local_irq_disable();
pTime.wSecond = tm->tm_sec;
pTime.wMinute = tm->tm_min;
pTime.wHour = tm->tm_hour;
pTime.wDay = tm->tm_mday;
pTime.wMonth = tm->tm_mon + 1;
pTime.wYear = tm->tm_year + 1900;
tca_rtc_settime((unsigned int)rtc_base, &pTime);
local_irq_enable();
return 0;
}
static int tcc_rtc_getalarm(struct device *dev, struct rtc_wkalrm *alrm)
{
struct rtc_time *alm_tm = &alrm->time;
unsigned int alm_en, alm_pnd;
rtctime pTime;
printk("%s\n", __func__);
local_irq_disable();
tcc_writel( tcc_readl(rtc_base + RTCCON) | Hw1, rtc_base + RTCCON);
tcc_writel( tcc_readl(rtc_base + INTCON) | Hw0, rtc_base + INTCON);
alm_en = tcc_readl(rtc_base + RTCALM);
alm_pnd = tcc_readl(rtc_base + RTCPEND);
alrm->enabled = (alm_en & Hw7) ? 1 : 0;
alrm->pending = (alm_pnd & Hw0) ? 1 : 0;
printk(" alrm->enabled = %d, alm_en = %d\n", alrm->enabled, alm_en);
tcc_writel( tcc_readl(rtc_base + INTCON) & ~Hw0, rtc_base + INTCON);
tcc_writel( tcc_readl(rtc_base + RTCCON) & ~Hw1, rtc_base + RTCCON);
tca_alarm_gettime((unsigned int)rtc_base, &pTime);
alm_tm->tm_sec = pTime.wSecond;
alm_tm->tm_min = pTime.wMinute;
alm_tm->tm_hour = pTime.wHour;
alm_tm->tm_mday = pTime.wDay ;
alm_tm->tm_mon = pTime.wMonth - 1;
alm_tm->tm_year = pTime.wYear - 1900;
printk("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);
local_irq_enable();
return 0;
}
static int tcc_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
{
rtctime pTime;
struct rtc_time *tm = &alrm->time;
printk("%s\n", __func__);
local_irq_disable();
alrm->enabled = 1;
pTime.wSecond = tm->tm_sec;
pTime.wMinute = tm->tm_min;
pTime.wHour = tm->tm_hour;
pTime.wDay = tm->tm_mday;
pTime.wMonth = tm->tm_mon + 1;
pTime.wYear = tm->tm_year + 1900;
printk("set alarm %02d.%02d.%02d %02d/%02d/%02d\n",
pTime.wSecond, pTime.wMinute, pTime.wHour,
pTime.wDay, pTime.wMonth, pTime.wYear);
tca_alarm_settime((unsigned int)rtc_base, &pTime);
#if 0
//tcc_writel( tcc_readl(rtc_base + RTCCON) & ~Hw0, rtc_base + RTCCON);
tcc_writel( tcc_readl(rtc_base + RTCCON) | Hw1, rtc_base + RTCCON);
tcc_writel( tcc_readl(rtc_base + INTCON) | Hw0, rtc_base + INTCON);
//tcc_writel( tcc_readl(rtc_base + INTCON) & ~Hw15, rtc_base + INTCON);
if (alrm->enabled) {
tcc_writel(tcc_readl(rtc_base + RTCALM)| Hw7, rtc_base + RTCALM);
enable_irq_wake(tcc_rtc_alarmno);
} else {
tcc_writel(tcc_readl(rtc_base + RTCALM)& ~Hw7, rtc_base + RTCALM);
disable_irq_wake(tcc_rtc_alarmno);
}
tcc_writel( tcc_readl(rtc_base + INTCON) & ~Hw0, rtc_base + INTCON);
tcc_writel( tcc_readl(rtc_base + RTCCON) & ~Hw1, rtc_base + RTCCON);
#endif
local_irq_enable();
return 0;
}
static int tcc_rtc_proc(struct device *dev, struct seq_file *seq)
{
return 0;
}
static int tcc_rtc_ioctl(struct device *dev,
unsigned int cmd, unsigned long arg)
{
unsigned int ret = -ENOIOCTLCMD;
switch (cmd) {
case RTC_AIE_OFF:
tcc_rtc_setaie(0);
ret = 0;
break;
case RTC_AIE_ON:
tcc_rtc_setaie(1);
ret = 0;
break;
case RTC_PIE_OFF:
break;
case RTC_PIE_ON:
break;
case RTC_IRQP_READ:
break;
case RTC_IRQP_SET:
break;
case RTC_UIE_ON:
break;
case RTC_UIE_OFF:
break;
ret = -EINVAL;
}
return ret;
}
static const struct rtc_class_ops tcc_rtcops = {
.read_time = tcc_rtc_gettime,
.set_time = tcc_rtc_settime,
.read_alarm = tcc_rtc_getalarm,
.set_alarm = tcc_rtc_setalarm,
.proc = tcc_rtc_proc,
.ioctl = tcc_rtc_ioctl,
};
#if 0
static void tcc_rtc_enable(struct platform_device *pdev, int en)
{
if (rtc_base == NULL)
return;
tca_alarm_setint((unsigned int)rtc_base);
}
#endif
static int tcc_rtc_remove(struct platform_device *dev)
{
struct rtc_device *rtc = platform_get_drvdata(dev);
platform_set_drvdata(dev, NULL);
rtc_device_unregister(rtc);
tcc_rtc_setaie(0);
free_irq(tcc_rtc_alarmno, rtc);
return 0;
}
static int tcc_rtc_probe(struct platform_device *pdev)
{
struct rtc_device *rtc;
int ret;
volatile PIOBUSCFG pIOBUSCFG = (volatile PIOBUSCFG)tcc_p2v(HwIOBUSCFG_BASE);
int valid_time = 1;
#if defined(CONFIG_MACH_TCC8900)
// BUS Enable
pIOBUSCFG->HCLKEN0 |= Hw26;
#endif
rtc_base = (void __iomem *)tcc_p2v(HwRTC_BASE);
pic_base = (void __iomem *)tcc_p2v(HwPIC_BASE);
if (rtc_base == NULL) {
printk("failed ioremap()\n");
return -ENOMEM;
}
// SW Reset
if(valid_time)
{
#if defined(CONFIG_MACH_TCC8900)
pIOBUSCFG->HRSTEN0 &= ~Hw26;
pIOBUSCFG->HRSTEN0 |= Hw26;
#endif
// tcc_rtc_init((unsigned int)rtc_base);
// RTC Initialization
tcc_writel( Hw1, rtc_base + RTCCON);
tcc_writel( Hw0, rtc_base + INTCON);
tcc_writel( tcc_readl(rtc_base + RTCCON) | Hw0, rtc_base + RTCCON);
//tcc_writel( tcc_readl(rtc_base + RTCCON) & ~Hw15, rtc_base + RTCCON);
tcc_writel( tcc_readl(rtc_base + INTCON) & ~Hw15, rtc_base + INTCON);
tcc_writel( tcc_readl(rtc_base + RTCCON) & ~(Hw13 | Hw12 | Hw10 | Hw9 | Hw8), rtc_base + INTCON);
tcc_writel( tcc_readl(rtc_base + INTCON) | Hw12, rtc_base + INTCON);
//tcc_writel( tcc_readl(rtc_base + RTCCON) | Hw15, rtc_base + RTCCON);
tcc_writel( tcc_readl(rtc_base + INTCON) | Hw15, rtc_base + INTCON);
tcc_writel( tcc_readl(rtc_base + RTCCON) & ~Hw0, rtc_base + RTCCON);
tcc_writel( tcc_readl(rtc_base + RTCIM) & ~(Hw3 | Hw0), rtc_base + RTCIM);
tcc_writel( tcc_readl(rtc_base + RTCIM) | Hw2, rtc_base + RTCIM);
tcc_writel( tcc_readl(rtc_base + RTCALM) & ~(Hw7 - Hw0), rtc_base + RTCALM);
tcc_writel( tcc_readl(rtc_base + INTCON) & ~Hw0, rtc_base + INTCON);
tcc_writel( tcc_readl(rtc_base + RTCCON) & ~Hw1, rtc_base + RTCCON);
}else {
// ADD PMWKUP RTC Normal mode}
}
//tcc_rtc_enable(pdev, 1);
//tca_alarm_setint((unsigned int)rtc_base);
//tca_alarm_clrpmwkup((unsigned int)rtc_base, (unsigned int)pic_base);
/* find the IRQs */
tcc_rtc_alarmno = platform_get_irq(pdev, 0);
printk("tcc_rtc: alarm irq %d\n", tcc_rtc_alarmno);
if (tcc_rtc_alarmno < 0) {
dev_err(&pdev->dev, "no irq for alarm\n");
return -ENOENT;
}
/* register RTC and exit */
rtc = rtc_device_register(pdev->name, &pdev->dev, &tcc_rtcops, THIS_MODULE);
if (IS_ERR(rtc)) {
dev_err(&pdev->dev, "cannot attach rtc\n");
ret = PTR_ERR(rtc);
goto err_nortc;
}
platform_set_drvdata(pdev, rtc);
#if 0
if(request_irq(tcc_rtc_alarmno, tcc_rtc_alarmirq,
IRQF_DISABLED, DRV_NAME, rtc))
{
printk("%s: RTC timer interrupt IRQ%d already claimed\n",
pdev->name, tcc_rtc_alarmno);
return 0;
}
PPIC pPIC = (PPIC)(unsigned int)pic_base;
pPIC->CLR1 = Hw11;
pPIC->MSTS1 &= ~Hw11;
pPIC->SEL1 &= ~Hw11;
tca_alarm_setpmwkup((unsigned int)rtc_base, (unsigned int)pic_base);
device_init_wakeup(&pdev->dev, 1);
#endif
return 0;
err_nortc:
//tcc_rtc_enable(pdev, 0);
rtc_device_unregister(rtc);
return ret;
}
#ifdef CONFIG_PM
/* RTC Power management control */
static int tcc_rtc_suspend(struct platform_device *pdev, pm_message_t state)
{
if (device_may_wakeup(&pdev->dev))
enable_irq_wake(tcc_rtc_alarmno);
return 0;
}
static int tcc_rtc_resume(struct platform_device *pdev)
{
if (device_may_wakeup(&pdev->dev))
disable_irq_wake(tcc_rtc_alarmno);
{
#if 0
volatile PIOBUSCFG pIOBUSCFG = (volatile PIOBUSCFG)tcc_p2v(HwIOBUSCFG_BASE);
pIOBUSCFG->HCLKEN0 |= Hw26; // BUS Enable
pIOBUSCFG->HRSTEN0 &= ~Hw26; // SW Reset
pIOBUSCFG->HRSTEN0 |= Hw26;
tcc_writel( Hw1, rtc_base + RTCCON);
tcc_writel( Hw0, rtc_base + INTCON);
tcc_writel( tcc_readl(rtc_base + RTCCON) | Hw0, rtc_base + RTCCON);
tcc_writel( tcc_readl(rtc_base + RTCCON) & ~Hw15, rtc_base + RTCCON);
tcc_writel( tcc_readl(rtc_base + RTCCON) & ~(Hw13 | Hw12 | Hw10 | Hw9 | Hw8), rtc_base + INTCON);
tcc_writel( tcc_readl(rtc_base + INTCON) | Hw12, rtc_base + INTCON);
tcc_writel( tcc_readl(rtc_base + RTCCON) | Hw15, rtc_base + RTCCON);
tcc_writel( tcc_readl(rtc_base + RTCCON) & ~Hw0, rtc_base + RTCCON);
tcc_writel( tcc_readl(rtc_base + RTCIM) & ~(Hw3 | Hw0), rtc_base + RTCIM);
tcc_writel( tcc_readl(rtc_base + RTCIM) | Hw2, rtc_base + RTCIM);
tcc_writel( tcc_readl(rtc_base + RTCALM) & ~(Hw7 - Hw0), rtc_base + RTCALM);
tcc_writel( tcc_readl(rtc_base + INTCON) & ~Hw0, rtc_base + INTCON);
tcc_writel( tcc_readl(rtc_base + RTCCON) & ~Hw1, rtc_base + RTCCON);
#endif
}
return 0;
}
#else
#define tcc_rtc_suspend NULL
#define tcc_rtc_resume NULL
#endif
static struct platform_driver tcc_rtc_driver = {
.probe = tcc_rtc_probe,
.remove = tcc_rtc_remove,
.suspend = tcc_rtc_suspend,
.resume = tcc_rtc_resume,
.driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
},
};
static char __initdata banner[] = "TCC RTC, (c) 2009, Telechips \n";
static int __init tcc_rtc_init(void)
{
printk(banner);
return platform_driver_register(&tcc_rtc_driver);
}
static void __exit tcc_rtc_exit(void)
{
platform_driver_unregister(&tcc_rtc_driver);
}
module_init(tcc_rtc_init);
module_exit(tcc_rtc_exit);
MODULE_AUTHOR("linux ");
MODULE_DESCRIPTION("Telechips RTC Driver");
MODULE_LICENSE("GPL");