// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013 BayHub Technology Ltd.
*
* Authors: Peter Guo <peter.guo@bayhubtech.com>
* Adam Lee <adam.lee@canonical.com>
* Ernest Zhang <ernest.zhang@bayhubtech.com>
*/
#include <linux/pci.h>
#include <linux/mmc/host.h>
#include <linux/mmc/mmc.h>
#include <linux/delay.h>
#include <linux/iopoll.h>
#include <linux/bitfield.h>
#include "sdhci.h"
#include "sdhci-pci.h"
/*
* O2Micro device registers
*/
#define O2_SD_PCIE_SWITCH 0x54
#define O2_SD_MISC_REG5 0x64
#define O2_SD_LD0_CTRL 0x68
#define O2_SD_DEV_CTRL 0x88
#define O2_SD_LOCK_WP 0xD3
#define O2_SD_TEST_REG 0xD4
#define O2_SD_FUNC_REG0 0xDC
#define O2_SD_MULTI_VCC3V 0xEE
#define O2_SD_CLKREQ 0xEC
#define O2_SD_CAPS 0xE0
#define O2_SD_ADMA1 0xE2
#define O2_SD_ADMA2 0xE7
#define O2_SD_MISC_CTRL2 0xF0
#define O2_SD_INF_MOD 0xF1
#define O2_SD_MISC_CTRL4 0xFC
#define O2_SD_MISC_CTRL 0x1C0
#define O2_SD_EXP_INT_REG 0x1E0
#define O2_SD_PWR_FORCE_L0 0x0002
#define O2_SD_TUNING_CTRL 0x300
#define O2_SD_PLL_SETTING 0x304
#define O2_SD_MISC_SETTING 0x308
#define O2_SD_CLK_SETTING 0x328
#define O2_SD_CAP_REG2 0x330
#define O2_SD_CAP_REG0 0x334
#define O2_SD_UHS1_CAP_SETTING 0x33C
#define O2_SD_DELAY_CTRL 0x350
#define O2_SD_OUTPUT_CLK_SOURCE_SWITCH 0x354
#define O2_SD_UHS2_L1_CTRL 0x35C
#define O2_SD_FUNC_REG3 0x3E0
#define O2_SD_FUNC_REG4 0x3E4
#define O2_SD_PARA_SET_REG1 0x444
#define O2_SD_VDDX_CTRL_REG 0x508
#define O2_SD_GPIO_CTRL_REG1 0x510
#define O2_SD_LED_ENABLE BIT(6)
#define O2_SD_FREG0_LEDOFF BIT(13)
#define O2_SD_SEL_DLL BIT(16)
#define O2_SD_FREG4_ENABLE_CLK_SET BIT(22)
#define O2_SD_PHASE_MASK GENMASK(23, 20)
#define O2_SD_FIX_PHASE FIELD_PREP(O2_SD_PHASE_MASK, 0x9)
#define O2_SD_VENDOR_SETTING 0x110
#define O2_SD_VENDOR_SETTING2 0x1C8
#define O2_SD_HW_TUNING_DISABLE BIT(4)
#define O2_PLL_DLL_WDT_CONTROL1 0x1CC
#define O2_PLL_FORCE_ACTIVE BIT(18)
#define O2_PLL_LOCK_STATUS BIT(14)
#define O2_PLL_SOFT_RESET BIT(12)
#define O2_DLL_LOCK_STATUS BIT(11)
#define O2_SD_DETECT_SETTING 0x324
static const u32 dmdn_table[] = {0x2B1C0000,
0x2C1A0000, 0x371B0000, 0x35100000};
#define DMDN_SZ ARRAY_SIZE(dmdn_table)
struct o2_host {
u8 dll_adjust_count;
};
static void sdhci_o2_wait_card_detect_stable(struct sdhci_host *host)
{
ktime_t timeout;
u32 scratch32;
/* Wait max 50 ms */
timeout = ktime_add_ms(ktime_get(), 50);
while (1) {
bool timedout = ktime_after(ktime_get(), timeout);
scratch32 = sdhci_readl(host, SDHCI_PRESENT_STATE);
if ((scratch32 & SDHCI_CARD_PRESENT) >> SDHCI_CARD_PRES_SHIFT
== (scratch32 & SDHCI_CD_LVL) >> SDHCI_CD_LVL_SHIFT)
break;
if (timedout) {
pr_err("%s: Card Detect debounce never finished.\n",
mmc_hostname(host->mmc));
sdhci_dumpregs(host);
return;
}
udelay(10);
}
}
static void sdhci_o2_enable_internal_clock(struct sdhci_host *host)
{
ktime_t timeout;
u16 scratch;
u32 scratch32;
/* PLL software reset */
scratch32 = sdhci_readl(host, O2_PLL_DLL_WDT_CONTROL1);
scratch32 |= O2_PLL_SOFT_RESET;
sdhci_writel(host, scratch32, O2_PLL_DLL_WDT_CONTROL1);
udelay(1);
scratch32 &= ~(O2_PLL_SOFT_RESET);
sdhci_writel(host, scratch32, O2_PLL_DLL_WDT_CONTROL1);
/* PLL force active */
scratch32 |= O2_PLL_FORCE_ACTIVE;
sdhci_writel(host, scratch32, O2_PLL_DLL_WDT_CONTROL1);
/* Wait max 20 ms */
timeout = ktime_add_ms(ktime_get(), 20);
while (1) {
bool timedout = ktime_after(ktime_get(), timeout);
scratch = sdhci_readw(host, O2_PLL_DLL_WDT_CONTROL1);
if (scratch & O2_PLL_LOCK_STATUS)
break;
if (timedout) {
pr_err("%s: Internal clock never stabilised.\n",
mmc_hostname(host->mmc));
sdhci_dumpregs(host);
goto out;
}
udelay(10);
}
/* Wait for card detect finish */
udelay(1);
sdhci_o2_wait_card_detect_stable(host);
out:
/* Cancel PLL force active */
scratch32 = sdhci_readl(host, O2_PLL_DLL_WDT_CONTROL1);
scratch32 &= ~O2_PLL_FORCE_ACTIVE;
sdhci_writel(host, scratch32, O2_PLL_DLL_WDT_CONTROL1);
}
static int sdhci_o2_get_cd(struct mmc_host *mmc)
{
struct sdhci_host *host = mmc_priv(mmc);
if (!(sdhci_readw(host, O2_PLL_DLL_WDT_CONTROL1) & O2_PLL_LOCK_STATUS))
sdhci_o2_enable_internal_clock(host);
else
sdhci_o2_wait_card_detect_stable(host);
return !!(sdhci_readl(host, SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT);
}
static void o2_pci_set_baseclk(struct sdhci_pci_chip *chip, u32 value)
{
u32 scratch_32;
pci_read_config_dword(chip->pdev,
O2_SD_PLL_SETTING, &scratch_32);
scratch_32 &= 0x0000FFFF;
scratch_32 |= value;
pci_write_config_dword(chip->pdev,
O2_SD_PLL_SETTING, scratch_32);
}
static u32 sdhci_o2_pll_dll_wdt_control(struct sdhci_host *host)
{
return sdhci_readl(host, O2_PLL_DLL_WDT_CONTROL1);
}
/*
* This function is used to detect dll lock status.
* Since the dll lock status bit will toggle randomly
* with very short interval which needs to be polled
* as fast as possible. Set sleep_us as 1 microsecond.
*/
static int sdhci_o2_wait_dll_detect_lock(struct sdhci_host *host)
{
u32 scratch32 = 0;
return readx_poll_timeout(sdhci_o2_pll_dll_wdt_control, host,
scratch32, !(scratch32 & O2_DLL_LOCK_STATUS), 1, 1000000);
}
static void sdhci_o2_set_tuning_mode(struct sdhci_host *host)
{
u16 reg;
/* enable hardware tuning */
reg = sdhci_readw(host, O2_SD_VENDOR_SETTING);
reg &= ~O2_SD_HW_TUNING_DISABLE;
sdhci_writew(host, reg, O2_SD_VENDOR_SETTING);
}
static void __sdhci_o2_execute_tuning(struct