diff options
author | Patrick Wildt <patrick@cvs.openbsd.org> | 2020-05-22 10:26:01 +0000 |
---|---|---|
committer | Patrick Wildt <patrick@cvs.openbsd.org> | 2020-05-22 10:26:01 +0000 |
commit | 5531705eea1f8d8934ab5506f43c4989993f3b96 (patch) | |
tree | fc1ab33c1ce6d442702b94e47830c6ebf94ebb5b /sys | |
parent | b0a0a0fe1658981b60bb3c9fa3e63ce44a6af13c (diff) |
Add support for the Marvell Xenon SDHC. This is used on the Armada
3700 and 8040 SoCs and allows me to use an SD card as storage on the
Turris Mox. It also should make eMMC/SD show up on the MACCHIATObin.
ok kettenis@
Diffstat (limited to 'sys')
-rw-r--r-- | sys/dev/fdt/sdhc_fdt.c | 248 |
1 files changed, 245 insertions, 3 deletions
diff --git a/sys/dev/fdt/sdhc_fdt.c b/sys/dev/fdt/sdhc_fdt.c index 43452b65d50..1c9335178bd 100644 --- a/sys/dev/fdt/sdhc_fdt.c +++ b/sys/dev/fdt/sdhc_fdt.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sdhc_fdt.c,v 1.10 2020/05/22 09:52:27 patrick Exp $ */ +/* $OpenBSD: sdhc_fdt.c,v 1.11 2020/05/22 10:26:00 patrick Exp $ */ /* * Copyright (c) 2017 Mark Kettenis * @@ -28,6 +28,7 @@ #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> @@ -42,14 +43,65 @@ #define GRF_EMMCCORE_CON11_CLOCKMULT_CLR (0xff << 16) #define GRF_EMMCCORE_CON11_CLOCKMULT_VAL(x) (((x) & 0xff) << 0) +/* Marvell Xenon */ +#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_vqmmc; + + /* Marvell Xenon */ + 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; @@ -66,6 +118,8 @@ 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) { @@ -73,7 +127,10 @@ sdhc_fdt_match(struct device *parent, void *match, void *aux) return (OF_is_compatible(faa->fa_node, "arasan,sdhci-5.1") || OF_is_compatible(faa->fa_node, "brcm,bcm2711-emmc2") || - OF_is_compatible(faa->fa_node, "brcm,bcm2835-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 @@ -82,7 +139,8 @@ 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; - uint32_t phandle, freq, cap = 0; + uint32_t reg, phandle, freq, cap = 0; + char pad_type[16] = { 0 }; if (faa->fa_nreg < 1) { printf(": no registers\n"); @@ -120,6 +178,8 @@ sdhc_fdt_attach(struct device *parent, struct device *self, void *aux) sc->sc.sc_card_detect = sdhc_fdt_card_detect; } + sc->sc_vqmmc = OF_getpropint(sc->sc_node, "vqmmc-supply", 0); + printf("\n"); sc->sc.sc_host = &sc->sc_host; @@ -185,6 +245,73 @@ sdhc_fdt_attach(struct device *parent, struct device *self, void *aux) sc->sc.sc_flags |= SDHC_F_32BIT_ACCESS; } + 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); + } + } + + cap = bus_space_read_4(sc->sc_iot, sc->sc_ioh, + SDHC_CAPABILITIES); + if (OF_getpropint(faa->fa_node, "bus-width", 1) != 8) + cap &= ~SDHC_8BIT_MODE_SUPP; + if (OF_getproplen(faa->fa_node, "no-1-8-v") == 0) + cap &= ~SDHC_VOLTAGE_SUPP_1_8V; + 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; + + /* XXX: not yet implemented */ + sc->sc.sc_flags |= SDHC_F_NODDR50; + } + sdhc_host_found(&sc->sc, sc->sc_iot, sc->sc_ioh, sc->sc_size, 1, cap); return; @@ -220,3 +347,118 @@ 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"); } + +/* Marvell Xenon */ +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; + + /* TODO: check for SMF_IO_MODE and set flag */ + 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; + + 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); + } +} |