Commit 420cc9e8 authored by Hugo Villeneuve's avatar Hugo Villeneuve Committed by Alexandre Belloni

rtc: pcf2127: add support for multiple TS functions

This will simplify the implementation of new variants into this driver.
Signed-off-by: default avatarHugo Villeneuve <hvilleneuve@dimonoff.com>
Link: https://lore.kernel.org/r/20230622145800.2442116-11-hugo@hugovil.comSigned-off-by: default avatarAlexandre Belloni <alexandre.belloni@bootlin.com>
parent fc16599e
...@@ -59,8 +59,8 @@ ...@@ -59,8 +59,8 @@
#define PCF2127_BIT_WD_CTL_CD0 BIT(6) #define PCF2127_BIT_WD_CTL_CD0 BIT(6)
#define PCF2127_BIT_WD_CTL_CD1 BIT(7) #define PCF2127_BIT_WD_CTL_CD1 BIT(7)
#define PCF2127_REG_WD_VAL 0x11 #define PCF2127_REG_WD_VAL 0x11
/* Tamper timestamp registers */ /* Tamper timestamp1 registers */
#define PCF2127_REG_TS_CTRL 0x12 #define PCF2127_REG_TS1_BASE 0x12
#define PCF2127_BIT_TS_CTRL_TSOFF BIT(6) #define PCF2127_BIT_TS_CTRL_TSOFF BIT(6)
#define PCF2127_BIT_TS_CTRL_TSM BIT(7) #define PCF2127_BIT_TS_CTRL_TSM BIT(7)
/* /*
...@@ -86,12 +86,36 @@ ...@@ -86,12 +86,36 @@
PCF2127_BIT_CTRL2_WDTF | \ PCF2127_BIT_CTRL2_WDTF | \
PCF2127_BIT_CTRL2_TSF2) PCF2127_BIT_CTRL2_TSF2)
#define PCF2127_MAX_TS_SUPPORTED 1
enum pcf21xx_type { enum pcf21xx_type {
PCF2127, PCF2127,
PCF2129, PCF2129,
PCF21XX_LAST_ID PCF21XX_LAST_ID
}; };
struct pcf21xx_ts_config {
u8 reg_base; /* Base register to read timestamp values. */
/*
* If the TS input pin is driven to GND, an interrupt can be generated
* (supported by all variants).
*/
u8 gnd_detect_reg; /* Interrupt control register address. */
u8 gnd_detect_bit; /* Interrupt bit. */
/*
* If the TS input pin is driven to an intermediate level between GND
* and supply, an interrupt can be generated (optional feature depending
* on variant).
*/
u8 inter_detect_reg; /* Interrupt control register address. */
u8 inter_detect_bit; /* Interrupt bit. */
u8 ie_reg; /* Interrupt enable control register. */
u8 ie_bit; /* Interrupt enable bit. */
};
struct pcf21xx_config { struct pcf21xx_config {
int type; /* IC variant */ int type; /* IC variant */
int max_register; int max_register;
...@@ -102,6 +126,9 @@ struct pcf21xx_config { ...@@ -102,6 +126,9 @@ struct pcf21xx_config {
u8 reg_wd_ctl; /* Watchdog control register. */ u8 reg_wd_ctl; /* Watchdog control register. */
u8 reg_wd_val; /* Watchdog value register. */ u8 reg_wd_val; /* Watchdog value register. */
u8 reg_clkout; /* Clkout register. */ u8 reg_clkout; /* Clkout register. */
unsigned int ts_count;
struct pcf21xx_ts_config ts[PCF2127_MAX_TS_SUPPORTED];
struct attribute_group attribute_group;
}; };
struct pcf2127 { struct pcf2127 {
...@@ -109,9 +136,9 @@ struct pcf2127 { ...@@ -109,9 +136,9 @@ struct pcf2127 {
struct watchdog_device wdd; struct watchdog_device wdd;
struct regmap *regmap; struct regmap *regmap;
const struct pcf21xx_config *cfg; const struct pcf21xx_config *cfg;
time64_t ts;
bool ts_valid;
bool irq_enabled; bool irq_enabled;
time64_t ts[PCF2127_MAX_TS_SUPPORTED]; /* Timestamp values. */
bool ts_valid[PCF2127_MAX_TS_SUPPORTED]; /* Timestamp valid indication. */
}; };
/* /*
...@@ -441,18 +468,19 @@ static int pcf2127_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) ...@@ -441,18 +468,19 @@ static int pcf2127_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
} }
/* /*
* This function reads ctrl2 register, caller is responsible for calling * This function reads one timestamp function data, caller is responsible for
* pcf2127_wdt_active_ping() * calling pcf2127_wdt_active_ping()
*/ */
static int pcf2127_rtc_ts_read(struct device *dev, time64_t *ts) static int pcf2127_rtc_ts_read(struct device *dev, time64_t *ts,
int ts_id)
{ {
struct pcf2127 *pcf2127 = dev_get_drvdata(dev); struct pcf2127 *pcf2127 = dev_get_drvdata(dev);
struct rtc_time tm; struct rtc_time tm;
int ret; int ret;
unsigned char data[7]; unsigned char data[7];
ret = regmap_bulk_read(pcf2127->regmap, PCF2127_REG_TS_CTRL, data, ret = regmap_bulk_read(pcf2127->regmap, pcf2127->cfg->ts[ts_id].reg_base,
sizeof(data)); data, sizeof(data));
if (ret) { if (ret) {
dev_err(dev, "%s: read error ret=%d\n", __func__, ret); dev_err(dev, "%s: read error ret=%d\n", __func__, ret);
return ret; return ret;
...@@ -482,18 +510,21 @@ static int pcf2127_rtc_ts_read(struct device *dev, time64_t *ts) ...@@ -482,18 +510,21 @@ static int pcf2127_rtc_ts_read(struct device *dev, time64_t *ts)
return 0; return 0;
}; };
static void pcf2127_rtc_ts_snapshot(struct device *dev) static void pcf2127_rtc_ts_snapshot(struct device *dev, int ts_id)
{ {
struct pcf2127 *pcf2127 = dev_get_drvdata(dev); struct pcf2127 *pcf2127 = dev_get_drvdata(dev);
int ret; int ret;
if (ts_id >= pcf2127->cfg->ts_count)
return;
/* Let userspace read the first timestamp */ /* Let userspace read the first timestamp */
if (pcf2127->ts_valid) if (pcf2127->ts_valid[ts_id])
return; return;
ret = pcf2127_rtc_ts_read(dev, &pcf2127->ts); ret = pcf2127_rtc_ts_read(dev, &pcf2127->ts[ts_id], ts_id);
if (!ret) if (!ret)
pcf2127->ts_valid = true; pcf2127->ts_valid[ts_id] = true;
} }
static irqreturn_t pcf2127_rtc_irq(int irq, void *dev) static irqreturn_t pcf2127_rtc_irq(int irq, void *dev)
...@@ -514,7 +545,7 @@ static irqreturn_t pcf2127_rtc_irq(int irq, void *dev) ...@@ -514,7 +545,7 @@ static irqreturn_t pcf2127_rtc_irq(int irq, void *dev)
return IRQ_NONE; return IRQ_NONE;
if (ctrl1 & PCF2127_BIT_CTRL1_TSF1 || ctrl2 & PCF2127_BIT_CTRL2_TSF2) if (ctrl1 & PCF2127_BIT_CTRL1_TSF1 || ctrl2 & PCF2127_BIT_CTRL2_TSF2)
pcf2127_rtc_ts_snapshot(dev); pcf2127_rtc_ts_snapshot(dev, 0);
if (ctrl1 & PCF2127_CTRL1_IRQ_MASK) if (ctrl1 & PCF2127_CTRL1_IRQ_MASK)
regmap_write(pcf2127->regmap, PCF2127_REG_CTRL1, regmap_write(pcf2127->regmap, PCF2127_REG_CTRL1,
...@@ -543,28 +574,41 @@ static const struct rtc_class_ops pcf2127_rtc_ops = { ...@@ -543,28 +574,41 @@ static const struct rtc_class_ops pcf2127_rtc_ops = {
/* sysfs interface */ /* sysfs interface */
static ssize_t timestamp0_store(struct device *dev, static ssize_t timestamp_store(struct device *dev,
struct device_attribute *attr, struct device_attribute *attr,
const char *buf, size_t count) const char *buf, size_t count, int ts_id)
{ {
struct pcf2127 *pcf2127 = dev_get_drvdata(dev->parent); struct pcf2127 *pcf2127 = dev_get_drvdata(dev->parent);
int ret; int ret;
if (ts_id >= pcf2127->cfg->ts_count)
return 0;
if (pcf2127->irq_enabled) { if (pcf2127->irq_enabled) {
pcf2127->ts_valid = false; pcf2127->ts_valid[ts_id] = false;
} else { } else {
ret = regmap_update_bits(pcf2127->regmap, PCF2127_REG_CTRL1, /* Always clear GND interrupt bit. */
PCF2127_BIT_CTRL1_TSF1, 0); ret = regmap_update_bits(pcf2127->regmap,
pcf2127->cfg->ts[ts_id].gnd_detect_reg,
pcf2127->cfg->ts[ts_id].gnd_detect_bit,
0);
if (ret) { if (ret) {
dev_err(dev, "%s: update ctrl1 ret=%d\n", __func__, ret); dev_err(dev, "%s: update TS gnd detect ret=%d\n", __func__, ret);
return ret; return ret;
} }
ret = regmap_update_bits(pcf2127->regmap, PCF2127_REG_CTRL2, if (pcf2127->cfg->ts[ts_id].inter_detect_bit) {
PCF2127_BIT_CTRL2_TSF2, 0); /* Clear intermediate level interrupt bit if supported. */
if (ret) { ret = regmap_update_bits(pcf2127->regmap,
dev_err(dev, "%s: update ctrl2 ret=%d\n", __func__, ret); pcf2127->cfg->ts[ts_id].inter_detect_reg,
return ret; pcf2127->cfg->ts[ts_id].inter_detect_bit,
0);
if (ret) {
dev_err(dev, "%s: update TS intermediate level detect ret=%d\n",
__func__, ret);
return ret;
}
} }
ret = pcf2127_wdt_active_ping(&pcf2127->wdd); ret = pcf2127_wdt_active_ping(&pcf2127->wdd);
...@@ -573,34 +617,63 @@ static ssize_t timestamp0_store(struct device *dev, ...@@ -573,34 +617,63 @@ static ssize_t timestamp0_store(struct device *dev,
} }
return count; return count;
}
static ssize_t timestamp0_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
return timestamp_store(dev, attr, buf, count, 0);
}; };
static ssize_t timestamp0_show(struct device *dev, static ssize_t timestamp_show(struct device *dev,
struct device_attribute *attr, char *buf) struct device_attribute *attr, char *buf,
int ts_id)
{ {
struct pcf2127 *pcf2127 = dev_get_drvdata(dev->parent); struct pcf2127 *pcf2127 = dev_get_drvdata(dev->parent);
unsigned int ctrl1, ctrl2;
int ret; int ret;
time64_t ts; time64_t ts;
if (ts_id >= pcf2127->cfg->ts_count)
return 0;
if (pcf2127->irq_enabled) { if (pcf2127->irq_enabled) {
if (!pcf2127->ts_valid) if (!pcf2127->ts_valid[ts_id])
return 0; return 0;
ts = pcf2127->ts; ts = pcf2127->ts[ts_id];
} else { } else {
ret = regmap_read(pcf2127->regmap, PCF2127_REG_CTRL1, &ctrl1); u8 valid_low = 0;
if (ret) u8 valid_inter = 0;
return 0; unsigned int ctrl;
ret = regmap_read(pcf2127->regmap, PCF2127_REG_CTRL2, &ctrl2); /* Check if TS input pin is driven to GND, supported by all
* variants.
*/
ret = regmap_read(pcf2127->regmap,
pcf2127->cfg->ts[ts_id].gnd_detect_reg,
&ctrl);
if (ret) if (ret)
return 0; return 0;
if (!(ctrl1 & PCF2127_BIT_CTRL1_TSF1) && valid_low = ctrl & pcf2127->cfg->ts[ts_id].gnd_detect_bit;
!(ctrl2 & PCF2127_BIT_CTRL2_TSF2))
if (pcf2127->cfg->ts[ts_id].inter_detect_bit) {
/* Check if TS input pin is driven to intermediate level
* between GND and supply, if supported by variant.
*/
ret = regmap_read(pcf2127->regmap,
pcf2127->cfg->ts[ts_id].inter_detect_reg,
&ctrl);
if (ret)
return 0;
valid_inter = ctrl & pcf2127->cfg->ts[ts_id].inter_detect_bit;
}
if (!valid_low && !valid_inter)
return 0; return 0;
ret = pcf2127_rtc_ts_read(dev->parent, &ts); ret = pcf2127_rtc_ts_read(dev->parent, &ts, ts_id);
if (ret) if (ret)
return 0; return 0;
...@@ -609,6 +682,12 @@ static ssize_t timestamp0_show(struct device *dev, ...@@ -609,6 +682,12 @@ static ssize_t timestamp0_show(struct device *dev,
return ret; return ret;
} }
return sprintf(buf, "%llu\n", (unsigned long long)ts); return sprintf(buf, "%llu\n", (unsigned long long)ts);
}
static ssize_t timestamp0_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return timestamp_show(dev, attr, buf, 0);
}; };
static DEVICE_ATTR_RW(timestamp0); static DEVICE_ATTR_RW(timestamp0);
...@@ -618,10 +697,6 @@ static struct attribute *pcf2127_attrs[] = { ...@@ -618,10 +697,6 @@ static struct attribute *pcf2127_attrs[] = {
NULL NULL
}; };
static const struct attribute_group pcf2127_attr_group = {
.attrs = pcf2127_attrs,
};
static struct pcf21xx_config pcf21xx_cfg[] = { static struct pcf21xx_config pcf21xx_cfg[] = {
[PCF2127] = { [PCF2127] = {
.type = PCF2127, .type = PCF2127,
...@@ -633,6 +708,19 @@ static struct pcf21xx_config pcf21xx_cfg[] = { ...@@ -633,6 +708,19 @@ static struct pcf21xx_config pcf21xx_cfg[] = {
.reg_wd_ctl = PCF2127_REG_WD_CTL, .reg_wd_ctl = PCF2127_REG_WD_CTL,
.reg_wd_val = PCF2127_REG_WD_VAL, .reg_wd_val = PCF2127_REG_WD_VAL,
.reg_clkout = PCF2127_REG_CLKOUT, .reg_clkout = PCF2127_REG_CLKOUT,
.ts_count = 1,
.ts[0] = {
.reg_base = PCF2127_REG_TS1_BASE,
.gnd_detect_reg = PCF2127_REG_CTRL1,
.gnd_detect_bit = PCF2127_BIT_CTRL1_TSF1,
.inter_detect_reg = PCF2127_REG_CTRL2,
.inter_detect_bit = PCF2127_BIT_CTRL2_TSF2,
.ie_reg = PCF2127_REG_CTRL2,
.ie_bit = PCF2127_BIT_CTRL2_TSIE,
},
.attribute_group = {
.attrs = pcf2127_attrs,
},
}, },
[PCF2129] = { [PCF2129] = {
.type = PCF2129, .type = PCF2129,
...@@ -644,9 +732,74 @@ static struct pcf21xx_config pcf21xx_cfg[] = { ...@@ -644,9 +732,74 @@ static struct pcf21xx_config pcf21xx_cfg[] = {
.reg_wd_ctl = PCF2127_REG_WD_CTL, .reg_wd_ctl = PCF2127_REG_WD_CTL,
.reg_wd_val = PCF2127_REG_WD_VAL, .reg_wd_val = PCF2127_REG_WD_VAL,
.reg_clkout = PCF2127_REG_CLKOUT, .reg_clkout = PCF2127_REG_CLKOUT,
.ts_count = 1,
.ts[0] = {
.reg_base = PCF2127_REG_TS1_BASE,
.gnd_detect_reg = PCF2127_REG_CTRL1,
.gnd_detect_bit = PCF2127_BIT_CTRL1_TSF1,
.inter_detect_reg = PCF2127_REG_CTRL2,
.inter_detect_bit = PCF2127_BIT_CTRL2_TSF2,
.ie_reg = PCF2127_REG_CTRL2,
.ie_bit = PCF2127_BIT_CTRL2_TSIE,
},
.attribute_group = {
.attrs = pcf2127_attrs,
},
}, },
}; };
/*
* Enable timestamp function and corresponding interrupt(s).
*/
static int pcf2127_enable_ts(struct device *dev, int ts_id)
{
struct pcf2127 *pcf2127 = dev_get_drvdata(dev);
int ret;
if (ts_id >= pcf2127->cfg->ts_count) {
dev_err(dev, "%s: invalid tamper detection ID (%d)\n",
__func__, ts_id);
return -EINVAL;
}
/* Enable timestamp function. */
ret = regmap_update_bits(pcf2127->regmap,
pcf2127->cfg->ts[ts_id].reg_base,
PCF2127_BIT_TS_CTRL_TSOFF |
PCF2127_BIT_TS_CTRL_TSM,
PCF2127_BIT_TS_CTRL_TSM);
if (ret) {
dev_err(dev, "%s: tamper detection config (ts%d_ctrl) failed\n",
__func__, ts_id);
return ret;
}
/* TS input pin driven to GND detection is supported by all variants.
* Make sure that interrupt bit is defined.
*/
if (pcf2127->cfg->ts[ts_id].gnd_detect_bit == 0) {
dev_err(dev, "%s: tamper detection to GND configuration invalid\n",
__func__);
return ret;
}
/*
* Enable interrupt generation when TSF timestamp flag is set.
* Interrupt signals are open-drain outputs and can be left floating if
* unused.
*/
ret = regmap_update_bits(pcf2127->regmap, pcf2127->cfg->ts[ts_id].ie_reg,
pcf2127->cfg->ts[ts_id].ie_bit,
pcf2127->cfg->ts[ts_id].ie_bit);
if (ret) {
dev_err(dev, "%s: tamper detection TSIE%d config failed\n",
__func__, ts_id);
return ret;
}
return ret;
}
static int pcf2127_probe(struct device *dev, struct regmap *regmap, static int pcf2127_probe(struct device *dev, struct regmap *regmap,
int alarm_irq, const char *name, const struct pcf21xx_config *config) int alarm_irq, const char *name, const struct pcf21xx_config *config)
{ {
...@@ -777,34 +930,15 @@ static int pcf2127_probe(struct device *dev, struct regmap *regmap, ...@@ -777,34 +930,15 @@ static int pcf2127_probe(struct device *dev, struct regmap *regmap,
} }
/* /*
* Enable timestamp function and store timestamp of first trigger * Enable timestamp functions 1 to 4.
* event until TSF1 and TSF2 interrupt flags are cleared.
*/ */
ret = regmap_update_bits(pcf2127->regmap, PCF2127_REG_TS_CTRL, for (int i = 0; i < pcf2127->cfg->ts_count; i++) {
PCF2127_BIT_TS_CTRL_TSOFF | ret = pcf2127_enable_ts(dev, i);
PCF2127_BIT_TS_CTRL_TSM, if (ret)
PCF2127_BIT_TS_CTRL_TSM); return ret;
if (ret) {
dev_err(dev, "%s: tamper detection config (ts_ctrl) failed\n",
__func__);
return ret;
}
/*
* Enable interrupt generation when TSF1 or TSF2 timestamp flags
* are set. Interrupt signal is an open-drain output and can be
* left floating if unused.
*/
ret = regmap_update_bits(pcf2127->regmap, PCF2127_REG_CTRL2,
PCF2127_BIT_CTRL2_TSIE,
PCF2127_BIT_CTRL2_TSIE);
if (ret) {
dev_err(dev, "%s: tamper detection config (ctrl2) failed\n",
__func__);
return ret;
} }
ret = rtc_add_group(pcf2127->rtc, &pcf2127_attr_group); ret = rtc_add_group(pcf2127->rtc, &pcf2127->cfg->attribute_group);
if (ret) { if (ret) {
dev_err(dev, "%s: tamper sysfs registering failed\n", dev_err(dev, "%s: tamper sysfs registering failed\n",
__func__); __func__);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment