summaryrefslogtreecommitdiff
path: root/drivers/rtc/rtc-isl1208.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/rtc/rtc-isl1208.c')
-rw-r--r--drivers/rtc/rtc-isl1208.c289
1 files changed, 226 insertions, 63 deletions
diff --git a/drivers/rtc/rtc-isl1208.c b/drivers/rtc/rtc-isl1208.c
index 37ab3e1d25f5..f71a6bb77b2a 100644
--- a/drivers/rtc/rtc-isl1208.c
+++ b/drivers/rtc/rtc-isl1208.c
@@ -1,18 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Intersil ISL1208 rtc class driver
*
* Copyright 2005,2006 Hebert Valerio Riedel <hvr@gnu.org>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation; either version 2 of the License, or (at your
- * option) any later version.
- *
*/
#include <linux/bcd.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/module.h>
+#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/rtc.h>
@@ -72,11 +70,69 @@
static struct i2c_driver isl1208_driver;
-/* ISL1208 various variants */
-enum {
- TYPE_ISL1208 = 0,
- TYPE_ISL1218,
- TYPE_ISL1219,
+/* Chip capabilities table */
+struct isl1208_config {
+ unsigned int nvmem_length;
+ unsigned has_tamper:1;
+ unsigned has_timestamp:1;
+ unsigned has_inverted_osc_bit:1;
+};
+
+static const struct isl1208_config config_isl1208 = {
+ .nvmem_length = 2,
+ .has_tamper = false,
+ .has_timestamp = false
+};
+
+static const struct isl1208_config config_isl1209 = {
+ .nvmem_length = 2,
+ .has_tamper = true,
+ .has_timestamp = false
+};
+
+static const struct isl1208_config config_isl1218 = {
+ .nvmem_length = 8,
+ .has_tamper = false,
+ .has_timestamp = false
+};
+
+static const struct isl1208_config config_isl1219 = {
+ .nvmem_length = 2,
+ .has_tamper = true,
+ .has_timestamp = true
+};
+
+static const struct isl1208_config config_raa215300_a0 = {
+ .nvmem_length = 2,
+ .has_tamper = false,
+ .has_timestamp = false,
+ .has_inverted_osc_bit = true
+};
+
+static const struct i2c_device_id isl1208_id[] = {
+ { "isl1208", .driver_data = (kernel_ulong_t)&config_isl1208 },
+ { "isl1209", .driver_data = (kernel_ulong_t)&config_isl1209 },
+ { "isl1218", .driver_data = (kernel_ulong_t)&config_isl1218 },
+ { "isl1219", .driver_data = (kernel_ulong_t)&config_isl1219 },
+ { "raa215300_a0", .driver_data = (kernel_ulong_t)&config_raa215300_a0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, isl1208_id);
+
+static const __maybe_unused struct of_device_id isl1208_of_match[] = {
+ { .compatible = "isil,isl1208", .data = &config_isl1208 },
+ { .compatible = "isil,isl1209", .data = &config_isl1209 },
+ { .compatible = "isil,isl1218", .data = &config_isl1218 },
+ { .compatible = "isil,isl1219", .data = &config_isl1219 },
+ { }
+};
+MODULE_DEVICE_TABLE(of, isl1208_of_match);
+
+/* Device state */
+struct isl1208_state {
+ struct nvmem_config nvmem_config;
+ struct rtc_device *rtc;
+ const struct isl1208_config *config;
};
/* block read */
@@ -130,6 +186,20 @@ isl1208_i2c_validate_client(struct i2c_client *client)
return 0;
}
+static int isl1208_set_xtoscb(struct i2c_client *client, int sr, int xtosb_val)
+{
+ /* Do nothing if bit is already set to desired value */
+ if (!!(sr & ISL1208_REG_SR_XTOSCB) == xtosb_val)
+ return 0;
+
+ if (xtosb_val)
+ sr |= ISL1208_REG_SR_XTOSCB;
+ else
+ sr &= ~ISL1208_REG_SR_XTOSCB;
+
+ return i2c_smbus_write_byte_data(client, ISL1208_REG_SR, sr);
+}
+
static int
isl1208_i2c_get_sr(struct i2c_client *client)
{
@@ -161,6 +231,7 @@ isl1208_i2c_get_atr(struct i2c_client *client)
return atr;
}
+/* returns adjustment value + 100 */
static int
isl1208_i2c_get_dtr(struct i2c_client *client)
{
@@ -171,7 +242,7 @@ isl1208_i2c_get_dtr(struct i2c_client *client)
/* dtr encodes adjustments of {-60,-40,-20,0,20,40,60} ppm */
dtr = ((dtr & 0x3) * 20) * (dtr & (1 << 2) ? -1 : 1);
- return dtr;
+ return dtr + 100;
}
static int
@@ -248,8 +319,8 @@ isl1208_rtc_proc(struct device *dev, struct seq_file *seq)
(sr & ISL1208_REG_SR_RTCF) ? "bad" : "okay");
dtr = isl1208_i2c_get_dtr(client);
- if (dtr >= 0 - 1)
- seq_printf(seq, "digital_trim\t: %d ppm\n", dtr);
+ if (dtr >= 0)
+ seq_printf(seq, "digital_trim\t: %d ppm\n", dtr - 100);
atr = isl1208_i2c_get_atr(client);
if (atr >= 0)
@@ -465,7 +536,6 @@ isl1208_i2c_set_time(struct i2c_client *client, struct rtc_time const *tm)
return 0;
}
-
static int
isl1208_rtc_set_time(struct device *dev, struct rtc_time *tm)
{
@@ -556,9 +626,21 @@ isl1208_rtc_interrupt(int irq, void *data)
{
unsigned long timeout = jiffies + msecs_to_jiffies(1000);
struct i2c_client *client = data;
- struct rtc_device *rtc = i2c_get_clientdata(client);
+ struct isl1208_state *isl1208 = i2c_get_clientdata(client);
int handled = 0, sr, err;
+ if (!isl1208->config->has_tamper) {
+ /*
+ * The INT# output is pulled low 250ms after the alarm is
+ * triggered. After the INT# output is pulled low, it is low for
+ * at least 250ms, even if the correct action is taken to clear
+ * it. It is impossible to clear ALM if it is still active. The
+ * host must wait for the RTC to progress past the alarm time
+ * plus the 250ms delay before clearing ALM.
+ */
+ msleep(250);
+ }
+
/*
* I2C reads get NAK'ed if we read straight away after an interrupt?
* Using a mdelay/msleep didn't seem to help either, so we work around
@@ -579,7 +661,14 @@ isl1208_rtc_interrupt(int irq, void *data)
if (sr & ISL1208_REG_SR_ALM) {
dev_dbg(&client->dev, "alarm!\n");
- rtc_update_irq(rtc, 1, RTC_IRQF | RTC_AF);
+ rtc_update_irq(isl1208->rtc, 1, RTC_IRQF | RTC_AF);
+
+ /* Disable the alarm */
+ err = isl1208_rtc_toggle_alarm(client, 0);
+ if (err)
+ return err;
+
+ fsleep(275);
/* Clear the alarm */
sr &= ~ISL1208_REG_SR_ALM;
@@ -589,18 +678,14 @@ isl1208_rtc_interrupt(int irq, void *data)
__func__);
else
handled = 1;
-
- /* Disable the alarm */
- err = isl1208_rtc_toggle_alarm(client, 0);
- if (err)
- return err;
}
- if (sr & ISL1208_REG_SR_EVT) {
- sysfs_notify(&rtc->dev.kobj, NULL,
- dev_attr_timestamp0.attr.name);
+ if (isl1208->config->has_tamper && (sr & ISL1208_REG_SR_EVT)) {
dev_warn(&client->dev, "event detected");
handled = 1;
+ if (isl1208->config->has_timestamp)
+ sysfs_notify(&isl1208->rtc->dev.kobj, NULL,
+ dev_attr_timestamp0.attr.name);
}
return handled ? IRQ_HANDLED : IRQ_NONE;
@@ -637,7 +722,7 @@ isl1208_sysfs_show_dtrim(struct device *dev,
if (dtr < 0)
return dtr;
- return sprintf(buf, "%d ppm\n", dtr);
+ return sprintf(buf, "%d ppm\n", dtr - 100);
}
static DEVICE_ATTR(dtrim, S_IRUGO, isl1208_sysfs_show_dtrim, NULL);
@@ -700,6 +785,43 @@ static const struct attribute_group isl1219_rtc_sysfs_files = {
.attrs = isl1219_rtc_attrs,
};
+static int isl1208_nvmem_read(void *priv, unsigned int off, void *buf,
+ size_t count)
+{
+ struct isl1208_state *isl1208 = priv;
+ struct i2c_client *client = to_i2c_client(isl1208->rtc->dev.parent);
+
+ /* nvmem sanitizes offset/count for us, but count==0 is possible */
+ if (!count)
+ return count;
+
+ return isl1208_i2c_read_regs(client, ISL1208_REG_USR1 + off, buf,
+ count);
+}
+
+static int isl1208_nvmem_write(void *priv, unsigned int off, void *buf,
+ size_t count)
+{
+ struct isl1208_state *isl1208 = priv;
+ struct i2c_client *client = to_i2c_client(isl1208->rtc->dev.parent);
+
+ /* nvmem sanitizes off/count for us, but count==0 is possible */
+ if (!count)
+ return count;
+
+ return isl1208_i2c_set_regs(client, ISL1208_REG_USR1 + off, buf,
+ count);
+}
+
+static const struct nvmem_config isl1208_nvmem_config = {
+ .name = "isl1208_nvram",
+ .word_size = 1,
+ .stride = 1,
+ /* .size from chip specific config */
+ .reg_read = isl1208_nvmem_read,
+ .reg_write = isl1208_nvmem_write,
+};
+
static int isl1208_setup_irq(struct i2c_client *client, int irq)
{
int rc = devm_request_threaded_irq(&client->dev, irq, NULL,
@@ -708,7 +830,7 @@ static int isl1208_setup_irq(struct i2c_client *client, int irq)
isl1208_driver.driver.name,
client);
if (!rc) {
- device_init_wakeup(&client->dev, 1);
+ device_init_wakeup(&client->dev, true);
enable_irq_wake(irq);
} else {
dev_err(&client->dev,
@@ -719,11 +841,25 @@ static int isl1208_setup_irq(struct i2c_client *client, int irq)
}
static int
-isl1208_probe(struct i2c_client *client, const struct i2c_device_id *id)
+isl1208_clk_present(struct i2c_client *client, const char *name)
{
- int rc = 0;
- struct rtc_device *rtc;
+ struct clk *clk;
+
+ clk = devm_clk_get_optional(&client->dev, name);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ return !!clk;
+}
+
+static int
+isl1208_probe(struct i2c_client *client)
+{
+ struct isl1208_state *isl1208;
int evdet_irq = -1;
+ int xtosb_val = 0;
+ int rc = 0;
+ int sr;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
return -ENODEV;
@@ -731,25 +867,59 @@ isl1208_probe(struct i2c_client *client, const struct i2c_device_id *id)
if (isl1208_i2c_validate_client(client) < 0)
return -ENODEV;
- rtc = devm_rtc_allocate_device(&client->dev);
- if (IS_ERR(rtc))
- return PTR_ERR(rtc);
+ /* Allocate driver state, point i2c client data to it */
+ isl1208 = devm_kzalloc(&client->dev, sizeof(*isl1208), GFP_KERNEL);
+ if (!isl1208)
+ return -ENOMEM;
+ i2c_set_clientdata(client, isl1208);
- rtc->ops = &isl1208_rtc_ops;
+ /* Determine which chip we have */
+ isl1208->config = i2c_get_match_data(client);
+ if (!isl1208->config)
+ return -ENODEV;
- i2c_set_clientdata(client, rtc);
+ rc = isl1208_clk_present(client, "xin");
+ if (rc < 0)
+ return rc;
+
+ if (!rc) {
+ rc = isl1208_clk_present(client, "clkin");
+ if (rc < 0)
+ return rc;
+
+ if (rc)
+ xtosb_val = 1;
+ }
- rc = isl1208_i2c_get_sr(client);
- if (rc < 0) {
+ isl1208->rtc = devm_rtc_allocate_device(&client->dev);
+ if (IS_ERR(isl1208->rtc))
+ return PTR_ERR(isl1208->rtc);
+
+ isl1208->rtc->ops = &isl1208_rtc_ops;
+
+ /* Setup nvmem configuration in driver state struct */
+ isl1208->nvmem_config = isl1208_nvmem_config;
+ isl1208->nvmem_config.size = isl1208->config->nvmem_length;
+ isl1208->nvmem_config.priv = isl1208;
+
+ sr = isl1208_i2c_get_sr(client);
+ if (sr < 0) {
dev_err(&client->dev, "reading status failed\n");
- return rc;
+ return sr;
}
- if (rc & ISL1208_REG_SR_RTCF)
+ if (isl1208->config->has_inverted_osc_bit)
+ xtosb_val = !xtosb_val;
+
+ rc = isl1208_set_xtoscb(client, sr, xtosb_val);
+ if (rc)
+ return rc;
+
+ if (sr & ISL1208_REG_SR_RTCF)
dev_warn(&client->dev, "rtc power failure detected, "
"please set clock.\n");
- if (id->driver_data == TYPE_ISL1219) {
+ if (isl1208->config->has_tamper) {
struct device_node *np = client->dev.of_node;
u32 evienb;
@@ -770,44 +940,37 @@ isl1208_probe(struct i2c_client *client, const struct i2c_device_id *id)
dev_err(&client->dev, "could not enable tamper detection\n");
return rc;
}
- rc = rtc_add_group(rtc, &isl1219_rtc_sysfs_files);
+ evdet_irq = of_irq_get_byname(np, "evdet");
+ }
+ if (isl1208->config->has_timestamp) {
+ rc = rtc_add_group(isl1208->rtc, &isl1219_rtc_sysfs_files);
if (rc)
return rc;
- evdet_irq = of_irq_get_byname(np, "evdet");
}
- rc = rtc_add_group(rtc, &isl1208_rtc_sysfs_files);
+ rc = rtc_add_group(isl1208->rtc, &isl1208_rtc_sysfs_files);
if (rc)
return rc;
- if (client->irq > 0)
+ if (client->irq > 0) {
rc = isl1208_setup_irq(client, client->irq);
- if (rc)
- return rc;
+ if (rc)
+ return rc;
+ } else {
+ clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, isl1208->rtc->features);
+ }
if (evdet_irq > 0 && evdet_irq != client->irq)
rc = isl1208_setup_irq(client, evdet_irq);
if (rc)
return rc;
- return rtc_register_device(rtc);
-}
-
-static const struct i2c_device_id isl1208_id[] = {
- { "isl1208", TYPE_ISL1208 },
- { "isl1218", TYPE_ISL1218 },
- { "isl1219", TYPE_ISL1219 },
- { }
-};
-MODULE_DEVICE_TABLE(i2c, isl1208_id);
+ rc = devm_rtc_nvmem_register(isl1208->rtc, &isl1208->nvmem_config);
+ if (rc)
+ return rc;
-static const struct of_device_id isl1208_of_match[] = {
- { .compatible = "isil,isl1208" },
- { .compatible = "isil,isl1218" },
- { .compatible = "isil,isl1219" },
- { }
-};
-MODULE_DEVICE_TABLE(of, isl1208_of_match);
+ return devm_rtc_register_device(isl1208->rtc);
+}
static struct i2c_driver isl1208_driver = {
.driver = {