#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 GRF_EMMCCORE_CON0_BASECLOCK 0xf000
#define GRF_EMMCCORE_CON0_BASECLOCK_CLR (0xff << 24)
#define GRF_EMMCCORE_CON0_BASECLOCK_VAL(x) (((x) & 0xff) << 8)
#define GRF_EMMCCORE_CON11 0xf02c
#define GRF_EMMCCORE_CON11_CLOCKMULT_CLR (0xff << 16)
#define GRF_EMMCCORE_CON11_CLOCKMULT_VAL(x) (((x) & 0xff) << 0)
#define XENON_SYS_OP_CTRL 0x108
#define XENON_SYS_OP_CTRL_SLOT_ENABLE(x) (1 << (x))
#define XENON_SYS_OP_CTRL_SDCLK_IDLEOFF_ENABLE(x) (1 << ((x) + 8))
#define XENON_SYS_OP_CTRL_AUTO_CLKGATE_DISABLE (1 << 20)
#define XENON_SYS_EXT_OP_CTRL 0x10c
#define XENON_SYS_EXT_OP_CTRL_PARALLEL_TRAN(x) (1 << (x))
#define XENON_SYS_EXT_OP_CTRL_MASK_CMD_CONFLICT_ERR (1 << 8)
#define XENON_SLOT_EMMC_CTRL 0x130
#define XENON_SLOT_EMMC_CTRL_ENABLE_DATA_STROBE (1 << 24)
#define XENON_SLOT_EMMC_CTRL_ENABLE_RESP_STROBE (1 << 25)
#define XENON_EMMC_PHY_TIMING_ADJUST 0x170
#define XENON_EMMC_PHY_TIMING_ADJUST_SAMPL_INV_QSP_PHASE_SELECT (1 << 18)
#define XENON_EMMC_PHY_TIMING_ADJUST_SDIO_MODE (1 << 28)
#define XENON_EMMC_PHY_TIMING_ADJUST_SLOW_MODE (1 << 29)
#define XENON_EMMC_PHY_TIMING_ADJUST_INIT (1U << 31)
#define XENON_EMMC_PHY_FUNC_CONTROL 0x174
#define XENON_EMMC_PHY_FUNC_CONTROL_DQ_ASYNC_MODE (1 << 4)
#define XENON_EMMC_PHY_FUNC_CONTROL_DQ_DDR_MODE (0xff << 8)
#define XENON_EMMC_PHY_FUNC_CONTROL_CMD_DDR_MODE (1 << 16)
#define XENON_EMMC_PHY_PAD_CONTROL 0x178
#define XENON_EMMC_PHY_PAD_CONTROL_FC_DQ_RECEN (1 << 24)
#define XENON_EMMC_PHY_PAD_CONTROL_FC_CMD_RECEN (1 << 25)
#define XENON_EMMC_PHY_PAD_CONTROL_FC_QSP_RECEN (1 << 26)
#define XENON_EMMC_PHY_PAD_CONTROL_FC_QSN_RECEN (1 << 27)
#define XENON_EMMC_PHY_PAD_CONTROL_FC_ALL_CMOS_RECVR 0xf000
#define XENON_EMMC_PHY_PAD_CONTROL1 0x17c
#define XENON_EMMC_PHY_PAD_CONTROL1_FC_CMD_PD (1 << 8)
#define XENON_EMMC_PHY_PAD_CONTROL1_FC_QSP_PD (1 << 9)
#define XENON_EMMC_PHY_PAD_CONTROL1_FC_CMD_PU (1 << 24)
#define XENON_EMMC_PHY_PAD_CONTROL1_FC_QSP_PU (1 << 25)
#define XENON_EMMC_PHY_PAD_CONTROL1_FC_DQ_PD 0xff
#define XENON_EMMC_PHY_PAD_CONTROL1_FC_DQ_PU (0xff << 16)
#define XENON_EMMC_PHY_PAD_CONTROL2 0x180
#define XENON_EMMC_PHY_PAD_CONTROL2_ZPR_SHIFT 0
#define XENON_EMMC_PHY_PAD_CONTROL2_ZPR_MASK 0x1f
#define XENON_EMMC_PHY_PAD_CONTROL2_ZNR_SHIFT 8
#define XENON_EMMC_PHY_PAD_CONTROL2_ZNR_MASK 0x1f
#define ARMADA_3700_SOC_PAD_CTL 0
#define ARMADA_3700_SOC_PAD_CTL_3_3V 0
#define ARMADA_3700_SOC_PAD_CTL_1_8V 1
struct sdhc_fdt_softc {
struct sdhc_softc sc;
bus_space_tag_t sc_iot;
bus_space_handle_t sc_ioh;
bus_space_handle_t sc_pad_ioh;
bus_size_t sc_size;
void *sc_ih;
int sc_node;
uint32_t sc_gpio[3];
uint32_t sc_vmmc;
uint32_t sc_vqmmc;
int sc_sdhc_id;
int sc_slow_mode;
uint32_t sc_znr;
uint32_t sc_zpr;
struct sdhc_host *sc_host;
struct clock_device sc_cd;
};
int sdhc_fdt_match(struct device *, void *, void *);
void sdhc_fdt_attach(struct device *, struct device *, void *);
const struct cfattach sdhc_fdt_ca = {
sizeof(struct sdhc_fdt_softc), sdhc_fdt_match, sdhc_fdt_attach,
NULL, sdhc_activate
};
int sdhc_fdt_card_detect(struct sdhc_softc *);
int sdhc_fdt_signal_voltage(struct sdhc_softc *, int);
uint32_t sdhc_fdt_get_frequency(void *, uint32_t *);
void sdhc_fdt_xenon_bus_clock_post(struct sdhc_softc *, int, int);
int
sdhc_fdt_match(struct device *parent, void *match, void *aux)
{
struct fdt_attach_args *faa = aux;
return (OF_is_compatible(faa->fa_node, "arasan,sdhci-5.1") ||
OF_is_compatible(faa->fa_node, "arasan,sdhci-8.9a") ||
OF_is_compatible(faa->fa_node, "brcm,bcm2711-emmc2") ||
OF_is_compatible(faa->fa_node, "brcm,bcm2712-sdhci") ||
OF_is_compatible(faa->fa_node, "brcm,bcm2835-sdhci") ||
OF_is_compatible(faa->fa_node, "marvell,armada-3700-sdhci") ||
OF_is_compatible(faa->fa_node, "marvell,armada-ap806-sdhci") ||
OF_is_compatible(faa->fa_node, "marvell,armada-cp110-sdhci"));
}
void
sdhc_fdt_attach(struct device *parent, struct device *self, void *aux)
{
struct sdhc_fdt_softc *sc = (struct sdhc_fdt_softc *)self;
struct fdt_attach_args *faa = aux;
struct regmap *rm = NULL;
uint64_t capmask, capset;
uint32_t reg, phandle, freq;
char pad_type[16] = { 0 };
if (faa->fa_nreg < 1) {
printf(": no registers\n");
return;
}
sc->sc_iot = faa->fa_iot;
sc->sc_size = faa->fa_reg[0].size;
sc->sc_node = faa->fa_node;
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;
}
pinctrl_byname(faa->fa_node, "default");
clock_set_assigned(faa->fa_node);
clock_enable_all(faa->fa_node);
reset_deassert_all(faa->fa_node);
sc->sc_ih = fdt_intr_establish(faa->fa_node, IPL_BIO,
sdhc_intr, sc, sc->sc.sc_dev.dv_xname);
if (sc->sc_ih == NULL) {
printf(": can't establish interrupt\n");
goto unmap;
}
capmask = OF_getpropint64(sc->sc_node, "sdhci-caps-mask", 0);
capset = OF_getpropint64(sc->sc_node, "sdhci-caps", 0);
if (OF_getproplen(faa->fa_node, "cd-gpios") > 0 ||
OF_getproplen(faa->fa_node, "non-removable") == 0) {
OF_getpropintarray(faa->fa_node, "cd-gpios", sc->sc_gpio,
sizeof(sc->sc_gpio));
gpio_controller_config_pin(sc->sc_gpio, GPIO_CONFIG_INPUT);
sc->sc.sc_card_detect = sdhc_fdt_card_detect;
}
sc->sc_vmmc = OF_getpropint(sc->sc_node, "vmmc-supply", 0);
sc->sc_vqmmc = OF_getpropint(sc->sc_node, "vqmmc-supply", 0);
printf("\n");
sc->sc.sc_host = &sc->sc_host;
sc->sc.sc_dmat = faa->fa_dmat;
if (OF_is_compatible(faa->fa_node, "arasan,sdhci-5.1"))
sc->sc.sc_signal_voltage = sdhc_fdt_signal_voltage;
if (OF_is_compatible(faa->fa_node, "rockchip,rk3399-sdhci-5.1")) {
freq = clock_get_frequency(faa->fa_node, "clk_xin");
freq /= 1000 * 1000;
phandle = OF_getpropint(faa->fa_node,
"arasan,soc-ctl-syscon", 0);
if (phandle)
rm = regmap_byphandle(phandle);
if (rm) {
regmap_write_4(rm, GRF_EMMCCORE_CON11,
GRF_EMMCCORE_CON11_CLOCKMULT_CLR |
GRF_EMMCCORE_CON11_CLOCKMULT_VAL(0));
regmap_write_4(rm, GRF_EMMCCORE_CON0_BASECLOCK,
GRF_EMMCCORE_CON0_BASECLOCK_CLR |
GRF_EMMCCORE_CON0_BASECLOCK_VAL(freq));
}
sc->sc_cd.cd_node = faa->fa_node;
sc->sc_cd.cd_cookie = sc;
sc->sc_cd.cd_get_frequency = sdhc_fdt_get_frequency;
clock_register(&sc->sc_cd);
phy_enable(faa->fa_node, "phy_arasan");
sc->sc.sc_flags |= SDHC_F_NOPWR0;
capmask |= (uint64_t)SDHC_DDR50_SUPP << 32;
}
if (OF_is_compatible(faa->fa_node, "arasan,sdhci-8.9a")) {
freq = clock_get_frequency(faa->fa_node, "clk_xin");
sc->sc.sc_clkbase = freq / 1000;
}
if (OF_is_compatible(faa->fa_node, "brcm,bcm2711-emmc2"))
sc->sc.sc_flags |= SDHC_F_NOPWR0;
if (OF_is_compatible(faa->fa_node, "brcm,bcm2835-sdhci")) {
capmask = 0xffffffff;
capset = SDHC_VOLTAGE_SUPP_3_3V | SDHC_HIGH_SPEED_SUPP;
capset |= SDHC_MAX_BLK_LEN_1024 << SDHC_MAX_BLK_LEN_SHIFT;
freq = clock_get_frequency(faa->fa_node, NULL);
sc->sc.sc_clkbase = freq / 1000;
sc->sc.sc_flags |= SDHC_F_32BIT_ACCESS;
sc->sc.sc_flags |= SDHC_F_NO_HS_BIT;
}
if (OF_is_compatible(faa->fa_node, "marvell,armada-3700-sdhci") ||
OF_is_compatible(faa->fa_node, "marvell,armada-ap806-sdhci") ||
OF_is_compatible(faa->fa_node, "marvell,armada-cp110-sdhci")) {
if (OF_is_compatible(faa->fa_node,
"marvell,armada-3700-sdhci")) {
KASSERT(faa->fa_nreg > 1);
if (bus_space_map(sc->sc_iot, faa->fa_reg[1].addr,
faa->fa_reg[1].size, 0, &sc->sc_pad_ioh)) {
printf("%s: can't map registers\n",
sc->sc.sc_dev.dv_xname);
return;
}
OF_getprop(faa->fa_node, "marvell,pad-type",
pad_type, sizeof(pad_type));
if (!strcmp(pad_type, "fixed-1-8v")) {
bus_space_write_4(sc->sc_iot, sc->sc_pad_ioh,
ARMADA_3700_SOC_PAD_CTL,
ARMADA_3700_SOC_PAD_CTL_1_8V);
} else {
bus_space_write_4(sc->sc_iot, sc->sc_pad_ioh,
ARMADA_3700_SOC_PAD_CTL,
ARMADA_3700_SOC_PAD_CTL_3_3V);
regulator_set_voltage(sc->sc_vqmmc, 3300000);
}
}
if (OF_getpropint(faa->fa_node, "bus-width", 1) != 8)
capmask |= SDHC_8BIT_MODE_SUPP;
if (OF_getproplen(faa->fa_node, "no-1-8-v") == 0) {
capmask |= SDHC_VOLTAGE_SUPP_1_8V;
capmask |= (uint64_t)SDHC_DDR50_SUPP << 32;
}
if (OF_getproplen(faa->fa_node,
"marvell,xenon-phy-slow-mode") == 0)
sc->sc_slow_mode = 1;
sc->sc_znr = OF_getpropint(faa->fa_node,
"marvell,xenon-phy-znr", 0xf);
sc->sc_znr &= XENON_EMMC_PHY_PAD_CONTROL2_ZNR_MASK;
sc->sc_zpr = OF_getpropint(faa->fa_node,
"marvell,xenon-phy-zpr", 0xf);
sc->sc_zpr &= XENON_EMMC_PHY_PAD_CONTROL2_ZPR_MASK;
sc->sc_sdhc_id = OF_getpropint(faa->fa_node,
"marvell,xenon-sdhc-id", 0);
reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
XENON_SYS_OP_CTRL);
reg |= XENON_SYS_OP_CTRL_SLOT_ENABLE(sc->sc_sdhc_id);
reg &= ~XENON_SYS_OP_CTRL_SDCLK_IDLEOFF_ENABLE(sc->sc_sdhc_id);
reg &= ~XENON_SYS_OP_CTRL_AUTO_CLKGATE_DISABLE;
bus_space_write_4(sc->sc_iot, sc->sc_ioh,
XENON_SYS_OP_CTRL, reg);
reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
XENON_SYS_EXT_OP_CTRL);
reg |= XENON_SYS_EXT_OP_CTRL_PARALLEL_TRAN(sc->sc_sdhc_id);
reg |= XENON_SYS_EXT_OP_CTRL_MASK_CMD_CONFLICT_ERR;
bus_space_write_4(sc->sc_iot, sc->sc_ioh,
XENON_SYS_EXT_OP_CTRL, reg);
freq = clock_get_frequency(faa->fa_node, NULL);
sc->sc.sc_clkbase = freq / 1000;
sc->sc.sc_bus_clock_post = sdhc_fdt_xenon_bus_clock_post;
}
if (sc->sc_vmmc)
regulator_enable(sc->sc_vmmc);
sdhc_host_found(&sc->sc, sc->sc_iot, sc->sc_ioh, sc->sc_size, 1,
capmask, capset);
return;
unmap:
bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_size);
}
int
sdhc_fdt_card_detect(struct sdhc_softc *ssc)
{
struct sdhc_fdt_softc *sc = (struct sdhc_fdt_softc *)ssc;
if (OF_getproplen(sc->sc_node, "non-removable") == 0)
return 1;
return gpio_controller_get_pin(sc->sc_gpio);
}
int
sdhc_fdt_signal_voltage(struct sdhc_softc *sc, int signal_voltage)
{
switch (signal_voltage) {
case SDMMC_SIGNAL_VOLTAGE_180:
return 0;
default:
return EINVAL;
}
}
uint32_t
sdhc_fdt_get_frequency(void *cookie, uint32_t *cells)
{
struct sdhc_fdt_softc *sc = cookie;
return clock_get_frequency(sc->sc_cd.cd_node, "clk_xin");
}
void
sdhc_fdt_xenon_bus_clock_post(struct sdhc_softc *ssc, int freq, int timing)
{
struct sdhc_fdt_softc *sc = (struct sdhc_fdt_softc *)ssc;
uint32_t reg;
int i;
if (freq == 0)
return;
reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
XENON_EMMC_PHY_PAD_CONTROL);
reg |= (XENON_EMMC_PHY_PAD_CONTROL_FC_DQ_RECEN |
XENON_EMMC_PHY_PAD_CONTROL_FC_CMD_RECEN |
XENON_EMMC_PHY_PAD_CONTROL_FC_QSP_RECEN |
XENON_EMMC_PHY_PAD_CONTROL_FC_QSN_RECEN |
XENON_EMMC_PHY_PAD_CONTROL_FC_ALL_CMOS_RECVR);
bus_space_write_4(sc->sc_iot, sc->sc_ioh,
XENON_EMMC_PHY_PAD_CONTROL, reg);
reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
XENON_EMMC_PHY_PAD_CONTROL1);
reg &= ~(XENON_EMMC_PHY_PAD_CONTROL1_FC_CMD_PD |
XENON_EMMC_PHY_PAD_CONTROL1_FC_DQ_PD);
reg |= (XENON_EMMC_PHY_PAD_CONTROL1_FC_CMD_PU |
XENON_EMMC_PHY_PAD_CONTROL1_FC_DQ_PU);
bus_space_write_4(sc->sc_iot, sc->sc_ioh,
XENON_EMMC_PHY_PAD_CONTROL1, reg);
if (timing == SDMMC_TIMING_LEGACY)
goto phy_init;
reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
XENON_EMMC_PHY_TIMING_ADJUST);
reg &= ~XENON_EMMC_PHY_TIMING_ADJUST_SDIO_MODE;
bus_space_write_4(sc->sc_iot, sc->sc_ioh,
XENON_EMMC_PHY_TIMING_ADJUST, reg);
reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
XENON_EMMC_PHY_PAD_CONTROL2);
reg &= ~(XENON_EMMC_PHY_PAD_CONTROL2_ZPR_MASK <<
XENON_EMMC_PHY_PAD_CONTROL2_ZPR_SHIFT |
XENON_EMMC_PHY_PAD_CONTROL2_ZNR_MASK <<
XENON_EMMC_PHY_PAD_CONTROL2_ZNR_SHIFT);
reg |= sc->sc_zpr << XENON_EMMC_PHY_PAD_CONTROL2_ZPR_SHIFT |
sc->sc_znr << XENON_EMMC_PHY_PAD_CONTROL2_ZNR_SHIFT;
bus_space_write_4(sc->sc_iot, sc->sc_ioh,
XENON_EMMC_PHY_PAD_CONTROL2, reg);
reg = bus_space_read_2(sc->sc_iot, sc->sc_ioh, SDHC_CLOCK_CTL);
reg &= ~SDHC_SDCLK_ENABLE;
bus_space_write_2(sc->sc_iot, sc->sc_ioh, SDHC_CLOCK_CTL, reg);
reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
XENON_EMMC_PHY_FUNC_CONTROL);
reg &= ~(XENON_EMMC_PHY_FUNC_CONTROL_DQ_DDR_MODE |
XENON_EMMC_PHY_FUNC_CONTROL_CMD_DDR_MODE);
reg |= XENON_EMMC_PHY_FUNC_CONTROL_DQ_ASYNC_MODE;
bus_space_write_4(sc->sc_iot, sc->sc_ioh,
XENON_EMMC_PHY_FUNC_CONTROL, reg);
reg = bus_space_read_2(sc->sc_iot, sc->sc_ioh, SDHC_CLOCK_CTL);
reg |= SDHC_SDCLK_ENABLE;
bus_space_write_2(sc->sc_iot, sc->sc_ioh, SDHC_CLOCK_CTL, reg);
reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
XENON_SLOT_EMMC_CTRL);
reg &= ~(XENON_SLOT_EMMC_CTRL_ENABLE_DATA_STROBE |
XENON_SLOT_EMMC_CTRL_ENABLE_RESP_STROBE);
bus_space_write_4(sc->sc_iot, sc->sc_ioh,
XENON_SLOT_EMMC_CTRL, reg);
reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
XENON_EMMC_PHY_PAD_CONTROL1);
reg &= ~(XENON_EMMC_PHY_PAD_CONTROL1_FC_QSP_PD |
XENON_EMMC_PHY_PAD_CONTROL1_FC_QSP_PU);
bus_space_write_4(sc->sc_iot, sc->sc_ioh,
XENON_EMMC_PHY_PAD_CONTROL1, reg);
phy_init:
reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
XENON_EMMC_PHY_TIMING_ADJUST);
reg |= XENON_EMMC_PHY_TIMING_ADJUST_SAMPL_INV_QSP_PHASE_SELECT;
reg &= ~XENON_EMMC_PHY_TIMING_ADJUST_SLOW_MODE;
if (timing == SDMMC_TIMING_LEGACY ||
timing == SDMMC_TIMING_HIGHSPEED || sc->sc_slow_mode)
reg |= XENON_EMMC_PHY_TIMING_ADJUST_SLOW_MODE;
bus_space_write_4(sc->sc_iot, sc->sc_ioh,
XENON_EMMC_PHY_TIMING_ADJUST, reg);
reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
XENON_EMMC_PHY_TIMING_ADJUST);
reg |= XENON_EMMC_PHY_TIMING_ADJUST_INIT;
bus_space_write_4(sc->sc_iot, sc->sc_ioh,
XENON_EMMC_PHY_TIMING_ADJUST, reg);
for (i = 1000; i > 0; i--) {
reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
XENON_EMMC_PHY_TIMING_ADJUST);
if (!(reg & XENON_EMMC_PHY_TIMING_ADJUST_INIT))
break;
delay(10);
}
if (i == 0)
printf("%s: phy initialization timeout\n",
sc->sc.sc_dev.dv_xname);
if (freq > SDMMC_SDCLK_400KHZ) {
reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
XENON_SYS_OP_CTRL);
reg |= XENON_SYS_OP_CTRL_SDCLK_IDLEOFF_ENABLE(sc->sc_sdhc_id);
bus_space_write_4(sc->sc_iot, sc->sc_ioh,
XENON_SYS_OP_CTRL, reg);
}
}