diff options
Diffstat (limited to 'drivers/rtc/rtc-isl1208.c')
| -rw-r--r-- | drivers/rtc/rtc-isl1208.c | 289 |
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 = { |
