Initial commit; kernel source import

This commit is contained in:
Nathan
2025-04-06 23:50:55 -05:00
commit 25c6d769f4
45093 changed files with 18199410 additions and 0 deletions

View File

@@ -0,0 +1,35 @@
config MFD_NVEC
tristate "NV Tegra Embedded Controller SMBus Interface"
depends on I2C && GPIOLIB && ARCH_TEGRA
select MFD_CORE
help
Say Y here to enable support for a nVidia compliant embedded
controller.
config KEYBOARD_NVEC
tristate "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
tristate "PS2 on nVidia EC"
depends on MFD_NVEC && SERIO
help
Say Y here to enable support for a Touchpad / Mouse connected
to a nVidia compliant embedded controller.
config NVEC_POWER
tristate "NVEC charger and battery"
depends on MFD_NVEC && POWER_SUPPLY
help
Say Y to enable support for battery and charger interface for
nVidia compliant embedded controllers.
config NVEC_PAZ00
tristate "Support for OEM specific functions on Compal PAZ00 based devices"
depends on MFD_NVEC && LEDS_CLASS
help
Say Y to enable control of the yellow side leds on Compal PAZ00 based
devices, e.g. Toshbia AC100 and Dynabooks AZ netbooks.

View File

@@ -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_PAZ00) += nvec_paz00.o

View File

@@ -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

View File

@@ -0,0 +1,8 @@
ToDo list (incomplete, unordered)
- add compile as module support
- 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

View File

@@ -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 };

982
drivers/staging/nvec/nvec.c Normal file
View File

@@ -0,0 +1,982 @@
/*
* 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 <linux/clk/tegra.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,
};
enum nvec_sleep_subcmds {
GLOBAL_EVENTS,
AP_PWR_DOWN,
AP_SUSPEND,
};
#define CNF_EVENT_REPORTING 0x01
#define GET_FIRMWARE_VERSION 0x15
#define LID_SWITCH BIT(1)
#define PWR_BUTTON BIT(15)
static struct nvec_chip *nvec_power_handle;
static struct mfd_cell nvec_devices[] = {
{
.name = "nvec-kbd",
.id = 1,
},
{
.name = "nvec-mouse",
.id = 1,
},
{
.name = "nvec-power",
.id = 1,
},
{
.name = "nvec-power",
.id = 2,
},
{
.name = "nvec-paz00",
.id = 1,
},
};
/**
* nvec_register_notifier - Register a notifier with nvec
* @nvec: A &struct nvec_chip
* @nb: The notifier block to register
*
* Registers a notifier with @nvec. The notifier will be added to an atomic
* notifier chain that is called for all received messages except those that
* correspond to a request initiated by nvec_write_sync().
*/
int nvec_register_notifier(struct nvec_chip *nvec, struct notifier_block *nb,
unsigned int events)
{
return atomic_notifier_chain_register(&nvec->notifier_list, nb);
}
EXPORT_SYMBOL_GPL(nvec_register_notifier);
/**
* nvec_unregister_notifier - Unregister a notifier with nvec
* @nvec: A &struct nvec_chip
* @nb: The notifier block to unregister
*
* Unregisters a notifier with @nvec. The notifier will be removed from the
* atomic notifier chain.
*/
int nvec_unregister_notifier(struct nvec_chip *nvec, struct notifier_block *nb)
{
return atomic_notifier_chain_unregister(&nvec->notifier_list, nb);
}
EXPORT_SYMBOL_GPL(nvec_unregister_notifier);
/**
* nvec_status_notifier - The final notifier
*
* Prints a message about control events not handled in the notifier
* chain.
*/
static int nvec_status_notifier(struct notifier_block *nb,
unsigned long event_type, void *data)
{
struct nvec_chip *nvec = container_of(nb, struct nvec_chip,
nvec_status_notifier);
unsigned char *msg = (unsigned char *)data;
if (event_type != NVEC_CNTL)
return NOTIFY_DONE;
dev_warn(nvec->dev, "unhandled msg type %ld\n", event_type);
print_hex_dump(KERN_WARNING, "payload: ", DUMP_PREFIX_NONE, 16, 1,
msg, msg[1] + 2, true);
return NOTIFY_OK;
}
/**
* 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
*/
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);
schedule_work(&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) {
mutex_unlock(&nvec->sync_write_mutex);
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_toggle_global_events - enables or disables global event reporting
* @nvec: nvec handle
* @state: true for enable, false for disable
*
* This switches on/off global event reports by the embedded controller.
*/
static void nvec_toggle_global_events(struct nvec_chip *nvec, bool state)
{
unsigned char global_events[] = { NVEC_SLEEP, GLOBAL_EVENTS, state };
nvec_write_async(nvec, global_events, 3);
}
/**
* nvec_event_mask - fill the command string with event bitfield
* ev: points to event command string
* mask: bit to insert into the event mask
*
* Configure event command expects a 32 bit bitfield which describes
* which events to enable. The bitfield has the following structure
* (from highest byte to lowest):
* system state bits 7-0
* system state bits 15-8
* oem system state bits 7-0
* oem system state bits 15-8
*/
static void nvec_event_mask(char *ev, u32 mask)
{
ev[3] = mask >> 16 & 0xff;
ev[4] = mask >> 24 & 0xff;
ev[5] = mask >> 0 & 0xff;
ev[6] = mask >> 8 & 0xff;
}
/**
* 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 %*ph\n", 4, msg->data);
return -EINVAL;
}
if ((msg->data[0] >> 7) == 1 && (msg->data[0] & 0x0f) == 5)
print_hex_dump(KERN_WARNING, "ec system event ",
DUMP_PREFIX_NONE, 16, 1, msg->data,
msg->data[1] + 2, true);
atomic_notifier_call_chain(&nvec->notifier_list, msg->data[0] & 0x8f,
msg->data);
return 0;
}
/**
* nvec_dispatch - Process messages received from the EC
* @work: A &struct work_struct (the tx_worker member of &struct nvec_chip)
*
* Process messages previously received from the EC and put into the RX
* queue of the &struct nvec_chip instance associated with @work.
*/
static void nvec_dispatch(struct work_struct *work)
{
struct nvec_chip *nvec = container_of(work, struct nvec_chip, rx_work);
unsigned long flags;
struct nvec_msg *msg;
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);
schedule_work(&nvec->rx_work);
}
/**
* nvec_invalid_flags - Send an error message about invalid flags and jump
* @nvec: The nvec device
* @status: The status flags
* @reset: Whether we shall jump to state 0.
*/
static void nvec_invalid_flags(struct nvec_chip *nvec, unsigned int status,
bool reset)
{
dev_err(nvec->dev, "unexpected status flags 0x%02x during state %i\n",
status, nvec->state);
if (reset)
nvec->state = 0;
}
/**
* nvec_tx_set - Set the message to transfer (nvec->tx)
* @nvec: A &struct nvec_chip
*
* Gets the first entry from the tx_data list of @nvec and sets the
* tx member to it. If the tx_data list is empty, this uses the
* tx_scratch message to send a no operation message.
*/
static void nvec_tx_set(struct nvec_chip *nvec)
{
spin_lock(&nvec->tx_lock);
if (list_empty(&nvec->tx_data)) {
dev_err(nvec->dev, "empty tx - sending no-op\n");
memcpy(nvec->tx_scratch.data, "\x02\x07\x02", 3);
nvec->tx_scratch.size = 3;
nvec->tx_scratch.pos = 0;
nvec->tx = &nvec->tx_scratch;
list_add_tail(&nvec->tx->node, &nvec->tx_data);
} else {
nvec->tx = list_first_entry(&nvec->tx_data, struct nvec_msg,
node);
nvec->tx->pos = 0;
}
spin_unlock(&nvec->tx_lock);
dev_dbg(nvec->dev, "Sending message of length %u, command 0x%x\n",
(uint)nvec->tx->size, nvec->tx->data[1]);
}
/**
* nvec_interrupt - Interrupt handler
* @irq: The IRQ
* @dev: The nvec device
*
* Interrupt handler that fills our RX buffers and empties our TX
* buffers. This uses a finite state machine with ridiculous amounts
* of error checking, in order to be fairly reliable.
*/
static irqreturn_t nvec_interrupt(int irq, void *dev)
{
unsigned long status;
unsigned 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_prepare_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_unprepare(nvec->i2c_clk);
}
#ifdef CONFIG_PM_SLEEP
static void nvec_disable_i2c_slave(struct nvec_chip *nvec)
{
disable_irq(nvec->irq);
writel(I2C_SL_NEWSL | I2C_SL_NACK, nvec->base + I2C_SL_CNFG);
clk_disable_unprepare(nvec->i2c_clk);
}
#endif
static void nvec_power_off(void)
{
char ap_pwr_down[] = { NVEC_SLEEP, AP_PWR_DOWN };
nvec_toggle_global_events(nvec_power_handle, false);
nvec_write_async(nvec_power_handle, ap_pwr_down, 2);
}
static int 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;
void __iomem *base;
char get_firmware_version[] = { NVEC_CNTL, GET_FIRMWARE_VERSION },
unmute_speakers[] = { NVEC_OEM0, 0x10, 0x59, 0x95 },
enable_event[7] = { NVEC_SYS, CNF_EVENT_REPORTING, true };
nvec = devm_kzalloc(&pdev->dev, sizeof(struct nvec_chip), GFP_KERNEL);
if (nvec == NULL) {
dev_err(&pdev->dev, "failed to reserve memory\n");
return -ENOMEM;
}
platform_set_drvdata(pdev, nvec);
nvec->dev = &pdev->dev;
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");
return -ENODEV;
}
if (of_property_read_u32(nvec->dev->of_node,
"slave-addr", &nvec->i2c_addr)) {
dev_err(&pdev->dev, "no i2c address specified");
return -ENODEV;
}
} else {
dev_err(&pdev->dev, "no platform data\n");
return -ENODEV;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!res) {
dev_err(&pdev->dev, "no irq resource?\n");
return -ENODEV;
}
i2c_clk = devm_clk_get(&pdev->dev, "div-clk");
if (IS_ERR(i2c_clk)) {
dev_err(nvec->dev, "failed to get controller clock\n");
return -ENODEV;
}
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);
err = devm_gpio_request_one(&pdev->dev, nvec->gpio, GPIOF_OUT_INIT_HIGH,
"nvec gpio");
if (err < 0) {
dev_err(nvec->dev, "couldn't request gpio\n");
return -ENODEV;
}
err = devm_request_irq(&pdev->dev, nvec->irq, nvec_interrupt, 0,
"nvec", nvec);
if (err) {
dev_err(nvec->dev, "couldn't request irq\n");
return -ENODEV;
}
disable_irq(nvec->irq);
tegra_init_i2c_slave(nvec);
clk_prepare_enable(i2c_clk);
/* enable event reporting */
nvec_toggle_global_events(nvec, true);
nvec->nvec_status_notifier.notifier_call = nvec_status_notifier;
nvec_register_notifier(nvec, &nvec->nvec_status_notifier, 0);
nvec_power_handle = nvec;
pm_power_off = nvec_power_off;
/* Get Firmware Version */
msg = nvec_write_sync(nvec, get_firmware_version, 2);
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, NULL);
if (ret)
dev_err(nvec->dev, "error adding subdevices\n");
/* unmute speakers? */
nvec_write_async(nvec, unmute_speakers, 4);
/* enable lid switch event */
nvec_event_mask(enable_event, LID_SWITCH);
nvec_write_async(nvec, enable_event, 7);
/* enable power button event */
nvec_event_mask(enable_event, PWR_BUTTON);
nvec_write_async(nvec, enable_event, 7);
return 0;
}
static int tegra_nvec_remove(struct platform_device *pdev)
{
struct nvec_chip *nvec = platform_get_drvdata(pdev);
nvec_toggle_global_events(nvec, false);
mfd_remove_devices(nvec->dev);
nvec_unregister_notifier(nvec, &nvec->nvec_status_notifier);
cancel_work_sync(&nvec->rx_work);
cancel_work_sync(&nvec->tx_work);
/* FIXME: needs check wether nvec is responsible for power off */
pm_power_off = NULL;
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int nvec_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct nvec_chip *nvec = platform_get_drvdata(pdev);
struct nvec_msg *msg;
char ap_suspend[] = { NVEC_SLEEP, AP_SUSPEND };
dev_dbg(nvec->dev, "suspending\n");
/* keep these sync or you'll break suspend */
nvec_toggle_global_events(nvec, false);
msg = nvec_write_sync(nvec, ap_suspend, sizeof(ap_suspend));
nvec_msg_free(nvec, msg);
nvec_disable_i2c_slave(nvec);
return 0;
}
static int nvec_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct nvec_chip *nvec = platform_get_drvdata(pdev);
dev_dbg(nvec->dev, "resuming\n");
tegra_init_i2c_slave(nvec);
nvec_toggle_global_events(nvec, true);
return 0;
}
#endif
static const SIMPLE_DEV_PM_OPS(nvec_pm_ops, nvec_suspend, nvec_resume);
/* Match table for of_platform binding */
static const struct of_device_id nvidia_nvec_of_match[] = {
{ .compatible = "nvidia,nvec", },
{},
};
MODULE_DEVICE_TABLE(of, nvidia_nvec_of_match);
static struct platform_driver nvec_device_driver = {
.probe = tegra_nvec_probe,
.remove = tegra_nvec_remove,
.driver = {
.name = "nvec",
.owner = THIS_MODULE,
.pm = &nvec_pm_ops,
.of_match_table = nvidia_nvec_of_match,
}
};
module_platform_driver(nvec_device_driver);
MODULE_ALIAS("platform:nvec");
MODULE_DESCRIPTION("NVIDIA compliant embedded controller interface");
MODULE_AUTHOR("Marc Dietrich <marvin24@gmx.de>");
MODULE_LICENSE("GPL");

205
drivers/staging/nvec/nvec.h Normal file
View File

@@ -0,0 +1,205 @@
/*
* 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 has
* up to as many bytes as the number in the count byte. The
* maximum is 32
*
* Events can be fixed or variable sized. This is useless on other message
* types, which are always variable sized.
*/
enum nvec_event_size {
NVEC_2BYTES,
NVEC_3BYTES,
NVEC_VAR_SIZE,
};
/**
* 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_GPIO,
NVEC_SLEEP,
NVEC_KBD,
NVEC_PS2,
NVEC_CNTL,
NVEC_OEM0 = 0x0d,
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 nvec_chip *dev,
struct notifier_block *nb);
extern void nvec_msg_free(struct nvec_chip *nvec, struct nvec_msg *msg);
#endif

View File

@@ -0,0 +1,199 @@
/*
* 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"
enum kbd_subcmds {
CNFG_WAKE = 3,
CNFG_WAKE_KEY_REPORTING,
SET_LEDS = 0xed,
ENABLE_KBD = 0xf4,
DISABLE_KBD,
};
static unsigned char keycodes[ARRAY_SIZE(code_tab_102us)
+ ARRAY_SIZE(extcode_tab_us102)];
struct nvec_keys {
struct input_dev *input;
struct notifier_block notifier;
struct nvec_chip *nvec;
bool caps_lock;
};
static struct nvec_keys keys_dev;
static void nvec_kbd_toggle_led(void)
{
char buf[] = { NVEC_KBD, SET_LEDS, 0 };
keys_dev.caps_lock = !keys_dev.caps_lock;
if (keys_dev.caps_lock)
/* should be BIT(0) only, firmware bug? */
buf[2] = BIT(0) | BIT(1) | BIT(2);
nvec_write_async(keys_dev.nvec, buf, sizeof(buf));
}
static int nvec_keys_notifier(struct notifier_block *nb,
unsigned long event_type, void *data)
{
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)
{
struct nvec_chip *nvec = keys_dev.nvec;
char buf[] = { NVEC_KBD, SET_LEDS, 0 };
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 nvec_kbd_probe(struct platform_device *pdev)
{
struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent);
int i, j, err;
struct input_dev *idev;
char clear_leds[] = { NVEC_KBD, SET_LEDS, 0 },
enable_kbd[] = { NVEC_KBD, ENABLE_KBD },
cnfg_wake[] = { NVEC_KBD, CNFG_WAKE, true, true },
cnfg_wake_key_reporting[] = { NVEC_KBD, CNFG_WAKE_KEY_REPORTING,
true };
j = 0;
for (i = 0; i < ARRAY_SIZE(code_tab_102us); ++i)
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, enable_kbd, 2);
/* configures wake on special keys */
nvec_write_async(nvec, cnfg_wake, 4);
/* enable wake key reporting */
nvec_write_async(nvec, cnfg_wake_key_reporting, 3);
/* Disable caps lock LED */
nvec_write_async(nvec, clear_leds, sizeof(clear_leds));
return 0;
fail:
input_free_device(idev);
return err;
}
static int nvec_kbd_remove(struct platform_device *pdev)
{
struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent);
char disable_kbd[] = { NVEC_KBD, DISABLE_KBD },
uncnfg_wake_key_reporting[] = { NVEC_KBD, CNFG_WAKE_KEY_REPORTING,
false };
nvec_write_async(nvec, uncnfg_wake_key_reporting, 3);
nvec_write_async(nvec, disable_kbd, 2);
nvec_unregister_notifier(nvec, &keys_dev.notifier);
input_unregister_device(keys_dev.input);
return 0;
}
static struct platform_driver nvec_kbd_driver = {
.probe = nvec_kbd_probe,
.remove = nvec_kbd_remove,
.driver = {
.name = "nvec-kbd",
.owner = THIS_MODULE,
},
};
module_platform_driver(nvec_kbd_driver);
MODULE_AUTHOR("Marc Dietrich <marvin24@gmx.de>");
MODULE_DESCRIPTION("NVEC keyboard driver");
MODULE_ALIAS("platform:nvec-kbd");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,98 @@
/*
* nvec_paz00: OEM specific driver for Compal PAZ00 based devices
*
* Copyright (C) 2011 The AC100 Kernel Team <ac100@lists.launchpad.net>
*
* Authors: Ilya Petrov <ilya.muromec@gmail.com>
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
*/
#include <linux/module.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/leds.h>
#include <linux/platform_device.h>
#include "nvec.h"
#define to_nvec_led(led_cdev) \
container_of(led_cdev, struct nvec_led, cdev)
#define NVEC_LED_REQ {'\x0d', '\x10', '\x45', '\x10', '\x00'}
#define NVEC_LED_MAX 8
struct nvec_led {
struct led_classdev cdev;
struct nvec_chip *nvec;
};
static void nvec_led_brightness_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct nvec_led *led = to_nvec_led(led_cdev);
unsigned char buf[] = NVEC_LED_REQ;
buf[4] = value;
nvec_write_async(led->nvec, buf, sizeof(buf));
led->cdev.brightness = value;
}
static int nvec_paz00_probe(struct platform_device *pdev)
{
struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent);
struct nvec_led *led;
int ret = 0;
led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL);
if (led == NULL)
return -ENOMEM;
led->cdev.max_brightness = NVEC_LED_MAX;
led->cdev.brightness_set = nvec_led_brightness_set;
led->cdev.name = "paz00-led";
led->cdev.flags |= LED_CORE_SUSPENDRESUME;
led->nvec = nvec;
platform_set_drvdata(pdev, led);
ret = led_classdev_register(&pdev->dev, &led->cdev);
if (ret < 0)
return ret;
/* to expose the default value to userspace */
led->cdev.brightness = 0;
return 0;
}
static int nvec_paz00_remove(struct platform_device *pdev)
{
struct nvec_led *led = platform_get_drvdata(pdev);
led_classdev_unregister(&led->cdev);
return 0;
}
static struct platform_driver nvec_paz00_driver = {
.probe = nvec_paz00_probe,
.remove = nvec_paz00_remove,
.driver = {
.name = "nvec-paz00",
.owner = THIS_MODULE,
},
};
module_platform_driver(nvec_paz00_driver);
MODULE_AUTHOR("Ilya Petrov <ilya.muromec@gmail.com>");
MODULE_DESCRIPTION("Tegra NVEC PAZ00 driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:nvec-paz00");

View File

@@ -0,0 +1,443 @@
/*
* 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"
#define GET_SYSTEM_STATUS 0x00
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[] = { NVEC_BAT, SLOT_STATUS };
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[] = { NVEC_SYS, GET_SYSTEM_STATUS };
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] = NVEC_BAT;
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 nvec_power_probe(struct platform_device *pdev)
{
struct power_supply *psy;
struct nvec_power *power;
struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent);
power = devm_kzalloc(&pdev->dev, sizeof(struct nvec_power), GFP_NOWAIT);
if (power == NULL)
return -ENOMEM;
dev_set_drvdata(&pdev->dev, power);
power->nvec = nvec;
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:
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 int nvec_power_remove(struct platform_device *pdev)
{
struct nvec_power *power = platform_get_drvdata(pdev);
cancel_delayed_work_sync(&power->poller);
nvec_unregister_notifier(power->nvec, &power->notifier);
switch (pdev->id) {
case AC:
power_supply_unregister(&nvec_psy);
break;
case BAT:
power_supply_unregister(&nvec_bat_psy);
}
return 0;
}
static struct platform_driver nvec_power_driver = {
.probe = nvec_power_probe,
.remove = nvec_power_remove,
.driver = {
.name = "nvec-power",
.owner = THIS_MODULE,
}
};
module_platform_driver(nvec_power_driver);
MODULE_AUTHOR("Ilya Petrov <ilya.muromec@gmail.com>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("NVEC battery and AC driver");
MODULE_ALIAS("platform:nvec-power");

View File

@@ -0,0 +1,188 @@
/*
* 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 PACKET_SIZE 6
#define ENABLE_MOUSE 0xf4
#define DISABLE_MOUSE 0xf5
#define PSMOUSE_RST 0xff
#ifdef NVEC_PS2_DEBUG
#define NVEC_PHD(str, buf, len) \
print_hex_dump(KERN_DEBUG, str, DUMP_PREFIX_NONE, \
16, 1, buf, len, false)
#else
#define NVEC_PHD(str, buf, len)
#endif
enum ps2_subcmds {
SEND_COMMAND = 1,
RECEIVE_N,
AUTO_RECEIVE_N,
CANCEL_AUTO_RECEIVE,
};
struct nvec_ps2 {
struct 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[] = { NVEC_PS2, AUTO_RECEIVE_N, PACKET_SIZE };
return nvec_write_async(ps2_dev.nvec, buf, sizeof(buf));
}
static void ps2_stopstreaming(struct serio *ser_dev)
{
unsigned char buf[] = { NVEC_PS2, CANCEL_AUTO_RECEIVE };
nvec_write_async(ps2_dev.nvec, buf, sizeof(buf));
}
static int ps2_sendcommand(struct serio *ser_dev, unsigned char cmd)
{
unsigned char buf[] = { NVEC_PS2, SEND_COMMAND, ENABLE_MOUSE, 1 };
buf[2] = cmd & 0xff;
dev_dbg(&ser_dev->dev, "Sending ps2 cmd %02x\n", cmd);
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 nvec_mouse_probe(struct platform_device *pdev)
{
struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent);
struct serio *ser_dev;
char mouse_reset[] = { NVEC_PS2, SEND_COMMAND, PSMOUSE_RST, 3 };
ser_dev = kzalloc(sizeof(struct serio), GFP_KERNEL);
if (ser_dev == NULL)
return -ENOMEM;
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, sizeof(mouse_reset));
return 0;
}
static int nvec_mouse_remove(struct platform_device *pdev)
{
struct nvec_chip *nvec = dev_get_drvdata(pdev->dev.parent);
ps2_sendcommand(ps2_dev.ser_dev, DISABLE_MOUSE);
ps2_stopstreaming(ps2_dev.ser_dev);
nvec_unregister_notifier(nvec, &ps2_dev.notifier);
serio_unregister_port(ps2_dev.ser_dev);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int nvec_mouse_suspend(struct device *dev)
{
/* disable mouse */
ps2_sendcommand(ps2_dev.ser_dev, DISABLE_MOUSE);
/* send cancel autoreceive */
ps2_stopstreaming(ps2_dev.ser_dev);
return 0;
}
static int nvec_mouse_resume(struct device *dev)
{
/* start streaming */
ps2_startstreaming(ps2_dev.ser_dev);
/* enable mouse */
ps2_sendcommand(ps2_dev.ser_dev, ENABLE_MOUSE);
return 0;
}
#endif
static const SIMPLE_DEV_PM_OPS(nvec_mouse_pm_ops, nvec_mouse_suspend,
nvec_mouse_resume);
static struct platform_driver nvec_mouse_driver = {
.probe = nvec_mouse_probe,
.remove = nvec_mouse_remove,
.driver = {
.name = "nvec-mouse",
.owner = THIS_MODULE,
.pm = &nvec_mouse_pm_ops,
},
};
module_platform_driver(nvec_mouse_driver);
MODULE_DESCRIPTION("NVEC mouse driver");
MODULE_AUTHOR("Marc Dietrich <marvin24@gmx.de>");
MODULE_ALIAS("platform:nvec-mouse");
MODULE_LICENSE("GPL");