* [rtc-linux] [PATCH 0/3] rtc: Add pcf85263
@ 2016-08-01 15:50 ` Martin Fuzzey
0 siblings, 0 replies; 15+ messages in thread
From: Martin Fuzzey @ 2016-08-01 15:50 UTC (permalink / raw)
To: Alessandro Zummo, Rob Herring, Alexandre Belloni; +Cc: devicetree, rtc-linux
This series adds support for the NXP PCF85263 RTC Chip.
3 patches instead of 1 to:
* Seperate the DT binding
* Seperate the "unconventional" century support
--
You received this message because you are subscribed to "rtc-linux".
Membership options at http://groups.google.com/group/rtc-linux .
Please read http://groups.google.com/group/rtc-linux/web/checklist
before submitting a driver.
---
You received this message because you are subscribed to the Google Groups "rtc-linux" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rtc-linux+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 0/3] rtc: Add pcf85263
@ 2016-08-01 15:50 ` Martin Fuzzey
0 siblings, 0 replies; 15+ messages in thread
From: Martin Fuzzey @ 2016-08-01 15:50 UTC (permalink / raw)
To: Alessandro Zummo, Rob Herring, Alexandre Belloni
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA, rtc-linux-/JYPxA39Uh5TLH3MbocFFw
This series adds support for the NXP PCF85263 RTC Chip.
3 patches instead of 1 to:
* Seperate the DT binding
* Seperate the "unconventional" century support
--
You received this message because you are subscribed to "rtc-linux".
Membership options at http://groups.google.com/group/rtc-linux .
Please read http://groups.google.com/group/rtc-linux/web/checklist
before submitting a driver.
---
You received this message because you are subscribed to the Google Groups "rtc-linux" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rtc-linux+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
For more options, visit https://groups.google.com/d/optout.
^ permalink raw reply [flat|nested] 15+ messages in thread
* [rtc-linux] [PATCH 1/3] dt-binding: rtc Add DT binding for NXP 85263 RTC
2016-08-01 15:50 ` Martin Fuzzey
@ 2016-08-01 15:50 ` Martin Fuzzey
-1 siblings, 0 replies; 15+ messages in thread
From: Martin Fuzzey @ 2016-08-01 15:50 UTC (permalink / raw)
To: Alessandro Zummo, Rob Herring, Alexandre Belloni; +Cc: devicetree, rtc-linux
Signed-off-by: Martin Fuzzey <mfuzzey@parkeon.com>
---
.../devicetree/bindings/rtc/nxp,pcf85263.txt | 41 ++++++++++++++++++++
include/dt-bindings/rtc/nxp,pcf85263.h | 14 +++++++
2 files changed, 55 insertions(+)
create mode 100644 Documentation/devicetree/bindings/rtc/nxp,pcf85263.txt
create mode 100644 include/dt-bindings/rtc/nxp,pcf85263.h
diff --git a/Documentation/devicetree/bindings/rtc/nxp,pcf85263.txt b/Documentation/devicetree/bindings/rtc/nxp,pcf85263.txt
new file mode 100644
index 0000000..03b9505
--- /dev/null
+++ b/Documentation/devicetree/bindings/rtc/nxp,pcf85263.txt
@@ -0,0 +1,41 @@
+NXP PCF85263 I2C Real Time Clock
+
+Required properties:
+- compatible: must be: "nxp,rtc-pcf85263"
+- reg: must be the I2C address
+
+Optional properties:
+- interrupt-names: Which interrupt signal is used must be "INTA" or "INTB"
+ Defaults to "INTA"
+
+- quartz-load-capacitance: The internal capacitor to select for the quartz:
+ PCF85263_QUARTZCAP_7pF [0]
+ PCF85263_QUARTZCAP_6pF [1]
+ PCF85263_QUARTZCAP_12p5pF [2] DEFAULT
+
+- quartz-drive-strength: Drive strength for the quartz:
+ PCF85263_QUARTZDRIVE_NORMAL [0] DEFAULT
+ PCF85263_QUARTZDRIVE_LOW [1]
+ PCF85263_QUARTZDRIVE_HIGH [2]
+
+- quartz-low-jitter: Boolean property, if present enables low jitter mode which
+ reduces jitter at the cost of increased power consumption.
+
+- wakeup-source: mark the chip as a wakeup source, independently of
+ the availability of an IRQ line connected to the SoC.
+ This is useful if the IRQ line is connected to a PMIC or other circuit
+ that can power up the device rather than to a normal SOC interrupt.
+
+Example:
+
+rtc@51 {
+ compatible = "nxp,pcf85263";
+ reg = <0x51>;
+
+ interrupt-parent = <&gpio4>;
+ interrupts = <5 IRQ_TYPE_LEVEL_LOW>;
+ interrupt-names = "INTB";
+
+ quartz-load-capacitance = <PCF85263_QUARTZCAP_12p5pF>;
+ quartz-drive-strength = <PCF85263_QUARTZDRIVE_LOW>;
+};
diff --git a/include/dt-bindings/rtc/nxp,pcf85263.h b/include/dt-bindings/rtc/nxp,pcf85263.h
new file mode 100644
index 0000000..ea87ae4
--- /dev/null
+++ b/include/dt-bindings/rtc/nxp,pcf85263.h
@@ -0,0 +1,14 @@
+#ifndef _DT_BINDINGS_RTC_NXP_PCF85263_H
+#define _DT_BINDINGS_RTC_NXP_PCF85263_H
+
+/* Quartz capacitance */
+#define PCF85263_QUARTZCAP_7pF 0
+#define PCF85263_QUARTZCAP_6pF 1
+#define PCF85263_QUARTZCAP_12p5pF 2
+
+/* Quartz drive strength */
+#define PCF85263_QUARTZDRIVE_NORMAL 0
+#define PCF85263_QUARTZDRIVE_LOW 1
+#define PCF85263_QUARTZDRIVE_HIGH 2
+
+#endif /* _DT_BINDINGS_RTC_NXP_PCF85263_H */
--
You received this message because you are subscribed to "rtc-linux".
Membership options at http://groups.google.com/group/rtc-linux .
Please read http://groups.google.com/group/rtc-linux/web/checklist
before submitting a driver.
---
You received this message because you are subscribed to the Google Groups "rtc-linux" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rtc-linux+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 1/3] dt-binding: rtc Add DT binding for NXP 85263 RTC
@ 2016-08-01 15:50 ` Martin Fuzzey
0 siblings, 0 replies; 15+ messages in thread
From: Martin Fuzzey @ 2016-08-01 15:50 UTC (permalink / raw)
To: Alessandro Zummo, Rob Herring, Alexandre Belloni
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA, rtc-linux-/JYPxA39Uh5TLH3MbocFFw
Signed-off-by: Martin Fuzzey <mfuzzey-mB3Nsq4MPf1BDgjK7y7TUQ@public.gmane.org>
---
.../devicetree/bindings/rtc/nxp,pcf85263.txt | 41 ++++++++++++++++++++
include/dt-bindings/rtc/nxp,pcf85263.h | 14 +++++++
2 files changed, 55 insertions(+)
create mode 100644 Documentation/devicetree/bindings/rtc/nxp,pcf85263.txt
create mode 100644 include/dt-bindings/rtc/nxp,pcf85263.h
diff --git a/Documentation/devicetree/bindings/rtc/nxp,pcf85263.txt b/Documentation/devicetree/bindings/rtc/nxp,pcf85263.txt
new file mode 100644
index 0000000..03b9505
--- /dev/null
+++ b/Documentation/devicetree/bindings/rtc/nxp,pcf85263.txt
@@ -0,0 +1,41 @@
+NXP PCF85263 I2C Real Time Clock
+
+Required properties:
+- compatible: must be: "nxp,rtc-pcf85263"
+- reg: must be the I2C address
+
+Optional properties:
+- interrupt-names: Which interrupt signal is used must be "INTA" or "INTB"
+ Defaults to "INTA"
+
+- quartz-load-capacitance: The internal capacitor to select for the quartz:
+ PCF85263_QUARTZCAP_7pF [0]
+ PCF85263_QUARTZCAP_6pF [1]
+ PCF85263_QUARTZCAP_12p5pF [2] DEFAULT
+
+- quartz-drive-strength: Drive strength for the quartz:
+ PCF85263_QUARTZDRIVE_NORMAL [0] DEFAULT
+ PCF85263_QUARTZDRIVE_LOW [1]
+ PCF85263_QUARTZDRIVE_HIGH [2]
+
+- quartz-low-jitter: Boolean property, if present enables low jitter mode which
+ reduces jitter at the cost of increased power consumption.
+
+- wakeup-source: mark the chip as a wakeup source, independently of
+ the availability of an IRQ line connected to the SoC.
+ This is useful if the IRQ line is connected to a PMIC or other circuit
+ that can power up the device rather than to a normal SOC interrupt.
+
+Example:
+
+rtc@51 {
+ compatible = "nxp,pcf85263";
+ reg = <0x51>;
+
+ interrupt-parent = <&gpio4>;
+ interrupts = <5 IRQ_TYPE_LEVEL_LOW>;
+ interrupt-names = "INTB";
+
+ quartz-load-capacitance = <PCF85263_QUARTZCAP_12p5pF>;
+ quartz-drive-strength = <PCF85263_QUARTZDRIVE_LOW>;
+};
diff --git a/include/dt-bindings/rtc/nxp,pcf85263.h b/include/dt-bindings/rtc/nxp,pcf85263.h
new file mode 100644
index 0000000..ea87ae4
--- /dev/null
+++ b/include/dt-bindings/rtc/nxp,pcf85263.h
@@ -0,0 +1,14 @@
+#ifndef _DT_BINDINGS_RTC_NXP_PCF85263_H
+#define _DT_BINDINGS_RTC_NXP_PCF85263_H
+
+/* Quartz capacitance */
+#define PCF85263_QUARTZCAP_7pF 0
+#define PCF85263_QUARTZCAP_6pF 1
+#define PCF85263_QUARTZCAP_12p5pF 2
+
+/* Quartz drive strength */
+#define PCF85263_QUARTZDRIVE_NORMAL 0
+#define PCF85263_QUARTZDRIVE_LOW 1
+#define PCF85263_QUARTZDRIVE_HIGH 2
+
+#endif /* _DT_BINDINGS_RTC_NXP_PCF85263_H */
--
You received this message because you are subscribed to "rtc-linux".
Membership options at http://groups.google.com/group/rtc-linux .
Please read http://groups.google.com/group/rtc-linux/web/checklist
before submitting a driver.
---
You received this message because you are subscribed to the Google Groups "rtc-linux" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rtc-linux+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
For more options, visit https://groups.google.com/d/optout.
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [rtc-linux] [PATCH 2/3] rtc: pcf85263: Basic driver
2016-08-01 15:50 ` Martin Fuzzey
@ 2016-08-01 15:50 ` Martin Fuzzey
-1 siblings, 0 replies; 15+ messages in thread
From: Martin Fuzzey @ 2016-08-01 15:50 UTC (permalink / raw)
To: Alessandro Zummo, Rob Herring, Alexandre Belloni; +Cc: devicetree, rtc-linux
Add basic support for NXP PCF85263 I2C RTC chip.
Only RTC and Alarm functions are supported.
Signed-off-by: Martin Fuzzey <mfuzzey@parkeon.com>
---
drivers/rtc/Kconfig | 8 +
drivers/rtc/Makefile | 1
drivers/rtc/rtc-pcf85263.c | 655 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 664 insertions(+)
create mode 100644 drivers/rtc/rtc-pcf85263.c
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 18639e0..63ec0fc 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -396,6 +396,14 @@ config RTC_DRV_PCF85063
This driver can also be built as a module. If so, the module
will be called rtc-pcf85063.
+config RTC_DRV_PCF85263
+ tristate "NXP PCF85263"
+ help
+ If you say yes here you get support for the PCF85263 RTC chip
+
+ This driver can also be built as a module. If so, the module
+ will be called rtc-pcf85263.
+
config RTC_DRV_PCF8563
tristate "Philips PCF8563/Epson RTC8564"
help
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index ea28337..04f48fd 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -108,6 +108,7 @@ obj-$(CONFIG_RTC_DRV_PCF2123) += rtc-pcf2123.o
obj-$(CONFIG_RTC_DRV_PCF2127) += rtc-pcf2127.o
obj-$(CONFIG_RTC_DRV_PCF50633) += rtc-pcf50633.o
obj-$(CONFIG_RTC_DRV_PCF85063) += rtc-pcf85063.o
+obj-$(CONFIG_RTC_DRV_PCF85263) += rtc-pcf85263.o
obj-$(CONFIG_RTC_DRV_PCF8523) += rtc-pcf8523.o
obj-$(CONFIG_RTC_DRV_PCF8563) += rtc-pcf8563.o
obj-$(CONFIG_RTC_DRV_PCF8583) += rtc-pcf8583.o
diff --git a/drivers/rtc/rtc-pcf85263.c b/drivers/rtc/rtc-pcf85263.c
new file mode 100644
index 0000000..997742d
--- /dev/null
+++ b/drivers/rtc/rtc-pcf85263.c
@@ -0,0 +1,655 @@
+/*
+ * rtc-pcf85263 Driver for the NXP PCF85263 RTC
+ *
+ * Copyright 2016 Parkeon
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/rtc.h>
+#include <linux/i2c.h>
+#include <linux/bcd.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+
+#include <dt-bindings/rtc/nxp,pcf85263.h>
+
+#define DRV_NAME "rtc-pcf85263"
+
+#define PCF85263_REG_RTC_SC 0x01 /* Seconds */
+#define PCF85263_REG_RTC_SC_OS BIT(7) /* Oscilator stopped flag */
+
+#define PCF85263_REG_RTC_MN 0x02 /* Minutes */
+#define PCF85263_REG_RTC_HR 0x03 /* Hours */
+#define PCF85263_REG_RTC_DT 0x04 /* Day of month 1-31 */
+#define PCF85263_REG_RTC_DW 0x05 /* Day of week 0-6 */
+#define PCF85263_REG_RTC_MO 0x06 /* Month 1-12 */
+#define PCF85263_REG_RTC_YR 0x07 /* Year 0-99 */
+
+#define PCF85263_REG_ALM1_SC 0x08 /* Seconds */
+#define PCF85263_REG_ALM1_MN 0x09 /* Minutes */
+#define PCF85263_REG_ALM1_HR 0x0a /* Hours */
+#define PCF85263_REG_ALM1_DT 0x0b /* Day of month 1-31 */
+#define PCF85263_REG_ALM1_MO 0x0c /* Month 1-12 */
+
+#define PCF85263_REG_ALM_CTL 0x10
+#define PCF85263_REG_ALM_CTL_ALL_A1E 0x1f /* sec,min,hr,day,mon alarm 1 */
+
+#define PCF85263_REG_OSC 0x25
+#define PCF85263_REG_OSC_CL_MASK (BIT(0) | BIT(1))
+#define PCF85263_REG_OSC_CL_SHIFT 0
+#define PCF85263_REG_OSC_OSCD_MASK (BIT(2) | BIT(3))
+#define PCF85263_REG_OSC_OSCD_SHIFT 2
+#define PCF85263_REG_OSC_LOWJ BIT(4)
+#define PCF85263_REG_OSC_12H BIT(5)
+
+#define PCF85263_REG_PINIO 0x27
+#define PCF85263_REG_PINIO_INTAPM_MASK (BIT(0) | BIT(1))
+#define PCF85263_REG_PINIO_INTAPM_SHIFT 0
+#define PCF85263_INTAPM_INTA (0x2 << PCF85263_REG_PINIO_INTAPM_SHIFT)
+#define PCF85263_INTAPM_HIGHZ (0x3 << PCF85263_REG_PINIO_INTAPM_SHIFT)
+#define PCF85263_REG_PINIO_TSPM_MASK (BIT(2) | BIT(3))
+#define PCF85263_REG_PINIO_TSPM_SHIFT 2
+#define PCF85263_TSPM_DISABLED (0x0 << PCF85263_REG_PINIO_TSPM_SHIFT)
+#define PCF85263_TSPM_INTB (0x1 << PCF85263_REG_PINIO_TSPM_SHIFT)
+#define PCF85263_REG_PINIO_CLKDISABLE BIT(7)
+
+#define PCF85263_REG_FUNCTION 0x28
+#define PCF85263_REG_FUNCTION_COF_MASK 0x7
+#define PCF85263_REG_FUNCTION_COF_OFF 0x7 /* No clock output */
+
+#define PCF85263_REG_INTA_CTL 0x29
+#define PCF85263_REG_INTB_CTL 0x2A
+#define PCF85263_REG_INTx_CTL_A1E BIT(4) /* Alarm 1 */
+#define PCF85263_REG_INTx_CTL_ILP BIT(7) /* 0=pulse, 1=level */
+
+#define PCF85263_REG_FLAGS 0x2B
+#define PCF85263_REG_FLAGS_A1F BIT(5)
+
+#define PCF85263_REG_RAM_BYTE 0x2c
+
+#define PCF85263_REG_STOPENABLE 0x2e
+#define PCF85263_REG_STOPENABLE_STOP BIT(0)
+
+#define PCF85263_REG_RESET 0x2f /* Reset command */
+#define PCF85263_REG_RESET_CMD_CPR 0xa4 /* Clear prescaler */
+
+#define PCF85263_MAX_REG 0x2f
+
+#define PCF85263_HR_PM BIT(5)
+
+enum pcf85263_irqpin {
+ PCF85263_IRQPIN_NONE,
+ PCF85263_IRQPIN_INTA,
+ PCF85263_IRQPIN_INTB
+};
+
+static const char *const pcf85263_irqpin_names[] = {
+ [PCF85263_IRQPIN_NONE] = "None",
+ [PCF85263_IRQPIN_INTA] = "INTA",
+ [PCF85263_IRQPIN_INTB] = "INTB"
+};
+
+struct pcf85263 {
+ struct device *dev;
+ struct rtc_device *rtc;
+ struct regmap *regmap;
+ enum pcf85263_irqpin irq_pin;
+ int irq;
+ bool mode_12h;
+};
+
+/*
+ * Helpers to convert 12h to 24h and vice versa.
+ * Values in register are stored in BCD with a PM flag in bit 5
+ *
+ * 23:00 <=> 11PM <=> 0x31
+ * 00:00 <=> 12AM <=> 0x12
+ * 01:00 <=> 1AM <=> 0x01
+ * 12:00 <=> 12PM <=> 0x32
+ * 13:00 <=> 1PM <=> 0x21
+ */
+static int pcf85263_bcd12h_to_bin24h(int regval)
+{
+ int hr = bcd2bin(regval & 0x1f);
+ bool pm = regval & PCF85263_HR_PM;
+
+ if (hr == 12)
+ return pm ? 12 : 0;
+
+ return pm ? hr + 12 : hr;
+}
+
+static int pcf85263_bin24h_to_bcd12h(int hr24)
+{
+ bool pm = hr24 >= 12;
+ int hr12 = hr24 % 12;
+
+ if (!hr12)
+ hr12++;
+
+ return bin2bcd(hr12) | pm ? 0 : PCF85263_HR_PM;
+}
+
+static int pcf85263_read_time(struct device *dev, struct rtc_time *tm)
+{
+ struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
+ const int first = PCF85263_REG_RTC_SC;
+ const int last = PCF85263_REG_RTC_YR;
+ const int len = last - first + 1;
+ u8 regs[len];
+ u8 hr_reg;
+ int ret;
+
+ ret = regmap_bulk_read(pcf85263->regmap, first, regs, len);
+ if (ret)
+ return ret;
+
+ if (regs[PCF85263_REG_RTC_SC - first] & PCF85263_REG_RTC_SC_OS) {
+ dev_warn(dev, "Oscillator stop detected, date/time is not reliable.\n");
+ return -EINVAL;
+ }
+
+ tm->tm_sec = bcd2bin(regs[PCF85263_REG_RTC_SC - first] & 0x7f);
+ tm->tm_min = bcd2bin(regs[PCF85263_REG_RTC_MN - first] & 0x7f);
+
+ hr_reg = regs[PCF85263_REG_RTC_HR - first];
+ if (pcf85263->mode_12h)
+ tm->tm_hour = pcf85263_bcd12h_to_bin24h(hr_reg);
+ else
+ tm->tm_hour = bcd2bin(hr_reg & 0x3f);
+
+ tm->tm_mday = bcd2bin(regs[PCF85263_REG_RTC_DT - first]);
+ tm->tm_wday = bcd2bin(regs[PCF85263_REG_RTC_DW - first]);
+ tm->tm_mon = bcd2bin(regs[PCF85263_REG_RTC_MO - first]) - 1;
+ tm->tm_year = bcd2bin(regs[PCF85263_REG_RTC_YR - first]);
+
+ tm->tm_year += 100; /* Assume 21st century */
+
+ return 0;
+}
+
+static int pcf85263_set_time(struct device *dev, struct rtc_time *tm)
+{
+ struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
+
+ /*
+ * Before setting time need to stop RTC and disable prescaler
+ * Do this all in a single I2C transaction exploiting wraparound
+ * as described in data sheet.
+ * This means that the array below must be in register order
+ */
+ u8 regs[] = {
+ PCF85263_REG_STOPENABLE_STOP, /* STOP */
+ PCF85263_REG_RESET_CMD_CPR, /* Disable prescaler */
+ /* Wrap around to register 0 (1/100s) */
+ 0, /* 1/100s always zero. */
+ bin2bcd(tm->tm_sec),
+ bin2bcd(tm->tm_min),
+ bin2bcd(tm->tm_hour), /* 24-hour */
+ bin2bcd(tm->tm_mday),
+ bin2bcd(tm->tm_wday + 1),
+ bin2bcd(tm->tm_mon + 1),
+ bin2bcd(tm->tm_year % 100)
+ };
+ int ret;
+
+ ret = regmap_bulk_write(pcf85263->regmap, PCF85263_REG_STOPENABLE,
+ regs, sizeof(regs));
+ if (ret)
+ return ret;
+
+ /* As we have set the time in 24H update the hardware for that */
+ if (pcf85263->mode_12h) {
+ pcf85263->mode_12h = false;
+ ret = regmap_update_bits(pcf85263->regmap, PCF85263_REG_OSC,
+ PCF85263_REG_OSC_12H, 0);
+ if (ret)
+ return ret;
+ }
+
+ /* Start it again */
+ return regmap_write(pcf85263->regmap, PCF85263_REG_STOPENABLE, 0);
+}
+
+static int pcf85263_enable_alarm(struct pcf85263 *pcf85263, bool enable)
+{
+ int reg;
+ int ret;
+
+ ret = regmap_update_bits(pcf85263->regmap, PCF85263_REG_ALM_CTL,
+ PCF85263_REG_ALM_CTL_ALL_A1E,
+ enable ? PCF85263_REG_ALM_CTL_ALL_A1E : 0);
+ if (ret)
+ return ret;
+
+ switch (pcf85263->irq_pin) {
+ case PCF85263_IRQPIN_NONE:
+ return 0;
+
+ case PCF85263_IRQPIN_INTA:
+ reg = PCF85263_REG_INTA_CTL;
+ break;
+
+ case PCF85263_IRQPIN_INTB:
+ reg = PCF85263_REG_INTB_CTL;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return regmap_update_bits(pcf85263->regmap, reg,
+ PCF85263_REG_INTx_CTL_A1E,
+ enable ? PCF85263_REG_INTx_CTL_A1E : 0);
+}
+
+static int pcf85263_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+ struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
+ struct rtc_time *tm = &alarm->time;
+ const int first = PCF85263_REG_ALM1_SC;
+ const int last = PCF85263_REG_ALM1_MO;
+ const int len = last - first + 1;
+ u8 regs[len];
+ u8 hr_reg;
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_bulk_read(pcf85263->regmap, first, regs, len);
+ if (ret)
+ return ret;
+
+ tm->tm_sec = bcd2bin(regs[PCF85263_REG_ALM1_SC - first] & 0x7f);
+ tm->tm_min = bcd2bin(regs[PCF85263_REG_ALM1_MN - first] & 0x7f);
+
+ hr_reg = regs[PCF85263_REG_ALM1_HR - first];
+ if (pcf85263->mode_12h)
+ tm->tm_hour = pcf85263_bcd12h_to_bin24h(hr_reg);
+ else
+ tm->tm_hour = bcd2bin(hr_reg & 0x3f);
+
+ tm->tm_mday = bcd2bin(regs[PCF85263_REG_ALM1_DT - first]);
+ tm->tm_mon = bcd2bin(regs[PCF85263_REG_ALM1_MO - first]) - 1;
+ tm->tm_year = -1;
+ tm->tm_wday = -1;
+
+ ret = regmap_read(pcf85263->regmap, PCF85263_REG_ALM_CTL, ®val);
+ if (ret)
+ return ret;
+ alarm->enabled = !!(regval & PCF85263_REG_ALM_CTL_ALL_A1E);
+
+ ret = regmap_read(pcf85263->regmap, PCF85263_REG_FLAGS, ®val);
+ if (ret)
+ return ret;
+ alarm->pending = !!(regval & PCF85263_REG_FLAGS_A1F);
+
+ return 0;
+}
+
+static int pcf85263_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+ struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
+ struct rtc_time *tm = &alarm->time;
+ const int first = PCF85263_REG_ALM1_SC;
+ const int last = PCF85263_REG_ALM1_MO;
+ const int len = last - first + 1;
+ u8 regs[len];
+ int ret;
+
+ /* Disable alarm comparison during update */
+ ret = pcf85263_enable_alarm(pcf85263, false);
+ if (ret)
+ return ret;
+
+ /* Clear any pending alarm (write 0=>clr, 1=>no change) */
+ ret = regmap_write(pcf85263->regmap, PCF85263_REG_FLAGS,
+ ~PCF85263_REG_FLAGS_A1F);
+ if (ret)
+ return ret;
+
+ /* Set the alarm time registers */
+ regs[PCF85263_REG_ALM1_SC - first] = bin2bcd(tm->tm_sec);
+ regs[PCF85263_REG_ALM1_MN - first] = bin2bcd(tm->tm_min);
+ regs[PCF85263_REG_ALM1_HR - first] = pcf85263->mode_12h ?
+ pcf85263_bin24h_to_bcd12h(tm->tm_hour) :
+ bin2bcd(tm->tm_hour);
+ regs[PCF85263_REG_ALM1_DT - first] = bin2bcd(tm->tm_mday);
+ regs[PCF85263_REG_ALM1_MO - first] = bin2bcd(tm->tm_mon + 1);
+
+ ret = regmap_bulk_write(pcf85263->regmap, first, regs, sizeof(regs));
+ if (ret)
+ return ret;
+
+ if (alarm->enabled)
+ ret = pcf85263_enable_alarm(pcf85263, true);
+
+ return ret;
+}
+
+static int pcf85263_alarm_irq_enable(struct device *dev, unsigned int enable)
+{
+ struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
+
+ return pcf85263_enable_alarm(pcf85263, !!enable);
+}
+
+static irqreturn_t pcf85263_irq(int irq, void *data)
+{
+ struct pcf85263 *pcf85263 = data;
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(pcf85263->regmap, PCF85263_REG_FLAGS, ®val);
+ if (ret)
+ return IRQ_NONE;
+
+ if (regval & PCF85263_REG_FLAGS_A1F) {
+ regmap_write(pcf85263->regmap, PCF85263_REG_FLAGS,
+ ~PCF85263_REG_FLAGS_A1F);
+
+ rtc_update_irq(pcf85263->rtc, 1, RTC_IRQF | RTC_AF);
+
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_NONE;
+}
+
+static int pcf85263_check_osc_stopped(struct pcf85263 *pcf85263)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(pcf85263->regmap, PCF85263_REG_RTC_SC, ®val);
+ if (ret)
+ return ret;
+
+ ret = regval & PCF85263_REG_RTC_SC_OS ? 1 : 0;
+ if (ret)
+ dev_warn(pcf85263->dev, "Oscillator stop detected, date/time is not reliable.\n");
+
+ return ret;
+}
+
+#ifdef CONFIG_RTC_INTF_DEV
+static int pcf85263_ioctl(struct device *dev,
+ unsigned int cmd, unsigned long arg)
+{
+ struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
+ int ret;
+
+ switch (cmd) {
+ case RTC_VL_READ:
+ ret = pcf85263_check_osc_stopped(pcf85263);
+ if (ret < 0)
+ return ret;
+
+ if (copy_to_user((void __user *)arg, &ret, sizeof(int)))
+ return -EFAULT;
+ return 0;
+
+ case RTC_VL_CLR:
+ return regmap_update_bits(pcf85263->regmap,
+ PCF85263_REG_RTC_SC,
+ PCF85263_REG_RTC_SC_OS, 0);
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+#else
+#define pcf85263_ioctl NULL
+#endif
+
+static int pcf85263_init_hw(struct pcf85263 *pcf85263)
+{
+ struct device_node *np = pcf85263->dev->of_node;
+ unsigned int regval;
+ u32 propval;
+ int ret;
+
+ /* Determine if oscilator has been stopped (probably low power) */
+ ret = pcf85263_check_osc_stopped(pcf85263);
+ if (ret < 0) {
+ /* Log here since this is the first hw access on probe */
+ dev_err(pcf85263->dev, "Unable to read register\n");
+
+ return ret;
+ }
+
+ /* Determine 12/24H mode */
+ ret = regmap_read(pcf85263->regmap, PCF85263_REG_OSC, ®val);
+ if (ret)
+ return ret;
+ pcf85263->mode_12h = !!(regval & PCF85263_REG_OSC_12H);
+
+ /* Set oscilator register */
+ regval &= ~PCF85263_REG_OSC_12H; /* keep current 12/24 h setting */
+
+ propval = PCF85263_QUARTZCAP_12p5pF;
+ of_property_read_u32(np, "quartz-load-capacitance", &propval);
+ regval |= ((propval << PCF85263_REG_OSC_CL_SHIFT)
+ & PCF85263_REG_OSC_CL_MASK);
+
+ propval = PCF85263_QUARTZDRIVE_NORMAL;
+ of_property_read_u32(np, "quartz-drive-strength", &propval);
+ regval |= ((propval << PCF85263_REG_OSC_OSCD_SHIFT)
+ & PCF85263_REG_OSC_OSCD_MASK);
+
+ if (of_property_read_bool(np, "quartz-low-jitter"))
+ regval |= PCF85263_REG_OSC_LOWJ;
+
+ ret = regmap_write(pcf85263->regmap, PCF85263_REG_OSC, regval);
+ if (ret)
+ return ret;
+
+ /* Set function register (RTC mode, 1s tick, clock output static) */
+ ret = regmap_write(pcf85263->regmap, PCF85263_REG_FUNCTION,
+ PCF85263_REG_FUNCTION_COF_OFF);
+ if (ret)
+ return ret;
+
+ /* Set all interrupts to disabled, level mode */
+ ret = regmap_write(pcf85263->regmap, PCF85263_REG_INTA_CTL,
+ PCF85263_REG_INTx_CTL_ILP);
+ if (ret)
+ return ret;
+ ret = regmap_write(pcf85263->regmap, PCF85263_REG_INTB_CTL,
+ PCF85263_REG_INTx_CTL_ILP);
+ if (ret)
+ return ret;
+
+ /* Setup IO pin config register */
+ regval = PCF85263_REG_PINIO_CLKDISABLE;
+ switch (pcf85263->irq_pin) {
+ case PCF85263_IRQPIN_INTA:
+ regval |= (PCF85263_INTAPM_INTA | PCF85263_TSPM_DISABLED);
+ break;
+ case PCF85263_IRQPIN_INTB:
+ regval |= (PCF85263_INTAPM_HIGHZ | PCF85263_TSPM_INTB);
+ break;
+ case PCF85263_IRQPIN_NONE:
+ regval |= (PCF85263_INTAPM_HIGHZ | PCF85263_TSPM_DISABLED);
+ break;
+ }
+ ret = regmap_write(pcf85263->regmap, PCF85263_REG_PINIO, regval);
+
+ return ret;
+}
+
+static const struct rtc_class_ops rtc_ops = {
+ .ioctl = pcf85263_ioctl,
+ .read_time = pcf85263_read_time,
+ .set_time = pcf85263_set_time,
+ .read_alarm = pcf85263_read_alarm,
+ .set_alarm = pcf85263_set_alarm,
+ .alarm_irq_enable = pcf85263_alarm_irq_enable,
+};
+
+static const struct regmap_config pcf85263_regmap_cfg = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = PCF85263_MAX_REG,
+};
+
+/*
+ * On some boards the interrupt line may not be wired to the CPU but only to
+ * a power supply circuit.
+ * In that case no interrupt will be specified in the device tree but the
+ * wakeup-source DT property may be used to enable wakeup programming in
+ * sysfs
+ */
+static bool pcf85263_can_wakeup_machine(struct pcf85263 *pcf85263)
+{
+ return pcf85263->irq ||
+ of_property_read_bool(pcf85263->dev->of_node, "wakeup-source");
+}
+
+static int pcf85263_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &client->dev;
+ struct pcf85263 *pcf85263;
+ int ret;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C |
+ I2C_FUNC_SMBUS_BYTE_DATA |
+ I2C_FUNC_SMBUS_I2C_BLOCK))
+ return -ENODEV;
+
+ pcf85263 = devm_kzalloc(dev, sizeof(*pcf85263), GFP_KERNEL);
+ if (!pcf85263)
+ return -ENOMEM;
+
+ pcf85263->dev = dev;
+ pcf85263->irq = client->irq;
+ dev_set_drvdata(dev, pcf85263);
+
+ pcf85263->regmap = devm_regmap_init_i2c(client, &pcf85263_regmap_cfg);
+ if (IS_ERR(pcf85263->regmap)) {
+ ret = PTR_ERR(pcf85263->regmap);
+ dev_err(dev, "regmap allocation failed (%d)\n", ret);
+
+ return ret;
+ }
+
+ /* Determine which interrupt pin the board uses */
+ if (pcf85263_can_wakeup_machine(pcf85263)) {
+ if (of_property_match_string(dev->of_node,
+ "interrupt-names", "INTB") >= 0)
+ pcf85263->irq_pin = PCF85263_IRQPIN_INTB;
+ else
+ pcf85263->irq_pin = PCF85263_IRQPIN_INTA;
+ } else {
+ pcf85263->irq_pin = PCF85263_IRQPIN_NONE;
+ }
+
+ ret = pcf85263_init_hw(pcf85263);
+ if (ret)
+ return ret;
+
+ if (pcf85263->irq) {
+ ret = devm_request_threaded_irq(dev, pcf85263->irq, NULL,
+ pcf85263_irq,
+ IRQF_ONESHOT,
+ dev->driver->name, pcf85263);
+ if (ret) {
+ dev_err(dev, "irq %d unavailable (%d)\n",
+ pcf85263->irq, ret);
+ pcf85263->irq = 0;
+ }
+ }
+
+ if (pcf85263_can_wakeup_machine(pcf85263))
+ device_init_wakeup(dev, true);
+
+ pcf85263->rtc = devm_rtc_device_register(dev, dev->driver->name,
+ &rtc_ops, THIS_MODULE);
+ ret = PTR_ERR_OR_ZERO(pcf85263->rtc);
+ if (ret)
+ return ret;
+
+ /* We cannot support UIE mode if we do not have an IRQ line */
+ if (!pcf85263->irq)
+ pcf85263->rtc->uie_unsupported = 1;
+
+ dev_info(pcf85263->dev,
+ "PCF85263 RTC (irqpin=%s irq=%d)\n",
+ pcf85263_irqpin_names[pcf85263->irq_pin],
+ pcf85263->irq);
+
+ return 0;
+}
+
+static int pcf85263_remove(struct i2c_client *client)
+{
+ struct pcf85263 *pcf85263 = i2c_get_clientdata(client);
+
+ if (pcf85263_can_wakeup_machine(pcf85263))
+ device_init_wakeup(pcf85263->dev, false);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int pcf85263_suspend(struct device *dev)
+{
+ struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
+ int ret = 0;
+
+ if (device_may_wakeup(dev))
+ ret = enable_irq_wake(pcf85263->irq);
+
+ return ret;
+}
+
+static int pcf85263_resume(struct device *dev)
+{
+ struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
+ int ret = 0;
+
+ if (device_may_wakeup(dev))
+ ret = disable_irq_wake(pcf85263->irq);
+
+ return ret;
+}
+
+#endif
+
+static const struct i2c_device_id pcf85263_id[] = {
+ { "pcf85263", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, pcf85263_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id pcf85263_of_match[] = {
+ { .compatible = "nxp,pcf85263" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, pcf85263_of_match);
+#endif
+
+static SIMPLE_DEV_PM_OPS(pcf85263_pm_ops, pcf85263_suspend, pcf85263_resume);
+
+static struct i2c_driver pcf85263_driver = {
+ .driver = {
+ .name = "rtc-pcf85263",
+ .of_match_table = of_match_ptr(pcf85263_of_match),
+ .pm = &pcf85263_pm_ops,
+ },
+ .probe = pcf85263_probe,
+ .remove = pcf85263_remove,
+ .id_table = pcf85263_id,
+};
+
+module_i2c_driver(pcf85263_driver);
+
+MODULE_AUTHOR("Martin Fuzzey <mfuzzey@parkeon.com>");
+MODULE_DESCRIPTION("PCF85263 RTC Driver");
+MODULE_LICENSE("GPL");
--
You received this message because you are subscribed to "rtc-linux".
Membership options at http://groups.google.com/group/rtc-linux .
Please read http://groups.google.com/group/rtc-linux/web/checklist
before submitting a driver.
---
You received this message because you are subscribed to the Google Groups "rtc-linux" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rtc-linux+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 2/3] rtc: pcf85263: Basic driver
@ 2016-08-01 15:50 ` Martin Fuzzey
0 siblings, 0 replies; 15+ messages in thread
From: Martin Fuzzey @ 2016-08-01 15:50 UTC (permalink / raw)
To: Alessandro Zummo, Rob Herring, Alexandre Belloni
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA, rtc-linux-/JYPxA39Uh5TLH3MbocFFw
Add basic support for NXP PCF85263 I2C RTC chip.
Only RTC and Alarm functions are supported.
Signed-off-by: Martin Fuzzey <mfuzzey-mB3Nsq4MPf1BDgjK7y7TUQ@public.gmane.org>
---
drivers/rtc/Kconfig | 8 +
drivers/rtc/Makefile | 1
drivers/rtc/rtc-pcf85263.c | 655 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 664 insertions(+)
create mode 100644 drivers/rtc/rtc-pcf85263.c
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 18639e0..63ec0fc 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -396,6 +396,14 @@ config RTC_DRV_PCF85063
This driver can also be built as a module. If so, the module
will be called rtc-pcf85063.
+config RTC_DRV_PCF85263
+ tristate "NXP PCF85263"
+ help
+ If you say yes here you get support for the PCF85263 RTC chip
+
+ This driver can also be built as a module. If so, the module
+ will be called rtc-pcf85263.
+
config RTC_DRV_PCF8563
tristate "Philips PCF8563/Epson RTC8564"
help
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index ea28337..04f48fd 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -108,6 +108,7 @@ obj-$(CONFIG_RTC_DRV_PCF2123) += rtc-pcf2123.o
obj-$(CONFIG_RTC_DRV_PCF2127) += rtc-pcf2127.o
obj-$(CONFIG_RTC_DRV_PCF50633) += rtc-pcf50633.o
obj-$(CONFIG_RTC_DRV_PCF85063) += rtc-pcf85063.o
+obj-$(CONFIG_RTC_DRV_PCF85263) += rtc-pcf85263.o
obj-$(CONFIG_RTC_DRV_PCF8523) += rtc-pcf8523.o
obj-$(CONFIG_RTC_DRV_PCF8563) += rtc-pcf8563.o
obj-$(CONFIG_RTC_DRV_PCF8583) += rtc-pcf8583.o
diff --git a/drivers/rtc/rtc-pcf85263.c b/drivers/rtc/rtc-pcf85263.c
new file mode 100644
index 0000000..997742d
--- /dev/null
+++ b/drivers/rtc/rtc-pcf85263.c
@@ -0,0 +1,655 @@
+/*
+ * rtc-pcf85263 Driver for the NXP PCF85263 RTC
+ *
+ * Copyright 2016 Parkeon
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/rtc.h>
+#include <linux/i2c.h>
+#include <linux/bcd.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+
+#include <dt-bindings/rtc/nxp,pcf85263.h>
+
+#define DRV_NAME "rtc-pcf85263"
+
+#define PCF85263_REG_RTC_SC 0x01 /* Seconds */
+#define PCF85263_REG_RTC_SC_OS BIT(7) /* Oscilator stopped flag */
+
+#define PCF85263_REG_RTC_MN 0x02 /* Minutes */
+#define PCF85263_REG_RTC_HR 0x03 /* Hours */
+#define PCF85263_REG_RTC_DT 0x04 /* Day of month 1-31 */
+#define PCF85263_REG_RTC_DW 0x05 /* Day of week 0-6 */
+#define PCF85263_REG_RTC_MO 0x06 /* Month 1-12 */
+#define PCF85263_REG_RTC_YR 0x07 /* Year 0-99 */
+
+#define PCF85263_REG_ALM1_SC 0x08 /* Seconds */
+#define PCF85263_REG_ALM1_MN 0x09 /* Minutes */
+#define PCF85263_REG_ALM1_HR 0x0a /* Hours */
+#define PCF85263_REG_ALM1_DT 0x0b /* Day of month 1-31 */
+#define PCF85263_REG_ALM1_MO 0x0c /* Month 1-12 */
+
+#define PCF85263_REG_ALM_CTL 0x10
+#define PCF85263_REG_ALM_CTL_ALL_A1E 0x1f /* sec,min,hr,day,mon alarm 1 */
+
+#define PCF85263_REG_OSC 0x25
+#define PCF85263_REG_OSC_CL_MASK (BIT(0) | BIT(1))
+#define PCF85263_REG_OSC_CL_SHIFT 0
+#define PCF85263_REG_OSC_OSCD_MASK (BIT(2) | BIT(3))
+#define PCF85263_REG_OSC_OSCD_SHIFT 2
+#define PCF85263_REG_OSC_LOWJ BIT(4)
+#define PCF85263_REG_OSC_12H BIT(5)
+
+#define PCF85263_REG_PINIO 0x27
+#define PCF85263_REG_PINIO_INTAPM_MASK (BIT(0) | BIT(1))
+#define PCF85263_REG_PINIO_INTAPM_SHIFT 0
+#define PCF85263_INTAPM_INTA (0x2 << PCF85263_REG_PINIO_INTAPM_SHIFT)
+#define PCF85263_INTAPM_HIGHZ (0x3 << PCF85263_REG_PINIO_INTAPM_SHIFT)
+#define PCF85263_REG_PINIO_TSPM_MASK (BIT(2) | BIT(3))
+#define PCF85263_REG_PINIO_TSPM_SHIFT 2
+#define PCF85263_TSPM_DISABLED (0x0 << PCF85263_REG_PINIO_TSPM_SHIFT)
+#define PCF85263_TSPM_INTB (0x1 << PCF85263_REG_PINIO_TSPM_SHIFT)
+#define PCF85263_REG_PINIO_CLKDISABLE BIT(7)
+
+#define PCF85263_REG_FUNCTION 0x28
+#define PCF85263_REG_FUNCTION_COF_MASK 0x7
+#define PCF85263_REG_FUNCTION_COF_OFF 0x7 /* No clock output */
+
+#define PCF85263_REG_INTA_CTL 0x29
+#define PCF85263_REG_INTB_CTL 0x2A
+#define PCF85263_REG_INTx_CTL_A1E BIT(4) /* Alarm 1 */
+#define PCF85263_REG_INTx_CTL_ILP BIT(7) /* 0=pulse, 1=level */
+
+#define PCF85263_REG_FLAGS 0x2B
+#define PCF85263_REG_FLAGS_A1F BIT(5)
+
+#define PCF85263_REG_RAM_BYTE 0x2c
+
+#define PCF85263_REG_STOPENABLE 0x2e
+#define PCF85263_REG_STOPENABLE_STOP BIT(0)
+
+#define PCF85263_REG_RESET 0x2f /* Reset command */
+#define PCF85263_REG_RESET_CMD_CPR 0xa4 /* Clear prescaler */
+
+#define PCF85263_MAX_REG 0x2f
+
+#define PCF85263_HR_PM BIT(5)
+
+enum pcf85263_irqpin {
+ PCF85263_IRQPIN_NONE,
+ PCF85263_IRQPIN_INTA,
+ PCF85263_IRQPIN_INTB
+};
+
+static const char *const pcf85263_irqpin_names[] = {
+ [PCF85263_IRQPIN_NONE] = "None",
+ [PCF85263_IRQPIN_INTA] = "INTA",
+ [PCF85263_IRQPIN_INTB] = "INTB"
+};
+
+struct pcf85263 {
+ struct device *dev;
+ struct rtc_device *rtc;
+ struct regmap *regmap;
+ enum pcf85263_irqpin irq_pin;
+ int irq;
+ bool mode_12h;
+};
+
+/*
+ * Helpers to convert 12h to 24h and vice versa.
+ * Values in register are stored in BCD with a PM flag in bit 5
+ *
+ * 23:00 <=> 11PM <=> 0x31
+ * 00:00 <=> 12AM <=> 0x12
+ * 01:00 <=> 1AM <=> 0x01
+ * 12:00 <=> 12PM <=> 0x32
+ * 13:00 <=> 1PM <=> 0x21
+ */
+static int pcf85263_bcd12h_to_bin24h(int regval)
+{
+ int hr = bcd2bin(regval & 0x1f);
+ bool pm = regval & PCF85263_HR_PM;
+
+ if (hr == 12)
+ return pm ? 12 : 0;
+
+ return pm ? hr + 12 : hr;
+}
+
+static int pcf85263_bin24h_to_bcd12h(int hr24)
+{
+ bool pm = hr24 >= 12;
+ int hr12 = hr24 % 12;
+
+ if (!hr12)
+ hr12++;
+
+ return bin2bcd(hr12) | pm ? 0 : PCF85263_HR_PM;
+}
+
+static int pcf85263_read_time(struct device *dev, struct rtc_time *tm)
+{
+ struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
+ const int first = PCF85263_REG_RTC_SC;
+ const int last = PCF85263_REG_RTC_YR;
+ const int len = last - first + 1;
+ u8 regs[len];
+ u8 hr_reg;
+ int ret;
+
+ ret = regmap_bulk_read(pcf85263->regmap, first, regs, len);
+ if (ret)
+ return ret;
+
+ if (regs[PCF85263_REG_RTC_SC - first] & PCF85263_REG_RTC_SC_OS) {
+ dev_warn(dev, "Oscillator stop detected, date/time is not reliable.\n");
+ return -EINVAL;
+ }
+
+ tm->tm_sec = bcd2bin(regs[PCF85263_REG_RTC_SC - first] & 0x7f);
+ tm->tm_min = bcd2bin(regs[PCF85263_REG_RTC_MN - first] & 0x7f);
+
+ hr_reg = regs[PCF85263_REG_RTC_HR - first];
+ if (pcf85263->mode_12h)
+ tm->tm_hour = pcf85263_bcd12h_to_bin24h(hr_reg);
+ else
+ tm->tm_hour = bcd2bin(hr_reg & 0x3f);
+
+ tm->tm_mday = bcd2bin(regs[PCF85263_REG_RTC_DT - first]);
+ tm->tm_wday = bcd2bin(regs[PCF85263_REG_RTC_DW - first]);
+ tm->tm_mon = bcd2bin(regs[PCF85263_REG_RTC_MO - first]) - 1;
+ tm->tm_year = bcd2bin(regs[PCF85263_REG_RTC_YR - first]);
+
+ tm->tm_year += 100; /* Assume 21st century */
+
+ return 0;
+}
+
+static int pcf85263_set_time(struct device *dev, struct rtc_time *tm)
+{
+ struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
+
+ /*
+ * Before setting time need to stop RTC and disable prescaler
+ * Do this all in a single I2C transaction exploiting wraparound
+ * as described in data sheet.
+ * This means that the array below must be in register order
+ */
+ u8 regs[] = {
+ PCF85263_REG_STOPENABLE_STOP, /* STOP */
+ PCF85263_REG_RESET_CMD_CPR, /* Disable prescaler */
+ /* Wrap around to register 0 (1/100s) */
+ 0, /* 1/100s always zero. */
+ bin2bcd(tm->tm_sec),
+ bin2bcd(tm->tm_min),
+ bin2bcd(tm->tm_hour), /* 24-hour */
+ bin2bcd(tm->tm_mday),
+ bin2bcd(tm->tm_wday + 1),
+ bin2bcd(tm->tm_mon + 1),
+ bin2bcd(tm->tm_year % 100)
+ };
+ int ret;
+
+ ret = regmap_bulk_write(pcf85263->regmap, PCF85263_REG_STOPENABLE,
+ regs, sizeof(regs));
+ if (ret)
+ return ret;
+
+ /* As we have set the time in 24H update the hardware for that */
+ if (pcf85263->mode_12h) {
+ pcf85263->mode_12h = false;
+ ret = regmap_update_bits(pcf85263->regmap, PCF85263_REG_OSC,
+ PCF85263_REG_OSC_12H, 0);
+ if (ret)
+ return ret;
+ }
+
+ /* Start it again */
+ return regmap_write(pcf85263->regmap, PCF85263_REG_STOPENABLE, 0);
+}
+
+static int pcf85263_enable_alarm(struct pcf85263 *pcf85263, bool enable)
+{
+ int reg;
+ int ret;
+
+ ret = regmap_update_bits(pcf85263->regmap, PCF85263_REG_ALM_CTL,
+ PCF85263_REG_ALM_CTL_ALL_A1E,
+ enable ? PCF85263_REG_ALM_CTL_ALL_A1E : 0);
+ if (ret)
+ return ret;
+
+ switch (pcf85263->irq_pin) {
+ case PCF85263_IRQPIN_NONE:
+ return 0;
+
+ case PCF85263_IRQPIN_INTA:
+ reg = PCF85263_REG_INTA_CTL;
+ break;
+
+ case PCF85263_IRQPIN_INTB:
+ reg = PCF85263_REG_INTB_CTL;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return regmap_update_bits(pcf85263->regmap, reg,
+ PCF85263_REG_INTx_CTL_A1E,
+ enable ? PCF85263_REG_INTx_CTL_A1E : 0);
+}
+
+static int pcf85263_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+ struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
+ struct rtc_time *tm = &alarm->time;
+ const int first = PCF85263_REG_ALM1_SC;
+ const int last = PCF85263_REG_ALM1_MO;
+ const int len = last - first + 1;
+ u8 regs[len];
+ u8 hr_reg;
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_bulk_read(pcf85263->regmap, first, regs, len);
+ if (ret)
+ return ret;
+
+ tm->tm_sec = bcd2bin(regs[PCF85263_REG_ALM1_SC - first] & 0x7f);
+ tm->tm_min = bcd2bin(regs[PCF85263_REG_ALM1_MN - first] & 0x7f);
+
+ hr_reg = regs[PCF85263_REG_ALM1_HR - first];
+ if (pcf85263->mode_12h)
+ tm->tm_hour = pcf85263_bcd12h_to_bin24h(hr_reg);
+ else
+ tm->tm_hour = bcd2bin(hr_reg & 0x3f);
+
+ tm->tm_mday = bcd2bin(regs[PCF85263_REG_ALM1_DT - first]);
+ tm->tm_mon = bcd2bin(regs[PCF85263_REG_ALM1_MO - first]) - 1;
+ tm->tm_year = -1;
+ tm->tm_wday = -1;
+
+ ret = regmap_read(pcf85263->regmap, PCF85263_REG_ALM_CTL, ®val);
+ if (ret)
+ return ret;
+ alarm->enabled = !!(regval & PCF85263_REG_ALM_CTL_ALL_A1E);
+
+ ret = regmap_read(pcf85263->regmap, PCF85263_REG_FLAGS, ®val);
+ if (ret)
+ return ret;
+ alarm->pending = !!(regval & PCF85263_REG_FLAGS_A1F);
+
+ return 0;
+}
+
+static int pcf85263_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+ struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
+ struct rtc_time *tm = &alarm->time;
+ const int first = PCF85263_REG_ALM1_SC;
+ const int last = PCF85263_REG_ALM1_MO;
+ const int len = last - first + 1;
+ u8 regs[len];
+ int ret;
+
+ /* Disable alarm comparison during update */
+ ret = pcf85263_enable_alarm(pcf85263, false);
+ if (ret)
+ return ret;
+
+ /* Clear any pending alarm (write 0=>clr, 1=>no change) */
+ ret = regmap_write(pcf85263->regmap, PCF85263_REG_FLAGS,
+ ~PCF85263_REG_FLAGS_A1F);
+ if (ret)
+ return ret;
+
+ /* Set the alarm time registers */
+ regs[PCF85263_REG_ALM1_SC - first] = bin2bcd(tm->tm_sec);
+ regs[PCF85263_REG_ALM1_MN - first] = bin2bcd(tm->tm_min);
+ regs[PCF85263_REG_ALM1_HR - first] = pcf85263->mode_12h ?
+ pcf85263_bin24h_to_bcd12h(tm->tm_hour) :
+ bin2bcd(tm->tm_hour);
+ regs[PCF85263_REG_ALM1_DT - first] = bin2bcd(tm->tm_mday);
+ regs[PCF85263_REG_ALM1_MO - first] = bin2bcd(tm->tm_mon + 1);
+
+ ret = regmap_bulk_write(pcf85263->regmap, first, regs, sizeof(regs));
+ if (ret)
+ return ret;
+
+ if (alarm->enabled)
+ ret = pcf85263_enable_alarm(pcf85263, true);
+
+ return ret;
+}
+
+static int pcf85263_alarm_irq_enable(struct device *dev, unsigned int enable)
+{
+ struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
+
+ return pcf85263_enable_alarm(pcf85263, !!enable);
+}
+
+static irqreturn_t pcf85263_irq(int irq, void *data)
+{
+ struct pcf85263 *pcf85263 = data;
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(pcf85263->regmap, PCF85263_REG_FLAGS, ®val);
+ if (ret)
+ return IRQ_NONE;
+
+ if (regval & PCF85263_REG_FLAGS_A1F) {
+ regmap_write(pcf85263->regmap, PCF85263_REG_FLAGS,
+ ~PCF85263_REG_FLAGS_A1F);
+
+ rtc_update_irq(pcf85263->rtc, 1, RTC_IRQF | RTC_AF);
+
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_NONE;
+}
+
+static int pcf85263_check_osc_stopped(struct pcf85263 *pcf85263)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(pcf85263->regmap, PCF85263_REG_RTC_SC, ®val);
+ if (ret)
+ return ret;
+
+ ret = regval & PCF85263_REG_RTC_SC_OS ? 1 : 0;
+ if (ret)
+ dev_warn(pcf85263->dev, "Oscillator stop detected, date/time is not reliable.\n");
+
+ return ret;
+}
+
+#ifdef CONFIG_RTC_INTF_DEV
+static int pcf85263_ioctl(struct device *dev,
+ unsigned int cmd, unsigned long arg)
+{
+ struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
+ int ret;
+
+ switch (cmd) {
+ case RTC_VL_READ:
+ ret = pcf85263_check_osc_stopped(pcf85263);
+ if (ret < 0)
+ return ret;
+
+ if (copy_to_user((void __user *)arg, &ret, sizeof(int)))
+ return -EFAULT;
+ return 0;
+
+ case RTC_VL_CLR:
+ return regmap_update_bits(pcf85263->regmap,
+ PCF85263_REG_RTC_SC,
+ PCF85263_REG_RTC_SC_OS, 0);
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+#else
+#define pcf85263_ioctl NULL
+#endif
+
+static int pcf85263_init_hw(struct pcf85263 *pcf85263)
+{
+ struct device_node *np = pcf85263->dev->of_node;
+ unsigned int regval;
+ u32 propval;
+ int ret;
+
+ /* Determine if oscilator has been stopped (probably low power) */
+ ret = pcf85263_check_osc_stopped(pcf85263);
+ if (ret < 0) {
+ /* Log here since this is the first hw access on probe */
+ dev_err(pcf85263->dev, "Unable to read register\n");
+
+ return ret;
+ }
+
+ /* Determine 12/24H mode */
+ ret = regmap_read(pcf85263->regmap, PCF85263_REG_OSC, ®val);
+ if (ret)
+ return ret;
+ pcf85263->mode_12h = !!(regval & PCF85263_REG_OSC_12H);
+
+ /* Set oscilator register */
+ regval &= ~PCF85263_REG_OSC_12H; /* keep current 12/24 h setting */
+
+ propval = PCF85263_QUARTZCAP_12p5pF;
+ of_property_read_u32(np, "quartz-load-capacitance", &propval);
+ regval |= ((propval << PCF85263_REG_OSC_CL_SHIFT)
+ & PCF85263_REG_OSC_CL_MASK);
+
+ propval = PCF85263_QUARTZDRIVE_NORMAL;
+ of_property_read_u32(np, "quartz-drive-strength", &propval);
+ regval |= ((propval << PCF85263_REG_OSC_OSCD_SHIFT)
+ & PCF85263_REG_OSC_OSCD_MASK);
+
+ if (of_property_read_bool(np, "quartz-low-jitter"))
+ regval |= PCF85263_REG_OSC_LOWJ;
+
+ ret = regmap_write(pcf85263->regmap, PCF85263_REG_OSC, regval);
+ if (ret)
+ return ret;
+
+ /* Set function register (RTC mode, 1s tick, clock output static) */
+ ret = regmap_write(pcf85263->regmap, PCF85263_REG_FUNCTION,
+ PCF85263_REG_FUNCTION_COF_OFF);
+ if (ret)
+ return ret;
+
+ /* Set all interrupts to disabled, level mode */
+ ret = regmap_write(pcf85263->regmap, PCF85263_REG_INTA_CTL,
+ PCF85263_REG_INTx_CTL_ILP);
+ if (ret)
+ return ret;
+ ret = regmap_write(pcf85263->regmap, PCF85263_REG_INTB_CTL,
+ PCF85263_REG_INTx_CTL_ILP);
+ if (ret)
+ return ret;
+
+ /* Setup IO pin config register */
+ regval = PCF85263_REG_PINIO_CLKDISABLE;
+ switch (pcf85263->irq_pin) {
+ case PCF85263_IRQPIN_INTA:
+ regval |= (PCF85263_INTAPM_INTA | PCF85263_TSPM_DISABLED);
+ break;
+ case PCF85263_IRQPIN_INTB:
+ regval |= (PCF85263_INTAPM_HIGHZ | PCF85263_TSPM_INTB);
+ break;
+ case PCF85263_IRQPIN_NONE:
+ regval |= (PCF85263_INTAPM_HIGHZ | PCF85263_TSPM_DISABLED);
+ break;
+ }
+ ret = regmap_write(pcf85263->regmap, PCF85263_REG_PINIO, regval);
+
+ return ret;
+}
+
+static const struct rtc_class_ops rtc_ops = {
+ .ioctl = pcf85263_ioctl,
+ .read_time = pcf85263_read_time,
+ .set_time = pcf85263_set_time,
+ .read_alarm = pcf85263_read_alarm,
+ .set_alarm = pcf85263_set_alarm,
+ .alarm_irq_enable = pcf85263_alarm_irq_enable,
+};
+
+static const struct regmap_config pcf85263_regmap_cfg = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = PCF85263_MAX_REG,
+};
+
+/*
+ * On some boards the interrupt line may not be wired to the CPU but only to
+ * a power supply circuit.
+ * In that case no interrupt will be specified in the device tree but the
+ * wakeup-source DT property may be used to enable wakeup programming in
+ * sysfs
+ */
+static bool pcf85263_can_wakeup_machine(struct pcf85263 *pcf85263)
+{
+ return pcf85263->irq ||
+ of_property_read_bool(pcf85263->dev->of_node, "wakeup-source");
+}
+
+static int pcf85263_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &client->dev;
+ struct pcf85263 *pcf85263;
+ int ret;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C |
+ I2C_FUNC_SMBUS_BYTE_DATA |
+ I2C_FUNC_SMBUS_I2C_BLOCK))
+ return -ENODEV;
+
+ pcf85263 = devm_kzalloc(dev, sizeof(*pcf85263), GFP_KERNEL);
+ if (!pcf85263)
+ return -ENOMEM;
+
+ pcf85263->dev = dev;
+ pcf85263->irq = client->irq;
+ dev_set_drvdata(dev, pcf85263);
+
+ pcf85263->regmap = devm_regmap_init_i2c(client, &pcf85263_regmap_cfg);
+ if (IS_ERR(pcf85263->regmap)) {
+ ret = PTR_ERR(pcf85263->regmap);
+ dev_err(dev, "regmap allocation failed (%d)\n", ret);
+
+ return ret;
+ }
+
+ /* Determine which interrupt pin the board uses */
+ if (pcf85263_can_wakeup_machine(pcf85263)) {
+ if (of_property_match_string(dev->of_node,
+ "interrupt-names", "INTB") >= 0)
+ pcf85263->irq_pin = PCF85263_IRQPIN_INTB;
+ else
+ pcf85263->irq_pin = PCF85263_IRQPIN_INTA;
+ } else {
+ pcf85263->irq_pin = PCF85263_IRQPIN_NONE;
+ }
+
+ ret = pcf85263_init_hw(pcf85263);
+ if (ret)
+ return ret;
+
+ if (pcf85263->irq) {
+ ret = devm_request_threaded_irq(dev, pcf85263->irq, NULL,
+ pcf85263_irq,
+ IRQF_ONESHOT,
+ dev->driver->name, pcf85263);
+ if (ret) {
+ dev_err(dev, "irq %d unavailable (%d)\n",
+ pcf85263->irq, ret);
+ pcf85263->irq = 0;
+ }
+ }
+
+ if (pcf85263_can_wakeup_machine(pcf85263))
+ device_init_wakeup(dev, true);
+
+ pcf85263->rtc = devm_rtc_device_register(dev, dev->driver->name,
+ &rtc_ops, THIS_MODULE);
+ ret = PTR_ERR_OR_ZERO(pcf85263->rtc);
+ if (ret)
+ return ret;
+
+ /* We cannot support UIE mode if we do not have an IRQ line */
+ if (!pcf85263->irq)
+ pcf85263->rtc->uie_unsupported = 1;
+
+ dev_info(pcf85263->dev,
+ "PCF85263 RTC (irqpin=%s irq=%d)\n",
+ pcf85263_irqpin_names[pcf85263->irq_pin],
+ pcf85263->irq);
+
+ return 0;
+}
+
+static int pcf85263_remove(struct i2c_client *client)
+{
+ struct pcf85263 *pcf85263 = i2c_get_clientdata(client);
+
+ if (pcf85263_can_wakeup_machine(pcf85263))
+ device_init_wakeup(pcf85263->dev, false);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int pcf85263_suspend(struct device *dev)
+{
+ struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
+ int ret = 0;
+
+ if (device_may_wakeup(dev))
+ ret = enable_irq_wake(pcf85263->irq);
+
+ return ret;
+}
+
+static int pcf85263_resume(struct device *dev)
+{
+ struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
+ int ret = 0;
+
+ if (device_may_wakeup(dev))
+ ret = disable_irq_wake(pcf85263->irq);
+
+ return ret;
+}
+
+#endif
+
+static const struct i2c_device_id pcf85263_id[] = {
+ { "pcf85263", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, pcf85263_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id pcf85263_of_match[] = {
+ { .compatible = "nxp,pcf85263" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, pcf85263_of_match);
+#endif
+
+static SIMPLE_DEV_PM_OPS(pcf85263_pm_ops, pcf85263_suspend, pcf85263_resume);
+
+static struct i2c_driver pcf85263_driver = {
+ .driver = {
+ .name = "rtc-pcf85263",
+ .of_match_table = of_match_ptr(pcf85263_of_match),
+ .pm = &pcf85263_pm_ops,
+ },
+ .probe = pcf85263_probe,
+ .remove = pcf85263_remove,
+ .id_table = pcf85263_id,
+};
+
+module_i2c_driver(pcf85263_driver);
+
+MODULE_AUTHOR("Martin Fuzzey <mfuzzey-mB3Nsq4MPf1BDgjK7y7TUQ@public.gmane.org>");
+MODULE_DESCRIPTION("PCF85263 RTC Driver");
+MODULE_LICENSE("GPL");
--
You received this message because you are subscribed to "rtc-linux".
Membership options at http://groups.google.com/group/rtc-linux .
Please read http://groups.google.com/group/rtc-linux/web/checklist
before submitting a driver.
---
You received this message because you are subscribed to the Google Groups "rtc-linux" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rtc-linux+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
For more options, visit https://groups.google.com/d/optout.
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [rtc-linux] [PATCH 3/3] rtc: pcf85263: Support multiple centuries
2016-08-01 15:50 ` Martin Fuzzey
@ 2016-08-01 15:50 ` Martin Fuzzey
-1 siblings, 0 replies; 15+ messages in thread
From: Martin Fuzzey @ 2016-08-01 15:50 UTC (permalink / raw)
To: Alessandro Zummo, Rob Herring, Alexandre Belloni; +Cc: devicetree, rtc-linux
The hardware has neither a century register nor a century warp around bit.
However it does have a single byte of non volatile RAM.
Use this to provide full century and wrap around support by storing:
* The current century
* The current half century (lower=00-49, upper=50-99)
If the byte is not set the 21st century is assumed.
Signed-off-by: Martin Fuzzey <mfuzzey@parkeon.com>
---
drivers/rtc/rtc-pcf85263.c | 105 +++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 103 insertions(+), 2 deletions(-)
diff --git a/drivers/rtc/rtc-pcf85263.c b/drivers/rtc/rtc-pcf85263.c
index 997742d..7b55d92 100644
--- a/drivers/rtc/rtc-pcf85263.c
+++ b/drivers/rtc/rtc-pcf85263.c
@@ -83,6 +83,10 @@
#define PCF85263_HR_PM BIT(5)
+/* Our data stored in the RAM byte */
+#define PCF85263_STATE_CENTURY_MASK 0x7f
+#define PCF85263_STATE_UPPER_HALF_CENTURY BIT(7)
+
enum pcf85263_irqpin {
PCF85263_IRQPIN_NONE,
PCF85263_IRQPIN_INTA,
@@ -101,6 +105,8 @@ struct pcf85263 {
struct regmap *regmap;
enum pcf85263_irqpin irq_pin;
int irq;
+ u8 century; /* 1 = 1900 2 = 2000, ... */
+ bool century_half; /* false = 0-49, true=50-99 */
bool mode_12h;
};
@@ -136,6 +142,85 @@ static int pcf85263_bin24h_to_bcd12h(int hr24)
return bin2bcd(hr12) | pm ? 0 : PCF85263_HR_PM;
}
+static inline bool pcf85263_century_half(int year)
+{
+ return (year % 100) >= 50;
+}
+
+/*
+ * Since the hardware only has a year range of 00 to 99 we use
+ * the ram byte to store the century. 1=1900, 2=2000, 3=2100
+ * A value of zero is assumed to be 2000
+ *
+ * Set the ram byte when we set the clock which lets us use any
+ * century supported by linux (tm_year=0 => 1900)
+ *
+ * Unfortunately the hardware has no wrap around flag so fix it
+ * by also storing a flag indicating if the year is in the
+ * upper or lower half of the century.
+ */
+static int pcf85263_update_ram_byte(struct pcf85263 *pcf85263)
+{
+ u8 val = pcf85263->century & PCF85263_STATE_CENTURY_MASK;
+
+ if (pcf85263->century_half)
+ val |= PCF85263_STATE_UPPER_HALF_CENTURY;
+
+ return regmap_write(pcf85263->regmap, PCF85263_REG_RAM_BYTE, val);
+}
+
+static int pcf85263_read_ram_byte(struct pcf85263 *pcf85263)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(pcf85263->regmap, PCF85263_REG_RAM_BYTE, ®val);
+ if (ret)
+ return ret;
+
+ pcf85263->century = regval & PCF85263_STATE_CENTURY_MASK;
+ pcf85263->century_half = !!(regval & PCF85263_STATE_UPPER_HALF_CENTURY);
+
+ if (!pcf85263->century) { /* Not valid => not initialised yet */
+ int year;
+
+ ret = regmap_read(pcf85263->regmap,
+ PCF85263_REG_RTC_YR, ®val);
+ if (ret)
+ return ret;
+
+ pcf85263->century = 2;
+ year = bcd2bin(regval) + 1900 + (pcf85263->century - 1) * 100;
+ pcf85263->century_half = pcf85263_century_half(year);
+
+ dev_warn(pcf85263->dev, "No century in NVRAM - assume %d\n",
+ year);
+ }
+
+ return 0;
+}
+
+/*
+ * Detect year overflow by comparing the half (upper, lower) of
+ * the current year with the half the last time we read it
+ */
+static int pcf85263_update_century(struct pcf85263 *pcf85263, int year)
+{
+ bool cur_century_half;
+
+ cur_century_half = pcf85263_century_half(year);
+
+ if (cur_century_half == pcf85263->century_half)
+ return 0;
+
+ if (!cur_century_half) /* Year has wrapped around */
+ pcf85263->century++;
+
+ pcf85263->century_half = cur_century_half;
+
+ return pcf85263_update_ram_byte(pcf85263);
+}
+
static int pcf85263_read_time(struct device *dev, struct rtc_time *tm)
{
struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
@@ -169,7 +254,11 @@ static int pcf85263_read_time(struct device *dev, struct rtc_time *tm)
tm->tm_mon = bcd2bin(regs[PCF85263_REG_RTC_MO - first]) - 1;
tm->tm_year = bcd2bin(regs[PCF85263_REG_RTC_YR - first]);
- tm->tm_year += 100; /* Assume 21st century */
+ ret = pcf85263_update_century(pcf85263, tm->tm_year);
+ if (ret)
+ return ret;
+
+ tm->tm_year += (pcf85263->century - 1) * 100;
return 0;
}
@@ -214,7 +303,14 @@ static int pcf85263_set_time(struct device *dev, struct rtc_time *tm)
}
/* Start it again */
- return regmap_write(pcf85263->regmap, PCF85263_REG_STOPENABLE, 0);
+ ret = regmap_write(pcf85263->regmap, PCF85263_REG_STOPENABLE, 0);
+ if (ret)
+ return ret;
+
+ pcf85263->century = (tm->tm_year / 100) + 1;
+ pcf85263->century_half = pcf85263_century_half(tm->tm_year);
+
+ return pcf85263_update_ram_byte(pcf85263);
}
static int pcf85263_enable_alarm(struct pcf85263 *pcf85263, bool enable)
@@ -422,6 +518,11 @@ static int pcf85263_init_hw(struct pcf85263 *pcf85263)
return ret;
}
+ /* Get our persistent state from the ram byte */
+ ret = pcf85263_read_ram_byte(pcf85263);
+ if (ret < 0)
+ return ret;
+
/* Determine 12/24H mode */
ret = regmap_read(pcf85263->regmap, PCF85263_REG_OSC, ®val);
if (ret)
--
You received this message because you are subscribed to "rtc-linux".
Membership options at http://groups.google.com/group/rtc-linux .
Please read http://groups.google.com/group/rtc-linux/web/checklist
before submitting a driver.
---
You received this message because you are subscribed to the Google Groups "rtc-linux" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rtc-linux+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 3/3] rtc: pcf85263: Support multiple centuries
@ 2016-08-01 15:50 ` Martin Fuzzey
0 siblings, 0 replies; 15+ messages in thread
From: Martin Fuzzey @ 2016-08-01 15:50 UTC (permalink / raw)
To: Alessandro Zummo, Rob Herring, Alexandre Belloni
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA, rtc-linux-/JYPxA39Uh5TLH3MbocFFw
The hardware has neither a century register nor a century warp around bit.
However it does have a single byte of non volatile RAM.
Use this to provide full century and wrap around support by storing:
* The current century
* The current half century (lower=00-49, upper=50-99)
If the byte is not set the 21st century is assumed.
Signed-off-by: Martin Fuzzey <mfuzzey-mB3Nsq4MPf1BDgjK7y7TUQ@public.gmane.org>
---
drivers/rtc/rtc-pcf85263.c | 105 +++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 103 insertions(+), 2 deletions(-)
diff --git a/drivers/rtc/rtc-pcf85263.c b/drivers/rtc/rtc-pcf85263.c
index 997742d..7b55d92 100644
--- a/drivers/rtc/rtc-pcf85263.c
+++ b/drivers/rtc/rtc-pcf85263.c
@@ -83,6 +83,10 @@
#define PCF85263_HR_PM BIT(5)
+/* Our data stored in the RAM byte */
+#define PCF85263_STATE_CENTURY_MASK 0x7f
+#define PCF85263_STATE_UPPER_HALF_CENTURY BIT(7)
+
enum pcf85263_irqpin {
PCF85263_IRQPIN_NONE,
PCF85263_IRQPIN_INTA,
@@ -101,6 +105,8 @@ struct pcf85263 {
struct regmap *regmap;
enum pcf85263_irqpin irq_pin;
int irq;
+ u8 century; /* 1 = 1900 2 = 2000, ... */
+ bool century_half; /* false = 0-49, true=50-99 */
bool mode_12h;
};
@@ -136,6 +142,85 @@ static int pcf85263_bin24h_to_bcd12h(int hr24)
return bin2bcd(hr12) | pm ? 0 : PCF85263_HR_PM;
}
+static inline bool pcf85263_century_half(int year)
+{
+ return (year % 100) >= 50;
+}
+
+/*
+ * Since the hardware only has a year range of 00 to 99 we use
+ * the ram byte to store the century. 1=1900, 2=2000, 3=2100
+ * A value of zero is assumed to be 2000
+ *
+ * Set the ram byte when we set the clock which lets us use any
+ * century supported by linux (tm_year=0 => 1900)
+ *
+ * Unfortunately the hardware has no wrap around flag so fix it
+ * by also storing a flag indicating if the year is in the
+ * upper or lower half of the century.
+ */
+static int pcf85263_update_ram_byte(struct pcf85263 *pcf85263)
+{
+ u8 val = pcf85263->century & PCF85263_STATE_CENTURY_MASK;
+
+ if (pcf85263->century_half)
+ val |= PCF85263_STATE_UPPER_HALF_CENTURY;
+
+ return regmap_write(pcf85263->regmap, PCF85263_REG_RAM_BYTE, val);
+}
+
+static int pcf85263_read_ram_byte(struct pcf85263 *pcf85263)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(pcf85263->regmap, PCF85263_REG_RAM_BYTE, ®val);
+ if (ret)
+ return ret;
+
+ pcf85263->century = regval & PCF85263_STATE_CENTURY_MASK;
+ pcf85263->century_half = !!(regval & PCF85263_STATE_UPPER_HALF_CENTURY);
+
+ if (!pcf85263->century) { /* Not valid => not initialised yet */
+ int year;
+
+ ret = regmap_read(pcf85263->regmap,
+ PCF85263_REG_RTC_YR, ®val);
+ if (ret)
+ return ret;
+
+ pcf85263->century = 2;
+ year = bcd2bin(regval) + 1900 + (pcf85263->century - 1) * 100;
+ pcf85263->century_half = pcf85263_century_half(year);
+
+ dev_warn(pcf85263->dev, "No century in NVRAM - assume %d\n",
+ year);
+ }
+
+ return 0;
+}
+
+/*
+ * Detect year overflow by comparing the half (upper, lower) of
+ * the current year with the half the last time we read it
+ */
+static int pcf85263_update_century(struct pcf85263 *pcf85263, int year)
+{
+ bool cur_century_half;
+
+ cur_century_half = pcf85263_century_half(year);
+
+ if (cur_century_half == pcf85263->century_half)
+ return 0;
+
+ if (!cur_century_half) /* Year has wrapped around */
+ pcf85263->century++;
+
+ pcf85263->century_half = cur_century_half;
+
+ return pcf85263_update_ram_byte(pcf85263);
+}
+
static int pcf85263_read_time(struct device *dev, struct rtc_time *tm)
{
struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
@@ -169,7 +254,11 @@ static int pcf85263_read_time(struct device *dev, struct rtc_time *tm)
tm->tm_mon = bcd2bin(regs[PCF85263_REG_RTC_MO - first]) - 1;
tm->tm_year = bcd2bin(regs[PCF85263_REG_RTC_YR - first]);
- tm->tm_year += 100; /* Assume 21st century */
+ ret = pcf85263_update_century(pcf85263, tm->tm_year);
+ if (ret)
+ return ret;
+
+ tm->tm_year += (pcf85263->century - 1) * 100;
return 0;
}
@@ -214,7 +303,14 @@ static int pcf85263_set_time(struct device *dev, struct rtc_time *tm)
}
/* Start it again */
- return regmap_write(pcf85263->regmap, PCF85263_REG_STOPENABLE, 0);
+ ret = regmap_write(pcf85263->regmap, PCF85263_REG_STOPENABLE, 0);
+ if (ret)
+ return ret;
+
+ pcf85263->century = (tm->tm_year / 100) + 1;
+ pcf85263->century_half = pcf85263_century_half(tm->tm_year);
+
+ return pcf85263_update_ram_byte(pcf85263);
}
static int pcf85263_enable_alarm(struct pcf85263 *pcf85263, bool enable)
@@ -422,6 +518,11 @@ static int pcf85263_init_hw(struct pcf85263 *pcf85263)
return ret;
}
+ /* Get our persistent state from the ram byte */
+ ret = pcf85263_read_ram_byte(pcf85263);
+ if (ret < 0)
+ return ret;
+
/* Determine 12/24H mode */
ret = regmap_read(pcf85263->regmap, PCF85263_REG_OSC, ®val);
if (ret)
--
You received this message because you are subscribed to "rtc-linux".
Membership options at http://groups.google.com/group/rtc-linux .
Please read http://groups.google.com/group/rtc-linux/web/checklist
before submitting a driver.
---
You received this message because you are subscribed to the Google Groups "rtc-linux" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rtc-linux+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
For more options, visit https://groups.google.com/d/optout.
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [rtc-linux] Re: [PATCH 1/3] dt-binding: rtc Add DT binding for NXP 85263 RTC
2016-08-01 15:50 ` Martin Fuzzey
@ 2016-09-21 20:43 ` Alexandre Belloni
-1 siblings, 0 replies; 15+ messages in thread
From: Alexandre Belloni @ 2016-09-21 20:43 UTC (permalink / raw)
To: Martin Fuzzey, Rob Herring; +Cc: Alessandro Zummo, devicetree, rtc-linux
Rob, can you review those bindings?
On 01/08/2016 at 17:50:32 +0200, Martin Fuzzey wrote :
Please always include a commit message.
> Signed-off-by: Martin Fuzzey <mfuzzey@parkeon.com>
> ---
> .../devicetree/bindings/rtc/nxp,pcf85263.txt | 41 ++++++++++++++++++++
> include/dt-bindings/rtc/nxp,pcf85263.h | 14 +++++++
> 2 files changed, 55 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/rtc/nxp,pcf85263.txt
> create mode 100644 include/dt-bindings/rtc/nxp,pcf85263.h
>
> diff --git a/Documentation/devicetree/bindings/rtc/nxp,pcf85263.txt b/Documentation/devicetree/bindings/rtc/nxp,pcf85263.txt
> new file mode 100644
> index 0000000..03b9505
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/rtc/nxp,pcf85263.txt
> @@ -0,0 +1,41 @@
> +NXP PCF85263 I2C Real Time Clock
> +
> +Required properties:
> +- compatible: must be: "nxp,rtc-pcf85263"
> +- reg: must be the I2C address
> +
> +Optional properties:
> +- interrupt-names: Which interrupt signal is used must be "INTA" or "INTB"
> + Defaults to "INTA"
> +
> +- quartz-load-capacitance: The internal capacitor to select for the quartz:
> + PCF85263_QUARTZCAP_7pF [0]
> + PCF85263_QUARTZCAP_6pF [1]
> + PCF85263_QUARTZCAP_12p5pF [2] DEFAULT
> +
> +- quartz-drive-strength: Drive strength for the quartz:
> + PCF85263_QUARTZDRIVE_NORMAL [0] DEFAULT
> + PCF85263_QUARTZDRIVE_LOW [1]
> + PCF85263_QUARTZDRIVE_HIGH [2]
> +
> +- quartz-low-jitter: Boolean property, if present enables low jitter mode which
> + reduces jitter at the cost of increased power consumption.
> +
Maybe that one should be handled using sysfs instead of DT as it is more
policy than HW related.
> +- wakeup-source: mark the chip as a wakeup source, independently of
> + the availability of an IRQ line connected to the SoC.
> + This is useful if the IRQ line is connected to a PMIC or other circuit
> + that can power up the device rather than to a normal SOC interrupt.
> +
> +Example:
> +
> +rtc@51 {
> + compatible = "nxp,pcf85263";
> + reg = <0x51>;
> +
> + interrupt-parent = <&gpio4>;
> + interrupts = <5 IRQ_TYPE_LEVEL_LOW>;
> + interrupt-names = "INTB";
> +
> + quartz-load-capacitance = <PCF85263_QUARTZCAP_12p5pF>;
> + quartz-drive-strength = <PCF85263_QUARTZDRIVE_LOW>;
> +};
> diff --git a/include/dt-bindings/rtc/nxp,pcf85263.h b/include/dt-bindings/rtc/nxp,pcf85263.h
> new file mode 100644
> index 0000000..ea87ae4
> --- /dev/null
> +++ b/include/dt-bindings/rtc/nxp,pcf85263.h
> @@ -0,0 +1,14 @@
> +#ifndef _DT_BINDINGS_RTC_NXP_PCF85263_H
> +#define _DT_BINDINGS_RTC_NXP_PCF85263_H
> +
> +/* Quartz capacitance */
> +#define PCF85263_QUARTZCAP_7pF 0
> +#define PCF85263_QUARTZCAP_6pF 1
> +#define PCF85263_QUARTZCAP_12p5pF 2
> +
> +/* Quartz drive strength */
> +#define PCF85263_QUARTZDRIVE_NORMAL 0
> +#define PCF85263_QUARTZDRIVE_LOW 1
> +#define PCF85263_QUARTZDRIVE_HIGH 2
> +
> +#endif /* _DT_BINDINGS_RTC_NXP_PCF85263_H */
>
--
Alexandre Belloni, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
--
You received this message because you are subscribed to "rtc-linux".
Membership options at http://groups.google.com/group/rtc-linux .
Please read http://groups.google.com/group/rtc-linux/web/checklist
before submitting a driver.
---
You received this message because you are subscribed to the Google Groups "rtc-linux" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rtc-linux+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 1/3] dt-binding: rtc Add DT binding for NXP 85263 RTC
@ 2016-09-21 20:43 ` Alexandre Belloni
0 siblings, 0 replies; 15+ messages in thread
From: Alexandre Belloni @ 2016-09-21 20:43 UTC (permalink / raw)
To: Martin Fuzzey, Rob Herring
Cc: Alessandro Zummo, devicetree-u79uwXL29TY76Z2rM5mHXA,
rtc-linux-/JYPxA39Uh5TLH3MbocFFw
Rob, can you review those bindings?
On 01/08/2016 at 17:50:32 +0200, Martin Fuzzey wrote :
Please always include a commit message.
> Signed-off-by: Martin Fuzzey <mfuzzey-mB3Nsq4MPf1BDgjK7y7TUQ@public.gmane.org>
> ---
> .../devicetree/bindings/rtc/nxp,pcf85263.txt | 41 ++++++++++++++++++++
> include/dt-bindings/rtc/nxp,pcf85263.h | 14 +++++++
> 2 files changed, 55 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/rtc/nxp,pcf85263.txt
> create mode 100644 include/dt-bindings/rtc/nxp,pcf85263.h
>
> diff --git a/Documentation/devicetree/bindings/rtc/nxp,pcf85263.txt b/Documentation/devicetree/bindings/rtc/nxp,pcf85263.txt
> new file mode 100644
> index 0000000..03b9505
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/rtc/nxp,pcf85263.txt
> @@ -0,0 +1,41 @@
> +NXP PCF85263 I2C Real Time Clock
> +
> +Required properties:
> +- compatible: must be: "nxp,rtc-pcf85263"
> +- reg: must be the I2C address
> +
> +Optional properties:
> +- interrupt-names: Which interrupt signal is used must be "INTA" or "INTB"
> + Defaults to "INTA"
> +
> +- quartz-load-capacitance: The internal capacitor to select for the quartz:
> + PCF85263_QUARTZCAP_7pF [0]
> + PCF85263_QUARTZCAP_6pF [1]
> + PCF85263_QUARTZCAP_12p5pF [2] DEFAULT
> +
> +- quartz-drive-strength: Drive strength for the quartz:
> + PCF85263_QUARTZDRIVE_NORMAL [0] DEFAULT
> + PCF85263_QUARTZDRIVE_LOW [1]
> + PCF85263_QUARTZDRIVE_HIGH [2]
> +
> +- quartz-low-jitter: Boolean property, if present enables low jitter mode which
> + reduces jitter at the cost of increased power consumption.
> +
Maybe that one should be handled using sysfs instead of DT as it is more
policy than HW related.
> +- wakeup-source: mark the chip as a wakeup source, independently of
> + the availability of an IRQ line connected to the SoC.
> + This is useful if the IRQ line is connected to a PMIC or other circuit
> + that can power up the device rather than to a normal SOC interrupt.
> +
> +Example:
> +
> +rtc@51 {
> + compatible = "nxp,pcf85263";
> + reg = <0x51>;
> +
> + interrupt-parent = <&gpio4>;
> + interrupts = <5 IRQ_TYPE_LEVEL_LOW>;
> + interrupt-names = "INTB";
> +
> + quartz-load-capacitance = <PCF85263_QUARTZCAP_12p5pF>;
> + quartz-drive-strength = <PCF85263_QUARTZDRIVE_LOW>;
> +};
> diff --git a/include/dt-bindings/rtc/nxp,pcf85263.h b/include/dt-bindings/rtc/nxp,pcf85263.h
> new file mode 100644
> index 0000000..ea87ae4
> --- /dev/null
> +++ b/include/dt-bindings/rtc/nxp,pcf85263.h
> @@ -0,0 +1,14 @@
> +#ifndef _DT_BINDINGS_RTC_NXP_PCF85263_H
> +#define _DT_BINDINGS_RTC_NXP_PCF85263_H
> +
> +/* Quartz capacitance */
> +#define PCF85263_QUARTZCAP_7pF 0
> +#define PCF85263_QUARTZCAP_6pF 1
> +#define PCF85263_QUARTZCAP_12p5pF 2
> +
> +/* Quartz drive strength */
> +#define PCF85263_QUARTZDRIVE_NORMAL 0
> +#define PCF85263_QUARTZDRIVE_LOW 1
> +#define PCF85263_QUARTZDRIVE_HIGH 2
> +
> +#endif /* _DT_BINDINGS_RTC_NXP_PCF85263_H */
>
--
Alexandre Belloni, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
--
You received this message because you are subscribed to "rtc-linux".
Membership options at http://groups.google.com/group/rtc-linux .
Please read http://groups.google.com/group/rtc-linux/web/checklist
before submitting a driver.
---
You received this message because you are subscribed to the Google Groups "rtc-linux" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rtc-linux+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
For more options, visit https://groups.google.com/d/optout.
^ permalink raw reply [flat|nested] 15+ messages in thread
* [rtc-linux] Re: [PATCH 3/3] rtc: pcf85263: Support multiple centuries
2016-08-01 15:50 ` Martin Fuzzey
@ 2016-09-21 21:33 ` Alexandre Belloni
-1 siblings, 0 replies; 15+ messages in thread
From: Alexandre Belloni @ 2016-09-21 21:33 UTC (permalink / raw)
To: Martin Fuzzey; +Cc: Alessandro Zummo, Rob Herring, devicetree, rtc-linux
Hi,
On 01/08/2016 at 17:50:36 +0200, Martin Fuzzey wrote :
> The hardware has neither a century register nor a century warp around bit.
>
> However it does have a single byte of non volatile RAM.
>
> Use this to provide full century and wrap around support by storing:
> * The current century
> * The current half century (lower=00-49, upper=50-99)
>
> If the byte is not set the 21st century is assumed.
>
While the idea is certainly interesting and properly implemented, this
is not working as expected because the RTC thinks that year 00 is a leap
year and both 1900 and 2100 are not leap years.
While it is possible to wark around that, I don't think there is any
added benefit as this RTC will work properly as is until 2099 (and by
then it will probably have been replaced).
> Signed-off-by: Martin Fuzzey <mfuzzey@parkeon.com>
> ---
> drivers/rtc/rtc-pcf85263.c | 105 +++++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 103 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/rtc/rtc-pcf85263.c b/drivers/rtc/rtc-pcf85263.c
> index 997742d..7b55d92 100644
> --- a/drivers/rtc/rtc-pcf85263.c
> +++ b/drivers/rtc/rtc-pcf85263.c
> @@ -83,6 +83,10 @@
>
> #define PCF85263_HR_PM BIT(5)
>
> +/* Our data stored in the RAM byte */
> +#define PCF85263_STATE_CENTURY_MASK 0x7f
> +#define PCF85263_STATE_UPPER_HALF_CENTURY BIT(7)
> +
> enum pcf85263_irqpin {
> PCF85263_IRQPIN_NONE,
> PCF85263_IRQPIN_INTA,
> @@ -101,6 +105,8 @@ struct pcf85263 {
> struct regmap *regmap;
> enum pcf85263_irqpin irq_pin;
> int irq;
> + u8 century; /* 1 = 1900 2 = 2000, ... */
> + bool century_half; /* false = 0-49, true=50-99 */
> bool mode_12h;
> };
>
> @@ -136,6 +142,85 @@ static int pcf85263_bin24h_to_bcd12h(int hr24)
> return bin2bcd(hr12) | pm ? 0 : PCF85263_HR_PM;
> }
>
> +static inline bool pcf85263_century_half(int year)
> +{
> + return (year % 100) >= 50;
> +}
> +
> +/*
> + * Since the hardware only has a year range of 00 to 99 we use
> + * the ram byte to store the century. 1=1900, 2=2000, 3=2100
> + * A value of zero is assumed to be 2000
> + *
> + * Set the ram byte when we set the clock which lets us use any
> + * century supported by linux (tm_year=0 => 1900)
> + *
> + * Unfortunately the hardware has no wrap around flag so fix it
> + * by also storing a flag indicating if the year is in the
> + * upper or lower half of the century.
> + */
> +static int pcf85263_update_ram_byte(struct pcf85263 *pcf85263)
> +{
> + u8 val = pcf85263->century & PCF85263_STATE_CENTURY_MASK;
> +
> + if (pcf85263->century_half)
> + val |= PCF85263_STATE_UPPER_HALF_CENTURY;
> +
> + return regmap_write(pcf85263->regmap, PCF85263_REG_RAM_BYTE, val);
> +}
> +
> +static int pcf85263_read_ram_byte(struct pcf85263 *pcf85263)
> +{
> + unsigned int regval;
> + int ret;
> +
> + ret = regmap_read(pcf85263->regmap, PCF85263_REG_RAM_BYTE, ®val);
> + if (ret)
> + return ret;
> +
> + pcf85263->century = regval & PCF85263_STATE_CENTURY_MASK;
> + pcf85263->century_half = !!(regval & PCF85263_STATE_UPPER_HALF_CENTURY);
> +
> + if (!pcf85263->century) { /* Not valid => not initialised yet */
> + int year;
> +
> + ret = regmap_read(pcf85263->regmap,
> + PCF85263_REG_RTC_YR, ®val);
> + if (ret)
> + return ret;
> +
> + pcf85263->century = 2;
> + year = bcd2bin(regval) + 1900 + (pcf85263->century - 1) * 100;
> + pcf85263->century_half = pcf85263_century_half(year);
> +
> + dev_warn(pcf85263->dev, "No century in NVRAM - assume %d\n",
> + year);
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * Detect year overflow by comparing the half (upper, lower) of
> + * the current year with the half the last time we read it
> + */
> +static int pcf85263_update_century(struct pcf85263 *pcf85263, int year)
> +{
> + bool cur_century_half;
> +
> + cur_century_half = pcf85263_century_half(year);
> +
> + if (cur_century_half == pcf85263->century_half)
> + return 0;
> +
> + if (!cur_century_half) /* Year has wrapped around */
> + pcf85263->century++;
> +
> + pcf85263->century_half = cur_century_half;
> +
> + return pcf85263_update_ram_byte(pcf85263);
> +}
> +
> static int pcf85263_read_time(struct device *dev, struct rtc_time *tm)
> {
> struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
> @@ -169,7 +254,11 @@ static int pcf85263_read_time(struct device *dev, struct rtc_time *tm)
> tm->tm_mon = bcd2bin(regs[PCF85263_REG_RTC_MO - first]) - 1;
> tm->tm_year = bcd2bin(regs[PCF85263_REG_RTC_YR - first]);
>
> - tm->tm_year += 100; /* Assume 21st century */
> + ret = pcf85263_update_century(pcf85263, tm->tm_year);
> + if (ret)
> + return ret;
> +
> + tm->tm_year += (pcf85263->century - 1) * 100;
>
> return 0;
> }
> @@ -214,7 +303,14 @@ static int pcf85263_set_time(struct device *dev, struct rtc_time *tm)
> }
>
> /* Start it again */
> - return regmap_write(pcf85263->regmap, PCF85263_REG_STOPENABLE, 0);
> + ret = regmap_write(pcf85263->regmap, PCF85263_REG_STOPENABLE, 0);
> + if (ret)
> + return ret;
> +
> + pcf85263->century = (tm->tm_year / 100) + 1;
> + pcf85263->century_half = pcf85263_century_half(tm->tm_year);
> +
> + return pcf85263_update_ram_byte(pcf85263);
> }
>
> static int pcf85263_enable_alarm(struct pcf85263 *pcf85263, bool enable)
> @@ -422,6 +518,11 @@ static int pcf85263_init_hw(struct pcf85263 *pcf85263)
> return ret;
> }
>
> + /* Get our persistent state from the ram byte */
> + ret = pcf85263_read_ram_byte(pcf85263);
> + if (ret < 0)
> + return ret;
> +
> /* Determine 12/24H mode */
> ret = regmap_read(pcf85263->regmap, PCF85263_REG_OSC, ®val);
> if (ret)
>
--
Alexandre Belloni, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
--
You received this message because you are subscribed to "rtc-linux".
Membership options at http://groups.google.com/group/rtc-linux .
Please read http://groups.google.com/group/rtc-linux/web/checklist
before submitting a driver.
---
You received this message because you are subscribed to the Google Groups "rtc-linux" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rtc-linux+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 3/3] rtc: pcf85263: Support multiple centuries
@ 2016-09-21 21:33 ` Alexandre Belloni
0 siblings, 0 replies; 15+ messages in thread
From: Alexandre Belloni @ 2016-09-21 21:33 UTC (permalink / raw)
To: Martin Fuzzey
Cc: Alessandro Zummo, Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA,
rtc-linux-/JYPxA39Uh5TLH3MbocFFw
Hi,
On 01/08/2016 at 17:50:36 +0200, Martin Fuzzey wrote :
> The hardware has neither a century register nor a century warp around bit.
>
> However it does have a single byte of non volatile RAM.
>
> Use this to provide full century and wrap around support by storing:
> * The current century
> * The current half century (lower=00-49, upper=50-99)
>
> If the byte is not set the 21st century is assumed.
>
While the idea is certainly interesting and properly implemented, this
is not working as expected because the RTC thinks that year 00 is a leap
year and both 1900 and 2100 are not leap years.
While it is possible to wark around that, I don't think there is any
added benefit as this RTC will work properly as is until 2099 (and by
then it will probably have been replaced).
> Signed-off-by: Martin Fuzzey <mfuzzey-mB3Nsq4MPf1BDgjK7y7TUQ@public.gmane.org>
> ---
> drivers/rtc/rtc-pcf85263.c | 105 +++++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 103 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/rtc/rtc-pcf85263.c b/drivers/rtc/rtc-pcf85263.c
> index 997742d..7b55d92 100644
> --- a/drivers/rtc/rtc-pcf85263.c
> +++ b/drivers/rtc/rtc-pcf85263.c
> @@ -83,6 +83,10 @@
>
> #define PCF85263_HR_PM BIT(5)
>
> +/* Our data stored in the RAM byte */
> +#define PCF85263_STATE_CENTURY_MASK 0x7f
> +#define PCF85263_STATE_UPPER_HALF_CENTURY BIT(7)
> +
> enum pcf85263_irqpin {
> PCF85263_IRQPIN_NONE,
> PCF85263_IRQPIN_INTA,
> @@ -101,6 +105,8 @@ struct pcf85263 {
> struct regmap *regmap;
> enum pcf85263_irqpin irq_pin;
> int irq;
> + u8 century; /* 1 = 1900 2 = 2000, ... */
> + bool century_half; /* false = 0-49, true=50-99 */
> bool mode_12h;
> };
>
> @@ -136,6 +142,85 @@ static int pcf85263_bin24h_to_bcd12h(int hr24)
> return bin2bcd(hr12) | pm ? 0 : PCF85263_HR_PM;
> }
>
> +static inline bool pcf85263_century_half(int year)
> +{
> + return (year % 100) >= 50;
> +}
> +
> +/*
> + * Since the hardware only has a year range of 00 to 99 we use
> + * the ram byte to store the century. 1=1900, 2=2000, 3=2100
> + * A value of zero is assumed to be 2000
> + *
> + * Set the ram byte when we set the clock which lets us use any
> + * century supported by linux (tm_year=0 => 1900)
> + *
> + * Unfortunately the hardware has no wrap around flag so fix it
> + * by also storing a flag indicating if the year is in the
> + * upper or lower half of the century.
> + */
> +static int pcf85263_update_ram_byte(struct pcf85263 *pcf85263)
> +{
> + u8 val = pcf85263->century & PCF85263_STATE_CENTURY_MASK;
> +
> + if (pcf85263->century_half)
> + val |= PCF85263_STATE_UPPER_HALF_CENTURY;
> +
> + return regmap_write(pcf85263->regmap, PCF85263_REG_RAM_BYTE, val);
> +}
> +
> +static int pcf85263_read_ram_byte(struct pcf85263 *pcf85263)
> +{
> + unsigned int regval;
> + int ret;
> +
> + ret = regmap_read(pcf85263->regmap, PCF85263_REG_RAM_BYTE, ®val);
> + if (ret)
> + return ret;
> +
> + pcf85263->century = regval & PCF85263_STATE_CENTURY_MASK;
> + pcf85263->century_half = !!(regval & PCF85263_STATE_UPPER_HALF_CENTURY);
> +
> + if (!pcf85263->century) { /* Not valid => not initialised yet */
> + int year;
> +
> + ret = regmap_read(pcf85263->regmap,
> + PCF85263_REG_RTC_YR, ®val);
> + if (ret)
> + return ret;
> +
> + pcf85263->century = 2;
> + year = bcd2bin(regval) + 1900 + (pcf85263->century - 1) * 100;
> + pcf85263->century_half = pcf85263_century_half(year);
> +
> + dev_warn(pcf85263->dev, "No century in NVRAM - assume %d\n",
> + year);
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * Detect year overflow by comparing the half (upper, lower) of
> + * the current year with the half the last time we read it
> + */
> +static int pcf85263_update_century(struct pcf85263 *pcf85263, int year)
> +{
> + bool cur_century_half;
> +
> + cur_century_half = pcf85263_century_half(year);
> +
> + if (cur_century_half == pcf85263->century_half)
> + return 0;
> +
> + if (!cur_century_half) /* Year has wrapped around */
> + pcf85263->century++;
> +
> + pcf85263->century_half = cur_century_half;
> +
> + return pcf85263_update_ram_byte(pcf85263);
> +}
> +
> static int pcf85263_read_time(struct device *dev, struct rtc_time *tm)
> {
> struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
> @@ -169,7 +254,11 @@ static int pcf85263_read_time(struct device *dev, struct rtc_time *tm)
> tm->tm_mon = bcd2bin(regs[PCF85263_REG_RTC_MO - first]) - 1;
> tm->tm_year = bcd2bin(regs[PCF85263_REG_RTC_YR - first]);
>
> - tm->tm_year += 100; /* Assume 21st century */
> + ret = pcf85263_update_century(pcf85263, tm->tm_year);
> + if (ret)
> + return ret;
> +
> + tm->tm_year += (pcf85263->century - 1) * 100;
>
> return 0;
> }
> @@ -214,7 +303,14 @@ static int pcf85263_set_time(struct device *dev, struct rtc_time *tm)
> }
>
> /* Start it again */
> - return regmap_write(pcf85263->regmap, PCF85263_REG_STOPENABLE, 0);
> + ret = regmap_write(pcf85263->regmap, PCF85263_REG_STOPENABLE, 0);
> + if (ret)
> + return ret;
> +
> + pcf85263->century = (tm->tm_year / 100) + 1;
> + pcf85263->century_half = pcf85263_century_half(tm->tm_year);
> +
> + return pcf85263_update_ram_byte(pcf85263);
> }
>
> static int pcf85263_enable_alarm(struct pcf85263 *pcf85263, bool enable)
> @@ -422,6 +518,11 @@ static int pcf85263_init_hw(struct pcf85263 *pcf85263)
> return ret;
> }
>
> + /* Get our persistent state from the ram byte */
> + ret = pcf85263_read_ram_byte(pcf85263);
> + if (ret < 0)
> + return ret;
> +
> /* Determine 12/24H mode */
> ret = regmap_read(pcf85263->regmap, PCF85263_REG_OSC, ®val);
> if (ret)
>
--
Alexandre Belloni, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 15+ messages in thread
* [rtc-linux] Re: [PATCH 2/3] rtc: pcf85263: Basic driver
2016-08-01 15:50 ` Martin Fuzzey
@ 2016-09-21 21:35 ` Alexandre Belloni
-1 siblings, 0 replies; 15+ messages in thread
From: Alexandre Belloni @ 2016-09-21 21:35 UTC (permalink / raw)
To: Martin Fuzzey; +Cc: Alessandro Zummo, Rob Herring, devicetree, rtc-linux
Hi,
Sorry for the very late review.
The driver seems good to me but I'd like a review from the DT
maintainers before taking it.
On 01/08/2016 at 17:50:34 +0200, Martin Fuzzey wrote :
> Add basic support for NXP PCF85263 I2C RTC chip.
>
> Only RTC and Alarm functions are supported.
>
> Signed-off-by: Martin Fuzzey <mfuzzey@parkeon.com>
> ---
> drivers/rtc/Kconfig | 8 +
> drivers/rtc/Makefile | 1
> drivers/rtc/rtc-pcf85263.c | 655 ++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 664 insertions(+)
> create mode 100644 drivers/rtc/rtc-pcf85263.c
>
> diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
> index 18639e0..63ec0fc 100644
> --- a/drivers/rtc/Kconfig
> +++ b/drivers/rtc/Kconfig
> @@ -396,6 +396,14 @@ config RTC_DRV_PCF85063
> This driver can also be built as a module. If so, the module
> will be called rtc-pcf85063.
>
> +config RTC_DRV_PCF85263
> + tristate "NXP PCF85263"
> + help
> + If you say yes here you get support for the PCF85263 RTC chip
> +
> + This driver can also be built as a module. If so, the module
> + will be called rtc-pcf85263.
> +
> config RTC_DRV_PCF8563
> tristate "Philips PCF8563/Epson RTC8564"
> help
> diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
> index ea28337..04f48fd 100644
> --- a/drivers/rtc/Makefile
> +++ b/drivers/rtc/Makefile
> @@ -108,6 +108,7 @@ obj-$(CONFIG_RTC_DRV_PCF2123) += rtc-pcf2123.o
> obj-$(CONFIG_RTC_DRV_PCF2127) += rtc-pcf2127.o
> obj-$(CONFIG_RTC_DRV_PCF50633) += rtc-pcf50633.o
> obj-$(CONFIG_RTC_DRV_PCF85063) += rtc-pcf85063.o
> +obj-$(CONFIG_RTC_DRV_PCF85263) += rtc-pcf85263.o
> obj-$(CONFIG_RTC_DRV_PCF8523) += rtc-pcf8523.o
> obj-$(CONFIG_RTC_DRV_PCF8563) += rtc-pcf8563.o
> obj-$(CONFIG_RTC_DRV_PCF8583) += rtc-pcf8583.o
> diff --git a/drivers/rtc/rtc-pcf85263.c b/drivers/rtc/rtc-pcf85263.c
> new file mode 100644
> index 0000000..997742d
> --- /dev/null
> +++ b/drivers/rtc/rtc-pcf85263.c
> @@ -0,0 +1,655 @@
> +/*
> + * rtc-pcf85263 Driver for the NXP PCF85263 RTC
> + *
> + * Copyright 2016 Parkeon
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/rtc.h>
> +#include <linux/i2c.h>
> +#include <linux/bcd.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/regmap.h>
> +
> +#include <dt-bindings/rtc/nxp,pcf85263.h>
> +
> +#define DRV_NAME "rtc-pcf85263"
> +
> +#define PCF85263_REG_RTC_SC 0x01 /* Seconds */
> +#define PCF85263_REG_RTC_SC_OS BIT(7) /* Oscilator stopped flag */
> +
> +#define PCF85263_REG_RTC_MN 0x02 /* Minutes */
> +#define PCF85263_REG_RTC_HR 0x03 /* Hours */
> +#define PCF85263_REG_RTC_DT 0x04 /* Day of month 1-31 */
> +#define PCF85263_REG_RTC_DW 0x05 /* Day of week 0-6 */
> +#define PCF85263_REG_RTC_MO 0x06 /* Month 1-12 */
> +#define PCF85263_REG_RTC_YR 0x07 /* Year 0-99 */
> +
> +#define PCF85263_REG_ALM1_SC 0x08 /* Seconds */
> +#define PCF85263_REG_ALM1_MN 0x09 /* Minutes */
> +#define PCF85263_REG_ALM1_HR 0x0a /* Hours */
> +#define PCF85263_REG_ALM1_DT 0x0b /* Day of month 1-31 */
> +#define PCF85263_REG_ALM1_MO 0x0c /* Month 1-12 */
> +
> +#define PCF85263_REG_ALM_CTL 0x10
> +#define PCF85263_REG_ALM_CTL_ALL_A1E 0x1f /* sec,min,hr,day,mon alarm 1 */
> +
> +#define PCF85263_REG_OSC 0x25
> +#define PCF85263_REG_OSC_CL_MASK (BIT(0) | BIT(1))
> +#define PCF85263_REG_OSC_CL_SHIFT 0
> +#define PCF85263_REG_OSC_OSCD_MASK (BIT(2) | BIT(3))
> +#define PCF85263_REG_OSC_OSCD_SHIFT 2
> +#define PCF85263_REG_OSC_LOWJ BIT(4)
> +#define PCF85263_REG_OSC_12H BIT(5)
> +
> +#define PCF85263_REG_PINIO 0x27
> +#define PCF85263_REG_PINIO_INTAPM_MASK (BIT(0) | BIT(1))
> +#define PCF85263_REG_PINIO_INTAPM_SHIFT 0
> +#define PCF85263_INTAPM_INTA (0x2 << PCF85263_REG_PINIO_INTAPM_SHIFT)
> +#define PCF85263_INTAPM_HIGHZ (0x3 << PCF85263_REG_PINIO_INTAPM_SHIFT)
> +#define PCF85263_REG_PINIO_TSPM_MASK (BIT(2) | BIT(3))
> +#define PCF85263_REG_PINIO_TSPM_SHIFT 2
> +#define PCF85263_TSPM_DISABLED (0x0 << PCF85263_REG_PINIO_TSPM_SHIFT)
> +#define PCF85263_TSPM_INTB (0x1 << PCF85263_REG_PINIO_TSPM_SHIFT)
> +#define PCF85263_REG_PINIO_CLKDISABLE BIT(7)
> +
> +#define PCF85263_REG_FUNCTION 0x28
> +#define PCF85263_REG_FUNCTION_COF_MASK 0x7
> +#define PCF85263_REG_FUNCTION_COF_OFF 0x7 /* No clock output */
> +
> +#define PCF85263_REG_INTA_CTL 0x29
> +#define PCF85263_REG_INTB_CTL 0x2A
> +#define PCF85263_REG_INTx_CTL_A1E BIT(4) /* Alarm 1 */
> +#define PCF85263_REG_INTx_CTL_ILP BIT(7) /* 0=pulse, 1=level */
> +
> +#define PCF85263_REG_FLAGS 0x2B
> +#define PCF85263_REG_FLAGS_A1F BIT(5)
> +
> +#define PCF85263_REG_RAM_BYTE 0x2c
> +
> +#define PCF85263_REG_STOPENABLE 0x2e
> +#define PCF85263_REG_STOPENABLE_STOP BIT(0)
> +
> +#define PCF85263_REG_RESET 0x2f /* Reset command */
> +#define PCF85263_REG_RESET_CMD_CPR 0xa4 /* Clear prescaler */
> +
> +#define PCF85263_MAX_REG 0x2f
> +
> +#define PCF85263_HR_PM BIT(5)
> +
> +enum pcf85263_irqpin {
> + PCF85263_IRQPIN_NONE,
> + PCF85263_IRQPIN_INTA,
> + PCF85263_IRQPIN_INTB
> +};
> +
> +static const char *const pcf85263_irqpin_names[] = {
> + [PCF85263_IRQPIN_NONE] = "None",
> + [PCF85263_IRQPIN_INTA] = "INTA",
> + [PCF85263_IRQPIN_INTB] = "INTB"
> +};
> +
> +struct pcf85263 {
> + struct device *dev;
> + struct rtc_device *rtc;
> + struct regmap *regmap;
> + enum pcf85263_irqpin irq_pin;
> + int irq;
> + bool mode_12h;
> +};
> +
> +/*
> + * Helpers to convert 12h to 24h and vice versa.
> + * Values in register are stored in BCD with a PM flag in bit 5
> + *
> + * 23:00 <=> 11PM <=> 0x31
> + * 00:00 <=> 12AM <=> 0x12
> + * 01:00 <=> 1AM <=> 0x01
> + * 12:00 <=> 12PM <=> 0x32
> + * 13:00 <=> 1PM <=> 0x21
> + */
> +static int pcf85263_bcd12h_to_bin24h(int regval)
> +{
> + int hr = bcd2bin(regval & 0x1f);
> + bool pm = regval & PCF85263_HR_PM;
> +
> + if (hr == 12)
> + return pm ? 12 : 0;
> +
> + return pm ? hr + 12 : hr;
> +}
> +
> +static int pcf85263_bin24h_to_bcd12h(int hr24)
> +{
> + bool pm = hr24 >= 12;
> + int hr12 = hr24 % 12;
> +
> + if (!hr12)
> + hr12++;
> +
> + return bin2bcd(hr12) | pm ? 0 : PCF85263_HR_PM;
> +}
> +
> +static int pcf85263_read_time(struct device *dev, struct rtc_time *tm)
> +{
> + struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
> + const int first = PCF85263_REG_RTC_SC;
> + const int last = PCF85263_REG_RTC_YR;
> + const int len = last - first + 1;
> + u8 regs[len];
> + u8 hr_reg;
> + int ret;
> +
> + ret = regmap_bulk_read(pcf85263->regmap, first, regs, len);
> + if (ret)
> + return ret;
> +
> + if (regs[PCF85263_REG_RTC_SC - first] & PCF85263_REG_RTC_SC_OS) {
> + dev_warn(dev, "Oscillator stop detected, date/time is not reliable.\n");
> + return -EINVAL;
> + }
> +
> + tm->tm_sec = bcd2bin(regs[PCF85263_REG_RTC_SC - first] & 0x7f);
> + tm->tm_min = bcd2bin(regs[PCF85263_REG_RTC_MN - first] & 0x7f);
> +
> + hr_reg = regs[PCF85263_REG_RTC_HR - first];
> + if (pcf85263->mode_12h)
> + tm->tm_hour = pcf85263_bcd12h_to_bin24h(hr_reg);
> + else
> + tm->tm_hour = bcd2bin(hr_reg & 0x3f);
> +
> + tm->tm_mday = bcd2bin(regs[PCF85263_REG_RTC_DT - first]);
> + tm->tm_wday = bcd2bin(regs[PCF85263_REG_RTC_DW - first]);
> + tm->tm_mon = bcd2bin(regs[PCF85263_REG_RTC_MO - first]) - 1;
> + tm->tm_year = bcd2bin(regs[PCF85263_REG_RTC_YR - first]);
> +
> + tm->tm_year += 100; /* Assume 21st century */
> +
> + return 0;
> +}
> +
> +static int pcf85263_set_time(struct device *dev, struct rtc_time *tm)
> +{
> + struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
> +
> + /*
> + * Before setting time need to stop RTC and disable prescaler
> + * Do this all in a single I2C transaction exploiting wraparound
> + * as described in data sheet.
> + * This means that the array below must be in register order
> + */
> + u8 regs[] = {
> + PCF85263_REG_STOPENABLE_STOP, /* STOP */
> + PCF85263_REG_RESET_CMD_CPR, /* Disable prescaler */
> + /* Wrap around to register 0 (1/100s) */
> + 0, /* 1/100s always zero. */
> + bin2bcd(tm->tm_sec),
> + bin2bcd(tm->tm_min),
> + bin2bcd(tm->tm_hour), /* 24-hour */
> + bin2bcd(tm->tm_mday),
> + bin2bcd(tm->tm_wday + 1),
> + bin2bcd(tm->tm_mon + 1),
> + bin2bcd(tm->tm_year % 100)
> + };
> + int ret;
> +
> + ret = regmap_bulk_write(pcf85263->regmap, PCF85263_REG_STOPENABLE,
> + regs, sizeof(regs));
> + if (ret)
> + return ret;
> +
> + /* As we have set the time in 24H update the hardware for that */
> + if (pcf85263->mode_12h) {
> + pcf85263->mode_12h = false;
> + ret = regmap_update_bits(pcf85263->regmap, PCF85263_REG_OSC,
> + PCF85263_REG_OSC_12H, 0);
> + if (ret)
> + return ret;
> + }
> +
> + /* Start it again */
> + return regmap_write(pcf85263->regmap, PCF85263_REG_STOPENABLE, 0);
> +}
> +
> +static int pcf85263_enable_alarm(struct pcf85263 *pcf85263, bool enable)
> +{
> + int reg;
> + int ret;
> +
> + ret = regmap_update_bits(pcf85263->regmap, PCF85263_REG_ALM_CTL,
> + PCF85263_REG_ALM_CTL_ALL_A1E,
> + enable ? PCF85263_REG_ALM_CTL_ALL_A1E : 0);
> + if (ret)
> + return ret;
> +
> + switch (pcf85263->irq_pin) {
> + case PCF85263_IRQPIN_NONE:
> + return 0;
> +
> + case PCF85263_IRQPIN_INTA:
> + reg = PCF85263_REG_INTA_CTL;
> + break;
> +
> + case PCF85263_IRQPIN_INTB:
> + reg = PCF85263_REG_INTB_CTL;
> + break;
> +
> + default:
> + return -EINVAL;
> + }
> +
> + return regmap_update_bits(pcf85263->regmap, reg,
> + PCF85263_REG_INTx_CTL_A1E,
> + enable ? PCF85263_REG_INTx_CTL_A1E : 0);
> +}
> +
> +static int pcf85263_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
> +{
> + struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
> + struct rtc_time *tm = &alarm->time;
> + const int first = PCF85263_REG_ALM1_SC;
> + const int last = PCF85263_REG_ALM1_MO;
> + const int len = last - first + 1;
> + u8 regs[len];
> + u8 hr_reg;
> + unsigned int regval;
> + int ret;
> +
> + ret = regmap_bulk_read(pcf85263->regmap, first, regs, len);
> + if (ret)
> + return ret;
> +
> + tm->tm_sec = bcd2bin(regs[PCF85263_REG_ALM1_SC - first] & 0x7f);
> + tm->tm_min = bcd2bin(regs[PCF85263_REG_ALM1_MN - first] & 0x7f);
> +
> + hr_reg = regs[PCF85263_REG_ALM1_HR - first];
> + if (pcf85263->mode_12h)
> + tm->tm_hour = pcf85263_bcd12h_to_bin24h(hr_reg);
> + else
> + tm->tm_hour = bcd2bin(hr_reg & 0x3f);
> +
> + tm->tm_mday = bcd2bin(regs[PCF85263_REG_ALM1_DT - first]);
> + tm->tm_mon = bcd2bin(regs[PCF85263_REG_ALM1_MO - first]) - 1;
> + tm->tm_year = -1;
> + tm->tm_wday = -1;
> +
> + ret = regmap_read(pcf85263->regmap, PCF85263_REG_ALM_CTL, ®val);
> + if (ret)
> + return ret;
> + alarm->enabled = !!(regval & PCF85263_REG_ALM_CTL_ALL_A1E);
> +
> + ret = regmap_read(pcf85263->regmap, PCF85263_REG_FLAGS, ®val);
> + if (ret)
> + return ret;
> + alarm->pending = !!(regval & PCF85263_REG_FLAGS_A1F);
> +
> + return 0;
> +}
> +
> +static int pcf85263_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
> +{
> + struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
> + struct rtc_time *tm = &alarm->time;
> + const int first = PCF85263_REG_ALM1_SC;
> + const int last = PCF85263_REG_ALM1_MO;
> + const int len = last - first + 1;
> + u8 regs[len];
> + int ret;
> +
> + /* Disable alarm comparison during update */
> + ret = pcf85263_enable_alarm(pcf85263, false);
> + if (ret)
> + return ret;
> +
> + /* Clear any pending alarm (write 0=>clr, 1=>no change) */
> + ret = regmap_write(pcf85263->regmap, PCF85263_REG_FLAGS,
> + ~PCF85263_REG_FLAGS_A1F);
> + if (ret)
> + return ret;
> +
> + /* Set the alarm time registers */
> + regs[PCF85263_REG_ALM1_SC - first] = bin2bcd(tm->tm_sec);
> + regs[PCF85263_REG_ALM1_MN - first] = bin2bcd(tm->tm_min);
> + regs[PCF85263_REG_ALM1_HR - first] = pcf85263->mode_12h ?
> + pcf85263_bin24h_to_bcd12h(tm->tm_hour) :
> + bin2bcd(tm->tm_hour);
> + regs[PCF85263_REG_ALM1_DT - first] = bin2bcd(tm->tm_mday);
> + regs[PCF85263_REG_ALM1_MO - first] = bin2bcd(tm->tm_mon + 1);
> +
> + ret = regmap_bulk_write(pcf85263->regmap, first, regs, sizeof(regs));
> + if (ret)
> + return ret;
> +
> + if (alarm->enabled)
> + ret = pcf85263_enable_alarm(pcf85263, true);
> +
> + return ret;
> +}
> +
> +static int pcf85263_alarm_irq_enable(struct device *dev, unsigned int enable)
> +{
> + struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
> +
> + return pcf85263_enable_alarm(pcf85263, !!enable);
> +}
> +
> +static irqreturn_t pcf85263_irq(int irq, void *data)
> +{
> + struct pcf85263 *pcf85263 = data;
> + unsigned int regval;
> + int ret;
> +
> + ret = regmap_read(pcf85263->regmap, PCF85263_REG_FLAGS, ®val);
> + if (ret)
> + return IRQ_NONE;
> +
> + if (regval & PCF85263_REG_FLAGS_A1F) {
> + regmap_write(pcf85263->regmap, PCF85263_REG_FLAGS,
> + ~PCF85263_REG_FLAGS_A1F);
> +
> + rtc_update_irq(pcf85263->rtc, 1, RTC_IRQF | RTC_AF);
> +
> + return IRQ_HANDLED;
> + }
> +
> + return IRQ_NONE;
> +}
> +
> +static int pcf85263_check_osc_stopped(struct pcf85263 *pcf85263)
> +{
> + unsigned int regval;
> + int ret;
> +
> + ret = regmap_read(pcf85263->regmap, PCF85263_REG_RTC_SC, ®val);
> + if (ret)
> + return ret;
> +
> + ret = regval & PCF85263_REG_RTC_SC_OS ? 1 : 0;
> + if (ret)
> + dev_warn(pcf85263->dev, "Oscillator stop detected, date/time is not reliable.\n");
> +
> + return ret;
> +}
> +
> +#ifdef CONFIG_RTC_INTF_DEV
> +static int pcf85263_ioctl(struct device *dev,
> + unsigned int cmd, unsigned long arg)
> +{
> + struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
> + int ret;
> +
> + switch (cmd) {
> + case RTC_VL_READ:
> + ret = pcf85263_check_osc_stopped(pcf85263);
> + if (ret < 0)
> + return ret;
> +
> + if (copy_to_user((void __user *)arg, &ret, sizeof(int)))
> + return -EFAULT;
> + return 0;
> +
> + case RTC_VL_CLR:
> + return regmap_update_bits(pcf85263->regmap,
> + PCF85263_REG_RTC_SC,
> + PCF85263_REG_RTC_SC_OS, 0);
> + default:
> + return -ENOIOCTLCMD;
> + }
> +}
> +#else
> +#define pcf85263_ioctl NULL
> +#endif
> +
> +static int pcf85263_init_hw(struct pcf85263 *pcf85263)
> +{
> + struct device_node *np = pcf85263->dev->of_node;
> + unsigned int regval;
> + u32 propval;
> + int ret;
> +
> + /* Determine if oscilator has been stopped (probably low power) */
> + ret = pcf85263_check_osc_stopped(pcf85263);
> + if (ret < 0) {
> + /* Log here since this is the first hw access on probe */
> + dev_err(pcf85263->dev, "Unable to read register\n");
> +
> + return ret;
> + }
> +
> + /* Determine 12/24H mode */
> + ret = regmap_read(pcf85263->regmap, PCF85263_REG_OSC, ®val);
> + if (ret)
> + return ret;
> + pcf85263->mode_12h = !!(regval & PCF85263_REG_OSC_12H);
> +
> + /* Set oscilator register */
> + regval &= ~PCF85263_REG_OSC_12H; /* keep current 12/24 h setting */
> +
> + propval = PCF85263_QUARTZCAP_12p5pF;
> + of_property_read_u32(np, "quartz-load-capacitance", &propval);
> + regval |= ((propval << PCF85263_REG_OSC_CL_SHIFT)
> + & PCF85263_REG_OSC_CL_MASK);
> +
> + propval = PCF85263_QUARTZDRIVE_NORMAL;
> + of_property_read_u32(np, "quartz-drive-strength", &propval);
> + regval |= ((propval << PCF85263_REG_OSC_OSCD_SHIFT)
> + & PCF85263_REG_OSC_OSCD_MASK);
> +
> + if (of_property_read_bool(np, "quartz-low-jitter"))
> + regval |= PCF85263_REG_OSC_LOWJ;
> +
> + ret = regmap_write(pcf85263->regmap, PCF85263_REG_OSC, regval);
> + if (ret)
> + return ret;
> +
> + /* Set function register (RTC mode, 1s tick, clock output static) */
> + ret = regmap_write(pcf85263->regmap, PCF85263_REG_FUNCTION,
> + PCF85263_REG_FUNCTION_COF_OFF);
> + if (ret)
> + return ret;
> +
> + /* Set all interrupts to disabled, level mode */
> + ret = regmap_write(pcf85263->regmap, PCF85263_REG_INTA_CTL,
> + PCF85263_REG_INTx_CTL_ILP);
> + if (ret)
> + return ret;
> + ret = regmap_write(pcf85263->regmap, PCF85263_REG_INTB_CTL,
> + PCF85263_REG_INTx_CTL_ILP);
> + if (ret)
> + return ret;
> +
> + /* Setup IO pin config register */
> + regval = PCF85263_REG_PINIO_CLKDISABLE;
> + switch (pcf85263->irq_pin) {
> + case PCF85263_IRQPIN_INTA:
> + regval |= (PCF85263_INTAPM_INTA | PCF85263_TSPM_DISABLED);
> + break;
> + case PCF85263_IRQPIN_INTB:
> + regval |= (PCF85263_INTAPM_HIGHZ | PCF85263_TSPM_INTB);
> + break;
> + case PCF85263_IRQPIN_NONE:
> + regval |= (PCF85263_INTAPM_HIGHZ | PCF85263_TSPM_DISABLED);
> + break;
> + }
> + ret = regmap_write(pcf85263->regmap, PCF85263_REG_PINIO, regval);
> +
> + return ret;
> +}
> +
> +static const struct rtc_class_ops rtc_ops = {
> + .ioctl = pcf85263_ioctl,
> + .read_time = pcf85263_read_time,
> + .set_time = pcf85263_set_time,
> + .read_alarm = pcf85263_read_alarm,
> + .set_alarm = pcf85263_set_alarm,
> + .alarm_irq_enable = pcf85263_alarm_irq_enable,
> +};
> +
> +static const struct regmap_config pcf85263_regmap_cfg = {
> + .reg_bits = 8,
> + .val_bits = 8,
> + .max_register = PCF85263_MAX_REG,
> +};
> +
> +/*
> + * On some boards the interrupt line may not be wired to the CPU but only to
> + * a power supply circuit.
> + * In that case no interrupt will be specified in the device tree but the
> + * wakeup-source DT property may be used to enable wakeup programming in
> + * sysfs
> + */
> +static bool pcf85263_can_wakeup_machine(struct pcf85263 *pcf85263)
> +{
> + return pcf85263->irq ||
> + of_property_read_bool(pcf85263->dev->of_node, "wakeup-source");
> +}
> +
> +static int pcf85263_probe(struct i2c_client *client,
> + const struct i2c_device_id *id)
> +{
> + struct device *dev = &client->dev;
> + struct pcf85263 *pcf85263;
> + int ret;
> +
> + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C |
> + I2C_FUNC_SMBUS_BYTE_DATA |
> + I2C_FUNC_SMBUS_I2C_BLOCK))
> + return -ENODEV;
> +
> + pcf85263 = devm_kzalloc(dev, sizeof(*pcf85263), GFP_KERNEL);
> + if (!pcf85263)
> + return -ENOMEM;
> +
> + pcf85263->dev = dev;
> + pcf85263->irq = client->irq;
> + dev_set_drvdata(dev, pcf85263);
> +
> + pcf85263->regmap = devm_regmap_init_i2c(client, &pcf85263_regmap_cfg);
> + if (IS_ERR(pcf85263->regmap)) {
> + ret = PTR_ERR(pcf85263->regmap);
> + dev_err(dev, "regmap allocation failed (%d)\n", ret);
> +
> + return ret;
> + }
> +
> + /* Determine which interrupt pin the board uses */
> + if (pcf85263_can_wakeup_machine(pcf85263)) {
> + if (of_property_match_string(dev->of_node,
> + "interrupt-names", "INTB") >= 0)
> + pcf85263->irq_pin = PCF85263_IRQPIN_INTB;
> + else
> + pcf85263->irq_pin = PCF85263_IRQPIN_INTA;
> + } else {
> + pcf85263->irq_pin = PCF85263_IRQPIN_NONE;
> + }
> +
> + ret = pcf85263_init_hw(pcf85263);
> + if (ret)
> + return ret;
> +
> + if (pcf85263->irq) {
> + ret = devm_request_threaded_irq(dev, pcf85263->irq, NULL,
> + pcf85263_irq,
> + IRQF_ONESHOT,
> + dev->driver->name, pcf85263);
> + if (ret) {
> + dev_err(dev, "irq %d unavailable (%d)\n",
> + pcf85263->irq, ret);
> + pcf85263->irq = 0;
> + }
> + }
> +
> + if (pcf85263_can_wakeup_machine(pcf85263))
> + device_init_wakeup(dev, true);
> +
> + pcf85263->rtc = devm_rtc_device_register(dev, dev->driver->name,
> + &rtc_ops, THIS_MODULE);
> + ret = PTR_ERR_OR_ZERO(pcf85263->rtc);
> + if (ret)
> + return ret;
> +
> + /* We cannot support UIE mode if we do not have an IRQ line */
> + if (!pcf85263->irq)
> + pcf85263->rtc->uie_unsupported = 1;
> +
> + dev_info(pcf85263->dev,
> + "PCF85263 RTC (irqpin=%s irq=%d)\n",
> + pcf85263_irqpin_names[pcf85263->irq_pin],
> + pcf85263->irq);
> +
> + return 0;
> +}
> +
> +static int pcf85263_remove(struct i2c_client *client)
> +{
> + struct pcf85263 *pcf85263 = i2c_get_clientdata(client);
> +
> + if (pcf85263_can_wakeup_machine(pcf85263))
> + device_init_wakeup(pcf85263->dev, false);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int pcf85263_suspend(struct device *dev)
> +{
> + struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
> + int ret = 0;
> +
> + if (device_may_wakeup(dev))
> + ret = enable_irq_wake(pcf85263->irq);
> +
> + return ret;
> +}
> +
> +static int pcf85263_resume(struct device *dev)
> +{
> + struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
> + int ret = 0;
> +
> + if (device_may_wakeup(dev))
> + ret = disable_irq_wake(pcf85263->irq);
> +
> + return ret;
> +}
> +
> +#endif
> +
> +static const struct i2c_device_id pcf85263_id[] = {
> + { "pcf85263", 0 },
> + { }
> +};
> +MODULE_DEVICE_TABLE(i2c, pcf85263_id);
> +
> +#ifdef CONFIG_OF
> +static const struct of_device_id pcf85263_of_match[] = {
> + { .compatible = "nxp,pcf85263" },
> + {}
> +};
> +MODULE_DEVICE_TABLE(of, pcf85263_of_match);
> +#endif
> +
> +static SIMPLE_DEV_PM_OPS(pcf85263_pm_ops, pcf85263_suspend, pcf85263_resume);
> +
> +static struct i2c_driver pcf85263_driver = {
> + .driver = {
> + .name = "rtc-pcf85263",
> + .of_match_table = of_match_ptr(pcf85263_of_match),
> + .pm = &pcf85263_pm_ops,
> + },
> + .probe = pcf85263_probe,
> + .remove = pcf85263_remove,
> + .id_table = pcf85263_id,
> +};
> +
> +module_i2c_driver(pcf85263_driver);
> +
> +MODULE_AUTHOR("Martin Fuzzey <mfuzzey@parkeon.com>");
> +MODULE_DESCRIPTION("PCF85263 RTC Driver");
> +MODULE_LICENSE("GPL");
>
--
Alexandre Belloni, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
--
You received this message because you are subscribed to "rtc-linux".
Membership options at http://groups.google.com/group/rtc-linux .
Please read http://groups.google.com/group/rtc-linux/web/checklist
before submitting a driver.
---
You received this message because you are subscribed to the Google Groups "rtc-linux" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rtc-linux+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/3] rtc: pcf85263: Basic driver
@ 2016-09-21 21:35 ` Alexandre Belloni
0 siblings, 0 replies; 15+ messages in thread
From: Alexandre Belloni @ 2016-09-21 21:35 UTC (permalink / raw)
To: Martin Fuzzey
Cc: Alessandro Zummo, Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA,
rtc-linux-/JYPxA39Uh5TLH3MbocFFw
Hi,
Sorry for the very late review.
The driver seems good to me but I'd like a review from the DT
maintainers before taking it.
On 01/08/2016 at 17:50:34 +0200, Martin Fuzzey wrote :
> Add basic support for NXP PCF85263 I2C RTC chip.
>
> Only RTC and Alarm functions are supported.
>
> Signed-off-by: Martin Fuzzey <mfuzzey-mB3Nsq4MPf1BDgjK7y7TUQ@public.gmane.org>
> ---
> drivers/rtc/Kconfig | 8 +
> drivers/rtc/Makefile | 1
> drivers/rtc/rtc-pcf85263.c | 655 ++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 664 insertions(+)
> create mode 100644 drivers/rtc/rtc-pcf85263.c
>
> diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
> index 18639e0..63ec0fc 100644
> --- a/drivers/rtc/Kconfig
> +++ b/drivers/rtc/Kconfig
> @@ -396,6 +396,14 @@ config RTC_DRV_PCF85063
> This driver can also be built as a module. If so, the module
> will be called rtc-pcf85063.
>
> +config RTC_DRV_PCF85263
> + tristate "NXP PCF85263"
> + help
> + If you say yes here you get support for the PCF85263 RTC chip
> +
> + This driver can also be built as a module. If so, the module
> + will be called rtc-pcf85263.
> +
> config RTC_DRV_PCF8563
> tristate "Philips PCF8563/Epson RTC8564"
> help
> diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
> index ea28337..04f48fd 100644
> --- a/drivers/rtc/Makefile
> +++ b/drivers/rtc/Makefile
> @@ -108,6 +108,7 @@ obj-$(CONFIG_RTC_DRV_PCF2123) += rtc-pcf2123.o
> obj-$(CONFIG_RTC_DRV_PCF2127) += rtc-pcf2127.o
> obj-$(CONFIG_RTC_DRV_PCF50633) += rtc-pcf50633.o
> obj-$(CONFIG_RTC_DRV_PCF85063) += rtc-pcf85063.o
> +obj-$(CONFIG_RTC_DRV_PCF85263) += rtc-pcf85263.o
> obj-$(CONFIG_RTC_DRV_PCF8523) += rtc-pcf8523.o
> obj-$(CONFIG_RTC_DRV_PCF8563) += rtc-pcf8563.o
> obj-$(CONFIG_RTC_DRV_PCF8583) += rtc-pcf8583.o
> diff --git a/drivers/rtc/rtc-pcf85263.c b/drivers/rtc/rtc-pcf85263.c
> new file mode 100644
> index 0000000..997742d
> --- /dev/null
> +++ b/drivers/rtc/rtc-pcf85263.c
> @@ -0,0 +1,655 @@
> +/*
> + * rtc-pcf85263 Driver for the NXP PCF85263 RTC
> + *
> + * Copyright 2016 Parkeon
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/rtc.h>
> +#include <linux/i2c.h>
> +#include <linux/bcd.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/regmap.h>
> +
> +#include <dt-bindings/rtc/nxp,pcf85263.h>
> +
> +#define DRV_NAME "rtc-pcf85263"
> +
> +#define PCF85263_REG_RTC_SC 0x01 /* Seconds */
> +#define PCF85263_REG_RTC_SC_OS BIT(7) /* Oscilator stopped flag */
> +
> +#define PCF85263_REG_RTC_MN 0x02 /* Minutes */
> +#define PCF85263_REG_RTC_HR 0x03 /* Hours */
> +#define PCF85263_REG_RTC_DT 0x04 /* Day of month 1-31 */
> +#define PCF85263_REG_RTC_DW 0x05 /* Day of week 0-6 */
> +#define PCF85263_REG_RTC_MO 0x06 /* Month 1-12 */
> +#define PCF85263_REG_RTC_YR 0x07 /* Year 0-99 */
> +
> +#define PCF85263_REG_ALM1_SC 0x08 /* Seconds */
> +#define PCF85263_REG_ALM1_MN 0x09 /* Minutes */
> +#define PCF85263_REG_ALM1_HR 0x0a /* Hours */
> +#define PCF85263_REG_ALM1_DT 0x0b /* Day of month 1-31 */
> +#define PCF85263_REG_ALM1_MO 0x0c /* Month 1-12 */
> +
> +#define PCF85263_REG_ALM_CTL 0x10
> +#define PCF85263_REG_ALM_CTL_ALL_A1E 0x1f /* sec,min,hr,day,mon alarm 1 */
> +
> +#define PCF85263_REG_OSC 0x25
> +#define PCF85263_REG_OSC_CL_MASK (BIT(0) | BIT(1))
> +#define PCF85263_REG_OSC_CL_SHIFT 0
> +#define PCF85263_REG_OSC_OSCD_MASK (BIT(2) | BIT(3))
> +#define PCF85263_REG_OSC_OSCD_SHIFT 2
> +#define PCF85263_REG_OSC_LOWJ BIT(4)
> +#define PCF85263_REG_OSC_12H BIT(5)
> +
> +#define PCF85263_REG_PINIO 0x27
> +#define PCF85263_REG_PINIO_INTAPM_MASK (BIT(0) | BIT(1))
> +#define PCF85263_REG_PINIO_INTAPM_SHIFT 0
> +#define PCF85263_INTAPM_INTA (0x2 << PCF85263_REG_PINIO_INTAPM_SHIFT)
> +#define PCF85263_INTAPM_HIGHZ (0x3 << PCF85263_REG_PINIO_INTAPM_SHIFT)
> +#define PCF85263_REG_PINIO_TSPM_MASK (BIT(2) | BIT(3))
> +#define PCF85263_REG_PINIO_TSPM_SHIFT 2
> +#define PCF85263_TSPM_DISABLED (0x0 << PCF85263_REG_PINIO_TSPM_SHIFT)
> +#define PCF85263_TSPM_INTB (0x1 << PCF85263_REG_PINIO_TSPM_SHIFT)
> +#define PCF85263_REG_PINIO_CLKDISABLE BIT(7)
> +
> +#define PCF85263_REG_FUNCTION 0x28
> +#define PCF85263_REG_FUNCTION_COF_MASK 0x7
> +#define PCF85263_REG_FUNCTION_COF_OFF 0x7 /* No clock output */
> +
> +#define PCF85263_REG_INTA_CTL 0x29
> +#define PCF85263_REG_INTB_CTL 0x2A
> +#define PCF85263_REG_INTx_CTL_A1E BIT(4) /* Alarm 1 */
> +#define PCF85263_REG_INTx_CTL_ILP BIT(7) /* 0=pulse, 1=level */
> +
> +#define PCF85263_REG_FLAGS 0x2B
> +#define PCF85263_REG_FLAGS_A1F BIT(5)
> +
> +#define PCF85263_REG_RAM_BYTE 0x2c
> +
> +#define PCF85263_REG_STOPENABLE 0x2e
> +#define PCF85263_REG_STOPENABLE_STOP BIT(0)
> +
> +#define PCF85263_REG_RESET 0x2f /* Reset command */
> +#define PCF85263_REG_RESET_CMD_CPR 0xa4 /* Clear prescaler */
> +
> +#define PCF85263_MAX_REG 0x2f
> +
> +#define PCF85263_HR_PM BIT(5)
> +
> +enum pcf85263_irqpin {
> + PCF85263_IRQPIN_NONE,
> + PCF85263_IRQPIN_INTA,
> + PCF85263_IRQPIN_INTB
> +};
> +
> +static const char *const pcf85263_irqpin_names[] = {
> + [PCF85263_IRQPIN_NONE] = "None",
> + [PCF85263_IRQPIN_INTA] = "INTA",
> + [PCF85263_IRQPIN_INTB] = "INTB"
> +};
> +
> +struct pcf85263 {
> + struct device *dev;
> + struct rtc_device *rtc;
> + struct regmap *regmap;
> + enum pcf85263_irqpin irq_pin;
> + int irq;
> + bool mode_12h;
> +};
> +
> +/*
> + * Helpers to convert 12h to 24h and vice versa.
> + * Values in register are stored in BCD with a PM flag in bit 5
> + *
> + * 23:00 <=> 11PM <=> 0x31
> + * 00:00 <=> 12AM <=> 0x12
> + * 01:00 <=> 1AM <=> 0x01
> + * 12:00 <=> 12PM <=> 0x32
> + * 13:00 <=> 1PM <=> 0x21
> + */
> +static int pcf85263_bcd12h_to_bin24h(int regval)
> +{
> + int hr = bcd2bin(regval & 0x1f);
> + bool pm = regval & PCF85263_HR_PM;
> +
> + if (hr == 12)
> + return pm ? 12 : 0;
> +
> + return pm ? hr + 12 : hr;
> +}
> +
> +static int pcf85263_bin24h_to_bcd12h(int hr24)
> +{
> + bool pm = hr24 >= 12;
> + int hr12 = hr24 % 12;
> +
> + if (!hr12)
> + hr12++;
> +
> + return bin2bcd(hr12) | pm ? 0 : PCF85263_HR_PM;
> +}
> +
> +static int pcf85263_read_time(struct device *dev, struct rtc_time *tm)
> +{
> + struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
> + const int first = PCF85263_REG_RTC_SC;
> + const int last = PCF85263_REG_RTC_YR;
> + const int len = last - first + 1;
> + u8 regs[len];
> + u8 hr_reg;
> + int ret;
> +
> + ret = regmap_bulk_read(pcf85263->regmap, first, regs, len);
> + if (ret)
> + return ret;
> +
> + if (regs[PCF85263_REG_RTC_SC - first] & PCF85263_REG_RTC_SC_OS) {
> + dev_warn(dev, "Oscillator stop detected, date/time is not reliable.\n");
> + return -EINVAL;
> + }
> +
> + tm->tm_sec = bcd2bin(regs[PCF85263_REG_RTC_SC - first] & 0x7f);
> + tm->tm_min = bcd2bin(regs[PCF85263_REG_RTC_MN - first] & 0x7f);
> +
> + hr_reg = regs[PCF85263_REG_RTC_HR - first];
> + if (pcf85263->mode_12h)
> + tm->tm_hour = pcf85263_bcd12h_to_bin24h(hr_reg);
> + else
> + tm->tm_hour = bcd2bin(hr_reg & 0x3f);
> +
> + tm->tm_mday = bcd2bin(regs[PCF85263_REG_RTC_DT - first]);
> + tm->tm_wday = bcd2bin(regs[PCF85263_REG_RTC_DW - first]);
> + tm->tm_mon = bcd2bin(regs[PCF85263_REG_RTC_MO - first]) - 1;
> + tm->tm_year = bcd2bin(regs[PCF85263_REG_RTC_YR - first]);
> +
> + tm->tm_year += 100; /* Assume 21st century */
> +
> + return 0;
> +}
> +
> +static int pcf85263_set_time(struct device *dev, struct rtc_time *tm)
> +{
> + struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
> +
> + /*
> + * Before setting time need to stop RTC and disable prescaler
> + * Do this all in a single I2C transaction exploiting wraparound
> + * as described in data sheet.
> + * This means that the array below must be in register order
> + */
> + u8 regs[] = {
> + PCF85263_REG_STOPENABLE_STOP, /* STOP */
> + PCF85263_REG_RESET_CMD_CPR, /* Disable prescaler */
> + /* Wrap around to register 0 (1/100s) */
> + 0, /* 1/100s always zero. */
> + bin2bcd(tm->tm_sec),
> + bin2bcd(tm->tm_min),
> + bin2bcd(tm->tm_hour), /* 24-hour */
> + bin2bcd(tm->tm_mday),
> + bin2bcd(tm->tm_wday + 1),
> + bin2bcd(tm->tm_mon + 1),
> + bin2bcd(tm->tm_year % 100)
> + };
> + int ret;
> +
> + ret = regmap_bulk_write(pcf85263->regmap, PCF85263_REG_STOPENABLE,
> + regs, sizeof(regs));
> + if (ret)
> + return ret;
> +
> + /* As we have set the time in 24H update the hardware for that */
> + if (pcf85263->mode_12h) {
> + pcf85263->mode_12h = false;
> + ret = regmap_update_bits(pcf85263->regmap, PCF85263_REG_OSC,
> + PCF85263_REG_OSC_12H, 0);
> + if (ret)
> + return ret;
> + }
> +
> + /* Start it again */
> + return regmap_write(pcf85263->regmap, PCF85263_REG_STOPENABLE, 0);
> +}
> +
> +static int pcf85263_enable_alarm(struct pcf85263 *pcf85263, bool enable)
> +{
> + int reg;
> + int ret;
> +
> + ret = regmap_update_bits(pcf85263->regmap, PCF85263_REG_ALM_CTL,
> + PCF85263_REG_ALM_CTL_ALL_A1E,
> + enable ? PCF85263_REG_ALM_CTL_ALL_A1E : 0);
> + if (ret)
> + return ret;
> +
> + switch (pcf85263->irq_pin) {
> + case PCF85263_IRQPIN_NONE:
> + return 0;
> +
> + case PCF85263_IRQPIN_INTA:
> + reg = PCF85263_REG_INTA_CTL;
> + break;
> +
> + case PCF85263_IRQPIN_INTB:
> + reg = PCF85263_REG_INTB_CTL;
> + break;
> +
> + default:
> + return -EINVAL;
> + }
> +
> + return regmap_update_bits(pcf85263->regmap, reg,
> + PCF85263_REG_INTx_CTL_A1E,
> + enable ? PCF85263_REG_INTx_CTL_A1E : 0);
> +}
> +
> +static int pcf85263_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
> +{
> + struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
> + struct rtc_time *tm = &alarm->time;
> + const int first = PCF85263_REG_ALM1_SC;
> + const int last = PCF85263_REG_ALM1_MO;
> + const int len = last - first + 1;
> + u8 regs[len];
> + u8 hr_reg;
> + unsigned int regval;
> + int ret;
> +
> + ret = regmap_bulk_read(pcf85263->regmap, first, regs, len);
> + if (ret)
> + return ret;
> +
> + tm->tm_sec = bcd2bin(regs[PCF85263_REG_ALM1_SC - first] & 0x7f);
> + tm->tm_min = bcd2bin(regs[PCF85263_REG_ALM1_MN - first] & 0x7f);
> +
> + hr_reg = regs[PCF85263_REG_ALM1_HR - first];
> + if (pcf85263->mode_12h)
> + tm->tm_hour = pcf85263_bcd12h_to_bin24h(hr_reg);
> + else
> + tm->tm_hour = bcd2bin(hr_reg & 0x3f);
> +
> + tm->tm_mday = bcd2bin(regs[PCF85263_REG_ALM1_DT - first]);
> + tm->tm_mon = bcd2bin(regs[PCF85263_REG_ALM1_MO - first]) - 1;
> + tm->tm_year = -1;
> + tm->tm_wday = -1;
> +
> + ret = regmap_read(pcf85263->regmap, PCF85263_REG_ALM_CTL, ®val);
> + if (ret)
> + return ret;
> + alarm->enabled = !!(regval & PCF85263_REG_ALM_CTL_ALL_A1E);
> +
> + ret = regmap_read(pcf85263->regmap, PCF85263_REG_FLAGS, ®val);
> + if (ret)
> + return ret;
> + alarm->pending = !!(regval & PCF85263_REG_FLAGS_A1F);
> +
> + return 0;
> +}
> +
> +static int pcf85263_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
> +{
> + struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
> + struct rtc_time *tm = &alarm->time;
> + const int first = PCF85263_REG_ALM1_SC;
> + const int last = PCF85263_REG_ALM1_MO;
> + const int len = last - first + 1;
> + u8 regs[len];
> + int ret;
> +
> + /* Disable alarm comparison during update */
> + ret = pcf85263_enable_alarm(pcf85263, false);
> + if (ret)
> + return ret;
> +
> + /* Clear any pending alarm (write 0=>clr, 1=>no change) */
> + ret = regmap_write(pcf85263->regmap, PCF85263_REG_FLAGS,
> + ~PCF85263_REG_FLAGS_A1F);
> + if (ret)
> + return ret;
> +
> + /* Set the alarm time registers */
> + regs[PCF85263_REG_ALM1_SC - first] = bin2bcd(tm->tm_sec);
> + regs[PCF85263_REG_ALM1_MN - first] = bin2bcd(tm->tm_min);
> + regs[PCF85263_REG_ALM1_HR - first] = pcf85263->mode_12h ?
> + pcf85263_bin24h_to_bcd12h(tm->tm_hour) :
> + bin2bcd(tm->tm_hour);
> + regs[PCF85263_REG_ALM1_DT - first] = bin2bcd(tm->tm_mday);
> + regs[PCF85263_REG_ALM1_MO - first] = bin2bcd(tm->tm_mon + 1);
> +
> + ret = regmap_bulk_write(pcf85263->regmap, first, regs, sizeof(regs));
> + if (ret)
> + return ret;
> +
> + if (alarm->enabled)
> + ret = pcf85263_enable_alarm(pcf85263, true);
> +
> + return ret;
> +}
> +
> +static int pcf85263_alarm_irq_enable(struct device *dev, unsigned int enable)
> +{
> + struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
> +
> + return pcf85263_enable_alarm(pcf85263, !!enable);
> +}
> +
> +static irqreturn_t pcf85263_irq(int irq, void *data)
> +{
> + struct pcf85263 *pcf85263 = data;
> + unsigned int regval;
> + int ret;
> +
> + ret = regmap_read(pcf85263->regmap, PCF85263_REG_FLAGS, ®val);
> + if (ret)
> + return IRQ_NONE;
> +
> + if (regval & PCF85263_REG_FLAGS_A1F) {
> + regmap_write(pcf85263->regmap, PCF85263_REG_FLAGS,
> + ~PCF85263_REG_FLAGS_A1F);
> +
> + rtc_update_irq(pcf85263->rtc, 1, RTC_IRQF | RTC_AF);
> +
> + return IRQ_HANDLED;
> + }
> +
> + return IRQ_NONE;
> +}
> +
> +static int pcf85263_check_osc_stopped(struct pcf85263 *pcf85263)
> +{
> + unsigned int regval;
> + int ret;
> +
> + ret = regmap_read(pcf85263->regmap, PCF85263_REG_RTC_SC, ®val);
> + if (ret)
> + return ret;
> +
> + ret = regval & PCF85263_REG_RTC_SC_OS ? 1 : 0;
> + if (ret)
> + dev_warn(pcf85263->dev, "Oscillator stop detected, date/time is not reliable.\n");
> +
> + return ret;
> +}
> +
> +#ifdef CONFIG_RTC_INTF_DEV
> +static int pcf85263_ioctl(struct device *dev,
> + unsigned int cmd, unsigned long arg)
> +{
> + struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
> + int ret;
> +
> + switch (cmd) {
> + case RTC_VL_READ:
> + ret = pcf85263_check_osc_stopped(pcf85263);
> + if (ret < 0)
> + return ret;
> +
> + if (copy_to_user((void __user *)arg, &ret, sizeof(int)))
> + return -EFAULT;
> + return 0;
> +
> + case RTC_VL_CLR:
> + return regmap_update_bits(pcf85263->regmap,
> + PCF85263_REG_RTC_SC,
> + PCF85263_REG_RTC_SC_OS, 0);
> + default:
> + return -ENOIOCTLCMD;
> + }
> +}
> +#else
> +#define pcf85263_ioctl NULL
> +#endif
> +
> +static int pcf85263_init_hw(struct pcf85263 *pcf85263)
> +{
> + struct device_node *np = pcf85263->dev->of_node;
> + unsigned int regval;
> + u32 propval;
> + int ret;
> +
> + /* Determine if oscilator has been stopped (probably low power) */
> + ret = pcf85263_check_osc_stopped(pcf85263);
> + if (ret < 0) {
> + /* Log here since this is the first hw access on probe */
> + dev_err(pcf85263->dev, "Unable to read register\n");
> +
> + return ret;
> + }
> +
> + /* Determine 12/24H mode */
> + ret = regmap_read(pcf85263->regmap, PCF85263_REG_OSC, ®val);
> + if (ret)
> + return ret;
> + pcf85263->mode_12h = !!(regval & PCF85263_REG_OSC_12H);
> +
> + /* Set oscilator register */
> + regval &= ~PCF85263_REG_OSC_12H; /* keep current 12/24 h setting */
> +
> + propval = PCF85263_QUARTZCAP_12p5pF;
> + of_property_read_u32(np, "quartz-load-capacitance", &propval);
> + regval |= ((propval << PCF85263_REG_OSC_CL_SHIFT)
> + & PCF85263_REG_OSC_CL_MASK);
> +
> + propval = PCF85263_QUARTZDRIVE_NORMAL;
> + of_property_read_u32(np, "quartz-drive-strength", &propval);
> + regval |= ((propval << PCF85263_REG_OSC_OSCD_SHIFT)
> + & PCF85263_REG_OSC_OSCD_MASK);
> +
> + if (of_property_read_bool(np, "quartz-low-jitter"))
> + regval |= PCF85263_REG_OSC_LOWJ;
> +
> + ret = regmap_write(pcf85263->regmap, PCF85263_REG_OSC, regval);
> + if (ret)
> + return ret;
> +
> + /* Set function register (RTC mode, 1s tick, clock output static) */
> + ret = regmap_write(pcf85263->regmap, PCF85263_REG_FUNCTION,
> + PCF85263_REG_FUNCTION_COF_OFF);
> + if (ret)
> + return ret;
> +
> + /* Set all interrupts to disabled, level mode */
> + ret = regmap_write(pcf85263->regmap, PCF85263_REG_INTA_CTL,
> + PCF85263_REG_INTx_CTL_ILP);
> + if (ret)
> + return ret;
> + ret = regmap_write(pcf85263->regmap, PCF85263_REG_INTB_CTL,
> + PCF85263_REG_INTx_CTL_ILP);
> + if (ret)
> + return ret;
> +
> + /* Setup IO pin config register */
> + regval = PCF85263_REG_PINIO_CLKDISABLE;
> + switch (pcf85263->irq_pin) {
> + case PCF85263_IRQPIN_INTA:
> + regval |= (PCF85263_INTAPM_INTA | PCF85263_TSPM_DISABLED);
> + break;
> + case PCF85263_IRQPIN_INTB:
> + regval |= (PCF85263_INTAPM_HIGHZ | PCF85263_TSPM_INTB);
> + break;
> + case PCF85263_IRQPIN_NONE:
> + regval |= (PCF85263_INTAPM_HIGHZ | PCF85263_TSPM_DISABLED);
> + break;
> + }
> + ret = regmap_write(pcf85263->regmap, PCF85263_REG_PINIO, regval);
> +
> + return ret;
> +}
> +
> +static const struct rtc_class_ops rtc_ops = {
> + .ioctl = pcf85263_ioctl,
> + .read_time = pcf85263_read_time,
> + .set_time = pcf85263_set_time,
> + .read_alarm = pcf85263_read_alarm,
> + .set_alarm = pcf85263_set_alarm,
> + .alarm_irq_enable = pcf85263_alarm_irq_enable,
> +};
> +
> +static const struct regmap_config pcf85263_regmap_cfg = {
> + .reg_bits = 8,
> + .val_bits = 8,
> + .max_register = PCF85263_MAX_REG,
> +};
> +
> +/*
> + * On some boards the interrupt line may not be wired to the CPU but only to
> + * a power supply circuit.
> + * In that case no interrupt will be specified in the device tree but the
> + * wakeup-source DT property may be used to enable wakeup programming in
> + * sysfs
> + */
> +static bool pcf85263_can_wakeup_machine(struct pcf85263 *pcf85263)
> +{
> + return pcf85263->irq ||
> + of_property_read_bool(pcf85263->dev->of_node, "wakeup-source");
> +}
> +
> +static int pcf85263_probe(struct i2c_client *client,
> + const struct i2c_device_id *id)
> +{
> + struct device *dev = &client->dev;
> + struct pcf85263 *pcf85263;
> + int ret;
> +
> + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C |
> + I2C_FUNC_SMBUS_BYTE_DATA |
> + I2C_FUNC_SMBUS_I2C_BLOCK))
> + return -ENODEV;
> +
> + pcf85263 = devm_kzalloc(dev, sizeof(*pcf85263), GFP_KERNEL);
> + if (!pcf85263)
> + return -ENOMEM;
> +
> + pcf85263->dev = dev;
> + pcf85263->irq = client->irq;
> + dev_set_drvdata(dev, pcf85263);
> +
> + pcf85263->regmap = devm_regmap_init_i2c(client, &pcf85263_regmap_cfg);
> + if (IS_ERR(pcf85263->regmap)) {
> + ret = PTR_ERR(pcf85263->regmap);
> + dev_err(dev, "regmap allocation failed (%d)\n", ret);
> +
> + return ret;
> + }
> +
> + /* Determine which interrupt pin the board uses */
> + if (pcf85263_can_wakeup_machine(pcf85263)) {
> + if (of_property_match_string(dev->of_node,
> + "interrupt-names", "INTB") >= 0)
> + pcf85263->irq_pin = PCF85263_IRQPIN_INTB;
> + else
> + pcf85263->irq_pin = PCF85263_IRQPIN_INTA;
> + } else {
> + pcf85263->irq_pin = PCF85263_IRQPIN_NONE;
> + }
> +
> + ret = pcf85263_init_hw(pcf85263);
> + if (ret)
> + return ret;
> +
> + if (pcf85263->irq) {
> + ret = devm_request_threaded_irq(dev, pcf85263->irq, NULL,
> + pcf85263_irq,
> + IRQF_ONESHOT,
> + dev->driver->name, pcf85263);
> + if (ret) {
> + dev_err(dev, "irq %d unavailable (%d)\n",
> + pcf85263->irq, ret);
> + pcf85263->irq = 0;
> + }
> + }
> +
> + if (pcf85263_can_wakeup_machine(pcf85263))
> + device_init_wakeup(dev, true);
> +
> + pcf85263->rtc = devm_rtc_device_register(dev, dev->driver->name,
> + &rtc_ops, THIS_MODULE);
> + ret = PTR_ERR_OR_ZERO(pcf85263->rtc);
> + if (ret)
> + return ret;
> +
> + /* We cannot support UIE mode if we do not have an IRQ line */
> + if (!pcf85263->irq)
> + pcf85263->rtc->uie_unsupported = 1;
> +
> + dev_info(pcf85263->dev,
> + "PCF85263 RTC (irqpin=%s irq=%d)\n",
> + pcf85263_irqpin_names[pcf85263->irq_pin],
> + pcf85263->irq);
> +
> + return 0;
> +}
> +
> +static int pcf85263_remove(struct i2c_client *client)
> +{
> + struct pcf85263 *pcf85263 = i2c_get_clientdata(client);
> +
> + if (pcf85263_can_wakeup_machine(pcf85263))
> + device_init_wakeup(pcf85263->dev, false);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int pcf85263_suspend(struct device *dev)
> +{
> + struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
> + int ret = 0;
> +
> + if (device_may_wakeup(dev))
> + ret = enable_irq_wake(pcf85263->irq);
> +
> + return ret;
> +}
> +
> +static int pcf85263_resume(struct device *dev)
> +{
> + struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
> + int ret = 0;
> +
> + if (device_may_wakeup(dev))
> + ret = disable_irq_wake(pcf85263->irq);
> +
> + return ret;
> +}
> +
> +#endif
> +
> +static const struct i2c_device_id pcf85263_id[] = {
> + { "pcf85263", 0 },
> + { }
> +};
> +MODULE_DEVICE_TABLE(i2c, pcf85263_id);
> +
> +#ifdef CONFIG_OF
> +static const struct of_device_id pcf85263_of_match[] = {
> + { .compatible = "nxp,pcf85263" },
> + {}
> +};
> +MODULE_DEVICE_TABLE(of, pcf85263_of_match);
> +#endif
> +
> +static SIMPLE_DEV_PM_OPS(pcf85263_pm_ops, pcf85263_suspend, pcf85263_resume);
> +
> +static struct i2c_driver pcf85263_driver = {
> + .driver = {
> + .name = "rtc-pcf85263",
> + .of_match_table = of_match_ptr(pcf85263_of_match),
> + .pm = &pcf85263_pm_ops,
> + },
> + .probe = pcf85263_probe,
> + .remove = pcf85263_remove,
> + .id_table = pcf85263_id,
> +};
> +
> +module_i2c_driver(pcf85263_driver);
> +
> +MODULE_AUTHOR("Martin Fuzzey <mfuzzey-mB3Nsq4MPf1BDgjK7y7TUQ@public.gmane.org>");
> +MODULE_DESCRIPTION("PCF85263 RTC Driver");
> +MODULE_LICENSE("GPL");
>
--
Alexandre Belloni, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 15+ messages in thread
* [rtc-linux] Re: [PATCH 2/3] rtc: pcf85263: Basic driver
2016-09-21 21:35 ` Alexandre Belloni
(?)
@ 2017-03-15 17:28 ` leonardo.romor
-1 siblings, 0 replies; 15+ messages in thread
From: leonardo.romor @ 2017-03-15 17:28 UTC (permalink / raw)
To: rtc-linux
[-- Attachment #1.1: Type: text/plain, Size: 29735 bytes --]
Hello,
I wanted to inform you that we are using this kernel driver on a beaglebone
black and we are not having any kind of issue. Thanks for your work
Il giorno mercoledì 21 settembre 2016 23:35:42 UTC+2, alexandre.belloni ha
scritto:
>
> Hi,
>
> Sorry for the very late review.
>
> The driver seems good to me but I'd like a review from the DT
> maintainers before taking it.
>
> On 01/08/2016 at 17:50:34 +0200, Martin Fuzzey wrote :
> > Add basic support for NXP PCF85263 I2C RTC chip.
> >
> > Only RTC and Alarm functions are supported.
> >
> > Signed-off-by: Martin Fuzzey <mfu...@parkeon.com <javascript:>>
> > ---
> > drivers/rtc/Kconfig | 8 +
> > drivers/rtc/Makefile | 1
> > drivers/rtc/rtc-pcf85263.c | 655
> ++++++++++++++++++++++++++++++++++++++++++++
> > 3 files changed, 664 insertions(+)
> > create mode 100644 drivers/rtc/rtc-pcf85263.c
> >
> > diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
> > index 18639e0..63ec0fc 100644
> > --- a/drivers/rtc/Kconfig
> > +++ b/drivers/rtc/Kconfig
> > @@ -396,6 +396,14 @@ config RTC_DRV_PCF85063
> > This driver can also be built as a module. If so, the module
> > will be called rtc-pcf85063.
> >
> > +config RTC_DRV_PCF85263
> > + tristate "NXP PCF85263"
> > + help
> > + If you say yes here you get support for the PCF85263 RTC chip
> > +
> > + This driver can also be built as a module. If so, the module
> > + will be called rtc-pcf85263.
> > +
> > config RTC_DRV_PCF8563
> > tristate "Philips PCF8563/Epson RTC8564"
> > help
> > diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
> > index ea28337..04f48fd 100644
> > --- a/drivers/rtc/Makefile
> > +++ b/drivers/rtc/Makefile
> > @@ -108,6 +108,7 @@ obj-$(CONFIG_RTC_DRV_PCF2123) +=
> rtc-pcf2123.o
> > obj-$(CONFIG_RTC_DRV_PCF2127) += rtc-pcf2127.o
> > obj-$(CONFIG_RTC_DRV_PCF50633) += rtc-pcf50633.o
> > obj-$(CONFIG_RTC_DRV_PCF85063) += rtc-pcf85063.o
> > +obj-$(CONFIG_RTC_DRV_PCF85263) += rtc-pcf85263.o
> > obj-$(CONFIG_RTC_DRV_PCF8523) += rtc-pcf8523.o
> > obj-$(CONFIG_RTC_DRV_PCF8563) += rtc-pcf8563.o
> > obj-$(CONFIG_RTC_DRV_PCF8583) += rtc-pcf8583.o
> > diff --git a/drivers/rtc/rtc-pcf85263.c b/drivers/rtc/rtc-pcf85263.c
> > new file mode 100644
> > index 0000000..997742d
> > --- /dev/null
> > +++ b/drivers/rtc/rtc-pcf85263.c
> > @@ -0,0 +1,655 @@
> > +/*
> > + * rtc-pcf85263 Driver for the NXP PCF85263 RTC
> > + *
> > + * Copyright 2016 Parkeon
> > + *
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License version 2 as
> > + * published by the Free Software Foundation.
> > + */
> > +
> > +#include <linux/module.h>
> > +#include <linux/mutex.h>
> > +#include <linux/rtc.h>
> > +#include <linux/i2c.h>
> > +#include <linux/bcd.h>
> > +#include <linux/of.h>
> > +#include <linux/of_device.h>
> > +#include <linux/regmap.h>
> > +
> > +#include <dt-bindings/rtc/nxp,pcf85263.h>
> > +
> > +#define DRV_NAME "rtc-pcf85263"
> > +
> > +#define PCF85263_REG_RTC_SC 0x01 /* Seconds */
> > +#define PCF85263_REG_RTC_SC_OS BIT(7) /*
> Oscilator stopped flag */
> > +
> > +#define PCF85263_REG_RTC_MN 0x02 /* Minutes */
> > +#define PCF85263_REG_RTC_HR 0x03 /* Hours */
> > +#define PCF85263_REG_RTC_DT 0x04 /* Day of month 1-31 */
> > +#define PCF85263_REG_RTC_DW 0x05 /* Day of week 0-6 */
> > +#define PCF85263_REG_RTC_MO 0x06 /* Month 1-12 */
> > +#define PCF85263_REG_RTC_YR 0x07 /* Year 0-99 */
> > +
> > +#define PCF85263_REG_ALM1_SC 0x08 /* Seconds */
> > +#define PCF85263_REG_ALM1_MN 0x09 /* Minutes */
> > +#define PCF85263_REG_ALM1_HR 0x0a /* Hours */
> > +#define PCF85263_REG_ALM1_DT 0x0b /* Day of month 1-31 */
> > +#define PCF85263_REG_ALM1_MO 0x0c /* Month 1-12 */
> > +
> > +#define PCF85263_REG_ALM_CTL 0x10
> > +#define PCF85263_REG_ALM_CTL_ALL_A1E 0x1f /* sec,min,hr,day,mon
> alarm 1 */
> > +
> > +#define PCF85263_REG_OSC 0x25
> > +#define PCF85263_REG_OSC_CL_MASK (BIT(0) | BIT(1))
> > +#define PCF85263_REG_OSC_CL_SHIFT 0
> > +#define PCF85263_REG_OSC_OSCD_MASK (BIT(2) | BIT(3))
> > +#define PCF85263_REG_OSC_OSCD_SHIFT 2
> > +#define PCF85263_REG_OSC_LOWJ BIT(4)
> > +#define PCF85263_REG_OSC_12H BIT(5)
> > +
> > +#define PCF85263_REG_PINIO 0x27
> > +#define PCF85263_REG_PINIO_INTAPM_MASK (BIT(0) | BIT(1))
> > +#define PCF85263_REG_PINIO_INTAPM_SHIFT 0
> > +#define PCF85263_INTAPM_INTA (0x2 <<
> PCF85263_REG_PINIO_INTAPM_SHIFT)
> > +#define PCF85263_INTAPM_HIGHZ (0x3 <<
> PCF85263_REG_PINIO_INTAPM_SHIFT)
> > +#define PCF85263_REG_PINIO_TSPM_MASK (BIT(2) | BIT(3))
> > +#define PCF85263_REG_PINIO_TSPM_SHIFT 2
> > +#define PCF85263_TSPM_DISABLED (0x0 <<
> PCF85263_REG_PINIO_TSPM_SHIFT)
> > +#define PCF85263_TSPM_INTB (0x1 <<
> PCF85263_REG_PINIO_TSPM_SHIFT)
> > +#define PCF85263_REG_PINIO_CLKDISABLE BIT(7)
> > +
> > +#define PCF85263_REG_FUNCTION 0x28
> > +#define PCF85263_REG_FUNCTION_COF_MASK 0x7
> > +#define PCF85263_REG_FUNCTION_COF_OFF 0x7 /* No clock
> output */
> > +
> > +#define PCF85263_REG_INTA_CTL 0x29
> > +#define PCF85263_REG_INTB_CTL 0x2A
> > +#define PCF85263_REG_INTx_CTL_A1E BIT(4) /* Alarm 1 */
> > +#define PCF85263_REG_INTx_CTL_ILP BIT(7) /* 0=pulse,
> 1=level */
> > +
> > +#define PCF85263_REG_FLAGS 0x2B
> > +#define PCF85263_REG_FLAGS_A1F BIT(5)
> > +
> > +#define PCF85263_REG_RAM_BYTE 0x2c
> > +
> > +#define PCF85263_REG_STOPENABLE 0x2e
> > +#define PCF85263_REG_STOPENABLE_STOP BIT(0)
> > +
> > +#define PCF85263_REG_RESET 0x2f /* Reset command */
> > +#define PCF85263_REG_RESET_CMD_CPR 0xa4 /* Clear
> prescaler */
> > +
> > +#define PCF85263_MAX_REG 0x2f
> > +
> > +#define PCF85263_HR_PM BIT(5)
> > +
> > +enum pcf85263_irqpin {
> > + PCF85263_IRQPIN_NONE,
> > + PCF85263_IRQPIN_INTA,
> > + PCF85263_IRQPIN_INTB
> > +};
> > +
> > +static const char *const pcf85263_irqpin_names[] = {
> > + [PCF85263_IRQPIN_NONE] = "None",
> > + [PCF85263_IRQPIN_INTA] = "INTA",
> > + [PCF85263_IRQPIN_INTB] = "INTB"
> > +};
> > +
> > +struct pcf85263 {
> > + struct device *dev;
> > + struct rtc_device *rtc;
> > + struct regmap *regmap;
> > + enum pcf85263_irqpin irq_pin;
> > + int irq;
> > + bool mode_12h;
> > +};
> > +
> > +/*
> > + * Helpers to convert 12h to 24h and vice versa.
> > + * Values in register are stored in BCD with a PM flag in bit 5
> > + *
> > + * 23:00 <=> 11PM <=> 0x31
> > + * 00:00 <=> 12AM <=> 0x12
> > + * 01:00 <=> 1AM <=> 0x01
> > + * 12:00 <=> 12PM <=> 0x32
> > + * 13:00 <=> 1PM <=> 0x21
> > + */
> > +static int pcf85263_bcd12h_to_bin24h(int regval)
> > +{
> > + int hr = bcd2bin(regval & 0x1f);
> > + bool pm = regval & PCF85263_HR_PM;
> > +
> > + if (hr == 12)
> > + return pm ? 12 : 0;
> > +
> > + return pm ? hr + 12 : hr;
> > +}
> > +
> > +static int pcf85263_bin24h_to_bcd12h(int hr24)
> > +{
> > + bool pm = hr24 >= 12;
> > + int hr12 = hr24 % 12;
> > +
> > + if (!hr12)
> > + hr12++;
> > +
> > + return bin2bcd(hr12) | pm ? 0 : PCF85263_HR_PM;
> > +}
> > +
> > +static int pcf85263_read_time(struct device *dev, struct rtc_time *tm)
> > +{
> > + struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
> > + const int first = PCF85263_REG_RTC_SC;
> > + const int last = PCF85263_REG_RTC_YR;
> > + const int len = last - first + 1;
> > + u8 regs[len];
> > + u8 hr_reg;
> > + int ret;
> > +
> > + ret = regmap_bulk_read(pcf85263->regmap, first, regs, len);
> > + if (ret)
> > + return ret;
> > +
> > + if (regs[PCF85263_REG_RTC_SC - first] & PCF85263_REG_RTC_SC_OS)
> {
> > + dev_warn(dev, "Oscillator stop detected, date/time is
> not reliable.\n");
> > + return -EINVAL;
> > + }
> > +
> > + tm->tm_sec = bcd2bin(regs[PCF85263_REG_RTC_SC - first] & 0x7f);
> > + tm->tm_min = bcd2bin(regs[PCF85263_REG_RTC_MN - first] & 0x7f);
> > +
> > + hr_reg = regs[PCF85263_REG_RTC_HR - first];
> > + if (pcf85263->mode_12h)
> > + tm->tm_hour = pcf85263_bcd12h_to_bin24h(hr_reg);
> > + else
> > + tm->tm_hour = bcd2bin(hr_reg & 0x3f);
> > +
> > + tm->tm_mday = bcd2bin(regs[PCF85263_REG_RTC_DT - first]);
> > + tm->tm_wday = bcd2bin(regs[PCF85263_REG_RTC_DW - first]);
> > + tm->tm_mon = bcd2bin(regs[PCF85263_REG_RTC_MO - first]) - 1;
> > + tm->tm_year = bcd2bin(regs[PCF85263_REG_RTC_YR - first]);
> > +
> > + tm->tm_year += 100; /* Assume 21st century */
> > +
> > + return 0;
> > +}
> > +
> > +static int pcf85263_set_time(struct device *dev, struct rtc_time *tm)
> > +{
> > + struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
> > +
> > + /*
> > + * Before setting time need to stop RTC and disable prescaler
> > + * Do this all in a single I2C transaction exploiting
> wraparound
> > + * as described in data sheet.
> > + * This means that the array below must be in register order
> > + */
> > + u8 regs[] = {
> > + PCF85263_REG_STOPENABLE_STOP, /* STOP */
> > + PCF85263_REG_RESET_CMD_CPR, /* Disable prescaler
> */
> > + /* Wrap around to register 0 (1/100s) */
> > + 0, /* 1/100s always
> zero. */
> > + bin2bcd(tm->tm_sec),
> > + bin2bcd(tm->tm_min),
> > + bin2bcd(tm->tm_hour), /* 24-hour */
> > + bin2bcd(tm->tm_mday),
> > + bin2bcd(tm->tm_wday + 1),
> > + bin2bcd(tm->tm_mon + 1),
> > + bin2bcd(tm->tm_year % 100)
> > + };
> > + int ret;
> > +
> > + ret = regmap_bulk_write(pcf85263->regmap,
> PCF85263_REG_STOPENABLE,
> > + regs, sizeof(regs));
> > + if (ret)
> > + return ret;
> > +
> > + /* As we have set the time in 24H update the hardware for that
> */
> > + if (pcf85263->mode_12h) {
> > + pcf85263->mode_12h = false;
> > + ret = regmap_update_bits(pcf85263->regmap,
> PCF85263_REG_OSC,
> > + PCF85263_REG_OSC_12H, 0);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + /* Start it again */
> > + return regmap_write(pcf85263->regmap, PCF85263_REG_STOPENABLE,
> 0);
> > +}
> > +
> > +static int pcf85263_enable_alarm(struct pcf85263 *pcf85263, bool
> enable)
> > +{
> > + int reg;
> > + int ret;
> > +
> > + ret = regmap_update_bits(pcf85263->regmap,
> PCF85263_REG_ALM_CTL,
> > + PCF85263_REG_ALM_CTL_ALL_A1E,
> > + enable ? PCF85263_REG_ALM_CTL_ALL_A1E
> : 0);
> > + if (ret)
> > + return ret;
> > +
> > + switch (pcf85263->irq_pin) {
> > + case PCF85263_IRQPIN_NONE:
> > + return 0;
> > +
> > + case PCF85263_IRQPIN_INTA:
> > + reg = PCF85263_REG_INTA_CTL;
> > + break;
> > +
> > + case PCF85263_IRQPIN_INTB:
> > + reg = PCF85263_REG_INTB_CTL;
> > + break;
> > +
> > + default:
> > + return -EINVAL;
> > + }
> > +
> > + return regmap_update_bits(pcf85263->regmap, reg,
> > + PCF85263_REG_INTx_CTL_A1E,
> > + enable ? PCF85263_REG_INTx_CTL_A1E :
> 0);
> > +}
> > +
> > +static int pcf85263_read_alarm(struct device *dev, struct rtc_wkalrm
> *alarm)
> > +{
> > + struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
> > + struct rtc_time *tm = &alarm->time;
> > + const int first = PCF85263_REG_ALM1_SC;
> > + const int last = PCF85263_REG_ALM1_MO;
> > + const int len = last - first + 1;
> > + u8 regs[len];
> > + u8 hr_reg;
> > + unsigned int regval;
> > + int ret;
> > +
> > + ret = regmap_bulk_read(pcf85263->regmap, first, regs, len);
> > + if (ret)
> > + return ret;
> > +
> > + tm->tm_sec = bcd2bin(regs[PCF85263_REG_ALM1_SC - first] &
> 0x7f);
> > + tm->tm_min = bcd2bin(regs[PCF85263_REG_ALM1_MN - first] &
> 0x7f);
> > +
> > + hr_reg = regs[PCF85263_REG_ALM1_HR - first];
> > + if (pcf85263->mode_12h)
> > + tm->tm_hour = pcf85263_bcd12h_to_bin24h(hr_reg);
> > + else
> > + tm->tm_hour = bcd2bin(hr_reg & 0x3f);
> > +
> > + tm->tm_mday = bcd2bin(regs[PCF85263_REG_ALM1_DT - first]);
> > + tm->tm_mon = bcd2bin(regs[PCF85263_REG_ALM1_MO - first]) - 1;
> > + tm->tm_year = -1;
> > + tm->tm_wday = -1;
> > +
> > + ret = regmap_read(pcf85263->regmap, PCF85263_REG_ALM_CTL,
> ®val);
> > + if (ret)
> > + return ret;
> > + alarm->enabled = !!(regval & PCF85263_REG_ALM_CTL_ALL_A1E);
> > +
> > + ret = regmap_read(pcf85263->regmap, PCF85263_REG_FLAGS,
> ®val);
> > + if (ret)
> > + return ret;
> > + alarm->pending = !!(regval & PCF85263_REG_FLAGS_A1F);
> > +
> > + return 0;
> > +}
> > +
> > +static int pcf85263_set_alarm(struct device *dev, struct rtc_wkalrm
> *alarm)
> > +{
> > + struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
> > + struct rtc_time *tm = &alarm->time;
> > + const int first = PCF85263_REG_ALM1_SC;
> > + const int last = PCF85263_REG_ALM1_MO;
> > + const int len = last - first + 1;
> > + u8 regs[len];
> > + int ret;
> > +
> > + /* Disable alarm comparison during update */
> > + ret = pcf85263_enable_alarm(pcf85263, false);
> > + if (ret)
> > + return ret;
> > +
> > + /* Clear any pending alarm (write 0=>clr, 1=>no change) */
> > + ret = regmap_write(pcf85263->regmap, PCF85263_REG_FLAGS,
> > + ~PCF85263_REG_FLAGS_A1F);
> > + if (ret)
> > + return ret;
> > +
> > + /* Set the alarm time registers */
> > + regs[PCF85263_REG_ALM1_SC - first] = bin2bcd(tm->tm_sec);
> > + regs[PCF85263_REG_ALM1_MN - first] = bin2bcd(tm->tm_min);
> > + regs[PCF85263_REG_ALM1_HR - first] = pcf85263->mode_12h ?
> > + pcf85263_bin24h_to_bcd12h(tm->tm_hour) :
> > + bin2bcd(tm->tm_hour);
> > + regs[PCF85263_REG_ALM1_DT - first] = bin2bcd(tm->tm_mday);
> > + regs[PCF85263_REG_ALM1_MO - first] = bin2bcd(tm->tm_mon + 1);
> > +
> > + ret = regmap_bulk_write(pcf85263->regmap, first, regs,
> sizeof(regs));
> > + if (ret)
> > + return ret;
> > +
> > + if (alarm->enabled)
> > + ret = pcf85263_enable_alarm(pcf85263, true);
> > +
> > + return ret;
> > +}
> > +
> > +static int pcf85263_alarm_irq_enable(struct device *dev, unsigned int
> enable)
> > +{
> > + struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
> > +
> > + return pcf85263_enable_alarm(pcf85263, !!enable);
> > +}
> > +
> > +static irqreturn_t pcf85263_irq(int irq, void *data)
> > +{
> > + struct pcf85263 *pcf85263 = data;
> > + unsigned int regval;
> > + int ret;
> > +
> > + ret = regmap_read(pcf85263->regmap, PCF85263_REG_FLAGS,
> ®val);
> > + if (ret)
> > + return IRQ_NONE;
> > +
> > + if (regval & PCF85263_REG_FLAGS_A1F) {
> > + regmap_write(pcf85263->regmap, PCF85263_REG_FLAGS,
> > + ~PCF85263_REG_FLAGS_A1F);
> > +
> > + rtc_update_irq(pcf85263->rtc, 1, RTC_IRQF | RTC_AF);
> > +
> > + return IRQ_HANDLED;
> > + }
> > +
> > + return IRQ_NONE;
> > +}
> > +
> > +static int pcf85263_check_osc_stopped(struct pcf85263 *pcf85263)
> > +{
> > + unsigned int regval;
> > + int ret;
> > +
> > + ret = regmap_read(pcf85263->regmap, PCF85263_REG_RTC_SC,
> ®val);
> > + if (ret)
> > + return ret;
> > +
> > + ret = regval & PCF85263_REG_RTC_SC_OS ? 1 : 0;
> > + if (ret)
> > + dev_warn(pcf85263->dev, "Oscillator stop detected,
> date/time is not reliable.\n");
> > +
> > + return ret;
> > +}
> > +
> > +#ifdef CONFIG_RTC_INTF_DEV
> > +static int pcf85263_ioctl(struct device *dev,
> > + unsigned int cmd, unsigned long arg)
> > +{
> > + struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
> > + int ret;
> > +
> > + switch (cmd) {
> > + case RTC_VL_READ:
> > + ret = pcf85263_check_osc_stopped(pcf85263);
> > + if (ret < 0)
> > + return ret;
> > +
> > + if (copy_to_user((void __user *)arg, &ret,
> sizeof(int)))
> > + return -EFAULT;
> > + return 0;
> > +
> > + case RTC_VL_CLR:
> > + return regmap_update_bits(pcf85263->regmap,
> > + PCF85263_REG_RTC_SC,
> > + PCF85263_REG_RTC_SC_OS, 0);
> > + default:
> > + return -ENOIOCTLCMD;
> > + }
> > +}
> > +#else
> > +#define pcf85263_ioctl NULL
> > +#endif
> > +
> > +static int pcf85263_init_hw(struct pcf85263 *pcf85263)
> > +{
> > + struct device_node *np = pcf85263->dev->of_node;
> > + unsigned int regval;
> > + u32 propval;
> > + int ret;
> > +
> > + /* Determine if oscilator has been stopped (probably low power)
> */
> > + ret = pcf85263_check_osc_stopped(pcf85263);
> > + if (ret < 0) {
> > + /* Log here since this is the first hw access on probe
> */
> > + dev_err(pcf85263->dev, "Unable to read register\n");
> > +
> > + return ret;
> > + }
> > +
> > + /* Determine 12/24H mode */
> > + ret = regmap_read(pcf85263->regmap, PCF85263_REG_OSC, ®val);
> > + if (ret)
> > + return ret;
> > + pcf85263->mode_12h = !!(regval & PCF85263_REG_OSC_12H);
> > +
> > + /* Set oscilator register */
> > + regval &= ~PCF85263_REG_OSC_12H; /* keep current 12/24 h
> setting */
> > +
> > + propval = PCF85263_QUARTZCAP_12p5pF;
> > + of_property_read_u32(np, "quartz-load-capacitance", &propval);
> > + regval |= ((propval << PCF85263_REG_OSC_CL_SHIFT)
> > + & PCF85263_REG_OSC_CL_MASK);
> > +
> > + propval = PCF85263_QUARTZDRIVE_NORMAL;
> > + of_property_read_u32(np, "quartz-drive-strength", &propval);
> > + regval |= ((propval << PCF85263_REG_OSC_OSCD_SHIFT)
> > + & PCF85263_REG_OSC_OSCD_MASK);
> > +
> > + if (of_property_read_bool(np, "quartz-low-jitter"))
> > + regval |= PCF85263_REG_OSC_LOWJ;
> > +
> > + ret = regmap_write(pcf85263->regmap, PCF85263_REG_OSC, regval);
> > + if (ret)
> > + return ret;
> > +
> > + /* Set function register (RTC mode, 1s tick, clock output
> static) */
> > + ret = regmap_write(pcf85263->regmap, PCF85263_REG_FUNCTION,
> > + PCF85263_REG_FUNCTION_COF_OFF);
> > + if (ret)
> > + return ret;
> > +
> > + /* Set all interrupts to disabled, level mode */
> > + ret = regmap_write(pcf85263->regmap, PCF85263_REG_INTA_CTL,
> > + PCF85263_REG_INTx_CTL_ILP);
> > + if (ret)
> > + return ret;
> > + ret = regmap_write(pcf85263->regmap, PCF85263_REG_INTB_CTL,
> > + PCF85263_REG_INTx_CTL_ILP);
> > + if (ret)
> > + return ret;
> > +
> > + /* Setup IO pin config register */
> > + regval = PCF85263_REG_PINIO_CLKDISABLE;
> > + switch (pcf85263->irq_pin) {
> > + case PCF85263_IRQPIN_INTA:
> > + regval |= (PCF85263_INTAPM_INTA |
> PCF85263_TSPM_DISABLED);
> > + break;
> > + case PCF85263_IRQPIN_INTB:
> > + regval |= (PCF85263_INTAPM_HIGHZ | PCF85263_TSPM_INTB);
> > + break;
> > + case PCF85263_IRQPIN_NONE:
> > + regval |= (PCF85263_INTAPM_HIGHZ |
> PCF85263_TSPM_DISABLED);
> > + break;
> > + }
> > + ret = regmap_write(pcf85263->regmap, PCF85263_REG_PINIO,
> regval);
> > +
> > + return ret;
> > +}
> > +
> > +static const struct rtc_class_ops rtc_ops = {
> > + .ioctl = pcf85263_ioctl,
> > + .read_time = pcf85263_read_time,
> > + .set_time = pcf85263_set_time,
> > + .read_alarm = pcf85263_read_alarm,
> > + .set_alarm = pcf85263_set_alarm,
> > + .alarm_irq_enable = pcf85263_alarm_irq_enable,
> > +};
> > +
> > +static const struct regmap_config pcf85263_regmap_cfg = {
> > + .reg_bits = 8,
> > + .val_bits = 8,
> > + .max_register = PCF85263_MAX_REG,
> > +};
> > +
> > +/*
> > + * On some boards the interrupt line may not be wired to the CPU but
> only to
> > + * a power supply circuit.
> > + * In that case no interrupt will be specified in the device tree but
> the
> > + * wakeup-source DT property may be used to enable wakeup programming
> in
> > + * sysfs
> > + */
> > +static bool pcf85263_can_wakeup_machine(struct pcf85263 *pcf85263)
> > +{
> > + return pcf85263->irq ||
> > + of_property_read_bool(pcf85263->dev->of_node,
> "wakeup-source");
> > +}
> > +
> > +static int pcf85263_probe(struct i2c_client *client,
> > + const struct i2c_device_id *id)
> > +{
> > + struct device *dev = &client->dev;
> > + struct pcf85263 *pcf85263;
> > + int ret;
> > +
> > + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C |
> > + I2C_FUNC_SMBUS_BYTE_DATA |
> > + I2C_FUNC_SMBUS_I2C_BLOCK))
> > + return -ENODEV;
> > +
> > + pcf85263 = devm_kzalloc(dev, sizeof(*pcf85263), GFP_KERNEL);
> > + if (!pcf85263)
> > + return -ENOMEM;
> > +
> > + pcf85263->dev = dev;
> > + pcf85263->irq = client->irq;
> > + dev_set_drvdata(dev, pcf85263);
> > +
> > + pcf85263->regmap = devm_regmap_init_i2c(client,
> &pcf85263_regmap_cfg);
> > + if (IS_ERR(pcf85263->regmap)) {
> > + ret = PTR_ERR(pcf85263->regmap);
> > + dev_err(dev, "regmap allocation failed (%d)\n", ret);
> > +
> > + return ret;
> > + }
> > +
> > + /* Determine which interrupt pin the board uses */
> > + if (pcf85263_can_wakeup_machine(pcf85263)) {
> > + if (of_property_match_string(dev->of_node,
> > + "interrupt-names", "INTB")
> >= 0)
> > + pcf85263->irq_pin = PCF85263_IRQPIN_INTB;
> > + else
> > + pcf85263->irq_pin = PCF85263_IRQPIN_INTA;
> > + } else {
> > + pcf85263->irq_pin = PCF85263_IRQPIN_NONE;
> > + }
> > +
> > + ret = pcf85263_init_hw(pcf85263);
> > + if (ret)
> > + return ret;
> > +
> > + if (pcf85263->irq) {
> > + ret = devm_request_threaded_irq(dev, pcf85263->irq,
> NULL,
> > + pcf85263_irq,
> > + IRQF_ONESHOT,
> > + dev->driver->name,
> pcf85263);
> > + if (ret) {
> > + dev_err(dev, "irq %d unavailable (%d)\n",
> > + pcf85263->irq, ret);
> > + pcf85263->irq = 0;
> > + }
> > + }
> > +
> > + if (pcf85263_can_wakeup_machine(pcf85263))
> > + device_init_wakeup(dev, true);
> > +
> > + pcf85263->rtc = devm_rtc_device_register(dev,
> dev->driver->name,
> > + &rtc_ops,
> THIS_MODULE);
> > + ret = PTR_ERR_OR_ZERO(pcf85263->rtc);
> > + if (ret)
> > + return ret;
> > +
> > + /* We cannot support UIE mode if we do not have an IRQ line */
> > + if (!pcf85263->irq)
> > + pcf85263->rtc->uie_unsupported = 1;
> > +
> > + dev_info(pcf85263->dev,
> > + "PCF85263 RTC (irqpin=%s irq=%d)\n",
> > + pcf85263_irqpin_names[pcf85263->irq_pin],
> > + pcf85263->irq);
> > +
> > + return 0;
> > +}
> > +
> > +static int pcf85263_remove(struct i2c_client *client)
> > +{
> > + struct pcf85263 *pcf85263 = i2c_get_clientdata(client);
> > +
> > + if (pcf85263_can_wakeup_machine(pcf85263))
> > + device_init_wakeup(pcf85263->dev, false);
> > +
> > + return 0;
> > +}
> > +
> > +#ifdef CONFIG_PM_SLEEP
> > +static int pcf85263_suspend(struct device *dev)
> > +{
> > + struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
> > + int ret = 0;
> > +
> > + if (device_may_wakeup(dev))
> > + ret = enable_irq_wake(pcf85263->irq);
> > +
> > + return ret;
> > +}
> > +
> > +static int pcf85263_resume(struct device *dev)
> > +{
> > + struct pcf85263 *pcf85263 = dev_get_drvdata(dev);
> > + int ret = 0;
> > +
> > + if (device_may_wakeup(dev))
> > + ret = disable_irq_wake(pcf85263->irq);
> > +
> > + return ret;
> > +}
> > +
> > +#endif
> > +
> > +static const struct i2c_device_id pcf85263_id[] = {
> > + { "pcf85263", 0 },
> > + { }
> > +};
> > +MODULE_DEVICE_TABLE(i2c, pcf85263_id);
> > +
> > +#ifdef CONFIG_OF
> > +static const struct of_device_id pcf85263_of_match[] = {
> > + { .compatible = "nxp,pcf85263" },
> > + {}
> > +};
> > +MODULE_DEVICE_TABLE(of, pcf85263_of_match);
> > +#endif
> > +
> > +static SIMPLE_DEV_PM_OPS(pcf85263_pm_ops, pcf85263_suspend,
> pcf85263_resume);
> > +
> > +static struct i2c_driver pcf85263_driver = {
> > + .driver = {
> > + .name = "rtc-pcf85263",
> > + .of_match_table = of_match_ptr(pcf85263_of_match),
> > + .pm = &pcf85263_pm_ops,
> > + },
> > + .probe = pcf85263_probe,
> > + .remove = pcf85263_remove,
> > + .id_table = pcf85263_id,
> > +};
> > +
> > +module_i2c_driver(pcf85263_driver);
> > +
> > +MODULE_AUTHOR("Martin Fuzzey <mfu...@parkeon.com <javascript:>>");
> > +MODULE_DESCRIPTION("PCF85263 RTC Driver");
> > +MODULE_LICENSE("GPL");
> >
>
> --
> Alexandre Belloni, Free Electrons
> Embedded Linux and Kernel engineering
> http://free-electrons.com
>
--
You received this message because you are subscribed to "rtc-linux".
Membership options at http://groups.google.com/group/rtc-linux .
Please read http://groups.google.com/group/rtc-linux/web/checklist
before submitting a driver.
---
You received this message because you are subscribed to the Google Groups "rtc-linux" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rtc-linux+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
[-- Attachment #1.2: Type: text/html, Size: 40627 bytes --]
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2017-03-15 17:28 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-08-01 15:50 [rtc-linux] [PATCH 0/3] rtc: Add pcf85263 Martin Fuzzey
2016-08-01 15:50 ` Martin Fuzzey
2016-08-01 15:50 ` [rtc-linux] [PATCH 1/3] dt-binding: rtc Add DT binding for NXP 85263 RTC Martin Fuzzey
2016-08-01 15:50 ` Martin Fuzzey
2016-09-21 20:43 ` [rtc-linux] " Alexandre Belloni
2016-09-21 20:43 ` Alexandre Belloni
2016-08-01 15:50 ` [rtc-linux] [PATCH 2/3] rtc: pcf85263: Basic driver Martin Fuzzey
2016-08-01 15:50 ` Martin Fuzzey
2016-09-21 21:35 ` [rtc-linux] " Alexandre Belloni
2016-09-21 21:35 ` Alexandre Belloni
2017-03-15 17:28 ` [rtc-linux] " leonardo.romor
2016-08-01 15:50 ` [rtc-linux] [PATCH 3/3] rtc: pcf85263: Support multiple centuries Martin Fuzzey
2016-08-01 15:50 ` Martin Fuzzey
2016-09-21 21:33 ` [rtc-linux] " Alexandre Belloni
2016-09-21 21:33 ` Alexandre Belloni
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.