blob: b253bf1b35314776ee3df0b9fc68c7c2f544ce78 [file] [log] [blame]
/*
* Real Time Clock driver for Conexant Digicolor
*
* Copyright (C) 2015 Paradox Innovation Ltd.
*
* Author: Baruch Siach <baruch@tkos.co.il>
*
* 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.
*/
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/rtc.h>
#include <linux/of.h>
#define DC_RTC_CONTROL 0x0
#define DC_RTC_TIME 0x8
#define DC_RTC_REFERENCE 0xc
#define DC_RTC_ALARM 0x10
#define DC_RTC_INTFLAG_CLEAR 0x14
#define DC_RTC_INTENABLE 0x16
#define DC_RTC_CMD_MASK 0xf
#define DC_RTC_GO_BUSY BIT(7)
#define CMD_NOP 0
#define CMD_RESET 1
#define CMD_WRITE 3
#define CMD_READ 4
#define CMD_DELAY_US (10*1000)
#define CMD_TIMEOUT_US (500*CMD_DELAY_US)
struct dc_rtc {
struct rtc_device *rtc_dev;
void __iomem *regs;
};
static int dc_rtc_cmds(struct dc_rtc *rtc, const u8 *cmds, int len)
{
u8 val;
int i, ret;
for (i = 0; i < len; i++) {
writeb_relaxed((cmds[i] & DC_RTC_CMD_MASK) | DC_RTC_GO_BUSY,
rtc->regs + DC_RTC_CONTROL);
ret = readb_relaxed_poll_timeout(
rtc->regs + DC_RTC_CONTROL, val,
!(val & DC_RTC_GO_BUSY), CMD_DELAY_US, CMD_TIMEOUT_US);
if (ret < 0)
return ret;
}
return 0;
}
static int dc_rtc_read(struct dc_rtc *rtc, unsigned long *val)
{
static const u8 read_cmds[] = {CMD_READ, CMD_NOP};
u32 reference, time1, time2;
int ret;
ret = dc_rtc_cmds(rtc, read_cmds, ARRAY_SIZE(read_cmds));
if (ret < 0)
return ret;
reference = readl_relaxed(rtc->regs + DC_RTC_REFERENCE);
time1 = readl_relaxed(rtc->regs + DC_RTC_TIME);
/* Read twice to ensure consistency */
while (1) {
time2 = readl_relaxed(rtc->regs + DC_RTC_TIME);
if (time1 == time2)
break;
time1 = time2;
}
*val = reference + time1;
return 0;
}
static int dc_rtc_write(struct dc_rtc *rtc, u32 val)
{
static const u8 write_cmds[] = {CMD_WRITE, CMD_NOP, CMD_RESET, CMD_NOP};
writel_relaxed(val, rtc->regs + DC_RTC_REFERENCE);
return dc_rtc_cmds(rtc, write_cmds, ARRAY_SIZE(write_cmds));
}
static int dc_rtc_read_time(struct device *dev, struct rtc_time *tm)
{
struct dc_rtc *rtc = dev_get_drvdata(dev);
unsigned long now;
int ret;
ret = dc_rtc_read(rtc, &now);
if (ret < 0)
return ret;
rtc_time64_to_tm(now, tm);
return 0;
}
static int dc_rtc_set_mmss(struct device *dev, unsigned long secs)
{
struct dc_rtc *rtc = dev_get_drvdata(dev);
return dc_rtc_write(rtc, secs);
}
static int dc_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
{
struct dc_rtc *rtc = dev_get_drvdata(dev);
u32 alarm_reg, reference;
unsigned long now;
int ret;
alarm_reg = readl_relaxed(rtc->regs + DC_RTC_ALARM);
reference = readl_relaxed(rtc->regs + DC_RTC_REFERENCE);
rtc_time64_to_tm(reference + alarm_reg, &alarm->time);
ret = dc_rtc_read(rtc, &now);
if (ret < 0)
return ret;
alarm->pending = alarm_reg + reference > now;
alarm->enabled = readl_relaxed(rtc->regs + DC_RTC_INTENABLE);
return 0;
}
static int dc_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
{
struct dc_rtc *rtc = dev_get_drvdata(dev);
time64_t alarm_time;
u32 reference;
alarm_time = rtc_tm_to_time64(&alarm->time);
reference = readl_relaxed(rtc->regs + DC_RTC_REFERENCE);
writel_relaxed(alarm_time - reference, rtc->regs + DC_RTC_ALARM);
writeb_relaxed(!!alarm->enabled, rtc->regs + DC_RTC_INTENABLE);
return 0;
}
static int dc_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
{
struct dc_rtc *rtc = dev_get_drvdata(dev);
writeb_relaxed(!!enabled, rtc->regs + DC_RTC_INTENABLE);
return 0;
}
static const struct rtc_class_ops dc_rtc_ops = {
.read_time = dc_rtc_read_time,
.set_mmss = dc_rtc_set_mmss,
.read_alarm = dc_rtc_read_alarm,
.set_alarm = dc_rtc_set_alarm,
.alarm_irq_enable = dc_rtc_alarm_irq_enable,
};
static irqreturn_t dc_rtc_irq(int irq, void *dev_id)
{
struct dc_rtc *rtc = dev_id;
writeb_relaxed(1, rtc->regs + DC_RTC_INTFLAG_CLEAR);
rtc_update_irq(rtc->rtc_dev, 1, RTC_AF | RTC_IRQF);
return IRQ_HANDLED;
}
static int __init dc_rtc_probe(struct platform_device *pdev)
{
struct resource *res;
struct dc_rtc *rtc;
int irq, ret;
rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL);
if (!rtc)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
rtc->regs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(rtc->regs))
return PTR_ERR(rtc->regs);
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
ret = devm_request_irq(&pdev->dev, irq, dc_rtc_irq, 0, pdev->name, rtc);
if (ret < 0)
return ret;
platform_set_drvdata(pdev, rtc);
rtc->rtc_dev = devm_rtc_device_register(&pdev->dev, pdev->name,
&dc_rtc_ops, THIS_MODULE);
if (IS_ERR(rtc->rtc_dev))
return PTR_ERR(rtc->rtc_dev);
return 0;
}
static const struct of_device_id dc_dt_ids[] = {
{ .compatible = "cnxt,cx92755-rtc" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, dc_dt_ids);
static struct platform_driver dc_rtc_driver = {
.driver = {
.name = "digicolor_rtc",
.of_match_table = of_match_ptr(dc_dt_ids),
},
};
module_platform_driver_probe(dc_rtc_driver, dc_rtc_probe);
MODULE_AUTHOR("Baruch Siach <baruch@tkos.co.il>");
MODULE_DESCRIPTION("Conexant Digicolor Realtime Clock Driver (RTC)");
MODULE_LICENSE("GPL");