#include <sys/param.h>
#include <sys/malloc.h>
#include <sys/systm.h>
#include <machine/bus.h>
#include <machine/fdt.h>
#include <machine/intr.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_clock.h>
#include <dev/ofw/ofw_gpio.h>
#include <dev/ofw/ofw_misc.h>
#include <dev/ofw/ofw_pinctrl.h>
#include <dev/ofw/ofw_regulator.h>
#include <dev/ofw/fdt.h>
#include <dev/sdmmc/sdhcreg.h>
#include <dev/sdmmc/sdhcvar.h>
#include <dev/sdmmc/sdmmcvar.h>
#define EMMC_VER_ID 0x500
#define EMMC_VER_TYPE 0x504
#define EMMC_HOST_CTRL3 0x508
#define EMMC_HOST_CTRL3_CMD_CONFLICT_CHECK (1U << 0)
#define EMMC_HOST_CTRL3_SW_CG_DIS (1U << 4)
#define EMMC_EMMC_CTRL 0x52c
#define EMMC_EMMC_CTRL_CARD_IS_EMMC (1U << 0)
#define EMMC_EMMC_CTRL_DISABLE_DATA_CRC_CHK (1U << 1)
#define EMMC_EMMC_CTRL_EMMC_RST_N (1U << 2)
#define EMMC_EMMC_CTRL_EMMC_RST_N_OE (1U << 3)
#define EMMC_EMMC_CTRL_ENH_STROBE_ENABLE (1U << 8)
#define EMMC_EMMC_CTRL_CQE_ALGO_SEL (1U << 9)
#define EMMC_EMMC_CTRL_CQE_PREFETCH_DISABLE (1U << 10)
#define EMMC_BOOT_CTRL 0x52e
#define EMMC_BOOT_CTRL_MAN_BOOT_EN (1U << 0)
#define EMMC_BOOT_CTRL_VALIDATE_BOOT (1U << 1)
#define EMMC_BOOT_CTRL_BOOT_ACK_ENABLE (1U << 8)
#define EMMC_BOOT_CTRL_BOOT_TOUT_CNT_SHIFT 12
#define EMMC_BOOT_CTRL_BOOT_TOUT_CNT_MASK 0xf
#define EMMC_AT_CTRL 0x540
#define EMMC_AT_CTRL_SWIN_TH_EN (1U << 2)
#define EMMC_AT_CTRL_RPT_TUNE_ERR (1U << 3)
#define EMMC_AT_CTRL_SW_TUNE_EN (1U << 4)
#define EMMC_AT_CTRL_TUNE_CLK_STOP_EN (1U << 16)
#define EMMC_AT_CTRL_PRE_CHANGE_DLY_MASK (0x3 << 17)
#define EMMC_AT_CTRL_PRE_CHANGE_DLY_LT1 (0x0 << 17)
#define EMMC_AT_CTRL_PRE_CHANGE_DLY_LT2 (0x1 << 17)
#define EMMC_AT_CTRL_PRE_CHANGE_DLY_LT3 (0x2 << 17)
#define EMMC_AT_CTRL_PRE_CHANGE_DLY_LT4 (0x3 << 17)
#define EMMC_AT_CTRL_POST_CHANGE_DLY_MASK (0x3 << 19)
#define EMMC_AT_CTRL_POST_CHANGE_DLY_LT1 (0x0 << 19)
#define EMMC_AT_CTRL_POST_CHANGE_DLY_LT2 (0x1 << 19)
#define EMMC_AT_CTRL_POST_CHANGE_DLY_LT3 (0x2 << 19)
#define EMMC_AT_CTRL_POST_CHANGE_DLY_LT4 (0x3 << 19)
#define EMMC_AT_STAT 0x544
#define EMMC_AT_STAT_CENTER_PH_CODE_SHIFT 0
#define EMMC_AT_STAT_CENTER_PH_CODE_MASK 0xff
#define EMMC_AT_STAT_R_EDGE_PH_CODE_SHIFT 8
#define EMMC_AT_STAT_R_EDGE_PH_CODE_MASK 0xff
#define EMMC_AT_STAT_L_EDGE_PH_CODE_SHIFT 16
#define EMMC_AT_STAT_L_EDGE_PH_CODE_MASK 0xff
#define EMMC_DLL_CTRL 0x800
#define EMMC_DLL_CTRL_DLL_START (1U << 0)
#define EMMC_DLL_CTRL_DLL_SRST (1U << 1)
#define EMMC_DLL_CTRL_DLL_INCREMENT_SHIFT 8
#define EMMC_DLL_CTRL_DLL_INCREMENT_MASK 0xff
#define EMMC_DLL_CTRL_DLL_START_POINT_SHIFT 16
#define EMMC_DLL_CTRL_DLL_START_POINT_MASK 0xff
#define EMMC_DLL_CTRL_DLL_BYPASS_MODE (1U << 24)
#define EMMC_DLL_RXCLK 0x804
#define EMMC_DLL_RXCLK_RX_TAP_NUM_SHIFT 0
#define EMMC_DLL_RXCLK_RX_TAP_NUM_MASK 0x1f
#define EMMC_DLL_RXCLK_RX_TAP_VALUE_SHIFT 8
#define EMMC_DLL_RXCLK_RX_TAP_VALUE_MASK 0xff
#define EMMC_DLL_RXCLK_RX_DELAY_NUM_SHIFT 16
#define EMMC_DLL_RXCLK_RX_DELAY_NUM_MASK 0xff
#define EMMC_DLL_RXCLK_RX_TAP_NUM_SEL (1U << 24)
#define EMMC_DLL_RXCLK_RX_TAP_VALUE_SEL (1U << 25)
#define EMMC_DLL_RXCLK_RX_DELAY_NUM_SEL (1U << 26)
#define EMMC_DLL_RXCLK_RX_CLK_OUT_SEL (1U << 27)
#define EMMC_DLL_RXCLK_RX_CLK_CHANGE_WINDOW (1U << 28)
#define EMMC_DLL_RXCLK_RX_CLK_SRC_SEL (1U << 29)
#define EMMC_DLL_TXCLK 0x808
#define EMMC_DLL_TXCLK_TX_TAP_NUM_SHIFT 0
#define EMMC_DLL_TXCLK_TX_TAP_NUM_MASK 0x1f
#define EMMC_DLL_TXCLK_TX_TAP_NUM_90_DEG 0x8
#define EMMC_DLL_TXCLK_TX_TAP_NUM_135_DEG 0xc
#define EMMC_DLL_TXCLK_TX_TAP_NUM_DEFAULT 0x10
#define EMMC_DLL_TXCLK_TX_TAP_VALUE_SHIFT 8
#define EMMC_DLL_TXCLK_TX_TAP_VALUE_MASK 0xff
#define EMMC_DLL_TXCLK_TX_DELAY_SHIFT 16
#define EMMC_DLL_TXCLK_TX_DELAY_MASK 0xff
#define EMMC_DLL_TXCLK_TX_TAP_NUM_SEL (1U << 24)
#define EMMC_DLL_TXCLK_TX_TAP_VALUE_SEL (1U << 25)
#define EMMC_DLL_TXCLK_TX_DELAY_SEL (1U << 26)
#define EMMC_DLL_TXCLK_TX_CLK_OUT_SEL (1U << 27)
#define EMMC_DLL_STRBIN 0x80c
#define EMMC_DLL_STRBIN_TAP_NUM_SHIFT 0
#define EMMC_DLL_STRBIN_TAP_NUM_MASK 0x1f
#define EMMC_DLL_STRBIN_TAP_NUM_90_DEG 0x8
#define EMMC_DLL_STRBIN_TAP_VALUE_SHIFT 8
#define EMMC_DLL_STRBIN_TAP_VALUE_MASK 0xff
#define EMMC_DLL_STRBIN_DELAY_NUM_SHIFT 16
#define EMMC_DLL_STRBIN_DELAY_NUM_MASK 0xff
#define EMMC_DLL_STRBIN_DELAY_NUM_DEFAULT 0x16
#define EMMC_DLL_STRBIN_TAP_NUM_SEL (1U << 24)
#define EMMC_DLL_STRBIN_TAP_VALUE_SEL (1U << 25)
#define EMMC_DLL_STRBIN_DELAY_NUM_SEL (1U << 26)
#define EMMC_DLL_STRBIN_DELAY_ENA (1U << 27)
#define EMMC_DLL_CMDOUT 0x810
#define EMMC_DLL_CMDOUT_TAP_NUM_SHIFT 0
#define EMMC_DLL_CMDOUT_TAP_NUM_MASK 0x1f
#define EMMC_DLL_CMDOUT_TAP_NUM_90_DEG 0x8
#define EMMC_DLL_CMDOUT_TAP_VALUE_SHIFT 8
#define EMMC_DLL_CMDOUT_TAP_VALUE_MASK 0xff
#define EMMC_DLL_CMDOUT_DELAY_NUM_SHIFT 16
#define EMMC_DLL_CMDOUT_DELAY_NUM_MASK 0xff
#define EMMC_DLL_CMDOUT_TAP_NUM_SEL (1U << 24)
#define EMMC_DLL_CMDOUT_TAP_VALUE_SEL (1U << 25)
#define EMMC_DLL_CMDOUT_DELAY_NUM_SEL (1U << 26)
#define EMMC_DLL_CMDOUT_DELAY_ENA (1U << 27)
#define EMMC_DLL_CMDOUT_SRC_SEL (1U << 28)
#define EMMC_DLL_CMDOUT_EN_SRC_SEL (1U << 29)
#define EMMC_DLL_STATUS0 0x840
#define EMMC_DLL_STATUS0_DLL_LOCK_VALUE_SHIFT 0
#define EMMC_DLL_STATUS0_DLL_LOCK_VALUE_MASK 0xff
#define EMMC_DLL_STATUS0_DLL_LOCK (1U << 8)
#define EMMC_DLL_STATUS0_DLL_LOCK_TIMEOUT (1U << 9)
#define EMMC_DLL_STATUS1 0x844
#define EMMC_DLL_STATUS1_DLL_TXCLK_DELAY_VALUE_SHIFT \
0
#define EMMC_DLL_STATUS1_DLL_TXCLK_DELAY_VALUE_MASK \
0xff
#define EMMC_DLL_STATUS1_DLL_RXCLK_DELAY_VALUE_SHIFT \
8
#define EMMC_DLL_STATUS1_DLL_RXCLK_DELAY_VALUE_MASK \
0xff
#define EMMC_DLL_STATUS1_DLL_STRBIN_DELAY_VALUE_SHIFT \
16
#define EMMC_DLL_STATUS1_DLL_STRBIN_DELAY_VALUE_MASK \
0xff
struct dwmshc_params {
const char *compatible;
uint32_t txclk_tapnum;
uint32_t rxclk;
int use_dll_tap_value;
};
struct dwmshc_softc {
struct sdhc_softc sc_sdhc;
int sc_node;
const struct dwmshc_params *sc_params;
bus_space_tag_t sc_iot;
bus_space_handle_t sc_ioh;
bus_size_t sc_ios;
bus_dma_tag_t sc_dmat;
void *sc_ih;
struct sdhc_host *sc_host;
};
static int dwmshc_match(struct device *, void *, void *);
static void dwmshc_attach(struct device *, struct device *,
void *);
static inline void dwmshc_wr1(struct dwmshc_softc *,
bus_size_t, uint8_t);
static inline void dwmshc_wr4(struct dwmshc_softc *,
bus_size_t, uint32_t);
static inline uint32_t dwmshc_rd4(struct dwmshc_softc *, bus_size_t);
static void dwmshc_clock_pre(struct sdhc_softc *, int, int);
static void dwmshc_clock_post(struct sdhc_softc *, int, int);
static int dwmshc_non_removable(struct sdhc_softc *);
const struct cfattach dwmshc_ca = {
sizeof(struct dwmshc_softc), dwmshc_match, dwmshc_attach
};
struct cfdriver dwmshc_cd = {
NULL, "dwmshc", DV_DULL
};
static const struct dwmshc_params dwmshc_hw_params[] = {
{
.compatible = "rockchip,rk3528-dwcmshc",
.txclk_tapnum = EMMC_DLL_TXCLK_TX_TAP_NUM_135_DEG,
.rxclk = EMMC_DLL_RXCLK_RX_CLK_OUT_SEL,
.use_dll_tap_value = 1
},
{
.compatible = "rockchip,rk3568-dwcmshc",
.txclk_tapnum = EMMC_DLL_TXCLK_TX_TAP_NUM_DEFAULT,
.rxclk = EMMC_DLL_RXCLK_RX_CLK_OUT_SEL |
EMMC_DLL_RXCLK_RX_CLK_SRC_SEL,
.use_dll_tap_value = 0
},
{
.compatible = "rockchip,rk3588-dwcmshc",
.txclk_tapnum = EMMC_DLL_TXCLK_TX_TAP_NUM_DEFAULT,
.rxclk = EMMC_DLL_RXCLK_RX_CLK_OUT_SEL,
.use_dll_tap_value = 0
}
};
int
dwmshc_match(struct device *parent, void *match, void *aux)
{
struct fdt_attach_args *faa = aux;
int i;
for (i = 0; i < nitems(dwmshc_hw_params); i++) {
if (OF_is_compatible(faa->fa_node,
dwmshc_hw_params[i].compatible))
return 1;
}
return 0;
}
static void
dwmshc_attach(struct device *parent, struct device *self, void *aux)
{
struct dwmshc_softc *sc = (struct dwmshc_softc *)self;
struct sdhc_softc *sdhc = &sc->sc_sdhc;
struct fdt_attach_args *faa = aux;
uint64_t capmask = 0;
uint16_t capset = 0;
int bus_width, i;
uint32_t freq;
sc->sc_node = faa->fa_node;
sc->sc_iot = faa->fa_iot;
sc->sc_ios = faa->fa_reg[0].size;
sc->sc_dmat = faa->fa_dmat;
if (faa->fa_nreg < 1) {
printf(": no registers\n");
return;
}
if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
faa->fa_reg[0].size, 0, &sc->sc_ioh)) {
printf(": can't map registers\n");
return;
}
for (i = 0; i < nitems(dwmshc_hw_params); i++) {
if (OF_is_compatible(sc->sc_node,
dwmshc_hw_params[i].compatible)) {
sc->sc_params = &dwmshc_hw_params[i];
break;
}
}
pinctrl_byname(sc->sc_node, "default");
clock_set_assigned(sc->sc_node);
clock_enable_all(sc->sc_node);
reset_deassert_all(sc->sc_node);
sc->sc_ih = fdt_intr_establish(faa->fa_node, IPL_BIO,
sdhc_intr, sdhc, DEVNAME(sdhc));
if (sc->sc_ih == NULL) {
printf(": can't establish interrupt\n");
return;
}
printf("\n");
dwmshc_wr1(sc, EMMC_HOST_CTRL3, 0);
sdhc->sc_host = &sc->sc_host;
sdhc->sc_dmat = faa->fa_dmat;
sdhc->sc_dma_boundary = 128 * 1024 * 1024;
sdhc->sc_bus_clock_pre = dwmshc_clock_pre;
sdhc->sc_bus_clock_post = dwmshc_clock_post;
if (OF_getpropbool(sc->sc_node, "non-removable")) {
SET(sdhc->sc_flags, SDHC_F_NONREMOVABLE);
sdhc->sc_card_detect = dwmshc_non_removable;
}
bus_width = OF_getpropint(faa->fa_node, "bus-width", 1);
if (bus_width < 8)
SET(capmask, SDHC_8BIT_MODE_SUPP);
freq = clock_get_frequency(faa->fa_node, "block");
sdhc->sc_clkbase = freq / 1000;
SET(sdhc->sc_flags, SDHC_F_NOPWR0);
SET(capmask, (uint64_t)SDHC_DDR50_SUPP << 32);
sdhc_host_found(sdhc, sc->sc_iot, sc->sc_ioh, sc->sc_ios, 1,
capmask, capset);
}
static void
dwmshc_clock_pre(struct sdhc_softc *sdhc, int freq, int timing)
{
#if 0
struct dwmshc_softc *sc = (struct dwmshc_softc *)sdhc;
if (timing == hs400es) {
dwmshc_wr4(sc, EMMC_DLL_STRBIN, EMMC_DLL_STRBIN_DELAY_ENA |
EMMC_DLL_STRBIN_DELAY_NUM_SEL |
(EMMC_DLL_STRBIN_DELAY_NUM_DEFAULT <<
EMMC_DLL_STRBIN_DELAY_NUM_SHIFT));
}
#endif
}
static int
dwmshc_dll_wait(struct dwmshc_softc *sc)
{
uint32_t status0;
int i;
for (i = 0; i < 500 * 1000; i++) {
delay(1);
status0 = dwmshc_rd4(sc, EMMC_DLL_STATUS0);
if (ISSET(status0, EMMC_DLL_STATUS0_DLL_LOCK)) {
if (ISSET(status0, EMMC_DLL_STATUS0_DLL_LOCK_TIMEOUT)) {
printf("%s: lock timeout\n",
DEVNAME(&sc->sc_sdhc));
return (EIO);
}
return (0);
}
}
printf("%s: poll timeout\n", DEVNAME(&sc->sc_sdhc));
return (ETIMEDOUT);
}
static void
dwmshc_clock_post(struct sdhc_softc *sdhc, int freq, int timing)
{
struct dwmshc_softc *sc = (struct dwmshc_softc *)sdhc;
uint32_t txclk_tapnum = sc->sc_params->txclk_tapnum;
uint32_t dll_val;
clock_set_frequency(sc->sc_node, 0, freq * 1000);
if (timing == SDMMC_TIMING_LEGACY) {
dwmshc_wr4(sc, EMMC_DLL_CTRL,
EMMC_DLL_CTRL_DLL_START | EMMC_DLL_CTRL_DLL_BYPASS_MODE);
dwmshc_wr4(sc, EMMC_DLL_RXCLK, 0);
dwmshc_wr4(sc, EMMC_DLL_TXCLK, 0);
dwmshc_wr4(sc, EMMC_DLL_STRBIN, 0);
return;
}
dwmshc_wr4(sc, EMMC_DLL_CTRL, EMMC_DLL_CTRL_DLL_SRST);
delay(1);
dwmshc_wr4(sc, EMMC_DLL_CTRL, 0);
dwmshc_wr4(sc, EMMC_DLL_CTRL, EMMC_DLL_CTRL_DLL_START |
0x5 << EMMC_DLL_CTRL_DLL_START_POINT_SHIFT |
0x2 << EMMC_DLL_CTRL_DLL_INCREMENT_SHIFT);
if (dwmshc_dll_wait(sc) != 0)
return;
dll_val = 0;
if (sc->sc_params->use_dll_tap_value) {
dll_val = EMMC_DLL_RXCLK_RX_TAP_VALUE_SEL |
((dwmshc_rd4(sc, EMMC_DLL_STATUS0) &
EMMC_DLL_STATUS0_DLL_LOCK_VALUE_MASK) * 2);
}
dwmshc_wr4(sc, EMMC_DLL_RXCLK, sc->sc_params->rxclk | dll_val);
dwmshc_wr4(sc, EMMC_AT_CTRL, EMMC_AT_CTRL_TUNE_CLK_STOP_EN |
EMMC_AT_CTRL_PRE_CHANGE_DLY_LT4 |
EMMC_AT_CTRL_POST_CHANGE_DLY_LT4);
if (timing >= SDMMC_TIMING_MMC_HS200) {
txclk_tapnum = OF_getpropint(sc->sc_node,
"rockchip,txclk-tapnum", txclk_tapnum);
#ifdef notyet
if (OF_is_compatible(sc->sc_node, "rockchip,rk3588-dwcmshc") &&
timing == SDMMC_TIMING_MMC_HS400) {
txclk_tapnum = EMMC_DLL_TXCLK_TX_TAP_NUM_90_DEG;
dwmshc_wr4(sc, EMMC_DLL_CMDOUT,
EMMC_DLL_CMDOUT_TAP_NUM_90_DEG |
EMMC_DLL_CMDOUT_TAP_NUM_SEL |
EMMC_DLL_CMDOUT_DELAY_ENA |
EMMC_DLL_CMDOUT_SRC_SEL |
EMMC_DLL_CMDOUT_EN_SRC_SEL);
}
#endif
}
dwmshc_wr4(sc, EMMC_DLL_TXCLK, EMMC_DLL_TXCLK_TX_CLK_OUT_SEL |
EMMC_DLL_TXCLK_TX_TAP_NUM_SEL |
txclk_tapnum << EMMC_DLL_TXCLK_TX_TAP_NUM_SHIFT |
dll_val);
dwmshc_wr4(sc, EMMC_DLL_STRBIN, EMMC_DLL_STRBIN_DELAY_ENA |
EMMC_DLL_STRBIN_TAP_NUM_SEL |
(EMMC_DLL_STRBIN_TAP_NUM_90_DEG <<
EMMC_DLL_STRBIN_TAP_NUM_SHIFT) | dll_val);
}
static int
dwmshc_non_removable(struct sdhc_softc *sdhc)
{
return (1);
}
static inline void
dwmshc_wr1(struct dwmshc_softc *sc, bus_size_t off, uint8_t v)
{
bus_space_write_1(sc->sc_iot, sc->sc_ioh, off, v);
}
static inline void
dwmshc_wr4(struct dwmshc_softc *sc, bus_size_t off, uint32_t v)
{
bus_space_write_4(sc->sc_iot, sc->sc_ioh, off, v);
}
static inline uint32_t
dwmshc_rd4(struct dwmshc_softc *sc, bus_size_t off)
{
return (bus_space_read_4(sc->sc_iot, sc->sc_ioh, off));
}