From: Jang JeongHoon Date: Fri, 11 May 2018 03:51:44 +0000 (+0900) Subject: [COMMON] i2c: busses: Added initial speedy driver. X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=5ed67879dad142e195998a93415b0c01c1fcf82c;p=GitHub%2FLineageOS%2Fandroid_kernel_motorola_exynos9610.git [COMMON] i2c: busses: Added initial speedy driver. Change-Id: I7488199a94cff870f7595ca221c26ae64b30f67c Signed-off-by: Jang JeongHoon --- diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 0f488bd74053..851f7083053b 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -598,6 +598,14 @@ config I2C_SAMSUNG_HWACG suspend state only when I2C master is not in busy state. To support HWACG on I2C, this configuration should be selected. +config EXYNOS_SPEEDY + tristate "SAMSUNG EXYNOS SPEEDY driver" + depends on ARCH_EXYNOS && OF + default y + help + Serial Protocol in an EffEctive Digital waY controller + on Exynos based Samsung SoCs. + config I2C_GPIO tristate "GPIO-based bitbanging I2C" depends on GPIOLIB || COMPILE_TEST diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 2ce8576540a2..ab329e739336 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -58,6 +58,7 @@ obj-$(CONFIG_I2C_EFM32) += i2c-efm32.o obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o obj-$(CONFIG_I2C_EMEV2) += i2c-emev2.o obj-$(CONFIG_I2C_EXYNOS5) += i2c-exynos5.o +obj-$(CONFIG_EXYNOS_SPEEDY) += i2c-speedy.o obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o obj-$(CONFIG_I2C_HIGHLANDER) += i2c-highlander.o obj-$(CONFIG_I2C_HIX5HD2) += i2c-hix5hd2.o diff --git a/drivers/i2c/busses/i2c-speedy.c b/drivers/i2c/busses/i2c-speedy.c new file mode 100644 index 000000000000..2c35c7f4a278 --- /dev/null +++ b/drivers/i2c/busses/i2c-speedy.c @@ -0,0 +1,1080 @@ +/* + * Driver for Samsung SPEEDY(Serial Protocol in an EffEctive Digital waY) + * + * Copyright (C) 2015 Samsung Electronics Ltd. + * Youngmin Nam + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../pinctrl/core.h" + +/* SPEEDY Register MAP */ +#define SPEEDY_CTRL 0x000 +#define SPEEDY_FIFO_CTRL 0x004 +#define SPEEDY_CMD 0x008 +#define SPEEDY_INT_ENABLE 0x00C +#define SPEEDY_INT_STATUS 0x010 +#define SPEEDY_FIFO_STATUS 0x030 +#define SPEEDY_TX_DATA 0x034 +#define SPEEDY_RX_DATA 0x038 +#define SPEEDY_PACKET_GAP_TIME 0x044 +#define SPEEDY_TIMEOUT_COUNT 0x048 +#define SPEEDY_FIFO_DEBUG 0x100 +#define SPEEDY_CTRL_STATUS 0x104 + +/* SPEEDY_CTRL Register bits */ +#define SPEEDY_ENABLE (1 << 0) +#define SPEEDY_TIMEOUT_CMD_DISABLE (1 << 1) +#define SPEEDY_TIMEOUT_STANDBY_DISABLE (1 << 2) +#define SPEEDY_TIMEOUT_DATA_DISABLE (1 << 3) +#define SPEEDY_ALWAYS_PULLUP_EN (1 << 7) +#define SPEEDY_DATA_WIDTH_8BIT (0 << 8) +#define SPEEDY_REMOTE_RESET_REQ (1 << 30) +#define SPEEDY_SW_RST (1 << 31) + +/* SPEEDY_FIFO_CTRL Register bits */ +#define SPEEDY_RX_TRIGGER_LEVEL(x) ((x) << 0) +#define SPEEDY_TX_TRIGGER_LEVEL(x) ((x) << 8) +#define SPEEDY_FIFO_DEBUG_INDEX (0 << 24) // TODO : modify define +#define SPEEDY_FIFO_RESET (1 << 31) + +/* SPEEDY_CMD Register bits */ +#define SPEEDY_BURST_LENGTH(x) ((x) << 0) +#define SPEEDY_BURST_FIXED (0 << 5) +#define SPEEDY_BURST_INCR (1 << 5) +#define SPEEDY_BURST_EXTENSION (2 << 5) +#define SPEEDY_ADDRESS(x) ((x & 0xFFF) << 7) +#define SPEEDY_ACCESS_BURST (0 << 19) +#define SPEEDY_ACCESS_RANDOM (1 << 19) +#define SPEEDY_DIRECTION_READ (0 << 20) +#define SPEEDY_DIRECTION_WRITE (1 << 20) + +/* SPEEDY_INT_ENABLE Register bits */ +#define SPEEDY_TRANSFER_DONE_EN (1 << 0) +#define SPEEDY_TIMEOUT_CMD_EN (1 << 1) +#define SPEEDY_TIMEOUT_STANDBY_EN (1 << 2) +#define SPEEDY_TIMEOUT_DATA_EN (1 << 3) +#define SPEEDY_FIFO_TX_ALMOST_EMPTY_EN (1 << 4) +#define SPEEDY_FIFO_RX_ALMOST_FULL_EN (1 << 8) +#define SPEEDY_RX_FIFO_INT_TRAILER_EN (1 << 9) +#define SPEEDY_RX_MODEBIT_ERR_EN (1 << 16) +#define SPEEDY_RX_GLITCH_ERR_EN (1 << 17) +#define SPEEDY_RX_ENDBIT_ERR_EN (1 << 18) +#define SPEEDY_TX_LINE_BUSY_ERR_EN (1 << 20) +#define SPEEDY_TX_STOPBIT_ERR_EN (1 << 21) +#define SPEEDY_REMOTE_RESET_REQ_EN (1 << 31) + +/* SPEEDY_INT_STATUS Register bits */ +#define SPEEDY_TRANSFER_DONE (1 << 0) +#define SPEEDY_TIMEOUT_CMD (1 << 1) +#define SPEEDY_TIMEOUT_STANDBY (1 << 2) +#define SPEEDY_TIMEOUT_DATA (1 << 3) +#define SPEEDY_FIFO_TX_ALMOST_EMPTY (1 << 4) +#define SPEEDY_FIFO_RX_ALMOST_FULL (1 << 8) +#define SPEEDY_RX_FIFO_INT_TRAILER (1 << 9) +#define SPEEDY_RX_MODEBIT_ERR (1 << 16) +#define SPEEDY_RX_GLITCH_ERR (1 << 17) +#define SPEEDY_RX_ENDBIT_ERR (1 << 18) +#define SPEEDY_TX_LINE_BUSY_ERR (1 << 20) +#define SPEEDY_TX_STOPBIT_ERR (1 << 21) +#define SPEEDY_REMOTE_RESET_REQ_STAT (1 << 31) + +/* SPEEDY_FIFO_STATUS Register bits */ +#define SPEEDY_VALID_DATA_CNT (0 << 0) // TODO : modify define +#define SPEEDY_FIFO_FULL (1 << 5) +#define SPEEDY_FIFO_EMPTY (1 << 6) + +/* SPEEDY_PACKET_GAP_TIME Register bits */ +#define SPEEDY_PULL_EN_CNT (0xF << 0) // TODO : modify define +#define SPEEDY_PACKET_GAP_TIME_CNT (0 << 16) // TODO : modify define + +/* SPEEDY_CTRL_STATUS Register bits */ +#define SPEEDY_FSM_IDLE (1 << 0) +#define SPEEDY_FSM_INIT (1 << 1) +#define SPEEDY_FSM_TX_CMD (1 << 2) +#define SPEEDY_FSM_STANDBY (1 << 3) +#define SPEEDY_FSM_DATA (1 << 4) +#define SPEEDY_FSM_TIMEOUT (1 << 5) +#define SPEEDY_FSM_TRANS_DONE (1 << 6) +#define SPEEDY_FSM_IO_RX_STAT_MASK (3 << 7) +#define SPEEDY_FSM_IO_TX_IDLE (1 << 9) +#define SPEEDY_FSM_IO_TX_GET_PACKET (1 << 10) +#define SPEEDY_FSM_IO_TX_PACKET (1 << 11) +#define SPEEDY_FSM_IO_TX_DONE (1 << 12) + +/* IP_BATCHER Register MAP */ +#define IPBATCHER_CON 0x0500 +#define IPBATCHER_STATE 0x0504 +#define IPBATCHER_INT_EN 0x0508 +#define IPBATCHER_FSM_UNEXPEN 0x050C +#define IPBATCHER_FSM_TXEN 0x0510 +#define IPBATCHER_FSM_RXFIFO 0x0514 +#define IPBATCHER_FSM_CON 0x0518 +#define IP_FIFO_STATUS 0x051C +#define IP_INT_STATUS 0x0520 +#define IP_INTR_UNEXP_STATE 0x0524 +#define IP_INTR_TX_STATE 0x0528 +#define IP_INTR_RX_STATE 0x052C +#define BATCHER_OPCODE 0x0600 +#define BATCHER_START_PAYLOAD 0x1000 +#define BATCHER_END_PAYLOAD 0x1060 +#define IPBATCHER_SEMA_REL 0x0200 + +/* IPBATCHER_CON Register bits */ +#define BATCHER_ENABLE (1 << 0) +#define DEDICATED_BATCHER_APB (1 << 1) +#define START_BATCHER (1 << 4) +#define APB_RESP_CPU (1 << 5) +#define IP_SW_RST (1 << 6) +#define MP_APBSEMA_SW_RST (1 << 7) +#define MP_APBSEMA_DISABLE (1 << 8) +#define SW_RESET (1 << 31) + +/* IPBATCHER_STATE Register bits */ +#define BATCHER_OPERATION_COMPLETE (1 << 0) +#define UNEXPECTED_IP_INTR (1 << 1) +#define BATCHER_FSM_STATE_IDLE (1 << 3) +#define BATCHER_FSM_STATE_INIT (1 << 4) +#define BATCHER_FSM_STATE_GET_SEMAPHORE (1 << 5) +#define BATCHER_FSM_STATE_CONFIG (1 << 6) +#define BATCHER_FSM_STATE_WAIT_INT (1 << 7) +#define BATCHER_FSM_STATE_SW_RESET_IP (1 << 8) +#define BATCHER_FSM_STATE_INTR_ROUTINE (1 << 9) +#define BATCHER_FSM_STATE_WRITE_TX_DATA (1 << 10) +#define BATCHER_FSM_STATE_READ_RX_DATA (1 << 11) +#define BATCHER_FSM_STATE_STOP_I2C (1 << 12) +#define BATCHER_FSM_STATE_CLEAN_INTR_STAT (1 << 13) +#define BATCHER_FSM_STATE_REL_SEMAPHORE (1 << 14) +#define BATCHER_FSM_STATE_GEN_INT (1 << 15) +#define BATCHER_FSM_STATE_UNEXPECTED_INT (1 << 16) +#define MP_APBSEMA_CH_LOCK_STATUS (1 << 20) +#define MP_APBSEMA_DISABLE_STATUS (1 << 21) +#define MP_APBSEMA_SW_RST_STATUS (1 << 22) + +/* IPBATCHER_INT_EN Register bits */ +#define BATCHER_INTERRUPT_ENABLE (1 << 0) + +/* IPBATCHER_FSM_CON Register bits */ +#define DISABLE_STOP_CMD (1 << 0) +#define DISABLE_SEMAPHORE_RELEASE (1 << 1) + +#define EXYNOS_SPEEDY_TIMEOUT (msecs_to_jiffies(500)) +#define BATCHER_INIT_CMD 0xFFFFFFFF + +#define ACCESS_BURST 0 +#define ACCESS_RANDOM 1 +#define DIRECTION_READ 0 +#define DIRECTION_WRITE 1 + +#define SRP_COUNT 3 +#define EMULATOR + +struct exynos_speedy { + struct list_head node; + struct i2c_adapter adap; + + struct i2c_msg *msg; + unsigned int msg_ptr; + unsigned int msg_len; + + unsigned int irq; + void __iomem *regs; + struct clk *clk; + struct device *dev; + + unsigned int cmd_buffer; + unsigned int cmd_index; + unsigned int cmd_pointer; + unsigned int desc_pointer; + unsigned int batcher_read_addr; + + int always_intr_high; + unsigned int int_en; +}; + +static void dump_speedy_register(struct exynos_speedy *speedy) +{ + dev_err(speedy->dev, "SPEEDY Register dump\n" + " CTRL 0x%08x\n" + " FIFO_CTRL 0x%08x\n" + " CMD 0x%08x\n" + " INT_ENABLE 0x%08x\n" + " INT_STATUS 0x%08x\n" + " FIFO_STATUS 0x%08x\n" + " PACKET_GAP_TIME 0x%08x\n" + " TIMEOUT_COUNT 0x%08x\n" + " CTRL_STATUS 0x%08x\n" + , readl(speedy->regs + SPEEDY_CTRL) + , readl(speedy->regs + SPEEDY_FIFO_CTRL) + , readl(speedy->regs + SPEEDY_CMD) + , readl(speedy->regs + SPEEDY_INT_ENABLE) + , readl(speedy->regs + SPEEDY_INT_STATUS) + , readl(speedy->regs + SPEEDY_FIFO_STATUS) + , readl(speedy->regs + SPEEDY_PACKET_GAP_TIME) + , readl(speedy->regs + SPEEDY_TIMEOUT_COUNT) + , readl(speedy->regs + SPEEDY_CTRL_STATUS) + ); +} + +static void dump_batcher_register(struct exynos_speedy *speedy) +{ + int i = 0; + char buf_opcode[SZ_256]; + char buf_payload[SZ_1K]; + u32 len = 0; + + dev_err(speedy->dev, "Batcher Register dump\n" + " CON 0x%08x\n" + " State 0x%08x\n" + " INT_EN 0x%08x\n" + " FSM_UNEXPEN 0x%08x\n" + " FSM_TXEN 0x%08x\n" + " FSM_RXFIFO 0x%08x\n" + " FSM_CON 0x%08x\n" + " FIFO_Status 0x%08x\n" + " INT_Status 0x%08x\n" + " INTR_UNEXP_state 0x%08x\n" + " INTR_TX_state 0x%08x\n" + " INTR_RX_state 0x%08x\n" + , readl(speedy->regs + IPBATCHER_CON) + , readl(speedy->regs + IPBATCHER_STATE) + , readl(speedy->regs + IPBATCHER_INT_EN) + , readl(speedy->regs + IPBATCHER_FSM_UNEXPEN) + , readl(speedy->regs + IPBATCHER_FSM_TXEN) + , readl(speedy->regs + IPBATCHER_FSM_RXFIFO) + , readl(speedy->regs + IPBATCHER_FSM_CON) + , readl(speedy->regs + IP_FIFO_STATUS) + , readl(speedy->regs + IP_INT_STATUS) + , readl(speedy->regs + IP_INTR_UNEXP_STATE) + , readl(speedy->regs + IP_INTR_TX_STATE) + , readl(speedy->regs + IP_INTR_RX_STATE) + ); + + len += snprintf(buf_opcode + len, sizeof(buf_opcode) - len, + "Batcher OPCODE dump\n"); + + for (i = 0; i < 7; i++) { + len += snprintf(buf_opcode + len, sizeof(buf_opcode) - len, + "OPCODE %d = 0x%08x\n", + i, readl(speedy->regs + (BATCHER_OPCODE + (i * 4)))); + } + + dev_err(speedy->dev, "%s", buf_opcode); + + len = 0; + len += snprintf(buf_payload + len, sizeof(buf_payload) - len, + "Batcher PAYLOAD dump\n"); + + for (i = 0; i < 25; i++) { + len += snprintf(buf_payload + len, sizeof(buf_payload) - len, + "PAYLOAD %02d = 0x%08x ", + i, readl(speedy->regs + (BATCHER_START_PAYLOAD + (i * 4)))); + if (i % 5 == 4) + len += snprintf(buf_payload + len, + sizeof(buf_payload) - len, "\n"); + } + + dev_err(speedy->dev, "%s", buf_payload); +} + +static void write_batcher(struct exynos_speedy *speedy, unsigned int description, + unsigned int opcode) +{ + if((BATCHER_START_PAYLOAD + (speedy->desc_pointer * 4)) <= + BATCHER_END_PAYLOAD) { + + /* clear cmd_buffer */ + speedy->cmd_buffer &= ~(0xFF << (8 * speedy->cmd_index)); + + /* write opcode to cmd_buffer */ + speedy->cmd_buffer |= (opcode << (8 * speedy->cmd_index)); + + /* write opcode to OPCODE_TABLE register */ + writel(speedy->cmd_buffer, speedy->regs + BATCHER_OPCODE + + (speedy->cmd_pointer * 4)); + + /* write payload to PAYLOAD_FIELD register */ + writel(description, speedy->regs + BATCHER_START_PAYLOAD + + (speedy->desc_pointer * 4)); + + /* increase cmd_index for next opcode */ + speedy->cmd_index++; + + /* increase desc_pointer for next payload */ + speedy->desc_pointer++; + } else { + /* Error handling for opcode overflow */ + dev_err(speedy->dev, "fail to write speedy batcher\n"); + } + + /* If cmd_index is 4, we need to update cmd_index, cmd_pointer */ + if(speedy->cmd_index == 4) { + /* initialize cmd_index to use OPCODE_TABLE from start point */ + speedy->cmd_index = 0; + /* increase OPCODE_TABLE offset to use next OPCODE_TABLE register */ + speedy->cmd_pointer++; + /* innitialize cmd_buffer */ + speedy->cmd_buffer = BATCHER_INIT_CMD; + } +} + +static void set_batcher_enable(struct exynos_speedy *speedy) +{ + u32 ip_batcher_con = readl(speedy->regs + IPBATCHER_CON); + + ip_batcher_con |= BATCHER_ENABLE; + ip_batcher_con |= DEDICATED_BATCHER_APB; + + writel(ip_batcher_con, speedy->regs + IPBATCHER_CON); +} + +static void start_batcher(struct exynos_speedy *speedy) +{ + u32 ip_batcher_con = readl(speedy->regs + IPBATCHER_CON); + + ip_batcher_con |= START_BATCHER; + writel(ip_batcher_con, speedy->regs + IPBATCHER_CON); +} + +static void mp_apbsema_sw_rst(struct exynos_speedy *speedy) +{ + u32 ip_batcher_con = readl(speedy->regs + IPBATCHER_CON); + + ip_batcher_con |= MP_APBSEMA_SW_RST; + writel(ip_batcher_con, speedy->regs + IPBATCHER_CON); + + ip_batcher_con &= (~MP_APBSEMA_SW_RST); + writel(ip_batcher_con, speedy->regs + IPBATCHER_CON); +} + +static void set_batcher_interrupt(struct exynos_speedy *speedy, int enable) +{ + u32 ip_batcher_int_en = readl(speedy->regs + IPBATCHER_INT_EN); + + if (enable) + ip_batcher_int_en |= BATCHER_INTERRUPT_ENABLE; + else + ip_batcher_int_en &= (~BATCHER_INTERRUPT_ENABLE); + + writel(ip_batcher_int_en, speedy->regs + IPBATCHER_INT_EN); +} + +static void set_batcher_idle(struct exynos_speedy *speedy) +{ + u32 ip_batcher_con = 0; + writel(ip_batcher_con, speedy->regs + IPBATCHER_CON); +} + +static void batcher_swreset(struct exynos_speedy *speedy) +{ + u32 ip_batcher_con = readl(speedy->regs + IPBATCHER_CON); + + ip_batcher_con |= SW_RESET; + writel(ip_batcher_con, speedy->regs + IPBATCHER_CON); + + ip_batcher_con &= (~SW_RESET); + writel(ip_batcher_con, speedy->regs + IPBATCHER_CON); + + dev_err(speedy->dev, "batcher s/w reset was done\n"); +} + +static void program_batcher_fsm(struct exynos_speedy *speedy) +{ + u32 ip_batcher_fsm_unexpec_enable = 0; + u32 ip_batcher_fsm_tx_enable = 0; + u32 ip_batcher_fsm_rx_fifo = 0; + u32 ip_batcher_fsm_con = 0; + + /* select unexpected interrupt of IP */ + /* "1" in each bit will be handled as unexpected interrupt */ + ip_batcher_fsm_unexpec_enable = + (SPEEDY_TIMEOUT_CMD_EN | SPEEDY_TIMEOUT_STANDBY_EN | + SPEEDY_TIMEOUT_DATA_EN | SPEEDY_RX_MODEBIT_ERR_EN | + SPEEDY_RX_GLITCH_ERR_EN | SPEEDY_RX_ENDBIT_ERR_EN | + SPEEDY_TX_LINE_BUSY_ERR_EN | SPEEDY_TX_STOPBIT_ERR_EN); + writel(ip_batcher_fsm_unexpec_enable, speedy->regs + IPBATCHER_FSM_UNEXPEN); + + /* select Tx, Rx normal interrupt of IP */ + /* "1" in each bit will be handled as Tx normal interrupt */ + /* "0" in each bit will be handled as Rx normal interrupt */ + ip_batcher_fsm_tx_enable = + (SPEEDY_TRANSFER_DONE_EN | SPEEDY_FIFO_TX_ALMOST_EMPTY_EN); + writel(ip_batcher_fsm_tx_enable, speedy->regs + IPBATCHER_FSM_TXEN); + + /* select Rx FIFO empty status check bit */ + /* "1" in each bit will monitor IP's RXFIFO empty status */ + ip_batcher_fsm_rx_fifo = SPEEDY_FIFO_EMPTY; + writel(ip_batcher_fsm_rx_fifo, speedy->regs + IPBATCHER_FSM_RXFIFO); + + ip_batcher_fsm_con = DISABLE_STOP_CMD; + writel(ip_batcher_fsm_con, speedy->regs + IPBATCHER_FSM_CON); +} + +static void release_semaphore(struct exynos_speedy *speedy) +{ + writel(0x01, speedy->regs + IPBATCHER_SEMA_REL); +} + +static void speedy_swreset_directly(struct exynos_speedy *speedy) +{ + u32 speedy_ctl = readl(speedy->regs + SPEEDY_CTRL); + + speedy_ctl |= SPEEDY_SW_RST; + writel(speedy_ctl, speedy->regs + SPEEDY_CTRL); + /* delay for speedy sw_rst */ + udelay(10); + + dev_err(speedy->dev, "speedy swreset directly was done\n"); +} + +static void speedy_swreset_with_batcher(struct exynos_speedy *speedy) +{ + u32 ip_batcher_state = readl(speedy->regs + IPBATCHER_STATE); + u32 ip_batcher_con; + unsigned long timeout; + + if (ip_batcher_state & MP_APBSEMA_CH_LOCK_STATUS) { + dev_err(speedy->dev, "speedy reset is started with semaphore\n"); + + if (ip_batcher_state & BATCHER_FSM_STATE_WAIT_INT) { + ip_batcher_con = readl(speedy->regs + IPBATCHER_CON); + ip_batcher_con |= IP_SW_RST; + writel(ip_batcher_con, speedy->regs + IPBATCHER_CON); + /* delay for speedy sw_rst */ + udelay(10); + dev_err(speedy->dev, "speedy swreset through batcher was done\n"); + } else { + /* SPEEDY SW reset directly */ + speedy_swreset_directly(speedy); + } + + udelay(100); + ip_batcher_state = readl(speedy->regs + IPBATCHER_STATE); + if (!(ip_batcher_state & BATCHER_FSM_STATE_INIT)) { + mp_apbsema_sw_rst(speedy); + udelay(100); + batcher_swreset(speedy); + program_batcher_fsm(speedy); + + timeout = jiffies + EXYNOS_SPEEDY_TIMEOUT; + + /* Wait IDLE or INIT state of IPBATCHER */ + while (time_before(jiffies, timeout)) { + ip_batcher_state = readl(speedy->regs + IPBATCHER_STATE); + if ((ip_batcher_state & BATCHER_FSM_STATE_IDLE) || + (ip_batcher_state & BATCHER_FSM_STATE_INIT)) { + timeout = 0; + break; + } else + udelay(10); + } + if (timeout) + dev_err(speedy->dev, "Timeout for waiting IDLE or INIT \n"); + } + release_semaphore(speedy); + } else { + dev_err(speedy->dev, "speedy reset can't be done by no semaphore\n"); + batcher_swreset(speedy); + program_batcher_fsm(speedy); + } + + timeout = jiffies + EXYNOS_SPEEDY_TIMEOUT; + + /* Check IDLE or INIT state of IPBATCHER */ + while (time_before(jiffies, timeout)) { + ip_batcher_state = readl(speedy->regs + IPBATCHER_STATE); + if ((ip_batcher_state & BATCHER_FSM_STATE_IDLE) || + (ip_batcher_state & BATCHER_FSM_STATE_INIT)) { + timeout = 0; + break; + } + else + udelay(10); + } + if (timeout) + dev_err(speedy->dev, "Timeout for waiting IDLE or INIT \n"); + + dev_err(speedy->dev, "speedy recovery is done\n"); +} + +static void speedy_set_cmd(struct exynos_speedy *speedy, int direction, u16 address, + int random, int burst_length) +{ + u32 speedy_fifo_ctl = 0; + u32 speedy_int_en = 0; + u32 speedy_command = 0; + + speedy_fifo_ctl |= SPEEDY_FIFO_RESET; + speedy_command |= SPEEDY_ADDRESS(address); + + switch (random) { + case ACCESS_BURST: + speedy_command |= (SPEEDY_ACCESS_BURST | SPEEDY_BURST_INCR | + SPEEDY_BURST_LENGTH(burst_length-1)); + + /* To prevent batcher timeout, interrupt state should be set as high */ + /* So, FIFO trigger level shoud be set to trigger interrupt always */ + if (speedy->always_intr_high) { + if (direction == DIRECTION_READ) { + speedy_fifo_ctl |= ( + SPEEDY_RX_TRIGGER_LEVEL(burst_length) | + SPEEDY_TX_TRIGGER_LEVEL(16) + ); + } else { + speedy_fifo_ctl |= ( + SPEEDY_RX_TRIGGER_LEVEL(0) | + SPEEDY_TX_TRIGGER_LEVEL(1) + ); + } + } else { + speedy_fifo_ctl |= ( + SPEEDY_RX_TRIGGER_LEVEL(burst_length) | + SPEEDY_TX_TRIGGER_LEVEL(1) + ); + } + break; + + case ACCESS_RANDOM: + speedy_command |= SPEEDY_ACCESS_RANDOM; + speedy_fifo_ctl |= (SPEEDY_RX_TRIGGER_LEVEL(1) | + SPEEDY_TX_TRIGGER_LEVEL(1)); + break; + } + + /* make opcode and payload to configure SPEEDY_FIFO_CTRL */ + write_batcher(speedy, speedy_fifo_ctl, SPEEDY_FIFO_CTRL); + + speedy_int_en |= (SPEEDY_TIMEOUT_CMD_EN | SPEEDY_TIMEOUT_STANDBY_EN | + SPEEDY_TIMEOUT_DATA_EN); + + switch (direction) { + case DIRECTION_READ: + speedy_command |= SPEEDY_DIRECTION_READ; + speedy_int_en |= (SPEEDY_FIFO_RX_ALMOST_FULL_EN | + SPEEDY_RX_FIFO_INT_TRAILER_EN | + SPEEDY_RX_MODEBIT_ERR_EN | + SPEEDY_RX_GLITCH_ERR_EN | + SPEEDY_RX_ENDBIT_ERR_EN); + + /* To prevent batcher timeout, interrupt state should be set as high */ + if (speedy->always_intr_high) { + speedy_int_en |= SPEEDY_FIFO_TX_ALMOST_EMPTY_EN; + } + + break; + + case DIRECTION_WRITE: + speedy_command |= SPEEDY_DIRECTION_WRITE; + speedy_int_en |= (SPEEDY_TRANSFER_DONE_EN | + SPEEDY_FIFO_TX_ALMOST_EMPTY_EN | + SPEEDY_TX_LINE_BUSY_ERR_EN | + SPEEDY_TX_STOPBIT_ERR_EN); + + /* To prevent batcher timeout, interrupt state should be set as high */ + if (speedy->always_intr_high) { + speedy_int_en |= SPEEDY_FIFO_RX_ALMOST_FULL_EN; + } + + break; + } + + /* store speedy_interrupt_enable status for re-configuration later */ + speedy->int_en = speedy_int_en; + + /* clear speedy interrupt status */ + write_batcher(speedy, 0xFFFFFFFF, SPEEDY_INT_STATUS); + + /* make opcode and payload to configure SPEEDY_INT_ENABLE */ + write_batcher(speedy, speedy_int_en, SPEEDY_INT_ENABLE); + + /* make opcode and payload to configure SPEEDY_CMD */ + write_batcher(speedy, speedy_command, SPEEDY_CMD); +} + +static int speedy_batcher_wait_complete(struct exynos_speedy *speedy) +{ + u32 ip_batcher_state; + u32 ip_batcher_int_status; + int ret = -EBUSY; + unsigned long timeout; + + timeout = jiffies + EXYNOS_SPEEDY_TIMEOUT; + + while (time_before(jiffies, timeout)) { + ip_batcher_state = readl(speedy->regs + IPBATCHER_STATE); + ip_batcher_int_status = readl(speedy->regs + IP_INT_STATUS); + + if (ip_batcher_state & BATCHER_OPERATION_COMPLETE) { + if ((ip_batcher_int_status & SPEEDY_TIMEOUT_CMD) | + (ip_batcher_int_status & SPEEDY_TIMEOUT_STANDBY) | + (ip_batcher_int_status & SPEEDY_TIMEOUT_DATA) | + (ip_batcher_int_status & SPEEDY_RX_MODEBIT_ERR) | + (ip_batcher_int_status & SPEEDY_RX_GLITCH_ERR) | + (ip_batcher_int_status & SPEEDY_RX_ENDBIT_ERR) | + (ip_batcher_int_status & SPEEDY_TX_LINE_BUSY_ERR) | + (ip_batcher_int_status & SPEEDY_TX_STOPBIT_ERR)) { + ret = -EIO; + break; + } else { + ret = 0; + break; + } + } else if (ip_batcher_state & UNEXPECTED_IP_INTR) { + if ((ip_batcher_int_status & SPEEDY_TIMEOUT_CMD) | + (ip_batcher_int_status & SPEEDY_TIMEOUT_STANDBY) | + (ip_batcher_int_status & SPEEDY_TIMEOUT_DATA) | + (ip_batcher_int_status & SPEEDY_RX_MODEBIT_ERR) | + (ip_batcher_int_status & SPEEDY_RX_GLITCH_ERR) | + (ip_batcher_int_status & SPEEDY_RX_ENDBIT_ERR) | + (ip_batcher_int_status & SPEEDY_TX_LINE_BUSY_ERR) | + (ip_batcher_int_status & SPEEDY_TX_STOPBIT_ERR)) { + ret = -EIO; + break; + } + } else { + udelay(10); + } + } + + if (ret != 0) { + if (ret == -EIO) + dev_err(speedy->dev, + "speedy timeout or error is occurred "); + else + dev_err(speedy->dev, + "speedy batcher operation timeout is occurred "); + } + + if (ip_batcher_state & BATCHER_OPERATION_COMPLETE) { + /* clear batcher operation complete */ + ip_batcher_state |= BATCHER_OPERATION_COMPLETE; + writel(ip_batcher_state, speedy->regs + IPBATCHER_STATE); + } + + return ret; +} + +static void finalize_batcher(struct exynos_speedy *speedy) +{ + write_batcher(speedy, 0x00, 0xFF); + + /* Initialize variables related opcode and payload of batcher */ + speedy->cmd_buffer = BATCHER_INIT_CMD; + speedy->cmd_index = 0; + speedy->cmd_pointer = 0; + speedy->desc_pointer = 0; +} + +static void speedy_set_srp(struct exynos_speedy *speedy) +{ + int ret; + int i; + u32 speedy_ctl; + + for (i = 0; i < SRP_COUNT; i++) { + speedy_ctl = 0x30051; + /* set batcher IDLE state */ + set_batcher_idle(speedy); + + /* set batcher IDLE->INIT state */ + set_batcher_enable(speedy); + + speedy_set_cmd(speedy, DIRECTION_WRITE, 0x0, ACCESS_RANDOM, 0); + + speedy_ctl |= SPEEDY_REMOTE_RESET_REQ; + write_batcher(speedy, speedy_ctl, SPEEDY_CTRL); + + write_batcher(speedy, 0x00, SPEEDY_TX_DATA); + + speedy_ctl &= (~SPEEDY_REMOTE_RESET_REQ); + write_batcher(speedy, speedy_ctl, SPEEDY_CTRL); + + finalize_batcher(speedy); + /* TODO : for polling mode, need to enable batcher interrupt ? */ + set_batcher_interrupt(speedy, 1); + start_batcher(speedy); + ret = speedy_batcher_wait_complete(speedy); + /* TODO : for polling mode, need to enable batcher interrupt ? */ + set_batcher_interrupt(speedy, 0); + set_batcher_idle(speedy); + + if (!ret) { + dev_err(speedy->dev, "SRP was done successfully\n"); + break; + } else { + dev_err(speedy->dev, "SRP timeout was occured\n"); + dump_speedy_register(speedy); + dump_batcher_register(speedy); + speedy_swreset_with_batcher(speedy); + } + } +} + +static int exynos_speedy_xfer_batcher(struct exynos_speedy *speedy, + struct i2c_msg *msgs) +{ + int i = 0; + int ret; + + /* speedy read / write direction */ + int direction; + /* speedy random(single) / burst access way */ + int random; + unsigned char byte; + unsigned int speedy_int_en; + + /* initialize as reset value of SPEEDY_CTRL */ + u32 speedy_ctl = 0x30050; + + speedy->msg = msgs; + speedy->msg_ptr = 0; + + speedy->cmd_buffer = BATCHER_INIT_CMD; + speedy->cmd_index = 0; + speedy->cmd_pointer = 0; + speedy->desc_pointer = 0; + + /* set batcher IDLE state */ + set_batcher_idle(speedy); + + /* set batcher IDLE->INIT state */ + set_batcher_enable(speedy); + + /* enable speedy master */ + speedy_ctl |= SPEEDY_ENABLE; + write_batcher(speedy, speedy_ctl, SPEEDY_CTRL); + + if (speedy->msg->flags & I2C_M_RD) + direction = DIRECTION_READ; + else + direction = DIRECTION_WRITE; + + if (speedy->msg->len > 1) + random = ACCESS_BURST; + else + random = ACCESS_RANDOM; + + speedy_set_cmd(speedy, direction, speedy->msg->addr, random, speedy->msg->len); + + if (direction == DIRECTION_READ) { + speedy->batcher_read_addr = BATCHER_START_PAYLOAD + + ((speedy->desc_pointer) * 4); + + for (i = 0; i < speedy->msg->len; i++) + write_batcher(speedy, 0x77, SPEEDY_RX_DATA); + } else { + /* direction == DIRECTION_WRITE */ + for (i = 0; i < speedy->msg->len; i++) { + byte = speedy->msg->buf[i]; + write_batcher(speedy, byte, SPEEDY_TX_DATA); + } + + /* + * To prevent interrupt pending by FIFO_TX_ALMOST_EMPTY + * We should disable FIFO_TX_ALMOST_EMPTY_EN after Tx + */ + speedy_int_en = speedy->int_en & (~SPEEDY_FIFO_TX_ALMOST_EMPTY_EN); + write_batcher(speedy, speedy_int_en, SPEEDY_INT_ENABLE); + } + + finalize_batcher(speedy); + + /* TODO : for polling mode, need to enable batcher interrupt ? */ + set_batcher_interrupt(speedy, 1); + + start_batcher(speedy); + + ret = speedy_batcher_wait_complete(speedy); + + /* TODO : for polling mode, need to enable batcher interrupt ? */ + set_batcher_interrupt(speedy, 0); + + if (!ret) { + if (direction == DIRECTION_READ) { + for (i = 0; i < speedy->msg->len; i++) { + byte = (unsigned char)readl(speedy->regs + + speedy->batcher_read_addr + (i * 4)); + speedy->msg->buf[i] = byte; + } + } + set_batcher_idle(speedy); + } else { + set_batcher_idle(speedy); + if (direction == DIRECTION_READ) + dev_err(speedy->dev, "at Read\n"); + else + dev_err(speedy->dev, "at Write\n"); + + dump_speedy_register(speedy); + dump_batcher_register(speedy); + + speedy_swreset_with_batcher(speedy); + speedy_set_srp(speedy); + udelay(1); + ret = -EAGAIN; + } + return ret; +} +#if 0 +static irqreturn_t exynos_speedy_irq_batcher(int irqno, void *dev_id) +{ + /* TODO : implementation is needed more */ + /* In ISR, we will only handle error situation */ + + struct exynos_speedy *speedy = dev_id; + u32 ip_batcher_state; + u32 ip_batcher_int_status; + + ip_batcher_state = readl(speedy->regs + IPBATCHER_STATE); + ip_batcher_int_status = readl(speedy->regs + IP_INT_STATUS); + + if (ip_batcher_int_status & SPEEDY_REMOTE_RESET_REQ_STAT) { + dev_err(speedy->dev, "remote_reset_req is occured\n"); + } + + if (ip_batcher_state & UNEXPECTED_IP_INTR) { + dev_err(speedy->dev, "unexpected interrupt is occured\n"); + + if (ip_batcher_int_status & SPEEDY_TIMEOUT_CMD) + dev_err(speedy->dev, "timout_cmd is occured\n"); + if (ip_batcher_int_status & SPEEDY_TIMEOUT_STANDBY) + dev_err(speedy->dev, "timeout_standby is occured\n"); + if (ip_batcher_int_status & SPEEDY_TIMEOUT_DATA) + dev_err(speedy->dev, "timeout_data is occured\n"); + if (ip_batcher_int_status & SPEEDY_RX_MODEBIT_ERR) + dev_err(speedy->dev, "rx_modebit_err is occured\n"); + if (ip_batcher_int_status & SPEEDY_RX_GLITCH_ERR) + dev_err(speedy->dev, "rx_glitch_err is occured\n"); + if (ip_batcher_int_status & SPEEDY_RX_ENDBIT_ERR) + dev_err(speedy->dev, "rx_endbit_err interrupt is occured\n"); + if (ip_batcher_int_status & SPEEDY_TX_LINE_BUSY_ERR) + dev_err(speedy->dev, "tx_line_busy_err interrupt is occured\n"); + if (ip_batcher_int_status & SPEEDY_TX_STOPBIT_ERR) + dev_err(speedy->dev, "tx_stopbit_err interrupt is occured\n"); + } + + if (ip_batcher_state & BATCHER_OPERATION_COMPLETE) { + dev_err(speedy->dev, "batcher operation is completed\n"); + + /* clear batcher operation complete */ + ip_batcher_state |= BATCHER_OPERATION_COMPLETE; + writel(BATCHER_OPERATION_COMPLETE, speedy->regs + IPBATCHER_STATE); + } + + return IRQ_HANDLED; +} +#endif + +static int exynos_speedy_xfer(struct i2c_adapter *adap, + struct i2c_msg *msgs, int num) +{ + struct exynos_speedy *speedy = (struct exynos_speedy *)adap->algo_data; + struct i2c_msg *msgs_ptr = msgs; + + int retry, i = 0; + int ret = 0; + + for (retry = 0; retry < adap->retries; retry++) { + for (i = 0; i < num; i++) { + ret = exynos_speedy_xfer_batcher(speedy, msgs_ptr); + + msgs_ptr++; + + if (ret == -EAGAIN) { + msgs_ptr = msgs; + break; + } else if (ret < 0) { + goto out; + } + } + + if ((i == num) && (ret != -EAGAIN)) + break; + + dev_err(speedy->dev, "retrying transfer (%d)\n", retry); + + udelay(100); + } + + if (i == num) { + ret = num; + } else { + ret = -EREMOTEIO; + dev_err(speedy->dev, "xfer message failed\n"); + } + + out: + return ret; +} + +static u32 exynos_speedy_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK); +} + +static const struct i2c_algorithm exynos_speedy_algorithm = { + .master_xfer = exynos_speedy_xfer, + .functionality = exynos_speedy_func, +}; + +static int exynos_speedy_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct exynos_speedy *speedy; + struct resource *mem; + int ret; + + dev_info(&pdev->dev, "speedy driver probe started\n"); + + if (!np) { + dev_err(&pdev->dev, "no device node\n"); + return -ENOENT; + } + + speedy = devm_kzalloc(&pdev->dev, sizeof(struct exynos_speedy), GFP_KERNEL); + if (!speedy) { + dev_err(&pdev->dev, "no memory for driver data\n"); + return -ENOMEM; + } + + if (of_get_property(np, "samsung,always-interrupt-high", NULL)) + speedy->always_intr_high = 1; + else + speedy->always_intr_high = 0; + + strlcpy(speedy->adap.name, "exynos-speedy", sizeof(speedy->adap.name)); + speedy->adap.owner = THIS_MODULE; + speedy->adap.algo = &exynos_speedy_algorithm; + speedy->adap.retries = 2; + speedy->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD; + + speedy->dev = &pdev->dev; +#if 0 + speedy->clk = devm_clk_get(&pdev->dev, "gate_speedy"); + if (IS_ERR(speedy->clk)) { + dev_err(&pdev->dev, "cannot get clock\n"); + return -ENOENT; + } +#endif + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + speedy->regs = devm_ioremap_resource(&pdev->dev, mem); + if (speedy->regs == NULL) { + dev_err(&pdev->dev, "cannot map speedy SFR register\n"); + ret = PTR_ERR(speedy->regs); + goto err_probe; + } + + speedy->adap.dev.of_node = np; + speedy->adap.algo_data = speedy; + speedy->adap.dev.parent = &pdev->dev; + + speedy->irq = irq_of_parse_and_map(np, 0); + if(speedy->irq <= 0) { + dev_err(&pdev->dev, "cannot find speedy IRQ\n"); + ret = -EINVAL; + goto err_probe; + } + platform_set_drvdata(pdev, speedy); + +#if 0 + /* clear speedy interrupt status */ + writel(0xFFFFFFFF, speedy->regs + SPEEDY_INT_STATUS); + + /* reset speedy ctrl SFR. It may be used by bootloader */ + speedy_swreset_directly(speedy); + + /* Do we need to register ISR for batcher polling mode? */ + ret = devm_request_irq(&pdev->dev, speedy->irq, + exynos_speedy_irq_batcher, 0, dev_name(&pdev->dev), speedy); + + disable_irq(speedy->irq); + if (ret != 0) { + dev_err(&pdev->dev, "cannot request speedy IRQ %d\n", speedy->irq); + goto err_probe; + } + /* release semaphore after direct SPEEDY SFR access */ + release_semaphore(speedy); + + /* reset batcher */ + batcher_swreset(speedy); + /* select bitfield to monitor interrupt and status by batcher */ + program_batcher_fsm(speedy); +#endif + + speedy->adap.nr = -1; + ret = i2c_add_numbered_adapter(&speedy->adap); + if (ret < 0) { + dev_err(&pdev->dev, "failed to add bus to i2c core\n"); + goto err_probe; + } + + dev_info(&pdev->dev, "speedy driver probe was succeeded\n"); + + return 0; + + err_probe: + dev_err(&pdev->dev, "speedy driver probe failed\n"); + return ret; +} + +static const struct of_device_id exynos_speedy_match[] = { + { .compatible = "samsung,exynos-speedy" }, + {} +}; +MODULE_DEVICE_TABLE(of, exynos_speedy_match); + +static struct platform_driver exynos_speedy_driver = { + .probe = exynos_speedy_probe, + .driver = { + .name = "exynos-speedy", + .owner = THIS_MODULE, + .of_match_table = exynos_speedy_match, + }, +}; + +static int __init exynos_speedy_init(void) +{ + return platform_driver_register(&exynos_speedy_driver); +} +subsys_initcall(exynos_speedy_init); + +static void __exit exynos_speedy_exit(void) +{ + platform_driver_unregister(&exynos_speedy_driver); +} +module_exit(exynos_speedy_exit); + +MODULE_DESCRIPTION("Exynos SPEEDY driver"); +MODULE_AUTHOR("Youngmin Nam, "); +MODULE_LICENSE("GPL v2");