From 32890b983086136fef8721363a2d3860f337ad53 Mon Sep 17 00:00:00 2001 From: Marc Dietrich Date: Thu, 19 May 2011 16:34:42 +0200 Subject: 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 Signed-off-by: Greg Kroah-Hartman diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index aac63cb..dfc16f9 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -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 diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index 00bed68..fa41b9c 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -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/ diff --git a/drivers/staging/nvec/Kconfig b/drivers/staging/nvec/Kconfig new file mode 100644 index 0000000..987ad48 --- /dev/null +++ b/drivers/staging/nvec/Kconfig @@ -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. diff --git a/drivers/staging/nvec/Makefile b/drivers/staging/nvec/Makefile new file mode 100644 index 0000000..4b5fcec1 --- /dev/null +++ b/drivers/staging/nvec/Makefile @@ -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 diff --git a/drivers/staging/nvec/README b/drivers/staging/nvec/README new file mode 100644 index 0000000..9a320b7 --- /dev/null +++ b/drivers/staging/nvec/README @@ -0,0 +1,14 @@ +NVEC: An NVidia compliant Embedded Controller Protocol Implemenation + +This is an implementation of the NVEC protocol used to communicate with an +embedded controller (EC) via I2C bus. The EC is an I2C master while the host +processor is the I2C slave. Requests from the host processor to the EC are +started by triggering a gpio line. + +There is no written documentation of the protocol available to the public, +but the source code[1] of the published nvec reference drivers can be a guide. +This driver is currently only used by the AC100 project[2], but it is likely, +that other Tegra boards (not yet mainlined, if ever) also use it. + +[1] e.g. http://nv-tegra.nvidia.com/gitweb/?p=linux-2.6.git;a=tree;f=arch/arm/mach-tegra/nvec;hb=android-tegra-2.6.32 +[2] http://gitorious.org/ac100, http://launchpad.net/ac100 diff --git a/drivers/staging/nvec/TODO b/drivers/staging/nvec/TODO new file mode 100644 index 0000000..77b47f7 --- /dev/null +++ b/drivers/staging/nvec/TODO @@ -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 + + diff --git a/drivers/staging/nvec/nvec-keytable.h b/drivers/staging/nvec/nvec-keytable.h new file mode 100644 index 0000000..6a1c4f7 --- /dev/null +++ b/drivers/staging/nvec/nvec-keytable.h @@ -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 }; diff --git a/drivers/staging/nvec/nvec.c b/drivers/staging/nvec/nvec.c new file mode 100644 index 0000000..1a94364 --- /dev/null +++ b/drivers/staging/nvec/nvec.c @@ -0,0 +1,468 @@ +// #define DEBUG + +/* ToDo list (incomplete, unorderd) + - convert mouse, keyboard, and power to platform devices +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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_postx_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"); diff --git a/drivers/staging/nvec/nvec.h b/drivers/staging/nvec/nvec.h new file mode 100644 index 0000000..a2d82dc --- /dev/null +++ b/drivers/staging/nvec/nvec.h @@ -0,0 +1,110 @@ +#ifndef __LINUX_MFD_NVEC +#define __LINUX_MFD_NVEC + +#include + +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 diff --git a/drivers/staging/nvec/nvec_kbd.c b/drivers/staging/nvec/nvec_kbd.c new file mode 100644 index 0000000..9a98507 --- /dev/null +++ b/drivers/staging/nvec/nvec_kbd.c @@ -0,0 +1,122 @@ +#include +#include +#include +#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; +} diff --git a/drivers/staging/nvec/nvec_power.c b/drivers/staging/nvec/nvec_power.c new file mode 100644 index 0000000..df164ad --- /dev/null +++ b/drivers/staging/nvec/nvec_power.c @@ -0,0 +1,418 @@ +#include +#include +#include +#include +#include +#include +#include +#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 "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("NVEC battery and AC driver"); +MODULE_ALIAS("platform:nvec-power"); diff --git a/drivers/staging/nvec/nvec_ps2.c b/drivers/staging/nvec/nvec_ps2.c new file mode 100644 index 0000000..6bb9430 --- /dev/null +++ b/drivers/staging/nvec/nvec_ps2.c @@ -0,0 +1,103 @@ +#include +#include +#include +#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; +} -- cgit v0.10.2