aboutsummaryrefslogtreecommitdiff
path: root/drivers/rtc/rtc-pl031.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/rtc/rtc-pl031.c')
-rw-r--r--drivers/rtc/rtc-pl031.c211
1 files changed, 95 insertions, 116 deletions
diff --git a/drivers/rtc/rtc-pl031.c b/drivers/rtc/rtc-pl031.c
index b7a6690e5b3..99181fff88f 100644
--- a/drivers/rtc/rtc-pl031.c
+++ b/drivers/rtc/rtc-pl031.c
@@ -44,6 +44,7 @@
#define RTC_YMR 0x34 /* Year match register */
#define RTC_YLR 0x38 /* Year data load register */
+#define RTC_CR_EN (1 << 0) /* counter enable bit */
#define RTC_CR_CWEN (1 << 26) /* Clockwatch enable bit */
#define RTC_TCR_EN (1 << 1) /* Periodic timer enable bit */
@@ -68,11 +69,26 @@
#define RTC_TIMER_FREQ 32768
+/**
+ * struct pl031_vendor_data - per-vendor variations
+ * @ops: the vendor-specific operations used on this silicon version
+ * @clockwatch: if this is an ST Microelectronics silicon version with a
+ * clockwatch function
+ * @st_weekday: if this is an ST Microelectronics silicon version that need
+ * the weekday fix
+ * @irqflags: special IRQ flags per variant
+ */
+struct pl031_vendor_data {
+ struct rtc_class_ops ops;
+ bool clockwatch;
+ bool st_weekday;
+ unsigned long irqflags;
+};
+
struct pl031_local {
+ struct pl031_vendor_data *vendor;
struct rtc_device *rtc;
void __iomem *base;
- u8 hw_designer;
- u8 hw_revision:4;
};
static int pl031_alarm_irq_enable(struct device *dev,
@@ -220,17 +236,9 @@ static irqreturn_t pl031_interrupt(int irq, void *dev_id)
unsigned long events = 0;
rtcmis = readl(ldata->base + RTC_MIS);
- if (rtcmis) {
- writel(rtcmis, ldata->base + RTC_ICR);
-
- if (rtcmis & RTC_BIT_AI)
- events |= (RTC_AF | RTC_IRQF);
-
- /* Timer interrupt is only available in ST variants */
- if ((rtcmis & RTC_BIT_PI) &&
- (ldata->hw_designer == AMBA_VENDOR_ST))
- events |= (RTC_PF | RTC_IRQF);
-
+ if (rtcmis & RTC_BIT_AI) {
+ writel(RTC_BIT_AI, ldata->base + RTC_ICR);
+ events |= (RTC_AF | RTC_IRQF);
rtc_update_irq(ldata->rtc, 1, events);
return IRQ_HANDLED;
@@ -293,63 +301,11 @@ static int pl031_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
return ret;
}
-/* Periodic interrupt is only available in ST variants. */
-static int pl031_irq_set_state(struct device *dev, int enabled)
-{
- struct pl031_local *ldata = dev_get_drvdata(dev);
-
- if (enabled == 1) {
- /* Clear any pending timer interrupt. */
- writel(RTC_BIT_PI, ldata->base + RTC_ICR);
-
- writel(readl(ldata->base + RTC_IMSC) | RTC_BIT_PI,
- ldata->base + RTC_IMSC);
-
- /* Now start the timer */
- writel(readl(ldata->base + RTC_TCR) | RTC_TCR_EN,
- ldata->base + RTC_TCR);
-
- } else {
- writel(readl(ldata->base + RTC_IMSC) & (~RTC_BIT_PI),
- ldata->base + RTC_IMSC);
-
- /* Also stop the timer */
- writel(readl(ldata->base + RTC_TCR) & (~RTC_TCR_EN),
- ldata->base + RTC_TCR);
- }
- /* Wait at least 1 RTC32 clock cycle to ensure next access
- * to RTC_TCR will succeed.
- */
- udelay(40);
-
- return 0;
-}
-
-static int pl031_irq_set_freq(struct device *dev, int freq)
-{
- struct pl031_local *ldata = dev_get_drvdata(dev);
-
- /* Cant set timer if it is already enabled */
- if (readl(ldata->base + RTC_TCR) & RTC_TCR_EN) {
- dev_err(dev, "can't change frequency while timer enabled\n");
- return -EINVAL;
- }
-
- /* If self start bit in RTC_TCR is set timer will start here,
- * but we never set that bit. Instead we start the timer when
- * set_state is called with enabled == 1.
- */
- writel(RTC_TIMER_FREQ / freq, ldata->base + RTC_TLR);
-
- return 0;
-}
-
static int pl031_remove(struct amba_device *adev)
{
struct pl031_local *ldata = dev_get_drvdata(&adev->dev);
- amba_set_drvdata(adev, NULL);
- free_irq(adev->irq[0], ldata->rtc);
+ free_irq(adev->irq[0], ldata);
rtc_device_unregister(ldata->rtc);
iounmap(ldata->base);
kfree(ldata);
@@ -358,11 +314,13 @@ static int pl031_remove(struct amba_device *adev)
return 0;
}
-static int pl031_probe(struct amba_device *adev, struct amba_id *id)
+static int pl031_probe(struct amba_device *adev, const struct amba_id *id)
{
int ret;
struct pl031_local *ldata;
- struct rtc_class_ops *ops = id->data;
+ struct pl031_vendor_data *vendor = id->data;
+ struct rtc_class_ops *ops = &vendor->ops;
+ unsigned long time, data;
ret = amba_request_regions(adev, NULL);
if (ret)
@@ -373,6 +331,7 @@ static int pl031_probe(struct amba_device *adev, struct amba_id *id)
ret = -ENOMEM;
goto out;
}
+ ldata->vendor = vendor;
ldata->base = ioremap(adev->res.start, resource_size(&adev->res));
@@ -383,18 +342,35 @@ static int pl031_probe(struct amba_device *adev, struct amba_id *id)
amba_set_drvdata(adev, ldata);
- ldata->hw_designer = amba_manf(adev);
- ldata->hw_revision = amba_rev(adev);
-
- dev_dbg(&adev->dev, "designer ID = 0x%02x\n", ldata->hw_designer);
- dev_dbg(&adev->dev, "revision = 0x%01x\n", ldata->hw_revision);
+ dev_dbg(&adev->dev, "designer ID = 0x%02x\n", amba_manf(adev));
+ dev_dbg(&adev->dev, "revision = 0x%01x\n", amba_rev(adev));
+ data = readl(ldata->base + RTC_CR);
/* Enable the clockwatch on ST Variants */
- if ((ldata->hw_designer == AMBA_VENDOR_ST) &&
- (ldata->hw_revision > 1))
- writel(readl(ldata->base + RTC_CR) | RTC_CR_CWEN,
- ldata->base + RTC_CR);
+ if (vendor->clockwatch)
+ data |= RTC_CR_CWEN;
+ else
+ data |= RTC_CR_EN;
+ writel(data, ldata->base + RTC_CR);
+
+ /*
+ * On ST PL031 variants, the RTC reset value does not provide correct
+ * weekday for 2000-01-01. Correct the erroneous sunday to saturday.
+ */
+ if (vendor->st_weekday) {
+ if (readl(ldata->base + RTC_YDR) == 0x2000) {
+ time = readl(ldata->base + RTC_DR);
+ if ((time &
+ (RTC_MON_MASK | RTC_MDAY_MASK | RTC_WDAY_MASK))
+ == 0x02120000) {
+ time = time | (0x7 << RTC_WDAY_SHIFT);
+ writel(0x2000, ldata->base + RTC_YLR);
+ writel(time, ldata->base + RTC_LR);
+ }
+ }
+ }
+ device_init_wakeup(&adev->dev, 1);
ldata->rtc = rtc_device_register("pl031", &adev->dev, ops,
THIS_MODULE);
if (IS_ERR(ldata->rtc)) {
@@ -403,7 +379,7 @@ static int pl031_probe(struct amba_device *adev, struct amba_id *id)
}
if (request_irq(adev->irq[0], pl031_interrupt,
- IRQF_DISABLED, "rtc-pl031", ldata)) {
+ vendor->irqflags, "rtc-pl031", ldata)) {
ret = -EIO;
goto out_no_irq;
}
@@ -414,7 +390,6 @@ out_no_irq:
rtc_device_unregister(ldata->rtc);
out_no_rtc:
iounmap(ldata->base);
- amba_set_drvdata(adev, NULL);
out_no_remap:
kfree(ldata);
out:
@@ -425,56 +400,71 @@ err_req:
}
/* Operations for the original ARM version */
-static struct rtc_class_ops arm_pl031_ops = {
- .read_time = pl031_read_time,
- .set_time = pl031_set_time,
- .read_alarm = pl031_read_alarm,
- .set_alarm = pl031_set_alarm,
- .alarm_irq_enable = pl031_alarm_irq_enable,
+static struct pl031_vendor_data arm_pl031 = {
+ .ops = {
+ .read_time = pl031_read_time,
+ .set_time = pl031_set_time,
+ .read_alarm = pl031_read_alarm,
+ .set_alarm = pl031_set_alarm,
+ .alarm_irq_enable = pl031_alarm_irq_enable,
+ },
+ .irqflags = IRQF_NO_SUSPEND,
};
/* The First ST derivative */
-static struct rtc_class_ops stv1_pl031_ops = {
- .read_time = pl031_read_time,
- .set_time = pl031_set_time,
- .read_alarm = pl031_read_alarm,
- .set_alarm = pl031_set_alarm,
- .alarm_irq_enable = pl031_alarm_irq_enable,
- .irq_set_state = pl031_irq_set_state,
- .irq_set_freq = pl031_irq_set_freq,
+static struct pl031_vendor_data stv1_pl031 = {
+ .ops = {
+ .read_time = pl031_read_time,
+ .set_time = pl031_set_time,
+ .read_alarm = pl031_read_alarm,
+ .set_alarm = pl031_set_alarm,
+ .alarm_irq_enable = pl031_alarm_irq_enable,
+ },
+ .clockwatch = true,
+ .st_weekday = true,
+ .irqflags = IRQF_NO_SUSPEND,
};
/* And the second ST derivative */
-static struct rtc_class_ops stv2_pl031_ops = {
- .read_time = pl031_stv2_read_time,
- .set_time = pl031_stv2_set_time,
- .read_alarm = pl031_stv2_read_alarm,
- .set_alarm = pl031_stv2_set_alarm,
- .alarm_irq_enable = pl031_alarm_irq_enable,
- .irq_set_state = pl031_irq_set_state,
- .irq_set_freq = pl031_irq_set_freq,
+static struct pl031_vendor_data stv2_pl031 = {
+ .ops = {
+ .read_time = pl031_stv2_read_time,
+ .set_time = pl031_stv2_set_time,
+ .read_alarm = pl031_stv2_read_alarm,
+ .set_alarm = pl031_stv2_set_alarm,
+ .alarm_irq_enable = pl031_alarm_irq_enable,
+ },
+ .clockwatch = true,
+ .st_weekday = true,
+ /*
+ * This variant shares the IRQ with another block and must not
+ * suspend that IRQ line.
+ */
+ .irqflags = IRQF_SHARED | IRQF_NO_SUSPEND,
};
static struct amba_id pl031_ids[] = {
{
.id = 0x00041031,
.mask = 0x000fffff,
- .data = &arm_pl031_ops,
+ .data = &arm_pl031,
},
/* ST Micro variants */
{
.id = 0x00180031,
.mask = 0x00ffffff,
- .data = &stv1_pl031_ops,
+ .data = &stv1_pl031,
},
{
.id = 0x00280031,
.mask = 0x00ffffff,
- .data = &stv2_pl031_ops,
+ .data = &stv2_pl031,
},
{0, 0},
};
+MODULE_DEVICE_TABLE(amba, pl031_ids);
+
static struct amba_driver pl031_driver = {
.drv = {
.name = "rtc-pl031",
@@ -484,18 +474,7 @@ static struct amba_driver pl031_driver = {
.remove = pl031_remove,
};
-static int __init pl031_init(void)
-{
- return amba_driver_register(&pl031_driver);
-}
-
-static void __exit pl031_exit(void)
-{
- amba_driver_unregister(&pl031_driver);
-}
-
-module_init(pl031_init);
-module_exit(pl031_exit);
+module_amba_driver(pl031_driver);
MODULE_AUTHOR("Deepak Saxena <dsaxena@plexity.net");
MODULE_DESCRIPTION("ARM AMBA PL031 RTC Driver");