Skip to content

Commit

Permalink
rtc: m41t80: add alarm functionality
Browse files Browse the repository at this point in the history
Previous 'commit c3b7977 ("Expire alarms after the time is set")'
and 'commit 48e9766 ("remove disabled alarm functionality")' removed
the alarm support because the alarm irq was not functional.

Add the alarm IRQ functionality with newer functions than previous
code. Tested with 'rtctest' and the alarm is functional.

Signed-off-by: Mylène Josserand <mylene.josserand@free-electrons.com>
Signed-off-by: Alexandre Belloni <alexandre.belloni@free-electrons.com>
  • Loading branch information
Mylène Josserand authored and Alexandre Belloni committed May 20, 2016
1 parent fc99b90 commit 9c6dfed
Showing 1 changed file with 167 additions and 13 deletions.
180 changes: 167 additions & 13 deletions drivers/rtc/rtc-m41t80.c
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,46 @@ struct m41t80_data {
struct rtc_device *rtc;
};

static irqreturn_t m41t80_handle_irq(int irq, void *dev_id)
{
struct i2c_client *client = dev_id;
struct m41t80_data *m41t80 = i2c_get_clientdata(client);
struct mutex *lock = &m41t80->rtc->ops_lock;
unsigned long events = 0;
int flags, flags_afe;

mutex_lock(lock);

flags_afe = i2c_smbus_read_byte_data(client, M41T80_REG_ALARM_MON);
if (flags_afe < 0) {
mutex_unlock(lock);
return IRQ_NONE;
}

flags = i2c_smbus_read_byte_data(client, M41T80_REG_FLAGS);
if (flags <= 0) {
mutex_unlock(lock);
return IRQ_NONE;
}

if (flags & M41T80_FLAGS_AF) {
flags &= ~M41T80_FLAGS_AF;
flags_afe &= ~M41T80_ALMON_AFE;
events |= RTC_AF;
}

if (events) {
rtc_update_irq(m41t80->rtc, 1, events);
i2c_smbus_write_byte_data(client, M41T80_REG_FLAGS, flags);
i2c_smbus_write_byte_data(client, M41T80_REG_ALARM_MON,
flags_afe);
}

mutex_unlock(lock);

return IRQ_HANDLED;
}

static int m41t80_get_datetime(struct i2c_client *client,
struct rtc_time *tm)
{
Expand Down Expand Up @@ -167,10 +207,109 @@ static int m41t80_rtc_set_time(struct device *dev, struct rtc_time *tm)
return m41t80_set_datetime(to_i2c_client(dev), tm);
}

/*
* XXX - m41t80 alarm functionality is reported broken.
* until it is fixed, don't register alarm functions.
*/
static int m41t80_alarm_irq_enable(struct device *dev, unsigned int enabled)
{
struct i2c_client *client = to_i2c_client(dev);
int flags, retval;

flags = i2c_smbus_read_byte_data(client, M41T80_REG_ALARM_MON);
if (flags < 0)
return flags;

if (enabled)
flags |= M41T80_ALMON_AFE;
else
flags &= ~M41T80_ALMON_AFE;

retval = i2c_smbus_write_byte_data(client, M41T80_REG_ALARM_MON, flags);
if (retval < 0) {
dev_info(dev, "Unable to enable alarm IRQ %d\n", retval);
return retval;
}
return 0;
}

static int m41t80_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
{
struct i2c_client *client = to_i2c_client(dev);
u8 alarmvals[5];
int ret, err;

alarmvals[0] = bin2bcd(alrm->time.tm_mon + 1);
alarmvals[1] = bin2bcd(alrm->time.tm_mday);
alarmvals[2] = bin2bcd(alrm->time.tm_hour);
alarmvals[3] = bin2bcd(alrm->time.tm_min);
alarmvals[4] = bin2bcd(alrm->time.tm_sec);

/* Clear AF and AFE flags */
ret = i2c_smbus_read_byte_data(client, M41T80_REG_ALARM_MON);
if (ret < 0)
return ret;
err = i2c_smbus_write_byte_data(client, M41T80_REG_ALARM_MON,
ret & ~(M41T80_ALMON_AFE));
if (err < 0) {
dev_err(dev, "Unable to clear AFE bit\n");
return err;
}

ret = i2c_smbus_read_byte_data(client, M41T80_REG_FLAGS);
if (ret < 0)
return ret;

err = i2c_smbus_write_byte_data(client, M41T80_REG_FLAGS,
ret & ~(M41T80_FLAGS_AF));
if (err < 0) {
dev_err(dev, "Unable to clear AF bit\n");
return err;
}

/* Write the alarm */
err = i2c_smbus_write_i2c_block_data(client, M41T80_REG_ALARM_MON,
5, alarmvals);
if (err)
return err;

/* Enable the alarm interrupt */
if (alrm->enabled) {
alarmvals[0] |= M41T80_ALMON_AFE;
err = i2c_smbus_write_byte_data(client, M41T80_REG_ALARM_MON,
alarmvals[0]);
if (err)
return err;
}

return 0;
}

static int m41t80_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
{
struct i2c_client *client = to_i2c_client(dev);
u8 alarmvals[5];
int flags, ret;

ret = i2c_smbus_read_i2c_block_data(client, M41T80_REG_ALARM_MON,
5, alarmvals);
if (ret != 5)
return ret < 0 ? ret : -EIO;

flags = i2c_smbus_read_byte_data(client, M41T80_REG_FLAGS);
if (flags < 0)
return flags;

alrm->time.tm_sec = bcd2bin(alarmvals[4] & 0x7f);
alrm->time.tm_min = bcd2bin(alarmvals[3] & 0x7f);
alrm->time.tm_hour = bcd2bin(alarmvals[2] & 0x3f);
alrm->time.tm_wday = -1;
alrm->time.tm_mday = bcd2bin(alarmvals[1] & 0x3f);
alrm->time.tm_mon = bcd2bin(alarmvals[0] & 0x3f);
alrm->time.tm_year = -1;

alrm->enabled = !!(alarmvals[0] & M41T80_ALMON_AFE);
alrm->pending = (flags & M41T80_FLAGS_AF) && alrm->enabled;

return 0;
}

static struct rtc_class_ops m41t80_rtc_ops = {
.read_time = m41t80_rtc_read_time,
.set_time = m41t80_rtc_set_time,
Expand Down Expand Up @@ -591,34 +730,49 @@ static int m41t80_probe(struct i2c_client *client,
int rc = 0;
struct rtc_device *rtc = NULL;
struct rtc_time tm;
struct m41t80_data *clientdata = NULL;
struct m41t80_data *m41t80_data = NULL;

if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK |
I2C_FUNC_SMBUS_BYTE_DATA)) {
dev_err(&adapter->dev, "doesn't support I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_I2C_BLOCK\n");
return -ENODEV;
}

clientdata = devm_kzalloc(&client->dev, sizeof(*clientdata),
GFP_KERNEL);
if (!clientdata)
m41t80_data = devm_kzalloc(&client->dev, sizeof(*m41t80_data),
GFP_KERNEL);
if (!m41t80_data)
return -ENOMEM;

clientdata->features = id->driver_data;
i2c_set_clientdata(client, clientdata);
m41t80_data->features = id->driver_data;
i2c_set_clientdata(client, m41t80_data);

if (client->irq > 0) {
rc = devm_request_threaded_irq(&client->dev, client->irq,
NULL, m41t80_handle_irq,
IRQF_TRIGGER_LOW | IRQF_ONESHOT,
"m41t80", client);
if (rc) {
dev_warn(&client->dev, "unable to request IRQ, alarms disabled\n");
client->irq = 0;
} else {
m41t80_rtc_ops.read_alarm = m41t80_read_alarm;
m41t80_rtc_ops.set_alarm = m41t80_set_alarm;
m41t80_rtc_ops.alarm_irq_enable = m41t80_alarm_irq_enable;
}
}

rtc = devm_rtc_device_register(&client->dev, client->name,
&m41t80_rtc_ops, THIS_MODULE);
if (IS_ERR(rtc))
return PTR_ERR(rtc);

clientdata->rtc = rtc;
m41t80_data->rtc = rtc;

/* Make sure HT (Halt Update) bit is cleared */
rc = i2c_smbus_read_byte_data(client, M41T80_REG_ALARM_HOUR);

if (rc >= 0 && rc & M41T80_ALHOUR_HT) {
if (clientdata->features & M41T80_FEATURE_HT) {
if (m41t80_data->features & M41T80_FEATURE_HT) {
m41t80_get_datetime(client, &tm);
dev_info(&client->dev, "HT bit was set!\n");
dev_info(&client->dev,
Expand Down Expand Up @@ -664,7 +818,7 @@ static int m41t80_probe(struct i2c_client *client,
}

#ifdef CONFIG_RTC_DRV_M41T80_WDT
if (clientdata->features & M41T80_FEATURE_HT) {
if (m41t80_data->features & M41T80_FEATURE_HT) {
save_client = client;
rc = misc_register(&wdt_dev);
if (rc)
Expand Down

0 comments on commit 9c6dfed

Please sign in to comment.