diff options
Diffstat (limited to 'drivers/ufs/ufs-mediatek.c')
| -rw-r--r-- | drivers/ufs/ufs-mediatek.c | 395 |
1 files changed, 395 insertions, 0 deletions
diff --git a/drivers/ufs/ufs-mediatek.c b/drivers/ufs/ufs-mediatek.c new file mode 100644 index 00000000000..e860d765eea --- /dev/null +++ b/drivers/ufs/ufs-mediatek.c @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2025, Igor Belwon <igor.belwon@mentallysanemainliners.org> + * + * Loosely based on Linux driver: drivers/ufs/host/ufs-mediatek.c + */ + +#include <asm/io.h> +#include <clk.h> +#include <dm.h> +#include <dm/device_compat.h> +#include <generic-phy.h> +#include <ufs.h> +#include <asm/gpio.h> +#include <reset.h> + +#include <linux/arm-smccc.h> +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/err.h> + +#include "ufs.h" +#include "ufs-mediatek.h" +#include "ufs-mediatek-sip.h" + +static void ufs_mtk_advertise_quirks(struct ufs_hba *hba) +{ + hba->quirks |= UFSHCI_QUIRK_SKIP_MANUAL_WB_FLUSH_CTRL | + UFSHCD_QUIRK_MCQ_BROKEN_INTR | + UFSHCD_QUIRK_BROKEN_LSDBS_CAP; +} + +static int ufs_mtk_hce_enable_notify(struct ufs_hba *hba, + enum ufs_notify_change_status status) +{ + struct ufs_mtk_host *host = dev_get_priv(hba->dev); + + if (status == PRE_CHANGE) { + if (host->caps & UFS_MTK_CAP_DISABLE_AH8) { + ufshcd_writel(hba, 0, + REG_AUTO_HIBERNATE_IDLE_TIMER); + hba->capabilities &= ~MASK_AUTO_HIBERN8_SUPPORT; + } + + /* + * Turn on CLK_CG early to bypass abnormal ERR_CHK signal + * to prevent host hang issue + */ + ufshcd_writel(hba, + ufshcd_readl(hba, REG_UFS_XOUFS_CTRL) | 0x80, + REG_UFS_XOUFS_CTRL); + + /* DDR_EN setting */ + if (host->ip_ver >= IP_VER_MT6989) { + ufshcd_rmwl(hba, UFS_MASK(0x7FFF, 8), + 0x453000, REG_UFS_MMIO_OPT_CTRL_0); + } + } + + return 0; +} + +static int ufs_mtk_unipro_set_lpm(struct ufs_hba *hba, bool lpm) +{ + int ret; + struct ufs_mtk_host *host = dev_get_priv(hba->dev); + + ret = ufshcd_dme_set(hba, + UIC_ARG_MIB_SEL(VS_UNIPROPOWERDOWNCONTROL, 0), + lpm ? 1 : 0); + if (!ret || !lpm) { + /* + * Forcibly set as non-LPM mode if UIC commands is failed + * to use default hba_enable_delay_us value for re-enabling + * the host. + */ + host->unipro_lpm = lpm; + } + + return ret; +} + +static int ufs_mtk_pre_link(struct ufs_hba *hba) +{ + int ret; + u32 tmp; + + ret = ufs_mtk_unipro_set_lpm(hba, false); + if (ret) + return ret; + + /* + * Setting PA_Local_TX_LCC_Enable to 0 before link startup + * to make sure that both host and device TX LCC are disabled + * once link startup is completed. + */ + ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_LOCAL_TX_LCC_ENABLE), 0); + if (ret) + return ret; + + /* disable deep stall */ + ret = ufshcd_dme_get(hba, UIC_ARG_MIB(VS_SAVEPOWERCONTROL), &tmp); + if (ret) + return ret; + + tmp &= ~(1 << 6); + + ret = ufshcd_dme_set(hba, UIC_ARG_MIB(VS_SAVEPOWERCONTROL), tmp); + if (ret) + return ret; + + ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_SCRAMBLING), tmp); + + return ret; +} + +static void ufs_mtk_cfg_unipro_cg(struct ufs_hba *hba, bool enable) +{ + u32 tmp; + + if (enable) { + ufshcd_dme_get(hba, + UIC_ARG_MIB(VS_SAVEPOWERCONTROL), &tmp); + tmp = tmp | + (1 << RX_SYMBOL_CLK_GATE_EN) | + (1 << SYS_CLK_GATE_EN) | + (1 << TX_CLK_GATE_EN); + ufshcd_dme_set(hba, + UIC_ARG_MIB(VS_SAVEPOWERCONTROL), tmp); + + ufshcd_dme_get(hba, + UIC_ARG_MIB(VS_DEBUGCLOCKENABLE), &tmp); + tmp = tmp & ~(1 << TX_SYMBOL_CLK_REQ_FORCE); + ufshcd_dme_set(hba, + UIC_ARG_MIB(VS_DEBUGCLOCKENABLE), tmp); + } else { + ufshcd_dme_get(hba, + UIC_ARG_MIB(VS_SAVEPOWERCONTROL), &tmp); + tmp = tmp & ~((1 << RX_SYMBOL_CLK_GATE_EN) | + (1 << SYS_CLK_GATE_EN) | + (1 << TX_CLK_GATE_EN)); + ufshcd_dme_set(hba, + UIC_ARG_MIB(VS_SAVEPOWERCONTROL), tmp); + + ufshcd_dme_get(hba, + UIC_ARG_MIB(VS_DEBUGCLOCKENABLE), &tmp); + tmp = tmp | (1 << TX_SYMBOL_CLK_REQ_FORCE); + ufshcd_dme_set(hba, + UIC_ARG_MIB(VS_DEBUGCLOCKENABLE), tmp); + } +} + +static void ufs_mtk_post_link(struct ufs_hba *hba) +{ + /* enable unipro clock gating feature */ + ufs_mtk_cfg_unipro_cg(hba, true); +} + +static int ufs_mtk_link_startup_notify(struct ufs_hba *hba, + enum ufs_notify_change_status status) +{ + int ret = 0; + + switch (status) { + case PRE_CHANGE: + ret = ufs_mtk_pre_link(hba); + break; + case POST_CHANGE: + ufs_mtk_post_link(hba); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int ufs_mtk_bind_mphy(struct ufs_hba *hba) +{ + struct ufs_mtk_host *host = dev_get_priv(hba->dev); + int err = 0; + + err = generic_phy_get_by_index(hba->dev, 0, host->mphy); + + if (IS_ERR(host->mphy)) { + err = PTR_ERR(host->mphy); + if (err != -ENODEV) { + dev_info(hba->dev, "%s: Could NOT get a valid PHY %d\n", __func__, + err); + } + } + + if (err) + host->mphy = NULL; + + return err; +} + +static void ufs_mtk_init_reset_control(struct ufs_hba *hba, + struct reset_ctl **rc, + char *str) +{ + *rc = devm_reset_control_get(hba->dev, str); + if (IS_ERR(*rc)) { + dev_info(hba->dev, "Failed to get reset control %s: %ld\n", + str, PTR_ERR(*rc)); + *rc = NULL; + } +} + +static void ufs_mtk_init_reset(struct ufs_hba *hba) +{ + struct ufs_mtk_host *host = dev_get_priv(hba->dev); + + ufs_mtk_init_reset_control(hba, &host->hci_reset, + "hci_rst"); + ufs_mtk_init_reset_control(hba, &host->unipro_reset, + "unipro_rst"); + ufs_mtk_init_reset_control(hba, &host->crypto_reset, + "crypto_rst"); +} + +static void ufs_mtk_get_hw_ip_version(struct ufs_hba *hba) +{ + struct ufs_mtk_host *host = dev_get_priv(hba->dev); + u32 hw_ip_ver; + + hw_ip_ver = ufshcd_readl(hba, REG_UFS_MTK_IP_VER); + + if (((hw_ip_ver & (0xFF << 24)) == (0x1 << 24)) || + ((hw_ip_ver & (0xFF << 24)) == 0)) { + hw_ip_ver &= ~(0xFF << 24); + hw_ip_ver |= (0x1 << 28); + } + + host->ip_ver = hw_ip_ver; + + dev_info(hba->dev, "MediaTek UFS IP Version: 0x%x\n", hw_ip_ver); +} + +static int ufs_mtk_setup_ref_clk(struct ufs_hba *hba, bool on) +{ + struct ufs_mtk_host *host = dev_get_priv(hba->dev); + struct arm_smccc_res res; + int timeout, time_checked = 0; + u32 value; + + if (host->ref_clk_enabled == on) + return 0; + + ufs_mtk_ref_clk_notify(on, PRE_CHANGE, res); + + if (on) { + ufshcd_writel(hba, REFCLK_REQUEST, REG_UFS_REFCLK_CTRL); + } else { + udelay(10); + ufshcd_writel(hba, REFCLK_RELEASE, REG_UFS_REFCLK_CTRL); + } + + /* Wait for ack */ + timeout = REFCLK_REQ_TIMEOUT_US; + do { + value = ufshcd_readl(hba, REG_UFS_REFCLK_CTRL); + + /* Wait until ack bit equals to req bit */ + if (((value & REFCLK_ACK) >> 1) == (value & REFCLK_REQUEST)) + goto out; + + udelay(200); + time_checked += 200; + } while (time_checked != timeout); + + dev_err(hba->dev, "missing ack of refclk req, reg: 0x%x\n", value); + + /* + * If clock on timeout, assume clock is off, notify tfa do clock + * off setting.(keep DIFN disable, release resource) + * If clock off timeout, assume clock will off finally, + * set ref_clk_enabled directly.(keep DIFN disable, keep resource) + */ + if (on) + ufs_mtk_ref_clk_notify(false, POST_CHANGE, res); + else + host->ref_clk_enabled = false; + + return -ETIMEDOUT; + +out: + host->ref_clk_enabled = on; + if (on) + udelay(10); + + ufs_mtk_ref_clk_notify(on, POST_CHANGE, res); + + return 0; +} + +/** + * ufs_mtk_init - bind phy with controller + * @hba: host controller instance + * + * Powers up PHY enabling clocks and regulators. + * + * Returns -ENODEV if binding fails, returns negative error + * on phy power up failure and returns zero on success. + */ +static int ufs_mtk_init(struct ufs_hba *hba) +{ + struct ufs_mtk_host *priv = dev_get_priv(hba->dev); + int err; + + priv->hba = hba; + + err = ufs_mtk_bind_mphy(hba); + if (err) + return -ENODEV; + + ufs_mtk_advertise_quirks(hba); + + ufs_mtk_init_reset(hba); + + // TODO: Clocking + + err = generic_phy_power_on(priv->mphy); + if (err) { + dev_err(hba->dev, "%s: phy init failed, err = %d\n", + __func__, err); + return err; + } + + ufs_mtk_setup_ref_clk(hba, true); + ufs_mtk_get_hw_ip_version(hba); + + return 0; +} + +static int ufs_mtk_device_reset(struct ufs_hba *hba) +{ + struct arm_smccc_res res; + + ufs_mtk_device_reset_ctrl(0, res); + + /* + * The reset signal is active low. UFS devices shall detect + * more than or equal to 1us of positive or negative RST_n + * pulse width. + * + * To be on safe side, keep the reset low for at least 10us. + */ + udelay(13); + + ufs_mtk_device_reset_ctrl(1, res); + + /* Some devices may need time to respond to rst_n */ + mdelay(13); + + dev_dbg(hba->dev, "device reset done\n"); + + return 0; +} + +static struct ufs_hba_ops ufs_mtk_hba_ops = { + .init = ufs_mtk_init, + .hce_enable_notify = ufs_mtk_hce_enable_notify, + .link_startup_notify = ufs_mtk_link_startup_notify, + .device_reset = ufs_mtk_device_reset, +}; + +static int ufs_mtk_probe(struct udevice *dev) +{ + int ret; + + ret = ufshcd_probe(dev, &ufs_mtk_hba_ops); + if (ret) { + dev_err(dev, "ufshcd_probe() failed, ret:%d\n", ret); + return ret; + } + + return 0; +} + +static const struct udevice_id ufs_mtk_ids[] = { + { .compatible = "mediatek,mt6878-ufshci" }, + {}, +}; + +U_BOOT_DRIVER(mediatek_ufshci) = { + .name = "mediatek-ufshci", + .id = UCLASS_UFS, + .of_match = ufs_mtk_ids, + .probe = ufs_mtk_probe, + .priv_auto = sizeof(struct ufs_mtk_host), +}; |
