Commit 32890b98 authored by Marc Dietrich's avatar Marc Dietrich Committed by Greg Kroah-Hartman

Staging: initial version of the nvec driver

This is an implementation of a NVidia compliant embedded controller
protocol driver. It is used on some ARM-Tegra boards for device
communication.
Signed-off-by: default avatarMarc Dietrich <marvin24@gmx.de>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 23a42311
......@@ -175,5 +175,7 @@ source "drivers/staging/altera-stapl/Kconfig"
source "drivers/staging/mei/Kconfig"
source "drivers/staging/nvec/Kconfig"
endif # !STAGING_EXCLUDE_BUILD
endif # STAGING
......@@ -70,3 +70,4 @@ obj-$(CONFIG_TOUCHSCREEN_CLEARPAD_TM1217) += cptm1217/
obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_I2C_RMI4) += ste_rmi4/
obj-$(CONFIG_DRM_PSB) += gma500/
obj-$(CONFIG_INTEL_MEI) += mei/
obj-$(CONFIG_MFD_NVEC) += nvec/
config MFD_NVEC
bool "NV Tegra Embedded Controller SMBus Interface"
depends on I2C && GPIOLIB && ARCH_TEGRA
help
Say Y here to enable support for a nVidia compliant embedded
controller.
config KEYBOARD_NVEC
bool "Keyboard on nVidia compliant EC"
depends on MFD_NVEC
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
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
help
Say Y to enable support for battery and charger interface for
nVidia compliant embedded controllers.
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
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
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
/*
* 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[] = {
KEY_GRAVE, // 0x00
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,
KEY_Q, // 0x10
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,
KEY_D, // 0x20
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,
KEY_B, // 0x30
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,
KEY_F6, // 0x40
KEY_F7,
KEY_F8,
KEY_F9,
KEY_F10,
KEY_FN,
0, //VK_SCROLL
KEY_KP7,
KEY_KP8,
KEY_KP9,
KEY_KPMINUS,
KEY_KP4,
KEY_KP5,
KEY_KP6,
KEY_KPPLUS,
KEY_KP1,
KEY_KP2, // 0x50
KEY_KP3,
KEY_KP0,
KEY_KPDOT,
KEY_MENU, //VK_SNAPSHOT
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
};
static unsigned short extcode_tab_us102[] = {
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0, // 0xE0 0x10
0,
0,
0,
0,
0,
0,
0,
0,
0, //VK_MEDIA_NEXT_TRACK,
0,
0,
0, //VK_RETURN,
KEY_RIGHTCTRL, //VK_RCONTROL,
0,
0,
KEY_MUTE, // 0xE0 0x20
0, //VK_LAUNCH_APP1
0, //VK_MEDIA_PLAY_PAUSE
0,
0, //VK_MEDIA_STOP
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
KEY_VOLUMEUP, // 0xE0 0x30
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
0,
KEY_LEFT,
0,
KEY_RIGHT,
0,
KEY_END,
KEY_DOWN, // 0xE0 0x50
KEY_PAGEDOWN, //VK_NEXT
KEY_INSERT,
KEY_DELETE,
0,
0,
0,
0,
0,
0,
0,
KEY_LEFTMETA, //VK_LWIN
0, //VK_RWIN
KEY_ESC, //VK_APPS
KEY_KPMINUS, //for power button workaround
0,
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
};
static unsigned short* code_tabs[] = {code_tab_102us, extcode_tab_us102 };
This diff is collapsed.
#ifndef __LINUX_MFD_NVEC
#define __LINUX_MFD_NVEC
#include <linux/semaphore.h>
typedef enum {
NVEC_2BYTES,
NVEC_3BYTES,
NVEC_VAR_SIZE
} nvec_size;
typedef enum {
NOT_REALLY,
YES,
NOT_AT_ALL,
} how_care;
typedef enum {
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;
struct nvec_msg {
unsigned char *data;
unsigned short size;
unsigned short pos;
struct list_head node;
};
struct nvec_subdev {
const char *name;
void *platform_data;
int id;
};
struct nvec_platform_data {
int num_subdevs;
int i2c_addr;
int gpio;
int irq;
int base;
int size;
char clock[16];
struct nvec_subdev *subdevs;
};
struct nvec_chip {
struct device *dev;
int gpio;
int irq;
unsigned char *i2c_regs;
nvec_state state;
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;
/* sync write stuff */
struct semaphore sync_write_mutex;
struct completion sync_write;
u16 sync_write_pending;
struct nvec_msg *last_sync_msg;
};
extern void nvec_write_async(struct nvec_chip *nvec, unsigned char *data, short size);
extern int nvec_register_notifier(struct nvec_chip *nvec,
struct notifier_block *nb, unsigned int events);
extern int nvec_unregister_notifier(struct device *dev,
struct notifier_block *nb, unsigned int events);
const char *nvec_send_msg(unsigned char *src, unsigned char *dst_size, how_care care_resp, void (*rt_handler)(unsigned char *data));
extern int nvec_ps2(struct nvec_chip *nvec);
extern int nvec_kbd_init(struct nvec_chip *nvec);
#define I2C_CNFG 0x00
#define I2C_CNFG_PACKET_MODE_EN (1<<10)
#define I2C_CNFG_NEW_MASTER_SFM (1<<11)
#define I2C_CNFG_DEBOUNCE_CNT_SHIFT 12
#define I2C_SL_CNFG 0x20
#define I2C_SL_NEWL (1<<2)
#define I2C_SL_NACK (1<<1)
#define I2C_SL_RESP (1<<0)
#define I2C_SL_IRQ (1<<3)
#define END_TRANS (1<<4)
#define RCVD (1<<2)
#define RNW (1<<1)
#define I2C_SL_RCVD 0x24
#define I2C_SL_STATUS 0x28
#define I2C_SL_ADDR1 0x2c
#define I2C_SL_ADDR2 0x30
#define I2C_SL_DELAY_COUNT 0x3c
#endif
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/delay.h>
#include "nvec-keytable.h"
#include "nvec.h"
#define ACK_KBD_EVENT {'\x05','\xed','\x01'}
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;
};
static struct nvec_keys keys_dev;
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) {
nvec_size _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;
input_report_key(keys_dev.input, code_tabs[_size][code], !state);
input_sync(keys_dev.input);
return NOTIFY_STOP;
}
return NOTIFY_DONE;
}
static int nvec_kbd_event(struct input_dev *dev, unsigned int type,
unsigned int code, int value)
{
unsigned char buf[] = ACK_KBD_EVENT;
struct nvec_chip *nvec = keys_dev.nvec;
if(type==EV_REP)
return 0;
if(type!=EV_LED)
return -1;
if(code!=LED_CAPSL)
return -1;
buf[2] = !!value;
nvec_write_async(nvec, buf, sizeof(buf));
return 0;
}
int __init nvec_kbd_init(struct nvec_chip *nvec)
{
int i, j, err;
struct input_dev *idev;
j = 0;
for(i = 0; i < ARRAY_SIZE(code_tab_102us); ++i)
keycodes[j++] = code_tab_102us[i];
for(i = 0; i < ARRAY_SIZE(extcode_tab_us102); ++i)
keycodes[j++]=extcode_tab_us102[i];
idev = input_allocate_device();
idev->name = "Tegra nvec keyboard";
idev->phys = "i2c3_slave/nvec";
idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP) | BIT_MASK(EV_LED);
idev->ledbit[0] = BIT_MASK(LED_CAPSL);
idev->event = nvec_kbd_event;
idev->keycode = keycodes;
idev->keycodesize = sizeof(unsigned char);
idev->keycodemax = ARRAY_SIZE(keycodes);
for( i = 0; i < ARRAY_SIZE(keycodes); ++i)
set_bit(keycodes[i], idev->keybit);
clear_bit(0, idev->keybit);
err = input_register_device(idev);
if(err)
goto fail;
keys_dev.input = idev;
keys_dev.notifier.notifier_call = nvec_keys_notifier;
keys_dev.nvec = nvec;
nvec_register_notifier(nvec, &keys_dev.notifier, 0);
/* Enable keyboard */
nvec_write_async(nvec, "\x05\xf4", 2);
/* keyboard reset? */
nvec_write_async(nvec, "\x05\x03\x01\x01", 4);
nvec_write_async(nvec, "\x05\x04\x01", 3);
nvec_write_async(nvec, "\x06\x01\xff\x03", 4);
/* FIXME
wait until keyboard reset is finished
or until we have a sync write */
mdelay(1000);
return 0;
fail:
input_free_device(idev);
return err;
}
This diff is collapsed.
#include <linux/slab.h>
#include <linux/serio.h>
#include <linux/delay.h>
#include "nvec.h"
#define START_STREAMING {'\x06','\x03','\x01'}
#define STOP_STREAMING {'\x06','\x04'}
#define SEND_COMMAND {'\x06','\x01','\xf4','\x01'}
struct nvec_ps2
{
struct serio *ser_dev;
struct notifier_block notifier;
struct nvec_chip *nvec;
};
static struct nvec_ps2 ps2_dev;
static int ps2_startstreaming(struct serio *ser_dev)
{
unsigned char buf[] = START_STREAMING;
nvec_write_async(ps2_dev.nvec, buf, sizeof(buf));
return 0;
}
static void ps2_stopstreaming(struct serio *ser_dev)
{
unsigned char buf[] = STOP_STREAMING;
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;
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;
}
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:
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;
}
return NOTIFY_DONE;
}
int __init nvec_ps2(struct nvec_chip *nvec)
{
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;
strlcpy(ser_dev->name, "NVEC PS2", sizeof(ser_dev->name));
strlcpy(ser_dev->phys, "NVEC I2C slave", 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, "\x06\x01\xff\x03", 4);
return 0;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment