diff options
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"); |