diff options
Diffstat (limited to 'drivers/staging/nvec')
| -rw-r--r-- | drivers/staging/nvec/Kconfig | 24 | ||||
| -rw-r--r-- | drivers/staging/nvec/Makefile | 1 | ||||
| -rw-r--r-- | drivers/staging/nvec/TODO | 14 | ||||
| -rw-r--r-- | drivers/staging/nvec/nvec-keytable.h | 225 | ||||
| -rw-r--r-- | drivers/staging/nvec/nvec.c | 1099 | ||||
| -rw-r--r-- | drivers/staging/nvec/nvec.h | 225 | ||||
| -rw-r--r-- | drivers/staging/nvec/nvec_kbd.c | 139 | ||||
| -rw-r--r-- | drivers/staging/nvec/nvec_paz00.c | 98 | ||||
| -rw-r--r-- | drivers/staging/nvec/nvec_power.c | 387 | ||||
| -rw-r--r-- | drivers/staging/nvec/nvec_ps2.c | 179 |
10 files changed, 1657 insertions, 734 deletions
diff --git a/drivers/staging/nvec/Kconfig b/drivers/staging/nvec/Kconfig index 987ad48ff93..9475e20c3d6 100644 --- a/drivers/staging/nvec/Kconfig +++ b/drivers/staging/nvec/Kconfig @@ -1,27 +1,35 @@ config MFD_NVEC - bool "NV Tegra Embedded Controller SMBus Interface" + tristate "NV Tegra Embedded Controller SMBus Interface" depends on I2C && GPIOLIB && ARCH_TEGRA + select MFD_CORE help Say Y here to enable support for a nVidia compliant embedded controller. config KEYBOARD_NVEC - bool "Keyboard on nVidia compliant EC" - depends on MFD_NVEC + tristate "Keyboard on nVidia compliant EC" + depends on MFD_NVEC && INPUT help - Say Y here to enable support for a keyboard connected to + Say Y here to enable support for a keyboard connected to a nVidia compliant embedded controller. config SERIO_NVEC_PS2 - bool "PS2 on nVidia EC" - depends on MFD_NVEC + tristate "PS2 on nVidia EC" + depends on MFD_NVEC && SERIO help Say Y here to enable support for a Touchpad / Mouse connected to a nVidia compliant embedded controller. config NVEC_POWER - bool "NVEC charger and battery" - depends on MFD_NVEC + tristate "NVEC charger and battery" + depends on MFD_NVEC && POWER_SUPPLY help Say Y to enable support for battery and charger interface for nVidia compliant embedded controllers. + +config NVEC_PAZ00 + tristate "Support for OEM specific functions on Compal PAZ00 based devices" + depends on MFD_NVEC && LEDS_CLASS + help + Say Y to enable control of the yellow side leds on Compal PAZ00 based + devices, e.g. Toshbia AC100 and Dynabooks AZ netbooks. diff --git a/drivers/staging/nvec/Makefile b/drivers/staging/nvec/Makefile index 4b5fcec1a10..0db0e1f4333 100644 --- a/drivers/staging/nvec/Makefile +++ b/drivers/staging/nvec/Makefile @@ -2,3 +2,4 @@ obj-$(CONFIG_SERIO_NVEC_PS2) += nvec_ps2.o obj-$(CONFIG_MFD_NVEC) += nvec.o obj-$(CONFIG_NVEC_POWER) += nvec_power.o obj-$(CONFIG_KEYBOARD_NVEC) += nvec_kbd.o +obj-$(CONFIG_NVEC_PAZ00) += nvec_paz00.o diff --git a/drivers/staging/nvec/TODO b/drivers/staging/nvec/TODO index 649d6b70dea..e5ae42a0b44 100644 --- a/drivers/staging/nvec/TODO +++ b/drivers/staging/nvec/TODO @@ -1,10 +1,8 @@ ToDo list (incomplete, unordered) - - convert mouse, keyboard, and power to platform devices - - add copyright / driver author / license - add compile as module support - - move nvec devices to mfd cells? - - adjust to kernel style - - fix clk usage - should not be using clk_get_sys(), but clk_get(&pdev->dev, conn) - where conn is either NULL if the device only has one clock, or - the device specific name if it has multiple clocks. + - move half of the nvec init stuff to i2c-tegra.c + - move event handling to nvec_events + - finish suspend/resume support + - modifiy the sync_write method to return the received + message in a variable (and return the error code). + - add support for more device implementations diff --git a/drivers/staging/nvec/nvec-keytable.h b/drivers/staging/nvec/nvec-keytable.h index 6a1c4f7f460..1dc22cb8812 100644 --- a/drivers/staging/nvec/nvec-keytable.h +++ b/drivers/staging/nvec/nvec-keytable.h @@ -22,7 +22,8 @@ */ static unsigned short code_tab_102us[] = { - KEY_GRAVE, // 0x00 + /* 0x00 */ + KEY_GRAVE, KEY_ESC, KEY_1, KEY_2, @@ -38,7 +39,8 @@ static unsigned short code_tab_102us[] = { KEY_EQUAL, KEY_BACKSPACE, KEY_TAB, - KEY_Q, // 0x10 + /* 0x10 */ + KEY_Q, KEY_W, KEY_E, KEY_R, @@ -54,7 +56,8 @@ static unsigned short code_tab_102us[] = { KEY_LEFTCTRL, KEY_A, KEY_S, - KEY_D, // 0x20 + /* 0x20 */ + KEY_D, KEY_F, KEY_G, KEY_H, @@ -70,7 +73,8 @@ static unsigned short code_tab_102us[] = { KEY_X, KEY_C, KEY_V, - KEY_B, // 0x30 + /* 0x30 */ + KEY_B, KEY_N, KEY_M, KEY_COMMA, @@ -86,13 +90,15 @@ static unsigned short code_tab_102us[] = { KEY_F3, KEY_F4, KEY_F5, - KEY_F6, // 0x40 + /* 0x40 */ + KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_FN, - 0, //VK_SCROLL + /* VK_SCROLL */ + 0, KEY_KP7, KEY_KP8, KEY_KP9, @@ -102,52 +108,57 @@ static unsigned short code_tab_102us[] = { KEY_KP6, KEY_KPPLUS, KEY_KP1, - KEY_KP2, // 0x50 + /* 0x50 */ + KEY_KP2, KEY_KP3, KEY_KP0, KEY_KPDOT, - KEY_MENU, //VK_SNAPSHOT + /* VK_SNAPSHOT */ + KEY_MENU, KEY_POWER, - KEY_102ND, //VK_OEM_102 henry+ 0x2B (43) BACKSLASH have been used,change to use 0X56 (86) - KEY_F11, //VK_F11 - KEY_F12, //VK_F12 - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, // 60 - 0, - 0, - KEY_SEARCH, // add search key map - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, // 70 - 0, - 0, - KEY_KP5, //73 for JP keyboard '\' key, report 0x4c - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - KEY_KP9, //7d for JP keyboard '|' key, report 0x49 + /* VK_OEM_102 */ + KEY_102ND, + KEY_F11, + KEY_F12, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + /* 0x60 */ + 0, + 0, + 0, + KEY_SEARCH, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + /* 0x70 */ + 0, + 0, + 0, + KEY_KP5, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + KEY_KP9, }; static unsigned short extcode_tab_us102[] = { @@ -167,27 +178,35 @@ static unsigned short extcode_tab_us102[] = { 0, 0, 0, - 0, // 0xE0 0x10 + /* 0x10 */ + 0, + 0, + 0, + 0, + 0, + 0, + 0, 0, 0, + /* VK_MEDIA_NEXT_TRACK */ 0, 0, 0, + /* VK_RETURN */ 0, + KEY_RIGHTCTRL, 0, 0, - 0, //VK_MEDIA_NEXT_TRACK, + /* 0x20 */ + KEY_MUTE, + /* VK_LAUNCH_APP1 */ 0, + /* VK_MEDIA_PLAY_PAUSE */ 0, - 0, //VK_RETURN, - KEY_RIGHTCTRL, //VK_RCONTROL, 0, + /* VK_MEDIA_STOP */ 0, - KEY_MUTE, // 0xE0 0x20 - 0, //VK_LAUNCH_APP1 - 0, //VK_MEDIA_PLAY_PAUSE 0, - 0, //VK_MEDIA_STOP 0, 0, 0, @@ -198,41 +217,54 @@ static unsigned short extcode_tab_us102[] = { 0, 0, 0, + /* 0x30 */ + KEY_VOLUMEUP, 0, - KEY_VOLUMEUP, // 0xE0 0x30 + /* VK_BROWSER_HOME */ + 0, + 0, + 0, + /* VK_DIVIDE */ + KEY_KPSLASH, + 0, + /* VK_SNAPSHOT */ + KEY_SYSRQ, + /* VK_RMENU */ + KEY_RIGHTALT, + /* VK_OEM_NV_BACKLIGHT_UP */ + 0, + /* VK_OEM_NV_BACKLIGHT_DN */ + 0, + /* VK_OEM_NV_BACKLIGHT_AUTOTOGGLE */ + 0, + /* VK_OEM_NV_POWER_INFO */ + 0, + /* VK_OEM_NV_WIFI_TOGGLE */ + 0, + /* VK_OEM_NV_DISPLAY_SELECT */ + 0, + /* VK_OEM_NV_AIRPLANE_TOGGLE */ + 0, + /* 0x40 */ + 0, + KEY_LEFT, 0, - 0, //VK_BROWSER_HOME 0, 0, - KEY_KPSLASH, //VK_DIVIDE 0, - KEY_SYSRQ, //VK_SNAPSHOT - KEY_RIGHTALT, //VK_RMENU - 0, //VK_OEM_NV_BACKLIGHT_UP - 0, //VK_OEM_NV_BACKLIGHT_DN - 0, //VK_OEM_NV_BACKLIGHT_AUTOTOGGLE - 0, //VK_OEM_NV_POWER_INFO - 0, //VK_OEM_NV_WIFI_TOGGLE - 0, //VK_OEM_NV_DISPLAY_SELECT - 0, //VK_OEM_NV_AIRPLANE_TOGGLE - 0, //0xE0 0x40 - KEY_LEFT, //VK_OEM_NV_RESERVED henry+ for JP keyboard - 0, //VK_OEM_NV_RESERVED - 0, //VK_OEM_NV_RESERVED - 0, //VK_OEM_NV_RESERVED - 0, //VK_OEM_NV_RESERVED KEY_CANCEL, KEY_HOME, KEY_UP, - KEY_PAGEUP, //VK_PRIOR + KEY_PAGEUP, 0, KEY_LEFT, 0, KEY_RIGHT, 0, KEY_END, - KEY_DOWN, // 0xE0 0x50 - KEY_PAGEDOWN, //VK_NEXT + /* 0x50 */ + KEY_DOWN, + KEY_PAGEDOWN, KEY_INSERT, KEY_DELETE, 0, @@ -242,25 +274,34 @@ static unsigned short extcode_tab_us102[] = { 0, 0, 0, - KEY_LEFTMETA, //VK_LWIN - 0, //VK_RWIN - KEY_ESC, //VK_APPS - KEY_KPMINUS, //for power button workaround - 0, + KEY_LEFTMETA, + 0, + KEY_ESC, + KEY_KPMINUS, + 0, + 0, + 0, + 0, + 0, + 0, + /* VK_BROWSER_SEARCH */ + 0, + /* VK_BROWSER_FAVORITES */ + 0, + /* VK_BROWSER_REFRESH */ + 0, + /* VK_BROWSER_STOP */ + 0, + /* VK_BROWSER_FORWARD */ 0, + /* VK_BROWSER_BACK */ 0, + /* VK_LAUNCH_APP2 */ 0, + /* VK_LAUNCH_MAIL */ 0, + /* VK_LAUNCH_MEDIA_SELECT */ 0, - 0, //VK_BROWSER_SEARCH - 0, //VK_BROWSER_FAVORITES - 0, //VK_BROWSER_REFRESH - 0, //VK_BROWSER_STOP - 0, //VK_BROWSER_FORWARD - 0, //VK_BROWSER_BACK - 0, //VK_LAUNCH_APP2 - 0, //VK_LAUNCH_MAIL - 0, //VK_LAUNCH_MEDIA_SELECT }; -static unsigned short* code_tabs[] = {code_tab_102us, extcode_tab_us102 }; +static unsigned short *code_tabs[] = { code_tab_102us, extcode_tab_us102 }; diff --git a/drivers/staging/nvec/nvec.c b/drivers/staging/nvec/nvec.c index 72258e8c64c..90f1c4d7fa8 100644 --- a/drivers/staging/nvec/nvec.c +++ b/drivers/staging/nvec/nvec.c @@ -1,384 +1,882 @@ -// #define DEBUG - -/* ToDo list (incomplete, unorderd) - - convert mouse, keyboard, and power to platform devices -*/ - -#include <asm/io.h> -#include <asm/irq.h> +/* + * NVEC: NVIDIA compliant embedded controller interface + * + * Copyright (C) 2011 The AC100 Kernel Team <ac100@lists.lauchpad.net> + * + * Authors: Pierre-Hugues Husson <phhusson@free.fr> + * Ilya Petrov <ilya.muromec@gmail.com> + * Marc Dietrich <marvin24@gmx.de> + * Julian Andres Klode <jak@jak-linux.org> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + */ + +/* #define DEBUG */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/atomic.h> +#include <linux/clk.h> #include <linux/completion.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/gpio.h> #include <linux/interrupt.h> +#include <linux/io.h> #include <linux/irq.h> -#include <linux/slab.h> -#include <linux/gpio.h> -#include <linux/serio.h> -#include <linux/delay.h> -#include <linux/input.h> -#include <linux/workqueue.h> -#include <linux/clk.h> -#include <mach/iomap.h> -#include <mach/clk.h> -#include <linux/semaphore.h> +#include <linux/of.h> +#include <linux/of_gpio.h> #include <linux/list.h> +#include <linux/mfd/core.h> +#include <linux/mutex.h> #include <linux/notifier.h> -#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/workqueue.h> + #include "nvec.h" -static unsigned char EC_DISABLE_EVENT_REPORTING[] = {'\x04','\x00','\x00'}; -static unsigned char EC_ENABLE_EVENT_REPORTING[] = {'\x04','\x00','\x01'}; -static unsigned char EC_GET_FIRMWARE_VERSION[] = {'\x07','\x15'}; +#define I2C_CNFG 0x00 +#define I2C_CNFG_PACKET_MODE_EN (1<<10) +#define I2C_CNFG_NEW_MASTER_SFM (1<<11) +#define I2C_CNFG_DEBOUNCE_CNT_SHIFT 12 + +#define I2C_SL_CNFG 0x20 +#define I2C_SL_NEWSL (1<<2) +#define I2C_SL_NACK (1<<1) +#define I2C_SL_RESP (1<<0) +#define I2C_SL_IRQ (1<<3) +#define END_TRANS (1<<4) +#define RCVD (1<<2) +#define RNW (1<<1) + +#define I2C_SL_RCVD 0x24 +#define I2C_SL_STATUS 0x28 +#define I2C_SL_ADDR1 0x2c +#define I2C_SL_ADDR2 0x30 +#define I2C_SL_DELAY_COUNT 0x3c + +/** + * enum nvec_msg_category - Message categories for nvec_msg_alloc() + * @NVEC_MSG_RX: The message is an incoming message (from EC) + * @NVEC_MSG_TX: The message is an outgoing message (to EC) + */ +enum nvec_msg_category { + NVEC_MSG_RX, + NVEC_MSG_TX, +}; + +enum nvec_sleep_subcmds { + GLOBAL_EVENTS, + AP_PWR_DOWN, + AP_SUSPEND, +}; + +#define CNF_EVENT_REPORTING 0x01 +#define GET_FIRMWARE_VERSION 0x15 +#define LID_SWITCH BIT(1) +#define PWR_BUTTON BIT(15) static struct nvec_chip *nvec_power_handle; +static const struct mfd_cell nvec_devices[] = { + { + .name = "nvec-kbd", + .id = 1, + }, + { + .name = "nvec-mouse", + .id = 1, + }, + { + .name = "nvec-power", + .id = 1, + }, + { + .name = "nvec-power", + .id = 2, + }, + { + .name = "nvec-paz00", + .id = 1, + }, +}; + +/** + * nvec_register_notifier - Register a notifier with nvec + * @nvec: A &struct nvec_chip + * @nb: The notifier block to register + * + * Registers a notifier with @nvec. The notifier will be added to an atomic + * notifier chain that is called for all received messages except those that + * correspond to a request initiated by nvec_write_sync(). + */ int nvec_register_notifier(struct nvec_chip *nvec, struct notifier_block *nb, - unsigned int events) + unsigned int events) { return atomic_notifier_chain_register(&nvec->notifier_list, nb); } EXPORT_SYMBOL_GPL(nvec_register_notifier); -static int nvec_status_notifier(struct notifier_block *nb, unsigned long event_type, - void *data) +/** + * nvec_unregister_notifier - Unregister a notifier with nvec + * @nvec: A &struct nvec_chip + * @nb: The notifier block to unregister + * + * Unregisters a notifier with @nvec. The notifier will be removed from the + * atomic notifier chain. + */ +int nvec_unregister_notifier(struct nvec_chip *nvec, struct notifier_block *nb) { + return atomic_notifier_chain_unregister(&nvec->notifier_list, nb); +} +EXPORT_SYMBOL_GPL(nvec_unregister_notifier); + +/** + * nvec_status_notifier - The final notifier + * + * Prints a message about control events not handled in the notifier + * chain. + */ +static int nvec_status_notifier(struct notifier_block *nb, + unsigned long event_type, void *data) +{ + struct nvec_chip *nvec = container_of(nb, struct nvec_chip, + nvec_status_notifier); unsigned char *msg = (unsigned char *)data; - int i; - if(event_type != NVEC_CNTL) + if (event_type != NVEC_CNTL) return NOTIFY_DONE; - printk("unhandled msg type %ld, payload: ", event_type); - for (i = 0; i < msg[1]; i++) - printk("%0x ", msg[i+2]); - printk("\n"); + dev_warn(nvec->dev, "unhandled msg type %ld\n", event_type); + print_hex_dump(KERN_WARNING, "payload: ", DUMP_PREFIX_NONE, 16, 1, + msg, msg[1] + 2, true); return NOTIFY_OK; } -void nvec_write_async(struct nvec_chip *nvec, unsigned char *data, short size) +/** + * nvec_msg_alloc: + * @nvec: A &struct nvec_chip + * @category: Pool category, see &enum nvec_msg_category + * + * Allocate a single &struct nvec_msg object from the message pool of + * @nvec. The result shall be passed to nvec_msg_free() if no longer + * used. + * + * Outgoing messages are placed in the upper 75% of the pool, keeping the + * lower 25% available for RX buffers only. The reason is to prevent a + * situation where all buffers are full and a message is thus endlessly + * retried because the response could never be processed. + */ +static struct nvec_msg *nvec_msg_alloc(struct nvec_chip *nvec, + enum nvec_msg_category category) { - struct nvec_msg *msg = kzalloc(sizeof(struct nvec_msg), GFP_NOWAIT); + int i = (category == NVEC_MSG_TX) ? (NVEC_POOL_SIZE / 4) : 0; + + for (; i < NVEC_POOL_SIZE; i++) { + if (atomic_xchg(&nvec->msg_pool[i].used, 1) == 0) { + dev_vdbg(nvec->dev, "INFO: Allocate %i\n", i); + return &nvec->msg_pool[i]; + } + } + + dev_err(nvec->dev, "could not allocate %s buffer\n", + (category == NVEC_MSG_TX) ? "TX" : "RX"); + + return NULL; +} + +/** + * nvec_msg_free: + * @nvec: A &struct nvec_chip + * @msg: A message (must be allocated by nvec_msg_alloc() and belong to @nvec) + * + * Free the given message + */ +void nvec_msg_free(struct nvec_chip *nvec, struct nvec_msg *msg) +{ + if (msg != &nvec->tx_scratch) + dev_vdbg(nvec->dev, "INFO: Free %ti\n", msg - nvec->msg_pool); + atomic_set(&msg->used, 0); +} +EXPORT_SYMBOL_GPL(nvec_msg_free); + +/** + * nvec_msg_is_event - Return %true if @msg is an event + * @msg: A message + */ +static bool nvec_msg_is_event(struct nvec_msg *msg) +{ + return msg->data[0] >> 7; +} + +/** + * nvec_msg_size - Get the size of a message + * @msg: The message to get the size for + * + * This only works for received messages, not for outgoing messages. + */ +static size_t nvec_msg_size(struct nvec_msg *msg) +{ + bool is_event = nvec_msg_is_event(msg); + int event_length = (msg->data[0] & 0x60) >> 5; + + /* for variable size, payload size in byte 1 + count (1) + cmd (1) */ + if (!is_event || event_length == NVEC_VAR_SIZE) + return (msg->pos || msg->size) ? (msg->data[1] + 2) : 0; + else if (event_length == NVEC_2BYTES) + return 2; + else if (event_length == NVEC_3BYTES) + return 3; + else + return 0; +} + +/** + * nvec_gpio_set_value - Set the GPIO value + * @nvec: A &struct nvec_chip + * @value: The value to write (0 or 1) + * + * Like gpio_set_value(), but generating debugging information + */ +static void nvec_gpio_set_value(struct nvec_chip *nvec, int value) +{ + dev_dbg(nvec->dev, "GPIO changed from %u to %u\n", + gpio_get_value(nvec->gpio), value); + gpio_set_value(nvec->gpio, value); +} + +/** + * nvec_write_async - Asynchronously write a message to NVEC + * @nvec: An nvec_chip instance + * @data: The message data, starting with the request type + * @size: The size of @data + * + * Queue a single message to be transferred to the embedded controller + * and return immediately. + * + * Returns: 0 on success, a negative error code on failure. If a failure + * occured, the nvec driver may print an error. + */ +int nvec_write_async(struct nvec_chip *nvec, const unsigned char *data, + short size) +{ + struct nvec_msg *msg; + unsigned long flags; + + msg = nvec_msg_alloc(nvec, NVEC_MSG_TX); + + if (msg == NULL) + return -ENOMEM; - msg->data = kzalloc(size, GFP_NOWAIT); msg->data[0] = size; memcpy(msg->data + 1, data, size); msg->size = size + 1; - msg->pos = 0; - INIT_LIST_HEAD(&msg->node); + spin_lock_irqsave(&nvec->tx_lock, flags); list_add_tail(&msg->node, &nvec->tx_data); + spin_unlock_irqrestore(&nvec->tx_lock, flags); + + schedule_work(&nvec->tx_work); - gpio_set_value(nvec->gpio, 0); + return 0; } EXPORT_SYMBOL(nvec_write_async); -static void nvec_request_master(struct work_struct *work) +/** + * nvec_write_sync - Write a message to nvec and read the response + * @nvec: An &struct nvec_chip + * @data: The data to write + * @size: The size of @data + * + * This is similar to nvec_write_async(), but waits for the + * request to be answered before returning. This function + * uses a mutex and can thus not be called from e.g. + * interrupt handlers. + * + * Returns: A pointer to the response message on success, + * %NULL on failure. Free with nvec_msg_free() once no longer + * used. + */ +struct nvec_msg *nvec_write_sync(struct nvec_chip *nvec, + const unsigned char *data, short size) { - struct nvec_chip *nvec = container_of(work, struct nvec_chip, tx_work); + struct nvec_msg *msg; - if(!list_empty(&nvec->tx_data)) { - gpio_set_value(nvec->gpio, 0); - } -} + mutex_lock(&nvec->sync_write_mutex); -static int parse_msg(struct nvec_chip *nvec, struct nvec_msg *msg) -{ - int i; + nvec->sync_write_pending = (data[1] << 8) + data[0]; - if((msg->data[0] & 1<<7) == 0 && msg->data[3]) { - dev_err(nvec->dev, "ec responded %02x %02x %02x %02x\n", msg->data[0], - msg->data[1], msg->data[2], msg->data[3]); - return -EINVAL; + if (nvec_write_async(nvec, data, size) < 0) { + mutex_unlock(&nvec->sync_write_mutex); + return NULL; } - if ((msg->data[0] >> 7 ) == 1 && (msg->data[0] & 0x0f) == 5) - { - dev_warn(nvec->dev, "ec system event "); - for (i=0; i < msg->data[1]; i++) - dev_warn(nvec->dev, "%02x ", msg->data[2+i]); - dev_warn(nvec->dev, "\n"); + dev_dbg(nvec->dev, "nvec_sync_write: 0x%04x\n", + nvec->sync_write_pending); + if (!(wait_for_completion_timeout(&nvec->sync_write, + msecs_to_jiffies(2000)))) { + dev_warn(nvec->dev, "timeout waiting for sync write to complete\n"); + mutex_unlock(&nvec->sync_write_mutex); + return NULL; } - atomic_notifier_call_chain(&nvec->notifier_list, msg->data[0] & 0x8f, msg->data); + dev_dbg(nvec->dev, "nvec_sync_write: pong!\n"); - return 0; + msg = nvec->last_sync_msg; + + mutex_unlock(&nvec->sync_write_mutex); + + return msg; +} +EXPORT_SYMBOL(nvec_write_sync); + +/** + * nvec_toggle_global_events - enables or disables global event reporting + * @nvec: nvec handle + * @state: true for enable, false for disable + * + * This switches on/off global event reports by the embedded controller. + */ +static void nvec_toggle_global_events(struct nvec_chip *nvec, bool state) +{ + unsigned char global_events[] = { NVEC_SLEEP, GLOBAL_EVENTS, state }; + + nvec_write_async(nvec, global_events, 3); } -static struct nvec_msg *nvec_write_sync(struct nvec_chip *nvec, unsigned char *data, short size) +/** + * nvec_event_mask - fill the command string with event bitfield + * ev: points to event command string + * mask: bit to insert into the event mask + * + * Configure event command expects a 32 bit bitfield which describes + * which events to enable. The bitfield has the following structure + * (from highest byte to lowest): + * system state bits 7-0 + * system state bits 15-8 + * oem system state bits 7-0 + * oem system state bits 15-8 + */ +static void nvec_event_mask(char *ev, u32 mask) { - down(&nvec->sync_write_mutex); + ev[3] = mask >> 16 & 0xff; + ev[4] = mask >> 24 & 0xff; + ev[5] = mask >> 0 & 0xff; + ev[6] = mask >> 8 & 0xff; +} - nvec->sync_write_pending = (data[1] << 8) + data[0]; - nvec_write_async(nvec, data, size); +/** + * nvec_request_master - Process outgoing messages + * @work: A &struct work_struct (the tx_worker member of &struct nvec_chip) + * + * Processes all outgoing requests by sending the request and awaiting the + * response, then continuing with the next request. Once a request has a + * matching response, it will be freed and removed from the list. + */ +static void nvec_request_master(struct work_struct *work) +{ + struct nvec_chip *nvec = container_of(work, struct nvec_chip, tx_work); + unsigned long flags; + long err; + struct nvec_msg *msg; - dev_dbg(nvec->dev, "nvec_sync_write: 0x%04x\n", nvec->sync_write_pending); - wait_for_completion(&nvec->sync_write); - dev_dbg(nvec->dev, "nvec_sync_write: pong!\n"); + spin_lock_irqsave(&nvec->tx_lock, flags); + while (!list_empty(&nvec->tx_data)) { + msg = list_first_entry(&nvec->tx_data, struct nvec_msg, node); + spin_unlock_irqrestore(&nvec->tx_lock, flags); + nvec_gpio_set_value(nvec, 0); + err = wait_for_completion_interruptible_timeout( + &nvec->ec_transfer, msecs_to_jiffies(5000)); + + if (err == 0) { + dev_warn(nvec->dev, "timeout waiting for ec transfer\n"); + nvec_gpio_set_value(nvec, 1); + msg->pos = 0; + } - up(&nvec->sync_write_mutex); + spin_lock_irqsave(&nvec->tx_lock, flags); - return nvec->last_sync_msg; + if (err > 0) { + list_del_init(&msg->node); + nvec_msg_free(nvec, msg); + } + } + spin_unlock_irqrestore(&nvec->tx_lock, flags); } -/* RX worker */ +/** + * parse_msg - Print some information and call the notifiers on an RX message + * @nvec: A &struct nvec_chip + * @msg: A message received by @nvec + * + * Paarse some pieces of the message and then call the chain of notifiers + * registered via nvec_register_notifier. + */ +static int parse_msg(struct nvec_chip *nvec, struct nvec_msg *msg) +{ + if ((msg->data[0] & 1 << 7) == 0 && msg->data[3]) { + dev_err(nvec->dev, "ec responded %*ph\n", 4, msg->data); + return -EINVAL; + } + + if ((msg->data[0] >> 7) == 1 && (msg->data[0] & 0x0f) == 5) + print_hex_dump(KERN_WARNING, "ec system event ", + DUMP_PREFIX_NONE, 16, 1, msg->data, + msg->data[1] + 2, true); + + atomic_notifier_call_chain(&nvec->notifier_list, msg->data[0] & 0x8f, + msg->data); + + return 0; +} + +/** + * nvec_dispatch - Process messages received from the EC + * @work: A &struct work_struct (the tx_worker member of &struct nvec_chip) + * + * Process messages previously received from the EC and put into the RX + * queue of the &struct nvec_chip instance associated with @work. + */ static void nvec_dispatch(struct work_struct *work) { struct nvec_chip *nvec = container_of(work, struct nvec_chip, rx_work); + unsigned long flags; struct nvec_msg *msg; - while(!list_empty(&nvec->rx_data)) - { + spin_lock_irqsave(&nvec->rx_lock, flags); + while (!list_empty(&nvec->rx_data)) { msg = list_first_entry(&nvec->rx_data, struct nvec_msg, node); list_del_init(&msg->node); + spin_unlock_irqrestore(&nvec->rx_lock, flags); - if(nvec->sync_write_pending == (msg->data[2] << 8) + msg->data[0]) - { + if (nvec->sync_write_pending == + (msg->data[2] << 8) + msg->data[0]) { dev_dbg(nvec->dev, "sync write completed!\n"); nvec->sync_write_pending = 0; nvec->last_sync_msg = msg; complete(&nvec->sync_write); } else { parse_msg(nvec, msg); - if((!msg) || (!msg->data)) - dev_warn(nvec->dev, "attempt access zero pointer"); - else { - kfree(msg->data); - kfree(msg); - } + nvec_msg_free(nvec, msg); } + spin_lock_irqsave(&nvec->rx_lock, flags); + } + spin_unlock_irqrestore(&nvec->rx_lock, flags); +} + +/** + * nvec_tx_completed - Complete the current transfer + * @nvec: A &struct nvec_chip + * + * This is called when we have received an END_TRANS on a TX transfer. + */ +static void nvec_tx_completed(struct nvec_chip *nvec) +{ + /* We got an END_TRANS, let's skip this, maybe there's an event */ + if (nvec->tx->pos != nvec->tx->size) { + dev_err(nvec->dev, "premature END_TRANS, resending\n"); + nvec->tx->pos = 0; + nvec_gpio_set_value(nvec, 0); + } else { + nvec->state = 0; } } -static irqreturn_t i2c_interrupt(int irq, void *dev) +/** + * nvec_rx_completed - Complete the current transfer + * @nvec: A &struct nvec_chip + * + * This is called when we have received an END_TRANS on a RX transfer. + */ +static void nvec_rx_completed(struct nvec_chip *nvec) +{ + if (nvec->rx->pos != nvec_msg_size(nvec->rx)) { + dev_err(nvec->dev, "RX incomplete: Expected %u bytes, got %u\n", + (uint) nvec_msg_size(nvec->rx), + (uint) nvec->rx->pos); + + nvec_msg_free(nvec, nvec->rx); + nvec->state = 0; + + /* Battery quirk - Often incomplete, and likes to crash */ + if (nvec->rx->data[0] == NVEC_BAT) + complete(&nvec->ec_transfer); + + return; + } + + spin_lock(&nvec->rx_lock); + + /* add the received data to the work list + and move the ring buffer pointer to the next entry */ + list_add_tail(&nvec->rx->node, &nvec->rx_data); + + spin_unlock(&nvec->rx_lock); + + nvec->state = 0; + + if (!nvec_msg_is_event(nvec->rx)) + complete(&nvec->ec_transfer); + + schedule_work(&nvec->rx_work); +} + +/** + * nvec_invalid_flags - Send an error message about invalid flags and jump + * @nvec: The nvec device + * @status: The status flags + * @reset: Whether we shall jump to state 0. + */ +static void nvec_invalid_flags(struct nvec_chip *nvec, unsigned int status, + bool reset) +{ + dev_err(nvec->dev, "unexpected status flags 0x%02x during state %i\n", + status, nvec->state); + if (reset) + nvec->state = 0; +} + +/** + * nvec_tx_set - Set the message to transfer (nvec->tx) + * @nvec: A &struct nvec_chip + * + * Gets the first entry from the tx_data list of @nvec and sets the + * tx member to it. If the tx_data list is empty, this uses the + * tx_scratch message to send a no operation message. + */ +static void nvec_tx_set(struct nvec_chip *nvec) +{ + spin_lock(&nvec->tx_lock); + if (list_empty(&nvec->tx_data)) { + dev_err(nvec->dev, "empty tx - sending no-op\n"); + memcpy(nvec->tx_scratch.data, "\x02\x07\x02", 3); + nvec->tx_scratch.size = 3; + nvec->tx_scratch.pos = 0; + nvec->tx = &nvec->tx_scratch; + list_add_tail(&nvec->tx->node, &nvec->tx_data); + } else { + nvec->tx = list_first_entry(&nvec->tx_data, struct nvec_msg, + node); + nvec->tx->pos = 0; + } + spin_unlock(&nvec->tx_lock); + + dev_dbg(nvec->dev, "Sending message of length %u, command 0x%x\n", + (uint)nvec->tx->size, nvec->tx->data[1]); +} + +/** + * nvec_interrupt - Interrupt handler + * @irq: The IRQ + * @dev: The nvec device + * + * Interrupt handler that fills our RX buffers and empties our TX + * buffers. This uses a finite state machine with ridiculous amounts + * of error checking, in order to be fairly reliable. + */ +static irqreturn_t nvec_interrupt(int irq, void *dev) { unsigned long status; - unsigned long received; - unsigned char to_send; - struct nvec_msg *msg; - struct nvec_chip *nvec = (struct nvec_chip *)dev; - unsigned char *i2c_regs = nvec->i2c_regs; + unsigned int received = 0; + unsigned char to_send = 0xff; + const unsigned long irq_mask = I2C_SL_IRQ | END_TRANS | RCVD | RNW; + struct nvec_chip *nvec = dev; + unsigned int state = nvec->state; - status = readl(i2c_regs + I2C_SL_STATUS); + status = readl(nvec->base + I2C_SL_STATUS); - if(!(status & I2C_SL_IRQ)) - { - dev_warn(nvec->dev, "nvec Spurious IRQ\n"); - //Yup, handled. ahum. - goto handled; + /* Filter out some errors */ + if ((status & irq_mask) == 0 && (status & ~irq_mask) != 0) { + dev_err(nvec->dev, "unexpected irq mask %lx\n", status); + return IRQ_HANDLED; } - if(status & END_TRANS && !(status & RCVD)) - { - //Reenable IRQ only when even has been sent - //printk("Write sequence ended !\n"); - //parse_msg(nvec); - nvec->state = NVEC_WAIT; - if(nvec->rx->size > 1) - { - list_add_tail(&nvec->rx->node, &nvec->rx_data); - schedule_work(&nvec->rx_work); + if ((status & I2C_SL_IRQ) == 0) { + dev_err(nvec->dev, "Spurious IRQ\n"); + return IRQ_HANDLED; + } + + /* The EC did not request a read, so it send us something, read it */ + if ((status & RNW) == 0) { + received = readl(nvec->base + I2C_SL_RCVD); + if (status & RCVD) + writel(0, nvec->base + I2C_SL_RCVD); + } + + if (status == (I2C_SL_IRQ | RCVD)) + nvec->state = 0; + + switch (nvec->state) { + case 0: /* Verify that its a transfer start, the rest later */ + if (status != (I2C_SL_IRQ | RCVD)) + nvec_invalid_flags(nvec, status, false); + break; + case 1: /* command byte */ + if (status != I2C_SL_IRQ) { + nvec_invalid_flags(nvec, status, true); } else { - kfree(nvec->rx->data); - kfree(nvec->rx); + nvec->rx = nvec_msg_alloc(nvec, NVEC_MSG_RX); + /* Should not happen in a normal world */ + if (unlikely(nvec->rx == NULL)) { + nvec->state = 0; + break; + } + nvec->rx->data[0] = received; + nvec->rx->pos = 1; + nvec->state = 2; } - return IRQ_HANDLED; - } else if(status & RNW) - { - // Work around for AP20 New Slave Hw Bug. Give 1us extra. - // nvec/smbus/nvec_i2c_transport.c in NV`s crap for reference - if(status & RCVD) - udelay(3); - - if(status & RCVD) - { - nvec->state = NVEC_WRITE; - //Master wants something from us. New communication -// dev_dbg(nvec->dev, "New read comm!\n"); + break; + case 2: /* first byte after command */ + if (status == (I2C_SL_IRQ | RNW | RCVD)) { + udelay(33); + if (nvec->rx->data[0] != 0x01) { + dev_err(nvec->dev, + "Read without prior read command\n"); + nvec->state = 0; + break; + } + nvec_msg_free(nvec, nvec->rx); + nvec->state = 3; + nvec_tx_set(nvec); + BUG_ON(nvec->tx->size < 1); + to_send = nvec->tx->data[0]; + nvec->tx->pos = 1; + } else if (status == (I2C_SL_IRQ)) { + BUG_ON(nvec->rx == NULL); + nvec->rx->data[1] = received; + nvec->rx->pos = 2; + nvec->state = 4; } else { - //Master wants something from us from a communication we've already started -// dev_dbg(nvec->dev, "Read comm cont !\n"); + nvec_invalid_flags(nvec, status, true); } - //if(msg_pos<msg_size) { - if(list_empty(&nvec->tx_data)) - { - dev_err(nvec->dev, "nvec empty tx - sending no-op\n"); - to_send = 0x8a; - nvec_write_async(nvec, "\x07\x02", 2); -// to_send = 0x01; + break; + case 3: /* EC does a block read, we transmit data */ + if (status & END_TRANS) { + nvec_tx_completed(nvec); + } else if ((status & RNW) == 0 || (status & RCVD)) { + nvec_invalid_flags(nvec, status, true); + } else if (nvec->tx && nvec->tx->pos < nvec->tx->size) { + to_send = nvec->tx->data[nvec->tx->pos++]; } else { - msg = list_first_entry(&nvec->tx_data, struct nvec_msg, node); - if(msg->pos < msg->size) { - to_send = msg->data[msg->pos]; - msg->pos++; - } else { - dev_err(nvec->dev, "nvec crap! %d\n", msg->size); - to_send = 0x01; - } - - if(msg->pos >= msg->size) - { - list_del_init(&msg->node); - kfree(msg->data); - kfree(msg); - schedule_work(&nvec->tx_work); - nvec->state = NVEC_WAIT; - } + dev_err(nvec->dev, "tx buffer underflow on %p (%u > %u)\n", + nvec->tx, + (uint) (nvec->tx ? nvec->tx->pos : 0), + (uint) (nvec->tx ? nvec->tx->size : 0)); + nvec->state = 0; } - writel(to_send, i2c_regs + I2C_SL_RCVD); - - gpio_set_value(nvec->gpio, 1); + break; + case 4: /* EC does some write, we read the data */ + if ((status & (END_TRANS | RNW)) == END_TRANS) + nvec_rx_completed(nvec); + else if (status & (RNW | RCVD)) + nvec_invalid_flags(nvec, status, true); + else if (nvec->rx && nvec->rx->pos < NVEC_MSG_SIZE) + nvec->rx->data[nvec->rx->pos++] = received; + else + dev_err(nvec->dev, + "RX buffer overflow on %p: Trying to write byte %u of %u\n", + nvec->rx, nvec->rx ? nvec->rx->pos : 0, + NVEC_MSG_SIZE); + break; + default: + nvec->state = 0; + } - dev_dbg(nvec->dev, "nvec sent %x\n", to_send); + /* If we are told that a new transfer starts, verify it */ + if ((status & (RCVD | RNW)) == RCVD) { + if (received != nvec->i2c_addr) + dev_err(nvec->dev, + "received address 0x%02x, expected 0x%02x\n", + received, nvec->i2c_addr); + nvec->state = 1; + } - goto handled; - } else { - received = readl(i2c_regs + I2C_SL_RCVD); - //Workaround? - if(status & RCVD) { - writel(0, i2c_regs + I2C_SL_RCVD); - goto handled; - } + /* Send data if requested, but not on end of transmission */ + if ((status & (RNW | END_TRANS)) == RNW) + writel(to_send, nvec->base + I2C_SL_RCVD); + + /* If we have send the first byte */ + if (status == (I2C_SL_IRQ | RNW | RCVD)) + nvec_gpio_set_value(nvec, 1); + + dev_dbg(nvec->dev, + "Handled: %s 0x%02x, %s 0x%02x in state %u [%s%s%s]\n", + (status & RNW) == 0 ? "received" : "R=", + received, + (status & (RNW | END_TRANS)) ? "sent" : "S=", + to_send, + state, + status & END_TRANS ? " END_TRANS" : "", + status & RCVD ? " RCVD" : "", + status & RNW ? " RNW" : ""); + + + /* + * TODO: A correct fix needs to be found for this. + * + * We experience less incomplete messages with this delay than without + * it, but we don't know why. Help is appreciated. + */ + udelay(100); - if (nvec->state == NVEC_WAIT) - { - nvec->state = NVEC_READ; - msg = kzalloc(sizeof(struct nvec_msg), GFP_NOWAIT); - msg->data = kzalloc(32, GFP_NOWAIT); - INIT_LIST_HEAD(&msg->node); - nvec->rx = msg; - } else - msg = nvec->rx; - - BUG_ON(msg->pos > 32); - - msg->data[msg->pos] = received; - msg->pos++; - msg->size = msg->pos; - dev_dbg(nvec->dev, "Got %02lx from Master (pos: %d)!\n", received, msg->pos); - } -handled: return IRQ_HANDLED; } -static int __devinit nvec_add_subdev(struct nvec_chip *nvec, struct nvec_subdev *subdev) +static void tegra_init_i2c_slave(struct nvec_chip *nvec) { - struct platform_device *pdev; + u32 val; - pdev = platform_device_alloc(subdev->name, subdev->id); - pdev->dev.parent = nvec->dev; - pdev->dev.platform_data = subdev->platform_data; + clk_prepare_enable(nvec->i2c_clk); - return platform_device_add(pdev); -} + reset_control_assert(nvec->rst); + udelay(2); + reset_control_deassert(nvec->rst); -static void tegra_init_i2c_slave(struct nvec_platform_data *pdata, unsigned char *i2c_regs, - struct clk *i2c_clk) -{ - u32 val; + val = I2C_CNFG_NEW_MASTER_SFM | I2C_CNFG_PACKET_MODE_EN | + (0x2 << I2C_CNFG_DEBOUNCE_CNT_SHIFT); + writel(val, nvec->base + I2C_CNFG); - clk_enable(i2c_clk); - tegra_periph_reset_assert(i2c_clk); - udelay(2); - tegra_periph_reset_deassert(i2c_clk); + clk_set_rate(nvec->i2c_clk, 8 * 80000); - writel(pdata->i2c_addr>>1, i2c_regs + I2C_SL_ADDR1); - writel(0, i2c_regs + I2C_SL_ADDR2); + writel(I2C_SL_NEWSL, nvec->base + I2C_SL_CNFG); + writel(0x1E, nvec->base + I2C_SL_DELAY_COUNT); - writel(0x1E, i2c_regs + I2C_SL_DELAY_COUNT); - val = I2C_CNFG_NEW_MASTER_SFM | I2C_CNFG_PACKET_MODE_EN | - (0x2 << I2C_CNFG_DEBOUNCE_CNT_SHIFT); - writel(val, i2c_regs + I2C_CNFG); - writel(I2C_SL_NEWL, i2c_regs + I2C_SL_CNFG); + writel(nvec->i2c_addr>>1, nvec->base + I2C_SL_ADDR1); + writel(0, nvec->base + I2C_SL_ADDR2); - clk_disable(i2c_clk); + enable_irq(nvec->irq); } +#ifdef CONFIG_PM_SLEEP +static void nvec_disable_i2c_slave(struct nvec_chip *nvec) +{ + disable_irq(nvec->irq); + writel(I2C_SL_NEWSL | I2C_SL_NACK, nvec->base + I2C_SL_CNFG); + clk_disable_unprepare(nvec->i2c_clk); +} +#endif + static void nvec_power_off(void) { - nvec_write_async(nvec_power_handle, EC_DISABLE_EVENT_REPORTING, 3); - nvec_write_async(nvec_power_handle, "\x04\x01", 2); + char ap_pwr_down[] = { NVEC_SLEEP, AP_PWR_DOWN }; + + nvec_toggle_global_events(nvec_power_handle, false); + nvec_write_async(nvec_power_handle, ap_pwr_down, 2); } -static int __devinit tegra_nvec_probe(struct platform_device *pdev) +/* + * Parse common device tree data + */ +static int nvec_i2c_parse_dt_pdata(struct nvec_chip *nvec) { - int err, i, ret; + nvec->gpio = of_get_named_gpio(nvec->dev->of_node, "request-gpios", 0); + + if (nvec->gpio < 0) { + dev_err(nvec->dev, "no gpio specified"); + return -ENODEV; + } + + if (of_property_read_u32(nvec->dev->of_node, "slave-addr", + &nvec->i2c_addr)) { + dev_err(nvec->dev, "no i2c address specified"); + return -ENODEV; + } + + return 0; +} + +static int tegra_nvec_probe(struct platform_device *pdev) +{ + int err, ret; struct clk *i2c_clk; - struct nvec_platform_data *pdata = pdev->dev.platform_data; struct nvec_chip *nvec; struct nvec_msg *msg; - unsigned char *i2c_regs; + struct resource *res; + void __iomem *base; + char get_firmware_version[] = { NVEC_CNTL, GET_FIRMWARE_VERSION }, + unmute_speakers[] = { NVEC_OEM0, 0x10, 0x59, 0x95 }, + enable_event[7] = { NVEC_SYS, CNF_EVENT_REPORTING, true }; + + if (!pdev->dev.of_node) { + dev_err(&pdev->dev, "must be instantiated using device tree\n"); + return -ENODEV; + } - nvec = kzalloc(sizeof(struct nvec_chip), GFP_KERNEL); - if(nvec == NULL) { + nvec = devm_kzalloc(&pdev->dev, sizeof(struct nvec_chip), GFP_KERNEL); + if (nvec == NULL) { dev_err(&pdev->dev, "failed to reserve memory\n"); return -ENOMEM; } platform_set_drvdata(pdev, nvec); nvec->dev = &pdev->dev; - nvec->gpio = pdata->gpio; - nvec->irq = pdata->irq; -/* - i2c_clk=clk_get_sys(NULL, "i2c"); - if(IS_ERR_OR_NULL(i2c_clk)) - printk(KERN_ERR"No such clock tegra-i2c.2\n"); - else - clk_enable(i2c_clk); -*/ - i2c_regs = ioremap(pdata->base, pdata->size); - if(!i2c_regs) { - dev_err(nvec->dev, "failed to ioremap registers\n"); - goto failed; - } + err = nvec_i2c_parse_dt_pdata(nvec); + if (err < 0) + return err; - nvec->i2c_regs = i2c_regs; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); - i2c_clk = clk_get_sys(pdata->clock, NULL); - if(IS_ERR_OR_NULL(i2c_clk)) { - dev_err(nvec->dev, "failed to get clock tegra-i2c.2\n"); - goto failed; + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(&pdev->dev, "no irq resource?\n"); + return -ENODEV; } - tegra_init_i2c_slave(pdata, i2c_regs, i2c_clk); - - err = request_irq(nvec->irq, i2c_interrupt, IRQF_DISABLED, "nvec", nvec); - if(err) { - dev_err(nvec->dev, "couldn't request irq"); - goto failed; + i2c_clk = devm_clk_get(&pdev->dev, "div-clk"); + if (IS_ERR(i2c_clk)) { + dev_err(nvec->dev, "failed to get controller clock\n"); + return -ENODEV; } - clk_enable(i2c_clk); - clk_set_rate(i2c_clk, 8*80000); - - /* Set the gpio to low when we've got something to say */ - err = gpio_request(nvec->gpio, "nvec gpio"); - if(err < 0) - dev_err(nvec->dev, "couldn't request gpio\n"); + nvec->rst = devm_reset_control_get(&pdev->dev, "i2c"); + if (IS_ERR(nvec->rst)) { + dev_err(nvec->dev, "failed to get controller reset\n"); + return PTR_ERR(nvec->rst); + } - tegra_gpio_enable(nvec->gpio); - gpio_direction_output(nvec->gpio, 1); - gpio_set_value(nvec->gpio, 1); + nvec->base = base; + nvec->irq = res->start; + nvec->i2c_clk = i2c_clk; + nvec->rx = &nvec->msg_pool[0]; ATOMIC_INIT_NOTIFIER_HEAD(&nvec->notifier_list); init_completion(&nvec->sync_write); - sema_init(&nvec->sync_write_mutex, 1); - INIT_LIST_HEAD(&nvec->tx_data); + init_completion(&nvec->ec_transfer); + mutex_init(&nvec->sync_write_mutex); + spin_lock_init(&nvec->tx_lock); + spin_lock_init(&nvec->rx_lock); INIT_LIST_HEAD(&nvec->rx_data); + INIT_LIST_HEAD(&nvec->tx_data); INIT_WORK(&nvec->rx_work, nvec_dispatch); INIT_WORK(&nvec->tx_work, nvec_request_master); - /* enable event reporting */ - nvec_write_async(nvec, EC_ENABLE_EVENT_REPORTING, - sizeof(EC_ENABLE_EVENT_REPORTING)); - - nvec_kbd_init(nvec); -#ifdef CONFIG_SERIO_NVEC_PS2 - nvec_ps2(nvec); -#endif + err = devm_gpio_request_one(&pdev->dev, nvec->gpio, GPIOF_OUT_INIT_HIGH, + "nvec gpio"); + if (err < 0) { + dev_err(nvec->dev, "couldn't request gpio\n"); + return -ENODEV; + } - /* setup subdevs */ - for (i = 0; i < pdata->num_subdevs; i++) { - ret = nvec_add_subdev(nvec, &pdata->subdevs[i]); + err = devm_request_irq(&pdev->dev, nvec->irq, nvec_interrupt, 0, + "nvec", nvec); + if (err) { + dev_err(nvec->dev, "couldn't request irq\n"); + return -ENODEV; } + disable_irq(nvec->irq); + + tegra_init_i2c_slave(nvec); + + /* enable event reporting */ + nvec_toggle_global_events(nvec, true); nvec->nvec_status_notifier.notifier_call = nvec_status_notifier; nvec_register_notifier(nvec, &nvec->nvec_status_notifier, 0); @@ -387,81 +885,106 @@ static int __devinit tegra_nvec_probe(struct platform_device *pdev) pm_power_off = nvec_power_off; /* Get Firmware Version */ - msg = nvec_write_sync(nvec, EC_GET_FIRMWARE_VERSION, - sizeof(EC_GET_FIRMWARE_VERSION)); + msg = nvec_write_sync(nvec, get_firmware_version, 2); - dev_warn(nvec->dev, "ec firmware version %02x.%02x.%02x / %02x\n", + if (msg) { + dev_warn(nvec->dev, "ec firmware version %02x.%02x.%02x / %02x\n", msg->data[4], msg->data[5], msg->data[6], msg->data[7]); - kfree(msg->data); - kfree(msg); + nvec_msg_free(nvec, msg); + } + + ret = mfd_add_devices(nvec->dev, -1, nvec_devices, + ARRAY_SIZE(nvec_devices), base, 0, NULL); + if (ret) + dev_err(nvec->dev, "error adding subdevices\n"); /* unmute speakers? */ - nvec_write_async(nvec, "\x0d\x10\x59\x94", 4); + nvec_write_async(nvec, unmute_speakers, 4); /* enable lid switch event */ - nvec_write_async(nvec, "\x01\x01\x01\x00\x00\x02\x00", 7); + nvec_event_mask(enable_event, LID_SWITCH); + nvec_write_async(nvec, enable_event, 7); /* enable power button event */ - nvec_write_async(nvec, "\x01\x01\x01\x00\x00\x80\x00", 7); + nvec_event_mask(enable_event, PWR_BUTTON); + nvec_write_async(nvec, enable_event, 7); return 0; - -failed: - kfree(nvec); - return -ENOMEM; } -static int __devexit tegra_nvec_remove(struct platform_device *pdev) +static int tegra_nvec_remove(struct platform_device *pdev) { - // TODO: unregister + struct nvec_chip *nvec = platform_get_drvdata(pdev); + + nvec_toggle_global_events(nvec, false); + mfd_remove_devices(nvec->dev); + nvec_unregister_notifier(nvec, &nvec->nvec_status_notifier); + cancel_work_sync(&nvec->rx_work); + cancel_work_sync(&nvec->tx_work); + /* FIXME: needs check wether nvec is responsible for power off */ + pm_power_off = NULL; + return 0; } -#ifdef CONFIG_PM - -static int tegra_nvec_suspend(struct platform_device *pdev, pm_message_t state) +#ifdef CONFIG_PM_SLEEP +static int nvec_suspend(struct device *dev) { + struct platform_device *pdev = to_platform_device(dev); struct nvec_chip *nvec = platform_get_drvdata(pdev); + struct nvec_msg *msg; + char ap_suspend[] = { NVEC_SLEEP, AP_SUSPEND }; dev_dbg(nvec->dev, "suspending\n"); - nvec_write_async(nvec, EC_DISABLE_EVENT_REPORTING, 3); - nvec_write_async(nvec, "\x04\x02", 2); + + /* keep these sync or you'll break suspend */ + nvec_toggle_global_events(nvec, false); + + msg = nvec_write_sync(nvec, ap_suspend, sizeof(ap_suspend)); + nvec_msg_free(nvec, msg); + + nvec_disable_i2c_slave(nvec); return 0; } -static int tegra_nvec_resume(struct platform_device *pdev) { - +static int nvec_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); struct nvec_chip *nvec = platform_get_drvdata(pdev); dev_dbg(nvec->dev, "resuming\n"); - nvec_write_async(nvec, EC_ENABLE_EVENT_REPORTING, 3); + tegra_init_i2c_slave(nvec); + nvec_toggle_global_events(nvec, true); return 0; } - -#else -#define tegra_nvec_suspend NULL -#define tegra_nvec_resume NULL #endif -static struct platform_driver nvec_device_driver = -{ - .probe = tegra_nvec_probe, - .remove = __devexit_p(tegra_nvec_remove), - .suspend = tegra_nvec_suspend, - .resume = tegra_nvec_resume, - .driver = { +static const SIMPLE_DEV_PM_OPS(nvec_pm_ops, nvec_suspend, nvec_resume); + +/* Match table for of_platform binding */ +static const struct of_device_id nvidia_nvec_of_match[] = { + { .compatible = "nvidia,nvec", }, + {}, +}; +MODULE_DEVICE_TABLE(of, nvidia_nvec_of_match); + +static struct platform_driver nvec_device_driver = { + .probe = tegra_nvec_probe, + .remove = tegra_nvec_remove, + .driver = { .name = "nvec", .owner = THIS_MODULE, + .pm = &nvec_pm_ops, + .of_match_table = nvidia_nvec_of_match, } }; -static int __init tegra_nvec_init(void) -{ - return platform_driver_register(&nvec_device_driver); -} +module_platform_driver(nvec_device_driver); -module_init(tegra_nvec_init); MODULE_ALIAS("platform:nvec"); +MODULE_DESCRIPTION("NVIDIA compliant embedded controller interface"); +MODULE_AUTHOR("Marc Dietrich <marvin24@gmx.de>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/nvec/nvec.h b/drivers/staging/nvec/nvec.h index a2d82dce62d..e271375053f 100644 --- a/drivers/staging/nvec/nvec.h +++ b/drivers/staging/nvec/nvec.h @@ -1,110 +1,183 @@ +/* + * NVEC: NVIDIA compliant embedded controller interface + * + * Copyright (C) 2011 The AC100 Kernel Team <ac100@lists.launchpad.net> + * + * Authors: Pierre-Hugues Husson <phhusson@free.fr> + * Ilya Petrov <ilya.muromec@gmail.com> + * Marc Dietrich <marvin24@gmx.de> + * Julian Andres Klode <jak@jak-linux.org> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + */ + #ifndef __LINUX_MFD_NVEC #define __LINUX_MFD_NVEC -#include <linux/semaphore.h> - -typedef enum { +#include <linux/atomic.h> +#include <linux/clk.h> +#include <linux/completion.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/notifier.h> +#include <linux/reset.h> +#include <linux/spinlock.h> +#include <linux/workqueue.h> + +/* NVEC_POOL_SIZE - Size of the pool in &struct nvec_msg */ +#define NVEC_POOL_SIZE 64 + +/* + * NVEC_MSG_SIZE - Maximum size of the data field of &struct nvec_msg. + * + * A message must store up to a SMBus block operation which consists of + * one command byte, one count byte, and up to 32 payload bytes = 34 + * byte. + */ +#define NVEC_MSG_SIZE 34 + +/** + * enum nvec_event_size - The size of an event message + * @NVEC_2BYTES: The message has one command byte and one data byte + * @NVEC_3BYTES: The message has one command byte and two data bytes + * @NVEC_VAR_SIZE: The message has one command byte, one count byte, and has + * up to as many bytes as the number in the count byte. The + * maximum is 32 + * + * Events can be fixed or variable sized. This is useless on other message + * types, which are always variable sized. + */ +enum nvec_event_size { NVEC_2BYTES, NVEC_3BYTES, - NVEC_VAR_SIZE -} nvec_size; - -typedef enum { - NOT_REALLY, - YES, - NOT_AT_ALL, -} how_care; + NVEC_VAR_SIZE, +}; -typedef enum { - NVEC_SYS=1, +/** + * enum nvec_msg_type - The type of a message + * @NVEC_SYS: A system request/response + * @NVEC_BAT: A battery request/response + * @NVEC_KBD: A keyboard request/response + * @NVEC_PS2: A mouse request/response + * @NVEC_CNTL: A EC control request/response + * @NVEC_KB_EVT: An event from the keyboard + * @NVEC_PS2_EVT: An event from the mouse + * + * Events can be fixed or variable sized. This is useless on other message + * types, which are always variable sized. + */ +enum nvec_msg_type { + NVEC_SYS = 1, NVEC_BAT, - NVEC_KBD = 5, + NVEC_GPIO, + NVEC_SLEEP, + NVEC_KBD, NVEC_PS2, NVEC_CNTL, + NVEC_OEM0 = 0x0d, NVEC_KB_EVT = 0x80, - NVEC_PS2_EVT -} nvec_event; - -typedef enum { - NVEC_WAIT, - NVEC_READ, - NVEC_WRITE -} nvec_state; + NVEC_PS2_EVT, +}; +/** + * struct nvec_msg - A buffer for a single message + * @node: Messages are part of various lists in a &struct nvec_chip + * @data: The data of the message + * @size: For TX messages, the number of bytes used in @data + * @pos: For RX messages, the current position to write to. For TX messages, + * the position to read from. + * @used: Used for the message pool to mark a message as free/allocated. + * + * This structure is used to hold outgoing and incoming messages. Outgoing + * messages have a different format than incoming messages, and that is not + * documented yet. + */ struct nvec_msg { - unsigned char *data; + struct list_head node; + unsigned char data[NVEC_MSG_SIZE]; unsigned short size; unsigned short pos; - struct list_head node; -}; - -struct nvec_subdev { - const char *name; - void *platform_data; - int id; -}; - -struct nvec_platform_data { - int num_subdevs; - int i2c_addr; - int gpio; - int irq; - int base; - int size; - char clock[16]; - struct nvec_subdev *subdevs; + atomic_t used; }; +/** + * struct nvec_chip - A single connection to an NVIDIA Embedded controller + * @dev: The device + * @gpio: The same as for &struct nvec_platform_data + * @irq: The IRQ of the I2C device + * @i2c_addr: The address of the I2C slave + * @base: The base of the memory mapped region of the I2C device + * @i2c_clk: The clock of the I2C device + * @rst: The reset of the I2C device + * @notifier_list: Notifiers to be called on received messages, see + * nvec_register_notifier() + * @rx_data: Received messages that have to be processed + * @tx_data: Messages waiting to be sent to the controller + * @nvec_status_notifier: Internal notifier (see nvec_status_notifier()) + * @rx_work: A work structure for the RX worker nvec_dispatch() + * @tx_work: A work structure for the TX worker nvec_request_master() + * @wq: The work queue in which @rx_work and @tx_work are executed + * @rx: The message currently being retrieved or %NULL + * @msg_pool: A pool of messages for allocation + * @tx: The message currently being transferred + * @tx_scratch: Used for building pseudo messages + * @ec_transfer: A completion that will be completed once a message has been + * received (see nvec_rx_completed()) + * @tx_lock: Spinlock for modifications on @tx_data + * @rx_lock: Spinlock for modifications on @rx_data + * @sync_write_mutex: A mutex for nvec_write_sync() + * @sync_write: A completion to signal that a synchronous message is complete + * @sync_write_pending: The first two bytes of the request (type and subtype) + * @last_sync_msg: The last synchronous message. + * @state: State of our finite state machine used in nvec_interrupt() + */ struct nvec_chip { struct device *dev; int gpio; int irq; - unsigned char *i2c_regs; - nvec_state state; + int i2c_addr; + void __iomem *base; + struct clk *i2c_clk; + struct reset_control *rst; struct atomic_notifier_head notifier_list; struct list_head rx_data, tx_data; struct notifier_block nvec_status_notifier; struct work_struct rx_work, tx_work; - struct nvec_msg *rx, *tx; + struct workqueue_struct *wq; + struct nvec_msg msg_pool[NVEC_POOL_SIZE]; + struct nvec_msg *rx; + + struct nvec_msg *tx; + struct nvec_msg tx_scratch; + struct completion ec_transfer; -/* sync write stuff */ - struct semaphore sync_write_mutex; + spinlock_t tx_lock, rx_lock; + + /* sync write stuff */ + struct mutex sync_write_mutex; struct completion sync_write; u16 sync_write_pending; struct nvec_msg *last_sync_msg; + + int state; }; -extern void nvec_write_async(struct nvec_chip *nvec, unsigned char *data, short size); +extern int nvec_write_async(struct nvec_chip *nvec, const unsigned char *data, + short size); + +extern struct nvec_msg *nvec_write_sync(struct nvec_chip *nvec, + const unsigned char *data, short size); extern int nvec_register_notifier(struct nvec_chip *nvec, - struct notifier_block *nb, unsigned int events); - -extern int nvec_unregister_notifier(struct device *dev, - struct notifier_block *nb, unsigned int events); - -const char *nvec_send_msg(unsigned char *src, unsigned char *dst_size, how_care care_resp, void (*rt_handler)(unsigned char *data)); - -extern int nvec_ps2(struct nvec_chip *nvec); -extern int nvec_kbd_init(struct nvec_chip *nvec); - -#define I2C_CNFG 0x00 -#define I2C_CNFG_PACKET_MODE_EN (1<<10) -#define I2C_CNFG_NEW_MASTER_SFM (1<<11) -#define I2C_CNFG_DEBOUNCE_CNT_SHIFT 12 - -#define I2C_SL_CNFG 0x20 -#define I2C_SL_NEWL (1<<2) -#define I2C_SL_NACK (1<<1) -#define I2C_SL_RESP (1<<0) -#define I2C_SL_IRQ (1<<3) -#define END_TRANS (1<<4) -#define RCVD (1<<2) -#define RNW (1<<1) - -#define I2C_SL_RCVD 0x24 -#define I2C_SL_STATUS 0x28 -#define I2C_SL_ADDR1 0x2c -#define I2C_SL_ADDR2 0x30 -#define I2C_SL_DELAY_COUNT 0x3c + struct notifier_block *nb, + unsigned int events); + +extern int nvec_unregister_notifier(struct nvec_chip *dev, + struct notifier_block *nb); + +extern void nvec_msg_free(struct nvec_chip *nvec, struct nvec_msg *msg); #endif diff --git a/drivers/staging/nvec/nvec_kbd.c b/drivers/staging/nvec/nvec_kbd.c index 9a9850725b5..c17a1c3eb3c 100644 --- a/drivers/staging/nvec/nvec_kbd.c +++ b/drivers/staging/nvec/nvec_kbd.c @@ -1,42 +1,83 @@ +/* + * nvec_kbd: keyboard driver for a NVIDIA compliant embedded controller + * + * Copyright (C) 2011 The AC100 Kernel Team <ac100@lists.launchpad.net> + * + * Authors: Pierre-Hugues Husson <phhusson@free.fr> + * Marc Dietrich <marvin24@gmx.de> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + */ + +#include <linux/module.h> #include <linux/slab.h> #include <linux/input.h> #include <linux/delay.h> +#include <linux/platform_device.h> + #include "nvec-keytable.h" #include "nvec.h" -#define ACK_KBD_EVENT {'\x05','\xed','\x01'} +enum kbd_subcmds { + CNFG_WAKE = 3, + CNFG_WAKE_KEY_REPORTING, + SET_LEDS = 0xed, + ENABLE_KBD = 0xf4, + DISABLE_KBD, +}; static unsigned char keycodes[ARRAY_SIZE(code_tab_102us) - + ARRAY_SIZE(extcode_tab_us102)]; + + ARRAY_SIZE(extcode_tab_us102)]; struct nvec_keys { struct input_dev *input; struct notifier_block notifier; struct nvec_chip *nvec; + bool caps_lock; }; static struct nvec_keys keys_dev; +static void nvec_kbd_toggle_led(void) +{ + char buf[] = { NVEC_KBD, SET_LEDS, 0 }; + + keys_dev.caps_lock = !keys_dev.caps_lock; + + if (keys_dev.caps_lock) + /* should be BIT(0) only, firmware bug? */ + buf[2] = BIT(0) | BIT(1) | BIT(2); + + nvec_write_async(keys_dev.nvec, buf, sizeof(buf)); +} + static int nvec_keys_notifier(struct notifier_block *nb, - unsigned long event_type, void *data) + unsigned long event_type, void *data) { int code, state; unsigned char *msg = (unsigned char *)data; if (event_type == NVEC_KB_EVT) { - nvec_size _size = (msg[0] & (3 << 5)) >> 5; + int _size = (msg[0] & (3 << 5)) >> 5; /* power on/off button */ - if(_size == NVEC_VAR_SIZE) + if (_size == NVEC_VAR_SIZE) return NOTIFY_STOP; - if(_size == NVEC_3BYTES) + if (_size == NVEC_3BYTES) msg++; code = msg[1] & 0x7f; state = msg[1] & 0x80; - input_report_key(keys_dev.input, code_tabs[_size][code], !state); + if (code_tabs[_size][code] == KEY_CAPSLOCK && state) + nvec_kbd_toggle_led(); + + input_report_key(keys_dev.input, code_tabs[_size][code], + !state); input_sync(keys_dev.input); return NOTIFY_STOP; @@ -46,18 +87,18 @@ static int nvec_keys_notifier(struct notifier_block *nb, } static int nvec_kbd_event(struct input_dev *dev, unsigned int type, - unsigned int code, int value) + unsigned int code, int value) { - unsigned char buf[] = ACK_KBD_EVENT; struct nvec_chip *nvec = keys_dev.nvec; + char buf[] = { NVEC_KBD, SET_LEDS, 0 }; - if(type==EV_REP) + if (type == EV_REP) return 0; - if(type!=EV_LED) + if (type != EV_LED) return -1; - if(code!=LED_CAPSL) + if (code != LED_CAPSL) return -1; buf[2] = !!value; @@ -66,22 +107,28 @@ static int nvec_kbd_event(struct input_dev *dev, unsigned int type, return 0; } -int __init nvec_kbd_init(struct nvec_chip *nvec) +static int nvec_kbd_probe(struct platform_device *pdev) { + struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent); int i, j, err; struct input_dev *idev; + char clear_leds[] = { NVEC_KBD, SET_LEDS, 0 }, + enable_kbd[] = { NVEC_KBD, ENABLE_KBD }, + cnfg_wake[] = { NVEC_KBD, CNFG_WAKE, true, true }, + cnfg_wake_key_reporting[] = { NVEC_KBD, CNFG_WAKE_KEY_REPORTING, + true }; j = 0; - for(i = 0; i < ARRAY_SIZE(code_tab_102us); ++i) + for (i = 0; i < ARRAY_SIZE(code_tab_102us); ++i) keycodes[j++] = code_tab_102us[i]; - for(i = 0; i < ARRAY_SIZE(extcode_tab_us102); ++i) - keycodes[j++]=extcode_tab_us102[i]; + for (i = 0; i < ARRAY_SIZE(extcode_tab_us102); ++i) + keycodes[j++] = extcode_tab_us102[i]; - idev = input_allocate_device(); - idev->name = "Tegra nvec keyboard"; - idev->phys = "i2c3_slave/nvec"; + idev = devm_input_allocate_device(&pdev->dev); + idev->name = "nvec keyboard"; + idev->phys = "nvec"; idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP) | BIT_MASK(EV_LED); idev->ledbit[0] = BIT_MASK(LED_CAPSL); idev->event = nvec_kbd_event; @@ -89,13 +136,13 @@ int __init nvec_kbd_init(struct nvec_chip *nvec) idev->keycodesize = sizeof(unsigned char); idev->keycodemax = ARRAY_SIZE(keycodes); - for( i = 0; i < ARRAY_SIZE(keycodes); ++i) + for (i = 0; i < ARRAY_SIZE(keycodes); ++i) set_bit(keycodes[i], idev->keybit); clear_bit(0, idev->keybit); err = input_register_device(idev); - if(err) - goto fail; + if (err) + return err; keys_dev.input = idev; keys_dev.notifier.notifier_call = nvec_keys_notifier; @@ -103,20 +150,44 @@ int __init nvec_kbd_init(struct nvec_chip *nvec) nvec_register_notifier(nvec, &keys_dev.notifier, 0); /* Enable keyboard */ - nvec_write_async(nvec, "\x05\xf4", 2); + nvec_write_async(nvec, enable_kbd, 2); - /* keyboard reset? */ - nvec_write_async(nvec, "\x05\x03\x01\x01", 4); - nvec_write_async(nvec, "\x05\x04\x01", 3); - nvec_write_async(nvec, "\x06\x01\xff\x03", 4); -/* FIXME - wait until keyboard reset is finished - or until we have a sync write */ - mdelay(1000); + /* configures wake on special keys */ + nvec_write_async(nvec, cnfg_wake, 4); + /* enable wake key reporting */ + nvec_write_async(nvec, cnfg_wake_key_reporting, 3); + + /* Disable caps lock LED */ + nvec_write_async(nvec, clear_leds, sizeof(clear_leds)); return 0; +} -fail: - input_free_device(idev); - return err; +static int nvec_kbd_remove(struct platform_device *pdev) +{ + struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent); + char disable_kbd[] = { NVEC_KBD, DISABLE_KBD }, + uncnfg_wake_key_reporting[] = { NVEC_KBD, CNFG_WAKE_KEY_REPORTING, + false }; + nvec_write_async(nvec, uncnfg_wake_key_reporting, 3); + nvec_write_async(nvec, disable_kbd, 2); + nvec_unregister_notifier(nvec, &keys_dev.notifier); + + return 0; } + +static struct platform_driver nvec_kbd_driver = { + .probe = nvec_kbd_probe, + .remove = nvec_kbd_remove, + .driver = { + .name = "nvec-kbd", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(nvec_kbd_driver); + +MODULE_AUTHOR("Marc Dietrich <marvin24@gmx.de>"); +MODULE_DESCRIPTION("NVEC keyboard driver"); +MODULE_ALIAS("platform:nvec-kbd"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/nvec/nvec_paz00.c b/drivers/staging/nvec/nvec_paz00.c new file mode 100644 index 00000000000..934b796222a --- /dev/null +++ b/drivers/staging/nvec/nvec_paz00.c @@ -0,0 +1,98 @@ +/* + * nvec_paz00: OEM specific driver for Compal PAZ00 based devices + * + * Copyright (C) 2011 The AC100 Kernel Team <ac100@lists.launchpad.net> + * + * Authors: Ilya Petrov <ilya.muromec@gmail.com> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + */ + +#include <linux/module.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/leds.h> +#include <linux/platform_device.h> +#include "nvec.h" + +#define to_nvec_led(led_cdev) \ + container_of(led_cdev, struct nvec_led, cdev) + +#define NVEC_LED_REQ {'\x0d', '\x10', '\x45', '\x10', '\x00'} + +#define NVEC_LED_MAX 8 + +struct nvec_led { + struct led_classdev cdev; + struct nvec_chip *nvec; +}; + +static void nvec_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct nvec_led *led = to_nvec_led(led_cdev); + unsigned char buf[] = NVEC_LED_REQ; + buf[4] = value; + + nvec_write_async(led->nvec, buf, sizeof(buf)); + + led->cdev.brightness = value; + +} + +static int nvec_paz00_probe(struct platform_device *pdev) +{ + struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent); + struct nvec_led *led; + int ret = 0; + + led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL); + if (led == NULL) + return -ENOMEM; + + led->cdev.max_brightness = NVEC_LED_MAX; + + led->cdev.brightness_set = nvec_led_brightness_set; + led->cdev.name = "paz00-led"; + led->cdev.flags |= LED_CORE_SUSPENDRESUME; + led->nvec = nvec; + + platform_set_drvdata(pdev, led); + + ret = led_classdev_register(&pdev->dev, &led->cdev); + if (ret < 0) + return ret; + + /* to expose the default value to userspace */ + led->cdev.brightness = 0; + + return 0; +} + +static int nvec_paz00_remove(struct platform_device *pdev) +{ + struct nvec_led *led = platform_get_drvdata(pdev); + + led_classdev_unregister(&led->cdev); + + return 0; +} + +static struct platform_driver nvec_paz00_driver = { + .probe = nvec_paz00_probe, + .remove = nvec_paz00_remove, + .driver = { + .name = "nvec-paz00", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(nvec_paz00_driver); + +MODULE_AUTHOR("Ilya Petrov <ilya.muromec@gmail.com>"); +MODULE_DESCRIPTION("Tegra NVEC PAZ00 driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:nvec-paz00"); diff --git a/drivers/staging/nvec/nvec_power.c b/drivers/staging/nvec/nvec_power.c index df164add837..aacfcd6954a 100644 --- a/drivers/staging/nvec/nvec_power.c +++ b/drivers/staging/nvec/nvec_power.c @@ -1,3 +1,17 @@ +/* + * nvec_power: power supply driver for a NVIDIA compliant embedded controller + * + * Copyright (C) 2011 The AC100 Kernel Team <ac100@lists.launchpad.net> + * + * Authors: Ilya Petrov <ilya.muromec@gmail.com> + * Marc Dietrich <marvin24@gmx.de> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + */ + #include <linux/module.h> #include <linux/platform_device.h> #include <linux/err.h> @@ -5,10 +19,12 @@ #include <linux/slab.h> #include <linux/workqueue.h> #include <linux/delay.h> + #include "nvec.h" -struct nvec_power -{ +#define GET_SYSTEM_STATUS 0x00 + +struct nvec_power { struct notifier_block notifier; struct delayed_work poller; struct nvec_chip *nvec; @@ -58,7 +74,8 @@ struct bat_response { u8 length; u8 sub_type; u8 status; - union { /* payload */ + /* payload */ + union { char plc[30]; u16 plu; s16 pls; @@ -69,18 +86,17 @@ static struct power_supply nvec_bat_psy; static struct power_supply nvec_psy; static int nvec_power_notifier(struct notifier_block *nb, - unsigned long event_type, void *data) + unsigned long event_type, void *data) { - struct nvec_power *power = container_of(nb, struct nvec_power, notifier); + struct nvec_power *power = + container_of(nb, struct nvec_power, notifier); struct bat_response *res = (struct bat_response *)data; if (event_type != NVEC_SYS) return NOTIFY_DONE; - if(res->sub_type == 0) - { - if (power->on != res->plu) - { + if (res->sub_type == 0) { + if (power->on != res->plu) { power->on = res->plu; power_supply_changed(&nvec_psy); } @@ -89,8 +105,7 @@ static int nvec_power_notifier(struct notifier_block *nb, return NOTIFY_OK; } -static const int bat_init[] = -{ +static const int bat_init[] = { LAST_FULL_CHARGE_CAPACITY, DESIGN_CAPACITY, CRITICAL_CAPACITY, MANUFACTURER, MODEL, TYPE, }; @@ -98,118 +113,117 @@ static const int bat_init[] = static void get_bat_mfg_data(struct nvec_power *power) { int i; - char buf[] = { '\x02', '\x00' }; + char buf[] = { NVEC_BAT, SLOT_STATUS }; - for (i = 0; i < ARRAY_SIZE(bat_init); i++) - { + for (i = 0; i < ARRAY_SIZE(bat_init); i++) { buf[1] = bat_init[i]; nvec_write_async(power->nvec, buf, 2); } } static int nvec_power_bat_notifier(struct notifier_block *nb, - unsigned long event_type, void *data) + unsigned long event_type, void *data) { - struct nvec_power *power = container_of(nb, struct nvec_power, notifier); + struct nvec_power *power = + container_of(nb, struct nvec_power, notifier); struct bat_response *res = (struct bat_response *)data; int status_changed = 0; if (event_type != NVEC_BAT) return NOTIFY_DONE; - switch(res->sub_type) - { - case SLOT_STATUS: - if (res->plc[0] & 1) - { - if (power->bat_present == 0) - { - status_changed = 1; - get_bat_mfg_data(power); - } - - power->bat_present = 1; - - switch ((res->plc[0] >> 1) & 3) - { - case 0: - power->bat_status = POWER_SUPPLY_STATUS_NOT_CHARGING; - break; - case 1: - power->bat_status = POWER_SUPPLY_STATUS_CHARGING; - break; - case 2: - power->bat_status = POWER_SUPPLY_STATUS_DISCHARGING; - break; - default: - power->bat_status = POWER_SUPPLY_STATUS_UNKNOWN; - } - } else { - if (power->bat_present == 1) - status_changed = 1; - - power->bat_present = 0; + switch (res->sub_type) { + case SLOT_STATUS: + if (res->plc[0] & 1) { + if (power->bat_present == 0) { + status_changed = 1; + get_bat_mfg_data(power); + } + + power->bat_present = 1; + + switch ((res->plc[0] >> 1) & 3) { + case 0: + power->bat_status = + POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case 1: + power->bat_status = + POWER_SUPPLY_STATUS_CHARGING; + break; + case 2: + power->bat_status = + POWER_SUPPLY_STATUS_DISCHARGING; + break; + default: power->bat_status = POWER_SUPPLY_STATUS_UNKNOWN; } - power->bat_cap = res->plc[1]; - if (status_changed) - power_supply_changed(&nvec_bat_psy); - break; - case VOLTAGE: - power->bat_voltage_now = res->plu * 1000; - break; - case TIME_REMAINING: - power->time_remain = res->plu * 3600; - break; - case CURRENT: - power->bat_current_now = res->pls * 1000; - break; - case AVERAGE_CURRENT: - power->bat_current_avg = res->pls * 1000; - break; - case CAPACITY_REMAINING: - power->capacity_remain = res->plu * 1000; - break; - case LAST_FULL_CHARGE_CAPACITY: - power->charge_last_full = res->plu * 1000; - break; - case DESIGN_CAPACITY: - power->charge_full_design = res->plu * 1000; - break; - case CRITICAL_CAPACITY: - power->critical_capacity = res->plu * 1000; - break; - case TEMPERATURE: - power->bat_temperature = res->plu - 2732; - break; - case MANUFACTURER: - memcpy(power->bat_manu, &res->plc, res->length-2); - power->bat_model[res->length-2] = '\0'; - break; - case MODEL: - memcpy(power->bat_model, &res->plc, res->length-2); - power->bat_model[res->length-2] = '\0'; - break; - case TYPE: - memcpy(power->bat_type, &res->plc, res->length-2); - power->bat_type[res->length-2] = '\0'; - /* this differs a little from the spec - fill in more if you find some */ - if (!strncmp(power->bat_type, "Li", 30)) - power->bat_type_enum = POWER_SUPPLY_TECHNOLOGY_LION; - else - power->bat_type_enum = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; - break; - default: - return NOTIFY_STOP; + } else { + if (power->bat_present == 1) + status_changed = 1; + + power->bat_present = 0; + power->bat_status = POWER_SUPPLY_STATUS_UNKNOWN; + } + power->bat_cap = res->plc[1]; + if (status_changed) + power_supply_changed(&nvec_bat_psy); + break; + case VOLTAGE: + power->bat_voltage_now = res->plu * 1000; + break; + case TIME_REMAINING: + power->time_remain = res->plu * 3600; + break; + case CURRENT: + power->bat_current_now = res->pls * 1000; + break; + case AVERAGE_CURRENT: + power->bat_current_avg = res->pls * 1000; + break; + case CAPACITY_REMAINING: + power->capacity_remain = res->plu * 1000; + break; + case LAST_FULL_CHARGE_CAPACITY: + power->charge_last_full = res->plu * 1000; + break; + case DESIGN_CAPACITY: + power->charge_full_design = res->plu * 1000; + break; + case CRITICAL_CAPACITY: + power->critical_capacity = res->plu * 1000; + break; + case TEMPERATURE: + power->bat_temperature = res->plu - 2732; + break; + case MANUFACTURER: + memcpy(power->bat_manu, &res->plc, res->length - 2); + power->bat_model[res->length - 2] = '\0'; + break; + case MODEL: + memcpy(power->bat_model, &res->plc, res->length - 2); + power->bat_model[res->length - 2] = '\0'; + break; + case TYPE: + memcpy(power->bat_type, &res->plc, res->length - 2); + power->bat_type[res->length - 2] = '\0'; + /* this differs a little from the spec + fill in more if you find some */ + if (!strncmp(power->bat_type, "Li", 30)) + power->bat_type_enum = POWER_SUPPLY_TECHNOLOGY_LION; + else + power->bat_type_enum = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; + break; + default: + return NOTIFY_STOP; } return NOTIFY_STOP; } static int nvec_power_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) + enum power_supply_property psp, + union power_supply_propval *val) { struct nvec_power *power = dev_get_drvdata(psy->dev->parent); switch (psp) { @@ -223,61 +237,60 @@ static int nvec_power_get_property(struct power_supply *psy, } static int nvec_battery_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) + enum power_supply_property psp, + union power_supply_propval *val) { struct nvec_power *power = dev_get_drvdata(psy->dev->parent); - switch(psp) - { - case POWER_SUPPLY_PROP_STATUS: - val->intval = power->bat_status; - break; - case POWER_SUPPLY_PROP_CAPACITY: - val->intval = power->bat_cap; - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval = power->bat_present; - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - val->intval = power->bat_voltage_now; - break; - case POWER_SUPPLY_PROP_CURRENT_NOW: - val->intval = power->bat_current_now; - break; - case POWER_SUPPLY_PROP_CURRENT_AVG: - val->intval = power->bat_current_avg; - break; - case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: - val->intval = power->time_remain; - break; - case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: - val->intval = power->charge_full_design; - break; - case POWER_SUPPLY_PROP_CHARGE_FULL: - val->intval = power->charge_last_full; - break; - case POWER_SUPPLY_PROP_CHARGE_EMPTY: - val->intval = power->critical_capacity; - break; - case POWER_SUPPLY_PROP_CHARGE_NOW: - val->intval = power->capacity_remain; - break; - case POWER_SUPPLY_PROP_TEMP: - val->intval = power->bat_temperature; - break; - case POWER_SUPPLY_PROP_MANUFACTURER: - val->strval = power->bat_manu; - break; - case POWER_SUPPLY_PROP_MODEL_NAME: - val->strval = power->bat_model; - break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = power->bat_type_enum; - break; - default: - return -EINVAL; - } + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = power->bat_status; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = power->bat_cap; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = power->bat_present; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = power->bat_voltage_now; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = power->bat_current_now; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = power->bat_current_avg; + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: + val->intval = power->time_remain; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = power->charge_full_design; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = power->charge_last_full; + break; + case POWER_SUPPLY_PROP_CHARGE_EMPTY: + val->intval = power->critical_capacity; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = power->capacity_remain; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = power->bat_temperature; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = power->bat_manu; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = power->bat_model; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = power->bat_type_enum; + break; + default: + return -EINVAL; + } return 0; } @@ -310,11 +323,11 @@ static char *nvec_power_supplied_to[] = { }; static struct power_supply nvec_bat_psy = { - .name = "battery", - .type = POWER_SUPPLY_TYPE_BATTERY, - .properties = nvec_battery_props, - .num_properties = ARRAY_SIZE(nvec_battery_props), - .get_property = nvec_battery_get_property, + .name = "battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = nvec_battery_props, + .num_properties = ARRAY_SIZE(nvec_battery_props), + .get_property = nvec_battery_get_property, }; static struct power_supply nvec_psy = { @@ -327,9 +340,8 @@ static struct power_supply nvec_psy = { .get_property = nvec_power_get_property, }; -static int counter = 0; -static int const bat_iter[] = -{ +static int counter; +static int const bat_iter[] = { SLOT_STATUS, VOLTAGE, CURRENT, CAPACITY_REMAINING, #ifdef EC_FULL_DIAG AVERAGE_CURRENT, TEMPERATURE, TIME_REMAINING, @@ -338,9 +350,9 @@ static int const bat_iter[] = static void nvec_power_poll(struct work_struct *work) { - char buf[] = { '\x01', '\x00' }; + char buf[] = { NVEC_SYS, GET_SYSTEM_STATUS }; struct nvec_power *power = container_of(work, struct nvec_power, - poller.work); + poller.work); if (counter >= ARRAY_SIZE(bat_iter)) counter = 0; @@ -351,21 +363,23 @@ static void nvec_power_poll(struct work_struct *work) /* select a battery request function via round robin doing it all at once seems to overload the power supply */ - buf[0] = '\x02'; /* battery */ - buf[1] = bat_iter[counter++]; + buf[0] = NVEC_BAT; + buf[1] = bat_iter[counter++]; nvec_write_async(power->nvec, buf, 2); -// printk("%02x %02x\n", buf[0], buf[1]); - schedule_delayed_work(to_delayed_work(work), msecs_to_jiffies(5000)); }; -static int __devinit nvec_power_probe(struct platform_device *pdev) +static int nvec_power_probe(struct platform_device *pdev) { struct power_supply *psy; - struct nvec_power *power = kzalloc(sizeof(struct nvec_power), GFP_NOWAIT); + struct nvec_power *power; struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent); + power = devm_kzalloc(&pdev->dev, sizeof(struct nvec_power), GFP_NOWAIT); + if (power == NULL) + return -ENOMEM; + dev_set_drvdata(&pdev->dev, power); power->nvec = nvec; @@ -381,10 +395,9 @@ static int __devinit nvec_power_probe(struct platform_device *pdev) case BAT: psy = &nvec_bat_psy; - power->notifier.notifier_call = nvec_power_bat_notifier; + power->notifier.notifier_call = nvec_power_bat_notifier; break; default: - kfree(power); return -ENODEV; } @@ -396,21 +409,33 @@ static int __devinit nvec_power_probe(struct platform_device *pdev) return power_supply_register(&pdev->dev, psy); } +static int nvec_power_remove(struct platform_device *pdev) +{ + struct nvec_power *power = platform_get_drvdata(pdev); + + cancel_delayed_work_sync(&power->poller); + nvec_unregister_notifier(power->nvec, &power->notifier); + switch (pdev->id) { + case AC: + power_supply_unregister(&nvec_psy); + break; + case BAT: + power_supply_unregister(&nvec_bat_psy); + } + + return 0; +} + static struct platform_driver nvec_power_driver = { .probe = nvec_power_probe, -// .remove = __devexit_p(nvec_power_remove), + .remove = nvec_power_remove, .driver = { - .name = "nvec-power", - .owner = THIS_MODULE, - } + .name = "nvec-power", + .owner = THIS_MODULE, + } }; -static int __init nvec_power_init(void) -{ - return platform_driver_register(&nvec_power_driver); -} - -module_init(nvec_power_init); +module_platform_driver(nvec_power_driver); MODULE_AUTHOR("Ilya Petrov <ilya.muromec@gmail.com>"); MODULE_LICENSE("GPL"); diff --git a/drivers/staging/nvec/nvec_ps2.c b/drivers/staging/nvec/nvec_ps2.c index 6bb9430f352..45b2f1308e0 100644 --- a/drivers/staging/nvec/nvec_ps2.c +++ b/drivers/staging/nvec/nvec_ps2.c @@ -1,14 +1,48 @@ +/* + * nvec_ps2: mouse driver for a NVIDIA compliant embedded controller + * + * Copyright (C) 2011 The AC100 Kernel Team <ac100@lists.launchpad.net> + * + * Authors: Pierre-Hugues Husson <phhusson@free.fr> + * Ilya Petrov <ilya.muromec@gmail.com> + * Marc Dietrich <marvin24@gmx.de> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + */ + +#include <linux/module.h> #include <linux/slab.h> #include <linux/serio.h> #include <linux/delay.h> +#include <linux/platform_device.h> + #include "nvec.h" -#define START_STREAMING {'\x06','\x03','\x01'} -#define STOP_STREAMING {'\x06','\x04'} -#define SEND_COMMAND {'\x06','\x01','\xf4','\x01'} +#define PACKET_SIZE 6 + +#define ENABLE_MOUSE 0xf4 +#define DISABLE_MOUSE 0xf5 +#define PSMOUSE_RST 0xff + +#ifdef NVEC_PS2_DEBUG +#define NVEC_PHD(str, buf, len) \ + print_hex_dump(KERN_DEBUG, str, DUMP_PREFIX_NONE, \ + 16, 1, buf, len, false) +#else +#define NVEC_PHD(str, buf, len) +#endif + +enum ps2_subcmds { + SEND_COMMAND = 1, + RECEIVE_N, + AUTO_RECEIVE_N, + CANCEL_AUTO_RECEIVE, +}; -struct nvec_ps2 -{ +struct nvec_ps2 { struct serio *ser_dev; struct notifier_block notifier; struct nvec_chip *nvec; @@ -18,76 +52,71 @@ static struct nvec_ps2 ps2_dev; static int ps2_startstreaming(struct serio *ser_dev) { - unsigned char buf[] = START_STREAMING; - nvec_write_async(ps2_dev.nvec, buf, sizeof(buf)); - return 0; + unsigned char buf[] = { NVEC_PS2, AUTO_RECEIVE_N, PACKET_SIZE }; + return nvec_write_async(ps2_dev.nvec, buf, sizeof(buf)); } static void ps2_stopstreaming(struct serio *ser_dev) { - unsigned char buf[] = STOP_STREAMING; + unsigned char buf[] = { NVEC_PS2, CANCEL_AUTO_RECEIVE }; nvec_write_async(ps2_dev.nvec, buf, sizeof(buf)); } -/* is this really needed? -static void nvec_resp_handler(unsigned char *data) { - serio_interrupt(ser_dev, data[4], 0); -} -*/ - static int ps2_sendcommand(struct serio *ser_dev, unsigned char cmd) { - unsigned char buf[] = SEND_COMMAND; + unsigned char buf[] = { NVEC_PS2, SEND_COMMAND, ENABLE_MOUSE, 1 }; buf[2] = cmd & 0xff; dev_dbg(&ser_dev->dev, "Sending ps2 cmd %02x\n", cmd); - nvec_write_async(ps2_dev.nvec, buf, sizeof(buf)); - - return 0; + return nvec_write_async(ps2_dev.nvec, buf, sizeof(buf)); } static int nvec_ps2_notifier(struct notifier_block *nb, - unsigned long event_type, void *data) + unsigned long event_type, void *data) { int i; unsigned char *msg = (unsigned char *)data; switch (event_type) { - case NVEC_PS2_EVT: - serio_interrupt(ps2_dev.ser_dev, msg[2], 0); - return NOTIFY_STOP; - - case NVEC_PS2: - if (msg[2] == 1) - for(i = 0; i < (msg[1] - 2); i++) - serio_interrupt(ps2_dev.ser_dev, msg[i+4], 0); - else if (msg[1] != 2) /* !ack */ - { - printk("nvec_ps2: unhandled mouse event "); - for(i = 0; i <= (msg[1]+1); i++) - printk("%02x ", msg[i]); - printk(".\n"); - } - - return NOTIFY_STOP; + case NVEC_PS2_EVT: + for (i = 0; i < msg[1]; i++) + serio_interrupt(ps2_dev.ser_dev, msg[2 + i], 0); + NVEC_PHD("ps/2 mouse event: ", &msg[2], msg[1]); + return NOTIFY_STOP; + + case NVEC_PS2: + if (msg[2] == 1) { + for (i = 0; i < (msg[1] - 2); i++) + serio_interrupt(ps2_dev.ser_dev, msg[i + 4], 0); + NVEC_PHD("ps/2 mouse reply: ", &msg[4], msg[1] - 2); + } + + else if (msg[1] != 2) /* !ack */ + NVEC_PHD("unhandled mouse event: ", msg, msg[1] + 2); + return NOTIFY_STOP; } return NOTIFY_DONE; } - -int __init nvec_ps2(struct nvec_chip *nvec) +static int nvec_mouse_probe(struct platform_device *pdev) { - struct serio *ser_dev = kzalloc(sizeof(struct serio), GFP_KERNEL); + struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent); + struct serio *ser_dev; + char mouse_reset[] = { NVEC_PS2, SEND_COMMAND, PSMOUSE_RST, 3 }; + + ser_dev = devm_kzalloc(&pdev->dev, sizeof(struct serio), GFP_KERNEL); + if (ser_dev == NULL) + return -ENOMEM; - ser_dev->id.type=SERIO_8042; - ser_dev->write=ps2_sendcommand; - ser_dev->open=ps2_startstreaming; - ser_dev->close=ps2_stopstreaming; + ser_dev->id.type = SERIO_PS_PSTHRU; + ser_dev->write = ps2_sendcommand; + ser_dev->start = ps2_startstreaming; + ser_dev->stop = ps2_stopstreaming; - strlcpy(ser_dev->name, "NVEC PS2", sizeof(ser_dev->name)); - strlcpy(ser_dev->phys, "NVEC I2C slave", sizeof(ser_dev->phys)); + strlcpy(ser_dev->name, "nvec mouse", sizeof(ser_dev->name)); + strlcpy(ser_dev->phys, "nvec", sizeof(ser_dev->phys)); ps2_dev.ser_dev = ser_dev; ps2_dev.notifier.notifier_call = nvec_ps2_notifier; @@ -97,7 +126,63 @@ int __init nvec_ps2(struct nvec_chip *nvec) serio_register_port(ser_dev); /* mouse reset */ - nvec_write_async(nvec, "\x06\x01\xff\x03", 4); + nvec_write_async(nvec, mouse_reset, sizeof(mouse_reset)); + + return 0; +} + +static int nvec_mouse_remove(struct platform_device *pdev) +{ + struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent); + + ps2_sendcommand(ps2_dev.ser_dev, DISABLE_MOUSE); + ps2_stopstreaming(ps2_dev.ser_dev); + nvec_unregister_notifier(nvec, &ps2_dev.notifier); + serio_unregister_port(ps2_dev.ser_dev); return 0; } + +#ifdef CONFIG_PM_SLEEP +static int nvec_mouse_suspend(struct device *dev) +{ + /* disable mouse */ + ps2_sendcommand(ps2_dev.ser_dev, DISABLE_MOUSE); + + /* send cancel autoreceive */ + ps2_stopstreaming(ps2_dev.ser_dev); + + return 0; +} + +static int nvec_mouse_resume(struct device *dev) +{ + /* start streaming */ + ps2_startstreaming(ps2_dev.ser_dev); + + /* enable mouse */ + ps2_sendcommand(ps2_dev.ser_dev, ENABLE_MOUSE); + + return 0; +} +#endif + +static const SIMPLE_DEV_PM_OPS(nvec_mouse_pm_ops, nvec_mouse_suspend, + nvec_mouse_resume); + +static struct platform_driver nvec_mouse_driver = { + .probe = nvec_mouse_probe, + .remove = nvec_mouse_remove, + .driver = { + .name = "nvec-mouse", + .owner = THIS_MODULE, + .pm = &nvec_mouse_pm_ops, + }, +}; + +module_platform_driver(nvec_mouse_driver); + +MODULE_DESCRIPTION("NVEC mouse driver"); +MODULE_AUTHOR("Marc Dietrich <marvin24@gmx.de>"); +MODULE_ALIAS("platform:nvec-mouse"); +MODULE_LICENSE("GPL"); |
