mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-30 22:26:55 +00:00
rtc: fixes and new functionality for fm3130
- add sanity check for alarm data in fm3130_probe - fix fm3130_set_alarm. According to the datasheet, setting match bit '0' indicates that the corresponding alarm field will be used in the match process - add operation alarm_irq_enable operation which is responsible for handling RTC_AIE_ON, RTC_AIE_OFF ioctls - remove clearing of AF bit after reading rtc/alarm control register: according to datasheet this bit is cleared anyway when rtc/alarm control register is read [akpm@linux-foundation.org: make fm3130_alarm_irq_enable() static, fix comment layout] Signed-off-by: Sergey Matyukevich <geomatsi@gmail.com> Acked-by: Wan ZongShun <mcuos.com@gmail.com> Acked-by: Sergey Lapin <slapin@ossfans.org> Cc: Alessandro Zummo <a.zummo@towertech.it> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
parent
5824c7e667
commit
f3f99cf391
1 changed files with 130 additions and 51 deletions
|
@ -52,8 +52,8 @@ struct fm3130 {
|
||||||
struct i2c_msg msg[4];
|
struct i2c_msg msg[4];
|
||||||
struct i2c_client *client;
|
struct i2c_client *client;
|
||||||
struct rtc_device *rtc;
|
struct rtc_device *rtc;
|
||||||
|
int alarm_valid;
|
||||||
int data_valid;
|
int data_valid;
|
||||||
int alarm;
|
|
||||||
};
|
};
|
||||||
static const struct i2c_device_id fm3130_id[] = {
|
static const struct i2c_device_id fm3130_id[] = {
|
||||||
{ "fm3130", 0 },
|
{ "fm3130", 0 },
|
||||||
|
@ -87,11 +87,7 @@ static void fm3130_rtc_mode(struct device *dev, int mode)
|
||||||
dev_dbg(dev, "invalid mode %d\n", mode);
|
dev_dbg(dev, "invalid mode %d\n", mode);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
/* Checking for alarm */
|
|
||||||
if (fm3130->regs[FM3130_RTC_CONTROL] & FM3130_RTC_CONTROL_BIT_AF) {
|
|
||||||
fm3130->alarm = 1;
|
|
||||||
fm3130->regs[FM3130_RTC_CONTROL] &= ~FM3130_RTC_CONTROL_BIT_AF;
|
|
||||||
}
|
|
||||||
i2c_smbus_write_byte_data(fm3130->client,
|
i2c_smbus_write_byte_data(fm3130->client,
|
||||||
FM3130_RTC_CONTROL, fm3130->regs[FM3130_RTC_CONTROL]);
|
FM3130_RTC_CONTROL, fm3130->regs[FM3130_RTC_CONTROL]);
|
||||||
}
|
}
|
||||||
|
@ -208,6 +204,17 @@ static int fm3130_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
|
||||||
struct fm3130 *fm3130 = dev_get_drvdata(dev);
|
struct fm3130 *fm3130 = dev_get_drvdata(dev);
|
||||||
int tmp;
|
int tmp;
|
||||||
struct rtc_time *tm = &alrm->time;
|
struct rtc_time *tm = &alrm->time;
|
||||||
|
|
||||||
|
if (!fm3130->alarm_valid) {
|
||||||
|
/*
|
||||||
|
* We have invalid alarm in RTC, probably due to battery faults
|
||||||
|
* or other problems. Return EIO for now, it will allow us to
|
||||||
|
* set alarm value later instead of error during probing which
|
||||||
|
* disables device
|
||||||
|
*/
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
/* read the RTC alarm registers all at once */
|
/* read the RTC alarm registers all at once */
|
||||||
tmp = i2c_transfer(to_i2c_adapter(fm3130->client->dev.parent),
|
tmp = i2c_transfer(to_i2c_adapter(fm3130->client->dev.parent),
|
||||||
&fm3130->msg[2], 2);
|
&fm3130->msg[2], 2);
|
||||||
|
@ -222,20 +229,31 @@ static int fm3130_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
|
||||||
fm3130->regs[FM3130_ALARM_DATE],
|
fm3130->regs[FM3130_ALARM_DATE],
|
||||||
fm3130->regs[FM3130_ALARM_MONTHS]);
|
fm3130->regs[FM3130_ALARM_MONTHS]);
|
||||||
|
|
||||||
|
|
||||||
tm->tm_sec = bcd2bin(fm3130->regs[FM3130_ALARM_SECONDS] & 0x7F);
|
tm->tm_sec = bcd2bin(fm3130->regs[FM3130_ALARM_SECONDS] & 0x7F);
|
||||||
tm->tm_min = bcd2bin(fm3130->regs[FM3130_ALARM_MINUTES] & 0x7F);
|
tm->tm_min = bcd2bin(fm3130->regs[FM3130_ALARM_MINUTES] & 0x7F);
|
||||||
tm->tm_hour = bcd2bin(fm3130->regs[FM3130_ALARM_HOURS] & 0x3F);
|
tm->tm_hour = bcd2bin(fm3130->regs[FM3130_ALARM_HOURS] & 0x3F);
|
||||||
tm->tm_mday = bcd2bin(fm3130->regs[FM3130_ALARM_DATE] & 0x3F);
|
tm->tm_mday = bcd2bin(fm3130->regs[FM3130_ALARM_DATE] & 0x3F);
|
||||||
tm->tm_mon = bcd2bin(fm3130->regs[FM3130_ALARM_MONTHS] & 0x1F);
|
tm->tm_mon = bcd2bin(fm3130->regs[FM3130_ALARM_MONTHS] & 0x1F);
|
||||||
|
|
||||||
if (tm->tm_mon > 0)
|
if (tm->tm_mon > 0)
|
||||||
tm->tm_mon -= 1; /* RTC is 1-12, tm_mon is 0-11 */
|
tm->tm_mon -= 1; /* RTC is 1-12, tm_mon is 0-11 */
|
||||||
|
|
||||||
dev_dbg(dev, "%s secs=%d, mins=%d, "
|
dev_dbg(dev, "%s secs=%d, mins=%d, "
|
||||||
"hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n",
|
"hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n",
|
||||||
"read alarm", tm->tm_sec, tm->tm_min,
|
"read alarm", tm->tm_sec, tm->tm_min,
|
||||||
tm->tm_hour, tm->tm_mday,
|
tm->tm_hour, tm->tm_mday,
|
||||||
tm->tm_mon, tm->tm_year, tm->tm_wday);
|
tm->tm_mon, tm->tm_year, tm->tm_wday);
|
||||||
|
|
||||||
|
/* check if alarm enabled */
|
||||||
|
fm3130->regs[FM3130_RTC_CONTROL] =
|
||||||
|
i2c_smbus_read_byte_data(fm3130->client, FM3130_RTC_CONTROL);
|
||||||
|
|
||||||
|
if ((fm3130->regs[FM3130_RTC_CONTROL] & FM3130_RTC_CONTROL_BIT_AEN) &&
|
||||||
|
(~fm3130->regs[FM3130_RTC_CONTROL] &
|
||||||
|
FM3130_RTC_CONTROL_BIT_CAL)) {
|
||||||
|
alrm->enabled = 1;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,25 +269,20 @@ static int fm3130_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
|
||||||
tm->tm_hour, tm->tm_mday,
|
tm->tm_hour, tm->tm_mday,
|
||||||
tm->tm_mon, tm->tm_year, tm->tm_wday);
|
tm->tm_mon, tm->tm_year, tm->tm_wday);
|
||||||
|
|
||||||
if (tm->tm_sec != -1)
|
fm3130->regs[FM3130_ALARM_SECONDS] =
|
||||||
fm3130->regs[FM3130_ALARM_SECONDS] =
|
(tm->tm_sec != -1) ? bin2bcd(tm->tm_sec) : 0x80;
|
||||||
bin2bcd(tm->tm_sec) | 0x80;
|
|
||||||
|
|
||||||
if (tm->tm_min != -1)
|
fm3130->regs[FM3130_ALARM_MINUTES] =
|
||||||
fm3130->regs[FM3130_ALARM_MINUTES] =
|
(tm->tm_min != -1) ? bin2bcd(tm->tm_min) : 0x80;
|
||||||
bin2bcd(tm->tm_min) | 0x80;
|
|
||||||
|
|
||||||
if (tm->tm_hour != -1)
|
fm3130->regs[FM3130_ALARM_HOURS] =
|
||||||
fm3130->regs[FM3130_ALARM_HOURS] =
|
(tm->tm_hour != -1) ? bin2bcd(tm->tm_hour) : 0x80;
|
||||||
bin2bcd(tm->tm_hour) | 0x80;
|
|
||||||
|
|
||||||
if (tm->tm_mday != -1)
|
fm3130->regs[FM3130_ALARM_DATE] =
|
||||||
fm3130->regs[FM3130_ALARM_DATE] =
|
(tm->tm_mday != -1) ? bin2bcd(tm->tm_mday) : 0x80;
|
||||||
bin2bcd(tm->tm_mday) | 0x80;
|
|
||||||
|
|
||||||
if (tm->tm_mon != -1)
|
fm3130->regs[FM3130_ALARM_MONTHS] =
|
||||||
fm3130->regs[FM3130_ALARM_MONTHS] =
|
(tm->tm_mon != -1) ? bin2bcd(tm->tm_mon + 1) : 0x80;
|
||||||
bin2bcd(tm->tm_mon + 1) | 0x80;
|
|
||||||
|
|
||||||
dev_dbg(dev, "alarm write %02x %02x %02x %02x %02x\n",
|
dev_dbg(dev, "alarm write %02x %02x %02x %02x %02x\n",
|
||||||
fm3130->regs[FM3130_ALARM_SECONDS],
|
fm3130->regs[FM3130_ALARM_SECONDS],
|
||||||
|
@ -285,11 +298,8 @@ static int fm3130_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
|
||||||
}
|
}
|
||||||
fm3130->regs[FM3130_RTC_CONTROL] =
|
fm3130->regs[FM3130_RTC_CONTROL] =
|
||||||
i2c_smbus_read_byte_data(fm3130->client, FM3130_RTC_CONTROL);
|
i2c_smbus_read_byte_data(fm3130->client, FM3130_RTC_CONTROL);
|
||||||
/* Checking for alarm */
|
|
||||||
if (fm3130->regs[FM3130_RTC_CONTROL] & FM3130_RTC_CONTROL_BIT_AF) {
|
/* enable or disable alarm */
|
||||||
fm3130->alarm = 1;
|
|
||||||
fm3130->regs[FM3130_RTC_CONTROL] &= ~FM3130_RTC_CONTROL_BIT_AF;
|
|
||||||
}
|
|
||||||
if (alrm->enabled) {
|
if (alrm->enabled) {
|
||||||
i2c_smbus_write_byte_data(fm3130->client, FM3130_RTC_CONTROL,
|
i2c_smbus_write_byte_data(fm3130->client, FM3130_RTC_CONTROL,
|
||||||
(fm3130->regs[FM3130_RTC_CONTROL] &
|
(fm3130->regs[FM3130_RTC_CONTROL] &
|
||||||
|
@ -298,16 +308,55 @@ static int fm3130_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
|
||||||
} else {
|
} else {
|
||||||
i2c_smbus_write_byte_data(fm3130->client, FM3130_RTC_CONTROL,
|
i2c_smbus_write_byte_data(fm3130->client, FM3130_RTC_CONTROL,
|
||||||
fm3130->regs[FM3130_RTC_CONTROL] &
|
fm3130->regs[FM3130_RTC_CONTROL] &
|
||||||
~(FM3130_RTC_CONTROL_BIT_AEN));
|
~(FM3130_RTC_CONTROL_BIT_CAL) &
|
||||||
|
~(FM3130_RTC_CONTROL_BIT_AEN));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* We assume here that data is valid once written */
|
||||||
|
if (!fm3130->alarm_valid)
|
||||||
|
fm3130->alarm_valid = 1;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int fm3130_alarm_irq_enable(struct device *dev, unsigned int enabled)
|
||||||
|
{
|
||||||
|
struct fm3130 *fm3130 = dev_get_drvdata(dev);
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
fm3130->regs[FM3130_RTC_CONTROL] =
|
||||||
|
i2c_smbus_read_byte_data(fm3130->client, FM3130_RTC_CONTROL);
|
||||||
|
|
||||||
|
dev_dbg(dev, "alarm_irq_enable: enable=%d, FM3130_RTC_CONTROL=%02x\n",
|
||||||
|
enabled, fm3130->regs[FM3130_RTC_CONTROL]);
|
||||||
|
|
||||||
|
switch (enabled) {
|
||||||
|
case 0: /* alarm off */
|
||||||
|
ret = i2c_smbus_write_byte_data(fm3130->client,
|
||||||
|
FM3130_RTC_CONTROL, fm3130->regs[FM3130_RTC_CONTROL] &
|
||||||
|
~(FM3130_RTC_CONTROL_BIT_CAL) &
|
||||||
|
~(FM3130_RTC_CONTROL_BIT_AEN));
|
||||||
|
break;
|
||||||
|
case 1: /* alarm on */
|
||||||
|
ret = i2c_smbus_write_byte_data(fm3130->client,
|
||||||
|
FM3130_RTC_CONTROL, (fm3130->regs[FM3130_RTC_CONTROL] &
|
||||||
|
~(FM3130_RTC_CONTROL_BIT_CAL)) |
|
||||||
|
FM3130_RTC_CONTROL_BIT_AEN);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ret = -EINVAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static const struct rtc_class_ops fm3130_rtc_ops = {
|
static const struct rtc_class_ops fm3130_rtc_ops = {
|
||||||
.read_time = fm3130_get_time,
|
.read_time = fm3130_get_time,
|
||||||
.set_time = fm3130_set_time,
|
.set_time = fm3130_set_time,
|
||||||
.read_alarm = fm3130_read_alarm,
|
.read_alarm = fm3130_read_alarm,
|
||||||
.set_alarm = fm3130_set_alarm,
|
.set_alarm = fm3130_set_alarm,
|
||||||
|
.alarm_irq_enable = fm3130_alarm_irq_enable,
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct i2c_driver fm3130_driver;
|
static struct i2c_driver fm3130_driver;
|
||||||
|
@ -356,6 +405,7 @@ static int __devinit fm3130_probe(struct i2c_client *client,
|
||||||
fm3130->msg[3].len = FM3130_ALARM_REGS;
|
fm3130->msg[3].len = FM3130_ALARM_REGS;
|
||||||
fm3130->msg[3].buf = &fm3130->regs[FM3130_ALARM_SECONDS];
|
fm3130->msg[3].buf = &fm3130->regs[FM3130_ALARM_SECONDS];
|
||||||
|
|
||||||
|
fm3130->alarm_valid = 0;
|
||||||
fm3130->data_valid = 0;
|
fm3130->data_valid = 0;
|
||||||
|
|
||||||
tmp = i2c_transfer(adapter, fm3130->msg, 4);
|
tmp = i2c_transfer(adapter, fm3130->msg, 4);
|
||||||
|
@ -370,12 +420,6 @@ static int __devinit fm3130_probe(struct i2c_client *client,
|
||||||
fm3130->regs[FM3130_CAL_CONTROL] =
|
fm3130->regs[FM3130_CAL_CONTROL] =
|
||||||
i2c_smbus_read_byte_data(client, FM3130_CAL_CONTROL);
|
i2c_smbus_read_byte_data(client, FM3130_CAL_CONTROL);
|
||||||
|
|
||||||
/* Checking for alarm */
|
|
||||||
if (fm3130->regs[FM3130_RTC_CONTROL] & FM3130_RTC_CONTROL_BIT_AF) {
|
|
||||||
fm3130->alarm = 1;
|
|
||||||
fm3130->regs[FM3130_RTC_CONTROL] &= ~FM3130_RTC_CONTROL_BIT_AF;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Disabling calibration mode */
|
/* Disabling calibration mode */
|
||||||
if (fm3130->regs[FM3130_RTC_CONTROL] & FM3130_RTC_CONTROL_BIT_CAL) {
|
if (fm3130->regs[FM3130_RTC_CONTROL] & FM3130_RTC_CONTROL_BIT_CAL) {
|
||||||
i2c_smbus_write_byte_data(client, FM3130_RTC_CONTROL,
|
i2c_smbus_write_byte_data(client, FM3130_RTC_CONTROL,
|
||||||
|
@ -400,44 +444,79 @@ static int __devinit fm3130_probe(struct i2c_client *client,
|
||||||
fm3130->regs[FM3130_CAL_CONTROL] &
|
fm3130->regs[FM3130_CAL_CONTROL] &
|
||||||
~(FM3130_CAL_CONTROL_BIT_nOSCEN));
|
~(FM3130_CAL_CONTROL_BIT_nOSCEN));
|
||||||
|
|
||||||
/* oscillator fault? clear flag, and warn */
|
/* low battery? clear flag, and warn */
|
||||||
if (fm3130->regs[FM3130_RTC_CONTROL] & FM3130_RTC_CONTROL_BIT_LB)
|
if (fm3130->regs[FM3130_RTC_CONTROL] & FM3130_RTC_CONTROL_BIT_LB) {
|
||||||
|
i2c_smbus_write_byte_data(client, FM3130_RTC_CONTROL,
|
||||||
|
fm3130->regs[FM3130_RTC_CONTROL] &
|
||||||
|
~(FM3130_RTC_CONTROL_BIT_LB));
|
||||||
dev_warn(&client->dev, "Low battery!\n");
|
dev_warn(&client->dev, "Low battery!\n");
|
||||||
|
}
|
||||||
|
|
||||||
/* oscillator fault? clear flag, and warn */
|
/* check if Power On Reset bit is set */
|
||||||
if (fm3130->regs[FM3130_RTC_CONTROL] & FM3130_RTC_CONTROL_BIT_POR) {
|
if (fm3130->regs[FM3130_RTC_CONTROL] & FM3130_RTC_CONTROL_BIT_POR) {
|
||||||
i2c_smbus_write_byte_data(client, FM3130_RTC_CONTROL,
|
i2c_smbus_write_byte_data(client, FM3130_RTC_CONTROL,
|
||||||
fm3130->regs[FM3130_RTC_CONTROL] &
|
fm3130->regs[FM3130_RTC_CONTROL] &
|
||||||
~FM3130_RTC_CONTROL_BIT_POR);
|
~FM3130_RTC_CONTROL_BIT_POR);
|
||||||
dev_warn(&client->dev, "SET TIME!\n");
|
dev_dbg(&client->dev, "POR bit is set\n");
|
||||||
}
|
}
|
||||||
/* ACS is controlled by alarm */
|
/* ACS is controlled by alarm */
|
||||||
i2c_smbus_write_byte_data(client, FM3130_ALARM_WP_CONTROL, 0x80);
|
i2c_smbus_write_byte_data(client, FM3130_ALARM_WP_CONTROL, 0x80);
|
||||||
|
|
||||||
/* TODO */
|
/* alarm registers sanity check */
|
||||||
/* TODO need to sanity check alarm */
|
tmp = bcd2bin(fm3130->regs[FM3130_RTC_SECONDS] & 0x7f);
|
||||||
tmp = fm3130->regs[FM3130_RTC_SECONDS];
|
if (tmp > 59)
|
||||||
tmp = bcd2bin(tmp & 0x7f);
|
goto bad_alarm;
|
||||||
if (tmp > 60)
|
|
||||||
goto exit_bad;
|
|
||||||
tmp = bcd2bin(fm3130->regs[FM3130_RTC_MINUTES] & 0x7f);
|
tmp = bcd2bin(fm3130->regs[FM3130_RTC_MINUTES] & 0x7f);
|
||||||
if (tmp > 60)
|
if (tmp > 59)
|
||||||
goto exit_bad;
|
goto bad_alarm;
|
||||||
|
|
||||||
|
tmp = bcd2bin(fm3130->regs[FM3130_RTC_HOURS] & 0x3f);
|
||||||
|
if (tmp > 23)
|
||||||
|
goto bad_alarm;
|
||||||
|
|
||||||
tmp = bcd2bin(fm3130->regs[FM3130_RTC_DATE] & 0x3f);
|
tmp = bcd2bin(fm3130->regs[FM3130_RTC_DATE] & 0x3f);
|
||||||
if (tmp == 0 || tmp > 31)
|
if (tmp == 0 || tmp > 31)
|
||||||
goto exit_bad;
|
goto bad_alarm;
|
||||||
|
|
||||||
tmp = bcd2bin(fm3130->regs[FM3130_RTC_MONTHS] & 0x1f);
|
tmp = bcd2bin(fm3130->regs[FM3130_RTC_MONTHS] & 0x1f);
|
||||||
if (tmp == 0 || tmp > 12)
|
if (tmp == 0 || tmp > 12)
|
||||||
goto exit_bad;
|
goto bad_alarm;
|
||||||
|
|
||||||
tmp = fm3130->regs[FM3130_RTC_HOURS];
|
fm3130->alarm_valid = 1;
|
||||||
|
|
||||||
|
bad_alarm:
|
||||||
|
|
||||||
|
/* clock registers sanity chek */
|
||||||
|
tmp = bcd2bin(fm3130->regs[FM3130_RTC_SECONDS] & 0x7f);
|
||||||
|
if (tmp > 59)
|
||||||
|
goto bad_clock;
|
||||||
|
|
||||||
|
tmp = bcd2bin(fm3130->regs[FM3130_RTC_MINUTES] & 0x7f);
|
||||||
|
if (tmp > 59)
|
||||||
|
goto bad_clock;
|
||||||
|
|
||||||
|
tmp = bcd2bin(fm3130->regs[FM3130_RTC_HOURS] & 0x3f);
|
||||||
|
if (tmp > 23)
|
||||||
|
goto bad_clock;
|
||||||
|
|
||||||
|
tmp = bcd2bin(fm3130->regs[FM3130_RTC_DAY] & 0x7);
|
||||||
|
if (tmp == 0 || tmp > 7)
|
||||||
|
goto bad_clock;
|
||||||
|
|
||||||
|
tmp = bcd2bin(fm3130->regs[FM3130_RTC_DATE] & 0x3f);
|
||||||
|
if (tmp == 0 || tmp > 31)
|
||||||
|
goto bad_clock;
|
||||||
|
|
||||||
|
tmp = bcd2bin(fm3130->regs[FM3130_RTC_MONTHS] & 0x1f);
|
||||||
|
if (tmp == 0 || tmp > 12)
|
||||||
|
goto bad_clock;
|
||||||
|
|
||||||
fm3130->data_valid = 1;
|
fm3130->data_valid = 1;
|
||||||
|
|
||||||
exit_bad:
|
bad_clock:
|
||||||
if (!fm3130->data_valid)
|
|
||||||
|
if (!fm3130->data_valid || !fm3130->alarm_valid)
|
||||||
dev_dbg(&client->dev,
|
dev_dbg(&client->dev,
|
||||||
"%s: %02x %02x %02x %02x %02x %02x %02x %02x"
|
"%s: %02x %02x %02x %02x %02x %02x %02x %02x"
|
||||||
"%02x %02x %02x %02x %02x %02x %02x\n",
|
"%02x %02x %02x %02x %02x %02x %02x\n",
|
||||||
|
|
Loading…
Reference in a new issue