Initial Contribution
msm-2.6.38: tag AU_LINUX_ANDROID_GINGERBREAD.02.03.04.00.142
Signed-off-by: Bryan Huntsman <bryanh@codeaurora.org>
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 27c3774..93feb81 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -94,6 +94,23 @@
If unsure, say Y.
+config RTC_INTF_ALARM
+ bool "Android alarm driver"
+ depends on RTC_CLASS
+ default y
+ help
+ Provides non-wakeup and rtc backed wakeup alarms based on rtc or
+ elapsed realtime, and a non-wakeup alarm on the monotonic clock.
+ Also provides an interface to set the wall time which must be used
+ for elapsed realtime to work.
+
+config RTC_INTF_ALARM_DEV
+ bool "Android alarm device"
+ depends on RTC_INTF_ALARM
+ default y
+ help
+ Exports the alarm interface to user-space.
+
config RTC_INTF_DEV_UIE_EMUL
bool "RTC UIE emulation on dev interface"
depends on RTC_INTF_DEV
@@ -739,6 +756,37 @@
comment "on-CPU RTC drivers"
+config RTC_DRV_MSM
+ tristate "RTC on Qualcomm Chipsets"
+ depends on ARCH_MSM
+ default y
+ help
+ RTC driver for Qualcomm chipsets
+
+
+config RTC_SECURE_TIME_SUPPORT
+ bool "Support for secure time on Qualcomm Chipsets"
+ depends on RTC_DRV_MSM = y
+ default y
+ help
+ Say yes here to have additional handle for reading secure time
+ maintained by ARM9.
+
+config RTC_ASYNC_MODEM_SUPPORT
+ bool "Support for time update on async modem boot"
+ depends on RTC_DRV_MSM && (ARCH_MSM8X60 || ARCH_QSD8X50)
+ default n
+ help
+ Say yes here to have the system time updated if there is
+ an asynchronous MODEM boot.
+
+config RTC_DRV_MSM7X00A
+ tristate "MSM7X00A"
+ depends on ARCH_MSM
+ default n
+ help
+ RTC driver for Qualcomm MSM7K chipsets
+
config RTC_DRV_DAVINCI
tristate "TI DaVinci RTC"
depends on ARCH_DAVINCI_DM365
@@ -749,6 +797,13 @@
This driver can also be built as a module. If so, the module
will be called rtc-davinci.
+config RTC_DRV_MSM7X00A
+ tristate "MSM7X00A"
+ depends on ARCH_MSM
+ default y
+ help
+ RTC driver for Qualcomm MSM7K chipsets
+
config RTC_DRV_OMAP
tristate "TI OMAP1"
depends on ARCH_OMAP15XX || ARCH_OMAP16XX || ARCH_OMAP730 || ARCH_DAVINCI_DA8XX
@@ -1078,4 +1133,33 @@
This drive can also be built as a module. If so, the module
will be called rtc-puv3.
+config RTC_PM8058
+ tristate "PMIC8058 RTC support"
+ default n
+ depends on PMIC8058
+ help
+ Say Y here if you want support for the PMIC8058 RTC.
+
+ To compile this driver as a module, choose M here: the
+ module will be called pmic8058-rtc.
+
+config RTC_PM8058_WRITE_ENABLE
+ bool "PM8058 RTC write enable"
+ default n
+ depends on RTC_PM8058
+ help
+ Say Y here if you want to support the write operation for
+ PMIC8058 RTC.
+
+ By default the write operation is not supported.
+
+config RTC_DRV_PM8XXX
+ tristate "Qualcomm PMIC8XXX RTC"
+ depends on MFD_PM8XXX
+ help
+ Say Y here if you want to support the Qualcomm PMIC8XXX RTC.
+
+ To compile this driver as a module, choose M here: the
+ module will be called rtc-pm8xxx.
+
endif # RTC_CLASS
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 7d27958..9e41297 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -67,6 +67,8 @@
obj-$(CONFIG_RTC_DRV_MAX8998) += rtc-max8998.o
obj-$(CONFIG_RTC_DRV_MAX6902) += rtc-max6902.o
obj-$(CONFIG_RTC_DRV_MC13XXX) += rtc-mc13xxx.o
+obj-$(CONFIG_RTC_DRV_MSM) += rtc-msm.o
+obj-$(CONFIG_RTC_DRV_MSM7X00A) += rtc-msm7x00a.o
obj-$(CONFIG_RTC_DRV_MSM6242) += rtc-msm6242.o
obj-$(CONFIG_RTC_DRV_MPC5121) += rtc-mpc5121.o
obj-$(CONFIG_RTC_DRV_MV) += rtc-mv.o
@@ -77,6 +79,7 @@
obj-$(CONFIG_RTC_DRV_PCF8583) += rtc-pcf8583.o
obj-$(CONFIG_RTC_DRV_PCF2123) += rtc-pcf2123.o
obj-$(CONFIG_RTC_DRV_PCF50633) += rtc-pcf50633.o
+obj-$(CONFIG_RTC_DRV_PM8XXX) += rtc-pm8xxx.o
obj-$(CONFIG_RTC_DRV_PL030) += rtc-pl030.o
obj-$(CONFIG_RTC_DRV_PL031) += rtc-pl031.o
obj-$(CONFIG_RTC_DRV_PS3) += rtc-ps3.o
@@ -110,3 +113,4 @@
obj-$(CONFIG_RTC_DRV_WM831X) += rtc-wm831x.o
obj-$(CONFIG_RTC_DRV_WM8350) += rtc-wm8350.o
obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o
+obj-$(CONFIG_RTC_PM8058) += rtc-pm8058.o
diff --git a/drivers/rtc/alarm.c b/drivers/rtc/alarm.c
index e0e98dd..3e7f698 100644
--- a/drivers/rtc/alarm.c
+++ b/drivers/rtc/alarm.c
@@ -299,6 +299,30 @@
return ret;
}
+
+void
+alarm_update_timedelta(struct timespec tmp_time, struct timespec new_time)
+{
+ int i;
+ unsigned long flags;
+
+ spin_lock_irqsave(&alarm_slock, flags);
+ for (i = 0; i < ANDROID_ALARM_SYSTEMTIME; i++) {
+ hrtimer_try_to_cancel(&alarms[i].timer);
+ alarms[i].stopped = true;
+ alarms[i].stopped_time = timespec_to_ktime(tmp_time);
+ }
+ alarms[ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP].delta =
+ alarms[ANDROID_ALARM_ELAPSED_REALTIME].delta =
+ ktime_sub(alarms[ANDROID_ALARM_ELAPSED_REALTIME].delta,
+ timespec_to_ktime(timespec_sub(tmp_time, new_time)));
+ for (i = 0; i < ANDROID_ALARM_SYSTEMTIME; i++) {
+ alarms[i].stopped = false;
+ update_timer_locked(&alarms[i], false);
+ }
+ spin_unlock_irqrestore(&alarm_slock, flags);
+}
+
/**
* alarm_get_elapsed_realtime - get the elapsed real time in ktime_t format
*
diff --git a/drivers/rtc/hctosys.c b/drivers/rtc/hctosys.c
index bc90b09..29735c2 100644
--- a/drivers/rtc/hctosys.c
+++ b/drivers/rtc/hctosys.c
@@ -24,7 +24,7 @@
int rtc_hctosys_ret = -ENODEV;
-static int __init rtc_hctosys(void)
+int rtc_hctosys(void)
{
int err = -ENODEV;
struct rtc_time tm;
diff --git a/drivers/rtc/rtc-msm.c b/drivers/rtc/rtc-msm.c
new file mode 100644
index 0000000..c17e461
--- /dev/null
+++ b/drivers/rtc/rtc-msm.c
@@ -0,0 +1,819 @@
+/*
+ * Copyright (C) 2008 Google, Inc.
+ * Copyright (c) 2009-2011 Code Aurora Forum. All rights reserved.
+ * Author: San Mehat <san@google.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/android_alarm.h>
+
+#include <linux/rtc.h>
+#include <linux/rtc-msm.h>
+#include <linux/msm_rpcrouter.h>
+#include <mach/msm_rpcrouter.h>
+
+#define APP_TIMEREMOTE_PDEV_NAME "rs00000000"
+
+#define TIMEREMOTE_PROCEEDURE_SET_JULIAN 6
+#define TIMEREMOTE_PROCEEDURE_GET_JULIAN 7
+#ifdef CONFIG_RTC_SECURE_TIME_SUPPORT
+#define TIMEREMOTE_PROCEEDURE_GET_SECURE_JULIAN 11
+#define TIMEREMOTE_PROCEEDURE_SET_SECURE_JULIAN 16
+#endif
+#define TIMEREMOTE_PROG_NUMBER 0x30000048
+#define TIMEREMOTE_PROG_VER_1 0x00010001
+#define TIMEREMOTE_PROG_VER_2 0x00040001
+
+#define RTC_REQUEST_CB_PROC 0x17
+#define RTC_CLIENT_INIT_PROC 0x12
+#define RTC_EVENT_CB_PROC 0x1
+#define RTC_CB_ID 0x1
+
+/* Client request errors */
+enum rtc_rpc_err {
+ ERR_NONE,
+ ERR_CLIENT_ID_PTR, /* Invalid client ID pointer */
+ ERR_CLIENT_TYPE, /* Invalid client type */
+ ERR_CLIENT_ID, /* Invalid client ID */
+ ERR_TASK_NOT_READY, /* task is not ready for clients */
+ ERR_INVALID_PROCESSOR, /* Invalid processor id */
+ ERR_UNSUPPORTED, /* Unsupported request */
+ ERR_GENERAL, /* Any General Error */
+ ERR_RPC, /* Any ONCRPC Error */
+ ERR_ALREADY_REG, /* Client already registered */
+ ERR_MAX
+};
+
+enum processor_type {
+ CLIENT_PROCESSOR_NONE = 0,
+ CLIENT_PROCESSOR_MODEM,
+ CLIENT_PROCESSOR_APP1,
+ CLIENT_PROCESSOR_APP2,
+ CLIENT_PROCESSOR_MAX
+};
+
+/* Client types */
+enum client_type {
+ CLIENT_TYPE_GEN1 = 0,
+ CLIENT_FLOATING1,
+ CLIENT_FLOATING2,
+ CLIENT_TYPE_INTERNAL,
+ CLIENT_TYPE_GENOFF_UPDATE,
+ CLIENT_TYPE_MAX
+};
+
+/* Event types */
+enum event_type {
+ EVENT_TOD_CHANGE = 0,
+ EVENT_GENOFF_CHANGE,
+ EVENT_MAX
+};
+
+struct tod_update_info {
+ uint32_t tick;
+ uint64_t stamp;
+ uint32_t freq;
+};
+
+enum time_bases_info {
+ TIME_RTC = 0,
+ TIME_TOD,
+ TIME_USER,
+ TIME_SECURE,
+ TIME_INVALID
+};
+
+struct genoff_update_info {
+ enum time_bases_info time_base;
+ uint64_t offset;
+};
+
+union cb_info {
+ struct tod_update_info tod_update;
+ struct genoff_update_info genoff_update;
+};
+
+struct rtc_cb_recv {
+ uint32_t client_cb_id;
+ enum event_type event;
+ uint32_t cb_info_ptr;
+ union cb_info cb_info_data;
+};
+
+struct msm_rtc {
+ int proc;
+ struct msm_rpc_client *rpc_client;
+ u8 client_id;
+ struct rtc_device *rtc;
+#ifdef CONFIG_RTC_SECURE_TIME_SUPPORT
+ struct rtc_device *rtcsecure;
+#endif
+ unsigned long rtcalarm_time;
+};
+
+struct rpc_time_julian {
+ uint32_t year;
+ uint32_t month;
+ uint32_t day;
+ uint32_t hour;
+ uint32_t minute;
+ uint32_t second;
+ uint32_t day_of_week;
+};
+
+struct rtc_tod_args {
+ int proc;
+ struct rtc_time *tm;
+};
+
+#ifdef CONFIG_PM
+struct suspend_state_info {
+ atomic_t state;
+ int64_t tick_at_suspend;
+};
+
+static struct suspend_state_info suspend_state = {ATOMIC_INIT(0), 0};
+
+void msmrtc_updateatsuspend(struct timespec *ts)
+{
+ int64_t now, sleep, sclk_max;
+
+ if (atomic_read(&suspend_state.state)) {
+ now = msm_timer_get_sclk_time(&sclk_max);
+
+ if (now && suspend_state.tick_at_suspend) {
+ if (now < suspend_state.tick_at_suspend) {
+ sleep = sclk_max -
+ suspend_state.tick_at_suspend + now;
+ } else
+ sleep = now - suspend_state.tick_at_suspend;
+
+ timespec_add_ns(ts, sleep);
+ suspend_state.tick_at_suspend = now;
+ } else
+ pr_err("%s: Invalid ticks from SCLK now=%lld"
+ "tick_at_suspend=%lld", __func__, now,
+ suspend_state.tick_at_suspend);
+ }
+
+}
+#else
+void msmrtc_updateatsuspend(struct timespec *ts) { }
+#endif
+EXPORT_SYMBOL(msmrtc_updateatsuspend);
+
+static int msmrtc_tod_proc_args(struct msm_rpc_client *client, void *buff,
+ void *data)
+{
+ struct rtc_tod_args *rtc_args = data;
+
+ if ((rtc_args->proc == TIMEREMOTE_PROCEEDURE_SET_JULIAN)
+#ifdef CONFIG_RTC_SECURE_TIME_SUPPORT
+ || (rtc_args->proc == TIMEREMOTE_PROCEEDURE_SET_SECURE_JULIAN)
+#endif
+ ) {
+ struct timeremote_set_julian_req {
+ uint32_t opt_arg;
+ struct rpc_time_julian time;
+ };
+ struct timeremote_set_julian_req *set_req = buff;
+
+ set_req->opt_arg = cpu_to_be32(0x1);
+ set_req->time.year = cpu_to_be32(rtc_args->tm->tm_year);
+ set_req->time.month = cpu_to_be32(rtc_args->tm->tm_mon + 1);
+ set_req->time.day = cpu_to_be32(rtc_args->tm->tm_mday);
+ set_req->time.hour = cpu_to_be32(rtc_args->tm->tm_hour);
+ set_req->time.minute = cpu_to_be32(rtc_args->tm->tm_min);
+ set_req->time.second = cpu_to_be32(rtc_args->tm->tm_sec);
+ set_req->time.day_of_week = cpu_to_be32(rtc_args->tm->tm_wday);
+
+ return sizeof(*set_req);
+
+ } else if ((rtc_args->proc == TIMEREMOTE_PROCEEDURE_GET_JULIAN)
+#ifdef CONFIG_RTC_SECURE_TIME_SUPPORT
+ || (rtc_args->proc == TIMEREMOTE_PROCEEDURE_GET_SECURE_JULIAN)
+#endif
+ ) {
+ *(uint32_t *)buff = (uint32_t) cpu_to_be32(0x1);
+
+ return sizeof(uint32_t);
+ } else
+ return 0;
+}
+
+static bool rtc_check_overflow(struct rtc_time *tm)
+{
+ if (tm->tm_year < 138)
+ return false;
+
+ if (tm->tm_year > 138)
+ return true;
+
+ if ((tm->tm_year == 138) && (tm->tm_mon == 0) && (tm->tm_mday < 19))
+ return false;
+
+ return true;
+}
+
+static int msmrtc_tod_proc_result(struct msm_rpc_client *client, void *buff,
+ void *data)
+{
+ struct rtc_tod_args *rtc_args = data;
+
+ if ((rtc_args->proc == TIMEREMOTE_PROCEEDURE_GET_JULIAN)
+#ifdef CONFIG_RTC_SECURE_TIME_SUPPORT
+ || (rtc_args->proc == TIMEREMOTE_PROCEEDURE_GET_SECURE_JULIAN)
+#endif
+ ) {
+ struct timeremote_get_julian_rep {
+ uint32_t opt_arg;
+ struct rpc_time_julian time;
+ };
+ struct timeremote_get_julian_rep *result = buff;
+
+ if (be32_to_cpu(result->opt_arg) != 0x1)
+ return -ENODATA;
+
+ rtc_args->tm->tm_year = be32_to_cpu(result->time.year);
+ rtc_args->tm->tm_mon = be32_to_cpu(result->time.month);
+ rtc_args->tm->tm_mday = be32_to_cpu(result->time.day);
+ rtc_args->tm->tm_hour = be32_to_cpu(result->time.hour);
+ rtc_args->tm->tm_min = be32_to_cpu(result->time.minute);
+ rtc_args->tm->tm_sec = be32_to_cpu(result->time.second);
+ rtc_args->tm->tm_wday = be32_to_cpu(result->time.day_of_week);
+
+ pr_debug("%s: %.2u/%.2u/%.4u %.2u:%.2u:%.2u (%.2u)\n",
+ __func__, rtc_args->tm->tm_mon, rtc_args->tm->tm_mday,
+ rtc_args->tm->tm_year, rtc_args->tm->tm_hour,
+ rtc_args->tm->tm_min, rtc_args->tm->tm_sec,
+ rtc_args->tm->tm_wday);
+
+ /* RTC layer expects years to start at 1900 */
+ rtc_args->tm->tm_year -= 1900;
+ /* RTC layer expects mons to be 0 based */
+ rtc_args->tm->tm_mon--;
+
+ if (rtc_valid_tm(rtc_args->tm) < 0) {
+ pr_err("%s: Retrieved data/time not valid\n", __func__);
+ rtc_time_to_tm(0, rtc_args->tm);
+ }
+
+ /*
+ * Check if the time received is > 01-19-2038, to prevent
+ * overflow. In such a case, return the EPOCH time.
+ */
+ if (rtc_check_overflow(rtc_args->tm) == true) {
+ pr_err("Invalid time (year > 2038)\n");
+ rtc_time_to_tm(0, rtc_args->tm);
+ }
+
+ return 0;
+ } else
+ return 0;
+}
+
+static int
+msmrtc_timeremote_set_time(struct device *dev, struct rtc_time *tm)
+{
+ int rc;
+ struct rtc_tod_args rtc_args;
+ struct msm_rtc *rtc_pdata = dev_get_drvdata(dev);
+
+ if (tm->tm_year < 1900)
+ tm->tm_year += 1900;
+
+ if (tm->tm_year < 1970)
+ return -EINVAL;
+
+ dev_dbg(dev, "%s: %.2u/%.2u/%.4u %.2u:%.2u:%.2u (%.2u)\n",
+ __func__, tm->tm_mon, tm->tm_mday, tm->tm_year,
+ tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_wday);
+
+ rtc_args.proc = TIMEREMOTE_PROCEEDURE_SET_JULIAN;
+ rtc_args.tm = tm;
+ rc = msm_rpc_client_req(rtc_pdata->rpc_client,
+ TIMEREMOTE_PROCEEDURE_SET_JULIAN,
+ msmrtc_tod_proc_args, &rtc_args,
+ NULL, NULL, -1);
+ if (rc) {
+ dev_err(dev, "%s: rtc time (TOD) could not be set\n", __func__);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int
+msmrtc_timeremote_read_time(struct device *dev, struct rtc_time *tm)
+{
+ int rc;
+ struct rtc_tod_args rtc_args;
+ struct msm_rtc *rtc_pdata = dev_get_drvdata(dev);
+
+ rtc_args.proc = TIMEREMOTE_PROCEEDURE_GET_JULIAN;
+ rtc_args.tm = tm;
+
+ rc = msm_rpc_client_req(rtc_pdata->rpc_client,
+ TIMEREMOTE_PROCEEDURE_GET_JULIAN,
+ msmrtc_tod_proc_args, &rtc_args,
+ msmrtc_tod_proc_result, &rtc_args, -1);
+
+ if (rc) {
+ dev_err(dev, "%s: Error retrieving rtc (TOD) time\n", __func__);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int
+msmrtc_virtual_alarm_set(struct device *dev, struct rtc_wkalrm *a)
+{
+ struct msm_rtc *rtc_pdata = dev_get_drvdata(dev);
+ unsigned long now = get_seconds();
+
+ if (!a->enabled) {
+ rtc_pdata->rtcalarm_time = 0;
+ return 0;
+ } else
+ rtc_tm_to_time(&a->time, &(rtc_pdata->rtcalarm_time));
+
+ if (now > rtc_pdata->rtcalarm_time) {
+ dev_err(dev, "%s: Attempt to set alarm in the past\n",
+ __func__);
+ rtc_pdata->rtcalarm_time = 0;
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static struct rtc_class_ops msm_rtc_ops = {
+ .read_time = msmrtc_timeremote_read_time,
+ .set_time = msmrtc_timeremote_set_time,
+ .set_alarm = msmrtc_virtual_alarm_set,
+};
+
+#ifdef CONFIG_RTC_SECURE_TIME_SUPPORT
+static int
+msmrtc_timeremote_set_time_secure(struct device *dev, struct rtc_time *tm)
+{
+ int rc;
+ struct rtc_tod_args rtc_args;
+ struct msm_rtc *rtc_pdata = dev_get_drvdata(dev);
+
+ if (tm->tm_year < 1900)
+ tm->tm_year += 1900;
+
+ if (tm->tm_year < 1970)
+ return -EINVAL;
+
+ dev_dbg(dev, "%s: %.2u/%.2u/%.4u %.2u:%.2u:%.2u (%.2u)\n",
+ __func__, tm->tm_mon, tm->tm_mday, tm->tm_year,
+ tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_wday);
+
+ rtc_args.proc = TIMEREMOTE_PROCEEDURE_SET_SECURE_JULIAN;
+ rtc_args.tm = tm;
+
+ rc = msm_rpc_client_req(rtc_pdata->rpc_client,
+ TIMEREMOTE_PROCEEDURE_SET_SECURE_JULIAN,
+ msmrtc_tod_proc_args, &rtc_args,
+ NULL, NULL, -1);
+ if (rc) {
+ dev_err(dev,
+ "%s: rtc secure time could not be set\n", __func__);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int
+msmrtc_timeremote_read_time_secure(struct device *dev, struct rtc_time *tm)
+{
+ int rc;
+ struct rtc_tod_args rtc_args;
+ struct msm_rtc *rtc_pdata = dev_get_drvdata(dev);
+ rtc_args.proc = TIMEREMOTE_PROCEEDURE_GET_SECURE_JULIAN;
+ rtc_args.tm = tm;
+
+ rc = msm_rpc_client_req(rtc_pdata->rpc_client,
+ TIMEREMOTE_PROCEEDURE_GET_SECURE_JULIAN, msmrtc_tod_proc_args,
+ &rtc_args, msmrtc_tod_proc_result, &rtc_args, -1);
+
+ if (rc) {
+ dev_err(dev,
+ "%s: Error retrieving secure rtc time\n", __func__);
+ return rc;
+ }
+
+ return 0;
+}
+
+static struct rtc_class_ops msm_rtc_ops_secure = {
+ .read_time = msmrtc_timeremote_read_time_secure,
+ .set_time = msmrtc_timeremote_set_time_secure,
+};
+#endif
+
+static void process_cb_request(void *buffer)
+{
+ struct rtc_cb_recv *rtc_cb = buffer;
+ struct timespec ts, tv;
+
+ rtc_cb->client_cb_id = be32_to_cpu(rtc_cb->client_cb_id);
+ rtc_cb->event = be32_to_cpu(rtc_cb->event);
+ rtc_cb->cb_info_ptr = be32_to_cpu(rtc_cb->cb_info_ptr);
+
+ if (rtc_cb->event == EVENT_TOD_CHANGE) {
+ /* A TOD update has been received from the Modem */
+ rtc_cb->cb_info_data.tod_update.tick =
+ be32_to_cpu(rtc_cb->cb_info_data.tod_update.tick);
+ rtc_cb->cb_info_data.tod_update.stamp =
+ be64_to_cpu(rtc_cb->cb_info_data.tod_update.stamp);
+ rtc_cb->cb_info_data.tod_update.freq =
+ be32_to_cpu(rtc_cb->cb_info_data.tod_update.freq);
+ pr_info("RPC CALL -- TOD TIME UPDATE: ttick = %d\n"
+ "stamp=%lld, freq = %d\n",
+ rtc_cb->cb_info_data.tod_update.tick,
+ rtc_cb->cb_info_data.tod_update.stamp,
+ rtc_cb->cb_info_data.tod_update.freq);
+
+ getnstimeofday(&ts);
+ msmrtc_updateatsuspend(&ts);
+ rtc_hctosys();
+ getnstimeofday(&tv);
+ /* Update the alarm information with the new time info. */
+ alarm_update_timedelta(ts, tv);
+
+ } else
+ pr_err("%s: Unknown event EVENT=%x\n",
+ __func__, rtc_cb->event);
+}
+
+static int msmrtc_cb_func(struct msm_rpc_client *client, void *buffer, int size)
+{
+ int rc = -1;
+ struct rpc_request_hdr *recv = buffer;
+
+ recv->xid = be32_to_cpu(recv->xid);
+ recv->type = be32_to_cpu(recv->type);
+ recv->rpc_vers = be32_to_cpu(recv->rpc_vers);
+ recv->prog = be32_to_cpu(recv->prog);
+ recv->vers = be32_to_cpu(recv->vers);
+ recv->procedure = be32_to_cpu(recv->procedure);
+
+ if (recv->procedure == RTC_EVENT_CB_PROC)
+ process_cb_request((void *) (recv + 1));
+
+ msm_rpc_start_accepted_reply(client, recv->xid,
+ RPC_ACCEPTSTAT_SUCCESS);
+
+ rc = msm_rpc_send_accepted_reply(client, 0);
+ if (rc) {
+ pr_debug("%s: sending reply failed: %d\n", __func__, rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int msmrtc_rpc_proc_args(struct msm_rpc_client *client, void *buff,
+ void *data)
+{
+ struct msm_rtc *rtc_pdata = data;
+
+ if (rtc_pdata->proc == RTC_CLIENT_INIT_PROC) {
+ /* arguments passed to the client_init function */
+ struct rtc_client_init_req {
+ enum client_type client;
+ uint32_t client_id_ptr;
+ u8 client_id;
+ enum processor_type processor;
+ };
+ struct rtc_client_init_req *req_1 = buff;
+
+ req_1->client = cpu_to_be32(CLIENT_TYPE_INTERNAL);
+ req_1->client_id_ptr = cpu_to_be32(0x1);
+ req_1->client_id = (u8) cpu_to_be32(0x1);
+ req_1->processor = cpu_to_be32(CLIENT_PROCESSOR_APP1);
+
+ return sizeof(*req_1);
+
+ } else if (rtc_pdata->proc == RTC_REQUEST_CB_PROC) {
+ /* arguments passed to the request_cb function */
+ struct rtc_event_req {
+ u8 client_id;
+ uint32_t rtc_cb_id;
+ };
+ struct rtc_event_req *req_2 = buff;
+
+ req_2->client_id = (u8) cpu_to_be32(rtc_pdata->client_id);
+ req_2->rtc_cb_id = cpu_to_be32(RTC_CB_ID);
+
+ return sizeof(*req_2);
+ } else
+ return 0;
+}
+
+static int msmrtc_rpc_proc_result(struct msm_rpc_client *client, void *buff,
+ void *data)
+{
+ uint32_t result = -EINVAL;
+ struct msm_rtc *rtc_pdata = data;
+
+ if (rtc_pdata->proc == RTC_CLIENT_INIT_PROC) {
+ /* process reply received from client_init function */
+ uint32_t client_id_ptr;
+ result = be32_to_cpu(*(uint32_t *)buff);
+ buff += sizeof(uint32_t);
+ client_id_ptr = be32_to_cpu(*(uint32_t *)(buff));
+ buff += sizeof(uint32_t);
+ if (client_id_ptr == 1)
+ rtc_pdata->client_id = (u8)
+ be32_to_cpu(*(uint32_t *)(buff));
+ else {
+ pr_debug("%s: Client-id not received from Modem\n",
+ __func__);
+ return -EINVAL;
+ }
+ } else if (rtc_pdata->proc == RTC_REQUEST_CB_PROC) {
+ /* process reply received from request_cb function */
+ result = be32_to_cpu(*(uint32_t *)buff);
+ }
+
+ if (result == ERR_NONE) {
+ pr_debug("%s: RPC client reply for PROC=%x success\n",
+ __func__, rtc_pdata->proc);
+ return 0;
+ }
+
+ pr_debug("%s: RPC client registration failed ERROR=%x\n",
+ __func__, result);
+ return -EINVAL;
+}
+
+static int msmrtc_setup_cb(struct msm_rtc *rtc_pdata)
+{
+ int rc;
+
+ /* Register with the server with client specific info */
+ rtc_pdata->proc = RTC_CLIENT_INIT_PROC;
+ rc = msm_rpc_client_req(rtc_pdata->rpc_client, RTC_CLIENT_INIT_PROC,
+ msmrtc_rpc_proc_args, rtc_pdata,
+ msmrtc_rpc_proc_result, rtc_pdata, -1);
+ if (rc) {
+ pr_debug("%s: RPC client registration for PROC:%x failed\n",
+ __func__, RTC_CLIENT_INIT_PROC);
+ return rc;
+ }
+
+ /* Register with server for the callback event */
+ rtc_pdata->proc = RTC_REQUEST_CB_PROC;
+ rc = msm_rpc_client_req(rtc_pdata->rpc_client, RTC_REQUEST_CB_PROC,
+ msmrtc_rpc_proc_args, rtc_pdata,
+ msmrtc_rpc_proc_result, rtc_pdata, -1);
+ if (rc) {
+ pr_debug("%s: RPC client registration for PROC:%x failed\n",
+ __func__, RTC_REQUEST_CB_PROC);
+ }
+
+ return rc;
+}
+
+static int __devinit
+msmrtc_probe(struct platform_device *pdev)
+{
+ int rc;
+ struct msm_rtc *rtc_pdata = NULL;
+ struct rpcsvr_platform_device *rdev =
+ container_of(pdev, struct rpcsvr_platform_device, base);
+ uint32_t prog_version;
+
+
+ if (pdev->id == (TIMEREMOTE_PROG_VER_1 & RPC_VERSION_MAJOR_MASK))
+ prog_version = TIMEREMOTE_PROG_VER_1;
+ else if (pdev->id == (TIMEREMOTE_PROG_VER_2 &
+ RPC_VERSION_MAJOR_MASK))
+ prog_version = TIMEREMOTE_PROG_VER_2;
+ else
+ return -EINVAL;
+
+ rtc_pdata = kzalloc(sizeof(*rtc_pdata), GFP_KERNEL);
+ if (rtc_pdata == NULL) {
+ dev_err(&pdev->dev,
+ "%s: Unable to allocate memory\n", __func__);
+ return -ENOMEM;
+ }
+ rtc_pdata->rpc_client = msm_rpc_register_client("rtc", rdev->prog,
+ prog_version, 1, msmrtc_cb_func);
+ if (IS_ERR(rtc_pdata->rpc_client)) {
+ dev_err(&pdev->dev,
+ "%s: init RPC failed! VERS = %x\n", __func__,
+ prog_version);
+ rc = PTR_ERR(rtc_pdata->rpc_client);
+ kfree(rtc_pdata);
+ return rc;
+ }
+
+ /*
+ * Set up the callback client.
+ * For older targets this initialization will fail
+ */
+ rc = msmrtc_setup_cb(rtc_pdata);
+ if (rc)
+ dev_dbg(&pdev->dev, "%s: Could not initialize RPC callback\n",
+ __func__);
+
+ rtc_pdata->rtcalarm_time = 0;
+ platform_set_drvdata(pdev, rtc_pdata);
+
+ rtc_pdata->rtc = rtc_device_register("msm_rtc",
+ &pdev->dev,
+ &msm_rtc_ops,
+ THIS_MODULE);
+ if (IS_ERR(rtc_pdata->rtc)) {
+ dev_err(&pdev->dev, "%s: Can't register RTC device (%ld)\n",
+ pdev->name, PTR_ERR(rtc_pdata->rtc));
+ rc = PTR_ERR(rtc_pdata->rtc);
+ goto fail_cb_setup;
+ }
+
+#ifdef CONFIG_RTC_SECURE_TIME_SUPPORT
+ rtc_pdata->rtcsecure = rtc_device_register("msm_rtc_secure",
+ &pdev->dev,
+ &msm_rtc_ops_secure,
+ THIS_MODULE);
+
+ if (IS_ERR(rtc_pdata->rtcsecure)) {
+ dev_err(&pdev->dev,
+ "%s: Can't register RTC Secure device (%ld)\n",
+ pdev->name, PTR_ERR(rtc_pdata->rtcsecure));
+ rtc_device_unregister(rtc_pdata->rtc);
+ rc = PTR_ERR(rtc_pdata->rtcsecure);
+ goto fail_cb_setup;
+ }
+#endif
+
+#ifdef CONFIG_RTC_ASYNC_MODEM_SUPPORT
+ rtc_hctosys();
+#endif
+
+ return 0;
+
+fail_cb_setup:
+ msm_rpc_unregister_client(rtc_pdata->rpc_client);
+ kfree(rtc_pdata);
+ return rc;
+}
+
+
+#ifdef CONFIG_PM
+
+static void
+msmrtc_alarmtimer_expired(unsigned long _data,
+ struct msm_rtc *rtc_pdata)
+{
+ pr_debug("%s: Generating alarm event (src %lu)\n",
+ rtc_pdata->rtc->name, _data);
+
+ rtc_update_irq(rtc_pdata->rtc, 1, RTC_IRQF | RTC_AF);
+ rtc_pdata->rtcalarm_time = 0;
+}
+
+static int
+msmrtc_suspend(struct platform_device *dev, pm_message_t state)
+{
+ int rc, diff;
+ struct rtc_time tm;
+ unsigned long now;
+ struct msm_rtc *rtc_pdata = platform_get_drvdata(dev);
+
+ suspend_state.tick_at_suspend = msm_timer_get_sclk_time(NULL);
+ if (rtc_pdata->rtcalarm_time) {
+ rc = msmrtc_timeremote_read_time(&dev->dev, &tm);
+ if (rc) {
+ dev_err(&dev->dev,
+ "%s: Unable to read from RTC\n", __func__);
+ return rc;
+ }
+ rtc_tm_to_time(&tm, &now);
+ diff = rtc_pdata->rtcalarm_time - now;
+ if (diff <= 0) {
+ msmrtc_alarmtimer_expired(1 , rtc_pdata);
+ msm_pm_set_max_sleep_time(0);
+ atomic_inc(&suspend_state.state);
+ return 0;
+ }
+ msm_pm_set_max_sleep_time((int64_t)
+ ((int64_t) diff * NSEC_PER_SEC));
+ } else
+ msm_pm_set_max_sleep_time(0);
+ atomic_inc(&suspend_state.state);
+ return 0;
+}
+
+static int
+msmrtc_resume(struct platform_device *dev)
+{
+ int rc, diff;
+ struct rtc_time tm;
+ unsigned long now;
+ struct msm_rtc *rtc_pdata = platform_get_drvdata(dev);
+
+ if (rtc_pdata->rtcalarm_time) {
+ rc = msmrtc_timeremote_read_time(&dev->dev, &tm);
+ if (rc) {
+ dev_err(&dev->dev,
+ "%s: Unable to read from RTC\n", __func__);
+ return rc;
+ }
+ rtc_tm_to_time(&tm, &now);
+ diff = rtc_pdata->rtcalarm_time - now;
+ if (diff <= 0)
+ msmrtc_alarmtimer_expired(2 , rtc_pdata);
+ }
+ suspend_state.tick_at_suspend = 0;
+ atomic_dec(&suspend_state.state);
+ return 0;
+}
+#else
+#define msmrtc_suspend NULL
+#define msmrtc_resume NULL
+#endif
+
+static int __devexit msmrtc_remove(struct platform_device *pdev)
+{
+ struct msm_rtc *rtc_pdata = platform_get_drvdata(pdev);
+
+ rtc_device_unregister(rtc_pdata->rtc);
+#ifdef CONFIG_RTC_SECURE_TIME_SUPPORT
+ rtc_device_unregister(rtc_pdata->rtcsecure);
+#endif
+ msm_rpc_unregister_client(rtc_pdata->rpc_client);
+ kfree(rtc_pdata);
+
+ return 0;
+}
+
+static struct platform_driver msmrtc_driver = {
+ .probe = msmrtc_probe,
+ .suspend = msmrtc_suspend,
+ .resume = msmrtc_resume,
+ .remove = __devexit_p(msmrtc_remove),
+ .driver = {
+ .name = APP_TIMEREMOTE_PDEV_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init msmrtc_init(void)
+{
+ int rc;
+
+ /*
+ * For backward compatibility, register multiple platform
+ * drivers with the RPC PROG_VERS to be supported.
+ *
+ * Explicit cast away of 'constness' for driver.name in order to
+ * initialize it here.
+ */
+ snprintf((char *)msmrtc_driver.driver.name,
+ strlen(msmrtc_driver.driver.name)+1,
+ "rs%08x", TIMEREMOTE_PROG_NUMBER);
+ pr_debug("RTC Registering with %s\n", msmrtc_driver.driver.name);
+
+ rc = platform_driver_register(&msmrtc_driver);
+ if (rc)
+ pr_err("%s: platfrom_driver_register failed\n", __func__);
+
+ return rc;
+}
+
+static void __exit msmrtc_exit(void)
+{
+ platform_driver_unregister(&msmrtc_driver);
+}
+
+module_init(msmrtc_init);
+module_exit(msmrtc_exit);
+
+MODULE_DESCRIPTION("RTC driver for Qualcomm MSM7x00a chipsets");
+MODULE_AUTHOR("San Mehat <san@android.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/rtc/rtc-msm7x00a.c b/drivers/rtc/rtc-msm7x00a.c
new file mode 100644
index 0000000..690bc39
--- /dev/null
+++ b/drivers/rtc/rtc-msm7x00a.c
@@ -0,0 +1,280 @@
+/* drivers/rtc/rtc-msm7x00a.c
+ *
+ * Copyright (C) 2008 Google, Inc.
+ * Author: San Mehat <san@google.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/version.h>
+
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/rtc.h>
+#include <linux/msm_rpcrouter.h>
+
+#include <mach/msm_rpcrouter.h>
+
+#define RTC_DEBUG 0
+
+extern void msm_pm_set_max_sleep_time(int64_t sleep_time_ns);
+
+#if CONFIG_MSM_AMSS_VERSION >= 6350 || defined(CONFIG_ARCH_QSD8X50)
+#define APP_TIMEREMOTE_PDEV_NAME "rs30000048:00010000"
+#else
+#define APP_TIMEREMOTE_PDEV_NAME "rs30000048:0da5b528"
+#endif
+
+#define TIMEREMOTE_PROCEEDURE_SET_JULIAN 6
+#define TIMEREMOTE_PROCEEDURE_GET_JULIAN 7
+
+struct rpc_time_julian {
+ uint32_t year;
+ uint32_t month;
+ uint32_t day;
+ uint32_t hour;
+ uint32_t minute;
+ uint32_t second;
+ uint32_t day_of_week;
+};
+
+static struct msm_rpc_endpoint *ep;
+static struct rtc_device *rtc;
+static unsigned long rtcalarm_time;
+
+static int
+msmrtc_timeremote_set_time(struct device *dev, struct rtc_time *tm)
+{
+ int rc;
+
+ struct timeremote_set_julian_req {
+ struct rpc_request_hdr hdr;
+ uint32_t opt_arg;
+
+ struct rpc_time_julian time;
+ } req;
+
+ struct timeremote_set_julian_rep {
+ struct rpc_reply_hdr hdr;
+ } rep;
+
+ if (tm->tm_year < 1900)
+ tm->tm_year += 1900;
+
+ if (tm->tm_year < 1970)
+ return -EINVAL;
+
+#if RTC_DEBUG
+ printk(KERN_DEBUG "%s: %.2u/%.2u/%.4u %.2u:%.2u:%.2u (%.2u)\n",
+ __func__, tm->tm_mon, tm->tm_mday, tm->tm_year,
+ tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_wday);
+#endif
+
+ req.opt_arg = cpu_to_be32(1);
+ req.time.year = cpu_to_be32(tm->tm_year);
+ req.time.month = cpu_to_be32(tm->tm_mon + 1);
+ req.time.day = cpu_to_be32(tm->tm_mday);
+ req.time.hour = cpu_to_be32(tm->tm_hour);
+ req.time.minute = cpu_to_be32(tm->tm_min);
+ req.time.second = cpu_to_be32(tm->tm_sec);
+ req.time.day_of_week = cpu_to_be32(tm->tm_wday);
+
+
+ rc = msm_rpc_call_reply(ep, TIMEREMOTE_PROCEEDURE_SET_JULIAN,
+ &req, sizeof(req),
+ &rep, sizeof(rep),
+ 5 * HZ);
+ return rc;
+}
+
+static int
+msmrtc_timeremote_read_time(struct device *dev, struct rtc_time *tm)
+{
+ int rc;
+
+ struct timeremote_get_julian_req {
+ struct rpc_request_hdr hdr;
+ uint32_t julian_time_not_null;
+ } req;
+
+ struct timeremote_get_julian_rep {
+ struct rpc_reply_hdr hdr;
+ uint32_t opt_arg;
+ struct rpc_time_julian time;
+ } rep;
+
+ req.julian_time_not_null = cpu_to_be32(1);
+
+ rc = msm_rpc_call_reply(ep, TIMEREMOTE_PROCEEDURE_GET_JULIAN,
+ &req, sizeof(req),
+ &rep, sizeof(rep),
+ 5 * HZ);
+ if (rc < 0)
+ return rc;
+
+ if (!be32_to_cpu(rep.opt_arg)) {
+ printk(KERN_ERR "%s: No data from RTC\n", __func__);
+ return -ENODATA;
+ }
+
+ tm->tm_year = be32_to_cpu(rep.time.year);
+ tm->tm_mon = be32_to_cpu(rep.time.month);
+ tm->tm_mday = be32_to_cpu(rep.time.day);
+ tm->tm_hour = be32_to_cpu(rep.time.hour);
+ tm->tm_min = be32_to_cpu(rep.time.minute);
+ tm->tm_sec = be32_to_cpu(rep.time.second);
+ tm->tm_wday = be32_to_cpu(rep.time.day_of_week);
+
+#if RTC_DEBUG
+ printk(KERN_DEBUG "%s: %.2u/%.2u/%.4u %.2u:%.2u:%.2u (%.2u)\n",
+ __func__, tm->tm_mon, tm->tm_mday, tm->tm_year,
+ tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_wday);
+#endif
+
+ tm->tm_year -= 1900; /* RTC layer expects years to start at 1900 */
+ tm->tm_mon--; /* RTC layer expects mons to be 0 based */
+
+ if (rtc_valid_tm(tm) < 0) {
+ dev_err(dev, "retrieved date/time is not valid.\n");
+ rtc_time_to_tm(0, tm);
+ }
+
+ return 0;
+}
+
+
+static int
+msmrtc_virtual_alarm_set(struct device *dev, struct rtc_wkalrm *a)
+{
+ unsigned long now = get_seconds();
+
+ if (!a->enabled) {
+ rtcalarm_time = 0;
+ return 0;
+ } else
+ rtc_tm_to_time(&a->time, &rtcalarm_time);
+
+ if (now > rtcalarm_time) {
+ printk(KERN_ERR "%s: Attempt to set alarm in the past\n",
+ __func__);
+ rtcalarm_time = 0;
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static struct rtc_class_ops msm_rtc_ops = {
+ .read_time = msmrtc_timeremote_read_time,
+ .set_time = msmrtc_timeremote_set_time,
+ .set_alarm = msmrtc_virtual_alarm_set,
+};
+
+static void
+msmrtc_alarmtimer_expired(unsigned long _data)
+{
+#if RTC_DEBUG
+ printk(KERN_DEBUG "%s: Generating alarm event (src %lu)\n",
+ rtc->name, _data);
+#endif
+ rtc_update_irq(rtc, 1, RTC_IRQF | RTC_AF);
+ rtcalarm_time = 0;
+}
+
+static int
+msmrtc_probe(struct platform_device *pdev)
+{
+ struct rpcsvr_platform_device *rdev =
+ container_of(pdev, struct rpcsvr_platform_device, base);
+
+ ep = msm_rpc_connect(rdev->prog, rdev->vers, 0);
+ if (IS_ERR(ep)) {
+ printk(KERN_ERR "%s: init rpc failed! rc = %ld\n",
+ __func__, PTR_ERR(ep));
+ return PTR_ERR(ep);
+ }
+
+ rtc = rtc_device_register("msm_rtc",
+ &pdev->dev,
+ &msm_rtc_ops,
+ THIS_MODULE);
+ if (IS_ERR(rtc)) {
+ printk(KERN_ERR "%s: Can't register RTC device (%ld)\n",
+ pdev->name, PTR_ERR(rtc));
+ return PTR_ERR(rtc);
+ }
+ return 0;
+}
+
+
+static unsigned long msmrtc_get_seconds(void)
+{
+ struct rtc_time tm;
+ unsigned long now;
+
+ msmrtc_timeremote_read_time(NULL, &tm);
+ rtc_tm_to_time(&tm, &now);
+ return now;
+}
+
+static int
+msmrtc_suspend(struct platform_device *dev, pm_message_t state)
+{
+ if (rtcalarm_time) {
+ unsigned long now = msmrtc_get_seconds();
+ int diff = rtcalarm_time - now;
+ if (diff <= 0) {
+ msmrtc_alarmtimer_expired(1);
+ msm_pm_set_max_sleep_time(0);
+ return 0;
+ }
+ msm_pm_set_max_sleep_time((int64_t) ((int64_t) diff * NSEC_PER_SEC));
+ } else
+ msm_pm_set_max_sleep_time(0);
+ return 0;
+}
+
+static int
+msmrtc_resume(struct platform_device *dev)
+{
+ if (rtcalarm_time) {
+ unsigned long now = msmrtc_get_seconds();
+ int diff = rtcalarm_time - now;
+ if (diff <= 0)
+ msmrtc_alarmtimer_expired(2);
+ }
+ return 0;
+}
+
+static struct platform_driver msmrtc_driver = {
+ .probe = msmrtc_probe,
+ .suspend = msmrtc_suspend,
+ .resume = msmrtc_resume,
+ .driver = {
+ .name = APP_TIMEREMOTE_PDEV_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init msmrtc_init(void)
+{
+ rtcalarm_time = 0;
+ return platform_driver_register(&msmrtc_driver);
+}
+
+module_init(msmrtc_init);
+
+MODULE_DESCRIPTION("RTC driver for Qualcomm MSM7x00a chipsets");
+MODULE_AUTHOR("San Mehat <san@android.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/rtc/rtc-pm8058.c b/drivers/rtc/rtc-pm8058.c
new file mode 100644
index 0000000..5d9111a
--- /dev/null
+++ b/drivers/rtc/rtc-pm8058.c
@@ -0,0 +1,563 @@
+/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rtc.h>
+#include <linux/mfd/pmic8058.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/rtc/rtc-pm8058.h>
+#include <linux/pm_runtime.h>
+
+#define PM8058_RTC_CTRL 0x1E8
+ #define PM8058_RTC_ENABLE BIT(7)
+ #define PM8058_RTC_ALARM_ENABLE BIT(1)
+#define PM8058_RTC_ALARM_CTRL 0x1E9
+ #define PM8058_RTC_ALARM_CLEAR BIT(0)
+#define PM8058_RTC_TEST 0x1F6
+#define PM8058_RTC_READ_BASE 0x1EE
+#define PM8058_RTC_WRITE_BASE 0x1EA
+#define PM8058_RTC_ALARM_BASE 0x1F2
+
+struct pm8058_rtc {
+ struct rtc_device *rtc0;
+ u8 rtc_ctrl_reg;
+ int rtc_irq;
+ int rtc_alarm_irq;
+ struct pm8058_chip *pm_chip;
+};
+
+static int
+pm8058_rtc_read_bytes(struct pm8058_rtc *rtc_dd, u8 *rtc_val, int base)
+{
+ int i, rc;
+
+ /*
+ * Read the 32-bit RTC/Alarm Value.
+ * These values have to be read 8-bit at a time.
+ */
+ for (i = 0; i < 4; i++) {
+ rc = pm8058_read(rtc_dd->pm_chip, base + i, &rtc_val[i], 1);
+ if (rc < 0) {
+ pr_err("%s: PM8058 read failed\n", __func__);
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+static int
+pm8058_rtc_write_bytes(struct pm8058_rtc *rtc_dd, u8 *rtc_val, int base)
+{
+ int i, rc;
+
+ /*
+ * Write the 32-bit Value.
+ * These values have to be written 8-bit at a time.
+ */
+ for (i = 0; i < 4; i++) {
+ rc = pm8058_write(rtc_dd->pm_chip, base + i, &rtc_val[i], 1);
+ if (rc < 0) {
+ pr_err("%s: PM8058 read failed\n", __func__);
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Steps to write the RTC registers.
+ * 1. Disable alarm if enabled.
+ * 2. Write 0x00 to LSB.
+ * 3. Write Byte[1], Byte[2], Byte[3] then Byte[0].
+ * 4. Enable alarm if disabled earlier.
+ */
+#ifdef CONFIG_RTC_PM8058_WRITE_ENABLE
+static int
+pm8058_rtc0_set_time(struct device *dev, struct rtc_time *tm)
+{
+ int rc;
+ unsigned long secs = 0;
+ u8 value[4], reg = 0, alarm_enabled = 0, ctrl_reg = 0, i;
+ struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev);
+
+ ctrl_reg = rtc_dd->rtc_ctrl_reg;
+
+ rtc_tm_to_time(tm, &secs);
+
+ value[0] = secs & 0xFF;
+ value[1] = (secs >> 8) & 0xFF;
+ value[2] = (secs >> 16) & 0xFF;
+ value[3] = (secs >> 24) & 0xFF;
+
+ pr_debug("%s: Seconds value to be written to RTC = %lu\n", __func__,
+ secs);
+ /* Disable alarm before updating RTC */
+ if (ctrl_reg & PM8058_RTC_ALARM_ENABLE) {
+ alarm_enabled = 1;
+ ctrl_reg &= ~PM8058_RTC_ALARM_ENABLE;
+ rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_CTRL,
+ &ctrl_reg, 1);
+ if (rc < 0) {
+ pr_err("%s: PM8058 write failed\n", __func__);
+ return rc;
+ }
+ }
+
+ /* Write Byte[1], Byte[2], Byte[3], Byte[0] */
+ reg = 0;
+ rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_WRITE_BASE, ®, 1);
+ if (rc < 0) {
+ pr_err("%s: PM8058 write failed\n", __func__);
+ return rc;
+ }
+
+ for (i = 1; i < 4; i++) {
+ rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_WRITE_BASE + i,
+ &value[i], 1);
+ if (rc < 0) {
+ pr_err("%s:Write to RTC registers failed\n", __func__);
+ return rc;
+ }
+ }
+
+ rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_WRITE_BASE,
+ &value[0], 1);
+ if (rc < 0) {
+ pr_err("%s: PM8058 write failed\n", __func__);
+ return rc;
+ }
+
+ if (alarm_enabled) {
+ ctrl_reg |= PM8058_RTC_ALARM_ENABLE;
+ rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_CTRL,
+ &ctrl_reg, 1);
+ if (rc < 0) {
+ pr_err("%s: PM8058 write failed\n", __func__);
+ return rc;
+ }
+ }
+
+ rtc_dd->rtc_ctrl_reg = ctrl_reg;
+
+ return 0;
+}
+#endif
+
+static int
+pm8058_rtc0_read_time(struct device *dev, struct rtc_time *tm)
+{
+ int rc;
+ u8 value[4], reg;
+ unsigned long secs = 0;
+ struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev);
+
+ rc = pm8058_rtc_read_bytes(rtc_dd, value, PM8058_RTC_READ_BASE);
+ if (rc < 0) {
+ pr_err("%s: RTC time read failed\n", __func__);
+ return rc;
+ }
+
+ /*
+ * Read the LSB again and check if there has been a carry over.
+ * If there is, redo the read operation.
+ */
+ rc = pm8058_read(rtc_dd->pm_chip, PM8058_RTC_READ_BASE, ®, 1);
+ if (rc < 0) {
+ pr_err("%s: PM8058 read failed\n", __func__);
+ return rc;
+ }
+
+ if (unlikely(reg < value[0])) {
+ rc = pm8058_rtc_read_bytes(rtc_dd, value,
+ PM8058_RTC_READ_BASE);
+ if (rc < 0) {
+ pr_err("%s: RTC time read failed\n", __func__);
+ return rc;
+ }
+ }
+
+ secs = value[0] | (value[1] << 8) | (value[2] << 16) | (value[3] << 24);
+
+ rtc_time_to_tm(secs, tm);
+
+ rc = rtc_valid_tm(tm);
+ if (rc < 0) {
+ pr_err("%s: Invalid time read from PMIC8058\n", __func__);
+ return rc;
+ }
+
+ pr_debug("%s: secs = %lu, h::m:s == %d::%d::%d, d/m/y = %d/%d/%d\n",
+ __func__, secs, tm->tm_hour, tm->tm_min, tm->tm_sec,
+ tm->tm_mday, tm->tm_mon, tm->tm_year);
+
+ return 0;
+}
+
+static int
+pm8058_rtc0_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+ int rc;
+ u8 value[4], reg;
+ struct rtc_time rtc_tm;
+ unsigned long secs_alarm, secs_rtc;
+ struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev);
+
+ reg = rtc_dd->rtc_ctrl_reg;
+
+ /* Check if the alarm is valid */
+ rc = rtc_valid_tm(&alarm->time);
+ if (rc < 0) {
+ pr_err("%s: Alarm time invalid\n", __func__);
+ return -EINVAL;
+ }
+
+ rtc_tm_to_time(&alarm->time, &secs_alarm);
+
+ /*
+ * Read the current RTC time and verify if the alarm time is in the
+ * past. If yes, return invalid.
+ */
+ rc = pm8058_rtc0_read_time(dev, &rtc_tm);
+ if (rc) {
+ pr_err("%s: Unable to read RTC time\n", __func__);
+ return -EINVAL;
+ }
+ rtc_tm_to_time(&rtc_tm, &secs_rtc);
+
+ if (secs_alarm < secs_rtc) {
+ pr_err("%s: Trying to set alarm in the past\n", __func__);
+ return -EINVAL;
+ }
+
+ value[0] = secs_alarm & 0xFF;
+ value[1] = (secs_alarm >> 8) & 0xFF;
+ value[2] = (secs_alarm >> 16) & 0xFF;
+ value[3] = (secs_alarm >> 24) & 0xFF;
+
+ rc = pm8058_rtc_write_bytes(rtc_dd, value, PM8058_RTC_ALARM_BASE);
+ if (rc < 0) {
+ pr_err("%s: Alarm could not be set\n", __func__);
+ return rc;
+ }
+
+ reg = (alarm->enabled) ? (reg | PM8058_RTC_ALARM_ENABLE) :
+ (reg & ~PM8058_RTC_ALARM_ENABLE);
+
+ rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_CTRL, ®, 1);
+ if (rc < 0) {
+ pr_err("%s: PM8058 write failed\n", __func__);
+ return rc;
+ }
+
+ rtc_dd->rtc_ctrl_reg = reg;
+
+ pr_debug("%s: Alarm Set for h:r:s=%d:%d:%d, d/m/y=%d/%d/%d\n",
+ __func__, alarm->time.tm_hour, alarm->time.tm_min,
+ alarm->time.tm_sec, alarm->time.tm_mday,
+ alarm->time.tm_mon, alarm->time.tm_year);
+
+ return 0;
+}
+
+static int
+pm8058_rtc0_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+ int rc;
+ u8 value[4], reg;
+ unsigned long secs = 0;
+ struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev);
+
+ reg = rtc_dd->rtc_ctrl_reg;
+
+ alarm->enabled = !!(reg & PM8058_RTC_ALARM_ENABLE);
+
+ rc = pm8058_rtc_read_bytes(rtc_dd, value,
+ PM8058_RTC_ALARM_BASE);
+ if (rc < 0) {
+ pr_err("%s: RTC alarm time read failed\n", __func__);
+ return rc;
+ }
+
+ secs = value[0] | (value[1] << 8) | (value[2] << 16) | (value[3] << 24);
+
+ rtc_time_to_tm(secs, &alarm->time);
+
+ rc = rtc_valid_tm(&alarm->time);
+ if (rc < 0) {
+ pr_err("%s: Invalid time read from PMIC8058\n", __func__);
+ return rc;
+ }
+
+ pr_debug("%s: Alarm set for - h:r:s=%d:%d:%d, d/m/y=%d/%d/%d\n",
+ __func__, alarm->time.tm_hour, alarm->time.tm_min,
+ alarm->time.tm_sec, alarm->time.tm_mday,
+ alarm->time.tm_mon, alarm->time.tm_year);
+
+ return 0;
+}
+
+
+static int
+pm8058_rtc0_alarm_irq_enable(struct device *dev, unsigned int enable)
+{
+ int rc;
+ struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev);
+ u8 reg;
+
+ reg = rtc_dd->rtc_ctrl_reg;
+ reg = (enable) ? (reg | PM8058_RTC_ALARM_ENABLE) :
+ (reg & ~PM8058_RTC_ALARM_ENABLE);
+
+ rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_CTRL, ®, 1);
+ if (rc < 0) {
+ pr_err("%s: PM8058 write failed\n", __func__);
+ return rc;
+ }
+
+ rtc_dd->rtc_ctrl_reg = reg;
+
+ return rc;
+}
+
+static struct rtc_class_ops pm8058_rtc0_ops = {
+ .read_time = pm8058_rtc0_read_time,
+ .set_alarm = pm8058_rtc0_set_alarm,
+ .read_alarm = pm8058_rtc0_read_alarm,
+ .alarm_irq_enable = pm8058_rtc0_alarm_irq_enable,
+};
+
+static irqreturn_t pm8058_alarm_trigger(int irq, void *dev_id)
+{
+ u8 reg;
+ int rc;
+ unsigned long events = 0;
+ struct pm8058_rtc *rtc_dd = dev_id;
+
+ events = RTC_IRQF | RTC_AF;
+ rtc_update_irq(rtc_dd->rtc0, 1, events);
+
+ pr_debug("%s: Alarm Triggered !!\n", __func__);
+
+ /* Clear the alarm enable bit */
+ reg = rtc_dd->rtc_ctrl_reg;
+
+ reg &= ~PM8058_RTC_ALARM_ENABLE;
+ rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_CTRL,
+ ®, 1);
+ if (rc < 0) {
+ pr_err("%s: PM8058 write failed\n", __func__);
+ goto rtc_alarm_handled;
+ }
+
+ rtc_dd->rtc_ctrl_reg = reg;
+
+ /* Clear RTC alarm register */
+ rc = pm8058_read(rtc_dd->pm_chip, PM8058_RTC_ALARM_CTRL, ®, 1);
+ if (rc < 0) {
+ pr_err("%s: PM8058 read failed\n", __func__);
+ goto rtc_alarm_handled;
+ }
+
+ reg &= ~PM8058_RTC_ALARM_CLEAR;
+ rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_ALARM_CTRL, ®, 1);
+ if (rc < 0) {
+ pr_err("%s: PM8058 write failed\n", __func__);
+ goto rtc_alarm_handled;
+ }
+
+rtc_alarm_handled:
+ return IRQ_HANDLED;
+}
+
+static int __devinit pm8058_rtc_probe(struct platform_device *pdev)
+{
+ int rc;
+ u8 reg, reg_alarm;
+ struct pm8058_rtc *rtc_dd;
+ struct pm8058_chip *pm_chip;
+
+ pm_chip = platform_get_drvdata(pdev);
+ if (pm_chip == NULL) {
+ pr_err("%s: Invalid driver information\n", __func__);
+ return -ENXIO;
+ }
+
+ rtc_dd = kzalloc(sizeof(*rtc_dd), GFP_KERNEL);
+ if (rtc_dd == NULL) {
+ pr_err("%s: Unable to allocate memory\n", __func__);
+ return -ENOMEM;
+ }
+
+ /* Enable runtime PM ops, start in ACTIVE mode */
+ rc = pm_runtime_set_active(&pdev->dev);
+ if (rc < 0)
+ dev_dbg(&pdev->dev, "unable to set runtime pm state\n");
+ pm_runtime_enable(&pdev->dev);
+
+ rtc_dd->rtc_irq = platform_get_irq(pdev, 0);
+ rtc_dd->rtc_alarm_irq = platform_get_irq(pdev, 1);
+ if (!rtc_dd->rtc_alarm_irq || !rtc_dd->rtc_irq) {
+ pr_err("%s: RTC Alarm IRQ absent\n", __func__);
+ rc = -ENXIO;
+ goto fail_rtc_enable;
+ }
+
+ rtc_dd->pm_chip = pm_chip;
+
+ rc = pm8058_read(pm_chip, PM8058_RTC_CTRL, ®, 1);
+ if (rc < 0) {
+ pr_err("%s: PM8058 read failed\n", __func__);
+ goto fail_rtc_enable;
+ }
+
+ if (!(reg & PM8058_RTC_ENABLE)) {
+ /* Enable RTC, clear alarm register */
+ reg |= PM8058_RTC_ENABLE;
+ reg &= ~PM8058_RTC_ALARM_ENABLE;
+ rc = pm8058_write(pm_chip, PM8058_RTC_CTRL, ®, 1);
+ if (rc < 0) {
+ pr_err("%s: PM8058 write failed\n", __func__);
+ goto fail_rtc_enable;
+ }
+
+ /* Clear RTC alarm register */
+ rc = pm8058_read(rtc_dd->pm_chip, PM8058_RTC_ALARM_CTRL,
+ ®_alarm, 1);
+ if (rc < 0) {
+ pr_err("%s: PM8058 read failed\n", __func__);
+ goto fail_rtc_enable;
+ }
+
+ reg_alarm &= ~PM8058_RTC_ALARM_CLEAR;
+ rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_ALARM_CTRL,
+ ®_alarm, 1);
+ if (rc < 0) {
+ pr_err("%s: PM8058 write failed\n", __func__);
+ goto fail_rtc_enable;
+ }
+ }
+ rtc_dd->rtc_ctrl_reg = reg;
+
+#ifdef CONFIG_RTC_PM8058_WRITE_ENABLE
+ pm8058_rtc0_ops.set_time = pm8058_rtc0_set_time,
+#endif
+
+ /* Register the RTC device */
+ rtc_dd->rtc0 = rtc_device_register("pm8058_rtc0", &pdev->dev,
+ &pm8058_rtc0_ops, THIS_MODULE);
+ if (IS_ERR(rtc_dd->rtc0)) {
+ pr_err("%s: RTC device registration failed (%ld)\n",
+ __func__, PTR_ERR(rtc_dd->rtc0));
+ rc = PTR_ERR(rtc_dd->rtc0);
+ goto fail_rtc_enable;
+ }
+
+ platform_set_drvdata(pdev, rtc_dd);
+
+ /* Request the alarm IRQ */
+ rc = request_threaded_irq(rtc_dd->rtc_alarm_irq, NULL,
+ pm8058_alarm_trigger, IRQF_TRIGGER_RISING,
+ "pm8058_rtc_alarm", rtc_dd);
+ if (rc < 0) {
+ pr_err("%s: Request IRQ failed (%d)\n", __func__, rc);
+ goto fail_req_irq;
+ }
+
+ device_init_wakeup(&pdev->dev, 1);
+
+ pr_debug("%s: Probe success !!\n", __func__);
+
+ return 0;
+
+fail_req_irq:
+ rtc_device_unregister(rtc_dd->rtc0);
+fail_rtc_enable:
+ pm_runtime_set_suspended(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+ kfree(rtc_dd);
+ return rc;
+}
+
+#ifdef CONFIG_PM
+static int pm8058_rtc_resume(struct device *dev)
+{
+ struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev);
+
+ if (device_may_wakeup(dev))
+ disable_irq_wake(rtc_dd->rtc_alarm_irq);
+
+ return 0;
+}
+
+static int pm8058_rtc_suspend(struct device *dev)
+{
+ struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev);
+
+ if (device_may_wakeup(dev))
+ enable_irq_wake(rtc_dd->rtc_alarm_irq);
+
+ return 0;
+}
+
+static struct dev_pm_ops pm8058_rtc_pm_ops = {
+ .suspend = pm8058_rtc_suspend,
+ .resume = pm8058_rtc_resume,
+};
+#endif
+
+static int __devexit pm8058_rtc_remove(struct platform_device *pdev)
+{
+ struct pm8058_rtc *rtc_dd = platform_get_drvdata(pdev);
+
+ pm_runtime_set_suspended(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ device_init_wakeup(&pdev->dev, 0);
+ free_irq(rtc_dd->rtc_alarm_irq, rtc_dd);
+ rtc_device_unregister(rtc_dd->rtc0);
+ kfree(rtc_dd);
+
+ return 0;
+}
+
+static struct platform_driver pm8058_rtc_driver = {
+ .probe = pm8058_rtc_probe,
+ .remove = __devexit_p(pm8058_rtc_remove),
+ .driver = {
+ .name = "pm8058-rtc",
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &pm8058_rtc_pm_ops,
+#endif
+ },
+};
+
+static int __init pm8058_rtc_init(void)
+{
+ return platform_driver_register(&pm8058_rtc_driver);
+}
+
+static void __exit pm8058_rtc_exit(void)
+{
+ platform_driver_unregister(&pm8058_rtc_driver);
+}
+
+module_init(pm8058_rtc_init);
+module_exit(pm8058_rtc_exit);
+
+MODULE_ALIAS("platform:pm8058-rtc");
+MODULE_DESCRIPTION("PMIC8058 RTC driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/rtc/rtc-pm8xxx.c b/drivers/rtc/rtc-pm8xxx.c
new file mode 100644
index 0000000..0bdb89e
--- /dev/null
+++ b/drivers/rtc/rtc-pm8xxx.c
@@ -0,0 +1,569 @@
+/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rtc.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include<linux/spinlock.h>
+
+#include <linux/mfd/pm8xxx/core.h>
+#include <linux/mfd/pm8xxx/rtc.h>
+
+
+/* RTC Register offsets from RTC CTRL REG */
+#define PM8XXX_ALARM_CTRL_OFFSET 0x01
+#define PM8XXX_RTC_WRITE_OFFSET 0x02
+#define PM8XXX_RTC_READ_OFFSET 0x06
+#define PM8XXX_ALARM_RW_OFFSET 0x0A
+
+/* RTC_CTRL register bit fields */
+#define PM8xxx_RTC_ENABLE BIT(7)
+#define PM8xxx_RTC_ALARM_ENABLE BIT(1)
+#define PM8xxx_RTC_ALARM_CLEAR BIT(0)
+
+#define NUM_8_BIT_RTC_REGS 0x4
+
+/**
+ * struct pm8xxx_rtc - rtc driver internal structure
+ * @rtc: rtc device for this driver
+ * @rtc_alarm_irq: rtc alarm irq number
+ */
+struct pm8xxx_rtc {
+ struct rtc_device *rtc;
+ int rtc_alarm_irq;
+ int rtc_base;
+ int rtc_read_base;
+ int rtc_write_base;
+ int alarm_rw_base;
+ u8 ctrl_reg;
+ struct device *rtc_dev;
+ spinlock_t ctrl_reg_lock;
+};
+
+/*
+ * The RTC registers need to be read/written one byte at a time. This is a
+ * hardware limitation.
+ */
+
+static int pm8xxx_read_wrapper(struct pm8xxx_rtc *rtc_dd, u8 *rtc_val,
+ int base, int count)
+{
+ int i, rc;
+ struct device *parent = rtc_dd->rtc_dev->parent;
+
+ for (i = 0; i < count; i++) {
+ rc = pm8xxx_readb(parent, base + i, &rtc_val[i]);
+ if (rc < 0) {
+ dev_err(rtc_dd->rtc_dev, "PM8xxx read failed\n");
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+static int pm8xxx_write_wrapper(struct pm8xxx_rtc *rtc_dd, u8 *rtc_val,
+ int base, int count)
+{
+ int i, rc;
+ struct device *parent = rtc_dd->rtc_dev->parent;
+
+ for (i = 0; i < count; i++) {
+ rc = pm8xxx_writeb(parent, base + i, rtc_val[i]);
+ if (rc < 0) {
+ dev_err(rtc_dd->rtc_dev, "PM8xxx write failed\n");
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+
+/*
+ * Steps to write the RTC registers.
+ * 1. Disable alarm if enabled.
+ * 2. Write 0x00 to LSB.
+ * 3. Write Byte[1], Byte[2], Byte[3] then Byte[0].
+ * 4. Enable alarm if disabled in step 1.
+ */
+static int
+pm8xxx_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+ int rc;
+ unsigned long secs, irq_flags;
+ u8 value[4], reg = 0, alarm_enabled = 0, ctrl_reg;
+ struct pm8xxx_rtc *rtc_dd = dev_get_drvdata(dev);
+
+ rtc_tm_to_time(tm, &secs);
+
+ value[0] = secs & 0xFF;
+ value[1] = (secs >> 8) & 0xFF;
+ value[2] = (secs >> 16) & 0xFF;
+ value[3] = (secs >> 24) & 0xFF;
+
+ dev_dbg(dev, "Seconds value to be written to RTC = %lu\n", secs);
+
+ spin_lock_irqsave(&rtc_dd->ctrl_reg_lock, irq_flags);
+ ctrl_reg = rtc_dd->ctrl_reg;
+
+ if (ctrl_reg & PM8xxx_RTC_ALARM_ENABLE) {
+ alarm_enabled = 1;
+ ctrl_reg &= ~PM8xxx_RTC_ALARM_ENABLE;
+ rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base,
+ 1);
+ if (rc < 0) {
+ dev_err(dev, "PM8xxx write failed\n");
+ goto rtc_rw_fail;
+ }
+ } else
+ spin_unlock_irqrestore(&rtc_dd->ctrl_reg_lock, irq_flags);
+
+ /* Write Byte[1], Byte[2], Byte[3], Byte[0] */
+ /* Write 0 to Byte[0] */
+ reg = 0;
+ rc = pm8xxx_write_wrapper(rtc_dd, ®, rtc_dd->rtc_write_base, 1);
+ if (rc < 0) {
+ dev_err(dev, "PM8xxx write failed\n");
+ goto rtc_rw_fail;
+ }
+
+ /* Write Byte[1], Byte[2], Byte[3] */
+ rc = pm8xxx_write_wrapper(rtc_dd, value + 1,
+ rtc_dd->rtc_write_base + 1, 3);
+ if (rc < 0) {
+ dev_err(dev, "Write to RTC registers failed\n");
+ goto rtc_rw_fail;
+ }
+
+ /* Write Byte[0] */
+ rc = pm8xxx_write_wrapper(rtc_dd, value, rtc_dd->rtc_write_base, 1);
+ if (rc < 0) {
+ dev_err(dev, "Write to RTC register failed\n");
+ goto rtc_rw_fail;
+ }
+
+ if (alarm_enabled) {
+ ctrl_reg |= PM8xxx_RTC_ALARM_ENABLE;
+ rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base,
+ 1);
+ if (rc < 0) {
+ dev_err(dev, "PM8xxx write failed\n");
+ goto rtc_rw_fail;
+ }
+ }
+
+ rtc_dd->ctrl_reg = ctrl_reg;
+
+rtc_rw_fail:
+ if (alarm_enabled)
+ spin_unlock_irqrestore(&rtc_dd->ctrl_reg_lock, irq_flags);
+
+ return rc;
+}
+
+static int
+pm8xxx_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ int rc;
+ u8 value[4], reg;
+ unsigned long secs;
+ struct pm8xxx_rtc *rtc_dd = dev_get_drvdata(dev);
+
+ rc = pm8xxx_read_wrapper(rtc_dd, value, rtc_dd->rtc_read_base,
+ NUM_8_BIT_RTC_REGS);
+ if (rc < 0) {
+ dev_err(dev, "RTC time read failed\n");
+ return rc;
+ }
+
+ /*
+ * Read the LSB again and check if there has been a carry over.
+ * If there is, redo the read operation.
+ */
+ rc = pm8xxx_read_wrapper(rtc_dd, ®, rtc_dd->rtc_read_base, 1);
+ if (rc < 0) {
+ dev_err(dev, "PM8xxx read failed\n");
+ return rc;
+ }
+
+ if (unlikely(reg < value[0])) {
+ rc = pm8xxx_read_wrapper(rtc_dd, value,
+ rtc_dd->rtc_read_base, NUM_8_BIT_RTC_REGS);
+ if (rc < 0) {
+ dev_err(dev, "RTC time read failed\n");
+ return rc;
+ }
+ }
+
+ secs = value[0] | (value[1] << 8) | (value[2] << 16) \
+ | (value[3] << 24);
+
+ rtc_time_to_tm(secs, tm);
+
+ rc = rtc_valid_tm(tm);
+ if (rc < 0) {
+ dev_err(dev, "Invalid time read from PM8xxx\n");
+ return rc;
+ }
+
+ dev_dbg(dev, "secs = %lu, h:m:s == %d:%d:%d, d/m/y = %d/%d/%d\n",
+ secs, tm->tm_hour, tm->tm_min, tm->tm_sec,
+ tm->tm_mday, tm->tm_mon, tm->tm_year);
+
+ return 0;
+}
+
+static int
+pm8xxx_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+ int rc;
+ u8 value[4], ctrl_reg;
+ unsigned long secs, secs_rtc, irq_flags;
+ struct pm8xxx_rtc *rtc_dd = dev_get_drvdata(dev);
+ struct rtc_time rtc_tm;
+
+ rtc_tm_to_time(&alarm->time, &secs);
+
+ /*
+ * Read the current RTC time and verify if the alarm time is in the
+ * past. If yes, return invalid.
+ */
+ rc = pm8xxx_rtc_read_time(dev, &rtc_tm);
+ if (rc < 0) {
+ dev_err(dev, "Unamble to read RTC time\n");
+ return -EINVAL;
+ }
+
+ rtc_tm_to_time(&rtc_tm, &secs_rtc);
+ if (secs < secs_rtc) {
+ dev_err(dev, "Trying to set alarm in the past\n");
+ return -EINVAL;
+ }
+
+ value[0] = secs & 0xFF;
+ value[1] = (secs >> 8) & 0xFF;
+ value[2] = (secs >> 16) & 0xFF;
+ value[3] = (secs >> 24) & 0xFF;
+
+ spin_lock_irqsave(&rtc_dd->ctrl_reg_lock, irq_flags);
+
+ rc = pm8xxx_write_wrapper(rtc_dd, value, rtc_dd->alarm_rw_base,
+ NUM_8_BIT_RTC_REGS);
+ if (rc < 0) {
+ dev_err(dev, "Write to RTC ALARM registers failed\n");
+ goto rtc_rw_fail;
+ }
+
+ ctrl_reg = rtc_dd->ctrl_reg;
+ ctrl_reg = (alarm->enabled) ? (ctrl_reg | PM8xxx_RTC_ALARM_ENABLE) :
+ (ctrl_reg & ~PM8xxx_RTC_ALARM_ENABLE);
+
+ rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base, 1);
+ if (rc < 0) {
+ dev_err(dev, "PM8xxx write failed\n");
+ goto rtc_rw_fail;
+ }
+
+ rtc_dd->ctrl_reg = ctrl_reg;
+
+ dev_dbg(dev, "Alarm Set for h:r:s=%d:%d:%d, d/m/y=%d/%d/%d\n",
+ alarm->time.tm_hour, alarm->time.tm_min,
+ alarm->time.tm_sec, alarm->time.tm_mday,
+ alarm->time.tm_mon, alarm->time.tm_year);
+rtc_rw_fail:
+ spin_unlock_irqrestore(&rtc_dd->ctrl_reg_lock, irq_flags);
+ return rc;
+}
+
+static int
+pm8xxx_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+ int rc;
+ u8 value[4];
+ unsigned long secs;
+ struct pm8xxx_rtc *rtc_dd = dev_get_drvdata(dev);
+
+ rc = pm8xxx_read_wrapper(rtc_dd, value, rtc_dd->alarm_rw_base,
+ NUM_8_BIT_RTC_REGS);
+ if (rc < 0) {
+ dev_err(dev, "RTC alarm time read failed\n");
+ return rc;
+ }
+
+ secs = value[0] | (value[1] << 8) | (value[2] << 16) | \
+ (value[3] << 24);
+
+ rtc_time_to_tm(secs, &alarm->time);
+
+ rc = rtc_valid_tm(&alarm->time);
+ if (rc < 0) {
+ dev_err(dev, "Invalid time read from PM8xxx\n");
+ return rc;
+ }
+
+ dev_dbg(dev, "Alarm set for - h:r:s=%d:%d:%d, d/m/y=%d/%d/%d\n",
+ alarm->time.tm_hour, alarm->time.tm_min,
+ alarm->time.tm_sec, alarm->time.tm_mday,
+ alarm->time.tm_mon, alarm->time.tm_year);
+
+ return 0;
+}
+
+
+static int
+pm8xxx_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
+{
+ int rc;
+ unsigned long irq_flags;
+ struct pm8xxx_rtc *rtc_dd = dev_get_drvdata(dev);
+ u8 ctrl_reg;
+
+ spin_lock_irqsave(&rtc_dd->ctrl_reg_lock, irq_flags);
+ ctrl_reg = rtc_dd->ctrl_reg;
+ ctrl_reg = (enabled) ? (ctrl_reg | PM8xxx_RTC_ALARM_ENABLE) :
+ (ctrl_reg & ~PM8xxx_RTC_ALARM_ENABLE);
+
+ rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base, 1);
+ if (rc < 0) {
+ dev_err(dev, "PM8xxx write failed\n");
+ goto rtc_rw_fail;
+ }
+
+ rtc_dd->ctrl_reg = ctrl_reg;
+
+rtc_rw_fail:
+ spin_unlock_irqrestore(&rtc_dd->ctrl_reg_lock, irq_flags);
+ return rc;
+}
+
+static struct rtc_class_ops pm8xxx_rtc_ops = {
+ .read_time = pm8xxx_rtc_read_time,
+ .set_alarm = pm8xxx_rtc_set_alarm,
+ .read_alarm = pm8xxx_rtc_read_alarm,
+ .alarm_irq_enable = pm8xxx_rtc_alarm_irq_enable,
+};
+
+static irqreturn_t pm8xxx_alarm_trigger(int irq, void *dev_id)
+{
+ struct pm8xxx_rtc *rtc_dd = dev_id;
+ u8 ctrl_reg;
+ int rc;
+ unsigned long irq_flags;
+
+ rtc_update_irq(rtc_dd->rtc, 1, RTC_IRQF | RTC_AF);
+
+ spin_lock_irqsave(&rtc_dd->ctrl_reg_lock, irq_flags);
+
+ /* Clear the alarm enable bit */
+ ctrl_reg = rtc_dd->ctrl_reg;
+ ctrl_reg &= ~PM8xxx_RTC_ALARM_ENABLE;
+
+ rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base, 1);
+ if (rc < 0) {
+ spin_unlock_irqrestore(&rtc_dd->ctrl_reg_lock, irq_flags);
+ dev_err(rtc_dd->rtc_dev, "PM8xxx write failed!\n");
+ goto rtc_alarm_handled;
+ }
+
+ rtc_dd->ctrl_reg = ctrl_reg;
+ spin_unlock_irqrestore(&rtc_dd->ctrl_reg_lock, irq_flags);
+
+ /* Clear RTC alarm register */
+ rc = pm8xxx_read_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base +
+ PM8XXX_ALARM_CTRL_OFFSET, 1);
+ if (rc < 0) {
+ dev_err(rtc_dd->rtc_dev, "PM8xxx write failed!\n");
+ goto rtc_alarm_handled;
+ }
+
+ ctrl_reg &= ~PM8xxx_RTC_ALARM_CLEAR;
+ rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base +
+ PM8XXX_ALARM_CTRL_OFFSET, 1);
+ if (rc < 0)
+ dev_err(rtc_dd->rtc_dev, "PM8xxx write failed!\n");
+
+rtc_alarm_handled:
+ return IRQ_HANDLED;
+}
+
+static int __devinit pm8xxx_rtc_probe(struct platform_device *pdev)
+{
+ int rc;
+ u8 ctrl_reg;
+ bool rtc_write_enable = false;
+ struct pm8xxx_rtc *rtc_dd;
+ struct resource *rtc_resource;
+ const struct pm8xxx_rtc_platform_data *pdata =
+ pdev->dev.platform_data;
+
+ if (pdata != NULL)
+ rtc_write_enable = pdata->rtc_write_enable;
+
+ rtc_dd = kzalloc(sizeof(*rtc_dd), GFP_KERNEL);
+ if (rtc_dd == NULL) {
+ dev_err(&pdev->dev, "Unable to allocate memory!\n");
+ return -ENOMEM;
+ }
+
+ /* Initialise spinlock to protect RTC cntrol register */
+ spin_lock_init(&rtc_dd->ctrl_reg_lock);
+
+ rtc_dd->rtc_alarm_irq = platform_get_irq(pdev, 0);
+ if (rtc_dd->rtc_alarm_irq < 0) {
+ dev_err(&pdev->dev, "Alarm IRQ resource absent!\n");
+ rc = -ENXIO;
+ goto fail_rtc_enable;
+ }
+
+ rtc_resource = platform_get_resource_byname(pdev, IORESOURCE_IO,
+ "pmic_rtc_base");
+ if (!(rtc_resource && rtc_resource->start)) {
+ dev_err(&pdev->dev, "RTC IO resource absent!\n");
+ rc = -ENXIO;
+ goto fail_rtc_enable;
+ }
+
+ rtc_dd->rtc_base = rtc_resource->start;
+
+ /* Setup RTC register addresses */
+ rtc_dd->rtc_write_base = rtc_dd->rtc_base + PM8XXX_RTC_WRITE_OFFSET;
+ rtc_dd->rtc_read_base = rtc_dd->rtc_base + PM8XXX_RTC_READ_OFFSET;
+ rtc_dd->alarm_rw_base = rtc_dd->rtc_base + PM8XXX_ALARM_RW_OFFSET;
+
+ rtc_dd->rtc_dev = &(pdev->dev);
+
+ /* Check if the RTC is on, else turn it on */
+ rc = pm8xxx_read_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base, 1);
+ if (rc < 0) {
+ dev_err(&pdev->dev, "PM8xxx read failed!\n");
+ goto fail_rtc_enable;
+ }
+
+ if (!(ctrl_reg & PM8xxx_RTC_ENABLE)) {
+ ctrl_reg |= PM8xxx_RTC_ENABLE;
+ rc = pm8xxx_write_wrapper(rtc_dd, &ctrl_reg, rtc_dd->rtc_base,
+ 1);
+ if (rc < 0) {
+ dev_err(&pdev->dev, "PM8xxx write failed!\n");
+ goto fail_rtc_enable;
+ }
+ }
+
+ rtc_dd->ctrl_reg = ctrl_reg;
+ if (rtc_write_enable == true)
+ pm8xxx_rtc_ops.set_time = pm8xxx_rtc_set_time;
+
+ platform_set_drvdata(pdev, rtc_dd);
+
+ /* Register the RTC device */
+ rtc_dd->rtc = rtc_device_register("pm8xxx_rtc", &pdev->dev,
+ &pm8xxx_rtc_ops, THIS_MODULE);
+ if (IS_ERR(rtc_dd->rtc)) {
+ dev_err(&pdev->dev, "%s: RTC registration failed (%ld)\n",
+ __func__, PTR_ERR(rtc_dd->rtc));
+ rc = PTR_ERR(rtc_dd->rtc);
+ goto fail_rtc_enable;
+ }
+
+ /* Request the alarm IRQ */
+ rc = request_any_context_irq(rtc_dd->rtc_alarm_irq,
+ pm8xxx_alarm_trigger, IRQF_TRIGGER_RISING,
+ "pm8xxx_rtc_alarm", rtc_dd);
+ if (rc < 0) {
+ dev_err(&pdev->dev, "Request IRQ failed (%d)\n", rc);
+ goto fail_req_irq;
+ }
+
+ device_init_wakeup(&pdev->dev, 1);
+
+ dev_dbg(&pdev->dev, "Probe success !!\n");
+
+ return 0;
+
+fail_req_irq:
+ rtc_device_unregister(rtc_dd->rtc);
+fail_rtc_enable:
+ platform_set_drvdata(pdev, NULL);
+ kfree(rtc_dd);
+ return rc;
+}
+
+#ifdef CONFIG_PM
+static int pm8xxx_rtc_resume(struct device *dev)
+{
+ struct pm8xxx_rtc *rtc_dd = dev_get_drvdata(dev);
+
+ if (device_may_wakeup(dev))
+ disable_irq_wake(rtc_dd->rtc_alarm_irq);
+
+ return 0;
+}
+
+static int pm8xxx_rtc_suspend(struct device *dev)
+{
+ struct pm8xxx_rtc *rtc_dd = dev_get_drvdata(dev);
+
+ if (device_may_wakeup(dev))
+ enable_irq_wake(rtc_dd->rtc_alarm_irq);
+
+ return 0;
+}
+
+static const struct dev_pm_ops pm8xxx_rtc_pm_ops = {
+ .suspend = pm8xxx_rtc_suspend,
+ .resume = pm8xxx_rtc_resume,
+};
+#endif
+static int __devexit pm8xxx_rtc_remove(struct platform_device *pdev)
+{
+ struct pm8xxx_rtc *rtc_dd = platform_get_drvdata(pdev);
+
+ device_init_wakeup(&pdev->dev, 0);
+ free_irq(rtc_dd->rtc_alarm_irq, rtc_dd);
+ rtc_device_unregister(rtc_dd->rtc);
+ platform_set_drvdata(pdev, NULL);
+ kfree(rtc_dd);
+
+ return 0;
+}
+
+static struct platform_driver pm8xxx_rtc_driver = {
+ .probe = pm8xxx_rtc_probe,
+ .remove = __devexit_p(pm8xxx_rtc_remove),
+ .driver = {
+ .name = PM8XXX_RTC_DEV_NAME,
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &pm8xxx_rtc_pm_ops,
+#endif
+ },
+};
+
+static int __init pm8xxx_rtc_init(void)
+{
+ return platform_driver_register(&pm8xxx_rtc_driver);
+}
+module_init(pm8xxx_rtc_init);
+
+static void __exit pm8xxx_rtc_exit(void)
+{
+ platform_driver_unregister(&pm8xxx_rtc_driver);
+}
+module_exit(pm8xxx_rtc_exit);
+
+MODULE_ALIAS("platform:rtc-pm8xxx");
+MODULE_DESCRIPTION("PMIC8xxx RTC driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Anirudh Ghayal <aghayal@codeaurora.org>");