2014-12-16 19:22:45

by Arun Ramamurthy

[permalink] [raw]
Subject: [PATCHv1] rtc: bcm-iproc: Add support for Broadcom iproc rtc

From: Arun Ramamurthy <[email protected]>

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 <[email protected]>
Reviewed-by: Ray Jui <[email protected]>
Reviewed-by: Scott Branden <[email protected]>
Tested-by: Scott Branden <[email protected]>
---
.../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@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, &reg_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, &reg_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, &reg_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, &reg_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, &reg_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


2014-12-16 19:43:33

by Arnd Bergmann

[permalink] [raw]
Subject: Re: [PATCHv1] rtc: bcm-iproc: Add support for Broadcom iproc rtc

On Tuesday 16 December 2014 11:22:30 [email protected] wrote:
> + rtc: iproc_rtc@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>;

The reg properties look really random, could it be that the registers
are really part of some other device that contains multiple functions?

Also, what do you use the labels for?

Arnd

2014-12-16 20:05:37

by Arun Ramamurthy

[permalink] [raw]
Subject: Re: [PATCHv1] rtc: bcm-iproc: Add support for Broadcom iproc rtc



On 14-12-16 11:42 AM, Arnd Bergmann wrote:
> On Tuesday 16 December 2014 11:22:30 [email protected] wrote:
>> + rtc: iproc_rtc@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>;
>
> The reg properties look really random, could it be that the registers
> are really part of some other device that contains multiple functions?
>
This RTC block is on a battery backed logic island and is accessed
indirectly using the spru_bbl registers. The CRMU registers are required
to read the power status and write to some authentication registers.
Without writing to these authentication
registers, we cannot access any of the spru_bbl registers. In conclusion
I don't think the CRMU register accesses can be considered as another
device access. Do you agree Arnd?

> Also, what do you use the labels for?
>
The labels are purely to improve readability of the device tree entry
> Arnd
>

2014-12-16 20:20:06

by Arnd Bergmann

[permalink] [raw]
Subject: Re: [PATCHv1] rtc: bcm-iproc: Add support for Broadcom iproc rtc

On Tuesday 16 December 2014 12:05:08 Arun Ramamurthy wrote:
> On 14-12-16 11:42 AM, Arnd Bergmann wrote:
> > On Tuesday 16 December 2014 11:22:30 [email protected] wrote:
> >> + rtc: iproc_rtc@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>;
> >
> > The reg properties look really random, could it be that the registers
> > are really part of some other device that contains multiple functions?
> >
> This RTC block is on a battery backed logic island and is accessed
> indirectly using the spru_bbl registers. The CRMU registers are required
> to read the power status and write to some authentication registers.
> Without writing to these authentication
> registers, we cannot access any of the spru_bbl registers. In conclusion
> I don't think the CRMU register accesses can be considered as another
> device access. Do you agree Arnd?

It sounds like CRMU is some other unit aside from the RTC. Could this
be something like a generic system controller? I think it should
either have its own driver or use the syscon logic if that is what
this is.

> > Also, what do you use the labels for?
> >
> The labels are purely to improve readability of the device tree entry

Please remove them then, they don't help at all.

Arnd

2014-12-16 20:27:57

by Ray Jui

[permalink] [raw]
Subject: Re: [PATCHv1] rtc: bcm-iproc: Add support for Broadcom iproc rtc



On 12/16/2014 12:19 PM, Arnd Bergmann wrote:
> On Tuesday 16 December 2014 12:05:08 Arun Ramamurthy wrote:
>> On 14-12-16 11:42 AM, Arnd Bergmann wrote:
>>> On Tuesday 16 December 2014 11:22:30 [email protected] wrote:
>>>> + rtc: iproc_rtc@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>;
>>>
>>> The reg properties look really random, could it be that the registers
>>> are really part of some other device that contains multiple functions?
>>>
>> This RTC block is on a battery backed logic island and is accessed
>> indirectly using the spru_bbl registers. The CRMU registers are required
>> to read the power status and write to some authentication registers.
>> Without writing to these authentication
>> registers, we cannot access any of the spru_bbl registers. In conclusion
>> I don't think the CRMU register accesses can be considered as another
>> device access. Do you agree Arnd?
>
> It sounds like CRMU is some other unit aside from the RTC. Could this
> be something like a generic system controller? I think it should
> either have its own driver or use the syscon logic if that is what
> this is.
>
Giving that CRMU has scattered, miscellaneous control logic for multiple
different peripherals, it probably makes more sense to use the syscon
logic here.

>>> Also, what do you use the labels for?
>>>
>> The labels are purely to improve readability of the device tree entry
>
> Please remove them then, they don't help at all.
>
> Arnd
>

2014-12-16 21:54:16

by Arun Ramamurthy

[permalink] [raw]
Subject: Re: [PATCHv1] rtc: bcm-iproc: Add support for Broadcom iproc rtc



On 14-12-16 12:27 PM, Ray Jui wrote:
>
>
> On 12/16/2014 12:19 PM, Arnd Bergmann wrote:
>> On Tuesday 16 December 2014 12:05:08 Arun Ramamurthy wrote:
>>> On 14-12-16 11:42 AM, Arnd Bergmann wrote:
>>>> On Tuesday 16 December 2014 11:22:30 [email protected]
>>>> wrote:
>>>>> + rtc: iproc_rtc@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>;
>>>>
>>>> The reg properties look really random, could it be that the registers
>>>> are really part of some other device that contains multiple functions?
>>>>
>>> This RTC block is on a battery backed logic island and is accessed
>>> indirectly using the spru_bbl registers. The CRMU registers are required
>>> to read the power status and write to some authentication registers.
>>> Without writing to these authentication
>>> registers, we cannot access any of the spru_bbl registers. In conclusion
>>> I don't think the CRMU register accesses can be considered as another
>>> device access. Do you agree Arnd?
>>
>> It sounds like CRMU is some other unit aside from the RTC. Could this
>> be something like a generic system controller? I think it should
>> either have its own driver or use the syscon logic if that is what
>> this is.
>>
> Giving that CRMU has scattered, miscellaneous control logic for multiple
> different peripherals, it probably makes more sense to use the syscon
> logic here.
>
Arnd, thanks for the feedback. If I was to write a separate driver for
the CRMU, I would have to export certain functions and create an api
that only this RTC driver would use. I am not sure that is efficient or
required. What is your opinion?
Would it be better if I use the syson api in my current driver and move
the CRMU registers to separate syscon device tree entry?

>>>> Also, what do you use the labels for?
>>>>
>>> The labels are purely to improve readability of the device tree entry
>>
>> Please remove them then, they don't help at all.
>>
Sure, I will remove the labels
>> Arnd
>>

2014-12-17 14:32:26

by Arnd Bergmann

[permalink] [raw]
Subject: Re: [PATCHv1] rtc: bcm-iproc: Add support for Broadcom iproc rtc

On Tuesday 16 December 2014 13:54:04 Arun Ramamurthy wrote:
> On 14-12-16 12:27 PM, Ray Jui wrote:
> > On 12/16/2014 12:19 PM, Arnd Bergmann wrote:
> >>
> >> It sounds like CRMU is some other unit aside from the RTC. Could this
> >> be something like a generic system controller? I think it should
> >> either have its own driver or use the syscon logic if that is what
> >> this is.
> >>
> > Giving that CRMU has scattered, miscellaneous control logic for multiple
> > different peripherals, it probably makes more sense to use the syscon
> > logic here.
> >
> Arnd, thanks for the feedback. If I was to write a separate driver for
> the CRMU, I would have to export certain functions and create an api
> that only this RTC driver would use. I am not sure that is efficient or
> required. What is your opinion?
> Would it be better if I use the syson api in my current driver and move
> the CRMU registers to separate syscon device tree entry?
>

This is something that's normally up to the platform maintainers, depending
on what works best for a given SoC. If you have a control block that
wants to export the same high-level API for multiple drivers, that's
fine, but if literally every register does something different, a syscon
driver works best.

It's also possible that some of the functions of the CRMU already have
abstractions, like system-reset, device-reset, regulator or clock support.
In that case, you can still use syscon but have the more other drivers
use that for accessing the registers.

Arnd

2015-02-12 22:16:24

by Arun Ramamurthy

[permalink] [raw]
Subject: Re: [PATCHv1] rtc: bcm-iproc: Add support for Broadcom iproc rtc

Hi Arnd

My apologies for the late reply, I was moved to other work items. I
wanted to get more clarification on the syscon issue so that I can
submit the next patch set. If I understand correctly, you would like
me to move the CRMU logic to a new driver under mfd/ and use the syscon
api calls in my rtc driver? Thanks

Arun

On 14-12-17 06:31 AM, Arnd Bergmann wrote:
> On Tuesday 16 December 2014 13:54:04 Arun Ramamurthy wrote:
>> On 14-12-16 12:27 PM, Ray Jui wrote:
>>> On 12/16/2014 12:19 PM, Arnd Bergmann wrote:
>>>>
>>>> It sounds like CRMU is some other unit aside from the RTC. Could this
>>>> be something like a generic system controller? I think it should
>>>> either have its own driver or use the syscon logic if that is what
>>>> this is.
>>>>
>>> Giving that CRMU has scattered, miscellaneous control logic for multiple
>>> different peripherals, it probably makes more sense to use the syscon
>>> logic here.
>>>
>> Arnd, thanks for the feedback. If I was to write a separate driver for
>> the CRMU, I would have to export certain functions and create an api
>> that only this RTC driver would use. I am not sure that is efficient or
>> required. What is your opinion?
>> Would it be better if I use the syson api in my current driver and move
>> the CRMU registers to separate syscon device tree entry?
>>
>
> This is something that's normally up to the platform maintainers, depending
> on what works best for a given SoC. If you have a control block that
> wants to export the same high-level API for multiple drivers, that's
> fine, but if literally every register does something different, a syscon
> driver works best.
>
> It's also possible that some of the functions of the CRMU already have
> abstractions, like system-reset, device-reset, regulator or clock support.
> In that case, you can still use syscon but have the more other drivers
> use that for accessing the registers.
>
> Arnd
>