diff options
Diffstat (limited to 'drivers/misc')
| -rw-r--r-- | drivers/misc/Kconfig | 4 | ||||
| -rw-r--r-- | drivers/misc/Makefile | 12 | ||||
| -rw-r--r-- | drivers/misc/m4sensorhub_audio.c | 496 | ||||
| -rw-r--r-- | drivers/misc/m4sensorhub_bmp180.c | 453 | ||||
| -rw-r--r-- | drivers/misc/m4sensorhub_display.c | 558 | ||||
| -rw-r--r-- | drivers/misc/m4sensorhub_download.c | 501 | ||||
| -rw-r--r-- | drivers/misc/m4sensorhub_gesture.c | 415 | ||||
| -rw-r--r-- | drivers/misc/m4sensorhub_mpu9150.c | 1243 | ||||
| -rw-r--r-- | drivers/misc/m4sensorhub_passive.c | 325 | ||||
| -rw-r--r-- | drivers/misc/m4sensorhub_pedometer.c | 689 | ||||
| -rw-r--r-- | drivers/misc/m4sensorhub_stillmode.c | 425 | ||||
| -rw-r--r-- | drivers/misc/m4sensorhub_tmp006.c | 432 | ||||
| -rw-r--r-- | drivers/misc/m4sensorhub_wrist.c | 836 | ||||
| -rw-r--r-- | drivers/misc/m4sensorhub_wrist.h | 106 | ||||
| -rw-r--r-- | drivers/misc/vib-gpio.c | 224 |
15 files changed, 6719 insertions, 0 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 3f5743424ff..18c65fcca01 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -333,6 +333,10 @@ config ISL29020 This driver can also be built as a module. If so, the module will be called isl29020. +config VIB_GPIO + bool "GPIO Vibrator" + default n + config SENSORS_TSL2550 tristate "Taos TSL2550 ambient light sensor" depends on I2C && SYSFS diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index a57666cec34..9f72ecb358d 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -54,3 +54,15 @@ obj-$(CONFIG_INTEL_MEI) += mei/ obj-$(CONFIG_VMWARE_VMCI) += vmw_vmci/ obj-$(CONFIG_LATTICE_ECP3_CONFIG) += lattice-ecp3-config.o obj-$(CONFIG_SRAM) += sram.o +obj-$(CONFIG_MFD_M4SENSORHUB) += m4sensorhub_tmp006.o +obj-$(CONFIG_MFD_M4SENSORHUB) += m4sensorhub_bmp180.o +obj-$(CONFIG_MFD_M4SENSORHUB) += m4sensorhub_mpu9150.o +obj-$(CONFIG_MFD_M4SENSORHUB) += m4sensorhub_pedometer.o +obj-$(CONFIG_MFD_M4SENSORHUB) += m4sensorhub_gesture.o +obj-$(CONFIG_MFD_M4SENSORHUB) += m4sensorhub_stillmode.o +obj-$(CONFIG_MFD_M4SENSORHUB) += m4sensorhub_download.o +obj-$(CONFIG_MFD_M4SENSORHUB) += m4sensorhub_display.o +obj-$(CONFIG_MFD_M4SENSORHUB) += m4sensorhub_audio.o +obj-$(CONFIG_MFD_M4SENSORHUB) += m4sensorhub_wrist.o +obj-$(CONFIG_MFD_M4SENSORHUB) += m4sensorhub_passive.o +obj-$(CONFIG_VIB_GPIO) += vib-gpio.o diff --git a/drivers/misc/m4sensorhub_audio.c b/drivers/misc/m4sensorhub_audio.c new file mode 100644 index 00000000000..511e2776bf8 --- /dev/null +++ b/drivers/misc/m4sensorhub_audio.c @@ -0,0 +1,496 @@ +/* + * 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/input.h> +#include <linux/kernel.h> +#include <linux/m4sensorhub.h> +#include <linux/m4sensorhub_client_ioctl.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/proc_fs.h> +#include <linux/sound.h> +#include <linux/soundcard.h> +#include <linux/types.h> +#include <linux/uaccess.h> +#include <linux/wait.h> +#include <linux/m4sensorhub/MemMapAudio.h> +#include <linux/spi/spi.h> + + +#define AUDIO_CLIENT_DRIVER_NAME "m4sensorhub_audio" +/* This is the number of total kernel buffers */ +#define AUDIO_NBFRAGS_READ 20 +#define AUDIO_SAMPLE_RATE 16000 +#define AUDIO_TIMEOUT HZ +#define MIC_ENABLE 0x01 +#define MIC_DISABLE 0x00 + +/* Mutex used to prevent mutiple calls to audio functions at same time */ +DEFINE_MUTEX(audio_lock); + +struct audio_client { + struct m4sensorhub_data *m4sensorhub; + struct spi_device *spi; + int dev_dsp; + int dev_dsp_open_count; + char *buffers[AUDIO_NBFRAGS_READ]; + unsigned int usr_head; /* user index where app is reading from */ + unsigned int buf_head; /* SPI index where SPI writing to */ + unsigned int usr_offset; /* offset in usr_head buffer to read */ + int read_buf_full; /* num buffers available for app */ + u32 total_buf_cnt; /* total num of bufs read from since audio enable*/ + wait_queue_head_t wq; /* wait till read buffer is available */ + int active; /* Indicates if audio transfer is active */ +}; + +struct audio_client *audio_data; + +static ssize_t audio_get_loglevel(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long long loglevel; + struct spi_device *spi = to_spi_device(dev); + struct audio_client *audio_client_data = spi_get_drvdata(spi); + + m4sensorhub_reg_read(audio_client_data->m4sensorhub, + M4SH_REG_LOG_LOGENABLE, (char *)&loglevel); + loglevel = get_log_level(loglevel, AUDIO_MASK_BIT_1); + return sprintf(buf, "%llu\n", loglevel); +} +static ssize_t audio_set_loglevel(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long level; + unsigned long long mask = 0, newlevel; + struct spi_device *spi = to_spi_device(dev); + struct audio_client *audio_client_data = spi_get_drvdata(spi); + + if (strict_strtoul(buf, 10, &level) < 0) + return -1; + if (level > M4_MAX_LOG_LEVEL) { + KDEBUG(M4SH_ERROR, " Invalid log level - %d\n", (int)level); + return -1; + } + mask = (1ULL << AUDIO_MASK_BIT_1) | (1ULL << AUDIO_MASK_BIT_2); + newlevel = (unsigned long long)level << AUDIO_MASK_BIT_1; + return m4sensorhub_reg_write(audio_client_data->m4sensorhub, + M4SH_REG_LOG_LOGENABLE, (char *)&newlevel, + (unsigned char *)&mask); +} +static DEVICE_ATTR(LogLevel, 0664, audio_get_loglevel, audio_set_loglevel); + +static void audio_client_spidma_read(struct audio_client *audio_client_data, + int len) +{ + int ret = 0; + struct spi_message msg; + struct spi_transfer rx; + unsigned char txbuff[AUDIO_BUFFER_SIZE]; + + memset(&rx, 0x00, sizeof(struct spi_transfer)); + memset(&msg, 0x00, sizeof(struct spi_message)); + + rx.rx_buf = audio_client_data->buffers[ + audio_client_data->buf_head]; + rx.tx_buf = txbuff; + rx.len = len; + + spi_message_init(&msg); + spi_message_add_tail(&rx, &msg); + + ret = spi_sync(audio_client_data->spi, &msg); + if (ret < 0) + KDEBUG(M4SH_ERROR, "%s failed to read %d bytes, ret = %d\n", + __func__, len, ret); + else { + audio_data->read_buf_full++; + audio_data->total_buf_cnt++; + wake_up_interruptible(&audio_data->wq); + + if (++audio_client_data->buf_head >= AUDIO_NBFRAGS_READ) + audio_client_data->buf_head = 0; + } + +} + +static void m4_handle_audio_irq(enum m4sensorhub_irqs int_event, + void *data) +{ + u32 m4_buf_cnt = 0; + u32 bufs_to_read = 0; + struct audio_client *audio_client_data = (struct audio_client *)data; + int ret = 0; + + mutex_lock(&audio_lock); + + /* Read the total buf count from M4 */ + ret = m4sensorhub_reg_read(audio_client_data->m4sensorhub, + M4SH_REG_AUDIO_TOTALPACKETS, + (char *)&m4_buf_cnt); + + if (ret != m4sensorhub_reg_getsize(audio_client_data->m4sensorhub, + M4SH_REG_AUDIO_TOTALPACKETS)) { + KDEBUG(M4SH_ERROR, "M4 packet count read failed %d\n", ret); + goto EXIT; + } + + bufs_to_read = m4_buf_cnt - audio_data->total_buf_cnt; + KDEBUG(M4SH_DEBUG, "R = %u, m4_cnt = %u, omap_cnt = %u\n", + bufs_to_read, m4_buf_cnt, audio_data->total_buf_cnt); + + /* If no free buffers, then skip reads from SPI */ + while ((bufs_to_read) && + (audio_data->read_buf_full < AUDIO_NBFRAGS_READ)) { + audio_client_spidma_read(audio_client_data, AUDIO_BUFFER_SIZE); + bufs_to_read--; + } + +EXIT: + mutex_unlock(&audio_lock); +} + +static int audio_client_open(struct inode *inode, struct file *file) +{ + int ret = 0, i = 0; + mutex_lock(&audio_lock); + + if (audio_data->dev_dsp_open_count == 1) { + KDEBUG(M4SH_ERROR, "Mic already opened, can't open again\n"); + ret = -EBUSY; + goto out; + } + + for (i = 0; i < AUDIO_NBFRAGS_READ; i++) { + audio_data->buffers[i] = kmalloc(AUDIO_BUFFER_SIZE, + GFP_KERNEL | GFP_DMA); + if (!audio_data->buffers[i]) { + KDEBUG(M4SH_ERROR, "Can't allocate memory for mic\n"); + ret = -ENOMEM; + goto free_buffers; + } + } + + audio_data->active = 0; + audio_data->usr_head = 0; + audio_data->usr_offset = 0; + audio_data->buf_head = 0; + audio_data->read_buf_full = 0; + + ret = m4sensorhub_irq_enable(audio_data->m4sensorhub, + M4SH_IRQ_MIC_DATA_READY); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Unable to enable mic irq, ret = %d\n", + ret); + goto free_buffers; + } + + init_waitqueue_head(&audio_data->wq); + + audio_data->dev_dsp_open_count = 1; + KDEBUG(M4SH_INFO, "M4 mic driver opened\n"); + goto out; + +free_buffers: + for (i = 0; i < AUDIO_NBFRAGS_READ; i++) { + kfree((void *) audio_data->buffers[i]); + audio_data->buffers[i] = NULL; + } +out: + mutex_unlock(&audio_lock); + return ret; +} + +static int audio_client_release(struct inode *inode, struct file *file) +{ + int i = 0, ret; + mutex_lock(&audio_lock); + + audio_data->active = 0; + ret = m4sensorhub_irq_disable(audio_data->m4sensorhub, + M4SH_IRQ_MIC_DATA_READY); + if (ret < 0) + KDEBUG(M4SH_ERROR, "Unable to disable mic, ret = %d\n", ret); + ret = m4sensorhub_reg_write_1byte(audio_data->m4sensorhub, + M4SH_REG_AUDIO_ENABLE, MIC_DISABLE, 0xFF); + /* Check that we wrote 1 byte */ + if (ret != 1) + KDEBUG(M4SH_ERROR, "Unable to disable mic, size = %d\n", ret); + + audio_data->dev_dsp_open_count = 0; + + for (i = 0; i < AUDIO_NBFRAGS_READ; i++) { + kfree((void *) audio_data->buffers[i]); + audio_data->buffers[i] = NULL; + } + + mutex_unlock(&audio_lock); + KDEBUG(M4SH_INFO, "M4 mic driver closed\n"); + return 0; +} + +static long audio_client_ioctl(struct file *filp, + unsigned int cmd, unsigned long arg) +{ + int ret = 0; + unsigned int samp_rate; + + mutex_lock(&audio_lock); + + switch (cmd) { + case OSS_GETVERSION: + ret = put_user(SOUND_VERSION, (int *)arg); + break; + + case SNDCTL_DSP_SPEED: + if (copy_from_user(&samp_rate, (unsigned int *)arg, + sizeof(unsigned int))) + ret = -EFAULT; + else if (samp_rate != AUDIO_SAMPLE_RATE) + ret = -EINVAL; + + break; + + case SNDCTL_DSP_GETBLKSIZE: + put_user(AUDIO_BUFFER_SIZE, (int *)arg); + break; + + default: + break; + } + mutex_unlock(&audio_lock); + return ret; +} + +static ssize_t audio_client_read(struct file *file, char *buffer, size_t size, + loff_t *nouse) +{ + int ret = 0; + int local_size = size; + int local_offset = 0; /* offset into output buffer */ + int remainder_buff = 0; /* Indicates bytes remaining in input buffer */ + + mutex_lock(&audio_lock); + + if (!audio_data->active) { + ret = m4sensorhub_reg_write_1byte(audio_data->m4sensorhub, + M4SH_REG_AUDIO_ENABLE, MIC_ENABLE, 0xFF); + /* Check that we wrote 1 byte */ + if (ret != 1) { + KDEBUG(M4SH_ERROR, "Unable to enable mic, size = %d\n", + ret); + goto out; + } + audio_data->active = 1; + audio_data->total_buf_cnt = 0; + } + + while (local_size > 0) { + mutex_unlock(&audio_lock); + ret = wait_event_interruptible_timeout(audio_data->wq, + audio_data->read_buf_full > 0, AUDIO_TIMEOUT); + mutex_lock(&audio_lock); + if (!ret) { + KDEBUG(M4SH_ERROR, + "Timed out waiting for mic buffer\n"); + goto out; + } + + remainder_buff = AUDIO_BUFFER_SIZE - audio_data->usr_offset; + if (local_size > remainder_buff) { + + if (copy_to_user(buffer + local_offset, + audio_data->buffers + [audio_data->usr_head] + + audio_data->usr_offset, + remainder_buff)) { + KDEBUG(M4SH_ERROR, + "Mic driver: copy_to_user failed \n"); + ret = -EFAULT; + goto out; + } + + if (++audio_data->usr_head >= AUDIO_NBFRAGS_READ) + audio_data->usr_head = 0; + + if (--audio_data->read_buf_full < 0) + audio_data->read_buf_full = 0; + + local_size -= remainder_buff; + local_offset += remainder_buff; + audio_data->usr_offset = 0; + } else { + + if (copy_to_user(buffer + local_offset, + audio_data->buffers + [audio_data->usr_head] + + audio_data->usr_offset, local_size)) { + KDEBUG(M4SH_ERROR, + "Mic driver: copy_to_user failed \n"); + ret = -EFAULT; + goto out; + } + + if (local_size == remainder_buff) { + if (++audio_data->usr_head >= + AUDIO_NBFRAGS_READ) + audio_data->usr_head = 0; + + if (--audio_data->read_buf_full < 0) + audio_data->read_buf_full = 0; + + audio_data->usr_offset = 0; + + } else { + audio_data->usr_offset += local_size; + } + + local_size = 0; + } + } + ret = size; + +out: + mutex_unlock(&audio_lock); + return ret; +} + +/* File Ops structure */ +static const struct file_operations audio_client_fops = { + .owner = THIS_MODULE, + .open = audio_client_open, + .release = audio_client_release, + .unlocked_ioctl = audio_client_ioctl, + .read = audio_client_read, +}; + +static int audio_client_probe(struct spi_device *spi) +{ + int ret = -1; + struct audio_client *audio_client_data; + struct m4sensorhub_data *m4sensorhub = m4sensorhub_client_get_drvdata(); + + if (!m4sensorhub) + return -EFAULT; + + audio_client_data = kzalloc(sizeof(*audio_client_data), GFP_KERNEL); + if (!audio_client_data) + return -ENOMEM; + audio_client_data->m4sensorhub = m4sensorhub; + spi_set_drvdata(spi, audio_client_data); + audio_client_data->spi = spi; + audio_data = audio_client_data; + + ret = register_sound_dsp(&audio_client_fops, -1); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error registering %s driver\n", + AUDIO_CLIENT_DRIVER_NAME); + goto free_client_data; + } + audio_client_data->dev_dsp = ret; + audio_client_data->dev_dsp_open_count = 0; + + ret = m4sensorhub_irq_register(m4sensorhub, M4SH_IRQ_MIC_DATA_READY, + m4_handle_audio_irq, audio_client_data); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error registering int %d (%d)\n", + M4SH_IRQ_MIC_DATA_READY, ret); + goto unregister_sound_device; + } + + ret = device_create_file(&spi->dev, &dev_attr_LogLevel); + if (ret) { + KDEBUG(M4SH_ERROR, "Error creating %s sys entry\n", + AUDIO_CLIENT_DRIVER_NAME); + goto unregister_irq; + } + + KDEBUG(M4SH_ERROR, "Initialized %s driver\n", AUDIO_CLIENT_DRIVER_NAME); + return 0; + +unregister_irq: + m4sensorhub_irq_unregister(m4sensorhub, M4SH_IRQ_MIC_DATA_READY); +unregister_sound_device: + unregister_sound_dsp(audio_client_data->dev_dsp); +free_client_data: + spi_set_drvdata(spi, NULL); + kfree(audio_client_data); + return ret; +} + +static int __exit audio_client_remove(struct spi_device *spi) +{ + struct audio_client *audio_client_data = spi_get_drvdata(spi); + + device_remove_file(&spi->dev, &dev_attr_LogLevel); + m4sensorhub_irq_disable(audio_client_data->m4sensorhub, + M4SH_IRQ_MIC_DATA_READY); + m4sensorhub_irq_unregister(audio_client_data->m4sensorhub, + M4SH_IRQ_MIC_DATA_READY); + unregister_sound_dsp(audio_client_data->dev_dsp); + spi_set_drvdata(spi, NULL); + kfree(audio_client_data); + return 0; +} + + +static struct of_device_id m4audio_match_tbl[] = { + {.compatible = "mot,m4audio"}, + {}, +}; + +static struct spi_driver audio_client_spi_driver = { + .driver = { + .name = AUDIO_CLIENT_DRIVER_NAME, + .bus = &spi_bus_type, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(m4audio_match_tbl), + }, + .suspend = NULL, + .resume = NULL, + .probe = audio_client_probe, + .remove = __exit_p(audio_client_remove), +}; + +static int __init audio_client_init(void) +{ + int ret = 0; + + ret = spi_register_driver(&audio_client_spi_driver); + + return ret; +} + +static void __exit audio_client_exit(void) +{ + spi_unregister_driver(&audio_client_spi_driver); +} + +module_init(audio_client_init); +module_exit(audio_client_exit); + +MODULE_ALIAS("platform:audio_client"); +MODULE_DESCRIPTION("M4 Sensor Hub audio client driver"); +MODULE_AUTHOR("Motorola"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/misc/m4sensorhub_bmp180.c b/drivers/misc/m4sensorhub_bmp180.c new file mode 100644 index 00000000000..c64fa377ee0 --- /dev/null +++ b/drivers/misc/m4sensorhub_bmp180.c @@ -0,0 +1,453 @@ +/* + * Copyright (C) 2012 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/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/uaccess.h> +#include <linux/m4sensorhub.h> +#include <linux/m4sensorhub_client_ioctl.h> +#include <linux/m4sensorhub/MemMapPressureSensor.h> +#include <linux/slab.h> + +#define PRESSURE_CLIENT_DRIVER_NAME "m4sensorhub_bmp180" +#define PRESSURE_MIN 30000 +#define PRESSURE_MAX 110000 + +struct pressure_client { + struct m4sensorhub_data *m4sensorhub; + struct input_dev *input_dev; + int pressure; + int altitude; + signed short samplerate; +}; + +struct pressure_client *misc_pressure_data; + +static int pressure_client_open(struct inode *inode, struct file *file) +{ + int err = 0; + + err = nonseekable_open(inode, file); + if (err < 0) { + KDEBUG(M4SH_ERROR, " %s failed\n", __func__); + return err; + } + file->private_data = misc_pressure_data; + + return 0; +} + +static int pressure_client_close(struct inode *inode, struct file *file) +{ + KDEBUG(M4SH_DEBUG, " pressure_client in %s\n", __func__); + return 0; +} + +static void m4_report_pressure_inputevent(struct pressure_client + *pressure_client_data) +{ + input_report_abs(pressure_client_data->input_dev, ABS_PRESSURE, + pressure_client_data->pressure); + input_report_abs(pressure_client_data->input_dev, ABS_ALTITUDE, + pressure_client_data->altitude); + input_sync(pressure_client_data->input_dev); +} + +static void m4_read_pressure_data(struct pressure_client *pressure_data) +{ + sPressureData pressure; + + m4sensorhub_reg_read(pressure_data->m4sensorhub, + M4SH_REG_PRESSURE_PRESSURE, + (char *)&pressure.pressure); + pressure_data->pressure = pressure.pressure; + m4sensorhub_reg_read(pressure_data->m4sensorhub, + M4SH_REG_PRESSURE_ABSOLUTEALTITUDE, + (char *)&pressure.absoluteAltitude); + pressure_data->altitude = pressure.absoluteAltitude; +} + +static void m4_handle_pressure_irq(enum m4sensorhub_irqs int_event, + void *pressure_data) +{ + struct pressure_client *pressure_client_data = pressure_data; + + m4_read_pressure_data(pressure_client_data); + m4_report_pressure_inputevent(pressure_client_data); +} + +static int m4_set_pressure_samplerate( + struct pressure_client *pressure_client_data, + signed int samplerate) +{ + int ret = 0; + + if (samplerate != pressure_client_data->samplerate) { + ret = m4sensorhub_reg_write(pressure_client_data->m4sensorhub, + M4SH_REG_PRESSURE_SAMPLERATE, + (char *)&samplerate, m4sh_no_mask); + if (ret != m4sensorhub_reg_getsize( + pressure_client_data->m4sensorhub, + M4SH_REG_PRESSURE_SAMPLERATE)) { + KDEBUG(M4SH_ERROR, "Unable to set delay for \ + pressure sensor\n"); + return ret; + } + + KDEBUG(M4SH_DEBUG, "%s() updating samplerate from %d to %d\n", + __func__, pressure_client_data->samplerate, + samplerate); + pressure_client_data->samplerate = samplerate; + + if (samplerate >= 0) + ret = m4sensorhub_irq_enable( + pressure_client_data->m4sensorhub, + M4SH_IRQ_PRESSURE_DATA_READY); + else + ret = m4sensorhub_irq_disable( + pressure_client_data->m4sensorhub, + M4SH_IRQ_PRESSURE_DATA_READY); + if (ret != 0) + KDEBUG(M4SH_ERROR, "Unable to enable/disable \ + pressure irq\n"); + } + + return ret; +} + +/* + * Handle commands from user-space. + */ +static long pressure_client_ioctl(struct file *filp, + unsigned int cmd, unsigned long arg) +{ + int ret = 0; + int delay; + unsigned char mask[] = {0xff, 0xff, 0xff, 0xff}; + static int status; + int altitude = 0; + void __user *argp = (void __user *)arg; + struct pressure_client *pressure_client_data = filp->private_data; + + switch (cmd) { + case M4_SENSOR_IOCTL_GET_PRESSURE: + m4_read_pressure_data(pressure_client_data); + m4_report_pressure_inputevent(pressure_client_data); + break; + + case M4_SENSOR_IOCTL_SET_DELAY: + if (copy_from_user(&delay, argp, sizeof(delay))) + return -EFAULT; + if (delay >= 0) + ret = m4_set_pressure_samplerate(pressure_client_data, + delay); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error enabling int %d (%d)\n", + M4SH_IRQ_PRESSURE_DATA_READY, ret); + return -EFAULT; + } + break; + + case M4_SENSOR_IOCTL_APP_GET_FLAG: + if (copy_to_user(argp, &status, sizeof(status))) + return -EFAULT; + break; + + case M4_SENSOR_IOCTL_APP_SET_FLAG: + if (copy_from_user(&status, argp, sizeof(status))) + return -EFAULT; + break; + case M4_SENSOR_IOCTL_SET_ALTITUDE: + if (copy_from_user(&altitude, argp, sizeof(altitude))) + return -EFAULT; + if (altitude > 0) + m4sensorhub_reg_write(pressure_client_data->m4sensorhub, + M4SH_REG_PRESSURE_REFERENCEALTITUDE, + (char *)&altitude, mask); + + break; + default: + KDEBUG(M4SH_ERROR, "Invalid IOCTL Command in %s \n", __func__); + ret = -EINVAL; + } + return ret; +} + +static ssize_t pressure_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pressure_client *pressure_client_data + = platform_get_drvdata(pdev); + + m4_read_pressure_data(pressure_client_data); + KDEBUG(M4SH_DEBUG, "%s : Pressure : = %d", + __func__, pressure_client_data->pressure); + return sprintf(buf, "%d\n", pressure_client_data->pressure); +} + +static ssize_t altitude_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pressure_client *pressure_client_data + = platform_get_drvdata(pdev); + + m4_read_pressure_data(pressure_client_data); + KDEBUG(M4SH_DEBUG, "%s : Altitude : %d", + __func__, pressure_client_data->altitude); + return sprintf(buf, "%d\n", pressure_client_data->altitude); +} + +static ssize_t bmp_get_loglevel(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long long loglevel; + struct platform_device *pdev = to_platform_device(dev); + struct pressure_client *pressure_client_data + = platform_get_drvdata(pdev); + + m4sensorhub_reg_read(pressure_client_data->m4sensorhub, + M4SH_REG_LOG_LOGENABLE, (char *)&loglevel); + loglevel = get_log_level(loglevel, BMP_MASK_BIT_1); + return sprintf(buf, "%llu\n", loglevel); +} +static ssize_t bmp_set_loglevel(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long level; + unsigned long long mask = 0, newlevel; + struct platform_device *pdev = to_platform_device(dev); + struct pressure_client *pressure_client_data + = platform_get_drvdata(pdev); + + if ((strict_strtoul(buf, 10, &level)) < 0) + return -1; + if (level > M4_MAX_LOG_LEVEL) { + KDEBUG(M4SH_ERROR, " Invalid log level - %d\n", (int)level); + return -1; + } + mask = (1ULL << BMP_MASK_BIT_1) | (1ULL << BMP_MASK_BIT_2); + newlevel = ((unsigned long long)level << BMP_MASK_BIT_1); + return m4sensorhub_reg_write(pressure_client_data->m4sensorhub, + M4SH_REG_LOG_LOGENABLE, (char *)&newlevel, (unsigned char *)&mask); +} + +static DEVICE_ATTR(pressure, 0444, pressure_show, NULL); +static DEVICE_ATTR(altitude, 0444, altitude_show, NULL); +static DEVICE_ATTR(LogLevel, 0664, bmp_get_loglevel, bmp_set_loglevel); + +static const struct file_operations pressure_client_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = pressure_client_ioctl, + .open = pressure_client_open, + .release = pressure_client_close, +}; + +static struct miscdevice pressure_client_miscdrv = { + .minor = MISC_DYNAMIC_MINOR, + .name = PRESSURE_CLIENT_DRIVER_NAME, + .fops = &pressure_client_fops, +}; + +static int pressure_client_probe(struct platform_device *pdev) +{ + int ret = -1; + struct pressure_client *pressure_client_data; + struct m4sensorhub_data *m4sensorhub = m4sensorhub_client_get_drvdata(); + + if (!m4sensorhub) + return -EFAULT; + + pressure_client_data = kzalloc(sizeof(*pressure_client_data), + GFP_KERNEL); + if (!pressure_client_data) + return -ENOMEM; + + pressure_client_data->m4sensorhub = m4sensorhub; + platform_set_drvdata(pdev, pressure_client_data); + + pressure_client_data->input_dev = input_allocate_device(); + if (!pressure_client_data->input_dev) { + ret = -ENOMEM; + KDEBUG(M4SH_ERROR, "%s: input device allocate failed: %d\n", + __func__, ret); + goto free_mem; + } + + pressure_client_data->input_dev->name = PRESSURE_CLIENT_DRIVER_NAME; + set_bit(EV_ABS, pressure_client_data->input_dev->evbit); + set_bit(ABS_PRESSURE, pressure_client_data->input_dev->absbit); + set_bit(ABS_ALTITUDE, pressure_client_data->input_dev->absbit); + input_set_abs_params(pressure_client_data->input_dev, ABS_PRESSURE, + PRESSURE_MIN, PRESSURE_MAX, 0, 0); + + if (input_register_device(pressure_client_data->input_dev)) { + KDEBUG(M4SH_ERROR, "%s: input device register failed\n", + __func__); + input_free_device(pressure_client_data->input_dev); + goto free_mem; + } + + ret = misc_register(&pressure_client_miscdrv); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error registering %s driver\n", + PRESSURE_CLIENT_DRIVER_NAME); + goto unregister_input_device; + } + misc_pressure_data = pressure_client_data; + ret = m4sensorhub_irq_register(m4sensorhub, + M4SH_IRQ_PRESSURE_DATA_READY, m4_handle_pressure_irq, + pressure_client_data); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error registering int %d (%d)\n", + M4SH_IRQ_PRESSURE_DATA_READY, ret); + goto unregister_misc_device; + } + if (device_create_file(&pdev->dev, &dev_attr_pressure)) { + KDEBUG(M4SH_ERROR, "Error creating %s sys entry\n", + PRESSURE_CLIENT_DRIVER_NAME); + ret = -1; + goto unregister_irq; + } + + if (device_create_file(&pdev->dev, &dev_attr_altitude)) { + KDEBUG(M4SH_ERROR, "Error creating %s sys entry\n", + PRESSURE_CLIENT_DRIVER_NAME); + ret = -1; + goto remove_device_file; + } + if (device_create_file(&pdev->dev, &dev_attr_LogLevel)) { + KDEBUG(M4SH_ERROR, "Error creating %s sys entry\n", + PRESSURE_CLIENT_DRIVER_NAME); + ret = -1; + goto remove_alt_device_file; + } + KDEBUG(M4SH_ERROR, "Initialized %s driver\n", __func__); + return 0; + +remove_alt_device_file: + device_remove_file(&pdev->dev, &dev_attr_altitude); +remove_device_file: + device_remove_file(&pdev->dev, &dev_attr_pressure); +unregister_irq: + m4sensorhub_irq_unregister(m4sensorhub, M4SH_IRQ_PRESSURE_DATA_READY); +unregister_misc_device: + misc_pressure_data = NULL; + misc_deregister(&pressure_client_miscdrv); +unregister_input_device: + input_unregister_device(pressure_client_data->input_dev); +free_mem: + platform_set_drvdata(pdev, NULL); + pressure_client_data->m4sensorhub = NULL; + kfree(pressure_client_data); + pressure_client_data = NULL; + return ret; +} + +static int __exit pressure_client_remove(struct platform_device *pdev) +{ + struct pressure_client *pressure_client_data = + platform_get_drvdata(pdev); + + device_remove_file(&pdev->dev, &dev_attr_LogLevel); + device_remove_file(&pdev->dev, &dev_attr_pressure); + device_remove_file(&pdev->dev, &dev_attr_altitude); + m4sensorhub_irq_disable(pressure_client_data->m4sensorhub, + M4SH_IRQ_PRESSURE_DATA_READY); + m4sensorhub_irq_unregister(pressure_client_data->m4sensorhub, + M4SH_IRQ_PRESSURE_DATA_READY); + misc_pressure_data = NULL; + misc_deregister(&pressure_client_miscdrv); + input_unregister_device(pressure_client_data->input_dev); + platform_set_drvdata(pdev, NULL); + pressure_client_data->m4sensorhub = NULL; + kfree(pressure_client_data); + pressure_client_data = NULL; + return 0; +} + +static void pressure_client_shutdown(struct platform_device *pdev) +{ + return; +} +#ifdef CONFIG_PM +static int pressure_client_suspend(struct platform_device *pdev, + pm_message_t message) +{ + struct pressure_client *pressure_client_data = + platform_get_drvdata(pdev); + + return m4_set_pressure_samplerate(pressure_client_data, -1); +} + +static int pressure_client_resume(struct platform_device *pdev) +{ + return 0; +} +#else +#define pressure_client_suspend NULL +#define pressure_client_resume NULL +#endif + +static struct of_device_id m4pressure_match_tbl[] = { + {.compatible = "mot,m4pressure" }, + {}, +}; + +static struct platform_driver pressure_client_driver = { + .probe = pressure_client_probe, + .remove = __exit_p(pressure_client_remove), + .shutdown = pressure_client_shutdown, + .suspend = pressure_client_suspend, + .resume = pressure_client_resume, + .driver = { + .name = PRESSURE_CLIENT_DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(m4pressure_match_tbl), + }, +}; + +static int __init pressure_client_init(void) +{ + return platform_driver_register(&pressure_client_driver); +} + +static void __exit pressure_client_exit(void) +{ + platform_driver_unregister(&pressure_client_driver); +} + +module_init(pressure_client_init); +module_exit(pressure_client_exit); + +MODULE_ALIAS("platform:pressure_client"); +MODULE_DESCRIPTION("M4 Sensor Hub Pressure client driver"); +MODULE_AUTHOR("Motorola"); +MODULE_LICENSE("GPL"); + 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"); diff --git a/drivers/misc/m4sensorhub_download.c b/drivers/misc/m4sensorhub_download.c new file mode 100644 index 00000000000..7ad1aae47b5 --- /dev/null +++ b/drivers/misc/m4sensorhub_download.c @@ -0,0 +1,501 @@ +/* + * Copyright (C) 2012 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/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/uaccess.h> +#include <linux/m4sensorhub.h> +#include <linux/m4sensorhub_client_ioctl.h> +#include <linux/m4sensorhub/MemMapDownload.h> +#include <linux/wait.h> +#include <linux/slab.h> + +#define DOWNLOAD_CLIENT_DRIVER_NAME "m4sensorhub_download" +#define M4_SENSOR_DL_MAX_RETRY_CNT 3 +#define M4_SENSOR_DL_MAX_RET_SIZE 8 +#define M4_SENSOR_DL_MIN_INPUT_SIZE \ + (sizeof(struct m4sh_download_packet) - M4_SENSOR_DL_MAX_PACKET_SIZE) + +enum { + i2c_reg_end, + i2c_reg_read, + i2c_reg_write, + i2c_reg_wait, +}; + +struct i2c_reg_sequence { + int direct; + int reg; +}; + +struct download_client { + struct m4sensorhub_data *m4sensorhub; +}; + +struct download_client *misc_download_data; +static wait_queue_head_t download_wq; +static atomic_t m4_dlcmd_resp_ready; +static atomic_t download_client_entry; + +static struct i2c_reg_sequence seq_m4dlm_get_checksum[] = { + {i2c_reg_write, M4SH_REG_DOWNLOAD_FILENAME}, + {i2c_reg_write, M4SH_REG_DOWNLOAD_COMMAND}, + {i2c_reg_read, M4SH_REG_DOWNLOAD_CHECKSUM}, + {i2c_reg_read, M4SH_REG_DOWNLOAD_STATUS}, + {i2c_reg_end, i2c_reg_end} +}; + +static struct i2c_reg_sequence seq_m4dlm_open_file[] = { + {i2c_reg_write, M4SH_REG_DOWNLOAD_FILENAME}, + {i2c_reg_write, M4SH_REG_DOWNLOAD_COMMAND}, + {i2c_reg_wait, M4SH_REG_DOWNLOAD_STATUS}, + {i2c_reg_end, i2c_reg_end} +}; + +static struct i2c_reg_sequence seq_m4dlm_close_file[] = { + {i2c_reg_write, M4SH_REG_DOWNLOAD_COMMAND}, + {i2c_reg_wait, M4SH_REG_DOWNLOAD_STATUS}, + {i2c_reg_read, M4SH_REG_DOWNLOAD_CHECKSUM}, + {i2c_reg_end, i2c_reg_end} +}; + +static struct i2c_reg_sequence seq_m4dlm_delete_file[] = { + {i2c_reg_write, M4SH_REG_DOWNLOAD_FILENAME}, + {i2c_reg_write, M4SH_REG_DOWNLOAD_COMMAND}, + {i2c_reg_wait, M4SH_REG_DOWNLOAD_STATUS}, + {i2c_reg_end, i2c_reg_end} +}; + +static struct i2c_reg_sequence seq_m4dlm_write_file[] = { + {i2c_reg_write, M4SH_REG_DOWNLOAD_PACKET}, + {i2c_reg_wait, M4SH_REG_DOWNLOAD_STATUS}, + {i2c_reg_end, i2c_reg_end} +}; + +static struct i2c_reg_sequence seq_m4dlm_write_size_file[] = { + {i2c_reg_write, M4SH_REG_DOWNLOAD_SIZE}, + {i2c_reg_write, M4SH_REG_DOWNLOAD_PACKET}, + {i2c_reg_wait, M4SH_REG_DOWNLOAD_STATUS}, + {i2c_reg_end, i2c_reg_end} +}; + +static int download_client_open(struct inode *inode, struct file *file) +{ + int err = atomic_inc_return(&download_client_entry); + if (err == 1) { + err = nonseekable_open(inode, file); + if (err >= 0) { + file->private_data = misc_download_data; + return 0; + } + } else + err = -EBUSY; + + atomic_dec_return(&download_client_entry); + KDEBUG(M4SH_ERROR, "%s: failed, err=%d\n", __func__, -err); + return err; +} + +static int download_client_close(struct inode *inode, struct file *file) +{ + int entry = atomic_dec_return(&download_client_entry); + file->private_data = NULL; + KDEBUG(M4SH_DEBUG, "%s: entry = %d\n", __func__, entry); + return 0; +} + +static void m4_handle_download_irq(enum m4sensorhub_irqs int_event, + void *download_data) +{ + atomic_set(&m4_dlcmd_resp_ready, true); + wake_up_interruptible(&download_wq); +} + +static inline void wait_m4_cmd_executed(void) +{ + wait_event_interruptible(download_wq, \ + (atomic_read(&m4_dlcmd_resp_ready))); + atomic_set(&m4_dlcmd_resp_ready, false); +} + +static char *m4dlm_i2c_reg_seq_getptr( + int reg, struct m4sh_download_packet *dl_packet) +{ + switch (reg) { + case M4SH_REG_DOWNLOAD_COMMAND: + return (char *)(&(dl_packet->command)); + case M4SH_REG_DOWNLOAD_STATUS: + return (char *)(&(dl_packet->status)); + case M4SH_REG_DOWNLOAD_SIZE: + return (char *)(&(dl_packet->size)); + case M4SH_REG_DOWNLOAD_CHECKSUM: + return (char *)(&(dl_packet->checksum)); + case M4SH_REG_DOWNLOAD_FILENAME: + return (char *)(dl_packet->filename); + case M4SH_REG_DOWNLOAD_PACKET: + return (char *)(dl_packet->buffer); + } + KDEBUG(M4SH_ERROR, "%s Invaild i2c reg %d\n", __func__, reg); + return NULL; +} + +static int m4dlm_i2c_reg_seq_process( + struct m4sensorhub_data *m4sensorhub, + struct m4sh_download_packet *dl_packet, + struct i2c_reg_sequence *i2c_reg_seq) +{ + int ret; + for (; i2c_reg_seq->direct != i2c_reg_end; i2c_reg_seq++) { + /*we don't need retry for I2C read/write as + *m4sensorhub_reg_write/read already had retry mechanism + */ + switch (i2c_reg_seq->direct) { + case i2c_reg_write: + ret = m4sensorhub_reg_write(m4sensorhub, + i2c_reg_seq->reg, + m4dlm_i2c_reg_seq_getptr(\ + i2c_reg_seq->reg, dl_packet), + m4sh_no_mask); + break; + case i2c_reg_wait: + /*Wait for IRQ answered*/ + wait_m4_cmd_executed(); + /*fallback to read status*/ + case i2c_reg_read: + ret = m4sensorhub_reg_read(m4sensorhub, + i2c_reg_seq->reg, + m4dlm_i2c_reg_seq_getptr(\ + i2c_reg_seq->reg, dl_packet) + ); + break; + default: + /*should be fault*/ + KDEBUG(M4SH_ERROR, "%s: Invaild I2C direct %d\n", \ + __func__, i2c_reg_seq->direct); + return -ENOEXEC; + } + if (ret != m4sensorhub_reg_getsize(\ + m4sensorhub, i2c_reg_seq->reg)) { + KDEBUG(M4SH_ERROR, "%s: Process I2C [%d-%d] failed!\n", + __func__, i2c_reg_seq->direct, + i2c_reg_seq->reg); + return -EIO; + } + } + return 0; +} + +static long download_client_ioctl( + struct file *filp, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + + struct download_client *download_data = filp->private_data; + struct i2c_reg_sequence *i2c_req_seq = NULL; + int ret = -EINVAL, retry; + static struct m4sh_download_packet packet; + static unsigned short packet_size; + + switch (cmd) { + case M4_SENSOR_IOCTL_DL_SEND_PACKET: + if (copy_from_user(&packet, argp, M4_SENSOR_DL_MIN_INPUT_SIZE)) + return -EFAULT; + + KDEBUG(M4SH_INFO, "%s cmd = %d\n", __func__, packet.command); + + switch (packet.command) { + case M4_SENSOR_DL_CMD_GET_CHECKSUM: + i2c_req_seq = seq_m4dlm_get_checksum; + break; + case M4_SENSOR_DL_CMD_OPEN_FILE: + packet_size = 0; + i2c_req_seq = seq_m4dlm_open_file; + break; + case M4_SENSOR_DL_CMD_DELETE_FILE: + i2c_req_seq = seq_m4dlm_delete_file; + break; + case M4_SENSOR_DL_CMD_CLOSE_FILE: + i2c_req_seq = seq_m4dlm_close_file; + break; + case M4_SENSOR_DL_CMD_WRITE_FILE: + if (!packet.size || \ + (packet.size > M4_SENSOR_DL_MAX_PACKET_SIZE)) { + packet.status = M4_SENSOR_DL_ERROR_INVALID_SIZE; + /*we only copy packet data before filename*/ + if (copy_to_user(argp, &packet, \ + M4_SENSOR_DL_MAX_RET_SIZE)) + return -EFAULT; + return 0; + } + if (copy_from_user(&packet, argp, sizeof(packet))) + return -EFAULT; + if (packet.size != packet_size) { + i2c_req_seq = seq_m4dlm_write_size_file; + packet_size = packet.size; + } else + i2c_req_seq = seq_m4dlm_write_file; + break; + default: + /*should be wrong command received*/ + KDEBUG(M4SH_ERROR, "%s Invaild packet cmd %d\n", \ + __func__, packet.command); + return -EINVAL; + } + for (retry = 0; retry++ < M4_SENSOR_DL_MAX_RETRY_CNT; ) { + ret = m4dlm_i2c_reg_seq_process(\ + download_data->m4sensorhub, + &packet, i2c_req_seq); + /*only retry if M4 has internal error*/ + if (!ret) { + switch (packet.status) { + case M4_SENSOR_DL_ERROR_SEND_CMD: + case M4_SENSOR_DL_ERROR_DATA_CHECKSUM: + /*something wrong and we need retry*/ + KDEBUG(M4SH_ERROR, \ + "Tried %d times for packet cmd %d\n", \ + retry, packet.command); + continue; + } + } + break; /*exit retry loop*/ + } + if (!ret) { + /*we only copy packet data before filename for return*/ + if (copy_to_user(argp, &packet, \ + M4_SENSOR_DL_MAX_RET_SIZE)) + return -EFAULT; + } + break; + default: + KDEBUG(M4SH_ERROR, "%s Invaild ioctl cmd %d\n", __func__, cmd); + break; + } + return ret; +} + +static ssize_t download_get_loglevel(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int loglevel; + struct platform_device *pdev = to_platform_device(dev); + struct download_client *download_client_data = + platform_get_drvdata(pdev); + + m4sensorhub_reg_read(download_client_data->m4sensorhub, + M4SH_REG_LOG_LOGENABLE, (char *)&loglevel); + loglevel = get_log_level(loglevel, DOWNLOAD_MASK_BIT_1); + return sprintf(buf, "%d\n", loglevel); +} + +static ssize_t download_set_loglevel(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long level; + unsigned int mask = 0; + struct platform_device *pdev = to_platform_device(dev); + struct download_client *download_client_data = + platform_get_drvdata(pdev); + + if ((strict_strtoul(buf, 10, &level)) < 0) + return -EINVAL; + if (level > M4_MAX_LOG_LEVEL) { + KDEBUG(M4SH_ERROR, " Invalid log level - %d\n", (int)level); + return -EINVAL; + } + mask = (1 << DOWNLOAD_MASK_BIT_1) | (1 << DOWNLOAD_MASK_BIT_2); + level = (level << DOWNLOAD_MASK_BIT_1); + return m4sensorhub_reg_write(download_client_data->m4sensorhub, + M4SH_REG_LOG_LOGENABLE, (char *)&level, (unsigned char *)&mask); +} + +static DEVICE_ATTR(LogLevel, 0664, \ + download_get_loglevel, download_set_loglevel); + +static const struct file_operations download_client_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = download_client_ioctl, + .open = download_client_open, + .release = download_client_close, +}; + +static struct miscdevice download_client_miscdrv = { + .minor = MISC_DYNAMIC_MINOR, + .name = DOWNLOAD_CLIENT_DRIVER_NAME, + .fops = &download_client_fops, +}; + +static int download_client_probe(struct platform_device *pdev) +{ + int ret = -1; + struct download_client *download_client_data; + struct m4sensorhub_data *m4sensorhub = m4sensorhub_client_get_drvdata(); + + if (!m4sensorhub) { + printk(KERN_WARNING "m4sensorhub is null\n"); + return -EFAULT; + } + + download_client_data = + kzalloc(sizeof(*download_client_data), GFP_KERNEL); + if (!download_client_data) + return -ENOMEM; + + download_client_data->m4sensorhub = m4sensorhub; + platform_set_drvdata(pdev, download_client_data); + + ret = misc_register(&download_client_miscdrv); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error registering %s driver\n", + DOWNLOAD_CLIENT_DRIVER_NAME); + goto free_memory; + } + misc_download_data = download_client_data; + ret = m4sensorhub_irq_register(m4sensorhub, M4SH_IRQ_DLCMD_RESP_READY, + m4_handle_download_irq, + download_client_data); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error registering int %d (%d)\n", + M4SH_IRQ_DLCMD_RESP_READY, ret); + goto unregister_misc_device; + } + + ret = m4sensorhub_irq_enable(m4sensorhub, M4SH_IRQ_DLCMD_RESP_READY); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error enable irq %d (%d)\n", + M4SH_IRQ_DLCMD_RESP_READY, ret); + goto unregister_irq; + } + + if (device_create_file(&pdev->dev, &dev_attr_LogLevel)) { + KDEBUG(M4SH_ERROR, "Error creating %s sys entry\n", + DOWNLOAD_CLIENT_DRIVER_NAME); + ret = -1; + goto disable_irq; + } + + init_waitqueue_head(&download_wq); + atomic_set(&m4_dlcmd_resp_ready, false); + atomic_set(&download_client_entry, 0); + + KDEBUG(M4SH_INFO, "Initialized %s driver\n", + DOWNLOAD_CLIENT_DRIVER_NAME); + return 0; + +disable_irq: + m4sensorhub_irq_disable(m4sensorhub, M4SH_IRQ_DLCMD_RESP_READY); +unregister_irq: + m4sensorhub_irq_unregister(m4sensorhub, M4SH_IRQ_DLCMD_RESP_READY); +unregister_misc_device: + misc_download_data = NULL; + misc_deregister(&download_client_miscdrv); +free_memory: + platform_set_drvdata(pdev, NULL); + download_client_data->m4sensorhub = NULL; + kfree(download_client_data); + download_client_data = NULL; + return ret; +} + +static int __exit download_client_remove(struct platform_device *pdev) +{ + struct download_client *download_client_data = + platform_get_drvdata(pdev); + + device_remove_file(&pdev->dev, &dev_attr_LogLevel); + m4sensorhub_irq_disable(download_client_data->m4sensorhub, + M4SH_IRQ_DLCMD_RESP_READY); + m4sensorhub_irq_unregister(download_client_data->m4sensorhub, + M4SH_IRQ_DLCMD_RESP_READY); + misc_download_data = NULL; + misc_deregister(&download_client_miscdrv); + platform_set_drvdata(pdev, NULL); + download_client_data->m4sensorhub = NULL; + kfree(download_client_data); + download_client_data = NULL; + return 0; +} + +static void download_client_shutdown(struct platform_device *pdev) +{ +} + +#ifdef CONFIG_PM + +static int download_client_suspend(struct platform_device *pdev, + pm_message_t message) +{ + return 0; +} + +static int download_client_resume(struct platform_device *pdev) +{ + return 0; +} + +#else +#define download_client_suspend NULL +#define download_client_resume NULL +#endif + +static struct of_device_id m4download_match_tbl[] = { + { .compatible = "mot,m4download" }, + {}, +}; + +static struct platform_driver download_client_driver = { + .probe = download_client_probe, + .remove = __exit_p(download_client_remove), + .shutdown = download_client_shutdown, + .suspend = download_client_suspend, + .resume = download_client_resume, + .driver = { + .name = DOWNLOAD_CLIENT_DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(m4download_match_tbl), + }, +}; + +static int __init download_client_init(void) +{ + return platform_driver_register(&download_client_driver); +} + +static void __exit download_client_exit(void) +{ + platform_driver_unregister(&download_client_driver); +} + +module_init(download_client_init); +module_exit(download_client_exit); + +MODULE_ALIAS("platform:download_client"); +MODULE_DESCRIPTION("M4 Sensor Hub driver"); +MODULE_AUTHOR("Motorola"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/misc/m4sensorhub_gesture.c b/drivers/misc/m4sensorhub_gesture.c new file mode 100644 index 00000000000..b067be71d24 --- /dev/null +++ b/drivers/misc/m4sensorhub_gesture.c @@ -0,0 +1,415 @@ +/* + * Copyright (C) 2012 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/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/m4sensorhub.h> +#include <linux/m4sensorhub_client_ioctl.h> +#include <linux/uaccess.h> +#include <linux/m4sensorhub/MemMapGesture.h> +#include <linux/slab.h> + +#define GESTURE_CLIENT_DRIVER_NAME "m4sensorhub_gesture" + +struct gesture_client { + struct m4sensorhub_data *m4sensorhub; + struct input_dev *input_dev; + struct memMapGesture gesture_data; +}; + +static bool read_gesture_value(struct gesture_client *list, + signed char *value, + eGestureType gesture) +{ + if (list->gesture_data.gesture1 == gesture) { + *value = list->gesture_data.value1; + return true; + } else if (list->gesture_data.gesture2 == gesture) { + *value = list->gesture_data.value2; + return true; + } else if (list->gesture_data.gesture3 == gesture) { + *value = list->gesture_data.value3; + return true; + } else { + *value = 0; + return false; + } +} + +static struct gesture_client *misc_gesture_data; + +static int gesture_client_open(struct inode *inode, struct file *file) +{ + int err = 0; + + err = nonseekable_open(inode, file); + if (err < 0) { + KDEBUG(M4SH_ERROR, "%s failed\n", __func__); + return err; + } + file->private_data = misc_gesture_data; + + return 0; +} + +static int gesture_client_close(struct inode *inode, struct file *file) +{ + KDEBUG(M4SH_DEBUG, "gesture_client in %s\n", __func__); + return 0; +} + +static char m4_read_gesture_data(struct gesture_client *gesture_client_data) +{ + int ret; + struct memMapGesture *gdata = &(gesture_client_data->gesture_data); + + ret = m4sensorhub_reg_read_n(gesture_client_data->m4sensorhub, + M4SH_REG_GESTURE_VERSION, + (char *)gdata, + sizeof(gesture_client_data->gesture_data)); + + if (ret != sizeof(gesture_client_data->gesture_data)) + goto ERR; + + KDEBUG(M4SH_DEBUG, "Gesture1 = %d, gesture2 = %d, gesture3 = %d\n", + gdata->gesture1, gdata->gesture2, gdata->gesture3); + KDEBUG(M4SH_DEBUG, "Confidence1 = %d, confidence2 = %d, confidence3 = %d\n", + gdata->confidence1, gdata->confidence2, gdata->confidence3); + KDEBUG(M4SH_DEBUG, "Value1 = %d, value2 = %d, value3 = %d\n", + gdata->value1, gdata->value2, gdata->value3); + return 0; +ERR: + KDEBUG(M4SH_ERROR, "Gesture read failed\n"); + return -1; +} + +static void m4_handle_gesture_irq(enum m4sensorhub_irqs int_event, + void *gesture_data) +{ + signed char value; + /*Trigger broadcast of display gesture intent*/ + struct gesture_client *gesture_client_data = + (struct gesture_client *)gesture_data; + + struct memMapGesture *gdata = &(gesture_client_data->gesture_data); + + if (m4_read_gesture_data(gesture_client_data) < 0) { + KDEBUG(M4SH_ERROR, "m4_read_gesture_data returned \ + error %s\n", __func__); + return; + } + + if (read_gesture_value(gesture_client_data, &value, GESTURE_TILT_SCROLL)) { + input_event(gesture_client_data->input_dev, EV_ABS, + ABS_TILTSCROLL, value); + } else { + if (read_gesture_value(gesture_client_data, &value, GESTURE_WRIST_ROTATE)) { + /* send event to stop scrolling for wrist rotate */ + input_event(gesture_client_data->input_dev, EV_ABS, + ABS_TILTSCROLL, 0); + } + } + + input_event(gesture_client_data->input_dev, EV_MSC, + MSC_GESTURE1, gdata->gesture1); + input_event(gesture_client_data->input_dev, EV_MSC, + MSC_GESTURE2, gdata->gesture2); + input_event(gesture_client_data->input_dev, EV_MSC, + MSC_GESTURE3, gdata->gesture3); + input_event(gesture_client_data->input_dev, EV_MSC, + MSC_GESTURE_CONFIDENCE1, gdata->confidence1); + input_event(gesture_client_data->input_dev, EV_MSC, + MSC_GESTURE_CONFIDENCE2, gdata->confidence2); + input_event(gesture_client_data->input_dev, EV_MSC, + MSC_GESTURE_CONFIDENCE3, gdata->confidence3); + input_event(gesture_client_data->input_dev, EV_MSC, + MSC_GESTURE_VALUE1, gdata->value1); + input_event(gesture_client_data->input_dev, EV_MSC, + MSC_GESTURE_VALUE2, gdata->value2); + input_event(gesture_client_data->input_dev, EV_MSC, + MSC_GESTURE_VALUE3, gdata->value3); + + input_sync(gesture_client_data->input_dev); + +} + +/* + * Handle commands from user-space. + */ +static long gesture_client_ioctl(struct file *filp, + unsigned int cmd, unsigned long arg) +{ + int ret = 0; + void __user *argp = (void __user *)arg; + struct gesture_client *gesture_client_data = filp->private_data; + unsigned char byte; + + switch (cmd) { + case M4_SENSOR_IOCTL_SET_SCREEN_ON_GESTURE_STATUS: + /* TODO + Turn on/off the gesture feature on M4 */ + break; + case M4_SENSOR_IOCTL_SET_SCREEN_STATUS: + if (copy_from_user(&byte, argp, sizeof(byte))) { + KDEBUG(M4SH_ERROR, "Copy frm usr err:screen status\n"); + ret = -EFAULT; + break; + } + /* validate data */ + if (byte > 1) { + KDEBUG(M4SH_DEBUG, "Invalid screen status=0x%x", byte); + ret = -EINVAL; + break; + } + KDEBUG(M4SH_DEBUG, "Screen status set to = 0x%x", byte); + ret = m4sensorhub_reg_write_1byte( + gesture_client_data->m4sensorhub, + M4SH_REG_USERSETTINGS_SCREENSTATUS, byte, 0xFF); + if (ret != 1) + KDEBUG(M4SH_ERROR, "Error writing screen status\n"); + break; + default: + KDEBUG(M4SH_ERROR, "Invalid IOCTL Command in %s\n", __func__); + ret = -EINVAL; + } + return ret; +} + +static ssize_t m4_gesture_status(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct gesture_client *gesture_client_data = platform_get_drvdata(pdev); + + if (m4_read_gesture_data(gesture_client_data) < 0) + KDEBUG(M4SH_ERROR, "%s: Read gesture data failed \n", __func__); + KDEBUG(M4SH_DEBUG, "%s:Gesture1,2,3 = %d %d %d conf1,2,3 = %d %d %d value1,2,3 = %d %d %d\n", + __func__, gesture_client_data->gesture_data.gesture1, + gesture_client_data->gesture_data.gesture2, + gesture_client_data->gesture_data.gesture3, + gesture_client_data->gesture_data.confidence1, + gesture_client_data->gesture_data.confidence2, + gesture_client_data->gesture_data.confidence3, + gesture_client_data->gesture_data.value1, + gesture_client_data->gesture_data.value2, + gesture_client_data->gesture_data.value3); + + return sprintf(buf, "gesture1,2,3=%d %d %d,confidence1,2,3=%d %d %d value1,2,3 = %d %d %d\n", + gesture_client_data->gesture_data.gesture1, + gesture_client_data->gesture_data.gesture2, + gesture_client_data->gesture_data.gesture3, + gesture_client_data->gesture_data.confidence1, + gesture_client_data->gesture_data.confidence2, + gesture_client_data->gesture_data.confidence3, + gesture_client_data->gesture_data.value1, + gesture_client_data->gesture_data.value2, + gesture_client_data->gesture_data.value3); +} + +static DEVICE_ATTR(gesture_status, 0444, m4_gesture_status, NULL); + +static const struct file_operations gesture_client_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = gesture_client_ioctl, + .open = gesture_client_open, + .release = gesture_client_close, +}; + +static struct miscdevice gesture_client_miscdrv = { + .minor = MISC_DYNAMIC_MINOR, + .name = GESTURE_CLIENT_DRIVER_NAME, + .fops = &gesture_client_fops, +}; + +static int gesture_client_probe(struct platform_device *pdev) +{ + int ret = -1; + struct gesture_client *gesture_client_data; + struct m4sensorhub_data *m4sensorhub = m4sensorhub_client_get_drvdata(); + + if (!m4sensorhub) + return -EFAULT; + + gesture_client_data = kzalloc(sizeof(*gesture_client_data), + GFP_KERNEL); + if (!gesture_client_data) + return -ENOMEM; + + gesture_client_data->m4sensorhub = m4sensorhub; + platform_set_drvdata(pdev, gesture_client_data); + + gesture_client_data->input_dev = input_allocate_device(); + if (!gesture_client_data->input_dev) { + ret = -ENOMEM; + KDEBUG(M4SH_ERROR, "%s: input device allocate failed: %d\n", + __func__, ret); + goto free_mem; + } + + gesture_client_data->input_dev->name = GESTURE_CLIENT_DRIVER_NAME; + set_bit(EV_MSC, gesture_client_data->input_dev->evbit); + set_bit(MSC_GESTURE1, gesture_client_data->input_dev->mscbit); + set_bit(MSC_GESTURE2, gesture_client_data->input_dev->mscbit); + set_bit(MSC_GESTURE3, gesture_client_data->input_dev->mscbit); + set_bit(MSC_GESTURE_CONFIDENCE1, gesture_client_data->input_dev->mscbit); + set_bit(MSC_GESTURE_CONFIDENCE2, gesture_client_data->input_dev->mscbit); + set_bit(MSC_GESTURE_CONFIDENCE3, gesture_client_data->input_dev->mscbit); + set_bit(MSC_GESTURE_VALUE1, gesture_client_data->input_dev->mscbit); + set_bit(MSC_GESTURE_VALUE2, gesture_client_data->input_dev->mscbit); + set_bit(MSC_GESTURE_VALUE3, gesture_client_data->input_dev->mscbit); + + set_bit(EV_ABS, gesture_client_data->input_dev->evbit); + set_bit(ABS_TILTSCROLL, gesture_client_data->input_dev->absbit); + + if (input_register_device(gesture_client_data->input_dev)) { + KDEBUG(M4SH_ERROR, "%s: input device register failed\n", + __func__); + input_free_device(gesture_client_data->input_dev); + goto free_mem; + } + + ret = misc_register(&gesture_client_miscdrv); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error registering %s driver\n", __func__); + goto unregister_input_device; + } + misc_gesture_data = gesture_client_data; + ret = m4sensorhub_irq_register(m4sensorhub, M4SH_IRQ_GESTURE_DETECTED, + m4_handle_gesture_irq, + gesture_client_data); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error registering int %d (%d)\n", + M4SH_IRQ_GESTURE_DETECTED, ret); + goto unregister_misc_device; + } + ret = m4sensorhub_irq_enable(m4sensorhub, M4SH_IRQ_GESTURE_DETECTED); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error enabling int %d (%d)\n", + M4SH_IRQ_GESTURE_DETECTED, ret); + goto unregister_display_gesture_irq; + } + if (device_create_file(&pdev->dev, &dev_attr_gesture_status)) { + KDEBUG(M4SH_ERROR, "Error creating %s sys entry\n", __func__); + ret = -1; + goto disable_display_gesture_irq; + } + KDEBUG(M4SH_INFO, "Initialized %s driver\n", __func__); + return 0; + +disable_display_gesture_irq: + m4sensorhub_irq_disable(m4sensorhub, M4SH_IRQ_GESTURE_DETECTED); +unregister_display_gesture_irq: + m4sensorhub_irq_unregister(m4sensorhub, M4SH_IRQ_GESTURE_DETECTED); +unregister_misc_device: + misc_gesture_data = NULL; + misc_deregister(&gesture_client_miscdrv); +unregister_input_device: + input_unregister_device(gesture_client_data->input_dev); +free_mem: + platform_set_drvdata(pdev, NULL); + gesture_client_data->m4sensorhub = NULL; + kfree(gesture_client_data); + gesture_client_data = NULL; + return ret; +} + +static int __exit gesture_client_remove(struct platform_device *pdev) +{ + struct gesture_client *gesture_client_data = + platform_get_drvdata(pdev); + + device_remove_file(&pdev->dev, &dev_attr_gesture_status); + m4sensorhub_irq_disable(gesture_client_data->m4sensorhub, + M4SH_IRQ_GESTURE_DETECTED); + m4sensorhub_irq_unregister(gesture_client_data->m4sensorhub, + M4SH_IRQ_GESTURE_DETECTED); + misc_gesture_data = NULL; + misc_deregister(&gesture_client_miscdrv); + input_unregister_device(gesture_client_data->input_dev); + platform_set_drvdata(pdev, NULL); + gesture_client_data->m4sensorhub = NULL; + kfree(gesture_client_data); + gesture_client_data = NULL; + return 0; +} + +static void gesture_client_shutdown(struct platform_device *pdev) +{ + return; +} +#ifdef CONFIG_PM +static int gesture_client_suspend(struct platform_device *pdev, + pm_message_t message) +{ + return 0; +} + +static int gesture_client_resume(struct platform_device *pdev) +{ + return 0; +} +#else +#define gesture_client_suspend NULL +#define gesture_client_resume NULL +#endif + +static struct of_device_id m4gesture_match_tbl[] = { + { .compatible = "mot,m4gesture" }, + {}, +}; + +static struct platform_driver gesture_client_driver = { + .probe = gesture_client_probe, + .remove = __exit_p(gesture_client_remove), + .shutdown = gesture_client_shutdown, + .suspend = gesture_client_suspend, + .resume = gesture_client_resume, + .driver = { + .name = GESTURE_CLIENT_DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(m4gesture_match_tbl), + }, +}; + +static int __init gesture_client_init(void) +{ + return platform_driver_register(&gesture_client_driver); +} + +static void __exit gesture_client_exit(void) +{ + platform_driver_unregister(&gesture_client_driver); +} + +module_init(gesture_client_init); +module_exit(gesture_client_exit); + +MODULE_ALIAS("platform:gesture_client"); +MODULE_DESCRIPTION("M4 Sensor Hub Gesture client driver"); +MODULE_AUTHOR("Motorola"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/misc/m4sensorhub_mpu9150.c b/drivers/misc/m4sensorhub_mpu9150.c new file mode 100644 index 00000000000..55195f4d955 --- /dev/null +++ b/drivers/misc/m4sensorhub_mpu9150.c @@ -0,0 +1,1243 @@ +/* + * Copyright (C) 2012 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/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/m4sensorhub.h> +#include <linux/m4sensorhub_client_ioctl.h> +#include <linux/m4sensorhub/MemMapGyroSensor.h> +#include <linux/m4sensorhub/MemMapAccelSensor.h> +#include <linux/m4sensorhub/MemMapCompassSensor.h> +#include <linux/m4sensorhub/MemMapFusionSensor.h> +#include <linux/uaccess.h> +#include <linux/slab.h> + +#ifdef CONFIG_DEBUG_FS +#define MPU9150_DEBUG 1 +#else +#define MPU9150_DEBUG 0 +#endif + +#define MPU9150_CLIENT_DRIVER_NAME "m4sensorhub_mpu9150" +#define SENSOR_IRQ_ENABLE 1 +#define SENSOR_IRQ_DISABLE 0 + +struct mpu9150_accel_data { + int x; + int y; + int z; +}; +struct mpu9150_gyro_data { + int rx; + int ry; + int rz; +}; +struct mpu9150_compass_data { + int cx; + int cy; + int cz; + int ca; +}; +struct mpu9150_accel_local_data { + int lx; + int ly; + int lz; +}; +struct mpu9150_accel_world_data { + int wx; + int wy; + int wz; +}; +struct mpu9150_euler_data { + int roll; + int pitch; + int yaw; +}; +struct mpu9150_heading_data { + int heading; + int accuracy; +}; +enum mpu9150_sensor { + TYPE_GYRO, + TYPE_COMPASS, + TYPE_ACCEL, + TYPE_FUSION, + + NUM_TYPES, /* Leave as last element */ +} sensor; + +struct mpu9150_client { + struct m4sensorhub_data *m4sensorhub; + struct input_dev *input_dev; + signed short samplerate[NUM_TYPES]; + struct mpu9150_accel_data accel_data; + struct mpu9150_gyro_data gyro_data; + struct mpu9150_compass_data compass_data; + struct mpu9150_accel_local_data accel_local_data; + struct mpu9150_accel_world_data accel_world_data; + struct mpu9150_euler_data euler_data; + struct mpu9150_heading_data heading_data; +}; + +struct mpu9150_client *misc_mpu9150_data; +static int mpu9150_irq_enable_disable(struct mpu9150_client *mpu9150_client_data, + enum mpu9150_sensor type, int flag); + +static int mpu9150_client_open(struct inode *inode, struct file *file) +{ + int err = 0; + + err = nonseekable_open(inode, file); + if (err < 0) { + KDEBUG(M4SH_ERROR, "%s failed\n", __func__); + return err; + } + file->private_data = misc_mpu9150_data; + + return 0; +} + +static int mpu9150_client_close(struct inode *inode, struct file *file) +{ + KDEBUG(M4SH_DEBUG, "mpu9150_client in %s\n", __func__); + return 0; +} + +static void m4_report_mpu9150_inputevent( + struct mpu9150_client *mpu9150_client_data, + enum mpu9150_sensor type) +{ + switch (type) { + case TYPE_GYRO: + input_report_rel(mpu9150_client_data->input_dev, REL_RX, + mpu9150_client_data->gyro_data.rx); + input_report_rel(mpu9150_client_data->input_dev, REL_RY, + mpu9150_client_data->gyro_data.ry); + input_report_rel(mpu9150_client_data->input_dev, REL_RZ, + mpu9150_client_data->gyro_data.rz); + input_sync(mpu9150_client_data->input_dev); + break; + case TYPE_ACCEL: + input_report_abs(mpu9150_client_data->input_dev, ABS_X, + mpu9150_client_data->accel_data.x); + input_report_abs(mpu9150_client_data->input_dev, ABS_Y, + mpu9150_client_data->accel_data.y); + input_report_abs(mpu9150_client_data->input_dev, ABS_Z, + mpu9150_client_data->accel_data.z); + input_sync(mpu9150_client_data->input_dev); + break; + case TYPE_COMPASS: + input_report_abs(mpu9150_client_data->input_dev, ABS_COMPASS_X, + mpu9150_client_data->compass_data.cx); + input_report_abs(mpu9150_client_data->input_dev, ABS_COMPASS_Y, + mpu9150_client_data->compass_data.cy); + input_report_abs(mpu9150_client_data->input_dev, ABS_COMPASS_Z, + mpu9150_client_data->compass_data.cz); + input_report_abs(mpu9150_client_data->input_dev, + ABS_COMPASS_ACCURACY, + mpu9150_client_data->compass_data.ca); + input_sync(mpu9150_client_data->input_dev); + break; + case TYPE_FUSION: + input_report_rel(mpu9150_client_data->input_dev, REL_X, + mpu9150_client_data->accel_data.x); + input_report_rel(mpu9150_client_data->input_dev, REL_Y, + mpu9150_client_data->accel_data.y); + input_report_rel(mpu9150_client_data->input_dev, REL_Z, + mpu9150_client_data->accel_data.z); + input_report_rel(mpu9150_client_data->input_dev, REL_GX, + mpu9150_client_data->gyro_data.rx); + input_report_rel(mpu9150_client_data->input_dev, REL_GY, + mpu9150_client_data->gyro_data.ry); + input_report_rel(mpu9150_client_data->input_dev, REL_GZ, + mpu9150_client_data->gyro_data.rz); + input_report_rel(mpu9150_client_data->input_dev, REL_LX, + mpu9150_client_data->accel_local_data.lx); + input_report_rel(mpu9150_client_data->input_dev, REL_LY, + mpu9150_client_data->accel_local_data.ly); + input_report_rel(mpu9150_client_data->input_dev, REL_LZ, + mpu9150_client_data->accel_local_data.lz); + input_report_rel(mpu9150_client_data->input_dev, REL_WX, + mpu9150_client_data->accel_world_data.wx); + input_report_rel(mpu9150_client_data->input_dev, REL_WY, + mpu9150_client_data->accel_world_data.wy); + input_report_rel(mpu9150_client_data->input_dev, REL_WZ, + mpu9150_client_data->accel_world_data.wz); + input_report_rel(mpu9150_client_data->input_dev, REL_ROLL, + mpu9150_client_data->euler_data.roll); + input_report_rel(mpu9150_client_data->input_dev, REL_PITCH, + mpu9150_client_data->euler_data.pitch); + input_report_rel(mpu9150_client_data->input_dev, REL_YAW, + mpu9150_client_data->euler_data.yaw); + input_report_rel(mpu9150_client_data->input_dev, REL_HEADING, + mpu9150_client_data->heading_data.heading); + input_report_rel(mpu9150_client_data->input_dev, + REL_HEADING_ACCURACY, + mpu9150_client_data->heading_data.accuracy); + input_sync(mpu9150_client_data->input_dev); + break; + default: + break; + } +} + +/* TO DO +***** implement the delay functionality when M4 changes will be ready +*/ +static void m4_set_mpu9150_delay(struct mpu9150_client *mpu9150_client_data, + int delay, enum mpu9150_sensor type) +{ + if (delay != mpu9150_client_data->samplerate[type]) { + switch (type) { + case TYPE_GYRO: + m4sensorhub_reg_write(mpu9150_client_data->m4sensorhub, + M4SH_REG_GYRO_SAMPLERATE, (char *)&delay, m4sh_no_mask); + break; + case TYPE_ACCEL: + m4sensorhub_reg_write(mpu9150_client_data->m4sensorhub, + M4SH_REG_ACCEL_SAMPLERATE, (char *)&delay, m4sh_no_mask); + break; + case TYPE_COMPASS: + m4sensorhub_reg_write(mpu9150_client_data->m4sensorhub, + M4SH_REG_COMPASS_SAMPLERATE, (char *)&delay, m4sh_no_mask); + break; + case TYPE_FUSION: + m4sensorhub_reg_write(mpu9150_client_data->m4sensorhub, + M4SH_REG_FUSION_SAMPLERATE, (char *)&delay, m4sh_no_mask); + break; + default: + return; + break; + } + KDEBUG(M4SH_DEBUG, "%s() updating samplerate for type %d from" + " %d to %d\n", __func__, type, + mpu9150_client_data->samplerate[type], + delay); + + mpu9150_client_data->samplerate[type] = delay; + } +} + +static void m4_read_mpu9150_data(struct mpu9150_client *mpu9150_client_data, + enum mpu9150_sensor type) +{ + sFusionData fusiondata; + sCompassData compassdata; + sAccelData acceldata; + sGyroData gyrodata; + + switch (type) { + case TYPE_GYRO: + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_GYRO_X, (char *)&gyrodata.x); + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_GYRO_Y, (char *)&gyrodata.y); + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_GYRO_Z, (char *)&gyrodata.z); + mpu9150_client_data->gyro_data.rx = gyrodata.x; + mpu9150_client_data->gyro_data.ry = gyrodata.y; + mpu9150_client_data->gyro_data.rz = gyrodata.z; + break; + case TYPE_ACCEL: + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_ACCEL_X, (char *)&acceldata.x); + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_ACCEL_Y, (char *)&acceldata.y); + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_ACCEL_Z, (char *)&acceldata.z); + mpu9150_client_data->accel_data.x = acceldata.x; + mpu9150_client_data->accel_data.y = acceldata.y; + mpu9150_client_data->accel_data.z = acceldata.z; + break; + case TYPE_COMPASS: + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_COMPASS_X, (char *)&compassdata.x); + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_COMPASS_Y, (char *)&compassdata.y); + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_COMPASS_Z, (char *)&compassdata.z); + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_COMPASS_ACCURACY, + (char *)&compassdata.accuracy); + + mpu9150_client_data->compass_data.cx = compassdata.x; + mpu9150_client_data->compass_data.cy = compassdata.y; + mpu9150_client_data->compass_data.cz = compassdata.z; + mpu9150_client_data->compass_data.ca = compassdata.accuracy; + + break; + case TYPE_FUSION: + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_ACCEL_X, (char *)&acceldata.x); + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_ACCEL_Y, (char *)&acceldata.y); + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_ACCEL_Z, (char *)&acceldata.z); + mpu9150_client_data->accel_data.x = acceldata.x; + mpu9150_client_data->accel_data.y = acceldata.y; + mpu9150_client_data->accel_data.z = acceldata.z; + + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_GYRO_X, (char *)&gyrodata.x); + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_GYRO_Y, (char *)&gyrodata.y); + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_GYRO_Z, (char *)&gyrodata.z); + mpu9150_client_data->gyro_data.rx = gyrodata.x; + mpu9150_client_data->gyro_data.ry = gyrodata.y; + mpu9150_client_data->gyro_data.rz = gyrodata.z; + + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_FUSION_LOCALX, (char *)&fusiondata.localX); + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_FUSION_LOCALY, (char *)&fusiondata.localY); + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_FUSION_LOCALZ, (char *)&fusiondata.localZ); + mpu9150_client_data->accel_local_data.lx = fusiondata.localX; + mpu9150_client_data->accel_local_data.ly = fusiondata.localY; + mpu9150_client_data->accel_local_data.lz = fusiondata.localZ; + + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_FUSION_WORLDX, (char *)&fusiondata.worldX); + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_FUSION_WORLDY, (char *)&fusiondata.worldY); + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_FUSION_WORLDZ, (char *)&fusiondata.worldZ); + mpu9150_client_data->accel_world_data.wx = fusiondata.worldX; + mpu9150_client_data->accel_world_data.wy = fusiondata.worldY; + mpu9150_client_data->accel_world_data.wz = fusiondata.worldZ; + + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_FUSION_EULERPITCH, + (char *)&fusiondata.eulerPitch); + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_FUSION_EULERROLL, + (char *)&fusiondata.eulerRoll); + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_FUSION_EULERYAW, + (char *)&fusiondata.eulerYaw); + mpu9150_client_data->euler_data.pitch = fusiondata.eulerPitch; + mpu9150_client_data->euler_data.roll = fusiondata.eulerRoll; + mpu9150_client_data->euler_data.yaw = fusiondata.eulerYaw; + + + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_FUSION_HEADING, + (char *)&fusiondata.heading); + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_FUSION_HEADING_ACCURACY, + (char *)&fusiondata.heading_accuracy); + mpu9150_client_data->heading_data.heading = fusiondata.heading; + mpu9150_client_data->heading_data.accuracy = + fusiondata.heading_accuracy; + + break; + default: + break; + } + +} +static void m4_handle_mpu9150_gyro_irq(enum m4sensorhub_irqs int_event, + void *mpu9150_data) +{ + struct mpu9150_client *mpu9150_client_data = mpu9150_data; + + m4_read_mpu9150_data(mpu9150_client_data, TYPE_GYRO); + m4_report_mpu9150_inputevent(mpu9150_client_data, TYPE_GYRO); +} + +static void m4_handle_mpu9150_accel_irq(enum m4sensorhub_irqs int_event, + void *mpu9150_data) +{ + struct mpu9150_client *mpu9150_client_data = mpu9150_data; + + m4_read_mpu9150_data(mpu9150_client_data, TYPE_ACCEL); + m4_report_mpu9150_inputevent(mpu9150_client_data, TYPE_ACCEL); +} + +static void m4_handle_mpu9150_compass_irq(enum m4sensorhub_irqs int_event, + void *mpu9150_data) +{ + struct mpu9150_client *mpu9150_client_data = mpu9150_data; + + m4_read_mpu9150_data(mpu9150_client_data, TYPE_COMPASS); + m4_report_mpu9150_inputevent(mpu9150_client_data, TYPE_COMPASS); +} + +static void m4_handle_mpu9150_fusion_irq(enum m4sensorhub_irqs int_event, + void *mpu9150_data) +{ + struct mpu9150_client *mpu9150_client_data = mpu9150_data; + + m4_read_mpu9150_data(mpu9150_client_data, TYPE_FUSION); + m4_report_mpu9150_inputevent(mpu9150_client_data, TYPE_FUSION); +} + +/* + * Handle commands from user-space. + */ +static long mpu9150_client_ioctl(struct file *filp, + unsigned int cmd, unsigned long arg) +{ + int ret = 0; + int delay; + static int status; + void __user *argp = (void __user *)arg; + struct mpu9150_client *mpu9150_client_data = filp->private_data; + + switch (cmd) { + case M4_SENSOR_IOCTL_APP_GET_FLAG: + if (copy_to_user(argp, &status, sizeof(status))) + return -EFAULT; + break; + case M4_SENSOR_IOCTL_APP_SET_FLAG: + if (copy_from_user(&status, argp, sizeof(status))) + return -EFAULT; + break; + case M4_SENSOR_IOCTL_GYRO_SET_DELAY: + if (copy_from_user(&delay, argp, sizeof(delay))) + return -EFAULT; + m4_set_mpu9150_delay(mpu9150_client_data, delay, TYPE_GYRO); + mpu9150_irq_enable_disable(mpu9150_client_data, TYPE_GYRO, + SENSOR_IRQ_ENABLE); + break; + case M4_SENSOR_IOCTL_COMPASS_SET_DELAY: + if (copy_from_user(&delay, argp, sizeof(delay))) + return -EFAULT; + m4_set_mpu9150_delay(mpu9150_client_data, delay, TYPE_COMPASS); + mpu9150_irq_enable_disable(mpu9150_client_data, TYPE_COMPASS, + SENSOR_IRQ_ENABLE); + break; + case M4_SENSOR_IOCTL_ACCEL_SET_DELAY: + if (copy_from_user(&delay, argp, sizeof(delay))) + return -EFAULT; + m4_set_mpu9150_delay(mpu9150_client_data, delay, TYPE_ACCEL); + mpu9150_irq_enable_disable(mpu9150_client_data, TYPE_ACCEL, + SENSOR_IRQ_ENABLE); + break; + case M4_SENSOR_IOCTL_FUSION_SET_DELAY: + if (copy_from_user(&delay, argp, sizeof(delay))) + return -EFAULT; + if (delay >= 0) + m4_set_mpu9150_delay(mpu9150_client_data, delay, TYPE_FUSION); + if (delay) + mpu9150_irq_enable_disable(mpu9150_client_data, TYPE_FUSION, + SENSOR_IRQ_ENABLE); + else + mpu9150_irq_enable_disable(mpu9150_client_data, TYPE_FUSION, + SENSOR_IRQ_DISABLE); + break; + default: + KDEBUG(M4SH_ERROR, "Invalid IOCTL Command in %s\n", __func__); + ret = -EINVAL; + } + return ret; +} + +#ifdef MPU9150_DEBUG +static ssize_t m4_mpu9150_local_x(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mpu9150_client *mpu9150_client_data = platform_get_drvdata(pdev); + + KDEBUG(M4SH_DEBUG, "%s : x_local = %d\n", + __func__, mpu9150_client_data->accel_local_data.lx); + return sprintf(buf, "%d \n", mpu9150_client_data->accel_local_data.lx); +} + +static ssize_t m4_mpu9150_local_y(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mpu9150_client *mpu9150_client_data = platform_get_drvdata(pdev); + + KDEBUG(M4SH_DEBUG, "%s : y_local = %d\n", + __func__, mpu9150_client_data->accel_local_data.ly); + return sprintf(buf, "%d \n", mpu9150_client_data->accel_local_data.ly); +} + +static ssize_t m4_mpu9150_local_z(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mpu9150_client *mpu9150_client_data = platform_get_drvdata(pdev); + + KDEBUG(M4SH_DEBUG, "%s : z_local = %d\n", + __func__, mpu9150_client_data->accel_local_data.lz); + return sprintf(buf, "%d \n", mpu9150_client_data->accel_local_data.lz); +} +static ssize_t m4_mpu9150_world_x(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mpu9150_client *mpu9150_client_data = platform_get_drvdata(pdev); + + KDEBUG(M4SH_DEBUG, "%s : x_world = %d\n", + __func__, mpu9150_client_data->accel_world_data.wx); + return sprintf(buf, "%d \n", mpu9150_client_data->accel_world_data.wx); +} + +static ssize_t m4_mpu9150_world_y(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mpu9150_client *mpu9150_client_data = platform_get_drvdata(pdev); + + KDEBUG(M4SH_DEBUG, "%s : y_world = %d\n", + __func__, mpu9150_client_data->accel_world_data.wy); + return sprintf(buf, "%d \n", mpu9150_client_data->accel_world_data.wy); +} + +static ssize_t m4_mpu9150_world_z(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mpu9150_client *mpu9150_client_data = platform_get_drvdata(pdev); + + KDEBUG(M4SH_DEBUG, "%s : z_world = %d\n", + __func__, mpu9150_client_data->accel_world_data.wz); + return sprintf(buf, "%d \n", mpu9150_client_data->accel_world_data.wz); +} +static ssize_t m4_mpu9150_pitch(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mpu9150_client *mpu9150_client_data = platform_get_drvdata(pdev); + + KDEBUG(M4SH_DEBUG, "%s : pitch = %d\n", + __func__, mpu9150_client_data->euler_data.pitch); + return sprintf(buf, "%d \n", mpu9150_client_data->euler_data.pitch); +} + +static ssize_t m4_mpu9150_roll(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mpu9150_client *mpu9150_client_data = platform_get_drvdata(pdev); + + KDEBUG(M4SH_DEBUG, "%s : roll = %d\n", + __func__, mpu9150_client_data->euler_data.roll); + return sprintf(buf, "%d \n", mpu9150_client_data->euler_data.roll); +} + +static ssize_t m4_mpu9150_yaw(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mpu9150_client *mpu9150_client_data = platform_get_drvdata(pdev); + + KDEBUG(M4SH_DEBUG, "%s : yaw = %d\n", + __func__, mpu9150_client_data->euler_data.yaw); + return sprintf(buf, "%d \n", mpu9150_client_data->euler_data.yaw); +} + +static ssize_t m4_mpu9150_heading(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mpu9150_client *mpu9150_client_data = platform_get_drvdata(pdev); + + KDEBUG(M4SH_DEBUG, "%s : heading = %d\n", + __func__, mpu9150_client_data->heading_data.heading); + return sprintf(buf, "%d \n", mpu9150_client_data->heading_data.heading); +} + +static ssize_t m4_mpu9150_heading_acc(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mpu9150_client *mpu9150_client_data = platform_get_drvdata(pdev); + + KDEBUG(M4SH_DEBUG, "%s : heading_acc = %d\n", + __func__, mpu9150_client_data->heading_data.accuracy); + return sprintf(buf, "%d\n", mpu9150_client_data->heading_data.accuracy); +} + +static ssize_t m4_mpu9150_x(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mpu9150_client *mpu9150_client_data = platform_get_drvdata(pdev); + + KDEBUG(M4SH_DEBUG, "%s : raw x = %d\n", + __func__, mpu9150_client_data->accel_data.x); + return sprintf(buf, "%d \n", mpu9150_client_data->accel_data.x); +} + +static ssize_t m4_mpu9150_y(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mpu9150_client *mpu9150_client_data = platform_get_drvdata(pdev); + + KDEBUG(M4SH_DEBUG, "%s : raw y = %d\n", + __func__, mpu9150_client_data->accel_data.y); + return sprintf(buf, "%d \n", mpu9150_client_data->accel_data.y); +} + +static ssize_t m4_mpu9150_z(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mpu9150_client *mpu9150_client_data = platform_get_drvdata(pdev); + + KDEBUG(M4SH_DEBUG, "%s : raw z = %d\n", + __func__, mpu9150_client_data->accel_data.z); + return sprintf(buf, "%d \n", mpu9150_client_data->accel_data.z); +} + +static ssize_t m4_mpu9150_cx(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mpu9150_client *mpu9150_client_data = platform_get_drvdata(pdev); + + KDEBUG(M4SH_DEBUG, "%s : compass cx = %d\n", + __func__, mpu9150_client_data->compass_data.cx); + return sprintf(buf, "%d \n", mpu9150_client_data->compass_data.cx); +} + +static ssize_t m4_mpu9150_cy(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mpu9150_client *mpu9150_client_data = platform_get_drvdata(pdev); + + KDEBUG(M4SH_DEBUG, "%s : compass cy = %d\n", + __func__, mpu9150_client_data->compass_data.cy); + return sprintf(buf, "%d \n", mpu9150_client_data->compass_data.cy); +} + +static ssize_t m4_mpu9150_cz(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mpu9150_client *mpu9150_client_data = platform_get_drvdata(pdev); + + KDEBUG(M4SH_DEBUG, "%s : compass cz = %d\n", + __func__, mpu9150_client_data->compass_data.cz); + return sprintf(buf, "%d \n", mpu9150_client_data->compass_data.cz); +} + +static ssize_t m4_mpu9150_ca(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mpu9150_client *mpu9150_client_data = platform_get_drvdata(pdev); + + KDEBUG(M4SH_DEBUG, "%s : compass ca = %d\n", + __func__, mpu9150_client_data->compass_data.ca); + return sprintf(buf, "%d \n", mpu9150_client_data->compass_data.ca); +} + +static ssize_t m4_mpu9150_rx(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mpu9150_client *mpu9150_client_data = platform_get_drvdata(pdev); + + KDEBUG(M4SH_DEBUG, "%s : rx = %d\n", + __func__, mpu9150_client_data->gyro_data.rx); + return sprintf(buf, "%d \n", mpu9150_client_data->gyro_data.rx); +} +static ssize_t m4_mpu9150_ry(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mpu9150_client *mpu9150_client_data = platform_get_drvdata(pdev); + + KDEBUG(M4SH_DEBUG, "%s : ry = %d\n", + __func__, mpu9150_client_data->gyro_data.ry); + return sprintf(buf, "%d \n", mpu9150_client_data->gyro_data.ry); +} + +static ssize_t m4_mpu9150_rz(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mpu9150_client *mpu9150_client_data = platform_get_drvdata(pdev); + + KDEBUG(M4SH_DEBUG, "%s : rz = %d\n", + __func__, mpu9150_client_data->gyro_data.rz); + return sprintf(buf, "%d \n", mpu9150_client_data->gyro_data.rz); +} + +static int mpu9150_set_loglevel(struct mpu9150_client *mpu9150_client_data, + unsigned long long level, enum mpu9150_sensor type) +{ + unsigned long long mask; + if (level > M4_MAX_LOG_LEVEL) { + KDEBUG(M4SH_ERROR, " Invalid log level - %llu !!!\n", level); + return -1; + } + switch (type) { + case TYPE_GYRO: + mask = (1ULL << GYRO_MASK_BIT_1) | (1ULL << GYRO_MASK_BIT_2); + level = (level << GYRO_MASK_BIT_1); + break; + case TYPE_ACCEL: + mask = (1ULL << ACCEL_MASK_BIT_1) | (1ULL << ACCEL_MASK_BIT_2); + level = (level << ACCEL_MASK_BIT_1); + break; + case TYPE_COMPASS: + mask = (1ULL << COMPASS_MASK_BIT_1) | (1ULL << COMPASS_MASK_BIT_2); + level = (level << COMPASS_MASK_BIT_1); + break; + case TYPE_FUSION: + mask = (1ULL << FUSION_MASK_BIT_1) | (1ULL << FUSION_MASK_BIT_2); + level = (level << FUSION_MASK_BIT_1); + break; + default: + return -1; + } + return m4sensorhub_reg_write(mpu9150_client_data->m4sensorhub, + M4SH_REG_LOG_LOGENABLE, (char *)&level, (unsigned char *)&mask); +} + +static ssize_t gyro_set_loglevel(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long level; + struct platform_device *pdev = to_platform_device(dev); + struct mpu9150_client *mpu9150_client_data = platform_get_drvdata(pdev); + + if ((strict_strtoul(buf, 10, &level)) < 0) + return -1; + return mpu9150_set_loglevel(mpu9150_client_data, level, TYPE_GYRO); +} + +static ssize_t gyro_get_loglevel(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long long loglevel; + struct platform_device *pdev = to_platform_device(dev); + struct mpu9150_client *mpu9150_client_data = platform_get_drvdata(pdev); + + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_LOG_LOGENABLE, (char *)&loglevel); + loglevel = get_log_level(loglevel, GYRO_MASK_BIT_1); + return sprintf(buf, "%llu\n", loglevel); +} + +static ssize_t accel_set_loglevel(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long level; + struct platform_device *pdev = to_platform_device(dev); + struct mpu9150_client *mpu9150_client_data = platform_get_drvdata(pdev); + + if ((strict_strtoul(buf, 10, &level)) < 0) + return -1; + return mpu9150_set_loglevel(mpu9150_client_data, level, TYPE_ACCEL); +} + +static ssize_t accel_get_loglevel(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int loglevel; + struct platform_device *pdev = to_platform_device(dev); + struct mpu9150_client *mpu9150_client_data = platform_get_drvdata(pdev); + + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_LOG_LOGENABLE, (char *)&loglevel); + loglevel = get_log_level(loglevel, ACCEL_MASK_BIT_1); + return sprintf(buf, "%d\n", loglevel); +} + +static ssize_t compass_set_loglevel(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long level; + struct platform_device *pdev = to_platform_device(dev); + struct mpu9150_client *mpu9150_client_data = platform_get_drvdata(pdev); + + if ((strict_strtoul(buf, 10, &level)) < 0) + return -1; + return mpu9150_set_loglevel(mpu9150_client_data, level, TYPE_COMPASS); +} + +static ssize_t compass_get_loglevel(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int loglevel; + struct platform_device *pdev = to_platform_device(dev); + struct mpu9150_client *mpu9150_client_data = platform_get_drvdata(pdev); + + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_LOG_LOGENABLE, (char *)&loglevel); + loglevel = get_log_level(loglevel, COMPASS_MASK_BIT_1); + return sprintf(buf, "%d\n", loglevel); +} + +static ssize_t fusion_set_loglevel(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long level; + struct platform_device *pdev = to_platform_device(dev); + struct mpu9150_client *mpu9150_client_data = platform_get_drvdata(pdev); + + if ((strict_strtoul(buf, 10, &level)) < 0) + return -1; + return mpu9150_set_loglevel(mpu9150_client_data, level, TYPE_FUSION); +} +static ssize_t fusion_get_loglevel(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int loglevel; + struct platform_device *pdev = to_platform_device(dev); + struct mpu9150_client *mpu9150_client_data = platform_get_drvdata(pdev); + + m4sensorhub_reg_read(mpu9150_client_data->m4sensorhub, + M4SH_REG_LOG_LOGENABLE, (char *)&loglevel); + loglevel = get_log_level(loglevel, FUSION_MASK_BIT_1); + return sprintf(buf, "%d\n", loglevel); +} + +static DEVICE_ATTR(x_local, 0444, m4_mpu9150_local_x, NULL); +static DEVICE_ATTR(y_local, 0444, m4_mpu9150_local_y, NULL); +static DEVICE_ATTR(z_local, 0444, m4_mpu9150_local_z, NULL); +static DEVICE_ATTR(x_world, 0444, m4_mpu9150_world_x, NULL); +static DEVICE_ATTR(y_world, 0444, m4_mpu9150_world_y, NULL); +static DEVICE_ATTR(z_world, 0444, m4_mpu9150_world_z, NULL); +static DEVICE_ATTR(pitch, 0444, m4_mpu9150_pitch, NULL); +static DEVICE_ATTR(roll, 0444, m4_mpu9150_roll, NULL); +static DEVICE_ATTR(yaw, 0444, m4_mpu9150_yaw, NULL); +static DEVICE_ATTR(heading, 0444, m4_mpu9150_heading, NULL); +static DEVICE_ATTR(heading_acc, 0444, m4_mpu9150_heading_acc, NULL); +static DEVICE_ATTR(raw_x, 0444, m4_mpu9150_x, NULL); +static DEVICE_ATTR(raw_y, 0444, m4_mpu9150_y, NULL); +static DEVICE_ATTR(raw_z, 0444, m4_mpu9150_z, NULL); +static DEVICE_ATTR(compass_cx, 0444, m4_mpu9150_cx, NULL); +static DEVICE_ATTR(compass_cy, 0444, m4_mpu9150_cy, NULL); +static DEVICE_ATTR(compass_cz, 0444, m4_mpu9150_cz, NULL); +static DEVICE_ATTR(compass_ca, 0444, m4_mpu9150_ca, NULL); +static DEVICE_ATTR(rx, 0444, m4_mpu9150_rx, NULL); +static DEVICE_ATTR(ry, 0444, m4_mpu9150_ry, NULL); +static DEVICE_ATTR(rz, 0444, m4_mpu9150_rz, NULL); +static DEVICE_ATTR(gyroLogLevel, 0644, gyro_get_loglevel, + gyro_set_loglevel); +static DEVICE_ATTR(accelLogLevel, 0644, accel_get_loglevel, + accel_set_loglevel); +static DEVICE_ATTR(compassLogLevel, 0644, compass_get_loglevel, + compass_set_loglevel); +static DEVICE_ATTR(fusionLogLevel, 0644, fusion_get_loglevel, + fusion_set_loglevel); + +static struct attribute *mpu9150_attributes[] = { + &dev_attr_x_local.attr, + &dev_attr_y_local.attr, + &dev_attr_z_local.attr, + &dev_attr_x_world.attr, + &dev_attr_y_world.attr, + &dev_attr_z_world.attr, + &dev_attr_pitch.attr, + &dev_attr_roll.attr, + &dev_attr_yaw.attr, + &dev_attr_heading.attr, + &dev_attr_heading_acc.attr, + &dev_attr_raw_x.attr, + &dev_attr_raw_y.attr, + &dev_attr_raw_z.attr, + &dev_attr_compass_cx.attr, + &dev_attr_compass_cy.attr, + &dev_attr_compass_cz.attr, + &dev_attr_compass_ca.attr, + &dev_attr_rx.attr, + &dev_attr_ry.attr, + &dev_attr_rz.attr, + &dev_attr_gyroLogLevel.attr, + &dev_attr_accelLogLevel.attr, + &dev_attr_compassLogLevel.attr, + &dev_attr_fusionLogLevel.attr, + NULL +}; + +static const struct attribute_group mpu9150_group = { + .attrs = mpu9150_attributes, +}; +#endif + +static const struct file_operations mpu9150_client_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = mpu9150_client_ioctl, + .open = mpu9150_client_open, + .release = mpu9150_client_close, +}; + +static struct miscdevice mpu9150_client_miscdrv = { + .minor = MISC_DYNAMIC_MINOR, + .name = MPU9150_CLIENT_DRIVER_NAME, + .fops = &mpu9150_client_fops, +}; + +static int mpu9150_irq_init(struct mpu9150_client *mpu9150_client_data) +{ + int ret = -1; + + ret = m4sensorhub_irq_register(mpu9150_client_data->m4sensorhub, + M4SH_IRQ_GYRO_DATA_READY, + m4_handle_mpu9150_gyro_irq, + mpu9150_client_data); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error registering int %d (%d)\n", + M4SH_IRQ_GYRO_DATA_READY, ret); + return ret; + } + ret = m4sensorhub_irq_register(mpu9150_client_data->m4sensorhub, + M4SH_IRQ_ACCEL_DATA_READY, + m4_handle_mpu9150_accel_irq, + mpu9150_client_data); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error registering int %d (%d)\n", + M4SH_IRQ_ACCEL_DATA_READY, ret); + goto unregister_gyro_irq; + } + ret = m4sensorhub_irq_register(mpu9150_client_data->m4sensorhub, + M4SH_IRQ_COMPASS_DATA_READY, + m4_handle_mpu9150_compass_irq, + mpu9150_client_data); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error registering int %d (%d)\n", + M4SH_IRQ_COMPASS_DATA_READY, ret); + goto unregister_accel_irq; + } + ret = m4sensorhub_irq_register(mpu9150_client_data->m4sensorhub, + M4SH_IRQ_FUSION_DATA_READY, + m4_handle_mpu9150_fusion_irq, + mpu9150_client_data); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error registering int %d (%d)\n", + M4SH_IRQ_FUSION_DATA_READY, ret); + goto unregister_compass_irq; + } + return ret; + +unregister_compass_irq: + m4sensorhub_irq_unregister(mpu9150_client_data->m4sensorhub, + M4SH_IRQ_COMPASS_DATA_READY); +unregister_accel_irq: + m4sensorhub_irq_unregister(mpu9150_client_data->m4sensorhub, + M4SH_IRQ_ACCEL_DATA_READY); +unregister_gyro_irq: + m4sensorhub_irq_unregister(mpu9150_client_data->m4sensorhub, + M4SH_IRQ_GYRO_DATA_READY); + return ret; +} + +static void mpu9150_irq_deinit(struct mpu9150_client *mpu9150_client_data) +{ + m4sensorhub_irq_unregister(mpu9150_client_data->m4sensorhub, + M4SH_IRQ_FUSION_DATA_READY); + m4sensorhub_irq_unregister(mpu9150_client_data->m4sensorhub, + M4SH_IRQ_COMPASS_DATA_READY); + m4sensorhub_irq_unregister(mpu9150_client_data->m4sensorhub, + M4SH_IRQ_ACCEL_DATA_READY); + m4sensorhub_irq_unregister(mpu9150_client_data->m4sensorhub, + M4SH_IRQ_GYRO_DATA_READY); +} + +static int mpu9150_irq_enable_disable(struct mpu9150_client *mpu9150_client_data, + enum mpu9150_sensor type, int flag) +{ + int ret = 0; + int irq_status = 0; + + switch (type) { + case TYPE_GYRO: + irq_status = m4sensorhub_irq_enable_get( + mpu9150_client_data->m4sensorhub, + M4SH_IRQ_GYRO_DATA_READY); + if (flag && (!irq_status)) { + ret = m4sensorhub_irq_enable( + mpu9150_client_data->m4sensorhub, + M4SH_IRQ_GYRO_DATA_READY); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error enabling int %d (%d)\n", + M4SH_IRQ_GYRO_DATA_READY, ret); + return ret; + } + } else if ((!flag) && irq_status) + m4sensorhub_irq_disable( + mpu9150_client_data->m4sensorhub, + M4SH_IRQ_GYRO_DATA_READY); + break; + case TYPE_ACCEL: + irq_status = m4sensorhub_irq_enable_get( + mpu9150_client_data->m4sensorhub, + M4SH_IRQ_ACCEL_DATA_READY); + if (flag && (!irq_status)) { + ret = m4sensorhub_irq_enable( + mpu9150_client_data->m4sensorhub, + M4SH_IRQ_ACCEL_DATA_READY); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error enabling int %d (%d)\n", + M4SH_IRQ_ACCEL_DATA_READY, ret); + return ret; + } + } else if ((!flag) && irq_status) + m4sensorhub_irq_disable( + mpu9150_client_data->m4sensorhub, + M4SH_IRQ_ACCEL_DATA_READY); + break; + case TYPE_COMPASS: + irq_status = m4sensorhub_irq_enable_get( + mpu9150_client_data->m4sensorhub, + M4SH_IRQ_COMPASS_DATA_READY); + if (flag && (!irq_status)) { + ret = m4sensorhub_irq_enable( + mpu9150_client_data->m4sensorhub, + M4SH_IRQ_COMPASS_DATA_READY); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error enabling int %d (%d)\n", + M4SH_IRQ_COMPASS_DATA_READY, ret); + return ret; + } + } else if ((!flag) && irq_status) + m4sensorhub_irq_disable( + mpu9150_client_data->m4sensorhub, + M4SH_IRQ_COMPASS_DATA_READY); + break; + case TYPE_FUSION: + irq_status = m4sensorhub_irq_enable_get( + mpu9150_client_data->m4sensorhub, + M4SH_IRQ_FUSION_DATA_READY); + if (flag && (!irq_status)) { + ret = m4sensorhub_irq_enable( + mpu9150_client_data->m4sensorhub, + M4SH_IRQ_FUSION_DATA_READY); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error enabling int %d (%d)\n", + M4SH_IRQ_FUSION_DATA_READY, ret); + return ret; + } + } else if ((!flag) && irq_status) + m4sensorhub_irq_disable( + mpu9150_client_data->m4sensorhub, + M4SH_IRQ_FUSION_DATA_READY); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int mpu9150_client_probe(struct platform_device *pdev) +{ + int ret = -1; + struct mpu9150_client *mpu9150_client_data; + struct m4sensorhub_data *m4sensorhub = m4sensorhub_client_get_drvdata(); + + if (!m4sensorhub) + return -EFAULT; + + mpu9150_client_data = kzalloc(sizeof(*mpu9150_client_data), + GFP_KERNEL); + if (!mpu9150_client_data) + return -ENOMEM; + + mpu9150_client_data->m4sensorhub = m4sensorhub; + platform_set_drvdata(pdev, mpu9150_client_data); + + mpu9150_client_data->input_dev = input_allocate_device(); + if (!mpu9150_client_data->input_dev) { + ret = -ENOMEM; + KDEBUG(M4SH_ERROR, "%s: input device allocate failed: %d\n", + __func__, ret); + goto free_mem; + } + + mpu9150_client_data->input_dev->name = MPU9150_CLIENT_DRIVER_NAME; + set_bit(EV_ABS, mpu9150_client_data->input_dev->evbit); + set_bit(EV_REL, mpu9150_client_data->input_dev->evbit); + + set_bit(ABS_COMPASS_X, mpu9150_client_data->input_dev->absbit); + set_bit(ABS_COMPASS_Y, mpu9150_client_data->input_dev->absbit); + set_bit(ABS_COMPASS_Z, mpu9150_client_data->input_dev->absbit); + set_bit(ABS_COMPASS_ACCURACY, mpu9150_client_data->input_dev->absbit); + set_bit(ABS_X, mpu9150_client_data->input_dev->absbit); + set_bit(ABS_Y, mpu9150_client_data->input_dev->absbit); + set_bit(ABS_Z, mpu9150_client_data->input_dev->absbit); + + set_bit(REL_X, mpu9150_client_data->input_dev->relbit); + set_bit(REL_Y, mpu9150_client_data->input_dev->relbit); + set_bit(REL_Z, mpu9150_client_data->input_dev->relbit); + set_bit(REL_GX, mpu9150_client_data->input_dev->relbit); + set_bit(REL_GY, mpu9150_client_data->input_dev->relbit); + set_bit(REL_GZ, mpu9150_client_data->input_dev->relbit); + set_bit(REL_LX, mpu9150_client_data->input_dev->relbit); + set_bit(REL_LY, mpu9150_client_data->input_dev->relbit); + set_bit(REL_LZ, mpu9150_client_data->input_dev->relbit); + set_bit(REL_WX, mpu9150_client_data->input_dev->relbit); + set_bit(REL_WY, mpu9150_client_data->input_dev->relbit); + set_bit(REL_WZ, mpu9150_client_data->input_dev->relbit); + set_bit(REL_ROLL, mpu9150_client_data->input_dev->relbit); + set_bit(REL_PITCH, mpu9150_client_data->input_dev->relbit); + set_bit(REL_YAW, mpu9150_client_data->input_dev->relbit); + set_bit(REL_RX, mpu9150_client_data->input_dev->relbit); + set_bit(REL_RY, mpu9150_client_data->input_dev->relbit); + set_bit(REL_RZ, mpu9150_client_data->input_dev->relbit); + set_bit(REL_HEADING, mpu9150_client_data->input_dev->relbit); + set_bit(REL_HEADING_ACCURACY, mpu9150_client_data->input_dev->relbit); + + if (input_register_device(mpu9150_client_data->input_dev)) { + KDEBUG(M4SH_ERROR, "%s: input device register failed\n", + __func__); + input_free_device(mpu9150_client_data->input_dev); + goto free_mem; + } + + ret = misc_register(&mpu9150_client_miscdrv); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error registering %s driver\n", __func__); + goto unregister_input_device; + } + misc_mpu9150_data = mpu9150_client_data; + ret = mpu9150_irq_init(mpu9150_client_data); + if (ret < 0) + goto unregister_misc_device; + +#ifdef MPU9150_DEBUG + ret = sysfs_create_group(&pdev->dev.kobj, &mpu9150_group); + if (ret) + goto unregister_irq; +#endif + KDEBUG(M4SH_INFO, "Initialized %s driver\n", __func__); + return 0; + +#ifdef MPU9150_DEBUG +unregister_irq: + mpu9150_irq_deinit(mpu9150_client_data); +#endif +unregister_misc_device: + misc_mpu9150_data = NULL; + misc_deregister(&mpu9150_client_miscdrv); +unregister_input_device: + input_unregister_device(mpu9150_client_data->input_dev); +free_mem: + platform_set_drvdata(pdev, NULL); + mpu9150_client_data->m4sensorhub = NULL; + kfree(mpu9150_client_data); + mpu9150_client_data = NULL; + return ret; +} + +static int __exit mpu9150_client_remove(struct platform_device *pdev) +{ + struct mpu9150_client *mpu9150_client_data = + platform_get_drvdata(pdev); +#ifdef MPU9150_DEBUG + sysfs_remove_group(&pdev->dev.kobj, &mpu9150_group); +#endif + mpu9150_irq_deinit(mpu9150_client_data); + misc_mpu9150_data = NULL; + misc_deregister(&mpu9150_client_miscdrv); + input_unregister_device(mpu9150_client_data->input_dev); + platform_set_drvdata(pdev, NULL); + mpu9150_client_data->m4sensorhub = NULL; + kfree(mpu9150_client_data); + mpu9150_client_data = NULL; + return 0; +} + +static void mpu9150_client_shutdown(struct platform_device *pdev) +{ + return; +} +#ifdef CONFIG_PM +static int mpu9150_client_suspend(struct platform_device *pdev, + pm_message_t message) +{ + struct mpu9150_client *mpu9150_client_data = + platform_get_drvdata(pdev); + + mpu9150_irq_enable_disable(mpu9150_client_data, TYPE_COMPASS, + SENSOR_IRQ_DISABLE); + mpu9150_irq_enable_disable(mpu9150_client_data, TYPE_GYRO, + SENSOR_IRQ_DISABLE); + mpu9150_irq_enable_disable(mpu9150_client_data, TYPE_ACCEL, + SENSOR_IRQ_DISABLE); + mpu9150_irq_enable_disable(mpu9150_client_data, TYPE_FUSION, + SENSOR_IRQ_DISABLE); + + m4_set_mpu9150_delay(mpu9150_client_data, -1, TYPE_COMPASS); + m4_set_mpu9150_delay(mpu9150_client_data, -1, TYPE_GYRO); + m4_set_mpu9150_delay(mpu9150_client_data, -1, TYPE_ACCEL); + m4_set_mpu9150_delay(mpu9150_client_data, -1, TYPE_FUSION); + + return 0; +} + +static int mpu9150_client_resume(struct platform_device *pdev) +{ + return 0; +} +#else +#define mpu9150_client_suspend NULL +#define mpu9150_client_resume NULL +#endif + +static struct of_device_id m4mpu9150_match_tbl[] = { + { .compatible = "mot,m4mpu9150" }, + {}, +}; + +static struct platform_driver mpu9150_client_driver = { + .probe = mpu9150_client_probe, + .remove = __exit_p(mpu9150_client_remove), + .shutdown = mpu9150_client_shutdown, + .suspend = mpu9150_client_suspend, + .resume = mpu9150_client_resume, + .driver = { + .name = MPU9150_CLIENT_DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(m4mpu9150_match_tbl), + }, +}; + +static int __init mpu9150_client_init(void) +{ + return platform_driver_register(&mpu9150_client_driver); +} + +static void __exit mpu9150_client_exit(void) +{ + platform_driver_unregister(&mpu9150_client_driver); +} + +module_init(mpu9150_client_init); +module_exit(mpu9150_client_exit); + +MODULE_ALIAS("platform:mpu9150_client"); +MODULE_DESCRIPTION("M4 Sensor Hub Mpu9150 client driver"); +MODULE_AUTHOR("Motorola"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/m4sensorhub_passive.c b/drivers/misc/m4sensorhub_passive.c new file mode 100644 index 00000000000..86c58fcc7e2 --- /dev/null +++ b/drivers/misc/m4sensorhub_passive.c @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2012 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/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/m4sensorhub.h> +#include <linux/m4sensorhub/MemMapPassive.h> +#include <linux/m4sensorhub_client_ioctl.h> +#include <linux/uaccess.h> +#include <linux/slab.h> + +#define PASSIVE_CLIENT_DRIVER_NAME "m4sensorhub_passive" + +struct m4_passive_data { + u32 mets; + u32 steps; + u32 floorsClimbed; + u32 timestamp; +}; + +struct passive_client { + struct m4sensorhub_data *m4sensorhub; + struct input_dev *input_dev; +}; + +static struct passive_client *misc_passive_data; +static struct m4_passive_data pdata_buffer[MAX_PASSIVE_BUFFERS] = {}; + +static int passive_client_open(struct inode *inode, struct file *file) +{ + int err = 0; + + err = nonseekable_open(inode, file); + if (err < 0) { + KDEBUG(M4SH_ERROR, "%s failed\n", __func__); + return err; + } + file->private_data = misc_passive_data; + + return 0; +} + +static int passive_client_close(struct inode *inode, struct file *file) +{ + KDEBUG(M4SH_DEBUG, "passive_client in %s\n", __func__); + return 0; +} + +static void m4_report_passive_inputevent( + struct passive_client *passive_client_data) +{ + int i; + for (i = 0; i < MAX_PASSIVE_BUFFERS; i++) { + input_event(passive_client_data->input_dev, EV_MSC, + MSC_PASSIVE_STEPS, + pdata_buffer[i].steps); + input_event(passive_client_data->input_dev, EV_MSC, + MSC_PASSIVE_METS, + pdata_buffer[i].mets); + input_event(passive_client_data->input_dev, EV_MSC, + MSC_PASSIVE_TIMESTAMP, + pdata_buffer[i].timestamp); + input_event(passive_client_data->input_dev, EV_MSC, + MSC_PASSIVE_FLOORSCLIMBED, + pdata_buffer[i].floorsClimbed); + input_sync(passive_client_data->input_dev); + } +} + + +static void m4_read_passive_data(struct passive_client *passive_client_data) +{ + int i; + u32 steps[MAX_PASSIVE_BUFFERS] = {0}; + u32 mets[MAX_PASSIVE_BUFFERS] = {0}; + u32 timestamp[12] = {0}; + u32 floorsClimbed[MAX_PASSIVE_BUFFERS] = {0}; + + /*read all buffers of steps*/ + m4sensorhub_reg_read(passive_client_data->m4sensorhub, + M4SH_REG_PASSIVE_STEPS, + (char *)&steps); + m4sensorhub_reg_read(passive_client_data->m4sensorhub, + M4SH_REG_PASSIVE_METS, + (char *)&mets); + m4sensorhub_reg_read(passive_client_data->m4sensorhub, + M4SH_REG_PASSIVE_TIMESTAMP, + (char *)×tamp); + m4sensorhub_reg_read(passive_client_data->m4sensorhub, + M4SH_REG_PASSIVE_FLOORSCLIMBED, + (char *)&floorsClimbed); + for (i = 0; i < MAX_PASSIVE_BUFFERS; i++) { + pdata_buffer[i].steps = steps[i]; + pdata_buffer[i].mets = mets[i]; + pdata_buffer[i].timestamp = timestamp[i]; + pdata_buffer[i].floorsClimbed = floorsClimbed[i]; + KDEBUG(M4SH_DEBUG, "steps = %u, mets = %u, timestamp = %u,\ + floorsClimbed = %u", pdata_buffer[i].steps, + pdata_buffer[i].mets, pdata_buffer[i].timestamp, + pdata_buffer[i].floorsClimbed); + } +} + +static void m4_handle_passive_irq(enum m4sensorhub_irqs int_event, + void *passive_data) +{ + struct passive_client *passive_client_data = passive_data; + + m4_read_passive_data(passive_client_data); + m4_report_passive_inputevent(passive_client_data); +} + +/* + * Handle commands from user-space. + */ +static long passive_client_ioctl(struct file *filp, + unsigned int cmd, unsigned long arg) +{ + int ret = 0; + struct passive_client *passive_client_data = filp->private_data; + + switch (cmd) { + case M4_SENSOR_IOCTL_GET_PASSIVE_DATA: + m4_read_passive_data(passive_client_data); + m4_report_passive_inputevent(passive_client_data); + break; + default: + KDEBUG(M4SH_ERROR, "Invalid IOCTL Command in %s\n", __func__); + ret = -EINVAL; + } + return ret; +} + +static const struct file_operations passive_client_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = passive_client_ioctl, + .open = passive_client_open, + .release = passive_client_close, +}; + +static struct miscdevice passive_client_miscdrv = { + .minor = MISC_DYNAMIC_MINOR, + .name = PASSIVE_CLIENT_DRIVER_NAME, + .fops = &passive_client_fops, +}; + +static int passive_client_probe(struct platform_device *pdev) +{ + int ret = -1; + struct passive_client *passive_client_data; + struct m4sensorhub_data *m4sensorhub = m4sensorhub_client_get_drvdata(); + + if (!m4sensorhub) + return -EFAULT; + + passive_client_data = kzalloc(sizeof(*passive_client_data), + GFP_KERNEL); + if (!passive_client_data) + return -ENOMEM; + + passive_client_data->m4sensorhub = m4sensorhub; + platform_set_drvdata(pdev, passive_client_data); + + passive_client_data->input_dev = input_allocate_device(); + if (!passive_client_data->input_dev) { + ret = -ENOMEM; + KDEBUG(M4SH_ERROR, "%s: input device allocate failed: %d\n", + __func__, ret); + goto free_mem; + } + + passive_client_data->input_dev->name = PASSIVE_CLIENT_DRIVER_NAME; + set_bit(EV_MSC, passive_client_data->input_dev->evbit); + set_bit(MSC_PASSIVE_STEPS, passive_client_data->input_dev->mscbit); + set_bit(MSC_PASSIVE_METS, passive_client_data->input_dev->mscbit); + set_bit(MSC_PASSIVE_TIMESTAMP, passive_client_data->input_dev->mscbit); + set_bit(MSC_PASSIVE_FLOORSCLIMBED, + passive_client_data->input_dev->mscbit); + if (input_register_device(passive_client_data->input_dev)) { + KDEBUG(M4SH_ERROR, "%s: input device register failed\n", + __func__); + input_free_device(passive_client_data->input_dev); + goto free_mem; + } + + ret = misc_register(&passive_client_miscdrv); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error registering %s driver\n", __func__); + goto unregister_input_device; + } + misc_passive_data = passive_client_data; + + ret = m4sensorhub_irq_register(m4sensorhub, + M4SH_IRQ_PASSIVE_BUFFER_FULL, + m4_handle_passive_irq, + passive_client_data); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error registering int %d (%d)\n", + M4SH_IRQ_PASSIVE_BUFFER_FULL, ret); + goto unregister_misc_device; + } + ret = m4sensorhub_irq_enable(m4sensorhub, M4SH_IRQ_PASSIVE_BUFFER_FULL); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error enabling int %d (%d)\n", + M4SH_IRQ_PASSIVE_BUFFER_FULL, ret); + goto unregister_irq; + } + KDEBUG(M4SH_INFO, "Initialized %s driver\n", __func__); + return 0; + +unregister_irq: + m4sensorhub_irq_unregister(m4sensorhub, M4SH_IRQ_PASSIVE_BUFFER_FULL); +unregister_misc_device: + misc_passive_data = NULL; + misc_deregister(&passive_client_miscdrv); +unregister_input_device: + input_unregister_device(passive_client_data->input_dev); +free_mem: + platform_set_drvdata(pdev, NULL); + passive_client_data->m4sensorhub = NULL; + kfree(passive_client_data); + passive_client_data = NULL; + return ret; +} + +static int __exit passive_client_remove(struct platform_device *pdev) +{ + struct passive_client *passive_client_data = + platform_get_drvdata(pdev); + + m4sensorhub_irq_disable(passive_client_data->m4sensorhub, + M4SH_IRQ_PASSIVE_BUFFER_FULL); + m4sensorhub_irq_unregister(passive_client_data->m4sensorhub, + M4SH_IRQ_PASSIVE_BUFFER_FULL); + + misc_passive_data = NULL; + misc_deregister(&passive_client_miscdrv); + input_unregister_device(passive_client_data->input_dev); + platform_set_drvdata(pdev, NULL); + passive_client_data->m4sensorhub = NULL; + kfree(passive_client_data); + passive_client_data = NULL; + return 0; +} + +static void passive_client_shutdown(struct platform_device *pdev) +{ + return; +} +#ifdef CONFIG_PM +static int passive_client_suspend(struct platform_device *pdev, + pm_message_t message) +{ + return 0; +} + +static int passive_client_resume(struct platform_device *pdev) +{ + return 0; +} +#else +#define passive_client_suspend NULL +#define passive_client_resume NULL +#endif + +static struct of_device_id m4passive_match_tbl[] = { + { .compatible = "mot,m4passive" }, + {}, +}; + +static struct platform_driver passive_client_driver = { + .probe = passive_client_probe, + .remove = __exit_p(passive_client_remove), + .shutdown = passive_client_shutdown, + .suspend = passive_client_suspend, + .resume = passive_client_resume, + .driver = { + .name = PASSIVE_CLIENT_DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(m4passive_match_tbl), + }, +}; + +static int __init passive_client_init(void) +{ + return platform_driver_register(&passive_client_driver); +} + +static void __exit passive_client_exit(void) +{ + platform_driver_unregister(&passive_client_driver); +} + +module_init(passive_client_init); +module_exit(passive_client_exit); + +MODULE_ALIAS("platform:passive_client"); +MODULE_DESCRIPTION("M4 Sensor Hub Passive mode client driver"); +MODULE_AUTHOR("Motorola"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/misc/m4sensorhub_pedometer.c b/drivers/misc/m4sensorhub_pedometer.c new file mode 100644 index 00000000000..9a6707fba0c --- /dev/null +++ b/drivers/misc/m4sensorhub_pedometer.c @@ -0,0 +1,689 @@ +/* + * Copyright (C) 2012 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/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/m4sensorhub.h> +#include <linux/m4sensorhub_client_ioctl.h> +#include <linux/m4sensorhub/MemMapPedometer.h> +#include <linux/uaccess.h> +#include <linux/slab.h> + +#define PEDOMETER_CLIENT_DRIVER_NAME "m4sensorhub_pedometer" + +struct pedometer_data { + unsigned char activity; + unsigned int distance; + unsigned int mets; + unsigned char metsactivity; + unsigned int calories; + unsigned short stepcount; + unsigned short speed; + unsigned short floorsclimbed; +}; + +struct pedometer_client { + struct m4sensorhub_data *m4sensorhub; + struct input_dev *input_dev; + struct pedometer_data prev_data; + struct pedometer_data curr_data; +}; + +struct pedometer_client *misc_pedometer_data; + +static int pedometer_client_open(struct inode *inode, struct file *file) +{ + int err = 0; + + err = nonseekable_open(inode, file); + if (err < 0) { + KDEBUG(M4SH_ERROR, "%s failed\n", __func__); + return err; + } + file->private_data = misc_pedometer_data; + + return 0; +} + +static int pedometer_client_close(struct inode *inode, struct file *file) +{ + KDEBUG(M4SH_DEBUG, "pedometer_client in %s\n", __func__); + return 0; +} + +static void m4_report_pedometer_inputevent( + struct pedometer_client *pedo_client_data) +{ + input_event(pedo_client_data->input_dev, EV_MSC, MSC_ACTIVITY_TYPE, + pedo_client_data->curr_data.activity); + input_event(pedo_client_data->input_dev, EV_MSC, MSC_STEPCOUNT, + pedo_client_data->curr_data.stepcount); + input_event(pedo_client_data->input_dev, EV_MSC, MSC_DISTANCE, + pedo_client_data->curr_data.distance); + input_event(pedo_client_data->input_dev, EV_MSC, MSC_SPEED, + pedo_client_data->curr_data.speed); + input_event(pedo_client_data->input_dev, EV_MSC, MSC_METS, + pedo_client_data->curr_data.mets); + input_event(pedo_client_data->input_dev, EV_MSC, MSC_CALORIES, + pedo_client_data->curr_data.calories); + input_event(pedo_client_data->input_dev, EV_MSC, MSC_FLOORSCLIMBED, + pedo_client_data->curr_data.floorsclimbed); + input_event(pedo_client_data->input_dev, EV_MSC, MSC_METSACTIVITY, + pedo_client_data->curr_data.metsactivity); + input_sync(pedo_client_data->input_dev); + + KDEBUG(M4SH_DEBUG, "Sending pedometer data : stepcount = %d,\ + speed = %d,distance = %d,mets = %d,calories = %d, \ + activity = %d,floorsclimbed = %d, metsactivity = %d\n", + pedo_client_data->curr_data.stepcount, + pedo_client_data->curr_data.speed, + pedo_client_data->curr_data.distance, + pedo_client_data->curr_data.mets, + pedo_client_data->curr_data.calories, + pedo_client_data->curr_data.activity, + pedo_client_data->curr_data.floorsclimbed, + pedo_client_data->curr_data.metsactivity); +} + + +static void m4_set_delay(int delay) +{ + +} + +static void m4_read_pedometer_data(struct pedometer_client *pedo_client_data) +{ + m4sensorhub_reg_read(pedo_client_data->m4sensorhub, + M4SH_REG_PEDOMETER_ACTIVITY, + (char *)&pedo_client_data->curr_data.activity); + m4sensorhub_reg_read(pedo_client_data->m4sensorhub, + M4SH_REG_PEDOMETER_TOTATDISTANCE, + (char *)&pedo_client_data->curr_data.distance); + m4sensorhub_reg_read(pedo_client_data->m4sensorhub, + M4SH_REG_PEDOMETER_TOTALSTEPS, + (char *)&pedo_client_data->curr_data.stepcount); + m4sensorhub_reg_read(pedo_client_data->m4sensorhub, + M4SH_REG_PEDOMETER_CURRENTSPEED, + (char *)&pedo_client_data->curr_data.speed); + m4sensorhub_reg_read(pedo_client_data->m4sensorhub, + M4SH_REG_METS_METS, + (char *)&pedo_client_data->curr_data.mets); + m4sensorhub_reg_read(pedo_client_data->m4sensorhub, + M4SH_REG_METS_CALORIES, + (char *)&pedo_client_data->curr_data.calories); + m4sensorhub_reg_read(pedo_client_data->m4sensorhub, + M4SH_REG_PEDOMETER_FLOORSCLIMBED, + (char *)&pedo_client_data->curr_data.floorsclimbed); + m4sensorhub_reg_read(pedo_client_data->m4sensorhub, + M4SH_REG_METS_METSACTIVITY, + (char *)&pedo_client_data->curr_data.metsactivity); +} + +static void m4_handle_pedometer_irq(enum m4sensorhub_irqs int_event, + void *pedometer_data) +{ + struct pedometer_client *pedometer_client_data = pedometer_data; + + m4_read_pedometer_data(pedometer_client_data); + m4_report_pedometer_inputevent(pedometer_client_data); +} + +/* + * Handle commands from user-space. + */ +static long pedometer_client_ioctl(struct file *filp, + unsigned int cmd, unsigned long arg) +{ + int ret = 0; + int flag; + unsigned char byte; + void __user *argp = (void __user *)arg; + struct m4sh_user_profile user; + struct m4sh_workout_data workout_data; + struct pedometer_client *pedometer_client_data = filp->private_data; + + switch (cmd) { + case M4_SENSOR_IOCTL_GET_PEDOMETER: + m4_read_pedometer_data(pedometer_client_data); + m4_report_pedometer_inputevent(pedometer_client_data); + break; + case M4_SENSOR_IOCTL_SET_DELAY: + if (copy_from_user(&flag, argp, sizeof(flag))) + return -EFAULT; + m4_set_delay(flag); + break; + /* TO DO + Need to implement the following ioctl's when M4 side implementation + will be ready + */ + case M4_SENSOR_IOCTL_SET_POSIX_TIME: + break; + case M4_SENSOR_IOCTL_SET_EQUIPMENT_TYPE: + if (copy_from_user(&byte, argp, sizeof(byte))) { + printk(KERN_ERR "copy from user returned error eq type\n"); + ret = -EFAULT; + break; + } + m4sensorhub_reg_write(pedometer_client_data->m4sensorhub, + M4SH_REG_PEDOMETER_EQUIPMENTTYPE, &byte, m4sh_no_mask); + break; + case M4_SENSOR_IOCTL_SET_MANUAL_CALIB_WALK_SPEED: + break; + case M4_SENSOR_IOCTL_SET_MANUAL_CALIB_JOG_SPEED: + break; + case M4_SENSOR_IOCTL_SET_MANUAL_CALIB_RUN_SPEED: + break; + case M4_SENSOR_IOCTL_SET_MANUAL_CALIB_STATUS: + break; + case M4_SENSOR_IOCTL_SET_USER_PROFILE: + if (copy_from_user(&user, argp, sizeof(user))) { + printk(KERN_ERR "copy from user returned error\n"); + ret = -EFAULT; + break; + } + m4sensorhub_reg_write_1byte(pedometer_client_data->m4sensorhub, + M4SH_REG_USERSETTINGS_USERAGE, user.age, 0xff); + m4sensorhub_reg_write_1byte(pedometer_client_data->m4sensorhub, + M4SH_REG_USERSETTINGS_USERGENDER, user.gender, 0xff); + m4sensorhub_reg_write_1byte(pedometer_client_data->m4sensorhub, + M4SH_REG_USERSETTINGS_USERHEIGHT, user.height, 0xff); + m4sensorhub_reg_write_1byte(pedometer_client_data->m4sensorhub, + M4SH_REG_USERSETTINGS_USERWEIGHT, user.weight, 0xff); + break; + case M4_SENSOR_IOCTL_SET_USER_DISTANCE: + if (copy_from_user(&workout_data, argp, sizeof(workout_data))) { + printk(KERN_ERR "copy from user returned error\n"); + ret = -EFAULT; + break; + } + m4sensorhub_reg_write(pedometer_client_data->m4sensorhub, + M4SH_REG_PEDOMETER_USERDISTANCE, + (unsigned char *)&workout_data.user_distance, + m4sh_no_mask); + m4sensorhub_reg_write(pedometer_client_data->m4sensorhub, + M4SH_REG_PEDOMETER_REPORTEDDISTANCE, + (unsigned char *)&workout_data.msp_distance, + m4sh_no_mask); + break; + case M4_SENSOR_IOCTL_SET_USER_CALIB_TABLE: + break; + case M4_SENSOR_IOCTL_GET_MANUAL_CALIB_STATUS: + break; + case M4_SENSOR_IOCTL_ERASE_CALIB: + break; + default: + KDEBUG(M4SH_ERROR, "Invalid IOCTL Command in %s\n", __func__); + ret = -EINVAL; + } + return ret; +} + +static ssize_t m4_pedometer_activity(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pedometer_client *pedo_client_data = platform_get_drvdata(pdev); + + m4_read_pedometer_data(pedo_client_data); + KDEBUG(M4SH_DEBUG, "%s : activity = %d\n", + __func__, pedo_client_data->curr_data.activity); + return sprintf(buf, "%d \n", pedo_client_data->curr_data.activity); +} + +static ssize_t m4_pedometer_distance(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pedometer_client *pedo_client_data = platform_get_drvdata(pdev); + + m4_read_pedometer_data(pedo_client_data); + KDEBUG(M4SH_DEBUG, "%s : distance = %d\n", + __func__, pedo_client_data->curr_data.distance); + return sprintf(buf, "%d \n", pedo_client_data->curr_data.distance); +} + +static ssize_t m4_pedometer_speed(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pedometer_client *pedo_client_data = platform_get_drvdata(pdev); + + m4_read_pedometer_data(pedo_client_data); + KDEBUG(M4SH_DEBUG, "%s : speed = %d\n", + __func__, pedo_client_data->curr_data.speed); + return sprintf(buf, "%d \n", pedo_client_data->curr_data.speed); +} + +static ssize_t m4_pedometer_stepcount(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pedometer_client *pedo_client_data = platform_get_drvdata(pdev); + + m4_read_pedometer_data(pedo_client_data); + KDEBUG(M4SH_DEBUG, "%s : stepcount = %d\n", + __func__, pedo_client_data->curr_data.stepcount); + return sprintf(buf, "%d \n", pedo_client_data->curr_data.stepcount); +} + +static ssize_t m4_pedometer_mets(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pedometer_client *pedo_client_data = platform_get_drvdata(pdev); + + KDEBUG(M4SH_DEBUG, "%s : mets = %d\n", + __func__, pedo_client_data->curr_data.mets); + return sprintf(buf, "%d \n", pedo_client_data->curr_data.mets); +} + +static ssize_t m4_pedometer_calories(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pedometer_client *pedo_client_data = platform_get_drvdata(pdev); + + KDEBUG(M4SH_DEBUG, "%s : calories = %d\n", + __func__, pedo_client_data->curr_data.calories); + return sprintf(buf, "%d \n", pedo_client_data->curr_data.calories); +} + +static ssize_t m4_pedometer_floorsclimbed(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pedometer_client *pedo_client_data = platform_get_drvdata(pdev); + + m4_read_pedometer_data(pedo_client_data); + KDEBUG(M4SH_DEBUG, "%s : floorsclimbed = %d\n", + __func__, pedo_client_data->curr_data.floorsclimbed); + return sprintf(buf, "%d \n", pedo_client_data->curr_data.floorsclimbed); +} + +static ssize_t m4_pedometer_metsactivity(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pedometer_client *pedo_client_data = platform_get_drvdata(pdev); + + KDEBUG(M4SH_DEBUG, "%s : metsactivity = %d\n", + __func__, pedo_client_data->curr_data.metsactivity); + return sprintf(buf, "%d \n", pedo_client_data->curr_data.metsactivity); +} + +static ssize_t pedo_get_loglevel(struct device *dev, + struct device_attribute *attr, char *buf) +{ + + int loglevel; + struct platform_device *pdev = to_platform_device(dev); + struct pedometer_client *pedo_client_data = platform_get_drvdata(pdev); + + m4sensorhub_reg_read(pedo_client_data->m4sensorhub, + M4SH_REG_LOG_LOGENABLE, (char *)&loglevel); + loglevel = get_log_level(loglevel, PEDO_MASK_BIT_1); + return sprintf(buf, "%d\n", loglevel); +} + +static ssize_t pedo_set_loglevel(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long level; + unsigned int mask = 0; + struct platform_device *pdev = to_platform_device(dev); + struct pedometer_client *pedo_client_data = platform_get_drvdata(pdev); + + if ((strict_strtoul(buf, 10, &level)) < 0) + return -1; + if (level > M4_MAX_LOG_LEVEL) { + KDEBUG(M4SH_ERROR, " Invalid log level - %d\n", (int)level); + return -1; + } + mask = (1 << PEDO_MASK_BIT_1) | (1 << PEDO_MASK_BIT_2); + level = (level << PEDO_MASK_BIT_1); + return m4sensorhub_reg_write(pedo_client_data->m4sensorhub, + M4SH_REG_LOG_LOGENABLE, (char *)&level, (unsigned char *)&mask); +} + +static ssize_t mets_get_loglevel(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int loglevel; + struct platform_device *pdev = to_platform_device(dev); + struct pedometer_client *pedo_client_data = platform_get_drvdata(pdev); + + m4sensorhub_reg_read(pedo_client_data->m4sensorhub, + M4SH_REG_LOG_LOGENABLE, (char *)&loglevel); + loglevel = get_log_level(loglevel, METS_MASK_BIT_1); + return sprintf(buf, "%d\n", loglevel); +} + +static ssize_t mets_set_loglevel(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long level; + unsigned int mask = 0; + struct platform_device *pdev = to_platform_device(dev); + struct pedometer_client *pedo_client_data = platform_get_drvdata(pdev); + + if ((strict_strtoul(buf, 10, &level)) < 0) + return -1; + if (level > M4_MAX_LOG_LEVEL) { + KDEBUG(M4SH_ERROR, " Invalid log level - %d\n", (int)level); + return -1; + } + mask = (1 << METS_MASK_BIT_1) | (1 << METS_MASK_BIT_2); + level = (level << METS_MASK_BIT_1); + return m4sensorhub_reg_write(pedo_client_data->m4sensorhub, + M4SH_REG_LOG_LOGENABLE, (char *)&level, (unsigned char *)&mask); +} + +static DEVICE_ATTR(activity, 0444, m4_pedometer_activity, NULL); +static DEVICE_ATTR(distance, 0444, m4_pedometer_distance, NULL); +static DEVICE_ATTR(speed, 0444, m4_pedometer_speed, NULL); +static DEVICE_ATTR(stepcount, 0444, m4_pedometer_stepcount, NULL); +static DEVICE_ATTR(mets, 0444, m4_pedometer_mets, NULL); +static DEVICE_ATTR(calories, 0444, m4_pedometer_calories, NULL); +static DEVICE_ATTR(floorsclimbed, 0444, m4_pedometer_floorsclimbed, NULL); +static DEVICE_ATTR(metsactivity, 0444, m4_pedometer_metsactivity, NULL); +static DEVICE_ATTR(pedoLogLevel, 0664, pedo_get_loglevel, pedo_set_loglevel); +static DEVICE_ATTR(metsLogLevel, 0644, mets_get_loglevel, mets_set_loglevel); + +static const struct file_operations pedometer_client_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = pedometer_client_ioctl, + .open = pedometer_client_open, + .release = pedometer_client_close, +}; + +static struct miscdevice pedometer_client_miscdrv = { + .minor = MISC_DYNAMIC_MINOR, + .name = PEDOMETER_CLIENT_DRIVER_NAME, + .fops = &pedometer_client_fops, +}; + +static int pedometer_client_probe(struct platform_device *pdev) +{ + int ret = -1; + struct pedometer_client *pedometer_client_data; + struct m4sensorhub_data *m4sensorhub = m4sensorhub_client_get_drvdata(); + + if (!m4sensorhub) + return -EFAULT; + + pedometer_client_data = kzalloc(sizeof(*pedometer_client_data), + GFP_KERNEL); + if (!pedometer_client_data) + return -ENOMEM; + + pedometer_client_data->m4sensorhub = m4sensorhub; + platform_set_drvdata(pdev, pedometer_client_data); + + pedometer_client_data->prev_data.stepcount = 0; + pedometer_client_data->prev_data.distance = 0; + pedometer_client_data->prev_data.activity = 0; + pedometer_client_data->prev_data.speed = 0; + pedometer_client_data->prev_data.floorsclimbed = 0; + + pedometer_client_data->input_dev = input_allocate_device(); + if (!pedometer_client_data->input_dev) { + ret = -ENOMEM; + KDEBUG(M4SH_ERROR, "%s: input device allocate failed: %d\n", + __func__, ret); + goto free_mem; + } + + pedometer_client_data->input_dev->name = PEDOMETER_CLIENT_DRIVER_NAME; + set_bit(EV_MSC, pedometer_client_data->input_dev->evbit); + set_bit(MSC_ACTIVITY_TYPE, pedometer_client_data->input_dev->mscbit); + set_bit(MSC_STEPCOUNT, pedometer_client_data->input_dev->mscbit); + set_bit(MSC_SPEED, pedometer_client_data->input_dev->mscbit); + set_bit(MSC_DISTANCE, pedometer_client_data->input_dev->mscbit); + set_bit(MSC_METS, pedometer_client_data->input_dev->mscbit); + set_bit(MSC_CALORIES, pedometer_client_data->input_dev->mscbit); + set_bit(MSC_FLOORSCLIMBED, pedometer_client_data->input_dev->mscbit); + set_bit(MSC_METSACTIVITY, pedometer_client_data->input_dev->mscbit); + + if (input_register_device(pedometer_client_data->input_dev)) { + KDEBUG(M4SH_ERROR, "%s: input device register failed\n", + __func__); + input_free_device(pedometer_client_data->input_dev); + goto free_mem; + } + + ret = misc_register(&pedometer_client_miscdrv); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error registering %s driver\n", __func__); + goto unregister_input_device; + } + misc_pedometer_data = pedometer_client_data; + ret = m4sensorhub_irq_register(m4sensorhub, + M4SH_IRQ_PEDOMETER_DATA_READY, m4_handle_pedometer_irq, + pedometer_client_data); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error registering int %d (%d)\n", + M4SH_IRQ_PEDOMETER_DATA_READY, ret); + goto unregister_misc_device; + } + ret = m4sensorhub_irq_register(m4sensorhub, M4SH_IRQ_ACTIVITY_CHANGE, + m4_handle_pedometer_irq, + pedometer_client_data); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error registering int %d (%d)\n", + M4SH_IRQ_ACTIVITY_CHANGE, ret); + goto unregister_pedometer_irq; + } + + ret = m4sensorhub_irq_enable(m4sensorhub, M4SH_IRQ_ACTIVITY_CHANGE); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error enabling int %d (%d)\n", + M4SH_IRQ_ACTIVITY_CHANGE, ret); + goto unregister_activity_irq; + } + + if (device_create_file(&pdev->dev, &dev_attr_activity)) { + KDEBUG(M4SH_ERROR, "Error creating %s sys entry\n", __func__); + ret = -1; + goto disable_activity_irq; + } + if (device_create_file(&pdev->dev, &dev_attr_distance)) { + KDEBUG(M4SH_ERROR, "Error creating %s sys entry\n", __func__); + ret = -1; + goto remove_activity_device_file; + } + if (device_create_file(&pdev->dev, &dev_attr_speed)) { + KDEBUG(M4SH_ERROR, "Error creating %s sys entry\n", __func__); + ret = -1; + goto remove_distance_device_file; + } + if (device_create_file(&pdev->dev, &dev_attr_stepcount)) { + KDEBUG(M4SH_ERROR, "Error creating %s sys entry\n", __func__); + ret = -1; + goto remove_speed_device_file; + } + if (device_create_file(&pdev->dev, &dev_attr_mets)) { + KDEBUG(M4SH_ERROR, "Error creating %s sys entry\n", __func__); + ret = -1; + goto remove_stepcount_device_file; + } + if (device_create_file(&pdev->dev, &dev_attr_pedoLogLevel)) { + KDEBUG(M4SH_ERROR, "Error creating %s sys entry\n", __func__); + ret = -1; + goto remove_mets_device_file; + } + if (device_create_file(&pdev->dev, &dev_attr_metsLogLevel)) { + KDEBUG(M4SH_ERROR, "Error creating %s sys entry\n", __func__); + ret = -1; + goto remove_pedoLogLevel_device_file; + } + if (device_create_file(&pdev->dev, &dev_attr_calories)) { + KDEBUG(M4SH_ERROR, "Error creating %s sys entry\n", __func__); + ret = -1; + goto remove_metsLogLevel_device_file; + } + if (device_create_file(&pdev->dev, &dev_attr_floorsclimbed)) { + KDEBUG(M4SH_ERROR, "Error creating %s sys entry\n", __func__); + ret = -1; + goto remove_cals_device_file; + } + if (device_create_file(&pdev->dev, &dev_attr_metsactivity)) { + KDEBUG(M4SH_ERROR, "Error creating %s sys entry\n", __func__); + ret = -1; + goto remove_floorsclimbed_device_file; + } + KDEBUG(M4SH_INFO, "Initialized %s driver\n", __func__); + return 0; + +remove_floorsclimbed_device_file: + device_remove_file(&pdev->dev, &dev_attr_floorsclimbed); +remove_cals_device_file: + device_remove_file(&pdev->dev, &dev_attr_calories); +remove_metsLogLevel_device_file: + device_remove_file(&pdev->dev, &dev_attr_metsLogLevel); +remove_pedoLogLevel_device_file: + device_remove_file(&pdev->dev, &dev_attr_pedoLogLevel); +remove_mets_device_file: + device_remove_file(&pdev->dev, &dev_attr_mets); +remove_stepcount_device_file: + device_remove_file(&pdev->dev, &dev_attr_stepcount); +remove_speed_device_file: + device_remove_file(&pdev->dev, &dev_attr_speed); +remove_distance_device_file: + device_remove_file(&pdev->dev, &dev_attr_distance); +remove_activity_device_file: + device_remove_file(&pdev->dev, &dev_attr_activity); +disable_activity_irq: + m4sensorhub_irq_disable(m4sensorhub, M4SH_IRQ_ACTIVITY_CHANGE); +unregister_activity_irq: + m4sensorhub_irq_unregister(m4sensorhub, M4SH_IRQ_ACTIVITY_CHANGE); +unregister_pedometer_irq: + m4sensorhub_irq_unregister(m4sensorhub, M4SH_IRQ_PEDOMETER_DATA_READY); +unregister_misc_device: + misc_pedometer_data = NULL; + misc_deregister(&pedometer_client_miscdrv); +unregister_input_device: + input_unregister_device(pedometer_client_data->input_dev); +free_mem: + platform_set_drvdata(pdev, NULL); + pedometer_client_data->m4sensorhub = NULL; + kfree(pedometer_client_data); + pedometer_client_data = NULL; + return ret; +} + +static int __exit pedometer_client_remove(struct platform_device *pdev) +{ + struct pedometer_client *pedometer_client_data = + platform_get_drvdata(pdev); + + device_remove_file(&pdev->dev, &dev_attr_metsLogLevel); + device_remove_file(&pdev->dev, &dev_attr_pedoLogLevel); + device_remove_file(&pdev->dev, &dev_attr_mets); + device_remove_file(&pdev->dev, &dev_attr_calories); + device_remove_file(&pdev->dev, &dev_attr_stepcount); + device_remove_file(&pdev->dev, &dev_attr_speed); + device_remove_file(&pdev->dev, &dev_attr_distance); + device_remove_file(&pdev->dev, &dev_attr_activity); + device_remove_file(&pdev->dev, &dev_attr_floorsclimbed); + device_remove_file(&pdev->dev, &dev_attr_metsactivity); + + m4sensorhub_irq_unregister(pedometer_client_data->m4sensorhub, + M4SH_IRQ_PEDOMETER_DATA_READY); + m4sensorhub_irq_disable(pedometer_client_data->m4sensorhub, + M4SH_IRQ_ACTIVITY_CHANGE); + m4sensorhub_irq_unregister(pedometer_client_data->m4sensorhub, + M4SH_IRQ_ACTIVITY_CHANGE); + misc_pedometer_data = NULL; + misc_deregister(&pedometer_client_miscdrv); + input_unregister_device(pedometer_client_data->input_dev); + platform_set_drvdata(pdev, NULL); + pedometer_client_data->m4sensorhub = NULL; + kfree(pedometer_client_data); + pedometer_client_data = NULL; + return 0; +} + +static void pedometer_client_shutdown(struct platform_device *pdev) +{ + return; +} +#ifdef CONFIG_PM +static int pedometer_client_suspend(struct platform_device *pdev, + pm_message_t message) +{ + return 0; +} + +static int pedometer_client_resume(struct platform_device *pdev) +{ + return 0; +} +#else +#define pedometer_client_suspend NULL +#define pedometer_client_resume NULL +#endif + + +static struct of_device_id m4pedometer_match_tbl[] = { + { .compatible = "mot,m4pedometer" }, + {}, +}; + + +static struct platform_driver pedometer_client_driver = { + .probe = pedometer_client_probe, + .remove = __exit_p(pedometer_client_remove), + .shutdown = pedometer_client_shutdown, + .suspend = pedometer_client_suspend, + .resume = pedometer_client_resume, + .driver = { + .name = PEDOMETER_CLIENT_DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(m4pedometer_match_tbl), + }, +}; + +static int __init pedometer_client_init(void) +{ + return platform_driver_register(&pedometer_client_driver); +} + +static void __exit pedometer_client_exit(void) +{ + platform_driver_unregister(&pedometer_client_driver); +} + +module_init(pedometer_client_init); +module_exit(pedometer_client_exit); + +MODULE_ALIAS("platform:pedometer_client"); +MODULE_DESCRIPTION("M4 Sensor Hub Pedometer client driver"); +MODULE_AUTHOR("Motorola"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/misc/m4sensorhub_stillmode.c b/drivers/misc/m4sensorhub_stillmode.c new file mode 100644 index 00000000000..3fa61219245 --- /dev/null +++ b/drivers/misc/m4sensorhub_stillmode.c @@ -0,0 +1,425 @@ +/* + * Copyright (C) 2012 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/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/miscdevice.h> +#include <linux/input.h> +#include <linux/wakelock.h> +#include <linux/workqueue.h> +#include <linux/m4sensorhub.h> +#include <linux/slab.h> + +#define STILLMODE_CLIENT_DRIVER_NAME "m4sensorhub_stillmode" +#define STILLMODE_DEFAULT_TIMEOUT 600 /* 10 minutes */ + +static DEFINE_MUTEX(state_access); + +enum m4_stillmode_type { + MOTION, + STILL, +}; + +struct stillmode_client { + struct m4sensorhub_data *m4sensorhub; + struct input_dev *input_dev; + enum m4_stillmode_type state; + struct wake_lock wakelock; + struct work_struct queued_work; + u16 timeout; +}; + +static struct stillmode_client *g_stillmode_data; + +static int stillmode_set_timeout(struct stillmode_client *stillmode_client_data, + u16 timeout) +{ + int ret; + + ret = m4sensorhub_reg_write(stillmode_client_data->m4sensorhub, + M4SH_REG_POWER_STILLMODETIMEOUT, + (char *)&timeout, m4sh_no_mask); + if (ret == m4sensorhub_reg_getsize(stillmode_client_data->m4sensorhub, + M4SH_REG_POWER_STILLMODETIMEOUT)) { + stillmode_client_data->timeout = timeout; + ret = 0; + } else + ret = -EIO; + + return ret; + +} + +static void stillmode_set_state(struct stillmode_client *stillmode_client_data, + enum m4_stillmode_type state) +{ + mutex_lock(&state_access); + if (stillmode_client_data->state == state) { + mutex_unlock(&state_access); + printk(KERN_WARNING "M4SH duplicate stillmode update (%s)\n", + (state == STILL) ? "still" : "moving"); + } else { + stillmode_client_data->state = state; + mutex_unlock(&state_access); + + /* Hold a 500ms wakelock to let data get to KineticManager */ + wake_lock_timeout(&stillmode_client_data->wakelock, 0.5 * HZ); + + input_report_switch(stillmode_client_data->input_dev, + SW_STILL_MODE, + (stillmode_client_data->state == STILL)); + input_sync(stillmode_client_data->input_dev); + printk(KERN_INFO "stillmode state changed to %s (%d)\n", + (state == STILL) ? "still" : "moving", state); + } +} + +static int m4_stillmode_exit(void) +{ + struct stillmode_client *stillmode_client_data = g_stillmode_data; + int ret = 0; + + KDEBUG(M4SH_INFO, "Resetting stillmode timer\n"); + + /* writing timeout value to M4 resets its timer */ + ret = stillmode_set_timeout(stillmode_client_data, + stillmode_client_data->timeout); + if (ret == 0) { + if (stillmode_client_data->state == STILL) + stillmode_set_state(stillmode_client_data, MOTION); + } else + KDEBUG(M4SH_ERROR, "M4SH Error setting timeout (%d)\n", ret); + + return ret; +} + + +int m4sensorhub_stillmode_exit(void) +{ + return m4_stillmode_exit(); +} +EXPORT_SYMBOL_GPL(m4sensorhub_stillmode_exit); + + +static void m4sensorhub_stillmode_work(struct work_struct *work) +{ + m4sensorhub_stillmode_exit(); +} + +static void m4_handle_stillmode_irq(enum m4sensorhub_irqs int_event, + void *stillmode_data) +{ + struct stillmode_client *stillmode_client_data = stillmode_data; + enum m4_stillmode_type new_state; + + KDEBUG(M4SH_INFO, "%s() got irq %d (%s)\n", __func__, int_event, + int_event == M4SH_IRQ_STILL_DETECTED ? "STILL_MODE" : "MOTION_MODE"); + + switch (int_event) { + case (M4SH_IRQ_STILL_DETECTED): + new_state = STILL; + break; + case (M4SH_IRQ_MOTION_DETECTED): + new_state = MOTION; + break; + default: + printk(KERN_ERR "%s() Unexpected irq: %d\n", + __func__, int_event); + return; + break; + } + + stillmode_set_state(stillmode_client_data, new_state); +} + +static ssize_t m4_stillmode_getstate(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct stillmode_client *stillmode_client_data = + platform_get_drvdata(pdev); + + return sprintf(buf, "%d \n", stillmode_client_data->state); +} + +static ssize_t m4_stillmode_setstate(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct platform_device *pdev = to_platform_device(dev); + struct stillmode_client *stillmode_client_data = + platform_get_drvdata(pdev); + long value; + int ret = size; + + if (((strict_strtoul(buf, 10, &value)) < 0) || + (value != MOTION)) { + KDEBUG(M4SH_ERROR, "M4SH stillmode invalid value: %ld. Only " + "%d is allowed\n", value, MOTION); + return -EINVAL; + } + + if (value != stillmode_client_data->state) + return m4sensorhub_stillmode_exit(); + + return ret; +} + +static ssize_t m4_stillmode_get_timeout(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct stillmode_client *stillmode_client_data = + platform_get_drvdata(pdev); + + return sprintf(buf, "%d \n", stillmode_client_data->timeout); +} + +static ssize_t m4_stillmode_set_timeout(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct platform_device *pdev = to_platform_device(dev); + struct stillmode_client *stillmode_client_data = + platform_get_drvdata(pdev); + long value; + int ret; + + if (((strict_strtoul(buf, 10, &value)) < 0) || + (value < 0) || (value > USHRT_MAX)) { + KDEBUG(M4SH_ERROR, "M4SH stillmode invalid timeout: %ld\n", + value); + return -EINVAL; + } + + KDEBUG(M4SH_DEBUG, "%s() setting timeout to %ld\n", __func__, value); + + ret = stillmode_set_timeout(stillmode_client_data, value); + + return ((ret == 0) ? size : ret); +} + +static DEVICE_ATTR(state, 0664, m4_stillmode_getstate, + m4_stillmode_setstate); +static DEVICE_ATTR(timeout, 0664, m4_stillmode_get_timeout, + m4_stillmode_set_timeout); + +static int stillmode_client_probe(struct platform_device *pdev) +{ + int ret = -1; + struct stillmode_client *stillmode_client_data; + struct m4sensorhub_data *m4sensorhub = m4sensorhub_client_get_drvdata(); + + if (!m4sensorhub) + return -EFAULT; + + stillmode_client_data = kzalloc(sizeof(*stillmode_client_data), + GFP_KERNEL); + if (!stillmode_client_data) + return -ENOMEM; + + g_stillmode_data = stillmode_client_data; + stillmode_client_data->m4sensorhub = m4sensorhub; + platform_set_drvdata(pdev, stillmode_client_data); + stillmode_client_data->state = MOTION; + stillmode_client_data->timeout = STILLMODE_DEFAULT_TIMEOUT; + + stillmode_client_data->input_dev = input_allocate_device(); + if (!stillmode_client_data->input_dev) { + ret = -ENOMEM; + KDEBUG(M4SH_ERROR, "%s: input device allocate failed: %d\n", + __func__, ret); + goto free_memory; + } + + stillmode_client_data->input_dev->name = STILLMODE_CLIENT_DRIVER_NAME; + set_bit(EV_SW, stillmode_client_data->input_dev->evbit); + set_bit(SW_STILL_MODE, stillmode_client_data->input_dev->swbit); + + if (input_register_device(stillmode_client_data->input_dev)) { + KDEBUG(M4SH_ERROR, "%s: input device register failed\n", + __func__); + input_free_device(stillmode_client_data->input_dev); + goto free_memory; + } + + wake_lock_init(&stillmode_client_data->wakelock, WAKE_LOCK_SUSPEND, + STILLMODE_CLIENT_DRIVER_NAME); + + INIT_WORK(&stillmode_client_data->queued_work, + m4sensorhub_stillmode_work); + + ret = m4sensorhub_irq_register(m4sensorhub, M4SH_IRQ_STILL_DETECTED, + m4_handle_stillmode_irq, + stillmode_client_data); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error registering still mode IRQ: " + "%d\n", ret); + goto destroy_wakelock; + } + ret = m4sensorhub_irq_enable(m4sensorhub, M4SH_IRQ_STILL_DETECTED); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error enabling still mode int: " + "%d\n", ret); + goto unregister_still_irq; + } + + ret = m4sensorhub_irq_register(m4sensorhub, M4SH_IRQ_MOTION_DETECTED, + m4_handle_stillmode_irq, + stillmode_client_data); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error registering moving mode IRQ: " + "%d\n", ret); + goto disable_still_irq; + } + ret = m4sensorhub_irq_enable(m4sensorhub, M4SH_IRQ_MOTION_DETECTED); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error enabling moving mode int: " + "%d\n", ret); + goto unregister_moving_irq; + } + + if (device_create_file(&pdev->dev, &dev_attr_state)) { + KDEBUG(M4SH_ERROR, "Error creating stillmode sys entry\n"); + ret = -1; + goto disable_moving_irq; + } + + if (device_create_file(&pdev->dev, &dev_attr_timeout)) { + KDEBUG(M4SH_ERROR, "Error creating timeout sys entry\n"); + ret = -1; + goto remove_stillmode_sysfs; + } + + /* initialize timer on M4 */ + m4sensorhub_stillmode_exit(); + + KDEBUG(M4SH_INFO, "Initialized %s driver\n", + STILLMODE_CLIENT_DRIVER_NAME); + + return 0; + +remove_stillmode_sysfs: + device_remove_file(&pdev->dev, &dev_attr_state); +disable_moving_irq: + m4sensorhub_irq_disable(m4sensorhub, M4SH_IRQ_MOTION_DETECTED); +unregister_moving_irq: + m4sensorhub_irq_unregister(m4sensorhub, M4SH_IRQ_MOTION_DETECTED); +disable_still_irq: + m4sensorhub_irq_disable(m4sensorhub, M4SH_IRQ_STILL_DETECTED); +unregister_still_irq: + m4sensorhub_irq_unregister(m4sensorhub, M4SH_IRQ_STILL_DETECTED); +destroy_wakelock: + wake_lock_destroy(&stillmode_client_data->wakelock); + input_unregister_device(stillmode_client_data->input_dev); +free_memory: + platform_set_drvdata(pdev, NULL); + m4sensorhub->pdev->stillmode_exit = NULL; + stillmode_client_data->m4sensorhub = NULL; + kfree(stillmode_client_data); + g_stillmode_data = NULL; + + return ret; +} + +static int __exit stillmode_client_remove(struct platform_device *pdev) +{ + struct stillmode_client *stillmode_client_data = + platform_get_drvdata(pdev); + struct m4sensorhub_data *m4sensorhub = + stillmode_client_data->m4sensorhub; + + device_remove_file(&pdev->dev, &dev_attr_timeout); + device_remove_file(&pdev->dev, &dev_attr_state); + m4sensorhub_irq_disable(m4sensorhub, M4SH_IRQ_MOTION_DETECTED); + m4sensorhub_irq_unregister(m4sensorhub, M4SH_IRQ_MOTION_DETECTED); + m4sensorhub_irq_disable(m4sensorhub, M4SH_IRQ_STILL_DETECTED); + m4sensorhub_irq_unregister(m4sensorhub, M4SH_IRQ_STILL_DETECTED); + wake_lock_destroy(&stillmode_client_data->wakelock); + input_unregister_device(stillmode_client_data->input_dev); + platform_set_drvdata(pdev, NULL); + m4sensorhub->pdev->stillmode_exit = NULL; + stillmode_client_data->m4sensorhub = NULL; + kfree(stillmode_client_data); + g_stillmode_data = NULL; + + return 0; +} + +static void stillmode_client_shutdown(struct platform_device *pdev) +{ + return; +} +#ifdef CONFIG_PM +static int stillmode_client_suspend(struct platform_device *pdev, + pm_message_t message) +{ + return 0; +} + +static int stillmode_client_resume(struct platform_device *pdev) +{ + return 0; +} +#else +#define stillmode_client_suspend NULL +#define stillmode_client_resume NULL +#endif + + +static struct of_device_id m4stillmode_match_tbl[] = { + { .compatible = "mot,m4stillmode" }, + {}, +}; + +static struct platform_driver stillmode_client_driver = { + .probe = stillmode_client_probe, + .remove = __exit_p(stillmode_client_remove), + .shutdown = stillmode_client_shutdown, + .suspend = stillmode_client_suspend, + .resume = stillmode_client_resume, + .driver = { + .name = STILLMODE_CLIENT_DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(m4stillmode_match_tbl), + }, +}; + +static int __init stillmode_client_init(void) +{ + return platform_driver_register(&stillmode_client_driver); +} + +static void __exit stillmode_client_exit(void) +{ + platform_driver_unregister(&stillmode_client_driver); +} + +module_init(stillmode_client_init); +module_exit(stillmode_client_exit); + +MODULE_ALIAS("platform:stillmode_client"); +MODULE_DESCRIPTION("M4 sensorhub still mode client driver"); +MODULE_AUTHOR("Motorola"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/misc/m4sensorhub_tmp006.c b/drivers/misc/m4sensorhub_tmp006.c new file mode 100644 index 00000000000..44cb293f9a4 --- /dev/null +++ b/drivers/misc/m4sensorhub_tmp006.c @@ -0,0 +1,432 @@ +/* + * Copyright (C) 2012 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/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/slab.h> +#include <linux/uaccess.h> +#include <linux/m4sensorhub.h> +#include <linux/m4sensorhub_client_ioctl.h> +#include <linux/m4sensorhub/MemMapTempSensor.h> + +#define TMP_CLIENT_DRIVER_NAME "m4sensorhub_tmp006" +#define TMP_MAX 1250 +#define TMP_MIN -400 + +struct tmp_client { + struct m4sensorhub_data *m4sensorhub; + struct input_dev *input_dev; + int internal_tmp; + int external_tmp; + signed short samplerate; +}; + +struct tmp_client *misc_tmp_data; + +static int temperature_client_open(struct inode *inode, struct file *file) +{ + int err = 0; + + err = nonseekable_open(inode, file); + if (err < 0) { + KDEBUG(M4SH_DEBUG, "temperature_clientopen failed\n"); + return err; + } + file->private_data = misc_tmp_data; + + return 0; +} + +static int temperature_client_close(struct inode *inode, struct file *file) +{ + KDEBUG(M4SH_DEBUG, "temperature_client in %s\n", __func__); + return 0; +} + +static void m4_report_temp_inputevent(struct tmp_client *tmp_data) +{ + input_report_abs(tmp_data->input_dev, ABS_THROTTLE, + tmp_data->external_tmp); + input_sync(tmp_data->input_dev); +} + +static void m4_read_temp_data(struct tmp_client *tmp_data) +{ + sTempData tmp; + + m4sensorhub_reg_read(tmp_data->m4sensorhub, + M4SH_REG_TEMP_EXTRNLTEMP, (char *)&tmp.extrnlTemp); + m4sensorhub_reg_read(tmp_data->m4sensorhub, + M4SH_REG_TEMP_INTRNLTEMP, (char *)&tmp.intrnlTemp); + tmp_data->internal_tmp = tmp.intrnlTemp; + tmp_data->external_tmp = tmp.extrnlTemp; +} + +static void m4_handle_tmp_irq(enum m4sensorhub_irqs int_event, + void *tmp_data) +{ + struct tmp_client *tmp_client_data = tmp_data; + + m4_read_temp_data(tmp_client_data); + m4_report_temp_inputevent(tmp_client_data); +} + +static int m4_set_tmp_samplerate( + struct tmp_client *tmp_client_data, + signed int samplerate) +{ + int ret = 0; + + if (samplerate != tmp_client_data->samplerate) { + ret = m4sensorhub_reg_write(tmp_client_data->m4sensorhub, + M4SH_REG_TEMP_SAMPLERATE, + (char *)&samplerate, m4sh_no_mask); + if (ret != m4sensorhub_reg_getsize( + tmp_client_data->m4sensorhub, + M4SH_REG_TEMP_SAMPLERATE)) { + KDEBUG(M4SH_ERROR, "Unable to set delay \ + for temperature sensor\n"); + return ret; + } + + KDEBUG(M4SH_DEBUG, "%s() updating samplerate from %d to %d\n", + __func__, tmp_client_data->samplerate, + samplerate); + tmp_client_data->samplerate = samplerate; + + if (samplerate >= 0) + ret = m4sensorhub_irq_enable( + tmp_client_data->m4sensorhub, + M4SH_IRQ_TMP_DATA_READY); + else + ret = m4sensorhub_irq_disable( + tmp_client_data->m4sensorhub, + M4SH_IRQ_TMP_DATA_READY); + if (ret != 0) + KDEBUG(M4SH_ERROR, "Unable to enable/disable \ + temperature irq\n"); + } + + return ret; +} + + +/* + * Handle commands from user-space. + */ +static long temperature_client_ioctl(struct file *filp, + unsigned int cmd, unsigned long arg) +{ + int ret = 0; + int delay = 0; + static int status; + + void __user *argp = (void __user *)arg; + struct tmp_client *tmp_data = filp->private_data; + + switch (cmd) { + case M4_SENSOR_IOCTL_GET_TEMPRATURE: + m4_read_temp_data(tmp_data); + m4_report_temp_inputevent(tmp_data); + break; + case M4_SENSOR_IOCTL_SET_DELAY: + if (copy_from_user(&delay, argp, sizeof(delay))) + return -EFAULT; + if (delay >= 0) + ret = m4_set_tmp_samplerate(tmp_data, delay); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error setting samplerate to %d" + " (%d)\n", delay, ret); + return -EFAULT; + } + break; + case M4_SENSOR_IOCTL_APP_GET_FLAG: + if (copy_to_user(argp, &status, sizeof(status))) + return -EFAULT; + break; + case M4_SENSOR_IOCTL_APP_SET_FLAG: + if (copy_from_user(&status, argp, sizeof(status))) + return -EFAULT; + break; + default: + KDEBUG(M4SH_ERROR, "Invalid IOCTL Command %d\n", cmd); + ret = -EINVAL; + } + return ret; +} + +static ssize_t GetExternalTemp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct tmp_client *tmp_client_data = platform_get_drvdata(pdev); + + m4_read_temp_data(tmp_client_data); + KDEBUG(M4SH_DEBUG, "%s : external temp = %d", + __func__, tmp_client_data->external_tmp); + return sprintf(buf, "%d\n", tmp_client_data->external_tmp); +} + +static ssize_t GetInternalTemp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct tmp_client *tmp_client_data = platform_get_drvdata(pdev); + + m4_read_temp_data(tmp_client_data); + KDEBUG(M4SH_DEBUG, "%s : internal temp = %d", + __func__, tmp_client_data->internal_tmp); + return sprintf(buf, "%d\n", tmp_client_data->internal_tmp); +} + +static ssize_t tmp_get_loglevel(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long long loglevel; + struct platform_device *pdev = to_platform_device(dev); + struct tmp_client *tmp_client_data = platform_get_drvdata(pdev); + + m4sensorhub_reg_read(tmp_client_data->m4sensorhub, + M4SH_REG_LOG_LOGENABLE, (char *)&loglevel); + loglevel = get_log_level(loglevel, TMP_MASK_BIT_1); + return sprintf(buf, "%llu\n", loglevel); +} + +static ssize_t tmp_set_loglevel(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned long level; + unsigned long long mask = 0, newlevel; + struct platform_device *pdev = to_platform_device(dev); + struct tmp_client *tmp_client_data = platform_get_drvdata(pdev); + + if ((strict_strtoul(buf, 10, &level)) < 0) + return -1; + if (level > M4_MAX_LOG_LEVEL) { + KDEBUG(M4SH_ERROR, " Invalid log level - %d\n", (int)level); + return -1; + } + mask = (1ULL << TMP_MASK_BIT_1) | (1ULL << TMP_MASK_BIT_2); + newlevel = (level << TMP_MASK_BIT_1); + return m4sensorhub_reg_write(tmp_client_data->m4sensorhub, + M4SH_REG_LOG_LOGENABLE, (char *)&newlevel, (unsigned char *)&mask); +} + +static DEVICE_ATTR(internal, 0444, GetInternalTemp, NULL); +static DEVICE_ATTR(external, 0444, GetExternalTemp, NULL); +static DEVICE_ATTR(LogLevel, 0444, tmp_get_loglevel, tmp_set_loglevel); + +static const struct file_operations temperature_client_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = temperature_client_ioctl, + .open = temperature_client_open, + .release = temperature_client_close, +}; + +static struct miscdevice temperature_client_miscdrv = { + .minor = MISC_DYNAMIC_MINOR, + .name = TMP_CLIENT_DRIVER_NAME, + .fops = &temperature_client_fops, +}; + +static int temperature_client_probe(struct platform_device *pdev) +{ + int ret = -1; + struct tmp_client *tmp_client_data; + struct m4sensorhub_data *m4sensorhub = m4sensorhub_client_get_drvdata(); + + if (!m4sensorhub) + return -EFAULT; + + tmp_client_data = kzalloc(sizeof(*tmp_client_data), GFP_KERNEL); + if (!tmp_client_data) + return -ENOMEM; + + tmp_client_data->m4sensorhub = m4sensorhub; + platform_set_drvdata(pdev, tmp_client_data); + + tmp_client_data->input_dev = input_allocate_device(); + if (!tmp_client_data->input_dev) { + ret = -ENOMEM; + KDEBUG(M4SH_ERROR, "%s: input device allocate failed: %d\n", + __func__, ret); + goto free_memory; + } + + tmp_client_data->input_dev->name = TMP_CLIENT_DRIVER_NAME; + set_bit(EV_ABS, tmp_client_data->input_dev->evbit); + set_bit(ABS_THROTTLE, tmp_client_data->input_dev->absbit); + input_set_abs_params(tmp_client_data->input_dev, ABS_THROTTLE, + TMP_MIN, TMP_MAX, 0, 0); + + if (input_register_device(tmp_client_data->input_dev)) { + KDEBUG(M4SH_INFO, "%s: input device register failed\n", + __func__); + input_free_device(tmp_client_data->input_dev); + goto free_memory; + } + + ret = misc_register(&temperature_client_miscdrv); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error registering %s driver\n", + TMP_CLIENT_DRIVER_NAME); + goto unregister_input_device; + } + misc_tmp_data = tmp_client_data; + ret = m4sensorhub_irq_register(m4sensorhub, M4SH_IRQ_TMP_DATA_READY, + m4_handle_tmp_irq, + tmp_client_data); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "Error registering int %d (%d)\n", + M4SH_IRQ_TMP_DATA_READY, ret); + goto unregister_misc_device; + } + + if (device_create_file(&pdev->dev, &dev_attr_internal)) { + KDEBUG(M4SH_ERROR, "Error creating %s sys entry\n", + TMP_CLIENT_DRIVER_NAME); + ret = -1; + goto unregister_irq; + } + + if (device_create_file(&pdev->dev, &dev_attr_external)) { + KDEBUG(M4SH_ERROR, "Error creating %s sys entry\n", + TMP_CLIENT_DRIVER_NAME); + ret = -1; + goto remove_internal_device_file; + } + if (device_create_file(&pdev->dev, &dev_attr_LogLevel)) { + KDEBUG(M4SH_ERROR, "Error creating %s sys entry\n", + TMP_CLIENT_DRIVER_NAME); + ret = -1; + goto remove_external_device_file; + } + KDEBUG(M4SH_INFO, "Initialized %s driver\n", TMP_CLIENT_DRIVER_NAME); + return 0; + +remove_external_device_file: + device_remove_file(&pdev->dev, &dev_attr_external); +remove_internal_device_file: + device_remove_file(&pdev->dev, &dev_attr_internal); +unregister_irq: + m4sensorhub_irq_unregister(m4sensorhub, M4SH_IRQ_TMP_DATA_READY); +unregister_misc_device: + misc_tmp_data = NULL; + misc_deregister(&temperature_client_miscdrv); +unregister_input_device: + input_unregister_device(tmp_client_data->input_dev); +free_memory: + platform_set_drvdata(pdev, NULL); + tmp_client_data->m4sensorhub = NULL; + kfree(tmp_client_data); + tmp_client_data = NULL; + return ret; +} + +static int __exit temperature_client_remove(struct platform_device *pdev) +{ + struct tmp_client *tmp_client_data = platform_get_drvdata(pdev); + + device_remove_file(&pdev->dev, &dev_attr_LogLevel); + device_remove_file(&pdev->dev, &dev_attr_external); + device_remove_file(&pdev->dev, &dev_attr_internal); + m4sensorhub_irq_disable(tmp_client_data->m4sensorhub, + M4SH_IRQ_TMP_DATA_READY); + m4sensorhub_irq_unregister(tmp_client_data->m4sensorhub, + M4SH_IRQ_TMP_DATA_READY); + misc_tmp_data = NULL; + misc_deregister(&temperature_client_miscdrv); + input_unregister_device(tmp_client_data->input_dev); + platform_set_drvdata(pdev, NULL); + tmp_client_data->m4sensorhub = NULL; + kfree(tmp_client_data); + tmp_client_data = NULL; + return 0; +} + +static void temperature_client_shutdown(struct platform_device *pdev) +{ + return; +} + +#ifdef CONFIG_PM + +static int temperature_client_suspend(struct platform_device *pdev, + pm_message_t message) +{ + struct tmp_client *tmp_client_data = platform_get_drvdata(pdev); + + return m4_set_tmp_samplerate(tmp_client_data, -1); +} + +static int temperature_client_resume(struct platform_device *pdev) +{ + return 0; +} + +#else +#define temperature_client_suspend NULL +#define temperature_client_resume NULL +#endif + +static struct of_device_id m4temp_match_tbl[] = { + { .compatible = "mot,m4temperature" }, + {}, +}; + +static struct platform_driver temp_client_driver = { + .probe = temperature_client_probe, + .remove = __exit_p(temperature_client_remove), + .shutdown = temperature_client_shutdown, + .suspend = temperature_client_suspend, + .resume = temperature_client_resume, + .driver = { + .name = TMP_CLIENT_DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(m4temp_match_tbl), + }, +}; + +static int __init temperature_client_init(void) +{ + return platform_driver_register(&temp_client_driver); +} + +static void __exit temperature_client_exit(void) +{ + platform_driver_unregister(&temp_client_driver); +} + +module_init(temperature_client_init); +module_exit(temperature_client_exit); + +MODULE_ALIAS("platform:temperature_client"); +MODULE_DESCRIPTION("M4 Sensor Hub driver"); +MODULE_AUTHOR("Motorola"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/misc/m4sensorhub_wrist.c b/drivers/misc/m4sensorhub_wrist.c new file mode 100644 index 00000000000..faed5f4068b --- /dev/null +++ b/drivers/misc/m4sensorhub_wrist.c @@ -0,0 +1,836 @@ +/* + * Copyright (C) 2013 Motorola Mobility, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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 + */ + +#include "m4sensorhub_wrist.h" +#include <linux/types.h> +#include <linux/platform_device.h> +#include <linux/m4sensorhub.h> +#include <linux/firmware.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/m4sensorhub_gpio.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/of_gpio.h> + +static struct platform_driver m4wrist_client_driver; +static int m4wrist_probe(struct platform_device *pdev); +static int m4wrist_remove(struct platform_device *pdev); +static int m4wrist_init(void); +static void m4wrist_exit(void); +static void m4wrist_free(struct m4wrist_driver_data *dd); +static int m4wrist_gpio_init(struct m4wrist_driver_data *dd); +static int m4wrist_request_firmware(struct m4wrist_driver_data *dd); +static int m4wrist_request_irq(struct m4wrist_driver_data *dd); +static void m4wrist_firmware_callback(const struct firmware *fw, + void *context); +static void m4wrist_irq(enum m4sensorhub_irqs event, void *context); +static int m4wrist_gpio_control(struct m4wrist_driver_data *dd); +static int m4wrist_gpio_release(struct m4wrist_driver_data *dd); +static int m4wrist_reflash_ic(struct m4wrist_driver_data *dd); +static int m4wrist_enter_reset_mode(struct m4wrist_driver_data *dd); +static int m4wrist_erase_flash(struct m4wrist_driver_data *dd); +static int m4wrist_program_image(struct m4wrist_driver_data *dd); +static void m4wrist_send_bitstream(struct m4wrist_driver_data *dd, + uint8_t *stream, uint32_t bits); +static void m4wrist_toggle_clock(struct m4wrist_driver_data *dd, int cycles); +static int m4wrist_wait_poll(struct m4wrist_driver_data *dd); +static int m4wrist_read_id_word(struct m4wrist_driver_data *dd, uint8_t *data); + + +static struct of_device_id m4wrist_match_tbl[] = { + { .compatible = "mot,m4wrist" }, + {}, +}; + +static struct platform_driver m4wrist_client_driver = { + .probe = m4wrist_probe, + .remove = m4wrist_remove, + .driver = { + .name = "m4sensorhub_wrist", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(m4wrist_match_tbl), + }, +}; + +static int m4wrist_probe(struct platform_device *pdev) +{ + int err = 0; + struct m4wrist_driver_data *dd = NULL; + struct device_node *node = pdev->dev.of_node; + + if (!node) { + pr_warn("devtree node not present!\n"); + return -ENODEV; + } + dd = kzalloc(sizeof(struct m4wrist_driver_data), GFP_KERNEL); + if (dd == NULL) { + printk(KERN_ERR "%s: Unable to create driver data.\n", + __func__); + err = -ENOMEM; + goto m4wrist_probe_fail; + } + + dd->pdev = pdev; + platform_set_drvdata(pdev, dd); + + dd->mutex = kzalloc(sizeof(struct mutex), GFP_KERNEL); + if (dd->mutex == NULL) { + printk(KERN_ERR "%s: Unable to create mutex lock.\n", + __func__); + err = -ENOMEM; + goto m4wrist_probe_fail; + } + mutex_init(dd->mutex); + + dd->gpio_xres = of_get_named_gpio_flags(node, + "mot,wrist_xres", 0, NULL); + if (dd->gpio_xres <= 0) { + printk(KERN_ERR "%s: gpio_xres is invalid.\n", __func__); + err = -EINVAL; + goto m4wrist_probe_fail; + } + dd->gpio_clk = of_get_named_gpio_flags(node, "mot,wrist_clk", 0, NULL); + if (dd->gpio_clk <= 0) { + printk(KERN_ERR "%s: gpio_clk is invalid.\n", __func__); + err = -EINVAL; + goto m4wrist_probe_fail; + } + dd->gpio_data = of_get_named_gpio_flags(node, + "mot,wrist_data", 0, NULL); + if (dd->gpio_data <= 0) { + printk(KERN_ERR "%s: gpio_data is invalid.\n", __func__); + err = -EINVAL; + goto m4wrist_probe_fail; + } + + err = m4wrist_gpio_init(dd); + if (err < 0) + goto m4wrist_probe_fail; + + err = m4wrist_request_firmware(dd); + if (err < 0) + goto m4wrist_probe_fail; + + err = m4wrist_request_irq(dd); + if (err < 0) + goto m4wrist_probe_fail; + + goto m4wrist_probe_pass; + +m4wrist_probe_fail: + m4wrist_free(dd); + printk(KERN_ERR "%s: Probe failed with error code %d.\n", + __func__, err); + return err; + +m4wrist_probe_pass: + return 0; +} + +static void m4wrist_free(struct m4wrist_driver_data *dd) +{ + if (dd != NULL) { + kfree(dd->img); + + if (dd->client != NULL) { + m4sensorhub_irq_disable(dd->client, + M4SH_IRQ_WRIST_READY); + m4sensorhub_irq_unregister(dd->client, + M4SH_IRQ_WRIST_READY); + } + + kfree(dd->mutex); + platform_set_drvdata(dd->pdev, NULL); + kfree(dd); + } + + return; +} + +static int m4wrist_gpio_init(struct m4wrist_driver_data *dd) +{ + int err = 0; + int i = 0; + int gpio_nums[3] = {dd->gpio_xres, dd->gpio_clk, dd->gpio_data}; + char *gpio_names[3] = {"wrist_xres", "wrist_clk", "wrist_data"}; + + for (i = 0; i < 3; i++) { + err = gpio_request(gpio_nums[i], gpio_names[i]); + if (err < 0) { + printk(KERN_ERR "%s: Failed to request %s.\n", + __func__, gpio_names[i]); + i--; + goto m4wrist_gpio_init_fail; + } + + err = gpio_direction_input(gpio_nums[i]); + if (err < 0) { + printk(KERN_ERR "%s: Failed to make %s an input.\n", + __func__, gpio_names[i]); + gpio_free(gpio_nums[i]); + i--; + goto m4wrist_gpio_init_fail; + } + } + + goto m4wrist_gpio_init_exit; + +m4wrist_gpio_init_fail: + while (i >= 0) { + gpio_free(gpio_nums[i]); + i--; + } + +m4wrist_gpio_init_exit: + return err; +} + +static int m4wrist_request_firmware(struct m4wrist_driver_data *dd) +{ + int err = 0; + const struct firmware *fw = NULL; + + err = request_firmware(&fw, + "m4sensorhub_wrist.bin", &(dd->pdev->dev)); + if (err < 0) { + printk(KERN_ERR "%s: Firmware request failed.\n", __func__); + goto m4wrist_request_firmware_fail; + } + + m4wrist_firmware_callback(fw, dd); + +m4wrist_request_firmware_fail: + return err; +} + +static int m4wrist_request_irq(struct m4wrist_driver_data *dd) +{ + int err = 0; + + dd->client = m4sensorhub_client_get_drvdata(); + if (dd->client == NULL) { + printk(KERN_ERR "%s: No client data retrieved.\n", + __func__); + err = -ENODATA; + goto m4wrist_request_irq_fail; + } + + err = m4sensorhub_irq_register(dd->client, + M4SH_IRQ_WRIST_READY, m4wrist_irq, dd); + if (err < 0) { + printk(KERN_ERR "%s: Failed to register IRQ.\n", __func__); + dd->client = NULL; + goto m4wrist_request_irq_fail; + } + + err = m4sensorhub_irq_enable(dd->client, M4SH_IRQ_WRIST_READY); + if (err < 0) { + printk(KERN_ERR "%s: Failed to enable IRQ.\n", __func__); + m4sensorhub_irq_unregister(dd->client, M4SH_IRQ_WRIST_READY); + dd->client = NULL; + goto m4wrist_request_irq_fail; + } + +m4wrist_request_irq_fail: + return err; +} + +static void m4wrist_firmware_callback(const struct firmware *fw, + void *context) +{ + struct m4wrist_driver_data *dd = context; + + mutex_lock(dd->mutex); + + if (fw == NULL) { + printk(KERN_ERR "%s: No firmware received.\n", __func__); + goto m4wrist_firmware_callback_fail; + } else if (fw->data == NULL || fw->size == 0) { + printk(KERN_ERR "%s: No data found.\n", __func__); + goto m4wrist_firmware_callback_fail; + } else if (fw->data[0] < 4) { + printk(KERN_ERR "%s: Firmware header is too small.\n", + __func__); + goto m4wrist_firmware_callback_fail; + } else if (fw->data[0] >= fw->size) { + printk(KERN_ERR "%s: Firmware data is missing.\n", __func__); + goto m4wrist_firmware_callback_fail; + } + + dd->size = fw->size - (fw->data[0] + 1); + dd->img = kzalloc(dd->size * sizeof(uint8_t), GFP_KERNEL); + if (dd->img == NULL) { + printk(KERN_ERR "%s: Failed to allocate memory for firmware.\n", + __func__); + goto m4wrist_firmware_callback_fail; + } + memcpy(dd->img, &(fw->data[fw->data[0] + 1]), dd->size); + dd->si_id[0] = fw->data[1]; + dd->si_id[1] = fw->data[2]; + dd->fw_ver[0] = fw->data[3]; + dd->fw_ver[1] = fw->data[4]; + +m4wrist_firmware_callback_fail: + release_firmware(fw); + mutex_unlock(dd->mutex); + return; +} + +static int m4wrist_remove(struct platform_device *pdev) +{ + struct m4wrist_driver_data *dd = NULL; + + dd = platform_get_drvdata(pdev); + if (dd != NULL) { + gpio_free(dd->gpio_xres); + gpio_free(dd->gpio_clk); + gpio_free(dd->gpio_data); + m4wrist_free(dd); + } + + return 0; +} + +static int m4wrist_init(void) +{ + return platform_driver_register(&m4wrist_client_driver); +} + +static void m4wrist_exit(void) +{ + platform_driver_unregister(&m4wrist_client_driver); +} + +module_init(m4wrist_init); +module_exit(m4wrist_exit); + +static void m4wrist_irq(enum m4sensorhub_irqs event, void *context) +{ + struct m4wrist_driver_data *dd = context; + int err = 0; + uint8_t irq_reason = 0x00; + uint8_t val[2] = {0x00, 0x00}; + uint8_t mask[2] = {0xFF, 0xFF}; + mutex_lock(dd->mutex); + + if (dd->img == NULL) { + printk(KERN_ERR "%s: Firmware image is missing--%s.\n", + __func__, "unable to respond to interrupts"); + err = -ENODATA; + goto m4wrist_irq_fail; + } + + err = m4sensorhub_reg_read(dd->client, + M4SH_REG_WRIST_INTERRUPTREASON, &irq_reason); + if (err < 0) { + printk(KERN_ERR "%s: Failed to read interrupt reason.\n", + __func__); + goto m4wrist_irq_fail; + } else if (err < 1) { + printk(KERN_ERR "%s: Read %d bytes instead of 1.\n", + __func__, err); + err = -EINVAL; + goto m4wrist_irq_fail; + } + + switch (irq_reason) { + case 0x00: + err = m4wrist_gpio_control(dd); + if (err < 0) { + printk(KERN_ERR + "%s: Failed to take control of GPIO lines.\n", + __func__); + goto m4wrist_irq_fail; + } + + err = m4wrist_reflash_ic(dd); + if (err < 0) { + printk(KERN_ERR "%s: Failed to reflash IC.\n", + __func__); + goto m4wrist_irq_fail; + } + + err = m4wrist_gpio_release(dd); + if (err < 0) { + printk(KERN_ERR "%s: Failed to release GPIO lines.\n", + __func__); + goto m4wrist_irq_fail; + } + + val[0] = 0x01; + err = m4sensorhub_reg_write(dd->client, + M4SH_REG_WRIST_HOSTRESPONSE, &(val[0]), &(mask[0])); + if (err < 0) { + printk(KERN_ERR "%s: Failed to write host response.\n", + __func__); + goto m4wrist_irq_fail; + } else if (err < 1) { + printk(KERN_ERR "%s: Wrote %d bytes instead of 1.\n", + __func__, err); + err = -EINVAL; + goto m4wrist_irq_fail; + } + break; + + case 0x01: + val[0] = dd->fw_ver[0]; + val[1] = dd->fw_ver[1]; + err = m4sensorhub_reg_write(dd->client, + M4SH_REG_WRIST_FMONFILE, &(val[0]), &(mask[0])); + if (err < 0) { + printk(KERN_ERR "%s: Failed to write firmware version.\n", + __func__); + goto m4wrist_irq_fail; + } else if (err < 2) { + printk(KERN_ERR "%s: Wrote %d bytes instead of 2.\n", + __func__, err); + err = -EINVAL; + goto m4wrist_irq_fail; + } + break; + + default: + printk(KERN_ERR "%s: Unexpected interrupt 0x%02X received.\n", + __func__, irq_reason); + err = -EINVAL; + goto m4wrist_irq_fail; + break; + } + + goto m4wrist_irq_pass; + +m4wrist_irq_fail: + printk(KERN_ERR "%s: IRQ handler failed with error code %d.\n", + __func__, err); + +m4wrist_irq_pass: + mutex_unlock(dd->mutex); + return; +} + +static int m4wrist_gpio_control(struct m4wrist_driver_data *dd) +{ + int err = 0; + int i = 0; + int gpio_nums[3] = {dd->gpio_xres, dd->gpio_clk, dd->gpio_data}; + char *gpio_names[3] = {"wrist_xres", "wrist_clk", "wrist_data"}; + + for (i = 0; i < 3; i++) { + err = gpio_direction_output(gpio_nums[i], 0); + if (err < 0) { + printk(KERN_ERR "%s: Failed to take control of %s.\n", + __func__, gpio_names[i]); + goto m4wrist_gpio_control_fail; + } + } + +m4wrist_gpio_control_fail: + return err; +} + +static int m4wrist_gpio_release(struct m4wrist_driver_data *dd) +{ + int err = 0; + int i = 0; + int gpio_nums[3] = {dd->gpio_xres, dd->gpio_clk, dd->gpio_data}; + char *gpio_names[3] = {"wrist_xres", "wrist_clk", "wrist_data"}; + + for (i = 0; i < 3; i++) { + err = gpio_direction_input(gpio_nums[i]); + if (err < 0) { + printk(KERN_ERR "%s: Failed to release %s.\n", + __func__, gpio_names[i]); + goto m4wrist_gpio_release_fail; + } + } + +m4wrist_gpio_release_fail: + return err; +} + +static int m4wrist_reflash_ic(struct m4wrist_driver_data *dd) +{ + int err = 0; + + err = m4wrist_enter_reset_mode(dd); + if (err < 0) { + printk(KERN_ERR "%s: Failed to enter programming mode.\n", + __func__); + goto m4wrist_reflash_ic_fail; + } + + err = m4wrist_erase_flash(dd); + if (err < 0) { + printk(KERN_ERR "%s: Failed to erase IC flash.\n", __func__); + goto m4wrist_reflash_ic_fail; + } + + err = m4wrist_program_image(dd); + if (err < 0) { + printk(KERN_ERR "%s: Failed to program firmware image", + __func__); + goto m4wrist_reflash_ic_fail; + } + + gpio_set_value(dd->gpio_xres, 1); + udelay(100); + gpio_set_value(dd->gpio_xres, 0); + +m4wrist_reflash_ic_fail: + return err; +} + +static int m4wrist_enter_reset_mode(struct m4wrist_driver_data *dd) +{ + int err = 0; + uint8_t silicon_id[2] = {0x00, 0x00}; + + msleep(20); + gpio_set_value(dd->gpio_xres, 1); + udelay(400); + gpio_set_value(dd->gpio_xres, 0); + udelay(1); + + m4wrist_send_bitstream(dd, &(m4wrist_id_setup_1[0]), + M4WRIST_ID_SETUP_1_BITS); + + err = m4wrist_wait_poll(dd); + if (err < 0) { + printk(KERN_ERR "%s: Failed to wait-and-poll 1.\n", __func__); + goto m4wrist_enter_reset_mode_fail; + } + + m4wrist_send_bitstream(dd, &(m4wrist_id_setup_2[0]), + M4WRIST_ID_SETUP_2_BITS); + + err = m4wrist_wait_poll(dd); + if (err < 0) { + printk(KERN_ERR "%s: Failed to wait-and-poll 2.\n", __func__); + goto m4wrist_enter_reset_mode_fail; + } + + m4wrist_send_bitstream(dd, &(m4wrist_sync_enable[0]), + M4WRIST_SYNC_ENABLE_BITS); + + err = m4wrist_read_id_word(dd, &(silicon_id[0])); + if (err < 0) { + printk(KERN_ERR "%s: Failed to read silicon ID\n", __func__); + goto m4wrist_enter_reset_mode_fail; + } + + m4wrist_send_bitstream(dd, &(m4wrist_sync_disable[0]), + M4WRIST_SYNC_DISABLE_BITS); + + if (silicon_id[0] != dd->si_id[0] || silicon_id[1] != dd->si_id[1]) { + printk(KERN_ERR "%s: Silicon ID mismatch (read 0x%02X%02X).\n", + __func__, silicon_id[0], silicon_id[1]); + err = -EINVAL; + goto m4wrist_enter_reset_mode_fail; + } + +m4wrist_enter_reset_mode_fail: + return err; +} + +static int m4wrist_erase_flash(struct m4wrist_driver_data *dd) +{ + int err = 0; + + m4wrist_send_bitstream(dd, &(m4wrist_erase[0]), + M4WRIST_ERASE_BITS); + + err = m4wrist_wait_poll(dd); + if (err < 0) { + printk(KERN_ERR "%s: Failed to wait-and-poll.\n", __func__); + goto m4wrist_erase_flash_fail; + } + +m4wrist_erase_flash_fail: + return err; +} + +static int m4wrist_program_image(struct m4wrist_driver_data *dd) +{ + int err = 0; + uint32_t iter = 0; + int i = 0; + uint8_t blk_num = 0x00; + uint8_t cur_addr = 0x00; + uint8_t cur_vector[3] = {0x00, 0x00, 0x00}; + uint8_t cur_blk_data[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t status[2] = {0x00, 0x00}; + + if (((dd->size % 128) != 0) || (dd->size == 0)) { + printk(KERN_ERR "%s: Firmware has invalid size of %u.\n", + __func__, dd->size); + err = -EINVAL; + goto m4wrist_program_image_fail; + } + + printk(KERN_INFO "%s: Flashing version 0x%02X 0x%02X...\n", __func__, + dd->fw_ver[0], dd->fw_ver[1]); + + while (iter < (dd->size - 1)) { + m4wrist_send_bitstream(dd, &(m4wrist_sync_enable[0]), + M4WRIST_SYNC_ENABLE_BITS); + + m4wrist_send_bitstream(dd, &(m4wrist_read_write_setup[0]), + M4WRIST_READ_WRITE_SETUP_BITS); + + cur_addr = 0x00; + for (i = 0; i < 128; i++) { + cur_vector[0] = 0x90 | ((cur_addr & 0x78) >> 3); + cur_vector[1] = ((cur_addr & 0x07) << 5) | + ((dd->img[iter] & 0xF8) >> 3); + cur_vector[2] = ((dd->img[iter] & 0x07) << 5) | 0x1C; + m4wrist_send_bitstream(dd, &(cur_vector[0]), 22); + + cur_addr++; + iter++; + } + + m4wrist_send_bitstream(dd, &(m4wrist_sync_enable[0]), + M4WRIST_SYNC_ENABLE_BITS); + + cur_blk_data[0] = 0xDE; + cur_blk_data[1] = 0xE0; + cur_blk_data[2] = 0x1E; + cur_blk_data[3] = 0x7D; + cur_blk_data[4] = (blk_num & 0xFE) >> 1; + cur_blk_data[5] = ((blk_num & 0x01) << 7) | 0x70; + m4wrist_send_bitstream(dd, &(cur_blk_data[0]), 44); + + m4wrist_send_bitstream(dd, &(m4wrist_sync_disable[0]), + M4WRIST_SYNC_DISABLE_BITS); + + m4wrist_send_bitstream(dd, &(m4wrist_program_and_verify[0]), + M4WRIST_PROGRAM_AND_VERIFY_BITS); + + err = m4wrist_wait_poll(dd); + if (err < 0) { + printk(KERN_ERR "%s: %s for block 0x%02X.\n", + __func__, "Failed to wait-and-poll", blk_num); + goto m4wrist_program_image_fail; + } + + m4wrist_send_bitstream(dd, &(m4wrist_sync_enable[0]), + M4WRIST_SYNC_ENABLE_BITS); + + err = m4wrist_read_id_word(dd, &(status[0])); + if (err < 0) { + printk(KERN_ERR "%s: %s for block 0x%02X.\n", + __func__, "Failed to read status", blk_num); + } + + m4wrist_send_bitstream(dd, &(m4wrist_sync_disable[0]), + M4WRIST_SYNC_DISABLE_BITS); + + if (status[0] != 0x00) { + printk(KERN_ERR "%s: %s 0x%02X %s 0x%02X.\n", __func__, + "Programming block", blk_num, + "failed with error code", status[0]); + err = -EINVAL; + goto m4wrist_program_image_fail; + } + + blk_num++; + } + +m4wrist_program_image_fail: + return err; +} + +static void m4wrist_send_bitstream(struct m4wrist_driver_data *dd, + uint8_t *stream, uint32_t bits) +{ + int i = 0; + int j = 0; + uint32_t bits_sent = 0; + + j = 7; + while (bits_sent < bits) { + if (stream[i] & (0x01 << j)) + gpio_set_value(dd->gpio_data, 1); + else + gpio_set_value(dd->gpio_data, 0); + + gpio_set_value(dd->gpio_clk, 1); + gpio_set_value(dd->gpio_clk, 0); + + bits_sent++; + j--; + if (j < 0) { + j = 7; + i++; + } + } + + return; +} + +static void m4wrist_toggle_clock(struct m4wrist_driver_data *dd, int cycles) +{ + int i = 0; + + for (i = 0; i < cycles; i++) { + gpio_set_value(dd->gpio_clk, 1); + gpio_set_value(dd->gpio_clk, 0); + } + + return; +} + +static int m4wrist_wait_poll(struct m4wrist_driver_data *dd) +{ + int err = 0; + int i = 0; + bool saw_event = false; + uint8_t clear[5] = {0x00, 0x00, 0x00, 0x00, 0x00}; + + err = gpio_direction_input(dd->gpio_data); + if (err < 0) { + printk(KERN_ERR "%s: Failed to release data line.\n", __func__); + goto m4wrist_wait_poll_fail; + } + + udelay(1); + + for (i = 0; i < 200000; i++) { + if (gpio_get_value(dd->gpio_data) == 1) { + saw_event = true; + break; + } else { + m4wrist_toggle_clock(dd, 1); + } + } + + if (!saw_event) { + printk(KERN_ERR "%s: Timeout waiting for data high.\n", + __func__); + err = -ETIME; + goto m4wrist_wait_poll_fail; + } + + saw_event = false; + for (i = 0; i < 200000; i++) { + if (gpio_get_value(dd->gpio_data) == 0) { + saw_event = true; + break; + } else { + udelay(1); + } + } + + if (!saw_event) { + printk(KERN_ERR "%s: Timeout waiting for data low.\n", + __func__); + err = -ETIME; + goto m4wrist_wait_poll_fail; + } + + err = gpio_direction_output(dd->gpio_data, 0); + if (err < 0) { + printk(KERN_ERR "%s: Failed to acquire data line.\n", + __func__); + goto m4wrist_wait_poll_fail; + } + + m4wrist_send_bitstream(dd, &(clear[0]), 40); + +m4wrist_wait_poll_fail: + return err; +} + +static int m4wrist_read_id_word(struct m4wrist_driver_data *dd, uint8_t *data) +{ + int err = 0; + int i = 0; + int bit = 0; + uint8_t stream1[2] = {0xBF, 0x00}; + uint8_t stream2[2] = {0xDF, 0x90}; + + m4wrist_send_bitstream(dd, &(stream1[0]), 11); + + err = gpio_direction_input(dd->gpio_data); + if (err < 0) { + printk(KERN_ERR "%s: Failed to release initial data line.\n", + __func__); + goto m4wrist_read_id_word_fail; + } + + m4wrist_toggle_clock(dd, 2); + + data[0] = 0x00; + for (i = 0; i <= 7; i++) { + gpio_set_value(dd->gpio_clk, 1); + data[0] = (data[0] << 1); + bit = gpio_get_value(dd->gpio_data); + if (bit == 1) + (data[0])++; + + gpio_set_value(dd->gpio_clk, 0); + } + + gpio_set_value(dd->gpio_clk, 1); + + err = gpio_direction_output(dd->gpio_data, 1); + if (err < 0) { + printk(KERN_ERR "%s: Failed to acquire initial data line.\n", + __func__); + goto m4wrist_read_id_word_fail; + } + + m4wrist_send_bitstream(dd, &(stream2[0]), 12); + + err = gpio_direction_input(dd->gpio_data); + if (err < 0) { + printk(KERN_ERR "%s: Failed to release final data line.\n", + __func__); + goto m4wrist_read_id_word_fail; + } + + m4wrist_toggle_clock(dd, 2); + + data[1] = 0x00; + for (i = 0; i <= 7; i++) { + gpio_set_value(dd->gpio_clk, 1); + data[1] = (data[1] << 1); + bit = gpio_get_value(dd->gpio_data); + if (bit == 1) + (data[1])++; + + gpio_set_value(dd->gpio_clk, 0); + } + + m4wrist_toggle_clock(dd, 1); + + err = gpio_direction_output(dd->gpio_data, 0); + if (err < 0) { + printk(KERN_ERR "%s: Failed to acquire final data line.\n", + __func__); + goto m4wrist_read_id_word_fail; + } + + m4wrist_toggle_clock(dd, 1); + +m4wrist_read_id_word_fail: + return err; +} + +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/m4sensorhub_wrist.h b/drivers/misc/m4sensorhub_wrist.h new file mode 100644 index 00000000000..7065935cc17 --- /dev/null +++ b/drivers/misc/m4sensorhub_wrist.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2013 Motorola Mobility, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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 + */ + +#ifndef _M4SENSORHUB_WRIST_H_ +#define _M4SENSORHUB_WRIST_H_ + +#include <linux/types.h> +#include <linux/platform_device.h> +#include <linux/m4sensorhub.h> + +struct m4wrist_driver_data { + struct m4sensorhub_data *client; + struct mutex *mutex; + struct platform_device *pdev; + + int gpio_xres; + int gpio_clk; + int gpio_data; + + uint8_t *img; + uint32_t size; + uint8_t si_id[2]; + uint8_t fw_ver[2]; +} __packed; + +#define M4WRIST_ID_SETUP_1_BITS 594 +static uint8_t m4wrist_id_setup_1[] = { + 0xCA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0D, 0xEE, 0x21, 0xF7, 0xF0, 0x27, 0xDC, 0x40, + 0x9F, 0x70, 0x01, 0xFD, 0xEE, 0x01, 0xE7, 0xC1, + 0xD7, 0x9F, 0x20, 0x7E, 0x7D, 0x88, 0x7D, 0xEE, + 0x21, 0xF7, 0xF0, 0x07, 0xDC, 0x40, 0x1F, 0x70, + 0x01, 0xFD, 0xEE, 0x01, 0xF7, 0xA0, 0x1F, 0xDE, + 0xA0, 0x1F, 0x7B, 0x00, 0x7D, 0xE0, 0x13, 0xF7, + 0xC0, 0x07, 0xDF, 0x28, 0x1F, 0x7D, 0x18, 0x7D, + 0xFE, 0x25, 0x80, +}; + +#define M4WRIST_ID_SETUP_2_BITS 418 +static uint8_t m4wrist_id_setup_2[] = { + 0xDE, 0xE2, 0x1F, 0x7F, 0x02, 0x7D, 0xC4, 0x09, + 0xF7, 0x00, 0x1F, 0x9F, 0x07, 0x5E, 0x7C, 0x81, + 0xF9, 0xF4, 0x01, 0xF7, 0xF0, 0x07, 0xDC, 0x40, + 0x1F, 0x70, 0x01, 0xFD, 0xEE, 0x01, 0xF7, 0xA0, + 0x1F, 0xDE, 0xA0, 0x1F, 0x7B, 0x00, 0x7D, 0xE0, + 0x0D, 0xF7, 0xC0, 0x07, 0xDF, 0x28, 0x1F, 0x7D, + 0x18, 0x7D, 0xFE, 0x25, 0x80, +}; + +#define M4WRIST_SYNC_ENABLE_BITS 110 +static uint8_t m4wrist_sync_enable[] = { + 0xDE, 0xE2, 0x1F, 0x7F, 0x02, 0x7D, 0xC4, 0x09, + 0xF7, 0x00, 0x1F, 0xDE, 0xE0, 0x1C, +}; + +#define M4WRIST_SYNC_DISABLE_BITS 110 +static uint8_t m4wrist_sync_disable[] = { + 0xDE, 0xE2, 0x1F, 0x71, 0x00, 0x7D, 0xFC, 0x01, + 0xF7, 0x00, 0x1F, 0xDE, 0xE0, 0x1C, +}; + +#define M4WRIST_ERASE_BITS 396 +static uint8_t m4wrist_erase[] = { + 0xDE, 0xE2, 0x1F, 0x7F, 0x02, 0x7D, 0xC4, 0x09, + 0xF7, 0x00, 0x1F, 0x9F, 0x07, 0x5E, 0x7C, 0x85, + 0xFD, 0xFC, 0x01, 0xF7, 0x10, 0x07, 0xDC, 0x00, + 0x7F, 0x7B, 0x80, 0x7D, 0xE0, 0x0B, 0xF7, 0xA0, + 0x1F, 0xDE, 0xA0, 0x1F, 0x7B, 0x04, 0x7D, 0xF0, + 0x01, 0xF7, 0xC9, 0x87, 0xDF, 0x48, 0x1F, 0x7F, + 0x89, 0x60, +}; + +#define M4WRIST_READ_WRITE_SETUP_BITS 66 +static uint8_t m4wrist_read_write_setup[] = { + 0xDE, 0xF0, 0x1F, 0x78, 0x00, 0x7D, 0xA0, 0x03, + 0xC0, +}; + +#define M4WRIST_PROGRAM_AND_VERIFY_BITS 440 +static uint8_t m4wrist_program_and_verify[] = { + 0xDE, 0xE2, 0x1F, 0x7F, 0x02, 0x7D, 0xC4, 0x09, + 0xF7, 0x00, 0x1F, 0x9F, 0x07, 0x5E, 0x7C, 0x81, + 0xF9, 0xF7, 0x01, 0xF7, 0xF0, 0x07, 0xDC, 0x40, + 0x1F, 0x70, 0x01, 0xFD, 0xEE, 0x01, 0xF6, 0xA0, + 0x0F, 0xDE, 0x80, 0x7F, 0x7A, 0x80, 0x7D, 0xEC, + 0x01, 0xF7, 0x80, 0x57, 0xDF, 0x00, 0x1F, 0x7C, + 0xA0, 0x7D, 0xF4, 0x61, 0xF7, 0xF8, 0x96, +}; + +#endif /* _M4SENSORHUB_WRIST_H_ */ diff --git a/drivers/misc/vib-gpio.c b/drivers/misc/vib-gpio.c new file mode 100644 index 00000000000..d80d34db9f3 --- /dev/null +++ b/drivers/misc/vib-gpio.c @@ -0,0 +1,224 @@ +/* drivers/misc/vib-gpio.c + * + * Copyright (C) 2009 Motorola, Inc. + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood <lockwood@android.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/hrtimer.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/vib-gpio.h> +#include <linux/workqueue.h> +#include <linux/slab.h> + +/* TODO: replace with correct header */ +#include "../staging/android/timed_output.h" + +struct vib_gpio_data { + struct timed_output_dev dev; + struct work_struct vib_work; + struct hrtimer timer; + spinlock_t lock; + + struct vib_gpio_platform_data *pdata; + + int vib_power_state; + int vib_state; +}; + +struct vib_gpio_data *misc_data; + +static void vib_gpio_set(int on) +{ + if (on) { + if (misc_data->pdata->power_on && !misc_data->vib_power_state) { + misc_data->pdata->power_on(); + misc_data->vib_power_state = 1; + } + if (misc_data->pdata->gpio >= 0) + gpio_direction_output(misc_data->pdata->gpio, + misc_data->pdata->active_low ? + 0 : 1); + } else { + if (misc_data->pdata->gpio >= 0) + gpio_direction_output(misc_data->pdata->gpio, + misc_data->pdata->active_low ? + 1 : 0); + + if (misc_data->pdata->power_off && misc_data->vib_power_state) { + misc_data->pdata->power_off(); + misc_data->vib_power_state = 0; + } + } +} + +static void vib_gpio_update(struct work_struct *work) +{ + vib_gpio_set(misc_data->vib_state); +} + +static enum hrtimer_restart gpio_timer_func(struct hrtimer *timer) +{ + struct vib_gpio_data *data = + container_of(timer, struct vib_gpio_data, timer); + data->vib_state = 0; + schedule_work(&data->vib_work); + return HRTIMER_NORESTART; +} + +static int vib_gpio_get_time(struct timed_output_dev *dev) +{ + struct vib_gpio_data *data = + container_of(dev, struct vib_gpio_data, dev); + + if (hrtimer_active(&data->timer)) { + ktime_t r = hrtimer_get_remaining(&data->timer); + struct timeval t = ktime_to_timeval(r); + return t.tv_sec * 1000 + t.tv_usec / 1000; + } else + return 0; +} + +static void vib_gpio_enable(struct timed_output_dev *dev, int value) +{ + struct vib_gpio_data *data = + container_of(dev, struct vib_gpio_data, dev); + unsigned long flags; + + spin_lock_irqsave(&data->lock, flags); + hrtimer_cancel(&data->timer); + + if (value == 0) + data->vib_state = 0; + else { + value = (value > data->pdata->max_timeout ? + data->pdata->max_timeout : value); + data->vib_state = 1; + hrtimer_start(&data->timer, + ktime_set(value / 1000, (value % 1000) * 1000000), + HRTIMER_MODE_REL); + } + + spin_unlock_irqrestore(&data->lock, flags); + + schedule_work(&data->vib_work); +} + +/* This is a temporary solution until a more global haptics soltion is + * available for haptics that need to occur in any application */ +void vibrator_haptic_fire(int value) +{ + vib_gpio_enable(&misc_data->dev, value); +} + +static int vib_gpio_probe(struct platform_device *pdev) +{ + struct vib_gpio_platform_data *pdata = pdev->dev.platform_data; + struct vib_gpio_data *gpio_data; + int ret = 0; + + if (!pdata) { + ret = -EBUSY; + goto err0; + } + gpio_data = kzalloc(sizeof(struct vib_gpio_data), GFP_KERNEL); + if (!gpio_data) { + ret = -ENOMEM; + goto err0; + } + + gpio_data->pdata = pdata; + + INIT_WORK(&gpio_data->vib_work, vib_gpio_update); + + hrtimer_init(&gpio_data->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + + gpio_data->timer.function = gpio_timer_func; + spin_lock_init(&gpio_data->lock); + + gpio_data->dev.name = "vibrator"; + gpio_data->dev.get_time = vib_gpio_get_time; + gpio_data->dev.enable = vib_gpio_enable; + ret = timed_output_dev_register(&gpio_data->dev); + if (ret < 0) + goto err1; + + if (gpio_data->pdata->init) + ret = gpio_data->pdata->init(); + if (ret < 0) + goto err2; + + misc_data = gpio_data; + if (misc_data->pdata->gpio >= 0) + gpio_direction_output(gpio_data->pdata->gpio, + gpio_data->pdata->active_low); + + platform_set_drvdata(pdev, gpio_data); + + vib_gpio_enable(&gpio_data->dev, gpio_data->pdata->initial_vibrate); + + pr_info("vib gpio probe done"); + return 0; + +err2: + timed_output_dev_unregister(&gpio_data->dev); +err1: + kfree(gpio_data->pdata); + kfree(gpio_data); +err0: + return ret; +} + +static int vib_gpio_remove(struct platform_device *pdev) +{ + struct vib_gpio_data *gpio_data = platform_get_drvdata(pdev); + + if (gpio_data->pdata->exit) + gpio_data->pdata->exit(); + + timed_output_dev_unregister(&gpio_data->dev); + + kfree(gpio_data->pdata); + kfree(gpio_data); + + return 0; +} + +static struct platform_driver vib_gpio_driver = { + .probe = vib_gpio_probe, + .remove = vib_gpio_remove, + .driver = { + .name = VIB_GPIO_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init vib_gpio_init(void) +{ + return platform_driver_register(&vib_gpio_driver); +} + +static void __exit vib_gpio_exit(void) +{ + platform_driver_unregister(&vib_gpio_driver); +} + +late_initcall(vib_gpio_init); +module_exit(vib_gpio_exit); + +MODULE_AUTHOR("Motorola"); +MODULE_DESCRIPTION("vib gpio driver"); +MODULE_LICENSE("GPL"); |