/* * UFS Host Controller driver for Exynos specific extensions * * Copyright (C) 2013-2014 Samsung Electronics Co., Ltd. * * 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. */ #include #include #include #include #include #include #include "ufshcd.h" #include "unipro.h" #include "mphy.h" #include "ufshcd-pltfrm.h" #include "ufs-exynos.h" #include #include #include #include #include #include #include /* * Unipro attribute value */ #define TXTRAILINGCLOCKS 0x10 #define TACTIVATE_10_USEC 400 /* unit: 10us */ /* Device ID */ #define DEV_ID 0x00 #define PEER_DEV_ID 0x01 #define PEER_CPORT_ID 0x00 #define TRAFFIC_CLASS 0x00 #define IATOVAL_NSEC 20000 /* unit: ns */ /* UFS CAL interface */ /* * Debugging information, SFR/attributes/misc */ static struct exynos_ufs *ufs_host_backup[1]; static int ufs_host_index = 0; static spinlock_t fsys0_tcxo_lock; static struct exynos_ufs_sfr_log ufs_log_std_sfr[] = { {"CAPABILITIES" , REG_CONTROLLER_CAPABILITIES, 0}, {"UFS VERSION" , REG_UFS_VERSION, 0}, {"PRODUCT ID" , REG_CONTROLLER_DEV_ID, 0}, {"MANUFACTURE ID" , REG_CONTROLLER_PROD_ID, 0}, {"INTERRUPT STATUS" , REG_INTERRUPT_STATUS, 0}, {"INTERRUPT ENABLE" , REG_INTERRUPT_ENABLE, 0}, {"CONTROLLER STATUS" , REG_CONTROLLER_STATUS, 0}, {"CONTROLLER ENABLE" , REG_CONTROLLER_ENABLE, 0}, {"UTP TRANSF REQ INT AGG CNTRL" , REG_UTP_TRANSFER_REQ_INT_AGG_CONTROL, 0}, {"UTP TRANSF REQ LIST BASE L" , REG_UTP_TRANSFER_REQ_LIST_BASE_L, 0}, {"UTP TRANSF REQ LIST BASE H" , REG_UTP_TRANSFER_REQ_LIST_BASE_H, 0}, {"UTP TRANSF REQ DOOR BELL" , REG_UTP_TRANSFER_REQ_DOOR_BELL, 0}, {"UTP TRANSF REQ LIST CLEAR" , REG_UTP_TRANSFER_REQ_LIST_CLEAR, 0}, {"UTP TRANSF REQ LIST RUN STOP" , REG_UTP_TRANSFER_REQ_LIST_RUN_STOP, 0}, {"UTP TASK REQ LIST BASE L" , REG_UTP_TASK_REQ_LIST_BASE_L, 0}, {"UTP TASK REQ LIST BASE H" , REG_UTP_TASK_REQ_LIST_BASE_H, 0}, {"UTP TASK REQ DOOR BELL" , REG_UTP_TASK_REQ_DOOR_BELL, 0}, {"UTP TASK REQ LIST CLEAR" , REG_UTP_TASK_REQ_LIST_CLEAR, 0}, {"UTP TASK REQ LIST RUN STOP" , REG_UTP_TASK_REQ_LIST_RUN_STOP, 0}, {"UIC COMMAND" , REG_UIC_COMMAND, 0}, {"UIC COMMAND ARG1" , REG_UIC_COMMAND_ARG_1, 0}, {"UIC COMMAND ARG2" , REG_UIC_COMMAND_ARG_2, 0}, {"UIC COMMAND ARG3" , REG_UIC_COMMAND_ARG_3, 0}, {}, }; /* Helper for UFS CAL interface */ static inline int ufs_init_cal(struct exynos_ufs *ufs, int idx, struct platform_device *pdev) { int ret = 0; struct device *dev = &pdev->dev; struct ufs_cal_param *p = NULL; p = devm_kzalloc(dev, sizeof(*p), GFP_KERNEL); if (!p) { dev_err(ufs->dev, "cannot allocate mem for cal param\n"); return -ENOMEM; } ufs->cal_param = p; p->host = ufs; p->board = 0; /* ken: need a dt node for board */ if ((ret = ufs_cal_init(p, idx)) != UFS_CAL_NO_ERROR) { dev_err(ufs->dev, "ufs_init_cal = %d!!!\n", ret); return -EPERM; } return 0; } static void exynos_ufs_update_active_lanes(struct ufs_hba *hba) { struct exynos_ufs *ufs = to_exynos_ufs(hba); struct ufs_cal_param *p = ufs->cal_param; u32 active_tx_lane = 0; u32 active_rx_lane = 0; ufshcd_dme_get(hba, UIC_ARG_MIB(PA_ACTIVETXDATALANES), &(active_tx_lane)); ufshcd_dme_get(hba, UIC_ARG_MIB(PA_ACTIVERXDATALANES), &(active_rx_lane)); p->active_tx_lane = (u8) active_tx_lane; p->active_rx_lane = (u8) active_rx_lane; dev_info(ufs->dev, "active_tx_lane(%d), active_rx_lane(%d)\n", p->active_tx_lane, p->active_rx_lane); } static inline int ufs_pre_link(struct exynos_ufs *ufs) { int ret = 0; struct ufs_cal_param *p = ufs->cal_param; p->mclk_rate = ufs->mclk_rate; p->available_lane = ufs->num_rx_lanes; if ((ret = ufs_cal_pre_link(p)) != UFS_CAL_NO_ERROR) { dev_err(ufs->dev, "ufs_pre_link = %d!!!\n", ret); return -EPERM; } return 0; } static inline int ufs_post_link(struct exynos_ufs *ufs) { int ret = 0; /* update active lanes after link*/ exynos_ufs_update_active_lanes(ufs->hba); if ((ret = ufs_cal_post_link(ufs->cal_param)) != UFS_CAL_NO_ERROR) { dev_err(ufs->dev, "ufs_post_link = %d!!!\n", ret); return -EPERM; } return 0; } static inline int ufs_pre_gear_change(struct exynos_ufs *ufs, struct uic_pwr_mode *pmd) { struct ufs_cal_param *p = ufs->cal_param; int ret = 0; p->pmd = pmd; if ((ret = ufs_cal_pre_pmc(p)) != UFS_CAL_NO_ERROR) { dev_err(ufs->dev, "ufs_pre_gear_change = %d!!!\n", ret); return -EPERM; } return 0; } static inline int ufs_post_gear_change(struct exynos_ufs *ufs) { int ret = 0; if ((ret = ufs_cal_post_pmc(ufs->cal_param)) != UFS_CAL_NO_ERROR) { dev_err(ufs->dev, "ufs_post_gear_change = %d!!!\n", ret); return -EPERM; } return 0; } static inline int ufs_post_h8_enter(struct exynos_ufs *ufs) { int ret = 0; if ((ret = ufs_cal_post_h8_enter(ufs->cal_param)) != UFS_CAL_NO_ERROR) { dev_err(ufs->dev, "ufs_post_h8_enter = %d!!!\n", ret); return -EPERM; } return 0; } static inline int ufs_pre_h8_exit(struct exynos_ufs *ufs) { int ret = 0; if ((ret = ufs_cal_pre_h8_exit(ufs->cal_param)) != UFS_CAL_NO_ERROR) { dev_err(ufs->dev, "ufs_pre_h8_exit = %d!!!\n", ret); return -EPERM; } return 0; } /* Adaptor for UFS CAL */ void ufs_lld_dme_set(void *h, u32 addr, u32 val) { ufshcd_dme_set(((struct exynos_ufs *)h)->hba, addr, val); } void ufs_lld_dme_get(void *h, u32 addr, u32 *val) { ufshcd_dme_get(((struct exynos_ufs *)h)->hba, addr, val); } void ufs_lld_dme_peer_set(void *h, u32 addr, u32 val) { ufshcd_dme_peer_set(((struct exynos_ufs *)h)->hba, addr, val); } void ufs_lld_pma_write(void *h, u32 val, u32 addr) { phy_pma_writel((struct exynos_ufs *)h, val, addr); } u32 ufs_lld_pma_read(void *h, u32 addr) { return phy_pma_readl((struct exynos_ufs *)h, addr); } void ufs_lld_unipro_write(void *h, u32 val, u32 addr) { unipro_writel((struct exynos_ufs *)h, val, addr); } void ufs_lld_udelay(u32 val) { udelay(val); } void ufs_lld_usleep_delay(u32 min, u32 max) { usleep_range(min, max); } unsigned long ufs_lld_get_time_count(unsigned long offset) { return jiffies; } unsigned long ufs_lld_calc_timeout(const unsigned int ms) { return msecs_to_jiffies(ms); } static inline void exynos_ufs_ctrl_phy_pwr(struct exynos_ufs *ufs, bool en) { int ret = 0; if (en) ret = regmap_update_bits(ufs->pmureg, ufs->cxt_iso.offset, ufs->cxt_iso.mask, ufs->cxt_iso.val); else ret = regmap_update_bits(ufs->pmureg, ufs->cxt_iso.offset, ufs->cxt_iso.mask, 0); if (ret) dev_err(ufs->dev, "Unable to update PHY ISO control\n"); } #ifndef __EXYNOS_UFS_VS_DEBUG__ static void exynos_ufs_dump_std_sfr(struct ufs_hba *hba) { struct exynos_ufs *ufs = to_exynos_ufs(hba); struct exynos_ufs_sfr_log* cfg = ufs->debug.std_sfr; dev_err(hba->dev, ": --------------------------------------------------- \n"); dev_err(hba->dev, ": \t\tREGISTER DUMP\n"); dev_err(hba->dev, ": --------------------------------------------------- \n"); while(cfg) { if (!cfg->name) break; cfg->val = ufshcd_readl(hba, cfg->offset); /* Dump */ dev_err(hba->dev, ": %s(0x%04x):\t\t\t\t0x%08x\n", cfg->name, cfg->offset, cfg->val); /* Next SFR */ cfg++; } } #endif /* * Exynos debugging main function */ static void exynos_ufs_dump_debug_info(struct ufs_hba *hba) { #ifdef __EXYNOS_UFS_VS_DEBUG__ exynos_ufs_get_uic_info(hba); #else exynos_ufs_dump_std_sfr(hba); #endif } static void exynos_ufs_select_refclk(struct exynos_ufs *ufs, bool en) { u32 reg; if (ufs->hw_rev != UFS_VER_0004) return; /* * true : alternative clock path, false : active clock path */ reg = hci_readl(ufs, HCI_MPHY_REFCLK_SEL); if (en) hci_writel(ufs, reg | MPHY_REFCLK_SEL, HCI_MPHY_REFCLK_SEL); else hci_writel(ufs, reg & ~MPHY_REFCLK_SEL, HCI_MPHY_REFCLK_SEL); } inline void exynos_ufs_set_hwacg_control(struct exynos_ufs *ufs, bool en) { u32 reg; if ((ufs->hw_rev != UFS_VER_0004) && (ufs->hw_rev != UFS_VER_0005)) return; /* * default value 1->0 at KC. so, * need to set "1(disable HWACG)" during UFS init */ reg = hci_readl(ufs, HCI_UFS_ACG_DISABLE); if (en) hci_writel(ufs, reg & (~HCI_UFS_ACG_DISABLE_EN), HCI_UFS_ACG_DISABLE); else hci_writel(ufs, reg | HCI_UFS_ACG_DISABLE_EN, HCI_UFS_ACG_DISABLE); } inline void exynos_ufs_ctrl_auto_hci_clk(struct exynos_ufs *ufs, bool en) { u32 reg = hci_readl(ufs, HCI_FORCE_HCS); if (en) hci_writel(ufs, reg | HCI_CORECLK_STOP_EN, HCI_FORCE_HCS); else hci_writel(ufs, reg & ~HCI_CORECLK_STOP_EN, HCI_FORCE_HCS); } static inline void exynos_ufs_ctrl_clk(struct exynos_ufs *ufs, bool en) { u32 reg = hci_readl(ufs, HCI_FORCE_HCS); if (en) hci_writel(ufs, reg | CLK_STOP_CTRL_EN_ALL, HCI_FORCE_HCS); else hci_writel(ufs, reg & ~CLK_STOP_CTRL_EN_ALL, HCI_FORCE_HCS); } static inline void exynos_ufs_gate_clk(struct exynos_ufs *ufs, bool en) { u32 reg = hci_readl(ufs, HCI_CLKSTOP_CTRL); if (en) hci_writel(ufs, reg | CLK_STOP_ALL, HCI_CLKSTOP_CTRL); else hci_writel(ufs, reg & ~CLK_STOP_ALL, HCI_CLKSTOP_CTRL); } static void exynos_ufs_set_unipro_mclk(struct exynos_ufs *ufs) { ufs->mclk_rate = (u32)clk_get_rate(ufs->clk_unipro); } static void exynos_ufs_fit_aggr_timeout(struct exynos_ufs *ufs) { u32 cnt_val; unsigned long nVal; /* IA_TICK_SEL : 1(1us_TO_CNT_VAL) */ nVal = hci_readl(ufs, HCI_UFSHCI_V2P1_CTRL); nVal |= IA_TICK_SEL; hci_writel(ufs, nVal, HCI_UFSHCI_V2P1_CTRL); cnt_val = ufs->mclk_rate / 1000000 ; hci_writel(ufs, cnt_val & CNT_VAL_1US_MASK, HCI_1US_TO_CNT_VAL); } static void exynos_ufs_init_pmc_req(struct ufs_hba *hba, struct ufs_pa_layer_attr *pwr_max, struct ufs_pa_layer_attr *pwr_req) { struct exynos_ufs *ufs = to_exynos_ufs(hba); struct uic_pwr_mode *req_pmd = &ufs->req_pmd_parm; struct uic_pwr_mode *act_pmd = &ufs->act_pmd_parm; struct ufs_cal_param *p = ufs->cal_param; /* update lane variable after link */ ufs->num_rx_lanes = pwr_max->lane_rx; ufs->num_tx_lanes = pwr_max->lane_tx; p->connected_rx_lane = pwr_max->lane_rx; p->connected_tx_lane = pwr_max->lane_tx; pwr_req->gear_rx = act_pmd->gear= min_t(u8, pwr_max->gear_rx, req_pmd->gear); pwr_req->gear_tx = act_pmd->gear = min_t(u8, pwr_max->gear_tx, req_pmd->gear); pwr_req->lane_rx = act_pmd->lane = min_t(u8, pwr_max->lane_rx, req_pmd->lane); pwr_req->lane_tx = act_pmd->lane = min_t(u8, pwr_max->lane_tx, req_pmd->lane); pwr_req->pwr_rx = act_pmd->mode = req_pmd->mode; pwr_req->pwr_tx = act_pmd->mode = req_pmd->mode; pwr_req->hs_rate = act_pmd->hs_series = req_pmd->hs_series; } static void exynos_ufs_config_intr(struct exynos_ufs *ufs, u32 errs, u8 index) { switch(index) { case UNIP_PA_LYR: hci_writel(ufs, DFES_ERR_EN | errs, HCI_ERROR_EN_PA_LAYER); break; case UNIP_DL_LYR: hci_writel(ufs, DFES_ERR_EN | errs, HCI_ERROR_EN_DL_LAYER); break; case UNIP_N_LYR: hci_writel(ufs, DFES_ERR_EN | errs, HCI_ERROR_EN_N_LAYER); break; case UNIP_T_LYR: hci_writel(ufs, DFES_ERR_EN | errs, HCI_ERROR_EN_T_LAYER); break; case UNIP_DME_LYR: hci_writel(ufs, DFES_ERR_EN | errs, HCI_ERROR_EN_DME_LAYER); break; } } static void exynos_ufs_dev_hw_reset(struct ufs_hba *hba) { struct exynos_ufs *ufs = to_exynos_ufs(hba); /* bit[1] for resetn */ hci_writel(ufs, 0 << 0, HCI_GPIO_OUT); udelay(5); hci_writel(ufs, 1 << 0, HCI_GPIO_OUT); } static void exynos_ufs_init_host(struct exynos_ufs *ufs) { u32 reg; /* internal clock control */ exynos_ufs_ctrl_auto_hci_clk(ufs, false); exynos_ufs_set_unipro_mclk(ufs); /* period for interrupt aggregation */ exynos_ufs_fit_aggr_timeout(ufs); /* misc HCI configurations */ hci_writel(ufs, 0xA, HCI_DATA_REORDER); hci_writel(ufs, PRDT_PREFECT_EN | PRDT_SET_SIZE(12), HCI_TXPRDT_ENTRY_SIZE); hci_writel(ufs, PRDT_SET_SIZE(12), HCI_RXPRDT_ENTRY_SIZE); hci_writel(ufs, 0xFFFFFFFF, HCI_UTRL_NEXUS_TYPE); hci_writel(ufs, 0xFFFFFFFF, HCI_UTMRL_NEXUS_TYPE); reg = hci_readl(ufs, HCI_AXIDMA_RWDATA_BURST_LEN) & ~BURST_LEN(0); hci_writel(ufs, WLU_EN | BURST_LEN(3), HCI_AXIDMA_RWDATA_BURST_LEN); /* * Enable HWAGC control by IOP * * default value 1->0 at KC. * always "0"(controlled by UFS_ACG_DISABLE) */ reg = hci_readl(ufs, HCI_IOP_ACG_DISABLE); hci_writel(ufs, reg & (~HCI_IOP_ACG_DISABLE_EN), HCI_IOP_ACG_DISABLE); } static void exynos_ufs_pre_hibern8(struct ufs_hba *hba, u8 enter) { } static void exynos_ufs_post_hibern8(struct ufs_hba *hba, u8 enter) { struct exynos_ufs *ufs = to_exynos_ufs(hba); if (!enter) { struct uic_pwr_mode *act_pmd = &ufs->act_pmd_parm; u32 mode = 0; ufshcd_dme_get(hba, UIC_ARG_MIB(PA_PWRMODE), &mode); if (mode != (act_pmd->mode << 4 | act_pmd->mode)) { dev_warn(hba->dev, "%s: power mode not matched, mode : 0x%x, act_mode : 0x%x\n", __func__, mode, act_pmd->mode); hba->pwr_info.pwr_rx = (mode >> 4) & 0xf; hba->pwr_info.pwr_tx = mode & 0xf; ufshcd_config_pwr_mode(hba, &hba->max_pwr_info.info); } } } static int exynos_ufs_init_system(struct exynos_ufs *ufs) { struct device *dev = ufs->dev; int ret = 0; bool is_io_coherency; bool is_dma_coherent; /* PHY isolation bypass */ exynos_ufs_ctrl_phy_pwr(ufs, true); /* IO cohernecy */ is_io_coherency = !IS_ERR(ufs->sysreg); is_dma_coherent = !!of_find_property(dev->of_node, "dma-coherent", NULL); if (is_io_coherency != is_dma_coherent) BUG(); if (!is_io_coherency) dev_err(dev, "Not configured to use IO coherency\n"); else ret = regmap_update_bits(ufs->sysreg, ufs->cxt_coherency.offset, ufs->cxt_coherency.mask, ufs->cxt_coherency.val); return ret; } static int exynos_ufs_get_clks(struct ufs_hba *hba) { struct exynos_ufs *ufs = to_exynos_ufs(hba); struct list_head *head = &hba->clk_list_head; struct ufs_clk_info *clki; int i = 0; ufs_host_backup[ufs_host_index++] = ufs; ufs->debug.std_sfr = ufs_log_std_sfr; if (!head || list_empty(head)) goto out; list_for_each_entry(clki, head, list) { /* * get clock with an order listed in device tree */ if (i == 0) ufs->clk_hci = clki->clk; else if (i == 1) ufs->clk_unipro = clki->clk; i++; } out: if (!ufs->clk_hci || !ufs->clk_unipro) return -EINVAL; return 0; } static void exynos_ufs_set_features(struct ufs_hba *hba, u32 hw_rev) { /* caps */ hba->caps = UFSHCD_CAP_CLK_GATING | UFSHCD_CAP_HIBERN8_WITH_CLK_GATING | UFSHCD_CAP_INTR_AGGR; /* quirks of common driver */ hba->quirks = UFSHCD_QUIRK_PRDT_BYTE_GRAN | UFSHCI_QUIRK_SKIP_INTR_AGGR | UFSHCD_QUIRK_UNRESET_INTR_AGGR | UFSHCD_QUIRK_BROKEN_REQ_LIST_CLR; /* quirks of exynos-specific driver */ } /* * Exynos-specific callback functions * * init | Pure SW init & system-related init * host_reset | Host SW reset & init * pre_setup_clocks | specific power down * setup_clocks | specific power up * ... * * Initializations for software, host controller and system * should be contained only in ->host_reset() as possible. */ static int exynos_ufs_init(struct ufs_hba *hba) { struct exynos_ufs *ufs = to_exynos_ufs(hba); int ret; int id; /* set features, such as caps or quirks */ exynos_ufs_set_features(hba, ufs->hw_rev); /* get some clock sources and debug infomation structures */ ret = exynos_ufs_get_clks(hba); if (ret) return ret; /* system init */ ret = exynos_ufs_init_system(ufs); if (ret) return ret; /* get fmp & smu id */ ret = of_property_read_u32(ufs->dev->of_node, "fmp-id", &id); if (ret) ufs->fmp = SMU_ID_MAX; else ufs->fmp = id; ret = of_property_read_u32(ufs->dev->of_node, "smu-id", &id); if (ret) ufs->smu = SMU_ID_MAX; else ufs->smu = id; /* FMPSECURITY & SMU */ ufshcd_vops_crypto_sec_cfg(hba, true); /* Enable log */ ret = exynos_ufs_init_dbg(hba); if (ret) return ret; ufs->misc_flags = EXYNOS_UFS_MISC_TOGGLE_LOG; return 0; } static void exynos_ufs_host_reset(struct ufs_hba *hba) { struct exynos_ufs *ufs = to_exynos_ufs(hba); unsigned long timeout = jiffies + msecs_to_jiffies(1); exynos_ufs_ctrl_auto_hci_clk(ufs, false); hci_writel(ufs, UFS_SW_RST_MASK, HCI_SW_RST); do { if (!(hci_readl(ufs, HCI_SW_RST) & UFS_SW_RST_MASK)) goto success; } while (time_before(jiffies, timeout)); dev_err(ufs->dev, "timeout host sw-reset\n"); exynos_ufs_dump_uic_info(hba); goto out; success: /* host init */ exynos_ufs_init_host(ufs); /* device reset */ exynos_ufs_dev_hw_reset(hba); /* secure log */ #ifdef CONFIG_EXYNOS_SMC_LOGGING exynos_smc(SMC_CMD_UFS_LOG, 0, 0, 0); #endif out: return; } static inline void exynos_ufs_dev_reset_ctrl(struct exynos_ufs *ufs, bool en) { if (en) hci_writel(ufs, 1 << 0, HCI_GPIO_OUT); else hci_writel(ufs, 0 << 0, HCI_GPIO_OUT); } static void exynos_ufs_tcxo_ctrl(struct exynos_ufs *ufs, bool tcxo_on) { unsigned int val; int ret; ret = regmap_read(ufs->pmureg, ufs->cxt_iso.offset, &val); if (tcxo_on == true) val |= (1 << 16); else val &= ~(1 << 16); if (!ret) ret = regmap_write(ufs->pmureg, ufs->cxt_iso.offset, val); if (ret) dev_err(ufs->dev, "Unable to access the pmureg using regmap\n"); } static bool tcxo_used_by[OWNER_MAX]; static int exynos_check_shared_resource(int owner) { if (owner == OWNER_FIRST) return tcxo_used_by[OWNER_SECOND]; else return tcxo_used_by[OWNER_FIRST]; } static bool exynos_use_shared_resource(int owner, bool use) { tcxo_used_by[owner] = use; return exynos_check_shared_resource(owner); } static int exynos_ufs_pre_setup_clocks(struct ufs_hba *hba, bool on) { struct exynos_ufs *ufs = to_exynos_ufs(hba); int ret = 0; unsigned long flags; if (on) { #ifdef CONFIG_ARM64_EXYNOS_CPUIDLE exynos_update_ip_idle_status(ufs->idle_ip_index, 0); #endif if (ufs->tcxo_ex_ctrl) { spin_lock_irqsave(&fsys0_tcxo_lock, flags); if (exynos_use_shared_resource(OWNER_FIRST, on) == !on) exynos_ufs_tcxo_ctrl(ufs, true); spin_unlock_irqrestore(&fsys0_tcxo_lock, flags); } /* * Now all used blocks would not be turned off in a host. */ exynos_ufs_ctrl_auto_hci_clk(ufs, false); exynos_ufs_gate_clk(ufs, false); /* HWAGC disable */ exynos_ufs_set_hwacg_control(ufs, false); } else { pm_qos_update_request(&ufs->pm_qos_int, 0); pm_qos_update_request(&ufs->pm_qos_fsys0, 0); } return ret; } static int exynos_ufs_setup_clocks(struct ufs_hba *hba, bool on) { struct exynos_ufs *ufs = to_exynos_ufs(hba); int ret = 0; unsigned long flags; if (on) { pm_qos_update_request(&ufs->pm_qos_int, ufs->pm_qos_int_value); pm_qos_update_request(&ufs->pm_qos_fsys0, ufs->pm_qos_fsys0_value); } else { /* * Now all used blocks would be turned off in a host. */ exynos_ufs_gate_clk(ufs, true); exynos_ufs_ctrl_auto_hci_clk(ufs, true); /* HWAGC enable */ exynos_ufs_set_hwacg_control(ufs, true); if (ufs->tcxo_ex_ctrl) { spin_lock_irqsave(&fsys0_tcxo_lock, flags); if (exynos_use_shared_resource(OWNER_FIRST, on) == on) exynos_ufs_tcxo_ctrl(ufs, false); spin_unlock_irqrestore(&fsys0_tcxo_lock, flags); } #ifdef CONFIG_ARM64_EXYNOS_CPUIDLE exynos_update_ip_idle_status(ufs->idle_ip_index, 1); #endif } return ret; } static int exynos_ufs_get_available_lane(struct ufs_hba *hba) { struct ufs_pa_layer_attr *pwr_info = &hba->max_pwr_info.info; struct exynos_ufs *ufs = to_exynos_ufs(hba); /* Get the available lane count */ ufshcd_dme_get(hba, UIC_ARG_MIB(PA_AVAILRXDATALANES), &pwr_info->available_lane_rx); ufshcd_dme_get(hba, UIC_ARG_MIB(PA_AVAILTXDATALANES), &pwr_info->available_lane_tx); if (!pwr_info->available_lane_rx || !pwr_info->available_lane_tx) { dev_err(hba->dev, "%s: invalid host available lanes value. rx=%d, tx=%d\n", __func__, pwr_info->available_lane_rx, pwr_info->available_lane_tx); return -EINVAL; } if (ufs->num_rx_lanes == 0 || ufs->num_tx_lanes == 0) { ufs->num_rx_lanes = pwr_info->available_lane_rx; ufs->num_tx_lanes = pwr_info->available_lane_tx; WARN(ufs->num_rx_lanes != ufs->num_tx_lanes, "available data lane is not equal(rx:%d, tx:%d)\n", ufs->num_rx_lanes, ufs->num_tx_lanes); } return 0; } static int exynos_ufs_hce_enable_notify(struct ufs_hba *hba, enum ufs_notify_change_status status) { struct exynos_ufs *ufs = to_exynos_ufs(hba); int ret = 0; switch (status) { case PRE_CHANGE: break; case POST_CHANGE: exynos_ufs_ctrl_clk(ufs, true); exynos_ufs_select_refclk(ufs, true); exynos_ufs_gate_clk(ufs, false); exynos_ufs_set_hwacg_control(ufs, false); ret = exynos_ufs_get_available_lane(hba); break; default: break; } return ret; } static int exynos_ufs_link_startup_notify(struct ufs_hba *hba, enum ufs_notify_change_status status) { struct exynos_ufs *ufs = to_exynos_ufs(hba); int ret = 0; switch (status) { case PRE_CHANGE: /* refer to hba */ ufs->hba = hba; /* hci */ exynos_ufs_config_intr(ufs, DFES_DEF_DL_ERRS, UNIP_DL_LYR); exynos_ufs_config_intr(ufs, DFES_DEF_N_ERRS, UNIP_N_LYR); exynos_ufs_config_intr(ufs, DFES_DEF_T_ERRS, UNIP_T_LYR); ufs->mclk_rate = clk_get_rate(ufs->clk_unipro); ret = ufs_pre_link(ufs); break; case POST_CHANGE: /* UIC configuration table after link startup */ ret = ufs_post_link(ufs); break; default: break; } return ret; } static int exynos_ufs_pwr_change_notify(struct ufs_hba *hba, enum ufs_notify_change_status status, struct ufs_pa_layer_attr *pwr_max, struct ufs_pa_layer_attr *pwr_req) { struct exynos_ufs *ufs = to_exynos_ufs(hba); struct uic_pwr_mode *act_pmd = &ufs->act_pmd_parm; int ret = 0; switch (status) { case PRE_CHANGE: /* Set PMC parameters to be requested */ exynos_ufs_init_pmc_req(hba, pwr_max, pwr_req); /* UIC configuration table before power mode change */ ret = ufs_pre_gear_change(ufs, act_pmd); break; case POST_CHANGE: /* update active lanes after pmc */ exynos_ufs_update_active_lanes(hba); /* UIC configuration table after power mode change */ ret = ufs_post_gear_change(ufs); dev_info(ufs->dev, "Power mode change(%d): M(%d)G(%d)L(%d)HS-series(%d)\n", ret, act_pmd->mode, act_pmd->gear, act_pmd->lane, act_pmd->hs_series); break; default: break; } return ret; } static void exynos_ufs_set_nexus_t_xfer_req(struct ufs_hba *hba, int tag, struct scsi_cmnd *cmd) { struct exynos_ufs *ufs = to_exynos_ufs(hba); u32 type; type = hci_readl(ufs, HCI_UTRL_NEXUS_TYPE); if (cmd) type |= (1 << tag); else type &= ~(1 << tag); hci_writel(ufs, type, HCI_UTRL_NEXUS_TYPE); } static void exynos_ufs_set_nexus_t_task_mgmt(struct ufs_hba *hba, int tag, u8 tm_func) { struct exynos_ufs *ufs = to_exynos_ufs(hba); u32 type; type = hci_readl(ufs, HCI_UTMRL_NEXUS_TYPE); switch (tm_func) { case UFS_ABORT_TASK: case UFS_QUERY_TASK: type |= (1 << tag); break; case UFS_ABORT_TASK_SET: case UFS_CLEAR_TASK_SET: case UFS_LOGICAL_RESET: case UFS_QUERY_TASK_SET: type &= ~(1 << tag); break; } hci_writel(ufs, type, HCI_UTMRL_NEXUS_TYPE); } static void exynos_ufs_hibern8_notify(struct ufs_hba *hba, u8 enter, bool notify) { int noti = (int) notify; switch (noti) { case PRE_CHANGE: exynos_ufs_pre_hibern8(hba, enter); break; case POST_CHANGE: exynos_ufs_post_hibern8(hba, enter); break; default: break; } } static int exynos_ufs_hibern8_prepare(struct ufs_hba *hba, u8 enter, bool notify) { struct exynos_ufs *ufs = to_exynos_ufs(hba); int ret = 0; int noti = (int) notify; switch (noti) { case PRE_CHANGE: if (!enter) ret = ufs_pre_h8_exit(ufs); break; case POST_CHANGE: if (enter) ret = ufs_post_h8_enter(ufs); break; default: break; } return ret; } static int __exynos_ufs_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op) { struct exynos_ufs *ufs = to_exynos_ufs(hba); pm_qos_update_request(&ufs->pm_qos_int, 0); pm_qos_update_request(&ufs->pm_qos_fsys0, 0); exynos_ufs_dev_reset_ctrl(ufs, false); exynos_ufs_ctrl_phy_pwr(ufs, false); return 0; } static int __exynos_ufs_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op) { struct exynos_ufs *ufs = to_exynos_ufs(hba); int ret = 0; exynos_ufs_ctrl_phy_pwr(ufs, true); /* system init */ ret = exynos_ufs_init_system(ufs); if (ret) return ret; if (ufshcd_is_clkgating_allowed(hba)) clk_prepare_enable(ufs->clk_hci); exynos_ufs_ctrl_auto_hci_clk(ufs, false); /* FMPSECURITY & SMU resume */ ufshcd_vops_crypto_sec_cfg(hba, false); /* secure log */ #ifdef CONFIG_EXYNOS_SMC_LOGGING exynos_smc(SMC_CMD_UFS_LOG, 0, 0, 0); #endif if (ufshcd_is_clkgating_allowed(hba)) clk_disable_unprepare(ufs->clk_hci); return 0; } static u8 exynos_ufs_get_unipro_direct(struct ufs_hba *hba, u32 num) { u32 offset[] = { UNIP_DME_LINKSTARTUP_CNF_RESULT, UNIP_DME_HIBERN8_ENTER_CNF_RESULT, UNIP_DME_HIBERN8_EXIT_CNF_RESULT, UNIP_DME_PWR_IND_RESULT, UNIP_DME_HIBERN8_ENTER_IND_RESULT, UNIP_DME_HIBERN8_EXIT_IND_RESULT }; struct exynos_ufs *ufs = to_exynos_ufs(hba); return unipro_readl(ufs, offset[num]); } #ifdef CONFIG_SCSI_UFS_EXYNOS_FMP static struct bio *get_bio(struct ufs_hba *hba, struct ufshcd_lrb *lrbp) { if (!hba || !lrbp) { pr_err("%s: Invalid MMC:%p data:%p\n", __func__, hba, lrbp); return NULL; } if (!virt_addr_valid(lrbp->cmd)) { dev_err(hba->dev, "Invalid cmd:%p\n", lrbp->cmd); return NULL; } if (!virt_addr_valid(lrbp->cmd->request->bio)) { if (lrbp->cmd->request->bio) dev_err(hba->dev, "Invalid bio:%p\n", lrbp->cmd->request->bio); return NULL; } else { return lrbp->cmd->request->bio; } } static int exynos_ufs_crypto_engine_cfg(struct ufs_hba *hba, struct ufshcd_lrb *lrbp) { int sg_segments = scsi_sg_count(lrbp->cmd); int idx; struct scatterlist *sg; struct bio *bio = get_bio(hba, lrbp); int ret; int sector_offset = 0; if (!bio || !sg_segments) return 0; scsi_for_each_sg(lrbp->cmd, sg, sg_segments, idx) { ret = exynos_fmp_crypt_cfg(bio, (void *)&lrbp->ucd_prdt_ptr[idx], idx, sector_offset); sector_offset += 8; /* UFSHCI_SECTOR_SIZE / MIN_SECTOR_SIZE */ if (ret) return ret; } return 0; } static int exynos_ufs_crypto_engine_clear(struct ufs_hba *hba, struct ufshcd_lrb *lrbp) { int sg_segments = scsi_sg_count(lrbp->cmd); int idx; struct scatterlist *sg; struct bio *bio = get_bio(hba, lrbp); int ret; if (!bio || !sg_segments) return 0; scsi_for_each_sg(lrbp->cmd, sg, sg_segments, idx) { ret = exynos_fmp_crypt_clear(bio, (void *)&lrbp->ucd_prdt_ptr[idx]); if (ret) return ret; } return 0; } static int exynos_ufs_crypto_sec_cfg(struct ufs_hba *hba, bool init) { struct exynos_ufs *ufs = to_exynos_ufs(hba); dev_err(ufs->dev, "%s: fmp:%d, smu:%d, init:%d\n", __func__, ufs->fmp, ufs->smu, init); return exynos_fmp_sec_cfg(ufs->fmp, ufs->smu, init); } static int exynos_ufs_access_control_abort(struct ufs_hba *hba) { struct exynos_ufs *ufs = to_exynos_ufs(hba); dev_err(ufs->dev, "%s: smu:%d, ret:%d\n", __func__, ufs->smu); return exynos_fmp_smu_abort(ufs->smu); } #else static int exynos_ufs_crypto_sec_cfg(struct ufs_hba *hba, bool init) { struct exynos_ufs *ufs = to_exynos_ufs(hba); writel(0x0, ufs->reg_ufsp + UFSPSBEGIN0); writel(0xffffffff, ufs->reg_ufsp + UFSPSEND0); writel(0xff, ufs->reg_ufsp + UFSPSLUN0); writel(0xf1, ufs->reg_ufsp + UFSPSCTRL0); return 0; } #endif static struct ufs_hba_variant_ops exynos_ufs_ops = { .init = exynos_ufs_init, .host_reset = exynos_ufs_host_reset, .pre_setup_clocks = exynos_ufs_pre_setup_clocks, .setup_clocks = exynos_ufs_setup_clocks, .hce_enable_notify = exynos_ufs_hce_enable_notify, .link_startup_notify = exynos_ufs_link_startup_notify, .pwr_change_notify = exynos_ufs_pwr_change_notify, .set_nexus_t_xfer_req = exynos_ufs_set_nexus_t_xfer_req, .set_nexus_t_task_mgmt = exynos_ufs_set_nexus_t_task_mgmt, .hibern8_notify = exynos_ufs_hibern8_notify, .hibern8_prepare = exynos_ufs_hibern8_prepare, .dbg_register_dump = exynos_ufs_dump_debug_info, .suspend = __exynos_ufs_suspend, .resume = __exynos_ufs_resume, .get_unipro_result = exynos_ufs_get_unipro_direct, #ifdef CONFIG_SCSI_UFS_EXYNOS_FMP .crypto_engine_cfg = exynos_ufs_crypto_engine_cfg, .crypto_engine_clear = exynos_ufs_crypto_engine_clear, .access_control_abort = exynos_ufs_access_control_abort, #endif .crypto_sec_cfg = exynos_ufs_crypto_sec_cfg, }; static int exynos_ufs_populate_dt_phy(struct device *dev, struct exynos_ufs *ufs) { struct device_node *ufs_phy; struct exynos_ufs_phy *phy = &ufs->phy; struct resource io_res; int ret; ufs_phy = of_get_child_by_name(dev->of_node, "ufs-phy"); if (!ufs_phy) { dev_err(dev, "failed to get ufs-phy node\n"); return -ENODEV; } ret = of_address_to_resource(ufs_phy, 0, &io_res); if (ret) { dev_err(dev, "failed to get i/o address phy pma\n"); goto err_0; } phy->reg_pma = devm_ioremap_resource(dev, &io_res); if (!phy->reg_pma) { dev_err(dev, "failed to ioremap for phy pma\n"); ret = -ENOMEM; goto err_0; } err_0: of_node_put(ufs_phy); return ret; } /* * This function is to define offset, mask and shift to access somewhere. */ static int exynos_ufs_set_context_for_access(struct device *dev, const char *name, struct exynos_access_cxt *cxt) { struct device_node *np; int ret; np = of_get_child_by_name(dev->of_node, name); if (!np) { dev_err(dev, "failed to get node(%s)\n", name); return 1; } ret = of_property_read_u32(np, "offset", &cxt->offset); if (IS_ERR(&cxt->offset)) { dev_err(dev, "failed to set cxt(%s) offset\n", name); return cxt->offset; } ret = of_property_read_u32(np, "mask", &cxt->mask); if (IS_ERR(&cxt->mask)) { dev_err(dev, "failed to set cxt(%s) mask\n", name); return cxt->mask; } ret = of_property_read_u32(np, "val", &cxt->val); if (IS_ERR(&cxt->val)) { dev_err(dev, "failed to set cxt(%s) val\n", name); return cxt->val; } return 0; } static int exynos_ufs_populate_dt_system(struct device *dev, struct exynos_ufs *ufs) { struct device_node *np = dev->of_node; int ret; /* regmap pmureg */ ufs->pmureg = syscon_regmap_lookup_by_phandle(dev->of_node, "samsung,pmu-phandle"); if (IS_ERR(ufs->pmureg)) { /* * phy isolation should be available. * so this case need to be failed. */ dev_err(dev, "pmu regmap lookup failed.\n"); return PTR_ERR(ufs->pmureg); } /* Set access context for phy isolation bypass */ ret = exynos_ufs_set_context_for_access(dev, "ufs-phy-iso", &ufs->cxt_iso); if (ret == 1) { /* no device node, default */ ufs->cxt_iso.offset = 0x0724; ufs->cxt_iso.mask = 0x1; ufs->cxt_iso.val = 0x1; ret = 0; } /* regmap sysreg */ ufs->sysreg = syscon_regmap_lookup_by_phandle(dev->of_node, "samsung,sysreg-fsys-phandle"); if (IS_ERR(ufs->sysreg)) { /* * Currently, ufs driver gets sysreg for io coherency. * Some architecture might not support this feature. * So the device node might not exist. */ dev_err(dev, "sysreg regmap lookup failed.\n"); return 0; } /* Set access context for io coherency */ ret = exynos_ufs_set_context_for_access(dev, "ufs-dma-coherency", &ufs->cxt_coherency); if (ret == 1) { /* no device node, default */ ufs->cxt_coherency.offset = 0x0700; ufs->cxt_coherency.mask = 0x300; /* bit 8,9 */ ufs->cxt_coherency.val = 0x3; ret = 0; } /* TCXO exclusive control */ if (of_property_read_u32(np, "tcxo-ex-ctrl", &ufs->tcxo_ex_ctrl)) ufs->tcxo_ex_ctrl = 1; return ret; } static int exynos_ufs_get_pwr_mode(struct device_node *np, struct exynos_ufs *ufs) { struct uic_pwr_mode *pmd = &ufs->req_pmd_parm; pmd->mode = FAST_MODE; if (of_property_read_u8(np, "ufs,pmd-attr-lane", &pmd->lane)) pmd->lane = 1; if (of_property_read_u8(np, "ufs,pmd-attr-gear", &pmd->gear)) pmd->gear = 1; pmd->hs_series = PA_HS_MODE_B; return 0; } static int exynos_ufs_populate_dt(struct device *dev, struct exynos_ufs *ufs) { struct device_node *np = dev->of_node; int ret; /* Get exynos-specific version for featuring */ if (of_property_read_u32(np, "hw-rev", &ufs->hw_rev)) ufs->hw_rev = UFS_VER_0004; ret = exynos_ufs_populate_dt_phy(dev, ufs); if (ret) { dev_err(dev, "failed to populate dt-phy\n"); goto out; } ret = exynos_ufs_populate_dt_system(dev, ufs); if (ret) { dev_err(dev, "failed to populate dt-pmu\n"); goto out; } exynos_ufs_get_pwr_mode(np, ufs); if (of_property_read_u8(np, "brd-for-cal", &ufs->cal_param->board)) ufs->cal_param->board = 0; if (of_property_read_u32(np, "ufs-pm-qos-int", &ufs->pm_qos_int_value)) ufs->pm_qos_int_value = 0; if (of_property_read_u32(np, "ufs-pm-qos-fsys0", &ufs->pm_qos_fsys0_value)) ufs->pm_qos_fsys0_value = 0; out: return ret; } static int exynos_ufs_lp_event(struct notifier_block *nb, unsigned long event, void *data) { struct exynos_ufs *ufs = container_of(nb, struct exynos_ufs, tcxo_nb); int ret = NOTIFY_DONE; bool on = true; unsigned long flags; spin_lock_irqsave(&fsys0_tcxo_lock, flags); switch (event) { case SLEEP_ENTER: on = false; if (exynos_use_shared_resource(OWNER_SECOND, on) == on) exynos_ufs_tcxo_ctrl(ufs, false); break; case SLEEP_EXIT: if (exynos_use_shared_resource(OWNER_SECOND, on) == !on) exynos_ufs_tcxo_ctrl(ufs, true); break; } spin_unlock_irqrestore(&fsys0_tcxo_lock, flags); return ret; } static u64 exynos_ufs_dma_mask = DMA_BIT_MASK(32); static int exynos_ufs_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct exynos_ufs *ufs; struct resource *res; int ret; ufs = devm_kzalloc(dev, sizeof(*ufs), GFP_KERNEL); if (!ufs) { dev_err(dev, "cannot allocate mem for exynos-ufs\n"); return -ENOMEM; } /* exynos-specific hci */ res = platform_get_resource(pdev, IORESOURCE_MEM, 1); ufs->reg_hci = devm_ioremap_resource(dev, res); if (!ufs->reg_hci) { dev_err(dev, "cannot ioremap for hci vendor register\n"); return -ENOMEM; } /* unipro */ res = platform_get_resource(pdev, IORESOURCE_MEM, 2); ufs->reg_unipro = devm_ioremap_resource(dev, res); if (!ufs->reg_unipro) { dev_err(dev, "cannot ioremap for unipro register\n"); return -ENOMEM; } /* ufs protector */ res = platform_get_resource(pdev, IORESOURCE_MEM, 3); ufs->reg_ufsp = devm_ioremap_resource(dev, res); if (!ufs->reg_ufsp) { dev_err(dev, "cannot ioremap for ufs protector register\n"); return -ENOMEM; } /* This must be before calling exynos_ufs_populate_dt */ ret = ufs_init_cal(ufs, ufs_host_index, pdev); if (ret) return ret; ret = exynos_ufs_populate_dt(dev, ufs); if (ret) { dev_err(dev, "failed to get dt info.\n"); return ret; } /* * pmu node and txco syscon node should be exclusive */ if (ufs->tcxo_ex_ctrl) { ufs->tcxo_nb.notifier_call = exynos_ufs_lp_event; ufs->tcxo_nb.next = NULL; ufs->tcxo_nb.priority = 0; ret = exynos_fsys0_tcxo_register_notifier(&ufs->tcxo_nb); if (ret) { dev_err(dev, "failed to register fsys0 txco notifier\n"); return ret; } } #ifdef CONFIG_ARM64_EXYNOS_CPUIDLE ufs->idle_ip_index = exynos_get_idle_ip_index(dev_name(&pdev->dev)); exynos_update_ip_idle_status(ufs->idle_ip_index, 0); #endif ufs->dev = dev; dev->platform_data = ufs; dev->dma_mask = &exynos_ufs_dma_mask; pm_qos_add_request(&ufs->pm_qos_int, PM_QOS_DEVICE_THROUGHPUT, 0); pm_qos_add_request(&ufs->pm_qos_fsys0, PM_QOS_BUS_THROUGHPUT, 0); if (ufs->tcxo_ex_ctrl) spin_lock_init(&fsys0_tcxo_lock); ret = ufshcd_pltfrm_init(pdev, &exynos_ufs_ops); return ret; } static int exynos_ufs_remove(struct platform_device *pdev) { struct exynos_ufs *ufs = dev_get_platdata(&pdev->dev); ufshcd_pltfrm_exit(pdev); pm_qos_remove_request(&ufs->pm_qos_fsys0); pm_qos_remove_request(&ufs->pm_qos_int); ufs->misc_flags = EXYNOS_UFS_MISC_TOGGLE_LOG; exynos_ufs_ctrl_phy_pwr(ufs, false); return 0; } #ifdef CONFIG_PM_SLEEP static int exynos_ufs_suspend(struct device *dev) { struct ufs_hba *hba = dev_get_drvdata(dev); return ufshcd_system_suspend(hba); } static int exynos_ufs_resume(struct device *dev) { struct ufs_hba *hba = dev_get_drvdata(dev); return ufshcd_system_resume(hba); } #else #define exynos_ufs_suspend NULL #define exynos_ufs_resume NULL #endif /* CONFIG_PM_SLEEP */ #ifdef CONFIG_PM_RUNTIME static int exynos_ufs_runtime_suspend(struct device *dev) { return ufshcd_system_suspend(dev_get_drvdata(dev)); } static int exynos_ufs_runtime_resume(struct device *dev) { return ufshcd_system_resume(dev_get_drvdata(dev)); } static int exynos_ufs_runtime_idle(struct device *dev) { return ufshcd_runtime_idle(dev_get_drvdata(dev)); } #else #define exynos_ufs_runtime_suspend NULL #define exynos_ufs_runtime_resume NULL #define exynos_ufs_runtime_idle NULL #endif /* CONFIG_PM_RUNTIME */ static void exynos_ufs_shutdown(struct platform_device *pdev) { ufshcd_shutdown((struct ufs_hba *)platform_get_drvdata(pdev)); } static const struct dev_pm_ops exynos_ufs_dev_pm_ops = { .suspend = exynos_ufs_suspend, .resume = exynos_ufs_resume, .runtime_suspend = exynos_ufs_runtime_suspend, .runtime_resume = exynos_ufs_runtime_resume, .runtime_idle = exynos_ufs_runtime_idle, }; static const struct ufs_hba_variant exynos_ufs_drv_data = { .ops = &exynos_ufs_ops, }; static const struct of_device_id exynos_ufs_match[] = { { .compatible = "samsung,exynos-ufs", }, {}, }; MODULE_DEVICE_TABLE(of, exynos_ufs_match); static struct platform_driver exynos_ufs_driver = { .driver = { .name = "exynos-ufs", .owner = THIS_MODULE, .pm = &exynos_ufs_dev_pm_ops, .of_match_table = exynos_ufs_match, .suppress_bind_attrs = true, }, .probe = exynos_ufs_probe, .remove = exynos_ufs_remove, .shutdown = exynos_ufs_shutdown, }; module_platform_driver(exynos_ufs_driver); MODULE_DESCRIPTION("Exynos Specific UFSHCI driver"); MODULE_AUTHOR("Seungwon Jeon "); MODULE_AUTHOR("Kiwoong Kim "); MODULE_LICENSE("GPL");