/* * Copyright (C) 2013 Motorola, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Adds ability to program periodic interrupts from user space that * can wake the phone out of low power modes. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DISPLAY_CLIENT_DRIVER_NAME "m4sensorhub_display" #define SENSORHUB_MIPI_DRIVER_REGULATOR "vcsi" #define SYNC_CLOCK_RETRY_TIMES 3 #define INVALID_UTC_TIME 0xFFFFFFFF struct display_client { struct m4sensorhub_data *m4sensorhub; struct regulator *regulator; atomic_t m4_lockcnt; struct mutex m4_mutex; int gpio_mipi_mux; int m4_control; int m4_enable; int timezone_offset; int m4_timezone_offset; int dailystep_offset; int m4_dailystep_offset; }; struct display_client *global_display_data; static u32 m4_display_get_clock(void); static u32 m4_display_get_kernel_clock(void); static int m4_display_set_clock(u32 ms); static int m4_display_sync_timezone(struct display_client *data); static int m4_display_sync_dailystep(struct display_client *data); static int m4_display_sync_clock(void); static int m4_display_sync_state(struct display_client *display_data); static int m4_display_lock(struct display_client *display_data, int lock); static int m4_display_set_control(int m4_ctrl, int gpio_mipi_mux); static int display_client_open(struct inode *inode, struct file *file) { int ret = -EFAULT; KDEBUG(M4SH_DEBUG, "%s:\n", __func__); if (global_display_data) { ret = nonseekable_open(inode, file); if (ret >= 0) { file->private_data = global_display_data; ret = 0; } } if (ret) KDEBUG(M4SH_ERROR, "%s: failed, err=%d\n", __func__, -ret); return ret; } static int display_client_close(struct inode *inode, struct file *file) { KDEBUG(M4SH_DEBUG, "%s:\n", __func__); file->private_data = NULL; return 0; } static long display_client_ioctl( struct file *filp, unsigned int cmd, unsigned long arg) { void __user *argp = (void __user *)arg; struct display_client *display_data = filp->private_data; int value; switch (cmd) { case M4_SENSOR_IOCTL_SET_TIMEZONE_OFFSET: if (copy_from_user(&value, argp, sizeof(value))) return -EFAULT; KDEBUG(M4SH_INFO, "%s update timezone offset %d\n",\ __func__, value); m4_display_lock(display_data, true); display_data->timezone_offset = value; value = m4_display_sync_timezone(display_data); /* sync M4 clock every time when app set timezone offset */ /* FIXME:Setting rtc clock on M4 happens in M4 ISR and leads to * i2c timeout for the first clock set, this causes the bus to * fail for a few mins on klockworks,hence commenting this part * of the code until fixed for klockworks if (value == 0) m4_display_sync_clock(); */ m4_display_lock(display_data, false); return value; case M4_SENSOR_IOCTL_SET_DAILYSTEP_OFFSET: if (copy_from_user(&value, argp, sizeof(value))) return -EFAULT; KDEBUG(M4SH_INFO, "%s update dailystep offset %d\n",\ __func__, value); display_data->dailystep_offset = value; return m4_display_sync_dailystep(display_data); case M4_SENSOR_IOCTL_LOCK_CLOCKFACE: if (copy_from_user(&value, argp, sizeof(value))) return -EFAULT; return m4_display_lock(display_data, value); default: KDEBUG(M4SH_ERROR, "%s Invaild ioctl cmd %d\n", __func__, cmd); break; } return -EINVAL; } /* Get current M4 RTC time of seconds elapsed since 00:00:00 * on January 1, 1970, Coordinated Universal Time * return 0xFFFFFFFF if it's something wrong */ static u32 m4_display_get_clock(void) { u32 seconds; struct display_client *data = global_display_data; if (m4sensorhub_reg_getsize(data->m4sensorhub,\ M4SH_REG_GENERAL_UTC) != m4sensorhub_reg_read(\ data->m4sensorhub, M4SH_REG_GENERAL_UTC,\ (char *)&seconds)) { seconds = INVALID_UTC_TIME; KDEBUG(M4SH_ERROR, "%s: Failed get M4 clock!\n", __func__); } return seconds; } /* Set current M4 RTC time of seconds elapsed since 00:00:00 * on January 1, 1970, Coordinated Universal Time * return < 0 if it's something wrong */ static int m4_display_set_clock(u32 seconds) { struct display_client *data = global_display_data; if (m4sensorhub_reg_getsize(data->m4sensorhub,\ M4SH_REG_GENERAL_UTC) != m4sensorhub_reg_write(\ data->m4sensorhub, M4SH_REG_GENERAL_UTC,\ (char *)&seconds, m4sh_no_mask)) { KDEBUG(M4SH_ERROR, "%s: Failed set M4 clock!\n", __func__); return -EIO; } return 0; } static void m4_notify_clock_change(void) { struct display_client *data = global_display_data; char notify = 1; if (m4sensorhub_reg_getsize(data->m4sensorhub,\ M4SH_REG_USERSETTINGS_RTCRESET) != m4sensorhub_reg_write(\ data->m4sensorhub, M4SH_REG_USERSETTINGS_RTCRESET,\ ¬ify, m4sh_no_mask)) { KDEBUG(M4SH_ERROR, "Failed to notify clock change"); } } /* Get current Kernel RTC time of seconds elapsed since 00:00:00 * on January 1, 1970, Coordinated Universal Time */ static u32 m4_display_get_kernel_clock(void) { struct timespec now = current_kernel_time(); /* adjust with 500ms */ if (now.tv_nsec > 500000000) now.tv_sec++; return (u32)now.tv_sec; } void print_time(char *info, u32 time) { struct tm t; /* it convert to year since 1900 */ time_to_tm((time_t)(time), 0, &t); KDEBUG(M4SH_INFO, "%s(%d) %02d:%02d:%02d %02d/%02d/%04d\n",\ info, (int)time, t.tm_hour, t.tm_min, t.tm_sec,\ t.tm_mon+1, t.tm_mday, (int)t.tm_year+1900); } /* Sync M4 clock with current kernel time */ static int m4_display_sync_clock(void) { int retry = 0; do { u32 m4_time = m4_display_get_clock(); u32 kernel_time = m4_display_get_kernel_clock(); u32 diff_time = m4_time > kernel_time \ ? m4_time-kernel_time : kernel_time-m4_time; #ifdef DEBUG_CLOCK print_time("M4 :", m4_time); print_time("KNL:", kernel_time); #endif /* it needs adjust M4 time if different large than 1 second */ if (diff_time < 2) { if (retry) { print_time("Synced M4 clock to", m4_time); m4_notify_clock_change(); } return 0; } m4_display_set_clock(kernel_time); } while (retry++ < SYNC_CLOCK_RETRY_TIMES); KDEBUG(M4SH_ERROR, "%s: Failed to sync M4 clock!\n", __func__); return -EIO; } /* Sync M4 TimeZone offset */ static int m4_display_sync_timezone(struct display_client *data) { if (data->m4_timezone_offset == data->timezone_offset) return 0; if (m4sensorhub_reg_getsize(data->m4sensorhub,\ M4SH_REG_GENERAL_LOCALTIMEZONE) != m4sensorhub_reg_write(\ data->m4sensorhub, M4SH_REG_GENERAL_LOCALTIMEZONE,\ (char *)&(data->timezone_offset), m4sh_no_mask)) { KDEBUG(M4SH_ERROR, "%s: Failed set M4 timezone!\n", __func__); return -EIO; } data->m4_timezone_offset = data->timezone_offset; return 0; } /* Sync M4 Daily Steps offset */ static int m4_display_sync_dailystep(struct display_client *data) { if (data->m4_dailystep_offset == data->dailystep_offset) return 0; if (m4sensorhub_reg_getsize(data->m4sensorhub,\ M4SH_REG_TIMEPIECE_OFFSETSTEPS) != m4sensorhub_reg_write(\ data->m4sensorhub, M4SH_REG_TIMEPIECE_OFFSETSTEPS,\ (char *)&(data->dailystep_offset), m4sh_no_mask)) { KDEBUG(M4SH_ERROR, "%s: Failed set M4 dailystep!\n", __func__); return -EIO; } data->m4_dailystep_offset = data->dailystep_offset; return 0; } /* Sync M4 clockface state, it will be enable only when clockface is unlocked */ static int m4_display_sync_state(struct display_client *display_data) { int enable = !!display_data->m4_control; if (atomic_inc_return(&(display_data->m4_lockcnt)) != 1) enable = 0; atomic_dec(&(display_data->m4_lockcnt)); mutex_lock(&(display_data->m4_mutex)); if (enable == display_data->m4_enable) goto __done__; if (enable) { /* switch display control to M4 */ m4_display_sync_clock(); m4_display_sync_timezone(display_data); m4_display_sync_dailystep(display_data); gpio_set_value(display_data->gpio_mipi_mux, 1); if (regulator_enable(display_data->regulator)) { KDEBUG(M4SH_ERROR, "Failed enable regulator!\n"); goto __error__; } } /* comunicate with M4 via I2C */ if (1 != m4sensorhub_reg_write_1byte(\ display_data->m4sensorhub,\ M4SH_REG_TIMEPIECE_ENABLE, enable, 0xFF)) { KDEBUG(M4SH_ERROR, "Failed set m4 display!\n"); goto __error__; } if (!enable) { mdelay(2); /* switch display control to OMAP */ regulator_disable(display_data->regulator); gpio_set_value(display_data->gpio_mipi_mux, 0); } display_data->m4_enable = enable; KDEBUG(M4SH_INFO, "Synced M4 display state(%d)\n", enable); __done__: mutex_unlock(&(display_data->m4_mutex)); return 0; __error__: /* when error occured, it always set control to OMAP */ regulator_disable(display_data->regulator); gpio_set_value(display_data->gpio_mipi_mux, 0); mutex_unlock(&(display_data->m4_mutex)); KDEBUG(M4SH_ERROR, "Failed sync m4 with state(%d)!\n", enable); return -EIO; } /* M4 clockface lock/unlock */ static int m4_display_lock(struct display_client *display_data, int lock) { if (lock) { atomic_inc(&(display_data->m4_lockcnt)); } else { if (atomic_dec_return(&(display_data->m4_lockcnt)) == -1) { atomic_inc(&(display_data->m4_lockcnt)); KDEBUG(M4SH_ERROR, "%s zero unlock count!\n",\ __func__); return -EINVAL; } } return m4_display_sync_state(display_data); } static int m4_display_set_control(int m4_ctrl, int gpio_mipi_mux) { struct display_client *display_data = global_display_data; KDEBUG(M4SH_INFO, "%s(%d)\n", __func__, m4_ctrl); if (!display_data || !display_data->m4sensorhub || (gpio_mipi_mux < 0)) return -EINVAL; if (m4_ctrl == display_data->m4_control) { KDEBUG(M4SH_DEBUG, "%s is already set!\n", __func__); return 0; } display_data->gpio_mipi_mux = gpio_mipi_mux; display_data->m4_control = m4_ctrl; return m4_display_sync_state(display_data); } int m4sensorhub_set_display_control(int m4_ctrl, int gpio_mipi_mux) { return m4_display_set_control(m4_ctrl, gpio_mipi_mux); } EXPORT_SYMBOL_GPL(m4sensorhub_set_display_control); static ssize_t display_get_loglevel(struct device *dev, struct device_attribute *attr, char *buf) { uint64_t loglevel; struct platform_device *pdev = to_platform_device(dev); struct display_client *display_data = platform_get_drvdata(pdev); m4sensorhub_reg_read(display_data->m4sensorhub, M4SH_REG_LOG_LOGENABLE, (char *)&loglevel); loglevel = get_log_level(loglevel, DISPLAY_MASK_BIT_1); return sprintf(buf, "%d\n", (int)loglevel); } static ssize_t display_set_loglevel(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { uint64_t level = 0; uint64_t mask = 0x3; struct platform_device *pdev = to_platform_device(dev); struct display_client *display_data = platform_get_drvdata(pdev); if ((strict_strtoul(buf, 10, (unsigned long *)&level)) < 0) return -EINVAL; if (level > M4_MAX_LOG_LEVEL) { KDEBUG(M4SH_ERROR, " Invalid log level - %d\n", (int)level); return -EINVAL; } mask <<= DISPLAY_MASK_BIT_1; level <<= DISPLAY_MASK_BIT_1; return m4sensorhub_reg_write(display_data->m4sensorhub, M4SH_REG_LOG_LOGENABLE, (char *)&level, (unsigned char *)&mask); } static DEVICE_ATTR(LogLevel, 0664, \ display_get_loglevel, display_set_loglevel); static const struct file_operations display_client_fops = { .owner = THIS_MODULE, .unlocked_ioctl = display_client_ioctl, .open = display_client_open, .release = display_client_close, }; static struct miscdevice display_client_miscdrv = { .minor = MISC_DYNAMIC_MINOR, .name = DISPLAY_CLIENT_DRIVER_NAME, .fops = &display_client_fops, }; /* display_panic_restore() Panic Callback Handler is called after M4 has been restarted */ static void display_panic_restore(\ struct m4sensorhub_data *m4sensorhub, void *data) { struct display_client *display_data = (struct display_client *)data; display_data->m4_timezone_offset = 0; display_data->m4_dailystep_offset = 0; if (display_data->m4_enable) { m4_display_sync_clock(); m4_display_sync_timezone(display_data); m4_display_sync_dailystep(display_data); /* comunicate with M4 via I2C */ if (1 != m4sensorhub_reg_write_1byte(\ display_data->m4sensorhub,\ M4SH_REG_TIMEPIECE_ENABLE, 1, 0xFF)) { KDEBUG(M4SH_ERROR, "Failed re-enable m4 display!\n"); /* TODO retry ? */ } } } static int display_client_probe(struct platform_device *pdev) { int ret = -EINVAL; struct display_client *display_data; struct m4sensorhub_data *m4sensorhub = m4sensorhub_client_get_drvdata(); if (!m4sensorhub) return -EFAULT; display_data = kzalloc(sizeof(*display_data), GFP_KERNEL); if (!display_data) return -ENOMEM; display_data->m4sensorhub = m4sensorhub; display_data->timezone_offset = 0; display_data->dailystep_offset = 0; display_data->m4_timezone_offset = 0; display_data->m4_dailystep_offset = 0; platform_set_drvdata(pdev, display_data); display_data->regulator = regulator_get(NULL, SENSORHUB_MIPI_DRIVER_REGULATOR); if (IS_ERR(display_data->regulator)) { KDEBUG(M4SH_ERROR, "Error requesting %s regulator\n", SENSORHUB_MIPI_DRIVER_REGULATOR); ret = -EFAULT; goto free_mem; } ret = misc_register(&display_client_miscdrv); if (ret < 0) { KDEBUG(M4SH_ERROR, "Error registering %s driver\n", DISPLAY_CLIENT_DRIVER_NAME); goto disable_regulator; } if (device_create_file(&pdev->dev, &dev_attr_LogLevel)) { KDEBUG(M4SH_ERROR, "Error creating %s sys entry\n", DISPLAY_CLIENT_DRIVER_NAME); ret = -EFAULT; goto unregister_misc_device; } /* default to host control */ display_data->m4_control = 0; display_data->m4_enable = 0; atomic_set(&(display_data->m4_lockcnt), 0); mutex_init(&(display_data->m4_mutex)); global_display_data = display_data; m4sensorhub_panic_register(m4sensorhub, PANICHDL_DISPLAY_RESTORE,\ display_panic_restore, display_data); KDEBUG(M4SH_INFO, "Initialized %s driver\n", __func__); return 0; unregister_misc_device: misc_deregister(&display_client_miscdrv); disable_regulator: regulator_put(display_data->regulator); free_mem: global_display_data = NULL; platform_set_drvdata(pdev, NULL); kfree(display_data); return ret; } static int __exit display_client_remove(struct platform_device *pdev) { struct display_client *display_data = platform_get_drvdata(pdev); display_data->m4sensorhub->pdev->set_display_control = NULL; device_remove_file(&pdev->dev, &dev_attr_LogLevel); misc_deregister(&display_client_miscdrv); global_display_data = NULL; regulator_put(display_data->regulator); platform_set_drvdata(pdev, NULL); display_data->m4sensorhub = NULL; kfree(display_data); return 0; } static void display_client_shutdown(struct platform_device *pdev) { return; } static struct of_device_id m4display_match_tbl[] = { { .compatible = "mot,m4display" }, {}, }; static struct platform_driver display_client_driver = { .probe = display_client_probe, .remove = __exit_p(display_client_remove), .shutdown = display_client_shutdown, .suspend = NULL, .resume = NULL, .driver = { .name = DISPLAY_CLIENT_DRIVER_NAME, .owner = THIS_MODULE, .of_match_table = of_match_ptr(m4display_match_tbl), }, }; static int __init display_client_init(void) { return platform_driver_register(&display_client_driver); } static void __exit display_client_exit(void) { platform_driver_unregister(&display_client_driver); } module_init(display_client_init); module_exit(display_client_exit); MODULE_ALIAS("platform:display_client"); MODULE_DESCRIPTION("M4 Sensor Hub AOD display driver"); MODULE_AUTHOR("Motorola"); MODULE_LICENSE("GPL");