diff options
Diffstat (limited to 'drivers/staging/nvec')
| -rw-r--r-- | drivers/staging/nvec/Kconfig | 35 | ||||
| -rw-r--r-- | drivers/staging/nvec/Makefile | 5 | ||||
| -rw-r--r-- | drivers/staging/nvec/README | 14 | ||||
| -rw-r--r-- | drivers/staging/nvec/TODO | 8 | ||||
| -rw-r--r-- | drivers/staging/nvec/nvec-keytable.h | 307 | ||||
| -rw-r--r-- | drivers/staging/nvec/nvec.c | 990 | ||||
| -rw-r--r-- | drivers/staging/nvec/nvec.h | 183 | ||||
| -rw-r--r-- | drivers/staging/nvec/nvec_kbd.c | 193 | ||||
| -rw-r--r-- | drivers/staging/nvec/nvec_paz00.c | 98 | ||||
| -rw-r--r-- | drivers/staging/nvec/nvec_power.c | 443 | ||||
| -rw-r--r-- | drivers/staging/nvec/nvec_ps2.c | 188 | 
11 files changed, 2464 insertions, 0 deletions
diff --git a/drivers/staging/nvec/Kconfig b/drivers/staging/nvec/Kconfig new file mode 100644 index 00000000000..9475e20c3d6 --- /dev/null +++ b/drivers/staging/nvec/Kconfig @@ -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. diff --git a/drivers/staging/nvec/Makefile b/drivers/staging/nvec/Makefile new file mode 100644 index 00000000000..0db0e1f4333 --- /dev/null +++ b/drivers/staging/nvec/Makefile @@ -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 diff --git a/drivers/staging/nvec/README b/drivers/staging/nvec/README new file mode 100644 index 00000000000..9a320b7fdbe --- /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 00000000000..e5ae42a0b44 --- /dev/null +++ b/drivers/staging/nvec/TODO @@ -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 diff --git a/drivers/staging/nvec/nvec-keytable.h b/drivers/staging/nvec/nvec-keytable.h new file mode 100644 index 00000000000..1dc22cb8812 --- /dev/null +++ b/drivers/staging/nvec/nvec-keytable.h @@ -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 }; diff --git a/drivers/staging/nvec/nvec.c b/drivers/staging/nvec/nvec.c new file mode 100644 index 00000000000..90f1c4d7fa8 --- /dev/null +++ b/drivers/staging/nvec/nvec.c @@ -0,0 +1,990 @@ +/* + * 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/slab.h> +#include <linux/spinlock.h> +#include <linux/workqueue.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 const 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 ? nvec->rx->pos : 0, +				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); + +	reset_control_assert(nvec->rst); +	udelay(2); +	reset_control_deassert(nvec->rst); + +	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); +} + +#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); +} + +/* + *  Parse common device tree data + */ +static int nvec_i2c_parse_dt_pdata(struct nvec_chip *nvec) +{ +	nvec->gpio = of_get_named_gpio(nvec->dev->of_node, "request-gpios", 0); + +	if (nvec->gpio < 0) { +		dev_err(nvec->dev, "no gpio specified"); +		return -ENODEV; +	} + +	if (of_property_read_u32(nvec->dev->of_node, "slave-addr", +				&nvec->i2c_addr)) { +		dev_err(nvec->dev, "no i2c address specified"); +		return -ENODEV; +	} + +	return 0; +} + +static int tegra_nvec_probe(struct platform_device *pdev) +{ +	int err, ret; +	struct clk *i2c_clk; +	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 }; + +	if (!pdev->dev.of_node) { +		dev_err(&pdev->dev, "must be instantiated using device tree\n"); +		return -ENODEV; +	} + +	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; + +	err = nvec_i2c_parse_dt_pdata(nvec); +	if (err < 0) +		return err; + +	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->rst = devm_reset_control_get(&pdev->dev, "i2c"); +	if (IS_ERR(nvec->rst)) { +		dev_err(nvec->dev, "failed to get controller reset\n"); +		return PTR_ERR(nvec->rst); +	} + +	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); + +	/* 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"); diff --git a/drivers/staging/nvec/nvec.h b/drivers/staging/nvec/nvec.h new file mode 100644 index 00000000000..e271375053f --- /dev/null +++ b/drivers/staging/nvec/nvec.h @@ -0,0 +1,183 @@ +/* + * 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/reset.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_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 + * @i2c_clk: The clock of the I2C device + * @rst: The reset 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 reset_control *rst; +	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 diff --git a/drivers/staging/nvec/nvec_kbd.c b/drivers/staging/nvec/nvec_kbd.c new file mode 100644 index 00000000000..c17a1c3eb3c --- /dev/null +++ b/drivers/staging/nvec/nvec_kbd.c @@ -0,0 +1,193 @@ +/* + * 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 = devm_input_allocate_device(&pdev->dev); +	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) +		return err; + +	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; +} + +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); + +	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"); diff --git a/drivers/staging/nvec/nvec_paz00.c b/drivers/staging/nvec/nvec_paz00.c new file mode 100644 index 00000000000..934b796222a --- /dev/null +++ b/drivers/staging/nvec/nvec_paz00.c @@ -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"); diff --git a/drivers/staging/nvec/nvec_power.c b/drivers/staging/nvec/nvec_power.c new file mode 100644 index 00000000000..aacfcd6954a --- /dev/null +++ b/drivers/staging/nvec/nvec_power.c @@ -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"); diff --git a/drivers/staging/nvec/nvec_ps2.c b/drivers/staging/nvec/nvec_ps2.c new file mode 100644 index 00000000000..45b2f1308e0 --- /dev/null +++ b/drivers/staging/nvec/nvec_ps2.c @@ -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 = devm_kzalloc(&pdev->dev, 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");  | 
