diff options
| author | Doug Zobel <dzobel1@motorola.com> | 2013-11-15 14:29:07 -0600 |
|---|---|---|
| committer | James Wylder <jwylder@motorola.com> | 2014-03-05 17:46:52 -0600 |
| commit | d2a782003a6047da120a33e6f8ee6fd33bb825d6 (patch) | |
| tree | 8d20bd4ecda62a06e98993c4108456bc1acb0d0b /drivers/misc/m4sensorhub_display.c | |
| parent | 32fd2d36d2464056d4522a9c02797b7c2b2e884f (diff) | |
| download | olio-linux-3.10-d2a782003a6047da120a33e6f8ee6fd33bb825d6.tar.xz olio-linux-3.10-d2a782003a6047da120a33e6f8ee6fd33bb825d6.zip | |
CW integration and minnow bringup
* create minnow machine type
* create Android makefile
* add pre-commit syntax check
* enable -Werror
* Add drivers: CPCAP, TPS65xxx, m4sensorhub, atmxt, lm3535,
usb gadget, minnow display, TI 12xx wireless
Change-Id: I7962f5e1256715f2452aed5a62a4f2f2383d5046
Diffstat (limited to 'drivers/misc/m4sensorhub_display.c')
| -rw-r--r-- | drivers/misc/m4sensorhub_display.c | 558 |
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,\ + ¬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"); |