[PATCH v3 10/10] rtc: pm8xxx: add support for pm8941

From: Josh Cartwright
Date: Mon Oct 28 2013 - 15:16:57 EST


Adapt the existing pm8xxx driver to work with the RTC available on
Qualcomm's 8941 PMIC. In order to do this, rework the register access
functions to use a regmap provided by the parent driver. Account for
slight differences in the RTC address space by parameterizing addresses
and providing a chip-specific initialization function.

Signed-off-by: Josh Cartwright <joshc@xxxxxxxxxxxxxx>
---
drivers/rtc/Kconfig | 1 -
drivers/rtc/rtc-pm8xxx.c | 229 +++++++++++++++++++++++++++++------------------
2 files changed, 143 insertions(+), 87 deletions(-)

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 9654aa3..19b89ee 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -1177,7 +1177,6 @@ config RTC_DRV_LPC32XX

config RTC_DRV_PM8XXX
tristate "Qualcomm PMIC8XXX RTC"
- depends on MFD_PM8XXX
help
If you say yes here you get support for the
Qualcomm PMIC8XXX RTC.
diff --git a/drivers/rtc/rtc-pm8xxx.c b/drivers/rtc/rtc-pm8xxx.c
index 03f8f75..a9044d4 100644
--- a/drivers/rtc/rtc-pm8xxx.c
+++ b/drivers/rtc/rtc-pm8xxx.c
@@ -1,4 +1,5 @@
/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ * Copyright (c) 2013, 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
@@ -13,35 +14,33 @@
#include <linux/module.h>
#include <linux/init.h>
#include <linux/rtc.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
#include <linux/pm.h>
+#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/spinlock.h>

#include <linux/mfd/pm8xxx/core.h>
#include <linux/mfd/pm8xxx/rtc.h>

-
-/* RTC Register offsets from RTC CTRL REG */
-#define PM8XXX_ALARM_CTRL_OFFSET 0x01
-#define PM8XXX_RTC_WRITE_OFFSET 0x02
-#define PM8XXX_RTC_READ_OFFSET 0x06
-#define PM8XXX_ALARM_RW_OFFSET 0x0A
-
/* RTC_CTRL register bit fields */
#define PM8xxx_RTC_ENABLE BIT(7)
#define PM8xxx_RTC_ALARM_ENABLE BIT(1)
#define PM8xxx_RTC_ALARM_CLEAR BIT(0)

#define NUM_8_BIT_RTC_REGS 0x4
-
/**
* struct pm8xxx_rtc - rtc driver internal structure
* @rtc: rtc device for this driver.
* @rtc_alarm_irq: rtc alarm irq number.
- * @rtc_base: address of rtc control register.
+ * @rtc_control_reg: address of control register.
* @rtc_read_base: base address of read registers.
* @rtc_write_base: base address of write registers.
* @alarm_rw_base: base address of alarm registers.
+ * @alarm_ctrl1: address of alarm ctrl1 register.
+ * @alarm_ctrl2: address of alarm ctrl2 register (only used on pm8941).
+ * @alarm_clear: RTC-specific callback to clear alarm interrupt.
* @ctrl_reg: rtc control register.
* @rtc_dev: device structure.
* @ctrl_reg_lock: spinlock protecting access to ctrl_reg.
@@ -49,51 +48,34 @@
struct pm8xxx_rtc {
struct rtc_device *rtc;
int rtc_alarm_irq;
- int rtc_base;
+ int rtc_control_reg;
int rtc_read_base;
int rtc_write_base;
int alarm_rw_base;
- u8 ctrl_reg;
+ int alarm_ctrl1;
+ int alarm_ctrl2;
+ int (*alarm_clear)(struct pm8xxx_rtc *);
+ u8 ctrl_reg;
struct device *rtc_dev;
spinlock_t ctrl_reg_lock;
+ struct regmap *regmap;
};

/*
* The RTC registers need to be read/written one byte at a time. This is a
* hardware limitation.
*/
-static int pm8xxx_read_wrapper(struct pm8xxx_rtc *rtc_dd, u8 *rtc_val,
- int base, int count)
+static inline int pm8xxx_read_wrapper(struct pm8xxx_rtc *rtc_dd, u8 *rtc_val,
+ int base, int count)
{
- int i, rc;
- struct device *parent = rtc_dd->rtc_dev->parent;
-
- for (i = 0; i < count; i++) {
- rc = pm8xxx_readb(parent, base + i, &rtc_val[i]);
- if (rc < 0) {
- dev_err(rtc_dd->rtc_dev, "PMIC read failed\n");
- return rc;
- }
- }
-
- return 0;
+ return regmap_bulk_read(rtc_dd->regmap, base, rtc_val, count);
}

-static int pm8xxx_write_wrapper(struct pm8xxx_rtc *rtc_dd, u8 *rtc_val,
- int base, int count)
+static inline int pm8xxx_write_wrapper(struct pm8xxx_rtc *rtc_dd,
+ const u8 *rtc_val,
+ int base, int count)
{
- int i, rc;
- struct device *parent = rtc_dd->rtc_dev->parent;
-
- for (i = 0; i < count; i++) {
- rc = pm8xxx_writeb(parent, base + i, rtc_val[i]);
- if (rc < 0) {
- dev_err(rtc_dd->rtc_dev, "PMIC write failed\n");
- return rc;
- }
- }
-
- return 0;
+ return regmap_bulk_write(rtc_dd->regmap, base, rtc_val, count);
}

/*
@@ -125,8 +107,8 @@ static int pm8xxx_rtc_set_time(struct device *dev, struct rtc_time *tm)
if (ctrl_reg & PM8xxx_RTC_ALARM_ENABLE) {
alarm_enabled = 1;
ctrl_reg &= ~PM8xxx_RTC_ALARM_ENABLE;
- rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base,
- 1);
+ rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg,
+ rtc_dd->rtc_control_reg, 1);
if (rc < 0) {
dev_err(dev, "Write to RTC control register "
"failed\n");
@@ -161,8 +143,8 @@ static int pm8xxx_rtc_set_time(struct device *dev, struct rtc_time *tm)

if (alarm_enabled) {
ctrl_reg |= PM8xxx_RTC_ALARM_ENABLE;
- rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base,
- 1);
+ rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg,
+ rtc_dd->rtc_control_reg, 1);
if (rc < 0) {
dev_err(dev, "Write to RTC control register "
"failed\n");
@@ -255,7 +237,8 @@ static int pm8xxx_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
ctrl_reg = alarm->enabled ? (ctrl_reg | PM8xxx_RTC_ALARM_ENABLE) :
(ctrl_reg & ~PM8xxx_RTC_ALARM_ENABLE);

- rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base, 1);
+ rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg,
+ rtc_dd->rtc_control_reg, 1);
if (rc < 0) {
dev_err(dev, "Write to RTC control register failed\n");
goto rtc_rw_fail;
@@ -316,7 +299,8 @@ static int pm8xxx_rtc_alarm_irq_enable(struct device *dev, unsigned int enable)
ctrl_reg = (enable) ? (ctrl_reg | PM8xxx_RTC_ALARM_ENABLE) :
(ctrl_reg & ~PM8xxx_RTC_ALARM_ENABLE);

- rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base, 1);
+ rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg,
+ rtc_dd->rtc_control_reg, 1);
if (rc < 0) {
dev_err(dev, "Write to RTC control register failed\n");
goto rtc_rw_fail;
@@ -351,7 +335,8 @@ static irqreturn_t pm8xxx_alarm_trigger(int irq, void *dev_id)
ctrl_reg = rtc_dd->ctrl_reg;
ctrl_reg &= ~PM8xxx_RTC_ALARM_ENABLE;

- rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base, 1);
+ rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg,
+ rtc_dd->rtc_control_reg, 1);
if (rc < 0) {
spin_unlock_irqrestore(&rtc_dd->ctrl_reg_lock, irq_flags);
dev_err(rtc_dd->rtc_dev, "Write to RTC control register "
@@ -363,37 +348,105 @@ static irqreturn_t pm8xxx_alarm_trigger(int irq, void *dev_id)
spin_unlock_irqrestore(&rtc_dd->ctrl_reg_lock, irq_flags);

/* Clear RTC alarm register */
- rc = pm8xxx_read_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base +
- PM8XXX_ALARM_CTRL_OFFSET, 1);
+ rc = rtc_dd->alarm_clear(rtc_dd);
+ if (rc < 0)
+ dev_err(rtc_dd->rtc_dev, "failed to clear RTC alarm\n");
+
+rtc_alarm_handled:
+ return IRQ_HANDLED;
+}
+
+static int pm8941_alarm_clear(struct pm8xxx_rtc *rtc_dd)
+{
+ u8 ctrl = PM8xxx_RTC_ALARM_CLEAR;
+ return pm8xxx_write_wrapper(rtc_dd, &ctrl, rtc_dd->alarm_ctrl2, 1);
+}
+
+static int pm8941_chip_init(struct platform_device *pdev,
+ struct pm8xxx_rtc *rtc)
+{
+ u32 addr[2];
+ int err;
+
+ err = of_property_read_u32_array(pdev->dev.of_node,
+ "reg", addr, 2);
+ if (err || addr[0] > 0xFFFF || addr[1] > 0xFFFF) {
+ dev_err(&pdev->dev, "RTC IO resources absent or invalid\n");
+ return err;
+ }
+
+ rtc->alarm_clear = pm8941_alarm_clear;
+
+ rtc->rtc_control_reg = addr[0] + 0x46;
+ rtc->rtc_write_base = addr[0] + 0x40;
+ rtc->rtc_read_base = addr[0] + 0x48;
+ rtc->alarm_rw_base = addr[1] + 0x40;
+ rtc->alarm_ctrl1 = addr[1] + 0x46;
+ rtc->alarm_ctrl2 = addr[1] + 0x48;
+ return 0;
+}
+
+static int pm8xxx_alarm_clear(struct pm8xxx_rtc *rtc_dd)
+{
+ u8 ctrl_reg;
+ int rc;
+
+ rc = pm8xxx_read_wrapper(rtc_dd, &ctrl_reg, rtc_dd->alarm_ctrl1, 1);
if (rc < 0) {
dev_err(rtc_dd->rtc_dev, "RTC Alarm control register read "
"failed\n");
- goto rtc_alarm_handled;
+ return rc;
}

ctrl_reg &= ~PM8xxx_RTC_ALARM_CLEAR;
- rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base +
- PM8XXX_ALARM_CTRL_OFFSET, 1);
- if (rc < 0)
- dev_err(rtc_dd->rtc_dev, "Write to RTC Alarm control register"
- " failed\n");
+ return pm8xxx_write_wrapper(rtc_dd, &ctrl_reg, rtc_dd->alarm_ctrl1, 1);
+}

-rtc_alarm_handled:
- return IRQ_HANDLED;
+static int pm8xxx_chip_init(struct platform_device *pdev,
+ struct pm8xxx_rtc *rtc)
+{
+ const struct pm8xxx_rtc_platform_data *pdata =
+ dev_get_platdata(&pdev->dev);
+ struct resource *res;
+
+ if (!pdata) {
+ dev_err(&pdev->dev, "Platform data not initialized.\n");
+ return -ENXIO;
+ }
+
+ if (pdata->rtc_write_enable)
+ pm8xxx_rtc_ops.set_time = pm8xxx_rtc_set_time;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_IO,
+ "pmic_rtc_base");
+ if (!res) {
+ dev_err(&pdev->dev, "RTC IO resource absent\n");
+ return -ENXIO;
+ }
+
+ rtc->rtc_control_reg = res->start;
+ rtc->rtc_write_base = res->start + 0x02;
+ rtc->rtc_read_base = res->start + 0x06;
+ rtc->alarm_rw_base = res->start + 0x0A;
+ rtc->alarm_ctrl1 = res->start + 0x01;
+
+ rtc->alarm_clear = pm8xxx_alarm_clear;
+ return 0;
}

+static const struct of_device_id pm8xxx_rtc_idtable[] = {
+ { .compatible = "qcom,pm8941-rtc", pm8941_chip_init },
+ {},
+};
+MODULE_DEVICE_TABLE(of, pm8xxx_rtc_idtable);
+
static int pm8xxx_rtc_probe(struct platform_device *pdev)
{
- int rc;
- u8 ctrl_reg;
- bool rtc_write_enable = false;
+ int (*chip_init)(struct platform_device *pdev, struct pm8xxx_rtc *rtc);
+ const struct device_node *node = pdev->dev.of_node;
struct pm8xxx_rtc *rtc_dd;
- struct resource *rtc_resource;
- const struct pm8xxx_rtc_platform_data *pdata =
- dev_get_platdata(&pdev->dev);
-
- if (pdata != NULL)
- rtc_write_enable = pdata->rtc_write_enable;
+ u8 ctrl_reg;
+ int rc;

rtc_dd = devm_kzalloc(&pdev->dev, sizeof(*rtc_dd), GFP_KERNEL);
if (rtc_dd == NULL) {
@@ -401,33 +454,40 @@ static int pm8xxx_rtc_probe(struct platform_device *pdev)
return -ENOMEM;
}

- /* Initialise spinlock to protect RTC control register */
- spin_lock_init(&rtc_dd->ctrl_reg_lock);
+ chip_init = pm8xxx_chip_init;
+ if (node) {
+ const
+ struct of_device_id *id = of_match_node(pm8xxx_rtc_idtable,
+ pdev->dev.of_node);
+ if (id && id->data)
+ chip_init = id->data;
+ }

- rtc_dd->rtc_alarm_irq = platform_get_irq(pdev, 0);
- if (rtc_dd->rtc_alarm_irq < 0) {
- dev_err(&pdev->dev, "Alarm IRQ resource absent!\n");
+ rc = chip_init(pdev, rtc_dd);
+ if (rc) {
+ dev_err(&pdev->dev, "Failed to initialize chip.\n");
return -ENXIO;
}

- rtc_resource = platform_get_resource_byname(pdev, IORESOURCE_IO,
- "pmic_rtc_base");
- if (!(rtc_resource && rtc_resource->start)) {
- dev_err(&pdev->dev, "RTC IO resource absent!\n");
+ rtc_dd->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!rtc_dd->regmap) {
+ dev_err(&pdev->dev, "Unable to get regmap.\n");
return -ENXIO;
}

- rtc_dd->rtc_base = rtc_resource->start;
+ /* Initialise spinlock to protect RTC control register */
+ spin_lock_init(&rtc_dd->ctrl_reg_lock);

- /* Setup RTC register addresses */
- rtc_dd->rtc_write_base = rtc_dd->rtc_base + PM8XXX_RTC_WRITE_OFFSET;
- rtc_dd->rtc_read_base = rtc_dd->rtc_base + PM8XXX_RTC_READ_OFFSET;
- rtc_dd->alarm_rw_base = rtc_dd->rtc_base + PM8XXX_ALARM_RW_OFFSET;
+ rtc_dd->rtc_alarm_irq = platform_get_irq(pdev, 0);
+ if (rtc_dd->rtc_alarm_irq < 0) {
+ dev_err(&pdev->dev, "Alarm IRQ resource absent!\n");
+ return -ENXIO;
+ }

rtc_dd->rtc_dev = &pdev->dev;

/* Check if the RTC is on, else turn it on */
- rc = pm8xxx_read_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base, 1);
+ rc = pm8xxx_read_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_control_reg, 1);
if (rc < 0) {
dev_err(&pdev->dev, "RTC control register read failed!\n");
return rc;
@@ -435,8 +495,8 @@ static int pm8xxx_rtc_probe(struct platform_device *pdev)

if (!(ctrl_reg & PM8xxx_RTC_ENABLE)) {
ctrl_reg |= PM8xxx_RTC_ENABLE;
- rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base,
- 1);
+ rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg,
+ rtc_dd->rtc_control_reg, 1);
if (rc < 0) {
dev_err(&pdev->dev, "Write to RTC control register "
"failed\n");
@@ -444,10 +504,6 @@ static int pm8xxx_rtc_probe(struct platform_device *pdev)
}
}

- rtc_dd->ctrl_reg = ctrl_reg;
- if (rtc_write_enable == true)
- pm8xxx_rtc_ops.set_time = pm8xxx_rtc_set_time;
-
platform_set_drvdata(pdev, rtc_dd);

/* Register the RTC device */
@@ -516,6 +572,7 @@ static struct platform_driver pm8xxx_rtc_driver = {
.name = PM8XXX_RTC_DEV_NAME,
.owner = THIS_MODULE,
.pm = &pm8xxx_rtc_pm_ops,
+ .of_match_table = pm8xxx_rtc_idtable,
},
};

--
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
hosted by The Linux Foundation

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/