diff options
Diffstat (limited to 'drivers/staging/nvec')
-rw-r--r-- | drivers/staging/nvec/Kconfig | 12 | ||||
-rw-r--r-- | drivers/staging/nvec/Makefile | 1 | ||||
-rw-r--r-- | drivers/staging/nvec/TODO | 16 | ||||
-rw-r--r-- | drivers/staging/nvec/nvec-keytable.h | 225 | ||||
-rw-r--r-- | drivers/staging/nvec/nvec.c | 959 | ||||
-rw-r--r-- | drivers/staging/nvec/nvec.h | 217 | ||||
-rw-r--r-- | drivers/staging/nvec/nvec_kbd.c | 95 | ||||
-rw-r--r-- | drivers/staging/nvec/nvec_power.c | 352 | ||||
-rw-r--r-- | drivers/staging/nvec/nvec_ps2.c | 114 |
9 files changed, 1339 insertions, 652 deletions
diff --git a/drivers/staging/nvec/Kconfig b/drivers/staging/nvec/Kconfig index 987ad48..86a8b8c 100644 --- a/drivers/staging/nvec/Kconfig +++ b/drivers/staging/nvec/Kconfig @@ -7,21 +7,27 @@ config MFD_NVEC config KEYBOARD_NVEC bool "Keyboard on nVidia compliant EC" - depends on MFD_NVEC + depends on MFD_NVEC && INPUT=y 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 + depends on MFD_NVEC && MOUSE_PS2 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 + depends on MFD_NVEC && POWER_SUPPLY=y 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 index 4b5fcec1..b844d60 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_LEDS) += nvec_leds.o diff --git a/drivers/staging/nvec/TODO b/drivers/staging/nvec/TODO index 77b47f7..f950ab8 100644 --- a/drivers/staging/nvec/TODO +++ b/drivers/staging/nvec/TODO @@ -1,8 +1,12 @@ 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 6a1c4f7..1dc22cb 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 1a94364..e06b867 100644 --- a/drivers/staging/nvec/nvec.c +++ b/drivers/staging/nvec/nvec.c @@ -1,295 +1,706 @@ -// #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/list.h> +#include <linux/mfd/core.h> +#include <linux/mutex.h> #include <linux/notifier.h> -#include <linux/workqueue.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" -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_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 + +/** + * 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) + 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_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; - 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"); + 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; } -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) +{ + 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) { - struct nvec_msg *msg = kzalloc(sizeof(struct nvec_msg), GFP_NOWAIT); + 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); + + queue_work(nvec->wq, &nvec->tx_work); - gpio_set_value(nvec->gpio, 0); + 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; + } - if(!list_empty(&nvec->tx_data)) { - gpio_set_value(nvec->gpio, 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) { - int i; - - 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]); + 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) - { - 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"); - } + 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); + atomic_notifier_call_chain(&nvec->notifier_list, msg->data[0] & 0x8f, + msg->data); return 0; } -static struct nvec_msg *nvec_write_sync(struct nvec_chip *nvec, unsigned char *data, short size) -{ - down(&nvec->sync_write_mutex); - - nvec->sync_write_pending = (data[1] << 8) + data[0]; - nvec_write_async(nvec, data, size); - - 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"); - - up(&nvec->sync_write_mutex); - - return nvec->last_sync_msg; -} - -/* RX worker */ +/** + * 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); + + 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 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->pos, 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_enable(nvec->i2c_clk); - return platform_device_add(pdev); -} + tegra_periph_reset_assert(nvec->i2c_clk); + udelay(2); + tegra_periph_reset_deassert(nvec->i2c_clk); -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_NEWL, 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); + + enable_irq(nvec->irq); - clk_disable(i2c_clk); + clk_disable(nvec->i2c_clk); +} + +static void nvec_disable_i2c_slave(struct nvec_chip *nvec) +{ + disable_irq(nvec->irq); + writel(I2C_SL_NEWL | I2C_SL_NACK, nvec->base + I2C_SL_CNFG); + clk_disable(nvec->i2c_clk); } static void nvec_power_off(void) @@ -300,86 +711,96 @@ static void nvec_power_off(void) static int __devinit tegra_nvec_probe(struct platform_device *pdev) { - int err, i, ret; + 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; + struct resource *iomem; + void __iomem *base; nvec = kzalloc(sizeof(struct nvec_chip), GFP_KERNEL); - if(nvec == NULL) { + 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; + nvec->i2c_addr = pdata->i2c_addr; -/* - 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; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "no mem resource?\n"); + return -ENODEV; } - nvec->i2c_regs = i2c_regs; + iomem = request_mem_region(res->start, resource_size(res), pdev->name); + if (!iomem) { + dev_err(&pdev->dev, "I2C region already claimed\n"); + return -EBUSY; + } - 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; + base = ioremap(iomem->start, resource_size(iomem)); + if (!base) { + dev_err(&pdev->dev, "Can't ioremap I2C region\n"); + return -ENOMEM; } - tegra_init_i2c_slave(pdata, i2c_regs, i2c_clk); + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(&pdev->dev, "no irq resource?\n"); + ret = -ENODEV; + goto err_iounmap; + } - 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 = 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; } - clk_enable(i2c_clk); - clk_set_rate(i2c_clk, 8*80000); + nvec->base = base; + nvec->irq = res->start; + nvec->i2c_clk = i2c_clk; + nvec->rx = &nvec->msg_pool[0]; /* Set the gpio to low when we've got something to say */ err = gpio_request(nvec->gpio, "nvec gpio"); - if(err < 0) + if (err < 0) dev_err(nvec->dev, "couldn't request gpio\n"); - tegra_gpio_enable(nvec->gpio); - gpio_direction_output(nvec->gpio, 1); - gpio_set_value(nvec->gpio, 1); - 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); + nvec->wq = alloc_workqueue("nvec", WQ_NON_REENTRANT, 2); - /* enable event reporting */ - nvec_write_async(nvec, EC_ENABLE_EVENT_REPORTING, - sizeof(EC_ENABLE_EVENT_REPORTING)); + 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); - nvec_kbd_init(nvec); -#ifdef CONFIG_SERIO_NVEC_PS2 - nvec_ps2(nvec); -#endif + tegra_init_i2c_slave(nvec); - /* setup subdevs */ - for (i = 0; i < pdata->num_subdevs; i++) { - ret = nvec_add_subdev(nvec, &pdata->subdevs[i]); - } + clk_enable(i2c_clk); + + gpio_direction_output(nvec->gpio, 1); + gpio_set_value(nvec->gpio, 1); + + /* 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); @@ -391,14 +812,20 @@ static int __devinit tegra_nvec_probe(struct platform_device *pdev) msg = nvec_write_sync(nvec, EC_GET_FIRMWARE_VERSION, sizeof(EC_GET_FIRMWARE_VERSION)); - 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); + 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, "\x0d\x10\x59\x95", 4); /* enable lid switch event */ nvec_write_async(nvec, "\x01\x01\x01\x00\x00\x02\x00", 7); @@ -408,6 +835,8 @@ static int __devinit tegra_nvec_probe(struct platform_device *pdev) return 0; +err_iounmap: + iounmap(base); failed: kfree(nvec); return -ENOMEM; @@ -415,7 +844,16 @@ failed: static int __devexit tegra_nvec_remove(struct platform_device *pdev) { - // TODO: unregister + 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; } @@ -424,19 +862,27 @@ static int __devexit tegra_nvec_remove(struct platform_device *pdev) 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"); - nvec_write_async(nvec, EC_DISABLE_EVENT_REPORTING, 3); - nvec_write_async(nvec, "\x04\x02", 2); + + /* 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) { - +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; @@ -447,13 +893,12 @@ static int tegra_nvec_resume(struct platform_device *pdev) { #define tegra_nvec_resume NULL #endif -static struct platform_driver nvec_device_driver = -{ - .probe = tegra_nvec_probe, - .remove = __devexit_p(tegra_nvec_remove), +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 = { + .resume = tegra_nvec_resume, + .driver = { .name = "nvec", .owner = THIS_MODULE, } @@ -465,4 +910,8 @@ static int __init tegra_nvec_init(void) } 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 a2d82dc..a4c17b0 100644 --- a/drivers/staging/nvec/nvec.h +++ b/drivers/staging/nvec/nvec.h @@ -1,110 +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/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/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 -} 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_PS2, NVEC_CNTL, 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; + 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 num_subdevs; int i2c_addr; int gpio; - int irq; - int base; - int size; - char clock[16]; - struct nvec_subdev *subdevs; }; +/** + * 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; - unsigned char *i2c_regs; - nvec_state state; + 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 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; + + spinlock_t tx_lock, rx_lock; -/* sync write stuff */ - struct semaphore sync_write_mutex; + /* 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); + 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 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 9a98507..a4ce5a7 100644 --- a/drivers/staging/nvec/nvec_kbd.c +++ b/drivers/staging/nvec/nvec_kbd.c @@ -1,42 +1,76 @@ +/* + * 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'} +#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)]; + + 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) + 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 +80,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; - 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 +100,23 @@ 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 __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) + 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->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,12 +124,12 @@ 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) + if (err) goto fail; keys_dev.input = idev; @@ -114,9 +149,31 @@ int __init nvec_kbd_init(struct nvec_chip *nvec) 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_power.c b/drivers/staging/nvec/nvec_power.c index df164ad..dfa966f 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,10 @@ #include <linux/slab.h> #include <linux/workqueue.h> #include <linux/delay.h> + #include "nvec.h" -struct nvec_power -{ +struct nvec_power { struct notifier_block notifier; struct delayed_work poller; struct nvec_chip *nvec; @@ -58,7 +72,8 @@ struct bat_response { u8 length; u8 sub_type; u8 status; - union { /* payload */ + /* payload */ + union { char plc[30]; u16 plu; s16 pls; @@ -69,18 +84,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 +103,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, }; @@ -100,116 +113,115 @@ 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++) - { + 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 +235,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 +321,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 +338,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, @@ -340,7 +350,7 @@ 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); + poller.work); if (counter >= ARRAY_SIZE(bat_iter)) counter = 0; @@ -351,19 +361,18 @@ 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] = '\x02'; /* battery */ + 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) { struct power_supply *psy; - struct nvec_power *power = kzalloc(sizeof(struct nvec_power), GFP_NOWAIT); + 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); @@ -381,7 +390,7 @@ 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); @@ -398,14 +407,13 @@ static int __devinit nvec_power_probe(struct platform_device *pdev) static struct platform_driver nvec_power_driver = { .probe = nvec_power_probe, -// .remove = __devexit_p(nvec_power_remove), .driver = { - .name = "nvec-power", - .owner = THIS_MODULE, - } + .name = "nvec-power", + .owner = THIS_MODULE, + } }; -static int __init nvec_power_init(void) +static int __init nvec_power_init(void) { return platform_driver_register(&nvec_power_driver); } diff --git a/drivers/staging/nvec/nvec_ps2.c b/drivers/staging/nvec/nvec_ps2.c index 6bb9430..742f5cc 100644 --- a/drivers/staging/nvec/nvec_ps2.c +++ b/drivers/staging/nvec/nvec_ps2.c @@ -1,14 +1,33 @@ +/* + * 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 START_STREAMING {'\x06', '\x03', '\x04'} +#define STOP_STREAMING {'\x06', '\x04'} +#define SEND_COMMAND {'\x06', '\x01', '\xf4', '\x01'} -struct nvec_ps2 -{ +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; @@ -19,8 +38,7 @@ 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; + return nvec_write_async(ps2_dev.nvec, buf, sizeof(buf)); } static void ps2_stopstreaming(struct serio *ser_dev) @@ -29,12 +47,6 @@ static void ps2_stopstreaming(struct serio *ser_dev) 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; @@ -42,52 +54,49 @@ static int ps2_sendcommand(struct serio *ser_dev, unsigned char cmd) 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); + 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 */ + print_hex_dump(KERN_WARNING, "unhandled mouse event: ", + DUMP_PREFIX_NONE, 16, 1, + msg, msg[1] + 2, true); + } + + return NOTIFY_STOP; } return NOTIFY_DONE; } - -int __init nvec_ps2(struct nvec_chip *nvec) +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_8042; - ser_dev->write=ps2_sendcommand; - ser_dev->open=ps2_startstreaming; - ser_dev->close=ps2_stopstreaming; + ser_dev->id.type = SERIO_8042; + ser_dev->write = ps2_sendcommand; + ser_dev->open = ps2_startstreaming; + ser_dev->close = 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 +106,26 @@ 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, 4); return 0; } + +static struct platform_driver nvec_mouse_driver = { + .probe = nvec_mouse_probe, + .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"); |