/* * Copyright(c) 2009 Dialog Semiconductor Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * rtc-da9052.c: RTC driver for DA9052 */ #include #include #include #include #include #define DRIVER_NAME "da9052-rtc" #define ENABLE 1 #define DISABLE 0 struct da9052_rtc { struct rtc_device *rtc; struct da9052 *da9052; struct da9052_eh_nb eh_data; unsigned char is_min_alarm; unsigned char enable_tick_alarm; unsigned char enable_clk_buffer; unsigned char set_osc_trim_freq; }; static int da9052_rtc_enable_alarm(struct da9052 *da9052, unsigned char flag); void da9052_rtc_notifier(struct da9052_eh_nb *eh_data, unsigned int event) { struct da9052_rtc *rtc = container_of(eh_data, struct da9052_rtc, eh_data); struct da9052_ssc_msg msg; unsigned int ret; /* Check the alarm type - TIMER or TICK */ msg.addr = DA9052_ALARMMI_REG; da9052_lock(rtc->da9052); ret = rtc->da9052->read(rtc->da9052, &msg); if (ret != 0) { da9052_unlock(rtc->da9052); return; } da9052_unlock(rtc->da9052); if (msg.data & DA9052_ALARMMI_ALARMTYPE) { da9052_rtc_enable_alarm(rtc->da9052, 0); printk(KERN_INFO "RTC: TIMER ALARM\n"); } else { kobject_uevent(&rtc->rtc->dev.kobj, KOBJ_CHANGE); printk(KERN_INFO "RTC: TICK ALARM\n"); } } static int da9052_rtc_validate_parameters(struct rtc_time *rtc_tm) { if (rtc_tm->tm_sec > DA9052_RTC_SECONDS_LIMIT) return DA9052_RTC_INVALID_SECONDS; if (rtc_tm->tm_min > DA9052_RTC_MINUTES_LIMIT) return DA9052_RTC_INVALID_MINUTES; if (rtc_tm->tm_hour > DA9052_RTC_HOURS_LIMIT) return DA9052_RTC_INVALID_HOURS; if (rtc_tm->tm_mday == 0) return DA9052_RTC_INVALID_DAYS; if ((rtc_tm->tm_mon > DA9052_RTC_MONTHS_LIMIT) || (rtc_tm->tm_mon == 0)) return DA9052_RTC_INVALID_MONTHS; if (rtc_tm->tm_year > DA9052_RTC_YEARS_LIMIT) return DA9052_RTC_INVALID_YEARS; if ((rtc_tm->tm_mon == FEBRUARY)) { if (((rtc_tm->tm_year % 4 == 0) && (rtc_tm->tm_year % 100 != 0)) || (rtc_tm->tm_year % 400 == 0)) { if (rtc_tm->tm_mday > 29) return DA9052_RTC_INVALID_DAYS; } else if (rtc_tm->tm_mday > 28) { return DA9052_RTC_INVALID_DAYS; } } if (((rtc_tm->tm_mon == APRIL) || (rtc_tm->tm_mon == JUNE) || (rtc_tm->tm_mon == SEPTEMBER) || (rtc_tm->tm_mon == NOVEMBER)) && (rtc_tm->tm_mday == 31)) { return DA9052_RTC_INVALID_DAYS; } return 0; } static int da9052_rtc_settime(struct da9052 *da9052, struct rtc_time *rtc_tm) { struct da9052_ssc_msg msg_arr[6]; int validate_param = 0; unsigned char loop_index = 0; int ret = 0; /* System compatability */ rtc_tm->tm_year -= 100; rtc_tm->tm_mon += 1; validate_param = da9052_rtc_validate_parameters(rtc_tm); if (validate_param) return validate_param; msg_arr[loop_index].addr = DA9052_COUNTS_REG; msg_arr[loop_index++].data = DA9052_COUNTS_MONITOR | rtc_tm->tm_sec; msg_arr[loop_index].addr = DA9052_COUNTMI_REG; msg_arr[loop_index].data = 0; msg_arr[loop_index++].data = rtc_tm->tm_min; msg_arr[loop_index].addr = DA9052_COUNTH_REG; msg_arr[loop_index].data = 0; msg_arr[loop_index++].data = rtc_tm->tm_hour; msg_arr[loop_index].addr = DA9052_COUNTD_REG; msg_arr[loop_index].data = 0; msg_arr[loop_index++].data = rtc_tm->tm_mday; msg_arr[loop_index].addr = DA9052_COUNTMO_REG; msg_arr[loop_index].data = 0; msg_arr[loop_index++].data = rtc_tm->tm_mon; msg_arr[loop_index].addr = DA9052_COUNTY_REG; msg_arr[loop_index].data = 0; msg_arr[loop_index++].data = rtc_tm->tm_year; da9052_lock(da9052); ret = da9052->write_many(da9052, msg_arr, loop_index); if (ret != 0) { da9052_unlock(da9052); return ret; } da9052_unlock(da9052); return 0; } static int da9052_rtc_gettime(struct da9052 *da9052, struct rtc_time *rtc_tm) { struct da9052_ssc_msg msg[6]; unsigned char loop_index = 0; int validate_param = 0; int ret = 0; msg[loop_index].data = 0; msg[loop_index++].addr = DA9052_COUNTS_REG; msg[loop_index].data = 0; msg[loop_index++].addr = DA9052_COUNTMI_REG; msg[loop_index].data = 0; msg[loop_index++].addr = DA9052_COUNTH_REG; msg[loop_index].data = 0; msg[loop_index++].addr = DA9052_COUNTD_REG; msg[loop_index].data = 0; msg[loop_index++].addr = DA9052_COUNTMO_REG; msg[loop_index].data = 0; msg[loop_index++].addr = DA9052_COUNTY_REG; da9052_lock(da9052); ret = da9052->read_many(da9052, msg, loop_index); if (ret != 0) { da9052_unlock(da9052); return ret; } da9052_unlock(da9052); rtc_tm->tm_year = msg[--loop_index].data & DA9052_COUNTY_COUNTYEAR; rtc_tm->tm_mon = msg[--loop_index].data & DA9052_COUNTMO_COUNTMONTH; rtc_tm->tm_mday = msg[--loop_index].data & DA9052_COUNTD_COUNTDAY; rtc_tm->tm_hour = msg[--loop_index].data & DA9052_COUNTH_COUNTHOUR; rtc_tm->tm_min = msg[--loop_index].data & DA9052_COUNTMI_COUNTMIN; rtc_tm->tm_sec = msg[--loop_index].data & DA9052_COUNTS_COUNTSEC; validate_param = da9052_rtc_validate_parameters(rtc_tm); if (validate_param) return validate_param; /* System compatability */ rtc_tm->tm_year += 100; rtc_tm->tm_mon -= 1; return 0; } static int da9052_alarm_gettime(struct da9052 *da9052, struct rtc_time *rtc_tm) { struct da9052_ssc_msg msg[5]; unsigned char loop_index = 0; int validate_param = 0; int ret = 0; msg[loop_index].data = 0; msg[loop_index++].addr = DA9052_ALARMMI_REG; msg[loop_index].data = 0; msg[loop_index++].addr = DA9052_ALARMH_REG; msg[loop_index].data = 0; msg[loop_index++].addr = DA9052_ALARMD_REG; msg[loop_index].data = 0; msg[loop_index++].addr = DA9052_ALARMMO_REG; msg[loop_index].data = 0; msg[loop_index++].addr = DA9052_ALARMY_REG; da9052_lock(da9052); ret = da9052->read_many(da9052, msg, loop_index); if (ret != 0) { da9052_unlock(da9052); return ret; } da9052_unlock(da9052); rtc_tm->tm_year = msg[--loop_index].data & DA9052_ALARMY_ALARMYEAR; rtc_tm->tm_mon = msg[--loop_index].data & DA9052_ALARMMO_ALARMMONTH; rtc_tm->tm_mday = msg[--loop_index].data & DA9052_ALARMD_ALARMDAY; rtc_tm->tm_hour = msg[--loop_index].data & DA9052_ALARMH_ALARMHOUR; rtc_tm->tm_min = msg[--loop_index].data & DA9052_ALARMMI_ALARMMIN; validate_param = da9052_rtc_validate_parameters(rtc_tm); if (validate_param) return validate_param; /* System compatability */ rtc_tm->tm_year += 100; rtc_tm->tm_mon -= 1; return 0; } static int da9052_alarm_settime(struct da9052 *da9052, struct rtc_time *rtc_tm) { struct da9052_ssc_msg msg_arr[5]; struct da9052_ssc_msg msg; int validate_param = 0; unsigned char loop_index = 0; int ret = 0; rtc_tm->tm_sec = 0; /* System compatability */ rtc_tm->tm_year -= 100; rtc_tm->tm_mon += 1; validate_param = da9052_rtc_validate_parameters(rtc_tm); if (validate_param) return validate_param; msg.addr = DA9052_ALARMMI_REG; msg.data = 0; da9052_lock(da9052); ret = da9052->read(da9052, &msg); if (ret != 0) { da9052_unlock(da9052); return ret; } msg.data = msg.data & ~(DA9052_ALARMMI_ALARMMIN); msg.data |= rtc_tm->tm_min; msg_arr[loop_index].addr = DA9052_ALARMMI_REG; msg_arr[loop_index].data = 0; msg_arr[loop_index++].data = msg.data; msg_arr[loop_index].addr = DA9052_ALARMH_REG; msg_arr[loop_index].data = 0; msg_arr[loop_index++].data = rtc_tm->tm_hour; msg_arr[loop_index].addr = DA9052_ALARMD_REG; msg_arr[loop_index].data = 0; msg_arr[loop_index++].data = rtc_tm->tm_mday; msg_arr[loop_index].addr = DA9052_ALARMMO_REG; msg_arr[loop_index].data = 0; msg_arr[loop_index++].data = rtc_tm->tm_mon; msg.addr = DA9052_ALARMY_REG; msg.data = 0; ret = da9052->read(da9052, &msg); if (ret != 0) { da9052_unlock(da9052); return ret; } msg.data = msg.data & ~(DA9052_ALARMY_ALARMYEAR); msg.data |= rtc_tm->tm_year; msg_arr[loop_index].addr = DA9052_ALARMY_REG; msg_arr[loop_index].data = 0; msg_arr[loop_index++].data = msg.data; ret = da9052->write_many(da9052, msg_arr, loop_index); if (ret) { da9052_unlock(da9052); return ret; } da9052_unlock(da9052); return 0; } static int da9052_rtc_get_alarm_status(struct da9052 *da9052) { struct da9052_ssc_msg msg; int ret = 0; msg.addr = DA9052_ALARMY_REG; msg.data = 0; da9052_lock(da9052); ret = da9052->read(da9052, &msg); if (ret != 0) { da9052_unlock(da9052); return ret; } da9052_unlock(da9052); msg.data &= DA9052_ALARMY_ALARMON; return (msg.data > 0) ? 1 : 0; } static int da9052_rtc_enable_alarm(struct da9052 *da9052, unsigned char flag) { struct da9052_ssc_msg msg; int ret = 0; msg.addr = DA9052_ALARMY_REG; da9052_lock(da9052); ret = da9052->read(da9052, &msg); if (ret != 0) { da9052_unlock(da9052); return ret; } if (flag) msg.data = msg.data | DA9052_ALARMY_ALARMON; else msg.data = msg.data & ~(DA9052_ALARMY_ALARMON); ret = da9052->write(da9052, &msg); if (ret != 0) { da9052_unlock(da9052); return ret; } da9052_unlock(da9052); return 0; } static ssize_t da9052_rtc_mask_irq(struct da9052 *da9052) { unsigned char data = 0; ssize_t ret = 0; struct da9052_ssc_msg ssc_msg; ssc_msg.addr = DA9052_IRQMASKA_REG; ssc_msg.data = 0; da9052_lock(da9052); ret = da9052->read(da9052, &ssc_msg); if (ret != 0) { da9052_unlock(da9052); return ret; } data = ret; ssc_msg.data = data |= DA9052_IRQMASKA_MALRAM; ret = da9052->write(da9052, &ssc_msg); if (ret != 0) { da9052_unlock(da9052); return ret; } da9052_unlock(da9052); return 0; } static ssize_t da9052_rtc_unmask_irq(struct da9052 *da9052) { unsigned char data = 0; ssize_t ret = 0; struct da9052_ssc_msg ssc_msg; ssc_msg.addr = DA9052_IRQMASKA_REG; ssc_msg.data = 0; da9052_lock(da9052); ret = da9052->read(da9052, &ssc_msg); if (ret != 0) { da9052_unlock(da9052); return ret; } data = ret; ssc_msg.data = data &= ~DA9052_IRQMASKA_MALRAM; ret = da9052->write(da9052, &ssc_msg); if (ret != 0) { da9052_unlock(da9052); return ret; } da9052_unlock(da9052); return 0; } static int da9052_rtc_class_ops_gettime (struct device *dev, struct rtc_time *rtc_tm) { int ret; struct da9052 *da9052 = dev_get_drvdata(dev->parent); ret = da9052_rtc_gettime(da9052, rtc_tm); if (ret) return ret; return 0; } static int da9052_rtc_class_ops_settime(struct device *dev, struct rtc_time *tm) { int ret; struct da9052 *da9052 = dev_get_drvdata(dev->parent); ret = da9052_rtc_settime(da9052, tm); return ret; } static int da9052_rtc_readalarm(struct device *dev, struct rtc_wkalrm *alrm) { int ret; struct rtc_time *tm = &alrm->time; struct da9052 *da9052 = dev_get_drvdata(dev->parent); ret = da9052_alarm_gettime(da9052, tm); if (ret) return ret; alrm->enabled = da9052_rtc_get_alarm_status(da9052); return 0; } static int da9052_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm) { int ret = 0; struct rtc_time *tm = &alrm->time; struct da9052 *da9052 = dev_get_drvdata(dev->parent); ret = da9052_alarm_settime(da9052, tm); if (ret) return ret; ret = da9052_rtc_enable_alarm(da9052, 1); return ret; } static int da9052_rtc_update_irq_enable(struct device *dev, unsigned int enabled) { struct da9052_rtc *priv = dev_get_drvdata(dev); int ret = -ENODATA; da9052_lock(priv->da9052); ret = (enabled ? da9052_rtc_unmask_irq : da9052_rtc_mask_irq) (priv->da9052); da9052_unlock(priv->da9052); return ret; } static int da9052_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) { struct da9052_rtc *priv = dev_get_drvdata(dev); if (enabled) return da9052_rtc_enable_alarm(priv->da9052, enabled); else return da9052_rtc_enable_alarm(priv->da9052, enabled); } static const struct rtc_class_ops da9052_rtc_ops = { .read_time = da9052_rtc_class_ops_gettime, .set_time = da9052_rtc_class_ops_settime, .read_alarm = da9052_rtc_readalarm, .set_alarm = da9052_rtc_setalarm, #if 0 .update_irq_enable = da9052_rtc_update_irq_enable, .alarm_irq_enable = da9052_rtc_alarm_irq_enable, #endif }; static int __devinit da9052_rtc_probe(struct platform_device *pdev) { int ret; struct da9052_rtc *priv; struct da9052_ssc_msg ssc_msg; priv = kzalloc(sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; priv->da9052 = dev_get_drvdata(pdev->dev.parent); platform_set_drvdata(pdev, priv); /* Added to support sysfs wakealarm attribute */ pdev->dev.power.can_wakeup = 1; /* Added to support sysfs wakealarm attribute */ /* Set the EH structure */ priv->eh_data.eve_type = ALARM_EVE; priv->eh_data.call_back = &da9052_rtc_notifier; ret = priv->da9052->register_event_notifier(priv->da9052, &priv->eh_data); if (ret) goto err_register_alarm; priv->is_min_alarm = 1; priv->enable_tick_alarm = 1; priv->enable_clk_buffer = 1; priv->set_osc_trim_freq = 5; /* Enable/Disable TICK Alarm */ /* Read ALARM YEAR register */ ssc_msg.addr = DA9052_ALARMY_REG; ssc_msg.data = 0; da9052_lock(priv->da9052); ret = priv->da9052->read(priv->da9052, &ssc_msg); if (ret != 0) { da9052_unlock(priv->da9052); goto err_ssc_comm; } if (priv->enable_tick_alarm) ssc_msg.data = (ssc_msg.data | DA9052_ALARMY_TICKON); else ssc_msg.data = ((ssc_msg.data & ~(DA9052_ALARMY_TICKON))); ret = priv->da9052->write(priv->da9052, &ssc_msg); if (ret != 0) { da9052_unlock(priv->da9052); goto err_ssc_comm; } /* Set TICK Alarm to 1 minute or 1 sec */ /* Read ALARM MINUTES register */ ssc_msg.addr = DA9052_ALARMMI_REG; ssc_msg.data = 0; ret = priv->da9052->read(priv->da9052, &ssc_msg); if (ret != 0) { da9052_unlock(priv->da9052); goto err_ssc_comm; } if (priv->is_min_alarm) /* Set 1 minute tick type */ ssc_msg.data = (ssc_msg.data | DA9052_ALARMMI_TICKTYPE); else /* Set 1 sec tick type */ ssc_msg.data = (ssc_msg.data & ~(DA9052_ALARMMI_TICKTYPE)); ret = priv->da9052->write(priv->da9052, &ssc_msg); if (ret != 0) { da9052_unlock(priv->da9052); goto err_ssc_comm; } /* Enable/Disable Clock buffer in Power Down Mode */ ssc_msg.addr = DA9052_PDDIS_REG; ssc_msg.data = 0; ret = priv->da9052->read(priv->da9052, &ssc_msg); if (ret != 0) { da9052_unlock(priv->da9052); goto err_ssc_comm; } if (priv->enable_clk_buffer) ssc_msg.data = (ssc_msg.data | DA9052_PDDIS_OUT32KPD); else ssc_msg.data = (ssc_msg.data & ~(DA9052_PDDIS_OUT32KPD)); ret = priv->da9052->write(priv->da9052, &ssc_msg); if (ret != 0) { da9052_unlock(priv->da9052); goto err_ssc_comm; } /* Set clock trim frequency value */ ssc_msg.addr = DA9052_OSCTRIM_REG; ssc_msg.data = priv->set_osc_trim_freq; ret = priv->da9052->write(priv->da9052, &ssc_msg); if (ret != 0) { da9052_unlock(priv->da9052); goto err_ssc_comm; } da9052_unlock(priv->da9052); priv->rtc = rtc_device_register(pdev->name, &pdev->dev, &da9052_rtc_ops, THIS_MODULE); if (IS_ERR(priv->rtc)) { ret = PTR_ERR(priv->rtc); goto err_ssc_comm; } return 0; err_ssc_comm: priv->da9052->unregister_event_notifier (priv->da9052, &priv->eh_data); err_register_alarm: platform_set_drvdata(pdev, NULL); kfree(priv); return ret; } static int __devexit da9052_rtc_remove(struct platform_device *pdev) { struct da9052_rtc *priv = platform_get_drvdata(pdev); rtc_device_unregister(priv->rtc); da9052_lock(priv->da9052); priv->da9052->unregister_event_notifier(priv->da9052, &priv->eh_data); da9052_unlock(priv->da9052); platform_set_drvdata(pdev, NULL); kfree(priv); return 0; } static struct platform_driver da9052_rtc_driver = { .probe = da9052_rtc_probe, .remove = __devexit_p(da9052_rtc_remove), .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, }, }; static int __init da9052_rtc_init(void) { return platform_driver_register(&da9052_rtc_driver); } module_init(da9052_rtc_init); static void __exit da9052_rtc_exit(void) { platform_driver_unregister(&da9052_rtc_driver); } module_exit(da9052_rtc_exit); MODULE_AUTHOR("Dialog Semiconductor Ltd "); MODULE_DESCRIPTION("RTC driver for Dialog DA9052 PMIC"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:" DRIVER_NAME);