mirror of
https://github.com/torvalds/linux
synced 2024-11-05 18:23:50 +00:00
2023c5c8fe
If an ISL1208 device does not have an interrupt line routed, the feature shouldn't be advertised (it is by default in rtc core) or it'll confuse userspace requesting that feature (such as hwclock from util-linux). Signed-off-by: Quentin Schulz <quentin.schulz@theobroma-systems.com> Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com> Link: https://lore.kernel.org/r/20220523145320.123713-1-foss+kernel@0leil.net
917 lines
23 KiB
C
917 lines
23 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Intersil ISL1208 rtc class driver
|
|
*
|
|
* Copyright 2005,2006 Hebert Valerio Riedel <hvr@gnu.org>
|
|
*/
|
|
|
|
#include <linux/bcd.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/rtc.h>
|
|
|
|
/* Register map */
|
|
/* rtc section */
|
|
#define ISL1208_REG_SC 0x00
|
|
#define ISL1208_REG_MN 0x01
|
|
#define ISL1208_REG_HR 0x02
|
|
#define ISL1208_REG_HR_MIL (1<<7) /* 24h/12h mode */
|
|
#define ISL1208_REG_HR_PM (1<<5) /* PM/AM bit in 12h mode */
|
|
#define ISL1208_REG_DT 0x03
|
|
#define ISL1208_REG_MO 0x04
|
|
#define ISL1208_REG_YR 0x05
|
|
#define ISL1208_REG_DW 0x06
|
|
#define ISL1208_RTC_SECTION_LEN 7
|
|
|
|
/* control/status section */
|
|
#define ISL1208_REG_SR 0x07
|
|
#define ISL1208_REG_SR_ARST (1<<7) /* auto reset */
|
|
#define ISL1208_REG_SR_XTOSCB (1<<6) /* crystal oscillator */
|
|
#define ISL1208_REG_SR_WRTC (1<<4) /* write rtc */
|
|
#define ISL1208_REG_SR_EVT (1<<3) /* event */
|
|
#define ISL1208_REG_SR_ALM (1<<2) /* alarm */
|
|
#define ISL1208_REG_SR_BAT (1<<1) /* battery */
|
|
#define ISL1208_REG_SR_RTCF (1<<0) /* rtc fail */
|
|
#define ISL1208_REG_INT 0x08
|
|
#define ISL1208_REG_INT_ALME (1<<6) /* alarm enable */
|
|
#define ISL1208_REG_INT_IM (1<<7) /* interrupt/alarm mode */
|
|
#define ISL1219_REG_EV 0x09
|
|
#define ISL1219_REG_EV_EVEN (1<<4) /* event detection enable */
|
|
#define ISL1219_REG_EV_EVIENB (1<<7) /* event in pull-up disable */
|
|
#define ISL1208_REG_ATR 0x0a
|
|
#define ISL1208_REG_DTR 0x0b
|
|
|
|
/* alarm section */
|
|
#define ISL1208_REG_SCA 0x0c
|
|
#define ISL1208_REG_MNA 0x0d
|
|
#define ISL1208_REG_HRA 0x0e
|
|
#define ISL1208_REG_DTA 0x0f
|
|
#define ISL1208_REG_MOA 0x10
|
|
#define ISL1208_REG_DWA 0x11
|
|
#define ISL1208_ALARM_SECTION_LEN 6
|
|
|
|
/* user section */
|
|
#define ISL1208_REG_USR1 0x12
|
|
#define ISL1208_REG_USR2 0x13
|
|
#define ISL1208_USR_SECTION_LEN 2
|
|
|
|
/* event section */
|
|
#define ISL1219_REG_SCT 0x14
|
|
#define ISL1219_REG_MNT 0x15
|
|
#define ISL1219_REG_HRT 0x16
|
|
#define ISL1219_REG_DTT 0x17
|
|
#define ISL1219_REG_MOT 0x18
|
|
#define ISL1219_REG_YRT 0x19
|
|
#define ISL1219_EVT_SECTION_LEN 6
|
|
|
|
static struct i2c_driver isl1208_driver;
|
|
|
|
/* ISL1208 various variants */
|
|
enum isl1208_id {
|
|
TYPE_ISL1208 = 0,
|
|
TYPE_ISL1209,
|
|
TYPE_ISL1218,
|
|
TYPE_ISL1219,
|
|
ISL_LAST_ID
|
|
};
|
|
|
|
/* Chip capabilities table */
|
|
static const struct isl1208_config {
|
|
const char name[8];
|
|
unsigned int nvmem_length;
|
|
unsigned has_tamper:1;
|
|
unsigned has_timestamp:1;
|
|
} isl1208_configs[] = {
|
|
[TYPE_ISL1208] = { "isl1208", 2, false, false },
|
|
[TYPE_ISL1209] = { "isl1209", 2, true, false },
|
|
[TYPE_ISL1218] = { "isl1218", 8, false, false },
|
|
[TYPE_ISL1219] = { "isl1219", 2, true, true },
|
|
};
|
|
|
|
static const struct i2c_device_id isl1208_id[] = {
|
|
{ "isl1208", TYPE_ISL1208 },
|
|
{ "isl1209", TYPE_ISL1209 },
|
|
{ "isl1218", TYPE_ISL1218 },
|
|
{ "isl1219", TYPE_ISL1219 },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, isl1208_id);
|
|
|
|
static const __maybe_unused struct of_device_id isl1208_of_match[] = {
|
|
{ .compatible = "isil,isl1208", .data = &isl1208_configs[TYPE_ISL1208] },
|
|
{ .compatible = "isil,isl1209", .data = &isl1208_configs[TYPE_ISL1209] },
|
|
{ .compatible = "isil,isl1218", .data = &isl1208_configs[TYPE_ISL1218] },
|
|
{ .compatible = "isil,isl1219", .data = &isl1208_configs[TYPE_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 */
|
|
static int
|
|
isl1208_i2c_read_regs(struct i2c_client *client, u8 reg, u8 buf[],
|
|
unsigned len)
|
|
{
|
|
int ret;
|
|
|
|
WARN_ON(reg > ISL1219_REG_YRT);
|
|
WARN_ON(reg + len > ISL1219_REG_YRT + 1);
|
|
|
|
ret = i2c_smbus_read_i2c_block_data(client, reg, len, buf);
|
|
return (ret < 0) ? ret : 0;
|
|
}
|
|
|
|
/* block write */
|
|
static int
|
|
isl1208_i2c_set_regs(struct i2c_client *client, u8 reg, u8 const buf[],
|
|
unsigned len)
|
|
{
|
|
int ret;
|
|
|
|
WARN_ON(reg > ISL1219_REG_YRT);
|
|
WARN_ON(reg + len > ISL1219_REG_YRT + 1);
|
|
|
|
ret = i2c_smbus_write_i2c_block_data(client, reg, len, buf);
|
|
return (ret < 0) ? ret : 0;
|
|
}
|
|
|
|
/* simple check to see whether we have a isl1208 */
|
|
static int
|
|
isl1208_i2c_validate_client(struct i2c_client *client)
|
|
{
|
|
u8 regs[ISL1208_RTC_SECTION_LEN] = { 0, };
|
|
u8 zero_mask[ISL1208_RTC_SECTION_LEN] = {
|
|
0x80, 0x80, 0x40, 0xc0, 0xe0, 0x00, 0xf8
|
|
};
|
|
int i;
|
|
int ret;
|
|
|
|
ret = isl1208_i2c_read_regs(client, 0, regs, ISL1208_RTC_SECTION_LEN);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
for (i = 0; i < ISL1208_RTC_SECTION_LEN; ++i) {
|
|
if (regs[i] & zero_mask[i]) /* check if bits are cleared */
|
|
return -ENODEV;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
isl1208_i2c_get_sr(struct i2c_client *client)
|
|
{
|
|
return i2c_smbus_read_byte_data(client, ISL1208_REG_SR);
|
|
}
|
|
|
|
static int
|
|
isl1208_i2c_get_atr(struct i2c_client *client)
|
|
{
|
|
int atr = i2c_smbus_read_byte_data(client, ISL1208_REG_ATR);
|
|
if (atr < 0)
|
|
return atr;
|
|
|
|
/* The 6bit value in the ATR register controls the load
|
|
* capacitance C_load * in steps of 0.25pF
|
|
*
|
|
* bit (1<<5) of the ATR register is inverted
|
|
*
|
|
* C_load(ATR=0x20) = 4.50pF
|
|
* C_load(ATR=0x00) = 12.50pF
|
|
* C_load(ATR=0x1f) = 20.25pF
|
|
*
|
|
*/
|
|
|
|
atr &= 0x3f; /* mask out lsb */
|
|
atr ^= 1 << 5; /* invert 6th bit */
|
|
atr += 2 * 9; /* add offset of 4.5pF; unit[atr] = 0.25pF */
|
|
|
|
return atr;
|
|
}
|
|
|
|
/* returns adjustment value + 100 */
|
|
static int
|
|
isl1208_i2c_get_dtr(struct i2c_client *client)
|
|
{
|
|
int dtr = i2c_smbus_read_byte_data(client, ISL1208_REG_DTR);
|
|
if (dtr < 0)
|
|
return -EIO;
|
|
|
|
/* dtr encodes adjustments of {-60,-40,-20,0,20,40,60} ppm */
|
|
dtr = ((dtr & 0x3) * 20) * (dtr & (1 << 2) ? -1 : 1);
|
|
|
|
return dtr + 100;
|
|
}
|
|
|
|
static int
|
|
isl1208_i2c_get_usr(struct i2c_client *client)
|
|
{
|
|
u8 buf[ISL1208_USR_SECTION_LEN] = { 0, };
|
|
int ret;
|
|
|
|
ret = isl1208_i2c_read_regs(client, ISL1208_REG_USR1, buf,
|
|
ISL1208_USR_SECTION_LEN);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return (buf[1] << 8) | buf[0];
|
|
}
|
|
|
|
static int
|
|
isl1208_i2c_set_usr(struct i2c_client *client, u16 usr)
|
|
{
|
|
u8 buf[ISL1208_USR_SECTION_LEN];
|
|
|
|
buf[0] = usr & 0xff;
|
|
buf[1] = (usr >> 8) & 0xff;
|
|
|
|
return isl1208_i2c_set_regs(client, ISL1208_REG_USR1, buf,
|
|
ISL1208_USR_SECTION_LEN);
|
|
}
|
|
|
|
static int
|
|
isl1208_rtc_toggle_alarm(struct i2c_client *client, int enable)
|
|
{
|
|
int icr = i2c_smbus_read_byte_data(client, ISL1208_REG_INT);
|
|
|
|
if (icr < 0) {
|
|
dev_err(&client->dev, "%s: reading INT failed\n", __func__);
|
|
return icr;
|
|
}
|
|
|
|
if (enable)
|
|
icr |= ISL1208_REG_INT_ALME | ISL1208_REG_INT_IM;
|
|
else
|
|
icr &= ~(ISL1208_REG_INT_ALME | ISL1208_REG_INT_IM);
|
|
|
|
icr = i2c_smbus_write_byte_data(client, ISL1208_REG_INT, icr);
|
|
if (icr < 0) {
|
|
dev_err(&client->dev, "%s: writing INT failed\n", __func__);
|
|
return icr;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
isl1208_rtc_proc(struct device *dev, struct seq_file *seq)
|
|
{
|
|
struct i2c_client *const client = to_i2c_client(dev);
|
|
int sr, dtr, atr, usr;
|
|
|
|
sr = isl1208_i2c_get_sr(client);
|
|
if (sr < 0) {
|
|
dev_err(&client->dev, "%s: reading SR failed\n", __func__);
|
|
return sr;
|
|
}
|
|
|
|
seq_printf(seq, "status_reg\t:%s%s%s%s%s%s (0x%.2x)\n",
|
|
(sr & ISL1208_REG_SR_RTCF) ? " RTCF" : "",
|
|
(sr & ISL1208_REG_SR_BAT) ? " BAT" : "",
|
|
(sr & ISL1208_REG_SR_ALM) ? " ALM" : "",
|
|
(sr & ISL1208_REG_SR_WRTC) ? " WRTC" : "",
|
|
(sr & ISL1208_REG_SR_XTOSCB) ? " XTOSCB" : "",
|
|
(sr & ISL1208_REG_SR_ARST) ? " ARST" : "", sr);
|
|
|
|
seq_printf(seq, "batt_status\t: %s\n",
|
|
(sr & ISL1208_REG_SR_RTCF) ? "bad" : "okay");
|
|
|
|
dtr = isl1208_i2c_get_dtr(client);
|
|
if (dtr >= 0)
|
|
seq_printf(seq, "digital_trim\t: %d ppm\n", dtr - 100);
|
|
|
|
atr = isl1208_i2c_get_atr(client);
|
|
if (atr >= 0)
|
|
seq_printf(seq, "analog_trim\t: %d.%.2d pF\n",
|
|
atr >> 2, (atr & 0x3) * 25);
|
|
|
|
usr = isl1208_i2c_get_usr(client);
|
|
if (usr >= 0)
|
|
seq_printf(seq, "user_data\t: 0x%.4x\n", usr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
isl1208_i2c_read_time(struct i2c_client *client, struct rtc_time *tm)
|
|
{
|
|
int sr;
|
|
u8 regs[ISL1208_RTC_SECTION_LEN] = { 0, };
|
|
|
|
sr = isl1208_i2c_get_sr(client);
|
|
if (sr < 0) {
|
|
dev_err(&client->dev, "%s: reading SR failed\n", __func__);
|
|
return -EIO;
|
|
}
|
|
|
|
sr = isl1208_i2c_read_regs(client, 0, regs, ISL1208_RTC_SECTION_LEN);
|
|
if (sr < 0) {
|
|
dev_err(&client->dev, "%s: reading RTC section failed\n",
|
|
__func__);
|
|
return sr;
|
|
}
|
|
|
|
tm->tm_sec = bcd2bin(regs[ISL1208_REG_SC]);
|
|
tm->tm_min = bcd2bin(regs[ISL1208_REG_MN]);
|
|
|
|
/* HR field has a more complex interpretation */
|
|
{
|
|
const u8 _hr = regs[ISL1208_REG_HR];
|
|
if (_hr & ISL1208_REG_HR_MIL) /* 24h format */
|
|
tm->tm_hour = bcd2bin(_hr & 0x3f);
|
|
else {
|
|
/* 12h format */
|
|
tm->tm_hour = bcd2bin(_hr & 0x1f);
|
|
if (_hr & ISL1208_REG_HR_PM) /* PM flag set */
|
|
tm->tm_hour += 12;
|
|
}
|
|
}
|
|
|
|
tm->tm_mday = bcd2bin(regs[ISL1208_REG_DT]);
|
|
tm->tm_mon = bcd2bin(regs[ISL1208_REG_MO]) - 1; /* rtc starts at 1 */
|
|
tm->tm_year = bcd2bin(regs[ISL1208_REG_YR]) + 100;
|
|
tm->tm_wday = bcd2bin(regs[ISL1208_REG_DW]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
isl1208_i2c_read_alarm(struct i2c_client *client, struct rtc_wkalrm *alarm)
|
|
{
|
|
struct rtc_time *const tm = &alarm->time;
|
|
u8 regs[ISL1208_ALARM_SECTION_LEN] = { 0, };
|
|
int icr, yr, sr = isl1208_i2c_get_sr(client);
|
|
|
|
if (sr < 0) {
|
|
dev_err(&client->dev, "%s: reading SR failed\n", __func__);
|
|
return sr;
|
|
}
|
|
|
|
sr = isl1208_i2c_read_regs(client, ISL1208_REG_SCA, regs,
|
|
ISL1208_ALARM_SECTION_LEN);
|
|
if (sr < 0) {
|
|
dev_err(&client->dev, "%s: reading alarm section failed\n",
|
|
__func__);
|
|
return sr;
|
|
}
|
|
|
|
/* MSB of each alarm register is an enable bit */
|
|
tm->tm_sec = bcd2bin(regs[ISL1208_REG_SCA - ISL1208_REG_SCA] & 0x7f);
|
|
tm->tm_min = bcd2bin(regs[ISL1208_REG_MNA - ISL1208_REG_SCA] & 0x7f);
|
|
tm->tm_hour = bcd2bin(regs[ISL1208_REG_HRA - ISL1208_REG_SCA] & 0x3f);
|
|
tm->tm_mday = bcd2bin(regs[ISL1208_REG_DTA - ISL1208_REG_SCA] & 0x3f);
|
|
tm->tm_mon =
|
|
bcd2bin(regs[ISL1208_REG_MOA - ISL1208_REG_SCA] & 0x1f) - 1;
|
|
tm->tm_wday = bcd2bin(regs[ISL1208_REG_DWA - ISL1208_REG_SCA] & 0x03);
|
|
|
|
/* The alarm doesn't store the year so get it from the rtc section */
|
|
yr = i2c_smbus_read_byte_data(client, ISL1208_REG_YR);
|
|
if (yr < 0) {
|
|
dev_err(&client->dev, "%s: reading RTC YR failed\n", __func__);
|
|
return yr;
|
|
}
|
|
tm->tm_year = bcd2bin(yr) + 100;
|
|
|
|
icr = i2c_smbus_read_byte_data(client, ISL1208_REG_INT);
|
|
if (icr < 0) {
|
|
dev_err(&client->dev, "%s: reading INT failed\n", __func__);
|
|
return icr;
|
|
}
|
|
alarm->enabled = !!(icr & ISL1208_REG_INT_ALME);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
isl1208_i2c_set_alarm(struct i2c_client *client, struct rtc_wkalrm *alarm)
|
|
{
|
|
struct rtc_time *alarm_tm = &alarm->time;
|
|
u8 regs[ISL1208_ALARM_SECTION_LEN] = { 0, };
|
|
const int offs = ISL1208_REG_SCA;
|
|
struct rtc_time rtc_tm;
|
|
int err, enable;
|
|
|
|
err = isl1208_i2c_read_time(client, &rtc_tm);
|
|
if (err)
|
|
return err;
|
|
|
|
/* If the alarm time is before the current time disable the alarm */
|
|
if (!alarm->enabled || rtc_tm_sub(alarm_tm, &rtc_tm) <= 0)
|
|
enable = 0x00;
|
|
else
|
|
enable = 0x80;
|
|
|
|
/* Program the alarm and enable it for each setting */
|
|
regs[ISL1208_REG_SCA - offs] = bin2bcd(alarm_tm->tm_sec) | enable;
|
|
regs[ISL1208_REG_MNA - offs] = bin2bcd(alarm_tm->tm_min) | enable;
|
|
regs[ISL1208_REG_HRA - offs] = bin2bcd(alarm_tm->tm_hour) |
|
|
ISL1208_REG_HR_MIL | enable;
|
|
|
|
regs[ISL1208_REG_DTA - offs] = bin2bcd(alarm_tm->tm_mday) | enable;
|
|
regs[ISL1208_REG_MOA - offs] = bin2bcd(alarm_tm->tm_mon + 1) | enable;
|
|
regs[ISL1208_REG_DWA - offs] = bin2bcd(alarm_tm->tm_wday & 7) | enable;
|
|
|
|
/* write ALARM registers */
|
|
err = isl1208_i2c_set_regs(client, offs, regs,
|
|
ISL1208_ALARM_SECTION_LEN);
|
|
if (err < 0) {
|
|
dev_err(&client->dev, "%s: writing ALARM section failed\n",
|
|
__func__);
|
|
return err;
|
|
}
|
|
|
|
err = isl1208_rtc_toggle_alarm(client, enable);
|
|
if (err)
|
|
return err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
isl1208_rtc_read_time(struct device *dev, struct rtc_time *tm)
|
|
{
|
|
return isl1208_i2c_read_time(to_i2c_client(dev), tm);
|
|
}
|
|
|
|
static int
|
|
isl1208_i2c_set_time(struct i2c_client *client, struct rtc_time const *tm)
|
|
{
|
|
int sr;
|
|
u8 regs[ISL1208_RTC_SECTION_LEN] = { 0, };
|
|
|
|
/* The clock has an 8 bit wide bcd-coded register (they never learn)
|
|
* for the year. tm_year is an offset from 1900 and we are interested
|
|
* in the 2000-2099 range, so any value less than 100 is invalid.
|
|
*/
|
|
if (tm->tm_year < 100)
|
|
return -EINVAL;
|
|
|
|
regs[ISL1208_REG_SC] = bin2bcd(tm->tm_sec);
|
|
regs[ISL1208_REG_MN] = bin2bcd(tm->tm_min);
|
|
regs[ISL1208_REG_HR] = bin2bcd(tm->tm_hour) | ISL1208_REG_HR_MIL;
|
|
|
|
regs[ISL1208_REG_DT] = bin2bcd(tm->tm_mday);
|
|
regs[ISL1208_REG_MO] = bin2bcd(tm->tm_mon + 1);
|
|
regs[ISL1208_REG_YR] = bin2bcd(tm->tm_year - 100);
|
|
|
|
regs[ISL1208_REG_DW] = bin2bcd(tm->tm_wday & 7);
|
|
|
|
sr = isl1208_i2c_get_sr(client);
|
|
if (sr < 0) {
|
|
dev_err(&client->dev, "%s: reading SR failed\n", __func__);
|
|
return sr;
|
|
}
|
|
|
|
/* set WRTC */
|
|
sr = i2c_smbus_write_byte_data(client, ISL1208_REG_SR,
|
|
sr | ISL1208_REG_SR_WRTC);
|
|
if (sr < 0) {
|
|
dev_err(&client->dev, "%s: writing SR failed\n", __func__);
|
|
return sr;
|
|
}
|
|
|
|
/* write RTC registers */
|
|
sr = isl1208_i2c_set_regs(client, 0, regs, ISL1208_RTC_SECTION_LEN);
|
|
if (sr < 0) {
|
|
dev_err(&client->dev, "%s: writing RTC section failed\n",
|
|
__func__);
|
|
return sr;
|
|
}
|
|
|
|
/* clear WRTC again */
|
|
sr = isl1208_i2c_get_sr(client);
|
|
if (sr < 0) {
|
|
dev_err(&client->dev, "%s: reading SR failed\n", __func__);
|
|
return sr;
|
|
}
|
|
sr = i2c_smbus_write_byte_data(client, ISL1208_REG_SR,
|
|
sr & ~ISL1208_REG_SR_WRTC);
|
|
if (sr < 0) {
|
|
dev_err(&client->dev, "%s: writing SR failed\n", __func__);
|
|
return sr;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
isl1208_rtc_set_time(struct device *dev, struct rtc_time *tm)
|
|
{
|
|
return isl1208_i2c_set_time(to_i2c_client(dev), tm);
|
|
}
|
|
|
|
static int
|
|
isl1208_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
|
|
{
|
|
return isl1208_i2c_read_alarm(to_i2c_client(dev), alarm);
|
|
}
|
|
|
|
static int
|
|
isl1208_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
|
|
{
|
|
return isl1208_i2c_set_alarm(to_i2c_client(dev), alarm);
|
|
}
|
|
|
|
static ssize_t timestamp0_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev->parent);
|
|
int sr;
|
|
|
|
sr = isl1208_i2c_get_sr(client);
|
|
if (sr < 0) {
|
|
dev_err(dev, "%s: reading SR failed\n", __func__);
|
|
return sr;
|
|
}
|
|
|
|
sr &= ~ISL1208_REG_SR_EVT;
|
|
|
|
sr = i2c_smbus_write_byte_data(client, ISL1208_REG_SR, sr);
|
|
if (sr < 0)
|
|
dev_err(dev, "%s: writing SR failed\n",
|
|
__func__);
|
|
|
|
return count;
|
|
};
|
|
|
|
static ssize_t timestamp0_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev->parent);
|
|
u8 regs[ISL1219_EVT_SECTION_LEN] = { 0, };
|
|
struct rtc_time tm;
|
|
int sr;
|
|
|
|
sr = isl1208_i2c_get_sr(client);
|
|
if (sr < 0) {
|
|
dev_err(dev, "%s: reading SR failed\n", __func__);
|
|
return sr;
|
|
}
|
|
|
|
if (!(sr & ISL1208_REG_SR_EVT))
|
|
return 0;
|
|
|
|
sr = isl1208_i2c_read_regs(client, ISL1219_REG_SCT, regs,
|
|
ISL1219_EVT_SECTION_LEN);
|
|
if (sr < 0) {
|
|
dev_err(dev, "%s: reading event section failed\n",
|
|
__func__);
|
|
return 0;
|
|
}
|
|
|
|
/* MSB of each alarm register is an enable bit */
|
|
tm.tm_sec = bcd2bin(regs[ISL1219_REG_SCT - ISL1219_REG_SCT] & 0x7f);
|
|
tm.tm_min = bcd2bin(regs[ISL1219_REG_MNT - ISL1219_REG_SCT] & 0x7f);
|
|
tm.tm_hour = bcd2bin(regs[ISL1219_REG_HRT - ISL1219_REG_SCT] & 0x3f);
|
|
tm.tm_mday = bcd2bin(regs[ISL1219_REG_DTT - ISL1219_REG_SCT] & 0x3f);
|
|
tm.tm_mon =
|
|
bcd2bin(regs[ISL1219_REG_MOT - ISL1219_REG_SCT] & 0x1f) - 1;
|
|
tm.tm_year = bcd2bin(regs[ISL1219_REG_YRT - ISL1219_REG_SCT]) + 100;
|
|
|
|
sr = rtc_valid_tm(&tm);
|
|
if (sr)
|
|
return sr;
|
|
|
|
return sprintf(buf, "%llu\n",
|
|
(unsigned long long)rtc_tm_to_time64(&tm));
|
|
};
|
|
|
|
static DEVICE_ATTR_RW(timestamp0);
|
|
|
|
static irqreturn_t
|
|
isl1208_rtc_interrupt(int irq, void *data)
|
|
{
|
|
unsigned long timeout = jiffies + msecs_to_jiffies(1000);
|
|
struct i2c_client *client = data;
|
|
struct isl1208_state *isl1208 = i2c_get_clientdata(client);
|
|
int handled = 0, sr, err;
|
|
|
|
/*
|
|
* 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
|
|
* this by continually trying to read the register for a short time.
|
|
*/
|
|
while (1) {
|
|
sr = isl1208_i2c_get_sr(client);
|
|
if (sr >= 0)
|
|
break;
|
|
|
|
if (time_after(jiffies, timeout)) {
|
|
dev_err(&client->dev, "%s: reading SR failed\n",
|
|
__func__);
|
|
return sr;
|
|
}
|
|
}
|
|
|
|
if (sr & ISL1208_REG_SR_ALM) {
|
|
dev_dbg(&client->dev, "alarm!\n");
|
|
|
|
rtc_update_irq(isl1208->rtc, 1, RTC_IRQF | RTC_AF);
|
|
|
|
/* Clear the alarm */
|
|
sr &= ~ISL1208_REG_SR_ALM;
|
|
sr = i2c_smbus_write_byte_data(client, ISL1208_REG_SR, sr);
|
|
if (sr < 0)
|
|
dev_err(&client->dev, "%s: writing SR failed\n",
|
|
__func__);
|
|
else
|
|
handled = 1;
|
|
|
|
/* Disable the alarm */
|
|
err = isl1208_rtc_toggle_alarm(client, 0);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static const struct rtc_class_ops isl1208_rtc_ops = {
|
|
.proc = isl1208_rtc_proc,
|
|
.read_time = isl1208_rtc_read_time,
|
|
.set_time = isl1208_rtc_set_time,
|
|
.read_alarm = isl1208_rtc_read_alarm,
|
|
.set_alarm = isl1208_rtc_set_alarm,
|
|
};
|
|
|
|
/* sysfs interface */
|
|
|
|
static ssize_t
|
|
isl1208_sysfs_show_atrim(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
int atr = isl1208_i2c_get_atr(to_i2c_client(dev->parent));
|
|
if (atr < 0)
|
|
return atr;
|
|
|
|
return sprintf(buf, "%d.%.2d pF\n", atr >> 2, (atr & 0x3) * 25);
|
|
}
|
|
|
|
static DEVICE_ATTR(atrim, S_IRUGO, isl1208_sysfs_show_atrim, NULL);
|
|
|
|
static ssize_t
|
|
isl1208_sysfs_show_dtrim(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
int dtr = isl1208_i2c_get_dtr(to_i2c_client(dev->parent));
|
|
if (dtr < 0)
|
|
return dtr;
|
|
|
|
return sprintf(buf, "%d ppm\n", dtr - 100);
|
|
}
|
|
|
|
static DEVICE_ATTR(dtrim, S_IRUGO, isl1208_sysfs_show_dtrim, NULL);
|
|
|
|
static ssize_t
|
|
isl1208_sysfs_show_usr(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
int usr = isl1208_i2c_get_usr(to_i2c_client(dev->parent));
|
|
if (usr < 0)
|
|
return usr;
|
|
|
|
return sprintf(buf, "0x%.4x\n", usr);
|
|
}
|
|
|
|
static ssize_t
|
|
isl1208_sysfs_store_usr(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
int usr = -1;
|
|
|
|
if (buf[0] == '0' && (buf[1] == 'x' || buf[1] == 'X')) {
|
|
if (sscanf(buf, "%x", &usr) != 1)
|
|
return -EINVAL;
|
|
} else {
|
|
if (sscanf(buf, "%d", &usr) != 1)
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (usr < 0 || usr > 0xffff)
|
|
return -EINVAL;
|
|
|
|
if (isl1208_i2c_set_usr(to_i2c_client(dev->parent), usr))
|
|
return -EIO;
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(usr, S_IRUGO | S_IWUSR, isl1208_sysfs_show_usr,
|
|
isl1208_sysfs_store_usr);
|
|
|
|
static struct attribute *isl1208_rtc_attrs[] = {
|
|
&dev_attr_atrim.attr,
|
|
&dev_attr_dtrim.attr,
|
|
&dev_attr_usr.attr,
|
|
NULL
|
|
};
|
|
|
|
static const struct attribute_group isl1208_rtc_sysfs_files = {
|
|
.attrs = isl1208_rtc_attrs,
|
|
};
|
|
|
|
static struct attribute *isl1219_rtc_attrs[] = {
|
|
&dev_attr_timestamp0.attr,
|
|
NULL
|
|
};
|
|
|
|
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);
|
|
int ret;
|
|
|
|
/* nvmem sanitizes offset/count for us, but count==0 is possible */
|
|
if (!count)
|
|
return count;
|
|
ret = isl1208_i2c_read_regs(client, ISL1208_REG_USR1 + off, buf,
|
|
count);
|
|
return ret == 0 ? count : ret;
|
|
}
|
|
|
|
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);
|
|
int ret;
|
|
|
|
/* nvmem sanitizes off/count for us, but count==0 is possible */
|
|
if (!count)
|
|
return count;
|
|
ret = isl1208_i2c_set_regs(client, ISL1208_REG_USR1 + off, buf,
|
|
count);
|
|
|
|
return ret == 0 ? count : ret;
|
|
}
|
|
|
|
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,
|
|
isl1208_rtc_interrupt,
|
|
IRQF_SHARED | IRQF_ONESHOT,
|
|
isl1208_driver.driver.name,
|
|
client);
|
|
if (!rc) {
|
|
device_init_wakeup(&client->dev, 1);
|
|
enable_irq_wake(irq);
|
|
} else {
|
|
dev_err(&client->dev,
|
|
"Unable to request irq %d, no alarm support\n",
|
|
irq);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
isl1208_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
|
{
|
|
int rc = 0;
|
|
struct isl1208_state *isl1208;
|
|
int evdet_irq = -1;
|
|
|
|
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
|
|
return -ENODEV;
|
|
|
|
if (isl1208_i2c_validate_client(client) < 0)
|
|
return -ENODEV;
|
|
|
|
/* 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);
|
|
|
|
/* Determine which chip we have */
|
|
if (client->dev.of_node) {
|
|
isl1208->config = of_device_get_match_data(&client->dev);
|
|
if (!isl1208->config)
|
|
return -ENODEV;
|
|
} else {
|
|
if (id->driver_data >= ISL_LAST_ID)
|
|
return -ENODEV;
|
|
isl1208->config = &isl1208_configs[id->driver_data];
|
|
}
|
|
|
|
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;
|
|
|
|
rc = isl1208_i2c_get_sr(client);
|
|
if (rc < 0) {
|
|
dev_err(&client->dev, "reading status failed\n");
|
|
return rc;
|
|
}
|
|
|
|
if (rc & ISL1208_REG_SR_RTCF)
|
|
dev_warn(&client->dev, "rtc power failure detected, "
|
|
"please set clock.\n");
|
|
|
|
if (isl1208->config->has_tamper) {
|
|
struct device_node *np = client->dev.of_node;
|
|
u32 evienb;
|
|
|
|
rc = i2c_smbus_read_byte_data(client, ISL1219_REG_EV);
|
|
if (rc < 0) {
|
|
dev_err(&client->dev, "failed to read EV reg\n");
|
|
return rc;
|
|
}
|
|
rc |= ISL1219_REG_EV_EVEN;
|
|
if (!of_property_read_u32(np, "isil,ev-evienb", &evienb)) {
|
|
if (evienb)
|
|
rc |= ISL1219_REG_EV_EVIENB;
|
|
else
|
|
rc &= ~ISL1219_REG_EV_EVIENB;
|
|
}
|
|
rc = i2c_smbus_write_byte_data(client, ISL1219_REG_EV, rc);
|
|
if (rc < 0) {
|
|
dev_err(&client->dev, "could not enable tamper detection\n");
|
|
return rc;
|
|
}
|
|
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;
|
|
}
|
|
|
|
rc = rtc_add_group(isl1208->rtc, &isl1208_rtc_sysfs_files);
|
|
if (rc)
|
|
return rc;
|
|
|
|
if (client->irq > 0) {
|
|
rc = isl1208_setup_irq(client, client->irq);
|
|
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;
|
|
|
|
rc = devm_rtc_nvmem_register(isl1208->rtc, &isl1208->nvmem_config);
|
|
if (rc)
|
|
return rc;
|
|
|
|
return devm_rtc_register_device(isl1208->rtc);
|
|
}
|
|
|
|
static struct i2c_driver isl1208_driver = {
|
|
.driver = {
|
|
.name = "rtc-isl1208",
|
|
.of_match_table = of_match_ptr(isl1208_of_match),
|
|
},
|
|
.probe = isl1208_probe,
|
|
.id_table = isl1208_id,
|
|
};
|
|
|
|
module_i2c_driver(isl1208_driver);
|
|
|
|
MODULE_AUTHOR("Herbert Valerio Riedel <hvr@gnu.org>");
|
|
MODULE_DESCRIPTION("Intersil ISL1208 RTC driver");
|
|
MODULE_LICENSE("GPL");
|