| From 3bd32722c827d00eafe8e6d5b83e9f3148ea7c7e Mon Sep 17 00:00:00 2001 |
| From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= <uwe@kleine-koenig.org> |
| Date: Sat, 2 Jul 2016 17:28:10 +0200 |
| Subject: rtc: s35390a: improve irq handling |
| MIME-Version: 1.0 |
| Content-Type: text/plain; charset=UTF-8 |
| Content-Transfer-Encoding: 8bit |
| |
| From: Uwe Kleine-König <uwe@kleine-koenig.org> |
| |
| commit 3bd32722c827d00eafe8e6d5b83e9f3148ea7c7e upstream. |
| |
| On some QNAP NAS devices the rtc can wake the machine. Several people |
| noticed that once the machine was woken this way it fails to shut down. |
| That's because the driver fails to acknowledge the interrupt and so it |
| keeps active and restarts the machine immediatly after shutdown. See |
| https://bugs.debian.org/794266 for a bug report. |
| |
| Doing this correctly requires to interpret the INT2 flag of the first read |
| of the STATUS1 register because this bit is cleared by read. |
| |
| Note this is not maximally robust though because a pending irq isn't |
| detected when the STATUS1 register was already read (and so INT2 is not |
| set) but the irq was not disabled. But that is a hardware imposed problem |
| that cannot easily be fixed by software. |
| |
| Signed-off-by: Uwe Kleine-König <uwe@kleine-koenig.org> |
| Signed-off-by: Alexandre Belloni <alexandre.belloni@free-electrons.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| drivers/rtc/rtc-s35390a.c | 48 +++++++++++++++++++++++++++++----------------- |
| 1 file changed, 31 insertions(+), 17 deletions(-) |
| |
| --- a/drivers/rtc/rtc-s35390a.c |
| +++ b/drivers/rtc/rtc-s35390a.c |
| @@ -35,10 +35,14 @@ |
| #define S35390A_ALRM_BYTE_HOURS 1 |
| #define S35390A_ALRM_BYTE_MINS 2 |
| |
| +/* flags for STATUS1 */ |
| #define S35390A_FLAG_POC 0x01 |
| #define S35390A_FLAG_BLD 0x02 |
| +#define S35390A_FLAG_INT2 0x04 |
| #define S35390A_FLAG_24H 0x40 |
| #define S35390A_FLAG_RESET 0x80 |
| + |
| +/* flag for STATUS2 */ |
| #define S35390A_FLAG_TEST 0x01 |
| |
| #define S35390A_INT2_MODE_MASK 0xF0 |
| @@ -408,11 +412,11 @@ static struct i2c_driver s35390a_driver; |
| static int s35390a_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| - int err; |
| + int err, err_reset; |
| unsigned int i; |
| struct s35390a *s35390a; |
| struct rtc_time tm; |
| - char buf[1], status1; |
| + char buf, status1; |
| |
| if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { |
| err = -ENODEV; |
| @@ -441,29 +445,35 @@ static int s35390a_probe(struct i2c_clie |
| } |
| } |
| |
| - err = s35390a_reset(s35390a, &status1); |
| - if (err < 0) { |
| + err_reset = s35390a_reset(s35390a, &status1); |
| + if (err_reset < 0) { |
| + err = err_reset; |
| dev_err(&client->dev, "error resetting chip\n"); |
| goto exit_dummy; |
| } |
| |
| - err = s35390a_disable_test_mode(s35390a); |
| - if (err < 0) { |
| - dev_err(&client->dev, "error disabling test mode\n"); |
| - goto exit_dummy; |
| - } |
| - |
| - err = s35390a_get_reg(s35390a, S35390A_CMD_STATUS1, buf, sizeof(buf)); |
| - if (err < 0) { |
| - dev_err(&client->dev, "error checking 12/24 hour mode\n"); |
| - goto exit_dummy; |
| - } |
| - if (buf[0] & S35390A_FLAG_24H) |
| + if (status1 & S35390A_FLAG_24H) |
| s35390a->twentyfourhour = 1; |
| else |
| s35390a->twentyfourhour = 0; |
| |
| - if (s35390a_get_datetime(client, &tm) < 0) |
| + if (status1 & S35390A_FLAG_INT2) { |
| + /* disable alarm (and maybe test mode) */ |
| + buf = 0; |
| + err = s35390a_set_reg(s35390a, S35390A_CMD_STATUS2, &buf, 1); |
| + if (err < 0) { |
| + dev_err(&client->dev, "error disabling alarm"); |
| + goto exit_dummy; |
| + } |
| + } else { |
| + err = s35390a_disable_test_mode(s35390a); |
| + if (err < 0) { |
| + dev_err(&client->dev, "error disabling test mode\n"); |
| + goto exit_dummy; |
| + } |
| + } |
| + |
| + if (err_reset > 0 || s35390a_get_datetime(client, &tm) < 0) |
| dev_warn(&client->dev, "clock needs to be set\n"); |
| |
| device_set_wakeup_capable(&client->dev, 1); |
| @@ -476,6 +486,10 @@ static int s35390a_probe(struct i2c_clie |
| err = PTR_ERR(s35390a->rtc); |
| goto exit_dummy; |
| } |
| + |
| + if (status1 & S35390A_FLAG_INT2) |
| + rtc_update_irq(s35390a->rtc, 1, RTC_AF); |
| + |
| return 0; |
| |
| exit_dummy: |