Commit afc505bf authored by Hugo Villeneuve's avatar Hugo Villeneuve Committed by Alexandre Belloni
Browse files

rtc: pcf2127: add support for PCF2131 RTC



This RTC is very similar in functionality to the PCF2127/29.

Basically it:
  -supports two new control registers at offsets 4 and 5
  -supports a new reset register (not implemented in this driver)
  -supports 4 tamper detection functions instead of 1
  -has no nvmem (like the PCF2129)
  -has two output interrupt pins

Because of that, most of the register addresses are very different,
although they still follow the same layout. For example, the tamper
registers have a different base address, but the offsets are all the same.

Signed-off-by: default avatarHugo Villeneuve <hvilleneuve@dimonoff.com>
Link: https://lore.kernel.org/r/20230622145800.2442116-12-hugo@hugovil.com


Signed-off-by: default avatarAlexandre Belloni <alexandre.belloni@bootlin.com>
parent 420cc9e8
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -904,9 +904,9 @@ config RTC_DRV_PCF2127
	select REGMAP_SPI if SPI_MASTER
	select WATCHDOG_CORE if WATCHDOG
	help
	  If you say yes here you get support for the NXP PCF2127/29 RTC
	  If you say yes here you get support for the NXP PCF2127/29/31 RTC
	  chips with integrated quartz crystal for industrial applications.
	  Both chips also have watchdog timer and tamper switch detection
	  These chips also have watchdog timer and tamper switch detection
	  features.

	  PCF2127 has an additional feature of 512 bytes battery backed
+213 −21
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/*
 * An I2C and SPI driver for the NXP PCF2127/29 RTC
 * An I2C and SPI driver for the NXP PCF2127/29/31 RTC
 * Copyright 2013 Til-Technologies
 *
 * Author: Renaud Cerrato <r.cerrato@til-technologies.fr>
@@ -8,9 +8,13 @@
 * Watchdog and tamper functions
 * Author: Bruno Thomsen <bruno.thomsen@gmail.com>
 *
 * PCF2131 support
 * Author: Hugo Villeneuve <hvilleneuve@dimonoff.com>
 *
 * based on the other drivers in this same directory.
 *
 * Datasheet: https://www.nxp.com/docs/en/data-sheet/PCF2127.pdf
 * Datasheets: https://www.nxp.com/docs/en/data-sheet/PCF2127.pdf
 *             https://www.nxp.com/docs/en/data-sheet/PCF2131DS.pdf
 */

#include <linux/i2c.h>
@@ -67,7 +71,7 @@
 * RAM registers
 * PCF2127 has 512 bytes general-purpose static RAM (SRAM) that is
 * battery backed and can survive a power outage.
 * PCF2129 doesn't have this feature.
 * PCF2129/31 doesn't have this feature.
 */
#define PCF2127_REG_RAM_ADDR_MSB	0x1A
#define PCF2127_REG_RAM_WRT_CMD		0x1C
@@ -86,11 +90,65 @@
		PCF2127_BIT_CTRL2_WDTF | \
		PCF2127_BIT_CTRL2_TSF2)

#define PCF2127_MAX_TS_SUPPORTED	1
#define PCF2127_MAX_TS_SUPPORTED	4

/* Control register 4 */
#define PCF2131_REG_CTRL4		0x03
#define PCF2131_BIT_CTRL4_TSF4			BIT(4)
#define PCF2131_BIT_CTRL4_TSF3			BIT(5)
#define PCF2131_BIT_CTRL4_TSF2			BIT(6)
#define PCF2131_BIT_CTRL4_TSF1			BIT(7)
/* Control register 5 */
#define PCF2131_REG_CTRL5		0x04
#define PCF2131_BIT_CTRL5_TSIE4			BIT(4)
#define PCF2131_BIT_CTRL5_TSIE3			BIT(5)
#define PCF2131_BIT_CTRL5_TSIE2			BIT(6)
#define PCF2131_BIT_CTRL5_TSIE1			BIT(7)
/* Software reset register */
#define PCF2131_REG_SR_RESET		0x05
#define PCF2131_SR_RESET_READ_PATTERN	(BIT(2) | BIT(5))
#define PCF2131_SR_RESET_CPR_CMD	(PCF2131_SR_RESET_READ_PATTERN | BIT(7))
/* Time and date registers */
#define PCF2131_REG_TIME_BASE		0x07
/* Alarm registers */
#define PCF2131_REG_ALARM_BASE		0x0E
/* CLKOUT control register */
#define PCF2131_REG_CLKOUT		0x13
/* Watchdog registers */
#define PCF2131_REG_WD_CTL		0x35
#define PCF2131_REG_WD_VAL		0x36
/* Tamper timestamp1 registers */
#define PCF2131_REG_TS1_BASE		0x14
/* Tamper timestamp2 registers */
#define PCF2131_REG_TS2_BASE		0x1B
/* Tamper timestamp3 registers */
#define PCF2131_REG_TS3_BASE		0x22
/* Tamper timestamp4 registers */
#define PCF2131_REG_TS4_BASE		0x29
/* Interrupt mask registers */
#define PCF2131_REG_INT_A_MASK1		0x31
#define PCF2131_REG_INT_A_MASK2		0x32
#define PCF2131_REG_INT_B_MASK1		0x33
#define PCF2131_REG_INT_B_MASK2		0x34
#define PCF2131_BIT_INT_BLIE		BIT(0)
#define PCF2131_BIT_INT_BIE		BIT(1)
#define PCF2131_BIT_INT_AIE		BIT(2)
#define PCF2131_BIT_INT_WD_CD		BIT(3)
#define PCF2131_BIT_INT_SI		BIT(4)
#define PCF2131_BIT_INT_MI		BIT(5)
#define PCF2131_CTRL2_IRQ_MASK ( \
		PCF2127_BIT_CTRL2_AF | \
		PCF2127_BIT_CTRL2_WDTF)
#define PCF2131_CTRL4_IRQ_MASK ( \
		PCF2131_BIT_CTRL4_TSF4 | \
		PCF2131_BIT_CTRL4_TSF3 | \
		PCF2131_BIT_CTRL4_TSF2 | \
		PCF2131_BIT_CTRL4_TSF1)

enum pcf21xx_type {
	PCF2127,
	PCF2129,
	PCF2131,
	PCF21XX_LAST_ID
};

@@ -530,14 +588,18 @@ static void pcf2127_rtc_ts_snapshot(struct device *dev, int ts_id)
static irqreturn_t pcf2127_rtc_irq(int irq, void *dev)
{
	struct pcf2127 *pcf2127 = dev_get_drvdata(dev);
	unsigned int ctrl1, ctrl2;
	unsigned int ctrl2;
	int ret = 0;

	ret = regmap_read(pcf2127->regmap, PCF2127_REG_CTRL1, &ctrl1);
	ret = regmap_read(pcf2127->regmap, PCF2127_REG_CTRL2, &ctrl2);
	if (ret)
		return IRQ_NONE;

	ret = regmap_read(pcf2127->regmap, PCF2127_REG_CTRL2, &ctrl2);
	if (pcf2127->cfg->ts_count == 1) {
		/* PCF2127/29 */
		unsigned int ctrl1;

		ret = regmap_read(pcf2127->regmap, PCF2127_REG_CTRL1, &ctrl1);
		if (ret)
			return IRQ_NONE;

@@ -554,6 +616,36 @@ static irqreturn_t pcf2127_rtc_irq(int irq, void *dev)
		if (ctrl2 & PCF2127_CTRL2_IRQ_MASK)
			regmap_write(pcf2127->regmap, PCF2127_REG_CTRL2,
				     ctrl2 & ~PCF2127_CTRL2_IRQ_MASK);
	} else {
		/* PCF2131. */
		unsigned int ctrl4;

		ret = regmap_read(pcf2127->regmap, PCF2131_REG_CTRL4, &ctrl4);
		if (ret)
			return IRQ_NONE;

		if (!(ctrl4 & PCF2131_CTRL4_IRQ_MASK || ctrl2 & PCF2131_CTRL2_IRQ_MASK))
			return IRQ_NONE;

		if (ctrl4 & PCF2131_CTRL4_IRQ_MASK) {
			int i;
			int tsf_bit = PCF2131_BIT_CTRL4_TSF1; /* Start at bit 7. */

			for (i = 0; i < pcf2127->cfg->ts_count; i++) {
				if (ctrl4 & tsf_bit)
					pcf2127_rtc_ts_snapshot(dev, i);

				tsf_bit = tsf_bit >> 1;
			}

			regmap_write(pcf2127->regmap, PCF2131_REG_CTRL4,
				     ctrl4 & ~PCF2131_CTRL4_IRQ_MASK);
		}

		if (ctrl2 & PCF2131_CTRL2_IRQ_MASK)
			regmap_write(pcf2127->regmap, PCF2127_REG_CTRL2,
				     ctrl2 & ~PCF2131_CTRL2_IRQ_MASK);
	}

	if (ctrl2 & PCF2127_BIT_CTRL2_AF)
		rtc_update_irq(pcf2127->rtc, 1, RTC_IRQF | RTC_AF);
@@ -626,6 +718,27 @@ static ssize_t timestamp0_store(struct device *dev,
	return timestamp_store(dev, attr, buf, count, 0);
};

static ssize_t timestamp1_store(struct device *dev,
				struct device_attribute *attr,
				const char *buf, size_t count)
{
	return timestamp_store(dev, attr, buf, count, 1);
};

static ssize_t timestamp2_store(struct device *dev,
				struct device_attribute *attr,
				const char *buf, size_t count)
{
	return timestamp_store(dev, attr, buf, count, 2);
};

static ssize_t timestamp3_store(struct device *dev,
				struct device_attribute *attr,
				const char *buf, size_t count)
{
	return timestamp_store(dev, attr, buf, count, 3);
};

static ssize_t timestamp_show(struct device *dev,
			      struct device_attribute *attr, char *buf,
			      int ts_id)
@@ -690,13 +803,42 @@ static ssize_t timestamp0_show(struct device *dev,
	return timestamp_show(dev, attr, buf, 0);
};

static ssize_t timestamp1_show(struct device *dev,
			       struct device_attribute *attr, char *buf)
{
	return timestamp_show(dev, attr, buf, 1);
};

static ssize_t timestamp2_show(struct device *dev,
			       struct device_attribute *attr, char *buf)
{
	return timestamp_show(dev, attr, buf, 2);
};

static ssize_t timestamp3_show(struct device *dev,
			       struct device_attribute *attr, char *buf)
{
	return timestamp_show(dev, attr, buf, 3);
};

static DEVICE_ATTR_RW(timestamp0);
static DEVICE_ATTR_RW(timestamp1);
static DEVICE_ATTR_RW(timestamp2);
static DEVICE_ATTR_RW(timestamp3);

static struct attribute *pcf2127_attrs[] = {
	&dev_attr_timestamp0.attr,
	NULL
};

static struct attribute *pcf2131_attrs[] = {
	&dev_attr_timestamp0.attr,
	&dev_attr_timestamp1.attr,
	&dev_attr_timestamp2.attr,
	&dev_attr_timestamp3.attr,
	NULL
};

static struct pcf21xx_config pcf21xx_cfg[] = {
	[PCF2127] = {
		.type = PCF2127,
@@ -746,6 +888,53 @@ static struct pcf21xx_config pcf21xx_cfg[] = {
			.attrs	= pcf2127_attrs,
		},
	},
	[PCF2131] = {
		.type = PCF2131,
		.max_register = 0x36,
		.has_nvmem = 0,
		.has_bit_wd_ctl_cd0 = 0,
		.reg_time_base = PCF2131_REG_TIME_BASE,
		.regs_alarm_base = PCF2131_REG_ALARM_BASE,
		.reg_wd_ctl = PCF2131_REG_WD_CTL,
		.reg_wd_val = PCF2131_REG_WD_VAL,
		.reg_clkout = PCF2131_REG_CLKOUT,
		.ts_count = 4,
		.ts[0] = {
			.reg_base  = PCF2131_REG_TS1_BASE,
			.gnd_detect_reg = PCF2131_REG_CTRL4,
			.gnd_detect_bit = PCF2131_BIT_CTRL4_TSF1,
			.inter_detect_bit = 0,
			.ie_reg    = PCF2131_REG_CTRL5,
			.ie_bit    = PCF2131_BIT_CTRL5_TSIE1,
		},
		.ts[1] = {
			.reg_base  = PCF2131_REG_TS2_BASE,
			.gnd_detect_reg = PCF2131_REG_CTRL4,
			.gnd_detect_bit = PCF2131_BIT_CTRL4_TSF2,
			.inter_detect_bit = 0,
			.ie_reg    = PCF2131_REG_CTRL5,
			.ie_bit    = PCF2131_BIT_CTRL5_TSIE2,
		},
		.ts[2] = {
			.reg_base  = PCF2131_REG_TS3_BASE,
			.gnd_detect_reg = PCF2131_REG_CTRL4,
			.gnd_detect_bit = PCF2131_BIT_CTRL4_TSF3,
			.inter_detect_bit = 0,
			.ie_reg    = PCF2131_REG_CTRL5,
			.ie_bit    = PCF2131_BIT_CTRL5_TSIE3,
		},
		.ts[3] = {
			.reg_base  = PCF2131_REG_TS4_BASE,
			.gnd_detect_reg = PCF2131_REG_CTRL4,
			.gnd_detect_bit = PCF2131_BIT_CTRL4_TSF4,
			.inter_detect_bit = 0,
			.ie_reg    = PCF2131_REG_CTRL5,
			.ie_bit    = PCF2131_BIT_CTRL5_TSIE4,
		},
		.attribute_group = {
			.attrs	= pcf2131_attrs,
		},
	},
};

/*
@@ -893,7 +1082,7 @@ static int pcf2127_probe(struct device *dev, struct regmap *regmap,
	 * Watchdog timer enabled and reset pin /RST activated when timed out.
	 * Select 1Hz clock source for watchdog timer.
	 * Note: Countdown timer disabled and not available.
	 * For pca2129, pcf2129, only bit[7] is for Symbol WD_CD
	 * For pca2129, pcf2129 and pcf2131, only bit[7] is for Symbol WD_CD
	 * of register watchdg_tim_ctl. The bit[6] is labeled
	 * as T. Bits labeled as T must always be written with
	 * logic 0.
@@ -953,6 +1142,7 @@ static const struct of_device_id pcf2127_of_match[] = {
	{ .compatible = "nxp,pcf2127", .data = &pcf21xx_cfg[PCF2127] },
	{ .compatible = "nxp,pcf2129", .data = &pcf21xx_cfg[PCF2129] },
	{ .compatible = "nxp,pca2129", .data = &pcf21xx_cfg[PCF2129] },
	{ .compatible = "nxp,pcf2131", .data = &pcf21xx_cfg[PCF2131] },
	{}
};
MODULE_DEVICE_TABLE(of, pcf2127_of_match);
@@ -1040,6 +1230,7 @@ static const struct i2c_device_id pcf2127_i2c_id[] = {
	{ "pcf2127", PCF2127 },
	{ "pcf2129", PCF2129 },
	{ "pca2129", PCF2129 },
	{ "pcf2131", PCF2131 },
	{ }
};
MODULE_DEVICE_TABLE(i2c, pcf2127_i2c_id);
@@ -1161,6 +1352,7 @@ static const struct spi_device_id pcf2127_spi_id[] = {
	{ "pcf2127", PCF2127 },
	{ "pcf2129", PCF2129 },
	{ "pca2129", PCF2129 },
	{ "pcf2131", PCF2131 },
	{ }
};
MODULE_DEVICE_TABLE(spi, pcf2127_spi_id);
@@ -1225,5 +1417,5 @@ static void __exit pcf2127_exit(void)
module_exit(pcf2127_exit)

MODULE_AUTHOR("Renaud Cerrato <r.cerrato@til-technologies.fr>");
MODULE_DESCRIPTION("NXP PCF2127/29 RTC driver");
MODULE_DESCRIPTION("NXP PCF2127/29/31 RTC driver");
MODULE_LICENSE("GPL v2");