blob: 05b9814a128be72b0b32c5ffdbc3c952d3c0d775 [file] [log] [blame]
/*
* CSR SiRF RTC alarm1 Driver
*
* Copyright (c) 2013 Cambridge Silicon Radio Limited, a CSR plc group company.
*
* This driver is specially used for sirfsoc's minigps running. It uses the
* function of alarm1 belongs to SYSRTC.
*
* Licensed under GPLv2 or later.
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/err.h>
#include <linux/rtc.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/proc_fs.h>
#include <linux/rtc/sirfsoc_rtciobrg.h>
#define RTC_CN 0x00
#define RTC_ALARM1 0x18
#define RTC_STATUS 0x08
#define SIRFSOC_RTC_AL1E (1<<6)
#define SIRFSOC_RTC_AL1 (1<<4)
/*
* RTC alarm1 is specially used for minigps, below are related
*ioctl definitions:
*/
#define SIRFSOC_MINIGPSRTC_ALM_SET _IOR('p', 0x13, unsigned long)
#define SIRFSOC_MINIGPSRTC_CNT_READ _IOR('p', 0x14, unsigned long)
#define SIRFSOC_MINIGPSRTC_ALM_READ _IOR('p', 0x15, unsigned long)
u32 sirf_sysrtc_base;
int sirf_rtc_alarm1_irq;
static unsigned int minigpsdata_mmc_base = 0x00b00000;
#undef MODULE_PARAM_PREFIX
#define MODULE_PARAM_PREFIX
static int param_set_minigpsinfo(const char *val, struct kernel_param *kp)
{
return kstrtoul(val, 0, (unsigned long *)kp->arg);
}
module_param_call(minigps_data_offset,
param_set_minigpsinfo, NULL, &minigpsdata_mmc_base, 0);
static int sirf_minigpsrtc_alarm_set(unsigned long count)
{
unsigned long rtc_status_reg, rtc_alarm;
local_irq_disable();
rtc_status_reg = sirfsoc_rtc_iobrg_readl(sirf_sysrtc_base + RTC_STATUS);
/*
* AL1E set, which means alarm1 is under way, so first clean it
*/
if (rtc_status_reg & SIRFSOC_RTC_AL1E) {
/*
* Clear the RTC status register's alarm bit
* Mask out the lower status bits
*/
rtc_status_reg &= ~0x50;
/*
* Write 1 into SIRFSOC_RTC_AL1 to force a clear
*/
rtc_status_reg |= (SIRFSOC_RTC_AL1);
/*
* Clear the Alarm enable bit
*/
rtc_status_reg &= ~(SIRFSOC_RTC_AL1E);
pr_info("STATUS_REG AL1E clear %lx\n", rtc_status_reg);
sirfsoc_rtc_iobrg_writel(rtc_status_reg,
sirf_sysrtc_base + RTC_STATUS);
}
/*
* Read current RTC_COUNT
*/
do {
rtc_alarm =
sirfsoc_rtc_iobrg_readl(sirf_sysrtc_base + RTC_CN);
} while (rtc_alarm !=
sirfsoc_rtc_iobrg_readl(sirf_sysrtc_base + RTC_CN));
/*
* Set useful alarm1 Count
*/
sirfsoc_rtc_iobrg_writel(rtc_alarm + count,
sirf_sysrtc_base + RTC_ALARM1);
/*
* Mask out the alarm1 status bits
*/
rtc_status_reg &= ~0x50;
/*
* Enable the RTC alarm interrupt
*/
rtc_status_reg |= SIRFSOC_RTC_AL1E;
pr_info("STATUS_REG AL1E set %lx\n", rtc_status_reg);
sirfsoc_rtc_iobrg_writel(rtc_status_reg, sirf_sysrtc_base + RTC_STATUS);
local_irq_enable();
return 0;
}
static int sirf_minigpsrtc_cnt_read(unsigned long *count)
{
/*
* TODO: This patch is taken from WinCE - Need to validate this for
* correctness. To work around sirfsoc RTC counter double sync logic
* fail, read several times to make sure get stable value.
*/
do {
*count = sirfsoc_rtc_iobrg_readl(sirf_sysrtc_base + RTC_CN);
} while (*count != sirfsoc_rtc_iobrg_readl(sirf_sysrtc_base + RTC_CN));
return 0;
}
static int sirf_minigpsrtc_alm_read(unsigned long *rtc_alarm)
{
local_irq_disable();
do {
*rtc_alarm =
sirfsoc_rtc_iobrg_readl(sirf_sysrtc_base + RTC_ALARM1);
} while (*rtc_alarm !=
sirfsoc_rtc_iobrg_readl(sirf_sysrtc_base + RTC_ALARM1));
local_irq_enable();
return 0;
}
static long sirf_minigpsrtc_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
switch (cmd) {
case SIRFSOC_MINIGPSRTC_ALM_SET:
return sirf_minigpsrtc_alarm_set(arg);
case SIRFSOC_MINIGPSRTC_CNT_READ:
return sirf_minigpsrtc_cnt_read((unsigned long *)arg);
case SIRFSOC_MINIGPSRTC_ALM_READ:
return sirf_minigpsrtc_alm_read((unsigned long *)arg);
default:
return -ENOTTY;
}
return 0;
}
static const struct file_operations minigpsrtc_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = sirf_minigpsrtc_ioctl,
};
static int sirf_proc_minigpsrtc_show(struct seq_file *m, void *v)
{
seq_printf(m, "%x\n", minigpsdata_mmc_base);
return 0;
}
static int sirf_proc_minigpsrtc_open(struct inode *inode, struct file *file)
{
return single_open(file, sirf_proc_minigpsrtc_show, PDE_DATA(inode));
}
static const struct file_operations sirf_proc_minigpsrtc_fops = {
.owner = THIS_MODULE,
.open = sirf_proc_minigpsrtc_open,
.read = seq_read,
};
static struct miscdevice sirf_minigpsrtc_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "minigpsrtc",
.fops = &minigpsrtc_fops,
};
static const struct of_device_id sirf_minigpsrtc_of_match[] = {
{ .compatible = "sirf,prima2-minigpsrtc"},
{},
};
MODULE_DEVICE_TABLE(of, sirf_minigpsrtc_of_match);
static int sirf_minigpsrtc_probe(struct platform_device *pdev)
{
int ret = 0;
struct device_node *np = pdev->dev.of_node;
ret = of_property_read_u32(np, "reg", &sirf_sysrtc_base);
if (ret) {
dev_err(&pdev->dev,
"unable to find base address of rtc node in dtb\n");
return ret;
}
ret = misc_register(&sirf_minigpsrtc_misc);
if (unlikely(ret)) {
dev_err(&pdev->dev,
"sirf_minigpsrtc: failed to register misc device!\n");
return ret;
}
sirf_rtc_alarm1_irq = platform_get_irq(pdev, 2);
device_init_wakeup(&pdev->dev, 1);
dev_info(&pdev->dev, "sirf_minigpsrtc registered.\n");
proc_create_data("minigpsdata_mmc_base",
S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH,
proc_mkdir("minigps", NULL),
&sirf_proc_minigpsrtc_fops,
&minigpsdata_mmc_base);
return ret;
}
static int sirf_minigpsrtc_remove(struct platform_device *pdev)
{
misc_deregister(&sirf_minigpsrtc_misc);
device_init_wakeup(&pdev->dev, 0);
return 0;
}
static int sirf_minigpsrtc_suspend(struct device *dev)
{
if (device_may_wakeup(dev))
enable_irq_wake(sirf_rtc_alarm1_irq);
return 0;
}
static int sirf_minigpsrtc_resume(struct device *dev)
{
if (device_may_wakeup(dev))
disable_irq_wake(sirf_rtc_alarm1_irq);
return 0;
}
static int sirf_minigpsrtc_freeze(struct device *dev)
{
if (device_may_wakeup(dev))
enable_irq_wake(sirf_rtc_alarm1_irq);
return 0;
}
static int sirf_minigpsrtc_thaw(struct device *dev)
{
return 0;
}
static int sirf_minigpsrtc_restore(struct device *dev)
{
return 0;
}
static const struct dev_pm_ops sirf_minigpsrtc_pm_ops = {
.suspend = sirf_minigpsrtc_suspend,
.resume = sirf_minigpsrtc_resume,
.freeze = sirf_minigpsrtc_freeze,
.thaw = sirf_minigpsrtc_thaw,
.restore = sirf_minigpsrtc_restore,
};
static struct platform_driver sirf_minigpsrtc_driver = {
.driver = {
.name = "sirf-minigpsrtc",
.owner = THIS_MODULE,
.pm = &sirf_minigpsrtc_pm_ops,
.of_match_table = of_match_ptr(sirf_minigpsrtc_of_match),
},
.probe = sirf_minigpsrtc_probe,
.remove = sirf_minigpsrtc_remove,
};
module_platform_driver(sirf_minigpsrtc_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Bin Shi <Bin.Shi@csr.com>,Xianglong Du <Xianglong.Du@csr.com>");
MODULE_DESCRIPTION("RTC Alarm1 Driver Module For MINIGPS");
MODULE_ALIAS("RTC Alarm1 Module");