Instead of duplicating the ufs_scsi_bind() call in every driver, call it from UFS uclass .post_bind callback for every driver in one place. While doing so, inline ufs_scsi_bind() directly into ufs_post_bind() as trivial device_bind_driver() call. Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org> Signed-off-by: Marek Vasut <marek.vasut+renesas@mailbox.org> Link: https://patch.msgid.link/20251028142335.18125-5-marek.vasut+renesas@mailbox.org [narmstrong: also updated the rockchip and mediatek drivers] Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
396 lines
8.7 KiB
C
396 lines
8.7 KiB
C
// 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),
|
|
};
|