| /* |
| * Driver for Samsung SPEEDY(Serial Protocol in an EffEctive Digital waY) |
| * |
| * Copyright (C) 2015 Samsung Electronics Ltd. |
| * Youngmin Nam <youngmin.nam@samsung.com> |
| * |
| * 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 <linux/kernel.h> |
| #include <linux/module.h> |
| |
| #include <linux/i2c.h> |
| #include <linux/init.h> |
| #include <linux/time.h> |
| #include <linux/interrupt.h> |
| #include <linux/delay.h> |
| #include <linux/errno.h> |
| #include <linux/err.h> |
| #include <linux/platform_device.h> |
| #include <linux/clk.h> |
| #include <linux/slab.h> |
| #include <linux/io.h> |
| #include <linux/of.h> |
| #include <linux/of_address.h> |
| #include <linux/of_irq.h> |
| #include <linux/of_gpio.h> |
| #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; |
| } |
| |
| 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; |
| } |
| |
| 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; |
| void __iomem *pmu_batcher; |
| 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; |
| speedy->clk = devm_clk_get(&pdev->dev, "gate_speedy"); |
| if (IS_ERR(speedy->clk)) { |
| dev_err(&pdev->dev, "cannot get clock\n"); |
| return -ENOENT; |
| } |
| |
| 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; |
| } |
| |
| /* TODO : need handling PMU register for batcher ? */ |
| /* for enable Batcher in PMU */ |
| mem = platform_get_resource(pdev, IORESOURCE_MEM, 1); |
| if (mem != NULL) { |
| pmu_batcher = devm_ioremap_resource(&pdev->dev, mem); |
| |
| if (pmu_batcher == NULL) { |
| dev_err(&pdev->dev, "cannot map PMU register for batcher enable\n"); |
| ret = PTR_ERR(pmu_batcher); |
| goto err_probe; |
| } |
| /* TODO : need handling PMU register for batcher ? */ |
| writel(0x3, pmu_batcher); |
| devm_iounmap(&pdev->dev, pmu_batcher); |
| } |
| |
| 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; |
| } |
| |
| /* 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; |
| } |
| |
| platform_set_drvdata(pdev, speedy); |
| |
| /* 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); |
| |
| 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, <youngmin.nam@samsung.com>"); |
| MODULE_LICENSE("GPL v2"); |