summaryrefslogtreecommitdiff
path: root/drivers/rtc/rtc-sensorhub.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/rtc/rtc-sensorhub.c')
-rw-r--r--drivers/rtc/rtc-sensorhub.c479
1 files changed, 479 insertions, 0 deletions
diff --git a/drivers/rtc/rtc-sensorhub.c b/drivers/rtc/rtc-sensorhub.c
new file mode 100644
index 00000000000..16f95758f93
--- /dev/null
+++ b/drivers/rtc/rtc-sensorhub.c
@@ -0,0 +1,479 @@
+/*
+ * RTC device/driver based on SensorHub
+ * Copyright (C) 2014 Motorola Mobility LLC
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/rtc.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/m4sensorhub.h>
+#include <linux/m4sensorhub/m4sensorhub_registers.h>
+#include <linux/m4sensorhub/m4sensorhub_irqs.h>
+
+#define SECONDS_IN_DAY (24*60*60)
+#define DRIVER_NAME "rtc-sensorhub"
+
+struct rtc_sensorhub_private_data {
+ struct rtc_device *p_rtc;
+ struct m4sensorhub_data *p_m4sensorhub_data;
+ struct rtc_wkalrm next_alarm_set;
+
+ unsigned long m4_seconds_cached;
+ unsigned long m4_boot_cached;
+ unsigned long sys_seconds_cached;
+ unsigned long sys_boot_cached;
+};
+
+static int rtc_sensorhub_rtc_alarm_irq_enable(struct device *p_dev,
+ unsigned int enable)
+{
+ int err = 0;
+ struct platform_device *p_platdev = to_platform_device(p_dev);
+ struct rtc_sensorhub_private_data *p_priv_data =
+ platform_get_drvdata(p_platdev);
+
+ dev_dbg(p_dev, "enable is %u\n", enable);
+
+ if (!(p_priv_data->p_m4sensorhub_data)) {
+ dev_err(p_dev, "Cannot %s alarm--RTC hardware not ready yet\n",
+ (enable ? "set" : "clear"));
+ return -EIO;
+ }
+
+ if (enable == 1) {
+ err = m4sensorhub_irq_enable(p_priv_data->p_m4sensorhub_data,
+ M4SH_IRQ_AP_ALARM_EXPIRED);
+ if (err < 0)
+ dev_err(p_dev, "Enabling IRQ failed (%d)\n", err);
+ } else {
+ err = m4sensorhub_irq_disable(p_priv_data->p_m4sensorhub_data,
+ M4SH_IRQ_AP_ALARM_EXPIRED);
+ if (err < 0)
+ dev_err(p_dev, "Disabling IRQ failed (%d)\n", err);
+ }
+
+ return err;
+}
+
+static int rtc_sensorhub_rtc_read_alarm(struct device *p_dev,
+ struct rtc_wkalrm *p_alrm)
+{
+ struct platform_device *p_platdev = to_platform_device(p_dev);
+ struct rtc_sensorhub_private_data *p_priv_data =
+ platform_get_drvdata(p_platdev);
+ struct rtc_time rtc = p_alrm->time;
+
+ memcpy(p_alrm, &(p_priv_data->next_alarm_set),
+ sizeof(struct rtc_wkalrm));
+
+ dev_info(p_dev, "alarm read for %d-%02d-%02d %02d:%02d:%02d UTC\n",
+ rtc.tm_year + 1900, rtc.tm_mon + 1, rtc.tm_mday,
+ rtc.tm_hour, rtc.tm_min, rtc.tm_sec);
+
+ return 0;
+}
+
+static int rtc_sensorhub_rtc_set_alarm(struct device *p_dev,
+ struct rtc_wkalrm *p_alrm)
+{
+ struct platform_device *p_platdev = to_platform_device(p_dev);
+ struct rtc_sensorhub_private_data *p_priv_data =
+ platform_get_drvdata(p_platdev);
+ struct m4sensorhub_data *p_m4_drvdata =
+ p_priv_data->p_m4sensorhub_data;
+ struct rtc_time rtc = p_alrm->time;
+
+ struct timeval tv_current;
+ unsigned long requested_time, time_diff;
+ int ret;
+
+ dev_info(p_dev, "alarm requested for %d-%02d-%02d %02d:%02d:%02d UTC\n",
+ rtc.tm_year + 1900, rtc.tm_mon + 1, rtc.tm_mday,
+ rtc.tm_hour, rtc.tm_min, rtc.tm_sec);
+
+ if (!p_m4_drvdata) {
+ dev_err(p_dev, "M4 not ready, ignore func call\n");
+ return -EIO;
+ }
+
+ rtc_tm_to_time(&rtc, &requested_time);
+ do_gettimeofday(&tv_current);
+
+ /* make sure alarm requested is for future*/
+ if (requested_time < tv_current.tv_sec) {
+ dev_err(p_dev, "alarm in past, rejecting\n");
+ return -EINVAL;
+ }
+
+ time_diff = requested_time - tv_current.tv_sec;
+ if (time_diff >= SECONDS_IN_DAY || time_diff <= 0) {
+ dev_err(p_dev,
+ "requested alarm out of range, rejecting alarm\n");
+ return -EINVAL;
+ }
+
+ if (m4sensorhub_reg_getsize(p_m4_drvdata, M4SH_REG_GENERAL_APALARM) !=
+ m4sensorhub_reg_write(p_m4_drvdata, M4SH_REG_GENERAL_APALARM,
+ (char *)&time_diff, m4sh_no_mask)) {
+ dev_err(p_dev, "Failed to set M4 alarm!\n");
+ return -EIO;
+ }
+
+ ret = rtc_sensorhub_rtc_alarm_irq_enable(p_dev, p_alrm->enabled);
+ if (ret < 0) {
+ dev_err(p_dev, "failed enabling irq for alarm\n");
+ return ret;
+ }
+
+ /* Store the info abt this alarm in our local datastructure */
+ memcpy(&(p_priv_data->next_alarm_set), p_alrm,
+ sizeof(struct rtc_wkalrm));
+ return 0;
+}
+
+static int rtc_sensorhub_get_rtc_from_m4(struct rtc_time *p_tm,
+ struct m4sensorhub_data *p_m4_drvdata)
+{
+ u32 seconds = 0;
+
+ if (m4sensorhub_reg_getsize(p_m4_drvdata, M4SH_REG_GENERAL_UTC) !=
+ m4sensorhub_reg_read(p_m4_drvdata, M4SH_REG_GENERAL_UTC,
+ (char *)&seconds)) {
+ pr_err("%s: Failed to get M4 clock!\n", DRIVER_NAME);
+ return -EIO;
+ }
+
+ rtc_time_to_tm(seconds, p_tm);
+ return 0;
+}
+
+static int rtc_sensorhub_rtc_read_time(struct device *p_dev,
+ struct rtc_time *p_tm)
+{
+ int err = 0;
+ struct platform_device *p_platdev = to_platform_device(p_dev);
+ struct rtc_sensorhub_private_data *p_priv_data =
+ platform_get_drvdata(p_platdev);
+
+ if (!(p_priv_data->p_m4sensorhub_data)) {
+ if ((p_priv_data->sys_seconds_cached != 0) &&
+ (p_priv_data->sys_boot_cached != 0)) {
+ dev_err(p_dev,
+ "Using saved set RTC time (%lu)\n",
+ p_priv_data->sys_seconds_cached +
+ get_seconds() - p_priv_data->sys_boot_cached);
+ /* Use saved settime request (will go to M4) */
+ rtc_time_to_tm(p_priv_data->sys_seconds_cached +
+ get_seconds() - p_priv_data->sys_boot_cached,
+ p_tm);
+ } else {
+ dev_err(p_dev,
+ "Using cached M4 RTC time (%lu)\n",
+ p_priv_data->m4_seconds_cached +
+ get_seconds() - p_priv_data->m4_boot_cached);
+ /* Use cached time from M4 */
+ rtc_time_to_tm(p_priv_data->m4_seconds_cached +
+ get_seconds() - p_priv_data->m4_boot_cached,
+ p_tm);
+ }
+ return 0;
+ }
+
+ err = rtc_sensorhub_get_rtc_from_m4(p_tm,
+ p_priv_data->p_m4sensorhub_data);
+
+ return err;
+}
+
+static int rtc_sensorhub_rtc_set_time(struct device *p_dev,
+ struct rtc_time *p_tm)
+{
+ unsigned long sec = 0;
+ struct platform_device *p_platdev = to_platform_device(p_dev);
+ struct rtc_sensorhub_private_data *p_priv_data =
+ platform_get_drvdata(p_platdev);
+ struct m4sensorhub_data *p_m4_drvdata =
+ p_priv_data->p_m4sensorhub_data;
+
+ /* M4 expects the UTC time in seconds from Jan 1, 1970,
+ basically epoch_time in seconds */
+ rtc_tm_to_time(p_tm, &sec);
+
+ if (!(p_m4_drvdata)) {
+ dev_err(p_dev,
+ "Saving set time request (%lu)\n", sec);
+ p_priv_data->sys_seconds_cached = sec;
+ p_priv_data->sys_boot_cached = get_seconds();
+ return 0;
+ }
+
+ /* M4 accepts time as u32*/
+ if (m4sensorhub_reg_getsize(p_m4_drvdata, M4SH_REG_GENERAL_UTC) !=
+ m4sensorhub_reg_write(p_m4_drvdata, M4SH_REG_GENERAL_UTC,
+ (char *)&sec, m4sh_no_mask)) {
+ dev_err(p_dev,
+ "set time, but failed to set M4 clock!\n");
+ return -EIO;
+ }
+ dev_dbg(p_dev, "Set RTC time to %d-%02d-%02d %02d:%02d:%02d UTC (%ld)\n",
+ p_tm->tm_year + 1900, p_tm->tm_mon + 1, p_tm->tm_mday,
+ p_tm->tm_hour, p_tm->tm_min, p_tm->tm_sec, sec);
+
+ return 0;
+}
+
+static const struct rtc_class_ops rtc_sensorhub_rtc_ops = {
+ .read_time = rtc_sensorhub_rtc_read_time,
+ .set_time = rtc_sensorhub_rtc_set_time,
+ .read_alarm = rtc_sensorhub_rtc_read_alarm,
+ .set_alarm = rtc_sensorhub_rtc_set_alarm,
+ .alarm_irq_enable = rtc_sensorhub_rtc_alarm_irq_enable,
+};
+
+static int rtc_sensorhub_preflash(struct init_calldata *p_arg)
+{
+ int err = 0;
+ uint32_t seconds = 0;
+ int size = 0;
+ struct rtc_sensorhub_private_data *rtcpd = NULL;
+ struct m4sensorhub_data *m4 = NULL;
+ struct timespec tv = {.tv_sec = 0, .tv_nsec = 0};
+ struct rtc_time rtc;
+
+ if (p_arg == NULL) {
+ pr_err("%s: No callback data received.\n", __func__);
+ err = -ENODATA;
+ goto rtc_sensorhub_preflash_fail;
+ } else if (p_arg->p_data == NULL) {
+ pr_err("%s: No private data received.\n", __func__);
+ err = -ENODATA;
+ goto rtc_sensorhub_preflash_fail;
+ } else if (p_arg->p_m4sensorhub_data == NULL) {
+ pr_err("%s: M4 data is NULL.\n", __func__);
+ err = -ENODATA;
+ goto rtc_sensorhub_preflash_fail;
+ }
+
+ /*
+ * NOTE: We don't want to save the M4 data struct;
+ * M4 will be reflashed after this callback returns
+ * (thus the struct will become invalid but we have
+ * no way to communicate that to the rest of the driver).
+ */
+ rtcpd = p_arg->p_data;
+ m4 = p_arg->p_m4sensorhub_data;
+ size = m4sensorhub_reg_getsize(m4, M4SH_REG_GENERAL_UTC);
+ err = m4sensorhub_reg_read(m4, M4SH_REG_GENERAL_UTC, (char *)&seconds);
+ if (err < 0) {
+ pr_err("%s: Failed to read RTC seconds from M4.\n", __func__);
+ goto rtc_sensorhub_preflash_fail;
+ } else if (err != size) {
+ pr_err("%s: Read %d bytes instead of %d.\n",
+ __func__, err, size);
+ }
+
+ rtcpd->m4_seconds_cached = seconds;
+ rtcpd->m4_boot_cached = get_seconds();
+
+ /* Set the time of day (external processes rely on it) */
+ tv.tv_sec = seconds;
+ err = do_settimeofday(&tv);
+ if (err < 0) {
+ pr_err("%s: Failed to set time of day (err=%d)\n",
+ __func__, err);
+ } else {
+ rtc_time_to_tm(seconds, &rtc);
+ pr_info("%s: %s %d-%02d-%02d %02d:%02d:%02d UTC (%u)\n",
+ __func__, "setting system clock to",
+ rtc.tm_year + 1900, rtc.tm_mon + 1, rtc.tm_mday,
+ rtc.tm_hour, rtc.tm_min, rtc.tm_sec,
+ (unsigned int) tv.tv_sec);
+ }
+
+rtc_sensorhub_preflash_fail:
+ return err;
+}
+
+static void rtc_handle_sensorhub_irq(enum m4sensorhub_irqs int_event,
+ void *p_data)
+{
+ struct rtc_sensorhub_private_data *p_priv_data =
+ (struct rtc_sensorhub_private_data *)(p_data);
+
+ pr_info("%s: RTC alarm fired\n", DRIVER_NAME);
+ rtc_update_irq(p_priv_data->p_rtc, 1, RTC_AF | RTC_IRQF);
+}
+
+static int rtc_sensorhub_init(struct init_calldata *p_arg)
+{
+ struct rtc_time rtc;
+ int err;
+ struct timespec tv = {.tv_sec = 0, .tv_nsec = 0};
+ struct rtc_sensorhub_private_data *p_priv_data =
+ (struct rtc_sensorhub_private_data *)(p_arg->p_data);
+ uint32_t seconds = 0;
+
+ p_priv_data->p_m4sensorhub_data = p_arg->p_m4sensorhub_data;
+
+ if ((p_priv_data->sys_seconds_cached != 0) &&
+ (p_priv_data->sys_boot_cached != 0)) {
+ pr_err("%s: Setting M4 to a saved time request\n", __func__);
+ seconds = p_priv_data->sys_seconds_cached +
+ get_seconds() - p_priv_data->sys_boot_cached;
+ if (m4sensorhub_reg_getsize(p_priv_data->p_m4sensorhub_data,
+ M4SH_REG_GENERAL_UTC) != m4sensorhub_reg_write(
+ p_priv_data->p_m4sensorhub_data, M4SH_REG_GENERAL_UTC,
+ (char *)&seconds, m4sh_no_mask)) {
+ pr_err("%s: Failed to set M4 RTC\n",
+ __func__);
+ return 0;
+ }
+ /*
+ * We don't write directly to tv_sec here because we want
+ * to print below the system clock we are setting,
+ * which means we need to populate rtc anyway.
+ */
+ rtc_time_to_tm(seconds, &rtc);
+ } else {
+ /* read RTC time from M4 and set the system time */
+ err = rtc_sensorhub_get_rtc_from_m4(&rtc,
+ p_priv_data->p_m4sensorhub_data);
+ if (err) {
+ pr_err("%s: get_rtc failed\n", DRIVER_NAME);
+ return 0;
+ }
+ }
+
+ rtc_tm_to_time(&rtc, &tv.tv_sec);
+ err = do_settimeofday(&tv);
+ if (err) {
+ pr_err("%s: settimeofday failed (err=%d)\n", DRIVER_NAME, err);
+ } else {
+ pr_info("%s: %s %d-%02d-%02d %02d:%02d:%02d UTC (%u)\n",
+ __func__, "setting system clock to",
+ rtc.tm_year + 1900, rtc.tm_mon + 1, rtc.tm_mday,
+ rtc.tm_hour, rtc.tm_min, rtc.tm_sec,
+ (unsigned int) tv.tv_sec);
+ }
+
+ /* register an irq handler*/
+ err = m4sensorhub_irq_register(p_priv_data->p_m4sensorhub_data,
+ M4SH_IRQ_AP_ALARM_EXPIRED, rtc_handle_sensorhub_irq,
+ p_priv_data, 1);
+
+ if (err < 0)
+ pr_err("%s: irq register failed\n", DRIVER_NAME);
+
+ return err;
+}
+
+static int rtc_sensorhub_probe(struct platform_device *p_platdev)
+{
+ int err;
+ struct rtc_device *p_rtc;
+ struct rtc_sensorhub_private_data *p_priv_data;
+
+ p_priv_data = kzalloc(sizeof(*p_priv_data),
+ GFP_KERNEL);
+ if (!p_priv_data)
+ return -ENOMEM;
+
+ p_priv_data->p_m4sensorhub_data = NULL;
+ p_priv_data->next_alarm_set.enabled = false;
+ /* Set the private data before registering this driver with RTC core
+ since hctosys will call rtc interface right away, we need to make sure
+ our private data is set by this time */
+ platform_set_drvdata(p_platdev, p_priv_data);
+
+ err = device_init_wakeup(&p_platdev->dev, true);
+ if (err) {
+ dev_err(&(p_platdev->dev), "failed to init as wakeup\n");
+ goto err_free_priv_data;
+ }
+
+ p_rtc = devm_rtc_device_register(&p_platdev->dev, "rtc_sensorhub",
+ &rtc_sensorhub_rtc_ops, THIS_MODULE);
+
+ if (IS_ERR(p_rtc)) {
+ err = PTR_ERR(p_rtc);
+ goto err_disable_wakeup;
+ }
+
+ p_priv_data->p_rtc = p_rtc;
+
+ err = m4sensorhub_register_preflash_callback(rtc_sensorhub_preflash,
+ p_priv_data);
+ if (err < 0) {
+ dev_err(&(p_platdev->dev),
+ "Failed to register M4 preflash callback\n");
+ goto err_unregister_rtc;
+ }
+
+ err = m4sensorhub_register_initcall(rtc_sensorhub_init, p_priv_data);
+ if (err) {
+ dev_err(&(p_platdev->dev), "can't register init with m4\n");
+ goto err_unregister_preflash_callback;
+ }
+
+ return 0;
+
+err_unregister_preflash_callback:
+ m4sensorhub_unregister_preflash_callback(rtc_sensorhub_preflash);
+err_unregister_rtc:
+ devm_rtc_device_unregister(&p_platdev->dev, p_rtc);
+ kfree(p_rtc);
+err_disable_wakeup:
+ device_init_wakeup(&p_platdev->dev, false);
+err_free_priv_data:
+ kfree(p_priv_data);
+ return err;
+}
+
+static int rtc_sensorhub_remove(struct platform_device *p_platdev)
+{
+ struct rtc_sensorhub_private_data *p_priv_data =
+ platform_get_drvdata(p_platdev);
+ struct rtc_device *p_rtc = p_priv_data->p_rtc;
+ device_init_wakeup(&p_platdev->dev, false);
+ devm_rtc_device_unregister(&p_platdev->dev, p_rtc);
+ m4sensorhub_unregister_initcall(rtc_sensorhub_init);
+ m4sensorhub_irq_disable(
+ p_priv_data->p_m4sensorhub_data,
+ M4SH_IRQ_AP_ALARM_EXPIRED);
+ m4sensorhub_irq_unregister(
+ p_priv_data->p_m4sensorhub_data,
+ M4SH_IRQ_AP_ALARM_EXPIRED);
+ kfree(p_priv_data->p_rtc);
+ kfree(p_priv_data);
+ return 0;
+}
+
+static const struct of_device_id of_rtc_sensorhub_match[] = {
+ { .compatible = "mot,rtc_from_sensorhub", },
+ {},
+};
+
+static struct platform_driver rtc_sensorhub_driver = {
+ .probe = rtc_sensorhub_probe,
+ .remove = rtc_sensorhub_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = of_rtc_sensorhub_match,
+ },
+};
+
+module_platform_driver(rtc_sensorhub_driver);
+
+MODULE_AUTHOR("Motorola Mobility LLC");
+MODULE_DESCRIPTION("SensorHub RTC driver/device");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:rtc_sensorhub");