@@ -26,6 +26,10 @@
#define PCF8563_REG_ST1 0x00 /* status */
#define PCF8563_REG_ST2 0x01
+#define PCF8563_BIT_ST2_TIE (1 << 0)
+#define PCF8563_BIT_ST2_AIE (1 << 1)
+#define PCF8563_BIT_ST2_TF (1 << 2)
+#define PCF8563_BIT_ST2_AF (1 << 3)
#define PCF8563_REG_SC 0x02 /* datetime */
#define PCF8563_REG_MN 0x03
@@ -181,6 +185,182 @@ static int pcf8563_set_datetime(struct i2c_client *client, struct rtc_time *tm)
&buf[PCF8563_REG_SC]);
}
+static int pcf8563_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct rtc_time *tm = &alrm->time;
+ uint8_t alarm_buf[3];
+ s32 status;
+ int ret;
+
+ ret = pcf8563_read_regs(client, PCF8563_REG_AMN,
+ sizeof(alarm_buf), alarm_buf);
+ if (ret)
+ return ret;
+
+ status = i2c_smbus_read_byte_data(client, PCF8563_REG_ST2);
+ if (status < 0)
+ return status;
+
+ tm->tm_sec = -1;
+ tm->tm_min = bcd2bin(alarm_buf[0] & 0x7f);
+ tm->tm_hour = bcd2bin(alarm_buf[1] & 0x3f);
+ tm->tm_mday = bcd2bin(alarm_buf[2] & 0x3f);
+ tm->tm_mon = -1;
+ tm->tm_year = -1;
+ alrm->pending = status & PCF8563_BIT_ST2_AF ? 1 : 0;
+
+ dev_dbg(dev, "%s: get alarm day=%d %02d:%02d pending=%d\n", __func__,
+ tm->tm_mday, tm->tm_hour, tm->tm_min, alrm->pending);
+
+ return 0;
+}
+
+
+static int pcf8563_rtc_enable_alarm_irq(struct i2c_client *client, bool enable)
+{
+ uint8_t st2;
+
+ dev_dbg(&client->dev, "%s %d\n", __func__, enable);
+
+ st2 = PCF8563_BIT_ST2_AF; /* Wired AND => leave as is */
+ if (enable)
+ st2 |= PCF8563_BIT_ST2_AIE;
+
+ return i2c_smbus_write_byte_data(client, PCF8563_REG_ST2, st2);
+}
+
+
+static int pcf8563_rtc_check_alarm_valid(struct i2c_client *client,
+ struct rtc_time *alrm)
+{
+ unsigned long alrmsecs, rtcsecs;
+ struct rtc_time rtctm;
+ int ret;
+
+ ret = pcf8563_get_datetime(client, &rtctm);
+ if (ret)
+ return ret;
+
+ rtc_tm_to_time(&rtctm, &rtcsecs);
+ rtc_tm_to_time(alrm, &alrmsecs);
+
+ /* Hardware doesn't have a seconds register for alarm time */
+ if ((alrmsecs - rtcsecs) < 60) {
+ dev_dbg(&client->dev, "Alarm < 1 minute not supported\n");
+ return -EINVAL;
+ }
+
+ /* Hardware doesn't have month or year registers for alarm time */
+ rtctm.tm_mon++;
+ if (rtctm.tm_mon >= 12) {
+ rtctm.tm_mon = 0;
+ rtctm.tm_year++;
+ }
+
+ rtctm.tm_mday = min(rtctm.tm_mday,
+ rtc_month_days(rtctm.tm_mon, rtctm.tm_year));
+ rtc_tm_to_time(&rtctm, &rtcsecs);
+
+ if (alrm->tm_sec > 0)
+ rtcsecs -= 60;
+
+ if (alrmsecs > rtcsecs) {
+ dev_dbg(&client->dev, "Alarm too far in future\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int pcf8563_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct rtc_time *tm = &alrm->time;
+ int min = tm->tm_min;
+ int hour = tm->tm_hour;
+ int mday = tm->tm_mday;
+ unsigned char data[3];
+ uint8_t enable;
+ int ret;
+
+ dev_dbg(dev, "%s: day=%d %02d:%02d:%02d enabled=%d\n", __func__,
+ tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec,
+ alrm->enabled);
+
+ ret = pcf8563_rtc_check_alarm_valid(client, tm);
+ if (ret)
+ return ret;
+
+ if (tm->tm_sec > 0) {
+ /* As hardware has no seconds alarm register
+ * add an extra minute otherwise alarm interrupt
+ * will occur before requested time causing the rtc core
+ * to reschedule (which will be refused since < 1 min) */
+ min++;
+ if (min > 59) {
+ min = 0;
+ hour++;
+ if (hour > 23) {
+ hour = 0;
+ mday++;
+ if (mday > rtc_month_days(
+ tm->tm_mon,
+ tm->tm_year + 1900))
+ mday = 1;
+ }
+ }
+ dev_dbg(dev, "round up alarm to day=%d %02d:%02d\n",
+ mday, hour, min);
+ }
+
+ /* Disable interrupt and clear any pending alarm */
+ ret = i2c_smbus_write_byte_data(client, PCF8563_REG_ST2, 0);
+ if (ret)
+ return ret;
+
+ enable = alrm->enabled ? 0 : 0x80; /* set to disable */
+ data[0] = bin2bcd(min) | enable;
+ data[1] = bin2bcd(hour) | enable;
+ data[2] = bin2bcd(mday) | enable;
+ ret = i2c_smbus_write_i2c_block_data(client, PCF8563_REG_AMN,
+ sizeof(data), data);
+ if (ret)
+ return ret;
+
+ return pcf8563_rtc_enable_alarm_irq(client, alrm->enabled);
+}
+
+static int pcf8563_rtc_alarm_irq_enable(struct device *dev,
+ unsigned int enabled)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+
+ dev_dbg(dev, "%s %d\n", __func__, enabled);
+
+ return pcf8563_rtc_enable_alarm_irq(client, enabled);
+}
+
+
+static irqreturn_t pcf8563_irq_handler(int irq, void *data)
+{
+ struct i2c_client *client = data;
+ struct pcf8563 *pcf8563 = i2c_get_clientdata(client);
+ int ret;
+
+ dev_dbg(&client->dev, "%s\n", __func__);
+
+ ret = pcf8563_rtc_enable_alarm_irq(client, false);
+ if (ret) {
+ dev_err(&client->dev, "Failed to disable irq: %d", ret);
+ return IRQ_NONE;
+ }
+
+ rtc_update_irq(pcf8563->rtc, 1, RTC_IRQF | RTC_AF);
+
+ return IRQ_HANDLED;
+}
+
#ifdef CONFIG_RTC_INTF_DEV
static int pcf8563_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
{
@@ -229,20 +409,54 @@ static int pcf8563_rtc_set_time(struct device *dev, struct rtc_time *tm)
return pcf8563_set_datetime(to_i2c_client(dev), tm);
}
+
+#ifdef CONFIG_PM_SLEEP
+
+static int pcf8563_rtc_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ int ret = 0;
+
+ if (device_may_wakeup(dev))
+ ret = enable_irq_wake(client->irq);
+
+ return ret;
+}
+
+static int pcf8563_rtc_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ int ret = 0;
+
+ if (device_may_wakeup(dev))
+ ret = disable_irq_wake(client->irq);
+
+ return ret;
+}
+
+#endif /* CONFIG_PM_SLEEP */
+
+
static const struct rtc_class_ops pcf8563_rtc_ops = {
.ioctl = pcf8563_rtc_ioctl,
.read_time = pcf8563_rtc_read_time,
.set_time = pcf8563_rtc_set_time,
+ .read_alarm = pcf8563_rtc_read_alarm,
+ .set_alarm = pcf8563_rtc_set_alarm,
+ .alarm_irq_enable = pcf8563_rtc_alarm_irq_enable,
};
static int pcf8563_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct pcf8563 *pcf8563;
+ int ret;
dev_dbg(&client->dev, "%s\n", __func__);
- if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK))
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_I2C |
+ I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_I2C_BLOCK))
return -ENODEV;
pcf8563 = devm_kzalloc(&client->dev, sizeof(struct pcf8563),
@@ -254,11 +468,33 @@ static int pcf8563_probe(struct i2c_client *client,
i2c_set_clientdata(client, pcf8563);
+ ret = pcf8563_rtc_enable_alarm_irq(client, false);
+ if (ret)
+ return ret;
+
+ if (client->irq > 0) {
+ ret = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, pcf8563_irq_handler,
+ IRQF_ONESHOT,
+ pcf8563_driver.driver.name,
+ client);
+ if (ret) {
+ dev_err(&client->dev, "unable to request IRQ\n");
+ return ret;
+ }
+
+ device_init_wakeup(&client->dev, 1);
+ }
+
pcf8563->rtc = devm_rtc_device_register(&client->dev,
pcf8563_driver.driver.name,
&pcf8563_rtc_ops, THIS_MODULE);
- return PTR_ERR_OR_ZERO(pcf8563->rtc);
+ if (IS_ERR(pcf8563->rtc))
+ return PTR_ERR(pcf8563->rtc);
+
+
+ return 0;
}
static const struct i2c_device_id pcf8563_id[] = {
@@ -276,11 +512,15 @@ static const struct of_device_id pcf8563_of_match[] = {
MODULE_DEVICE_TABLE(of, pcf8563_of_match);
#endif
+static SIMPLE_DEV_PM_OPS(pcf8563_pm_ops,
+ pcf8563_rtc_suspend, pcf8563_rtc_resume);
+
static struct i2c_driver pcf8563_driver = {
.driver = {
.name = "rtc-pcf8563",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(pcf8563_of_match),
+ .pm = &pcf8563_pm_ops,
},
.probe = pcf8563_probe,
.id_table = pcf8563_id,
Signed-off-by: Martin Fuzzey <mfuzzey@parkeon.com> --- drivers/rtc/rtc-pcf8563.c | 244 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 242 insertions(+), 2 deletions(-)