diff options
Diffstat (limited to 'drivers/mxc/pmic/mc9sdz60/mcu_pmic_rtc.c')
-rw-r--r-- | drivers/mxc/pmic/mc9sdz60/mcu_pmic_rtc.c | 599 |
1 files changed, 599 insertions, 0 deletions
diff --git a/drivers/mxc/pmic/mc9sdz60/mcu_pmic_rtc.c b/drivers/mxc/pmic/mc9sdz60/mcu_pmic_rtc.c new file mode 100644 index 000000000000..a83d13d435a4 --- /dev/null +++ b/drivers/mxc/pmic/mc9sdz60/mcu_pmic_rtc.c @@ -0,0 +1,599 @@ +/* + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mc9sdz60/mcu_pmic_rtc.c + * @brief This is the main file of mc9sdz60 RTC driver. + * + * @ingroup PMIC_POWER + */ + +/* + * Includes + */ +#include <linux/platform_device.h> +#include <asm/ioctl.h> +#include <linux/rtc.h> +#include <linux/bcd.h> + +#include <asm/arch/pmic_status.h> +#include <asm/arch/pmic_rtc.h> +#include <asm/arch/pmic_external.h> +#include <asm/arch/pmic_power.h> + +#define MCU_PMIC_RTC_NAME "pmic_rtc" +/* + * Global variables + */ +static int pmic_rtc_major; +static void callback_alarm_asynchronous(void *); +static void callback_alarm_synchronous(void *); +static unsigned int pmic_rtc_poll(struct file *file, poll_table *wait); +static DECLARE_WAIT_QUEUE_HEAD(queue_alarm); +static DECLARE_WAIT_QUEUE_HEAD(pmic_rtc_wait); +static pmic_event_callback_t alarm_callback; +static pmic_event_callback_t rtc_callback; +static int pmic_rtc_detected; +static bool pmic_rtc_done; +static struct class *pmic_rtc_class; +struct rtc_time alarm_greg_time; +static DECLARE_MUTEX(mutex); + +#define DEBUG_PMIC_RTC 1 +#if DEBUG_PMIC_RTC +#define DPRINTK(format, args...) printk(KERN_ERR "pmic_rtc"format"\n", ##args) +#else +#define DPRINTK(format, args...) +#endif + +/* + * Real Time Clock Pmic API + */ + +/*! + * This is the callback function called on TSI Pmic event, used in asynchronous + * call. + */ +static void callback_alarm_asynchronous(void *unused) +{ + pmic_rtc_done = true; +} + +/*! + * This is the callback function is used in test code for (un)sub. + */ +static void callback_test_sub(void) +{ + printk(KERN_INFO "*****************************************\n"); + printk(KERN_INFO "***** PMIC RTC 'Alarm IT CallBack' ******\n"); + printk(KERN_INFO "*****************************************\n"); +} + +/*! + * This is the callback function called on TSI Pmic event, used in synchronous + * call. + */ +static void callback_alarm_synchronous(void *unused) +{ + printk(KERN_INFO "*** Alarm IT Pmic ***\n"); + wake_up(&queue_alarm); +} + +/*! + * This function wait the Alarm event + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_wait_alarm(void) +{ + DEFINE_WAIT(wait); + alarm_callback.func = callback_alarm_synchronous; + alarm_callback.param = NULL; + CHECK_ERROR(pmic_event_subscribe(EVENT_RTC, alarm_callback)); + prepare_to_wait(&queue_alarm, &wait, TASK_UNINTERRUPTIBLE); + schedule(); + finish_wait(&queue_alarm, &wait); + CHECK_ERROR(pmic_event_unsubscribe(EVENT_RTC, alarm_callback)); + return PMIC_SUCCESS; +} +EXPORT_SYMBOL(pmic_rtc_wait_alarm); + +/*! + * This function set the real time clock of PMIC + * + * @param pmic_time value of date and time + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_set_time(struct timeval *pmic_time) +{ + u8 reg_val; + struct rtc_time greg_time; + + rtc_time_to_tm(pmic_time->tv_sec, &greg_time); + + reg_val = greg_time.tm_sec % 10; + reg_val |= ((greg_time.tm_sec / 10) << 4); + CHECK_ERROR(pmic_write_reg(REG_MCU_SECS, reg_val, 0x7f)); + + reg_val = greg_time.tm_min % 10; + reg_val |= ((greg_time.tm_min / 10) << 4); + CHECK_ERROR(pmic_write_reg(REG_MCU_MINS, reg_val, 0x7f)); + + reg_val = greg_time.tm_hour % 10; + reg_val |= ((greg_time.tm_hour / 10) << 4); + CHECK_ERROR(pmic_write_reg(REG_MCU_HRS, reg_val, 0x3f)); + + reg_val = greg_time.tm_wday; + CHECK_ERROR(pmic_write_reg(REG_MCU_DAY, reg_val, 0x3)); + + reg_val = greg_time.tm_mday % 10; + reg_val |= ((greg_time.tm_mday / 10) << 4); + CHECK_ERROR(pmic_write_reg(REG_MCU_DATE, reg_val, 0x3f)); + + reg_val = greg_time.tm_mon % 10; + reg_val |= ((greg_time.tm_mon / 10) << 4); + CHECK_ERROR(pmic_write_reg(REG_MCU_MONTH, reg_val, 0x1f)); + + reg_val = greg_time.tm_year % 10; + reg_val |= ((greg_time.tm_year / 10) << 4); + CHECK_ERROR(pmic_write_reg(REG_MCU_YEAR, reg_val, 0xff)); + + DPRINTK("set time = %d y, %d m, %d d, %d h, %d m, %d s", + greg_time.tm_year, greg_time.tm_mon, greg_time.tm_mday, + greg_time.tm_hour, greg_time.tm_min, greg_time.tm_sec); + return PMIC_SUCCESS; +} +EXPORT_SYMBOL(pmic_rtc_set_time); + +/*! + * This function get the real time clock of PMIC + * + * @param pmic_time return value of date and time + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_get_time(struct timeval *pmic_time) +{ + unsigned int reg_val; + struct rtc_time greg_time; + unsigned long time = 0; + + CHECK_ERROR(pmic_read_reg(REG_MCU_SECS, ®_val, 0x7f)); + greg_time.tm_sec = BCD2BIN(reg_val); + + CHECK_ERROR(pmic_read_reg(REG_MCU_MINS, ®_val, 0x7f)); + greg_time.tm_min = BCD2BIN(reg_val); + + CHECK_ERROR(pmic_read_reg(REG_MCU_HRS, ®_val, 0x3f)); + greg_time.tm_hour = BCD2BIN(reg_val); + + CHECK_ERROR(pmic_read_reg(REG_MCU_DAY, ®_val, 0x3)); + greg_time.tm_wday = reg_val; + + CHECK_ERROR(pmic_read_reg(REG_MCU_DATE, ®_val, 0x3f)); + greg_time.tm_mday = BCD2BIN(reg_val); + + CHECK_ERROR(pmic_read_reg(REG_MCU_MONTH, ®_val, 0x1f)); + greg_time.tm_mon = BCD2BIN(reg_val); + + CHECK_ERROR(pmic_read_reg(REG_MCU_YEAR, ®_val, 0xff)); + greg_time.tm_year = BCD2BIN(reg_val); + + DPRINTK("get time = %d y, %d m, %d d, %d h, %d m, %d s", + greg_time.tm_year, greg_time.tm_mon, greg_time.tm_mday, + greg_time.tm_hour, greg_time.tm_min, greg_time.tm_sec); + + rtc_tm_to_time(&greg_time, &time); + pmic_time->tv_sec = time; + + return PMIC_SUCCESS; +} +EXPORT_SYMBOL(pmic_rtc_get_time); + +/*! + * This function set the real time clock alarm of PMIC + * + * @param pmic_time value of date and time + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_set_time_alarm(struct timeval *pmic_time) +{ + u8 reg_val; + struct rtc_time greg_time; + + down_interruptible(&mutex); + + rtc_time_to_tm(pmic_time->tv_sec, &greg_time); + rtc_time_to_tm(pmic_time->tv_sec, &alarm_greg_time); + + reg_val = greg_time.tm_sec % 10; + reg_val |= ((greg_time.tm_sec / 10) << 4); + CHECK_ERROR(pmic_write_reg(REG_MCU_ALARM_SECS, reg_val, 0x7f)); + + reg_val = greg_time.tm_min % 10; + reg_val |= ((greg_time.tm_min / 10) << 4); + CHECK_ERROR(pmic_write_reg(REG_MCU_ALARM_MINS, reg_val, 0x7f)); + + reg_val = greg_time.tm_hour % 10; + reg_val |= ((greg_time.tm_hour / 10) << 4); + CHECK_ERROR(pmic_write_reg(REG_MCU_ALARM_HRS, reg_val, 0x3f)); + + /* enable alarm */ + CHECK_ERROR(pmic_write_reg(REG_MCU_ALARM_SECS, 0x80, 0x80)); + DPRINTK("set alarm time = %dh, %dm, %ds\n", + greg_time.tm_hour, greg_time.tm_min, greg_time.tm_sec); + + up(&mutex); + + return PMIC_SUCCESS; +} +EXPORT_SYMBOL(pmic_rtc_set_time_alarm); + +/*! + * This function get the real time clock alarm of PMIC + * + * @param pmic_time return value of date and time + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_get_time_alarm(struct timeval *pmic_time) +{ + unsigned int reg_val; + struct rtc_time greg_time; + unsigned long time = 0; + + memzero(&greg_time, sizeof(struct rtc_time)); + greg_time = alarm_greg_time; + + CHECK_ERROR(pmic_read_reg(REG_MCU_ALARM_SECS, ®_val, 0x7f)); + greg_time.tm_sec = BCD2BIN(reg_val); + + CHECK_ERROR(pmic_read_reg(REG_MCU_ALARM_MINS, ®_val, 0x7f)); + greg_time.tm_min = BCD2BIN(reg_val); + + CHECK_ERROR(pmic_read_reg(REG_MCU_ALARM_HRS, ®_val, 0x3f)); + greg_time.tm_hour = BCD2BIN(reg_val); + + rtc_tm_to_time(&greg_time, &time); + pmic_time->tv_sec = time; + DPRINTK("get alarm time = %dh, %dm, %ds\n", + greg_time.tm_hour, greg_time.tm_min, greg_time.tm_sec); + + return PMIC_SUCCESS; +} +EXPORT_SYMBOL(pmic_rtc_get_time_alarm); + +/*! + * This function is used to un/subscribe on RTC event IT. + * + * @param event type of event. + * @param callback event callback function. + * @param sub define if Un/subscribe event. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_event(t_rtc_int event, void *callback, bool sub) +{ + type_event rtc_event; + if (callback == NULL) { + return PMIC_ERROR; + } else { + rtc_callback.func = callback; + rtc_callback.param = NULL; + } + switch (event) { + case RTC_IT_ALARM: + rtc_event = EVENT_RTC; + break; + default: + return PMIC_PARAMETER_ERROR; + } + if (sub) + CHECK_ERROR(pmic_event_subscribe(rtc_event, rtc_callback)); + else + CHECK_ERROR(pmic_event_unsubscribe(rtc_event, rtc_callback)); + + return PMIC_SUCCESS; +} + +/*! + * This function is used to subscribe on RTC event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_event_sub(t_rtc_int event, void *callback) +{ + CHECK_ERROR(pmic_rtc_event(event, callback, true)); + return PMIC_SUCCESS; +} +EXPORT_SYMBOL(pmic_rtc_event_sub); + +/*! + * This function is used to un subscribe on RTC event IT. + * + * @param event type of event. + * @param callback event callback function. + * + * @return This function returns PMIC_SUCCESS if successful. + */ +PMIC_STATUS pmic_rtc_event_unsub(t_rtc_int event, void *callback) +{ + CHECK_ERROR(pmic_rtc_event(event, callback, false)); + return PMIC_SUCCESS; +} +EXPORT_SYMBOL(pmic_rtc_event_unsub); + +/* Called without the kernel lock - fine */ +static unsigned int pmic_rtc_poll(struct file *file, poll_table *wait) +{ + if (pmic_rtc_done) + return POLLIN | POLLRDNORM; + return 0; +} + +/*! + * This function implements IOCTL controls on a PMIC RTC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @param cmd the command + * @param arg the parameter + * @return This function returns 0 if successful. + */ +static int pmic_rtc_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct timeval *pmic_time = NULL; + + if (_IOC_TYPE(cmd) != 'p') + return -ENOTTY; + + if (arg) { + pmic_time = kmalloc(sizeof(struct timeval), GFP_KERNEL); + if (pmic_time == NULL) + return -ENOMEM; + + /* if (copy_from_user(pmic_time, (struct timeval *)arg, + sizeof(struct timeval))) { + return -EFAULT; + } */ + } + + switch (cmd) { + case PMIC_RTC_SET_TIME: + if (copy_from_user(pmic_time, (struct timeval *)arg, + sizeof(struct timeval))) + return -EFAULT; + + pr_debug("SET RTC\n"); + CHECK_ERROR(pmic_rtc_set_time(pmic_time)); + break; + case PMIC_RTC_GET_TIME: + if (copy_to_user((struct timeval *)arg, pmic_time, + sizeof(struct timeval))) + return -EFAULT; + + pr_debug("GET RTC\n"); + CHECK_ERROR(pmic_rtc_get_time(pmic_time)); + break; + case PMIC_RTC_SET_ALARM: + if (copy_from_user(pmic_time, (struct timeval *)arg, + sizeof(struct timeval))) + return -EFAULT; + + pr_debug("SET RTC ALARM\n"); + CHECK_ERROR(pmic_rtc_set_time_alarm(pmic_time)); + break; + case PMIC_RTC_GET_ALARM: + if (copy_to_user((struct timeval *)arg, pmic_time, + sizeof(struct timeval))) + return -EFAULT; + + pr_debug("GET RTC ALARM\n"); + CHECK_ERROR(pmic_rtc_get_time_alarm(pmic_time)); + break; + case PMIC_RTC_WAIT_ALARM: + printk(KERN_INFO "WAIT ALARM...\n"); + CHECK_ERROR(pmic_rtc_event_sub(RTC_IT_ALARM, + callback_test_sub)); + CHECK_ERROR(pmic_rtc_wait_alarm()); + printk(KERN_INFO "ALARM DONE\n"); + CHECK_ERROR(pmic_rtc_event_unsub(RTC_IT_ALARM, + callback_test_sub)); + break; + case PMIC_RTC_ALARM_REGISTER: + printk(KERN_INFO "PMIC RTC ALARM REGISTER\n"); + alarm_callback.func = callback_alarm_asynchronous; + alarm_callback.param = NULL; + CHECK_ERROR(pmic_event_subscribe(EVENT_RTC, alarm_callback)); + break; + case PMIC_RTC_ALARM_UNREGISTER: + printk(KERN_INFO "PMIC RTC ALARM UNREGISTER\n"); + alarm_callback.func = callback_alarm_asynchronous; + alarm_callback.param = NULL; + CHECK_ERROR(pmic_event_unsubscribe(EVENT_RTC, alarm_callback)); + pmic_rtc_done = false; + break; + default: + pr_debug("%d unsupported ioctl command\n", (int)cmd); + return -EINVAL; + } + + if (arg) { + if (copy_to_user((struct timeval *)arg, pmic_time, + sizeof(struct timeval))) + return -EFAULT; + + kfree(pmic_time); + } + + return 0; +} + +/*! + * This function implements the open method on a PMIC RTC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_rtc_open(struct inode *inode, struct file *file) +{ + return 0; +} + +/*! + * This function implements the release method on a PMIC RTC device. + * + * @param inode pointer on the node + * @param file pointer on the file + * @return This function returns 0. + */ +static int pmic_rtc_release(struct inode *inode, struct file *file) +{ + return 0; +} + +/*! + * This function is called to put the RTC in a low power state. + * There is no need for power handlers for the RTC device. + * The RTC cannot be suspended. + * + * @param pdev the device structure used to give information on which RTC + * device (0 through 3 channels) to suspend + * @param state the power state the device is entering + * + * @return The function always returns 0. + */ +static int pmic_rtc_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +} + +/*! + * This function is called to resume the RTC from a low power state. + * + * @param pdev the device structure used to give information on which RTC + * device (0 through 3 channels) to suspend + * + * @return The function always returns 0. + */ +static int pmic_rtc_resume(struct platform_device *pdev) +{ + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ + +static struct file_operations pmic_rtc_fops = { + .owner = THIS_MODULE, + .ioctl = pmic_rtc_ioctl, + .poll = pmic_rtc_poll, + .open = pmic_rtc_open, + .release = pmic_rtc_release, +}; + +int pmic_rtc_loaded(void) +{ + return pmic_rtc_detected; +} +EXPORT_SYMBOL(pmic_rtc_loaded); + + +static int pmic_rtc_remove(struct platform_device *pdev) +{ + class_device_destroy(pmic_rtc_class, MKDEV(pmic_rtc_major, 0)); + class_destroy(pmic_rtc_class); + unregister_chrdev(pmic_rtc_major, MCU_PMIC_RTC_NAME); + return 0; +} + +static int pmic_rtc_probe(struct platform_device *pdev) +{ + int ret = 0; + struct class_device *temp_class; + + pmic_rtc_major = register_chrdev(0, MCU_PMIC_RTC_NAME, &pmic_rtc_fops); + if (pmic_rtc_major < 0) { + printk(KERN_ERR "Unable to get a major for pmic_rtc\n"); + return pmic_rtc_major; + } + + pmic_rtc_class = class_create(THIS_MODULE, MCU_PMIC_RTC_NAME); + if (IS_ERR(pmic_rtc_class)) { + printk(KERN_ERR "Error creating pmic rtc class.\n"); + ret = PTR_ERR(pmic_rtc_class); + goto err_out1; + } + + temp_class = class_device_create(pmic_rtc_class, NULL, + MKDEV(pmic_rtc_major, 0), + NULL, MCU_PMIC_RTC_NAME); + if (IS_ERR(temp_class)) { + printk(KERN_ERR "Error creating pmic rtc class device.\n"); + ret = PTR_ERR(temp_class); + goto err_out2; + } + + pmic_rtc_detected = 1; + + /* start RTC */ + pmic_write_reg(REG_MCU_SECS, 0x80, 0x80); + + return ret; + +err_out2: + class_destroy(pmic_rtc_class); +err_out1: + unregister_chrdev(pmic_rtc_major, MCU_PMIC_RTC_NAME); + return ret; +} + +static struct platform_driver pmic_rtc_driver_ldm = { + .driver = { + .name = MCU_PMIC_RTC_NAME, + .owner = THIS_MODULE, + }, + .suspend = pmic_rtc_suspend, + .resume = pmic_rtc_resume, + .probe = pmic_rtc_probe, + .remove = pmic_rtc_remove, +}; + +static int __init pmic_rtc_init(void) +{ + return platform_driver_register(&pmic_rtc_driver_ldm); +} +static void __exit pmic_rtc_exit(void) +{ + platform_driver_unregister(&pmic_rtc_driver_ldm); + pr_debug("PMIC RTC driver successfully unloaded\n"); +} + +/* + * Module entry points + */ + +subsys_initcall(pmic_rtc_init); +module_exit(pmic_rtc_exit); + +MODULE_DESCRIPTION("Pmic_rtc driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); |