Signed-off-by: Andrey Danin <danind...@mail.ru> CC: Stephen Warren <swar...@nvidia.com> CC: Marc Dietrich <marvi...@gmx.de> CC: Julian Andres Klode <j...@jak-linux.org> CC: ac...@lists.launchpad.net --- Changes for v2: - NVEC driver was reworked to use tegra-i2c
arch/arm/include/asm/arch-tegra/tegra_nvec.h | 130 ++++++++++++ board/nvidia/common/board.c | 12 ++ drivers/i2c/Makefile | 1 + drivers/i2c/tegra_nvec.c | 294 ++++++++++++++++++++++++++ include/fdtdec.h | 1 + lib/fdtdec.c | 1 + 6 files changed, 439 insertions(+) create mode 100644 arch/arm/include/asm/arch-tegra/tegra_nvec.h create mode 100644 drivers/i2c/tegra_nvec.c diff --git a/arch/arm/include/asm/arch-tegra/tegra_nvec.h b/arch/arm/include/asm/arch-tegra/tegra_nvec.h new file mode 100644 index 0000000..f1cec0d --- /dev/null +++ b/arch/arm/include/asm/arch-tegra/tegra_nvec.h @@ -0,0 +1,130 @@ +/* + * (C) Copyright 2014 + * Andrey Danin <danind...@mail.ru> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef _TEGRA_NVEC_H_ +#define _TEGRA_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_type { + NVEC_KEYBOARD = 0, + 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, + NVEC_LAST_MSG, +}; + +enum nvec_event_size { + NVEC_2BYTES, + NVEC_3BYTES, + NVEC_VAR_SIZE, +}; + +enum sys_subcmds { + SYS_GET_STATUS, + SYS_CNFG_EVENT_REPORTING, + SYS_ACK_STATUS, + SYS_CNFG_WAKE = 0xfd, +}; + +enum kbd_subcmds { + CNFG_WAKE = 3, + CNFG_WAKE_KEY_REPORTING, + SET_LEDS = 0xed, + ENABLE_KBD = 0xf4, + DISABLE_KBD, +}; + +enum cntl_subcmds { + CNTL_RESET_EC = 0x00, + CNTL_SELF_TEST = 0x01, + CNTL_NOOP = 0x02, + CNTL_GET_EC_SPEC_VER = 0x10, + CNTL_GET_FIRMWARE_VERSION = 0x15, +}; + +enum nvec_sleep_subcmds { + GLOBAL_EVENTS, + AP_PWR_DOWN, + AP_SUSPEND, +}; + +enum ps2_subcmds { + MOUSE_SEND_CMD = 0x01 +}; + +#define MOUSE_RESET 0xff + +typedef int (*periph_start)(void); +typedef int (*periph_process_msg)(const unsigned char *); + +struct nvec_periph { + periph_start start; + periph_process_msg process_msg; +}; + + +int board_nvec_init(void); + +/** + * Read all available events. + * + * @return count of available events if ok, -1 on error + */ +int nvec_read_events(void); + +int nvec_msg_is_event(const unsigned char *msg); +int nvec_msg_event_type(const unsigned char *msg); + +/** + * Register perepherial device driver. + * + * @param msg_type type of messages that divece processes. + * @param periph pointer to device description. + * + * @return 0 if ok, -1 on error + */ +int nvec_register_periph(int msg_type, struct nvec_periph *periph); + +/** + * Send request and read response. If write or read failed + * operation will be repeated NVEC_ATTEMPTS_MAX times. + * + * @param buf request data + * @param size request data size + * @return 0 if ok, -1 on error + */ +int nvec_do_request(char *buf, int size); + + +#endif /* _TEGRA_NVEC_H_ */ diff --git a/board/nvidia/common/board.c b/board/nvidia/common/board.c index 3b18e28..6d3055c 100644 --- a/board/nvidia/common/board.c +++ b/board/nvidia/common/board.c @@ -38,6 +38,9 @@ #include <asm/arch-tegra/tegra_mmc.h> #include <asm/arch-tegra/mmc.h> #endif +#ifdef CONFIG_SYS_I2C_TEGRA_NVEC +#include <asm/arch-tegra/tegra_nvec.h> +#endif #include <i2c.h> #include <spi.h> #include "emc.h" @@ -194,10 +197,19 @@ int board_early_init_f(void) int board_late_init(void) { + __maybe_unused int err; + #ifdef CONFIG_LCD /* Make sure we finish initing the LCD */ tegra_lcd_check_next_stage(gd->fdt_blob, 1); #endif + +#ifdef CONFIG_SYS_I2C_TEGRA_NVEC + err = board_nvec_init(); + if (err) + debug("NVEC controller init failed: %d\n", err); +#endif + return 0; } diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile index fa3a875..3041191 100644 --- a/drivers/i2c/Makefile +++ b/drivers/i2c/Makefile @@ -27,4 +27,5 @@ obj-$(CONFIG_SYS_I2C_S3C24X0) += s3c24x0_i2c.o obj-$(CONFIG_SYS_I2C_SH) += sh_i2c.o obj-$(CONFIG_SYS_I2C_SOFT) += soft_i2c.o obj-$(CONFIG_SYS_I2C_TEGRA) += tegra_i2c.o +obj-$(CONFIG_SYS_I2C_TEGRA_NVEC) += tegra_nvec.o obj-$(CONFIG_SYS_I2C_ZYNQ) += zynq_i2c.o diff --git a/drivers/i2c/tegra_nvec.c b/drivers/i2c/tegra_nvec.c new file mode 100644 index 0000000..b568988 --- /dev/null +++ b/drivers/i2c/tegra_nvec.c @@ -0,0 +1,294 @@ +/* + * (C) Copyright 2014 + * Andrey Danin <danind...@mail.ru> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <fdtdec.h> +#include <i2c.h> +#include <asm/io.h> +#include <asm/gpio.h> +#include <asm/arch/clock.h> +#include <asm/arch/funcmux.h> +#include <asm/arch-tegra/tegra_nvec.h> + +#ifndef CONFIG_SYS_I2C_TEGRA_NVEC +#error "You should enable CONFIG_SYS_I2C_TEGRA_NVEC" +#endif + +DECLARE_GLOBAL_DATA_PTR; + +struct nvec_periph devices[NVEC_LAST_MSG]; + + +struct nvec_t { + int i2c_bus; + struct fdt_gpio_state gpio; +} nvec_data; + + +/* nvec commands */ +char noop[] = { NVEC_CNTL, CNTL_NOOP }; + +void nvec_signal_request(void) +{ + gpio_set_value(nvec_data.gpio.gpio, 0); +} + + +void nvec_clear_request(void) +{ + gpio_set_value(nvec_data.gpio.gpio, 1); +} + + +int nvec_msg_is_event(const unsigned char *msg) +{ + return msg[0] >> 7; +} + + +int nvec_msg_event_type(const unsigned char *msg) +{ + return msg[0] & 0x0f; +} + + +/** + * Process incoming io message. + * If message is keyboard event then key code will + * be added to keys buffer. + * + * @param nvec nvec state struct + */ +void nvec_process_msg(struct nvec_t *nvec, struct i2c_transaction *trans) +{ + (void) nvec; + + const unsigned char *msg = (const unsigned char *)&trans->rx_buf[1]; + int event_type; + + if (!nvec_msg_is_event(msg)) + return; + + event_type = nvec_msg_event_type(msg); + + if (event_type < NVEC_KEYBOARD || event_type >= NVEC_LAST_MSG) + return; + + if (devices[event_type].process_msg) + devices[event_type].process_msg(msg); +} + + +/** + * Perform complete io operation (read or write). + * NOTE: function will wait NVEC_TIMEOUT_MIN (20ms) + * before status check to avoid nvec hang. + * + * @param nvec nvec state struct + * @param wait_for_ec if 1(NVEC_WAIT_FOR_EC) operation + * timeout is NVEC_TIMEOUT_MAX (600ms), + * otherwise function will return if io + * is not ready. + * + * @return nvec_io_* code + */ +int nvec_do_io(struct nvec_t *nvec) +{ + static unsigned int prev_timer; + unsigned int poll_start_ms = get_timer(prev_timer); + struct i2c_transaction trans; + int res; + + if (poll_start_ms > 30000) { + if (prev_timer != 0) + nvec_do_request(noop, sizeof(noop)); + prev_timer = get_timer(0); + } + + memset(&trans, 0, sizeof(trans)); + trans.start_timeout = 1; + trans.timeout = 6000; + + res = i2c_slave_io(&trans); + if (res == 0) { + nvec_process_msg(nvec, &trans); + return 0; + } + + if (res != -1) + debug("Error: i2c slave io failed with code %d\n", res); + + return -1; +} + +/** + * Send request and read response. If write or read failed + * operation will be repeated NVEC_ATTEMPTS_MAX times. + * + * @param buf request data + * @param size request data size + * @return 0 if ok, -1 on error + */ +int nvec_do_request(char *buf, int size) +{ + int res; + struct i2c_transaction trans; + int i; + + nvec_signal_request(); + + /* Request */ + for (i = 0; i < 10; ++i) { + memset(&trans, 0, sizeof(trans)); + trans.start_timeout = 600; + trans.timeout = 6000; + + trans.tx_buf[0] = (char)size; + memcpy(&trans.tx_buf[1], buf, size); + trans.tx_size = size + 1; + + res = i2c_slave_io(&trans); + if (res == 0) { + if (trans.tx_pos == trans.tx_size) + break; + + debug("Request was not sent completely"); + } else if (res != -1) { + debug("Unknown error while slave io"); + } + } + nvec_clear_request(); + if (res != 0) { + error("nvec failed to perform request\n"); + return -1; + } + + /* Response */ + for (i = 0; i < 10; ++i) { + memset(&trans, 0, sizeof(trans)); + trans.start_timeout = 600; + trans.timeout = 6000; + + res = i2c_slave_io(&trans); + if (res == 0) + break; + } + if (res != 0) { + error("nvec failed to read response\n"); + return -1; + } + + /* TODO Parse response */ + + return 0; +} + + +/** + * Decode the nvec information from the fdt. + * + * @param blob fdt blob + * @param nvec nvec device sturct + * @return 0 if ok, -ve on error + */ +static int nvec_decode_config(const void *blob, + struct nvec_t *nvec) +{ + int node, parent; + int i2c_bus; + + node = fdtdec_next_compatible(blob, 0, COMPAT_NVIDIA_TEGRA20_NVEC); + if (node < 0) { + error("Cannot find NVEC node in fdt\n"); + return node; + } + + parent = fdt_parent_offset(blob, node); + if (parent < 0) { + debug("%s: Cannot find node parent\n", __func__); + return -1; + } + + i2c_bus = i2c_get_bus_num_fdt(parent); + if (i2c_bus < 0) + return -1; + nvec->i2c_bus = i2c_bus; + + if (fdtdec_decode_gpio(blob, node, "request-gpios", + &nvec->gpio)) { + error("No NVEC request gpio\n"); + return -1; + } + + debug("NVEC: i2c:%d, gpio:%s(%u)\n", + nvec->i2c_bus, nvec->gpio.name, nvec->gpio.gpio); + return 0; +} + + +int board_nvec_init(void) +{ + int res = 0; + int i; + + if (nvec_decode_config(gd->fdt_blob, &nvec_data)) { + error("Can't parse NVEC node in device tree\n"); + return -1; + } + + debug("NVEC initialization...\n"); + + res = gpio_request(nvec_data.gpio.gpio, NULL); + if (res != 0) + error("NVEC: err, gpio_request\n"); + res = gpio_direction_output(nvec_data.gpio.gpio, 1); + if (res != 0) + error("NVEC: err, gpio_direction\n"); + res = gpio_set_value(nvec_data.gpio.gpio, 1); + if (res != 0) + error("NVEC: err, gpio_set_value\n"); + udelay(100); + + i2c_set_bus_num(nvec_data.i2c_bus); + + for (i = NVEC_KEYBOARD; i < NVEC_LAST_MSG; ++i) + if (devices[i].start) { + debug("Starting device %d(0x%x)\n", i, i); + devices[i].start(); + } + + return 1; +} + + +int nvec_read_events(void) +{ + int res; + int cnt = 0; + + while (++cnt <= 8) { + res = nvec_do_io(&nvec_data); + if (res) + break; + + /* TODO Process nvec communication errors */ + } + + return cnt; +} + + +int nvec_register_periph(int msg_type, struct nvec_periph *periph) +{ + if (devices[msg_type].start || devices[msg_type].process_msg) { + error("Device for msg %d already registered\n", msg_type); + return -1; + } + + devices[msg_type] = *periph; + return 0; +} diff --git a/include/fdtdec.h b/include/fdtdec.h index 19bab79..7b84335 100644 --- a/include/fdtdec.h +++ b/include/fdtdec.h @@ -64,6 +64,7 @@ enum fdt_compat_id { COMPAT_NVIDIA_TEGRA20_SDMMC, /* Tegra20 SDMMC controller */ COMPAT_NVIDIA_TEGRA20_SFLASH, /* Tegra 2 SPI flash controller */ COMPAT_NVIDIA_TEGRA20_SLINK, /* Tegra 2 SPI SLINK controller */ + COMPAT_NVIDIA_TEGRA20_NVEC, /* Tegra 2 EC controller */ COMPAT_NVIDIA_TEGRA114_SPI, /* Tegra 114 SPI controller */ COMPAT_SMSC_LAN9215, /* SMSC 10/100 Ethernet LAN9215 */ COMPAT_SAMSUNG_EXYNOS5_SROMC, /* Exynos5 SROMC */ diff --git a/lib/fdtdec.c b/lib/fdtdec.c index 1fecab3..82df701 100644 --- a/lib/fdtdec.c +++ b/lib/fdtdec.c @@ -37,6 +37,7 @@ static const char * const compat_names[COMPAT_COUNT] = { COMPAT(NVIDIA_TEGRA20_SDMMC, "nvidia,tegra20-sdhci"), COMPAT(NVIDIA_TEGRA20_SFLASH, "nvidia,tegra20-sflash"), COMPAT(NVIDIA_TEGRA20_SLINK, "nvidia,tegra20-slink"), + COMPAT(NVIDIA_TEGRA20_NVEC, "nvidia,tegra20-nvec"), COMPAT(NVIDIA_TEGRA114_SPI, "nvidia,tegra114-spi"), COMPAT(SMSC_LAN9215, "smsc,lan9215"), COMPAT(SAMSUNG_EXYNOS5_SROMC, "samsung,exynos-sromc"), -- 1.7.9.5 _______________________________________________ U-Boot mailing list U-Boot@lists.denx.de http://lists.denx.de/mailman/listinfo/u-boot