diff options
Diffstat (limited to 'drivers/staging/nvec')
-rw-r--r-- | drivers/staging/nvec/Kconfig | 33 | ||||
-rw-r--r-- | drivers/staging/nvec/Makefile | 5 | ||||
-rw-r--r-- | drivers/staging/nvec/README | 14 | ||||
-rw-r--r-- | drivers/staging/nvec/TODO | 12 | ||||
-rw-r--r-- | drivers/staging/nvec/nvec-keytable.h | 307 | ||||
-rw-r--r-- | drivers/staging/nvec/nvec.c | 942 | ||||
-rw-r--r-- | drivers/staging/nvec/nvec.h | 203 | ||||
-rw-r--r-- | drivers/staging/nvec/nvec_kbd.c | 179 | ||||
-rw-r--r-- | drivers/staging/nvec/nvec_leds.c | 114 | ||||
-rw-r--r-- | drivers/staging/nvec/nvec_power.c | 426 | ||||
-rw-r--r-- | drivers/staging/nvec/nvec_ps2.c | 166 |
11 files changed, 2401 insertions, 0 deletions
diff --git a/drivers/staging/nvec/Kconfig b/drivers/staging/nvec/Kconfig new file mode 100644 index 00000000..731301f5 --- /dev/null +++ b/drivers/staging/nvec/Kconfig @@ -0,0 +1,33 @@ +config MFD_NVEC + bool "NV Tegra Embedded Controller SMBus Interface" + depends on I2C && GPIOLIB && ARCH_TEGRA + 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 && INPUT + help + 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 && 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 && POWER_SUPPLY + help + Say Y to enable support for battery and charger interface for + nVidia compliant embedded controllers. + +config NVEC_LEDS + bool "NVEC leds" + depends on MFD_NVEC && LEDS_CLASS + help + Say Y to enable yellow side leds on AC100 or other nVidia tegra nvec leds diff --git a/drivers/staging/nvec/Makefile b/drivers/staging/nvec/Makefile new file mode 100644 index 00000000..b844d604 --- /dev/null +++ b/drivers/staging/nvec/Makefile @@ -0,0 +1,5 @@ +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_LEDS) += nvec_leds.o diff --git a/drivers/staging/nvec/README b/drivers/staging/nvec/README new file mode 100644 index 00000000..9a320b7f --- /dev/null +++ b/drivers/staging/nvec/README @@ -0,0 +1,14 @@ +NVEC: An NVidia compliant Embedded Controller Protocol Implemenation + +This is an implementation of the NVEC protocol used to communicate with an +embedded controller (EC) via I2C bus. The EC is an I2C master while the host +processor is the I2C slave. Requests from the host processor to the EC are +started by triggering a gpio line. + +There is no written documentation of the protocol available to the public, +but the source code[1] of the published nvec reference drivers can be a guide. +This driver is currently only used by the AC100 project[2], but it is likely, +that other Tegra boards (not yet mainlined, if ever) also use it. + +[1] e.g. http://nv-tegra.nvidia.com/gitweb/?p=linux-2.6.git;a=tree;f=arch/arm/mach-tegra/nvec;hb=android-tegra-2.6.32 +[2] http://gitorious.org/ac100, http://launchpad.net/ac100 diff --git a/drivers/staging/nvec/TODO b/drivers/staging/nvec/TODO new file mode 100644 index 00000000..f950ab89 --- /dev/null +++ b/drivers/staging/nvec/TODO @@ -0,0 +1,12 @@ +ToDo list (incomplete, unordered) + - add compile as module support + - 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 new file mode 100644 index 00000000..1dc22cb8 --- /dev/null +++ b/drivers/staging/nvec/nvec-keytable.h @@ -0,0 +1,307 @@ +/* + * drivers/input/keyboard/tegra-nvec.c + * + * Keyboard class input driver for keyboards connected to an NvEc compliant + * embedded controller + * + * Copyright (c) 2009, NVIDIA 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +static unsigned short code_tab_102us[] = { + /* 0x00 */ + KEY_GRAVE, + KEY_ESC, + KEY_1, + KEY_2, + KEY_3, + KEY_4, + KEY_5, + KEY_6, + KEY_7, + KEY_8, + KEY_9, + KEY_0, + KEY_MINUS, + KEY_EQUAL, + KEY_BACKSPACE, + KEY_TAB, + /* 0x10 */ + KEY_Q, + KEY_W, + KEY_E, + KEY_R, + KEY_T, + KEY_Y, + KEY_U, + KEY_I, + KEY_O, + KEY_P, + KEY_LEFTBRACE, + KEY_RIGHTBRACE, + KEY_ENTER, + KEY_LEFTCTRL, + KEY_A, + KEY_S, + /* 0x20 */ + KEY_D, + KEY_F, + KEY_G, + KEY_H, + KEY_J, + KEY_K, + KEY_L, + KEY_SEMICOLON, + KEY_APOSTROPHE, + KEY_GRAVE, + KEY_LEFTSHIFT, + KEY_BACKSLASH, + KEY_Z, + KEY_X, + KEY_C, + KEY_V, + /* 0x30 */ + KEY_B, + KEY_N, + KEY_M, + KEY_COMMA, + KEY_DOT, + KEY_SLASH, + KEY_RIGHTSHIFT, + KEY_KPASTERISK, + KEY_LEFTALT, + KEY_SPACE, + KEY_CAPSLOCK, + KEY_F1, + KEY_F2, + KEY_F3, + KEY_F4, + KEY_F5, + /* 0x40 */ + KEY_F6, + KEY_F7, + KEY_F8, + KEY_F9, + KEY_F10, + KEY_FN, + /* VK_SCROLL */ + 0, + KEY_KP7, + KEY_KP8, + KEY_KP9, + KEY_KPMINUS, + KEY_KP4, + KEY_KP5, + KEY_KP6, + KEY_KPPLUS, + KEY_KP1, + /* 0x50 */ + KEY_KP2, + KEY_KP3, + KEY_KP0, + KEY_KPDOT, + /* VK_SNAPSHOT */ + KEY_MENU, + KEY_POWER, + /* 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[] = { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + /* 0x10 */ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + /* VK_MEDIA_NEXT_TRACK */ + 0, + 0, + 0, + /* VK_RETURN */ + 0, + KEY_RIGHTCTRL, + 0, + 0, + /* 0x20 */ + KEY_MUTE, + /* VK_LAUNCH_APP1 */ + 0, + /* VK_MEDIA_PLAY_PAUSE */ + 0, + 0, + /* VK_MEDIA_STOP */ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + /* 0x30 */ + KEY_VOLUMEUP, + 0, + /* 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, + 0, + 0, + KEY_CANCEL, + KEY_HOME, + KEY_UP, + KEY_PAGEUP, + 0, + KEY_LEFT, + 0, + KEY_RIGHT, + 0, + KEY_END, + /* 0x50 */ + KEY_DOWN, + KEY_PAGEDOWN, + KEY_INSERT, + KEY_DELETE, + 0, + 0, + 0, + 0, + 0, + 0, + 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, +}; + +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 new file mode 100644 index 00000000..3c600888 --- /dev/null +++ b/drivers/staging/nvec/nvec.c @@ -0,0 +1,942 @@ +/* + * 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/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 <mach/clk.h> +#include <mach/iomap.h> + +#include "nvec.h" + +#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, +}; + +static const unsigned char EC_DISABLE_EVENT_REPORTING[3] = "\x04\x00\x00"; +static const unsigned char EC_ENABLE_EVENT_REPORTING[3] = "\x04\x00\x01"; +static const unsigned char EC_GET_FIRMWARE_VERSION[2] = "\x07\x15"; + +static struct nvec_chip *nvec_power_handle; + +static 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-leds", + .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) +{ + return atomic_notifier_chain_register(&nvec->notifier_list, nb); +} +EXPORT_SYMBOL_GPL(nvec_register_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) +{ + unsigned char *msg = (unsigned char *)data; + + if (event_type != NVEC_CNTL) + return NOTIFY_DONE; + + printk(KERN_WARNING "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; +} + +/** + * 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) +{ + 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 + */ +inline 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[0] = size; + memcpy(msg->data + 1, data, size); + msg->size = size + 1; + + spin_lock_irqsave(&nvec->tx_lock, flags); + list_add_tail(&msg->node, &nvec->tx_data); + spin_unlock_irqrestore(&nvec->tx_lock, flags); + + queue_work(nvec->wq, &nvec->tx_work); + + return 0; +} +EXPORT_SYMBOL(nvec_write_async); + +/** + * 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_msg *msg; + + mutex_lock(&nvec->sync_write_mutex); + + nvec->sync_write_pending = (data[1] << 8) + data[0]; + + if (nvec_write_async(nvec, data, size) < 0) + return NULL; + + 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; + } + + dev_dbg(nvec->dev, "nvec_sync_write: pong!\n"); + + msg = nvec->last_sync_msg; + + mutex_unlock(&nvec->sync_write_mutex); + + return msg; +} +EXPORT_SYMBOL(nvec_write_sync); + +/** + * 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; + + 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; + } + + spin_lock_irqsave(&nvec->tx_lock, flags); + + if (err > 0) { + list_del_init(&msg->node); + nvec_msg_free(nvec, msg); + } + } + spin_unlock_irqrestore(&nvec->tx_lock, flags); +} + +/** + * 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 %02x %02x %02x %02x\n", + msg->data[0], msg->data[1], msg->data[2], msg->data[3]); + 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; + + 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]) { + 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); + 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; + } +} + +/** + * 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); + + queue_work(nvec->wq, &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 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(nvec->base + I2C_SL_STATUS); + + /* 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 & 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 { + 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; + } + 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 { + nvec_invalid_flags(nvec, status, true); + } + 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 { + 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; + } + 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->pos, NVEC_MSG_SIZE); + break; + default: + nvec->state = 0; + } + + /* 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; + } + + /* 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); + + return IRQ_HANDLED; +} + +static void tegra_init_i2c_slave(struct nvec_chip *nvec) +{ + u32 val; + + clk_enable(nvec->i2c_clk); + + tegra_periph_reset_assert(nvec->i2c_clk); + udelay(2); + tegra_periph_reset_deassert(nvec->i2c_clk); + + val = I2C_CNFG_NEW_MASTER_SFM | I2C_CNFG_PACKET_MODE_EN | + (0x2 << I2C_CNFG_DEBOUNCE_CNT_SHIFT); + writel(val, nvec->base + I2C_CNFG); + + clk_set_rate(nvec->i2c_clk, 8 * 80000); + + writel(I2C_SL_NEWSL, nvec->base + I2C_SL_CNFG); + writel(0x1E, nvec->base + I2C_SL_DELAY_COUNT); + + writel(nvec->i2c_addr>>1, nvec->base + I2C_SL_ADDR1); + writel(0, nvec->base + I2C_SL_ADDR2); + + enable_irq(nvec->irq); + + clk_disable(nvec->i2c_clk); +} + +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(nvec->i2c_clk); +} + +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); +} + +static int __devinit 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; + struct resource *res; + struct resource *iomem; + void __iomem *base; + + nvec = kzalloc(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; + + if (pdata) { + nvec->gpio = pdata->gpio; + nvec->i2c_addr = pdata->i2c_addr; + } else if (nvec->dev->of_node) { + nvec->gpio = of_get_named_gpio(nvec->dev->of_node, "request-gpios", 0); + if (nvec->gpio < 0) { + dev_err(&pdev->dev, "no gpio specified"); + goto failed; + } + if (of_property_read_u32(nvec->dev->of_node, "slave-addr", &nvec->i2c_addr)) { + dev_err(&pdev->dev, "no i2c address specified"); + goto failed; + } + } else { + dev_err(&pdev->dev, "no platform data\n"); + goto failed; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "no mem resource?\n"); + return -ENODEV; + } + + iomem = request_mem_region(res->start, resource_size(res), pdev->name); + if (!iomem) { + dev_err(&pdev->dev, "I2C region already claimed\n"); + return -EBUSY; + } + + base = ioremap(iomem->start, resource_size(iomem)); + if (!base) { + dev_err(&pdev->dev, "Can't ioremap I2C region\n"); + return -ENOMEM; + } + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(&pdev->dev, "no irq resource?\n"); + ret = -ENODEV; + goto err_iounmap; + } + + i2c_clk = clk_get_sys("tegra-i2c.2", NULL); + if (IS_ERR(i2c_clk)) { + dev_err(nvec->dev, "failed to get controller clock\n"); + goto err_iounmap; + } + + 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); + 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); + nvec->wq = alloc_workqueue("nvec", WQ_NON_REENTRANT, 2); + + err = gpio_request_one(nvec->gpio, GPIOF_OUT_INIT_HIGH, "nvec gpio"); + if (err < 0) { + dev_err(nvec->dev, "couldn't request gpio\n"); + goto failed; + } + + err = request_irq(nvec->irq, nvec_interrupt, 0, "nvec", nvec); + if (err) { + dev_err(nvec->dev, "couldn't request irq\n"); + goto failed; + } + disable_irq(nvec->irq); + + tegra_init_i2c_slave(nvec); + + clk_enable(i2c_clk); + + + /* enable event reporting */ + nvec_write_async(nvec, EC_ENABLE_EVENT_REPORTING, + sizeof(EC_ENABLE_EVENT_REPORTING)); + + nvec->nvec_status_notifier.notifier_call = nvec_status_notifier; + nvec_register_notifier(nvec, &nvec->nvec_status_notifier, 0); + + nvec_power_handle = nvec; + pm_power_off = nvec_power_off; + + /* Get Firmware Version */ + msg = nvec_write_sync(nvec, EC_GET_FIRMWARE_VERSION, + sizeof(EC_GET_FIRMWARE_VERSION)); + + 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]); + + nvec_msg_free(nvec, msg); + } + + ret = mfd_add_devices(nvec->dev, -1, nvec_devices, + ARRAY_SIZE(nvec_devices), base, 0); + if (ret) + dev_err(nvec->dev, "error adding subdevices\n"); + + /* unmute speakers? */ + nvec_write_async(nvec, "\x0d\x10\x59\x95", 4); + + /* enable lid switch event */ + nvec_write_async(nvec, "\x01\x01\x01\x00\x00\x02\x00", 7); + + /* enable power button event */ + nvec_write_async(nvec, "\x01\x01\x01\x00\x00\x80\x00", 7); + + return 0; + +err_iounmap: + iounmap(base); +failed: + kfree(nvec); + return -ENOMEM; +} + +static int __devexit tegra_nvec_remove(struct platform_device *pdev) +{ + struct nvec_chip *nvec = platform_get_drvdata(pdev); + + nvec_write_async(nvec, EC_DISABLE_EVENT_REPORTING, 3); + mfd_remove_devices(nvec->dev); + free_irq(nvec->irq, &nvec_interrupt); + iounmap(nvec->base); + gpio_free(nvec->gpio); + destroy_workqueue(nvec->wq); + kfree(nvec); + + return 0; +} + +#ifdef CONFIG_PM + +static int tegra_nvec_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct nvec_chip *nvec = platform_get_drvdata(pdev); + struct nvec_msg *msg; + + dev_dbg(nvec->dev, "suspending\n"); + + /* keep these sync or you'll break suspend */ + msg = nvec_write_sync(nvec, EC_DISABLE_EVENT_REPORTING, 3); + nvec_msg_free(nvec, msg); + msg = nvec_write_sync(nvec, "\x04\x02", 2); + nvec_msg_free(nvec, msg); + + nvec_disable_i2c_slave(nvec); + + return 0; +} + +static int tegra_nvec_resume(struct platform_device *pdev) +{ + struct nvec_chip *nvec = platform_get_drvdata(pdev); + + dev_dbg(nvec->dev, "resuming\n"); + tegra_init_i2c_slave(nvec); + nvec_write_async(nvec, EC_ENABLE_EVENT_REPORTING, 3); + + return 0; +} + +#else +#define tegra_nvec_suspend NULL +#define tegra_nvec_resume NULL +#endif + +/* Match table for of_platform binding */ +static const struct of_device_id nvidia_nvec_of_match[] __devinitconst = { + { .compatible = "nvidia,nvec", }, + {}, +}; +MODULE_DEVICE_TABLE(of, nvidia_nvec_of_match); + +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 = { + .name = "nvec", + .owner = THIS_MODULE, + .of_match_table = nvidia_nvec_of_match, + } +}; + +static int __init tegra_nvec_init(void) +{ + return platform_driver_register(&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 new file mode 100644 index 00000000..a4c17b0e --- /dev/null +++ b/drivers/staging/nvec/nvec.h @@ -0,0 +1,203 @@ +/* + * 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/atomic.h> +#include <linux/clk.h> +#include <linux/completion.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/notifier.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 as + * 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, +}; + +/** + * 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_PS2, + NVEC_CNTL, + NVEC_KB_EVT = 0x80, + 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 { + struct list_head node; + unsigned char data[NVEC_MSG_SIZE]; + unsigned short size; + unsigned short pos; + atomic_t used; +}; + +/** + * struct nvec_subdev - A subdevice of nvec, such as nvec_kbd + * @name: The name of the sub device + * @platform_data: Platform data + * @id: Identifier of the sub device + */ +struct nvec_subdev { + const char *name; + void *platform_data; + int id; +}; + +/** + * struct nvec_platform_data - platform data for a tegra slave controller + * @i2c_addr: number of i2c slave adapter the ec is connected to + * @gpio: gpio number for the ec request line + * + * Platform data, to be used in board definitions. For an example, take a + * look at the paz00 board in arch/arm/mach-tegra/board-paz00.c + */ +struct nvec_platform_data { + int i2c_addr; + int gpio; +}; + +/** + * 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 + * @clk: The clock 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; + int i2c_addr; + void __iomem *base; + struct clk *i2c_clk; + 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 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; + + 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 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); + +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 new file mode 100644 index 00000000..a4ce5a74 --- /dev/null +++ b/drivers/staging/nvec/nvec_kbd.c @@ -0,0 +1,179 @@ +/* + * 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'} + +static const char led_on[3] = "\x05\xed\x07"; +static const char led_off[3] = "\x05\xed\x00"; +static unsigned char keycodes[ARRAY_SIZE(code_tab_102us) + + 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) +{ + keys_dev.caps_lock = !keys_dev.caps_lock; + + if (keys_dev.caps_lock) + nvec_write_async(keys_dev.nvec, led_on, sizeof(led_on)); + else + nvec_write_async(keys_dev.nvec, led_off, sizeof(led_off)); +} + +static int nvec_keys_notifier(struct notifier_block *nb, + unsigned long event_type, void *data) +{ + int code, state; + unsigned char *msg = (unsigned char *)data; + + if (event_type == NVEC_KB_EVT) { + int _size = (msg[0] & (3 << 5)) >> 5; + +/* power on/off button */ + if (_size == NVEC_VAR_SIZE) + return NOTIFY_STOP; + + if (_size == NVEC_3BYTES) + msg++; + + code = msg[1] & 0x7f; + state = msg[1] & 0x80; + + 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; + } + + return NOTIFY_DONE; +} + +static int nvec_kbd_event(struct input_dev *dev, unsigned int type, + unsigned int code, int value) +{ + unsigned char buf[] = ACK_KBD_EVENT; + struct nvec_chip *nvec = keys_dev.nvec; + + if (type == EV_REP) + return 0; + + if (type != EV_LED) + return -1; + + if (code != LED_CAPSL) + return -1; + + buf[2] = !!value; + nvec_write_async(nvec, buf, sizeof(buf)); + + return 0; +} + +static int __devinit 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; + + j = 0; + + 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]; + + idev = input_allocate_device(); + 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; + idev->keycode = keycodes; + idev->keycodesize = sizeof(unsigned char); + idev->keycodemax = ARRAY_SIZE(keycodes); + + 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; + + keys_dev.input = idev; + keys_dev.notifier.notifier_call = nvec_keys_notifier; + keys_dev.nvec = nvec; + nvec_register_notifier(nvec, &keys_dev.notifier, 0); + + /* Enable keyboard */ + nvec_write_async(nvec, "\x05\xf4", 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); + + /* Disable caps lock LED */ + nvec_write_async(nvec, led_off, sizeof(led_off)); + + return 0; + +fail: + input_free_device(idev); + return err; +} + +static struct platform_driver nvec_kbd_driver = { + .probe = nvec_kbd_probe, + .driver = { + .name = "nvec-kbd", + .owner = THIS_MODULE, + }, +}; + +static int __init nvec_kbd_init(void) +{ + return platform_driver_register(&nvec_kbd_driver); +} + +module_init(nvec_kbd_init); + +MODULE_AUTHOR("Marc Dietrich <marvin24@gmx.de>"); +MODULE_DESCRIPTION("NVEC keyboard driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/nvec/nvec_leds.c b/drivers/staging/nvec/nvec_leds.c new file mode 100644 index 00000000..f4cbcd62 --- /dev/null +++ b/drivers/staging/nvec/nvec_leds.c @@ -0,0 +1,114 @@ +/* + * nvec_leds: LED 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> + * + * 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 __devinit nvec_led_probe(struct platform_device *pdev) +{ + struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent); + struct nvec_led *led; + int ret = 0; + + led = kzalloc(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 = "nvec-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) + goto err_led; + + /* to expose the default value to userspace */ + led->cdev.brightness = 0; + + return 0; + +err_led: + kfree(led); + return ret; +} + +static int __devexit nvec_led_remove(struct platform_device *pdev) +{ + struct nvec_led *led = platform_get_drvdata(pdev); + + led_classdev_unregister(&led->cdev); + kfree(led); + return 0; +} + +static struct platform_driver nvec_led_driver = { + .probe = nvec_led_probe, + .remove = __devexit_p(nvec_led_remove), + .driver = { + .name = "nvec-leds", + .owner = THIS_MODULE, + }, +}; + +static int __init nvec_led_init(void) +{ + return platform_driver_register(&nvec_led_driver); +} + +module_init(nvec_led_init); + +static void __exit nvec_led_exit(void) +{ + platform_driver_unregister(&nvec_led_driver); +} + +module_exit(nvec_led_exit); + +MODULE_AUTHOR("Ilya Petrov <ilya.muromec@gmail.com>"); +MODULE_DESCRIPTION("Tegra NVEC LED driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:nvec-leds"); diff --git a/drivers/staging/nvec/nvec_power.c b/drivers/staging/nvec/nvec_power.c new file mode 100644 index 00000000..dfa966f6 --- /dev/null +++ b/drivers/staging/nvec/nvec_power.c @@ -0,0 +1,426 @@ +/* + * 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> +#include <linux/power_supply.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/delay.h> + +#include "nvec.h" + +struct nvec_power { + struct notifier_block notifier; + struct delayed_work poller; + struct nvec_chip *nvec; + int on; + int bat_present; + int bat_status; + int bat_voltage_now; + int bat_current_now; + int bat_current_avg; + int time_remain; + int charge_full_design; + int charge_last_full; + int critical_capacity; + int capacity_remain; + int bat_temperature; + int bat_cap; + int bat_type_enum; + char bat_manu[30]; + char bat_model[30]; + char bat_type[30]; +}; + +enum { + SLOT_STATUS, + VOLTAGE, + TIME_REMAINING, + CURRENT, + AVERAGE_CURRENT, + AVERAGING_TIME_INTERVAL, + CAPACITY_REMAINING, + LAST_FULL_CHARGE_CAPACITY, + DESIGN_CAPACITY, + CRITICAL_CAPACITY, + TEMPERATURE, + MANUFACTURER, + MODEL, + TYPE, +}; + +enum { + AC, + BAT, +}; + +struct bat_response { + u8 event_type; + u8 length; + u8 sub_type; + u8 status; + /* payload */ + union { + char plc[30]; + u16 plu; + s16 pls; + }; +}; + +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) +{ + 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) { + power->on = res->plu; + power_supply_changed(&nvec_psy); + } + return NOTIFY_STOP; + } + return NOTIFY_OK; +} + +static const int bat_init[] = { + LAST_FULL_CHARGE_CAPACITY, DESIGN_CAPACITY, CRITICAL_CAPACITY, + MANUFACTURER, MODEL, TYPE, +}; + +static void get_bat_mfg_data(struct nvec_power *power) +{ + int i; + char buf[] = { '\x02', '\x00' }; + + 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) +{ + 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; + 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) +{ + struct nvec_power *power = dev_get_drvdata(psy->dev->parent); + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = power->on; + break; + default: + return -EINVAL; + } + return 0; +} + +static int nvec_battery_get_property(struct power_supply *psy, + 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; + } + return 0; +} + +static enum power_supply_property nvec_power_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static enum power_supply_property nvec_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, +#ifdef EC_FULL_DIAG + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, +#endif + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_EMPTY, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_TECHNOLOGY, +}; + +static char *nvec_power_supplied_to[] = { + "battery", +}; + +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, +}; + +static struct power_supply nvec_psy = { + .name = "ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .supplied_to = nvec_power_supplied_to, + .num_supplicants = ARRAY_SIZE(nvec_power_supplied_to), + .properties = nvec_power_props, + .num_properties = ARRAY_SIZE(nvec_power_props), + .get_property = nvec_power_get_property, +}; + +static int counter; +static int const bat_iter[] = { + SLOT_STATUS, VOLTAGE, CURRENT, CAPACITY_REMAINING, +#ifdef EC_FULL_DIAG + AVERAGE_CURRENT, TEMPERATURE, TIME_REMAINING, +#endif +}; + +static void nvec_power_poll(struct work_struct *work) +{ + char buf[] = { '\x01', '\x00' }; + struct nvec_power *power = container_of(work, struct nvec_power, + poller.work); + + if (counter >= ARRAY_SIZE(bat_iter)) + counter = 0; + +/* AC status via sys req */ + nvec_write_async(power->nvec, buf, 2); + msleep(100); + +/* 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++]; + nvec_write_async(power->nvec, buf, 2); + + schedule_delayed_work(to_delayed_work(work), msecs_to_jiffies(5000)); +}; + +static int __devinit nvec_power_probe(struct platform_device *pdev) +{ + struct power_supply *psy; + struct nvec_power *power = + kzalloc(sizeof(struct nvec_power), GFP_NOWAIT); + struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent); + + dev_set_drvdata(&pdev->dev, power); + power->nvec = nvec; + + switch (pdev->id) { + case AC: + psy = &nvec_psy; + + power->notifier.notifier_call = nvec_power_notifier; + + INIT_DELAYED_WORK(&power->poller, nvec_power_poll); + schedule_delayed_work(&power->poller, msecs_to_jiffies(5000)); + break; + case BAT: + psy = &nvec_bat_psy; + + power->notifier.notifier_call = nvec_power_bat_notifier; + break; + default: + kfree(power); + return -ENODEV; + } + + nvec_register_notifier(nvec, &power->notifier, NVEC_SYS); + + if (pdev->id == BAT) + get_bat_mfg_data(power); + + return power_supply_register(&pdev->dev, psy); +} + +static struct platform_driver nvec_power_driver = { + .probe = nvec_power_probe, + .driver = { + .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_AUTHOR("Ilya Petrov <ilya.muromec@gmail.com>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("NVEC battery and AC driver"); +MODULE_ALIAS("platform:nvec-power"); diff --git a/drivers/staging/nvec/nvec_ps2.c b/drivers/staging/nvec/nvec_ps2.c new file mode 100644 index 00000000..14a6f687 --- /dev/null +++ b/drivers/staging/nvec/nvec_ps2.c @@ -0,0 +1,166 @@ +/* + * 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', '\x06'} +#define STOP_STREAMING {'\x06', '\x04'} +#define SEND_COMMAND {'\x06', '\x01', '\xf4', '\x01'} + +#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 + +static const unsigned char MOUSE_RESET[] = {'\x06', '\x01', '\xff', '\x03'}; + +struct nvec_ps2 { + struct serio *ser_dev; + struct notifier_block notifier; + struct nvec_chip *nvec; +}; + +static struct nvec_ps2 ps2_dev; + +static int ps2_startstreaming(struct serio *ser_dev) +{ + unsigned char buf[] = START_STREAMING; + return nvec_write_async(ps2_dev.nvec, buf, sizeof(buf)); +} + +static void ps2_stopstreaming(struct serio *ser_dev) +{ + unsigned char buf[] = STOP_STREAMING; + nvec_write_async(ps2_dev.nvec, buf, sizeof(buf)); +} + +static int ps2_sendcommand(struct serio *ser_dev, unsigned char cmd) +{ + unsigned char buf[] = SEND_COMMAND; + + buf[2] = cmd & 0xff; + + dev_dbg(&ser_dev->dev, "Sending ps2 cmd %02x\n", cmd); + 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) +{ + int i; + unsigned char *msg = (unsigned char *)data; + + switch (event_type) { + 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; +} + +static int __devinit nvec_mouse_probe(struct platform_device *pdev) +{ + struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent); + struct serio *ser_dev = kzalloc(sizeof(struct serio), GFP_KERNEL); + + 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 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; + ps2_dev.nvec = nvec; + nvec_register_notifier(nvec, &ps2_dev.notifier, 0); + + serio_register_port(ser_dev); + + /* mouse reset */ + nvec_write_async(nvec, MOUSE_RESET, 4); + + return 0; +} + +static int nvec_mouse_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent); + + /* disable mouse */ + nvec_write_async(nvec, "\x06\xf4", 2); + + /* send cancel autoreceive */ + nvec_write_async(nvec, "\x06\x04", 2); + + return 0; +} + +static int nvec_mouse_resume(struct platform_device *pdev) +{ + struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent); + + ps2_startstreaming(ps2_dev.ser_dev); + + /* enable mouse */ + nvec_write_async(nvec, "\x06\xf5", 2); + + return 0; +} + +static struct platform_driver nvec_mouse_driver = { + .probe = nvec_mouse_probe, + .suspend = nvec_mouse_suspend, + .resume = nvec_mouse_resume, + .driver = { + .name = "nvec-mouse", + .owner = THIS_MODULE, + }, +}; + +static int __init nvec_mouse_init(void) +{ + return platform_driver_register(&nvec_mouse_driver); +} + +module_init(nvec_mouse_init); + +MODULE_DESCRIPTION("NVEC mouse driver"); +MODULE_AUTHOR("Marc Dietrich <marvin24@gmx.de>"); +MODULE_LICENSE("GPL"); |