[PATCHv1] rtc: bcm-iproc: Add support for Broadcom iproc rtc
arun.ramamurthy at broadcom.com
arun.ramamurthy at broadcom.com
Tue Dec 16 11:22:30 PST 2014
From: Arun Ramamurthy <arunrama at broadcom.com>
Adding support for the RTC module used in Broadcom's
iproc architecture
Changes in v1:
Resending patch to larger distribution
Signed-off-by: Arun Ramamurthy <arunrama at broadcom.com>
Reviewed-by: Ray Jui <rjui at broadcom.com>
Reviewed-by: Scott Branden <sbranden at broadcom.com>
Tested-by: Scott Branden <sbranden at broadcom.com>
---
.../devicetree/bindings/rtc/brcm,iproc-rtc.txt | 26 +
drivers/rtc/Kconfig | 11 +
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc-bcm-iproc.c | 613 +++++++++++++++++++++
4 files changed, 651 insertions(+)
create mode 100644 Documentation/devicetree/bindings/rtc/brcm,iproc-rtc.txt
create mode 100644 drivers/rtc/rtc-bcm-iproc.c
diff --git a/Documentation/devicetree/bindings/rtc/brcm,iproc-rtc.txt b/Documentation/devicetree/bindings/rtc/brcm,iproc-rtc.txt
new file mode 100644
index 0000000..f9b354b
--- /dev/null
+++ b/Documentation/devicetree/bindings/rtc/brcm,iproc-rtc.txt
@@ -0,0 +1,26 @@
+Broadcom IPROC Real Time Clock
+
+Required Properties:
+- compatible: should be "brcm,iproc-rtc"
+
+- reg : Requires three separate register sets.
+ spru_bbl: Base address of SPRU_BBL_WDATA used for
+ indirect access to RTC registers
+ crmu_pwr_good_status: Base address of CRMU_PWR_GOOD_STATUS register,
+ used to check power status of BBL
+ (battery backed logic)
+ crum_bbl_auth: Base address of CRMU_BBL_AUTH_CODE register.
+
+- interrupts: RTC Periodic interrupt and RTC Alarm interrupt.
+
+
+Example:
+ rtc: iproc_rtc at 0x03026000 {
+ compatible = "brcm,iproc-rtc";
+ reg = spru_bbl: <0x03026000 0xC>,
+ crmu_pwr_good_status: <0x0301C02C 0x14>,
+ crmu_bbl_auth: <0x03024C74 0x8>;
+ interrupts = spru_rtc_periodic: <GIC_SPI 142 IRQ_TYPE_LEVEL_HIGH>,
+ spru_alarm: <GIC_SPI 133 IRQ_TYPE_LEVEL_HIGH>;
+ status = "disabled";
+ };
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 4511ddc..0a3fd03 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -1426,6 +1426,17 @@ config RTC_DRV_XGENE
This driver can also be built as a module, if so, the module
will be called "rtc-xgene".
+config RTC_DRV_IPROC
+ tristate "IPROC RTC support"
+ depends on ARCH_BCM
+ default ARCH_BCM_IPROC
+ help
+ If you say yes here you get support for Broadcom IPROC RTC subsystem.
+ If unsure, say N.
+
+ This driver can also be built as a module, if so, the module
+ will be called "rtc-bcm-iproc"
+
comment "HID Sensor RTC drivers"
config RTC_DRV_HID_SENSOR_TIME
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index b188323..eca6560 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -64,6 +64,7 @@ obj-$(CONFIG_RTC_DRV_GENERIC) += rtc-generic.o
obj-$(CONFIG_RTC_DRV_HID_SENSOR_TIME) += rtc-hid-sensor-time.o
obj-$(CONFIG_RTC_DRV_HYM8563) += rtc-hym8563.o
obj-$(CONFIG_RTC_DRV_IMXDI) += rtc-imxdi.o
+obj-$(CONFIG_RTC_DRV_IPROC) += rtc-bcm-iproc.o
obj-$(CONFIG_RTC_DRV_ISL1208) += rtc-isl1208.o
obj-$(CONFIG_RTC_DRV_ISL12022) += rtc-isl12022.o
obj-$(CONFIG_RTC_DRV_ISL12057) += rtc-isl12057.o
diff --git a/drivers/rtc/rtc-bcm-iproc.c b/drivers/rtc/rtc-bcm-iproc.c
new file mode 100644
index 0000000..fe29b94
--- /dev/null
+++ b/drivers/rtc/rtc-bcm-iproc.c
@@ -0,0 +1,613 @@
+/*
+ * Copyright (C) 2014 Broadcom Corporation
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/rtc.h>
+#include <linux/bcd.h>
+#include <linux/platform_device.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/bitops.h>
+
+/***********************************************************************
+ * Definitions
+ ***********************************************************************/
+#define IREG_BBL_RTC_PER 0x00000000
+#define IREG_BBL_RTC_MATCH 0x00000004
+#define IREG_BBL_RTC_DIV 0x00000008
+#define IREG_BBL_RTC_SECOND 0x0000000C
+#define IREG_BBL_INTERRUPT_EN 0x00000010
+#define IREG_BBL_INTERRUPT_STAT 0x00000014
+#define IREG_BBL_INTERRUPT_CLR 0x00000018
+#define IREG_BBL_CONTROL 0x0000001C
+#define IREG_BBL_ADDR_MASK 0x3FF
+
+#define BBL_PER_1s 0x00000008
+
+#define CRMU_AUTH_CODE_PWD 0x12345678
+#define CRMU_AUTH_CODE_PWD_RST 0x99999999
+#define CRMU_AUTH_CODE_PWD_CLR 0x0
+
+#define RTC_REG_ACC_DONE BIT(0)
+#define RTC_REG_RTC_STOP BIT(0)
+#define RTC_REG_PERIO_INTR BIT(0)
+#define RTC_REG_ALARM_INTR BIT(1)
+#define RTC_IND_SOFT_RST_N BIT(10)
+#define RTC_REG_WR_CMD BIT(11)
+#define RTC_REG_RD_CMD BIT(12)
+#define CRMU_ISO_PDBBL BIT(16)
+#define CRMU_ISO_PDBBL_TAMPER BIT(24)
+
+/* Timeout when waiting on register
+reads or writes */
+#define REG_TIMEOUT_MICROSECONDS 250
+
+/* SPRU Source Select status
+ 0 - SPRU is powered by AON power
+ 1 - SPRU is powerd by battery */
+#define CRMU_SPRU_SOURCE_SEL_AON 0
+
+struct rtc_regs_t {
+ u32 SPRU_BBL_WDATA;
+ u32 SPRU_BBL_CMD;
+ u32 SPRU_BBL_STATUS;
+ u32 SPRU_BBL_RDATA;
+};
+
+struct crmu_regs_t {
+ u32 CRMU_PWR_GOOD_STATUS;
+ u32 CRMU_POWER_REQ_CFG;
+ u32 CRMU_POWER_POLL;
+ u32 CRMU_ISO_CELL_CONTROL;
+ u32 rsvd;
+ u32 CRMU_SPRU_SOURCE_SEL_STAT;
+};
+
+struct bbl_auth_t {
+ u32 CRMU_BBL_AUTH_CODE;
+ u32 CRMU_BBL_AUTH_CHECK;
+};
+
+struct iproc_rtc_t {
+ struct rtc_device *rtc;
+ struct rtc_regs_t *regs;
+ struct crmu_regs_t *crmu_regs;
+ struct bbl_auth_t *auth_regs;
+ int periodic_irq;
+ int alarm_irq;
+ spinlock_t lock;
+};
+/***********************************************************************
+ * End Definitions
+ ***********************************************************************/
+
+static inline int wait_acc_done(struct iproc_rtc_t *iproc_rtc)
+{
+ u32 reg_val;
+ int timeout = REG_TIMEOUT_MICROSECONDS;
+
+ reg_val = readl(&iproc_rtc->regs->SPRU_BBL_STATUS);
+ while (!(reg_val & RTC_REG_ACC_DONE)) {
+ if (--timeout == 0)
+ return -EIO;
+ udelay(1);
+ reg_val = readl(&iproc_rtc->regs->SPRU_BBL_STATUS);
+ }
+
+ return 0;
+}
+
+static inline int rtc_reg_write(u32 reg_addr, u32 reg_val,
+ struct iproc_rtc_t *iproc_rtc)
+{
+ u32 cmd;
+ int ret;
+
+ writel(reg_val, &iproc_rtc->regs->SPRU_BBL_WDATA);
+ /* Write command */
+ cmd = (reg_addr & IREG_BBL_ADDR_MASK) |
+ RTC_REG_WR_CMD | RTC_IND_SOFT_RST_N;
+ writel(cmd, &iproc_rtc->regs->SPRU_BBL_CMD);
+ ret = wait_acc_done(iproc_rtc);
+ if (ret < 0)
+ pr_err("RTC: reg write to 0x%x failed!", reg_addr);
+
+ return ret;
+}
+
+static inline int rtc_reg_read(u32 reg_addr, u32 *data,
+ struct iproc_rtc_t *iproc_rtc)
+{
+ u32 cmd;
+ int ret;
+
+ /* Read command */
+ cmd = (reg_addr & IREG_BBL_ADDR_MASK) |
+ RTC_REG_RD_CMD | RTC_IND_SOFT_RST_N;
+ writel(cmd, &iproc_rtc->regs->SPRU_BBL_CMD);
+ ret = wait_acc_done(iproc_rtc);
+ if (ret < 0)
+ pr_err("RTC: reg read to 0x%x failed", reg_addr);
+ else
+ *data = readl(&iproc_rtc->regs->SPRU_BBL_RDATA);
+
+ return ret;
+}
+
+static int bbl_init(struct iproc_rtc_t *iproc_rtc)
+{
+ u32 reg_val;
+ int timeout = REG_TIMEOUT_MICROSECONDS;
+
+ /* Check SPRU Source Select status
+ 0 - SPRU is powered by AON power
+ 1 - SPRU is powerd by battery */
+ reg_val = readl(&iproc_rtc->crmu_regs->CRMU_SPRU_SOURCE_SEL_STAT);
+ while (reg_val != CRMU_SPRU_SOURCE_SEL_AON) {
+ if (--timeout == 0) {
+ pr_info("RTC: BBL AON power not available\n");
+ return -ENODEV;
+ }
+ udelay(1);
+ reg_val = readl(
+ &iproc_rtc->crmu_regs->CRMU_SPRU_SOURCE_SEL_STAT);
+ }
+
+ /* Wait for reset cycle */
+ writel(0, &iproc_rtc->regs->SPRU_BBL_CMD);
+ udelay(200);
+ writel(RTC_IND_SOFT_RST_N, &iproc_rtc->regs->SPRU_BBL_CMD);
+
+ /* Remove BBL related isolation from CRMU */
+ reg_val = readl(&iproc_rtc->crmu_regs->CRMU_ISO_CELL_CONTROL);
+ reg_val &= ~(CRMU_ISO_PDBBL | CRMU_ISO_PDBBL_TAMPER);
+ writel(reg_val, &iproc_rtc->crmu_regs->CRMU_ISO_CELL_CONTROL);
+
+ /* program CRMU auth_code resister */
+ writel(CRMU_AUTH_CODE_PWD, &iproc_rtc->auth_regs->CRMU_BBL_AUTH_CODE);
+ /* program CRMU auth_code_check register */
+ /* auth_code must equal to auth_code_check */
+ writel(CRMU_AUTH_CODE_PWD, &iproc_rtc->auth_regs->CRMU_BBL_AUTH_CHECK);
+
+ return 0;
+}
+
+static void bbl_exit(struct iproc_rtc_t *iproc_rtc)
+{
+ u32 reg_val;
+
+ /*- Set BBL related isolation from CRMU */
+ reg_val = readl(&iproc_rtc->crmu_regs->CRMU_ISO_CELL_CONTROL);
+ reg_val |= (CRMU_ISO_PDBBL | CRMU_ISO_PDBBL_TAMPER);
+ writel(reg_val, &iproc_rtc->crmu_regs->CRMU_ISO_CELL_CONTROL);
+
+ /* Change the AUTH CODE register so it does not match
+ * the AUTH_CHECK register */
+ writel(CRMU_AUTH_CODE_PWD_CLR,
+ &iproc_rtc->auth_regs->CRMU_BBL_AUTH_CODE);
+ writel(CRMU_AUTH_CODE_PWD_RST,
+ &iproc_rtc->auth_regs->CRMU_BBL_AUTH_CHECK);
+}
+static int iproc_rtc_enable(struct iproc_rtc_t *iproc_rtc)
+{
+ u32 reg_val;
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&iproc_rtc->lock, flags);
+
+ /* Set periodic timer as 1 second */
+ ret = rtc_reg_write(IREG_BBL_RTC_PER, BBL_PER_1s, iproc_rtc);
+ if (ret < 0)
+ goto err;
+
+ ret = rtc_reg_read(IREG_BBL_INTERRUPT_EN, ®_val, iproc_rtc);
+ if (ret < 0)
+ goto err;
+
+ /* Disable alarm&periodic interrupt */
+ reg_val &= ~(RTC_REG_PERIO_INTR | RTC_REG_ALARM_INTR);
+
+ ret = rtc_reg_write(IREG_BBL_INTERRUPT_EN, reg_val, iproc_rtc);
+ if (ret < 0)
+ goto err;
+
+ ret = rtc_reg_write(IREG_BBL_INTERRUPT_CLR,
+ RTC_REG_PERIO_INTR | RTC_REG_ALARM_INTR, iproc_rtc);
+ if (ret < 0)
+ goto err;
+
+ reg_val |= RTC_REG_PERIO_INTR | RTC_REG_ALARM_INTR;
+ /* enable RTC periodic interrupt */
+ ret = rtc_reg_write(IREG_BBL_INTERRUPT_EN, reg_val, iproc_rtc);
+
+err:
+ spin_unlock_irqrestore(&iproc_rtc->lock, flags);
+ return ret;
+}
+
+static int iproc_rtc_disable(struct iproc_rtc_t *iproc_rtc)
+{
+ u32 reg_val;
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&iproc_rtc->lock, flags);
+
+ ret = rtc_reg_read(IREG_BBL_INTERRUPT_EN, ®_val, iproc_rtc);
+ if (ret < 0)
+ goto err;
+
+ reg_val &= ~(RTC_REG_PERIO_INTR | RTC_REG_ALARM_INTR);
+ /* To disable RTC periodic interrupt */
+ ret = rtc_reg_write(IREG_BBL_INTERRUPT_EN, reg_val, iproc_rtc);
+
+err:
+ spin_unlock_irqrestore(&iproc_rtc->lock, flags);
+ return ret;
+}
+
+static irqreturn_t iproc_rtc_interrupt(int irq, void *class_dev)
+{
+ unsigned long flags, events = 0;
+ u32 reg_val, irq_flg;
+ int ret = 0;
+ struct iproc_rtc_t *iproc_rtc = class_dev;
+
+ spin_lock_irqsave(&iproc_rtc->lock, flags);
+ ret = rtc_reg_read(IREG_BBL_INTERRUPT_STAT, &irq_flg, iproc_rtc);
+ if (ret < 0) {
+ pr_err("RTC: Interrupt Routine failed");
+ goto err;
+ }
+
+ irq_flg &= RTC_REG_PERIO_INTR | RTC_REG_ALARM_INTR;
+ if (!irq_flg)
+ goto err;
+
+ if (irq_flg & RTC_REG_PERIO_INTR) {
+ /* Clear periodic interrupt status */
+ ret = rtc_reg_write(IREG_BBL_INTERRUPT_CLR, RTC_REG_PERIO_INTR,
+ iproc_rtc);
+ if (ret < 0)
+ pr_err("RTC: Failed to clear RTC periodic interrupt event");
+ events |= RTC_IRQF | RTC_PF;
+ }
+
+ if (irq_flg & RTC_REG_ALARM_INTR) {
+ /* Clear alarm interrupt status */
+ ret = rtc_reg_write(IREG_BBL_INTERRUPT_CLR, RTC_REG_ALARM_INTR,
+ iproc_rtc);
+ if (ret < 0)
+ pr_err("RTC: Failed to clear RTC Alarm interrupt event");
+ events |= RTC_IRQF | RTC_AF;
+
+ ret = rtc_reg_read(IREG_BBL_INTERRUPT_EN, ®_val, iproc_rtc);
+ if (ret < 0) {
+ pr_err("RTC: Failed to disable RTC Alarm interrupt after clear");
+ goto err;
+ }
+ reg_val &= ~RTC_REG_ALARM_INTR;
+ /* Disable Alarm interrupt */
+ ret = rtc_reg_write(IREG_BBL_INTERRUPT_EN, reg_val, iproc_rtc);
+ if (ret < 0) {
+ pr_err("RTC: Failed to disable RTC Alarm interrupt after clear");
+ goto err;
+ }
+ }
+
+err:
+ if (events)
+ rtc_update_irq(iproc_rtc->rtc, 1, events);
+ spin_unlock_irqrestore(&iproc_rtc->lock, flags);
+ return IRQ_HANDLED;
+}
+
+static int iproc_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ unsigned long flags;
+ u32 seconds;
+ int ret = 0;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct iproc_rtc_t *iproc_rtc = platform_get_drvdata(pdev);
+
+ spin_lock_irqsave(&iproc_rtc->lock, flags);
+
+ ret = rtc_reg_read(IREG_BBL_RTC_SECOND, &seconds, iproc_rtc);
+ if (ret < 0) {
+ pr_err("RTC: iproc_rtc_read_time failed");
+ goto err;
+ }
+ rtc_time_to_tm(seconds, tm);
+
+ ret = rtc_valid_tm(tm);
+
+err:
+ spin_unlock_irqrestore(&iproc_rtc->lock, flags);
+ return ret;
+}
+
+static int iproc_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+ unsigned long flags, seconds;
+ int ret = 0;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct iproc_rtc_t *iproc_rtc = platform_get_drvdata(pdev);
+
+ rtc_tm_to_time(tm, &seconds);
+ spin_lock_irqsave(&iproc_rtc->lock, flags);
+
+ /* bbl_rtc_stop = 1, RTC hal */
+ ret = rtc_reg_write(IREG_BBL_CONTROL, RTC_REG_RTC_STOP, iproc_rtc);
+ if (ret < 0)
+ goto err;
+ /* Update DIV */
+ ret = rtc_reg_write(IREG_BBL_RTC_DIV, 0, iproc_rtc);
+ if (ret < 0)
+ goto err;
+ /* Update second */
+ ret = rtc_reg_write(IREG_BBL_RTC_SECOND, seconds, iproc_rtc);
+ if (ret < 0)
+ goto err;
+ /* bl_rtc_stop = 0, RTC release */
+ ret = rtc_reg_write(IREG_BBL_CONTROL, ~RTC_REG_RTC_STOP, iproc_rtc);
+
+err:
+ spin_unlock_irqrestore(&iproc_rtc->lock, flags);
+ if (ret < 0)
+ pr_err("RTC: iproc_rtc_set_time failed");
+ return ret;
+}
+
+static int iproc_rtc_alarm_irq_enable(struct device *dev,
+ unsigned int enabled)
+{
+ unsigned long flags;
+ u32 reg_val;
+ int ret = 0;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct iproc_rtc_t *iproc_rtc = platform_get_drvdata(pdev);
+
+ spin_lock_irqsave(&iproc_rtc->lock, flags);
+ ret = rtc_reg_read(IREG_BBL_INTERRUPT_EN, ®_val, iproc_rtc);
+ if (ret < 0)
+ goto err;
+
+ if (enabled)
+ reg_val |= RTC_REG_ALARM_INTR;
+ else
+ reg_val &= ~RTC_REG_ALARM_INTR;
+
+ ret = rtc_reg_write(IREG_BBL_INTERRUPT_EN, reg_val, iproc_rtc);
+
+err:
+ spin_unlock_irqrestore(&iproc_rtc->lock, flags);
+ if (ret < 0)
+ pr_err("RTC: iproc_rtc_alarm_irq_enable failed");
+ return ret;
+}
+
+static int iproc_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm)
+{
+ unsigned long flags;
+ u32 reg_val, seconds;
+ int ret = 0;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct iproc_rtc_t *iproc_rtc = platform_get_drvdata(pdev);
+
+ spin_lock_irqsave(&iproc_rtc->lock, flags);
+ ret = rtc_reg_read(IREG_BBL_RTC_MATCH, &seconds, iproc_rtc);
+ if (ret < 0)
+ goto err;
+ seconds = (seconds << 7);
+ rtc_time_to_tm(seconds, &alm->time);
+ ret = rtc_reg_read(IREG_BBL_INTERRUPT_EN, ®_val, iproc_rtc);
+ if (ret < 0)
+ goto err;
+ reg_val &= RTC_REG_ALARM_INTR;
+ alm->pending = !reg_val;
+ alm->enabled = alm->pending && device_may_wakeup(dev);
+
+err:
+ spin_unlock_irqrestore(&iproc_rtc->lock, flags);
+ if (ret < 0)
+ pr_err("RTC: iproc_rtc_read_alarm failed");
+ return ret;
+}
+
+static int iproc_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm)
+{
+ unsigned long flags;
+ unsigned long seconds;
+ int ret = 0;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct iproc_rtc_t *iproc_rtc = platform_get_drvdata(pdev);
+
+ spin_lock_irqsave(&iproc_rtc->lock, flags);
+ rtc_tm_to_time(&alm->time, &seconds);
+ seconds = ((seconds & (0xFFFF << 7)) >> 7) & 0xFFFF;
+ ret = rtc_reg_write(IREG_BBL_RTC_MATCH, seconds, iproc_rtc);
+ spin_unlock_irqrestore(&iproc_rtc->lock, flags);
+ if (ret < 0)
+ pr_err("RTC: iproc_rtc_set_alarm failed");
+
+ return ret;
+}
+
+static struct rtc_class_ops iproc_rtc_ops = {
+ .read_time = iproc_rtc_read_time,
+ .set_time = iproc_rtc_set_time,
+ .alarm_irq_enable = iproc_rtc_alarm_irq_enable,
+ .read_alarm = iproc_rtc_read_alarm,
+ .set_alarm = iproc_rtc_set_alarm,
+};
+
+static const struct of_device_id iproc_rtc_of_match[] = {
+ {.compatible = "brcm,iproc-rtc",},
+ { }
+};
+
+static int iproc_rtc_probe(struct platform_device *pdev)
+{
+ struct device_node *dev_of = pdev->dev.of_node;
+ struct resource *res;
+ int ret = 0;
+ struct iproc_rtc_t *iproc_rtc;
+
+ iproc_rtc = devm_kzalloc(&pdev->dev, sizeof(struct iproc_rtc_t),
+ GFP_KERNEL);
+ spin_lock_init(&iproc_rtc->lock);
+
+ iproc_rtc->periodic_irq = irq_of_parse_and_map(dev_of, 0);
+ if (iproc_rtc->periodic_irq < 0) {
+ dev_err(&pdev->dev, "no RTC periodic irq\n");
+ ret = -ENODEV;
+ goto fail;
+ }
+
+ iproc_rtc->alarm_irq = irq_of_parse_and_map(dev_of, 1);
+ if (iproc_rtc->alarm_irq < 0) {
+ dev_err(&pdev->dev, "no RTC alarm irq\n");
+ ret = -ENODEV;
+ goto fail;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (res == NULL) {
+ dev_err(&pdev->dev, "spru_bbl MEM resource missing\n");
+ ret = -ENOMEM;
+ goto fail;
+ }
+ iproc_rtc->regs = (struct rtc_regs_t *)
+ devm_ioremap_resource(&pdev->dev, res);
+ if (!iproc_rtc->regs) {
+ dev_err(&pdev->dev, "unable to ioremap spru_bbl MEM resource\n");
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (res == NULL) {
+ dev_err(&pdev->dev, "crmu_pwr_good MEM resource missing\n");
+ ret = -ENOMEM;
+ goto fail;
+ }
+ iproc_rtc->crmu_regs = (struct crmu_regs_t *)
+ devm_ioremap_resource(&pdev->dev, res);
+ if (!iproc_rtc->crmu_regs) {
+ dev_err(&pdev->dev, "unable to ioremap crmu_pwr_good MEM resource\n");
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+ if (res == NULL) {
+ dev_err(&pdev->dev, "crmu_bbl_auth MEM resource missing\n");
+ ret = -ENOMEM;
+ goto fail;
+ }
+ iproc_rtc->auth_regs = (struct bbl_auth_t *)
+ devm_ioremap_resource(&pdev->dev, res);
+ if (!iproc_rtc->auth_regs) {
+ dev_err(&pdev->dev, "unable to ioremap crmu_bbl_auth MEM resource\n");
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ platform_set_drvdata(pdev, iproc_rtc);
+
+ ret = bbl_init(iproc_rtc);
+ if (ret < 0)
+ goto fail;
+
+ ret = iproc_rtc_enable(iproc_rtc);
+ if (ret < 0) {
+ pr_err("RTC: Enable failed");
+ goto fail_bbl;
+ }
+
+ ret = devm_request_irq
+ (&pdev->dev, iproc_rtc->periodic_irq, iproc_rtc_interrupt,
+ IRQF_SHARED, "iproc_rtc_peri", iproc_rtc);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "unable to register iproc RTC periodic interrupt\n");
+ goto fail_rtc;
+ }
+
+ ret = devm_request_irq
+ (&pdev->dev, iproc_rtc->alarm_irq, iproc_rtc_interrupt, 0,
+ "iproc_rtc_alarm", iproc_rtc);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "unable to register iproc RTC alarm interrupt\n");
+ goto fail_rtc;
+ }
+
+ iproc_rtc->rtc = devm_rtc_device_register(&pdev->dev, "iproc-rtc",
+ &iproc_rtc_ops, THIS_MODULE);
+ if (IS_ERR(iproc_rtc->rtc)) {
+ dev_err(&pdev->dev, "unable to register RTC device, err %ld\n",
+ PTR_ERR(iproc_rtc->rtc));
+ ret = PTR_ERR(iproc_rtc->rtc);
+ goto fail_rtc;
+ }
+
+ device_init_wakeup(&pdev->dev, 1);
+ return 0;
+
+fail_rtc:
+ iproc_rtc_disable(iproc_rtc);
+fail_bbl:
+ bbl_exit(iproc_rtc);
+fail:
+ platform_set_drvdata(pdev, NULL);
+ return ret;
+}
+
+static int iproc_rtc_remove(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct iproc_rtc_t *iproc_rtc = platform_get_drvdata(pdev);
+
+ device_init_wakeup(&pdev->dev, 0);
+ rtc_device_unregister(iproc_rtc->rtc);
+ ret = iproc_rtc_disable(iproc_rtc);
+ if (ret < 0)
+ pr_err("RTC: Disable failed");
+ bbl_exit(iproc_rtc);
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+static struct platform_driver iproc_rtc_driver = {
+ .probe = iproc_rtc_probe,
+ .remove = iproc_rtc_remove,
+ .driver = {
+ .name = "iproc-rtc",
+ .of_match_table = iproc_rtc_of_match
+ },
+};
+
+module_platform_driver(iproc_rtc_driver);
+
+MODULE_AUTHOR("Broadcom");
+MODULE_DESCRIPTION("Broadcom IPROC RTC Driver");
+MODULE_LICENSE("GPL");
--
2.2.0
More information about the linux-arm-kernel
mailing list