diff options
| author | Doug Zobel <dzobel1@motorola.com> | 2013-11-15 14:29:07 -0600 |
|---|---|---|
| committer | James Wylder <jwylder@motorola.com> | 2014-03-05 17:46:52 -0600 |
| commit | d2a782003a6047da120a33e6f8ee6fd33bb825d6 (patch) | |
| tree | 8d20bd4ecda62a06e98993c4108456bc1acb0d0b /drivers | |
| parent | 32fd2d36d2464056d4522a9c02797b7c2b2e884f (diff) | |
| download | olio-linux-3.10-d2a782003a6047da120a33e6f8ee6fd33bb825d6.tar.xz olio-linux-3.10-d2a782003a6047da120a33e6f8ee6fd33bb825d6.zip | |
CW integration and minnow bringup
* create minnow machine type
* create Android makefile
* add pre-commit syntax check
* enable -Werror
* Add drivers: CPCAP, TPS65xxx, m4sensorhub, atmxt, lm3535,
usb gadget, minnow display, TI 12xx wireless
Change-Id: I7962f5e1256715f2452aed5a62a4f2f2383d5046
Diffstat (limited to 'drivers')
64 files changed, 24032 insertions, 29 deletions
diff --git a/drivers/dma/of-dma.c b/drivers/dma/of-dma.c index 7aa0864cd48..4dd80703513 100644 --- a/drivers/dma/of-dma.c +++ b/drivers/dma/of-dma.c @@ -64,7 +64,7 @@ int of_dma_controller_register(struct device_node *np, void *data) { struct of_dma *ofdma; - int nbcells; + int nbcells = 0; const __be32 *prop; if (!np || !of_dma_xlate) { diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index f9a5fd89bc0..bb3895a81c9 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -11,6 +11,14 @@ menuconfig INPUT_TOUCHSCREEN if INPUT_TOUCHSCREEN +config TOUCHSCREEN_ATMXT + tristate "Atmel mXT Touchscreen Driver" + depends on I2C + help + Say Y here if you have an Atmel mXT touchscreen. + + If unsure, say N. + config TOUCHSCREEN_88PM860X tristate "Marvell 88PM860x touchscreen" depends on MFD_88PM860X diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 6bfbeab67c9..5f41086e3b1 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -72,3 +72,4 @@ obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE) += mainstone-wm97xx.o obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE) += zylonite-wm97xx.o obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o obj-$(CONFIG_TOUCHSCREEN_TPS6507X) += tps6507x-ts.o +obj-$(CONFIG_TOUCHSCREEN_ATMXT) += atmxt.o diff --git a/drivers/input/touchscreen/atmxt.c b/drivers/input/touchscreen/atmxt.c new file mode 100644 index 00000000000..e872282c258 --- /dev/null +++ b/drivers/input/touchscreen/atmxt.c @@ -0,0 +1,3786 @@ +/* + * Copyright (C) 2010-2012 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 + */ + +/* Driver for Atmel maXTouch touchscreens that uses tdat files */ +#include "atmxt.h" +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/init.h> +#include <linux/gpio.h> +#include <linux/device.h> +#include <linux/stat.h> +#include <linux/string.h> +#include <linux/firmware.h> +#ifdef CONFIG_HAS_EARLYSUSPEND +#include <linux/earlysuspend.h> +#endif + +#define FAMILY_ID 0x82 + +static int atmxt_probe(struct i2c_client *client, + const struct i2c_device_id *id); +static int atmxt_remove(struct i2c_client *client); +static int atmxt_suspend(struct i2c_client *client, pm_message_t message); +static int atmxt_resume(struct i2c_client *client); +#ifdef CONFIG_HAS_EARLYSUSPEND +static void atmxt_early_suspend(struct early_suspend *handler); +static void atmxt_late_resume(struct early_suspend *handler); +#endif +static int atmxt_init(void); +static void atmxt_exit(void); +static void atmxt_free(struct atmxt_driver_data *dd); +static void atmxt_free_ic_data(struct atmxt_driver_data *dd); +static void atmxt_set_drv_state(struct atmxt_driver_data *dd, + enum atmxt_driver_state state); +static int atmxt_get_drv_state(struct atmxt_driver_data *dd); +static void atmxt_set_ic_state(struct atmxt_driver_data *dd, + enum atmxt_ic_state state); +static int atmxt_get_ic_state(struct atmxt_driver_data *dd); +static int atmxt_verify_pdata(struct atmxt_driver_data *dd); +static int atmxt_request_tdat(struct atmxt_driver_data *dd); +static void atmxt_tdat_callback(const struct firmware *tdat, void *context); +static int atmxt_validate_tdat(const struct firmware *tdat); +static int atmxt_validate_settings(uint8_t *data, uint32_t size); +static int atmxt_gpio_init(struct atmxt_driver_data *dd); +static int atmxt_register_inputs(struct atmxt_driver_data *dd, + uint8_t *rdat, int rsize); +static int atmxt_request_irq(struct atmxt_driver_data *dd); +static int atmxt_restart_ic(struct atmxt_driver_data *dd); +static irqreturn_t atmxt_isr(int irq, void *handle); +static int atmxt_get_info_header(struct atmxt_driver_data *dd); +static int atmxt_get_object_table(struct atmxt_driver_data *dd); +static int atmxt_process_object_table(struct atmxt_driver_data *dd); +static uint8_t *atmxt_get_settings_entry(struct atmxt_driver_data *dd, + uint16_t num); +static int atmxt_copy_platform_data(uint8_t *reg, uint8_t *entry, + uint8_t *tsett); +static int atmxt_check_settings(struct atmxt_driver_data *dd, bool *reset); +static int atmxt_send_settings(struct atmxt_driver_data *dd, bool save_nvm); +static int atmxt_recalibrate_ic(struct atmxt_driver_data *dd); +static int atmxt_start_ic_calibration_fix(struct atmxt_driver_data *dd); +static int atmxt_verify_ic_calibration_fix(struct atmxt_driver_data *dd); +static int atmxt_stop_ic_calibration_fix(struct atmxt_driver_data *dd); +static int atmxt_i2c_write(struct atmxt_driver_data *dd, + uint8_t addr_lo, uint8_t addr_hi, uint8_t *buf, int size); +static int atmxt_i2c_read(struct atmxt_driver_data *dd, uint8_t *buf, int size); +static int atmxt_save_internal_data(struct atmxt_driver_data *dd); +static int atmxt_save_data5(struct atmxt_driver_data *dd, uint8_t *entry); +static int atmxt_save_data6(struct atmxt_driver_data *dd, uint8_t *entry); +static int atmxt_save_data7(struct atmxt_driver_data *dd, + uint8_t *entry, uint8_t *reg); +static int atmxt_save_data8(struct atmxt_driver_data *dd, + uint8_t *entry, uint8_t *reg); +static int atmxt_save_data9(struct atmxt_driver_data *dd, + uint8_t *entry, uint8_t *reg); +static void atmxt_compute_checksum(struct atmxt_driver_data *dd); +static void atmxt_compute_partial_checksum(uint8_t *byte1, uint8_t *byte2, + uint8_t *low, uint8_t *mid, uint8_t *high); +static void atmxt_active_handler(struct atmxt_driver_data *dd); +static int atmxt_process_message(struct atmxt_driver_data *dd, + uint8_t *msg, uint8_t size); +static void atmxt_report_touches(struct atmxt_driver_data *dd); +static void atmxt_release_touches(struct atmxt_driver_data *dd); +static int atmxt_message_handler6(struct atmxt_driver_data *dd, + uint8_t *msg, uint8_t size); +static int atmxt_message_handler9(struct atmxt_driver_data *dd, + uint8_t *msg, uint8_t size); +static int atmxt_message_handler42(struct atmxt_driver_data *dd, + uint8_t *msg, uint8_t size); +static int atmxt_resume_restart(struct atmxt_driver_data *dd); +static int atmxt_force_bootloader(struct atmxt_driver_data *dd); +static bool atmxt_check_firmware_update(struct atmxt_driver_data *dd); +static int atmxt_validate_firmware(uint8_t *data, uint32_t size); +static int atmxt_flash_firmware(struct atmxt_driver_data *dd); +static char *atmxt_msg2str(const uint8_t *msg, uint8_t size); +static bool atmxt_wait4irq(struct atmxt_driver_data *dd); +static int atmxt_create_sysfs_files(struct atmxt_driver_data *dd); +static void atmxt_remove_sysfs_files(struct atmxt_driver_data *dd); + +static const struct i2c_device_id atmxt_id[] = { + /* This name must match the i2c_board_info name */ + { ATMXT_I2C_NAME, 0 }, { } +}; + +MODULE_DEVICE_TABLE(i2c, atmxt_id); + +#ifdef CONFIG_OF +static struct of_device_id atmxt_match_tbl[] = { + { .compatible = "atmel,atmxt-ts" }, + { }, +}; +MODULE_DEVICE_TABLE(of, atmxt_match_tbl); +#endif + +static struct i2c_driver atmxt_driver = { + .driver = { + .name = ATMXT_I2C_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_OF + .of_match_table = of_match_ptr(atmxt_match_tbl), +#endif + }, + .probe = atmxt_probe, + .remove = atmxt_remove, + .id_table = atmxt_id, +#ifndef CONFIG_HAS_EARLYSUSPEND + .suspend = atmxt_suspend, + .resume = atmxt_resume, +#endif +}; + +#ifdef CONFIG_OF +static struct touch_platform_data * +atmxt_of_init(struct i2c_client *client) +{ + struct touch_platform_data *pdata; + struct device_node *np = client->dev.of_node; + const char *fp = NULL; + + pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(&client->dev, "pdata allocation failure\n"); + return NULL; + } + + of_property_read_string(np, "atmel,atmxt-tdat-filename", &fp); + + pdata->filename = (char *)fp; + pdata->gpio_interrupt = of_get_gpio(np, 0); + pdata->gpio_reset = of_get_gpio(np, 1); + + return pdata; +} +#else +static inline struct touch_platform_data * +atmxt_of_init(struct i2c_client *client) +{ + return NULL; +} +#endif + +static int atmxt_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct atmxt_driver_data *dd = NULL; + int err = 0; + bool debugfail = false; + + printk(KERN_INFO "%s: Driver: %s, Version: %s, Date: %s\n", __func__, + ATMXT_I2C_NAME, ATMXT_DRIVER_VERSION, ATMXT_DRIVER_DATE); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + printk(KERN_ERR "%s: Missing I2C adapter support.\n", __func__); + err = -ENODEV; + goto atmxt_probe_fail; + } + + dd = kzalloc(sizeof(struct atmxt_driver_data), GFP_KERNEL); + if (dd == NULL) { + printk(KERN_ERR "%s: Unable to create driver data.\n", + __func__); + err = -ENOMEM; + goto atmxt_probe_fail; + } + + dd->drv_stat = ATMXT_DRV_INIT; + dd->ic_stat = ATMXT_IC_UNKNOWN; + dd->status = 0x0000; + dd->client = client; + + if (client->dev.of_node) + dd->pdata = atmxt_of_init(client); + else + dd->pdata = client->dev.platform_data; + + if (!dd->pdata) { + printk(KERN_ERR "%s: No platform data found.\n", + __func__); + err = -EINVAL; + goto atmxt_probe_fail; + } + + i2c_set_clientdata(client, dd); + dd->in_dev = NULL; + + 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 atmxt_probe_fail; + } + mutex_init(dd->mutex); + +#ifdef CONFIG_TOUCHSCREEN_DEBUG + dd->dbg = kzalloc(sizeof(struct atmxt_debug), GFP_KERNEL); + if (dd->dbg == NULL) { + printk(KERN_ERR "%s: Unable to create driver debug data.\n", + __func__); + err = -ENOMEM; + goto atmxt_probe_fail; + } + dd->dbg->dbg_lvl = ATMXT_DBG0; +#endif + + dd->util = kzalloc(sizeof(struct atmxt_util_data), GFP_KERNEL); + if (dd->util == NULL) { + printk(KERN_ERR "%s: Unable to create touch utility data.\n", + __func__); + err = -ENOMEM; + goto atmxt_probe_fail; + } + + err = atmxt_verify_pdata(dd); + if (err < 0) + goto atmxt_probe_fail; + + err = atmxt_gpio_init(dd); + if (err < 0) + goto atmxt_probe_fail; + +#ifdef CONFIG_HAS_EARLYSUSPEND + dd->es.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; + dd->es.suspend = atmxt_early_suspend; + dd->es.resume = atmxt_late_resume; + register_early_suspend(&dd->es); +#endif + + err = atmxt_request_irq(dd); + if (err < 0) + goto atmxt_unreg_suspend; + + err = atmxt_request_tdat(dd); + if (err < 0) + goto atmxt_free_irq; + + err = atmxt_create_sysfs_files(dd); + if (err < 0) { + printk(KERN_ERR + "%s: Probe had error %d when creating sysfs files.\n", + __func__, err); + debugfail = true; + } + + goto atmxt_probe_pass; + +atmxt_free_irq: + free_irq(dd->client->irq, dd); +atmxt_unreg_suspend: + gpio_free(dd->pdata->gpio_reset); + gpio_free(dd->pdata->gpio_interrupt); +atmxt_probe_fail: + atmxt_free(dd); + printk(KERN_ERR "%s: Probe failed with error code %d.\n", + __func__, err); + return err; + +atmxt_probe_pass: + if (debugfail) { + printk(KERN_INFO "%s: Probe completed with errors.\n", + __func__); + } else { + printk(KERN_INFO "%s: Probe successful.\n", __func__); + } + return 0; +} + +static int atmxt_remove(struct i2c_client *client) +{ + struct atmxt_driver_data *dd = NULL; + + dd = i2c_get_clientdata(client); + if (dd != NULL) { + free_irq(dd->client->irq, dd); + atmxt_remove_sysfs_files(dd); + gpio_free(dd->pdata->gpio_reset); + gpio_free(dd->pdata->gpio_interrupt); + atmxt_free(dd); + } + + i2c_set_clientdata(client, NULL); + + return 0; +} + +static int atmxt_suspend(struct i2c_client *client, pm_message_t message) +{ + int err = 0; + struct atmxt_driver_data *dd; + int drv_state; + int ic_state; + uint8_t sleep_cmd[2] = {0x00, 0x00}; + + dd = i2c_get_clientdata(client); + if (dd == NULL) { + printk(KERN_ERR "%s: Driver data is missing.\n", __func__); + err = -ENODATA; + goto atmxt_suspend_no_dd_fail; + } + + mutex_lock(dd->mutex); + + atmxt_dbg(dd, ATMXT_DBG3, "%s: Suspending...\n", __func__); + + drv_state = atmxt_get_drv_state(dd); + ic_state = atmxt_get_ic_state(dd); + + switch (drv_state) { + case ATMXT_DRV_ACTIVE: + case ATMXT_DRV_IDLE: + switch (ic_state) { + case ATMXT_IC_ACTIVE: + atmxt_dbg(dd, ATMXT_DBG3, + "%s: Putting touch IC to sleep...\n", __func__); + dd->status = dd->status & + ~(1 << ATMXT_FIXING_CALIBRATION); + err = atmxt_i2c_write(dd, + dd->addr->pwr[0], dd->addr->pwr[1], + &(sleep_cmd[0]), 2); + if (err < 0) { + printk(KERN_ERR + "%s: %s %s %d.\n", __func__, + "Failed to put touch IC to sleep", + "with error code", err); + goto atmxt_suspend_fail; + } else { + atmxt_set_ic_state(dd, ATMXT_IC_SLEEP); + } + break; + default: + printk(KERN_ERR "%s: Driver %s, IC %s suspend.\n", + __func__, atmxt_driver_state_string[drv_state], + atmxt_ic_state_string[ic_state]); + } + break; + + default: + printk(KERN_ERR "%s: Driver state \"%s\" suspend.\n", + __func__, atmxt_driver_state_string[drv_state]); + } + + atmxt_dbg(dd, ATMXT_DBG3, "%s: Suspend complete.\n", __func__); + +atmxt_suspend_fail: + mutex_unlock(dd->mutex); + +atmxt_suspend_no_dd_fail: + return err; +} + +static int atmxt_resume(struct i2c_client *client) +{ + int err = 0; + struct atmxt_driver_data *dd; + int drv_state; + int ic_state; + + dd = i2c_get_clientdata(client); + if (dd == NULL) { + printk(KERN_ERR "%s: Driver data is missing.\n", __func__); + err = -ENODATA; + goto atmxt_resume_no_dd_fail; + } + + mutex_lock(dd->mutex); + + atmxt_dbg(dd, ATMXT_DBG3, "%s: Resuming...\n", __func__); + + drv_state = atmxt_get_drv_state(dd); + ic_state = atmxt_get_ic_state(dd); + + switch (drv_state) { + case ATMXT_DRV_ACTIVE: + case ATMXT_DRV_IDLE: + switch (ic_state) { + case ATMXT_IC_ACTIVE: + printk(KERN_ERR "%s: Driver %s, IC %s resume.\n", + __func__, atmxt_driver_state_string[drv_state], + atmxt_ic_state_string[ic_state]); + break; + case ATMXT_IC_SLEEP: + atmxt_dbg(dd, ATMXT_DBG3, + "%s: Waking touch IC...\n", __func__); + err = atmxt_i2c_write(dd, + dd->addr->pwr[0], dd->addr->pwr[1], + &(dd->data->pwr[0]), 2); + if (err < 0) { + printk(KERN_ERR + "%s: Failed to wake touch IC %s %d.\n", + __func__, "with error code", err); + err = atmxt_resume_restart(dd); + if (err < 0) { + printk(KERN_ERR + "%s: %s %s %d.\n", + __func__, + "Failed restart after resume", + "with error code", err); + } + goto atmxt_resume_fail; + } else { + atmxt_set_ic_state(dd, ATMXT_IC_ACTIVE); + } + err = atmxt_start_ic_calibration_fix(dd); + if (err < 0) { + printk(KERN_ERR "%s: %s %s %d.\n", __func__, + "Failed to start calibration fix", + "with error code", err); + goto atmxt_resume_fail; + } + err = atmxt_recalibrate_ic(dd); + if (err < 0) { + printk(KERN_ERR + "%s: Recalibration failed %s %d.\n", + __func__, "with error code", err); + goto atmxt_resume_fail; + } + atmxt_release_touches(dd); + break; + default: + printk(KERN_ERR "%s: Driver %s, IC %s resume--%s...\n", + __func__, atmxt_driver_state_string[drv_state], + atmxt_ic_state_string[ic_state], "recovering"); + err = atmxt_resume_restart(dd); + if (err < 0) { + printk(KERN_ERR "%s: Recovery failed %s %d.\n", + __func__, "with error code", err); + goto atmxt_resume_fail; + } + } + break; + + case ATMXT_DRV_INIT: + printk(KERN_ERR "%s: Driver state \"%s\" resume.\n", + __func__, atmxt_driver_state_string[drv_state]); + break; + + default: + printk(KERN_ERR "%s: Driver %s, IC %s resume--%s...\n", + __func__, atmxt_driver_state_string[drv_state], + atmxt_ic_state_string[ic_state], "recovering"); + err = atmxt_resume_restart(dd); + if (err < 0) { + printk(KERN_ERR "%s: Recovery failed %s %d.\n", + __func__, "with error code", err); + goto atmxt_resume_fail; + } + break; + } + + atmxt_dbg(dd, ATMXT_DBG3, "%s: Resume complete.\n", __func__); + +atmxt_resume_fail: + mutex_unlock(dd->mutex); + +atmxt_resume_no_dd_fail: + return err; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void atmxt_early_suspend(struct early_suspend *handler) +{ + int err = 0; + struct atmxt_driver_data *dd; + + dd = container_of(handler, struct atmxt_driver_data, es); + + err = atmxt_suspend(dd->client, PMSG_SUSPEND); + if (err < 0) { + printk(KERN_ERR "%s: Suspend failed with error code %d", + __func__, err); + } + + return; +} +static void atmxt_late_resume(struct early_suspend *handler) +{ + int err = 0; + struct atmxt_driver_data *dd; + + dd = container_of(handler, struct atmxt_driver_data, es); + + err = atmxt_resume(dd->client); + if (err < 0) { + printk(KERN_ERR "%s: Resume failed with error code %d", + __func__, err); + } + + return; +} +#endif + +static int __init atmxt_init(void) +{ + return i2c_add_driver(&atmxt_driver); +} + +static void __exit atmxt_exit(void) +{ + i2c_del_driver(&atmxt_driver); + return; +} + +module_init(atmxt_init); +module_exit(atmxt_exit); + +static void atmxt_free(struct atmxt_driver_data *dd) +{ + if (dd != NULL) { + dd->pdata = NULL; + dd->client = NULL; +#ifdef CONFIG_HAS_EARLYSUSPEND + if (dd->es.link.prev != NULL && dd->es.link.next != NULL) + unregister_early_suspend(&dd->es); +#endif + + if (dd->mutex != NULL) { + kfree(dd->mutex); + dd->mutex = NULL; + } + + if (dd->util != NULL) { + kfree(dd->util->data); + dd->util->data = NULL; + kfree(dd->util); + dd->util = NULL; + } + + if (dd->in_dev != NULL) { + input_unregister_device(dd->in_dev); + dd->in_dev = NULL; + } + + atmxt_free_ic_data(dd); + + if (dd->rdat != NULL) { + kfree(dd->rdat); + dd->rdat = NULL; + } + + if (dd->dbg != NULL) { + kfree(dd->dbg); + dd->dbg = NULL; + } + + kfree(dd); + dd = NULL; + } + + return; +} + +static void atmxt_free_ic_data(struct atmxt_driver_data *dd) +{ + if (dd->info_blk != NULL) { + kfree(dd->info_blk->data); + dd->info_blk->data = NULL; + kfree(dd->info_blk->msg_id); + dd->info_blk->msg_id = NULL; + kfree(dd->info_blk); + dd->info_blk = NULL; + } + + if (dd->nvm != NULL) { + kfree(dd->nvm->data); + dd->nvm->data = NULL; + kfree(dd->nvm); + dd->nvm = NULL; + } + + if (dd->addr != NULL) { + kfree(dd->addr); + dd->addr = NULL; + } + + if (dd->data != NULL) { + kfree(dd->data); + dd->data = NULL; + } + + return; +} + +static void atmxt_set_drv_state(struct atmxt_driver_data *dd, + enum atmxt_driver_state state) +{ + printk(KERN_INFO "%s: Driver state %s -> %s\n", __func__, + atmxt_driver_state_string[dd->drv_stat], + atmxt_driver_state_string[state]); + dd->drv_stat = state; + return; +} + +static int atmxt_get_drv_state(struct atmxt_driver_data *dd) +{ + return dd->drv_stat; +} + +static void atmxt_set_ic_state(struct atmxt_driver_data *dd, + enum atmxt_ic_state state) +{ + printk(KERN_INFO "%s: IC state %s -> %s\n", __func__, + atmxt_ic_state_string[dd->ic_stat], + atmxt_ic_state_string[state]); + dd->ic_stat = state; + return; +} + +static int atmxt_get_ic_state(struct atmxt_driver_data *dd) +{ + return dd->ic_stat; +} + +static int atmxt_verify_pdata(struct atmxt_driver_data *dd) +{ + int err = 0; + + atmxt_dbg(dd, ATMXT_DBG3, "%s: Verifying platform data...\n", __func__); + + if (dd->pdata == NULL) { + printk(KERN_ERR "%s: Platform data is missing.\n", __func__); + err = -ENODATA; + goto atmxt_verify_pdata_fail; + } + + if (dd->pdata->gpio_reset == 0) { + printk(KERN_ERR "%s: Reset GPIO is invalid.\n", __func__); + err = -EINVAL; + goto atmxt_verify_pdata_fail; + } + + if (dd->pdata->gpio_interrupt == 0) { + printk(KERN_ERR "%s: Interrupt GPIO is invalid.\n", __func__); + err = -EINVAL; + goto atmxt_verify_pdata_fail; + } + + if (dd->pdata->filename == NULL) { + printk(KERN_ERR "%s: Touch data filename is missing.\n", + __func__); + err = -ENODATA; + goto atmxt_verify_pdata_fail; + } + +atmxt_verify_pdata_fail: + return err; +} + +static int atmxt_request_tdat(struct atmxt_driver_data *dd) +{ + int err = 0; + + atmxt_dbg(dd, ATMXT_DBG3, "%s: Requesting tdat data...%s\n", __func__, + dd->pdata->filename); + + err = request_firmware_nowait(THIS_MODULE, + FW_ACTION_HOTPLUG, dd->pdata->filename, &(dd->client->dev), + GFP_KERNEL, dd, atmxt_tdat_callback); + if (err < 0) { + printk(KERN_ERR "%s: Failed to schedule tdat request.\n", + __func__); + goto atmxt_request_tdat_fail; + } + +atmxt_request_tdat_fail: + return err; +} + +static void atmxt_tdat_callback(const struct firmware *tdat, void *context) +{ + int err = 0; + struct atmxt_driver_data *dd = context; + bool icfail = false; + uint8_t cur_id = 0x00; + uint32_t cur_size = 0; + uint8_t *cur_data = NULL; + size_t loc = 0; + + mutex_lock(dd->mutex); + +#ifdef CONFIG_TOUCHSCREEN_DEBUG + if (dd->status & (1 << ATMXT_WAITING_FOR_TDAT)) { + printk(KERN_INFO "%s: Processing new tdat file...\n", __func__); + dd->status = dd->status & ~(1 << ATMXT_WAITING_FOR_TDAT); + } else { + printk(KERN_INFO "%s: Processing %s...\n", __func__, + dd->pdata->filename); + } +#else + printk(KERN_INFO "%s: Processing %s...\n", __func__, + dd->pdata->filename); +#endif + + if (tdat == NULL) { + printk(KERN_ERR "%s: No data received.\n", __func__); + err = -ENODATA; + goto atmxt_tdat_callback_fail; + } + + err = atmxt_validate_tdat(tdat); + if (err < 0) + goto atmxt_tdat_callback_fail; + +#ifdef CONFIG_TOUCHSCREEN_DEBUG + if (atmxt_get_drv_state(dd) != ATMXT_DRV_INIT) { + if (dd->status & (1 << ATMXT_IRQ_ENABLED_FLAG)) { + disable_irq_nosync(dd->client->irq); + dd->status = dd->status & + ~(1 << ATMXT_IRQ_ENABLED_FLAG); + } + atmxt_set_drv_state(dd, ATMXT_DRV_INIT); + } + + if (dd->util->data != NULL) { + kfree(dd->util->data); + dd->util->data = NULL; + dd->util->size = 0; + dd->util->tsett = NULL; + dd->util->tsett_size = 0; + dd->util->fw = NULL; + dd->util->fw_size = 0; + dd->util->addr[0] = 0x00; + dd->util->addr[1] = 0x00; + } +#endif + + dd->util->data = kzalloc(tdat->size * sizeof(uint8_t), GFP_KERNEL); + if (dd->util->data == NULL) { + printk(KERN_ERR "%s: Unable to copy tdat.\n", __func__); + err = -ENOMEM; + goto atmxt_tdat_callback_fail; + } + memcpy(dd->util->data, tdat->data, tdat->size); + dd->util->size = tdat->size; + + atmxt_dbg(dd, ATMXT_DBG3, "%s: MCV is 0x%02X.\n", __func__, + dd->util->data[loc]); + loc++; + + while (loc < dd->util->size) { + cur_id = dd->util->data[loc]; + cur_size = (dd->util->data[loc+3] << 16) | + (dd->util->data[loc+2] << 8) | + dd->util->data[loc+1]; + cur_data = &(dd->util->data[loc+4]); + + switch (cur_id) { + case 0x00: + break; + + case 0x01: + dd->util->tsett = cur_data; + dd->util->tsett_size = cur_size; + break; + + case 0x02: + dd->util->fw = cur_data; + dd->util->fw_size = cur_size; + break; + + case 0x03: + if (cur_size == 0 || cur_size % 10 != 0) { + printk(KERN_ERR + "%s: Abs data format is invalid.\n", + __func__); + err = -EINVAL; + goto atmxt_tdat_callback_fail; + } + + err = atmxt_register_inputs(dd, cur_data, cur_size); + if (err < 0) + goto atmxt_tdat_callback_fail; + break; + + case 0x04: + if (cur_size < 4) { + printk(KERN_ERR + "%s: Driver data is too small.\n", + __func__); + err = -EINVAL; + goto atmxt_tdat_callback_fail; + } + dd->settings = (cur_data[1] << 8) | cur_data[0]; + dd->util->addr[0] = cur_data[2]; + dd->util->addr[1] = cur_data[3]; + dd->client->addr = dd->util->addr[0]; + break; + + default: + printk(KERN_ERR "%s: Record %hu found but not used.\n", + __func__, cur_id); + break; + } + + loc = loc + cur_size + 4; + } + + err = atmxt_validate_settings(dd->util->tsett, dd->util->tsett_size); + if (err < 0) + goto atmxt_tdat_callback_fail; + + err = atmxt_validate_firmware(dd->util->fw, dd->util->fw_size); + if (err < 0) + goto atmxt_tdat_callback_fail; + + err = atmxt_restart_ic(dd); + if (err < 0) { + printk(KERN_ERR + "%s: Restarting IC failed with error code %d.\n", + __func__, err); + icfail = true; + } + + if (!icfail) { + atmxt_set_drv_state(dd, ATMXT_DRV_ACTIVE); + dd->status = dd->status | (1 << ATMXT_IRQ_ENABLED_FLAG); + enable_irq(dd->client->irq); + printk(KERN_INFO "%s: Touch initialization successful.\n", + __func__); + } else { + atmxt_set_drv_state(dd, ATMXT_DRV_IDLE); + printk(KERN_INFO + "%s: Touch initialization completed with errors.\n", + __func__); + } + + goto atmxt_tdat_callback_exit; + +atmxt_tdat_callback_fail: + printk(KERN_ERR "%s: Touch initialization failed with error code %d.\n", + __func__, err); + +atmxt_tdat_callback_exit: + release_firmware(tdat); + mutex_unlock(dd->mutex); + + return; +} + +static int atmxt_validate_tdat(const struct firmware *tdat) +{ + int err = 0; + int length = 0; + size_t loc = 0; + + if (tdat->data == NULL || tdat->size == 0) { + printk(KERN_ERR "%s: No data found.\n", __func__); + err = -ENODATA; + goto atmxt_validate_tdat_fail; + } + + if (tdat->data[loc] != 0x31) { + printk(KERN_ERR "%s: MCV 0x%02X is not supported.\n", + __func__, tdat->data[loc]); + err = -EINVAL; + goto atmxt_validate_tdat_fail; + } + loc++; + + while (loc < (tdat->size - 3)) { + length = (tdat->data[loc+3] << 16) | + (tdat->data[loc+2] << 8) | + tdat->data[loc+1]; + if ((loc + length + 4) > tdat->size) { + printk(KERN_ERR + "%s: Overflow in data at byte %u %s %hu.\n", + __func__, loc, "and record", tdat->data[loc]); + err = -EOVERFLOW; + goto atmxt_validate_tdat_fail; + } + + loc = loc + length + 4; + } + + if (loc != (tdat->size)) { + printk(KERN_ERR "%s: Data is misaligned.\n", __func__); + err = -ENOEXEC; + goto atmxt_validate_tdat_fail; + } + +atmxt_validate_tdat_fail: + return err; +} + +static int atmxt_validate_settings(uint8_t *data, uint32_t size) +{ + int err = 0; + uint32_t iter = 0; + uint16_t length = 0x0000; + + if (data == NULL || size == 0) { + printk(KERN_ERR "%s: No settings data found.\n", __func__); + err = -ENODATA; + goto atmxt_validate_settings_fail; + } else if (size <= 5) { + printk(KERN_ERR "%s: Settings data is malformed.\n", __func__); + err = -EINVAL; + goto atmxt_validate_settings_fail; + } + + while (iter < (size - 1)) { + length = (data[iter+4] << 8) | data[iter+3]; + if ((iter + length + 5) > size) { + printk(KERN_ERR + "%s: Group record overflow on iter %u.\n", + __func__, iter); + err = -EOVERFLOW; + goto atmxt_validate_settings_fail; + } + + iter = iter + length + 5; + } + + if (iter != size) { + printk(KERN_ERR "%s: Group records misaligned.\n", __func__); + err = -ENOEXEC; + goto atmxt_validate_settings_fail; + } + +atmxt_validate_settings_fail: + return err; +} + +static int atmxt_gpio_init(struct atmxt_driver_data *dd) +{ + int err = 0; + struct gpio touch_gpio[] = { + {dd->pdata->gpio_reset, GPIOF_OUT_INIT_LOW, "touch_reset"}, + {dd->pdata->gpio_interrupt, GPIOF_IN, "touch_irq"}, + }; + + atmxt_dbg(dd, ATMXT_DBG3, "%s: Requesting touch GPIOs...\n", __func__); + + err = gpio_request_array(touch_gpio, ARRAY_SIZE(touch_gpio)); + if (err < 0) { + printk(KERN_ERR "%s: Failed to request touch GPIOs.\n", + __func__); + goto atmxt_gpio_init_fail; + } + + dd->client->irq = gpio_to_irq(dd->pdata->gpio_interrupt); + +atmxt_gpio_init_fail: + return err; +} + +static int atmxt_register_inputs(struct atmxt_driver_data *dd, + uint8_t *rdat, int rsize) +{ + int err = 0; + int i = 0; + int iter = 0; + + atmxt_dbg(dd, ATMXT_DBG3, "%s: Registering inputs...\n", __func__); + + if (dd->rdat != NULL) + kfree(dd->rdat); + + dd->rdat = kzalloc(sizeof(struct atmxt_report_data), GFP_KERNEL); + if (dd->rdat == NULL) { + printk(KERN_ERR "%s: Unable to create report data.\n", + __func__); + err = -ENOMEM; + goto atmxt_register_inputs_fail; + } + + if (dd->in_dev != NULL) + input_unregister_device(dd->in_dev); + + dd->in_dev = input_allocate_device(); + if (dd->in_dev == NULL) { + printk(KERN_ERR "%s: Failed to allocate input device.\n", + __func__); + err = -ENODEV; + goto atmxt_register_inputs_fail; + } + + dd->in_dev->name = ATMXT_I2C_NAME; + input_set_drvdata(dd->in_dev, dd); + set_bit(INPUT_PROP_DIRECT, dd->in_dev->propbit); + + set_bit(EV_ABS, dd->in_dev->evbit); + for (i = 0; i < rsize; i += 10) { + if (((rdat[i+1] << 8) | rdat[i+0]) != ATMXT_ABS_RESERVED) { + input_set_abs_params(dd->in_dev, + (rdat[i+1] << 8) | rdat[i+0], + (rdat[i+3] << 8) | rdat[i+2], + (rdat[i+5] << 8) | rdat[i+4], + (rdat[i+7] << 8) | rdat[i+6], + (rdat[i+9] << 8) | rdat[i+8]); + } + + if (iter < ARRAY_SIZE(dd->rdat->axis)) { + dd->rdat->axis[iter] = (rdat[i+1] << 8) | rdat[i+0]; + iter++; + } + } + + for (i = iter; i < ARRAY_SIZE(dd->rdat->axis); i++) + dd->rdat->axis[i] = ATMXT_ABS_RESERVED; + + input_set_events_per_packet(dd->in_dev, + ATMXT_MAX_TOUCHES * (ARRAY_SIZE(dd->rdat->axis) + 1)); + + err = input_register_device(dd->in_dev); + if (err < 0) { + printk(KERN_ERR "%s: Failed to register input device.\n", + __func__); + err = -ENODEV; + input_free_device(dd->in_dev); + dd->in_dev = NULL; + goto atmxt_register_inputs_fail; + } + +atmxt_register_inputs_fail: + return err; +} + +static int atmxt_request_irq(struct atmxt_driver_data *dd) +{ + int err = 0; + + atmxt_dbg(dd, ATMXT_DBG3, "%s: Requesting IRQ...\n", __func__); + + err = gpio_get_value(dd->pdata->gpio_interrupt); + if (err < 0) { + printk(KERN_ERR "%s: Cannot test IRQ line level.\n", __func__); + goto atmxt_request_irq_fail; + } else if (err == 0) { + printk(KERN_ERR + "%s: Line already active; cannot request IRQ.\n", + __func__); + err = -EIO; + goto atmxt_request_irq_fail; + } + + err = request_threaded_irq(dd->client->irq, NULL, atmxt_isr, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, ATMXT_I2C_NAME, dd); + if (err < 0) { + printk(KERN_ERR "%s: IRQ request failed.\n", __func__); + goto atmxt_request_irq_fail; + } + + disable_irq_nosync(dd->client->irq); + +atmxt_request_irq_fail: + return err; +} + +static int atmxt_restart_ic(struct atmxt_driver_data *dd) +{ + int err = 0; + bool irq_low = false; + uint32_t size = 0; + bool update_fw = false; + bool need_reset = false; + int cur_drv_state = 0; + bool update_complete = false; + +atmxt_restart_ic_start: + atmxt_dbg(dd, ATMXT_DBG3, "%s: Restarting IC...\n", __func__); + + atmxt_free_ic_data(dd); + atmxt_release_touches(dd); + irq_low = false; + if (atmxt_get_ic_state(dd) != ATMXT_IC_UNKNOWN) + atmxt_set_ic_state(dd, ATMXT_IC_UNKNOWN); + + if (!update_fw && !update_complete) { + atmxt_dbg(dd, ATMXT_DBG2, + "%s: Resetting touch IC...\n", __func__); + gpio_set_value(dd->pdata->gpio_reset, 0); + udelay(ATMXT_IC_RESET_HOLD_TIME); + gpio_set_value(dd->pdata->gpio_reset, 1); + } + + irq_low = atmxt_wait4irq(dd); + if (!irq_low && !update_fw) { + printk(KERN_ERR "%s: Timeout waiting for interrupt.\n", + __func__); + err = -ETIME; + goto atmxt_restart_ic_fail; + } + + dd->info_blk = kzalloc(sizeof(struct atmxt_info_block), GFP_KERNEL); + if (dd->info_blk == NULL) { + printk(KERN_ERR "%s: Unable to create info block data.\n", + __func__); + err = -ENOMEM; + goto atmxt_restart_ic_fail; + } + + if (update_fw) { + if (!irq_low) { + atmxt_dbg(dd, ATMXT_DBG3, + "%s: Ignored interrupt timeout.\n", __func__); + } + + atmxt_dbg(dd, ATMXT_DBG3, + "%s: Trying bootloader...\n", __func__); + update_fw = false; + goto atmxt_restart_ic_updatefw_start; + } + + err = atmxt_get_info_header(dd); + if (err < 0) { + atmxt_dbg(dd, ATMXT_DBG3, + "%s: Error reaching IC in normal mode. %s\n", + __func__, "Trying bootloader..."); +atmxt_restart_ic_updatefw_start: + dd->client->addr = dd->util->addr[1]; + err = atmxt_i2c_read(dd, &(dd->info_blk->header[0]), 3); + if (err < 0) { + printk(KERN_ERR "%s: Failed to find touch IC.\n", + __func__); + dd->client->addr = dd->util->addr[0]; + goto atmxt_restart_ic_fail; + } + + atmxt_set_ic_state(dd, ATMXT_IC_BOOTLOADER); + if (dd->info_blk->header[0] & 0x20) { + printk(KERN_INFO "%s: %s: 0x%02X, %s: 0x%02X\n", + __func__, + "Bootloader ID", dd->info_blk->header[1], + "Bootloader Version", dd->info_blk->header[2]); + } else { + printk(KERN_INFO "%s: Bootloader ID: 0x%02X\n", + __func__, dd->info_blk->header[0] & 0x1F); + } + + if ((dd->info_blk->header[0] & 0xC0) == 0x40) { + if (update_complete) { + printk(KERN_ERR "%s: Firmware CRC failure.\n", + __func__); + } else { + printk(KERN_INFO + "%s: Firmware CRC failure; %s.\n", + __func__, "going to try reflashing IC"); + } + } + + if (update_complete) { + printk(KERN_ERR "%s: %s--%s.\n", __func__, + "Still in bootloader mode after reflash", + "check firmware image"); + dd->client->addr = dd->util->addr[0]; + err = -EINVAL; + goto atmxt_restart_ic_fail; + } + + cur_drv_state = atmxt_get_drv_state(dd); + atmxt_set_drv_state(dd, ATMXT_DRV_REFLASH); + err = atmxt_flash_firmware(dd); + if (err < 0) { + printk(KERN_ERR "%s: Failed to update IC firmware.\n", + __func__); + dd->client->addr = dd->util->addr[0]; + atmxt_set_drv_state(dd, cur_drv_state); + goto atmxt_restart_ic_fail; + } + + atmxt_set_drv_state(dd, cur_drv_state); + dd->client->addr = dd->util->addr[0]; + atmxt_dbg(dd, ATMXT_DBG3, + "%s: Reflash completed. Re-starting cycle...\n", + __func__); + update_complete = true; + goto atmxt_restart_ic_start; + } + + if (dd->info_blk->header[0] != FAMILY_ID) { + printk(KERN_ERR "%s: Family ID mismatch:" + " expected 0x%02x actual is 0x%02x\n", + __func__, FAMILY_ID, dd->info_blk->header[0]); + err = -EIO; + goto atmxt_restart_ic_fail; + } + + atmxt_set_ic_state(dd, ATMXT_IC_PRESENT); + printk(KERN_INFO "%s: Family ID: 0x%02X, Variant ID: 0x%02X, " \ + "Version: 0x%02X, Build: 0x%02X, Matrix: %ux%u, Objects: %u\n", + __func__, dd->info_blk->header[0], dd->info_blk->header[1], + dd->info_blk->header[2], dd->info_blk->header[3], + dd->info_blk->header[4], dd->info_blk->header[5], + dd->info_blk->header[6]); + + if (atmxt_get_drv_state(dd) == ATMXT_DRV_INIT) { + update_fw = atmxt_check_firmware_update(dd); + if (update_fw & update_complete) { + printk(KERN_ERR "%s: %s %s %s.\n", __func__, + "Platform firmware version", + "does not match platform firmware", + "after update"); + update_fw = false; + } + } + + size = dd->info_blk->header[6] * 6; + if (size > 255) { + printk(KERN_ERR "%s: Too many objects present.\n", __func__); + err = -EOVERFLOW; + goto atmxt_restart_ic_fail; + } + dd->info_blk->size = size; + + dd->info_blk->data = kzalloc(sizeof(uint8_t) * size, GFP_KERNEL); + if (dd->info_blk->data == NULL) { + printk(KERN_ERR "%s: Unable to create table data.\n", __func__); + err = -ENOMEM; + goto atmxt_restart_ic_fail; + } + + err = atmxt_get_object_table(dd); + if (err < 0) { + printk(KERN_ERR "%s: Error getting object table.\n", __func__); + if (update_fw) + goto atmxt_restart_updatefw_check; + else + goto atmxt_restart_ic_fail; + } + +atmxt_restart_updatefw_check: + if (update_fw) { + printk(KERN_INFO "%s: Resetting IC to update firmware...\n", + __func__); + err = atmxt_force_bootloader(dd); + if (err < 0) { + printk(KERN_ERR "%s: Unable to force flash mode.\n", + __func__); + goto atmxt_restart_ic_fail; + } + goto atmxt_restart_ic_start; + } + + dd->nvm = kzalloc(sizeof(struct atmxt_nvm), GFP_KERNEL); + if (dd->nvm == NULL) { + printk(KERN_ERR "%s: Unable to create NVM struct.\n", + __func__); + err = -ENOMEM; + goto atmxt_restart_ic_fail; + } + + dd->addr = kzalloc(sizeof(struct atmxt_addr), GFP_KERNEL); + if (dd->addr == NULL) { + printk(KERN_ERR "%s: Unable to create address book.\n", + __func__); + err = -ENOMEM; + goto atmxt_restart_ic_fail; + } + + dd->data = kzalloc(sizeof(struct atmxt_data), GFP_KERNEL); + if (dd->data == NULL) { + printk(KERN_ERR "%s: Unable to create data book.\n", + __func__); + err = -ENOMEM; + goto atmxt_restart_ic_fail; + } + + err = atmxt_process_object_table(dd); + if (err < 0) { + printk(KERN_ERR "%s: Processing info block failed.\n", + __func__); + goto atmxt_restart_ic_fail; + } + + err = atmxt_save_internal_data(dd); + if (err < 0) { + printk(KERN_ERR "%s: Failed to save internal data.\n", + __func__); + goto atmxt_restart_ic_fail; + } + + atmxt_compute_checksum(dd); + + err = atmxt_check_settings(dd, &need_reset); + if (err < 0) { + printk(KERN_ERR "%s: Unable to check/update IC %s.\n", + __func__, "with platform settings"); + goto atmxt_restart_ic_fail; + } else if (need_reset) { + update_fw = false; + update_complete = false; + goto atmxt_restart_ic_start; + } + + atmxt_set_ic_state(dd, ATMXT_IC_ACTIVE); + + err = atmxt_start_ic_calibration_fix(dd); + if (err < 0) { + printk(KERN_ERR "%s: Failed to start IC calibration fix.\n", + __func__); + goto atmxt_restart_ic_fail; + } + +atmxt_restart_ic_fail: + return err; +} + +static irqreturn_t atmxt_isr(int irq, void *handle) +{ + struct atmxt_driver_data *dd = handle; + int drv_state; + int ic_state; + + mutex_lock(dd->mutex); + + drv_state = atmxt_get_drv_state(dd); + ic_state = atmxt_get_ic_state(dd); + + atmxt_dbg(dd, ATMXT_DBG3, + "%s: IRQ Received -- Driver: %s, IC: %s\n", __func__, + atmxt_driver_state_string[drv_state], + atmxt_ic_state_string[ic_state]); + + switch (drv_state) { + case ATMXT_DRV_ACTIVE: + switch (ic_state) { + case ATMXT_IC_SLEEP: + atmxt_dbg(dd, ATMXT_DBG3, + "%s: Servicing IRQ during sleep...\n", + __func__); + case ATMXT_IC_ACTIVE: + atmxt_active_handler(dd); + break; + default: + printk(KERN_ERR "%s: Driver %s, IC %s IRQ received.\n", + __func__, atmxt_driver_state_string[drv_state], + atmxt_ic_state_string[ic_state]); + if (dd->status & (1 << ATMXT_IRQ_ENABLED_FLAG)) { + disable_irq_nosync(dd->client->irq); + dd->status = dd->status & + ~(1 << ATMXT_IRQ_ENABLED_FLAG); + atmxt_set_drv_state(dd, ATMXT_DRV_IDLE); + } + break; + } + break; + + default: + printk(KERN_ERR "%s: Driver state \"%s\" IRQ received.\n", + __func__, atmxt_driver_state_string[drv_state]); + if (dd->status & (1 << ATMXT_IRQ_ENABLED_FLAG)) { + disable_irq_nosync(dd->client->irq); + dd->status = dd->status & + ~(1 << ATMXT_IRQ_ENABLED_FLAG); + atmxt_set_drv_state(dd, ATMXT_DRV_IDLE); + } + break; + } + + atmxt_dbg(dd, ATMXT_DBG3, "%s: IRQ Serviced.\n", __func__); + mutex_unlock(dd->mutex); + + return IRQ_HANDLED; +} + +static int atmxt_get_info_header(struct atmxt_driver_data *dd) +{ + int err = 0; + + err = atmxt_i2c_write(dd, 0x00, 0x00, NULL, 0); + if (err < 0) + goto atmxt_get_info_header_fail; + + err = atmxt_i2c_read(dd, &(dd->info_blk->header[0]), 7); + if (err < 0) + goto atmxt_get_info_header_fail; + +atmxt_get_info_header_fail: + return err; +} + +static int atmxt_get_object_table(struct atmxt_driver_data *dd) +{ + int err = 0; + int i = 0; /* Fix Atmel's data order */ + int top = 0; /* Fix Atmel's data order */ + int cur = 0; /* Fix Atmel's data order */ + uint8_t lo_addr = 255; /* Fix Atmel's data order */ + uint8_t hi_addr = 255; /* Fix Atmel's data order */ + uint8_t temp[6]; /* Fix Atmel's data order */ + + err = atmxt_i2c_write(dd, 0x07, 0x00, NULL, 0); + if (err < 0) { + printk(KERN_ERR + "%s: Unable to set address pointer to object table.\n", + __func__); + goto atmxt_get_object_table_fail; + } + + err = atmxt_i2c_read(dd, dd->info_blk->data, dd->info_blk->size); + if (err < 0) { + printk(KERN_ERR "%s: Object table read failed.\n", __func__); + goto atmxt_get_object_table_fail; + } + + /* Fix Atmel's data order */ + while (top < dd->info_blk->size) { + for (i = top; i < dd->info_blk->size; i += 6) { + if (dd->info_blk->data[i+2] < hi_addr) { + lo_addr = dd->info_blk->data[i+1]; + hi_addr = dd->info_blk->data[i+2]; + cur = i; + } else if ((dd->info_blk->data[i+2] == hi_addr) && + (dd->info_blk->data[i+1] < lo_addr)) { + lo_addr = dd->info_blk->data[i+1]; + hi_addr = dd->info_blk->data[i+2]; + cur = i; + } + } + + memcpy(&(temp[0]), &(dd->info_blk->data[top]), 6); + memmove(&(dd->info_blk->data[top]), + &(dd->info_blk->data[cur]), 6); + memcpy(&(dd->info_blk->data[cur]), &(temp[0]), 6); + + lo_addr = 255; + hi_addr = 255; + top = top + 6; + cur = top; + } + +atmxt_get_object_table_fail: + return err; +} + +static int atmxt_process_object_table(struct atmxt_driver_data *dd) +{ + int err = 0; + int i = 0; + int j = 0; + int k = 0; + uint32_t ids = 0; + uint32_t nvm_size = 0; + int usr_start = 0; + bool usr_start_seen = false; + uint8_t *tsett = NULL; + int id_iter = 0; + int nvm_iter = 0; + + ids++; + for (i = 0; i < dd->info_blk->size; i += 6) { + ids += dd->info_blk->data[i+5] * (dd->info_blk->data[i+4] + 1); + if (usr_start_seen) { + nvm_size += (dd->info_blk->data[i+3] + 1) + * (dd->info_blk->data[i+4] + 1); + } else if (dd->info_blk->data[i+0] == 38) { + usr_start = i; + usr_start_seen = true; + } + } + + if (ids > 255) { + printk(KERN_ERR "%s: Too many report IDs used.\n", __func__); + err = -EOVERFLOW; + goto atmxt_process_object_table_fail; + } + + dd->info_blk->msg_id = kzalloc(sizeof(uint8_t) * ids, GFP_KERNEL); + if (dd->info_blk->msg_id == NULL) { + printk(KERN_ERR "%s: Unable to create ID table.\n", __func__); + err = -ENOMEM; + goto atmxt_process_object_table_fail; + } + dd->info_blk->id_size = ids; + + dd->nvm->data = kzalloc(sizeof(uint8_t) * nvm_size, GFP_KERNEL); + if (dd->nvm->data == NULL) { + printk(KERN_ERR "%s: Unable to create NVM block.\n", + __func__); + err = -ENOMEM; + goto atmxt_process_object_table_fail; + } + dd->nvm->size = nvm_size; + dd->nvm->addr[0] = dd->info_blk->data[usr_start+6+1]; + dd->nvm->addr[1] = dd->info_blk->data[usr_start+6+2]; + + dd->info_blk->msg_id[id_iter] = 0; + id_iter++; + for (i = 0; i < dd->info_blk->size; i += 6) { + for (j = 0; j <= dd->info_blk->data[i+4]; j++) { + for (k = 0; k < dd->info_blk->data[i+5]; k++) { + dd->info_blk->msg_id[id_iter] = + dd->info_blk->data[i+0]; + id_iter++; + } + } + + if (i <= usr_start) + continue; + + tsett = atmxt_get_settings_entry(dd, dd->info_blk->data[i+0]); + err = atmxt_copy_platform_data(&(dd->nvm->data[nvm_iter]), + &(dd->info_blk->data[i+0]), tsett); + if (err < 0) { + printk(KERN_ERR "%s: Failed to copy platform data.\n", + __func__); + goto atmxt_process_object_table_fail; + } + + nvm_iter += (dd->info_blk->data[i+3] + 1) * + (dd->info_blk->data[i+4] + 1); + } + +atmxt_process_object_table_fail: + return err; +} + +static uint8_t *atmxt_get_settings_entry(struct atmxt_driver_data *dd, + uint16_t num) +{ + uint8_t *entry = NULL; + uint32_t iter = 0; + + while (iter < dd->util->tsett_size) { + if (num == ((dd->util->tsett[iter+1] << 8) | + dd->util->tsett[iter])) { + entry = &(dd->util->tsett[iter]); + break; + } else { + iter += 5 + ((dd->util->tsett[iter+4] << 8) | + dd->util->tsett[iter+3]); + } + } + + return entry; +} + +static int atmxt_copy_platform_data(uint8_t *reg, uint8_t *entry, + uint8_t *tsett) +{ + int err = 0; + int i = 0; + int iter = 0; + int size = 0; + uint8_t *data = NULL; + int data_size = 0; + int obj_size = 0; + int obj_inst = 0; + uint8_t inst_count = 0x00; + uint16_t tsett_size = 0x0000; + + if (tsett == NULL) + goto atmxt_copy_platform_data_fail; + + tsett_size = (tsett[4] << 8) | tsett[3]; + if (tsett[2] == 0) { + inst_count = 1; + data_size = tsett_size; + data = &(tsett[5]); + } else { + inst_count = tsett[2]; + + if ((tsett_size % inst_count) != 0) { + printk(KERN_ERR "%s: Settings data unevenly packed.\n", + __func__); + err = -EINVAL; + goto atmxt_copy_platform_data_fail; + } + + data_size = tsett_size / inst_count; + data = &(tsett[5]); + } + + obj_size = entry[3] + 1; + obj_inst = entry[4] + 1; + + if (data_size > obj_size) + size = obj_size; + else + size = data_size; + + while ((i < inst_count) && (i < obj_inst)) { + memcpy(&(reg[i*obj_size]), &(data[iter]), size); + iter += data_size; + i++; + } + +atmxt_copy_platform_data_fail: + return err; +} + +static int atmxt_check_settings(struct atmxt_driver_data *dd, bool *reset) +{ + int err = 0; + uint8_t *msg_buf = NULL; + char *contents = NULL; + + atmxt_dbg(dd, ATMXT_DBG3, "%s: Checking IC settings...\n", __func__); + + if (dd->data->max_msg_size < 5) { + printk(KERN_ERR "%s: Message size is too small.\n", __func__); + err = -EINVAL; + goto atmxt_check_settings_fail; + } + + msg_buf = kzalloc(sizeof(uint8_t) * dd->data->max_msg_size, GFP_KERNEL); + if (msg_buf == NULL) { + printk(KERN_ERR + "%s: Unable to allocate memory for message buffer.\n", + __func__); + err = -ENOMEM; + goto atmxt_check_settings_fail; + } + + err = atmxt_i2c_write(dd, dd->addr->msg[0], dd->addr->msg[1], NULL, 0); + if (err < 0) { + printk(KERN_ERR "%s: Failed to set message buffer pointer.\n", + __func__); + goto atmxt_check_settings_fail; + } + + dd->status = dd->status | (1 << ATMXT_SET_MESSAGE_POINTER); + + err = atmxt_i2c_read(dd, msg_buf, dd->data->max_msg_size); + if (err < 0) { + printk(KERN_ERR "%s: Failed to read message.\n", __func__); + goto atmxt_check_settings_fail; + } + + if (msg_buf[0] <= (dd->info_blk->id_size-1)) { + if (dd->info_blk->msg_id[msg_buf[0]] == 6) { + if (!(msg_buf[1] & 0x80)) { + printk(KERN_ERR "%s: %s: 0x%02X\n", __func__, + "Received checksum without reset", + msg_buf[1]); + } + } else { + contents = atmxt_msg2str(msg_buf, + dd->data->max_msg_size); + printk(KERN_ERR "%s: %s--%s %u instead: %s.\n", + __func__, "Failed to receive reset message", + "received this from Object", + dd->info_blk->msg_id[msg_buf[0]], contents); + err = -EIO; + goto atmxt_check_settings_fail; + } + } else { + contents = atmxt_msg2str(msg_buf, dd->data->max_msg_size); + printk(KERN_ERR "%s: %s--%s: %s.\n", + __func__, "Failed to receive reset message", + "received unknown message instead", contents); + err = -EIO; + goto atmxt_check_settings_fail; + } + + atmxt_dbg(dd, ATMXT_DBG3, + "%s: %s 0x%02X%02X%02X, %s 0x%02X%02X%02X.\n", __func__, + "Driver checksum is", dd->nvm->chksum[0], + dd->nvm->chksum[1], dd->nvm->chksum[2], + "IC checksum is", msg_buf[2], msg_buf[3], msg_buf[4]); + + if ((msg_buf[2] == dd->nvm->chksum[0]) && + (msg_buf[3] == dd->nvm->chksum[1]) && + (msg_buf[4] == dd->nvm->chksum[2])) { + err = atmxt_process_message(dd, + msg_buf, dd->data->max_msg_size); + if (err < 0) { + printk(KERN_ERR "%s: Error processing first message.\n", + __func__); + goto atmxt_check_settings_fail; + } + *reset = false; + } else if (*reset) { + printk(KERN_ERR "%s: %s.\n", __func__, + "Previous attempt to write platform settings failed"); + err = -EINVAL; + goto atmxt_check_settings_fail; + } else { + printk(KERN_INFO "%s: Updating IC settings...\n", __func__); + err = atmxt_send_settings(dd, true); + if (err < 0) { + printk(KERN_ERR "%s: Failed to update IC settings.\n", + __func__); + goto atmxt_check_settings_fail; + } + + msleep(500); + *reset = true; + } + +atmxt_check_settings_fail: + kfree(msg_buf); + kfree(contents); + return err; +} + +static int atmxt_send_settings(struct atmxt_driver_data *dd, bool save_nvm) +{ + int err = 0; + uint8_t nvm_cmd = 0x55; + + err = atmxt_i2c_write(dd, dd->nvm->addr[0], dd->nvm->addr[1], + dd->nvm->data, dd->nvm->size); + if (err < 0) { + printk(KERN_ERR "%s: Error writing settings to IC.\n", + __func__); + goto atmxt_send_settings_fail; + } + + if (save_nvm) { + err = atmxt_i2c_write(dd, dd->addr->nvm[0], dd->addr->nvm[1], + &nvm_cmd, sizeof(uint8_t)); + if (err < 0) { + printk(KERN_ERR "%s: Error backing up to NVM.\n", + __func__); + goto atmxt_send_settings_fail; + } + } + +atmxt_send_settings_fail: + return err; +} + +static int atmxt_recalibrate_ic(struct atmxt_driver_data *dd) +{ + int err = 0; + uint8_t cmd = 0x01; + + atmxt_dbg(dd, ATMXT_DBG3, "%s: Asking touch IC to recalibrate...\n", + __func__); + + err = atmxt_i2c_write(dd, dd->addr->cal[0], dd->addr->cal[1], &cmd, 1); + if (err < 0) { + printk(KERN_ERR "%s: Failed to send calibrate to touch IC.\n", + __func__); + goto atmxt_recalibrate_ic_fail; + } + +atmxt_recalibrate_ic_fail: + return err; +} + +static int atmxt_start_ic_calibration_fix(struct atmxt_driver_data *dd) +{ + int err = 0; + uint8_t sett[6] = {0x05, 0x00, 0x00, 0x00, 0x01, 0x80}; + + atmxt_dbg(dd, ATMXT_DBG3, "%s: Starting IC calibration fix...\n", + __func__); + + sett[1] = dd->data->acq[1]; + err = atmxt_i2c_write(dd, dd->addr->acq[0], dd->addr->acq[1], + &(sett[0]), 6); + if (err < 0) { + printk(KERN_ERR "%s: Failed to update acquisition settings.\n", + __func__); + goto atmxt_start_ic_calibration_fix_fail; + } + + dd->data->timer = 0; + dd->status = dd->status & ~(1 << ATMXT_RECEIVED_CALIBRATION); + dd->status = dd->status | (1 << ATMXT_FIXING_CALIBRATION); + +atmxt_start_ic_calibration_fix_fail: + return err; +} + +static int atmxt_verify_ic_calibration_fix(struct atmxt_driver_data *dd) +{ + int err = 0; + unsigned long toc = 0; + + atmxt_dbg(dd, ATMXT_DBG3, "%s: Verifying IC calibration fix...\n", + __func__); + + toc = jiffies; + if (dd->status & (1 << ATMXT_RECEIVED_CALIBRATION)) { + dd->data->timer = 0; + dd->status = dd->status & ~(1 << ATMXT_RECEIVED_CALIBRATION); + } else if (dd->status & (1 << ATMXT_REPORT_TOUCHES)) { + if (dd->data->timer == 0) + dd->data->timer = toc; + + if (((toc - dd->data->timer) * 1000 / HZ) >= 2500) { + err = atmxt_stop_ic_calibration_fix(dd); + if (err < 0) { + printk(KERN_ERR "%s: %s %s.\n", __func__, + "Failed to stop", + "fixing IC calibration"); + goto atmxt_verify_ic_calibration_fix_fail; + } + } + } + +atmxt_verify_ic_calibration_fix_fail: + return err; +} + +static int atmxt_stop_ic_calibration_fix(struct atmxt_driver_data *dd) +{ + int err = 0; + + atmxt_dbg(dd, ATMXT_DBG3, "%s: Stopping IC calibration fix...\n", + __func__); + + err = atmxt_i2c_write(dd, dd->addr->acq[0], dd->addr->acq[1], + &(dd->data->acq[0]), 6); + if (err < 0) { + printk(KERN_ERR "%s: Failed to update acquisition settings.\n", + __func__); + goto atmxt_stop_ic_calibration_fix_fail; + } + + dd->status = dd->status & ~(1 << ATMXT_FIXING_CALIBRATION); + +atmxt_stop_ic_calibration_fix_fail: + return err; +} + +static int atmxt_i2c_write(struct atmxt_driver_data *dd, + uint8_t addr_lo, uint8_t addr_hi, uint8_t *buf, int size) +{ + int err = 0; + uint8_t *data_out = NULL; + int size_out = 0; + int i = 0; + char *str = NULL; + + dd->status = dd->status & ~(1 << ATMXT_SET_MESSAGE_POINTER); + + size_out = size + 2; + data_out = kzalloc(sizeof(uint8_t) * size_out, GFP_KERNEL); + if (data_out == NULL) { + printk(KERN_ERR "%s: Unable to allocate write memory.\n", + __func__); + err = -ENOMEM; + goto atmxt_i2c_write_exit; + } + + data_out[0] = addr_lo; + data_out[1] = addr_hi; + if (buf != NULL && size > 0) + memcpy(&(data_out[2]), buf, size); + + for (i = 1; i <= ATMXT_I2C_ATTEMPTS; i++) { + err = i2c_master_send(dd->client, data_out, size_out); + if (err < 0) { + printk(KERN_ERR + "%s: %s %d, failed with error code %d.\n", + __func__, "On I2C write attempt", i, err); + } else if (err < size_out) { + printk(KERN_ERR + "%s: %s %d, wrote %d bytes instead of %d.\n", + __func__, "On I2C write attempt", i, err, + size_out); + err = -EBADE; + } else { + break; + } + + udelay(ATMXT_I2C_WAIT_TIME); + } + + if (err < 0) + printk(KERN_ERR "%s: I2C write failed.\n", __func__); + +#ifdef CONFIG_TOUCHSCREEN_DEBUG + if ((dd->dbg->dbg_lvl) >= ATMXT_DBG2) + str = atmxt_msg2str(data_out, size_out); +#endif + atmxt_dbg(dd, ATMXT_DBG2, "%s: %s\n", __func__, str); + kfree(str); + +atmxt_i2c_write_exit: + kfree(data_out); + + return err; +} + +static int atmxt_i2c_read(struct atmxt_driver_data *dd, uint8_t *buf, int size) +{ + int err = 0; + int i = 0; + char *str = NULL; + + for (i = 1; i <= ATMXT_I2C_ATTEMPTS; i++) { + err = i2c_master_recv(dd->client, buf, size); + if (err < 0) { + printk(KERN_ERR + "%s: %s %d, failed with error code %d.\n", + __func__, "On I2C read attempt", i, err); + } else if (err < size) { + printk(KERN_ERR + "%s: %s %d, received %d bytes instead of %d.\n", + __func__, "On I2C read attempt", i, err, size); + err = -EBADE; + } else { + break; + } + + udelay(ATMXT_I2C_WAIT_TIME); + } + + if (err < 0) + printk(KERN_ERR "%s: I2C read failed.\n", __func__); + +#ifdef CONFIG_TOUCHSCREEN_DEBUG + if ((dd->dbg->dbg_lvl) >= ATMXT_DBG1) + str = atmxt_msg2str(buf, size); +#endif + atmxt_dbg(dd, ATMXT_DBG1, "%s: %s\n", __func__, str); + kfree(str); + + return err; +} + +static int atmxt_save_internal_data(struct atmxt_driver_data *dd) +{ + int err = 0; + int i = 0; + bool usr_start_seen = false; + int nvm_iter = 0; + bool chk_5 = false; + bool chk_6 = false; + bool chk_7 = false; + bool chk_8 = false; + bool chk_9 = false; + + for (i = 0; i < dd->info_blk->size; i += 6) { + switch (dd->info_blk->data[i+0]) { + case 5: + chk_5 = true; + err = atmxt_save_data5(dd, &(dd->info_blk->data[i+0])); + if (err < 0) + goto atmxt_save_internal_data_fail; + break; + + case 6: + chk_6 = true; + err = atmxt_save_data6(dd, &(dd->info_blk->data[i+0])); + if (err < 0) + goto atmxt_save_internal_data_fail; + break; + + case 7: + chk_7 = true; + err = atmxt_save_data7(dd, &(dd->info_blk->data[i+0]), + &(dd->nvm->data[nvm_iter])); + if (err < 0) + goto atmxt_save_internal_data_fail; + break; + + case 8: + chk_8 = true; + err = atmxt_save_data8(dd, &(dd->info_blk->data[i+0]), + &(dd->nvm->data[nvm_iter])); + if (err < 0) + goto atmxt_save_internal_data_fail; + break; + + case 9: + chk_9 = true; + err = atmxt_save_data9(dd, &(dd->info_blk->data[i+0]), + &(dd->nvm->data[nvm_iter])); + if (err < 0) + goto atmxt_save_internal_data_fail; + break; + + case 38: + usr_start_seen = true; + break; + + default: + break; + } + + if (usr_start_seen && (dd->info_blk->data[i+0] != 38)) { + nvm_iter += (dd->info_blk->data[i+3] + 1) * + (dd->info_blk->data[i+4] + 1); + } + } + + if (!chk_5) { + printk(KERN_ERR "%s: Object 5 is missing.\n", __func__); + err = -ENODATA; + } + + if (!chk_6) { + printk(KERN_ERR "%s: Object 6 is missing.\n", __func__); + err = -ENODATA; + } + + if (!chk_7) { + printk(KERN_ERR "%s: Object 7 is missing.\n", __func__); + err = -ENODATA; + } + + if (!chk_8) { + printk(KERN_ERR "%s: Object 8 is missing.\n", __func__); + err = -ENODATA; + } + + if (!chk_9) { + printk(KERN_ERR "%s: Object 9 is missing.\n", __func__); + err = -ENODATA; + } + +atmxt_save_internal_data_fail: + return err; +} + +static int atmxt_save_data5(struct atmxt_driver_data *dd, uint8_t *entry) +{ + int err = 0; + + dd->addr->msg[0] = entry[1]; + dd->addr->msg[1] = entry[2]; + dd->data->max_msg_size = entry[3]; + + return err; +} + +static int atmxt_save_data6(struct atmxt_driver_data *dd, uint8_t *entry) +{ + int err = 0; + + if (entry[3] < 2) { + printk(KERN_ERR "%s: Command object is too small.\n", __func__); + err = -ENODATA; + goto atmxt_save_data6_fail; + } + + dd->addr->rst[0] = entry[1]; + dd->addr->rst[1] = entry[2]; + + dd->addr->nvm[0] = entry[1] + 1; + dd->addr->nvm[1] = entry[2]; + if (dd->addr->nvm[0] < entry[1]) + dd->addr->nvm[1]++; + + dd->addr->cal[0] = entry[1] + 2; + dd->addr->cal[1] = entry[2]; + if (dd->addr->cal[0] < entry[1]) + dd->addr->cal[1]++; + +atmxt_save_data6_fail: + return err; +} + +static int atmxt_save_data7(struct atmxt_driver_data *dd, + uint8_t *entry, uint8_t *reg) +{ + int err = 0; + + if (entry[3] < 1) { + printk(KERN_ERR "%s: Power object is too small.\n", __func__); + err = -ENODATA; + goto atmxt_save_data7_fail; + } + + dd->addr->pwr[0] = entry[1]; + dd->addr->pwr[1] = entry[2]; + dd->data->pwr[0] = reg[0]; + dd->data->pwr[1] = reg[1]; + +atmxt_save_data7_fail: + return err; +} + +static int atmxt_save_data8(struct atmxt_driver_data *dd, + uint8_t *entry, uint8_t *reg) +{ + int err = 0; + + if (entry[3] < 9) { + printk(KERN_ERR "%s: Acquisition object is too small.\n", + __func__); + err = -ENODATA; + goto atmxt_save_data8_fail; + } + + dd->addr->acq[0] = entry[1] + 4; + dd->addr->acq[1] = entry[2]; + if (dd->addr->acq[0] < entry[1]) + dd->addr->acq[1]++; + + dd->data->acq[0] = reg[4]; + dd->data->acq[1] = reg[5]; + dd->data->acq[2] = reg[6]; + dd->data->acq[3] = reg[7]; + dd->data->acq[4] = reg[8]; + dd->data->acq[5] = reg[9]; + +atmxt_save_data8_fail: + return err; +} + +static int atmxt_save_data9(struct atmxt_driver_data *dd, + uint8_t *entry, uint8_t *reg) +{ + int err = 0; + int i = 0; + + for (i = 1; i < dd->info_blk->id_size; i++) { + if (dd->info_blk->msg_id[i] == 9) { + dd->data->touch_id_offset = i; + break; + } + } + + if (dd->data->touch_id_offset == 0) { + printk(KERN_ERR + "%s: Touch object has reporting error.\n", + __func__); + err = -ENODATA; + goto atmxt_save_data9_fail; + } + + dd->data->res[0] = false; + dd->data->res[1] = false; + + if (entry[3] < 21) { + atmxt_dbg(dd, ATMXT_DBG3, + "%s: Only 10-bit resolution is available.\n", __func__); + } else { + if (reg[19] >= 0x04) + dd->data->res[0] = true; + + if (reg[21] >= 0x04) + dd->data->res[1] = true; + } + +atmxt_save_data9_fail: + return err; +} + +static void atmxt_compute_checksum(struct atmxt_driver_data *dd) +{ + uint8_t low = 0x00; + uint8_t mid = 0x00; + uint8_t high = 0x00; + uint8_t byte1 = 0x00; + uint8_t byte2 = 0x00; + uint32_t iter = 0; + uint32_t range = 0; + + range = dd->nvm->size - (dd->nvm->size % 2); + + while (iter < range) { + byte1 = dd->nvm->data[iter]; + iter++; + byte2 = dd->nvm->data[iter]; + iter++; + atmxt_compute_partial_checksum(&byte1, &byte2, + &low, &mid, &high); + } + + if ((dd->nvm->size % 2) != 0) { + byte1 = dd->nvm->data[iter]; + byte2 = 0x00; + atmxt_compute_partial_checksum(&byte1, &byte2, + &low, &mid, &high); + } + + dd->nvm->chksum[0] = low; + dd->nvm->chksum[1] = mid; + dd->nvm->chksum[2] = high; + + return; +} + +static void atmxt_compute_partial_checksum(uint8_t *byte1, uint8_t *byte2, + uint8_t *low, uint8_t *mid, uint8_t *high) +{ + bool xor_result = false; + + if (*high & 0x80) + xor_result = true; + + *high = *high << 1; + if (*mid & 0x80) + (*high)++; + + *mid = *mid << 1; + if (*low & 0x80) + (*mid)++; + + *low = *low << 1; + + *low = *low ^ *byte1; + *mid = *mid ^ *byte2; + + if (xor_result) { + *low = *low ^ 0x1B; + *high = *high ^ 0x80; + } + + return; +} + +static void atmxt_active_handler(struct atmxt_driver_data *dd) +{ + int err = 0; + int i = 0; + uint8_t *msg_buf = NULL; + int size = 0; + char *contents = NULL; + bool msg_fail = false; + int last_err = 0; + int msg_size = 0; + bool inv_msg_seen = false; + + atmxt_dbg(dd, ATMXT_DBG3, "%s: Starting active handler...\n", __func__); + + msg_size = dd->data->max_msg_size; + size = (dd->rdat->active_touches + 1) * msg_size; + if (size == msg_size) + size = msg_size * 2; + + msg_buf = kzalloc(sizeof(uint8_t) * size, GFP_KERNEL); + if (msg_buf == NULL) { + printk(KERN_ERR + "%s: Unable to allocate memory for message buffer.\n", + __func__); + err = -ENOMEM; + goto atmxt_active_handler_fail; + } + + if (!(dd->status & (1 << ATMXT_SET_MESSAGE_POINTER))) { + err = atmxt_i2c_write(dd, dd->addr->msg[0], dd->addr->msg[1], + NULL, 0); + if (err < 0) { + printk(KERN_ERR + "%s: Failed to set message buffer pointer.\n", + __func__); + goto atmxt_active_handler_fail; + } + + dd->status = dd->status | (1 << ATMXT_SET_MESSAGE_POINTER); + } + + err = atmxt_i2c_read(dd, msg_buf, size); + if (err < 0) { + printk(KERN_ERR "%s: Failed to read messages.\n", __func__); + goto atmxt_active_handler_fail; + } + + if (msg_buf[0] == 0xFF) { + contents = atmxt_msg2str(msg_buf, size); + printk(KERN_ERR "%s: Received invalid data: %s.\n", + __func__, contents); + err = -EINVAL; + goto atmxt_active_handler_fail; + } + + for (i = 0; i < size; i += msg_size) { + if (msg_buf[i] == 0xFF) { + atmxt_dbg(dd, ATMXT_DBG3, "%s: Reached 0xFF message.\n", + __func__); + inv_msg_seen = true; + continue; + } + + atmxt_dbg(dd, ATMXT_DBG3, + "%s: Processing message %d...\n", __func__, + (i + 1) / msg_size); + + if (inv_msg_seen) { + printk(KERN_INFO "%s: %s %s (%d).\n", __func__, + "System response time lagging", + "IC report rate", + (i / msg_size) + 1); + } + + err = atmxt_process_message(dd, &(msg_buf[i]), msg_size); + if (err < 0) { + printk(KERN_ERR + "%s: Processing message %d failed %s %d.\n", + __func__, (i / msg_size) + 1, + "with error code", err); + msg_fail = true; + last_err = err; + } + } + + if (dd->status & (1 << ATMXT_RESTART_REQUIRED)) { + printk(KERN_ERR "%s: Restarting touch IC...\n", __func__); + dd->status = dd->status & ~(1 << ATMXT_RESTART_REQUIRED); + err = atmxt_resume_restart(dd); + if (err < 0) { + printk(KERN_ERR "%s: Failed to restart touch IC.\n", + __func__); + goto atmxt_active_handler_fail; + } else if (msg_fail) { + err = last_err; + goto atmxt_active_handler_fail; + } else { + goto atmxt_active_handler_pass; + } + } + + if (dd->status & (1 << ATMXT_FIXING_CALIBRATION)) { + err = atmxt_verify_ic_calibration_fix(dd); + if (err < 0) { + printk(KERN_ERR "%s: Unable to verify IC calibration.\n", + __func__); + goto atmxt_active_handler_fail; + } + } + + if (dd->status & (1 << ATMXT_REPORT_TOUCHES)) { + atmxt_report_touches(dd); + dd->status = dd->status & ~(1 << ATMXT_REPORT_TOUCHES); + } + + if (msg_fail) { + err = last_err; + goto atmxt_active_handler_fail; + } + + goto atmxt_active_handler_pass; + +atmxt_active_handler_fail: + printk(KERN_ERR "%s: Touch active handler failed with error code %d.\n", + __func__, err); + +atmxt_active_handler_pass: + kfree(msg_buf); + kfree(contents); + return; +} + +static int atmxt_process_message(struct atmxt_driver_data *dd, + uint8_t *msg, uint8_t size) +{ + int err = 0; + char *contents = NULL; + + if (msg[0] <= (dd->info_blk->id_size-1)) { + switch (dd->info_blk->msg_id[msg[0]]) { + case 6: + err = atmxt_message_handler6(dd, msg, size); + break; + case 9: + err = atmxt_message_handler9(dd, msg, size); + break; + case 42: + err = atmxt_message_handler42(dd, msg, size); + break; + default: + contents = atmxt_msg2str(msg, size); + printk(KERN_ERR "%s: Object %u sent this: %s.\n", + __func__, dd->info_blk->msg_id[msg[0]], + contents); + break; + } + } else { + contents = atmxt_msg2str(msg, size); + printk(KERN_ERR "%s: Received unknown message: %s.\n", + __func__, contents); + } + + if (err < 0) + printk(KERN_ERR "%s: Message processing failed.\n", __func__); + + kfree(contents); + return err; +} + +static void atmxt_report_touches(struct atmxt_driver_data *dd) +{ + int i = 0; + int j = 0; + int rval = 0; + int id = 0; + int x = 0; + int y = 0; + int p = 0; + int w = 0; + + dd->rdat->active_touches = 0; + + for (i = 0; i < ATMXT_MAX_TOUCHES; i++) { + if (!(dd->rdat->tchdat[i].active)) + continue; + + id = dd->rdat->tchdat[i].id; + x = dd->rdat->tchdat[i].x; + y = dd->rdat->tchdat[i].y; + p = dd->rdat->tchdat[i].p; + w = dd->rdat->tchdat[i].w; + + dd->rdat->active_touches++; + + atmxt_dbg(dd, ATMXT_DBG1, "%s: ID=%d, X=%d, Y=%d, P=%d, W=%d\n", + __func__, id, x, y, p, w); + + for (j = 0; j < ARRAY_SIZE(dd->rdat->axis); j++) { + switch (j) { + case 0: + rval = x; + break; + case 1: + rval = y; + break; + case 2: + rval = p; + break; + case 3: + rval = w; + break; + case 4: + rval = id; + break; + } + if (dd->rdat->axis[j] != ATMXT_ABS_RESERVED) { + input_report_abs(dd->in_dev, + dd->rdat->axis[j], rval); + } + } + input_mt_sync(dd->in_dev); + } + + if (dd->rdat->active_touches == 0) + input_mt_sync(dd->in_dev); + + input_sync(dd->in_dev); + + return; +} + +static void atmxt_release_touches(struct atmxt_driver_data *dd) +{ + int i = 0; + + atmxt_dbg(dd, ATMXT_DBG1, "%s: Releasing all touches...\n", __func__); + + for (i = 0; i < ATMXT_MAX_TOUCHES; i++) + dd->rdat->tchdat[i].active = false; + + atmxt_report_touches(dd); + dd->status = dd->status & ~(1 << ATMXT_REPORT_TOUCHES); + + return; +} + +static int atmxt_message_handler6(struct atmxt_driver_data *dd, + uint8_t *msg, uint8_t size) +{ + int err = 0; + + atmxt_dbg(dd, ATMXT_DBG3, "%s: Handling message type 6...\n", __func__); + + if (size < 5) { + printk(KERN_ERR "%s: Message size is too small.\n", __func__); + err = -EINVAL; + goto atmxt_message_handler6_fail; + } + + if (msg[1] & 0x80) { + printk(KERN_INFO "%s: Touch IC reset complete.\n", __func__); + dd->data->last_stat = 0x00; + } + + if ((msg[1] & 0x40) && !(dd->data->last_stat & 0x40)) { + printk(KERN_ERR "%s: Acquisition cycle overflow.\n", __func__); + } else if (!(msg[1] & 0x40) && (dd->data->last_stat & 0x40)) { + printk(KERN_INFO "%s: Acquisition cycle now normal.\n", + __func__); + } + + if ((msg[1] & 0x20) && !(dd->data->last_stat & 0x20)) { + printk(KERN_ERR "%s: Signal error in IC acquisition.\n", + __func__); + } else if (!(msg[1] & 0x20) && (dd->data->last_stat & 0x20)) { + printk(KERN_INFO "%s: IC acquisition signal now in range.\n", + __func__); + } + + if ((msg[1] & 0x10) && !(dd->data->last_stat & 0x10)) { + printk(KERN_INFO "%s: Touch IC is calibrating.\n", __func__); + dd->status = dd->status | (1 << ATMXT_RECEIVED_CALIBRATION); + } else if (!(msg[1] & 0x10) && (dd->data->last_stat & 0x10)) { + printk(KERN_INFO "%s: Touch IC calibration complete.\n", + __func__); + dd->status = dd->status | (1 << ATMXT_RECEIVED_CALIBRATION); + } + + if (msg[1] & 0x08) { + printk(KERN_ERR "%s: Hardware configuration error--%s.\n", + __func__, "check platform settings"); + dd->data->last_stat = dd->data->last_stat & 0xF7; + } else if (!(msg[1] & 0x08) && (dd->data->last_stat & 0x08)) { + printk(KERN_INFO + "%s: Hardware configuration error corrected.\n", + __func__); + } + + if (msg[1] & 0x04) { + printk(KERN_ERR "%s: IC reports I2C communication error.\n", + __func__); + } + + if (msg[1] == dd->data->last_stat) { + printk(KERN_INFO "%s: Received checksum 0x%02X%02X%02X.\n", + __func__, msg[2], msg[3], msg[4]); + } + + dd->data->last_stat = msg[1]; + +#ifdef CONFIG_TOUCHSCREEN_DEBUG + if (dd->status & (1 << ATMXT_IGNORE_CHECKSUM)) + goto atmxt_message_handler6_fail; +#endif + + if ((msg[2] != dd->nvm->chksum[0]) || + (msg[3] != dd->nvm->chksum[1]) || + (msg[4] != dd->nvm->chksum[2])) { + if (!(dd->status & (1 << ATMXT_CHECKSUM_FAILED))) { + printk(KERN_ERR "%s: IC settings checksum fail.\n", + __func__); + dd->status = dd->status | (1 << ATMXT_RESTART_REQUIRED); + dd->status = dd->status | (1 << ATMXT_CHECKSUM_FAILED); + } else { + printk(KERN_ERR "%s: IC settings checksum fail. %s\n", + __func__, "Sending settings (no backup)..."); + err = atmxt_send_settings(dd, false); + if (err < 0) { + printk(KERN_ERR + "%s: Failed to update IC settings.\n", + __func__); + goto atmxt_message_handler6_fail; + } + } + } + +atmxt_message_handler6_fail: + return err; +} + +static int atmxt_message_handler9(struct atmxt_driver_data *dd, + uint8_t *msg, uint8_t size) +{ + int err = 0; + uint8_t tchidx = 0; + + atmxt_dbg(dd, ATMXT_DBG3, "%s: Handling message type 9...\n", __func__); + + if (size < 7) { + printk(KERN_ERR "%s: Message size is too small.\n", __func__); + err = -EINVAL; + goto atmxt_message_handler9_fail; + } + + tchidx = msg[0] - dd->data->touch_id_offset; + if (tchidx >= ARRAY_SIZE(dd->rdat->tchdat)) { + printk(KERN_ERR "%s: Touch %hu is unsupported.\n", + __func__, tchidx); + err = -EOVERFLOW; + goto atmxt_message_handler9_fail; + } + + dd->status = dd->status | (1 << ATMXT_REPORT_TOUCHES); + + dd->rdat->tchdat[tchidx].id = tchidx; + + dd->rdat->tchdat[tchidx].x = (msg[2] << 4) | ((msg[4] & 0xF0) >> 4); + if (!(dd->data->res[0])) + dd->rdat->tchdat[tchidx].x = dd->rdat->tchdat[tchidx].x >> 2; + + dd->rdat->tchdat[tchidx].y = (msg[3] << 4) | (msg[4] & 0x0F); + if (!(dd->data->res[1])) + dd->rdat->tchdat[tchidx].y = dd->rdat->tchdat[tchidx].y >> 2; + + dd->rdat->tchdat[tchidx].p = msg[6]; + dd->rdat->tchdat[tchidx].w = msg[5]; + + if (((msg[1] & 0x40) && (msg[1] & 0x20)) || + ((msg[1] & 0x40) && (msg[1] & 0x02)) || + ((msg[1] & 0x20) && (msg[1] & 0x02))) { + printk(KERN_ERR "%s: System too slow %s 0x%02X.\n", + __func__, "to see all touch events for report id", + msg[0]); + } + + if (msg[1] & 0x22) { + atmxt_dbg(dd, ATMXT_DBG1, "%s: Touch ID %hu released.\n", + __func__, tchidx); + dd->rdat->tchdat[tchidx].active = false; + } else { + dd->rdat->tchdat[tchidx].active = true; + } + + if (msg[1] & 0x02) { + printk(KERN_INFO "%s: Touch ID %hu suppressed.\n", + __func__, tchidx); + } + +atmxt_message_handler9_fail: + return err; +} + +static int atmxt_message_handler42(struct atmxt_driver_data *dd, + uint8_t *msg, uint8_t size) +{ + int err = 0; + + atmxt_dbg(dd, ATMXT_DBG3, + "%s: Handling message type 42...\n", __func__); + + if (size < 2) { + printk(KERN_ERR "%s: Message size is too small.\n", __func__); + err = -EINVAL; + goto atmxt_message_handler42_fail; + } + + if (msg[1] & 0x01) { + printk(KERN_ERR "%s: Touch suppression is active.\n", + __func__); + } else { + printk(KERN_INFO "%s: Touch suppression is disabled.\n", + __func__); + } + +atmxt_message_handler42_fail: + return err; +} + +static int atmxt_resume_restart(struct atmxt_driver_data *dd) +{ + int err = 0; + + atmxt_dbg(dd, ATMXT_DBG3, "%s: Resume restarting IC...\n", __func__); + + if (dd->status & (1 << ATMXT_IRQ_ENABLED_FLAG)) { + disable_irq_nosync(dd->client->irq); + dd->status = dd->status & ~(1 << ATMXT_IRQ_ENABLED_FLAG); + } + + err = atmxt_restart_ic(dd); + if (err < 0) { + printk(KERN_ERR "%s: Failed to restart the touch IC.\n", + __func__); + atmxt_set_drv_state(dd, ATMXT_DRV_IDLE); + goto atmxt_resume_restart_fail; + } + + if (!(dd->status & (1 << ATMXT_IRQ_ENABLED_FLAG))) { + if (atmxt_get_drv_state(dd) != ATMXT_DRV_ACTIVE) + atmxt_set_drv_state(dd, ATMXT_DRV_ACTIVE); + dd->status = dd->status | (1 << ATMXT_IRQ_ENABLED_FLAG); + enable_irq(dd->client->irq); + } + +atmxt_resume_restart_fail: + return err; +} + +static int atmxt_force_bootloader(struct atmxt_driver_data *dd) +{ + int err = 0; + int i = 0; + uint8_t cmd = 0x00; + bool chg_used = false; + uint8_t chg_cmd[2] = {0x00, 0x00}; + uint8_t rst[2] = {0x00, 0x00}; + + for (i = 0; i < dd->info_blk->size; i += 6) { + if (dd->info_blk->data[i+0] == 18) { + if (dd->info_blk->data[i+3] < 1) { + atmxt_dbg(dd, ATMXT_DBG3, + "%s: Comm is too small--%s.\n", + __func__, "will pool instead"); + goto atmxt_force_bootloader_check_reset; + } + chg_cmd[0] = dd->info_blk->data[i+1] + 1; + chg_cmd[1] = dd->info_blk->data[i+2]; + if (chg_cmd[0] < dd->info_blk->data[i+1]) + chg_cmd[1]++; + break; + } + } + + if ((chg_cmd[0] == 0x00) && (chg_cmd[1] == 0x00)) { + atmxt_dbg(dd, ATMXT_DBG3, + "%s: No interrupt force available--%s.\n", + __func__, "will pool instead"); + goto atmxt_force_bootloader_check_reset; + } + + cmd = 0x02; + err = atmxt_i2c_write(dd, chg_cmd[0], chg_cmd[1], &cmd, 1); + if (err < 0) { + printk(KERN_ERR "%s: Unable to force interrupt low--%s.\n", + __func__, "will poll instead"); + } else { + chg_used = true; + } + +atmxt_force_bootloader_check_reset: + for (i = 0; i < dd->info_blk->size; i += 6) { + if (dd->info_blk->data[i+0] == 6) { + rst[0] = dd->info_blk->data[i+1]; + rst[1] = dd->info_blk->data[i+2]; + break; + } + } + + if ((rst[0] == 0x00) && (rst[1] == 0x00)) { + atmxt_dbg(dd, ATMXT_DBG3, + "%s: No soft reset available--%s.\n", __func__, + "will try hardware recovery instead"); + goto atmxt_force_bootloader_use_recov; + } + + cmd = 0xA5; + err = atmxt_i2c_write(dd, rst[0], rst[1], &cmd, 1); + if (err < 0) { + printk(KERN_ERR "%s: Unable to send flash reset command.\n", + __func__); + goto atmxt_force_bootloader_use_recov; + } + + if (!chg_used) { + for (i = 0; i < 128; i++) { + if (gpio_get_value(dd->pdata->gpio_interrupt) == 1) + break; + else + udelay(2000); + } + + if (i == 128) { + printk(KERN_ERR "%s: %s.\n", __func__, + "Waiting for flash reset timed out"); + err = -ETIME; + goto atmxt_force_bootloader_exit; + } + } + + goto atmxt_force_bootloader_exit; + +atmxt_force_bootloader_use_recov: + atmxt_dbg(dd, ATMXT_DBG2, "%s: Using hardware recovery...\n", __func__); + printk(KERN_ERR "%s: Forced hardware recovery failed--%s.\n", + __func__, "unable to reflash IC"); + err = -ENOSYS; + +atmxt_force_bootloader_exit: + return err; +} + +static bool atmxt_check_firmware_update(struct atmxt_driver_data *dd) +{ + bool update_fw = false; + + if ((dd->util->fw[1] != dd->info_blk->header[0]) || + (dd->util->fw[2] != dd->info_blk->header[1])) { + printk(KERN_ERR + "%s: Platform firmware does not match touch IC. %s\n", + __func__, "Unable to check for firmware update."); + goto atmxt_check_firmware_update_exit; + } + + update_fw = !((dd->util->fw[3] == dd->info_blk->header[2]) && + (dd->util->fw[4] == dd->info_blk->header[3])); + +atmxt_check_firmware_update_exit: + return update_fw; +} + +static int atmxt_validate_firmware(uint8_t *data, uint32_t size) +{ + int err = 0; + uint32_t iter = 0; + int length = 0; + + if (data == NULL || size == 0) { + printk(KERN_ERR "%s: No firmware data found.\n", __func__); + err = -ENODATA; + goto atmxt_validate_firmware_fail; + } + + if (data[0] < 4) { + printk(KERN_ERR "%s: Invalid firmware header.\n", __func__); + err = -EINVAL; + goto atmxt_validate_firmware_fail; + } else if ((data[0] + 1) >= size) { + printk(KERN_ERR "%s: Firmware is malformed.\n", __func__); + err = -EOVERFLOW; + goto atmxt_validate_firmware_fail; + } + + iter = iter + data[0] + 1; + + while (iter < (size - 1)) { + length = (data[iter+0] << 8) | data[iter+1]; + if ((iter + length + 2) > size) { + printk(KERN_ERR + "%s: Overflow in firmware image %s %u.\n", + __func__, "on iter", iter); + err = -EOVERFLOW; + goto atmxt_validate_firmware_fail; + } + + iter = iter + length + 2; + } + + if (iter != size) { + printk(KERN_ERR "%s: Firmware image misaligned.\n", __func__); + err = -ENOEXEC; + goto atmxt_validate_firmware_fail; + } + +atmxt_validate_firmware_fail: + return err; +} + +static int atmxt_flash_firmware(struct atmxt_driver_data *dd) +{ + int err = 0; + uint8_t *img = dd->util->fw; + uint32_t size = dd->util->fw_size; + uint32_t iter = 0; + uint8_t status = 0x00; + bool irq_low = false; + bool frame_crc_failed = false; + + printk(KERN_INFO "%s: Reflashing touch IC...\n", __func__); + + err = atmxt_i2c_write(dd, 0xDC, 0xAA, NULL, 0); + if (err < 0) { + printk(KERN_ERR "%s: Unable to send unlock command.\n", + __func__); + goto atmxt_flash_firmware_fail; + } + + iter = img[0] + 1; + while (iter < (size - 1)) { + irq_low = atmxt_wait4irq(dd); + if (!irq_low) { + printk(KERN_ERR + "%s: Timeout waiting %s for iter %u.\n", + __func__, "for frame interrupt", iter); + err = -ETIME; + goto atmxt_flash_firmware_fail; + } + + err = atmxt_i2c_read(dd, &status, 1); + if (err < 0) { + printk(KERN_ERR + "%s: Error reading frame byte for iter %u.\n", + __func__, iter); + goto atmxt_flash_firmware_fail; + } + + if ((status & 0xC0) != 0x80) { + printk(KERN_ERR "%s: %s 0x%02X %s %u.\n", __func__, + "Unexpected wait status", status, + "received for iter", iter); + err = -EPROTO; + goto atmxt_flash_firmware_fail; + } + + err = atmxt_i2c_write(dd, img[iter+0], img[iter+1], + &(img[iter+2]), (img[iter+0] << 8) | img[iter+1]); + if (err < 0) { + printk(KERN_ERR "%s: Error sending frame iter %u.\n", + __func__, iter); + goto atmxt_flash_firmware_fail; + } + + irq_low = atmxt_wait4irq(dd); + if (!irq_low) { + printk(KERN_ERR + "%s: Timeout waiting %s for iter %u.\n", + __func__, "for check interrupt", iter); + err = -ETIME; + goto atmxt_flash_firmware_fail; + } + + err = atmxt_i2c_read(dd, &status, 1); + if (err < 0) { + printk(KERN_ERR + "%s: Error reading check byte for iter %u.\n", + __func__, iter); + goto atmxt_flash_firmware_fail; + } + + if (status != 0x02) { + printk(KERN_ERR "%s: %s 0x%02X %s %u.\n", __func__, + "Unexpected frame status", status, + "received for iter", iter); + err = -EPROTO; + goto atmxt_flash_firmware_fail; + } + + irq_low = atmxt_wait4irq(dd); + if (!irq_low) { + printk(KERN_ERR + "%s: Timeout waiting %s for iter %u.\n", + __func__, "for result interrupt", iter); + err = -ETIME; + goto atmxt_flash_firmware_fail; + } + + err = atmxt_i2c_read(dd, &status, 1); + if (err < 0) { + printk(KERN_ERR + "%s: Error reading result byte for iter %u.\n", + __func__, iter); + goto atmxt_flash_firmware_fail; + } + + if (status == 0x04) { + iter = iter + ((img[iter+0] << 8) | img[iter+1]) + 2; + frame_crc_failed = false; + } else if (!frame_crc_failed) { + printk(KERN_ERR "%s: %s %u--%s.\n", + __func__, "Frame CRC failed for iter", + iter, "will try to re-send"); + frame_crc_failed = true; + } else { + printk(KERN_ERR "%s: %s %u--%s.\n", + __func__, "Frame CRC failed for iter", + iter, "check firmware image"); + err = -ECOMM; + goto atmxt_flash_firmware_fail; + } + } + +atmxt_flash_firmware_fail: + return err; +} + +static char *atmxt_msg2str(const uint8_t *msg, uint8_t size) +{ + char *str = NULL; + int i = 0; + int err = 0; + + str = kzalloc(sizeof(char) * (size * 5), GFP_KERNEL); + if (str == NULL) { + printk(KERN_ERR "%s: Failed to allocate message string.\n", + __func__); + goto atmxt_msg2str_exit; + } + + for (i = 0; i < size; i++) { + err = sprintf(str, "%s0x%02X ", str, msg[i]); + if (err < 0) { + printk(KERN_ERR "%s: Error in sprintf on pass %d", + __func__, i); + goto atmxt_msg2str_exit; + } + } + + str[err-1] = '\0'; + +atmxt_msg2str_exit: + return str; +} + +static bool atmxt_wait4irq(struct atmxt_driver_data *dd) +{ + bool irq_low = false; + int i = 0; + + for (i = 0; i < 500; i++) { + if (gpio_get_value(dd->pdata->gpio_interrupt) != 0) { + msleep(20); + } else { + irq_low = true; + break; + } + } + + return irq_low; +} + +#ifdef CONFIG_TOUCHSCREEN_DEBUG +static ssize_t atmxt_debug_drv_debug_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct atmxt_driver_data *dd = dev_get_drvdata(dev); + + return sprintf(buf, "Current Debug Level: %hu\n", dd->dbg->dbg_lvl); +} +static ssize_t atmxt_debug_drv_debug_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int err = 0; + struct atmxt_driver_data *dd = dev_get_drvdata(dev); + unsigned long value = 0; + + mutex_lock(dd->mutex); + + err = strict_strtoul(buf, 10, &value); + if (err < 0) { + printk(KERN_ERR "%s: Failed to convert value.\n", __func__); + goto atmxt_debug_drv_debug_store_exit; + } + + if (value > 255) { + printk(KERN_ERR "%s: Invalid debug level %lu--setting to %u.\n", + __func__, value, ATMXT_DBG3); + dd->dbg->dbg_lvl = ATMXT_DBG3; + } else { + dd->dbg->dbg_lvl = value; + printk(KERN_INFO "%s: Debug level is now %hu.\n", + __func__, dd->dbg->dbg_lvl); + } + + err = size; + +atmxt_debug_drv_debug_store_exit: + mutex_unlock(dd->mutex); + + return err; +} +static DEVICE_ATTR(drv_debug, S_IRUSR | S_IWUSR, + atmxt_debug_drv_debug_show, atmxt_debug_drv_debug_store); + +static ssize_t atmxt_debug_drv_flags_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct atmxt_driver_data *dd = dev_get_drvdata(dev); + + return sprintf(buf, "Current Driver Flags: 0x%04X\n", dd->settings); +} +static ssize_t atmxt_debug_drv_flags_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int err = 0; + struct atmxt_driver_data *dd = dev_get_drvdata(dev); + unsigned long value = 0; + + mutex_lock(dd->mutex); + + err = strict_strtoul(buf, 16, &value); + if (err < 0) { + printk(KERN_ERR "%s: Failed to convert value.\n", __func__); + goto atmxt_debug_drv_flags_store_exit; + } + + if (value > 65535) { + printk(KERN_ERR "%s: Invalid flag settings 0x%08lX passed.\n", + __func__, value); + err = -EOVERFLOW; + goto atmxt_debug_drv_flags_store_exit; + } else { + dd->settings = value; + atmxt_dbg(dd, ATMXT_DBG3, + "%s: Driver flags now set to 0x%04X.\n", + __func__, dd->settings); + } + + err = size; + +atmxt_debug_drv_flags_store_exit: + mutex_unlock(dd->mutex); + + return err; +} +static DEVICE_ATTR(drv_flags, S_IRUSR | S_IWUSR, + atmxt_debug_drv_flags_show, atmxt_debug_drv_flags_store); +#endif + +static ssize_t atmxt_debug_drv_irq_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct atmxt_driver_data *dd = dev_get_drvdata(dev); + + if (dd->status & (1 << ATMXT_IRQ_ENABLED_FLAG)) + return sprintf(buf, "Driver interrupt is ENABLED.\n"); + else + return sprintf(buf, "Driver interrupt is DISABLED.\n"); +} +static ssize_t atmxt_debug_drv_irq_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int err = 0; + struct atmxt_driver_data *dd = dev_get_drvdata(dev); + unsigned long value = 0; + + mutex_lock(dd->mutex); + + err = strict_strtoul(buf, 10, &value); + if (err < 0) { + printk(KERN_ERR "%s: Failed to convert value.\n", __func__); + goto atmxt_debug_drv_irq_store_exit; + } + + if ((atmxt_get_drv_state(dd) != ATMXT_DRV_ACTIVE) && + (atmxt_get_drv_state(dd) != ATMXT_DRV_IDLE)) { + printk(KERN_ERR "%s: %s %s or %s states.\n", + __func__, "Interrupt can be changed only in", + atmxt_driver_state_string[ATMXT_DRV_ACTIVE], + atmxt_driver_state_string[ATMXT_DRV_IDLE]); + err = -EACCES; + goto atmxt_debug_drv_irq_store_exit; + } + + switch (value) { + case 0: + if (dd->status & (1 << ATMXT_IRQ_ENABLED_FLAG)) { + disable_irq_nosync(dd->client->irq); + atmxt_set_drv_state(dd, ATMXT_DRV_IDLE); + dd->status = + dd->status & ~(1 << ATMXT_IRQ_ENABLED_FLAG); + } + break; + + case 1: + if (!(dd->status & (1 << ATMXT_IRQ_ENABLED_FLAG))) { + dd->status = + dd->status | (1 << ATMXT_IRQ_ENABLED_FLAG); + enable_irq(dd->client->irq); + atmxt_set_drv_state(dd, ATMXT_DRV_ACTIVE); + } + break; + + default: + printk(KERN_ERR "%s: Invalid value passed.\n", __func__); + err = -EINVAL; + goto atmxt_debug_drv_irq_store_exit; + } + + err = size; + +atmxt_debug_drv_irq_store_exit: + mutex_unlock(dd->mutex); + + return err; +} +static DEVICE_ATTR(drv_irq, S_IRUSR | S_IWUSR, + atmxt_debug_drv_irq_show, atmxt_debug_drv_irq_store); + +static ssize_t atmxt_debug_drv_stat_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct atmxt_driver_data *dd = dev_get_drvdata(dev); + + return sprintf(buf, "Driver state is %s.\nIC state is %s.\n", + atmxt_driver_state_string[atmxt_get_drv_state(dd)], + atmxt_ic_state_string[atmxt_get_ic_state(dd)]); +} +static DEVICE_ATTR(drv_stat, S_IRUGO, atmxt_debug_drv_stat_show, NULL); + +#ifdef CONFIG_TOUCHSCREEN_DEBUG +static ssize_t atmxt_debug_drv_tdat_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct atmxt_driver_data *dd = dev_get_drvdata(dev); + + if (dd->status & (1 << ATMXT_WAITING_FOR_TDAT)) + return sprintf(buf, "Driver is waiting for data load.\n"); + else + return sprintf(buf, "No data loading in progress.\n"); +} +static ssize_t atmxt_debug_drv_tdat_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int err = 0; + struct atmxt_driver_data *dd = dev_get_drvdata(dev); + + mutex_lock(dd->mutex); + + if (dd->status & (1 << ATMXT_WAITING_FOR_TDAT)) { + printk(KERN_ERR "%s: Driver is already waiting for data.\n", + __func__); + err = -EALREADY; + goto atmxt_debug_drv_tdat_store_fail; + } + + printk(KERN_INFO "%s: Enabling firmware class loader...\n", __func__); + + err = request_firmware_nowait(THIS_MODULE, + FW_ACTION_NOHOTPLUG, "", &(dd->client->dev), + GFP_KERNEL, dd, atmxt_tdat_callback); + if (err < 0) { + printk(KERN_ERR + "%s: Firmware request failed with error code %d.\n", + __func__, err); + goto atmxt_debug_drv_tdat_store_fail; + } + + dd->status = dd->status | (1 << ATMXT_WAITING_FOR_TDAT); + err = size; + +atmxt_debug_drv_tdat_store_fail: + mutex_unlock(dd->mutex); + + return err; +} +static DEVICE_ATTR(drv_tdat, S_IRUSR | S_IWUSR, + atmxt_debug_drv_tdat_show, atmxt_debug_drv_tdat_store); +#endif + +static ssize_t atmxt_debug_drv_ver_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "Driver: %s\nVersion: %s\nDate: %s\n", + ATMXT_I2C_NAME, ATMXT_DRIVER_VERSION, ATMXT_DRIVER_DATE); +} +static DEVICE_ATTR(drv_ver, S_IRUGO, atmxt_debug_drv_ver_show, NULL); + +static ssize_t atmxt_debug_hw_irqstat_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int err = 0; + struct atmxt_driver_data *dd = dev_get_drvdata(dev); + + err = gpio_get_value(dd->pdata->gpio_interrupt); + if (err < 0) { + printk(KERN_ERR + "%s: Failed to read irq level with error code %d.\n", + __func__, err); + err = sprintf(buf, + "Failed to read irq level with error code %d.\n", + err); + goto atmxt_debug_hw_irqstat_show_exit; + } + + switch (err) { + case 0: + err = sprintf(buf, "Interrupt line is LOW.\n"); + break; + case 1: + err = sprintf(buf, "Interrupt line is HIGH.\n"); + break; + default: + err = sprintf(buf, "Read irq level of %d.\n", err); + break; + } + +atmxt_debug_hw_irqstat_show_exit: + return err; +} +static DEVICE_ATTR(hw_irqstat, S_IRUGO, atmxt_debug_hw_irqstat_show, NULL); + +#ifdef CONFIG_TOUCHSCREEN_DEBUG +static ssize_t atmxt_debug_hw_reset_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int err = 0; + struct atmxt_driver_data *dd = dev_get_drvdata(dev); + + mutex_lock(dd->mutex); + + if (atmxt_get_drv_state(dd) == ATMXT_DRV_INIT) { + printk(KERN_ERR "%s: %s %s.\n", __func__, + "Unable to restart IC in driver state", + atmxt_driver_state_string[ATMXT_DRV_INIT]); + err = -EACCES; + goto atmxt_debug_hw_reset_store_fail; + } + + if (dd->status & (1 << ATMXT_IRQ_ENABLED_FLAG)) { + disable_irq_nosync(dd->client->irq); + dd->status = dd->status & ~(1 << ATMXT_IRQ_ENABLED_FLAG); + } + + err = atmxt_restart_ic(dd); + if (err < 0) { + printk(KERN_ERR "%s: Failed to %s with error code %d.\n", + __func__, "re-initialize the touch IC", err); + atmxt_set_drv_state(dd, ATMXT_DRV_IDLE); + goto atmxt_debug_hw_reset_store_fail; + } + + if (((atmxt_get_drv_state(dd) == ATMXT_DRV_ACTIVE)) && + (!(dd->status & (1 << ATMXT_IRQ_ENABLED_FLAG)))) { + dd->status = dd->status | (1 << ATMXT_IRQ_ENABLED_FLAG); + enable_irq(dd->client->irq); + } + + err = size; + +atmxt_debug_hw_reset_store_fail: + mutex_unlock(dd->mutex); + return err; +} +static DEVICE_ATTR(hw_reset, S_IWUSR, NULL, atmxt_debug_hw_reset_store); +#endif + +static ssize_t atmxt_debug_hw_ver_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct atmxt_driver_data *dd = dev_get_drvdata(dev); + + return sprintf(buf, "Touch Data File: %s\n", dd->pdata->filename); +} +static DEVICE_ATTR(hw_ver, S_IRUGO, atmxt_debug_hw_ver_show, NULL); + +#ifdef CONFIG_TOUCHSCREEN_DEBUG +static ssize_t atmxt_debug_ic_grpdata_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int err = 0; + struct atmxt_driver_data *dd = dev_get_drvdata(dev); + int i = 0; + uint8_t addr_lo = 0x00; + uint8_t addr_hi = 0x00; + uint8_t *entry = NULL; + int size = 0; + uint8_t *data_in = NULL; + + mutex_lock(dd->mutex); + + if ((atmxt_get_ic_state(dd) != ATMXT_IC_ACTIVE) && + (atmxt_get_ic_state(dd) != ATMXT_IC_SLEEP)) { + printk(KERN_ERR "%s: %s %s or %s states.\n", __func__, + "Group data can be read only in IC", + atmxt_ic_state_string[ATMXT_IC_ACTIVE], + atmxt_ic_state_string[ATMXT_IC_SLEEP]); + err = sprintf(buf, "%s %s or %s states.\n", + "Group data can be read only in IC", + atmxt_ic_state_string[ATMXT_IC_ACTIVE], + atmxt_ic_state_string[ATMXT_IC_SLEEP]); + goto atmxt_debug_ic_grpdata_show_exit; + } + + for (i = 0; i < dd->info_blk->size; i += 6) { + if (dd->info_blk->data[i+0] == dd->dbg->grp_num) { + entry = &(dd->info_blk->data[i]); + break; + } + } + + if (entry == NULL) { + printk(KERN_ERR "%s: Group %hu does not exist.\n", + __func__, dd->dbg->grp_num); + err = sprintf(buf, "Group %hu does not exist.\n", + dd->dbg->grp_num); + goto atmxt_debug_ic_grpdata_show_exit; + } + + if (dd->dbg->grp_off > entry[3]) { + printk(KERN_ERR "%s: Offset %hu exceeds group size of %u.\n", + __func__, dd->dbg->grp_off, entry[3]+1); + err = sprintf(buf, "Offset %hu exceeds group size of %u.\n", + dd->dbg->grp_off, entry[3]+1); + goto atmxt_debug_ic_grpdata_show_exit; + } + + addr_lo = entry[1] + dd->dbg->grp_off; + addr_hi = entry[2]; + if (addr_lo < entry[1]) + addr_hi++; + + err = atmxt_i2c_write(dd, addr_lo, addr_hi, NULL, 0); + if (err < 0) { + printk(KERN_ERR + "%s: Failed to set group pointer with error code %d.\n", + __func__, err); + err = sprintf(buf, + "Failed to set group pointer with error code %d.\n", + err); + goto atmxt_debug_ic_grpdata_show_exit; + } + + size = entry[3] - dd->dbg->grp_off + 1; + data_in = kzalloc(sizeof(uint8_t) * size, GFP_KERNEL); + if (data_in == NULL) { + printk(KERN_ERR "%s: Unable to allocate memory buffer.\n", + __func__); + err = sprintf(buf, "Unable to allocate memory buffer.\n"); + goto atmxt_debug_ic_grpdata_show_exit; + } + + err = atmxt_i2c_read(dd, data_in, size); + if (err < 0) { + printk(KERN_ERR "%s: Failed to read group data.\n", __func__); + err = sprintf(buf, "Failed to read group data.\n"); + goto atmxt_debug_ic_grpdata_show_exit; + } + + err = sprintf(buf, "Group %hu, Offset %hu:\n", + dd->dbg->grp_num, dd->dbg->grp_off); + if (err < 0) { + printk(KERN_ERR "%s: Error in header sprintf.\n", __func__); + goto atmxt_debug_ic_grpdata_show_exit; + } + + for (i = 0; i < size; i++) { + err = sprintf(buf, "%s0x%02hX\n", buf, data_in[i]); + if (err < 0) { + printk(KERN_ERR "%s: Error in sprintf loop %d.\n", + __func__, i); + goto atmxt_debug_ic_grpdata_show_exit; + } + } + + err = sprintf(buf, "%s(%u bytes)\n", buf, size); + if (err < 0) { + printk(KERN_ERR "%s: Error in byte count sprintf.\n", __func__); + goto atmxt_debug_ic_grpdata_show_exit; + } + +atmxt_debug_ic_grpdata_show_exit: + kfree(data_in); + mutex_unlock(dd->mutex); + + return (ssize_t) err; +} +static ssize_t atmxt_debug_ic_grpdata_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int err = 0; + struct atmxt_driver_data *dd = dev_get_drvdata(dev); + int i = 0; + uint8_t addr_lo = 0x00; + uint8_t addr_hi = 0x00; + uint8_t *entry = NULL; + int data_size = 0; + uint8_t *data_out = NULL; + unsigned long value = 0; + uint8_t *conv_buf = NULL; + + mutex_lock(dd->mutex); + + if ((atmxt_get_ic_state(dd) != ATMXT_IC_ACTIVE) && + (atmxt_get_ic_state(dd) != ATMXT_IC_SLEEP)) { + printk(KERN_ERR "%s: %s %s or %s states.\n", __func__, + "Group data can be written only in IC", + atmxt_ic_state_string[ATMXT_IC_ACTIVE], + atmxt_ic_state_string[ATMXT_IC_SLEEP]); + err = -EACCES; + goto atmxt_debug_ic_grpdata_store_exit; + } + + for (i = 0; i < dd->info_blk->size; i += 6) { + if (dd->info_blk->data[i+0] == dd->dbg->grp_num) { + entry = &(dd->info_blk->data[i]); + break; + } + } + + if (entry == NULL) { + printk(KERN_ERR "%s: Group %hu does not exist.\n", + __func__, dd->dbg->grp_num); + err = -ENOENT; + goto atmxt_debug_ic_grpdata_store_exit; + } + + if ((dd->dbg->grp_off) > entry[3]) { + printk(KERN_ERR "%s: Offset %hu exceeds data size.\n", + __func__, dd->dbg->grp_off); + err = -EOVERFLOW; + goto atmxt_debug_ic_grpdata_store_exit; + } + + if ((size % 5 != 0) || (size == 0)) { + printk(KERN_ERR "%s: Invalid data format. %s\n", + __func__, "Use \"0xHH,0xHH,...,0xHH\" instead."); + err = -EINVAL; + goto atmxt_debug_ic_grpdata_store_exit; + } + + data_out = kzalloc(sizeof(uint8_t) * (size / 5), GFP_KERNEL); + if (data_out == NULL) { + printk(KERN_ERR "%s: Unable to allocate output buffer.\n", + __func__); + err = -ENOMEM; + goto atmxt_debug_ic_grpdata_store_exit; + } + + conv_buf = kzalloc(sizeof(uint8_t) * 5, GFP_KERNEL); + if (conv_buf == NULL) { + printk(KERN_ERR "%s: Unable to allocate conversion buffer.\n", + __func__); + err = -ENOMEM; + goto atmxt_debug_ic_grpdata_store_exit; + } + + for (i = 0; i < size; i += 5) { + memcpy(conv_buf, &(buf[i]), 4); + err = strict_strtoul(conv_buf, 16, &value); + if (err < 0) { + printk(KERN_ERR "%s: Argument conversion failed.\n", + __func__); + goto atmxt_debug_ic_grpdata_store_exit; + } else if (value > 255) { + printk(KERN_ERR "%s: Value 0x%lX is too large.\n", + __func__, value); + err = -EOVERFLOW; + goto atmxt_debug_ic_grpdata_store_exit; + } + + data_out[data_size] = value; + data_size++; + } + + if ((dd->dbg->grp_off + data_size) > (entry[3] + 1)) { + printk(KERN_ERR "%s: Trying to write %d bytes at offset %hu, " + "which exceeds group size of %hu.\n", __func__, + data_size, dd->dbg->grp_off, entry[3]+1); + err = -EOVERFLOW; + goto atmxt_debug_ic_grpdata_store_exit; + } + + addr_lo = entry[1] + dd->dbg->grp_off; + addr_hi = entry[2]; + if (addr_lo < entry[1]) + addr_hi++; + + err = atmxt_i2c_write(dd, addr_lo, addr_hi, data_out, data_size); + if (err < 0) { + printk(KERN_ERR + "%s: Failed to write data with error code %d.\n", + __func__, err); + goto atmxt_debug_ic_grpdata_store_exit; + } + + if (!(dd->status & (1 << ATMXT_IGNORE_CHECKSUM))) { + printk(KERN_INFO + "%s: Disabled settings checksum verification %s.\n", + __func__, "until next boot"); + } + dd->status = dd->status | (1 << ATMXT_IGNORE_CHECKSUM); + + err = size; + +atmxt_debug_ic_grpdata_store_exit: + kfree(data_out); + kfree(conv_buf); + mutex_unlock(dd->mutex); + + return (ssize_t) err; +} +static DEVICE_ATTR(ic_grpdata, S_IRUSR | S_IWUSR, + atmxt_debug_ic_grpdata_show, atmxt_debug_ic_grpdata_store); + +static ssize_t atmxt_debug_ic_grpnum_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct atmxt_driver_data *dd = dev_get_drvdata(dev); + + return sprintf(buf, "Current Group: %hu\n", dd->dbg->grp_num); +} +static ssize_t atmxt_debug_ic_grpnum_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int err = 0; + struct atmxt_driver_data *dd = dev_get_drvdata(dev); + unsigned long value = 0; + + mutex_lock(dd->mutex); + + err = strict_strtoul(buf, 10, &value); + if (err < 0) { + printk(KERN_ERR "%s: Failed to convert value.\n", __func__); + goto atmxt_debug_ic_grpnum_store_exit; + } + + if (value > 255) { + printk(KERN_ERR "%s: Invalid group number %lu--%s.\n", + __func__, value, "setting to 255"); + dd->dbg->grp_num = 255; + } else { + dd->dbg->grp_num = value; + } + + err = size; + +atmxt_debug_ic_grpnum_store_exit: + mutex_unlock(dd->mutex); + + return err; +} +static DEVICE_ATTR(ic_grpnum, S_IRUSR | S_IWUSR, + atmxt_debug_ic_grpnum_show, atmxt_debug_ic_grpnum_store); + +static ssize_t atmxt_debug_ic_grpoffset_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct atmxt_driver_data *dd = dev_get_drvdata(dev); + + return sprintf(buf, "Current Offset: %hu\n", dd->dbg->grp_off); +} +static ssize_t atmxt_debug_ic_grpoffset_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int err = 0; + struct atmxt_driver_data *dd = dev_get_drvdata(dev); + unsigned long value = 0; + + mutex_lock(dd->mutex); + + err = strict_strtoul(buf, 10, &value); + if (err < 0) { + printk(KERN_ERR "%s: Failed to convert value.\n", __func__); + goto atmxt_debug_ic_grpoffset_store_exit; + } + + if (value > 255) { + printk(KERN_ERR "%s: Invalid offset %lu--setting to 255.\n", + __func__, value); + dd->dbg->grp_off = 255; + } else { + dd->dbg->grp_off = value; + } + + err = size; + +atmxt_debug_ic_grpoffset_store_exit: + mutex_unlock(dd->mutex); + + return err; +} +static DEVICE_ATTR(ic_grpoffset, S_IRUSR | S_IWUSR, + atmxt_debug_ic_grpoffset_show, atmxt_debug_ic_grpoffset_store); +#endif + +static ssize_t atmxt_debug_ic_ver_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct atmxt_driver_data *dd = dev_get_drvdata(dev); + + if (dd->info_blk == NULL) { + return sprintf(buf, + "No touch IC version information is available.\n"); + } else { + return sprintf(buf, "%s0x%02X\n%s0x%02X\n%s0x%02X\n%s0x%02X\n", + "Family ID: ", dd->info_blk->header[0], + "Variant ID: ", dd->info_blk->header[1], + "Version: ", dd->info_blk->header[2], + "Build: ", dd->info_blk->header[3]); + } +} +static DEVICE_ATTR(ic_ver, S_IRUGO, atmxt_debug_ic_ver_show, NULL); + +static int atmxt_create_sysfs_files(struct atmxt_driver_data *dd) +{ + int err = 0; + int check = 0; + +#ifdef CONFIG_TOUCHSCREEN_DEBUG + check = device_create_file(&(dd->client->dev), &dev_attr_drv_debug); + if (check < 0) { + printk(KERN_ERR "%s: Failed to create drv_debug.\n", __func__); + err = check; + } + + check = device_create_file(&(dd->client->dev), &dev_attr_drv_flags); + if (check < 0) { + printk(KERN_ERR "%s: Failed to create drv_flags.\n", __func__); + err = check; + } +#endif + + check = device_create_file(&(dd->client->dev), &dev_attr_drv_irq); + if (check < 0) { + printk(KERN_ERR "%s: Failed to create drv_irq.\n", __func__); + err = check; + } + + check = device_create_file(&(dd->client->dev), &dev_attr_drv_stat); + if (check < 0) { + printk(KERN_ERR "%s: Failed to create drv_stat.\n", __func__); + err = check; + } + +#ifdef CONFIG_TOUCHSCREEN_DEBUG + check = device_create_file(&(dd->client->dev), &dev_attr_drv_tdat); + if (check < 0) { + printk(KERN_ERR "%s: Failed to create drv_tdat.\n", __func__); + err = check; + } +#endif + + check = device_create_file(&(dd->client->dev), &dev_attr_drv_ver); + if (check < 0) { + printk(KERN_ERR "%s: Failed to create drv_ver.\n", __func__); + err = check; + } + + check = device_create_file(&(dd->client->dev), &dev_attr_hw_irqstat); + if (check < 0) { + printk(KERN_ERR "%s: Failed to create hw_irqstat.\n", __func__); + err = check; + } + +#ifdef CONFIG_TOUCHSCREEN_DEBUG + check = device_create_file(&(dd->client->dev), &dev_attr_hw_reset); + if (check < 0) { + printk(KERN_ERR "%s: Failed to create hw_reset.\n", __func__); + err = check; + } +#endif + + check = device_create_file(&(dd->client->dev), &dev_attr_hw_ver); + if (check < 0) { + printk(KERN_ERR "%s: Failed to create hw_ver.\n", __func__); + err = check; + } + +#ifdef CONFIG_TOUCHSCREEN_DEBUG + check = device_create_file(&(dd->client->dev), &dev_attr_ic_grpdata); + if (check < 0) { + printk(KERN_ERR "%s: Failed to create ic_grpdata.\n", __func__); + err = check; + } + + check = device_create_file(&(dd->client->dev), &dev_attr_ic_grpnum); + if (check < 0) { + printk(KERN_ERR "%s: Failed to create ic_grpnum.\n", __func__); + err = check; + } + + check = device_create_file(&(dd->client->dev), &dev_attr_ic_grpoffset); + if (check < 0) { + printk(KERN_ERR "%s: Failed to create ic_grpoffset.\n", + __func__); + err = check; + } +#endif + + check = device_create_file(&(dd->client->dev), &dev_attr_ic_ver); + if (check < 0) { + printk(KERN_ERR "%s: Failed to create ic_ver.\n", __func__); + err = check; + } + + return err; +} + +static void atmxt_remove_sysfs_files(struct atmxt_driver_data *dd) +{ +#ifdef CONFIG_TOUCHSCREEN_DEBUG + device_remove_file(&(dd->client->dev), &dev_attr_drv_debug); + device_remove_file(&(dd->client->dev), &dev_attr_drv_flags); +#endif + device_remove_file(&(dd->client->dev), &dev_attr_drv_irq); + device_remove_file(&(dd->client->dev), &dev_attr_drv_stat); +#ifdef CONFIG_TOUCHSCREEN_DEBUG + device_remove_file(&(dd->client->dev), &dev_attr_drv_tdat); +#endif + device_remove_file(&(dd->client->dev), &dev_attr_drv_ver); + device_remove_file(&(dd->client->dev), &dev_attr_hw_irqstat); +#ifdef CONFIG_TOUCHSCREEN_DEBUG + device_remove_file(&(dd->client->dev), &dev_attr_hw_reset); +#endif + device_remove_file(&(dd->client->dev), &dev_attr_hw_ver); +#ifdef CONFIG_TOUCHSCREEN_DEBUG + device_remove_file(&(dd->client->dev), &dev_attr_ic_grpdata); + device_remove_file(&(dd->client->dev), &dev_attr_ic_grpnum); + device_remove_file(&(dd->client->dev), &dev_attr_ic_grpoffset); +#endif + device_remove_file(&(dd->client->dev), &dev_attr_ic_ver); + return; +} diff --git a/drivers/input/touchscreen/atmxt.h b/drivers/input/touchscreen/atmxt.h new file mode 100644 index 00000000000..3711ef5180f --- /dev/null +++ b/drivers/input/touchscreen/atmxt.h @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2010-2012 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 + */ + +/* Local header for Atmel maXTouch touchscreens that uses tdat files */ +#ifndef _LINUX_ATMXT_H +#define _LINUX_ATMXT_H + +#include <linux/types.h> +#include <linux/input/touch_platform.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/mutex.h> +#ifdef CONFIG_HAS_EARLYSUSPEND +#include <linux/earlysuspend.h> +#endif + +#define ATMXT_DRIVER_VERSION "YN-04-01" +#define ATMXT_DRIVER_DATE "2012-06-28" + +#ifdef CONFIG_TOUCHSCREEN_DEBUG +#define atmxt_dbg(dd, level, format, args...) \ +{\ + if ((dd->dbg->dbg_lvl) >= level) \ + printk(KERN_INFO format, ## args); \ +} +#else +#define atmxt_dbg(dd, level, format, args...) {} +#endif + +#define ATMXT_DBG0 0 +#define ATMXT_DBG1 1 +#define ATMXT_DBG2 2 +#define ATMXT_DBG3 3 + +#define ATMXT_IRQ_ENABLED_FLAG 0 +#define ATMXT_WAITING_FOR_TDAT 1 +#define ATMXT_CHECKSUM_FAILED 2 +#define ATMXT_IGNORE_CHECKSUM 3 +#define ATMXT_REPORT_TOUCHES 4 +#define ATMXT_FIXING_CALIBRATION 5 +#define ATMXT_RECEIVED_CALIBRATION 6 +#define ATMXT_RESTART_REQUIRED 7 +#define ATMXT_SET_MESSAGE_POINTER 8 + +#define ATMXT_I2C_ATTEMPTS 10 +#define ATMXT_I2C_WAIT_TIME 50 +#define ATMXT_MAX_TOUCHES 10 +#define ATMXT_ABS_RESERVED 0xFFFF +#define ATMXT_IC_RESET_HOLD_TIME 1000 + + +enum atmxt_driver_state { + ATMXT_DRV_ACTIVE, + ATMXT_DRV_IDLE, + ATMXT_DRV_REFLASH, + ATMXT_DRV_INIT, +}; +static const char * const atmxt_driver_state_string[] = { + "ACTIVE", + "IDLE", + "REFLASH", + "INIT", +}; + +enum atmxt_ic_state { + ATMXT_IC_ACTIVE, + ATMXT_IC_SLEEP, + ATMXT_IC_UNKNOWN, + ATMXT_IC_BOOTLOADER, + ATMXT_IC_PRESENT, +}; +static const char * const atmxt_ic_state_string[] = { + "ACTIVE", + "SLEEP", + "UNKNOWN", + "BOOTLOADER", + "PRESENT", +}; + + +struct atmxt_util_data { + uint8_t *data; + size_t size; + uint8_t *tsett; + uint32_t tsett_size; + uint8_t *fw; + uint32_t fw_size; + uint8_t addr[2]; +} __packed; + +struct atmxt_nvm { + uint8_t *data; + uint16_t size; + uint8_t addr[2]; + uint8_t chksum[3]; +} __packed; + +struct atmxt_info_block { + uint8_t header[7]; + uint8_t *data; + uint8_t size; + uint8_t *msg_id; + uint8_t id_size; +} __packed; + +struct atmxt_addr { + uint8_t msg[2]; + uint8_t pwr[2]; + uint8_t rst[2]; + uint8_t nvm[2]; + uint8_t cal[2]; + uint8_t acq[2]; +} __packed; + +struct atmxt_data { + uint8_t pwr[2]; + uint8_t max_msg_size; + uint8_t touch_id_offset; + bool res[2]; + uint8_t acq[6]; + unsigned long timer; + uint8_t last_stat; +} __packed; + +struct atmxt_touch_data { + bool active; + uint16_t x; + uint16_t y; + uint8_t p; + uint8_t w; + uint8_t id; +} __packed; + +struct atmxt_report_data { + uint16_t axis[5]; + uint8_t active_touches; + struct atmxt_touch_data tchdat[ATMXT_MAX_TOUCHES]; +} __packed; + +struct atmxt_debug { + uint8_t dbg_lvl; + uint8_t grp_num; + uint8_t grp_off; +} __packed; + + +struct atmxt_driver_data { + struct touch_platform_data *pdata; + struct atmxt_util_data *util; + struct i2c_client *client; + struct mutex *mutex; + struct input_dev *in_dev; +#ifdef CONFIG_HAS_EARLYSUSPEND + struct early_suspend es; +#endif + + enum atmxt_driver_state drv_stat; + enum atmxt_ic_state ic_stat; + + struct atmxt_info_block *info_blk; + struct atmxt_nvm *nvm; + struct atmxt_addr *addr; + struct atmxt_data *data; + struct atmxt_report_data *rdat; + struct atmxt_debug *dbg; + + uint16_t status; + uint16_t settings; +} __packed; + +#endif /* _LINUX_ATMXT_H */ diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index ef992293598..9828842ceda 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -50,6 +50,12 @@ config LEDS_LM3530 controlled manually or using PWM input or using ambient light automatically. +config LEDS_LM3535 + tristate "LM3535 ALS driver" + depends on LEDS_CLASS && I2C + help + This option enables support for the LM3535 . + config LEDS_LM3533 tristate "LED support for LM3533" depends on LEDS_CLASS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index ac2897732b0..a287da98d30 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_LEDS_ATMEL_PWM) += leds-atmel-pwm.o obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o obj-$(CONFIG_LEDS_LM3530) += leds-lm3530.o +obj-$(CONFIG_LEDS_LM3535) += leds-lm3535.o obj-$(CONFIG_LEDS_LM3533) += leds-lm3533.o obj-$(CONFIG_LEDS_LM3642) += leds-lm3642.o obj-$(CONFIG_LEDS_MIKROTIK_RB532) += leds-rb532.o diff --git a/drivers/leds/als.h b/drivers/leds/als.h new file mode 100644 index 00000000000..6084e46b95f --- /dev/null +++ b/drivers/leds/als.h @@ -0,0 +1,14 @@ +#ifndef __ALS_H +#define __ALS_H + +#define NUM_ALS_ZONES 6 // 5 zones and 1 undefined + +typedef void (*als_cb)(unsigned prev_zone, unsigned curr_zone, uint32_t cookie); +int lm3535_register_als_callback(als_cb func, uint32_t cookie); +void lm3535_unregister_als_callback(als_cb func); +int adp8862_register_als_callback(als_cb func, uint32_t cookie); +void adp8862_unregister_als_callback(als_cb func); +unsigned lm3535_als_is_dark(void); +unsigned adp8862_als_is_dark(void); +typedef unsigned (*als_is_dark_func)(void); +#endif diff --git a/drivers/leds/leds-lm3535.c b/drivers/leds/leds-lm3535.c new file mode 100644 index 00000000000..03915b63b5e --- /dev/null +++ b/drivers/leds/leds-lm3535.c @@ -0,0 +1,1470 @@ +/* + * Copyright (C) 2013 Motorola Mobility, Inc. + * Author: Alina Yakovleva <qvdh43@motorola.com> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// Linux driver for LM3535 display backlight + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/hrtimer.h> +#include <linux/platform_device.h> +#include <linux/miscdevice.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <asm/io.h> +#include <linux/clk.h> +#include <linux/irq.h> +#include <linux/sysfs.h> +#include <linux/hwmon-sysfs.h> +#include <linux/leds.h> +#include <linux/mutex.h> +#include <linux/debugfs.h> +#include <linux/list.h> +#include <asm/gpio.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include "als.h" +#undef CONFIG_HAS_EARLYSUSPEND +#ifdef CONFIG_HAS_EARLYSUSPEND +#include <linux/earlysuspend.h> +#endif +#ifdef CONFIG_LM3535_ESD_RECOVERY +#include <mot/esd_poll.h> +#endif /* CONFIG_LM3535_ESD_RECOVERY */ + +#define MODULE_NAME "leds_lm3535" + +/****************************************************************************** + * LM3535 registers + ******************************************************************************/ +#define LM3535_ENABLE_REG 0x10 +#define LM3535_CONFIG_REG 0x20 +#define LM3535_OPTIONS_REG 0x30 +#define LM3535_ALS_REG 0x40 +#define LM3535_ALS_CTRL_REG 0x50 +#define LM3535_ALS_RESISTOR_REG 0x51 +#ifdef CONFIG_LM3535_BUTTON_BL +#define LM3535_ALS_SELECT_REG 0x52 +#endif +#define LM3535_BRIGHTNESS_CTRL_REG_A 0xA0 +#define LM3535_BRIGHTNESS_CTRL_REG_B 0xB0 +#define LM3535_BRIGHTNESS_CTRL_REG_C 0xC0 +#define LM3535_ALS_ZB0_REG 0X60 +#define LM3535_ALS_ZB1_REG 0X61 +#define LM3535_ALS_ZB2_REG 0X62 +#define LM3535_ALS_ZB3_REG 0X63 +#define LM3535_ALS_Z0T_REG 0X70 +#define LM3535_ALS_Z1T_REG 0X71 +#define LM3535_ALS_Z2T_REG 0X72 +#define LM3535_ALS_Z3T_REG 0X73 +#define LM3535_ALS_Z4T_REG 0X74 +#define LM3535_TRIM_REG 0xD0 + +#define ALS_FLAG_MASK 0x08 +#define ALS_ZONE_MASK 0x07 +#define ALS_NO_ZONE 5 +#define LM3535_TRIM_VALUE 0x68 + +#define LM3535_LED_MAX 0x7F // Max brightness value supported by LM3535 + +#define LM3535_HWEN_GPIO 98 + +/* Final revision CONFIG value */ +#define CONFIG_VALUE 0x4C +#define CONFIG_VALUE_NO_ALS 0x0C + +/* ALS Averaging time */ +#define ALS_AVERAGING 0x50 // 1600ms, needs 2 ave. periods + +/* Zone boundaries */ +static unsigned als_zb[] = {0x04, 0x42, 0x96, 0xFF}; +module_param_array(als_zb, uint, NULL, 0644); +static unsigned resistor_value = 0x30; +module_param(resistor_value, uint, 0644); +static unsigned pwm_value = 0x1; +module_param(pwm_value, uint, 0644); + +static unsigned als_sleep = 350; +module_param(als_sleep, uint, 0644); +static unsigned ramp_time = 200000; +module_param(ramp_time, uint, 0644); +enum { + TRACE_SUSPEND = 0x1, + TRACE_ALS = 0x2, + TRACE_BRIGHTNESS = 0x4, + TRACE_WRITE = 0x8, + TRACE_EVENT = 0x10, +}; +unsigned do_trace; /* = TRACE_ALS | TRACE_SUSPEND | TRACE_BRIGHTNESS;*/ +module_param(do_trace, uint, 0644); + +#define printk_write(fmt,args...) if (do_trace & TRACE_WRITE) printk(KERN_INFO fmt, ##args) +#define printk_br(fmt,args...) if (do_trace & TRACE_BRIGHTNESS) printk(KERN_INFO fmt, ##args) +#define printk_als(fmt,args...) if (do_trace & TRACE_ALS) printk(KERN_INFO fmt, ##args) +#define printk_suspend(fmt,args...) if (do_trace & TRACE_SUSPEND) printk(KERN_INFO fmt, ##args) +#define printk_event(fmt,args...) if (do_trace & TRACE_EVENT) printk(KERN_INFO fmt, ##args) + +/* ALS callbacks */ +static DEFINE_MUTEX(als_cb_mutex); +static LIST_HEAD(als_callbacks); +struct als_callback { + als_cb cb; + uint32_t cookie; + struct list_head entry; +}; + +struct lm3535_options_register_r1 { + int rs : 2; // Ramp step + int gt : 2; // Gain transition filter + int rev : 2; +}; +/* Ramp times for Rev1 in microseconds */ +static unsigned int lm3535_ramp_r1[] = {51, 13000, 26000, 52000}; + +struct lm3535_options_register_r2 { + int rs_down : 2; + int rs_up : 2; + int gt : 2; +}; +/* Ramp times for Rev2 in microseconds */ +static unsigned int lm3535_ramp_r2[] = {6, 6000, 12000, 24000}; + +struct lm3535_options_register_r3 { + int rs_down : 3; + int rs_up : 3; + int gt : 2; +}; +/* Ramp times for Rev3 in microseconds */ +static unsigned int lm3535_ramp_r3[] = + {6, 770, 1500, 3000, 6000, 12000, 25000, 50000}; + +static void lm3535_send_als_event (int zone); +static char *reg_name (int reg); +static int lm3535_configure (void); +static void lm3535_call_als_callbacks (unsigned old_zone, unsigned zone); +static void lm3535_set_options_r1 (uint8_t *buf, unsigned ramp); +static void lm3535_set_options_r2 (uint8_t *buf, unsigned ramp); +static void lm3535_set_options_r3 (uint8_t *buf, unsigned ramp); +static int lm3535_write_reg (unsigned reg, uint8_t value, const char *caller); +static int lm3535_read_reg (unsigned reg, uint8_t *value); +static int lm3535_set_ramp (struct i2c_client *client, + unsigned int on, unsigned int nsteps, unsigned int *rtime); +static int lm3535_enable(struct i2c_client *client, unsigned int on); +static int lm3535_probe(struct i2c_client *client, + const struct i2c_device_id *id); +static int lm3535_setup (struct i2c_client *client); +static int lm3535_remove (struct i2c_client *client); +static void lm3535_work_func (struct work_struct *work); +static irqreturn_t lm3535_irq_handler (int irq, void *dev_id); +static void lm3535_brightness_set(struct led_classdev *led_cdev, + enum led_brightness value); +/* static unsigned lm3535_read_als_zone (void); */ +#ifdef CONFIG_PM +#ifdef CONFIG_HAS_EARLYSUSPEND +static void lm3535_early_suspend (struct early_suspend *h); +static void lm3535_late_resume (struct early_suspend *h); +static struct early_suspend early_suspend_data = { + .level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 5, + .suspend = lm3535_early_suspend, + .resume = lm3535_late_resume, +}; + +#endif +static int lm3535_suspend (struct i2c_client *client, pm_message_t mesg); +static int lm3535_resume (struct i2c_client *client); +#endif + +static void (*lm3535_set_options_f)(uint8_t *buf, unsigned ramp) = + lm3535_set_options_r1; +static unsigned int *lm3535_ramp = lm3535_ramp_r1; + +/* LED class struct */ +static struct led_classdev lm3535_led = { + .name = "lcd-backlight", + .brightness_set = lm3535_brightness_set, +}; + +/* LED class struct for no ramping */ +static struct led_classdev lm3535_led_noramp = { + .name = "lcd-nr-backlight", + .brightness_set = lm3535_brightness_set, +}; +#ifdef CONFIG_LM3535_BUTTON_BL +static struct led_classdev lm3535_led_button = { + .name = "button-backlight", + .brightness_set = lm3535_button_brightness_set, +}; +#endif + +static const struct of_device_id of_lm3535_match[] = { + { .compatible = "ti,lm3535", }, + {}, +}; + +static const struct i2c_device_id lm3535_id[] = { + { "lm3535", 0 }, + { } +}; + +/* This is the I2C driver that will be inserted */ +static struct i2c_driver lm3535_driver = +{ + .driver = { + .name = "lm3535", + .of_match_table = of_match_ptr(of_lm3535_match), + }, + .id_table = lm3535_id, + .probe = lm3535_probe, + .remove = lm3535_remove, +#ifndef CONFIG_HAS_EARLYSUSPEND + .suspend = lm3535_suspend, + .resume = lm3535_resume, +#endif +}; + +#define LM3535_NUM_ZONES 6 +struct lm3535 { + uint16_t addr; + struct i2c_client *client; + struct input_dev *idev; + unsigned initialized; + unsigned enabled; + int use_irq; + int revision; + int nramp; + atomic_t als_zone; // Current ALS zone + atomic_t bright_zone; // Current brightness zone, diff. from ALS + atomic_t use_als; // Whether to use ALS + atomic_t in_suspend; // Whether the driver is in TCMD SUSPEND mode + atomic_t do_als_config; // Whether to configure ALS averaging + unsigned bvalue; // Current brightness register value + unsigned saved_bvalue; // Brightness before TCMD SUSPEND + //struct hrtimer timer; + struct work_struct work; +}; +static DEFINE_MUTEX(lm3535_mutex); + +static struct lm3535 lm3535_data = { + .nramp = 4, + .bvalue = 0x79, +}; + +#if 0 +unsigned lm3535_read_als_zone (void) +{ + uint8_t reg; + int ret; + + if (lm3535_data.revision <= 1) { + printk (KERN_ERR "%s: early revison, setting zone to 5 (no ALS)\n", + __FUNCTION__); + atomic_set (lm3535_data.als_zone, ALS_NO_ZONE); + return ALS_NO_ZONE; + } + lm3535_read_reg (LM3535_CONFIG_REG, ®); + if (reg & 0x50) { + ret = lm3535_read_reg (LM3535_ALS_REG, ®); + lm3535_data.als_zone = reg & ALS_ZONE_MASK; + return lm3535_data.als_zone; + } else { + printk (KERN_ERR "%s: ALS is not enabled, CONFIG=0x%x, zone=5\n", + __FUNCTION__, reg); + lm3535_data.als_zone = 5; + return 5; + } +} +#endif +int lm3535_register_als_callback(als_cb func, uint32_t cookie) +{ + struct als_callback *c; + + //printk (KERN_INFO "%s: enter\n", __FUNCTION__); + c = kzalloc (sizeof (struct als_callback), GFP_KERNEL); + if (c == NULL) { + printk (KERN_ERR "%s: unable to register ALS callback: kzalloc\n", + __FUNCTION__); + return -ENOMEM; + } + c->cb = func; + c->cookie = cookie; + mutex_lock (&als_cb_mutex); + list_add (&c->entry, &als_callbacks); + mutex_unlock (&als_cb_mutex); + return 0; +} +EXPORT_SYMBOL(lm3535_register_als_callback); + +void lm3535_unregister_als_callback (als_cb func) +{ + struct als_callback *c; + + if (!lm3535_data.initialized) { + printk (KERN_ERR "%s: not initialized\n", __FUNCTION__); + return; + } + printk (KERN_INFO "%s: enter\n", __FUNCTION__); + mutex_lock (&als_cb_mutex); + list_for_each_entry(c, &als_callbacks, entry) { + if (c->cb == func) { + list_del (&c->entry); + kfree(c); + mutex_unlock (&als_cb_mutex); + return; + } + } + mutex_unlock (&als_cb_mutex); + printk (KERN_ERR "%s: callback 0x%x not found\n", + __FUNCTION__, (unsigned int)func); +} +EXPORT_SYMBOL(lm3535_unregister_als_callback); + +unsigned lm3535_als_is_dark (void) +{ + unsigned zone; + + zone = atomic_read (&lm3535_data.als_zone); + printk (KERN_ERR "%s: enter, zone = %d\n", + __FUNCTION__, zone); + if (zone == 0 || zone == 5) + return 1; + else + return 0; +} +EXPORT_SYMBOL(lm3535_als_is_dark); + +static int lm3535_read_reg (unsigned reg, uint8_t *value) +{ + struct i2c_client *client = lm3535_data.client; + uint8_t buf[1]; + int ret = 0; + + if (!value) + return -EINVAL; + buf[0] = reg; + ret = i2c_master_send (client, buf, 1); + if (ret > 0) { + msleep_interruptible (1); + ret = i2c_master_recv (client, buf, 1); + if (ret > 0) + *value = buf[0]; + } + return ret; +} + +static int lm3535_write_reg (unsigned reg, uint8_t value, const char *caller) +{ + uint8_t buf[2] = {reg, value}; + int ret = 0; + + printk_write ("%s: writing 0x%X to reg 0x%X (%s) at addr 0x%X\n", + caller, buf[1], buf[0], reg_name (reg), lm3535_data.client->addr); + ret = i2c_master_send (lm3535_data.client, buf, 2); + if (ret < 0) + printk (KERN_ERR "%s: i2c_master_send error %d\n", + caller, ret); + return ret; +} + +/* ALS Coefficients */ +static long als_z0[] = {-48, 22225, -616209, 587754883}; +module_param_array(als_z0, long, NULL, 0644); + +static long als_z1[] = {24, -14596, 3987380, 659307205}; +module_param_array(als_z1, long, NULL, 0644); + +static long als_z2[] = {0, -5700, 2553000, 953424200}; +module_param_array(als_z2, long, NULL, 0644); + +static long als_z3[] = {0, -5700, 2553000, 953424200}; +module_param_array(als_z3, long, NULL, 0644); + +static long als_z4[] = {0, -5700, 2553000, 953424200}; +module_param_array(als_z4, long, NULL, 0644); + +static unsigned long als_denom = 10000000; +module_param(als_denom, ulong, 0644); + +static unsigned dim_values[] = {0x24, 0x30, 0x50, 0x50, 0x50}; +module_param_array(dim_values, uint, NULL, 0644); + +/* Convert slider value into LM3535 register value */ +static uint8_t lm3535_convert_value (unsigned value, unsigned zone) +{ + uint8_t reg; + uint32_t res; + + if (!value) + return 0; + + if (atomic_read (&lm3535_data.in_suspend)) { + printk_br ("%s: in TCMD SUSPEND, returning 0x%x\n", + __FUNCTION__, value/2); + return value/2; + } + switch (zone) { + case 0: + if (value == 1) + res = dim_values[0]; // DIM value + else + res = als_z0[0] * value * value * value + +als_z0[1] * value * value + +als_z0[2] * value + +als_z0[3]; + break; + case 1: + if (value == 1) + res = dim_values[1]; // DIM value + else + res = als_z1[0] * value * value * value + +als_z1[1] * value * value + +als_z1[2] * value + +als_z1[3]; + break; + case 2: + if (value == 1) + res = dim_values[2]; // DIM value + else + res = als_z2[0] * value * value * value + +als_z2[1] * value * value + +als_z2[2] * value + +als_z2[3]; + break; + case 3: + if (value == 1) + res = dim_values[3]; // DIM value + else + res = als_z3[0] * value * value * value + +als_z3[1] * value * value + +als_z3[2] * value + +als_z3[3]; + break; + case 4: + default: + if (value == 1) + res = dim_values[4]; // DIM value + else + res = als_z4[0] * value * value * value + +als_z4[1] * value * value + +als_z4[2] * value + +als_z4[3]; + break; + } + if (value == 1) + reg = res; + else + reg = res / als_denom; + printk_br (KERN_INFO "%s: v=%d, z=%d, res=0x%x, reg=0x%x\n", + __FUNCTION__, value, zone, res, reg); + return reg; +} + +static void lm3535_brightness_set (struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct i2c_client *client = lm3535_data.client; + int ret, nsteps; + unsigned int total_time = 0; + unsigned breg = LM3535_BRIGHTNESS_CTRL_REG_A; + unsigned bright_zone; + unsigned bvalue; + unsigned do_ramp = 1; + + printk_br ("%s: %s, 0x%x (%d)\n", __FUNCTION__, + led_cdev->name, value, value); + if (!lm3535_data.initialized) { + printk (KERN_ERR "%s: not initialized\n", __FUNCTION__); + return; + } + if (strstr (led_cdev->name, "nr")) + do_ramp = 0; + + bright_zone = atomic_read (&lm3535_data.bright_zone); + mutex_lock (&lm3535_mutex); + if (value == -1) { // Special case for ALS adjustment + value = led_cdev->brightness; + } + + if ((value > 0) && (value <= 5)) + value = 0; /* Special case for G2 */ + if ((value > 5) && (value <= 10)) + value = 1; /* Special dim case for G2 */ + + if ((value == 0) && (!lm3535_data.enabled)) { + /* If LED already disabled, we don't need to do anything */ + mutex_unlock(&lm3535_mutex); + return; + } + +#ifdef CONFIG_LM3535_ESD_RECOVERY + if (value == LED_OFF && esd_polling) + { + esd_poll_stop(lm3535_check_esd); + esd_polling = 0; + } +#endif /* CONFIG_LM3535_ESD_RECOVERY */ + if (!lm3535_data.enabled && value != 0) + lm3535_enable(client, 1); + + /* Calculate brightness value for each zone relative to its cap */ + bvalue = lm3535_convert_value (value, bright_zone); + + /* Calculate number of steps for ramping */ + nsteps = bvalue - lm3535_data.bvalue; + if (nsteps < 0) + nsteps = nsteps * (-1); + + lm3535_set_ramp (client, do_ramp, nsteps, &total_time); + + printk_br ("%s: zone %d, 0x%x => 0x%x, %d steps, ramp time %dus\n", + __FUNCTION__, bright_zone, + lm3535_data.bvalue, bvalue, nsteps, total_time); + + /* Write to each zone brightness register so that when it jumps into + * the next zone the value is adjusted automatically + */ + ret = lm3535_write_reg (breg, bvalue, __FUNCTION__); + lm3535_data.bvalue = bvalue; + + if (value == 0) { + /* Disable everything */ + if (do_ramp) { + /* Wait for ramping to finish */ + udelay (total_time); + } + lm3535_enable(client, 0); + } + +#ifdef CONFIG_LM3535_ESD_RECOVERY + if ((value > 0) && (!esd_polling)) + { + esd_poll_start(lm3535_check_esd, 0); + esd_polling = 1; + } +#endif /* CONFIG_LM3535_ESD_RECOVERY */ + + + mutex_unlock (&lm3535_mutex); +} + +#ifdef CONFIG_LM3535_BUTTON_BL +static void lm3535_button_brightness_set (struct led_classdev *led_cdev, + enum led_brightness value) +{ + int ret; + unsigned breg = LM3535_BRIGHTNESS_CTRL_REG_C; + struct i2c_client *client = lm3535_data.client; + + printk_br ("%s: %s, 0x%x (%d)\n", __FUNCTION__, + led_cdev->name, value, value); + + mutex_lock (&lm3535_mutex); + + if (!lm3535_data.button_enabled && value != 0) { + lm3535_enable (client, lm3535_data.enabled, 1); + } + + ret = lm3535_write_reg (breg, 0xF8, __FUNCTION__); // Lowest setting + + if (value == 0) + lm3535_enable(client, 0); + + mutex_unlock(&lm3535_mutex); +} +#endif + +static int lm3535_als_open (struct inode *inode, struct file *file) +{ + if (!lm3535_data.initialized) + return -ENODEV; + + return 0; +} + +static int lm3535_als_release (struct inode *inode, struct file *file) +{ + return 0; +} +#define CMD_LEN 5 +static ssize_t lm3535_als_write (struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + unsigned char cmd[CMD_LEN]; + int len; + uint8_t value; + unsigned old_zone; + + if (count < 1) + return 0; + + len = count > CMD_LEN-1 ? CMD_LEN-1 : count; + + if (copy_from_user (cmd, buf, len)) + return -EFAULT; + + if (lm3535_data.revision <= 1) + return -EFAULT; + + cmd[len] = '\0'; + if (cmd[len-1] == '\n') { + cmd[len-1] = '\0'; + len--; + } + if (!strcmp (cmd, "1")) { + printk (KERN_INFO "%s: enabling ALS\n", __FUNCTION__); + value = CONFIG_VALUE | 0x80; + mutex_lock (&lm3535_mutex); + atomic_set (&lm3535_data.use_als, 1); + /* No need to change ALS zone; interrupt handler will do it */ + lm3535_write_reg (LM3535_CONFIG_REG, value, __FUNCTION__); + mutex_unlock (&lm3535_mutex); + } else if (!strcmp (cmd, "0")) { + printk (KERN_INFO "%s: disabling ALS\n", __FUNCTION__); + value = CONFIG_VALUE_NO_ALS; + mutex_lock (&lm3535_mutex); + old_zone = atomic_read (&lm3535_data.als_zone); + lm3535_write_reg (LM3535_CONFIG_REG, value, __FUNCTION__); + atomic_set (&lm3535_data.use_als, 0); + atomic_set (&lm3535_data.als_zone, ALS_NO_ZONE); + mutex_unlock (&lm3535_mutex); + if (atomic_read (&lm3535_data.bright_zone) < 2) { + atomic_set (&lm3535_data.bright_zone, ALS_NO_ZONE); + printk_als ("%s: ALS canceled; changing brightness\n", + __FUNCTION__); + /* Adjust brightness */ + lm3535_brightness_set (&lm3535_led, -1); + } else { + atomic_set (&lm3535_data.bright_zone, ALS_NO_ZONE); + } + lm3535_call_als_callbacks (old_zone, 0); + lm3535_send_als_event (0); + } else { + printk (KERN_ERR "%s: invalid command %s\n", __FUNCTION__, cmd); + return -EFAULT; + } + + return count; +} + +static ssize_t lm3535_als_read (struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + char z[23]; + + if (file->private_data) + return 0; + + if (!atomic_read (&lm3535_data.use_als)) { + sprintf (z, "%d\n", ALS_NO_ZONE); + } else { + sprintf (z, "%d %d\n", + atomic_read (&lm3535_data.als_zone), + atomic_read (&lm3535_data.bright_zone)); + } + if (copy_to_user (buf, z, strlen (z))) + return -EFAULT; + + file->private_data = (void *)1; + return strlen (z); +} + +static const struct file_operations als_fops = { + .owner = THIS_MODULE, + .read = lm3535_als_read, + .write = lm3535_als_write, + .open = lm3535_als_open, + .release = lm3535_als_release, +}; + +static struct miscdevice als_miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "als", + .fops = &als_fops, +}; + +static ssize_t lm3535_suspend_show (struct device *dev, + struct device_attribute *attr, char *buf) +{ + sprintf (buf, "%d\n", atomic_read (&lm3535_data.in_suspend)); + return strlen(buf)+1; +} + +static ssize_t lm3535_suspend_store (struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned value = 0; + + if (!buf || size == 0) { + printk (KERN_ERR "%s: invalid command\n", __FUNCTION__); + return -EINVAL; + } + + sscanf (buf, "%d", &value); + if (value) { + printk (KERN_INFO "%s: going into TCMD SUSPEND mode\n", + __FUNCTION__); + atomic_set (&lm3535_data.in_suspend, 1); + lm3535_data.saved_bvalue = lm3535_led.brightness; + lm3535_led.brightness = 255; + } else { + printk (KERN_INFO "%s: exiting TCMD SUSPEND mode\n", + __FUNCTION__); + atomic_set (&lm3535_data.in_suspend, 0); + lm3535_led.brightness = lm3535_data.saved_bvalue; + } + /* Adjust brightness */ + lm3535_brightness_set (&lm3535_led, -1); + return size; +} +static DEVICE_ATTR(suspend, 0644, lm3535_suspend_show, lm3535_suspend_store); + +/* This function is called by i2c_probe */ +static int lm3535_probe (struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + unsigned long request_flags = IRQF_TRIGGER_LOW; + + gpio_request(LM3535_HWEN_GPIO, "LM3535 HWEN"); + gpio_direction_output(LM3535_HWEN_GPIO, 1); + msleep(1); + + /* We should be able to read and write byte data */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + printk (KERN_ERR "%s: I2C_FUNC_I2C not supported\n", + __FUNCTION__); + return -ENOTSUPP; + } + + lm3535_data.client = client; + i2c_set_clientdata (client, &lm3535_data); + + /* Initialize chip */ + lm3535_setup (lm3535_data.client); + + /* Initialize interrupts */ + if (lm3535_data.revision > 1) { + INIT_WORK(&lm3535_data.work, lm3535_work_func); + if (client->irq) { + ret = request_irq (client->irq, lm3535_irq_handler, request_flags, + "lm3535", &lm3535_data); + + if (ret == 0) { + lm3535_data.use_irq = 1; + ret = irq_set_irq_wake (client->irq, 1); + } else { + printk (KERN_ERR "request_irq %d for lm3535 failed: %d\n", + client->irq, ret); + free_irq (client->irq, &lm3535_data); + lm3535_data.use_irq = 0; + } + } + } + + /* Register LED class */ + ret = led_classdev_register (&client->adapter->dev, &lm3535_led); + if (ret) { + printk (KERN_ERR "%s: led_classdev_register %s failed: %d\n", + __FUNCTION__, lm3535_led.name, ret); + return ret; + } + + /* Register LED class for no ramping */ + ret = led_classdev_register (&client->adapter->dev, &lm3535_led_noramp); + if (ret) { + printk (KERN_ERR "%s: led_classdev_register %s failed: %d\n", + __FUNCTION__, lm3535_led.name, ret); + } + if ((ret = misc_register (&als_miscdev))) { + printk (KERN_ERR "%s: misc_register failed, error %d\n", + __FUNCTION__, ret); + led_classdev_unregister (&lm3535_led); + led_classdev_unregister(&lm3535_led_noramp); + return ret; + } + + atomic_set (&lm3535_data.in_suspend, 0); + ret = device_create_file (lm3535_led.dev, &dev_attr_suspend); + if (ret) { + printk (KERN_ERR "%s: unable to create suspend device file for %s: %d\n", + __FUNCTION__, lm3535_led.name, ret); + led_classdev_unregister (&lm3535_led); + led_classdev_unregister(&lm3535_led_noramp); + misc_deregister (&als_miscdev); + return ret; + } + dev_set_drvdata (lm3535_led.dev, &lm3535_led); +#if 0 + lm3535_data.idev = input_allocate_device(); + if (lm3535_data.idev == NULL) { + printk (KERN_ERR "%s: unable to allocate input device file for als\n", + __FUNCTION__); + led_classdev_unregister (&lm3535_led); + led_classdev_unregister(&lm3535_led_noramp); + misc_deregister (&als_miscdev); + device_remove_file (lm3535_led.dev, &dev_attr_suspend); + return -ENOMEM; + } + lm3535_data.idev->name = "als"; + input_set_capability(lm3535_data.idev, EV_MSC, MSC_RAW); + input_set_capability(lm3535_data.idev, EV_LED, LED_MISC); + ret = input_register_device (lm3535_data.idev); + if (ret) { + printk (KERN_ERR "%s: unable to register input device file for als: %d\n", + __FUNCTION__, ret); + led_classdev_unregister (&lm3535_led); + led_classdev_unregister(&lm3535_led_noramp); + misc_deregister (&als_miscdev); + device_remove_file (lm3535_led.dev, &dev_attr_suspend); + input_free_device (lm3535_data.idev); + return ret; + } +#endif + +#ifdef CONFIG_HAS_EARLYSUSPEND + register_early_suspend (&early_suspend_data); +#endif + + lm3535_led.brightness = 255; + lm3535_led_noramp.brightness = 255; + //lm3535_brightness_set (&lm3535_led_noramp, 255); + lm3535_write_reg (LM3535_BRIGHTNESS_CTRL_REG_A, 0x79, __FUNCTION__); + lm3535_data.initialized = 1; + + return 0; +} + +static irqreturn_t lm3535_irq_handler (int irq, void *dev_id) +{ + struct lm3535 *data_ptr = (struct lm3535 *)dev_id; + + pr_debug ("%s: got an interrupt %d\n", __FUNCTION__, irq); + + disable_irq (irq); + schedule_work (&data_ptr->work); + + return IRQ_HANDLED; +} + +static void lm3535_send_als_event (int zone) +{ +#ifdef CONFIG_ALS_UEVENT + char event_string[20]; + char *envp[] = {event_string, NULL}; + int ret; + + sprintf (event_string, "ALS_ZONE=%d", zone); + ret = kobject_uevent_env (&als_miscdev.this_device->kobj, + KOBJ_CHANGE, envp); + if (ret) { + printk (KERN_ERR "%s: kobject_uevent_env failed: %d\n", + __FUNCTION__, ret); + } else { + printk_event ("%s: kobject_uevent_env %s success\n", + __FUNCTION__, event_string); + } +#else + //input_event (lm3535_data.idev, EV_MSC, MSC_RAW, light_value); + input_event (lm3535_data.idev, EV_LED, LED_MISC, zone); + input_sync (lm3535_data.idev); + +#endif +} + +static void lm3535_call_als_callbacks(unsigned old_zone, unsigned zone) +{ + struct als_callback *c; + unsigned old, new; + + old = (old_zone == ALS_NO_ZONE) ? 0 : old_zone; + new = (zone == ALS_NO_ZONE) ? 0 : zone; + + mutex_lock (&als_cb_mutex); + list_for_each_entry(c, &als_callbacks, entry) { + c->cb(old, new, c->cookie); + } + mutex_unlock (&als_cb_mutex); +} + +static void lm3535_work_func (struct work_struct *work) +{ + int ret; + uint8_t reg; + unsigned zone, old_zone; + + pr_debug ("%s: work function called\n", __FUNCTION__); + ret = lm3535_read_reg (LM3535_ALS_REG, ®); + if (ret) { + if (reg & ALS_FLAG_MASK) { + zone = reg & ALS_ZONE_MASK; + if (zone > 4) + zone = 4; + old_zone = atomic_read (&lm3535_data.als_zone); + printk_als ("%s: ALS zone changed: %d => %d, register = 0x%x\n", + __FUNCTION__, old_zone, zone, reg); + atomic_set (&lm3535_data.als_zone, zone); + if (zone > atomic_read (&lm3535_data.bright_zone) || + atomic_read (&lm3535_data.bright_zone) == ALS_NO_ZONE) { + atomic_set (&lm3535_data.bright_zone, zone); + if (!atomic_read (&lm3535_data.in_suspend)) { + printk_als ("%s: ALS zone increased; changing brightness\n", + __FUNCTION__); + /* Adjust brightness */ + lm3535_brightness_set (&lm3535_led, -1); + } else { + printk_als ("%s: ALS zone increased; SUSPEND mode - not changing brightness\n", + __FUNCTION__); + } + } + /* See if PWM needs to be changed */ + if (old_zone < 2 && zone >= 2) { + lm3535_write_reg (LM3535_CONFIG_REG, CONFIG_VALUE | 0x80, + __FUNCTION__); + printk_als ("%s: moved from dim/dark to bright; disable PWM\n", + __FUNCTION__); + } else if (old_zone >= 2 && zone < 2) { + lm3535_write_reg (LM3535_CONFIG_REG, + CONFIG_VALUE|0x80|pwm_value, + __FUNCTION__); + printk_als ("%s: moved from bright to dim/dark; enable PWM\n", + __FUNCTION__); + } + if (!atomic_read (&lm3535_data.in_suspend)) { + lm3535_call_als_callbacks (old_zone, zone); + } + lm3535_send_als_event (zone); + } else { + printk_als ("%s: got ALS interrupt but flag is not set: 0x%x\n", + __FUNCTION__, reg); + } + } + if (atomic_read (&lm3535_data.do_als_config)) { + lm3535_write_reg (LM3535_ALS_CTRL_REG, ALS_AVERAGING, __FUNCTION__); + atomic_set (&lm3535_data.do_als_config, 0); + printk_als ("%s: configured ALS averaging 0x%x\n", + __FUNCTION__, ALS_AVERAGING); + } + enable_irq (lm3535_data.client->irq); +} + +#if 0 +static enum hrtimer_restart lm3535_timer_func (struct hrtimer *timer) +{ + schedule_work(&lm3535_data.work); + + hrtimer_start(&lm3535_data.timer, + ktime_set(1, 0), HRTIMER_MODE_REL); + return HRTIMER_NORESTART; +} +#endif + +static void lm3535_set_options_r1 (uint8_t *buf, unsigned ramp) +{ + struct lm3535_options_register_r1 *r = + (struct lm3535_options_register_r1 *)buf; + + if (!r) + return; + *buf = 0; + r->rs = ramp; + r->gt = 0x2; + r->rev = 0; +} + +static void lm3535_set_options_r2 (uint8_t *buf, unsigned ramp) +{ + struct lm3535_options_register_r2 *r = + (struct lm3535_options_register_r2 *)buf; + + if (!r) + return; + *buf = 0; + r->rs_up = ramp; + r->rs_down = ramp; + r->gt = 0; +} + +static void lm3535_set_options_r3 (uint8_t *buf, unsigned ramp) +{ + struct lm3535_options_register_r3 *r = + (struct lm3535_options_register_r3 *)buf; + + if (!r) + return; + *buf = 0; + r->rs_up = ramp; + r->rs_down = ramp; + r->gt = 0x02; +} + +/* This function calculates ramp step time so that total ramp time is + * equal to ramp_time defined currently at 200ms + */ +static int lm3535_set_ramp (struct i2c_client *client, + unsigned int on, unsigned int nsteps, unsigned int *rtime) +{ + int ret, i = 0; + uint8_t value = 0; + unsigned int total_time = 0; + + if (on) { + /* Calculate the closest possible ramp time */ + for (i = 0; i < lm3535_data.nramp; i++) { + total_time = nsteps * lm3535_ramp[i]; + if (total_time >= ramp_time) + break; + } + if (i > 0 && total_time > ramp_time) { +#if 0 + /* If previous value is closer */ + if (total_time - ramp_time > + ramp_time - nsteps * lm3535_ramp[i-1]) { + i--; + total_time = nsteps * lm3535_ramp[i]; + } +#endif + i--; + total_time = nsteps * lm3535_ramp[i]; + } + lm3535_set_options_f (&value, i); + } + +#if 0 + printk (KERN_ERR "%s: ramp = %s, ramp step = %d us (total = %d us)\n", + __FUNCTION__, on ? "on" : "off", lm3535_ramp[i], total_time); +#endif + if (rtime) + *rtime = total_time; + ret = lm3535_write_reg (LM3535_OPTIONS_REG, value, __FUNCTION__); +#if 0 + printk_br ("%s: nsteps = %d, OPTIONS_REG = 0x%x, total ramp = %dus\n", + __FUNCTION__, nsteps, value, lm3535_ramp[i] * nsteps); +#endif + return ret; +} + +static int lm3535_enable(struct i2c_client *client, unsigned int on) +{ + int ret; + uint8_t value = 0x0F; // Enable A + + if (on) { + gpio_set_value(LM3535_HWEN_GPIO, 1); + msleep(1); + } else + value = 0; + + ret = lm3535_write_reg (LM3535_ENABLE_REG, value, __FUNCTION__); + if (ret < 0) + return ret; + + if (lm3535_data.revision == 2 || lm3535_data.revision == 3) { + if (on) { + ret = lm3535_write_reg (LM3535_TRIM_REG, LM3535_TRIM_VALUE, + __FUNCTION__); + } + } + if (!on) + gpio_set_value(LM3535_HWEN_GPIO, 0); + + lm3535_data.enabled = on; + return ret; +} + +static int lm3535_configure (void) +{ + int ret = 0; + +#if 1 + /* On G2, we are not using ALS interrupt so we want to leave the ALS + interrupt disabled and ALS resistor in high impedance mode */ + /* Disable ALS interrupt */ + lm3535_write_reg(LM3535_CONFIG_REG, 0, __func__); + /* Put ALS Resistor into high impedance mode to save current */ + lm3535_write_reg(LM3535_ALS_RESISTOR_REG, 0, __func__); + +#else + uint8_t value = 0x03; + uint8_t reg = 0; + unsigned old_zone = 0; + unsigned new_zone = ALS_NO_ZONE; + + /* Config register bits: + * Rev1: AVE2 AVE1 AVE0 ALS-SD ALS-EN PWM-EN 53A 62A + * Rev2: ALSF ALS-SD ALS-ENB ALS-ENA 62A 53A PWM-P PWM-EN + * Rev3: ALSF ALS-EN ALS-ENB ALS-ENA 62A 53A PWM-P PWM-EN + * + * ALSF: sets E1B/INT pin to interrupt Pin; 0 = D1B, 1 = INT. + * Open Drain Interrupt. Pulls low when change occurs. Flag cleared + * once a I2C read command for register 0x40 occurs. + * ALS-SD (rev2) and ALS-EN (final): turn off ALS feature + * ALS-SD: 0 = Active, 1 = Shutdown. Rev2 cannot have ALS active without + * LEDs turned on (ENxx bits = 1). + * ALS-EN: 0 = Shutdown, 1 = Active. Final can have ALS active without + * LEDs turned on. ALS-EN overrides the ENC bit. + * ALS-ENB and ALS-ENA: 1 enables ALS control of diode current. BankA has + * full ALS control, BankB just has on/off ability. + * 62A and 53A: 1 sets D62 and D53 to BankA (Required for 6 LEDs in BankA). + * 0 sets them to BankB. + * PWM-P: PWM Polarity, 0 = Active (Diodes on) High, 1 = Active (Diodes on) + * Low. + * PWM-EN: Enables PWM Functionality. 1 = Active. + */ + +#ifndef CONFIG_MAC_MOT +#ifdef CONFIG_LM3535_ESD_RECOVERY + /* Configure lighting zone max brightness */ + lm3535_write_reg (LM3535_ALS_Z0T_REG, als_zone_max[0], + __FUNCTION__); + lm3535_write_reg (LM3535_ALS_Z1T_REG, als_zone_max[1], + __FUNCTION__); + lm3535_write_reg (LM3535_ALS_Z2T_REG, als_zone_max[2], + __FUNCTION__); + lm3535_write_reg (LM3535_ALS_Z3T_REG, als_zone_max[3], + __FUNCTION__); + lm3535_write_reg (LM3535_ALS_Z4T_REG, als_zone_max[4], + __FUNCTION__); +#endif +#endif + + if (lm3535_data.revision > 1) { + /* Configure internal ALS resistor register */ + ret = lm3535_write_reg (LM3535_ALS_RESISTOR_REG, + (uint8_t)resistor_value, __FUNCTION__); + +#ifndef CONFIG_MAC_MOT +#ifdef CONFIG_LM3535_BUTTON_BL + ret = lm3535_write_reg (LM3535_ALS_SELECT_REG, 0x2, + __FUNCTION__); +#endif +#endif + /* Configure lighting zone boundaries */ + lm3535_write_reg (LM3535_ALS_ZB0_REG, als_zb[0], __FUNCTION__); + lm3535_write_reg (LM3535_ALS_ZB1_REG, als_zb[1], __FUNCTION__); + lm3535_write_reg (LM3535_ALS_ZB2_REG, als_zb[2], __FUNCTION__); + lm3535_write_reg (LM3535_ALS_ZB3_REG, als_zb[3], __FUNCTION__); + + /* Configure ALS averaging to be very short the first time */ + lm3535_write_reg (LM3535_ALS_CTRL_REG, 0x80, __FUNCTION__); + } + + if (lm3535_data.revision == 0) { + value = 0x3; // Just enable A, don't bother with ALS + atomic_set (&lm3535_data.use_als, 0); + } else if (lm3535_data.revision == 1) { + value = 0x0C; // No ALS or PWM + atomic_set (&lm3535_data.use_als, 0); + } else { + if (atomic_read (&lm3535_data.use_als)) + value = CONFIG_VALUE; + else + value = CONFIG_VALUE_NO_ALS; + } + pr_debug ("%s: use_als is %d, value = 0x%x\n", __FUNCTION__, + atomic_read (&lm3535_data.use_als), value); + ret = lm3535_write_reg (LM3535_CONFIG_REG, value, __FUNCTION__); + // Nothing else to do for older revisions + if (lm3535_data.revision <= 1) { + return 0; + } + + /* Has to be at least 300ms even with ALS averaging set to 0 */ + msleep_interruptible (als_sleep); // Wait for ALS to kick in + /* Read current ALS zone */ + old_zone = atomic_read (&lm3535_data.als_zone); + if (atomic_read (&lm3535_data.use_als)) { + ret = lm3535_read_reg (LM3535_ALS_REG, ®); + if (ret) { + new_zone = reg & ALS_ZONE_MASK; + if (new_zone > 4) { + new_zone = 4; + } + atomic_set (&lm3535_data.als_zone, new_zone); + printk_als ("%s: ALS Register: 0x%x, zone %d\n", + __FUNCTION__, reg, atomic_read (&lm3535_data.als_zone)); + } else { + atomic_set (&lm3535_data.als_zone, ALS_NO_ZONE); + atomic_set (&lm3535_data.use_als, 0); + printk (KERN_ERR "%s: unable to read ALS zone; disabling ALS\n", + __FUNCTION__); + } + } else { + atomic_set (&lm3535_data.als_zone, ALS_NO_ZONE); + } + /* Brightness zone is for now the same as ALS zone */ + atomic_set (&lm3535_data.bright_zone, + atomic_read (&lm3535_data.als_zone)); + lm3535_call_als_callbacks (old_zone, new_zone); + if (lm3535_data.initialized) { + lm3535_send_als_event (new_zone); + } + if (atomic_read (&lm3535_data.use_als)) { + /* Configure averaging */ + //lm3535_write_reg (LM3535_ALS_CTRL_REG, ALS_AVERAGING, __FUNCTION__); + /* Enable interrupt and PWM for CABC */ + if (new_zone <= 1) { + lm3535_write_reg (LM3535_CONFIG_REG, CONFIG_VALUE|0x80|pwm_value, + __FUNCTION__); + } else { + lm3535_write_reg (LM3535_CONFIG_REG, CONFIG_VALUE | 0x80, + __FUNCTION__); + } + } else { + lm3535_write_reg (LM3535_CONFIG_REG, CONFIG_VALUE | pwm_value, + __FUNCTION__); + } +#endif + + return ret; +} + +static int lm3535_setup (struct i2c_client *client) +{ + int ret; + uint8_t value; + + /* Read revision number */ + ret = lm3535_read_reg (LM3535_ALS_CTRL_REG, &value); + if (ret < 0) { + printk (KERN_ERR "%s: unable to read from chip: %d\n", + __FUNCTION__, ret); + printk(KERN_ERR "client->addr = %x failed\n", client->addr); + + /* If the first I2C address doesn't work, try 0x36 */ + client->addr = 0x36; + ret = lm3535_read_reg (LM3535_ALS_CTRL_REG, &value); + if (ret < 0) { + printk (KERN_ERR "%s: unable to read from chip: %d\n", + __FUNCTION__, ret); + printk(KERN_ERR "client->addr = %x failed\n", client->addr); + return ret; + } + } + + switch (value) { + case 0xFF: lm3535_data.revision = 0; break; + case 0xF0: lm3535_data.revision = 1; break; + case 0x02: lm3535_data.revision = 2; break; + case 0x00: lm3535_data.revision = 3; break; + case 0x01: lm3535_data.revision = 4; break; + default: lm3535_data.revision = 4; break; // Assume final + } + /* revision is going to be an index to lm3535_ramp array */ + printk (KERN_INFO "%s: revision %d (0x%X)\n", + __FUNCTION__, lm3535_data.revision+1, value); + if (lm3535_data.revision == 0) { + lm3535_ramp = lm3535_ramp_r1; + lm3535_set_options_f = lm3535_set_options_r1; + } else if (lm3535_data.revision == 1) { + lm3535_ramp = lm3535_ramp_r2; + lm3535_set_options_f = lm3535_set_options_r2; + } else { + lm3535_ramp = lm3535_ramp_r3; + lm3535_set_options_f = lm3535_set_options_r3; + lm3535_data.nramp = 8; + } + + /* PWM */ + if (lm3535_data.revision < 4) { + pwm_value = 0; + } + atomic_set (&lm3535_data.als_zone, ALS_NO_ZONE); + atomic_set (&lm3535_data.use_als, 1); + atomic_set (&lm3535_data.do_als_config, 1); + ret = lm3535_configure (); + if (ret < 0) + return ret; + ret = lm3535_enable(client, 1); + if (ret < 0) + return ret; + + //hrtimer_init (&lm3535_data.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + //lm3535_data.timer.function = lm3535_timer_func; + //hrtimer_start(&lm3535_data.timer, ktime_set(2, 0), HRTIMER_MODE_REL); + + return ret; +} + +static int lm3535_remove (struct i2c_client *client) +{ + struct lm3535 *data_ptr = i2c_get_clientdata(client); + if (data_ptr->use_irq) + free_irq (client->irq, data_ptr); + led_classdev_unregister (&lm3535_led); + led_classdev_unregister(&lm3535_led_noramp); +/* led_classdev_unregister (&lm3535_led_noramp); */ + misc_deregister (&als_miscdev); + device_remove_file (lm3535_led.dev, &dev_attr_suspend); +#if 0 + input_unregister_device (lm3535_data.idev); + input_free_device (lm3535_data.idev); +#endif + return 0; +} + +static int lm3535_suspend (struct i2c_client *client, pm_message_t mesg) +{ + printk_suspend ("%s: called with pm message %d\n", + __FUNCTION__, mesg.event); + + led_classdev_suspend (&lm3535_led); + +#if 0 + /* Disable ALS interrupt */ + lm3535_write_reg (LM3535_CONFIG_REG, 0, __FUNCTION__); + /* Put ALS Resistor into high impedance mode to save current */ + lm3535_write_reg (LM3535_ALS_RESISTOR_REG, 0, __FUNCTION__); + + /* Reset ALS averaging */ + lm3535_write_reg (LM3535_ALS_CTRL_REG, 0, __FUNCTION__); + atomic_set (&lm3535_data.do_als_config, 1); +#endif + + gpio_set_value(LM3535_HWEN_GPIO, 0); + + return 0; +} + +static int lm3535_resume (struct i2c_client *client) +{ + printk_suspend ("%s: resuming\n", __FUNCTION__); + mutex_lock (&lm3535_mutex); + + /* If LED was disabled going into suspend, we don't want to enable yet + until brightness is set to non-zero value */ + if (lm3535_data.enabled == 0) { + gpio_set_value(LM3535_HWEN_GPIO, 1); + udelay(10); + + lm3535_configure(); + } + mutex_unlock (&lm3535_mutex); + led_classdev_resume (&lm3535_led); + printk_suspend ("%s: driver resumed\n", __FUNCTION__); + + return 0; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void lm3535_early_suspend (struct early_suspend *h) +{ + lm3535_suspend (lm3535_data.client, PMSG_SUSPEND); +} + +static void lm3535_late_resume (struct early_suspend *h) +{ + lm3535_resume (lm3535_data.client); +} +#endif + + +static int __init lm3535_init (void) +{ + int ret; + + printk (KERN_INFO "%s: enter\n", __FUNCTION__); + ret = i2c_add_driver (&lm3535_driver); + if (ret) { + printk (KERN_ERR "%s: i2c_add_driver failed, error %d\n", + __FUNCTION__, ret); + } + + return ret; +} + +static void __exit lm3535_exit(void) +{ + i2c_del_driver (&lm3535_driver); +} + +static char *reg_name (int reg) +{ + switch (reg) { + case (LM3535_ENABLE_REG): return "ENABLE"; break; + case (LM3535_CONFIG_REG): return "CONFIG"; break; + case (LM3535_OPTIONS_REG): return "OPTIONS"; break; + case (LM3535_ALS_REG): return "ALS"; break; + case (LM3535_ALS_RESISTOR_REG): return "ALS_RESISTOR"; break; + case (LM3535_ALS_CTRL_REG): return "ALS CONTROL"; break; + case (LM3535_BRIGHTNESS_CTRL_REG_A): return "BRIGHTNESS_CTRL_A"; break; + case (LM3535_BRIGHTNESS_CTRL_REG_B): return "BRIGHTNESS_CTRL_B"; break; + case (LM3535_BRIGHTNESS_CTRL_REG_C): return "BRIGHTNESS_CTRL_C"; break; + case (LM3535_ALS_ZB0_REG): return "ALS_ZB0"; break; + case (LM3535_ALS_ZB1_REG): return "ALS_ZB1"; break; + case (LM3535_ALS_ZB2_REG): return "ALS_ZB2"; break; + case (LM3535_ALS_ZB3_REG): return "ALS_ZB3"; break; + case (LM3535_ALS_Z0T_REG): return "ALS_Z0T"; break; + case (LM3535_ALS_Z1T_REG): return "ALS_Z1T"; break; + case (LM3535_ALS_Z2T_REG): return "ALS_Z2T"; break; + case (LM3535_ALS_Z3T_REG): return "ALS_Z3T"; break; + case (LM3535_ALS_Z4T_REG): return "ALS_Z4T"; break; + case (LM3535_TRIM_REG): return "TRIM"; break; + default: return "UNKNOWN"; break; + } + return "UNKNOWN"; +} + +unsigned lmxxxx_detect_esd (void) +{ + uint8_t value = 0; + + if (!lm3535_data.initialized) { + printk (KERN_ERR "%s: not initialized\n", __FUNCTION__); + return 0; + } + //0 - no ESD, 1 - ESD + lm3535_read_reg (LM3535_ALS_RESISTOR_REG, &value); + if (value != (uint8_t)resistor_value) + return 1; + return 0; +} +EXPORT_SYMBOL(lmxxxx_detect_esd); + +void lmxxxx_fix_esd (void) +{ + if (!lm3535_data.initialized) { + printk (KERN_ERR "%s: not initialized\n", __FUNCTION__); + return; + } + mutex_lock (&lm3535_mutex); + lm3535_configure (); + mutex_unlock (&lm3535_mutex); + return; +} +EXPORT_SYMBOL(lmxxxx_fix_esd); + +void lmxxxx_set_pwm (unsigned en_dis) +{ + if (en_dis) { + pwm_value = 1; + } else { + pwm_value = 0; + } + printk (KERN_INFO "%s: setting PWM to %d\n", + __FUNCTION__, pwm_value); +} +EXPORT_SYMBOL(lmxxxx_set_pwm); +module_init(lm3535_init); +module_exit(lm3535_exit); + +MODULE_DESCRIPTION("LM3535 DISPLAY BACKLIGHT DRIVER"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index d54e985748b..2bed2fd1315 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -356,6 +356,12 @@ config EZX_PCAP This enables the PCAP ASIC present on EZX Phones. This is needed for MMC, TouchScreen, Sound, USB, etc.. +config MFD_CPCAP + tristate "Support for CPCAP" + depends on SPI + help + Say yes here if you want to include drivers for the CPCAP chip. + config MFD_VIPERBOARD tristate "Nano River Technologies Viperboard" select MFD_CORE @@ -602,6 +608,12 @@ config MFD_DB8500_PRCMU system controller running an XP70 microprocessor, which is accessed through a register map. +config MFD_M4SENSORHUB + tristate "Support for M4 Sensor Hub" + depends on I2C + help + Say yes here if you want to include drivers for the M4 sensor hub. + config MFD_STMPE bool "STMicroelectronics STMPE" depends on (I2C=y || SPI_MASTER=y) && GENERIC_HARDIRQS @@ -841,6 +853,13 @@ config MFD_TPS65912_SPI If you say yes here you get support for the TPS65912 series of PM chips with SPI interface. +config MFD_TPS65912_DEBUGFS + bool "TI TPS65912 Power Management chip debugfs support" + depends on MFD_TPS65912 && DEBUG_FS + help + If you say yes here you get support for the TPS65912 series of + PM chips debugfs register access. + config MFD_TPS80031 bool "TI TPS80031/TPS80032 Power Management chips" depends on I2C=y && GENERIC_HARDIRQS diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 718e94a2a9a..c4aa99058c7 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -64,6 +64,7 @@ tps65912-objs := tps65912-core.o tps65912-irq.o obj-$(CONFIG_MFD_TPS65912) += tps65912.o obj-$(CONFIG_MFD_TPS65912_I2C) += tps65912-i2c.o obj-$(CONFIG_MFD_TPS65912_SPI) += tps65912-spi.o +obj-$(CONFIG_MFD_TPS65912_DEBUGFS) += tps65912-debugfs.o obj-$(CONFIG_MFD_TPS80031) += tps80031.o obj-$(CONFIG_MENELAUS) += menelaus.o @@ -155,3 +156,21 @@ obj-$(CONFIG_MFD_LM3533) += lm3533-core.o lm3533-ctrlbank.o obj-$(CONFIG_VEXPRESS_CONFIG) += vexpress-config.o vexpress-sysreg.o obj-$(CONFIG_MFD_RETU) += retu-mfd.o obj-$(CONFIG_MFD_AS3711) += as3711.o + +cpcap-objs := cpcap-core.o \ + cpcap-irq.o \ + cpcap-regacc.o \ + cpcap-usb-det.o \ + cpcap-key.o \ + cpcap-adc.o \ + cpcap-uc.o +obj-$(CONFIG_MFD_CPCAP) += cpcap.o + +m4sensorhub-objs := m4sensorhub-core.o \ + m4sensorhub-reg.o \ + m4sensorhub-irq.o \ + m4sensorhub-panic.o \ + m4sensorhub-stm32-fw.o + +obj-$(CONFIG_MFD_M4SENSORHUB) += m4sensorhub.o + diff --git a/drivers/mfd/cpcap-adc.c b/drivers/mfd/cpcap-adc.c new file mode 100644 index 00000000000..6389c23ea61 --- /dev/null +++ b/drivers/mfd/cpcap-adc.c @@ -0,0 +1,708 @@ +/* + * Copyright (C) 2009-2010 Motorola, 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/completion.h> +#include <linux/sched.h> + +#include <linux/spi/cpcap.h> +#include <linux/spi/cpcap-regbits.h> +#include <linux/spi/spi.h> + + +#define MAX_ADC_FIFO_DEPTH 8 /* this must be a power of 2 */ +#define MAX_TEMP_LVL 27 +#define FOUR_POINT_TWO_ADC 801 + +#define DEFAULT_ICHARGE_SENSE_RES 100 +#define DEFAULT_ISENSE_RANGE 5000 + +struct cpcap_adc { + struct cpcap_device *cpcap; + + /* Private stuff */ + struct cpcap_adc_request *queue[MAX_ADC_FIFO_DEPTH]; + int queue_head; + int queue_tail; + struct mutex queue_mutex; + struct delayed_work work; +}; + +struct phasing_tbl { + short offset; + unsigned short multiplier; + unsigned short divider; + short min; + short max; +}; + +static struct phasing_tbl bank0_phasing[CPCAP_ADC_BANK0_NUM] = { + [CPCAP_ADC_AD0_BATTDETB] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_BATTP] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_VBUS] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_AD3] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_BPLUS_AD4] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_CHG_ISENSE] = {0, 0x80, 0x80, -512, 511}, + [CPCAP_ADC_BATTI_ADC] = {0, 0x80, 0x80, -512, 511}, + [CPCAP_ADC_USB_ID] = {0, 0x80, 0x80, 0, 1023}, +}; + +static struct phasing_tbl bank1_phasing[CPCAP_ADC_BANK1_NUM] = { + [CPCAP_ADC_AD8] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_AD9] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_LICELL] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_HV_BATTP] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_TSX1_AD12] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_TSX2_AD13] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_TSY1_AD14] = {0, 0x80, 0x80, 0, 1023}, + [CPCAP_ADC_TSY2_AD15] = {0, 0x80, 0x80, 0, 1023}, +}; + +enum conv_type { + CONV_TYPE_NONE, + CONV_TYPE_DIRECT, + CONV_TYPE_MAPPING, +}; + +struct conversion_tbl { + enum conv_type conv_type; + int align_offset; + int conv_offset; + int cal_offset; + int multiplier; + int divider; +}; + +static struct conversion_tbl bank0_conversion[CPCAP_ADC_BANK0_NUM] = { + [CPCAP_ADC_AD0_BATTDETB] = { + CONV_TYPE_MAPPING, 0, 0, 0, 1, 1}, + [CPCAP_ADC_BATTP] = { + CONV_TYPE_DIRECT, 0, 2400, 0, 2300, 1023}, + [CPCAP_ADC_VBUS] = { + CONV_TYPE_DIRECT, 0, 0, 0, 10000, 1023}, + [CPCAP_ADC_AD3] = { + CONV_TYPE_MAPPING, 0, 0, 0, 1, 1}, + [CPCAP_ADC_BPLUS_AD4] = { + CONV_TYPE_DIRECT, 0, 2400, 0, 2300, 1023}, + /* ISENSE multiplier is updated in probe function below */ + [CPCAP_ADC_CHG_ISENSE] = { + CONV_TYPE_DIRECT, -512, 2, 0, 5000, 1023}, + [CPCAP_ADC_BATTI_ADC] = { + CONV_TYPE_DIRECT, -512, 2, 0, 5000, 1023}, + [CPCAP_ADC_USB_ID] = { + CONV_TYPE_NONE, 0, 0, 0, 1, 1}, +}; + +static struct conversion_tbl bank1_conversion[CPCAP_ADC_BANK1_NUM] = { + [CPCAP_ADC_AD8] = {CONV_TYPE_NONE, 0, 0, 0, 1, 1}, + [CPCAP_ADC_AD9] = {CONV_TYPE_NONE, 0, 0, 0, 1, 1}, + [CPCAP_ADC_LICELL] = {CONV_TYPE_DIRECT, 0, 0, 0, 3400, 1023}, + [CPCAP_ADC_HV_BATTP] = {CONV_TYPE_NONE, 0, 0, 0, 1, 1}, + [CPCAP_ADC_TSX1_AD12] = {CONV_TYPE_NONE, 0, 0, 0, 1, 1}, + [CPCAP_ADC_TSX2_AD13] = {CONV_TYPE_NONE, 0, 0, 0, 1, 1}, + [CPCAP_ADC_TSY1_AD14] = {CONV_TYPE_NONE, 0, 0, 0, 1, 1}, + [CPCAP_ADC_TSY2_AD15] = {CONV_TYPE_NONE, 0, 0, 0, 1, 1}, +}; + +static const unsigned short temp_map[MAX_TEMP_LVL][2] = { + {0x03ff, 233}, /* -40C */ + {0x03ff, 238}, /* -35C */ + {0x03ef, 243}, /* -30C */ + {0x03b2, 248}, /* -25C */ + {0x036c, 253}, /* -20C */ + {0x0320, 258}, /* -15C */ + {0x02d0, 263}, /* -10C */ + {0x027f, 268}, /* -5C */ + {0x022f, 273}, /* 0C */ + {0x01e4, 278}, /* 5C */ + {0x019f, 283}, /* 10C */ + {0x0161, 288}, /* 15C */ + {0x012b, 293}, /* 20C */ + {0x00fc, 298}, /* 25C */ + {0x00d4, 303}, /* 30C */ + {0x00b2, 308}, /* 35C */ + {0x0095, 313}, /* 40C */ + {0x007d, 318}, /* 45C */ + {0x0069, 323}, /* 50C */ + {0x0059, 328}, /* 55C */ + {0x004b, 333}, /* 60C */ + {0x003f, 338}, /* 65C */ + {0x0036, 343}, /* 70C */ + {0x002e, 348}, /* 75C */ + {0x0027, 353}, /* 80C */ + {0x0022, 358}, /* 85C */ + {0x001d, 363}, /* 90C */ +}; + +static unsigned short convert_to_kelvins(unsigned short value) +{ + int i; + unsigned short result = 0; + signed short alpha = 0; + + if (value <= temp_map[MAX_TEMP_LVL - 1][0]) + return temp_map[MAX_TEMP_LVL - 1][1]; + + if (value >= temp_map[0][0]) + return temp_map[0][1]; + + for (i = 0; i < MAX_TEMP_LVL - 1; i++) { + if ((value <= temp_map[i][0]) && + (value >= temp_map[i+1][0])) { + if (value == temp_map[i][0]) + result = temp_map[i][1]; + else if (value == temp_map[i+1][0]) + result = temp_map[i+1][1]; + else { + alpha = ((value - temp_map[i][0])*1000)/ + (temp_map[i+1][0] - temp_map[i][0]); + + result = temp_map[i][1] + + ((alpha*(temp_map[i+1][1] - + temp_map[i][1]))/1000); + } + break; + } + } + return result; +} + +static void adc_setup(struct cpcap_device *cpcap, + struct cpcap_adc_request *req) +{ + struct cpcap_adc_ato *ato; + struct cpcap_platform_data *data; + unsigned short value1 = 0; + unsigned short value2 = 0; + + data = cpcap->spi->controller_data; + ato = data->adc_ato; + + if (req->type == CPCAP_ADC_TYPE_BANK_1) + value1 |= CPCAP_BIT_AD_SEL1; + else if (req->type == CPCAP_ADC_TYPE_BATT_PI) + value1 |= CPCAP_BIT_RAND1; + + switch (req->timing) { + case CPCAP_ADC_TIMING_IN: + value1 |= ato->ato_in; + value1 |= ato->atox_in; + value2 |= ato->adc_ps_factor_in; + value2 |= ato->atox_ps_factor_in; + break; + + case CPCAP_ADC_TIMING_OUT: + value1 |= ato->ato_out; + value1 |= ato->atox_out; + value2 |= ato->adc_ps_factor_out; + value2 |= ato->atox_ps_factor_out; + break; + + case CPCAP_ADC_TIMING_IMM: + default: + break; + } + + cpcap_regacc_write(cpcap, CPCAP_REG_ADCC1, value1, + (CPCAP_BIT_CAL_MODE | CPCAP_BIT_ATOX | + CPCAP_BIT_ATO3 | CPCAP_BIT_ATO2 | + CPCAP_BIT_ATO1 | CPCAP_BIT_ATO0 | + CPCAP_BIT_ADA2 | CPCAP_BIT_ADA1 | + CPCAP_BIT_ADA0 | CPCAP_BIT_AD_SEL1 | + CPCAP_BIT_RAND1 | CPCAP_BIT_RAND0)); + + cpcap_regacc_write(cpcap, CPCAP_REG_ADCC2, value2, + (CPCAP_BIT_ATOX_PS_FACTOR | + CPCAP_BIT_ADC_PS_FACTOR1 | + CPCAP_BIT_ADC_PS_FACTOR0)); + + if (req->timing == CPCAP_ADC_TIMING_IMM) { + cpcap_regacc_write(cpcap, CPCAP_REG_ADCC2, + CPCAP_BIT_ADTRIG_DIS, + CPCAP_BIT_ADTRIG_DIS); + cpcap_irq_clear(cpcap, CPCAP_IRQ_ADCDONE); + cpcap_regacc_write(cpcap, CPCAP_REG_ADCC2, + CPCAP_BIT_ASC, + CPCAP_BIT_ASC); + } else { + cpcap_regacc_write(cpcap, CPCAP_REG_ADCC2, + CPCAP_BIT_ADTRIG_ONESHOT, + CPCAP_BIT_ADTRIG_ONESHOT); + cpcap_irq_clear(cpcap, CPCAP_IRQ_ADCDONE); + cpcap_regacc_write(cpcap, CPCAP_REG_ADCC2, + 0, + CPCAP_BIT_ADTRIG_DIS); + } + + schedule_delayed_work(&((struct cpcap_adc *)(cpcap->adcdata))->work, + msecs_to_jiffies(500)); + + cpcap_irq_unmask(cpcap, CPCAP_IRQ_ADCDONE); +} + +static void adc_setup_calibrate(struct cpcap_device *cpcap, + enum cpcap_adc_bank0 chan) +{ + unsigned short value = 0; + unsigned long timeout = jiffies + msecs_to_jiffies(11); + + if ((chan != CPCAP_ADC_CHG_ISENSE) && + (chan != CPCAP_ADC_BATTI_ADC)) + return; + + value |= CPCAP_BIT_CAL_MODE | CPCAP_BIT_RAND0; + value |= ((chan << 4) & + (CPCAP_BIT_ADA2 | CPCAP_BIT_ADA1 | CPCAP_BIT_ADA0)); + + cpcap_regacc_write(cpcap, CPCAP_REG_ADCC1, value, + (CPCAP_BIT_CAL_MODE | CPCAP_BIT_ATOX | + CPCAP_BIT_ATO3 | CPCAP_BIT_ATO2 | + CPCAP_BIT_ATO1 | CPCAP_BIT_ATO0 | + CPCAP_BIT_ADA2 | CPCAP_BIT_ADA1 | + CPCAP_BIT_ADA0 | CPCAP_BIT_AD_SEL1 | + CPCAP_BIT_RAND1 | CPCAP_BIT_RAND0)); + + cpcap_regacc_write(cpcap, CPCAP_REG_ADCC2, 0, + (CPCAP_BIT_ATOX_PS_FACTOR | + CPCAP_BIT_ADC_PS_FACTOR1 | + CPCAP_BIT_ADC_PS_FACTOR0)); + + cpcap_regacc_write(cpcap, CPCAP_REG_ADCC2, + CPCAP_BIT_ADTRIG_DIS, + CPCAP_BIT_ADTRIG_DIS); + + cpcap_regacc_write(cpcap, CPCAP_REG_ADCC2, + CPCAP_BIT_ASC, + CPCAP_BIT_ASC); + + do { + schedule_timeout_uninterruptible(1); + cpcap_regacc_read(cpcap, CPCAP_REG_ADCC2, &value); + } while ((value & CPCAP_BIT_ASC) && time_before(jiffies, timeout)); + + if (value & CPCAP_BIT_ASC) + dev_err(&(cpcap->spi->dev), + "Timeout waiting for calibration to complete\n"); + + cpcap_irq_clear(cpcap, CPCAP_IRQ_ADCDONE); + + cpcap_regacc_write(cpcap, CPCAP_REG_ADCC1, 0, CPCAP_BIT_CAL_MODE); +} + +static void trigger_next_adc_job_if_any(struct cpcap_device *cpcap) +{ + struct cpcap_adc *adc = cpcap->adcdata; + int head; + + mutex_lock(&adc->queue_mutex); + + head = adc->queue_head; + + if (!adc->queue[head]) { + mutex_unlock(&adc->queue_mutex); + return; + } + mutex_unlock(&adc->queue_mutex); + + adc_setup(cpcap, adc->queue[head]); +} + +static int +adc_enqueue_request(struct cpcap_device *cpcap, struct cpcap_adc_request *req) +{ + struct cpcap_adc *adc = cpcap->adcdata; + int head; + int tail; + int running; + + mutex_lock(&adc->queue_mutex); + + head = adc->queue_head; + tail = adc->queue_tail; + running = (head != tail); + + if (adc->queue[tail]) { + mutex_unlock(&adc->queue_mutex); + return -EBUSY; + } + + adc->queue[tail] = req; + adc->queue_tail = (tail + 1) & (MAX_ADC_FIFO_DEPTH - 1); + + mutex_unlock(&adc->queue_mutex); + + if (!running) + trigger_next_adc_job_if_any(cpcap); + + return 0; +} + +static void +cpcap_adc_sync_read_callback(struct cpcap_device *cpcap, void *param) +{ + struct cpcap_adc_request *req = param; + + complete(&req->completion); +} + +int cpcap_adc_sync_read(struct cpcap_device *cpcap, + struct cpcap_adc_request *request) +{ + int ret; + + request->callback = cpcap_adc_sync_read_callback; + request->callback_param = request; + init_completion(&request->completion); + ret = adc_enqueue_request(cpcap, request); + if (ret) + return ret; + wait_for_completion(&request->completion); + + return 0; +} +EXPORT_SYMBOL_GPL(cpcap_adc_sync_read); + +int cpcap_adc_async_read(struct cpcap_device *cpcap, + struct cpcap_adc_request *request) +{ + return adc_enqueue_request(cpcap, request); +} +EXPORT_SYMBOL_GPL(cpcap_adc_async_read); + +void cpcap_adc_phase(struct cpcap_device *cpcap, struct cpcap_adc_phase *phase) +{ + bank0_phasing[CPCAP_ADC_BATTI_ADC].offset = phase->offset_batti; + bank0_phasing[CPCAP_ADC_BATTI_ADC].multiplier = phase->slope_batti; + + bank0_phasing[CPCAP_ADC_CHG_ISENSE].offset = phase->offset_chrgi; + bank0_phasing[CPCAP_ADC_CHG_ISENSE].multiplier = phase->slope_chrgi; + + bank0_phasing[CPCAP_ADC_BATTP].offset = phase->offset_battp; + bank0_phasing[CPCAP_ADC_BATTP].multiplier = phase->slope_battp; + + bank0_phasing[CPCAP_ADC_BPLUS_AD4].offset = phase->offset_bp; + bank0_phasing[CPCAP_ADC_BPLUS_AD4].multiplier = phase->slope_bp; + + bank0_phasing[CPCAP_ADC_AD0_BATTDETB].offset = phase->offset_battt; + bank0_phasing[CPCAP_ADC_AD0_BATTDETB].multiplier = phase->slope_battt; + + bank0_phasing[CPCAP_ADC_VBUS].offset = phase->offset_chrgv; + bank0_phasing[CPCAP_ADC_VBUS].multiplier = phase->slope_chrgv; +} +EXPORT_SYMBOL_GPL(cpcap_adc_phase); + +static void adc_phase(struct cpcap_adc_request *req, int index) +{ + struct conversion_tbl *conv_tbl = bank0_conversion; + struct phasing_tbl *phase_tbl = bank0_phasing; + int tbl_index = index; + + if (req->type == CPCAP_ADC_TYPE_BANK_1) { + conv_tbl = bank1_conversion; + phase_tbl = bank1_phasing; + } + + if (req->type == CPCAP_ADC_TYPE_BATT_PI) + tbl_index = (tbl_index % 2) ? CPCAP_ADC_BATTI_ADC : + CPCAP_ADC_BATTP; + + if (((req->type == CPCAP_ADC_TYPE_BANK_0) || + (req->type == CPCAP_ADC_TYPE_BATT_PI)) && + (tbl_index == CPCAP_ADC_BATTP)) { + req->result[index] -= phase_tbl[tbl_index].offset; + req->result[index] -= FOUR_POINT_TWO_ADC; + req->result[index] *= phase_tbl[tbl_index].multiplier; + req->result[index] /= phase_tbl[tbl_index].divider; + req->result[index] += FOUR_POINT_TWO_ADC; + } else { + req->result[index] += conv_tbl[tbl_index].cal_offset; + req->result[index] += conv_tbl[tbl_index].align_offset; + req->result[index] *= phase_tbl[tbl_index].multiplier; + req->result[index] /= phase_tbl[tbl_index].divider; + req->result[index] += phase_tbl[tbl_index].offset; + } + + if (req->result[index] < phase_tbl[tbl_index].min) + req->result[index] = phase_tbl[tbl_index].min; + else if (req->result[index] > phase_tbl[tbl_index].max) + req->result[index] = phase_tbl[tbl_index].max; +} + +static void adc_convert(struct cpcap_adc_request *req, int index) +{ + struct conversion_tbl *conv_tbl = bank0_conversion; + int tbl_index = index; + + if (req->type == CPCAP_ADC_TYPE_BANK_1) + conv_tbl = bank1_conversion; + + if (req->type == CPCAP_ADC_TYPE_BATT_PI) + tbl_index = (tbl_index % 2) ? CPCAP_ADC_BATTI_ADC : + CPCAP_ADC_BATTP; + + if (conv_tbl[tbl_index].conv_type == CONV_TYPE_DIRECT) { + req->result[index] *= conv_tbl[tbl_index].multiplier; + req->result[index] /= conv_tbl[tbl_index].divider; + req->result[index] += conv_tbl[tbl_index].conv_offset; + } else if (conv_tbl[tbl_index].conv_type == CONV_TYPE_MAPPING) + req->result[index] = convert_to_kelvins(req->result[tbl_index]); +} + +static void adc_raw(struct cpcap_adc_request *req, int index) +{ + struct conversion_tbl *conv_tbl = bank0_conversion; + struct phasing_tbl *phase_tbl = bank0_phasing; + int tbl_index = index; + + if (req->type == CPCAP_ADC_TYPE_BANK_1) + return; + + if (req->type == CPCAP_ADC_TYPE_BATT_PI) + tbl_index = (tbl_index % 2) ? CPCAP_ADC_BATTI_ADC : + CPCAP_ADC_BATTP; + + req->result[index] += conv_tbl[tbl_index].cal_offset; + + if (req->result[index] < + (phase_tbl[tbl_index].min - conv_tbl[tbl_index].align_offset)) { + req->result[index] = (phase_tbl[tbl_index].min - + conv_tbl[tbl_index].align_offset); + } else if (req->result[index] > + (phase_tbl[tbl_index].max - + conv_tbl[tbl_index].align_offset)) { + req->result[index] = (phase_tbl[tbl_index].max - + conv_tbl[tbl_index].align_offset); + } +} + +static void adc_result(struct cpcap_device *cpcap, + struct cpcap_adc_request *req) +{ + int i; + int j; + unsigned short cal_data; + + cal_data = 0; + cpcap_regacc_read(cpcap, CPCAP_REG_ADCAL1, &cal_data); + bank0_conversion[CPCAP_ADC_CHG_ISENSE].cal_offset = + ((short)cal_data * -1) + 512; + + cal_data = 0; + cpcap_regacc_read(cpcap, CPCAP_REG_ADCAL2, &cal_data); + bank0_conversion[CPCAP_ADC_BATTI_ADC].cal_offset = + ((short)cal_data * -1) + 512; + + + for (i = CPCAP_REG_ADCD0; i <= CPCAP_REG_ADCD7; i++) { + j = i - CPCAP_REG_ADCD0; + cpcap_regacc_read(cpcap, i, (unsigned short *)&req->result[j]); + req->result[j] &= 0x3FF; + + switch (req->format) { + case CPCAP_ADC_FORMAT_PHASED: + adc_phase(req, j); + break; + + case CPCAP_ADC_FORMAT_CONVERTED: + adc_phase(req, j); + adc_convert(req, j); + break; + + case CPCAP_ADC_FORMAT_RAW: + adc_raw(req, j); + break; + + default: + break; + } + } +} + +static void cpcap_adc_irq(enum cpcap_irqs irq, void *data) +{ + struct cpcap_adc *adc = data; + struct cpcap_device *cpcap = adc->cpcap; + struct cpcap_adc_request *req; + int head; + + cancel_delayed_work_sync(&adc->work); + + cpcap_regacc_write(cpcap, CPCAP_REG_ADCC2, + CPCAP_BIT_ADTRIG_DIS, + CPCAP_BIT_ADTRIG_DIS); + + mutex_lock(&adc->queue_mutex); + head = adc->queue_head; + + req = adc->queue[head]; + if (!req) { + dev_info(&(cpcap->spi->dev), + "cpcap_adc_irq: ADC queue empty!\n"); + mutex_unlock(&adc->queue_mutex); + return; + } + adc->queue[head] = NULL; + adc->queue_head = (head + 1) & (MAX_ADC_FIFO_DEPTH - 1); + + mutex_unlock(&adc->queue_mutex); + + adc_result(cpcap, req); + + trigger_next_adc_job_if_any(cpcap); + + req->status = 0; + + req->callback(cpcap, req->callback_param); +} + +static void cpcap_adc_cancel(struct work_struct *work) +{ + int head; + struct cpcap_adc_request *req; + struct cpcap_adc *adc = + container_of(work, struct cpcap_adc, work.work); + + cpcap_irq_mask(adc->cpcap, CPCAP_IRQ_ADCDONE); + + cpcap_regacc_write(adc->cpcap, CPCAP_REG_ADCC2, + CPCAP_BIT_ADTRIG_DIS, + CPCAP_BIT_ADTRIG_DIS); + + mutex_lock(&adc->queue_mutex); + head = adc->queue_head; + + req = adc->queue[head]; + if (!req) { + dev_info(&(adc->cpcap->spi->dev), + "cpcap_adc_cancel: ADC queue empty!\n"); + mutex_unlock(&adc->queue_mutex); + return; + } + adc->queue[head] = NULL; + adc->queue_head = (head + 1) & (MAX_ADC_FIFO_DEPTH - 1); + + mutex_unlock(&adc->queue_mutex); + + req->status = -ETIMEDOUT; + + req->callback(adc->cpcap, req->callback_param); + + trigger_next_adc_job_if_any(adc->cpcap); +} + +static int cpcap_adc_probe(struct platform_device *pdev) +{ + struct cpcap_adc *adc; + unsigned short cal_data; + struct cpcap_platform_data *data; + + if (pdev->dev.platform_data == NULL) { + dev_err(&pdev->dev, "no platform_data\n"); + return -EINVAL; + } + + adc = kzalloc(sizeof(*adc), GFP_KERNEL); + if (!adc) + return -ENOMEM; + + adc->cpcap = pdev->dev.platform_data; + + platform_set_drvdata(pdev, adc); + adc->cpcap->adcdata = adc; + + data = adc->cpcap->spi->controller_data; + + /* Scale ICHRG sense ADCs for non-standard sense resistor */ + bank0_conversion[CPCAP_ADC_CHG_ISENSE].multiplier = + (DEFAULT_ICHARGE_SENSE_RES * DEFAULT_ISENSE_RANGE) / + data->adc_ato->ichrg_sense_res; + + mutex_init(&adc->queue_mutex); + + adc_setup_calibrate(adc->cpcap, CPCAP_ADC_CHG_ISENSE); + adc_setup_calibrate(adc->cpcap, CPCAP_ADC_BATTI_ADC); + + cal_data = 0; + cpcap_regacc_read(adc->cpcap, CPCAP_REG_ADCAL1, &cal_data); + bank0_conversion[CPCAP_ADC_CHG_ISENSE].cal_offset = + ((short)cal_data * -1) + 512; + + cal_data = 0; + cpcap_regacc_read(adc->cpcap, CPCAP_REG_ADCAL2, &cal_data); + bank0_conversion[CPCAP_ADC_BATTI_ADC].cal_offset = + ((short)cal_data * -1) + 512; + + INIT_DELAYED_WORK(&adc->work, cpcap_adc_cancel); + + cpcap_irq_register(adc->cpcap, CPCAP_IRQ_ADCDONE, + cpcap_adc_irq, adc); + + dev_info(&pdev->dev, "CPCAP ADC device probed\n"); + + return 0; +} + +static int cpcap_adc_remove(struct platform_device *pdev) +{ + struct cpcap_adc *adc = platform_get_drvdata(pdev); + int head; + + cancel_delayed_work_sync(&adc->work); + + cpcap_irq_free(adc->cpcap, CPCAP_IRQ_ADCDONE); + + mutex_lock(&adc->queue_mutex); + head = adc->queue_head; + + if (WARN_ON(adc->queue[head])) + dev_err(&pdev->dev, + "adc driver removed with request pending\n"); + + mutex_unlock(&adc->queue_mutex); + kfree(adc); + + return 0; +} + +static struct platform_driver cpcap_adc_driver = { + .driver = { + .name = "cpcap_adc", + }, + .probe = cpcap_adc_probe, + .remove = cpcap_adc_remove, +}; + +static int __init cpcap_adc_init(void) +{ + return platform_driver_register(&cpcap_adc_driver); +} +subsys_initcall(cpcap_adc_init); + +static void __exit cpcap_adc_exit(void) +{ + platform_driver_unregister(&cpcap_adc_driver); +} +module_exit(cpcap_adc_exit); + +MODULE_ALIAS("platform:cpcap_adc"); +MODULE_DESCRIPTION("CPCAP ADC driver"); +MODULE_AUTHOR("Motorola"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/cpcap-core.c b/drivers/mfd/cpcap-core.c new file mode 100644 index 00000000000..b248c9d0d4a --- /dev/null +++ b/drivers/mfd/cpcap-core.c @@ -0,0 +1,577 @@ +/* + * Copyright (C) 2007-2010 Motorola, 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 <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regulator/machine.h> +#include <linux/spi/spi.h> +#include <linux/spi/cpcap.h> +#include <linux/spi/cpcap-regbits.h> +#include <linux/uaccess.h> +#include <linux/reboot.h> +#include <linux/notifier.h> +#include <linux/delay.h> + +struct cpcap_driver_info { + struct list_head list; + struct platform_device *pdev; +}; + +static long ioctl(struct file *file, unsigned int cmd, unsigned long arg); +static int cpcap_probe(struct spi_device *spi); +static int cpcap_remove(struct spi_device *spi); + +const static struct file_operations cpcap_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = ioctl, +}; + +static struct miscdevice cpcap_dev = { + .minor = MISC_DYNAMIC_MINOR, + .name = CPCAP_DEV_NAME, + .fops = &cpcap_fops, +}; + +static struct spi_driver cpcap_driver = { + .driver = { + .name = "cpcap", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = cpcap_probe, + .remove = cpcap_remove, +}; + +static struct platform_device cpcap_adc_device = { + .name = "cpcap_adc", + .id = -1, + .dev.platform_data = NULL, +}; + + +static struct platform_device cpcap_key_device = { + .name = "cpcap_key", + .id = -1, + .dev.platform_data = NULL, +}; + +static struct platform_device cpcap_batt_device = { + .name = "cpcap_battery", + .id = -1, + .dev.platform_data = NULL, +}; + +static struct platform_device cpcap_uc_device = { + .name = "cpcap_uc", + .id = -1, + .dev.platform_data = NULL, +}; + +static struct platform_device cpcap_rtc_device = { + .name = "cpcap_rtc", + .id = -1, + .dev.platform_data = NULL, +}; + +/* List of required CPCAP devices that will ALWAYS be present. + * + * DO NOT ADD NEW DEVICES TO THIS LIST! You must use cpcap_driver_register() + * for any new drivers for non-core functionality of CPCAP. + */ +static struct platform_device *cpcap_devices[] = { + &cpcap_uc_device, + &cpcap_adc_device, + &cpcap_key_device, + &cpcap_batt_device, + &cpcap_rtc_device, +}; + +static struct cpcap_device *misc_cpcap; + +static LIST_HEAD(cpcap_device_list); +static DEFINE_MUTEX(cpcap_driver_lock); + +static int cpcap_reboot(struct notifier_block *this, unsigned long code, + void *cmd) +{ + int ret = -1; + int result = NOTIFY_DONE; + char *mode = cmd; + unsigned short value; + unsigned short counter = 0; + + /* Disable the USB transceiver */ + ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_USBC2, 0, + CPCAP_BIT_USBXCVREN); + + if (ret) { + dev_err(&(misc_cpcap->spi->dev), + "Disable Transciever failure.\n"); + result = NOTIFY_BAD; + } + + if (code == SYS_RESTART) { + if (mode != NULL && !strncmp("outofcharge", mode, 12)) { + /* Set the outofcharge bit in the cpcap */ + ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_VAL1, + CPCAP_BIT_OUT_CHARGE_ONLY, + CPCAP_BIT_OUT_CHARGE_ONLY); + if (ret) { + dev_err(&(misc_cpcap->spi->dev), + "outofcharge cpcap set failure.\n"); + result = NOTIFY_BAD; + } + /* Set the soft reset bit in the cpcap */ + cpcap_regacc_write(misc_cpcap, CPCAP_REG_VAL1, + CPCAP_BIT_SOFT_RESET, + CPCAP_BIT_SOFT_RESET); + if (ret) { + dev_err(&(misc_cpcap->spi->dev), + "reset cpcap set failure.\n"); + result = NOTIFY_BAD; + } + } + + /* Check if we are starting recovery mode */ + if (mode != NULL && !strncmp("recovery", mode, 9)) { + /* Set the fota (recovery mode) bit in the cpcap */ + ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_VAL1, + CPCAP_BIT_FOTA_MODE, CPCAP_BIT_FOTA_MODE); + if (ret) { + dev_err(&(misc_cpcap->spi->dev), + "Recovery cpcap set failure.\n"); + result = NOTIFY_BAD; + } + } else { + /* Set the fota (recovery mode) bit in the cpcap */ + ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_VAL1, 0, + CPCAP_BIT_FOTA_MODE); + if (ret) { + dev_err(&(misc_cpcap->spi->dev), + "Recovery cpcap clear failure.\n"); + result = NOTIFY_BAD; + } + } + /* Check if we are going into fast boot mode */ + if (mode != NULL && !strncmp("bootloader", mode, 11)) { + /* Set the bootmode bit in the cpcap */ + ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_VAL1, + CPCAP_BIT_BOOT_MODE, CPCAP_BIT_BOOT_MODE); + if (ret) { + dev_err(&(misc_cpcap->spi->dev), + "Boot mode cpcap set failure.\n"); + result = NOTIFY_BAD; + } + } + cpcap_regacc_write(misc_cpcap, CPCAP_REG_MI2, 0, 0xFFFF); + } else { + ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_VAL1, + 0, + CPCAP_BIT_OUT_CHARGE_ONLY); + if (ret) { + dev_err(&(misc_cpcap->spi->dev), + "outofcharge cpcap set failure.\n"); + result = NOTIFY_BAD; + } + + /* Clear the soft reset bit in the cpcap */ + ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_VAL1, 0, + CPCAP_BIT_SOFT_RESET); + if (ret) { + dev_err(&(misc_cpcap->spi->dev), + "SW Reset cpcap set failure.\n"); + result = NOTIFY_BAD; + } + /* Clear the fota (recovery mode) bit in the cpcap */ + ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_VAL1, 0, + CPCAP_BIT_FOTA_MODE); + if (ret) { + dev_err(&(misc_cpcap->spi->dev), + "Recovery cpcap clear failure.\n"); + result = NOTIFY_BAD; + } + } + + /* Always clear the kpanic bit */ + ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_VAL1, + 0, CPCAP_BIT_AP_KERNEL_PANIC); + if (ret) { + dev_err(&(misc_cpcap->spi->dev), + "Clear kernel panic bit failure.\n"); + result = NOTIFY_BAD; + } + + /* Always clear the power cut bit on SW Shutdown*/ + ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_PC1, + 0, CPCAP_BIT_PC1_PCEN); + if (ret) { + dev_err(&(misc_cpcap->spi->dev), + "Clear Power Cut bit failure.\n"); + result = NOTIFY_BAD; + } + + cpcap_regacc_write(misc_cpcap, CPCAP_REG_CRM, 0x0300, 0x3FFF); + + (void)cpcap_regacc_read(misc_cpcap, CPCAP_REG_INTS2, &value); + if (!(value & CPCAP_BIT_VBUSVLD_S)) { + while ((value & CPCAP_BIT_SESSVLD_S) && (counter < 100)) { + mdelay(10); + counter++; + (void)cpcap_regacc_read(misc_cpcap, CPCAP_REG_INTS2, + &value); + } + } + + /* Clear the charger and charge path settings to avoid a false turn on + * event in caused by CPCAP. After clearing these settings, 100ms is + * needed to before SYSRSTRTB is pulled low to avoid the false turn on + * event. + */ + cpcap_regacc_write(misc_cpcap, CPCAP_REG_CRM, 0, 0x3FFF); + mdelay(100); + + return result; +} + +static struct notifier_block cpcap_reboot_notifier = { + .notifier_call = cpcap_reboot, +}; + +static int __init cpcap_init(void) +{ + return spi_register_driver(&cpcap_driver); +} + +static void cpcap_vendor_read(struct cpcap_device *cpcap) +{ + unsigned short value; + + (void)cpcap_regacc_read(cpcap, CPCAP_REG_VERSC1, &value); + + cpcap->vendor = (enum cpcap_vendor)((value >> 6) & 0x0007); + cpcap->revision = (enum cpcap_revision)(((value >> 3) & 0x0007) | + ((value << 3) & 0x0038)); +} + + +int cpcap_device_unregister(struct platform_device *pdev) +{ + struct cpcap_driver_info *info; + struct cpcap_driver_info *tmp; + int found; + + + found = 0; + mutex_lock(&cpcap_driver_lock); + + list_for_each_entry_safe(info, tmp, &cpcap_device_list, list) { + if (info->pdev == pdev) { + list_del(&info->list); + + /* + * misc_cpcap != NULL suggests pdev + * already registered + */ + if (misc_cpcap) { + printk(KERN_INFO "CPCAP: unregister %s\n", + pdev->name); + platform_device_unregister(pdev); + } + info->pdev = NULL; + kfree(info); + found = 1; + } + } + + mutex_unlock(&cpcap_driver_lock); + + BUG_ON(!found); + return 0; +} + +int cpcap_device_register(struct platform_device *pdev) +{ + int retval; + struct cpcap_driver_info *info; + + retval = 0; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + printk(KERN_ERR "Cannot save device %s\n", pdev->name); + return -ENOMEM; + } + + mutex_lock(&cpcap_driver_lock); + + info->pdev = pdev; + list_add_tail(&info->list, &cpcap_device_list); + + /* If misc_cpcap is valid, the CPCAP driver has already been probed. + * Therefore, call platform_device_register() to probe the device. + */ + if (misc_cpcap) { + dev_info(&(misc_cpcap->spi->dev), + "Probing CPCAP device %s\n", pdev->name); + + /* + * platform_data is non-empty indicates + * CPCAP client devices need to pass their own data + * In that case we put cpcap data in driver_data + */ + if (pdev->dev.platform_data != NULL) + platform_set_drvdata(pdev, misc_cpcap); + else + pdev->dev.platform_data = misc_cpcap; + retval = platform_device_register(pdev); + } else + printk(KERN_INFO "CPCAP: delaying %s probe\n", + pdev->name); + mutex_unlock(&cpcap_driver_lock); + + return retval; +} + +static int cpcap_probe(struct spi_device *spi) +{ + int retval = -EINVAL; + struct cpcap_device *cpcap; + struct cpcap_platform_data *data; + int i; + struct cpcap_driver_info *info; + + cpcap = kzalloc(sizeof(*cpcap), GFP_KERNEL); + if (cpcap == NULL) + return -ENOMEM; + + cpcap->spi = spi; + data = spi->controller_data; + spi_set_drvdata(spi, cpcap); + + retval = cpcap_regacc_init(cpcap); + if (retval < 0) + goto free_mem; + retval = cpcap_irq_init(cpcap); + if (retval < 0) + goto free_cpcap_irq; + + cpcap_vendor_read(cpcap); + + for (i = 0; i < ARRAY_SIZE(cpcap_devices); i++) + cpcap_devices[i]->dev.platform_data = cpcap; + + retval = misc_register(&cpcap_dev); + if (retval < 0) + goto free_cpcap_irq; + + /* loop twice becuase cpcap_regulator_probe may refer to other devices + * in this list to handle dependencies between regulators. Create them + * all and then add them */ + for (i = 0; i < CPCAP_NUM_REGULATORS; i++) { + struct platform_device *pdev; + + pdev = platform_device_alloc("cpcap-regltr", i); + if (!pdev) { + dev_err(&(spi->dev), "Cannot create regulator\n"); + continue; + } + + pdev->dev.parent = &(spi->dev); + pdev->dev.platform_data = &data->regulator_init[i]; + platform_set_drvdata(pdev, cpcap); + cpcap->regulator_pdev[i] = pdev; + } + + for (i = 0; i < CPCAP_NUM_REGULATORS; i++) { + /* vusb has to be added after sw5 so skip it for now, + * it will be added from probe of sw5 */ + if (i == CPCAP_VUSB) + continue; + platform_device_add(cpcap->regulator_pdev[i]); + } + + platform_add_devices(cpcap_devices, ARRAY_SIZE(cpcap_devices)); + + mutex_lock(&cpcap_driver_lock); + misc_cpcap = cpcap; /* kept for misc device */ + + list_for_each_entry(info, &cpcap_device_list, list) { + int ret = 0; + dev_info(&(spi->dev), "Probing CPCAP device %s\n", + info->pdev->name); + if (info->pdev->dev.platform_data != NULL) + platform_set_drvdata(info->pdev, cpcap); + else + info->pdev->dev.platform_data = cpcap; + ret = platform_device_register(info->pdev); + } + mutex_unlock(&cpcap_driver_lock); + + register_reboot_notifier(&cpcap_reboot_notifier); + + return 0; + +free_cpcap_irq: + cpcap_irq_shutdown(cpcap); +free_mem: + kfree(cpcap); + return retval; +} + +static int cpcap_remove(struct spi_device *spi) +{ + struct cpcap_device *cpcap = spi_get_drvdata(spi); + struct cpcap_driver_info *info; + int i; + + unregister_reboot_notifier(&cpcap_reboot_notifier); + + mutex_lock(&cpcap_driver_lock); + list_for_each_entry(info, &cpcap_device_list, list) { + dev_info(&(spi->dev), "Removing CPCAP device %s\n", + info->pdev->name); + platform_device_unregister(info->pdev); + } + misc_cpcap = NULL; + mutex_unlock(&cpcap_driver_lock); + + for (i = ARRAY_SIZE(cpcap_devices); i > 0; i--) + platform_device_unregister(cpcap_devices[i-1]); + + for (i = 0; i < CPCAP_NUM_REGULATORS; i++) + platform_device_unregister(cpcap->regulator_pdev[i]); + + misc_deregister(&cpcap_dev); + cpcap_irq_shutdown(cpcap); + kfree(cpcap); + return 0; +} + + +static int test_ioctl(unsigned int cmd, unsigned long arg) +{ + int retval = -EINVAL; + struct cpcap_regacc read_data; + struct cpcap_regacc write_data; + + switch (cmd) { + case CPCAP_IOCTL_TEST_READ_REG: + if (copy_from_user((void *)&read_data, (void *)arg, + sizeof(read_data))) + return -EFAULT; + retval = cpcap_regacc_read(misc_cpcap, read_data.reg, + &read_data.value); + if (retval < 0) + return retval; + if (copy_to_user((void *)arg, (void *)&read_data, + sizeof(read_data))) + return -EFAULT; + return 0; + break; + + case CPCAP_IOCTL_TEST_WRITE_REG: + if (copy_from_user((void *) &write_data, + (void *) arg, + sizeof(write_data))) + return -EFAULT; + retval = cpcap_regacc_write(misc_cpcap, write_data.reg, + write_data.value, write_data.mask); + break; + + default: + retval = -ENOTTY; + break; + } + + return retval; +} + +static int adc_ioctl(unsigned int cmd, unsigned long arg) +{ + int retval = -EINVAL; + struct cpcap_adc_phase phase; + + switch (cmd) { + case CPCAP_IOCTL_ADC_PHASE: + if (copy_from_user((void *) &phase, (void *) arg, + sizeof(phase))) + return -EFAULT; + + cpcap_adc_phase(misc_cpcap, &phase); + retval = 0; + break; + + default: + retval = -ENOTTY; + break; + } + + return retval; +} + +#if defined(CONFIG_LEDS_FLASH_RESET) +int cpcap_direct_misc_write(unsigned short reg, unsigned short value,\ + unsigned short mask) +{ + int retval = -EINVAL; + + retval = cpcap_regacc_write(misc_cpcap, reg, value, mask); + + return retval; +} +#endif + +static long ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int retval = -ENOTTY; + unsigned int cmd_num; + + cmd_num = _IOC_NR(cmd); + + if ((cmd_num > CPCAP_IOCTL_NUM_TEST__START) && + (cmd_num < CPCAP_IOCTL_NUM_TEST__END)) { + retval = test_ioctl(cmd, arg); + } + if ((cmd_num > CPCAP_IOCTL_NUM_ADC__START) && + (cmd_num < CPCAP_IOCTL_NUM_ADC__END)) { + retval = adc_ioctl(cmd, arg); + } + + return retval; +} + +static void cpcap_shutdown(void) +{ + spi_unregister_driver(&cpcap_driver); +} + +int cpcap_disable_offmode_wakeups(bool disable) +{ + int retval = 0; + return retval; +} + +subsys_initcall(cpcap_init); +module_exit(cpcap_shutdown); + +MODULE_ALIAS("platform:cpcap"); +MODULE_DESCRIPTION("CPCAP driver"); +MODULE_AUTHOR("Motorola"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/cpcap-irq.c b/drivers/mfd/cpcap-irq.c new file mode 100644 index 00000000000..8d253ea2c56 --- /dev/null +++ b/drivers/mfd/cpcap-irq.c @@ -0,0 +1,809 @@ +/* + * Copyright (C) 2009 - 2010, Motorola, All Rights Reserved. + * + * 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 <linux/gpio.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/mutex.h> +#include <linux/workqueue.h> +#include <linux/wakelock.h> + +#include <linux/spi/cpcap.h> +#include <linux/spi/spi.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> + +#ifdef CONFIG_PM_DEEPSLEEP +#include <linux/suspend.h> +#endif + +#define LONG_KEYPRESS_DURATION 4 /* in seconds */ + +#define NUM_INT_REGS 5 +#define NUM_INTS_PER_REG 16 + +#define CPCAP_INT1_VALID_BITS 0xFFFB +#define CPCAP_INT2_VALID_BITS 0xFFFF +#define CPCAP_INT3_VALID_BITS 0xFFFF +#define CPCAP_INT4_VALID_BITS 0x03FF +#define CPCAP_INT5_VALID_BITS 0xFFFF + +struct cpcap_event_handler { + void (*func)(enum cpcap_irqs, void *); + void *data; +}; + +struct cpcap_irq_info { + uint8_t registered; + uint8_t enabled; + uint32_t count; +}; + +struct cpcap_irqdata { + struct mutex lock; + struct work_struct work; + struct workqueue_struct *workqueue; + struct cpcap_device *cpcap; + struct cpcap_event_handler event_handler[CPCAP_IRQ__NUM]; + struct cpcap_irq_info irq_info[CPCAP_IRQ__NUM]; + struct wake_lock wake_lock; +}; + +#define EVENT_MASK(event) (1 << ((event) % NUM_INTS_PER_REG)) + +enum pwrkey_states { + PWRKEY_RELEASE, /* Power key released state. */ + PWRKEY_PRESS, /* Power key pressed state. */ + PWRKEY_UNKNOWN, /* Unknown power key state. */ +}; + +static irqreturn_t event_isr(int irq, void *data) +{ + struct cpcap_irqdata *irq_data = data; + disable_irq_nosync(irq); + wake_lock(&irq_data->wake_lock); + queue_work(irq_data->workqueue, &irq_data->work); + + return IRQ_HANDLED; +} + +static unsigned short get_int_reg(enum cpcap_irqs event) +{ + unsigned short ret; + + if ((event) >= CPCAP_IRQ_INT5_INDEX) + ret = CPCAP_REG_MI1; + else if ((event) >= CPCAP_IRQ_INT4_INDEX) + ret = CPCAP_REG_INT4; + else if ((event) >= CPCAP_IRQ_INT3_INDEX) + ret = CPCAP_REG_INT3; + else if ((event) >= CPCAP_IRQ_INT2_INDEX) + ret = CPCAP_REG_INT2; + else + ret = CPCAP_REG_INT1; + + return ret; +} + +static unsigned short get_mask_reg(enum cpcap_irqs event) +{ + unsigned short ret; + + if (event >= CPCAP_IRQ_INT5_INDEX) + ret = CPCAP_REG_MIM1; + else if (event >= CPCAP_IRQ_INT4_INDEX) + ret = CPCAP_REG_INTM4; + else if (event >= CPCAP_IRQ_INT3_INDEX) + ret = CPCAP_REG_INTM3; + else if (event >= CPCAP_IRQ_INT2_INDEX) + ret = CPCAP_REG_INTM2; + else + ret = CPCAP_REG_INTM1; + + return ret; +} + +static unsigned short get_sense_reg(enum cpcap_irqs event) +{ + unsigned short ret; + + if (event >= CPCAP_IRQ_INT5_INDEX) + ret = CPCAP_REG_MI2; + else if (event >= CPCAP_IRQ_INT4_INDEX) + ret = CPCAP_REG_INTS4; + else if (event >= CPCAP_IRQ_INT3_INDEX) + ret = CPCAP_REG_INTS3; + else if (event >= CPCAP_IRQ_INT2_INDEX) + ret = CPCAP_REG_INTS2; + else + ret = CPCAP_REG_INTS1; + + return ret; +} + +void cpcap_irq_mask_all(struct cpcap_device *cpcap) +{ + int i; + + static const struct { + unsigned short mask_reg; + unsigned short valid; + } int_reg[NUM_INT_REGS] = { + {CPCAP_REG_INTM1, CPCAP_INT1_VALID_BITS}, + {CPCAP_REG_INTM2, CPCAP_INT2_VALID_BITS}, + {CPCAP_REG_INTM3, CPCAP_INT3_VALID_BITS}, + {CPCAP_REG_INTM4, CPCAP_INT4_VALID_BITS}, + {CPCAP_REG_MIM1, CPCAP_INT5_VALID_BITS} + }; + + for (i = 0; i < NUM_INT_REGS; i++) { + cpcap_regacc_write(cpcap, int_reg[i].mask_reg, + int_reg[i].valid, + int_reg[i].valid); + } +} + +struct pwrkey_data { + struct cpcap_device *cpcap; + enum pwrkey_states state; + struct wake_lock wake_lock; + struct delayed_work pwrkey_work; + int power_double_pressed; +#ifdef CONFIG_PM_DEEPSLEEP + struct hrtimer longPress_timer; + int expired; +#endif + +}; + +#ifdef CONFIG_PM_DBG_DRV +static struct cpcap_irq_pm_dbg { + unsigned short en_ints[NUM_INT_REGS]; + unsigned char suspend; + unsigned char wakeup; +} pm_dbg_info; +#endif /* CONFIG_PM_DBG_DRV */ + +#ifdef CONFIG_PM_DEEPSLEEP + +static enum hrtimer_restart longPress_timer_callback(struct hrtimer *timer) +{ + struct pwrkey_data *pwrkey_data = + container_of(timer, struct pwrkey_data, longPress_timer); + struct cpcap_device *cpcap = pwrkey_data->cpcap; + enum pwrkey_states new_state = PWRKEY_PRESS; + + if (wake_lock_active(&pwrkey_data->wake_lock)) + wake_unlock(&pwrkey_data->wake_lock); + wake_lock_timeout(&pwrkey_data->wake_lock, 20); + + /* long timer expired without being cancelled so send long press + keydown event */ + pwrkey_data->expired = 1; + cpcap_broadcast_key_event(cpcap, KEY_SENDFILE, new_state); + pwrkey_data->state = new_state; + + return HRTIMER_NORESTART; + +} +#endif + +static void pwrkey_work_func(struct work_struct *work) +{ + struct pwrkey_data *pwrkey_data = + container_of(work, struct pwrkey_data, pwrkey_work.work); + struct cpcap_device *cpcap = pwrkey_data->cpcap; + + if (wake_lock_active(&pwrkey_data->wake_lock)) + wake_unlock(&pwrkey_data->wake_lock); + wake_lock_timeout(&pwrkey_data->wake_lock, 20); + if (pwrkey_data->state == PWRKEY_RELEASE) { + /* keyup was detected before keydown was sent, so send + keydown first */ + cpcap_broadcast_key_event(cpcap, KEY_END, PWRKEY_PRESS); + } + + /* Send detected state (keyup/keydown) */ + cpcap_broadcast_key_event(cpcap, KEY_END, pwrkey_data->state); +} + +static void pwrkey_handler(enum cpcap_irqs irq, void *data) +{ + struct pwrkey_data *pwrkey_data = data; + enum pwrkey_states new_state, last_state = pwrkey_data->state; + struct cpcap_device *cpcap = pwrkey_data->cpcap; + + new_state = (enum pwrkey_states) cpcap_irq_sense(cpcap, irq, 0); + + /* First do long keypress detection */ + if (new_state == PWRKEY_RELEASE) { +#ifdef CONFIG_PM_DEEPSLEEP + /* Got a keyup so cancel 2 second timer */ + hrtimer_cancel(&pwrkey_data->longPress_timer); + /* If longpress keydown was previously sent, then send the + long press keyup */ + if (pwrkey_data->expired == 1) { + pwrkey_data->expired = 0; +#endif + cpcap_broadcast_key_event(cpcap, + KEY_SENDFILE, new_state); + pwrkey_data->state = new_state; +#ifdef CONFIG_PM_DEEPSLEEP + } +#endif + } else if (new_state == PWRKEY_PRESS) { +#ifdef CONFIG_PM_DEEPSLEEP + /* Got a keydown so start long keypress timer */ + pwrkey_data->expired = 0; + hrtimer_start(&pwrkey_data->longPress_timer, + ktime_set(LONG_KEYPRESS_DURATION, 0), + HRTIMER_MODE_REL); +#endif + wake_lock_timeout(&pwrkey_data->wake_lock, + (LONG_KEYPRESS_DURATION*HZ)+5); + } + + /* Now do normal powerkey detection (in addition to long press) */ + if ((new_state < PWRKEY_UNKNOWN) && (new_state != last_state)) { + if (new_state == PWRKEY_PRESS) { + if (wake_lock_active(&pwrkey_data->wake_lock)) + wake_unlock(&pwrkey_data->wake_lock); + if (delayed_work_pending(&pwrkey_data->pwrkey_work)) { + /* If 600ms delayed work exists and we got a + keydown, then a doublepress has occured */ + cancel_delayed_work_sync(&pwrkey_data-> \ + pwrkey_work); + wake_lock_timeout(&pwrkey_data->wake_lock, 20); + cpcap_broadcast_key_event(cpcap, + KEY_POWER_DOUBLE, new_state); + pwrkey_data->power_double_pressed = 1; + } else { + /* If no delayed work was pending and we got a + keydown, then start 600ms delayed work */ + wake_lock(&pwrkey_data->wake_lock); + schedule_delayed_work(&pwrkey_data->pwrkey_work, + msecs_to_jiffies(600)); + } + } else { + /* Got a keyup. If we previously sent a doublepress + keydown, then send a doublepress keyup now */ + if (pwrkey_data->power_double_pressed) { + if (wake_lock_active(&pwrkey_data->wake_lock)) + wake_unlock(&pwrkey_data->wake_lock); + wake_lock_timeout(&pwrkey_data->wake_lock, 20); + cpcap_broadcast_key_event(cpcap, + KEY_POWER_DOUBLE, new_state); + pwrkey_data->power_double_pressed = 0; + /* If the 600ms delayed work is done and we got a keyup, + then send the keyup now */ + } else if (!delayed_work_pending(&pwrkey_data-> \ + pwrkey_work)) { + if (wake_lock_active(&pwrkey_data->wake_lock)) + wake_unlock(&pwrkey_data->wake_lock); + wake_lock_timeout(&pwrkey_data->wake_lock, 20); + cpcap_broadcast_key_event(cpcap, KEY_END, + new_state); + } + /* If we got a keyup while 600ms delayed work is still + pending, then do nothing now and let the delayed + work handler handle this */ + } + pwrkey_data->state = new_state; + } + cpcap_irq_unmask(cpcap, CPCAP_IRQ_ON); +} + +static int pwrkey_init(struct cpcap_device *cpcap) +{ + struct pwrkey_data *data = kmalloc(sizeof(struct pwrkey_data), + GFP_KERNEL); + int retval; + + if (!data) + return -ENOMEM; + data->cpcap = cpcap; + data->state = PWRKEY_RELEASE; + data->power_double_pressed = 0; + retval = cpcap_irq_register(cpcap, CPCAP_IRQ_ON, pwrkey_handler, data); + if (retval) + kfree(data); + wake_lock_init(&data->wake_lock, WAKE_LOCK_SUSPEND, "pwrkey"); +#ifdef CONFIG_PM_DEEPSLEEP + + hrtimer_init(&(data->longPress_timer), + CLOCK_MONOTONIC, + HRTIMER_MODE_REL); + + (data->longPress_timer).function = longPress_timer_callback; +#endif + INIT_DELAYED_WORK(&data->pwrkey_work, pwrkey_work_func); + + return retval; +} + +static void pwrkey_remove(struct cpcap_device *cpcap) +{ + struct pwrkey_data *data; + + cpcap_irq_get_data(cpcap, CPCAP_IRQ_ON, (void **)&data); + if (!data) + return; + cancel_delayed_work_sync(&data->pwrkey_work); + cpcap_irq_free(cpcap, CPCAP_IRQ_ON); + wake_lock_destroy(&data->wake_lock); + kfree(data); +} + +static int int_read_and_clear(struct cpcap_device *cpcap, + unsigned short status_reg, + unsigned short mask_reg, + unsigned short valid_mask, + unsigned short *en) +{ + unsigned short ireg_val, mreg_val; + int ret; + ret = cpcap_regacc_read(cpcap, status_reg, &ireg_val); + if (ret) + return ret; + ret = cpcap_regacc_read(cpcap, mask_reg, &mreg_val); + if (ret) + return ret; + *en |= ireg_val & ~mreg_val; + *en &= valid_mask; + ret = cpcap_regacc_write(cpcap, mask_reg, *en, *en); + if (ret) + return ret; + ret = cpcap_regacc_write(cpcap, status_reg, *en, *en); + if (ret) + return ret; + return 0; +} + + +static void irq_work_func(struct work_struct *work) +{ + int retval = 0; + unsigned short en_ints[NUM_INT_REGS]; + int i; + struct cpcap_irqdata *data; + struct cpcap_device *cpcap; + struct spi_device *spi; + struct cpcap_platform_data *pdata; + unsigned int irq_gpio; + + static const struct { + unsigned short status_reg; + unsigned short mask_reg; + unsigned short valid; + } int_reg[NUM_INT_REGS] = { + {CPCAP_REG_INT1, CPCAP_REG_INTM1, CPCAP_INT1_VALID_BITS}, + {CPCAP_REG_INT2, CPCAP_REG_INTM2, CPCAP_INT2_VALID_BITS}, + {CPCAP_REG_INT3, CPCAP_REG_INTM3, CPCAP_INT3_VALID_BITS}, + {CPCAP_REG_INT4, CPCAP_REG_INTM4, CPCAP_INT4_VALID_BITS}, + {CPCAP_REG_MI1, CPCAP_REG_MIM1, CPCAP_INT5_VALID_BITS} + }; + + for (i = 0; i < NUM_INT_REGS; ++i) + en_ints[i] = 0; + + data = container_of(work, struct cpcap_irqdata, work); + cpcap = data->cpcap; + spi = cpcap->spi; + pdata = (struct cpcap_platform_data *) spi->controller_data; + irq_gpio = pdata->irq_gpio; + + while (gpio_get_value(irq_gpio)) { + for (i = 0; i < NUM_INT_REGS; ++i) { + retval = int_read_and_clear(cpcap, + int_reg[i].status_reg, + int_reg[i].mask_reg, + int_reg[i].valid, + &en_ints[i]); + if (retval < 0) { + dev_err(&cpcap->spi->dev, + "Error reading interrupts\n"); + break; + } + } + } + enable_irq(spi->irq); + +#ifdef CONFIG_PM_DBG_DRV + if ((pm_dbg_info.suspend != 0) && (pm_dbg_info.wakeup == 0)) { + for (i = 0; i < NUM_INT_REGS; ++i) + pm_dbg_info.en_ints[i] = en_ints[i]; + pm_dbg_info.wakeup = 1; + } +#endif /* CONFIG_PM_DBG_DRV */ + + /* lock protects event handlers and data */ + mutex_lock(&data->lock); + for (i = 0; i < NUM_INT_REGS; ++i) { + unsigned char index; + + while (en_ints[i] > 0) { + struct cpcap_event_handler *event_handler; + + /* find the first set bit */ + index = (unsigned char)(ffs(en_ints[i]) - 1); + if (index >= CPCAP_IRQ__NUM) + goto error; + /* clear the bit */ + en_ints[i] &= ~(1 << index); + /* find the event that occurred */ + index += CPCAP_IRQ__START + (i * NUM_INTS_PER_REG); + if (index >= CPCAP_IRQ__NUM) + goto error; + event_handler = &data->event_handler[index]; + + if (event_handler->func) + event_handler->func(index, event_handler->data); + + data->irq_info[index].count++; + + } + } +error: + mutex_unlock(&data->lock); + wake_unlock(&data->wake_lock); +} + +#ifdef CONFIG_DEBUG_FS +static int cpcap_dbg_irq_show(struct seq_file *s, void *data) +{ + static const char *irq_name[] = { + [CPCAP_IRQ_HSCLK] = "HSCLK", + [CPCAP_IRQ_PRIMAC] = "PRIMAC", + [CPCAP_IRQ_SECMAC] = "SECMAC", + [CPCAP_IRQ_LOWBPL] = "LOWBPL", + [CPCAP_IRQ_SEC2PRI] = "SEC2PRI", + [CPCAP_IRQ_LOWBPH] = "LOWBPH", + [CPCAP_IRQ_EOL] = "EOL", + [CPCAP_IRQ_TS] = "TS", + [CPCAP_IRQ_ADCDONE] = "ADCDONE", + [CPCAP_IRQ_HS] = "HS", + [CPCAP_IRQ_MB2] = "MB2", + [CPCAP_IRQ_VBUSOV] = "VBUSOV", + [CPCAP_IRQ_RVRS_CHRG] = "RVRS_CHRG", + [CPCAP_IRQ_CHRG_DET] = "CHRG_DET", + [CPCAP_IRQ_IDFLOAT] = "IDFLOAT", + [CPCAP_IRQ_IDGND] = "IDGND", + + [CPCAP_IRQ_SE1] = "SE1", + [CPCAP_IRQ_SESSEND] = "SESSEND", + [CPCAP_IRQ_SESSVLD] = "SESSVLD", + [CPCAP_IRQ_VBUSVLD] = "VBUSVLD", + [CPCAP_IRQ_CHRG_CURR1] = "CHRG_CURR1", + [CPCAP_IRQ_CHRG_CURR2] = "CHRG_CURR2", + [CPCAP_IRQ_RVRS_MODE] = "RVRS_MODE", + [CPCAP_IRQ_ON] = "ON", + [CPCAP_IRQ_ON2] = "ON2", + [CPCAP_IRQ_CLK] = "CLK", + [CPCAP_IRQ_1HZ] = "1HZ", + [CPCAP_IRQ_PTT] = "PTT", + [CPCAP_IRQ_SE0CONN] = "SE0CONN", + [CPCAP_IRQ_CHRG_SE1B] = "CHRG_SE1B", + [CPCAP_IRQ_UART_ECHO_OVERRUN] = "UART_ECHO_OVERRUN", + [CPCAP_IRQ_EXTMEMHD] = "EXTMEMHD", + + [CPCAP_IRQ_WARM] = "WARM", + [CPCAP_IRQ_SYSRSTR] = "SYSRSTR", + [CPCAP_IRQ_SOFTRST] = "SOFTRST", + [CPCAP_IRQ_DIEPWRDWN] = "DIEPWRDWN", + [CPCAP_IRQ_DIETEMPH] = "DIETEMPH", + [CPCAP_IRQ_PC] = "PC", + [CPCAP_IRQ_OFLOWSW] = "OFLOWSW", + [CPCAP_IRQ_TODA] = "TODA", + [CPCAP_IRQ_OPT_SEL_DTCH] = "OPT_SEL_DTCH", + [CPCAP_IRQ_OPT_SEL_STATE] = "OPT_SEL_STATE", + [CPCAP_IRQ_ONEWIRE1] = "ONEWIRE1", + [CPCAP_IRQ_ONEWIRE2] = "ONEWIRE2", + [CPCAP_IRQ_ONEWIRE3] = "ONEWIRE3", + [CPCAP_IRQ_UCRESET] = "UCRESET", + [CPCAP_IRQ_PWRGOOD] = "PWRGOOD", + [CPCAP_IRQ_USBDPLLCLK] = "USBDPLLCLK", + + [CPCAP_IRQ_DPI] = "DPI", + [CPCAP_IRQ_DMI] = "DMI", + [CPCAP_IRQ_UCBUSY] = "UCBUSY", + [CPCAP_IRQ_GCAI_CURR1] = "GCAI_CURR1", + [CPCAP_IRQ_GCAI_CURR2] = "GCAI_CURR2", + [CPCAP_IRQ_SB_MAX_RETRANSMIT_ERR] = "SB_MAX_RETRANSMIT_ERR", + [CPCAP_IRQ_BATTDETB] = "BATTDETB", + [CPCAP_IRQ_PRIHALT] = "PRIHALT", + [CPCAP_IRQ_SECHALT] = "SECHALT", + [CPCAP_IRQ_CC_CAL] = "CC_CAL", + + [CPCAP_IRQ_UC_PRIROMR] = "UC_PRIROMR", + [CPCAP_IRQ_UC_PRIRAMW] = "UC_PRIRAMW", + [CPCAP_IRQ_UC_PRIRAMR] = "UC_PRIRAMR", + [CPCAP_IRQ_UC_USEROFF] = "UC_USEROFF", + [CPCAP_IRQ_UC_PRIMACRO_4] = "UC_PRIMACRO_4", + [CPCAP_IRQ_UC_PRIMACRO_5] = "UC_PRIMACRO_5", + [CPCAP_IRQ_UC_PRIMACRO_6] = "UC_PRIMACRO_6", + [CPCAP_IRQ_UC_PRIMACRO_7] = "UC_PRIMACRO_7", + [CPCAP_IRQ_UC_PRIMACRO_8] = "UC_PRIMACRO_8", + [CPCAP_IRQ_UC_PRIMACRO_9] = "UC_PRIMACRO_9", + [CPCAP_IRQ_UC_PRIMACRO_10] = "UC_PRIMACRO_10", + [CPCAP_IRQ_UC_PRIMACRO_11] = "UC_PRIMACRO_11", + [CPCAP_IRQ_UC_PRIMACRO_12] = "UC_PRIMACRO_12", + [CPCAP_IRQ_UC_PRIMACRO_13] = "UC_PRIMACRO_13", + [CPCAP_IRQ_UC_PRIMACRO_14] = "UC_PRIMACRO_14", + [CPCAP_IRQ_UC_PRIMACRO_15] = "UC_PRIMACRO_15", + }; + unsigned int i; + struct cpcap_irqdata *irqdata = s->private; + + seq_printf(s, "%21s%9s%12s%10s\n", + "CPCAP IRQ", "Enabled", "Registered", "Count"); + + for (i = 0; i < CPCAP_IRQ__NUM; i++) { + if ((i <= CPCAP_IRQ_CC_CAL) || (i >= CPCAP_IRQ_UC_PRIROMR)) { + seq_printf(s, "%21s%9d%12d%10d\n", + irq_name[i], + irqdata->irq_info[i].enabled, + irqdata->irq_info[i].registered, + irqdata->irq_info[i].count); + } + } + return 0; +} + +static int cpcap_dbg_irq_open(struct inode *inode, struct file *file) +{ + return single_open(file, cpcap_dbg_irq_show, inode->i_private); +} + +static const struct file_operations debug_fops = { + .open = cpcap_dbg_irq_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; +#endif + +int cpcap_irq_init(struct cpcap_device *cpcap) +{ + int retval; + struct spi_device *spi = cpcap->spi; + struct cpcap_irqdata *data; + + data = kzalloc(sizeof(struct cpcap_irqdata), GFP_KERNEL); + if (!data) + return -ENOMEM; + + cpcap_irq_mask_all(cpcap); + + data->workqueue = create_workqueue("cpcap_irq"); + INIT_WORK(&data->work, irq_work_func); + mutex_init(&data->lock); + wake_lock_init(&data->wake_lock, WAKE_LOCK_SUSPEND, "cpcap-irq"); + data->cpcap = cpcap; + + retval = request_irq(spi->irq, event_isr, IRQF_DISABLED | + IRQF_TRIGGER_RISING, "cpcap-irq", data); + if (retval) { + printk(KERN_ERR "cpcap_irq: Failed requesting irq.\n"); + goto error; + } + + enable_irq_wake(spi->irq); + + cpcap->irqdata = data; + retval = pwrkey_init(cpcap); + if (retval) { + printk(KERN_ERR "cpcap_irq: Failed initializing pwrkey.\n"); + goto error; + } +#ifdef CONFIG_DEBUG_FS + (void)debugfs_create_file("cpcap-irq", S_IRUGO, NULL, data, + &debug_fops); +#endif + return 0; + +error: + free_irq(spi->irq, data); + kfree(data); + printk(KERN_ERR "cpcap_irq: Error registering cpcap irq.\n"); + return retval; +} + +void cpcap_irq_shutdown(struct cpcap_device *cpcap) +{ + struct spi_device *spi = cpcap->spi; + struct cpcap_irqdata *data = cpcap->irqdata; + + pwrkey_remove(cpcap); + cancel_work_sync(&data->work); + destroy_workqueue(data->workqueue); + free_irq(spi->irq, data); + kfree(data); +} + +int cpcap_irq_register(struct cpcap_device *cpcap, + enum cpcap_irqs irq, + void (*cb_func) (enum cpcap_irqs, void *), + void *data) +{ + struct cpcap_irqdata *irqdata = cpcap->irqdata; + int retval = 0; + + if ((irq >= CPCAP_IRQ__NUM) || (!cb_func)) + return -EINVAL; + + mutex_lock(&irqdata->lock); + + if (irqdata->event_handler[irq].func == NULL) { + irqdata->irq_info[irq].registered = 1; + cpcap_irq_unmask(cpcap, irq); + irqdata->event_handler[irq].func = cb_func; + irqdata->event_handler[irq].data = data; + } else + retval = -EPERM; + + mutex_unlock(&irqdata->lock); + + return retval; +} +EXPORT_SYMBOL_GPL(cpcap_irq_register); + +int cpcap_irq_free(struct cpcap_device *cpcap, enum cpcap_irqs irq) +{ + struct cpcap_irqdata *data = cpcap->irqdata; + int retval; + + if (irq >= CPCAP_IRQ__NUM) + return -EINVAL; + + mutex_lock(&data->lock); + retval = cpcap_irq_mask(cpcap, irq); + data->event_handler[irq].func = NULL; + data->event_handler[irq].data = NULL; + data->irq_info[irq].registered = 0; + mutex_unlock(&data->lock); + + return retval; +} +EXPORT_SYMBOL_GPL(cpcap_irq_free); + +int cpcap_irq_get_data(struct cpcap_device *cpcap, + enum cpcap_irqs irq, + void **data) +{ + struct cpcap_irqdata *irqdata = cpcap->irqdata; + + if (irq >= CPCAP_IRQ__NUM) + return -EINVAL; + + mutex_lock(&irqdata->lock); + *data = irqdata->event_handler[irq].data; + mutex_unlock(&irqdata->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(cpcap_irq_get_data); + +int cpcap_irq_clear(struct cpcap_device *cpcap, + enum cpcap_irqs irq) +{ + int retval = -EINVAL; + + if ((irq < CPCAP_IRQ__NUM) && (irq != CPCAP_IRQ_SECMAC)) { + retval = cpcap_regacc_write(cpcap, + get_int_reg(irq), + EVENT_MASK(irq), + EVENT_MASK(irq)); + } + + return retval; +} +EXPORT_SYMBOL_GPL(cpcap_irq_clear); + +int cpcap_irq_mask(struct cpcap_device *cpcap, + enum cpcap_irqs irq) +{ + struct cpcap_irqdata *data = cpcap->irqdata; + int retval = -EINVAL; + + if ((irq < CPCAP_IRQ__NUM) && (irq != CPCAP_IRQ_SECMAC)) { + data->irq_info[irq].enabled = 0; + retval = cpcap_regacc_write(cpcap, + get_mask_reg(irq), + EVENT_MASK(irq), + EVENT_MASK(irq)); + } + + return retval; +} +EXPORT_SYMBOL_GPL(cpcap_irq_mask); + +int cpcap_irq_unmask(struct cpcap_device *cpcap, + enum cpcap_irqs irq) +{ + struct cpcap_irqdata *data = cpcap->irqdata; + int retval = -EINVAL; + + if ((irq < CPCAP_IRQ__NUM) && (irq != CPCAP_IRQ_SECMAC)) { + data->irq_info[irq].enabled = 1; + retval = cpcap_regacc_write(cpcap, + get_mask_reg(irq), + 0, + EVENT_MASK(irq)); + } + + return retval; +} +EXPORT_SYMBOL_GPL(cpcap_irq_unmask); + +int cpcap_irq_mask_get(struct cpcap_device *cpcap, + enum cpcap_irqs irq) +{ + struct cpcap_irqdata *data = cpcap->irqdata; + int retval = -EINVAL; + + if ((irq < CPCAP_IRQ__NUM) && (irq != CPCAP_IRQ_SECMAC)) + return data->irq_info[irq].enabled; + + return retval; +} +EXPORT_SYMBOL_GPL(cpcap_irq_mask_get); + +int cpcap_irq_sense(struct cpcap_device *cpcap, + enum cpcap_irqs irq, + unsigned char clear) +{ + unsigned short val; + int retval; + + if (irq >= CPCAP_IRQ__NUM) + return -EINVAL; + + retval = cpcap_regacc_read(cpcap, get_sense_reg(irq), &val); + if (retval) + return retval; + + if (clear) + retval = cpcap_irq_clear(cpcap, irq); + if (retval) + return retval; + + return ((val & EVENT_MASK(irq)) != 0) ? 1 : 0; +} +EXPORT_SYMBOL_GPL(cpcap_irq_sense); + +#ifdef CONFIG_PM_DBG_DRV +void cpcap_irq_pm_dbg_suspend(void) +{ + pm_dbg_info.suspend = 1; + pm_dbg_info.wakeup = 0; +} + +void cpcap_irq_pm_dbg_resume(void) +{ + pm_dbg_info.suspend = 0; + if (pm_dbg_info.wakeup != 0) { + printk(KERN_INFO "PM_DBG WAKEUP CPCAP IRQ = 0x%x.0x%x.0%x.0x%x.0x%x\n", + pm_dbg_info.en_ints[0], + pm_dbg_info.en_ints[1], + pm_dbg_info.en_ints[2], + pm_dbg_info.en_ints[3], + pm_dbg_info.en_ints[4]); + } +} +#endif /* CONFIG_PM_DBG_DRV */ diff --git a/drivers/mfd/cpcap-key.c b/drivers/mfd/cpcap-key.c new file mode 100644 index 00000000000..c9f066391fb --- /dev/null +++ b/drivers/mfd/cpcap-key.c @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2009 Motorola, 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 <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/spi/cpcap.h> +#include <linux/spi/cpcap-regbits.h> + +#if defined(CONFIG_MFD_M4SENSORHUB) || defined(CONFIG_MFD_M4SENSORHUB_MODULE) +extern int m4sensorhub_stillmode_exit(void); +#endif + +struct cpcap_key_data { + struct input_dev *input_dev; + struct cpcap_device *cpcap; +}; + +static int __init cpcap_key_probe(struct platform_device *pdev) +{ + int err; + struct cpcap_key_data *key; + + if (pdev->dev.platform_data == NULL) { + dev_err(&pdev->dev, "no platform_data\n"); + return -EINVAL; + } + + key = kzalloc(sizeof(*key), GFP_KERNEL); + if (!key) + return -ENOMEM; + + key->cpcap = pdev->dev.platform_data; + + key->input_dev = input_allocate_device(); + if (key->input_dev == NULL) { + dev_err(&pdev->dev, "can't allocate input device\n"); + err = -ENOMEM; + goto err0; + } + + set_bit(EV_KEY, key->input_dev->evbit); + set_bit(KEY_MEDIA, key->input_dev->keybit); + set_bit(KEY_END, key->input_dev->keybit); + set_bit(KEY_POWER_DOUBLE, key->input_dev->keybit); + set_bit(KEY_PLAYCD, key->input_dev->keybit); + set_bit(KEY_VOLUMEDOWN, key->input_dev->keybit); + set_bit(KEY_VOLUMEUP, key->input_dev->keybit); + set_bit(KEY_POWER_SONG, key->input_dev->keybit); + set_bit(KEY_SENDFILE, key->input_dev->keybit); + + key->input_dev->name = "cpcap-key"; + + err = input_register_device(key->input_dev); + if (err < 0) { + dev_err(&pdev->dev, "could not register input device.\n"); + goto err1; + } + + platform_set_drvdata(pdev, key); + cpcap_set_keydata(key->cpcap, key); + + dev_info(&pdev->dev, "CPCAP key device probed\n"); + + return 0; + +err1: + input_free_device(key->input_dev); +err0: + kfree(key); + return err; +} + +static int __exit cpcap_key_remove(struct platform_device *pdev) +{ + struct cpcap_key_data *key = platform_get_drvdata(pdev); + + input_unregister_device(key->input_dev); + input_free_device(key->input_dev); + kfree(key); + + return 0; +} + +void cpcap_broadcast_key_event(struct cpcap_device *cpcap, + unsigned int code, int value) +{ + struct cpcap_key_data *key = cpcap_get_keydata(cpcap); + +/* TODO +#if defined(CONFIG_MFD_M4SENSORHUB) || defined(CONFIG_MFD_M4SENSORHUB_MODULE) + //Notify sensorhub driver of power key down event + if (key && value) + m4sensorhub_stillmode_exit(); +#endif +*/ + if (key && key->input_dev) { + input_report_key(key->input_dev, code, value); + /*sync with input subsystem to solve the key cached problem*/ + input_sync(key->input_dev); + } +} +EXPORT_SYMBOL(cpcap_broadcast_key_event); + +static struct platform_driver cpcap_key_driver = { + .probe = cpcap_key_probe, + .remove = __exit_p(cpcap_key_remove), + .driver = { + .name = "cpcap_key", + .owner = THIS_MODULE, + }, +}; + +static int __init cpcap_key_init(void) +{ + return platform_driver_register(&cpcap_key_driver); +} +module_init(cpcap_key_init); + +static void __exit cpcap_key_exit(void) +{ + platform_driver_unregister(&cpcap_key_driver); +} +module_exit(cpcap_key_exit); + +MODULE_ALIAS("platform:cpcap_key"); +MODULE_DESCRIPTION("CPCAP KEY driver"); +MODULE_AUTHOR("Motorola"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/cpcap-regacc.c b/drivers/mfd/cpcap-regacc.c new file mode 100644 index 00000000000..1a616014188 --- /dev/null +++ b/drivers/mfd/cpcap-regacc.c @@ -0,0 +1,393 @@ +/* + * Copyright (C) 2007-2009 Motorola, 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 <linux/device.h> +#include <linux/mutex.h> +#include <linux/spi/spi.h> +#include <linux/spi/cpcap.h> +#include <linux/spi/cpcap-regbits.h> + +#define IS_CPCAP(reg) ((reg) >= CPCAP_REG_START && (reg) <= CPCAP_REG_END) + +static DEFINE_MUTEX(reg_access); + +/* + * This table contains information about a single register in the power IC. + * It is used during register access to information such as the register address + * and the modifiability of each bit in the register. Special notes for + * particular elements of this structure follows: + * + * constant_mask: A '1' in this mask indicates that the corresponding bit has a + * 'constant' modifiability, and therefore must never be changed by any register + * access. + * + * It is important to note that any bits that are 'constant' must have + * synchronized read/write values. That is to say, when a 'constant' bit is + * read the value read must be identical to the value that must be written to + * that bit in order for that bit to be read with the same value. + * + * rbw_mask: A '1' in this mask indicates that the corresponding bit (when not + * being changed) should be written with the current value of that bit. A '0' + * in this mask indicates that the corresponding bit (when not being changed) + * should be written with a value of '0'. + */ +static const struct { + unsigned short address; /* Address of the register */ + unsigned short constant_mask; /* Constant modifiability mask */ + unsigned short rbw_mask; /* Read-before-write mask */ +} register_info_tbl[CPCAP_NUM_REG_CPCAP] = { + [CPCAP_REG_INT1] = {0, 0x0004, 0x0000}, + [CPCAP_REG_INT2] = {1, 0x0000, 0x0000}, + [CPCAP_REG_INT3] = {2, 0x0000, 0x0000}, + [CPCAP_REG_INT4] = {3, 0xFC00, 0x0000}, + [CPCAP_REG_INTM1] = {4, 0x0004, 0xFFFF}, + [CPCAP_REG_INTM2] = {5, 0x0000, 0xFFFF}, + [CPCAP_REG_INTM3] = {6, 0x0000, 0xFFFF}, + [CPCAP_REG_INTM4] = {7, 0xFC00, 0xFFFF}, + [CPCAP_REG_INTS1] = {8, 0xFFFF, 0xFFFF}, + [CPCAP_REG_INTS2] = {9, 0xFFFF, 0xFFFF}, + [CPCAP_REG_INTS3] = {10, 0xFFFF, 0xFFFF}, + [CPCAP_REG_INTS4] = {11, 0xFFFF, 0xFFFF}, + [CPCAP_REG_ASSIGN1] = {12, 0x80F8, 0xFFFF}, + [CPCAP_REG_ASSIGN2] = {13, 0x0000, 0xFFFF}, + [CPCAP_REG_ASSIGN3] = {14, 0x0004, 0xFFFF}, + [CPCAP_REG_ASSIGN4] = {15, 0x0068, 0xFFFF}, + [CPCAP_REG_ASSIGN5] = {16, 0x0000, 0xFFFF}, + [CPCAP_REG_ASSIGN6] = {17, 0xFC00, 0xFFFF}, + [CPCAP_REG_VERSC1] = {18, 0xFFFF, 0xFFFF}, + [CPCAP_REG_VERSC2] = {19, 0xFFFF, 0xFFFF}, + [CPCAP_REG_MI1] = {128, 0x0000, 0x0000}, + [CPCAP_REG_MIM1] = {129, 0x0000, 0xFFFF}, + [CPCAP_REG_MI2] = {130, 0x0000, 0xFFFF}, + [CPCAP_REG_MIM2] = {131, 0xFFFF, 0xFFFF}, + [CPCAP_REG_UCC1] = {132, 0xF000, 0xFFFF}, + [CPCAP_REG_UCC2] = {133, 0xFC00, 0xFFFF}, + [CPCAP_REG_PC1] = {135, 0xFC00, 0xFFFF}, + [CPCAP_REG_PC2] = {136, 0xFC00, 0xFFFF}, + [CPCAP_REG_BPEOL] = {137, 0xFE00, 0xFFFF}, + [CPCAP_REG_PGC] = {138, 0xFE00, 0xFFFF}, + [CPCAP_REG_MT1] = {139, 0x0000, 0x0000}, + [CPCAP_REG_MT2] = {140, 0x0000, 0x0000}, + [CPCAP_REG_MT3] = {141, 0x0000, 0x0000}, + [CPCAP_REG_PF] = {142, 0x0000, 0xFFFF}, + [CPCAP_REG_SCC] = {256, 0xFF00, 0xFFFF}, + [CPCAP_REG_SW1] = {257, 0xFFFF, 0xFFFF}, + [CPCAP_REG_SW2] = {258, 0xFC7F, 0xFFFF}, + [CPCAP_REG_UCTM] = {259, 0xFFFE, 0xFFFF}, + [CPCAP_REG_TOD1] = {260, 0xFF00, 0xFFFF}, + [CPCAP_REG_TOD2] = {261, 0xFE00, 0xFFFF}, + [CPCAP_REG_TODA1] = {262, 0xFF00, 0xFFFF}, + [CPCAP_REG_TODA2] = {263, 0xFE00, 0xFFFF}, + [CPCAP_REG_DAY] = {264, 0x8000, 0xFFFF}, + [CPCAP_REG_DAYA] = {265, 0x8000, 0xFFFF}, + [CPCAP_REG_VAL1] = {266, 0x0000, 0xFFFF}, + [CPCAP_REG_VAL2] = {267, 0x0000, 0xFFFF}, + [CPCAP_REG_SDVSPLL] = {384, 0x2488, 0xFFFF}, + [CPCAP_REG_SI2CC1] = {385, 0x8000, 0xFFFF}, + [CPCAP_REG_Si2CC2] = {386, 0xFF00, 0xFFFF}, + [CPCAP_REG_S1C1] = {387, 0x9080, 0xFFFF}, + [CPCAP_REG_S1C2] = {388, 0x8080, 0xFFFF}, + [CPCAP_REG_S2C1] = {389, 0x9080, 0xFFFF}, + [CPCAP_REG_S2C2] = {390, 0x8080, 0xFFFF}, + [CPCAP_REG_S3C] = {391, 0xFA84, 0xFFFF}, + [CPCAP_REG_S4C1] = {392, 0x9080, 0xFFFF}, + [CPCAP_REG_S4C2] = {393, 0x8080, 0xFFFF}, + [CPCAP_REG_S5C] = {394, 0xFFD5, 0xFFFF}, + [CPCAP_REG_S6C] = {395, 0xFFF4, 0xFFFF}, + [CPCAP_REG_VCAMC] = {396, 0xFF48, 0xFFFF}, + [CPCAP_REG_VCSIC] = {397, 0xFFA8, 0xFFFF}, + [CPCAP_REG_VDACC] = {398, 0xFF48, 0xFFFF}, + [CPCAP_REG_VDIGC] = {399, 0xFF48, 0xFFFF}, + [CPCAP_REG_VFUSEC] = {400, 0xFF50, 0xFFFF}, + [CPCAP_REG_VHVIOC] = {401, 0xFFE8, 0xFFFF}, + [CPCAP_REG_VSDIOC] = {402, 0xFF40, 0xFFFF}, + [CPCAP_REG_VPLLC] = {403, 0xFFA4, 0xFFFF}, + [CPCAP_REG_VRF1C] = {404, 0xFF50, 0xFFFF}, + [CPCAP_REG_VRF2C] = {405, 0xFFD4, 0xFFFF}, + [CPCAP_REG_VRFREFC] = {406, 0xFFD4, 0xFFFF}, + [CPCAP_REG_VWLAN1C] = {407, 0xFFA8, 0xFFFF}, + [CPCAP_REG_VWLAN2C] = {408, 0xFD32, 0xFFFF}, + [CPCAP_REG_VSIMC] = {409, 0xE154, 0xFFFF}, + [CPCAP_REG_VVIBC] = {410, 0xFFF2, 0xFFFF}, +#ifdef CONFIG_EMU_UART_DEBUG + [CPCAP_REG_VUSBC] = {411, 0xFFFF, 0xFFFF}, +#else + [CPCAP_REG_VUSBC] = {411, 0xFEA2, 0xFFFF}, +#endif + [CPCAP_REG_VUSBINT1C] = {412, 0xFFD4, 0xFFFF}, + [CPCAP_REG_VUSBINT2C] = {413, 0xFFD4, 0xFFFF}, + [CPCAP_REG_URT] = {414, 0xFFFE, 0xFFFF}, + [CPCAP_REG_URM1] = {415, 0x0000, 0xFFFF}, + [CPCAP_REG_URM2] = {416, 0xFC00, 0xFFFF}, + [CPCAP_REG_VAUDIOC] = {512, 0xFF88, 0xFFFF}, + [CPCAP_REG_CC] = {513, 0x0000, 0xFEDF}, + [CPCAP_REG_CDI] = {514, 0x4000, 0xFFFF}, + [CPCAP_REG_SDAC] = {515, 0xF000, 0xFCFF}, + [CPCAP_REG_SDACDI] = {516, 0xC000, 0xFFFF}, + [CPCAP_REG_TXI] = {517, 0x0000, 0xFFFF}, + [CPCAP_REG_TXMP] = {518, 0xF000, 0xFFFF}, + [CPCAP_REG_RXOA] = {519, 0xF800, 0xFFFF}, + [CPCAP_REG_RXVC] = {520, 0x00C3, 0xFFFF}, + [CPCAP_REG_RXCOA] = {521, 0xF800, 0xFFFF}, + [CPCAP_REG_RXSDOA] = {522, 0xE000, 0xFFFF}, + [CPCAP_REG_RXEPOA] = {523, 0x8000, 0xFFFF}, + [CPCAP_REG_RXLL] = {524, 0x0000, 0xFFFF}, + [CPCAP_REG_A2LA] = {525, 0xFF00, 0xFFFF}, + [CPCAP_REG_MIPIS1] = {526, 0x0000, 0xFFFF}, + [CPCAP_REG_MIPIS2] = {527, 0xFF00, 0xFFFF}, + [CPCAP_REG_MIPIS3] = {528, 0xFFFC, 0xFFFF}, + [CPCAP_REG_LVAB] = {529, 0xFFFC, 0xFFFF}, + [CPCAP_REG_CCC1] = {640, 0xFFF0, 0xFFFF}, + [CPCAP_REG_CRM] = {641, 0xC000, 0xFFFF}, + [CPCAP_REG_CCCC2] = {642, 0xFFC0, 0xFFFF}, + [CPCAP_REG_CCS1] = {643, 0x0000, 0xFFFF}, + [CPCAP_REG_CCS2] = {644, 0xFF00, 0xFFFF}, + [CPCAP_REG_CCA1] = {645, 0x0000, 0xFFFF}, + [CPCAP_REG_CCA2] = {646, 0x0000, 0xFFFF}, + [CPCAP_REG_CCM] = {647, 0xFC00, 0xFFFF}, + [CPCAP_REG_CCO] = {648, 0xFC00, 0xFFFF}, + [CPCAP_REG_CCI] = {649, 0xC000, 0xFFFF}, + [CPCAP_REG_ADCC1] = {768, 0x0000, 0xFFFF}, + [CPCAP_REG_ADCC2] = {769, 0x0080, 0xFFFF}, + [CPCAP_REG_ADCD0] = {770, 0xFFFF, 0xFFFF}, + [CPCAP_REG_ADCD1] = {771, 0xFFFF, 0xFFFF}, + [CPCAP_REG_ADCD2] = {772, 0xFFFF, 0xFFFF}, + [CPCAP_REG_ADCD3] = {773, 0xFFFF, 0xFFFF}, + [CPCAP_REG_ADCD4] = {774, 0xFFFF, 0xFFFF}, + [CPCAP_REG_ADCD5] = {775, 0xFFFF, 0xFFFF}, + [CPCAP_REG_ADCD6] = {776, 0xFFFF, 0xFFFF}, + [CPCAP_REG_ADCD7] = {777, 0xFFFF, 0xFFFF}, + [CPCAP_REG_ADCAL1] = {778, 0xFFFF, 0xFFFF}, + [CPCAP_REG_ADCAL2] = {779, 0xFFFF, 0xFFFF}, + [CPCAP_REG_USBC1] = {896, 0x0000, 0xFFFF}, +#ifdef CONFIG_EMU_UART_DEBUG + [CPCAP_REG_USBC2] = {897, 0x0F07, 0xFFFF}, +#else + [CPCAP_REG_USBC2] = {897, 0x0000, 0xFFFF}, +#endif + [CPCAP_REG_USBC3] = {898, 0x8200, 0xFFFF}, + [CPCAP_REG_UVIDL] = {899, 0xFFFF, 0xFFFF}, + [CPCAP_REG_UVIDH] = {900, 0xFFFF, 0xFFFF}, + [CPCAP_REG_UPIDL] = {901, 0xFFFF, 0xFFFF}, + [CPCAP_REG_UPIDH] = {902, 0xFFFF, 0xFFFF}, + [CPCAP_REG_UFC1] = {903, 0xFF80, 0xFFFF}, + [CPCAP_REG_UFC2] = {904, 0xFF80, 0xFFFF}, + [CPCAP_REG_UFC3] = {905, 0xFF80, 0xFFFF}, + [CPCAP_REG_UIC1] = {906, 0xFF64, 0xFFFF}, + [CPCAP_REG_UIC2] = {907, 0xFF64, 0xFFFF}, + [CPCAP_REG_UIC3] = {908, 0xFF64, 0xFFFF}, + [CPCAP_REG_USBOTG1] = {909, 0xFFC0, 0xFFFF}, + [CPCAP_REG_USBOTG2] = {910, 0xFFC0, 0xFFFF}, + [CPCAP_REG_USBOTG3] = {911, 0xFFC0, 0xFFFF}, + [CPCAP_REG_UIER1] = {912, 0xFFE0, 0xFFFF}, + [CPCAP_REG_UIER2] = {913, 0xFFE0, 0xFFFF}, + [CPCAP_REG_UIER3] = {914, 0xFFE0, 0xFFFF}, + [CPCAP_REG_UIEF1] = {915, 0xFFE0, 0xFFFF}, + [CPCAP_REG_UIEF2] = {916, 0xFFE0, 0xFFFF}, + [CPCAP_REG_UIEF3] = {917, 0xFFE0, 0xFFFF}, + [CPCAP_REG_UIS] = {918, 0xFFFF, 0xFFFF}, + [CPCAP_REG_UIL] = {919, 0xFFFF, 0xFFFF}, + [CPCAP_REG_USBD] = {920, 0xFFFF, 0xFFFF}, + [CPCAP_REG_SCR1] = {921, 0xFF00, 0xFFFF}, + [CPCAP_REG_SCR2] = {922, 0xFF00, 0xFFFF}, + [CPCAP_REG_SCR3] = {923, 0xFF00, 0xFFFF}, + [CPCAP_REG_VMC] = {939, 0xFFFE, 0xFFFF}, + [CPCAP_REG_OWDC] = {940, 0xFFFC, 0xFFFF}, + [CPCAP_REG_GPIO0] = {941, 0x0D11, 0x3FFF}, + [CPCAP_REG_GPIO1] = {943, 0x0D11, 0x3FFF}, + [CPCAP_REG_GPIO2] = {945, 0x0D11, 0x3FFF}, + [CPCAP_REG_GPIO3] = {947, 0x0D11, 0x3FFF}, + [CPCAP_REG_GPIO4] = {949, 0x0D11, 0x3FFF}, + [CPCAP_REG_GPIO5] = {951, 0x0C11, 0x3FFF}, + [CPCAP_REG_GPIO6] = {953, 0x0C11, 0x3FFF}, + [CPCAP_REG_MDLC] = {1024, 0x0000, 0xFFFF}, + [CPCAP_REG_KLC] = {1025, 0x8000, 0xFFFF}, + [CPCAP_REG_ADLC] = {1026, 0x8000, 0xFFFF}, + [CPCAP_REG_REDC] = {1027, 0xFC00, 0xFFFF}, + [CPCAP_REG_GREENC] = {1028, 0xFC00, 0xFFFF}, + [CPCAP_REG_BLUEC] = {1029, 0xFC00, 0xFFFF}, + [CPCAP_REG_CFC] = {1030, 0xF000, 0xFFFF}, + [CPCAP_REG_ABC] = {1031, 0xFFC3, 0xFFFF}, + [CPCAP_REG_BLEDC] = {1032, 0xFC00, 0xFFFF}, + [CPCAP_REG_CLEDC] = {1033, 0xFC00, 0xFFFF}, + [CPCAP_REG_OW1C] = {1152, 0xFF00, 0xFFFF}, + [CPCAP_REG_OW1D] = {1153, 0xFF00, 0xFFFF}, + [CPCAP_REG_OW1I] = {1154, 0xFFFF, 0xFFFF}, + [CPCAP_REG_OW1IE] = {1155, 0xFF00, 0xFFFF}, + [CPCAP_REG_OW1] = {1157, 0xFF00, 0xFFFF}, + [CPCAP_REG_OW2C] = {1160, 0xFF00, 0xFFFF}, + [CPCAP_REG_OW2D] = {1161, 0xFF00, 0xFFFF}, + [CPCAP_REG_OW2I] = {1162, 0xFFFF, 0xFFFF}, + [CPCAP_REG_OW2IE] = {1163, 0xFF00, 0xFFFF}, + [CPCAP_REG_OW2] = {1165, 0xFF00, 0xFFFF}, + [CPCAP_REG_OW3C] = {1168, 0xFF00, 0xFFFF}, + [CPCAP_REG_OW3D] = {1169, 0xFF00, 0xFFFF}, + [CPCAP_REG_OW3I] = {1170, 0xFF00, 0xFFFF}, + [CPCAP_REG_OW3IE] = {1171, 0xFF00, 0xFFFF}, + [CPCAP_REG_OW3] = {1173, 0xFF00, 0xFFFF}, + [CPCAP_REG_GCAIC] = {1174, 0xFF00, 0xFFFF}, + [CPCAP_REG_GCAIM] = {1175, 0xFF00, 0xFFFF}, + [CPCAP_REG_LGDIR] = {1176, 0xFFE0, 0xFFFF}, + [CPCAP_REG_LGPU] = {1177, 0xFFE0, 0xFFFF}, + [CPCAP_REG_LGPIN] = {1178, 0xFF00, 0xFFFF}, + [CPCAP_REG_LGMASK] = {1179, 0xFFE0, 0xFFFF}, + [CPCAP_REG_LDEB] = {1180, 0xFF00, 0xFFFF}, + [CPCAP_REG_LGDET] = {1181, 0xFF00, 0xFFFF}, + [CPCAP_REG_LMISC] = {1182, 0xFF07, 0xFFFF}, + [CPCAP_REG_LMACE] = {1183, 0xFFF8, 0xFFFF}, + [CPCAP_REG_TEST] = {7936, 0x0000, 0xFFFF}, + [CPCAP_REG_ST_TEST1] = {8002, 0x0000, 0xFFFF}, +}; + +static int cpcap_spi_access(struct spi_device *spi, u8 *buf, + size_t len) +{ + struct spi_message m; + struct spi_transfer t = { + .tx_buf = buf, + .len = len, + .rx_buf = buf, + .bits_per_word = 32, + }; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + return spi_sync(spi, &m); +} + +static int cpcap_config_for_read(struct spi_device *spi, unsigned short reg, + unsigned short *data) +{ + int status = -ENOTTY; + u32 buf32; /* force buf to be 32bit aligned */ + u8 *buf = (u8 *) &buf32; + + if (spi != NULL) { + buf[3] = (reg >> 6) & 0x000000FF; + buf[2] = (reg << 2) & 0x000000FF; + buf[1] = 0; + buf[0] = 0; + + status = cpcap_spi_access(spi, buf, 4); + + if (status == 0) + *data = buf[0] | (buf[1] << 8); + } + + return status; +} + +static int cpcap_config_for_write(struct spi_device *spi, unsigned short reg, + unsigned short data) +{ + int status = -ENOTTY; + u32 buf32; /* force buf to be 32bit aligned */ + u8 *buf = (u8 *) &buf32; + + if (spi != NULL) { + buf[3] = ((reg >> 6) & 0x000000FF) | 0x80; + buf[2] = (reg << 2) & 0x000000FF; + buf[1] = (data >> 8) & 0x000000FF; + buf[0] = data & 0x000000FF; + + status = cpcap_spi_access(spi, buf, 4); + } + + return status; +} + +int cpcap_regacc_read(struct cpcap_device *cpcap, enum cpcap_reg reg, + unsigned short *value_ptr) +{ + int retval = -EINVAL; + struct spi_device *spi = cpcap->spi; + + if (IS_CPCAP(reg) && (value_ptr != 0)) { + mutex_lock(®_access); + + retval = cpcap_config_for_read(spi, register_info_tbl + [reg].address, value_ptr); + + mutex_unlock(®_access); + } + + return retval; +} + +int cpcap_regacc_write(struct cpcap_device *cpcap, + enum cpcap_reg reg, + unsigned short value, + unsigned short mask) +{ + int retval = -EINVAL; + unsigned short old_value = 0; + struct cpcap_platform_data *data; + struct spi_device *spi = cpcap->spi; + + data = (struct cpcap_platform_data *)spi->controller_data; + + if (IS_CPCAP(reg) && + (mask & register_info_tbl[reg].constant_mask) == 0) { + mutex_lock(®_access); + + value &= mask; + + if ((register_info_tbl[reg].rbw_mask) != 0) { + retval = cpcap_config_for_read(spi, register_info_tbl + [reg].address, + &old_value); + if (retval != 0) + goto error; + } + + old_value &= register_info_tbl[reg].rbw_mask; + old_value &= ~mask; + value |= old_value; + retval = cpcap_config_for_write(spi, + register_info_tbl[reg].address, + value); +error: + mutex_unlock(®_access); + } + + return retval; +} + +int cpcap_regacc_init(struct cpcap_device *cpcap) +{ + unsigned short i; + unsigned short mask; + int retval = 0; + struct cpcap_platform_data *data; + struct spi_device *spi = cpcap->spi; + + data = (struct cpcap_platform_data *)spi->controller_data; + + for (i = 0; i < data->init_len; i++) { + mask = 0xFFFF; + mask &= ~(register_info_tbl[data->init[i].reg].constant_mask); + + retval = cpcap_regacc_write(cpcap, data->init[i].reg, + data->init[i].data, + mask); + if (retval) + break; + } + + return retval; +} diff --git a/drivers/mfd/cpcap-uc.c b/drivers/mfd/cpcap-uc.c new file mode 100644 index 00000000000..2626bfcb5f7 --- /dev/null +++ b/drivers/mfd/cpcap-uc.c @@ -0,0 +1,927 @@ +/* + * Copyright (C) 2008-2010 Motorola, 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 <linux/completion.h> +#include <linux/errno.h> +#include <linux/firmware.h> +#include <linux/fs.h> +#include <linux/ihex.h> +#include <linux/miscdevice.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/uaccess.h> +#include <linux/module.h> + +#include <linux/spi/cpcap.h> +#include <linux/spi/cpcap-regbits.h> +#include <linux/spi/spi.h> + +#define ERROR_MACRO_TIMEOUT 0x81 +#define ERROR_MACRO_WRITE 0x82 +#define ERROR_MACRO_READ 0x83 + +#define RAM_START_TI 0x9000 +#define RAM_END_TI 0x9FA0 +#define RAM_START_ST 0x0000 +#define RAM_END_ST 0x0FFF + +#define HWCFG_ADDR_ST 0x0148 +#define HWCFG_ADDR_TI 0x90F4 /* Not yet implemented in the TI uC. */ + +enum { + READ_STATE_1, /* Send size and location of RAM read. */ + READ_STATE_2, /*!< Read MT registers. */ + READ_STATE_3, /*!< Read data from uC. */ + READ_STATE_4, /*!< Check for error. */ +}; + +enum { + WRITE_STATE_1, /* Send size and location of RAM write. */ + WRITE_STATE_2, /* Check for error. */ + WRITE_STATE_3, /* Write data to uC. */ + WRITE_STATE_4 /* Check for error. */ +}; + +struct cpcap_uc_data { + struct cpcap_device *cpcap; + unsigned char is_supported; + unsigned char is_ready; + struct completion completion; + int cb_status; + struct mutex lock; + unsigned char uc_reset; + unsigned char state; + unsigned short state_cntr; + struct { + unsigned short address; + unsigned short *data; + unsigned short num_words; + } req; +}; + +static struct cpcap_uc_data *cpcap_uc_info; + +static int fops_open(struct inode *inode, struct file *file); +static long fops_ioctl(struct file *file, unsigned int cmd, unsigned long arg); +static ssize_t fops_write(struct file *file, const char *buf, + size_t count, loff_t *ppos); +static ssize_t fops_read(struct file *file, char *buf, + size_t count, loff_t *ppos); + + +static const struct file_operations fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = fops_ioctl, + .open = fops_open, + .read = fops_read, + .write = fops_write, +}; + +static struct miscdevice uc_dev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "cpcap_uc", + .fops = &fops, +}; + +static int is_valid_address(struct cpcap_device *cpcap, unsigned short address, + unsigned short num_words) +{ + int vld = 0; + + if (cpcap->vendor == CPCAP_VENDOR_TI) { + vld = (address >= RAM_START_TI) && + ((address + num_words) <= RAM_END_TI); + } else if (cpcap->vendor == CPCAP_VENDOR_ST) { + vld = ((address + num_words) <= RAM_END_ST); + } + + return vld; +} + +static void ram_read_state_machine(enum cpcap_irqs irq, void *data) +{ + struct cpcap_uc_data *uc_data = data; + unsigned short temp; + + if (irq != CPCAP_IRQ_UC_PRIRAMR) + return; + + switch (uc_data->state) { + case READ_STATE_1: + cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT1, + uc_data->req.address, 0xFFFF); + cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT2, + uc_data->req.num_words, 0xFFFF); + cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT3, 0, 0xFFFF); + + if (uc_data->cpcap->vendor == CPCAP_VENDOR_ST) + uc_data->state = READ_STATE_2; + else + uc_data->state = READ_STATE_3; + + cpcap_irq_unmask(uc_data->cpcap, CPCAP_IRQ_UC_PRIRAMR); + + break; + + case READ_STATE_2: + cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT1, &temp); + + if (temp == ERROR_MACRO_READ) { + uc_data->state = READ_STATE_1; + uc_data->state_cntr = 0; + + cpcap_irq_mask(uc_data->cpcap, CPCAP_IRQ_UC_PRIRAMR); + + uc_data->cb_status = -EIO; + + complete(&uc_data->completion); + } else { + cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT2, &temp); + cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT3, &temp); + + uc_data->state = READ_STATE_3; + cpcap_irq_unmask(uc_data->cpcap, CPCAP_IRQ_UC_PRIRAMR); + } + break; + + case READ_STATE_3: + cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT1, + uc_data->req.data + uc_data->state_cntr); + + uc_data->state_cntr += 1; + + if (uc_data->state_cntr == uc_data->req.num_words) + cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT2, &temp); + else { + cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT2, + uc_data->req.data + + uc_data->state_cntr); + + uc_data->state_cntr += 1; + } + + if (uc_data->state_cntr == uc_data->req.num_words) + cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT3, &temp); + else { + cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT3, + uc_data->req.data + + uc_data->state_cntr); + + uc_data->state_cntr += 1; + } + + if (uc_data->state_cntr == uc_data->req.num_words) + uc_data->state = READ_STATE_4; + + cpcap_irq_unmask(uc_data->cpcap, CPCAP_IRQ_UC_PRIRAMR); + break; + + case READ_STATE_4: + cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT1, &temp); + + if (temp != ERROR_MACRO_READ) + uc_data->cb_status = 0; + else + uc_data->cb_status = -EIO; + + complete(&uc_data->completion); + + uc_data->state = READ_STATE_1; + uc_data->state_cntr = 0; + break; + + default: + uc_data->state = READ_STATE_1; + uc_data->state_cntr = 0; + break; + } +} + +static void ram_write_state_machine(enum cpcap_irqs irq, void *data) +{ + struct cpcap_uc_data *uc_data = data; + unsigned short error_check; + + if (irq != CPCAP_IRQ_UC_PRIRAMW) + return; + + switch (uc_data->state) { + case WRITE_STATE_1: + cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT1, + uc_data->req.address, 0xFFFF); + cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT2, + uc_data->req.num_words, 0xFFFF); + cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT3, 0, 0xFFFF); + + uc_data->state = WRITE_STATE_2; + cpcap_irq_unmask(uc_data->cpcap, CPCAP_IRQ_UC_PRIRAMW); + break; + + case WRITE_STATE_2: + cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT1, &error_check); + + if (error_check == ERROR_MACRO_WRITE) { + uc_data->state = WRITE_STATE_1; + uc_data->state_cntr = 0; + + cpcap_irq_mask(uc_data->cpcap, + CPCAP_IRQ_UC_PRIRAMW); + + uc_data->cb_status = -EIO; + complete(&uc_data->completion); + break; + } else + uc_data->state = WRITE_STATE_3; + + /* No error has occured, fall through */ + + case WRITE_STATE_3: + cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT1, + *(uc_data->req.data + uc_data->state_cntr), + 0xFFFF); + uc_data->state_cntr += 1; + + if (uc_data->state_cntr == uc_data->req.num_words) + cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT2, 0, + 0xFFFF); + else { + cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT2, + *(uc_data->req.data + + uc_data->state_cntr), 0xFFFF); + + uc_data->state_cntr += 1; + } + + if (uc_data->state_cntr == uc_data->req.num_words) + cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT3, 0, + 0xFFFF); + else { + cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MT3, + *(uc_data->req.data + + uc_data->state_cntr), 0xFFFF); + + uc_data->state_cntr += 1; + } + + if (uc_data->state_cntr == uc_data->req.num_words) + uc_data->state = WRITE_STATE_4; + + cpcap_irq_unmask(uc_data->cpcap, CPCAP_IRQ_UC_PRIRAMW); + break; + + case WRITE_STATE_4: + cpcap_regacc_read(uc_data->cpcap, CPCAP_REG_MT1, &error_check); + + if (error_check != ERROR_MACRO_WRITE) + uc_data->cb_status = 0; + else + uc_data->cb_status = -EIO; + + complete(&uc_data->completion); + + uc_data->state = WRITE_STATE_1; + uc_data->state_cntr = 0; + break; + + default: + uc_data->state = WRITE_STATE_1; + uc_data->state_cntr = 0; + break; + } +} + +static void reset_handler(enum cpcap_irqs irq, void *data) +{ + int i; + unsigned short regval; + struct cpcap_uc_data *uc_data = data; + + if (irq != CPCAP_IRQ_UCRESET) + return; + + cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_UCC1, + CPCAP_BIT_PRIHALT, CPCAP_BIT_PRIHALT); + + cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_PGC, + CPCAP_BIT_PRI_UC_SUSPEND, CPCAP_BIT_PRI_UC_SUSPEND); + + uc_data->uc_reset = 1; + uc_data->cb_status = -EIO; + complete(&uc_data->completion); + + cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MI2, 0, 0xFFFF); + cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MIM1, 0xFFFF, 0xFFFF); + cpcap_irq_mask(uc_data->cpcap, CPCAP_IRQ_PRIMAC); + cpcap_irq_unmask(uc_data->cpcap, CPCAP_IRQ_UCRESET); + + for (i = 0; i <= CPCAP_REG_END; i++) { + cpcap_regacc_read(uc_data->cpcap, i, ®val); + dev_err(&uc_data->cpcap->spi->dev, + "cpcap reg %d = 0x%04X\n", i, regval); + } + + BUG(); +} + +static void primac_handler(enum cpcap_irqs irq, void *data) +{ + struct cpcap_uc_data *uc_data = data; + + if (irq == CPCAP_IRQ_PRIMAC) + cpcap_irq_unmask(uc_data->cpcap, CPCAP_IRQ_PRIMAC); +} + +static int ram_write(struct cpcap_uc_data *uc_data, unsigned short address, + unsigned short num_words, unsigned short *data) +{ + int retval = -EFAULT; + + mutex_lock(&uc_data->lock); + + if ((uc_data->cpcap->vendor == CPCAP_VENDOR_ST) && + (uc_data->cpcap->revision <= CPCAP_REVISION_2_0)) { + cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_UCTM, + CPCAP_BIT_UCTM, CPCAP_BIT_UCTM); + } + + if (uc_data->is_supported && (num_words > 0) && + (data != NULL) && + is_valid_address(uc_data->cpcap, address, num_words) && + !uc_data->uc_reset) { + uc_data->req.address = address; + uc_data->req.data = data; + uc_data->req.num_words = num_words; + uc_data->state = WRITE_STATE_1; + uc_data->state_cntr = 0; + INIT_COMPLETION(uc_data->completion); + + retval = cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MI2, + CPCAP_BIT_PRIRAMW, + CPCAP_BIT_PRIRAMW); + if (retval) + goto err; + + /* Cannot call cpcap_irq_register() here because unregister + * cannot be called from the state machine. Doing so causes + * a deadlock. */ + retval = cpcap_irq_unmask(uc_data->cpcap, CPCAP_IRQ_UC_PRIRAMW); + if (retval) + goto err; + + wait_for_completion(&uc_data->completion); + retval = uc_data->cb_status; + } + +err: + if ((uc_data->cpcap->vendor == CPCAP_VENDOR_ST) && + (uc_data->cpcap->revision <= CPCAP_REVISION_2_0)) { + cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_UCTM, + 0, CPCAP_BIT_UCTM); + } + + mutex_unlock(&uc_data->lock); + + return retval; +} + +static int ram_read(struct cpcap_uc_data *uc_data, unsigned short address, + unsigned short num_words, unsigned short *data) +{ + int retval = -EFAULT; + + mutex_lock(&uc_data->lock); + + if ((uc_data->cpcap->vendor == CPCAP_VENDOR_ST) && + (uc_data->cpcap->revision <= CPCAP_REVISION_2_0)) { + cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_UCTM, + CPCAP_BIT_UCTM, CPCAP_BIT_UCTM); + } + + if (uc_data->is_supported && (num_words > 0) && + is_valid_address(uc_data->cpcap, address, num_words) && + !uc_data->uc_reset) { + uc_data->req.address = address; + uc_data->req.data = data; + uc_data->req.num_words = num_words; + uc_data->state = READ_STATE_1; + uc_data->state_cntr = 0; + INIT_COMPLETION(uc_data->completion); + + retval = cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_MI2, + CPCAP_BIT_PRIRAMR, + CPCAP_BIT_PRIRAMR); + if (retval) + goto err; + + /* Cannot call cpcap_irq_register() here because unregister + * cannot be called from the state machine. Doing so causes + * a deadlock. */ + retval = cpcap_irq_unmask(uc_data->cpcap, CPCAP_IRQ_UC_PRIRAMR); + if (retval) + goto err; + + wait_for_completion(&uc_data->completion); + retval = uc_data->cb_status; + } + +err: + if ((uc_data->cpcap->vendor == CPCAP_VENDOR_ST) && + (uc_data->cpcap->revision <= CPCAP_REVISION_2_0)) { + cpcap_regacc_write(uc_data->cpcap, CPCAP_REG_UCTM, + 0, CPCAP_BIT_UCTM); + } + + mutex_unlock(&uc_data->lock); + + return retval; +} + +static int ram_load(struct cpcap_uc_data *uc_data, unsigned int num_words, + unsigned short *data) +{ + int retval = -EINVAL; + + if ((data != NULL) && (num_words > 0)) + retval = ram_write(uc_data, data[0], (num_words - 1), + (data + 1)); + + return retval; +} + +static ssize_t fops_write(struct file *file, const char *buf, + size_t count, loff_t *ppos) +{ + ssize_t retval = -EINVAL; + unsigned short address; + unsigned short num_words; + unsigned short *data; + struct cpcap_uc_data *uc_data = file->private_data; + + if ((buf != NULL) && (ppos != NULL) && (count >= 2)) { + data = kzalloc(count, GFP_KERNEL); + + if (data != NULL) { + num_words = (unsigned short) (count >> 1); + + /* If the position (uC RAM address) is zero then the + * data contains the address */ + if (*ppos == 0) { + if (copy_from_user((void *) data, (void *) buf, + count) == 0) + retval = ram_load(uc_data, num_words, + data); + else + retval = -EFAULT; + } + /* If the position (uC RAM address) is not zero then the + * position holds the address to load the data */ + else { + address = (unsigned short) (*ppos); + + if (copy_from_user((void *) data, (void *) buf, + count) == 0) + retval = ram_write(uc_data, address, + num_words, data); + else + retval = -EFAULT; + } + + kfree(data); + } else { + retval = -ENOMEM; + } + } + + if (retval == 0) + retval = num_words; + + return retval; +} + +static ssize_t fops_read(struct file *file, char *buf, + size_t count, loff_t *ppos) +{ + ssize_t retval = -EFAULT; + unsigned short address; + unsigned short num_words; + unsigned short *data; + struct cpcap_uc_data *uc_data = file->private_data; + + if ((buf != NULL) && (ppos != NULL) && (count >= 2)) { + data = kzalloc(count, GFP_KERNEL); + + if (data != NULL) { + address = (unsigned short) (*ppos); + num_words = (unsigned short) (count >> 1); + + retval = ram_read(uc_data, address, num_words, data); + + if (retval) + goto err; + + if (copy_to_user((void *)buf, (void *)data, count) == 0) + retval = count; + else + retval = -EFAULT; + +err: + kfree(data); + } else { + retval = -ENOMEM; + } + } + + return retval; +} + +static long fops_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int retval = -ENOTTY; + struct cpcap_uc_data *data = file->private_data; + + switch (cmd) { + case CPCAP_IOCTL_UC_MACRO_START: + /* User space will only attempt to start the init macro if + * the ram load requests complete successfully. This is used + * as an indication that kernel requests to start macros can + * be allowed. + */ + data->is_ready = 1; + + retval = cpcap_uc_start(data->cpcap, (enum cpcap_macro)arg); + + break; + + case CPCAP_IOCTL_UC_MACRO_STOP: + retval = cpcap_uc_stop(data->cpcap, (enum cpcap_macro)arg); + break; + + case CPCAP_IOCTL_UC_GET_VENDOR: + retval = copy_to_user((enum cpcap_vendor *)arg, + &(data->cpcap->vendor), + sizeof(enum cpcap_vendor)); + break; + + case CPCAP_IOCTL_UC_SET_TURBO_MODE: + if (arg != 0) + arg = 1; + retval = cpcap_regacc_write(data->cpcap, CPCAP_REG_UCTM, + (unsigned short)arg, + CPCAP_BIT_UCTM); + break; + + default: + break; + } + + return retval; +} + +static int fops_open(struct inode *inode, struct file *file) +{ + int retval = -ENOTTY; + + if (cpcap_uc_info->is_supported) + retval = 0; + + file->private_data = cpcap_uc_info; + dev_info(&cpcap_uc_info->cpcap->spi->dev, "CPCAP uC: open status:%d\n", + retval); + + return retval; +} + +int cpcap_uc_start(struct cpcap_device *cpcap, enum cpcap_macro macro) +{ + int retval = -EFAULT; + struct cpcap_uc_data *data = cpcap->ucdata; + + if ((data->is_ready) && + (macro > CPCAP_MACRO_USEROFF) && (macro < CPCAP_MACRO__END) && + (data->uc_reset == 0)) { + if ((macro == CPCAP_MACRO_4) || + ((cpcap->vendor == CPCAP_VENDOR_ST) && + (macro == CPCAP_MACRO_12)) || + ((cpcap->vendor == CPCAP_VENDOR_ST) && + (macro == CPCAP_MACRO_15))) { + retval = cpcap_regacc_write(cpcap, CPCAP_REG_MI2, + (1 << macro), + (1 << macro)); + } else { + retval = cpcap_regacc_write(cpcap, CPCAP_REG_MIM1, + 0, (1 << macro)); + } + } + + return retval; +} +EXPORT_SYMBOL_GPL(cpcap_uc_start); + +int cpcap_uc_stop(struct cpcap_device *cpcap, enum cpcap_macro macro) +{ + int retval = -EFAULT; + + if ((macro > CPCAP_MACRO_4) && + (macro < CPCAP_MACRO__END)) { + if ((cpcap->vendor == CPCAP_VENDOR_ST) && + (macro == CPCAP_MACRO_12)) { + retval = cpcap_regacc_write(cpcap, CPCAP_REG_MI2, + 0, (1 << macro)); + } else { + retval = cpcap_regacc_write(cpcap, CPCAP_REG_MIM1, + (1 << macro), (1 << macro)); + } + } + + return retval; +} +EXPORT_SYMBOL_GPL(cpcap_uc_stop); + +unsigned char cpcap_uc_status(struct cpcap_device *cpcap, + enum cpcap_macro macro) +{ + unsigned char retval = 0; + unsigned short regval; + + if (macro < CPCAP_MACRO__END) { + if ((macro <= CPCAP_MACRO_4) || + ((cpcap->vendor == CPCAP_VENDOR_ST) && + (macro == CPCAP_MACRO_12))) { + cpcap_regacc_read(cpcap, CPCAP_REG_MI2, ®val); + + if (regval & (1 << macro)) + retval = 1; + } else { + cpcap_regacc_read(cpcap, CPCAP_REG_MIM1, ®val); + + if (!(regval & (1 << macro))) + retval = 1; + } + } + + return retval; +} +EXPORT_SYMBOL_GPL(cpcap_uc_status); + +#ifdef CONFIG_PM_DBG_DRV +int cpcap_uc_ram_write(struct cpcap_device *cpcap, unsigned short address, + unsigned short num_words, unsigned short *data) +{ + return ram_write(cpcap->ucdata, address, num_words, data); +} + +int cpcap_uc_ram_read(struct cpcap_device *cpcap, unsigned short address, + unsigned short num_words, unsigned short *data) +{ + return ram_read(cpcap->ucdata, address, num_words, data); +} +#endif /* CONFIG_PM_DBG_DRV */ + +static int fw_load(struct cpcap_uc_data *uc_data, struct device *dev) +{ + int err; + const struct ihex_binrec *rec; + const struct firmware *fw; + unsigned short *buf; + int i; + unsigned short num_bytes; + unsigned short num_words; + unsigned char odd_bytes; + struct cpcap_platform_data *data; + + data = uc_data->cpcap->spi->controller_data; + + if (!uc_data || !dev) + return -EINVAL; + + if (uc_data->cpcap->vendor == CPCAP_VENDOR_ST) + err = request_ihex_firmware(&fw, "cpcap/firmware_0_2x.fw", dev); + else + err = request_ihex_firmware(&fw, "cpcap/firmware_1_2x.fw", dev); + + if (err) { + dev_err(dev, "Failed to load \"cpcap/firmware_%d_2x.fw\": %d\n", + uc_data->cpcap->vendor, err); + goto err; + } + + for (rec = (void *)fw->data; rec; rec = ihex_next_binrec(rec)) { + odd_bytes = 0; + num_bytes = be16_to_cpu(rec->len); + + /* Since loader requires words, need even number of bytes. */ + if (be16_to_cpu(rec->len) % 2) { + num_bytes++; + odd_bytes = 1; + } + + num_words = num_bytes >> 1; + dev_dbg(dev, "Loading %d word(s) at 0x%04x\n", + num_words, be32_to_cpu(rec->addr)); + + buf = kzalloc(num_bytes, GFP_KERNEL); + if (buf) { + for (i = 0; i < num_words; i++) { + if (odd_bytes && (i == (num_words - 1))) + buf[i] = rec->data[i * 2]; + else + buf[i] = ((uint16_t *)rec->data)[i]; + + buf[i] = be16_to_cpu(buf[i]); + } + + err = ram_write(uc_data, be32_to_cpu(rec->addr), + num_words, buf); + kfree(buf); + + if (err) { + dev_err(dev, "RAM write failed: %d\n", err); + break; + } + } else { + err = -ENOMEM; + dev_err(dev, "RAM write failed: %d\n", err); + break; + } + } + + release_firmware(fw); + + if (!err) { + uc_data->is_ready = 1; + + if (uc_data->cpcap->vendor == CPCAP_VENDOR_ST) + err = ram_write(uc_data, 0x012C, 1, &(data->is_umts)); + else + err = ram_write(uc_data, 0x90F0, 1, &(data->is_umts)); + + dev_info(dev, "Loaded Sec SPI Init = %d: %d\n", + data->is_umts, err); + + if (uc_data->cpcap->vendor == CPCAP_VENDOR_ST) + err = ram_write(uc_data, HWCFG_ADDR_ST, + CPCAP_HWCFG_NUM, data->hwcfg); + else + err = ram_write(uc_data, HWCFG_ADDR_TI, + CPCAP_HWCFG_NUM, data->hwcfg); + + dev_info(dev, "Loaded HWCFG data:"); + for (i = 0; i < CPCAP_HWCFG_NUM; i++) + dev_info(dev, " 0x%04x", data->hwcfg[i]); + dev_info(dev, "result: %d\n", err); + + err = cpcap_uc_start(uc_data->cpcap, CPCAP_MACRO_4); + dev_info(dev, "Started macro 4: %d\n", err); + + err = cpcap_uc_start(uc_data->cpcap, CPCAP_MACRO_15); + dev_info(dev, "Started macro 15: %d\n", err); + } + +err: + return err; +} + +static int cpcap_uc_probe(struct platform_device *pdev) +{ + int retval = 0; + struct cpcap_uc_data *data; + + if (pdev->dev.platform_data == NULL) { + dev_err(&pdev->dev, "no platform_data\n"); + return -EINVAL; + } + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->cpcap = pdev->dev.platform_data; + data->uc_reset = 0; + data->is_supported = 0; + data->req.address = 0; + data->req.data = NULL; + data->req.num_words = 0; + + init_completion(&data->completion); + mutex_init(&data->lock); + platform_set_drvdata(pdev, data); + cpcap_uc_info = data; + data->cpcap->ucdata = data; + + if (((data->cpcap->vendor == CPCAP_VENDOR_TI) && + (data->cpcap->revision >= CPCAP_REVISION_2_0)) || + (data->cpcap->vendor == CPCAP_VENDOR_ST)) { + retval = cpcap_irq_register(data->cpcap, CPCAP_IRQ_PRIMAC, + primac_handler, data); + if (retval) + goto err_free; + + cpcap_irq_clear(data->cpcap, CPCAP_IRQ_UCRESET); + retval = cpcap_irq_register(data->cpcap, CPCAP_IRQ_UCRESET, + reset_handler, data); + if (retval) + goto err_primac; + + retval = cpcap_irq_register(data->cpcap, + CPCAP_IRQ_UC_PRIRAMR, + ram_read_state_machine, data); + if (retval) + goto err_ucreset; + + retval = cpcap_irq_register(data->cpcap, + CPCAP_IRQ_UC_PRIRAMW, + ram_write_state_machine, data); + if (retval) + goto err_priramr; + + retval = misc_register(&uc_dev); + if (retval) + goto err_priramw; + + data->is_supported = 1; + + cpcap_regacc_write(data->cpcap, CPCAP_REG_MIM1, 0xFFFF, + 0xFFFF); + + retval = fw_load(data, &pdev->dev); + if (retval) + goto err_fw; + } else + retval = -ENODEV; + + return retval; + +err_fw: + misc_deregister(&uc_dev); +err_priramw: + cpcap_irq_free(data->cpcap, CPCAP_IRQ_UC_PRIRAMW); +err_priramr: + cpcap_irq_free(data->cpcap, CPCAP_IRQ_UC_PRIRAMR); +err_ucreset: + cpcap_irq_free(data->cpcap, CPCAP_IRQ_UCRESET); +err_primac: + cpcap_irq_free(data->cpcap, CPCAP_IRQ_PRIMAC); +err_free: + kfree(data); + + return retval; +} + +static int __exit cpcap_uc_remove(struct platform_device *pdev) +{ + struct cpcap_uc_data *data = platform_get_drvdata(pdev); + + misc_deregister(&uc_dev); + + cpcap_irq_free(data->cpcap, CPCAP_IRQ_PRIMAC); + cpcap_irq_free(data->cpcap, CPCAP_IRQ_UC_PRIRAMW); + cpcap_irq_free(data->cpcap, CPCAP_IRQ_UC_PRIRAMR); + cpcap_irq_free(data->cpcap, CPCAP_IRQ_UCRESET); + + kfree(data); + return 0; +} + + +static struct platform_driver cpcap_uc_driver = { + .probe = cpcap_uc_probe, + .remove = __exit_p(cpcap_uc_remove), + .driver = { + .name = "cpcap_uc", + .owner = THIS_MODULE, + }, +}; + +static int __init cpcap_uc_init(void) +{ + return platform_driver_register(&cpcap_uc_driver); +} +subsys_initcall(cpcap_uc_init); + +static void __exit cpcap_uc_exit(void) +{ + platform_driver_unregister(&cpcap_uc_driver); +} +module_exit(cpcap_uc_exit); + +MODULE_ALIAS("platform:cpcap_uc"); +MODULE_DESCRIPTION("CPCAP uC driver"); +MODULE_AUTHOR("Motorola"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE("cpcap/firmware_0_2x.fw"); +MODULE_FIRMWARE("cpcap/firmware_1_2x.fw"); diff --git a/drivers/mfd/cpcap-usb-det.c b/drivers/mfd/cpcap-usb-det.c new file mode 100644 index 00000000000..d594b123098 --- /dev/null +++ b/drivers/mfd/cpcap-usb-det.c @@ -0,0 +1,948 @@ +/* + * Copyright (C) 2007 - 2010 Motorola, 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 <linux/errno.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/wakelock.h> +#include <linux/workqueue.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/ktime.h> +#include <linux/gpio.h> + +#include <linux/regulator/consumer.h> + +#include <linux/spi/cpcap.h> +#include <linux/spi/cpcap-regbits.h> +#include <linux/spi/spi.h> + +#define MS_TO_NS(x) ((x) * NSEC_PER_MSEC) +#define CPCAP_SENSE4_LS 8 +#define CPCAP_BIT_DP_S_LS (CPCAP_BIT_DP_S << CPCAP_SENSE4_LS) +#define CPCAP_BIT_DM_S_LS (CPCAP_BIT_DM_S << CPCAP_SENSE4_LS) + +#define SENSE_USB (CPCAP_BIT_ID_FLOAT_S | \ + CPCAP_BIT_CHRGCURR1_S | \ + CPCAP_BIT_VBUSVLD_S | \ + CPCAP_BIT_SESSVLD_S) + +#define SENSE_2WIRE (CPCAP_BIT_ID_FLOAT_S | \ + CPCAP_BIT_CHRGCURR1_S | \ + CPCAP_BIT_VBUSVLD_S | \ + CPCAP_BIT_SESSVLD_S | \ + CPCAP_BIT_DP_S_LS) + +#define SENSE_USB_FLASH (CPCAP_BIT_CHRGCURR1_S | \ + CPCAP_BIT_VBUSVLD_S | \ + CPCAP_BIT_SESSVLD_S) + +#define SENSE_FACTORY (CPCAP_BIT_ID_FLOAT_S | \ + CPCAP_BIT_ID_GROUND_S | \ + CPCAP_BIT_CHRGCURR1_S | \ + CPCAP_BIT_VBUSVLD_S | \ + CPCAP_BIT_SESSVLD_S) + +/* This Sense mask is needed because on TI the CHRGCURR1 interrupt is not always + * set. In Factory Mode the comparator follows the Charge current only. */ +#define SENSE_FACTORY_COM (CPCAP_BIT_ID_FLOAT_S | \ + CPCAP_BIT_ID_GROUND_S | \ + CPCAP_BIT_VBUSVLD_S | \ + CPCAP_BIT_SESSVLD_S) + +#define SENSE_CHARGER_FLOAT (CPCAP_BIT_ID_FLOAT_S | \ + CPCAP_BIT_CHRGCURR1_S | \ + CPCAP_BIT_VBUSVLD_S | \ + CPCAP_BIT_SESSVLD_S | \ + CPCAP_BIT_SE1_S | \ + CPCAP_BIT_DM_S_LS | \ + CPCAP_BIT_DP_S_LS) + +#define SENSE_CHARGER (CPCAP_BIT_CHRGCURR1_S | \ + CPCAP_BIT_VBUSVLD_S | \ + CPCAP_BIT_SESSVLD_S | \ + CPCAP_BIT_SE1_S | \ + CPCAP_BIT_DM_S_LS | \ + CPCAP_BIT_DP_S_LS) + +#define SENSE_IDLOW_CHARGER (CPCAP_BIT_CHRGCURR1_S | \ + CPCAP_BIT_VBUSVLD_S | \ + CPCAP_BIT_SESSVLD_S | \ + CPCAP_BIT_ID_GROUND_S | \ + CPCAP_BIT_DP_S_LS) + +#define SENSE_CHARGER_MASK (CPCAP_BIT_ID_GROUND_S | \ + CPCAP_BIT_SESSVLD_S) + +#ifdef CONFIG_CHARGER_CPCAP_2WIRE +#define TWOWIRE_HANDSHAKE_LEN 6 /* Number of bytes in handshake sequence */ +#define TWOWIRE_DELAY MS_TO_NS(10) /* delay between edges in ns */ +#define BI2BY 8 /* bits per byte */ +#define TWOWIRE_HANDSHAKE_SEQUENCE {0x07, 0xC1, 0xF3, 0xE7, 0xCF, 0x9F} +#endif + +#define UNDETECT_TRIES 5 + +#define CPCAP_USB_DET_PRINT_STATUS (1U << 0) +#define CPCAP_USB_DET_PRINT_TRANSITION (1U << 1) +static int cpcap_usb_det_debug_mask; + +module_param_named(cpcap_usb_det_debug_mask, cpcap_usb_det_debug_mask, int, + S_IRUGO | S_IWUSR | S_IWGRP); + +#define cpcap_usb_det_debug(debug_level_mask, args...) \ + do { \ + if (cpcap_usb_det_debug_mask & \ + CPCAP_USB_DET_PRINT_##debug_level_mask) { \ + pr_info(args); \ + } \ + } while (0) + +enum cpcap_det_state { + CONFIG, + SAMPLE_1, + SAMPLE_2, + IDENTIFY, + USB, + FACTORY, +#ifdef CONFIG_CHARGER_CPCAP_2WIRE + START2WIRE, + FINISH2WIRE, +#endif +}; + +enum cpcap_accy { + CPCAP_ACCY_USB, + CPCAP_ACCY_FACTORY, + CPCAP_ACCY_CHARGER, + CPCAP_ACCY_NONE, +#ifdef CONFIG_CHARGER_CPCAP_2WIRE + CPCAP_ACCY_2WIRE, +#endif + /* Used while debouncing the accessory. */ + CPCAP_ACCY_UNKNOWN, +}; + +#ifdef CONFIG_CHARGER_CPCAP_2WIRE +enum cpcap_twowire_state { + CPCAP_TWOWIRE_RUNNING, + CPCAP_TWOWIRE_DONE, +}; + +struct cpcap_usb_det_2wire { + int gpio; + unsigned short pos; + unsigned char data[TWOWIRE_HANDSHAKE_LEN]; + enum cpcap_twowire_state state; +}; +#endif + +struct cpcap_usb_det_data { + struct cpcap_device *cpcap; + struct delayed_work work; + unsigned short sense; + unsigned short prev_sense; + enum cpcap_det_state state; + enum cpcap_accy usb_accy; + struct platform_device *usb_dev; + struct platform_device *usb_connected_dev; + struct platform_device *charger_connected_dev; + struct regulator *regulator; + struct wake_lock wake_lock; + unsigned char is_vusb_enabled; + unsigned char undetect_cnt; +#ifdef CONFIG_CHARGER_CPCAP_2WIRE + struct hrtimer hr_timer; + struct cpcap_usb_det_2wire twowire_data; +#endif +}; + +static unsigned char vbus_valid_adc_check(struct cpcap_usb_det_data *data); + +static const char *accy_devices[] = { + "cpcap_usb_charger", + "cpcap_factory", + "cpcap_charger", +}; + +#ifdef CONFIG_USB_TESTING_POWER +static int testing_power_enable = -1; +module_param(testing_power_enable, int, 0644); +MODULE_PARM_DESC(testing_power_enable, "Enable factory cable power " + "supply function for testing"); +#endif + +static void vusb_enable(struct cpcap_usb_det_data *data) +{ + int ret; + if (!data->is_vusb_enabled) { + wake_lock(&data->wake_lock); + ret = regulator_enable(data->regulator); + data->is_vusb_enabled = 1; + } +} + +static void vusb_disable(struct cpcap_usb_det_data *data) +{ + int ret; + if (data->is_vusb_enabled) { + wake_unlock(&data->wake_lock); + ret = regulator_disable(data->regulator); + data->is_vusb_enabled = 0; + } +} + +static int get_sense(struct cpcap_usb_det_data *data) +{ + int retval = -EFAULT; + unsigned short value; + struct cpcap_device *cpcap; + + if (!data) + return -EFAULT; + cpcap = data->cpcap; + + retval = cpcap_regacc_read(cpcap, CPCAP_REG_INTS1, &value); + if (retval) + return retval; + + /* Clear ASAP after read. */ + retval = cpcap_regacc_write(cpcap, CPCAP_REG_INT1, + (CPCAP_BIT_CHRG_DET_I | + CPCAP_BIT_ID_FLOAT_I | + CPCAP_BIT_ID_GROUND_I), + (CPCAP_BIT_CHRG_DET_I | + CPCAP_BIT_ID_FLOAT_I | + CPCAP_BIT_ID_GROUND_I)); + if (retval) + return retval; + + data->sense = value & (CPCAP_BIT_ID_FLOAT_S | + CPCAP_BIT_ID_GROUND_S); + + retval = cpcap_regacc_read(cpcap, CPCAP_REG_INTS2, &value); + if (retval) + return retval; + + /* Clear ASAP after read. */ + retval = cpcap_regacc_write(cpcap, CPCAP_REG_INT2, + (CPCAP_BIT_CHRGCURR1_I | + CPCAP_BIT_VBUSVLD_I | + CPCAP_BIT_SESSVLD_I | + CPCAP_BIT_SE1_I), + (CPCAP_BIT_CHRGCURR1_I | + CPCAP_BIT_VBUSVLD_I | + CPCAP_BIT_SESSVLD_I | + CPCAP_BIT_SE1_I)); + if (retval) + return retval; + + data->sense |= value & (CPCAP_BIT_CHRGCURR1_S | + CPCAP_BIT_VBUSVLD_S | + CPCAP_BIT_SESSVLD_S | + CPCAP_BIT_SE1_S); + + retval = cpcap_regacc_read(cpcap, CPCAP_REG_INTS4, &value); + if (retval) + return retval; + + /* Clear ASAP after read. */ + retval = cpcap_regacc_write(cpcap, CPCAP_REG_INT4, + (CPCAP_BIT_DP_I | + CPCAP_BIT_DM_I), + (CPCAP_BIT_DP_I | + CPCAP_BIT_DM_I)); + if (retval) + return retval; + + data->sense |= (value & (CPCAP_BIT_DP_S | + CPCAP_BIT_DM_S)) << CPCAP_SENSE4_LS; + + return 0; +} + +static int configure_hardware(struct cpcap_usb_det_data *data, + enum cpcap_accy accy) +{ + int retval; + + /* Take control of pull up from ULPI. */ + retval = cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC3, + CPCAP_BIT_PU_SPI, + CPCAP_BIT_PU_SPI); + retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC1, + CPCAP_BIT_DP150KPU, + (CPCAP_BIT_DP150KPU | CPCAP_BIT_DP1K5PU | + CPCAP_BIT_DM1K5PU | CPCAP_BIT_DPPD | + CPCAP_BIT_DMPD)); + + switch (accy) { + case CPCAP_ACCY_USB: + case CPCAP_ACCY_FACTORY: + retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC1, 0, + CPCAP_BIT_VBUSPD); + retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC2, + CPCAP_BIT_USBXCVREN, + CPCAP_BIT_USBXCVREN); + /* Give USB driver control of pull up via ULPI. */ + retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC3, + 0, + CPCAP_BIT_PU_SPI | + CPCAP_BIT_DMPD_SPI | + CPCAP_BIT_DPPD_SPI | + CPCAP_BIT_SUSPEND_SPI | + CPCAP_BIT_ULPI_SPI_SEL); + + if ((data->cpcap->vendor == CPCAP_VENDOR_ST) && + (data->cpcap->revision == CPCAP_REVISION_2_0)) + vusb_enable(data); + + break; + + case CPCAP_ACCY_CHARGER: + /* Disable Reverse Mode */ + retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_CRM, + 0, CPCAP_BIT_RVRSMODE); + /* Enable VBus PullDown */ + retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC1, + CPCAP_BIT_VBUSPD, + CPCAP_BIT_VBUSPD); + retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC3, 0, + CPCAP_BIT_VBUSSTBY_EN); + break; + + case CPCAP_ACCY_UNKNOWN: + retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC1, 0, + CPCAP_BIT_VBUSPD); + break; + + case CPCAP_ACCY_NONE: + default: + retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC1, + CPCAP_BIT_VBUSPD, + CPCAP_BIT_VBUSPD); + retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC2, 0, + CPCAP_BIT_USBXCVREN); + retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC3, + CPCAP_BIT_DMPD_SPI | + CPCAP_BIT_DPPD_SPI | + CPCAP_BIT_SUSPEND_SPI | + CPCAP_BIT_ULPI_SPI_SEL, + CPCAP_BIT_DMPD_SPI | + CPCAP_BIT_DPPD_SPI | + CPCAP_BIT_SUSPEND_SPI | + CPCAP_BIT_ULPI_SPI_SEL); + break; + } + + if (retval != 0) + retval = -EFAULT; + + return retval; +} + +static unsigned char vbus_valid_adc_check(struct cpcap_usb_det_data *data) +{ + struct cpcap_adc_request req; + int ret; + + req.format = CPCAP_ADC_FORMAT_CONVERTED; + req.timing = CPCAP_ADC_TIMING_IMM; + req.type = CPCAP_ADC_TYPE_BANK_0; + + ret = cpcap_adc_sync_read(data->cpcap, &req); + if (ret) { + dev_err(&data->cpcap->spi->dev, + "%s: ADC Read failed\n", __func__); + return false; + } + return ((req.result[CPCAP_ADC_CHG_ISENSE] < 50) && + (req.result[CPCAP_ADC_VBUS] < + (req.result[CPCAP_ADC_BATTP]))) ? false : true; +} + + +static void notify_accy(struct cpcap_usb_det_data *data, enum cpcap_accy accy) +{ + dev_info(&data->cpcap->spi->dev, "notify_accy: accy=%d\n", accy); + + if ((data->usb_accy != CPCAP_ACCY_NONE) && (data->usb_dev != NULL)) { + platform_device_del(data->usb_dev); + data->usb_dev = NULL; + } + + configure_hardware(data, accy); + data->usb_accy = accy; + + if (accy != CPCAP_ACCY_NONE) { + data->usb_dev = platform_device_alloc(accy_devices[accy], -1); + if (data->usb_dev) { + data->usb_dev->dev.platform_data = data->cpcap; + platform_device_add(data->usb_dev); + } + } else + vusb_disable(data); + + if ((accy == CPCAP_ACCY_USB) || (accy == CPCAP_ACCY_FACTORY)) { + if (!data->usb_connected_dev) { + data->usb_connected_dev = + platform_device_alloc("cpcap_usb_connected", -1); + platform_device_add(data->usb_connected_dev); + } + } else if (data->usb_connected_dev) { + platform_device_del(data->usb_connected_dev); + data->usb_connected_dev = NULL; + } + + if (accy == CPCAP_ACCY_CHARGER) { + if (!data->charger_connected_dev) { + data->charger_connected_dev = + platform_device_alloc("cpcap_charger_connected", + -1); + platform_device_add(data->charger_connected_dev); + } + } else if (data->charger_connected_dev) { + platform_device_del(data->charger_connected_dev); + data->charger_connected_dev = NULL; + } +} + +#ifdef CONFIG_CHARGER_CPCAP_2WIRE +static enum hrtimer_restart cpcap_send_2wire_sendbit(struct hrtimer *timer) +{ + struct cpcap_usb_det_data *usb_det_data = + container_of(timer, struct cpcap_usb_det_data, hr_timer); + struct cpcap_usb_det_2wire *twd = &(usb_det_data->twowire_data); + enum hrtimer_restart ret = HRTIMER_NORESTART; + bool value; + + if (gpio_is_valid(twd->gpio) && + (twd->pos < TWOWIRE_HANDSHAKE_LEN * BI2BY)) { + value = !!(twd->data[twd->pos/BI2BY] & + (1 << (BI2BY - (twd->pos % BI2BY) - 1))); + gpio_set_value(twd->gpio, value); + ret = HRTIMER_RESTART; + } + + if (++twd->pos == TWOWIRE_HANDSHAKE_LEN * BI2BY || + !gpio_is_valid(twd->gpio)) { + twd->state = CPCAP_TWOWIRE_DONE; + ret = HRTIMER_NORESTART; + } + + if (ret == HRTIMER_RESTART) + hrtimer_forward(timer, ktime_get(), ns_to_ktime(TWOWIRE_DELAY)); + + return ret; +} +#endif + +static void detection_work(struct work_struct *work) +{ + struct cpcap_usb_det_data *data = + container_of(work, struct cpcap_usb_det_data, work.work); + unsigned char isVBusValid = 0; +#ifdef CONFIG_CHARGER_CPCAP_2WIRE + ktime_t next_time; + int sessvalid; + unsigned char handshake[TWOWIRE_HANDSHAKE_LEN] = + TWOWIRE_HANDSHAKE_SEQUENCE; +#endif + + switch (data->state) { + case CONFIG: + vusb_enable(data); + cpcap_irq_mask(data->cpcap, CPCAP_IRQ_CHRG_DET); + cpcap_irq_mask(data->cpcap, CPCAP_IRQ_CHRG_CURR1); + cpcap_irq_mask(data->cpcap, CPCAP_IRQ_SE1); + cpcap_irq_mask(data->cpcap, CPCAP_IRQ_IDGND); + cpcap_irq_mask(data->cpcap, CPCAP_IRQ_VBUSVLD); + cpcap_irq_mask(data->cpcap, CPCAP_IRQ_IDFLOAT); + cpcap_irq_mask(data->cpcap, CPCAP_IRQ_DPI); + cpcap_irq_mask(data->cpcap, CPCAP_IRQ_DMI); + cpcap_irq_mask(data->cpcap, CPCAP_IRQ_SESSVLD); + + configure_hardware(data, CPCAP_ACCY_UNKNOWN); + + data->undetect_cnt = 0; + data->state = SAMPLE_1; + schedule_delayed_work(&data->work, msecs_to_jiffies(11)); + break; + + case SAMPLE_1: + get_sense(data); + data->state = SAMPLE_2; + schedule_delayed_work(&data->work, msecs_to_jiffies(100)); + break; + + case SAMPLE_2: + data->prev_sense = data->sense; + get_sense(data); + + if (data->prev_sense != data->sense) { + /* Stay in this state */ + data->state = SAMPLE_2; + schedule_delayed_work(&data->work, + msecs_to_jiffies(100)); + } else if (!(data->sense & CPCAP_BIT_SE1_S) && + (data->sense & CPCAP_BIT_ID_FLOAT_S) && + !(data->sense & CPCAP_BIT_ID_GROUND_S) && + !(data->sense & CPCAP_BIT_SESSVLD_S)) { + data->state = IDENTIFY; + schedule_delayed_work(&data->work, + msecs_to_jiffies(100)); + } else { + data->state = IDENTIFY; + schedule_delayed_work(&data->work, 0); + } + break; + + case IDENTIFY: + get_sense(data); + data->state = CONFIG; + isVBusValid = vbus_valid_adc_check(data); + + if ((data->sense == SENSE_USB) || + (data->sense == SENSE_USB_FLASH)) { + notify_accy(data, CPCAP_ACCY_USB); + + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_CHRG_DET); + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_CHRG_CURR1); + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_SE1); + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_IDGND); + + /* Special handling of USB cable undetect. */ + data->state = USB; + } else if ((data->sense == SENSE_FACTORY) || + (data->sense == SENSE_FACTORY_COM)) { +#ifdef CONFIG_USB_TESTING_POWER + if (testing_power_enable > 0) { + notify_accy(data, CPCAP_ACCY_NONE); + cpcap_irq_unmask(data->cpcap, + CPCAP_IRQ_CHRG_DET); + cpcap_irq_unmask(data->cpcap, + CPCAP_IRQ_CHRG_CURR1); + cpcap_irq_unmask(data->cpcap, + CPCAP_IRQ_VBUSVLD); + break; + } +#endif + notify_accy(data, CPCAP_ACCY_FACTORY); + + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_SE1); + + /* Special handling of factory cable undetect. */ + data->state = FACTORY; + } else if (((data->sense | CPCAP_BIT_VBUSVLD_S) == \ + SENSE_CHARGER_FLOAT) || + ((data->sense | CPCAP_BIT_VBUSVLD_S) == \ + SENSE_CHARGER) || +#ifdef CONFIG_CHARGER_CPCAP_2WIRE + (data->usb_accy == CPCAP_ACCY_2WIRE) || +#endif + (data->sense == SENSE_IDLOW_CHARGER)) { + + if ((isVBusValid) && ((data->sense == \ + SENSE_CHARGER_FLOAT) || + (data->sense == SENSE_CHARGER) || + (data->sense == SENSE_IDLOW_CHARGER) || +#ifdef CONFIG_CHARGER_CPCAP_2WIRE + (data->usb_accy == CPCAP_ACCY_2WIRE) || +#endif + (data->sense & CPCAP_BIT_SESSVLD_S))) { + /* Wakeup device from Suspend especially when + * you are coming from dipping voltage[<4.2V] + * to higher one [4.6V - VBUS,5V] + */ + if (!(wake_lock_active(&data->wake_lock))) + wake_lock(&data->wake_lock); + + notify_accy(data, CPCAP_ACCY_CHARGER); + /* VBUS is valid and also session valid bit + * is set hence, we notify that charger is + * connected + */ + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_SE1); + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_IDGND); +#ifdef CONFIG_CHARGER_CPCAP_2WIRE + data->state = FINISH2WIRE; + schedule_delayed_work(&data->work, + msecs_to_jiffies(500)); +#else + data->state = CONFIG; +#endif + } else if ((!isVBusValid) && + ((!(data->sense & CPCAP_BIT_SESSVLD_S) || +#ifdef CONFIG_CHARGER_CPCAP_2WIRE + (data->usb_accy == CPCAP_ACCY_2WIRE) || +#endif + (!(data->sense & CPCAP_BIT_VBUSVLD_S))))) { + /* Condition when the USB charger is connected & + * for some reason Voltage falls below the 4.4V + * threshold. Since USB is connected, we reset + * the State Machine and wait for the voltage to + * reach the high threshold + */ + data->state = CONFIG; +#ifdef CONFIG_CHARGER_CPCAP_2WIRE + data->usb_accy = CPCAP_ACCY_NONE; + if (gpio_is_valid(data->twowire_data.gpio)) + gpio_set_value(data->twowire_data.gpio, + 0); +#endif + + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_IDGND); + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_SE1); + cpcap_irq_unmask(data->cpcap, + CPCAP_IRQ_VBUSVLD); + cpcap_irq_unmask(data->cpcap, + CPCAP_IRQ_CHRG_DET); + + cpcap_irq_mask(data->cpcap, CPCAP_IRQ_VBUSVLD); + schedule_delayed_work(&data->work, 0); + } +#ifdef CONFIG_CHARGER_CPCAP_2WIRE + } else if ((data->sense == SENSE_2WIRE) && + (data->usb_accy == CPCAP_ACCY_NONE)) { + /* wait 750ms with GPIO low to force idle state */ + if (gpio_is_valid(data->twowire_data.gpio)) { + gpio_set_value(data->twowire_data.gpio, 0); + data->state = START2WIRE; + schedule_delayed_work(&data->work, + msecs_to_jiffies(750)); + } else { + printk(KERN_ERR "Detected 2wire charger but " + "GPIO is not configured\n"); + data->state = CONFIG; + cpcap_irq_unmask(data->cpcap, + CPCAP_IRQ_CHRG_DET); + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_DPI); + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_DMI); + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_SE1); + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_IDGND); + } +#endif + } else if ((data->sense & CPCAP_BIT_VBUSVLD_S) && + (data->usb_accy == CPCAP_ACCY_NONE)) { + data->state = CONFIG; + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_CHRG_DET); + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_DPI); + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_DMI); + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_SE1); + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_IDGND); + } else { + notify_accy(data, CPCAP_ACCY_NONE); + + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_CHRG_DET); + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_CHRG_CURR1); + + /* When a charger is unpowered by unplugging from the + * wall, VBUS voltage will drop below CHRG_DET (3.5V) + * until the ICHRG bits are cleared. Once ICHRG is + * cleared, VBUS will rise above CHRG_DET, but below + * VBUSVLD (4.4V) briefly as it decays. If the charger + * is re-powered while VBUS is within this window, the + * VBUSVLD interrupt is needed to trigger charger + * detection. + * + * VBUSVLD must be masked before going into suspend. + * See cpcap_usb_det_suspend() for details. + */ + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_VBUSVLD); + } + break; + + case USB: + get_sense(data); + + if ((data->sense & CPCAP_BIT_SE1_S) || + (data->sense & CPCAP_BIT_ID_GROUND_S)) { + data->state = CONFIG; + schedule_delayed_work(&data->work, 0); + } else if (!(data->sense & CPCAP_BIT_VBUSVLD_S)) { + if (data->undetect_cnt++ < UNDETECT_TRIES) { + cpcap_irq_mask(data->cpcap, CPCAP_IRQ_CHRG_DET); + cpcap_irq_mask(data->cpcap, + CPCAP_IRQ_CHRG_CURR1); + cpcap_irq_mask(data->cpcap, CPCAP_IRQ_SE1); + cpcap_irq_mask(data->cpcap, CPCAP_IRQ_IDGND); + data->state = USB; + schedule_delayed_work(&data->work, + msecs_to_jiffies(100)); + } else { + data->state = CONFIG; + schedule_delayed_work(&data->work, 0); + } + } else { + data->state = USB; + data->undetect_cnt = 0; + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_CHRG_DET); + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_CHRG_CURR1); + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_SE1); + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_IDGND); + } + break; + + case FACTORY: + get_sense(data); + + /* The removal of a factory cable can only be detected if a + * charger is attached. + */ + if (data->sense & CPCAP_BIT_SE1_S) { +#ifdef CONFIG_TTA_CHARGER + enable_tta(); +#endif + data->state = CONFIG; + schedule_delayed_work(&data->work, 0); + } else { + data->state = FACTORY; + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_SE1); + } + break; +#ifdef CONFIG_CHARGER_CPCAP_2WIRE + case START2WIRE: + sessvalid = (data->sense & CPCAP_BIT_SESSVLD_S); + memcpy(data->twowire_data.data, handshake, + TWOWIRE_HANDSHAKE_LEN); + data->twowire_data.pos = 5; + data->twowire_data.state = CPCAP_TWOWIRE_RUNNING; + next_time = ktime_set(0, TWOWIRE_DELAY); + hrtimer_start(&data->hr_timer, next_time, HRTIMER_MODE_REL); + + while (sessvalid && data->twowire_data.state != + CPCAP_TWOWIRE_DONE) { + msleep(10); + get_sense(data); + sessvalid = (data->sense & CPCAP_BIT_SESSVLD_S); + } + + if (sessvalid && data->twowire_data.state == + CPCAP_TWOWIRE_DONE) { + data->usb_accy = CPCAP_ACCY_2WIRE; + data->state = IDENTIFY; + schedule_delayed_work(&data->work, 0); + } else { + printk(KERN_ERR "2wire removed durring handshake\n"); + hrtimer_cancel(&data->hr_timer); + if (gpio_is_valid(data->twowire_data.gpio)) + gpio_set_value(data->twowire_data.gpio, 0); + data->state = CONFIG; + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_CHRG_DET); + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_DPI); + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_DMI); + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_SE1); + cpcap_irq_unmask(data->cpcap, CPCAP_IRQ_IDGND); + } + break; + case FINISH2WIRE: + if (gpio_is_valid(data->twowire_data.gpio)) + gpio_set_value(data->twowire_data.gpio, 0); + data->state = CONFIG; + break; +#endif + default: + /* This shouldn't happen. Need to reset state machine. */ + vusb_disable(data); + data->state = CONFIG; + schedule_delayed_work(&data->work, 0); + break; + } +} + +static void int_handler(enum cpcap_irqs int_event, void *data) +{ + struct cpcap_usb_det_data *usb_det_data = data; + schedule_delayed_work(&(usb_det_data->work), 0); +} + +static int cpcap_usb_det_probe(struct platform_device *pdev) +{ + int retval; + struct cpcap_usb_det_data *data; +#ifdef CONFIG_CHARGER_CPCAP_2WIRE + struct cpcap_platform_data *platform_data; +#endif + + if (pdev->dev.platform_data == NULL) { + dev_err(&pdev->dev, "no platform_data\n"); + return -EINVAL; + } + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->cpcap = pdev->dev.platform_data; + data->state = CONFIG; + platform_set_drvdata(pdev, data); + INIT_DELAYED_WORK(&data->work, detection_work); + data->usb_accy = CPCAP_ACCY_NONE; + wake_lock_init(&data->wake_lock, WAKE_LOCK_SUSPEND, "usb"); + data->undetect_cnt = 0; + + data->regulator = regulator_get(NULL, "vusb"); + if (IS_ERR(data->regulator)) { + dev_err(&pdev->dev, "Could not get regulator for cpcap_usb\n"); + retval = PTR_ERR(data->regulator); + goto free_mem; + } + regulator_set_voltage(data->regulator, 3300000, 3300000); + + retval = cpcap_irq_register(data->cpcap, CPCAP_IRQ_CHRG_DET, + int_handler, data); + retval |= cpcap_irq_register(data->cpcap, CPCAP_IRQ_CHRG_CURR1, + int_handler, data); + retval |= cpcap_irq_register(data->cpcap, CPCAP_IRQ_SE1, + int_handler, data); + retval |= cpcap_irq_register(data->cpcap, CPCAP_IRQ_IDGND, + int_handler, data); + retval |= cpcap_irq_register(data->cpcap, CPCAP_IRQ_VBUSVLD, + int_handler, data); + retval |= cpcap_irq_register(data->cpcap, CPCAP_IRQ_IDFLOAT, + int_handler, data); + retval |= cpcap_irq_register(data->cpcap, CPCAP_IRQ_DPI, + int_handler, data); + retval |= cpcap_irq_register(data->cpcap, CPCAP_IRQ_DMI, + int_handler, data); + retval |= cpcap_irq_register(data->cpcap, CPCAP_IRQ_SESSVLD, + int_handler, data); + + /* Now that HW initialization is done, give USB control via ULPI. */ + retval |= cpcap_regacc_write(data->cpcap, CPCAP_REG_USBC3, + 0, CPCAP_BIT_ULPI_SPI_SEL); + +#ifdef CONFIG_CHARGER_CPCAP_2WIRE + hrtimer_init(&(data->hr_timer), CLOCK_REALTIME, HRTIMER_MODE_REL); + data->hr_timer.function = &cpcap_send_2wire_sendbit; + if (data->cpcap->spi && data->cpcap->spi->controller_data) { + platform_data = data->cpcap->spi->controller_data; + data->twowire_data.gpio = platform_data->twowire_hndshk_gpio; + } else { + data->twowire_data.gpio = -1; + dev_err(&pdev->dev, "SPI platform_data missing\n"); + retval = -EINVAL; + } +#endif + + if (retval != 0) { + dev_err(&pdev->dev, "Initialization Error\n"); + retval = -ENODEV; + goto free_irqs; + } + + dev_info(&pdev->dev, "CPCAP USB detection device probed\n"); + + /* Perform initial detection */ + detection_work(&(data->work.work)); + + return 0; + +free_irqs: + cpcap_irq_free(data->cpcap, CPCAP_IRQ_VBUSVLD); + cpcap_irq_free(data->cpcap, CPCAP_IRQ_IDGND); + cpcap_irq_free(data->cpcap, CPCAP_IRQ_SE1); + cpcap_irq_free(data->cpcap, CPCAP_IRQ_CHRG_CURR1); + cpcap_irq_free(data->cpcap, CPCAP_IRQ_CHRG_DET); + cpcap_irq_free(data->cpcap, CPCAP_IRQ_IDFLOAT); + cpcap_irq_free(data->cpcap, CPCAP_IRQ_DPI); + cpcap_irq_free(data->cpcap, CPCAP_IRQ_DMI); + cpcap_irq_free(data->cpcap, CPCAP_IRQ_SESSVLD); + regulator_put(data->regulator); +free_mem: + wake_lock_destroy(&data->wake_lock); + kfree(data); + + return retval; +} + +static int cpcap_usb_det_remove(struct platform_device *pdev) +{ + struct cpcap_usb_det_data *data = platform_get_drvdata(pdev); + + cpcap_irq_free(data->cpcap, CPCAP_IRQ_CHRG_DET); + cpcap_irq_free(data->cpcap, CPCAP_IRQ_CHRG_CURR1); + cpcap_irq_free(data->cpcap, CPCAP_IRQ_SE1); + cpcap_irq_free(data->cpcap, CPCAP_IRQ_IDGND); + cpcap_irq_free(data->cpcap, CPCAP_IRQ_VBUSVLD); + cpcap_irq_free(data->cpcap, CPCAP_IRQ_IDFLOAT); + cpcap_irq_free(data->cpcap, CPCAP_IRQ_SESSVLD); + + configure_hardware(data, CPCAP_ACCY_NONE); + cancel_delayed_work_sync(&data->work); + + if ((data->usb_accy != CPCAP_ACCY_NONE) && (data->usb_dev != NULL)) + platform_device_del(data->usb_dev); + + vusb_disable(data); + regulator_put(data->regulator); + + wake_lock_destroy(&data->wake_lock); + + kfree(data); + return 0; +} + +#ifdef CONFIG_PM +static int cpcap_usb_det_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct cpcap_usb_det_data *data = platform_get_drvdata(pdev); + + /* VBUSVLD cannot be unmasked when entering suspend. If left + * unmasked, a false interrupt will be received, keeping the + * device out of suspend. The interrupt does not need to be + * unmasked when resuming from suspend since the use case + * for having the interrupt unmasked is over. + */ + cpcap_irq_mask(data->cpcap, CPCAP_IRQ_VBUSVLD); + + return 0; +} +#else +#define cpcap_usb_det_suspend NULL +#endif + +static struct platform_driver cpcap_usb_det_driver = { + .probe = cpcap_usb_det_probe, + .remove = cpcap_usb_det_remove, + .suspend = cpcap_usb_det_suspend, + .driver = { + .name = "cpcap_usb_det", + .owner = THIS_MODULE, + }, +}; + +static int __init cpcap_usb_det_init(void) +{ + return cpcap_driver_register(&cpcap_usb_det_driver); +} +/* The CPCAP USB detection driver must be started later to give the MUSB + * driver time to complete its initialization. */ +late_initcall(cpcap_usb_det_init); + +static void __exit cpcap_usb_det_exit(void) +{ + platform_driver_unregister(&cpcap_usb_det_driver); +} +module_exit(cpcap_usb_det_exit); + +MODULE_ALIAS("platform:cpcap_usb_det"); +MODULE_DESCRIPTION("CPCAP USB detection driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/m4sensorhub-core.c b/drivers/mfd/m4sensorhub-core.c new file mode 100644 index 00000000000..cf1a363921a --- /dev/null +++ b/drivers/mfd/m4sensorhub-core.c @@ -0,0 +1,569 @@ +/* + * 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 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/platform_device.h> +#include <linux/miscdevice.h> +#include <linux/fs.h> +#include <linux/rtc.h> +#include <linux/gpio.h> +#include <linux/string.h> +#include <linux/m4sensorhub/MemMapLog.h> +#include <linux/m4sensorhub.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/delay.h> + + +#define M4SENSORHUB_NUM_GPIOS 6 + +/* --------------- Global Declarations -------------- */ +char m4sensorhub_debug; +EXPORT_SYMBOL_GPL(m4sensorhub_debug); + +/* ------------ Local Function Prototypes ----------- */ + +/* -------------- Local Data Structures ------------- */ +static struct miscdevice m4sensorhub_misc_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = M4SENSORHUB_DRIVER_NAME, +}; + +/* --------------- Local Declarations -------------- */ +static struct m4sensorhub_data *m4sensorhub_misc_data; +static DEFINE_MUTEX(m4sensorhub_driver_lock); + +unsigned short force_upgrade; +module_param(force_upgrade, short, 0644); +MODULE_PARM_DESC(force_upgrade, "Force FW download ignoring version check"); + +unsigned short debug_level; +module_param(debug_level, short, 0644); +MODULE_PARM_DESC(debug_level, "Set debug level 1 (CRITICAL) to " + "7 (VERBOSE_DEBUG)"); + +/* -------------- Global Functions ----------------- */ +struct m4sensorhub_data *m4sensorhub_client_get_drvdata(void) +{ + return m4sensorhub_misc_data; +} +EXPORT_SYMBOL_GPL(m4sensorhub_client_get_drvdata); + + +/* -------------- Local Functions ----------------- */ + +static ssize_t m4sensorhub_get_dbg(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", m4sensorhub_debug); +} + +/* BEGIN BOARD FILE */ +/* TODO: replace with request array */ + +int m4sensorhub_set_bootmode(struct m4sensorhub_data *m4sensorhub, + enum m4sensorhub_bootmode bootmode) +{ + if (!m4sensorhub) { + printk(KERN_ERR "set_bootmode: invalid pointer\n"); + return -EINVAL; + } + + switch (bootmode) { + case BOOTMODE00: + gpio_set_value(m4sensorhub->hwconfig.boot0_gpio, 0); + gpio_set_value(m4sensorhub->hwconfig.boot1_gpio, 0); + break; + case BOOTMODE01: + gpio_set_value(m4sensorhub->hwconfig.boot0_gpio, 1); + gpio_set_value(m4sensorhub->hwconfig.boot1_gpio, 0); + break; + case BOOTMODE10: + gpio_set_value(m4sensorhub->hwconfig.boot0_gpio, 0); + gpio_set_value(m4sensorhub->hwconfig.boot1_gpio, 1); + break; + case BOOTMODE11: + gpio_set_value(m4sensorhub->hwconfig.boot0_gpio, 1); + gpio_set_value(m4sensorhub->hwconfig.boot1_gpio, 1); + default: + break; + } + + return 0; +} + +static void minnow_m4sensorhub_hw_reset(struct m4sensorhub_data *m4sensorhub) +{ + if (!m4sensorhub) { + printk(KERN_ERR "m4sensorhub_hw_reset: invalid pointer\n"); + return; + } + + m4sensorhub_set_bootmode(m4sensorhub, BOOTMODE00); + gpio_set_value(m4sensorhub->hwconfig.reset_gpio, 1); + msleep(5); + gpio_set_value(m4sensorhub->hwconfig.reset_gpio, 0); + msleep(5); + gpio_set_value(m4sensorhub->hwconfig.reset_gpio, 1); +} + +/* callback from driver to initialize hardware on probe */ +static int minnow_m4sensorhub_hw_init(struct m4sensorhub_data *m4sensorhub, + struct device_node *node) +{ + int gpio; + int err = -EINVAL; + + if (!m4sensorhub) { + printk(KERN_ERR "m4sensorhub_hw_init: invalid pointer\n"); + err = -EINVAL; + goto error; + } + if (node == NULL) { + printk(KERN_ERR "m4sensorhub_hw_init: node null\n"); + err = -EINVAL; + goto error; + } + + gpio = of_get_named_gpio_flags(node, "mot,irq-gpio", 0, NULL); + err = (gpio < 0) ? -ENODEV : gpio_request(gpio, "m4sensorhub-intr"); + if (err) { + pr_err("Failed acquiring M4 Sensor Hub IRQ GPIO-%d (%d)\n", + gpio, err); + goto error; + } + gpio_direction_input(gpio); + m4sensorhub->hwconfig.irq_gpio = gpio; + + gpio = of_get_named_gpio_flags(node, "mot,reset-gpio", 0, NULL); + err = (gpio < 0) ? -ENODEV : gpio_request(gpio, "m4sensorhub-reset"); + if (err) { + pr_err("Failed acquiring M4 Sensor Hub Reset GPIO-%d (%d)\n", + gpio, err); + goto error_reset; + } + gpio_direction_output(gpio, 1); + m4sensorhub->hwconfig.reset_gpio = gpio; + + gpio = of_get_named_gpio_flags(node, "mot,wake-gpio", 0, NULL); + err = (gpio < 0) ? -ENODEV : gpio_request(gpio, "m4sensorhub-wake"); + if (err) { + pr_err("Failed acquiring M4 Sensor Hub Wake GPIO-%d (%d)\n", + gpio, err); + goto error_wake; + } + gpio_direction_output(gpio, 0); + m4sensorhub->hwconfig.wake_gpio = gpio; + + gpio = of_get_named_gpio_flags(node, "mot,boot0-gpio", 0, NULL); + err = (gpio < 0) ? -ENODEV : gpio_request(gpio, "m4sensorhub-boot0"); + if (err) { + pr_err("Failed acquiring M4 Sensor Hub Boot0 GPIO-%d (%d)\n", + gpio, err); + goto error_boot0; + } + gpio_direction_output(gpio, 0); + m4sensorhub->hwconfig.boot0_gpio = gpio; + + gpio = of_get_named_gpio_flags(node, "mot,boot1-gpio", 0, NULL); + err = (gpio < 0) ? -ENODEV : gpio_request(gpio, "m4sensorhub-boot1"); + if (err) { + pr_err("Failed acquiring M4 Sensor Hub Boot1 GPIO-%d (%d)\n", + gpio, err); + goto error_boot1; + } + gpio_direction_output(gpio, 0); + m4sensorhub->hwconfig.boot1_gpio = gpio; + + gpio = of_get_named_gpio_flags(node, "mot,enable-gpio", 0, NULL); + err = (gpio < 0) ? -ENODEV : gpio_request(gpio, "m4sensorhub-enable"); + if (err) { + pr_err("Failed acquiring M4 Sensor Hub Enable GPIO-%d (%d)\n", + gpio, err); + goto error_enable; + } + gpio_direction_output(gpio, 0); + m4sensorhub->hwconfig.mpu_9150_en_gpio = gpio; + + minnow_m4sensorhub_hw_reset(m4sensorhub); + + return 0; + +error_enable: + gpio_free(m4sensorhub->hwconfig.boot1_gpio); + m4sensorhub->hwconfig.boot1_gpio = -1; +error_boot1: + gpio_free(m4sensorhub->hwconfig.boot0_gpio); + m4sensorhub->hwconfig.boot0_gpio = -1; +error_boot0: + gpio_free(m4sensorhub->hwconfig.wake_gpio); + m4sensorhub->hwconfig.wake_gpio = -1; +error_wake: + gpio_free(m4sensorhub->hwconfig.reset_gpio); + m4sensorhub->hwconfig.reset_gpio = -1; +error_reset: + gpio_free(m4sensorhub->hwconfig.irq_gpio); + m4sensorhub->hwconfig.irq_gpio = -1; +error: + return err; +} + +/* callback from driver to free hardware on shutdown */ +static void minnow_m4sensorhub_hw_free(struct m4sensorhub_data *m4sensorhub) +{ + + if (!m4sensorhub) { + printk(KERN_ERR "hw_free: invalid pointer\n"); + return; + } + + if (m4sensorhub->hwconfig.irq_gpio >= 0) { + gpio_free(m4sensorhub->hwconfig.irq_gpio); + m4sensorhub->hwconfig.irq_gpio = -1; + } + + if (m4sensorhub->hwconfig.reset_gpio >= 0) { + gpio_free(m4sensorhub->hwconfig.reset_gpio); + m4sensorhub->hwconfig.reset_gpio = -1; + } + + if (m4sensorhub->hwconfig.wake_gpio >= 0) { + gpio_free(m4sensorhub->hwconfig.wake_gpio); + m4sensorhub->hwconfig.wake_gpio = -1; + } + + if (m4sensorhub->hwconfig.boot0_gpio >= 0) { + gpio_free(m4sensorhub->hwconfig.boot0_gpio); + m4sensorhub->hwconfig.boot0_gpio = -1; + } + + if (m4sensorhub->hwconfig.boot1_gpio >= 0) { + gpio_free(m4sensorhub->hwconfig.boot1_gpio); + m4sensorhub->hwconfig.boot1_gpio = -1; + } + + if (m4sensorhub->hwconfig.mpu_9150_en_gpio >= 0) { + gpio_free(m4sensorhub->hwconfig.mpu_9150_en_gpio); + m4sensorhub->hwconfig.mpu_9150_en_gpio = -1; + } +} + +/* END BOARD FILE FUNCTIONS */ + +static ssize_t m4sensorhub_set_dbg(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned long debug; + + if ((strict_strtol(buf, 10, &debug) < 0) || + (debug < M4SH_NODEBUG) || (debug > M4SH_VERBOSE_DEBUG)) + return -EINVAL; + + m4sensorhub_debug = debug; + KDEBUG(M4SH_CRITICAL, "M4 Sensor Hub debug level = %d\n", + m4sensorhub_debug); + + return count; +} + +static DEVICE_ATTR(debug_level, S_IRUGO|S_IWUGO, m4sensorhub_get_dbg, + m4sensorhub_set_dbg); + +static ssize_t m4sensorhub_get_loglevel(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long long loglevel; + + m4sensorhub_reg_read(m4sensorhub_misc_data, + M4SH_REG_LOG_LOGENABLE, (char *)&loglevel); + KDEBUG(M4SH_INFO, "M4 loglevel = %llx", loglevel); + return sprintf(buf, "%llu\n", loglevel); +} +void ParseAndUpdateLogLevels(char *tag, char *level, + unsigned long long *logLevels) +{ + int i; + int levelindex = -1; + int tagindex = -1; + unsigned long long mask; + + for (i = 0; i < LOG_LEVELS_MAX; i++) { + if (strcmp(acLogLevels[i], level) == 0) { + levelindex = i; + break; + } + } + + for (i = 0; i < LOG_MAX; i++) { + if (strcmp(acLogTags[i], tag) == 0) { + tagindex = i; + break; + } + } + + if ((tagindex == -1) || (levelindex == -1)) + return; + + /*Clear the revelant bits*/ + mask = 0x03; + *logLevels &= ~(mask << (tagindex * 2)); + /*set debug level for the relevant bits*/ + *logLevels |= (levelindex << (tagindex * 2)); + KDEBUG(M4SH_INFO, "New M4 log levels = 0x%llx \n", *logLevels); +} + +/* Usage: adb shell into the directory of sysinterface log_level and + echo LOG_ACCEL=LOG_DEGUB,LOG_POWER=LOG_ERROR > log_level */ +static ssize_t m4sensorhub_set_loglevel(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned long long currentLogLevels; + char *tag, *level; + char **logbuf = (char **) &buf; + + m4sensorhub_reg_read(m4sensorhub_misc_data, + M4SH_REG_LOG_LOGENABLE, (char *)¤tLogLevels); + while (1) { + tag = strsep(logbuf, "=,\n "); + if (tag == NULL) + break; + level = strsep(logbuf, "=,\n "); + if (level == NULL) + break; + ParseAndUpdateLogLevels(tag, level, ¤tLogLevels); + } + + return m4sensorhub_reg_write(m4sensorhub_misc_data, + M4SH_REG_LOG_LOGENABLE, (char *)¤tLogLevels, + m4sh_no_mask); +} + +static DEVICE_ATTR(log_level, S_IRUGO|S_IWUGO, m4sensorhub_get_loglevel, + m4sensorhub_set_loglevel); + +static int m4sensorhub_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct m4sensorhub_data *m4sensorhub; + struct device_node *node = client->dev.of_node; + int err = -EINVAL; + + + /* Set debug based on module argument if set, otherwise use + default logging rate based on build type */ + if (debug_level) + m4sensorhub_debug = debug_level; + else { +#ifdef CONFIG_DEBUG_FS + /* engineering build */ + m4sensorhub_debug = M4SH_INFO; +#else + /* user/userdebug builds */ + m4sensorhub_debug = M4SH_ERROR; +#endif + } + KDEBUG(M4SH_ERROR, "Initializing M4 Sensor Hub: force_upgrade=%d " + "debug=%d\n", force_upgrade, m4sensorhub_debug); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + KDEBUG(M4SH_ERROR, "client not i2c capable\n"); + err = -ENODEV; + goto err_unload; + } + m4sensorhub = kzalloc(sizeof(*m4sensorhub), GFP_KERNEL); + if (m4sensorhub == NULL) { + err = -ENOMEM; + KDEBUG(M4SH_ERROR, + "failed to allocate memory for module data: %d\n", err); + goto err_unload; + } + m4sensorhub_misc_data = m4sensorhub; + + err = minnow_m4sensorhub_hw_init(m4sensorhub, node); + if (err) + printk(KERN_ERR "%s: hw_init Failed!", __func__); + + /* link i2c_client to m4sensorhub */ + i2c_set_clientdata(client, m4sensorhub); + + /* link m4sensorhub to i2c_client */ + m4sensorhub->i2c_client = client; + + err = misc_register(&m4sensorhub_misc_device); + if (err < 0) { + KDEBUG(M4SH_ERROR, "misc register failed: %d\n", err); + goto err_hw_free; + } + + err = device_create_file(&client->dev, &dev_attr_debug_level); + if (err < 0) { + KDEBUG(M4SH_ERROR, "Error creating debug_level file\n"); + goto err_deregister; + } + + err = device_create_file(&client->dev, &dev_attr_log_level); + if (err < 0) { + KDEBUG(M4SH_ERROR, "Error creating log_level file\n"); + goto err_del_debug_file; + } + + err = m4sensorhub_load_firmware(m4sensorhub, force_upgrade); + if (err < 0) { + dev_err(&client->dev, "load firmware file failed: %d\n", err); + goto err_del_log_file; + } + + err = m4sensorhub_reg_init(m4sensorhub); + if (err < 0) + goto err_set_bootmode; + + if (m4sensorhub->hwconfig.irq_gpio >= 0) + client->irq = gpio_to_irq(m4sensorhub->hwconfig.irq_gpio); + else { + KDEBUG(M4SH_ERROR, "Error: No IRQ configured\n"); + err = -ENODEV; + goto err_reg_shutdown; + } + + err = m4sensorhub_panic_init(m4sensorhub); + if (err < 0) + goto err_reg_shutdown; + + err = m4sensorhub_irq_init(m4sensorhub); + if (err < 0) + goto err_panic_shutdown; + + + KDEBUG(M4SH_NOTICE, "Registered M4 Sensor Hub\n"); + + goto done; + +err_panic_shutdown: + m4sensorhub_panic_shutdown(m4sensorhub); +err_reg_shutdown: + m4sensorhub_reg_shutdown(m4sensorhub); +err_set_bootmode: + minnow_m4sensorhub_hw_reset(m4sensorhub); +err_del_log_file: + device_remove_file(&client->dev, &dev_attr_log_level); +err_del_debug_file: + device_remove_file(&client->dev, &dev_attr_debug_level); +err_deregister: + misc_deregister(&m4sensorhub_misc_device); +err_hw_free: + m4sensorhub->i2c_client = NULL; + i2c_set_clientdata(client, NULL); + minnow_m4sensorhub_hw_free(m4sensorhub); + kfree(m4sensorhub); + m4sensorhub = NULL; + m4sensorhub_misc_data = NULL; +err_unload: +done: + return err; +} + +static int __exit m4sensorhub_remove(struct i2c_client *client) +{ + struct m4sensorhub_data *m4sensorhub = i2c_get_clientdata(client); + KDEBUG(M4SH_INFO, "Removing M4 Sensor Hub Driver\n"); + + m4sensorhub_irq_shutdown(m4sensorhub); + m4sensorhub_panic_shutdown(m4sensorhub); + m4sensorhub_reg_shutdown(m4sensorhub); + device_remove_file(&client->dev, &dev_attr_log_level); + device_remove_file(&client->dev, &dev_attr_debug_level); + minnow_m4sensorhub_hw_reset(m4sensorhub); + misc_deregister(&m4sensorhub_misc_device); + m4sensorhub->i2c_client = NULL; + i2c_set_clientdata(client, NULL); + minnow_m4sensorhub_hw_free(m4sensorhub); + kfree(m4sensorhub); + m4sensorhub = NULL; + m4sensorhub_misc_data = NULL; + + return 0; +} + +#ifdef CONFIG_PM +static int m4sensorhub_suspend(struct i2c_client *client, pm_message_t mesg) +{ + int err = 0; + KDEBUG(M4SH_INFO, "%s\n", __func__); + m4sensorhub_irq_pm_dbg_suspend(); + return err; +} + +static int m4sensorhub_resume(struct i2c_client *client) +{ + + int err = 0; + KDEBUG(M4SH_INFO, "%s\n", __func__); + m4sensorhub_irq_pm_dbg_resume(); + return err; +} +#endif /* CONFIG_PM */ +static const struct of_device_id of_m4sensorhub_match[] = { + { .compatible = "mot,m4sensorhub", }, + {}, +}; + +static const struct i2c_device_id m4sensorhub_id[] = { + {M4SENSORHUB_DRIVER_NAME, 0}, + { }, +}; + +MODULE_DEVICE_TABLE(i2c, m4sensorhub_id); + +static struct i2c_driver m4sensorhub_driver = { + .driver = { + .name = M4SENSORHUB_DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(of_m4sensorhub_match), + }, + .probe = m4sensorhub_probe, + .remove = __exit_p(m4sensorhub_remove), +#ifdef CONFIG_PM + .suspend = m4sensorhub_suspend, + .resume = m4sensorhub_resume, +#endif /* CONFIG_PM */ + .id_table = m4sensorhub_id, +}; + +static int __init m4sensorhub_init(void) +{ + return i2c_add_driver(&m4sensorhub_driver); +} + +static void __exit m4sensorhub_exit(void) +{ + i2c_del_driver(&m4sensorhub_driver); + return; +} + +module_init(m4sensorhub_init); +module_exit(m4sensorhub_exit); + +MODULE_ALIAS("platform:m4sensorhub"); +MODULE_DESCRIPTION("M4 Sensor Hub driver"); +MODULE_AUTHOR("Motorola"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/m4sensorhub-irq.c b/drivers/mfd/m4sensorhub-irq.c new file mode 100644 index 00000000000..56b9504fecd --- /dev/null +++ b/drivers/mfd/m4sensorhub-irq.c @@ -0,0 +1,675 @@ +/* + * 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 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 <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/mutex.h> +#include <linux/workqueue.h> +#include <linux/wakelock.h> +#include <linux/i2c.h> +#include <linux/slab.h> + +#include <linux/debugfs.h> +#include <linux/seq_file.h> + +#include <linux/m4sensorhub.h> + +#ifdef CONFIG_PM_DEEPSLEEP +#include <linux/suspend.h> +#endif + +#define NUM_INT_REGS 2 +#define NUM_INTS_PER_REG 8 +#define NUM_INTS_LAST_REG (((M4SH_IRQ__NUM-1)%NUM_INTS_PER_REG)+1) +#define INTR_VALID_BITS(n) (unsigned char)((1 << (n)) - 1) + +#define EVENT_MASK(event) (1 << ((event) % NUM_INTS_PER_REG)) + +#define DBG_BUF_LINE_LEN 80 + +/* --------------- Global Declarations -------------- */ + +/* ------------ Local Function Prototypes ----------- */ +static int m4sensorhub_irq_disable_all(struct m4sensorhub_data *m4sensorhub); +static unsigned short get_enable_reg(enum m4sensorhub_irqs event); +static void irq_work_func(struct work_struct *work); +#ifdef CONFIG_DEBUG_FS +static int m4sensorhub_dbg_irq_open(struct inode *inode, struct file *file); +#endif +static void m4sensorhub_irq_restore(struct m4sensorhub_data *m4sensorhub,\ + void *data); + +/* ---------------- Local Declarations -------------- */ + +static const char *irq_name[] = { + [M4SH_IRQ_TMP_DATA_READY] = "TMP_DATA_READY", + [M4SH_IRQ_PRESSURE_DATA_READY] = "PRES_DATA_READY", + [M4SH_IRQ_GYRO_DATA_READY] = "GYRO_DATA_READY", + [M4SH_IRQ_PEDOMETER_DATA_READY] = "PEDO_DATA_READY", + [M4SH_IRQ_COMPASS_DATA_READY] = "COMPASS_DATA_READY", + [M4SH_IRQ_FUSION_DATA_READY] = "FUSION_DATA_READY", + [M4SH_IRQ_ACCEL_DATA_READY] = "ACCEL_DATA_READY", + [M4SH_IRQ_GESTURE_DETECTED] = "GESTURE_DETECTED", + [M4SH_IRQ_STILL_DETECTED] = "STILL_DETECTED", + [M4SH_IRQ_MOTION_DETECTED] = "MOTION_DETECTED", + [M4SH_IRQ_ACTIVITY_CHANGE] = "ACTIVITY_CHANGE", + [M4SH_IRQ_DLCMD_RESP_READY] = "DLCMD_RESP_READY", + [M4SH_IRQ_MIC_DATA_READY] = "MIC_DATA_READY", + [M4SH_IRQ_WRIST_READY] = "WRIST_READY", + [M4SH_IRQ_PASSIVE_BUFFER_FULL] = "PASSIVE_BUFFER_FULL", +}; + +/* -------------- Local Data Structures ------------- */ + +struct m4sensorhub_event_handler { + void (*func)(enum m4sensorhub_irqs, void *); + void *data; +}; + +struct m4sensorhub_irq_info { + uint8_t registered; + uint8_t enabled; + uint32_t ena_fired; + uint32_t disa_fired; +}; + +struct m4sensorhub_irqdata { + struct mutex lock; /* lock event handlers and data */ + struct work_struct work; + struct workqueue_struct *workqueue; + struct m4sensorhub_data *m4sensorhub; + struct m4sensorhub_event_handler event_handler[M4SH_IRQ__NUM]; + struct m4sensorhub_irq_info irq_info[M4SH_IRQ__NUM]; + struct wake_lock wake_lock; +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs; +#endif +}; + + +static const struct { + enum m4sensorhub_reg status_reg; + enum m4sensorhub_reg enable_reg; + unsigned char valid_bits; +} int_registers[NUM_INT_REGS] = { + {M4SH_REG_GENERAL_INTERRUPT0STATUS, + M4SH_REG_GENERAL_INTERRUPT0ENABLE, + INTR_VALID_BITS(NUM_INTS_PER_REG)}, + {M4SH_REG_GENERAL_INTERRUPT1STATUS, + M4SH_REG_GENERAL_INTERRUPT1ENABLE, + INTR_VALID_BITS(NUM_INTS_LAST_REG)}, +}; + +static irqreturn_t event_isr(int irq, void *data) +{ + /* Interrupts are left enabled; if multiple interrupts arrive, there + * will be multiple jobs in the workqueue. In this case, the first + * job in the workqueue may service multple interrupts and + * susbsequent jobs will have no interrupts left to service. + */ + struct m4sensorhub_irqdata *irq_data = data; + wake_lock(&irq_data->wake_lock); + queue_work(irq_data->workqueue, &irq_data->work); + + return IRQ_HANDLED; +} + +static struct mrsensorhub_irq_dbg { + unsigned short en_ints[NUM_INT_REGS]; + unsigned char suspend; + unsigned char wakeup; +} irq_dbg_info; + +#ifdef CONFIG_DEBUG_FS +static const struct file_operations debug_fops = { + .open = m4sensorhub_dbg_irq_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; +#endif + +/* -------------- Global Functions ----------------- */ + +/* m4sensorhub_irq_init() + + Intialize M4 sensor hub IRQ subsystem + + Returns 0 on success. Returns negative error code on failure + + m4sensorhub - pointer to the main m4sensorhub data struct +*/ + +int m4sensorhub_irq_init(struct m4sensorhub_data *m4sensorhub) +{ + int retval; + struct i2c_client *i2c = m4sensorhub->i2c_client; + struct m4sensorhub_irqdata *data; + + data = kzalloc(sizeof(struct m4sensorhub_irqdata), GFP_KERNEL); + if (!data) { + KDEBUG(M4SH_ERROR, "m4sensorhub: Memory error in irq_init\n"); + retval = -ENOMEM; + goto done; + } + + KDEBUG(M4SH_INFO, "m4sensorhub: %u IRQs with valid_bits %02X%02X\n",\ + M4SH_IRQ__NUM, int_registers[1].valid_bits,\ + int_registers[0].valid_bits); + retval = m4sensorhub_irq_disable_all(m4sensorhub); + if (retval) { + KDEBUG(M4SH_ERROR, "m4sensorhub: Failed disable all irqs\n"); + goto err_free; + } + + data->workqueue = create_workqueue("m4sensorhub_irq"); + if (data->workqueue == NULL) { + KDEBUG(M4SH_ERROR, "m4sensorhub: IRQ Workqueue init failure\n"); + retval = -ENOMEM; + goto err_free; + } + INIT_WORK(&data->work, irq_work_func); + + mutex_init(&data->lock); + + wake_lock_init(&data->wake_lock, WAKE_LOCK_SUSPEND, "m4sensorhub-irq"); + + retval = request_irq(i2c->irq, event_isr, IRQF_DISABLED | + IRQF_TRIGGER_RISING, "m4sensorhub-irq", data); + if (retval) { + KDEBUG(M4SH_ERROR, "m4sensorhub: Failed requesting irq.\n"); + goto err_destroy_wq; + } + + data->m4sensorhub = m4sensorhub; + m4sensorhub->irqdata = data; + + retval = enable_irq_wake(i2c->irq); + if (retval) { + KDEBUG(M4SH_ERROR, "m4sensorhub: Failed enabling irq wake.\n"); + goto err_free_irq; + } + +#ifdef CONFIG_DEBUG_FS + data->debugfs = debugfs_create_file("m4sensorhub-irq", S_IRUGO, NULL, + data, &debug_fops); + if (data->debugfs == NULL) { + KDEBUG(M4SH_ERROR, "m4sensorhub: Error creating debufs\n"); + retval = -EINVAL; + goto err_disa_irq; + } +#endif + m4sensorhub_panic_register(m4sensorhub, PANICHDL_IRQ_RESTORE,\ + m4sensorhub_irq_restore, data); + KDEBUG(M4SH_INFO, "m4sensorhub IRQ subsystem initialized\n"); + retval = 0; + goto done; + +err_disa_irq: + disable_irq_wake(i2c->irq); +err_free_irq: + free_irq(i2c->irq, data); + m4sensorhub->irqdata = NULL; + data->m4sensorhub = NULL; +err_destroy_wq: + wake_lock_destroy(&data->wake_lock); + mutex_destroy(&data->lock); + destroy_workqueue(data->workqueue); +err_free: + kfree(data); +done: + return retval; +} +EXPORT_SYMBOL_GPL(m4sensorhub_irq_init); + +/* m4sensorhub_irq_shutdown() + + Shutdown the M4 sensor hub IRQ subsystem + + m4sensorhub - pointer to the main m4sensorhub data struct +*/ +void m4sensorhub_irq_shutdown(struct m4sensorhub_data *m4sensorhub) +{ + struct i2c_client *i2c = m4sensorhub->i2c_client; + struct m4sensorhub_irqdata *data = m4sensorhub->irqdata; + + KDEBUG(M4SH_INFO, "shutdown m4sensorhub IRQ subsystem\n"); + + m4sensorhub_panic_unregister(m4sensorhub, PANICHDL_IRQ_RESTORE); + +#ifdef CONFIG_DEBUG_FS + debugfs_remove(data->debugfs); +#endif + + disable_irq_wake(i2c->irq); + free_irq(i2c->irq, data); + + m4sensorhub->irqdata = NULL; + data->m4sensorhub = NULL; + + if (wake_lock_active(&data->wake_lock)) + wake_unlock(&data->wake_lock); + wake_lock_destroy(&data->wake_lock); + + if (mutex_is_locked(&data->lock)) + mutex_unlock(&data->lock); + mutex_destroy(&data->lock); + + cancel_work_sync(&data->work); + destroy_workqueue(data->workqueue); + + kfree(data); +} +EXPORT_SYMBOL_GPL(m4sensorhub_irq_shutdown); + +/* m4sensorhub_irq_register() + + Register an interupt handler in the M4 Sensor Hub IRQ subsystem. + This does not enable the IRQ, that needs to be done by caller + with m4sensorhub_irq_enable() + + Returns 0 on success. Returns negative error code on failure + + m4sensorhub - pointer to the main m4sensorhub data struct + irq - M4 Sensor Hub interupt to resiter for + cb_func - IRQ handler function to execute on inturrupt + data - pointer to data for IRQ handler function +*/ + +int m4sensorhub_irq_register(struct m4sensorhub_data *m4sensorhub, + enum m4sensorhub_irqs irq, + void (*cb_func) (enum m4sensorhub_irqs, void *), + void *data) +{ + struct m4sensorhub_irqdata *irqdata; + int retval = 0; + + if ((!m4sensorhub) || (irq >= M4SH_IRQ__NUM) || (!cb_func)) + return -EINVAL; + + irqdata = m4sensorhub->irqdata; + + mutex_lock(&irqdata->lock); + + if (irqdata->event_handler[irq].func == NULL) { + irqdata->irq_info[irq].registered = 1; + irqdata->event_handler[irq].func = cb_func; + irqdata->event_handler[irq].data = data; + KDEBUG(M4SH_NOTICE, "m4sensorhub: %s IRQ registered\n", + irq_name[irq]); + } else { + KDEBUG(M4SH_ERROR, "m4sensorhub: %s IRQ registration failed\n", + irq_name[irq]); + retval = -EPERM; + } + + mutex_unlock(&irqdata->lock); + + return retval; +} +EXPORT_SYMBOL_GPL(m4sensorhub_irq_register); + +/* m4sensorhub_irq_unregister() + + Unregister an interupt handler in the M4 Sensor Hub IRQ subsystem + + Returns 0 on success. Returns negative error code on failure + + m4sensorhub - pointer to the main m4sensorhub data struct + irq - M4 Sensor Hub interupt to unresiter for +*/ +int m4sensorhub_irq_unregister(struct m4sensorhub_data *m4sensorhub, + enum m4sensorhub_irqs irq) +{ + struct m4sensorhub_irqdata *data = m4sensorhub->irqdata; + int retval; + + if (irq >= M4SH_IRQ__NUM) + return -EINVAL; + + retval = m4sensorhub_irq_disable(m4sensorhub, irq); + + mutex_lock(&data->lock); + data->event_handler[irq].func = NULL; + data->event_handler[irq].data = NULL; + data->irq_info[irq].registered = 0; + mutex_unlock(&data->lock); + + KDEBUG(M4SH_NOTICE, "m4sensorhub: %s IRQ un-registered\n", + irq_name[irq]); + + return retval; +} +EXPORT_SYMBOL_GPL(m4sensorhub_irq_unregister); + +/* m4sensorhub_irq_enable_get() + + Check if an IRQ is enabled + + Returns 1 if enabled, 0 if disabled. + Returns negative error code on failure + + m4sensorhub - pointer to the main m4sensorhub data struct + irq - M4 Sensor Hub interupt to check +*/ + +int m4sensorhub_irq_enable_get(struct m4sensorhub_data *m4sensorhub, + enum m4sensorhub_irqs irq) +{ + struct m4sensorhub_irqdata *data = m4sensorhub->irqdata; + int retval = -EINVAL; + + if (irq < M4SH_IRQ__NUM) + return data->irq_info[irq].enabled; + + return retval; +} +EXPORT_SYMBOL_GPL(m4sensorhub_irq_enable_get); + +/* m4sensorhub_irq_disable() + + Disable M4 Sensor Hub subsystem IRQ + + Returns 0 on success. Returns negative error code on failure + + m4sensorhub - pointer to the main m4sensorhub data struct + irq - M4 Sensor Hub interupt to disable +*/ + +int m4sensorhub_irq_disable(struct m4sensorhub_data *m4sensorhub, + enum m4sensorhub_irqs irq) +{ + struct m4sensorhub_irqdata *data = m4sensorhub->irqdata; + int retval = -EINVAL; + + if (irq < M4SH_IRQ__NUM) { + mutex_lock(&data->lock); + data->irq_info[irq].enabled = 0; + mutex_unlock(&data->lock); + retval = m4sensorhub_reg_write_1byte(m4sensorhub, + get_enable_reg(irq), 0, EVENT_MASK(irq)); + retval = CHECK_REG_ACCESS_RETVAL(m4sensorhub, retval, + get_enable_reg(irq)); + } + + return retval; +} +EXPORT_SYMBOL_GPL(m4sensorhub_irq_disable); + +/* m4sensorhub_irq_enable() + + Enable M4 Sensor Hub subsystem IRQ + + Returns 0 on success. Returns negative error code on failure + + m4sensorhub - pointer to the main m4sensorhub data struct + irq - M4 Sensor Hub interupt to enable +*/ + +int m4sensorhub_irq_enable(struct m4sensorhub_data *m4sensorhub, + enum m4sensorhub_irqs irq) +{ + struct m4sensorhub_irqdata *data = m4sensorhub->irqdata; + int retval = -EINVAL; + + if (irq < M4SH_IRQ__NUM) { + mutex_lock(&data->lock); + data->irq_info[irq].enabled = 1; + mutex_unlock(&data->lock); + retval = m4sensorhub_reg_write_1byte(m4sensorhub, + get_enable_reg(irq), EVENT_MASK(irq), + EVENT_MASK(irq)); + retval = CHECK_REG_ACCESS_RETVAL(m4sensorhub, retval, + get_enable_reg(irq)); + } + + return retval; +} +EXPORT_SYMBOL_GPL(m4sensorhub_irq_enable); + +/* m4sensorhub_irq_pm_suspend() + + Called by core to track suspend state and wakeup cause + +*/ + +void m4sensorhub_irq_pm_dbg_suspend(void) +{ + irq_dbg_info.suspend = 1; + irq_dbg_info.wakeup = 0; +} +EXPORT_SYMBOL_GPL(m4sensorhub_irq_pm_dbg_suspend); + +/* m4sensorhub_irq_pm_resume() + + Called by core to print interupt source on M4SH wakeup + +*/ + +void m4sensorhub_irq_pm_dbg_resume(void) +{ + char buffer[DBG_BUF_LINE_LEN]; + int i; + + irq_dbg_info.suspend = 0; + if ((irq_dbg_info.wakeup != 0) && (m4sensorhub_debug >= M4SH_NOTICE)) { + strcpy(buffer, "M4 Sensor Hub IRQ registers:"); + for (i = 0; (i < NUM_INT_REGS) && + (strlen(buffer) < DBG_BUF_LINE_LEN-5); ++i) { + sprintf(&buffer[strlen(buffer)], " 0x%02x", + irq_dbg_info.en_ints[i]); + } + + KDEBUG(M4SH_NOTICE, "newbuf: %s\n", buffer); + + /* Decode the bits */ + KDEBUG(M4SH_NOTICE, "M4 Sensor Hub IRQ sources:\n"); + for (i = 0; i < NUM_INT_REGS; ++i) { + unsigned char index; + + while (irq_dbg_info.en_ints[i] > 0) { + /* find the first set bit */ + index = (unsigned char) + (ffs(irq_dbg_info.en_ints[i]) - 1); + if (index >= M4SH_IRQ__NUM) + goto error; + + /* clear the bit */ + irq_dbg_info.en_ints[i] &= ~(1 << index); + /* find the event that occurred */ + index += M4SH_IRQ__START + + (i * NUM_INTS_PER_REG); + if (index >= M4SH_IRQ__NUM) + goto error; + + KDEBUG(M4SH_NOTICE, "\t%s\n", irq_name[index]); + } + } + } +error: + return; +} +EXPORT_SYMBOL_GPL(m4sensorhub_irq_pm_dbg_resume); + +/* --------------- Local Functions ----------------- */ + +static unsigned short get_enable_reg(enum m4sensorhub_irqs event) +{ + unsigned short ret; + + if ((event) >= M4SH_IRQ__NUM) + ret = M4SH_REG__INVALID; + else if ((event) >= M4SH_IRQ_INT1_INDEX) + ret = M4SH_REG_GENERAL_INTERRUPT1ENABLE; + else if ((event) >= M4SH_IRQ_INT0_INDEX) + ret = M4SH_REG_GENERAL_INTERRUPT0ENABLE; + else + ret = M4SH_REG__INVALID; + + return ret; +} + +static int m4sensorhub_irq_disable_all(struct m4sensorhub_data *m4sensorhub) +{ + int i; + + for (i = 0; i < NUM_INT_REGS; i++) { + if (1 != m4sensorhub_reg_write_1byte(m4sensorhub, + int_registers[i].enable_reg, 0, + int_registers[i].valid_bits)) { + KDEBUG(M4SH_ERROR, "m4sensorhub_irq: " + "Failed disabling INT%d\n", i); + return -EFAULT; + } + } + return 0; +} + +static void irq_work_func(struct work_struct *work) +{ + unsigned short en_ints[NUM_INT_REGS] = { 0 }; + int i; + struct m4sensorhub_irqdata *data; + struct m4sensorhub_data *m4sensorhub; + struct i2c_client *i2c; + unsigned char value, is_irq_set = 0; + + data = container_of(work, struct m4sensorhub_irqdata, work); + m4sensorhub = data->m4sensorhub; + i2c = m4sensorhub->i2c_client; + + for (i = 0; i < NUM_INT_REGS; ++i) { + /* M4 is expected to clear these bits when read */ + if (1 != m4sensorhub_reg_read(m4sensorhub, + int_registers[i].status_reg, &value)) { + dev_err(&m4sensorhub->i2c_client->dev, + "Error reading INT%d\n", i); + goto error; + } + en_ints[i] = value; + is_irq_set |= value; + } + + if (!is_irq_set) { + /* Got the checkpoint to check if M4 panicked */ + m4sensorhub_panic_process(m4sensorhub); + goto error; + } + + if ((irq_dbg_info.suspend != 0) && (irq_dbg_info.wakeup == 0)) { + for (i = 0; i < NUM_INT_REGS; ++i) + irq_dbg_info.en_ints[i] = en_ints[i]; + irq_dbg_info.wakeup = 1; + } + + for (i = 0; i < NUM_INT_REGS; ++i) { + unsigned char index; + + while (en_ints[i] > 0) { + struct m4sensorhub_event_handler *event_handler; + + /* find the first set bit */ + index = (unsigned char)(ffs(en_ints[i]) - 1); + if (index >= M4SH_IRQ__NUM) + goto error; + /* clear the bit */ + en_ints[i] &= ~(1 << index); + /* find the event that occurred */ + index += M4SH_IRQ__START + (i * NUM_INTS_PER_REG); + if (index >= M4SH_IRQ__NUM) + goto error; + + if (data->irq_info[index].enabled) { + event_handler = &data->event_handler[index]; + + if (event_handler && event_handler->func) + event_handler->func(index, + event_handler->data); + + mutex_lock(&data->lock); + data->irq_info[index].ena_fired++; + mutex_unlock(&data->lock); + } else { + mutex_lock(&data->lock); + data->irq_info[index].disa_fired++; + mutex_unlock(&data->lock); + } + } + } +error: + wake_unlock(&data->wake_lock); +} + +#ifdef CONFIG_DEBUG_FS +static int m4sensorhub_dbg_irq_show(struct seq_file *s, void *data) +{ + unsigned int i; + struct m4sensorhub_irqdata *irqdata = s->private; + + seq_printf(s, "%21s%9s%12s%15s%16s\n", + "M4SENSORHUB IRQ", "Enabled", "Registered", + "Fired Enabled", "Fired Disabled"); + + for (i = 0; i < M4SH_IRQ__NUM; i++) { + seq_printf(s, "%21s%9d%12d%15d%16d\n", + irq_name[i], + irqdata->irq_info[i].enabled, + irqdata->irq_info[i].registered, + irqdata->irq_info[i].ena_fired, + irqdata->irq_info[i].disa_fired); + } + return 0; +} + +static int m4sensorhub_dbg_irq_open(struct inode *inode, struct file *file) +{ + return single_open(file, m4sensorhub_dbg_irq_show, inode->i_private); +} +#endif + +/* m4sensorhub_irq_restore() + + Callback Handler is called by Panic after M4 has been restarted + +*/ +static void m4sensorhub_irq_restore(\ + struct m4sensorhub_data *m4sensorhub, void *data) +{ + int i; + unsigned short en_ints[NUM_INT_REGS] = {0}; + + mutex_lock(&((struct m4sensorhub_irqdata *)data)->lock); + for (i = 0; i < M4SH_IRQ__NUM; i++) { + if (!((struct m4sensorhub_irqdata *)data)->irq_info[i].enabled) + continue; + en_ints[i/NUM_INTS_PER_REG] |= EVENT_MASK(i); + } + mutex_unlock(&((struct m4sensorhub_irqdata *)data)->lock); + + for (i = 0; i < NUM_INT_REGS; i++) { + KDEBUG(M4SH_INFO, "m4sensorhub_irq: Reseting INT%d-%02X\n",\ + i, en_ints[i]); + if (1 != m4sensorhub_reg_write_1byte(m4sensorhub, + int_registers[i].enable_reg, en_ints[i], + int_registers[i].valid_bits)) { + KDEBUG(M4SH_ERROR, "m4sensorhub_irq: " + "Failed reseting INT%d\n", i); + } + } +} diff --git a/drivers/mfd/m4sensorhub-panic.c b/drivers/mfd/m4sensorhub-panic.c new file mode 100644 index 00000000000..aaf35f89886 --- /dev/null +++ b/drivers/mfd/m4sensorhub-panic.c @@ -0,0 +1,248 @@ +/* + * 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 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 <linux/module.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/m4sensorhub.h> +#include <linux/slab.h> + + + +/* --------------- Global Declarations -------------- */ +#define PANIC_BANK 0xFF /* Reserved for Panic bank */ +#define PANIC_CMD_CHECK 0xCD /* Panic Handoff command */ +#define PANIC_RESP_CHECK 0xDeadBeef /* Panic Handoff Magic code */ + +/* ------------ Local Function Prototypes ----------- */ + +/* --------------- Local Declarations -------------- */ +static const char *callback_name[PANICHDL_MAX] = { + [PANICHDL_IRQ_RESTORE] = "irq_restore", +}; + +struct m4sensorhub_panic_callback { + void (*callback)(struct m4sensorhub_data *, void *); + void *data; +}; + +struct m4sensorhub_panicdata { + struct mutex lock; /* lock callback and data */ + struct m4sensorhub_panic_callback funcs[PANICHDL_MAX]; +}; + +union panic_buf { + struct _in { + unsigned char bank; + unsigned char cmd; + } in; + unsigned int data; +}; + +/* -------------- Local Data Structures ------------- */ + +/* -------------- Global Functions ----------------- */ + +/* m4sensorhub_panic_init() + + Initialized panic private data structures. + + Returns 0 on success or negative error code on failure + + m4sensorhub - pointer to the main m4sensorhub data struct +*/ +int m4sensorhub_panic_init(struct m4sensorhub_data *m4sensorhub) +{ + int retval = 0; + struct m4sensorhub_panicdata *data; + + data = kzalloc(sizeof(struct m4sensorhub_panicdata), GFP_KERNEL); + if (data) { + mutex_init(&data->lock); + m4sensorhub->panicdata = data; + } else { + KDEBUG(M4SH_ERROR, "m4sensorhub: Memory error in panic_init\n"); + retval = -ENOMEM; + } + return retval; +} +EXPORT_SYMBOL_GPL(m4sensorhub_panic_init); + +/* m4sensorhub_panic_shutdown() + + Shutdown the M4 sensor hub Panic subsystem + + m4sensorhub - pointer to the main m4sensorhub data struct +*/ +void m4sensorhub_panic_shutdown(struct m4sensorhub_data *m4sensorhub) +{ + if (m4sensorhub && m4sensorhub->panicdata) { + struct m4sensorhub_panicdata *data = m4sensorhub->panicdata; + m4sensorhub->panicdata = NULL; + if (mutex_is_locked(&data->lock)) + mutex_unlock(&data->lock); + mutex_destroy(&data->lock); + kfree(data); + } +} +EXPORT_SYMBOL_GPL(m4sensorhub_panic_shutdown); + +/* m4sensorhub_panic_register() + + Register an panic handler to monitor M4 panic reset + + Returns 0 on success or negative error code on failure + + m4sensorhub - pointer to the main m4sensorhub data struct + index - M4 Sensor Hub panic handler to resiter for + cb_func - panic handler function to execute after M4 reset + data - pointer to data for panic handler function +*/ + +int m4sensorhub_panic_register(struct m4sensorhub_data *m4sensorhub, + enum m4sensorhub_panichdl_index index, + void (*cb_func) (struct m4sensorhub_data *, void *), + void *data) +{ + struct m4sensorhub_panicdata *panicdata; + int retval = 0; + + if (!m4sensorhub || (index >= PANICHDL_MAX) || !cb_func) + return -EINVAL; + + panicdata = (struct m4sensorhub_panicdata *)m4sensorhub->panicdata; + mutex_lock(&panicdata->lock); + if (panicdata->funcs[index].callback == NULL) { + panicdata->funcs[index].callback = cb_func; + panicdata->funcs[index].data = data; + KDEBUG(M4SH_NOTICE, "m4sensorhub: %s callback registered\n", + callback_name[index]); + } else { + KDEBUG(M4SH_ERROR, "m4sensorhub: %s callback"\ + " registration failed\n", callback_name[index]); + retval = -EPERM; + } + mutex_unlock(&panicdata->lock); + + return retval; +} +EXPORT_SYMBOL_GPL(m4sensorhub_panic_register); + +/* m4sensorhub_panic_unregister() + + Unregister an panic handler to monitor M4 panic reset + + Returns 0 on success or negative error code on failure + + m4sensorhub - pointer to the main m4sensorhub data struct + index - M4 Sensor Hub panic handler to unresiter for +*/ +int m4sensorhub_panic_unregister(struct m4sensorhub_data *m4sensorhub, + enum m4sensorhub_panichdl_index index) +{ + struct m4sensorhub_panicdata *panicdata; + + if (!m4sensorhub || (index >= PANICHDL_MAX)) + return -EINVAL; + + panicdata = (struct m4sensorhub_panicdata *)m4sensorhub->panicdata; + mutex_lock(&panicdata->lock); + panicdata->funcs[index].callback = NULL; + panicdata->funcs[index].data = NULL; + mutex_unlock(&panicdata->lock); + KDEBUG(M4SH_NOTICE, "m4sensorhub: %s callback un-registered\n", + callback_name[index]); + + return 0; +} +EXPORT_SYMBOL_GPL(m4sensorhub_panic_unregister); + + +/* m4sensorhub_panic_process() + + Check M4 if it's panicked, use I2C to communicate with M4 panic handler + OMAP use the same i2c sequences to send command via i2c master, then M4 + i2c slave program will handle these commands, it may have 2 slave programs + 1. Normal i2c slave program handles all vaild banks'(limit on + M4SH_TYPE__NUM) command, for invalid bank, it always responses 0xFF + 2. Panic i2c slave program handles panic bank(reserved 0xFF for it) command, + for others, it always responses 0x00 + + To detect whether M4 is panicked, the process should be + i. When OMAP got interrupt from M4, OMAP will check which irq is raised, it + send normal banks' command to M4, for panic case, it always returns 0x00, + so OMAP has a checkpoint as there's interrupt request from M4 without + active IRQ + ii.Then OMAP will confirm if M4 is panic via send panic bank command, if M4 + is panicked, it will handle this bank and response panic magic code; + Otherwise, if no panic magic code returned from M4, it always means M4 + isn't panicked. + + m4sensorhub - pointer to the main m4sensorhub data struct + */ +void m4sensorhub_panic_process(struct m4sensorhub_data *m4sensorhub) +{ + int i, ret; + union panic_buf buf; + struct m4sensorhub_panic_callback handler; + + if (!m4sensorhub || !m4sensorhub->panicdata) { + KDEBUG(M4SH_ERROR, "m4sensorhub: Invalid parameter in %s!\n",\ + __func__); + return; + } + + m4sensorhub_reg_access_lock(); + + buf.in.bank = PANIC_BANK; + buf.in.cmd = PANIC_CMD_CHECK; + ret = m4sensorhub_i2c_write_read(m4sensorhub,\ + (u8 *)&buf, sizeof(buf.in), sizeof(buf.data)); + if ((ret != sizeof(buf.data)) || (buf.data != PANIC_RESP_CHECK)) { + /* TODO maybe we shall check if M4/OMAP i2c broken */ + KDEBUG(M4SH_ERROR, "m4sensorhub: Unknown IRQ status! "\ + "M4 panic handoff ret=%d, data=0x%x\n",\ + ret, buf.data); + m4sensorhub_reg_access_unlock(); + return; + } + + KDEBUG(M4SH_ERROR, "m4sensorhub_panic: Detected M4 panic, reset M4!\n"); + m4sensorhub->pdev->hw_reset(m4sensorhub); + msleep(100); + ret = m4sensorhub_load_firmware(m4sensorhub, 0); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "m4sensorhub_panic: "\ + "Failed to restart M4, ret = %d\n", ret); + BUG(); + } + + m4sensorhub_reg_access_unlock(); + + for (i = 0; i < PANICHDL_MAX; i++) { + handler = ((struct m4sensorhub_panicdata *)\ + (m4sensorhub->panicdata))->funcs[i]; + if (handler.callback) { + KDEBUG(M4SH_NOTICE, "m4sensorhub_panic: "\ + "Calling %s as M4 restarted!\n",\ + callback_name[i]); + handler.callback(m4sensorhub, handler.data); + } + } +} +EXPORT_SYMBOL_GPL(m4sensorhub_panic_process); diff --git a/drivers/mfd/m4sensorhub-reg.c b/drivers/mfd/m4sensorhub-reg.c new file mode 100644 index 00000000000..cb9e8b1b371 --- /dev/null +++ b/drivers/mfd/m4sensorhub-reg.c @@ -0,0 +1,384 @@ +/* + * 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 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 <linux/module.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/m4sensorhub.h> +#include <linux/slab.h> + +#include "m4sensorhub-reg.h" /* auto-generated header defining registers */ + +#define I2C_RETRY_DELAY 5 +#define I2C_RETRIES 5 + +#define DEBUG_LINE_LENGTH 80 + +/* --------------- Global Declarations -------------- */ + +/* ------------ Local Function Prototypes ----------- */ +static int m4sensorhub_mapsize(enum m4sensorhub_reg reg); + +/* --------------- Local Declarations -------------- */ +static DEFINE_MUTEX(reg_access); + +/* -------------- Local Data Structures ------------- */ + +/* -------------- Global Functions ----------------- */ + +/* m4sensorhub_reg_init() + + Initialized register access data structures. + + Returns 0 on success or negative error code on failure + + m4sensorhub - pointer to the main m4sensorhub data struct +*/ + +int m4sensorhub_reg_init(struct m4sensorhub_data *m4sensorhub) +{ + return 0; +} +EXPORT_SYMBOL_GPL(m4sensorhub_reg_init); + +/* m4sensorhub_reg_shutdown() + + Clean up register subsystem on driver removal + + Returns 0 on success or negative error code on failure + + m4sensorhub - pointer to the main m4sensorhub data struct +*/ + +int m4sensorhub_reg_shutdown(struct m4sensorhub_data *m4sensorhub) +{ + return 0; +} +EXPORT_SYMBOL_GPL(m4sensorhub_reg_shutdown); + +/* m4sensorhub_reg_read_n() + + Read a n bytes from the M4 sensor hub starting at 'register'. Use + m4sensorhub_reg_read() instead where possible; + + Returns number of bytes read on success. + Returns negative error code on failure + + m4sensorhub - pointer to the main m4sensorhub data struct + reg - Register to be read + value - array to return data. Needs to be at least register's size + num - number of bytes to read +*/ + +int m4sensorhub_reg_read_n(struct m4sensorhub_data *m4sensorhub, + enum m4sensorhub_reg reg, unsigned char *value, + short num) +{ + int ret = -EINVAL; + u8 stack_buf[M4SH_MAX_STACK_BUF_SIZE]; + + if (!m4sensorhub || !value || !num) { + KDEBUG(M4SH_ERROR, "%s() invalid parameter\n", __func__); + return ret; + } + + if ((reg < M4SH_REG__NUM) && num <= M4SH_MAX_REG_SIZE &&\ + register_info_tbl[reg].offset + num <= + m4sensorhub_mapsize(reg)) { + u8 *buf = (num > (M4SH_MAX_STACK_BUF_SIZE-2))\ + ? kmalloc(num+2, GFP_KERNEL) : stack_buf; + if (!buf) { + KDEBUG(M4SH_ERROR, "%s() Failed alloc %d memeory\n"\ + , __func__, num+2); + return -ENOMEM; + } + buf[0] = register_info_tbl[reg].type; + buf[1] = register_info_tbl[reg].offset; + + mutex_lock(®_access); + ret = m4sensorhub_i2c_write_read(m4sensorhub, buf, 2, num); + mutex_unlock(®_access); + + if (ret != num) + KDEBUG(M4SH_ERROR, "%s() read failure\n", __func__); + else + memcpy(value, buf, num); + if (buf != stack_buf) + kfree(buf); + } else { + KDEBUG(M4SH_ERROR, "%s() invalid register access reg=%d " + "maxreg=%d size=%d maxsze=%d mapsize=%d\n", __func__, + reg, M4SH_REG__NUM, num, M4SH_MAX_REG_SIZE, + m4sensorhub_mapsize(reg)); + } + return ret; +} +EXPORT_SYMBOL_GPL(m4sensorhub_reg_read_n); + +/* m4sensorhub_reg_write() + + Write data to a register in the M4 sensor hub. Use + m4sensorhub_reg_write() instead where possible; + + Returns number of bytes written on success. + Returns negative error code on failure + + m4sensorhub - pointer to the main m4sensorhub data struct + reg - Register to be written to + value - array of data to write. Needs to be at least register's size + mask - mask representing which bits to change in register. If all bits + are to be changed, then &m4sh_no_mask can be passed here. + num - number of bytes to write +*/ + +int m4sensorhub_reg_write_n(struct m4sensorhub_data *m4sensorhub, + enum m4sensorhub_reg reg, unsigned char *value, + unsigned char *mask, short num) +{ + int i, ret = -EINVAL; + u8 stack_buf[M4SH_MAX_STACK_BUF_SIZE]; + + if (!m4sensorhub || !value || !num) { + KDEBUG(M4SH_ERROR, "%s() invalid parameter\n", __func__); + return ret; + } + + if ((reg < M4SH_REG__NUM) && num <= M4SH_MAX_REG_SIZE &&\ + register_info_tbl[reg].offset + num <= + m4sensorhub_mapsize(reg)) { + u8 *buf = (num > (M4SH_MAX_STACK_BUF_SIZE-2))\ + ? kmalloc(num+2, GFP_KERNEL) : stack_buf; + if (!buf) { + KDEBUG(M4SH_ERROR, "%s() Failed alloc %d memeory\n"\ + , __func__, num+2); + return -ENOMEM; + } + + buf[0] = register_info_tbl[reg].type; + buf[1] = register_info_tbl[reg].offset; + + mutex_lock(®_access); + if (mask) { + ret = m4sensorhub_i2c_write_read(m4sensorhub, buf, + 2, num); + if (ret != num) { + KDEBUG(M4SH_ERROR, "%s() register read" + "failure\n", __func__); + goto error; + } + /* move data right 2 positions and apply mask and + new data to prepare for writeback */ + for (i = num-1; i >= 0; i--) { + buf[i+2] = (buf[i] & ~mask[i]) | + (value[i] & mask[i]); + } + buf[0] = register_info_tbl[reg].type; + buf[1] = register_info_tbl[reg].offset; + } else + memcpy(&buf[2], value, num); + + ret = m4sensorhub_i2c_write_read(m4sensorhub, buf, + num + 2, 0); + if (ret != num + 2) { + KDEBUG(M4SH_ERROR, "%s() register write failure\n", + __func__); + ret = -EINVAL; + } else + ret -= 2; + +error: mutex_unlock(®_access); + if (buf != stack_buf) + kfree(buf); + } else { + KDEBUG(M4SH_ERROR, "%s() invalid register access reg=%d" + " maxreg=%d num=%d maxsze=%d mapsize=%d\n", __func__, + reg, M4SH_REG__NUM, num, M4SH_MAX_REG_SIZE, + m4sensorhub_mapsize(reg)); + } + + return ret; +} +EXPORT_SYMBOL_GPL(m4sensorhub_reg_write_n); + +/* m4sensorhub_reg_write_1byte() + + Write data to a 1 byte register in the M4 sensor hub. Avoids need to pass + data and mask by reference + + Returns number of bytes written on success. + Returns negative error code on failure + + m4sensorhub - pointer to the main m4sensorhub data struct + reg - Register to be written to + value - byte of data to write + mask - mask representing which bits to change in register. +*/ + +int m4sensorhub_reg_write_1byte(struct m4sensorhub_data *m4sensorhub, + enum m4sensorhub_reg reg, unsigned char value, + unsigned char mask) +{ + if (register_info_tbl[reg].size == 1) + return m4sensorhub_reg_write(m4sensorhub, reg, &value, &mask); + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(m4sensorhub_reg_write_1byte); + +/* m4sensorhub_reg_getsize() + + Get the size of an M4 register + + Returns size of register on success + Returns negative error code on failure + + m4sensorhub - pointer to the main m4sensorhub data struct + reg - Register to get size of +*/ + +int m4sensorhub_reg_getsize(struct m4sensorhub_data *m4sensorhub, + enum m4sensorhub_reg reg) +{ + if (reg < M4SH_REG__NUM) + return register_info_tbl[reg].size; + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(m4sensorhub_reg_getsize); + +/* m4sensorhub_reg_access_lock() + + Lock reg access to avoid broken I2C transmit process + +*/ + +void m4sensorhub_reg_access_lock(void) +{ + mutex_lock(®_access); +} +EXPORT_SYMBOL_GPL(m4sensorhub_reg_access_lock); + +/* m4sensorhub_reg_access_unlock() + + Unlock reg access to wake up blocked I2C transmit process + +*/ + +void m4sensorhub_reg_access_unlock(void) +{ + mutex_unlock(®_access); +} +EXPORT_SYMBOL_GPL(m4sensorhub_reg_access_unlock); + +/* m4sensorhub_i2c_write_read() + + Directly I2C access to communicate with the M4 sensor hub. + It always read after write if both write and read length are non-zero + + Returns number of bytes write on success if readlen is zero + Returns number of bytes read on success if readlen is non-zero + Returns negative error code on failure + + m4sensorhub - pointer to the main m4sensorhub data struct + buf - buffer to be used for write to and read from + writelen - number of bytes write to + readlen - number of bytes read from +*/ + +int m4sensorhub_i2c_write_read(struct m4sensorhub_data *m4sensorhub, + u8 *buf, int writelen, int readlen) +{ + int i, msglen, msgstart, err, tries = 0; + char buffer[DEBUG_LINE_LENGTH]; + struct i2c_msg msgs[] = { + { + .addr = m4sensorhub->i2c_client->addr, + .flags = m4sensorhub->i2c_client->flags, + .len = writelen, + .buf = buf, + }, + { + .addr = m4sensorhub->i2c_client->addr, + .flags = m4sensorhub->i2c_client->flags | I2C_M_RD, + .len = readlen, + .buf = buf, + }, + }; + + if (buf == NULL || (writelen == 0 && readlen == 0)) + return -EFAULT; + + /* Offset and size in msgs array depending on msg type */ + msglen = (writelen && readlen) ? 2 : 1; + msgstart = writelen ? 0 : 1; + + if (m4sensorhub_debug >= M4SH_VERBOSE_DEBUG && writelen) { + sprintf(buffer, "Writing to M4:"); + for (i = 0; i < writelen; i++) { + if (strlen(buffer) >= DEBUG_LINE_LENGTH-5) { + KDEBUG(M4SH_VERBOSE_DEBUG, "%s\n", buffer); + buffer[0] = '\0'; + } + sprintf(&buffer[strlen(buffer)], " 0x%02x", buf[i]); + } + KDEBUG(M4SH_VERBOSE_DEBUG, "%s\n", buffer); + } + + do { + err = i2c_transfer(m4sensorhub->i2c_client->adapter, + &msgs[msgstart], msglen); + if (err != msglen) + msleep_interruptible(I2C_RETRY_DELAY); + } while ((err != msglen) && (++tries < I2C_RETRIES)); + if (err != msglen) { + dev_err(&m4sensorhub->i2c_client->dev, "i2c transfer error; " + "type=%d offset=%d\n", buf[0], buf[1]); + err = -EIO; + } else { + err = (readlen ? readlen : writelen); + + if (m4sensorhub_debug >= M4SH_VERBOSE_DEBUG && readlen) { + sprintf(buffer, "Read from M4:"); + for (i = 0; i < readlen; i++) { + if (strlen(buffer) >= DEBUG_LINE_LENGTH-5) { + KDEBUG(M4SH_VERBOSE_DEBUG, "%s\n", + buffer); + buffer[0] = '\0'; + } + sprintf(&buffer[strlen(buffer)], " 0x%02x", + buf[i]); + } + KDEBUG(M4SH_VERBOSE_DEBUG, "%s\n", buffer); + } + } + return err; +} +EXPORT_SYMBOL_GPL(m4sensorhub_i2c_write_read); + +/* -------------- Local Functions ----------------- */ + +static int m4sensorhub_mapsize(enum m4sensorhub_reg reg) +{ + int retval = -EINVAL; + + if (reg < M4SH_REG__NUM) + retval = bank_size_tbl[register_info_tbl[reg].type]; + + return retval; +} diff --git a/drivers/mfd/m4sensorhub-reg.h b/drivers/mfd/m4sensorhub-reg.h new file mode 100644 index 00000000000..087726c064e --- /dev/null +++ b/drivers/mfd/m4sensorhub-reg.h @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2013, Motorola, Inc. All Rights Reserved. + * + * 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, see <http://www.gnu.org/licenses/>. + * + */ + +/***************************** WARNING ****************************** + * * + * DO NOT EDIT THIS FILE * + * * + * This is an auto-generated file based on register maps in the * + * M4 Sensor Hub source tree. * + * * + ***************************** WARNING *****************************/ + +#ifndef __M4SENSORHUB_MEM_INTERNAL_H__ +#define __M4SENSORHUB_MEM_INTERNAL_H__ +static const struct { + enum m4sensorhub_type type; /* Type of register */ + unsigned short offset; /* Offset into type's memory */ + unsigned short size; /* Size of register in bytes */ +} register_info_tbl[M4SH_REG__NUM] = { + [M4SH_REG_ACCEL_VERSION] = {M4SH_TYPE_ACCEL, 0x0, 1}, + [M4SH_REG_ACCEL_DUMMY] = {M4SH_TYPE_ACCEL, 0x1, 1}, + [M4SH_REG_ACCEL_SAMPLERATE] = {M4SH_TYPE_ACCEL, 0x2, 2}, + [M4SH_REG_ACCEL_X] = {M4SH_TYPE_ACCEL, 0x4, 4}, + [M4SH_REG_ACCEL_Y] = {M4SH_TYPE_ACCEL, 0x8, 4}, + [M4SH_REG_ACCEL_Z] = {M4SH_TYPE_ACCEL, 0xc, 4}, + [M4SH_REG_ACCEL_SCALEDMAGSQUARED] = {M4SH_TYPE_ACCEL, 0x10, 4}, + [M4SH_REG_ACCEL_MAGNITUDE] = {M4SH_TYPE_ACCEL, 0x14, 4}, + [M4SH_REG_ACCEL_TILT] = {M4SH_TYPE_ACCEL, 0x18, 2}, + [M4SH_REG_ACCEL_ORIENTATION] = {M4SH_TYPE_ACCEL, 0x1a, 2}, + [M4SH_REG_TEMP_VERSION] = {M4SH_TYPE_TEMP, 0x0, 1}, + [M4SH_REG_TEMP_DUMMY] = {M4SH_TYPE_TEMP, 0x1, 1}, + [M4SH_REG_TEMP_SAMPLERATE] = {M4SH_TYPE_TEMP, 0x2, 2}, + [M4SH_REG_TEMP_EXTRNLTEMP] = {M4SH_TYPE_TEMP, 0x4, 2}, + [M4SH_REG_TEMP_INTRNLTEMP] = {M4SH_TYPE_TEMP, 0x6, 2}, + [M4SH_REG_GENERAL_UTC] = {M4SH_TYPE_GENERAL, 0x0, 4}, + [M4SH_REG_GENERAL_LOCALTIMEZONE] = {M4SH_TYPE_GENERAL, 0x4, 2}, + [M4SH_REG_GENERAL_VERSION] = {M4SH_TYPE_GENERAL, 0x6, 1}, + [M4SH_REG_GENERAL_INTERRUPT0ENABLE] = {M4SH_TYPE_GENERAL, 0x7, 1}, + [M4SH_REG_GENERAL_INTERRUPT1ENABLE] = {M4SH_TYPE_GENERAL, 0x8, 1}, + [M4SH_REG_GENERAL_INTERRUPT0STATUS] = {M4SH_TYPE_GENERAL, 0x9, 1}, + [M4SH_REG_GENERAL_INTERRUPT1STATUS] = {M4SH_TYPE_GENERAL, 0xa, 1}, + [M4SH_REG_PRESSURE_VERSION] = {M4SH_TYPE_PRESSURE, 0x0, 1}, + [M4SH_REG_PRESSURE_DUMMY] = {M4SH_TYPE_PRESSURE, 0x1, 1}, + [M4SH_REG_PRESSURE_SAMPLERATE] = {M4SH_TYPE_PRESSURE, 0x2, 2}, + [M4SH_REG_PRESSURE_PRESSURE] = {M4SH_TYPE_PRESSURE, 0x4, 4}, + [M4SH_REG_PRESSURE_REFERENCEALTITUDE] = {M4SH_TYPE_PRESSURE, 0x8, 4}, + [M4SH_REG_PRESSURE_SEALEVELPRESSURE] = {M4SH_TYPE_PRESSURE, 0xc, 4}, + [M4SH_REG_PRESSURE_ABSOLUTEALTITUDE] = {M4SH_TYPE_PRESSURE, 0x10, 4}, + [M4SH_REG_PRESSURE_TEMPERATURE] = {M4SH_TYPE_PRESSURE, 0x14, 2}, + [M4SH_REG_PRESSURE_ISVALID] = {M4SH_TYPE_PRESSURE, 0x16, 1}, + [M4SH_REG_PEDOMETER_VERSION] = {M4SH_TYPE_PEDOMETER, 0x0, 1}, + [M4SH_REG_PEDOMETER_TESTCMD] = {M4SH_TYPE_PEDOMETER, 0x1, 1}, + [M4SH_REG_PEDOMETER_ACTIVITY] = {M4SH_TYPE_PEDOMETER, 0x2, 1}, + [M4SH_REG_PEDOMETER_EQUIPMENTTYPE] = {M4SH_TYPE_PEDOMETER, 0x3, 1}, + [M4SH_REG_PEDOMETER_TOTALSTEPS] = {M4SH_TYPE_PEDOMETER, 0x4, 2}, + [M4SH_REG_PEDOMETER_FLOORSCLIMBED] = {M4SH_TYPE_PEDOMETER, 0x6, 2}, + [M4SH_REG_PEDOMETER_TOTATDISTANCE] = {M4SH_TYPE_PEDOMETER, 0x8, 4}, + [M4SH_REG_PEDOMETER_CURRENTSPEED] = {M4SH_TYPE_PEDOMETER, 0xc, 4}, + [M4SH_REG_PEDOMETER_REPORTEDDISTANCE] = {M4SH_TYPE_PEDOMETER, 0x10, 4}, + [M4SH_REG_PEDOMETER_USERDISTANCE] = {M4SH_TYPE_PEDOMETER, 0x14, 4}, + [M4SH_REG_TCMD_OPCODE] = {M4SH_TYPE_TCMD, 0x0, 1}, + [M4SH_REG_LOG_LOGENABLE] = {M4SH_TYPE_LOG, 0x0, 8}, + [M4SH_REG_LOG_ISLOGIMMEDIATE] = {M4SH_TYPE_LOG, 0x8, 1}, + [M4SH_REG_FUSION_VERSION] = {M4SH_TYPE_FUSION, 0x0, 1}, + [M4SH_REG_FUSION_DUMMY] = {M4SH_TYPE_FUSION, 0x1, 1}, + [M4SH_REG_FUSION_SAMPLERATE] = {M4SH_TYPE_FUSION, 0x2, 2}, + [M4SH_REG_FUSION_EULERPITCH] = {M4SH_TYPE_FUSION, 0x4, 4}, + [M4SH_REG_FUSION_EULERROLL] = {M4SH_TYPE_FUSION, 0x8, 4}, + [M4SH_REG_FUSION_EULERYAW] = {M4SH_TYPE_FUSION, 0xc, 4}, + [M4SH_REG_FUSION_LOCALX] = {M4SH_TYPE_FUSION, 0x10, 4}, + [M4SH_REG_FUSION_LOCALY] = {M4SH_TYPE_FUSION, 0x14, 4}, + [M4SH_REG_FUSION_LOCALZ] = {M4SH_TYPE_FUSION, 0x18, 4}, + [M4SH_REG_FUSION_WORLDX] = {M4SH_TYPE_FUSION, 0x1c, 4}, + [M4SH_REG_FUSION_WORLDY] = {M4SH_TYPE_FUSION, 0x20, 4}, + [M4SH_REG_FUSION_WORLDZ] = {M4SH_TYPE_FUSION, 0x24, 4}, + [M4SH_REG_FUSION_HEADING] = {M4SH_TYPE_FUSION, 0x28, 2}, + [M4SH_REG_FUSION_HEADING_ACCURACY] = {M4SH_TYPE_FUSION, 0x2a, 1}, + [M4SH_REG_COMPASS_VERSION] = {M4SH_TYPE_COMPASS, 0x0, 1}, + [M4SH_REG_COMPASS_DUMMY] = {M4SH_TYPE_COMPASS, 0x1, 1}, + [M4SH_REG_COMPASS_SAMPLERATE] = {M4SH_TYPE_COMPASS, 0x2, 2}, + [M4SH_REG_COMPASS_X] = {M4SH_TYPE_COMPASS, 0x4, 4}, + [M4SH_REG_COMPASS_Y] = {M4SH_TYPE_COMPASS, 0x8, 4}, + [M4SH_REG_COMPASS_Z] = {M4SH_TYPE_COMPASS, 0xc, 4}, + [M4SH_REG_COMPASS_ACCURACY] = {M4SH_TYPE_COMPASS, 0x10, 1}, + [M4SH_REG_GYRO_VERSION] = {M4SH_TYPE_GYRO, 0x0, 1}, + [M4SH_REG_GYRO_DUMMY] = {M4SH_TYPE_GYRO, 0x1, 1}, + [M4SH_REG_GYRO_SAMPLERATE] = {M4SH_TYPE_GYRO, 0x2, 2}, + [M4SH_REG_GYRO_X] = {M4SH_TYPE_GYRO, 0x4, 4}, + [M4SH_REG_GYRO_Y] = {M4SH_TYPE_GYRO, 0x8, 4}, + [M4SH_REG_GYRO_Z] = {M4SH_TYPE_GYRO, 0xc, 4}, + [M4SH_REG_METS_VERSION] = {M4SH_TYPE_METS, 0x0, 1}, + [M4SH_REG_METS_METSACTIVITY] = {M4SH_TYPE_METS, 0x1, 1}, + [M4SH_REG_METS_MSSAMPLETIME] = {M4SH_TYPE_METS, 0x2, 2}, + [M4SH_REG_METS_METS] = {M4SH_TYPE_METS, 0x4, 4}, + [M4SH_REG_METS_CALORIES] = {M4SH_TYPE_METS, 0x8, 4}, + [M4SH_REG_USERSETTINGS_VERSION] = {M4SH_TYPE_USERSETTINGS, 0x0, 1}, + [M4SH_REG_USERSETTINGS_USERAGE] = {M4SH_TYPE_USERSETTINGS, 0x1, 1}, + [M4SH_REG_USERSETTINGS_USERGENDER] = {M4SH_TYPE_USERSETTINGS, 0x2, 1}, + [M4SH_REG_USERSETTINGS_USERHEIGHT] = {M4SH_TYPE_USERSETTINGS, 0x3, 1}, + [M4SH_REG_USERSETTINGS_USERWEIGHT] = {M4SH_TYPE_USERSETTINGS, 0x4, 2}, + [M4SH_REG_USERSETTINGS_SCREENSTATUS] = {M4SH_TYPE_USERSETTINGS, 0x6, 1}, + [M4SH_REG_USERSETTINGS_RTCRESET] = {M4SH_TYPE_USERSETTINGS, 0x7, 1}, + [M4SH_REG_POWER_VERSION] = {M4SH_TYPE_POWER, 0x0, 1}, + [M4SH_REG_POWER_DUMMY] = {M4SH_TYPE_POWER, 0x1, 1}, + [M4SH_REG_POWER_STILLMODETIMEOUT] = {M4SH_TYPE_POWER, 0x2, 2}, + [M4SH_REG_POWER_MOTIONDURATION] = {M4SH_TYPE_POWER, 0x4, 1}, + [M4SH_REG_POWER_MOTIONTHRESHOLD] = {M4SH_TYPE_POWER, 0x5, 1}, + [M4SH_REG_POWER_NOMOTIONDURATION] = {M4SH_TYPE_POWER, 0x6, 1}, + [M4SH_REG_POWER_NOMOTIONTHRESHOLD] = {M4SH_TYPE_POWER, 0x7, 1}, + [M4SH_REG_LOCATION_VERSION] = {M4SH_TYPE_LOCATION, 0x0, 1}, + [M4SH_REG_LOCATION_SOURCE] = {M4SH_TYPE_LOCATION, 0x1, 1}, + [M4SH_REG_LOCATION_SPEED] = {M4SH_TYPE_LOCATION, 0x2, 2}, + [M4SH_REG_LOCATION_LATITUDE] = {M4SH_TYPE_LOCATION, 0x4, 4}, + [M4SH_REG_LOCATION_LONGITUDE] = {M4SH_TYPE_LOCATION, 0x8, 4}, + [M4SH_REG_LOCATION_ALTITUDE] = {M4SH_TYPE_LOCATION, 0xc, 2}, + [M4SH_REG_DOWNLOAD_COMMAND] = {M4SH_TYPE_DOWNLOAD, 0x0, 1}, + [M4SH_REG_DOWNLOAD_STATUS] = {M4SH_TYPE_DOWNLOAD, 0x1, 1}, + [M4SH_REG_DOWNLOAD_SIZE] = {M4SH_TYPE_DOWNLOAD, 0x2, 2}, + [M4SH_REG_DOWNLOAD_CHECKSUM] = {M4SH_TYPE_DOWNLOAD, 0x4, 4}, + [M4SH_REG_DOWNLOAD_FILENAME] = {M4SH_TYPE_DOWNLOAD, 0x8, 16}, + [M4SH_REG_DOWNLOAD_PACKET] = {M4SH_TYPE_DOWNLOAD, 0x18, 2048}, + [M4SH_REG_AUDIO_VERSION] = {M4SH_TYPE_AUDIO, 0x0, 1}, + [M4SH_REG_AUDIO_ENABLE] = {M4SH_TYPE_AUDIO, 0x1, 1}, + [M4SH_REG_AUDIO_DUMMY] = {M4SH_TYPE_AUDIO, 0x2, 2}, + [M4SH_REG_AUDIO_TOTALPACKETS] = {M4SH_TYPE_AUDIO, 0x4, 4}, + [M4SH_REG_TIMEPIECE_VERSION] = {M4SH_TYPE_TIMEPIECE, 0x0, 1}, + [M4SH_REG_TIMEPIECE_ENABLE] = {M4SH_TYPE_TIMEPIECE, 0x1, 1}, + [M4SH_REG_TIMEPIECE_SAMPLERATE] = {M4SH_TYPE_TIMEPIECE, 0x2, 2}, + [M4SH_REG_TIMEPIECE_OFFSETSTEPS] = {M4SH_TYPE_TIMEPIECE, 0x4, 4}, + [M4SH_REG_WRIST_VERSION] = {M4SH_TYPE_WRIST, 0x0, 1}, + [M4SH_REG_WRIST_ENABLE] = {M4SH_TYPE_WRIST, 0x1, 1}, + [M4SH_REG_WRIST_INTERRUPTREASON] = {M4SH_TYPE_WRIST, 0x2, 1}, + [M4SH_REG_WRIST_HOSTRESPONSE] = {M4SH_TYPE_WRIST, 0x3, 1}, + [M4SH_REG_WRIST_FMONCHIP] = {M4SH_TYPE_WRIST, 0x4, 2}, + [M4SH_REG_WRIST_FMONFILE] = {M4SH_TYPE_WRIST, 0x6, 2}, + [M4SH_REG_GESTURE_VERSION] = {M4SH_TYPE_GESTURE, 0x0, 1}, + [M4SH_REG_GESTURE_GESTURE1] = {M4SH_TYPE_GESTURE, 0x1, 1}, + [M4SH_REG_GESTURE_CONFIDENCE1] = {M4SH_TYPE_GESTURE, 0x2, 1}, + [M4SH_REG_GESTURE_VALUE1] = {M4SH_TYPE_GESTURE, 0x3, 1}, + [M4SH_REG_GESTURE_GESTURE2] = {M4SH_TYPE_GESTURE, 0x4, 1}, + [M4SH_REG_GESTURE_CONFIDENCE2] = {M4SH_TYPE_GESTURE, 0x5, 1}, + [M4SH_REG_GESTURE_VALUE2] = {M4SH_TYPE_GESTURE, 0x6, 1}, + [M4SH_REG_GESTURE_GESTURE3] = {M4SH_TYPE_GESTURE, 0x7, 1}, + [M4SH_REG_GESTURE_CONFIDENCE3] = {M4SH_TYPE_GESTURE, 0x8, 1}, + [M4SH_REG_GESTURE_VALUE3] = {M4SH_TYPE_GESTURE, 0x9, 1}, + [M4SH_REG_PASSIVE_TIMESTAMP] = {M4SH_TYPE_PASSIVE, 0x0, 48}, + [M4SH_REG_PASSIVE_STEPS] = {M4SH_TYPE_PASSIVE, 0x30, 48}, + [M4SH_REG_PASSIVE_METS] = {M4SH_TYPE_PASSIVE, 0x60, 48}, + [M4SH_REG_PASSIVE_FLOORSCLIMBED] = {M4SH_TYPE_PASSIVE, 0x90, 48}, +}; + +static const unsigned int bank_size_tbl[M4SH_TYPE__NUM] = { + [M4SH_TYPE_ACCEL] = 28, + [M4SH_TYPE_TEMP] = 8, + [M4SH_TYPE_GENERAL] = 11, + [M4SH_TYPE_PRESSURE] = 23, + [M4SH_TYPE_PEDOMETER] = 24, + [M4SH_TYPE_TCMD] = 1, + [M4SH_TYPE_LOG] = 9, + [M4SH_TYPE_FUSION] = 43, + [M4SH_TYPE_COMPASS] = 17, + [M4SH_TYPE_GYRO] = 16, + [M4SH_TYPE_METS] = 12, + [M4SH_TYPE_USERSETTINGS] = 8, + [M4SH_TYPE_POWER] = 8, + [M4SH_TYPE_LOCATION] = 14, + [M4SH_TYPE_DOWNLOAD] = 2072, + [M4SH_TYPE_AUDIO] = 8, + [M4SH_TYPE_TIMEPIECE] = 8, + [M4SH_TYPE_WRIST] = 8, + [M4SH_TYPE_GESTURE] = 10, + [M4SH_TYPE_PASSIVE] = 192, +}; +#endif /*__M4SENSORHUB_MEM_INTERNAL_H__ */ diff --git a/drivers/mfd/m4sensorhub-stm32-fw.c b/drivers/mfd/m4sensorhub-stm32-fw.c new file mode 100644 index 00000000000..55aca3ebb05 --- /dev/null +++ b/drivers/mfd/m4sensorhub-stm32-fw.c @@ -0,0 +1,419 @@ +/* + * 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 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 <linux/delay.h> +#include <linux/firmware.h> +#include <linux/m4sensorhub.h> +#include <linux/slab.h> + +/* --------------- Global Declarations -------------- */ + +/* ------------ Local Function Prototypes ----------- */ +static int m4sensorhub_jump_to_user(struct m4sensorhub_data *m4sensorhub); + +/* --------------- Local Declarations -------------- */ +/* Firmware */ +#define FIRMWARE_NAME "m4sensorhub.bin" +/* The M4 Flash Memory Map + * From the Flash Programming Manual: + Sector Start Address Size comments + ------ ------------- ---------- ---------------------------- + 0 0x08000000 16 Kbytes reserved for M4 bootload + 1 0x08004000 16 Kbytes first M4 firmware code page + 2 0x08008000 16 Kbytes + 3 0x0800C000 16 Kbytes + 4 0x08010000 64 Kbytes + 5 0x08020000 128 Kbytes last M4 firmware code page + 6 0x08040000 128 Kbytes first M4 file system page + 7 0x08060000 128 Kbytes + 8 0x08080000 128 Kbytes + 9 0x080A0000 128 Kbytes + 10 0x080C0000 128 Kbytes + 11 0x080E0000 128 Kbytes last M4 file system page + */ +enum { + M4_FLASH_SECTOR0 = 0x08000000, + M4_FLASH_SECTOR1 = 0x08004000, + M4_FLASH_SECTOR2 = 0x08008000, + M4_FLASH_SECTOR3 = 0x0800C000, + M4_FLASH_SECTOR4 = 0x08010000, + M4_FLASH_SECTOR5 = 0x08020000, + M4_FLASH_SECTOR6 = 0x08040000, + M4_FLASH_SECTOR7 = 0x08060000, + M4_FLASH_SECTOR8 = 0x08080000, + M4_FLASH_SECTOR9 = 0x080A0000, + M4_FLASH_SECTORA = 0x080C0000, + M4_FLASH_SECTORB = 0x080E0000, + M4_FLASH_END = 0x08100000 +}; +#define USER_FLASH_FIRST_PAGE_ADDRESS M4_FLASH_SECTOR1 +#define USER_FLASH_FIRST_FILE_ADDRESS M4_FLASH_SECTOR6 +#define VERSION_OFFSET 0x200 +#define VERSION_ADDRESS (USER_FLASH_FIRST_PAGE_ADDRESS + VERSION_OFFSET) +#define BARKER_SIZE 4 +#define BARKER_ADDRESS (USER_FLASH_FIRST_FILE_ADDRESS - BARKER_SIZE) +#define BARKER_NUMBER 0xACEC0DE +/* The MAX_FILE_SIZE is the size of sectors 1-5 where the firmware code + * will reside (minus the barker size). + */ +#define MAX_FILE_SIZE (USER_FLASH_FIRST_FILE_ADDRESS \ + - USER_FLASH_FIRST_PAGE_ADDRESS \ + - BARKER_SIZE) /* bytes */ +#define MAX_TRANSFER_SIZE 1024 /* bytes */ +#define MAX_RETRIES 5 +#define OPC_READ (uint8_t)(0x03) +#define OPC_WREN (uint8_t)(0x06) +#define OPC_ERPG (uint8_t)(0x20) +#define OPC_ERUSM (uint8_t)(0x60) +#define OPC_USRCD (uint8_t)(0x77) + +/* -------------- Local Data Structures ------------- */ +#define NUM_FLASH_TO_ERASE 5 +int flash_address[NUM_FLASH_TO_ERASE] = { + M4_FLASH_SECTOR1, + M4_FLASH_SECTOR2, + M4_FLASH_SECTOR3, + M4_FLASH_SECTOR4, + M4_FLASH_SECTOR5 +}; +int flash_delay[NUM_FLASH_TO_ERASE] = { + 440, 440, 440, 1320, 4000 +}; + +/* -------------- Global Functions ----------------- */ + +/* m4sensorhub_load_firmware() + + Check firmware and load if different from what's already on the M4. + Then jump to user code on M4. + + Returns 0 on success or negative error code on failure + + m4sensorhub - pointer to the main m4sensorhub data struct +*/ + +int m4sensorhub_load_firmware(struct m4sensorhub_data *m4sensorhub, + unsigned short force_upgrade) +{ + const struct firmware *firmware; + int i = MAX_RETRIES; + int ret = 0; + int bytes_left, bytes_to_write; + int address_to_write; + u8 *buf_to_read, *buf = NULL; + u16 fw_version_file, fw_version_device; + u32 barker_read_from_device; + int j = 0; + + buf = kzalloc(MAX_TRANSFER_SIZE+8, GFP_KERNEL); + if (!buf) { + ret = -ENOMEM; + goto done; + } + + ret = request_firmware(&firmware, FIRMWARE_NAME, + &m4sensorhub->i2c_client->dev); + if (ret < 0) { + KDEBUG(M4SH_ERROR, "%s: request_firmware failed for %s\n", + __func__, FIRMWARE_NAME); + KDEBUG(M4SH_ERROR, "Trying to run firmware already on hw.\n"); + ret = 0; + goto done; + } + + if (firmware->size > MAX_FILE_SIZE) { + KDEBUG(M4SH_ERROR, "%s: firmware file size is too big.\n", + __func__); + ret = -EINVAL; + goto done; + } + + fw_version_file = *(u16 *) (firmware->data + + VERSION_ADDRESS - USER_FLASH_FIRST_PAGE_ADDRESS); + + if (!force_upgrade) { + /* Verify Barker number from device */ + buf[0] = OPC_READ; + buf[1] = (BARKER_ADDRESS >> 24) & 0xFF; + buf[2] = (BARKER_ADDRESS >> 16) & 0xFF; + buf[3] = (BARKER_ADDRESS >> 8) & 0xFF; + buf[4] = BARKER_ADDRESS & 0xFF; + buf[5] = 0x00; + buf[6] = 0x04; + if (m4sensorhub_i2c_write_read(m4sensorhub, + buf, 7, 0) < 0) { + KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n", + __func__, __LINE__); + ret = -EINVAL; + goto done; + } + + msleep(100); + + if (m4sensorhub_i2c_write_read(m4sensorhub, + (u8 *) &barker_read_from_device, 0, BARKER_SIZE) < 0) { + KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n", + __func__, __LINE__); + ret = -EINVAL; + goto done; + } + + if (barker_read_from_device != BARKER_NUMBER) { + KDEBUG(M4SH_NOTICE, + "Barker Number read 0x%8x does not match 0x%8x\n", + barker_read_from_device, BARKER_NUMBER); + KDEBUG(M4SH_NOTICE, + "forcing firmware update from file\n"); + } else { + + /* Read firmware version from device */ + buf[0] = OPC_READ; + buf[1] = (VERSION_ADDRESS >> 24) & 0xFF; + buf[2] = (VERSION_ADDRESS >> 16) & 0xFF; + buf[3] = (VERSION_ADDRESS >> 8) & 0xFF; + buf[4] = VERSION_ADDRESS & 0xFF; + buf[5] = 0x00; + buf[6] = 0x02; + if (m4sensorhub_i2c_write_read( + m4sensorhub, buf, 7, 0) < 0) { + KDEBUG(M4SH_ERROR, + "%s : %d : I2C transfer error\n", + __func__, __LINE__); + ret = -EINVAL; + goto done; + } + + msleep(100); + + if (m4sensorhub_i2c_write_read(m4sensorhub, + (u8 *) &fw_version_device, 0, 2) < 0) { + KDEBUG(M4SH_ERROR, + "%s : %d : I2C transfer error\n", + __func__, __LINE__); + ret = -EINVAL; + goto done; + } + + if (fw_version_file == fw_version_device) { + KDEBUG(M4SH_NOTICE, + "Version of firmware on device is 0x%04x\n", + fw_version_device); + KDEBUG(M4SH_NOTICE, + "Firmware on device same as file, not loading firmware.\n"); + goto done; + } + /* Print statement below isn't really an ERROR, but + * this ensures it is always printed */ + KDEBUG(M4SH_ERROR, + "Version of firmware on device is 0x%04x\n", + fw_version_device); + KDEBUG(M4SH_ERROR, + "Version of firmware on file is 0x%04x\n", + fw_version_file); + KDEBUG(M4SH_ERROR, + "Firmware on device different from file, updating...\n"); + } + } else { + KDEBUG(M4SH_NOTICE, "Version of firmware on file is 0x%04x\n", + fw_version_file); + } + + /* The flash memory to update has to be erased before updating */ + for (j = 0; j < NUM_FLASH_TO_ERASE; j++) { + buf[0] = OPC_ERPG; + buf[1] = (flash_address[j] >> 24) & 0xFF; + buf[2] = (flash_address[j] >> 16) & 0xFF; + buf[3] = (flash_address[j] >> 8) & 0xFF; + buf[4] = flash_address[j] & 0xFF; + buf[5] = 0x00; + buf[6] = 0x01; + if (m4sensorhub_i2c_write_read(m4sensorhub, buf, 7, 0) < 0) { + KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n", + __func__, __LINE__); + KDEBUG(M4SH_ERROR, "Erasing %8x address failed\n", + flash_address[j]); + ret = -EINVAL; + goto done; + } + msleep(flash_delay[j]); + } + + bytes_left = firmware->size; + address_to_write = USER_FLASH_FIRST_PAGE_ADDRESS; + buf_to_read = (u8 *) firmware->data; + + KDEBUG(M4SH_DEBUG, "%s: %d bytes to be written\n", __func__, + firmware->size); + + while (bytes_left && i) { + if (bytes_left > MAX_TRANSFER_SIZE) + bytes_to_write = MAX_TRANSFER_SIZE; + else + bytes_to_write = bytes_left; + + buf[0] = OPC_WREN; + buf[1] = (address_to_write >> 24) & 0xFF; + buf[2] = (address_to_write >> 16) & 0xFF; + buf[3] = (address_to_write >> 8) & 0xFF; + buf[4] = address_to_write & 0xFF; + buf[5] = (bytes_to_write >> 8) & 0xFF; + buf[6] = bytes_to_write & 0xFF; + buf[7] = 0xFF; + memcpy(&buf[8], buf_to_read, bytes_to_write); + if (m4sensorhub_i2c_write_read(m4sensorhub, buf, + bytes_to_write+8, 0) < 0) { + KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n", + __func__, __LINE__); + ret = -EINVAL; + goto done; + } + + msleep(20); + + /* Read back the code that was written and validate it */ + buf[0] = OPC_READ; + buf[1] = (address_to_write >> 24) & 0xFF; + buf[2] = (address_to_write >> 16) & 0xFF; + buf[3] = (address_to_write >> 8) & 0xFF; + buf[4] = address_to_write & 0xFF; + buf[5] = (bytes_to_write >> 8) & 0xFF; + buf[6] = bytes_to_write & 0xFF; + if (m4sensorhub_i2c_write_read( + m4sensorhub, buf, 7, 0) < 0) { + KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n", + __func__, __LINE__); + ret = -EINVAL; + goto done; + } + + if (m4sensorhub_i2c_write_read(m4sensorhub, buf, 0, + bytes_to_write) < 0) { + KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n", + __func__, __LINE__); + ret = -EINVAL; + goto done; + } + + if (memcmp(buf, buf_to_read, bytes_to_write) != 0) { + /* If memory write fails, try again */ + KDEBUG(M4SH_ERROR, + "memory write to 0x%x of %d bytes failed, try again\n", + address_to_write, bytes_to_write); + i--; + } else { + address_to_write += bytes_to_write; + buf_to_read += bytes_to_write; + bytes_left -= bytes_to_write; + /* Reset reter of retries */ + i = MAX_RETRIES; + } + } + + if (!i) { + KDEBUG(M4SH_ERROR, "%s: firmware transfer failed\n", __func__); + ret = -EINVAL; + } else { + /* Write barker number when firmware successfully written */ + buf[0] = OPC_WREN; + buf[1] = (BARKER_ADDRESS >> 24) & 0xFF; + buf[2] = (BARKER_ADDRESS >> 16) & 0xFF; + buf[3] = (BARKER_ADDRESS >> 8) & 0xFF; + buf[4] = BARKER_ADDRESS & 0xFF; + buf[5] = 0x00; + buf[6] = 0x04; + buf[7] = 0xFF; + buf[8] = BARKER_NUMBER & 0xFF; + buf[9] = (BARKER_NUMBER >> 8) & 0xFF; + buf[10] = (BARKER_NUMBER >> 16) & 0xFF; + buf[11] = (BARKER_NUMBER >> 24) & 0xFF; + if (m4sensorhub_i2c_write_read(m4sensorhub, + buf, 12, 0) < 0) { + KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n", + __func__, __LINE__); + ret = -EINVAL; + goto done; + } + + KDEBUG(M4SH_NOTICE, "%s: %d bytes written successfully\n", + __func__, firmware->size); + } + +done: + release_firmware(firmware); + + /* If ret is invalid, then we don't try to jump to user code */ + if (ret >= 0 && m4sensorhub_jump_to_user(m4sensorhub) < 0) + /* If jump to user code fails, return failure */ + ret = -EINVAL; + + kfree(buf); + + return ret; +} +EXPORT_SYMBOL_GPL(m4sensorhub_load_firmware); + + +/* -------------- Local Functions ----------------- */ + +static int m4sensorhub_jump_to_user(struct m4sensorhub_data *m4sensorhub) +{ + int ret = -1; + u8 buf[7] = {0}; + u32 barker_read_from_device = 0; + + buf[0] = OPC_READ; + buf[1] = (BARKER_ADDRESS >> 24) & 0xFF; + buf[2] = (BARKER_ADDRESS >> 16) & 0xFF; + buf[3] = (BARKER_ADDRESS >> 8) & 0xFF; + buf[4] = BARKER_ADDRESS & 0xFF; + buf[5] = 0x00; + buf[6] = 0x04; + if (m4sensorhub_i2c_write_read(m4sensorhub, buf, 7, 0) < 0) { + KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n", + __func__, __LINE__); + return ret; + } + + msleep(100); + + if (m4sensorhub_i2c_write_read(m4sensorhub, + (u8 *) &barker_read_from_device, 0, BARKER_SIZE) < 0) { + KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n", + __func__, __LINE__); + return ret; + } + + if (barker_read_from_device == BARKER_NUMBER) { + buf[0] = OPC_USRCD; + if (m4sensorhub_i2c_write_read(m4sensorhub, buf, 1, 0) < 0) { + KDEBUG(M4SH_ERROR, "%s : %d : I2C transfer error\n", + __func__, __LINE__); + return ret; + } + KDEBUG(M4SH_NOTICE, "Executing M4 code \n"); + msleep(5000); /* 5 secs delay */ + ret = 0; + } else { + KDEBUG(M4SH_ERROR, + "Barker Number read 0x%8x does not match 0x%8x\n", + barker_read_from_device, BARKER_NUMBER); + KDEBUG(M4SH_ERROR, "*** Not executing M4 code ***\n"); + } + + return ret; +} diff --git a/drivers/mfd/tps65912-core.c b/drivers/mfd/tps65912-core.c index aeb8e40ab42..dfd832d0aef 100644 --- a/drivers/mfd/tps65912-core.c +++ b/drivers/mfd/tps65912-core.c @@ -156,9 +156,17 @@ int tps65912_device_init(struct tps65912 *tps65912) if (ret < 0) goto err; +#ifdef CONFIG_MFD_TPS65912_DEBUGFS + ret = tps65912_debugfs_create(tps65912); + if (ret < 0) + goto err_debugfs; +#endif + kfree(init_data); return ret; +err_debugfs: + tps65912_irq_exit(tps65912); err: kfree(init_data); mfd_remove_devices(tps65912->dev); @@ -168,6 +176,9 @@ err: void tps65912_device_exit(struct tps65912 *tps65912) { +#ifdef CONFIG_MFD_TPS65912_DEBUGFS + tps65912_debugfs_remove(tps65912); +#endif mfd_remove_devices(tps65912->dev); tps65912_irq_exit(tps65912); kfree(tps65912); diff --git a/drivers/mfd/tps65912-debugfs.c b/drivers/mfd/tps65912-debugfs.c new file mode 100644 index 00000000000..2a7d870aa8e --- /dev/null +++ b/drivers/mfd/tps65912-debugfs.c @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2013 Motorola Mobility LLC + * + * 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 <linux/debugfs.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/gpio.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/stat.h> +#include <linux/mfd/tps65912.h> + + +struct tps65912_debugfs_reg_descr { + char *name; /* Debugfs file name */ + u8 reg; /* Register address */ +}; + +/* Registers exposed in debugfs */ +static const struct tps65912_debugfs_reg_descr debugfs_regs[] = { + { "DCDC1_CTRL", 0x00 }, + { "DCDC2_CTRL", 0x01 }, + { "DCDC3_CTRL", 0x02 }, + { "DCDC4_CTRL", 0x03 }, + { "DCDC1_OP", 0x04 }, + { "DCDC1_AVS", 0x05 }, + { "DCDC1_LIMIT", 0x06 }, + { "DCDC2_OP", 0x07 }, + { "DCDC2_AVS", 0x08 }, + { "DCDC2_LIMIT", 0x09 }, + { "DCDC3_OP", 0x0A }, + { "DCDC3_AVS", 0x0B }, + { "DCDC3_LIMIT", 0x0C }, + { "DCDC4_OP", 0x0D }, + { "DCDC4_AVS", 0x0E }, + { "DCDC4_LIMIT", 0x0F }, + { "LDO1_OP", 0x10 }, + { "LDO1_AVS", 0x11 }, + { "LDO1_LIMIT", 0x12 }, + { "LDO2_OP", 0x13 }, + { "LDO2_AVS", 0x14 }, + { "LDO2_LIMIT", 0x15 }, + { "LDO3_OP", 0x16 }, + { "LDO3_AVS", 0x17 }, + { "LDO3_LIMIT", 0x18 }, + { "LDO4_OP", 0x19 }, + { "LDO4_AVS", 0x1A }, + { "LDO4_LIMIT", 0x1B }, + { "LDO5", 0x1C }, + { "LDO6", 0x1D }, + { "LDO7", 0x1E }, + { "LDO8", 0x1F }, + { "LDO9", 0x20 }, + { "LDO10", 0x21 }, + { "THRM", 0x22 }, + { "CLK32OUT", 0x23 }, + { "DEVCTRL", 0x24 }, + { "DEVCTRL2", 0x25 }, + { "I2C_SPI_CFG", 0x26 }, + { "KEEP_ON", 0x27 }, + { "KEEP_ON2", 0x28 }, + { "SET_OFF1", 0x29 }, + { "SET_OFF2", 0x2A }, + { "DEF_VOLT", 0x2B }, + { "DEF_VOLT_MAPPING", 0x2C }, + { "DISCHARGE", 0x2D }, + { "DISCHARGE2", 0x2E }, + { "EN1_SET1", 0x2F }, + { "EN1_SET2", 0x30 }, + { "EN2_SET1", 0x31 }, + { "EN2_SET2", 0x32 }, + { "EN3_SET1", 0x33 }, + { "EN3_SET2", 0x34 }, + { "EN4_SET1", 0x35 }, + { "EN4_SET2", 0x36 }, + { "PGOOD", 0x37 }, + { "PGOOD2", 0x38 }, + { "INT_STS", 0x39 }, + { "INT_MSK", 0x3A }, + { "INT_STS2", 0x3B }, + { "INT_MSK2", 0x3C }, + { "INT_STS3", 0x3D }, + { "INT_MSK3", 0x3E }, + { "INT_STS4", 0x3F }, + { "INT_MSK4", 0x40 }, + { "GPIO1", 0x41 }, + { "GPIO2", 0x42 }, + { "GPIO3", 0x43 }, + { "GPIO4", 0x44 }, + { "GPIO5", 0x45 }, + { "VMON", 0x46 }, + { "LEDA_CTRL1", 0x47 }, + { "LEDA_CTRL2", 0x48 }, + { "LEDA_CTRL3", 0x49 }, + { "LEDA_CTRL4", 0x4A }, + { "LEDA_CTRL5", 0x4B }, + { "LEDA_CTRL6", 0x4C }, + { "LEDA_CTRL7", 0x4D }, + { "LEDA_CTRL8", 0x4E }, + { "LEDB_CTRL1", 0x4F }, + { "LEDB_CTRL2", 0x50 }, + { "LEDB_CTRL3", 0x51 }, + { "LEDB_CTRL4", 0x52 }, + { "LEDB_CTRL5", 0x53 }, + { "LEDB_CTRL6", 0x54 }, + { "LEDB_CTRL7", 0x55 }, + { "LEDB_CTRL8", 0x56 }, + { "LEDC_CTRL1", 0x57 }, + { "LEDC_CTRL2", 0x58 }, + { "LEDC_CTRL3", 0x59 }, + { "LEDC_CTRL4", 0x5A }, + { "LEDC_CTRL5", 0x5B }, + { "LEDC_CTRL6", 0x5C }, + { "LEDC_CTRL7", 0x5D }, + { "LEDC_CTRL8", 0x5E }, + { "LED_RAMP_UP_TIME", 0x5F }, + { "LED_RAMP_DOWN_TIME", 0x60 }, + { "LED_SEQ_EN", 0x61 }, + { "LOADSWITCH", 0x62 }, + { "SPARE", 0x63 }, + { "VERNUM", 0x64 }, +}; + +struct tps65912_debugfs_reg_data { + struct tps65912 *tps65912; /* Device */ + u8 reg; /* Register address */ +}; + +struct tps65912_debugfs_data { + struct dentry *root; /* Debugfs directory of the device */ + struct tps65912_debugfs_reg_data reg_data[ARRAY_SIZE(debugfs_regs)]; +}; + +static int reg_read(void *data, u64 *val) +{ + struct tps65912_debugfs_reg_data *reg_data; + + reg_data = (struct tps65912_debugfs_reg_data *) data; + *val = tps65912_reg_read(reg_data->tps65912, reg_data->reg); + return 0; +} + +static int reg_write(void *data, u64 val) +{ + struct tps65912_debugfs_reg_data *reg_data; + + reg_data = (struct tps65912_debugfs_reg_data *) data; + return tps65912_reg_write(reg_data->tps65912, reg_data->reg, val); +} + +DEFINE_SIMPLE_ATTRIBUTE(reg_fops, reg_read, reg_write, "0x%02llx\n"); + +int tps65912_debugfs_create(struct tps65912 *tps65912) +{ + int i; + struct tps65912_debugfs_data *debugfs_data; + + debugfs_data = kzalloc(sizeof(struct tps65912_debugfs_data), + GFP_KERNEL); + if (!debugfs_data) + goto err_data; + + debugfs_data->root = debugfs_create_dir(dev_name(tps65912->dev), NULL); + if (!debugfs_data->root) + goto err_root; + + for (i = 0; i < ARRAY_SIZE(debugfs_regs); i++) { + debugfs_data->reg_data[i].tps65912 = tps65912; + debugfs_data->reg_data[i].reg = debugfs_regs[i].reg; + if (!debugfs_create_file(debugfs_regs[i].name, + S_IRUGO | S_IWUSR, debugfs_data->root, + debugfs_data->reg_data + i, ®_fops)) + goto err_file; + } + + tps65912->debugfs_data = debugfs_data; + return 0; + +err_file: + debugfs_remove_recursive(debugfs_data->root); +err_root: + kfree(debugfs_data); +err_data: + tps65912->debugfs_data = NULL; + return -ENOMEM; +} + +void tps65912_debugfs_remove(struct tps65912 *tps65912) +{ + struct tps65912_debugfs_data *debugfs_data; + + debugfs_data = (struct tps65912_debugfs_data *) tps65912->debugfs_data; + if (debugfs_data) { + debugfs_remove_recursive(debugfs_data->root); + kfree(debugfs_data); + } +} 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"); diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index eccedc7d06a..a347ade7664 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -40,6 +40,8 @@ #include <linux/pinctrl/consumer.h> #include <linux/pm_runtime.h> #include <linux/platform_data/mmc-omap.h> +#include <linux/wl12xx.h> +#include <asm-generic/gpio.h> /* OMAP HSMMC Host Controller Registers */ #define OMAP_HSMMC_SYSSTATUS 0x0014 @@ -1716,9 +1718,10 @@ MODULE_DEVICE_TABLE(of, omap_mmc_of_match); static struct omap_mmc_platform_data *of_get_hsmmc_pdata(struct device *dev) { struct omap_mmc_platform_data *pdata; + struct wl12xx_platform_data minnow_wlan_data; struct device_node *np = dev->of_node; u32 bus_width, max_freq; - int cd_gpio, wp_gpio; + int cd_gpio, wp_gpio, wl_host_wake_gpio = -1; cd_gpio = of_get_named_gpio(np, "cd-gpios", 0); wp_gpio = of_get_named_gpio(np, "wp-gpios", 0); @@ -1756,6 +1759,30 @@ static struct omap_mmc_platform_data *of_get_hsmmc_pdata(struct device *dev) if (of_find_property(np, "ti,needs-special-hs-handling", NULL)) pdata->slots[0].features |= HSMMC_HAS_HSPE_SUPPORT; + /* wlan_host_wake_gpio is needed by TI WLAN driver in TI proprietary + * code under repo hardware/ti/wlan, retrieve this gpio here from dts + * and pass to TI WLAN driver through the existing interface + * of wl12xx_set_platform_data, so not to chanage TI code + */ + if (!of_property_read_u32(np, "wl_host_wake_gpio", + &wl_host_wake_gpio)) { + pr_info("wl_host_wake_gpio is %d\n", wl_host_wake_gpio); + if (gpio_request(wl_host_wake_gpio, "wifi_irq") < 0) { + printk(KERN_ERR "%s: can't reserve GPIO: %d\n", + __func__, wl_host_wake_gpio); + devm_kfree(dev, pdata); + return NULL; + } + gpio_direction_input(wl_host_wake_gpio); + minnow_wlan_data.irq = __gpio_to_irq(wl_host_wake_gpio); + if (wl12xx_set_platform_data(&minnow_wlan_data)) { + printk(KERN_ERR "%s: Error setting wl18xx data\n", + __func__); + devm_kfree(dev, pdata); + return NULL; + } + } + return pdata; } #else diff --git a/drivers/of/base.c b/drivers/of/base.c index a6f584a7f4a..57534569fbc 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -237,7 +237,7 @@ static int __of_device_is_compatible(const struct device_node *device, const char *compat) { const char* cp; - int cplen, l; + int cplen = 0, l; cp = __of_get_property(device, "compatible", &cplen); if (cp == NULL) @@ -301,7 +301,7 @@ EXPORT_SYMBOL(of_machine_is_compatible); static int __of_device_is_available(const struct device_node *device) { const char *status; - int statlen; + int statlen = 0; status = __of_get_property(device, "status", &statlen); if (status == NULL) diff --git a/drivers/pinctrl/pinconf.c b/drivers/pinctrl/pinconf.c index 694c3ace452..4d9ee680553 100644 --- a/drivers/pinctrl/pinconf.c +++ b/drivers/pinctrl/pinconf.c @@ -604,7 +604,7 @@ static int pinconf_dbg_config_print(struct seq_file *s, void *d) const struct pinctrl_map *map; struct pinctrl_dev *pctldev = NULL; const struct pinconf_ops *confops = NULL; - const struct pinctrl_map_configs *configs; + const struct pinctrl_map_configs *configs = NULL; struct dbg_cfg *dbg = &pinconf_dbg_conf; int i, j; bool found = false; @@ -682,7 +682,7 @@ static int pinconf_dbg_config_write(struct file *file, struct pinctrl_dev *pctldev = NULL; const struct pinconf_ops *confops = NULL; struct dbg_cfg *dbg = &pinconf_dbg_conf; - const struct pinctrl_map_configs *configs; + const struct pinctrl_map_configs *configs = NULL; char config[MAX_NAME_LEN+1]; bool found = false; char buf[128]; diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 7b8979c63f4..1aefca3cb15 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -362,6 +362,18 @@ config BATTERY_GOLDFISH Say Y to enable support for the battery and AC power in the Goldfish emulator. +config CHARGER_CPCAP + tristate "CPCAP Charger Driver" + depends on MFD_CPCAP && BATTERY_CPCAP + help + Say Y to include support for CPCAP Main Battery Charger. + +config BATTERY_CPCAP + tristate "CPCAP Battery Driver" + depends on MFD_CPCAP + help + Say Y to include support for CPCAP Main Battery. + source "drivers/power/reset/Kconfig" endif # POWER_SUPPLY diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 653bf6ceff3..9311e1a75d6 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -54,3 +54,7 @@ obj-$(CONFIG_POWER_AVS) += avs/ obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o obj-$(CONFIG_POWER_RESET) += reset/ +obj-$(CONFIG_CHARGER_CPCAP) += cpcap-charger.o +obj-$(CONFIG_CHARGER_CPCAP) += cpcap-usb-charger.o +obj-$(CONFIG_CHARGER_CPCAP) += cpcap-factory.o +obj-$(CONFIG_BATTERY_CPCAP) += cpcap-battery.o
\ No newline at end of file diff --git a/drivers/power/cpcap-battery.c b/drivers/power/cpcap-battery.c new file mode 100644 index 00000000000..517d41bb919 --- /dev/null +++ b/drivers/power/cpcap-battery.c @@ -0,0 +1,823 @@ +/* + * Copyright (C) 2007-2009 Motorola, 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 <asm/div64.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/power_supply.h> +#include <linux/types.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/spi/cpcap.h> +#include <linux/spi/cpcap-regbits.h> +#include <linux/spi/spi.h> +#include <linux/time.h> +#include <linux/miscdevice.h> +#include <linux/debugfs.h> + +#define CPCAP_BATT_IRQ_BATTDET 0x01 +#define CPCAP_BATT_IRQ_OV 0x02 +#define CPCAP_BATT_IRQ_CC_CAL 0x04 +#define CPCAP_BATT_IRQ_ADCDONE 0x08 +#define CPCAP_BATT_IRQ_MACRO 0x10 + +static long cpcap_batt_ioctl( + struct file *file, + unsigned int cmd, + unsigned long arg); +static unsigned int cpcap_batt_poll(struct file *file, poll_table *wait); +static int cpcap_batt_open(struct inode *inode, struct file *file); +static ssize_t cpcap_batt_read(struct file *file, char *buf, size_t count, + loff_t *ppos); +static int cpcap_batt_probe(struct platform_device *pdev); +static int cpcap_batt_remove(struct platform_device *pdev); +static int cpcap_batt_resume(struct platform_device *pdev); + +struct cpcap_batt_ps { + struct power_supply batt; + struct power_supply ac; + struct power_supply usb; + struct cpcap_device *cpcap; + struct cpcap_batt_data batt_state; + struct cpcap_batt_ac_data ac_state; + struct cpcap_batt_usb_data usb_state; + struct cpcap_adc_request req; + struct mutex lock; + char irq_status; + char data_pending; + wait_queue_head_t wait; + char async_req_pending; + unsigned long last_run_time; + bool no_update; +}; + +static const struct file_operations batt_fops = { + .owner = THIS_MODULE, + .open = cpcap_batt_open, + .unlocked_ioctl = cpcap_batt_ioctl, + .read = cpcap_batt_read, + .poll = cpcap_batt_poll, +}; + +static struct miscdevice batt_dev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "cpcap_batt", + .fops = &batt_fops, +}; + +static enum power_supply_property cpcap_batt_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_COUNTER +}; + +static enum power_supply_property cpcap_batt_ac_props[] = +{ + POWER_SUPPLY_PROP_ONLINE +}; + +static enum power_supply_property cpcap_batt_usb_props[] = +{ + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_MODEL_NAME +}; + +static struct platform_driver cpcap_batt_driver = { + .probe = cpcap_batt_probe, + .remove = cpcap_batt_remove, + .resume = cpcap_batt_resume, + .driver = { + .name = "cpcap_battery", + .owner = THIS_MODULE, + }, +}; + +static struct cpcap_batt_ps *cpcap_batt_sply; + +void cpcap_batt_irq_hdlr(enum cpcap_irqs irq, void *data) +{ + struct cpcap_batt_ps *sply = data; + + mutex_lock(&sply->lock); + sply->data_pending = 1; + + switch (irq) { + case CPCAP_IRQ_BATTDETB: + sply->irq_status |= CPCAP_BATT_IRQ_BATTDET; + cpcap_irq_unmask(sply->cpcap, irq); + break; + + case CPCAP_IRQ_VBUSOV: + sply->irq_status |= CPCAP_BATT_IRQ_OV; + cpcap_irq_unmask(sply->cpcap, irq); + break; + + case CPCAP_IRQ_CC_CAL: + sply->irq_status |= CPCAP_BATT_IRQ_CC_CAL; + cpcap_irq_unmask(sply->cpcap, irq); + break; + + case CPCAP_IRQ_UC_PRIMACRO_7: + case CPCAP_IRQ_UC_PRIMACRO_8: + case CPCAP_IRQ_UC_PRIMACRO_9: + case CPCAP_IRQ_UC_PRIMACRO_10: + case CPCAP_IRQ_UC_PRIMACRO_11: + sply->irq_status |= CPCAP_BATT_IRQ_MACRO; + break; + default: + break; + } + + mutex_unlock(&sply->lock); + + wake_up_interruptible(&sply->wait); +} + +void cpcap_batt_adc_hdlr(struct cpcap_device *cpcap, void *data) +{ + struct cpcap_batt_ps *sply = data; + mutex_lock(&sply->lock); + + sply->async_req_pending = 0; + + sply->data_pending = 1; + + sply->irq_status |= CPCAP_BATT_IRQ_ADCDONE; + + mutex_unlock(&sply->lock); + + wake_up_interruptible(&sply->wait); +} + +static int cpcap_batt_open(struct inode *inode, struct file *file) +{ + file->private_data = cpcap_batt_sply; + return 0; +} + +static unsigned int cpcap_batt_poll(struct file *file, poll_table *wait) +{ + struct cpcap_batt_ps *sply = file->private_data; + unsigned int ret = 0; + + poll_wait(file, &sply->wait, wait); + + if (sply->data_pending) + ret = (POLLIN | POLLRDNORM); + + return ret; +} + +static ssize_t cpcap_batt_read(struct file *file, + char *buf, size_t count, loff_t *ppos) +{ + struct cpcap_batt_ps *sply = file->private_data; + int ret = -EFBIG; + unsigned long long temp; + + if (count >= sizeof(char)) { + mutex_lock(&sply->lock); + if (!copy_to_user((void *)buf, (void *)&sply->irq_status, + sizeof(sply->irq_status))) + ret = sizeof(sply->irq_status); + else + ret = -EFAULT; + sply->data_pending = 0; + temp = sched_clock(); + do_div(temp, NSEC_PER_SEC); + sply->last_run_time = (unsigned long)temp; + + sply->irq_status = 0; + mutex_unlock(&sply->lock); + } + + return ret; +} + +static long cpcap_batt_ioctl( + struct file *file, + unsigned int cmd, + unsigned long arg) +{ + long ret = 0; + int i; + struct cpcap_batt_ps *sply = file->private_data; + struct cpcap_adc_request *req_async = &sply->req; + struct cpcap_adc_request req; + struct cpcap_adc_us_request req_us; + struct spi_device *spi = sply->cpcap->spi; + struct cpcap_platform_data *data = spi->controller_data; + + switch (cmd) { + case CPCAP_IOCTL_BATT_DISPLAY_UPDATE: + if (sply->no_update) + return 0; + if (copy_from_user((void *)&sply->batt_state, + (void *)arg, sizeof(struct cpcap_batt_data))) + return -EFAULT; + power_supply_changed(&sply->batt); + + if (data->batt_changed) + data->batt_changed(&sply->batt, &sply->batt_state); + break; + + case CPCAP_IOCTL_BATT_ATOD_ASYNC: + mutex_lock(&sply->lock); + if (!sply->async_req_pending) { + if (copy_from_user((void *)&req_us, (void *)arg, + sizeof(struct cpcap_adc_us_request) + )) { + mutex_unlock(&sply->lock); + return -EFAULT; + } + + req_async->format = req_us.format; + req_async->timing = req_us.timing; + req_async->type = req_us.type; + req_async->callback = cpcap_batt_adc_hdlr; + req_async->callback_param = sply; + + ret = cpcap_adc_async_read(sply->cpcap, req_async); + if (!ret) + sply->async_req_pending = 1; + } else { + ret = -EAGAIN; + } + mutex_unlock(&sply->lock); + + break; + + case CPCAP_IOCTL_BATT_ATOD_SYNC: + if (copy_from_user((void *)&req_us, (void *)arg, + sizeof(struct cpcap_adc_us_request))) + return -EFAULT; + + req.format = req_us.format; + req.timing = req_us.timing; + req.type = req_us.type; + + ret = cpcap_adc_sync_read(sply->cpcap, &req); + + if (ret) + return ret; + + req_us.status = req.status; + for (i = 0; i < CPCAP_ADC_BANK0_NUM; i++) + req_us.result[i] = req.result[i]; + + if (copy_to_user((void *)arg, (void *)&req_us, + sizeof(struct cpcap_adc_us_request))) + return -EFAULT; + break; + + case CPCAP_IOCTL_BATT_ATOD_READ: + req_us.format = req_async->format; + req_us.timing = req_async->timing; + req_us.type = req_async->type; + req_us.status = req_async->status; + for (i = 0; i < CPCAP_ADC_BANK0_NUM; i++) + req_us.result[i] = req_async->result[i]; + + if (copy_to_user((void *)arg, (void *)&req_us, + sizeof(struct cpcap_adc_us_request))) + return -EFAULT; + break; + + default: + return -ENOTTY; + break; + } + + return ret; +} + +static int cpcap_batt_ac_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + struct cpcap_batt_ps *sply = container_of(psy, struct cpcap_batt_ps, + ac); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = sply->ac_state.online; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static char *cpcap_batt_usb_models[] = { + "none", "usb", "factory" +}; + +static int cpcap_batt_usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + struct cpcap_batt_ps *sply = container_of(psy, struct cpcap_batt_ps, + usb); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = sply->usb_state.online; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = sply->usb_state.current_now; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = cpcap_batt_usb_models[sply->usb_state.model]; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int cpcap_batt_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + struct cpcap_batt_ps *sply = container_of(psy, struct cpcap_batt_ps, + batt); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = sply->batt_state.status; + break; + + case POWER_SUPPLY_PROP_HEALTH: + val->intval = sply->batt_state.health; + break; + + case POWER_SUPPLY_PROP_PRESENT: + val->intval = sply->batt_state.present; + break; + + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = sply->batt_state.capacity; + break; + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = sply->batt_state.batt_volt; + break; + + case POWER_SUPPLY_PROP_TEMP: + val->intval = sply->batt_state.batt_temp; + break; + + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = sply->batt_state.batt_full_capacity; + break; + + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + val->intval = sply->batt_state.batt_capacity_one; + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int cpcap_batt_probe(struct platform_device *pdev) +{ + int ret = 0; + struct cpcap_batt_ps *sply; + + if (pdev->dev.platform_data == NULL) { + dev_err(&pdev->dev, "no platform_data\n"); + ret = -EINVAL; + goto prb_exit; + } + + sply = kzalloc(sizeof(struct cpcap_batt_ps), GFP_KERNEL); + if (sply == NULL) { + ret = -ENOMEM; + goto prb_exit; + } + + sply->cpcap = pdev->dev.platform_data; + mutex_init(&sply->lock); + init_waitqueue_head(&sply->wait); + + sply->batt_state.status = POWER_SUPPLY_STATUS_UNKNOWN; + sply->batt_state.health = POWER_SUPPLY_HEALTH_GOOD; + sply->batt_state.present = 1; + sply->batt_state.capacity = 100; /* Percentage */ + sply->batt_state.batt_volt = 4200000; /* uV */ + sply->batt_state.batt_temp = 230; /* tenths of degrees Celsius */ + sply->batt_state.batt_full_capacity = 0; + sply->batt_state.batt_capacity_one = 100; + + sply->ac_state.online = 0; + + sply->usb_state.online = 0; + sply->usb_state.current_now = 0; + sply->usb_state.model = CPCAP_BATT_USB_MODEL_NONE; + + sply->batt.properties = cpcap_batt_props; + sply->batt.num_properties = ARRAY_SIZE(cpcap_batt_props); + sply->batt.get_property = cpcap_batt_get_property; + sply->batt.name = "battery"; + sply->batt.type = POWER_SUPPLY_TYPE_BATTERY; + + sply->ac.properties = cpcap_batt_ac_props; + sply->ac.num_properties = ARRAY_SIZE(cpcap_batt_ac_props); + sply->ac.get_property = cpcap_batt_ac_get_property; + sply->ac.name = "ac"; + sply->ac.type = POWER_SUPPLY_TYPE_MAINS; + + sply->usb.properties = cpcap_batt_usb_props; + sply->usb.num_properties = ARRAY_SIZE(cpcap_batt_usb_props); + sply->usb.get_property = cpcap_batt_usb_get_property; + sply->usb.name = "usb"; + sply->usb.type = POWER_SUPPLY_TYPE_USB; + + sply->no_update = false; + + ret = power_supply_register(&pdev->dev, &sply->ac); + if (ret) + goto prb_exit; + ret = power_supply_register(&pdev->dev, &sply->batt); + if (ret) + goto unregac_exit; + ret = power_supply_register(&pdev->dev, &sply->usb); + if (ret) + goto unregbatt_exit; + platform_set_drvdata(pdev, sply); + sply->cpcap->battdata = sply; + cpcap_batt_sply = sply; + + ret = misc_register(&batt_dev); + if (ret) + goto unregusb_exit; + + ret = cpcap_irq_register(sply->cpcap, CPCAP_IRQ_VBUSOV, + cpcap_batt_irq_hdlr, sply); + if (ret) + goto unregmisc_exit; + ret = cpcap_irq_register(sply->cpcap, CPCAP_IRQ_BATTDETB, + cpcap_batt_irq_hdlr, sply); + if (ret) + goto unregirq_exit; + ret = cpcap_irq_register(sply->cpcap, CPCAP_IRQ_CC_CAL, + cpcap_batt_irq_hdlr, sply); + if (ret) + goto unregirq_exit; + + ret = cpcap_irq_register(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_7, + cpcap_batt_irq_hdlr, sply); + cpcap_irq_mask(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_7); + + if (ret) + goto unregirq_exit; + + ret = cpcap_irq_register(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_8, + cpcap_batt_irq_hdlr, sply); + cpcap_irq_mask(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_8); + + if (ret) + goto unregirq_exit; + + ret = cpcap_irq_register(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_9, + cpcap_batt_irq_hdlr, sply); + cpcap_irq_mask(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_9); + + if (ret) + goto unregirq_exit; + + ret = cpcap_irq_register(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_10, + cpcap_batt_irq_hdlr, sply); + cpcap_irq_mask(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_10); + + if (ret) + goto unregirq_exit; + + ret = cpcap_irq_register(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_11, + cpcap_batt_irq_hdlr, sply); + cpcap_irq_mask(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_11); + + if (ret) + goto unregirq_exit; + + goto prb_exit; + +unregirq_exit: + cpcap_irq_free(sply->cpcap, CPCAP_IRQ_VBUSOV); + cpcap_irq_free(sply->cpcap, CPCAP_IRQ_BATTDETB); + cpcap_irq_free(sply->cpcap, CPCAP_IRQ_CC_CAL); + cpcap_irq_free(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_7); + cpcap_irq_free(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_8); + cpcap_irq_free(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_9); + cpcap_irq_free(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_10); + cpcap_irq_free(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_11); +unregmisc_exit: + misc_deregister(&batt_dev); +unregusb_exit: + power_supply_unregister(&sply->usb); +unregbatt_exit: + power_supply_unregister(&sply->batt); +unregac_exit: + power_supply_unregister(&sply->ac); + +prb_exit: + return ret; +} + +static int cpcap_batt_remove(struct platform_device *pdev) +{ + struct cpcap_batt_ps *sply = platform_get_drvdata(pdev); + + power_supply_unregister(&sply->batt); + power_supply_unregister(&sply->ac); + power_supply_unregister(&sply->usb); + misc_deregister(&batt_dev); + cpcap_irq_free(sply->cpcap, CPCAP_IRQ_VBUSOV); + cpcap_irq_free(sply->cpcap, CPCAP_IRQ_BATTDETB); + cpcap_irq_free(sply->cpcap, CPCAP_IRQ_CC_CAL); + cpcap_irq_free(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_7); + cpcap_irq_free(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_8); + cpcap_irq_free(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_9); + cpcap_irq_free(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_10); + cpcap_irq_free(sply->cpcap, CPCAP_IRQ_UC_PRIMACRO_11); + sply->cpcap->battdata = NULL; + kfree(sply); + + return 0; +} + +static int cpcap_batt_resume(struct platform_device *pdev) +{ + struct cpcap_batt_ps *sply = platform_get_drvdata(pdev); + unsigned long cur_time; + unsigned long long temp; + + temp = sched_clock(); + do_div(temp, NSEC_PER_SEC); + cur_time = ((unsigned long)temp); + if ((cur_time - sply->last_run_time) < 0) + sply->last_run_time = 0; + + if ((cur_time - sply->last_run_time) > 50) { + mutex_lock(&sply->lock); + sply->data_pending = 1; + sply->irq_status |= CPCAP_BATT_IRQ_MACRO; + + mutex_unlock(&sply->lock); + + wake_up_interruptible(&sply->wait); + } + + return 0; +} + +void cpcap_batt_set_ac_prop(struct cpcap_device *cpcap, int online) +{ + struct cpcap_batt_ps *sply = cpcap->battdata; + struct spi_device *spi = cpcap->spi; + struct cpcap_platform_data *data = spi->controller_data; + + if (sply != NULL) { + sply->ac_state.online = online; + power_supply_changed(&sply->ac); + + if (data->ac_changed) + data->ac_changed(&sply->ac, &sply->ac_state); + } +} +EXPORT_SYMBOL(cpcap_batt_set_ac_prop); + +void cpcap_batt_set_usb_prop_online(struct cpcap_device *cpcap, int online, + enum cpcap_batt_usb_model model) +{ + struct cpcap_batt_ps *sply = cpcap->battdata; + struct spi_device *spi = cpcap->spi; + struct cpcap_platform_data *data = spi->controller_data; + + if (sply != NULL) { + sply->usb_state.online = online; + sply->usb_state.model = model; + power_supply_changed(&sply->usb); + + if (data->usb_changed) + data->usb_changed(&sply->usb, &sply->usb_state); + } +} +EXPORT_SYMBOL(cpcap_batt_set_usb_prop_online); + +void cpcap_batt_set_usb_prop_curr(struct cpcap_device *cpcap, unsigned int curr) +{ + struct cpcap_batt_ps *sply = cpcap->battdata; + struct spi_device *spi = cpcap->spi; + struct cpcap_platform_data *data = spi->controller_data; + + if (sply != NULL) { + sply->usb_state.current_now = curr; + power_supply_changed(&sply->usb); + + if (data->usb_changed) + data->usb_changed(&sply->usb, &sply->usb_state); + } +} +EXPORT_SYMBOL(cpcap_batt_set_usb_prop_curr); + +/* + * Debugfs interface to test how system works with different values of + * the battery properties. Once the propety value is set through the + * debugfs, updtes from the drivers will be discarded. + */ +#ifdef CONFIG_DEBUG_FS + +static int cpcap_batt_debug_set(void *prop, u64 val) +{ + int data = (int)val; + enum power_supply_property psp = (enum power_supply_property)prop; + struct cpcap_batt_ps *sply = cpcap_batt_sply; + bool changed = true; + sply->no_update = true; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + sply->batt_state.status = data; + break; + + case POWER_SUPPLY_PROP_HEALTH: + sply->batt_state.health = data; + break; + + case POWER_SUPPLY_PROP_PRESENT: + sply->batt_state.present = data; + break; + + case POWER_SUPPLY_PROP_CAPACITY: + sply->batt_state.capacity = data; + break; + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + sply->batt_state.batt_volt = data; + break; + + case POWER_SUPPLY_PROP_TEMP: + sply->batt_state.batt_temp = data; + break; + + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + sply->batt_state.batt_full_capacity = data; + break; + + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + sply->batt_state.batt_capacity_one = data; + break; + + default: + changed = false; + break; + } + + if (changed) + power_supply_changed(&sply->batt); + + return 0; +} + +static int cpcap_batt_debug_get(void *prop, u64 *val) +{ + enum power_supply_property psp = (enum power_supply_property)prop; + struct cpcap_batt_ps *sply = cpcap_batt_sply; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + *val = sply->batt_state.status; + break; + + case POWER_SUPPLY_PROP_HEALTH: + *val = sply->batt_state.health; + break; + + case POWER_SUPPLY_PROP_PRESENT: + *val = sply->batt_state.present; + break; + + case POWER_SUPPLY_PROP_CAPACITY: + *val = sply->batt_state.capacity; + break; + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + *val = sply->batt_state.batt_volt; + break; + + case POWER_SUPPLY_PROP_TEMP: + *val = sply->batt_state.batt_temp; + break; + + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + *val = sply->batt_state.batt_full_capacity; + break; + + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + *val = sply->batt_state.batt_capacity_one; + break; + + default: + break; + } + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(cpcap_battery_fops, cpcap_batt_debug_get, + cpcap_batt_debug_set, "%llu\n"); + +static int __init cpcap_batt_debug_init(void) +{ + struct dentry *dent = debugfs_create_dir("battery", 0); + int ret = 0; + + if (!IS_ERR(dent)) { + debugfs_create_file("status", 0666, dent, + (void *)POWER_SUPPLY_PROP_STATUS, &cpcap_battery_fops); + debugfs_create_file("health", 0666, dent, + (void *)POWER_SUPPLY_PROP_HEALTH, &cpcap_battery_fops); + debugfs_create_file("present", 0666, dent, + (void *)POWER_SUPPLY_PROP_PRESENT, &cpcap_battery_fops); + debugfs_create_file("voltage", 0666, dent, + (void *)POWER_SUPPLY_PROP_VOLTAGE_NOW, &cpcap_battery_fops); + debugfs_create_file("capacity", 0666, dent, + (void *)POWER_SUPPLY_PROP_CAPACITY, &cpcap_battery_fops); + debugfs_create_file("temp", 0666, dent, + (void *)POWER_SUPPLY_PROP_TEMP, &cpcap_battery_fops); + debugfs_create_file("charge_full_design", 0666, dent, + (void *)POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + &cpcap_battery_fops); + debugfs_create_file("charge_counter", 0666, dent, + (void *)POWER_SUPPLY_PROP_CHARGE_COUNTER, + &cpcap_battery_fops); + } else { + ret = PTR_ERR(dent); + } + + return ret; +} + +late_initcall(cpcap_batt_debug_init); + +#endif /* CONFIG_DEBUG_FS */ + +static int __init cpcap_batt_init(void) +{ + return platform_driver_register(&cpcap_batt_driver); +} +subsys_initcall(cpcap_batt_init); + +static void __exit cpcap_batt_exit(void) +{ + platform_driver_unregister(&cpcap_batt_driver); +} +module_exit(cpcap_batt_exit); + +MODULE_ALIAS("platform:cpcap_batt"); +MODULE_DESCRIPTION("CPCAP BATTERY driver"); +MODULE_AUTHOR("Motorola"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/cpcap-charger.c b/drivers/power/cpcap-charger.c new file mode 100644 index 00000000000..1d73e43ea8f --- /dev/null +++ b/drivers/power/cpcap-charger.c @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2007-2009 Motorola, 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 <linux/device.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/spi/cpcap.h> + +static int cpcap_chgr_probe(struct platform_device *pdev); +static int cpcap_chgr_remove(struct platform_device *pdev); + +static struct platform_driver cpcap_chgr_driver = { + .probe = cpcap_chgr_probe, + .remove = cpcap_chgr_remove, + .driver = { + .name = "cpcap_charger", + .owner = THIS_MODULE, + }, +}; + +static int cpcap_chgr_probe(struct platform_device *pdev) +{ + int ret = 0; + struct cpcap_device *cpcap; + + if (pdev->dev.platform_data == NULL) { + dev_err(&pdev->dev, "no platform_data\n"); + return -EINVAL; + } + + cpcap = pdev->dev.platform_data; + platform_set_drvdata(pdev, cpcap); + cpcap_batt_set_ac_prop(cpcap, 1); + return ret; +} + +static int cpcap_chgr_remove(struct platform_device *pdev) +{ + struct cpcap_device *cpcap = platform_get_drvdata(pdev); + cpcap_batt_set_ac_prop(cpcap, 0); + + return 0; +} + +static int __init cpcap_chgr_init(void) +{ + return platform_driver_register(&cpcap_chgr_driver); +} +module_init(cpcap_chgr_init); + +static void __exit cpcap_chgr_exit(void) +{ + platform_driver_unregister(&cpcap_chgr_driver); +} +module_exit(cpcap_chgr_exit); + +MODULE_ALIAS("platform:cpcap_chgr"); +MODULE_DESCRIPTION("CPCAP CHARGER driver"); +MODULE_AUTHOR("Motorola"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/cpcap-factory.c b/drivers/power/cpcap-factory.c new file mode 100644 index 00000000000..802b917ecd2 --- /dev/null +++ b/drivers/power/cpcap-factory.c @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2007-2009 Motorola, 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 <linux/device.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/spi/cpcap.h> + +static int cpcap_factory_probe(struct platform_device *pdev); +static int cpcap_factory_remove(struct platform_device *pdev); + +static struct platform_driver cpcap_factory_driver = { + .probe = cpcap_factory_probe, + .remove = cpcap_factory_remove, + .driver = { + .name = "cpcap_factory", + .owner = THIS_MODULE, + }, +}; + +static int cpcap_factory_probe(struct platform_device *pdev) +{ + int ret = 0; + struct cpcap_device *cpcap; + + if (pdev->dev.platform_data == NULL) { + dev_err(&pdev->dev, "no platform_data\n"); + return -EINVAL; + } + + cpcap = pdev->dev.platform_data; + platform_set_drvdata(pdev, cpcap); + + cpcap_batt_set_usb_prop_curr(cpcap, 0); + cpcap_batt_set_usb_prop_online(cpcap, 1, CPCAP_BATT_USB_MODEL_FACTORY); + + return ret; +} + +static int cpcap_factory_remove(struct platform_device *pdev) +{ + struct cpcap_device *cpcap = platform_get_drvdata(pdev); + + cpcap_batt_set_usb_prop_curr(cpcap, 0); + cpcap_batt_set_usb_prop_online(cpcap, 0, CPCAP_BATT_USB_MODEL_NONE); + + return 0; +} + +static int __init cpcap_factory_init(void) +{ + return platform_driver_register(&cpcap_factory_driver); +} +module_init(cpcap_factory_init); + +static void __exit cpcap_factory_exit(void) +{ + platform_driver_unregister(&cpcap_factory_driver); +} +module_exit(cpcap_factory_exit); + +MODULE_ALIAS("platform:cpcap_factory"); +MODULE_DESCRIPTION("CPCAP Factory Device driver"); +MODULE_AUTHOR("Motorola"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/cpcap-usb-charger.c b/drivers/power/cpcap-usb-charger.c new file mode 100644 index 00000000000..ac98a144fef --- /dev/null +++ b/drivers/power/cpcap-usb-charger.c @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2007-2009 Motorola, 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 <linux/device.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/spi/cpcap.h> + +static int cpcap_usb_chgr_probe(struct platform_device *pdev); +static int cpcap_usb_chgr_remove(struct platform_device *pdev); + +static struct platform_driver cpcap_usb_chgr_driver = { + .probe = cpcap_usb_chgr_probe, + .remove = cpcap_usb_chgr_remove, + .driver = { + .name = "cpcap_usb_charger", + .owner = THIS_MODULE, + }, +}; + +static int cpcap_usb_chgr_probe(struct platform_device *pdev) +{ + int ret = 0; + struct cpcap_device *cpcap; + + if (pdev->dev.platform_data == NULL) { + dev_err(&pdev->dev, "no platform_data\n"); + return -EINVAL; + } + + cpcap = pdev->dev.platform_data; + platform_set_drvdata(pdev, cpcap); + + cpcap_batt_set_usb_prop_curr(cpcap, 500); + cpcap_batt_set_usb_prop_online(cpcap, 1, CPCAP_BATT_USB_MODEL_USB); + + return ret; +} + +static int cpcap_usb_chgr_remove(struct platform_device *pdev) +{ + struct cpcap_device *cpcap = platform_get_drvdata(pdev); + + cpcap_batt_set_usb_prop_curr(cpcap, 0); + cpcap_batt_set_usb_prop_online(cpcap, 0, CPCAP_BATT_USB_MODEL_NONE); + + return 0; +} + +static int __init cpcap_usb_chgr_init(void) +{ + return platform_driver_register(&cpcap_usb_chgr_driver); +} +module_init(cpcap_usb_chgr_init); + +static void __exit cpcap_usb_chgr_exit(void) +{ + platform_driver_unregister(&cpcap_usb_chgr_driver); +} +module_exit(cpcap_usb_chgr_exit); + +MODULE_ALIAS("platform:cpcap_usb_chgr"); +MODULE_DESCRIPTION("CPCAP USB Charger Device driver"); +MODULE_AUTHOR("Motorola"); +MODULE_LICENSE("GPL"); diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 8bb26446037..07ad8046641 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -514,5 +514,11 @@ config REGULATOR_AS3711 This driver provides support for the voltage regulators on the AS3711 PMIC +config REGULATOR_CPCAP + tristate "CPCAP regulator driver" + depends on MFD_CPCAP + help + Say Y here to support the voltage regulators on CPCAP + endif diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 47a34ff88f9..f9d18aacf81 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -70,6 +70,7 @@ obj-$(CONFIG_REGULATOR_WM831X) += wm831x-ldo.o obj-$(CONFIG_REGULATOR_WM8350) += wm8350-regulator.o obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o obj-$(CONFIG_REGULATOR_WM8994) += wm8994-regulator.o +obj-$(CONFIG_REGULATOR_CPCAP) += cpcap-regulator.o ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG diff --git a/drivers/regulator/cpcap-regulator.c b/drivers/regulator/cpcap-regulator.c new file mode 100644 index 00000000000..1bed6fb32ac --- /dev/null +++ b/drivers/regulator/cpcap-regulator.c @@ -0,0 +1,605 @@ +/* + * Copyright (C) 2009-2011 Motorola, 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/delay.h> + +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> + +#include <linux/spi/spi.h> +#include <linux/spi/cpcap.h> +#include <linux/spi/cpcap-regbits.h> + +#define CPCAP_REGULATOR(_name, _id) \ + { \ + .name = _name, \ + .id = _id, \ + .ops = &cpcap_regulator_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + } + + +static const int sw4_val_tbl[] = {600000, 612500, 625000, 637500, 650000, + 662500, 675000, 687500, 700000, 712500, + 725000, 737500, 750000, 762500, 775000, + 787500, 800000, 812500, 825000, 837500, + 850000, 862500, 885000, 887500, 900000, + 912500, 925000, 937500, 950000, 962500, + 975000, 987500, 1000000, 1012500, 1025000, + 1037500, 1050000, 1062500, 1075000, 1087500, + 1100000, 1112500, 1125000, 1137500, 1150000, + 1162500, 1175000, 1187500, 1200000, 1212500, + 1225000, 1237500, 1250000, 1262500, 1275000, + 1287500, 1300000, 1312500, 1325000, 1337500, + 1350000, 1362500, 1375000, 1387500, 1400000, + 1412500, 1425000, 1437500, 1450000}; +static const int sw5_val_tbl[] = {0, 5050000}; +static const int vcam_val_tbl[] = {2600000, 2700000, 2800000, 2900000}; +static const int vcsi_val_tbl[] = {1200000, 1800000}; +static const int vdac_val_tbl[] = {1200000, 1500000, 1800000, 2500000}; +static const int vdig_val_tbl[] = {1200000, 1350000, 1500000, 1875000}; +static const int vfuse_val_tbl[] = {1500000, 1600000, 1700000, 1800000, 1900000, + 2000000, 2100000, 2200000, 2300000, 2400000, + 2500000, 2600000, 2700000, 3150000}; +static const int vhvio_val_tbl[] = {2775000}; +static const int vsdio_val_tbl[] = {1500000, 1600000, 1800000, 2600000, + 2700000, 2800000, 2900000, 3000000}; +static const int vpll_val_tbl[] = {1200000, 1300000, 1400000, 1800000}; +static const int vrf1_val_tbl[] = {2775000, 2500000}; /* Yes, this is correct */ +static const int vrf2_val_tbl[] = {0, 2775000}; +static const int vrfref_val_tbl[] = {2500000, 2775000}; +static const int vwlan1_val_tbl[] = {1800000, 1900000}; +static const int vwlan2_val_tbl[] = {2775000, 3000000, 3300000, 3300000}; +static const int vsim_val_tbl[] = {1800000, 2900000}; +static const int vsimcard_val_tbl[] = {1800000, 2900000}; +static const int vvib_val_tbl[] = {1300000, 1800000, 2000000, 3000000}; +static const int vusb_val_tbl[] = {0, 3300000}; +static const int vaudio_val_tbl[] = {0, 2775000}; + +static struct { + const enum cpcap_reg reg; + const unsigned short mode_mask; + const unsigned short volt_mask; + const unsigned char volt_shft; + unsigned short mode_val; + unsigned short off_mode_val; + const int val_tbl_sz; + const int *val_tbl; + unsigned int mode_cntr; + const unsigned int volt_trans_time; /* in micro seconds */ + const unsigned int turn_on_time; /* in micro seconds */ +} cpcap_regltr_data[CPCAP_NUM_REGULATORS] = { + [CPCAP_SW4] = {CPCAP_REG_S4C1, + 0x6F00, + 0x007F, + 0, + 0x0000, + 0x0000, + ARRAY_SIZE(sw4_val_tbl), + sw4_val_tbl, + 0, + 100, + 1500}, + + [CPCAP_SW5] = {CPCAP_REG_S5C, + 0x002A, + 0x0000, + 0, + 0x0000, + 0x0000, + ARRAY_SIZE(sw5_val_tbl), + sw5_val_tbl, + 0, + 0, + 1500}, + + [CPCAP_VCAM] = {CPCAP_REG_VCAMC, + 0x0087, + 0x0030, + 4, + 0x0000, + 0x0000, + ARRAY_SIZE(vcam_val_tbl), + vcam_val_tbl, + 0, + 420, + 1000}, + + [CPCAP_VCSI] = {CPCAP_REG_VCSIC, + 0x0047, + 0x0010, + 4, + 0x0000, + 0x0000, + ARRAY_SIZE(vcsi_val_tbl), + vcsi_val_tbl, + 0, + 350, + 1000}, + + [CPCAP_VDAC] = {CPCAP_REG_VDACC, + 0x0087, + 0x0030, + 4, + 0x0000, + 0x0000, + ARRAY_SIZE(vdac_val_tbl), + vdac_val_tbl, + 0, + 420, + 1000}, + + [CPCAP_VDIG] = {CPCAP_REG_VDIGC, + 0x0087, + 0x0030, + 4, + 0x0000, + 0x0000, + ARRAY_SIZE(vdig_val_tbl), + vdig_val_tbl, + 0, + 420, + 1000}, + + [CPCAP_VFUSE] = {CPCAP_REG_VFUSEC, + 0x0080, + 0x000F, + 0, + 0x0000, + 0x0000, + ARRAY_SIZE(vfuse_val_tbl), + vfuse_val_tbl, + 0, + 420, + 1000}, + + [CPCAP_VHVIO] = {CPCAP_REG_VHVIOC, + 0x0017, + 0x0000, + 0, + 0x0000, + 0x0000, + ARRAY_SIZE(vhvio_val_tbl), + vhvio_val_tbl, + 0, + 0, + 1000}, + + [CPCAP_VSDIO] = {CPCAP_REG_VSDIOC, + 0x0087, + 0x0038, + 3, + 0x0000, + 0x0000, + ARRAY_SIZE(vsdio_val_tbl), + vsdio_val_tbl, + 0, + 420, + 1000}, + + [CPCAP_VPLL] = {CPCAP_REG_VPLLC, + 0x0043, + 0x0018, + 3, + 0x0000, + 0x0000, + ARRAY_SIZE(vpll_val_tbl), + vpll_val_tbl, + 0, + 420, + 100}, + + [CPCAP_VRF1] = {CPCAP_REG_VRF1C, + 0x00AC, + 0x0002, + 1, + 0x0000, + 0x0000, + ARRAY_SIZE(vrf1_val_tbl), + vrf1_val_tbl, + 0, + 10, + 1000}, + + [CPCAP_VRF2] = {CPCAP_REG_VRF2C, + 0x0023, + 0x0008, + 3, + 0x0000, + 0x0000, + ARRAY_SIZE(vrf2_val_tbl), + vrf2_val_tbl, + 0, + 10, + 1000}, + + [CPCAP_VRFREF] = {CPCAP_REG_VRFREFC, + 0x0023, + 0x0008, + 3, + 0x0000, + 0x0000, + ARRAY_SIZE(vrfref_val_tbl), + vrfref_val_tbl, + 0, + 420, + 100}, + + [CPCAP_VWLAN1] = {CPCAP_REG_VWLAN1C, + 0x0047, + 0x0010, + 4, + 0x0000, + 0x0000, + ARRAY_SIZE(vwlan1_val_tbl), + vwlan1_val_tbl, + 0, + 420, + 1000}, + + [CPCAP_VWLAN2] = {CPCAP_REG_VWLAN2C, + 0x020C, + 0x00C0, + 6, + 0x0000, + 0x0000, + ARRAY_SIZE(vwlan2_val_tbl), + vwlan2_val_tbl, + 0, + 420, + 1000}, + + [CPCAP_VSIM] = {CPCAP_REG_VSIMC, + 0x0023, + 0x0008, + 3, + 0x0000, + 0x0000, + ARRAY_SIZE(vsim_val_tbl), + vsim_val_tbl, + 0, + 420, + 1000}, + + [CPCAP_VSIMCARD] = {CPCAP_REG_VSIMC, + 0x1E80, + 0x0008, + 3, + 0x0000, + 0x0000, + ARRAY_SIZE(vsimcard_val_tbl), + vsimcard_val_tbl, + 0, + 420, + 1000}, + + [CPCAP_VVIB] = {CPCAP_REG_VVIBC, + 0x0001, + 0x000C, + 2, + 0x0000, + 0x0000, + ARRAY_SIZE(vvib_val_tbl), + vvib_val_tbl, + 0, + 500, + 500}, + + [CPCAP_VUSB] = {CPCAP_REG_VUSBC, + 0x011C, + 0x0040, + 6, + 0x0000, + 0x0000, + ARRAY_SIZE(vusb_val_tbl), + vusb_val_tbl, + 0, + 0, + 1000}, + + [CPCAP_VAUDIO] = {CPCAP_REG_VAUDIOC, + 0x0016, + 0x0001, + 0, + 0x0000, + 0x0000, + ARRAY_SIZE(vaudio_val_tbl), + vaudio_val_tbl, + 0, + 0, + 1000}, +}; + +static int cpcap_regulator_set_voltage(struct regulator_dev *rdev, int min_uV, + int max_uV, unsigned *selector) +{ + struct cpcap_device *cpcap; + int regltr_id; + int retval; + enum cpcap_reg regnr; + int i; + + cpcap = rdev_get_drvdata(rdev); + + regltr_id = rdev_get_id(rdev); + if (regltr_id >= CPCAP_NUM_REGULATORS) + return -EINVAL; + + regnr = cpcap_regltr_data[regltr_id].reg; + + if (regltr_id == CPCAP_VRF1) { + if (min_uV > 2500000) + i = 0; + else + i = cpcap_regltr_data[regltr_id].volt_mask; + } else { + for (i = 0; i < cpcap_regltr_data[regltr_id].val_tbl_sz; i++) + if (cpcap_regltr_data[regltr_id].val_tbl[i] >= min_uV) + break; + + if (i >= cpcap_regltr_data[regltr_id].val_tbl_sz) + i--; + + i <<= cpcap_regltr_data[regltr_id].volt_shft; + } + + retval = cpcap_regacc_write(cpcap, regnr, i, + cpcap_regltr_data[regltr_id].volt_mask); + + if ((cpcap_regltr_data[regltr_id].volt_trans_time) && (retval == 0)) + udelay(cpcap_regltr_data[regltr_id].volt_trans_time); + + return retval; +} + +static int cpcap_regulator_get_voltage(struct regulator_dev *rdev) +{ + struct cpcap_device *cpcap; + int regltr_id; + unsigned short volt_bits; + enum cpcap_reg regnr; + unsigned int shift; + + cpcap = rdev_get_drvdata(rdev); + + regltr_id = rdev_get_id(rdev); + if (regltr_id >= CPCAP_NUM_REGULATORS) + return -EINVAL; + + regnr = cpcap_regltr_data[regltr_id].reg; + + if (cpcap_regacc_read(cpcap, regnr, &volt_bits) < 0) + return -1; + + if (!(volt_bits & cpcap_regltr_data[regltr_id].mode_mask)) + return 0; + + volt_bits &= cpcap_regltr_data[regltr_id].volt_mask; + shift = cpcap_regltr_data[regltr_id].volt_shft; + + return cpcap_regltr_data[regltr_id].val_tbl[volt_bits >> shift]; +} + +static int cpcap_regulator_enable(struct regulator_dev *rdev) +{ + struct cpcap_device *cpcap = rdev_get_drvdata(rdev); + int regltr_id; + int retval; + enum cpcap_reg regnr; + + regltr_id = rdev_get_id(rdev); + if (regltr_id >= CPCAP_NUM_REGULATORS) + return -EINVAL; + + regnr = cpcap_regltr_data[regltr_id].reg; + + retval = cpcap_regacc_write(cpcap, regnr, + cpcap_regltr_data[regltr_id].mode_val, + cpcap_regltr_data[regltr_id].mode_mask); + + if ((cpcap_regltr_data[regltr_id].turn_on_time) && (retval == 0)) + udelay(cpcap_regltr_data[regltr_id].turn_on_time); + + return retval; +} + +static int cpcap_regulator_disable(struct regulator_dev *rdev) +{ + struct cpcap_device *cpcap = rdev_get_drvdata(rdev); + int regltr_id; + enum cpcap_reg regnr; + + regltr_id = rdev_get_id(rdev); + if (regltr_id >= CPCAP_NUM_REGULATORS) + return -EINVAL; + + regnr = cpcap_regltr_data[regltr_id].reg; + + return cpcap_regacc_write(cpcap, regnr, + cpcap_regltr_data[regltr_id].off_mode_val, + cpcap_regltr_data[regltr_id].mode_mask); +} + +static int cpcap_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct cpcap_device *cpcap = rdev_get_drvdata(rdev); + int regltr_id; + enum cpcap_reg regnr; + unsigned short value; + + regltr_id = rdev_get_id(rdev); + if (regltr_id >= CPCAP_NUM_REGULATORS) + return -EINVAL; + + regnr = cpcap_regltr_data[regltr_id].reg; + + if (cpcap_regacc_read(cpcap, regnr, &value)) + return -1; + + return ((value & cpcap_regltr_data[regltr_id].mode_mask) + == cpcap_regltr_data[regltr_id].mode_val) ? 1 : 0; +} + +static int cpcap_regulator_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct cpcap_device *cpcap = rdev_get_drvdata(rdev); + int regltr_id; + enum cpcap_reg regnr; + int ret = 0; + + regltr_id = rdev_get_id(rdev); + if (regltr_id != CPCAP_VAUDIO) + return -EINVAL; + + regnr = cpcap_regltr_data[regltr_id].reg; + + if (mode == REGULATOR_MODE_NORMAL) { + if (cpcap_regltr_data[regltr_id].mode_cntr == 0) { + ret = cpcap_regacc_write(cpcap, regnr, + 0, + CPCAP_BIT_AUDIO_LOW_PWR); + } + if (ret == 0) + cpcap_regltr_data[regltr_id].mode_cntr++; + } else if (mode == REGULATOR_MODE_STANDBY) { + if (cpcap_regltr_data[regltr_id].mode_cntr == 1) { + ret = cpcap_regacc_write(cpcap, regnr, + CPCAP_BIT_AUDIO_LOW_PWR, + CPCAP_BIT_AUDIO_LOW_PWR); + } else if (WARN((cpcap_regltr_data[regltr_id].mode_cntr == 0), + "Unbalanced modes for supply vaudio\n")) + ret = -EIO; + + if (ret == 0) + cpcap_regltr_data[regltr_id].mode_cntr--; + } + + return ret; +} + +static struct regulator_ops cpcap_regulator_ops = { + .set_voltage = cpcap_regulator_set_voltage, + .get_voltage = cpcap_regulator_get_voltage, + .enable = cpcap_regulator_enable, + .disable = cpcap_regulator_disable, + .is_enabled = cpcap_regulator_is_enabled, + .set_mode = cpcap_regulator_set_mode, +}; + +static struct regulator_desc regulators[] = { + [CPCAP_SW4] = CPCAP_REGULATOR("sw4", CPCAP_SW4), + [CPCAP_SW5] = CPCAP_REGULATOR("sw5", CPCAP_SW5), + [CPCAP_VCAM] = CPCAP_REGULATOR("vcam", CPCAP_VCAM), + [CPCAP_VCSI] = CPCAP_REGULATOR("vcsi", CPCAP_VCSI), + [CPCAP_VDAC] = CPCAP_REGULATOR("vdac", CPCAP_VDAC), + [CPCAP_VDIG] = CPCAP_REGULATOR("vdig", CPCAP_VDIG), + [CPCAP_VFUSE] = CPCAP_REGULATOR("vfuse", CPCAP_VFUSE), + [CPCAP_VHVIO] = CPCAP_REGULATOR("vhvio", CPCAP_VHVIO), + [CPCAP_VSDIO] = CPCAP_REGULATOR("vsdio", CPCAP_VSDIO), + [CPCAP_VPLL] = CPCAP_REGULATOR("vpll", CPCAP_VPLL), + [CPCAP_VRF1] = CPCAP_REGULATOR("vrf1", CPCAP_VRF1), + [CPCAP_VRF2] = CPCAP_REGULATOR("vrf2", CPCAP_VRF2), + [CPCAP_VRFREF] = CPCAP_REGULATOR("vrfref", CPCAP_VRFREF), + [CPCAP_VWLAN1] = CPCAP_REGULATOR("vwlan1", CPCAP_VWLAN1), + [CPCAP_VWLAN2] = CPCAP_REGULATOR("vwlan2", CPCAP_VWLAN2), + [CPCAP_VSIM] = CPCAP_REGULATOR("vsim", CPCAP_VSIM), + [CPCAP_VSIMCARD] = CPCAP_REGULATOR("vsimcard", CPCAP_VSIMCARD), + [CPCAP_VVIB] = CPCAP_REGULATOR("vvib", CPCAP_VVIB), + [CPCAP_VUSB] = CPCAP_REGULATOR("vusb", CPCAP_VUSB), + [CPCAP_VAUDIO] = CPCAP_REGULATOR("vaudio", CPCAP_VAUDIO), +}; + +static int cpcap_regulator_probe(struct platform_device *pdev) +{ + struct regulator_dev *rdev; + struct cpcap_device *cpcap; + struct cpcap_platform_data *data; + struct regulator_config config; + int i; + + /* Already set by core driver */ + cpcap = platform_get_drvdata(pdev); + data = cpcap->spi->controller_data; + config.dev = &pdev->dev; + config.init_data = pdev->dev.platform_data; + config.driver_data = platform_get_drvdata(pdev); + + for (i = 0; i < CPCAP_NUM_REGULATORS; i++) { + cpcap_regltr_data[i].mode_val = data->regulator_mode_values[i]; + cpcap_regltr_data[i].off_mode_val = + data->regulator_off_mode_values[i]; + } + + rdev = regulator_register(®ulators[pdev->id], &config); + if (IS_ERR(rdev)) + return PTR_ERR(rdev); + /* this is ok since the cpcap is still reachable from the rdev */ + platform_set_drvdata(pdev, rdev); + + if (pdev->id == CPCAP_SW5) { + data->regulator_init = + cpcap->regulator_pdev[CPCAP_VUSB]->dev.platform_data; + data->regulator_init->supply_regulator = + regulators[CPCAP_SW5].name; + platform_device_add(cpcap->regulator_pdev[CPCAP_VUSB]); + } + + return 0; +} + +static int cpcap_regulator_remove(struct platform_device *pdev) +{ + struct regulator_dev *rdev = platform_get_drvdata(pdev); + + regulator_unregister(rdev); + + return 0; +} + +static struct platform_driver cpcap_regulator_driver = { + .driver = { + .name = "cpcap-regltr", + }, + .probe = cpcap_regulator_probe, + .remove = cpcap_regulator_remove, +}; + +static int __init cpcap_regulator_init(void) +{ + return platform_driver_register(&cpcap_regulator_driver); +} +subsys_initcall(cpcap_regulator_init); + +static void __exit cpcap_regulator_exit(void) +{ + platform_driver_unregister(&cpcap_regulator_driver); +} +module_exit(cpcap_regulator_exit); + +MODULE_ALIAS("platform:cpcap-regulator"); +MODULE_DESCRIPTION("CPCAP regulator driver"); +MODULE_AUTHOR("Motorola"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/f_usbnet.c b/drivers/usb/gadget/f_usbnet.c new file mode 100644 index 00000000000..5431406aa20 --- /dev/null +++ b/drivers/usb/gadget/f_usbnet.c @@ -0,0 +1,839 @@ +/* + * Gadget Driver for Motorola USBNet + * + * Copyright (C) 2009 Motorola Corporation + * + * 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 + */ + +#include <linux/device.h> +#include <linux/fcntl.h> +#include <linux/spinlock.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/if.h> +#include <linux/inetdevice.h> +#include <linux/uaccess.h> +#include <linux/workqueue.h> + +#include <linux/usb/ch9.h> +#include "gadget_chips.h" + + +/* + * Macro Defines + */ + +#define EP0_BUFSIZE 256 + + +/* Vendor Request to config IP */ +#define USBNET_SET_IP_ADDRESS 0x05 +#define USBNET_SET_SUBNET_MASK 0x06 +#define USBNET_SET_HOST_IP 0x07 + +/* Linux Network Interface */ +#define USB_MTU 1536 +#define MAX_BULK_TX_REQ_NUM 8 +#define MAX_BULK_RX_REQ_NUM 8 +#define MAX_INTR_RX_REQ_NUM 8 +#define STRING_INTERFACE 0 + +struct usbnet_context { + spinlock_t lock; /* For RX/TX list */ + struct net_device *dev; + + struct usb_gadget *gadget; + + struct usb_ep *bulk_in; + struct usb_ep *bulk_out; + struct usb_ep *intr_out; + u16 config; /* current USB config w_value */ + + struct list_head rx_reqs; + struct list_head tx_reqs; + + struct net_device_stats stats; + struct work_struct usbnet_config_wq; + u32 ip_addr; + u32 subnet_mask; + u32 router_ip; + u32 iff_flag; +}; + + +struct usbnet_device { + struct usb_function function; + struct usb_composite_dev *cdev; + struct usbnet_context *net_ctxt; +}; + +/* static strings, in UTF-8 */ +static struct usb_string usbnet_string_defs[] = { + [STRING_INTERFACE].s = "Motorola Networking Interface", + { /* ZEROES END LIST */ }, +}; + +static struct usb_gadget_strings usbnet_string_table = { + .language = 0x0409, /* en-us */ + .strings = usbnet_string_defs, +}; + +static struct usb_gadget_strings *usbnet_strings[] = { + &usbnet_string_table, + NULL, +}; + + + +/* There is only one interface. */ + +static struct usb_interface_descriptor usbnet_intf_desc = { + .bLength = sizeof usbnet_intf_desc, + .bDescriptorType = USB_DT_INTERFACE, + + .bNumEndpoints = 3, + .bInterfaceClass = 0x02, + .bInterfaceSubClass = 0x0a, + .bInterfaceProtocol = 0x01, +}; + + +static struct usb_endpoint_descriptor usbnet_fs_bulk_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor usbnet_fs_bulk_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor fs_intr_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(64), + .bInterval = 1, +}; + +static struct usb_descriptor_header *fs_function[] = { + (struct usb_descriptor_header *) &usbnet_intf_desc, + (struct usb_descriptor_header *) &usbnet_fs_bulk_in_desc, + (struct usb_descriptor_header *) &usbnet_fs_bulk_out_desc, + (struct usb_descriptor_header *) &fs_intr_out_desc, + NULL, +}; + +static struct usb_endpoint_descriptor usbnet_hs_bulk_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), + .bInterval = 0, +}; + +static struct usb_endpoint_descriptor usbnet_hs_bulk_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), + .bInterval = 0, +}; + +static struct usb_endpoint_descriptor hs_intr_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(64), + .bInterval = 1, +}; + +static struct usb_descriptor_header *hs_function[] = { + (struct usb_descriptor_header *) &usbnet_intf_desc, + (struct usb_descriptor_header *) &usbnet_hs_bulk_in_desc, + (struct usb_descriptor_header *) &usbnet_hs_bulk_out_desc, + (struct usb_descriptor_header *) &hs_intr_out_desc, + NULL, +}; +/* +static struct switch_dev usbnet_enable_device = { + .name = "usbnet_enable", +};*/ + +#define DO_NOT_STOP_QUEUE 0 +#define STOP_QUEUE 1 + +#define USBNETDBG(context, fmt, args...) \ + if (context && context->gadget) \ + dev_err(&(context->gadget->dev) , fmt , ## args) + +static const char *usb_description = "Motorola BLAN Interface"; + +static ssize_t usbnet_desc_show(struct device *dev, + struct device_attribute *attr, char *buff) +{ + ssize_t status = 0; + status = sprintf(buff, "%s\n", usb_description); + return status; +} + +static DEVICE_ATTR(description, S_IRUGO, usbnet_desc_show, NULL); + +static inline struct usbnet_device *usbnet_func_to_dev(struct usb_function *f) +{ + return container_of(f, struct usbnet_device, function); +} + + +static int ether_queue_out(struct usb_request *req , + struct usbnet_context *context) +{ + unsigned long flags; + struct sk_buff *skb; + int ret; + + skb = alloc_skb(USB_MTU + NET_IP_ALIGN, GFP_ATOMIC); + if (!skb) { + USBNETDBG(context, "%s: failed to alloc skb\n", __func__); + ret = -ENOMEM; + goto fail; + } + + /* + * RX: Do not move data by IP_ALIGN + * if the DMA Controller cannot handle it + */ + if (!gadget_dma32(context->gadget)) + skb_reserve(skb, NET_IP_ALIGN); + + req->buf = skb->data; + req->length = USB_MTU; + req->context = skb; + + ret = usb_ep_queue(context->bulk_out, req, GFP_KERNEL); + if (ret == 0) + return 0; + else + kfree_skb(skb); +fail: + spin_lock_irqsave(&context->lock, flags); + list_add_tail(&req->list, &context->rx_reqs); + spin_unlock_irqrestore(&context->lock, flags); + + return ret; +} + +struct usb_request *usb_get_recv_request(struct usbnet_context *context) +{ + unsigned long flags; + struct usb_request *req; + + spin_lock_irqsave(&context->lock, flags); + if (list_empty(&context->rx_reqs)) { + req = NULL; + } else { + req = list_first_entry(&context->rx_reqs, + struct usb_request, list); + list_del(&req->list); + } + spin_unlock_irqrestore(&context->lock, flags); + + return req; +} + +struct usb_request *usb_get_xmit_request(int stop_flag, struct net_device *dev) +{ + struct usbnet_context *context = netdev_priv(dev); + unsigned long flags; + struct usb_request *req; + + spin_lock_irqsave(&context->lock, flags); + if (list_empty(&context->tx_reqs)) { + req = NULL; + } else { + req = list_first_entry(&context->tx_reqs, + struct usb_request, list); + list_del(&req->list); + if (stop_flag == STOP_QUEUE && + list_empty(&context->tx_reqs)) + netif_stop_queue(dev); + } + spin_unlock_irqrestore(&context->lock, flags); + return req; +} + +static int usb_ether_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct usbnet_context *context = netdev_priv(dev); + struct usb_request *req; + unsigned long flags; + unsigned len; + int rc; + + req = usb_get_xmit_request(STOP_QUEUE, dev); + + if (!req) { + USBNETDBG(context, "%s: could not obtain tx request\n", + __func__); + return 1; + } + + /* Add 4 bytes CRC */ + skb->len += 4; + + /* ensure that we end with a short packet */ + len = skb->len; + if (!(len & 63) || !(len & 511)) + len++; + + /* Align Data to 32 bit if the dma controller requires it */ + if (gadget_dma32(context->gadget)) { + unsigned long align = (unsigned long)skb->data & 3; + if (WARN_ON(skb_headroom(skb) < align)) { + goto drop; + } else if (align) { + u8 *data = skb->data; + size_t len = skb_headlen(skb); + skb->data -= align; + memmove(skb->data, data, len); + skb_set_tail_pointer(skb, len); + } + } + + req->context = skb; + req->buf = skb->data; + req->length = len; + + rc = usb_ep_queue(context->bulk_in, req, GFP_KERNEL); + if (rc != 0) { +drop: + spin_lock_irqsave(&context->lock, flags); + if (list_empty(&context->tx_reqs)) + netif_start_queue(context->dev); + list_add_tail(&req->list, &context->tx_reqs); + spin_unlock_irqrestore(&context->lock, flags); + + dev_kfree_skb_any(skb); + context->stats.tx_dropped++; + + USBNETDBG(context, + "%s: could not queue tx request\n", __func__); + } + + return 0; +} + +static int usb_ether_open(struct net_device *dev) +{ + struct usbnet_context *context = netdev_priv(dev); + USBNETDBG(context, "%s\n", __func__); + return 0; +} + +static int usb_ether_stop(struct net_device *dev) +{ + struct usbnet_context *context = netdev_priv(dev); + USBNETDBG(context, "%s\n", __func__); + return 0; +} + +static struct net_device_stats *usb_ether_get_stats(struct net_device *dev) +{ + struct usbnet_context *context = netdev_priv(dev); + USBNETDBG(context, "%s\n", __func__); + return &context->stats; +} + +static void usbnet_if_config(struct work_struct *work) +{ + struct ifreq ifr; + mm_segment_t saved_fs; + unsigned err; + struct sockaddr_in *sin; + struct usbnet_context *context = container_of(work, + struct usbnet_context, usbnet_config_wq); + + pr_info("%s : Configuring with config = %d, ip_addr = 0x%08x," + " subnet = 0x%08x, router_ip = 0x%08x, flags = 0x%08x\n", + __func__, context->config, context->ip_addr, + context->subnet_mask, context->router_ip, context->iff_flag); + memset(&ifr, 0, sizeof(ifr)); + sin = (void *) &(ifr.ifr_ifru.ifru_addr); + strncpy(ifr.ifr_ifrn.ifrn_name, context->dev->name, + sizeof(ifr.ifr_ifrn.ifrn_name)); + sin->sin_family = AF_INET; + + sin->sin_addr.s_addr = context->ip_addr; + saved_fs = get_fs(); + set_fs(get_ds()); + err = devinet_ioctl(dev_net(context->dev), SIOCSIFADDR, &ifr); + if (err) + USBNETDBG(context, "%s: Error in SIOCSIFADDR\n", __func__); + + sin->sin_addr.s_addr = context->subnet_mask; + err = devinet_ioctl(dev_net(context->dev), SIOCSIFNETMASK, &ifr); + if (err) + USBNETDBG(context, "%s: Error in SIOCSIFNETMASK\n", __func__); + + sin->sin_addr.s_addr = context->ip_addr | ~(context->subnet_mask); + err = devinet_ioctl(dev_net(context->dev), SIOCSIFBRDADDR, &ifr); + if (err) + USBNETDBG(context, "%s: Error in SIOCSIFBRDADDR\n", __func__); + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_ifrn.ifrn_name, context->dev->name, + sizeof(ifr.ifr_ifrn.ifrn_name)); + ifr.ifr_flags = context->dev->flags & ~IFF_UP; + ifr.ifr_flags |= context->iff_flag; + err = devinet_ioctl(dev_net(context->dev), SIOCSIFFLAGS, &ifr); + if (err) + USBNETDBG(context, "%s: Error in SIOCSIFFLAGS\n", __func__); + + set_fs(saved_fs); + //switch_set_state(&usbnet_enable_device, context->config); +} + +static const struct net_device_ops usbnet_eth_netdev_ops = { + .ndo_open = usb_ether_open, + .ndo_stop = usb_ether_stop, + .ndo_start_xmit = usb_ether_xmit, + .ndo_get_stats = usb_ether_get_stats, +}; + +static void usb_ether_setup(struct net_device *dev) +{ + struct usbnet_context *context = netdev_priv(dev); + INIT_LIST_HEAD(&context->rx_reqs); + INIT_LIST_HEAD(&context->tx_reqs); + + spin_lock_init(&context->lock); + context->dev = dev; + + dev->netdev_ops = &usbnet_eth_netdev_ops; + dev->watchdog_timeo = 20; + + ether_setup(dev); + + random_ether_addr(dev->dev_addr); +} + +/*-------------------------------------------------------------------------*/ +static void usbnet_cleanup(struct usbnet_device *dev) +{ + struct usbnet_context *context = dev->net_ctxt; + if (context) { + device_remove_file(&(context->dev->dev), &dev_attr_description); + unregister_netdev(context->dev); + free_netdev(context->dev); + dev->net_ctxt = NULL; + } +} + +static void usbnet_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct usbnet_device *dev = usbnet_func_to_dev(f); + struct usb_composite_dev *cdev = c->cdev; + struct usbnet_context *context = dev->net_ctxt; + struct usb_request *req; + + dev->cdev = cdev; + + /* Free BULK OUT Requests */ + while ((req = usb_get_recv_request(context))) + usb_ep_free_request(context->bulk_out, req); + + /* Free BULK IN Requests */ + while ((req = usb_get_xmit_request(DO_NOT_STOP_QUEUE, + context->dev))) { + usb_ep_free_request(context->bulk_in, req); + } + + context->config = 0; +} + +static void ether_out_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct sk_buff *skb = req->context; + struct usbnet_context *context = ep->driver_data; + + if (req->status == 0) { + skb_put(skb, req->actual); + skb->protocol = eth_type_trans(skb, context->dev); + if (gadget_dma32(context->gadget) && NET_IP_ALIGN) { + u8 *data = skb->data; + size_t len = skb_headlen(skb); + skb_reserve(skb, NET_IP_ALIGN); + memmove(skb->data, data, len); + } + context->stats.rx_packets++; + context->stats.rx_bytes += req->actual; + netif_rx(skb); + } else { + dev_kfree_skb_any(skb); + context->stats.rx_errors++; + } + + /* don't bother requeuing if we just went offline */ + if ((req->status == -ENODEV) || (req->status == -ESHUTDOWN)) { + unsigned long flags; + spin_lock_irqsave(&context->lock, flags); + list_add_tail(&req->list, &context->rx_reqs); + spin_unlock_irqrestore(&context->lock, flags); + } else { + if (ether_queue_out(req, context)) + USBNETDBG(context, "ether_out: cannot requeue\n"); + } +} + +static void ether_in_complete(struct usb_ep *ep, struct usb_request *req) +{ + unsigned long flags; + struct sk_buff *skb = req->context; + struct usbnet_context *context = ep->driver_data; + + if (req->status == 0) { + context->stats.tx_packets++; + context->stats.tx_bytes += req->actual; + } else { + context->stats.tx_errors++; + } + + dev_kfree_skb_any(skb); + + spin_lock_irqsave(&context->lock, flags); + if (list_empty(&context->tx_reqs)) + netif_start_queue(context->dev); + + list_add_tail(&req->list, &context->tx_reqs); + spin_unlock_irqrestore(&context->lock, flags); +} + +static int usbnet_bind(struct usb_configuration *c, + struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct usbnet_device *dev = usbnet_func_to_dev(f); + struct usbnet_context *context = dev->net_ctxt; + int n, rc, id; + struct usb_ep *ep; + struct usb_request *req; + unsigned long flags; + + dev->cdev = cdev; + + id = usb_interface_id(c, f); + if (id < 0) + return id; + usbnet_intf_desc.bInterfaceNumber = id; + context->gadget = cdev->gadget; + + /* Find all the endpoints we will use */ + ep = usb_ep_autoconfig(cdev->gadget, &usbnet_fs_bulk_in_desc); + if (!ep) { + USBNETDBG(context, "%s auto-configure hs_bulk_in_desc error\n", + __func__); + goto autoconf_fail; + } + ep->driver_data = context; + context->bulk_in = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &usbnet_fs_bulk_out_desc); + if (!ep) { + USBNETDBG(context, "%s auto-configure hs_bulk_out_desc error\n", + __func__); + goto autoconf_fail; + } + ep->driver_data = context; + context->bulk_out = ep; + + + ep = usb_ep_autoconfig(cdev->gadget, &fs_intr_out_desc); + if (!ep) { + USBNETDBG(context, "%s auto-configure hs_intr_out_desc error\n", + __func__); + goto autoconf_fail; + } + ep->driver_data = context; + context->intr_out = ep; + + if (gadget_is_dualspeed(cdev->gadget)) { + + /* Assume endpoint addresses are the same for both speeds */ + usbnet_hs_bulk_in_desc.bEndpointAddress = + usbnet_fs_bulk_in_desc.bEndpointAddress; + usbnet_hs_bulk_out_desc.bEndpointAddress = + usbnet_fs_bulk_out_desc.bEndpointAddress; + hs_intr_out_desc.bEndpointAddress = + fs_intr_out_desc.bEndpointAddress; + } + + + rc = -ENOMEM; + + for (n = 0; n < MAX_BULK_RX_REQ_NUM; n++) { + req = usb_ep_alloc_request(context->bulk_out, + GFP_KERNEL); + if (!req) { + USBNETDBG(context, "%s: alloc request bulk_out fail\n", + __func__); + break; + } + req->complete = ether_out_complete; + spin_lock_irqsave(&context->lock, flags); + list_add_tail(&req->list, &context->rx_reqs); + spin_unlock_irqrestore(&context->lock, flags); + } + for (n = 0; n < MAX_BULK_TX_REQ_NUM; n++) { + req = usb_ep_alloc_request(context->bulk_in, + GFP_KERNEL); + if (!req) { + USBNETDBG(context, "%s: alloc request bulk_in fail\n", + __func__); + break; + } + req->complete = ether_in_complete; + spin_lock_irqsave(&context->lock, flags); + list_add_tail(&req->list, &context->tx_reqs); + spin_unlock_irqrestore(&context->lock, flags); + } + + return 0; + +autoconf_fail: + rc = -ENOTSUPP; + usbnet_unbind(c, f); + return rc; +} + + + + +static void do_set_config(struct usb_function *f, u16 new_config) +{ + struct usbnet_device *dev = usbnet_func_to_dev(f); + struct usbnet_context *context = dev->net_ctxt; + int result = 0; + struct usb_request *req; + int high_speed_flag = 0; + + if (context->config == new_config) /* Config did not change */ + return; + + context->config = new_config; + + if (new_config == 1) { /* Enable End points */ + if (gadget_is_dualspeed(context->gadget) + && context->gadget->speed == USB_SPEED_HIGH) + high_speed_flag = 1; + + if (high_speed_flag){ + context->bulk_in->desc = &usbnet_hs_bulk_in_desc; + result = usb_ep_enable(context->bulk_in); + } + else{ + context->bulk_in->desc = &usbnet_fs_bulk_in_desc; + result = usb_ep_enable(context->bulk_in); + } + + if (result != 0) { + USBNETDBG(context, + "%s: failed to enable BULK_IN EP ret=%d\n", + __func__, result); + } + + context->bulk_in->driver_data = context; + + if (high_speed_flag){ + context->bulk_out->desc = &usbnet_hs_bulk_out_desc; + result = usb_ep_enable(context->bulk_out); + } + else{ + context->bulk_out->desc = &usbnet_fs_bulk_out_desc; + result = usb_ep_enable(context->bulk_out); + } + + if (result != 0) { + USBNETDBG(context, + "%s: failed to enable BULK_OUT EP ret = %d\n", + __func__, result); + } + + context->bulk_out->driver_data = context; + + if (high_speed_flag){ + context->intr_out->desc = &hs_intr_out_desc; + result = usb_ep_enable(context->intr_out); + } + else{ + context->intr_out->desc = &fs_intr_out_desc; + result = usb_ep_enable(context->intr_out); + } + + if (result != 0) { + USBNETDBG(context, + "%s: failed to enable INTR_OUT EP ret = %d\n", + __func__, result); + } + + context->intr_out->driver_data = context; + + /* we're online -- get all rx requests queued */ + while ((req = usb_get_recv_request(context))) { + if (ether_queue_out(req, context)) { + USBNETDBG(context, + "%s: ether_queue_out failed\n", + __func__); + break; + } + } + + } else {/* Disable Endpoints */ + if (context->bulk_in) + usb_ep_disable(context->bulk_in); + if (context->bulk_out) + usb_ep_disable(context->bulk_out); + if (context->intr_out) + usb_ep_disable(context->intr_out); + context->ip_addr = 0; + context->subnet_mask = 0; + context->router_ip = 0; + context->iff_flag = 0; + schedule_work(&context->usbnet_config_wq); + } +} + + +static int usbnet_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct usbnet_device *dev = usbnet_func_to_dev(f); + struct usbnet_context *context = dev->net_ctxt; + USBNETDBG(context, "usbnet_set_alt intf: %d alt: %d\n", intf, alt); + do_set_config(f, 1); + return 0; +} + +static int usbnet_ctrlrequest(struct usbnet_device *dev, + struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *ctrl) +{ + struct usbnet_context *context = dev->net_ctxt; + int rc = -EOPNOTSUPP; + int wIndex = le16_to_cpu(ctrl->wIndex); + int wValue = le16_to_cpu(ctrl->wValue); + int wLength = le16_to_cpu(ctrl->wLength); + struct usb_request *req = cdev->req; + + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_VENDOR) { + switch (ctrl->bRequest) { + case USBNET_SET_IP_ADDRESS: + context->ip_addr = (wValue << 16) | wIndex; + rc = 0; + break; + case USBNET_SET_SUBNET_MASK: + context->subnet_mask = (wValue << 16) | wIndex; + rc = 0; + break; + case USBNET_SET_HOST_IP: + context->router_ip = (wValue << 16) | wIndex; + rc = 0; + break; + default: + break; + } + + if (context->ip_addr && context->subnet_mask + && context->router_ip) { + context->iff_flag = IFF_UP; + /* schedule a work queue to do this because we + need to be able to sleep */ + schedule_work(&context->usbnet_config_wq); + } + } + + /* respond with data transfer or status phase? */ + if (rc >= 0) { + req->zero = rc < wLength; + req->length = rc; + rc = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (rc < 0) + USBNETDBG(context, "usbnet setup response error\n"); + } + + return rc; +} + +static void usbnet_disable(struct usb_function *f) +{ + struct usbnet_device *dev = usbnet_func_to_dev(f); + struct usbnet_context *context = dev->net_ctxt; + USBNETDBG(context, "%s\n", __func__); + do_set_config(f, 0); +} + +static void usbnet_suspend(struct usb_function *f) +{ + struct usbnet_device *dev = usbnet_func_to_dev(f); + struct usbnet_context *context = dev->net_ctxt; + USBNETDBG(context, "%s\n", __func__); +} + +static void usbnet_resume(struct usb_function *f) +{ + struct usbnet_device *dev = usbnet_func_to_dev(f); + struct usbnet_context *context = dev->net_ctxt; + USBNETDBG(context, "%s\n", __func__); +} + +int usbnet_bind_config(struct usbnet_device *dev, struct usb_configuration *c) +{ + int ret, status; + + pr_debug("usbnet_bind_config\n"); + + if (usbnet_string_defs[STRING_INTERFACE].id == 0) { + status = usb_string_id(c->cdev); + if (status >= 0) { + usbnet_string_defs[STRING_INTERFACE].id = status; + usbnet_intf_desc.iInterface = status; + } + } + + dev->cdev = c->cdev; + dev->function.name = "usbnet"; + dev->function.fs_descriptors = fs_function; + dev->function.hs_descriptors = hs_function; + dev->function.bind = usbnet_bind; + dev->function.unbind = usbnet_unbind; + dev->function.set_alt = usbnet_set_alt; + dev->function.disable = usbnet_disable; + dev->function.suspend = usbnet_suspend; + dev->function.resume = usbnet_resume; + dev->function.strings = usbnet_strings; + + ret = usb_add_function(c, &dev->function); + + return ret; +} diff --git a/drivers/usb/gadget/gadget_chips.h b/drivers/usb/gadget/gadget_chips.h index bcd04bc66b9..9ee7a549651 100644 --- a/drivers/usb/gadget/gadget_chips.h +++ b/drivers/usb/gadget/gadget_chips.h @@ -51,5 +51,14 @@ static inline bool gadget_supports_altsettings(struct usb_gadget *gadget) /* Everything else is *presumably* fine ... */ return true; } - + /** + * gadget_dma32 - return true if we want buffer aligned on 32 bits (for dma) + * @gadget: the gadget in question + */ +static inline bool gadget_dma32(struct usb_gadget *gadget) +{ + if (gadget_is_musbhdrc(gadget)) + return true; + return false; +} #endif /* __GADGET_CHIPS_H */ diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index 3312ad2bb67..6fcfa960fad 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -217,5 +217,9 @@ config USB_ULPI_VIEWPORT help Provides read/write operations to the ULPI phy register set for controllers with a viewport register (e.g. Chipidea/ARC controllers). - +config CPCAP_USB + bool "CPCAP USB Transceiver Driver" + help + Enable this to support the USB OTG transceiver on the + Motorola CPCAP PMIC endif # USB_PHY diff --git a/drivers/usb/phy/Makefile b/drivers/usb/phy/Makefile index a0a6cbad880..bef5f525ab8 100644 --- a/drivers/usb/phy/Makefile +++ b/drivers/usb/phy/Makefile @@ -32,3 +32,4 @@ obj-$(CONFIG_USB_MXS_PHY) += phy-mxs-usb.o obj-$(CONFIG_USB_RCAR_PHY) += phy-rcar-usb.o obj-$(CONFIG_USB_ULPI) += phy-ulpi.o obj-$(CONFIG_USB_ULPI_VIEWPORT) += phy-ulpi-viewport.o +obj-$(CONFIG_CPCAP_USB) += cpcap-usb.o diff --git a/drivers/usb/phy/cpcap-usb.c b/drivers/usb/phy/cpcap-usb.c new file mode 100644 index 00000000000..6e7bc16b695 --- /dev/null +++ b/drivers/usb/phy/cpcap-usb.c @@ -0,0 +1,211 @@ +/* + * cpcap_usb - CPCAP USB transceiver, talking to OMAP OTG controller + * + * Copyright (C) 2004-2007 Texas Instruments + * Copyright (C) 2008 Nokia Corporation + * Copyright (C) 2009 Google, Inc. + * Contact: Erik Gilling <konkers@android.com> + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Based on twl4030-usb.c + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/workqueue.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/usb/otg.h> +#include <linux/spi/cpcap.h> +#include <linux/spi/cpcap-regbits.h> + +enum linkstat { + USB_LINK_UNKNOWN = 0, + USB_LINK_NONE, + USB_LINK_VBUS, + USB_LINK_ID, +}; + +struct cpcap_usb { + //struct otg_transceiver otg; + struct usb_phy phy; + struct device *dev; + + struct cpcap_device *cpcap; + + /* for vbus reporting with irqs disabled */ + spinlock_t lock; + + int irq; + u8 linkstat; + u8 asleep; + bool irq_enabled; +}; + +static int cpcap_set_suspend(struct usb_phy *x, int suspend) +{ + return 0; +} + + +static int cpcap_set_peripheral(struct usb_otg *otg, + struct usb_gadget *gadget) +{ + struct usb_phy *phy = otg->phy; + + otg->gadget = gadget; + if (!gadget) + phy->state = OTG_STATE_UNDEFINED; + + return 0; +} + +static int cpcap_set_host(struct usb_otg *otg, struct usb_bus *host) +{ + struct usb_phy *phy = otg->phy; + + otg->host = host; + if (!host) + phy->state = OTG_STATE_UNDEFINED; + + return 0; +} + + +static int cpcap_usb_setup(struct cpcap_usb *cpcap) +{ + unsigned short mask; + unsigned short value; + int r; + + r = cpcap_regacc_read(cpcap->cpcap, CPCAP_REG_INTS2, &value); + + if (value & CPCAP_BIT_SE1_S) + mask = CPCAP_BIT_VBUSEN_SPI | CPCAP_BIT_VBUSPU_SPI | + CPCAP_BIT_SUSPEND_SPI | CPCAP_BIT_ULPI_SPI_SEL; + else + mask = CPCAP_BIT_VBUSEN_SPI | CPCAP_BIT_VBUSPU_SPI | + CPCAP_BIT_DMPD_SPI | CPCAP_BIT_DPPD_SPI | + CPCAP_BIT_SUSPEND_SPI | CPCAP_BIT_PU_SPI | + CPCAP_BIT_ULPI_SPI_SEL; + + r = cpcap_regacc_write(cpcap->cpcap, CPCAP_REG_USBC3, 0x0, mask); + if (r < 0) { + dev_err(cpcap->dev, + "Can't disable SPI control of CPCAP transceiver\n"); + return r; + } + return 0; +} +static int cpcap_usb_set_vbus(struct usb_otg *otg, bool enabled) +{ + return 0; +} +static int cpcap_usb_start_srp(struct usb_otg *otg) +{ + return 0; +} +static int cpcap_usb_probe(struct platform_device *pdev) +{ + + struct cpcap_usb *cpcap; + struct usb_otg *otg; + int err; + cpcap = devm_kzalloc(&pdev->dev, sizeof(*cpcap), GFP_KERNEL); + if (!cpcap) { + dev_err(&pdev->dev, "unable to allocate memory for cpcap PHY\n"); + return -ENOMEM; + } + + otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL); + if (!otg) { + dev_err(&pdev->dev, "unable to allocate memory for USB OTG\n"); + return -ENOMEM; + } + + cpcap->dev = &pdev->dev; + cpcap->phy.dev = cpcap->dev; + cpcap->phy.label = "cpcap"; + cpcap->phy.set_suspend = cpcap_set_suspend; + cpcap->phy.otg = otg; + cpcap->phy.type = USB_PHY_TYPE_USB2; + cpcap->asleep = 1; + cpcap->cpcap = pdev->dev.platform_data; + + + otg->set_host = cpcap_set_host; + otg->set_peripheral = cpcap_set_peripheral; + otg->set_vbus = cpcap_usb_set_vbus; + otg->start_srp = cpcap_usb_start_srp; + otg->phy = &cpcap->phy; + + spin_lock_init(&cpcap->lock); + usb_add_phy_dev(&cpcap->phy); + + platform_set_drvdata(pdev, cpcap); + + + err = cpcap_usb_setup(cpcap); + if (err < 0) + goto err0; + return 0; + +err0: + usb_remove_phy(&cpcap->phy); + devm_kfree(&pdev->dev,cpcap); + devm_kfree(&pdev->dev,otg); + return err; +} + +static int cpcap_usb_remove(struct platform_device *pdev) +{ + struct cpcap_usb *cpcap = platform_get_drvdata(pdev); + devm_kfree(&pdev->dev,cpcap); + + return 0; +} + +static struct platform_driver cpcap_usb_driver = { + .probe = cpcap_usb_probe, + .remove = (cpcap_usb_remove), + .driver = { + .name = "cpcap_usb", + .owner = THIS_MODULE, + }, +}; + +static int __init cpcap_usb_init(void) +{ + return cpcap_driver_register(&cpcap_usb_driver); +} +subsys_initcall(cpcap_usb_init); + +static void __exit cpcap_usb_exit(void) +{ + platform_driver_unregister(&cpcap_usb_driver); +} +module_exit(cpcap_usb_exit); + + + + +MODULE_ALIAS("platform:cpcap_usb"); +MODULE_DESCRIPTION("CPCAP USB transceiver driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/omap2/displays/Kconfig b/drivers/video/omap2/displays/Kconfig index c3853c92279..af8f97ebbe0 100644 --- a/drivers/video/omap2/displays/Kconfig +++ b/drivers/video/omap2/displays/Kconfig @@ -72,4 +72,11 @@ config PANEL_N8X0 depends on BACKLIGHT_CLASS_DEVICE help This is the LCD panel used on Nokia N8x0 + +config PANEL_MINNOW + tristate "Minnow DSI Command Mode Panel" + depends on OMAP2_DSS_DSI + help + Driver for Minnow DSI command mode panels. + endmenu diff --git a/drivers/video/omap2/displays/Makefile b/drivers/video/omap2/displays/Makefile index 58a5176b07b..abc0d9dae56 100644 --- a/drivers/video/omap2/displays/Makefile +++ b/drivers/video/omap2/displays/Makefile @@ -9,3 +9,4 @@ obj-$(CONFIG_PANEL_PICODLP) += panel-picodlp.o obj-$(CONFIG_PANEL_TPO_TD043MTEA1) += panel-tpo-td043mtea1.o obj-$(CONFIG_PANEL_ACX565AKM) += panel-acx565akm.o obj-$(CONFIG_PANEL_N8X0) += panel-n8x0.o +obj-$(CONFIG_PANEL_MINNOW) += panel-minnow.o diff --git a/drivers/video/omap2/displays/panel-minnow.c b/drivers/video/omap2/displays/panel-minnow.c new file mode 100644 index 00000000000..aa0a380a5da --- /dev/null +++ b/drivers/video/omap2/displays/panel-minnow.c @@ -0,0 +1,1761 @@ +/* + * Minnow DSI command mode panel + * + * Copyright (C) 2013 Motorola Mobility LLC. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +/*#define DEBUG*/ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/jiffies.h> +#include <linux/sched.h> +#include <linux/backlight.h> +#include <linux/fb.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <linux/workqueue.h> +#include <linux/slab.h> +#include <linux/mutex.h> + +#include <video/omapdss.h> +#include <video/omap-panel-data.h> +#include <video/mipi_display.h> + +/* DSI Virtual channel. Hardcoded for now. */ +#define TCH 0 + +#define DCS_READ_NUM_ERRORS 0x05 +#define DCS_BRIGHTNESS 0x51 +#define DCS_CTRL_DISPLAY 0x53 +#define DCS_WRITE_CABC 0x55 +#define DCS_READ_CABC 0x56 +#define DCS_GET_ID1 0xda +#define DCS_GET_ID2 0xdb +#define DCS_GET_ID3 0xdc + +enum minnow_panel_id { + MINNOW_PANEL_CM_220X176, + MINNOW_PANEL_CM_220X220, + MINNOW_PANEL_MAX +}; + +/* Panel Initialize DSI DCS command buffer description: + * it uses compact DCS command buffer to store all DCS commands, the first + * byte of each command is the command length in byte + */ +static u8 panel_init_220x176[] = { +/*n, data_0, data_1 ... data_n-1*/ + 3, 0xF0, 0x5A, 0x5A, + 3, 0xF1, 0x5A, 0x5A, +18, 0xF2, 0x16, 0xDC, 0x03, 0x28, 0x28, 0x10, 0x00, 0x60, 0xF8, 0x00, 0x07, 0x02, 0x00, 0x00, 0xDC, 0x28, 0x28, +15, 0xF4, 0x0A, 0x00, 0x00, 0x00, 0x77, 0x7F, 0x07, 0x22, 0x2A, 0x43, 0x07, 0x2A, 0x43, 0x07, +11, 0xF5, 0x00, 0x50, 0x28, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, +10, 0xF6, 0x07, 0x00, 0x07, 0x00, 0x0B, 0x04, 0x04, 0x04, 0x07, + 5, 0xF7, 0x00, 0x00, 0x00, 0x00, + 3, 0xF8, 0x44, 0x08, + 2, 0xF9, 0x04, +17, 0xFA, 0x0F, 0x0F, 0x1E, 0x23, 0x26, 0x2D, 0x21, 0x2B, 0x33, 0x32, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, +17, 0xFB, 0x0F, 0x0F, 0x1E, 0x23, 0x26, 0x2D, 0x21, 0x2B, 0x33, 0x32, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, + 2, 0xF9, 0x02, +17, 0xFA, 0x00, 0x00, 0x0A, 0x16, 0x1D, 0x27, 0x1C, 0x30, 0x38, 0x37, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, +17, 0xFB, 0x00, 0x00, 0x0A, 0x16, 0x1D, 0x27, 0x1C, 0x30, 0x38, 0x37, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, + 2, 0xF9, 0x01, +17, 0xFA, 0x00, 0x00, 0x13, 0x14, 0x19, 0x24, 0x1A, 0x31, 0x39, 0x38, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, +17, 0xFB, 0x00, 0x00, 0x13, 0x14, 0x19, 0x24, 0x1A, 0x31, 0x39, 0x38, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, + 3, 0xF0, 0x00, 0x00, + 3, 0xF1, 0x00, 0x00, + 2, 0x36, 0xD0, + 2, 0x3A, 0x06 +}; + +static u8 panel_init_220x220[] = { +/*n, data_0, data_1 ... data_n-1*/ + 3, 0xF0, 0x5A, 0x5A, + 3, 0xF1, 0x5A, 0x5A, +18, 0xF2, 0x1C, 0xDC, 0x03, 0x28, 0x28, 0x10, 0x00, 0x60, 0xF8, 0x00, 0x07, 0x02, 0x00, 0x00, 0xDC, 0x28, 0x28, +15, 0xF4, 0x0A, 0x00, 0x00, 0x00, 0x77, 0x7F, 0x07, 0x22, 0x2A, 0x43, 0x07, 0x2A, 0x43, 0x07, +11, 0xF5, 0x00, 0x50, 0x28, 0x00, 0x00, 0x09, 0x00, 0x00, 0x01, 0x01, +10, 0xF6, 0x07, 0x00, 0x07, 0x00, 0x0B, 0x04, 0x04, 0x04, 0x07, + 5, 0xF7, 0x00, 0x00, 0x00, 0x00, + 3, 0xF8, 0x44, 0x02, + 2, 0xF9, 0x04, +17, 0xFA, 0x1E, 0x1E, 0x0D, 0x1D, 0x21, 0x2C, 0x23, 0x28, 0x2C, 0x28, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, +17, 0xFB, 0x1E, 0x1E, 0x0D, 0x1D, 0x21, 0x2C, 0x23, 0x28, 0x2C, 0x28, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, + 2, 0xF9, 0x02, +17, 0xFA, 0x19, 0x18, 0x08, 0x0F, 0x18, 0x26, 0x1E, 0x2C, 0x30, 0x2C, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, +17, 0xFB, 0x19, 0x18, 0x08, 0x0F, 0x18, 0x26, 0x1E, 0x2C, 0x30, 0x2C, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, + 2, 0xF9, 0x01, +17, 0xFA, 0x19, 0x19, 0x09, 0x0D, 0x12, 0x21, 0x1B, 0x2E, 0x31, 0x2E, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, +17, 0xFB, 0x19, 0x19, 0x09, 0x0D, 0x12, 0x21, 0x1B, 0x2E, 0x31, 0x2E, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, + 3, 0xF0, 0x00, 0x00, + 3, 0xF1, 0x00, 0x00, + 2, 0x36, 0xD0, + 2, 0x3A, 0x06 +}; + +struct minnow_panel_attr { + int xres; + int yres; + int rate; + int xoffset; + int yoffset; + int init_cmd_count; + u8 *init_cmd; +}; + +#define INIT_CMD(buf) .init_cmd_count = sizeof(buf), .init_cmd = (buf) +static struct minnow_panel_attr panel_attr_table[MINNOW_PANEL_MAX] = { + [MINNOW_PANEL_CM_220X176] = { + .xres = 220, + .yres = 176, + .rate = 60, + .xoffset = 0x32, + .yoffset = 0, + INIT_CMD(panel_init_220x176) + }, + [MINNOW_PANEL_CM_220X220] = { + .xres = 220, + .yres = 220, + .rate = 60, + .xoffset = 0x32, + .yoffset = 0x4, + INIT_CMD(panel_init_220x220) + }, +}; + +static irqreturn_t minnow_panel_te_isr(int irq, void *data); +static void minnow_panel_te_timeout_work_callback(struct work_struct *work); +static int _minnow_panel_enable_te(struct omap_dss_device *dssdev, bool enable); + +static int minnow_panel_reset(struct omap_dss_device *dssdev); + +struct minnow_panel_data { + struct mutex lock; /* mutex */ + + struct backlight_device *bldev; + + unsigned long hw_guard_end; /* next value of jiffies when we can + * issue the next sleep in/out command + */ + unsigned long hw_guard_wait; /* max guard time in jiffies */ + + struct omap_dss_device *dssdev; + + /* panel HW configuration from DT or platform data */ + int reset_gpio; + int ext_te_gpio; + + bool use_dsi_backlight; + + struct omap_dsi_pin_config pin_config; + struct omap_dss_dsi_config dsi_config; + + u8 *init_cmd_data; + int init_cmd_count; + int x_offset; + int y_offset; + + /* runtime variables */ + bool enabled; + + bool te_enabled; + + atomic_t do_update; + int channel; + + struct delayed_work te_timeout_work; + + unsigned cabc_mode; + + bool intro_printed; + + struct workqueue_struct *workqueue; + + struct delayed_work esd_work; + unsigned esd_interval; + + bool ulps_enabled; + unsigned ulps_timeout; + struct delayed_work ulps_work; +}; + +static void minnow_panel_esd_work(struct work_struct *work); +static void minnow_panel_ulps_work(struct work_struct *work); + +static void hw_guard_start(struct minnow_panel_data *mpd, int guard_msec) +{ + mpd->hw_guard_wait = msecs_to_jiffies(guard_msec); + mpd->hw_guard_end = jiffies + mpd->hw_guard_wait; +} + +static void hw_guard_wait(struct minnow_panel_data *mpd) +{ + unsigned long wait = mpd->hw_guard_end - jiffies; + + if ((long)wait > 0 && wait <= mpd->hw_guard_wait) { + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(wait); + } +} + +static int minnow_panel_dcs_read_1(struct minnow_panel_data *mpd, u8 dcs_cmd, u8 *data) +{ + int r; + u8 buf[1]; + + r = dsi_vc_dcs_read(mpd->dssdev, mpd->channel, dcs_cmd, buf, 1); + + if (r < 0) + return r; + + *data = buf[0]; + + return 0; +} + +static int minnow_panel_dcs_write_0(struct minnow_panel_data *mpd, u8 dcs_cmd) +{ + return dsi_vc_dcs_write(mpd->dssdev, mpd->channel, &dcs_cmd, 1); +} + +static int minnow_panel_dcs_write_1(struct minnow_panel_data *mpd, u8 dcs_cmd, u8 param) +{ + u8 buf[2]; + buf[0] = dcs_cmd; + buf[1] = param; + return dsi_vc_dcs_write(mpd->dssdev, mpd->channel, buf, 2); +} + +static int minnow_panel_sleep_in(struct minnow_panel_data *mpd) + +{ + u8 cmd; + int r; + + hw_guard_wait(mpd); + + cmd = MIPI_DCS_ENTER_SLEEP_MODE; + r = dsi_vc_dcs_write_nosync(mpd->dssdev, mpd->channel, &cmd, 1); + if (r) + return r; + + hw_guard_start(mpd, 120); + + msleep(10); + + return 0; +} + +static int minnow_panel_sleep_out(struct minnow_panel_data *mpd) +{ + int r; + + hw_guard_wait(mpd); + + r = minnow_panel_dcs_write_0(mpd, MIPI_DCS_EXIT_SLEEP_MODE); + if (r) + return r; + + hw_guard_start(mpd, 120); + + msleep(10); + + return 0; +} + +static int minnow_panel_get_id(struct minnow_panel_data *mpd, u8 *id1, u8 *id2, u8 *id3) +{ + int r; + + r = minnow_panel_dcs_read_1(mpd, DCS_GET_ID1, id1); + if (r) + return r; + r = minnow_panel_dcs_read_1(mpd, DCS_GET_ID2, id2); + if (r) + return r; + r = minnow_panel_dcs_read_1(mpd, DCS_GET_ID3, id3); + if (r) + return r; + + return 0; +} + +static int _minnow_panel_init(struct minnow_panel_data *mpd) +{ + int i, r; + for (i = 0; i < mpd->init_cmd_count; ) { + u8 *data = mpd->init_cmd_data + i; + r = *data++; + i += r + 1; + if (i > mpd->init_cmd_count) { + dev_err(&mpd->dssdev->dev, "Invalid init command data selected!\n"); + return -EINVAL; + } + r = dsi_vc_dcs_write(mpd->dssdev, mpd->channel, data, r); + if (r) + break; + } + return r; +} + +static int minnow_panel_set_update_window(struct minnow_panel_data *mpd, + u16 x, u16 y, u16 w, u16 h) +{ + int r; + u16 x1 = x + mpd->x_offset; + u16 x2 = x + mpd->x_offset + w - 1; + u16 y1 = y + mpd->y_offset; + u16 y2 = y + mpd->y_offset + h - 1; + + u8 buf[5]; + buf[0] = MIPI_DCS_SET_COLUMN_ADDRESS; + buf[1] = (x1 >> 8) & 0xff; + buf[2] = (x1 >> 0) & 0xff; + buf[3] = (x2 >> 8) & 0xff; + buf[4] = (x2 >> 0) & 0xff; + + r = dsi_vc_dcs_write_nosync(mpd->dssdev, mpd->channel, buf, sizeof(buf)); + if (r) + return r; + + buf[0] = MIPI_DCS_SET_PAGE_ADDRESS; + buf[1] = (y1 >> 8) & 0xff; + buf[2] = (y1 >> 0) & 0xff; + buf[3] = (y2 >> 8) & 0xff; + buf[4] = (y2 >> 0) & 0xff; + + r = dsi_vc_dcs_write_nosync(mpd->dssdev, mpd->channel, buf, sizeof(buf)); + if (r) + return r; + + dsi_vc_send_bta_sync(mpd->dssdev, mpd->channel); + + return r; +} + +static void minnow_panel_queue_esd_work(struct omap_dss_device *dssdev) +{ + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + + if (mpd->esd_interval > 0) + queue_delayed_work(mpd->workqueue, &mpd->esd_work, + msecs_to_jiffies(mpd->esd_interval)); +} + +static void minnow_panel_cancel_esd_work(struct omap_dss_device *dssdev) +{ + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + + cancel_delayed_work(&mpd->esd_work); +} + +static void minnow_panel_queue_ulps_work(struct omap_dss_device *dssdev) +{ + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + + if (mpd->ulps_timeout > 0) + queue_delayed_work(mpd->workqueue, &mpd->ulps_work, + msecs_to_jiffies(mpd->ulps_timeout)); +} + +static void minnow_panel_cancel_ulps_work(struct omap_dss_device *dssdev) +{ + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + + cancel_delayed_work(&mpd->ulps_work); +} + +static int minnow_panel_enter_ulps(struct omap_dss_device *dssdev) +{ + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + int r; + + if (mpd->ulps_enabled) + return 0; + + minnow_panel_cancel_ulps_work(dssdev); + + r = _minnow_panel_enable_te(dssdev, false); + if (r) + goto err; + + if (gpio_is_valid(mpd->ext_te_gpio)) + disable_irq(gpio_to_irq(mpd->ext_te_gpio)); + + omapdss_dsi_display_disable(dssdev, false, true); + + mpd->ulps_enabled = true; + + return 0; + +err: + dev_err(&dssdev->dev, "enter ULPS failed"); + minnow_panel_reset(dssdev); + + mpd->ulps_enabled = false; + + minnow_panel_queue_ulps_work(dssdev); + + return r; +} + +static int minnow_panel_exit_ulps(struct omap_dss_device *dssdev) +{ + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + int r; + + if (!mpd->ulps_enabled) + return 0; + + r = omapdss_dsi_display_enable(dssdev); + if (r) { + dev_err(&dssdev->dev, "failed to enable DSI\n"); + goto err1; + } + + omapdss_dsi_vc_enable_hs(dssdev, mpd->channel, true); + + r = _minnow_panel_enable_te(dssdev, true); + if (r) { + dev_err(&dssdev->dev, "failed to re-enable TE"); + goto err2; + } + + if (gpio_is_valid(mpd->ext_te_gpio)) + enable_irq(gpio_to_irq(mpd->ext_te_gpio)); + + minnow_panel_queue_ulps_work(dssdev); + + mpd->ulps_enabled = false; + + return 0; + +err2: + dev_err(&dssdev->dev, "failed to exit ULPS"); + + r = minnow_panel_reset(dssdev); + if (!r) { + if (gpio_is_valid(mpd->ext_te_gpio)) + enable_irq(gpio_to_irq(mpd->ext_te_gpio)); + mpd->ulps_enabled = false; + } +err1: + minnow_panel_queue_ulps_work(dssdev); + + return r; +} + +static int minnow_panel_wake_up(struct omap_dss_device *dssdev) +{ + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + + if (mpd->ulps_enabled) + return minnow_panel_exit_ulps(dssdev); + + minnow_panel_cancel_ulps_work(dssdev); + minnow_panel_queue_ulps_work(dssdev); + return 0; +} + +static int minnow_panel_bl_update_status(struct backlight_device *dev) +{ + struct omap_dss_device *dssdev = dev_get_drvdata(&dev->dev); + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + int r; + int level; + + if (dev->props.fb_blank == FB_BLANK_UNBLANK && + dev->props.power == FB_BLANK_UNBLANK) + level = dev->props.brightness; + else + level = 0; + + dev_dbg(&dssdev->dev, "update brightness to %d\n", level); + + mutex_lock(&mpd->lock); + + if (mpd->enabled) { + dsi_bus_lock(dssdev); + + r = minnow_panel_wake_up(dssdev); + if (!r) + r = minnow_panel_dcs_write_1(mpd, DCS_BRIGHTNESS, level); + + dsi_bus_unlock(dssdev); + } else { + r = 0; + } + + mutex_unlock(&mpd->lock); + + return r; +} + +static int minnow_panel_bl_get_intensity(struct backlight_device *dev) +{ + if (dev->props.fb_blank == FB_BLANK_UNBLANK && + dev->props.power == FB_BLANK_UNBLANK) + return dev->props.brightness; + + return 0; +} + +static const struct backlight_ops minnow_panel_bl_ops = { + .get_brightness = minnow_panel_bl_get_intensity, + .update_status = minnow_panel_bl_update_status, +}; + +static void minnow_panel_get_resolution(struct omap_dss_device *dssdev, + u16 *xres, u16 *yres) +{ + *xres = dssdev->panel.timings.x_res; + *yres = dssdev->panel.timings.y_res; +} + +static ssize_t minnow_panel_num_errors_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct omap_dss_device *dssdev = to_dss_device(dev); + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + u8 errors = 0; + int r; + + mutex_lock(&mpd->lock); + + if (mpd->enabled) { + dsi_bus_lock(dssdev); + + r = minnow_panel_wake_up(dssdev); + if (!r) + r = minnow_panel_dcs_read_1(mpd, DCS_READ_NUM_ERRORS, &errors); + + dsi_bus_unlock(dssdev); + } else { + r = -ENODEV; + } + + mutex_unlock(&mpd->lock); + + if (r) + return r; + + return snprintf(buf, PAGE_SIZE, "%d\n", errors); +} + +static ssize_t minnow_panel_hw_revision_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct omap_dss_device *dssdev = to_dss_device(dev); + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + u8 id1, id2, id3; + int r; + + mutex_lock(&mpd->lock); + + if (mpd->enabled) { + dsi_bus_lock(dssdev); + + r = minnow_panel_wake_up(dssdev); + if (!r) + r = minnow_panel_get_id(mpd, &id1, &id2, &id3); + + dsi_bus_unlock(dssdev); + } else { + r = -ENODEV; + } + + mutex_unlock(&mpd->lock); + + if (r) + return r; + + return snprintf(buf, PAGE_SIZE, "%02x.%02x.%02x\n", id1, id2, id3); +} + +static const char *cabc_modes[] = { + "off", /* used also always when CABC is not supported */ + "ui", + "still-image", + "moving-image", +}; + +static ssize_t show_cabc_mode(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct omap_dss_device *dssdev = to_dss_device(dev); + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + const char *mode_str; + int mode; + int len; + + mode = mpd->cabc_mode; + + mode_str = "unknown"; + if (mode >= 0 && mode < ARRAY_SIZE(cabc_modes)) + mode_str = cabc_modes[mode]; + len = snprintf(buf, PAGE_SIZE, "%s\n", mode_str); + + return len < PAGE_SIZE - 1 ? len : PAGE_SIZE - 1; +} + +static ssize_t store_cabc_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct omap_dss_device *dssdev = to_dss_device(dev); + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + int i; + int r; + + for (i = 0; i < ARRAY_SIZE(cabc_modes); i++) { + if (sysfs_streq(cabc_modes[i], buf)) + break; + } + + if (i == ARRAY_SIZE(cabc_modes)) + return -EINVAL; + + mutex_lock(&mpd->lock); + + if (mpd->enabled) { + dsi_bus_lock(dssdev); + + r = minnow_panel_wake_up(dssdev); + if (r) + goto err; + + r = minnow_panel_dcs_write_1(mpd, DCS_WRITE_CABC, i); + if (r) + goto err; + + dsi_bus_unlock(dssdev); + } + + mpd->cabc_mode = i; + + mutex_unlock(&mpd->lock); + + return count; +err: + dsi_bus_unlock(dssdev); + mutex_unlock(&mpd->lock); + return r; +} + +static ssize_t show_cabc_available_modes(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int len; + int i; + + for (i = 0, len = 0; + len < PAGE_SIZE && i < ARRAY_SIZE(cabc_modes); i++) + len += snprintf(&buf[len], PAGE_SIZE - len, "%s%s%s", + i ? " " : "", cabc_modes[i], + i == ARRAY_SIZE(cabc_modes) - 1 ? "\n" : ""); + + return len < PAGE_SIZE ? len : PAGE_SIZE - 1; +} + +static ssize_t minnow_panel_store_esd_interval(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct omap_dss_device *dssdev = to_dss_device(dev); + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + + unsigned long t; + int r; + + r = strict_strtoul(buf, 10, &t); + if (r) + return r; + + mutex_lock(&mpd->lock); + minnow_panel_cancel_esd_work(dssdev); + mpd->esd_interval = t; + if (mpd->enabled) + minnow_panel_queue_esd_work(dssdev); + mutex_unlock(&mpd->lock); + + return count; +} + +static ssize_t minnow_panel_show_esd_interval(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct omap_dss_device *dssdev = to_dss_device(dev); + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + unsigned t; + + mutex_lock(&mpd->lock); + t = mpd->esd_interval; + mutex_unlock(&mpd->lock); + + return snprintf(buf, PAGE_SIZE, "%u\n", t); +} + +static ssize_t minnow_panel_store_ulps(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct omap_dss_device *dssdev = to_dss_device(dev); + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + unsigned long t; + int r; + + r = strict_strtoul(buf, 10, &t); + if (r) + return r; + + mutex_lock(&mpd->lock); + + if (mpd->enabled) { + dsi_bus_lock(dssdev); + + if (t) + r = minnow_panel_enter_ulps(dssdev); + else + r = minnow_panel_wake_up(dssdev); + + dsi_bus_unlock(dssdev); + } + + mutex_unlock(&mpd->lock); + + if (r) + return r; + + return count; +} + +static ssize_t minnow_panel_show_ulps(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct omap_dss_device *dssdev = to_dss_device(dev); + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + unsigned t; + + mutex_lock(&mpd->lock); + t = mpd->ulps_enabled; + mutex_unlock(&mpd->lock); + + return snprintf(buf, PAGE_SIZE, "%u\n", t); +} + +static ssize_t minnow_panel_store_ulps_timeout(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct omap_dss_device *dssdev = to_dss_device(dev); + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + unsigned long t; + int r; + + r = strict_strtoul(buf, 10, &t); + if (r) + return r; + + mutex_lock(&mpd->lock); + mpd->ulps_timeout = t; + + if (mpd->enabled) { + /* minnow_panel_wake_up will restart the timer */ + dsi_bus_lock(dssdev); + r = minnow_panel_wake_up(dssdev); + dsi_bus_unlock(dssdev); + } + + mutex_unlock(&mpd->lock); + + if (r) + return r; + + return count; +} + +static ssize_t minnow_panel_show_ulps_timeout(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct omap_dss_device *dssdev = to_dss_device(dev); + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + unsigned t; + + mutex_lock(&mpd->lock); + t = mpd->ulps_timeout; + mutex_unlock(&mpd->lock); + + return snprintf(buf, PAGE_SIZE, "%u\n", t); +} + +static DEVICE_ATTR(num_dsi_errors, S_IRUGO, minnow_panel_num_errors_show, NULL); +static DEVICE_ATTR(hw_revision, S_IRUGO, minnow_panel_hw_revision_show, NULL); +static DEVICE_ATTR(cabc_mode, S_IRUGO | S_IWUSR, + show_cabc_mode, store_cabc_mode); +static DEVICE_ATTR(cabc_available_modes, S_IRUGO, + show_cabc_available_modes, NULL); +static DEVICE_ATTR(esd_interval, S_IRUGO | S_IWUSR, + minnow_panel_show_esd_interval, minnow_panel_store_esd_interval); +static DEVICE_ATTR(ulps, S_IRUGO | S_IWUSR, + minnow_panel_show_ulps, minnow_panel_store_ulps); +static DEVICE_ATTR(ulps_timeout, S_IRUGO | S_IWUSR, + minnow_panel_show_ulps_timeout, minnow_panel_store_ulps_timeout); + +static struct attribute *minnow_panel_attrs[] = { + &dev_attr_num_dsi_errors.attr, + &dev_attr_hw_revision.attr, + &dev_attr_cabc_mode.attr, + &dev_attr_cabc_available_modes.attr, + &dev_attr_esd_interval.attr, + &dev_attr_ulps.attr, + &dev_attr_ulps_timeout.attr, + NULL, +}; + +static struct attribute_group minnow_panel_attr_group = { + .attrs = minnow_panel_attrs, +}; + +static void minnow_panel_hw_reset(struct omap_dss_device *dssdev) +{ + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + + if (!gpio_is_valid(mpd->reset_gpio)) + return; + + gpio_set_value(mpd->reset_gpio, 1); + udelay(10); + /* reset the panel */ + gpio_set_value(mpd->reset_gpio, 0); + /* assert reset */ + udelay(10); + gpio_set_value(mpd->reset_gpio, 1); + + /* wait after releasing reset */ + msleep(5); +} + +#define DEBUG_DT +#ifdef DEBUG_DT +#define DTINFO(fmt, ...) \ + printk(KERN_INFO "minnow-panel DT: " fmt, ## __VA_ARGS__) +#define DTINFO_PIXFMT(msg, pix) \ +{ char *fmt[4] = {"RGB888", "RGB666", "RGB666_PACKED", "RGB565"};\ + DTINFO(msg"%s\n", fmt[pix]);\ +} +#define DTINFO_ARRAY(msg, a, n, fmt, blen) \ +{ int i; char str[blen], *p = str;\ + for (i = 0; i < n; i++) {\ + sprintf(p, fmt, a[i]);\ + p += strlen(p); \ + } \ + DTINFO(msg"%s\n", str);\ +} +#else /* DEBUG_DT */ +#define DTINFO(fmt, ...) +#define DTINFO_PIXFMT(msg, pix) +#define DTINFO_ARRAY(msg, a, n, fmt, blen) +#endif + +static struct of_device_id minnow_panel_ids[] = { + { .compatible = "mot,minnow-panel-dsi-cm" }, + { /*sentinel*/ } +}; + +static int minnow_panel_dt_init(struct minnow_panel_data *mpd) +{ + u32 range[2], value = 0; + struct minnow_panel_attr *panel_attr; + struct device_node *dt_node; + + dt_node = of_find_matching_node(NULL, minnow_panel_ids); + if (dt_node == NULL) { + dev_err(&mpd->dssdev->dev, "No dt_node found!\n"); + return -ENODEV; + } + + if (of_property_read_u32(dt_node, "id_panel", &value) \ + || (value >= MINNOW_PANEL_MAX)) { + dev_err(&mpd->dssdev->dev, \ + "Invalid id_panel = %u!\n", value); + return -EINVAL; + } + DTINFO("id_panel = %d\n", value); + panel_attr = &panel_attr_table[value]; + mpd->init_cmd_data = panel_attr->init_cmd; + mpd->init_cmd_count = panel_attr->init_cmd_count; + mpd->x_offset = panel_attr->xoffset; + mpd->y_offset = panel_attr->yoffset; + mpd->dssdev->panel.timings.x_res = panel_attr->xres; + mpd->dssdev->panel.timings.y_res = panel_attr->yres; + mpd->dssdev->panel.timings.pixel_clock = DIV_ROUND_UP(panel_attr->xres\ + * panel_attr->yres * panel_attr->rate, 1000); + mpd->dssdev->panel.dsi_pix_fmt = OMAP_DSS_DSI_FMT_RGB888; + mpd->dssdev->caps = OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE | + OMAP_DSS_DISPLAY_CAP_TEAR_ELIM; + mpd->dsi_config.mode = OMAP_DSS_DSI_CMD_MODE; + mpd->dsi_config.pixel_format = OMAP_DSS_DSI_FMT_RGB888; + mpd->dsi_config.hs_clk_min = 90000000; + mpd->dsi_config.hs_clk_max = 150000000; + mpd->dsi_config.lp_clk_min = 7000000; + mpd->dsi_config.lp_clk_max = 9000000; + + mpd->reset_gpio = of_get_named_gpio(dt_node, "gpio_reset", 0); + DTINFO("gpio_reset = %d\n", mpd->reset_gpio); + mpd->ext_te_gpio = of_get_named_gpio(dt_node, "gpio_te", 0); + DTINFO("ext_te: gpio_te = %d\n", mpd->ext_te_gpio); + + mpd->esd_interval = 0; + if (!of_property_read_u32(dt_node, "esd_interval", &value)) { + mpd->esd_interval = value; + DTINFO("esd_interval = %d\n", mpd->esd_interval); + } + mpd->ulps_timeout = 0; + mpd->use_dsi_backlight = false; + + mpd->pin_config.num_pins = 4; + mpd->pin_config.pins[0] = 0; + mpd->pin_config.pins[1] = 1; + mpd->pin_config.pins[2] = 2; + mpd->pin_config.pins[3] = 3; + if (of_get_property(dt_node, "pins", &value)) { + u32 pins[OMAP_DSS_MAX_DSI_PINS]; + u32 num_pins = value / sizeof(u32); + if (!num_pins || (num_pins > OMAP_DSS_MAX_DSI_PINS)) { + dev_err(&mpd->dssdev->dev, \ + "Invalid DSI pins count = %u!\n", num_pins); + return -EINVAL; + } + value = 0; + if (!of_property_read_u32_array(dt_node, \ + "pins", pins, num_pins)) { + for (; value < num_pins; value++) { + if (pins[value] >= OMAP_DSS_MAX_DSI_PINS) + break; + mpd->pin_config.pins[value]\ + = pins[value]; + } + } + if (value < num_pins) { + dev_err(&mpd->dssdev->dev, \ + "Invalid DSI pins config!\n"); + return -EINVAL; + } + mpd->pin_config.num_pins = num_pins; + DTINFO("num_pins = %d\n", \ + mpd->pin_config.num_pins); + DTINFO_ARRAY("pins =", mpd->pin_config.pins,\ + mpd->pin_config.num_pins, " %u", 64); + } + + if (!of_property_read_u32(dt_node, "pixel_clock", &value)) { + if (value < mpd->dssdev->panel.timings.pixel_clock) { + dev_err(&mpd->dssdev->dev, \ + "Invalid pixel_clock = %u!\n", value); + return -EINVAL; + } + mpd->dssdev->panel.timings.pixel_clock = value; + DTINFO("pixel_clock = %u\n", \ + mpd->dssdev->panel.timings.pixel_clock); + } + + if (!of_property_read_u32(dt_node, "pixel_format", &value)) { + switch (value) { + case OMAP_DSS_DSI_FMT_RGB888: + case OMAP_DSS_DSI_FMT_RGB666: + case OMAP_DSS_DSI_FMT_RGB666_PACKED: + case OMAP_DSS_DSI_FMT_RGB565: + break; + default: + dev_err(&mpd->dssdev->dev, \ + "Invalid pixel_format = %u!\n", value); + return -EINVAL; + } + mpd->dssdev->panel.dsi_pix_fmt = \ + mpd->dsi_config.pixel_format = value; + DTINFO_PIXFMT("pixel_format = ", \ + mpd->dssdev->panel.dsi_pix_fmt); + } + + if (!of_property_read_u32_array(dt_node, "hs_clk", range, 2)) { + mpd->dsi_config.hs_clk_min = range[0]; + mpd->dsi_config.hs_clk_max = range[1]; + DTINFO("hs_clk_min = %lu, hs_clk_max = %lu\n", \ + mpd->dsi_config.hs_clk_min, \ + mpd->dsi_config.hs_clk_max); + } + + if (!of_property_read_u32_array(dt_node, "lp_clk", range, 2)) { + mpd->dsi_config.lp_clk_min = range[0]; + mpd->dsi_config.lp_clk_max = range[1]; + DTINFO("lp_clk_min = %lu, lp_clk_max = %lu\n", \ + mpd->dsi_config.lp_clk_min, \ + mpd->dsi_config.lp_clk_max); + } + + return 0; +} + +static int minnow_panel_probe(struct omap_dss_device *dssdev) +{ + struct backlight_properties props; + struct minnow_panel_data *mpd; + struct backlight_device *bldev = NULL; + int r; + + dev_dbg(&dssdev->dev, "probe\n"); + + mpd = devm_kzalloc(&dssdev->dev, sizeof(*mpd), GFP_KERNEL); + if (!mpd) + return -ENOMEM; + + dev_set_drvdata(&dssdev->dev, mpd); + mpd->dssdev = dssdev; + + r = minnow_panel_dt_init(mpd); + if (r) + return r; + + mutex_init(&mpd->lock); + + atomic_set(&mpd->do_update, 0); + + if (gpio_is_valid(mpd->reset_gpio)) { + r = devm_gpio_request_one(&dssdev->dev, mpd->reset_gpio, + GPIOF_OUT_INIT_HIGH, "minnow-panel reset"); + if (r) { + dev_err(&dssdev->dev, "failed to request reset gpio\n"); + return r; + } + } + + if (gpio_is_valid(mpd->ext_te_gpio)) { + r = devm_gpio_request_one(&dssdev->dev, mpd->ext_te_gpio, + GPIOF_IN, "minnow-panel irq"); + if (r) { + dev_err(&dssdev->dev, "failed to request ext_te gpio\n"); + return r; + } + + r = devm_request_irq(&dssdev->dev, gpio_to_irq(mpd->ext_te_gpio), + minnow_panel_te_isr, + IRQF_TRIGGER_RISING, + "minnow-panel vsync", dssdev); + + if (r) { + dev_err(&dssdev->dev, "IRQ request failed\n"); + return r; + } + + INIT_DEFERRABLE_WORK(&mpd->te_timeout_work, + minnow_panel_te_timeout_work_callback); + + dev_dbg(&dssdev->dev, "Using GPIO TE\n"); + } + + mpd->workqueue = create_singlethread_workqueue("minnow_panel_esd"); + if (mpd->workqueue == NULL) { + dev_err(&dssdev->dev, "can't create ESD workqueue\n"); + return -ENOMEM; + } + INIT_DEFERRABLE_WORK(&mpd->esd_work, minnow_panel_esd_work); + INIT_DELAYED_WORK(&mpd->ulps_work, minnow_panel_ulps_work); + + if (mpd->use_dsi_backlight) { + memset(&props, 0, sizeof(struct backlight_properties)); + props.max_brightness = 255; + + props.type = BACKLIGHT_RAW; + bldev = backlight_device_register(dev_name(&dssdev->dev), + &dssdev->dev, dssdev, &minnow_panel_bl_ops, &props); + if (IS_ERR(bldev)) { + r = PTR_ERR(bldev); + goto err_bl; + } + + mpd->bldev = bldev; + + bldev->props.fb_blank = FB_BLANK_UNBLANK; + bldev->props.power = FB_BLANK_UNBLANK; + bldev->props.brightness = 255; + + minnow_panel_bl_update_status(bldev); + } + + r = omap_dsi_request_vc(dssdev, &mpd->channel); + if (r) { + dev_err(&dssdev->dev, "failed to get virtual channel\n"); + goto err_req_vc; + } + + r = omap_dsi_set_vc_id(dssdev, mpd->channel, TCH); + if (r) { + dev_err(&dssdev->dev, "failed to set VC_ID\n"); + goto err_vc_id; + } + + r = sysfs_create_group(&dssdev->dev.kobj, &minnow_panel_attr_group); + if (r) { + dev_err(&dssdev->dev, "failed to create sysfs files\n"); + goto err_vc_id; + } + + return 0; + +err_vc_id: + omap_dsi_release_vc(dssdev, mpd->channel); +err_req_vc: + if (bldev != NULL) + backlight_device_unregister(bldev); +err_bl: + destroy_workqueue(mpd->workqueue); + return r; +} + +static void __exit minnow_panel_remove(struct omap_dss_device *dssdev) +{ + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + struct backlight_device *bldev; + + dev_dbg(&dssdev->dev, "remove\n"); + + sysfs_remove_group(&dssdev->dev.kobj, &minnow_panel_attr_group); + omap_dsi_release_vc(dssdev, mpd->channel); + + bldev = mpd->bldev; + if (bldev != NULL) { + bldev->props.power = FB_BLANK_POWERDOWN; + minnow_panel_bl_update_status(bldev); + backlight_device_unregister(bldev); + } + + minnow_panel_cancel_ulps_work(dssdev); + minnow_panel_cancel_esd_work(dssdev); + destroy_workqueue(mpd->workqueue); + + /* reset, to be sure that the panel is in a valid state */ + minnow_panel_hw_reset(dssdev); +} + +static int minnow_panel_power_on(struct omap_dss_device *dssdev) +{ + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + u8 id1, id2, id3; + int r; + + r = omapdss_dsi_configure_pins(dssdev, &mpd->pin_config); + if (r) { + dev_err(&dssdev->dev, "failed to configure DSI pins\n"); + goto err0; + }; + + mpd->dsi_config.timings = &dssdev->panel.timings; + r = omapdss_dsi_set_config(dssdev, &mpd->dsi_config); + if (r) { + dev_err(&dssdev->dev, "failed to configure DSI\n"); + goto err0; + } + + r = omapdss_dsi_display_enable(dssdev); + if (r) { + dev_err(&dssdev->dev, "failed to enable DSI\n"); + goto err0; + } + + if (mpd->intro_printed) + minnow_panel_hw_reset(dssdev); + + omapdss_dsi_vc_enable_hs(dssdev, mpd->channel, false); + + r = minnow_panel_sleep_out(mpd); + if (r) + goto err; + + _minnow_panel_init(mpd); + + r = minnow_panel_get_id(mpd, &id1, &id2, &id3); + if (r) + goto err; + + r = minnow_panel_dcs_write_0(mpd, MIPI_DCS_SET_DISPLAY_ON); + if (r) + goto err; + + r = _minnow_panel_enable_te(dssdev, mpd->te_enabled); + if (r) + goto err; + + r = dsi_enable_video_output(dssdev, mpd->channel); + if (r) + goto err; + + mpd->enabled = 1; + + if (!mpd->intro_printed) { + dev_info(&dssdev->dev, "panel revision %02x.%02x.%02x\n", + id1, id2, id3); + mpd->intro_printed = true; + } + + omapdss_dsi_vc_enable_hs(dssdev, mpd->channel, true); + + return 0; +err: + dev_err(&dssdev->dev, "error while enabling panel, issuing HW reset\n"); + + minnow_panel_hw_reset(dssdev); + + omapdss_dsi_display_disable(dssdev, true, false); +err0: + return r; +} + +static void minnow_panel_power_off(struct omap_dss_device *dssdev) +{ + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + int r; + + dsi_disable_video_output(dssdev, mpd->channel); + + r = minnow_panel_dcs_write_0(mpd, MIPI_DCS_SET_DISPLAY_OFF); + if (!r) + r = minnow_panel_sleep_in(mpd); + + if (r) { + dev_err(&dssdev->dev, + "error disabling panel, issuing HW reset\n"); + minnow_panel_hw_reset(dssdev); + } + + omapdss_dsi_display_disable(dssdev, true, false); + + mpd->enabled = 0; +} + +static int minnow_panel_reset(struct omap_dss_device *dssdev) +{ + dev_err(&dssdev->dev, "performing LCD reset\n"); + + minnow_panel_power_off(dssdev); + minnow_panel_hw_reset(dssdev); + return minnow_panel_power_on(dssdev); +} + +static int minnow_panel_enable(struct omap_dss_device *dssdev) +{ + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + int r; + + dev_dbg(&dssdev->dev, "enable\n"); + + mutex_lock(&mpd->lock); + + if (dssdev->state != OMAP_DSS_DISPLAY_DISABLED) { + r = -EINVAL; + goto err; + } + + dsi_bus_lock(dssdev); + + r = minnow_panel_power_on(dssdev); + + dsi_bus_unlock(dssdev); + + if (r) + goto err; + + minnow_panel_queue_esd_work(dssdev); + + dssdev->state = OMAP_DSS_DISPLAY_ACTIVE; + + mutex_unlock(&mpd->lock); + + return 0; +err: + dev_dbg(&dssdev->dev, "enable failed\n"); + mutex_unlock(&mpd->lock); + return r; +} + +static void minnow_panel_disable(struct omap_dss_device *dssdev) +{ + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + + dev_dbg(&dssdev->dev, "disable\n"); + + mutex_lock(&mpd->lock); + + minnow_panel_cancel_ulps_work(dssdev); + minnow_panel_cancel_esd_work(dssdev); + + dsi_bus_lock(dssdev); + + if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) { + int r; + + r = minnow_panel_wake_up(dssdev); + if (!r) + minnow_panel_power_off(dssdev); + } + + dsi_bus_unlock(dssdev); + + dssdev->state = OMAP_DSS_DISPLAY_DISABLED; + + mutex_unlock(&mpd->lock); +} + +static void minnow_panel_framedone_cb(int err, void *data) +{ + struct omap_dss_device *dssdev = data; + dev_dbg(&dssdev->dev, "framedone, err %d\n", err); + dsi_bus_unlock(dssdev); +} + +static irqreturn_t minnow_panel_te_isr(int irq, void *data) +{ + struct omap_dss_device *dssdev = data; + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + int old; + int r; + + old = atomic_cmpxchg(&mpd->do_update, 1, 0); + + if (old) { + cancel_delayed_work(&mpd->te_timeout_work); + + r = omap_dsi_update(dssdev, mpd->channel, minnow_panel_framedone_cb, + dssdev); + if (r) + goto err; + } + + return IRQ_HANDLED; +err: + dev_err(&dssdev->dev, "start update failed\n"); + dsi_bus_unlock(dssdev); + return IRQ_HANDLED; +} + +static void minnow_panel_te_timeout_work_callback(struct work_struct *work) +{ + struct minnow_panel_data *mpd = container_of(work, struct minnow_panel_data, + te_timeout_work.work); + struct omap_dss_device *dssdev = mpd->dssdev; + + dev_err(&dssdev->dev, "TE not received for 250ms!\n"); + + atomic_set(&mpd->do_update, 0); + dsi_bus_unlock(dssdev); +} + +static int minnow_panel_update(struct omap_dss_device *dssdev, + u16 x, u16 y, u16 w, u16 h) +{ + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + int r; + + dev_dbg(&dssdev->dev, "update %d, %d, %d x %d\n", x, y, w, h); + + mutex_lock(&mpd->lock); + dsi_bus_lock(dssdev); + + r = minnow_panel_wake_up(dssdev); + if (r) + goto err; + + if (!mpd->enabled) { + r = 0; + goto err; + } + + /* XXX no need to send this every frame, but dsi break if not done */ + r = minnow_panel_set_update_window(mpd, 0, 0, + dssdev->panel.timings.x_res, + dssdev->panel.timings.y_res); + if (r) + goto err; + + if (mpd->te_enabled && gpio_is_valid(mpd->ext_te_gpio)) { + schedule_delayed_work(&mpd->te_timeout_work, + msecs_to_jiffies(250)); + atomic_set(&mpd->do_update, 1); + } else { + r = omap_dsi_update(dssdev, mpd->channel, minnow_panel_framedone_cb, + dssdev); + if (r) + goto err; + } + + /* note: no bus_unlock here. unlock is in framedone_cb */ + mutex_unlock(&mpd->lock); + return 0; +err: + dsi_bus_unlock(dssdev); + mutex_unlock(&mpd->lock); + return r; +} + +static int minnow_panel_sync(struct omap_dss_device *dssdev) +{ + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + + dev_dbg(&dssdev->dev, "sync\n"); + + mutex_lock(&mpd->lock); + dsi_bus_lock(dssdev); + dsi_bus_unlock(dssdev); + mutex_unlock(&mpd->lock); + + dev_dbg(&dssdev->dev, "sync done\n"); + + return 0; +} + +static int _minnow_panel_enable_te(struct omap_dss_device *dssdev, bool enable) +{ + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + int r; + + if (enable) + r = minnow_panel_dcs_write_1(mpd, MIPI_DCS_SET_TEAR_ON, 0); + else + r = minnow_panel_dcs_write_0(mpd, MIPI_DCS_SET_TEAR_OFF); + + if (!gpio_is_valid(mpd->ext_te_gpio)) + omapdss_dsi_enable_te(dssdev, enable); + + /* possible panel bug */ + msleep(100); + + return r; +} + +static int minnow_panel_enable_te(struct omap_dss_device *dssdev, bool enable) +{ + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + int r; + + mutex_lock(&mpd->lock); + + if (mpd->te_enabled == enable) + goto end; + + dsi_bus_lock(dssdev); + + if (mpd->enabled) { + r = minnow_panel_wake_up(dssdev); + if (r) + goto err; + + r = _minnow_panel_enable_te(dssdev, enable); + if (r) + goto err; + } + + mpd->te_enabled = enable; + + dsi_bus_unlock(dssdev); +end: + mutex_unlock(&mpd->lock); + + return 0; +err: + dsi_bus_unlock(dssdev); + mutex_unlock(&mpd->lock); + + return r; +} + +static int minnow_panel_get_te(struct omap_dss_device *dssdev) +{ + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + int r; + + mutex_lock(&mpd->lock); + r = mpd->te_enabled; + mutex_unlock(&mpd->lock); + + return r; +} + +static int minnow_panel_run_test(struct omap_dss_device *dssdev, int test_num) +{ + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + u8 id1, id2, id3; + int r; + + mutex_lock(&mpd->lock); + + if (!mpd->enabled) { + r = -ENODEV; + goto err1; + } + + dsi_bus_lock(dssdev); + + r = minnow_panel_wake_up(dssdev); + if (r) + goto err2; + + r = minnow_panel_dcs_read_1(mpd, DCS_GET_ID1, &id1); + if (r) + goto err2; + r = minnow_panel_dcs_read_1(mpd, DCS_GET_ID2, &id2); + if (r) + goto err2; + r = minnow_panel_dcs_read_1(mpd, DCS_GET_ID3, &id3); + if (r) + goto err2; + + dsi_bus_unlock(dssdev); + mutex_unlock(&mpd->lock); + return 0; +err2: + dsi_bus_unlock(dssdev); +err1: + mutex_unlock(&mpd->lock); + return r; +} + +static int minnow_panel_memory_read(struct omap_dss_device *dssdev, + void *buf, size_t size, + u16 x, u16 y, u16 w, u16 h) +{ + int r; + int first = 1; + int plen; + unsigned buf_used = 0; + struct minnow_panel_data *mpd = dev_get_drvdata(&dssdev->dev); + + if (size < w * h * 3) + return -ENOMEM; + + mutex_lock(&mpd->lock); + + if (!mpd->enabled) { + r = -ENODEV; + goto err1; + } + + size = min(w * h * 3, + dssdev->panel.timings.x_res * + dssdev->panel.timings.y_res * 3); + + dsi_bus_lock(dssdev); + + r = minnow_panel_wake_up(dssdev); + if (r) + goto err2; + + /* plen 1 or 2 goes into short packet. until checksum error is fixed, + * use short packets. plen 32 works, but bigger packets seem to cause + * an error. */ + if (size % 2) + plen = 1; + else + plen = 2; + + minnow_panel_set_update_window(mpd, x, y, w, h); + + r = dsi_vc_set_max_rx_packet_size(dssdev, mpd->channel, plen); + if (r) + goto err2; + + while (buf_used < size) { + u8 dcs_cmd = first ? 0x2e : 0x3e; + first = 0; + + r = dsi_vc_dcs_read(dssdev, mpd->channel, dcs_cmd, + buf + buf_used, size - buf_used); + + if (r < 0) { + dev_err(&dssdev->dev, "read error\n"); + goto err3; + } + + buf_used += r; + + if (r < plen) { + dev_err(&dssdev->dev, "short read\n"); + break; + } + + if (signal_pending(current)) { + dev_err(&dssdev->dev, "signal pending, " + "aborting memory read\n"); + r = -ERESTARTSYS; + goto err3; + } + } + + r = buf_used; + +err3: + dsi_vc_set_max_rx_packet_size(dssdev, mpd->channel, 1); +err2: + dsi_bus_unlock(dssdev); +err1: + mutex_unlock(&mpd->lock); + return r; +} + +static void minnow_panel_ulps_work(struct work_struct *work) +{ + struct minnow_panel_data *mpd = container_of(work, struct minnow_panel_data, + ulps_work.work); + struct omap_dss_device *dssdev = mpd->dssdev; + + mutex_lock(&mpd->lock); + + if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE || !mpd->enabled) { + mutex_unlock(&mpd->lock); + return; + } + + dsi_bus_lock(dssdev); + + minnow_panel_enter_ulps(dssdev); + + dsi_bus_unlock(dssdev); + mutex_unlock(&mpd->lock); +} + +static void minnow_panel_esd_work(struct work_struct *work) +{ + struct minnow_panel_data *mpd = container_of(work, struct minnow_panel_data, + esd_work.work); + struct omap_dss_device *dssdev = mpd->dssdev; + u8 state1, state2; + int r; + + mutex_lock(&mpd->lock); + + if (!mpd->enabled) { + mutex_unlock(&mpd->lock); + return; + } + + dsi_bus_lock(dssdev); + + r = minnow_panel_wake_up(dssdev); + if (r) { + dev_err(&dssdev->dev, "failed to exit ULPS\n"); + goto err; + } + + r = minnow_panel_dcs_read_1(mpd, MIPI_DCS_GET_DIAGNOSTIC_RESULT, &state1); + if (r) { + dev_err(&dssdev->dev, "failed to read minnow-panel status\n"); + goto err; + } + + /* Run self diagnostics */ + r = minnow_panel_sleep_out(mpd); + if (r) { + dev_err(&dssdev->dev, "failed to run minnow-panel self-diagnostics\n"); + goto err; + } + + r = minnow_panel_dcs_read_1(mpd, MIPI_DCS_GET_DIAGNOSTIC_RESULT, &state2); + if (r) { + dev_err(&dssdev->dev, "failed to read minnow-panel status\n"); + goto err; + } + + /* Each sleep out command will trigger a self diagnostic and flip + * Bit6 if the test passes. + */ + if (!((state1 ^ state2) & (1 << 6))) { + dev_err(&dssdev->dev, "LCD self diagnostics failed\n"); + goto err; + } + /* Self-diagnostics result is also shown on TE GPIO line. We need + * to re-enable TE after self diagnostics */ + if (mpd->te_enabled && gpio_is_valid(mpd->ext_te_gpio)) { + r = minnow_panel_dcs_write_1(mpd, MIPI_DCS_SET_TEAR_ON, 0); + if (r) + goto err; + } + + dsi_bus_unlock(dssdev); + + minnow_panel_queue_esd_work(dssdev); + + mutex_unlock(&mpd->lock); + + return; +err: + dev_err(&dssdev->dev, "performing LCD reset\n"); + + minnow_panel_reset(dssdev); + + dsi_bus_unlock(dssdev); + + minnow_panel_queue_esd_work(dssdev); + + mutex_unlock(&mpd->lock); +} + +static struct omap_dss_driver minnow_panel_driver = { + .probe = minnow_panel_probe, + .remove = __exit_p(minnow_panel_remove), + + .enable = minnow_panel_enable, + .disable = minnow_panel_disable, + + .update = minnow_panel_update, + .sync = minnow_panel_sync, + + .get_resolution = minnow_panel_get_resolution, + .get_recommended_bpp = omapdss_default_get_recommended_bpp, + + .enable_te = minnow_panel_enable_te, + .get_te = minnow_panel_get_te, + + .run_test = minnow_panel_run_test, + .memory_read = minnow_panel_memory_read, + + .driver = { + .name = "minnow-panel", + .owner = THIS_MODULE, + }, +}; + +static int __init minnow_panel_init(void) +{ + omap_dss_register_driver(&minnow_panel_driver); + + return 0; +} + +static void __exit minnow_panel_exit(void) +{ + omap_dss_unregister_driver(&minnow_panel_driver); +} + +module_init(minnow_panel_init); +module_exit(minnow_panel_exit); + +MODULE_DESCRIPTION("Minnow Panel DSI Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/omap2/dss/dsi.c b/drivers/video/omap2/dss/dsi.c index a73dedc3310..749aa38605a 100644 --- a/drivers/video/omap2/dss/dsi.c +++ b/drivers/video/omap2/dss/dsi.c @@ -4272,10 +4272,6 @@ static void dsi_update_screen_dispc(struct platform_device *dsidev) dsi_perf_mark_start(dsidev); - r = schedule_delayed_work(&dsi->framedone_timeout_work, - msecs_to_jiffies(250)); - BUG_ON(r == 0); - dss_mgr_set_timings(mgr, &dsi->timings); dss_mgr_start_update(mgr); @@ -4291,6 +4287,11 @@ static void dsi_update_screen_dispc(struct platform_device *dsidev) mod_timer(&dsi->te_timer, jiffies + msecs_to_jiffies(250)); #endif } + /* Start timer at bottom to avoid dispc update start delayed sometimes + */ + r = schedule_delayed_work(&dsi->framedone_timeout_work, + msecs_to_jiffies(250)); + BUG_ON(r == 0); } #ifdef DSI_CATCH_MISSING_TE @@ -4593,6 +4594,9 @@ int omapdss_dsi_display_enable(struct omap_dss_device *dssdev) dsi_enable_pll_clock(dsidev, 1); + dsi_vc_enable(dsidev, 0, 0); + dsi_vc_enable(dsidev, 1, 0); + _dsi_initialize_irq(dsidev); r = dsi_display_init_dsi(dsidev); @@ -5521,13 +5525,6 @@ static int omap_dsihw_probe(struct platform_device *dsidev) return -ENODEV; } - r = devm_request_irq(&dsidev->dev, dsi->irq, omap_dsi_irq_handler, - IRQF_SHARED, dev_name(&dsidev->dev), dsi->pdev); - if (r < 0) { - DSSERR("request_irq failed\n"); - return r; - } - /* DSI VCs initialization */ for (i = 0; i < ARRAY_SIZE(dsi->vc); i++) { dsi->vc[i].source = DSI_VC_SOURCE_L4; @@ -5547,6 +5544,13 @@ static int omap_dsihw_probe(struct platform_device *dsidev) if (r) goto err_runtime_get; + r = devm_request_irq(&dsidev->dev, dsi->irq, omap_dsi_irq_handler, + IRQF_SHARED, dev_name(&dsidev->dev), dsi->pdev); + if (r < 0) { + DSSERR("request_irq failed\n"); + goto err_runtime_get; + } + rev = dsi_read_reg(dsidev, DSI_REVISION); dev_dbg(&dsidev->dev, "OMAP DSI rev %d.%d\n", FLD_GET(rev, 7, 4), FLD_GET(rev, 3, 0)); diff --git a/drivers/video/omap2/omapfb/omapfb-ioctl.c b/drivers/video/omap2/omapfb/omapfb-ioctl.c index d30b45d7264..c9879f3a204 100644 --- a/drivers/video/omap2/omapfb/omapfb-ioctl.c +++ b/drivers/video/omap2/omapfb/omapfb-ioctl.c @@ -303,6 +303,18 @@ static int omapfb_update_window(struct fb_info *fbi, return display->driver->update(display, x, y, w, h); } +static int omapfb_update_display(struct fb_info *fbi) +{ + struct omap_dss_device *display = fb2display(fbi); + u16 dw, dh; + + if (!display) + return 0; + + display->driver->get_resolution(display, &dw, &dh); + return display->driver->update(display, 0, 0, dw, dh); +} + int omapfb_set_update_mode(struct fb_info *fbi, enum omapfb_update_mode mode) { @@ -604,6 +616,10 @@ int omapfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg) int r = 0; switch (cmd) { + case FBIO_UPDATE_DISPLAY: + DBG("ioctl FBIO_UPDATE_DISPLAY\n"); + r = omapfb_update_display(fbi); + break; case OMAPFB_SYNC_GFX: DBG("ioctl SYNC_GFX\n"); if (!display || !display->driver->sync) { diff --git a/drivers/video/omap2/omapfb/omapfb-main.c b/drivers/video/omap2/omapfb/omapfb-main.c index 856917b3361..f5f57a4e9a2 100644 --- a/drivers/video/omap2/omapfb/omapfb-main.c +++ b/drivers/video/omap2/omapfb/omapfb-main.c @@ -2326,8 +2326,6 @@ static int omapfb_init_display(struct omapfb2_device *fbdev, d->fbdev = fbdev; if (dssdev->caps & OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE) { - u16 w, h; - if (auto_update) { omapfb_start_auto_update(fbdev, dssdev); d->update_mode = OMAPFB_AUTO_UPDATE; @@ -2342,14 +2340,6 @@ static int omapfb_init_display(struct omapfb2_device *fbdev, return r; } } - - dssdrv->get_resolution(dssdev, &w, &h); - r = dssdrv->update(dssdev, 0, 0, w, h); - if (r) { - dev_err(fbdev->dev, - "Failed to update display\n"); - return r; - } } else { d->update_mode = OMAPFB_AUTO_UPDATE; } |