summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/rtc/rtc-armada38x.c117
1 files changed, 94 insertions, 23 deletions
diff --git a/drivers/rtc/rtc-armada38x.c b/drivers/rtc/rtc-armada38x.c
index 9a3f2a6f512e..bc823700a9fe 100644
--- a/drivers/rtc/rtc-armada38x.c
+++ b/drivers/rtc/rtc-armada38x.c
@@ -29,50 +29,117 @@
#define RTC_TIME 0xC
#define RTC_ALARM1 0x10
+#define SOC_RTC_BRIDGE_TIMING_CTL 0x0
+#define SOC_RTC_PERIOD_OFFS 0
+#define SOC_RTC_PERIOD_MASK (0x3FF << SOC_RTC_PERIOD_OFFS)
+#define SOC_RTC_READ_DELAY_OFFS 26
+#define SOC_RTC_READ_DELAY_MASK (0x1F << SOC_RTC_READ_DELAY_OFFS)
+
#define SOC_RTC_INTERRUPT 0x8
#define SOC_RTC_ALARM1 BIT(0)
#define SOC_RTC_ALARM2 BIT(1)
#define SOC_RTC_ALARM1_MASK BIT(2)
#define SOC_RTC_ALARM2_MASK BIT(3)
+#define SAMPLE_NR 100
+
+struct value_to_freq {
+ u32 value;
+ u8 freq;
+};
+
struct armada38x_rtc {
struct rtc_device *rtc_dev;
void __iomem *regs;
void __iomem *regs_soc;
spinlock_t lock;
int irq;
+ struct value_to_freq *val_to_freq;
};
/*
* According to the datasheet, the OS should wait 5us after every
* register write to the RTC hard macro so that the required update
* can occur without holding off the system bus
+ * According to errata RES-3124064, Write to any RTC register
+ * may fail. As a workaround, before writing to RTC
+ * register, issue a dummy write of 0x0 twice to RTC Status
+ * register.
*/
+
static void rtc_delayed_write(u32 val, struct armada38x_rtc *rtc, int offset)
{
+ writel(0, rtc->regs + RTC_STATUS);
+ writel(0, rtc->regs + RTC_STATUS);
writel(val, rtc->regs + offset);
udelay(5);
}
+/* Update RTC-MBUS bridge timing parameters */
+static void rtc_update_mbus_timing_params(struct armada38x_rtc *rtc)
+{
+ u32 reg;
+
+ reg = readl(rtc->regs_soc + SOC_RTC_BRIDGE_TIMING_CTL);
+ reg &= ~SOC_RTC_PERIOD_MASK;
+ reg |= 0x3FF << SOC_RTC_PERIOD_OFFS; /* Maximum value */
+ reg &= ~SOC_RTC_READ_DELAY_MASK;
+ reg |= 0x1F << SOC_RTC_READ_DELAY_OFFS; /* Maximum value */
+ writel(reg, rtc->regs_soc + SOC_RTC_BRIDGE_TIMING_CTL);
+}
+
+static u32 read_rtc_register_wa(struct armada38x_rtc *rtc, u8 rtc_reg)
+{
+ int i, index_max = 0, max = 0;
+
+ for (i = 0; i < SAMPLE_NR; i++) {
+ rtc->val_to_freq[i].value = readl(rtc->regs + rtc_reg);
+ rtc->val_to_freq[i].freq = 0;
+ }
+
+ for (i = 0; i < SAMPLE_NR; i++) {
+ int j = 0;
+ u32 value = rtc->val_to_freq[i].value;
+
+ while (rtc->val_to_freq[j].freq) {
+ if (rtc->val_to_freq[j].value == value) {
+ rtc->val_to_freq[j].freq++;
+ break;
+ }
+ j++;
+ }
+
+ if (!rtc->val_to_freq[j].freq) {
+ rtc->val_to_freq[j].value = value;
+ rtc->val_to_freq[j].freq = 1;
+ }
+
+ if (rtc->val_to_freq[j].freq > max) {
+ index_max = j;
+ max = rtc->val_to_freq[j].freq;
+ }
+
+ /*
+ * If a value already has half of the sample this is the most
+ * frequent one and we can stop the research right now
+ */
+ if (max > SAMPLE_NR / 2)
+ break;
+ }
+
+ return rtc->val_to_freq[index_max].value;
+}
+
static int armada38x_rtc_read_time(struct device *dev, struct rtc_time *tm)
{
struct armada38x_rtc *rtc = dev_get_drvdata(dev);
- unsigned long time, time_check, flags;
+ unsigned long time, flags;
spin_lock_irqsave(&rtc->lock, flags);
- time = readl(rtc->regs + RTC_TIME);
- /*
- * WA for failing time set attempts. As stated in HW ERRATA if
- * more than one second between two time reads is detected
- * then read once again.
- */
- time_check = readl(rtc->regs + RTC_TIME);
- if ((time_check - time) > 1)
- time_check = readl(rtc->regs + RTC_TIME);
-
+ time = read_rtc_register_wa(rtc, RTC_TIME);
spin_unlock_irqrestore(&rtc->lock, flags);
- rtc_time_to_tm(time_check, tm);
+ rtc_time_to_tm(time, tm);
return 0;
}
@@ -87,16 +154,9 @@ static int armada38x_rtc_set_time(struct device *dev, struct rtc_time *tm)
if (ret)
goto out;
- /*
- * According to errata FE-3124064, Write to RTC TIME register
- * may fail. As a workaround, after writing to RTC TIME
- * register, issue a dummy write of 0x0 twice to RTC Status
- * register.
- */
+
spin_lock_irqsave(&rtc->lock, flags);
rtc_delayed_write(time, rtc, RTC_TIME);
- rtc_delayed_write(0, rtc, RTC_STATUS);
- rtc_delayed_write(0, rtc, RTC_STATUS);
spin_unlock_irqrestore(&rtc->lock, flags);
out:
@@ -111,8 +171,8 @@ static int armada38x_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
spin_lock_irqsave(&rtc->lock, flags);
- time = readl(rtc->regs + RTC_ALARM1);
- val = readl(rtc->regs + RTC_IRQ1_CONF) & RTC_IRQ1_AL_EN;
+ time = read_rtc_register_wa(rtc, RTC_ALARM1);
+ val = read_rtc_register_wa(rtc, RTC_IRQ1_CONF) & RTC_IRQ1_AL_EN;
spin_unlock_irqrestore(&rtc->lock, flags);
@@ -182,7 +242,7 @@ static irqreturn_t armada38x_rtc_alarm_irq(int irq, void *data)
val = readl(rtc->regs_soc + SOC_RTC_INTERRUPT);
writel(val & ~SOC_RTC_ALARM1, rtc->regs_soc + SOC_RTC_INTERRUPT);
- val = readl(rtc->regs + RTC_IRQ1_CONF);
+ val = read_rtc_register_wa(rtc, RTC_IRQ1_CONF);
/* disable all the interrupts for alarm 1 */
rtc_delayed_write(0, rtc, RTC_IRQ1_CONF);
/* Ack the event */
@@ -221,6 +281,11 @@ static __init int armada38x_rtc_probe(struct platform_device *pdev)
if (!rtc)
return -ENOMEM;
+ rtc->val_to_freq = devm_kcalloc(&pdev->dev, SAMPLE_NR,
+ sizeof(struct value_to_freq), GFP_KERNEL);
+ if (!rtc->val_to_freq)
+ return -ENOMEM;
+
spin_lock_init(&rtc->lock);
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rtc");
@@ -253,6 +318,9 @@ static __init int armada38x_rtc_probe(struct platform_device *pdev)
if (rtc->irq != -1)
device_init_wakeup(&pdev->dev, 1);
+ /* Update RTC-MBUS bridge timing parameters */
+ rtc_update_mbus_timing_params(rtc);
+
rtc->rtc_dev = devm_rtc_device_register(&pdev->dev, pdev->name,
&armada38x_rtc_ops, THIS_MODULE);
if (IS_ERR(rtc->rtc_dev)) {
@@ -280,6 +348,9 @@ static int armada38x_rtc_resume(struct device *dev)
if (device_may_wakeup(dev)) {
struct armada38x_rtc *rtc = dev_get_drvdata(dev);
+ /* Update RTC-MBUS bridge timing parameters */
+ rtc_update_mbus_timing_params(rtc);
+
return disable_irq_wake(rtc->irq);
}