Message ID | 1418755898-3342-1-git-send-email-arun.ramamurthy@broadcom.com |
---|---|
State | Superseded |
Headers | show |
Forgot to run get_maintainers script. Will resend to larger distribution. On 14-12-16 10:51 AM, arun.ramamurthy@broadcom.com wrote: > From: Arun Ramamurthy <arunrama@broadcom.com> > > Adding support for the RTC module used in Broadcom's > iproc architecture > > Signed-off-by: Arun Ramamurthy <arunrama@broadcom.com> > Reviewed-by: Ray Jui <rjui@broadcom.com> > Reviewed-by: Scott Branden <sbranden@broadcom.com> > Tested-by: Scott Branden <sbranden@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@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"); >
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, ®_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");