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: Marc Dietrich <marvin24@gmx.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
This commit is contained in:
Marc Dietrich 2011-05-19 16:34:42 +02:00 коммит произвёл Greg Kroah-Hartman
Родитель 23a4231150
Коммит 32890b9830
12 изменённых файлов: 1543 добавлений и 0 удалений

Просмотреть файл

@ -175,5 +175,7 @@ source "drivers/staging/altera-stapl/Kconfig"
source "drivers/staging/mei/Kconfig" source "drivers/staging/mei/Kconfig"
source "drivers/staging/nvec/Kconfig"
endif # !STAGING_EXCLUDE_BUILD endif # !STAGING_EXCLUDE_BUILD
endif # STAGING endif # STAGING

Просмотреть файл

@ -70,3 +70,4 @@ obj-$(CONFIG_TOUCHSCREEN_CLEARPAD_TM1217) += cptm1217/
obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_I2C_RMI4) += ste_rmi4/ obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_I2C_RMI4) += ste_rmi4/
obj-$(CONFIG_DRM_PSB) += gma500/ obj-$(CONFIG_DRM_PSB) += gma500/
obj-$(CONFIG_INTEL_MEI) += mei/ obj-$(CONFIG_INTEL_MEI) += mei/
obj-$(CONFIG_MFD_NVEC) += nvec/

Просмотреть файл

@ -0,0 +1,27 @@
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.

Просмотреть файл

@ -0,0 +1,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

Просмотреть файл

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

Просмотреть файл

@ -0,0 +1,8 @@
ToDo list (incomplete, unordered)
- convert mouse, keyboard, and power to platform devices
- add copyright / driver author / license
- add compile as module support
- move nvec devices to mfd cells?
- adjust to kernel style

Просмотреть файл

@ -0,0 +1,266 @@
/*
* 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 };

468
drivers/staging/nvec/nvec.c Normal file
Просмотреть файл

@ -0,0 +1,468 @@
// #define DEBUG
/* ToDo list (incomplete, unorderd)
- convert mouse, keyboard, and power to platform devices
*/
#include <asm/io.h>
#include <asm/irq.h>
#include <linux/completion.h>
#include <linux/interrupt.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/notifier.h>
#include <linux/workqueue.h>
#include <linux/platform_device.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'};
static struct nvec_chip *nvec_power_handle;
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);
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)
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");
return NOTIFY_OK;
}
void nvec_write_async(struct nvec_chip *nvec, unsigned char *data, short size)
{
struct nvec_msg *msg = kzalloc(sizeof(struct nvec_msg), GFP_NOWAIT);
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);
list_add_tail(&msg->node, &nvec->tx_data);
gpio_set_value(nvec->gpio, 0);
}
EXPORT_SYMBOL(nvec_write_async);
static void nvec_request_master(struct work_struct *work)
{
struct nvec_chip *nvec = container_of(work, struct nvec_chip, tx_work);
if(!list_empty(&nvec->tx_data)) {
gpio_set_value(nvec->gpio, 0);
}
}
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]);
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");
}
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 */
static void nvec_dispatch(struct work_struct *work)
{
struct nvec_chip *nvec = container_of(work, struct nvec_chip, rx_work);
struct nvec_msg *msg;
while(!list_empty(&nvec->rx_data))
{
msg = list_first_entry(&nvec->rx_data, struct nvec_msg, node);
list_del_init(&msg->node);
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);
}
}
}
}
static irqreturn_t i2c_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;
status = readl(i2c_regs + I2C_SL_STATUS);
if(!(status & I2C_SL_IRQ))
{
dev_warn(nvec->dev, "nvec Spurious IRQ\n");
//Yup, handled. ahum.
goto 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);
} else {
kfree(nvec->rx->data);
kfree(nvec->rx);
}
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");
} else {
//Master wants something from us from a communication we've already started
// dev_dbg(nvec->dev, "Read comm cont !\n");
}
//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;
} 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;
}
}
writel(to_send, i2c_regs + I2C_SL_RCVD);
gpio_set_value(nvec->gpio, 1);
dev_dbg(nvec->dev, "nvec sent %x\n", to_send);
goto handled;
} else {
received = readl(i2c_regs + I2C_SL_RCVD);
//Workaround?
if(status & RCVD) {
writel(0, i2c_regs + I2C_SL_RCVD);
goto handled;
}
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)
{
struct platform_device *pdev;
pdev = platform_device_alloc(subdev->name, subdev->id);
pdev->dev.parent = nvec->dev;
pdev->dev.platform_data = subdev->platform_data;
return platform_device_add(pdev);
}
static void tegra_init_i2c_slave(struct nvec_platform_data *pdata, unsigned char *i2c_regs,
struct clk *i2c_clk)
{
u32 val;
clk_enable(i2c_clk);
tegra_periph_reset_assert(i2c_clk);
udelay(2);
tegra_periph_reset_deassert(i2c_clk);
writel(pdata->i2c_addr>>1, i2c_regs + I2C_SL_ADDR1);
writel(0, i2c_regs + I2C_SL_ADDR2);
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);
clk_disable(i2c_clk);
}
static void nvec_power_off(void)
{
nvec_write_async(nvec_power_handle, EC_DISABLE_EVENT_REPORTING, 3);
nvec_write_async(nvec_power_handle, "\x04\x01", 2);
}
static int __devinit tegra_nvec_probe(struct platform_device *pdev)
{
int err, i, 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;
nvec = kzalloc(sizeof(struct nvec_chip), GFP_KERNEL);
if(nvec == NULL) {
dev_err(&pdev->dev, "failed to reserve memory\n");
return -ENOMEM;
}
platform_set_drvdata(pdev, nvec);
nvec->dev = &pdev->dev;
nvec->gpio = pdata->gpio;
nvec->irq = pdata->irq;
/*
i2c_clk=clk_get_sys(NULL, "i2c");
if(IS_ERR_OR_NULL(i2c_clk))
printk(KERN_ERR"No such clock tegra-i2c.2\n");
else
clk_enable(i2c_clk);
*/
i2c_regs = ioremap(pdata->base, pdata->size);
if(!i2c_regs) {
dev_err(nvec->dev, "failed to ioremap registers\n");
goto failed;
}
nvec->i2c_regs = i2c_regs;
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;
}
tegra_init_i2c_slave(pdata, i2c_regs, i2c_clk);
err = request_irq(nvec->irq, i2c_interrupt, IRQF_DISABLED, "nvec", nvec);
if(err) {
dev_err(nvec->dev, "couldn't request irq");
goto failed;
}
clk_enable(i2c_clk);
clk_set_rate(i2c_clk, 8*80000);
/* Set the gpio to low when we've got something to say */
err = gpio_request(nvec->gpio, "nvec gpio");
if(err < 0)
dev_err(nvec->dev, "couldn't request gpio\n");
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_LIST_HEAD(&nvec->rx_data);
INIT_WORK(&nvec->rx_work, nvec_dispatch);
INIT_WORK(&nvec->tx_work, nvec_request_master);
/* enable event reporting */
nvec_write_async(nvec, EC_ENABLE_EVENT_REPORTING,
sizeof(EC_ENABLE_EVENT_REPORTING));
nvec_kbd_init(nvec);
#ifdef CONFIG_SERIO_NVEC_PS2
nvec_ps2(nvec);
#endif
/* setup subdevs */
for (i = 0; i < pdata->num_subdevs; i++) {
ret = nvec_add_subdev(nvec, &pdata->subdevs[i]);
}
nvec->nvec_status_notifier.notifier_call = nvec_status_notifier;
nvec_register_notifier(nvec, &nvec->nvec_status_notifier, 0);
nvec_power_handle = nvec;
pm_power_off = nvec_power_off;
/* Get Firmware Version */
msg = nvec_write_sync(nvec, EC_GET_FIRMWARE_VERSION,
sizeof(EC_GET_FIRMWARE_VERSION));
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);
/* unmute speakers? */
nvec_write_async(nvec, "\x0d\x10\x59\x94", 4);
/* enable lid switch event */
nvec_write_async(nvec, "\x01\x01\x01\x00\x00\x02\x00", 7);
/* enable power button event */
nvec_write_async(nvec, "\x01\x01\x01\x00\x00\x80\x00", 7);
return 0;
failed:
kfree(nvec);
return -ENOMEM;
}
static int __devexit tegra_nvec_remove(struct platform_device *pdev)
{
// TODO: unregister
return 0;
}
#ifdef CONFIG_PM
static int tegra_nvec_suspend(struct platform_device *pdev, pm_message_t state)
{
struct nvec_chip *nvec = platform_get_drvdata(pdev);
dev_dbg(nvec->dev, "suspending\n");
nvec_write_async(nvec, EC_DISABLE_EVENT_REPORTING, 3);
nvec_write_async(nvec, "\x04\x02", 2);
return 0;
}
static int tegra_nvec_resume(struct platform_device *pdev) {
struct nvec_chip *nvec = platform_get_drvdata(pdev);
dev_dbg(nvec->dev, "resuming\n");
nvec_write_async(nvec, EC_ENABLE_EVENT_REPORTING, 3);
return 0;
}
#else
#define tegra_nvec_suspend NULL
#define tegra_nvec_resume NULL
#endif
static struct platform_driver nvec_device_driver =
{
.probe = tegra_nvec_probe,
.remove = __devexit_p(tegra_nvec_remove),
.suspend = tegra_nvec_suspend,
.resume = tegra_nvec_resume,
.driver = {
.name = "nvec",
.owner = THIS_MODULE,
}
};
static int __init tegra_nvec_init(void)
{
return platform_driver_register(&nvec_device_driver);
}
module_init(tegra_nvec_init);
MODULE_ALIAS("platform:nvec");

110
drivers/staging/nvec/nvec.h Normal file
Просмотреть файл

@ -0,0 +1,110 @@
#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

Просмотреть файл

@ -0,0 +1,122 @@
#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;
}

Просмотреть файл

@ -0,0 +1,418 @@
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/power_supply.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/delay.h>
#include "nvec.h"
struct nvec_power
{
struct notifier_block notifier;
struct delayed_work poller;
struct nvec_chip *nvec;
int on;
int bat_present;
int bat_status;
int bat_voltage_now;
int bat_current_now;
int bat_current_avg;
int time_remain;
int charge_full_design;
int charge_last_full;
int critical_capacity;
int capacity_remain;
int bat_temperature;
int bat_cap;
int bat_type_enum;
char bat_manu[30];
char bat_model[30];
char bat_type[30];
};
enum {
SLOT_STATUS,
VOLTAGE,
TIME_REMAINING,
CURRENT,
AVERAGE_CURRENT,
AVERAGING_TIME_INTERVAL,
CAPACITY_REMAINING,
LAST_FULL_CHARGE_CAPACITY,
DESIGN_CAPACITY,
CRITICAL_CAPACITY,
TEMPERATURE,
MANUFACTURER,
MODEL,
TYPE,
};
enum {
AC,
BAT,
};
struct bat_response {
u8 event_type;
u8 length;
u8 sub_type;
u8 status;
union { /* payload */
char plc[30];
u16 plu;
s16 pls;
};
};
static struct power_supply nvec_bat_psy;
static struct power_supply nvec_psy;
static int nvec_power_notifier(struct notifier_block *nb,
unsigned long event_type, void *data)
{
struct nvec_power *power = container_of(nb, struct nvec_power, notifier);
struct bat_response *res = (struct bat_response *)data;
if (event_type != NVEC_SYS)
return NOTIFY_DONE;
if(res->sub_type == 0)
{
if (power->on != res->plu)
{
power->on = res->plu;
power_supply_changed(&nvec_psy);
}
return NOTIFY_STOP;
}
return NOTIFY_OK;
}
static const int bat_init[] =
{
LAST_FULL_CHARGE_CAPACITY, DESIGN_CAPACITY, CRITICAL_CAPACITY,
MANUFACTURER, MODEL, TYPE,
};
static void get_bat_mfg_data(struct nvec_power *power)
{
int i;
char buf[] = { '\x02', '\x00' };
for (i = 0; i < ARRAY_SIZE(bat_init); i++)
{
buf[1] = bat_init[i];
nvec_write_async(power->nvec, buf, 2);
}
}
static int nvec_power_bat_notifier(struct notifier_block *nb,
unsigned long event_type, void *data)
{
struct nvec_power *power = container_of(nb, struct nvec_power, notifier);
struct bat_response *res = (struct bat_response *)data;
int status_changed = 0;
if (event_type != NVEC_BAT)
return NOTIFY_DONE;
switch(res->sub_type)
{
case SLOT_STATUS:
if (res->plc[0] & 1)
{
if (power->bat_present == 0)
{
status_changed = 1;
get_bat_mfg_data(power);
}
power->bat_present = 1;
switch ((res->plc[0] >> 1) & 3)
{
case 0:
power->bat_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
break;
case 1:
power->bat_status = POWER_SUPPLY_STATUS_CHARGING;
break;
case 2:
power->bat_status = POWER_SUPPLY_STATUS_DISCHARGING;
break;
default:
power->bat_status = POWER_SUPPLY_STATUS_UNKNOWN;
}
} else {
if (power->bat_present == 1)
status_changed = 1;
power->bat_present = 0;
power->bat_status = POWER_SUPPLY_STATUS_UNKNOWN;
}
power->bat_cap = res->plc[1];
if (status_changed)
power_supply_changed(&nvec_bat_psy);
break;
case VOLTAGE:
power->bat_voltage_now = res->plu * 1000;
break;
case TIME_REMAINING:
power->time_remain = res->plu * 3600;
break;
case CURRENT:
power->bat_current_now = res->pls * 1000;
break;
case AVERAGE_CURRENT:
power->bat_current_avg = res->pls * 1000;
break;
case CAPACITY_REMAINING:
power->capacity_remain = res->plu * 1000;
break;
case LAST_FULL_CHARGE_CAPACITY:
power->charge_last_full = res->plu * 1000;
break;
case DESIGN_CAPACITY:
power->charge_full_design = res->plu * 1000;
break;
case CRITICAL_CAPACITY:
power->critical_capacity = res->plu * 1000;
break;
case TEMPERATURE:
power->bat_temperature = res->plu - 2732;
break;
case MANUFACTURER:
memcpy(power->bat_manu, &res->plc, res->length-2);
power->bat_model[res->length-2] = '\0';
break;
case MODEL:
memcpy(power->bat_model, &res->plc, res->length-2);
power->bat_model[res->length-2] = '\0';
break;
case TYPE:
memcpy(power->bat_type, &res->plc, res->length-2);
power->bat_type[res->length-2] = '\0';
/* this differs a little from the spec
fill in more if you find some */
if (!strncmp(power->bat_type, "Li", 30))
power->bat_type_enum = POWER_SUPPLY_TECHNOLOGY_LION;
else
power->bat_type_enum = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
break;
default:
return NOTIFY_STOP;
}
return NOTIFY_STOP;
}
static int nvec_power_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct nvec_power *power = dev_get_drvdata(psy->dev->parent);
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
val->intval = power->on;
break;
default:
return -EINVAL;
}
return 0;
}
static int nvec_battery_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct nvec_power *power = dev_get_drvdata(psy->dev->parent);
switch(psp)
{
case POWER_SUPPLY_PROP_STATUS:
val->intval = power->bat_status;
break;
case POWER_SUPPLY_PROP_CAPACITY:
val->intval = power->bat_cap;
break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = power->bat_present;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
val->intval = power->bat_voltage_now;
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
val->intval = power->bat_current_now;
break;
case POWER_SUPPLY_PROP_CURRENT_AVG:
val->intval = power->bat_current_avg;
break;
case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
val->intval = power->time_remain;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
val->intval = power->charge_full_design;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL:
val->intval = power->charge_last_full;
break;
case POWER_SUPPLY_PROP_CHARGE_EMPTY:
val->intval = power->critical_capacity;
break;
case POWER_SUPPLY_PROP_CHARGE_NOW:
val->intval = power->capacity_remain;
break;
case POWER_SUPPLY_PROP_TEMP:
val->intval = power->bat_temperature;
break;
case POWER_SUPPLY_PROP_MANUFACTURER:
val->strval = power->bat_manu;
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = power->bat_model;
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = power->bat_type_enum;
break;
default:
return -EINVAL;
}
return 0;
}
static enum power_supply_property nvec_power_props[] = {
POWER_SUPPLY_PROP_ONLINE,
};
static enum power_supply_property nvec_battery_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
#ifdef EC_FULL_DIAG
POWER_SUPPLY_PROP_CURRENT_AVG,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
#endif
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_EMPTY,
POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_MANUFACTURER,
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_TECHNOLOGY,
};
static char *nvec_power_supplied_to[] = {
"battery",
};
static struct power_supply nvec_bat_psy = {
.name = "battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
.properties = nvec_battery_props,
.num_properties = ARRAY_SIZE(nvec_battery_props),
.get_property = nvec_battery_get_property,
};
static struct power_supply nvec_psy = {
.name = "ac",
.type = POWER_SUPPLY_TYPE_MAINS,
.supplied_to = nvec_power_supplied_to,
.num_supplicants = ARRAY_SIZE(nvec_power_supplied_to),
.properties = nvec_power_props,
.num_properties = ARRAY_SIZE(nvec_power_props),
.get_property = nvec_power_get_property,
};
static int counter = 0;
static int const bat_iter[] =
{
SLOT_STATUS, VOLTAGE, CURRENT, CAPACITY_REMAINING,
#ifdef EC_FULL_DIAG
AVERAGE_CURRENT, TEMPERATURE, TIME_REMAINING,
#endif
};
static void nvec_power_poll(struct work_struct *work)
{
char buf[] = { '\x01', '\x00' };
struct nvec_power *power = container_of(work, struct nvec_power,
poller.work);
if (counter >= ARRAY_SIZE(bat_iter))
counter = 0;
/* AC status via sys req */
nvec_write_async(power->nvec, buf, 2);
msleep(100);
/* select a battery request function via round robin
doing it all at once seems to overload the power supply */
buf[0] = '\x02'; /* battery */
buf[1] = bat_iter[counter++];
nvec_write_async(power->nvec, buf, 2);
// 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_chip *nvec = dev_get_drvdata(pdev->dev.parent);
dev_set_drvdata(&pdev->dev, power);
power->nvec = nvec;
switch (pdev->id) {
case AC:
psy = &nvec_psy;
power->notifier.notifier_call = nvec_power_notifier;
INIT_DELAYED_WORK(&power->poller, nvec_power_poll);
schedule_delayed_work(&power->poller, msecs_to_jiffies(5000));
break;
case BAT:
psy = &nvec_bat_psy;
power->notifier.notifier_call = nvec_power_bat_notifier;
break;
default:
kfree(power);
return -ENODEV;
}
nvec_register_notifier(nvec, &power->notifier, NVEC_SYS);
if (pdev->id == BAT)
get_bat_mfg_data(power);
return power_supply_register(&pdev->dev, psy);
}
static struct platform_driver nvec_power_driver = {
.probe = nvec_power_probe,
// .remove = __devexit_p(nvec_power_remove),
.driver = {
.name = "nvec-power",
.owner = THIS_MODULE,
}
};
static int __init nvec_power_init(void)
{
return platform_driver_register(&nvec_power_driver);
}
module_init(nvec_power_init);
MODULE_AUTHOR("Ilya Petrov <ilya.muromec@gmail.com>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("NVEC battery and AC driver");
MODULE_ALIAS("platform:nvec-power");

Просмотреть файл

@ -0,0 +1,103 @@
#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;
}