summaryrefslogtreecommitdiff
path: root/drivers/misc/m4sensorhub_display.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/misc/m4sensorhub_display.c')
-rw-r--r--drivers/misc/m4sensorhub_display.c558
1 files changed, 558 insertions, 0 deletions
diff --git a/drivers/misc/m4sensorhub_display.c b/drivers/misc/m4sensorhub_display.c
new file mode 100644
index 00000000000..2bc96643d89
--- /dev/null
+++ b/drivers/misc/m4sensorhub_display.c
@@ -0,0 +1,558 @@
+/*
+ * 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 <linux/delay.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/platform_device.h>
+#include <linux/proc_fs.h>
+#include <linux/input.h>
+#include <linux/regulator/consumer.h>
+#include <linux/m4sensorhub.h>
+#include <linux/m4sensorhub_client_ioctl.h>
+#include <linux/uaccess.h>
+#include <linux/time.h>
+#include <linux/gpio.h>
+#include <linux/slab.h>
+
+#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,\
+ &notify, 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");