A 32bits RTC is housed inside PMIC. The RTC driver uses QPNP SPMI interface to communicate with the PMIC RTC module. The RTC device is divided into two sub-peripherals: - RTC read-write peripheral having basic RTC registers - alarm peripheral for controlling alarm These two RTC peripherals are childrens of QPNP SPMI bus. They use regmap to read/write to its registers into PMIC. Signed-off-by: Stanimir Varbanov <svarbanov@xxxxxxxxxx> --- drivers/rtc/Kconfig | 8 + drivers/rtc/Makefile | 1 + drivers/rtc/rtc-qpnp.c | 489 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 498 insertions(+), 0 deletions(-) create mode 100644 drivers/rtc/rtc-qpnp.c diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 0754f5c..eb97b0a 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -1365,6 +1365,14 @@ 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_QPNP + tristate "Qualcomm QPNP PMIC RTC" + depends on MFD_QPNP_SPMI + help + Say Y here if you want to support the Qualcomm QPNP PMIC RTC. + To compile this driver as a module, choose M here: the + module will be called rtc-qpnp. + comment "HID Sensor RTC drivers" config RTC_DRV_HID_SENSOR_TIME diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 70347d0..52488b5 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -141,3 +141,4 @@ obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o obj-$(CONFIG_RTC_DRV_XGENE) += rtc-xgene.o obj-$(CONFIG_RTC_DRV_SIRFSOC) += rtc-sirfsoc.o obj-$(CONFIG_RTC_DRV_MOXART) += rtc-moxart.o +obj-$(CONFIG_RTC_DRV_QPNP) += rtc-qpnp.o diff --git a/drivers/rtc/rtc-qpnp.c b/drivers/rtc/rtc-qpnp.c new file mode 100644 index 0000000..ea26e62 --- /dev/null +++ b/drivers/rtc/rtc-qpnp.c @@ -0,0 +1,489 @@ +/* Copyright (c) 2012-2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/rtc.h> +#include <linux/spinlock.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +/* RTC/ALARM register offsets */ +#define REG_ALARM_RW 0x40 +#define REG_ALARM_CTRL1 0x46 +#define REG_ALARM_CTRL2 0x48 +#define REG_RTC_WRITE 0x40 +#define REG_RTC_CTRL 0x46 +#define REG_RTC_READ 0x48 +#define REG_PERP_SUBTYPE 0x05 + +/* RTC_CTRL register bit fields */ +#define RTC_ENABLE BIT(7) +#define RTC_ALARM_ENABLE BIT(7) +#define RTC_ABORT_ENABLE BIT(0) +#define RTC_ALARM_CLEAR BIT(0) + +/* RTC/ALARM peripheral subtype values */ +#define PERPH_SUBTYPE_RTC 0x1 +#define PERPH_SUBTYPE_ALARM 0x3 +#define NUM_8_BIT_RTC_REGS 0x4 + +#define TO_SECS(arr) \ + (arr[0] | (arr[1] << 8) | (arr[2] << 16) | (arr[3] << 24)) + +/* rtc driver internal structure */ +struct qpnp_rtc { + struct regmap *regmap; + struct device *dev; + struct rtc_device *rtc; + spinlock_t lock; /* to protect RTC control register */ + struct rtc_class_ops ops; + u8 rtc_ctrl_reg; + u8 alarm_ctrl_reg1; + u16 rtc_base; + u16 alarm_base; + int alarm_irq; +}; + +static int qpnp_rtc_read(struct qpnp_rtc *rtc, u8 *val, u16 off, int len) +{ + return regmap_bulk_read(rtc->regmap, rtc->rtc_base + off, val, len); +} + +static int qpnp_rtc_write(struct qpnp_rtc *rtc, u8 *val, u16 off, int len) +{ + return regmap_bulk_write(rtc->regmap, rtc->rtc_base + off, val, len); +} + +static int qpnp_alarm_read(struct qpnp_rtc *rtc, u8 *val, u16 off, int len) +{ + return regmap_bulk_read(rtc->regmap, rtc->alarm_base + off, val, len); +} + +static int qpnp_alarm_write(struct qpnp_rtc *rtc, u8 *val, u16 off, int len) +{ + return regmap_bulk_write(rtc->regmap, rtc->alarm_base + off, val, len); +} + +static int qpnp_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct qpnp_rtc *rtc = dev_get_drvdata(dev); + u8 value[4], reg = 0, alarm_enabled = 0, ctrl_reg; + u8 rtc_disabled = 0, rtc_ctrl_reg; + unsigned long secs, flags; + int ret; + + rtc_tm_to_time(tm, &secs); + + value[0] = (secs >> 0) & 0xff; + value[1] = (secs >> 8) & 0xff; + value[2] = (secs >> 16) & 0xff; + value[3] = (secs >> 24) & 0xff; + + dev_dbg(dev, "Seconds value to be written to RTC = %lu\n", secs); + + spin_lock_irqsave(&rtc->lock, flags); + ctrl_reg = rtc->alarm_ctrl_reg1; + + if (ctrl_reg & RTC_ALARM_ENABLE) { + alarm_enabled = 1; + ctrl_reg &= ~RTC_ALARM_ENABLE; + ret = qpnp_alarm_write(rtc, &ctrl_reg, REG_ALARM_CTRL1, 1); + if (ret) + goto rtc_rw_fail; + } else { + spin_unlock_irqrestore(&rtc->lock, flags); + } + + /* + * 32 bit seconds value is coverted to four 8 bit values + * |<------ 32 bit time value in seconds ------>| + * <- 8 bit ->|<- 8 bit ->|<- 8 bit ->|<- 8 bit ->| + * ---------------------------------------------- + * | BYTE[3] | BYTE[2] | BYTE[1] | BYTE[0] | + * ---------------------------------------------- + * + * RTC has four 8 bit registers for writting time in seconds: + * WDATA[3], WDATA[2], WDATA[1], WDATA[0] + * + * Write to the RTC registers should be done in following order + * Clear WDATA[0] register + * + * Write BYTE[1], BYTE[2] and BYTE[3] of time to + * RTC WDATA[3], WDATA[2], WDATA[1] registers + * + * Write BYTE[0] of time to RTC WDATA[0] register + * + * Clearing BYTE[0] and writting in the end will prevent any + * unintentional overflow from WDATA[0] to higher bytes during the + * write operation + */ + + /* Disable RTC H/w before writing on RTC register*/ + rtc_ctrl_reg = rtc->rtc_ctrl_reg; + if (rtc_ctrl_reg & RTC_ENABLE) { + rtc_disabled = 1; + rtc_ctrl_reg &= ~RTC_ENABLE; + ret = qpnp_rtc_write(rtc, &rtc_ctrl_reg, REG_RTC_CTRL, 1); + if (ret) + goto rtc_rw_fail; + rtc->rtc_ctrl_reg = rtc_ctrl_reg; + } + + /* Clear WDATA[0] */ + reg = 0x0; + ret = qpnp_rtc_write(rtc, ®, REG_RTC_WRITE, 1); + if (ret) + goto rtc_rw_fail; + + /* Write to WDATA[3], WDATA[2] and WDATA[1] */ + ret = qpnp_rtc_write(rtc, &value[1], REG_RTC_WRITE + 1, 3); + if (ret) + goto rtc_rw_fail; + + /* Write to WDATA[0] */ + ret = qpnp_rtc_write(rtc, value, REG_RTC_WRITE, 1); + if (ret) + goto rtc_rw_fail; + + /* Enable RTC after writing on RTC register */ + if (rtc_disabled) { + rtc_ctrl_reg |= RTC_ENABLE; + ret = qpnp_rtc_write(rtc, &rtc_ctrl_reg, REG_RTC_CTRL, 1); + if (ret) + goto rtc_rw_fail; + rtc->rtc_ctrl_reg = rtc_ctrl_reg; + } + + if (alarm_enabled) { + ctrl_reg |= RTC_ALARM_ENABLE; + ret = qpnp_alarm_write(rtc, &ctrl_reg, REG_ALARM_CTRL1, 1); + if (ret) + goto rtc_rw_fail; + } + + rtc->alarm_ctrl_reg1 = ctrl_reg; + +rtc_rw_fail: + if (alarm_enabled) + spin_unlock_irqrestore(&rtc->lock, flags); + + return ret; +} + +static int qpnp_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct qpnp_rtc *rtc = dev_get_drvdata(dev); + u8 value[4], reg; + unsigned long secs; + int ret; + + ret = qpnp_rtc_read(rtc, value, REG_RTC_READ, NUM_8_BIT_RTC_REGS); + if (ret) + return ret; + + /* + * Read the LSB again and check if there has been a carry over + * If there is, redo the read operation + */ + ret = qpnp_rtc_read(rtc, ®, REG_RTC_READ, 1); + if (ret) + return ret; + + if (reg < value[0]) { + ret = qpnp_rtc_read(rtc, value, REG_RTC_READ, + NUM_8_BIT_RTC_REGS); + if (ret) + return ret; + } + + secs = TO_SECS(value); + rtc_time_to_tm(secs, tm); + + ret = rtc_valid_tm(tm); + if (ret) + return ret; + + dev_dbg(dev, "secs = %lu, h:m:s == %d:%d:%d, d/m/y = %d/%d/%d\n", + secs, tm->tm_hour, tm->tm_min, tm->tm_sec, + tm->tm_mday, tm->tm_mon, tm->tm_year); + + return 0; +} + +static int qpnp_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct qpnp_rtc *rtc = dev_get_drvdata(dev); + unsigned long secs, secs_rtc, irq_flags; + struct rtc_time rtc_tm; + u8 value[4], ctrl_reg; + int ret; + + rtc_tm_to_time(&alarm->time, &secs); + + /* + * Read the current RTC time and verify if the alarm time is in the + * past. If yes, return invalid + */ + ret = qpnp_rtc_read_time(dev, &rtc_tm); + if (ret) + return -EINVAL; + + rtc_tm_to_time(&rtc_tm, &secs_rtc); + if (secs < secs_rtc) + return -EINVAL; + + value[0] = (secs >> 0) & 0xff; + value[1] = (secs >> 8) & 0xff; + value[2] = (secs >> 16) & 0xff; + value[3] = (secs >> 24) & 0xff; + + spin_lock_irqsave(&rtc->lock, irq_flags); + + ret = qpnp_alarm_write(rtc, value, REG_ALARM_RW, NUM_8_BIT_RTC_REGS); + if (ret) + goto rtc_rw_fail; + + ctrl_reg = alarm->enabled ? + (rtc->alarm_ctrl_reg1 | RTC_ALARM_ENABLE) : + (rtc->alarm_ctrl_reg1 & ~RTC_ALARM_ENABLE); + + ret = qpnp_alarm_write(rtc, &ctrl_reg, REG_ALARM_CTRL1, 1); + if (ret) + goto rtc_rw_fail; + + rtc->alarm_ctrl_reg1 = ctrl_reg; + + dev_dbg(dev, "Alarm Set for h:r:s=%d:%d:%d, d/m/y=%d/%d/%d\n", + alarm->time.tm_hour, alarm->time.tm_min, + alarm->time.tm_sec, alarm->time.tm_mday, + alarm->time.tm_mon, alarm->time.tm_year); + +rtc_rw_fail: + spin_unlock_irqrestore(&rtc->lock, irq_flags); + return ret; +} + +static int qpnp_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct qpnp_rtc *rtc = dev_get_drvdata(dev); + unsigned long secs; + u8 value[4]; + int ret; + + ret = qpnp_alarm_read(rtc, value, REG_ALARM_RW, NUM_8_BIT_RTC_REGS); + if (ret) + return ret; + + secs = TO_SECS(value); + rtc_time_to_tm(secs, &alarm->time); + + ret = rtc_valid_tm(&alarm->time); + if (ret) + return ret; + + dev_dbg(dev, "Alarm set for - h:r:s=%d:%d:%d, d/m/y=%d/%d/%d\n", + alarm->time.tm_hour, alarm->time.tm_min, + alarm->time.tm_sec, alarm->time.tm_mday, + alarm->time.tm_mon, alarm->time.tm_year); + + return 0; +} + +static int qpnp_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) +{ + struct qpnp_rtc *rtc = dev_get_drvdata(dev); + unsigned long flags; + u8 ctrl_reg; + u8 value[4] = {0}; + int ret; + + spin_lock_irqsave(&rtc->lock, flags); + + ctrl_reg = rtc->alarm_ctrl_reg1; + ctrl_reg = enabled ? + (ctrl_reg | RTC_ALARM_ENABLE) : (ctrl_reg & ~RTC_ALARM_ENABLE); + + ret = qpnp_alarm_write(rtc, &ctrl_reg, REG_ALARM_CTRL1, 1); + if (ret) + goto rtc_rw_fail; + + rtc->alarm_ctrl_reg1 = ctrl_reg; + + /* Clear Alarm register */ + if (!enabled) + ret = qpnp_alarm_write(rtc, value, REG_ALARM_RW, + NUM_8_BIT_RTC_REGS); + +rtc_rw_fail: + spin_unlock_irqrestore(&rtc->lock, flags); + + return ret; +} + +static irqreturn_t qpnp_alarm_trigger(int irq, void *dev_id) +{ + struct qpnp_rtc *rtc = dev_id; + unsigned long flags; + u8 ctrl_reg; + int ret; + + rtc_update_irq(rtc->rtc, 1, RTC_IRQF | RTC_AF); + + spin_lock_irqsave(&rtc->lock, flags); + + /* Clear the alarm enable bit */ + ctrl_reg = rtc->alarm_ctrl_reg1; + ctrl_reg &= ~RTC_ALARM_ENABLE; + + ret = qpnp_alarm_write(rtc, &ctrl_reg, REG_ALARM_CTRL1, 1); + if (ret) { + spin_unlock_irqrestore(&rtc->lock, flags); + return IRQ_HANDLED; + } + + rtc->alarm_ctrl_reg1 = ctrl_reg; + spin_unlock_irqrestore(&rtc->lock, flags); + + /* Set ALARM_CLR bit */ + ctrl_reg = 0x1; + ret = qpnp_alarm_write(rtc, &ctrl_reg, REG_ALARM_CTRL2, 1); + if (ret) + dev_dbg(rtc->dev, "Write to ALARM control reg failed\n"); + + return IRQ_HANDLED; +} + +static int qpnp_rtc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct qpnp_rtc *rtc; + struct resource *res; + u8 subtype; + int ret; + + rtc = devm_kzalloc(dev, sizeof(*rtc), GFP_KERNEL); + if (!rtc) + return -ENOMEM; + + spin_lock_init(&rtc->lock); + + rtc->regmap = dev_get_regmap(dev->parent, NULL); + if (!rtc->regmap) + return -ENODEV; + + rtc->dev = dev; + + res = platform_get_resource_byname(pdev, IORESOURCE_REG, "rtc"); + if (!res) + return -ENODEV; + + rtc->rtc_base = res->start; + + res = platform_get_resource_byname(pdev, IORESOURCE_REG, "alarm"); + if (!res) + return -ENODEV; + + rtc->alarm_base = res->start; + + ret = qpnp_rtc_read(rtc, &subtype, REG_PERP_SUBTYPE, 1); + if (ret) + return ret; + + if (subtype != PERPH_SUBTYPE_RTC) + return -ENODEV; + + ret = qpnp_alarm_read(rtc, &subtype, REG_PERP_SUBTYPE, 1); + if (ret) + return ret; + + if (subtype != PERPH_SUBTYPE_ALARM) + return -ENODEV; + + rtc->alarm_irq = platform_get_irq(pdev, 0); + if (rtc->alarm_irq < 0) + return rtc->alarm_irq; + + ret = qpnp_rtc_read(rtc, &rtc->rtc_ctrl_reg, REG_RTC_CTRL, 1); + if (ret) + return ret; + + if (!(rtc->rtc_ctrl_reg & RTC_ENABLE)) { + dev_dbg(dev, "RTC h/w disabled, rtc not registered\n"); + return -ENODEV; + } + + ret = qpnp_alarm_read(rtc, &rtc->alarm_ctrl_reg1, REG_ALARM_CTRL1, 1); + if (ret) + return ret; + + rtc->alarm_ctrl_reg1 |= RTC_ABORT_ENABLE; + ret = qpnp_alarm_write(rtc, &rtc->alarm_ctrl_reg1, REG_ALARM_CTRL1, 1); + if (ret) + return ret; + + ret = devm_request_any_context_irq(dev, rtc->alarm_irq, + qpnp_alarm_trigger, + IRQF_TRIGGER_RISING, + "qpnp_rtc_alarm", rtc); + if (ret < 0) + return ret; + + device_init_wakeup(dev, 1); + enable_irq_wake(rtc->alarm_irq); + + platform_set_drvdata(pdev, rtc); + + rtc->ops.read_time = qpnp_rtc_read_time; + rtc->ops.set_alarm = qpnp_rtc_set_alarm; + rtc->ops.read_alarm = qpnp_rtc_read_alarm; + rtc->ops.alarm_irq_enable = qpnp_rtc_alarm_irq_enable; + rtc->ops.set_time = qpnp_rtc_set_time; + + rtc->rtc = rtc_device_register("qpnp-rtc", dev, &rtc->ops, THIS_MODULE); + if (IS_ERR(rtc->rtc)) + return PTR_ERR(rtc->rtc); + + return 0; +} + +static int qpnp_rtc_remove(struct platform_device *pdev) +{ + struct qpnp_rtc *rtc = platform_get_drvdata(pdev); + + device_init_wakeup(rtc->dev, 0); + free_irq(rtc->alarm_irq, rtc); + rtc_device_unregister(rtc->rtc); + + return 0; +} + +static const struct of_device_id qpnp_rtc_table[] = { + { .compatible = "qcom,qpnp-rtc", }, + {} +}; +MODULE_DEVICE_TABLE(of, rtc_qpnp_table); + +static struct platform_driver qpnp_rtc_driver = { + .probe = qpnp_rtc_probe, + .remove = qpnp_rtc_remove, + .driver = { + .name = "qpnp-rtc", + .owner = THIS_MODULE, + .of_match_table = qpnp_rtc_table, + }, +}; +module_platform_driver(qpnp_rtc_driver); + +MODULE_ALIAS("platform:" KBUILD_MODNAME); +MODULE_DESCRIPTION("SMPI PMIC RTC driver"); +MODULE_AUTHOR("The Linux Foundation"); +MODULE_LICENSE("GPL v2"); -- 1.7.0.4 -- To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html