Index: sys/riscv/eswin/eswin_sdhci.c =================================================================== --- /dev/null +++ sys/riscv/eswin/eswin_sdhci.c @@ -0,0 +1,532 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2024 Ruslan Bukin + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "mmcbr_if.h" +#include "sdhci_if.h" +#include "clkdev_if.h" + +#include "opt_mmccam.h" + +#define DWC_MSHC_PTR_PHY_R 0x300 +#define PHY_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x00) +#define PHY_CMDPAD_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x04) +#define PHY_DATAPAD_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x06) +#define PHY_CLKPAD_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x08) +#define PHY_STBPAD_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x0a) +#define PHY_RSTNPAD_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x0c) +#define PHY_PADTEST_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x0e) +#define PHY_PADTEST_OUT_R (DWC_MSHC_PTR_PHY_R + 0x10) +#define PHY_PADTEST_IN_R (DWC_MSHC_PTR_PHY_R + 0x12) +#define PHY_PRBS_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x18) +#define PHY_PHYLBK_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x1a) +#define PHY_COMMDL_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x1c) +#define PHY_SDCLKDL_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x1d) +#define PHY_SDCLKDL_DC_R (DWC_MSHC_PTR_PHY_R + 0x1e) +#define PHY_SMPLDL_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x20) +#define PHY_ATDL_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x21) +#define PHY_DLL_CTRL_R (DWC_MSHC_PTR_PHY_R + 0x24) +#define PHY_DLL_CNFG1_R (DWC_MSHC_PTR_PHY_R + 0x25) +#define PHY_DLL_CNFG2_R (DWC_MSHC_PTR_PHY_R + 0x26) +#define PHY_DLLDL_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x28) +#define PHY_DLL_OFFST_R (DWC_MSHC_PTR_PHY_R + 0x29) +#define PHY_DLLMST_TSTDC_R (DWC_MSHC_PTR_PHY_R + 0x2a) +#define PHY_DLLBT_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x2c) +#define PHY_DLL_STATUS_R (DWC_MSHC_PTR_PHY_R + 0x2e) +#define PHY_DLLDBG_MLKDC_R (DWC_MSHC_PTR_PHY_R + 0x30) +#define PHY_DLLDBG_SLKDC_R (DWC_MSHC_PTR_PHY_R + 0x32) + +/* Strength definition. */ +#define PHYCTRL_DR_33OHM 0xee +#define PHYCTRL_DR_40OHM 0xcc +#define PHYCTRL_DR_50OHM 0x88 +#define PHYCTRL_DR_66OHM 0x44 +#define PHYCTRL_DR_100OHM 0x00 + +#define PHY_PAD_MAX_DRIVE_STRENGTH 0xf +#define PHY_CLK_MAX_DELAY_MASK 0x7f +#define PHY_PAD_SP_DRIVE_SHIF 16 +#define PHY_PAD_SN_DRIVE_SHIF 20 + +#define PHY_RSTN (1 << 0) +#define PHY_UPDATE_DELAY_CODE (1 << 4) + +#define PHY_SLEW_0 0x0 +#define PHY_SLEW_1 0x1 +#define PHY_SLEW_2 0x2 +#define PHY_SLEW_3 0x3 +#define PHY_TX_SLEW_CTRL_P_BIT_S 0x5 +#define PHY_TX_SLEW_CTRL_N_BIT_S 0x9 + +#define PHY_PULL_BIT_SHIF 0x3 +#define PHY_PULL_DISABLED 0x0 +#define PHY_PULL_UP 0x1 +#define PHY_PULL_DOWN 0x2 +#define PHY_PULL_MASK 0x3 + +#define PHY_PAD_RXSEL_0 0x0 +#define PHY_PAD_RXSEL_1 0x1 +#define PHY_PAD_RXSEL_2 0x2 +#define PHY_PAD_RXSEL_3 0x3 + +#define RD4(sc, off) (bus_read_4((sc)->mem_res, (off))) +#define WR4(sc, off, val) (bus_write_4((sc)->mem_res, (off), (val))) +#define RD2(sc, off) (bus_read_2((sc)->mem_res, (off))) +#define WR2(sc, off, val) (bus_write_2((sc)->mem_res, (off), (val))) +#define RD1(sc, off) (bus_read_1((sc)->mem_res, (off))) +#define WR1(sc, off, val) (bus_write_1((sc)->mem_res, (off), (val))) + +static struct ofw_compat_data compat_data[] = { + { "eswin,sdhci-sdio", 1 }, + { NULL, 0 } +}; + +struct eswin_sdhci_softc { + device_t dev; /* Controller device */ + u_int quirks; /* Chip specific quirks */ + u_int caps; /* If we override SDHCI_CAPABILITIES */ + uint32_t max_clk; /* Max possible freq */ + uint8_t sdma_boundary; /* If we override the SDMA boundary */ + struct resource *irq_res; /* IRQ resource */ + void *intrhand; /* Interrupt handle */ + struct sdhci_slot slot; + struct resource *mem_res; /* Memory resource */ + + bool wp_inverted; /* WP pin is inverted */ + bool wp_disabled; /* WP pin is not supported */ + bool no_18v; /* No 1.8V support */ + + clk_t clk_xin; /* xin24m fixed clock */ + clk_t clk_ahb; /* ahb clock */ +}; + +static int +sdhci_init_clocks(struct eswin_sdhci_softc *sc) +{ + int error; + + error = clk_get_by_ofw_name(sc->dev, 0, "clk_xin", &sc->clk_xin); + if (error != 0) { + device_printf(sc->dev, "Can't get xin clock.\n"); + return (ENXIO); + } + + error = clk_enable(sc->clk_xin); + if (error != 0) { + device_printf(sc->dev, "Can't enable xin clock.\n"); + return (ENXIO); + } + + error = clk_get_by_ofw_name(sc->dev, 0, "clk_ahb", &sc->clk_ahb); + if (error != 0) { + device_printf(sc->dev, "Can't get ahb clock.\n"); + return (ENXIO); + } + + error = clk_enable(sc->clk_ahb); + if (error != 0) { + device_printf(sc->dev, "Can't enable ahb clock.\n"); + return (ENXIO); + } + + return (0); +} + +static void +sdhci_phy_poweron(struct eswin_sdhci_softc *sc) +{ + uint32_t reg; + int timeout; + + WR4(sc, 0x608, 0x10010101); + WR4(sc, 0x60c, 0x1); + WR4(sc, 0x0b08, 0x2000000); + + WR1(sc, SDHCI_SOFTWARE_RESET, 0x1); + WR2(sc, 0x508, 0x2); + WR1(sc, SDHCI_POWER_CONTROL, 0xf); + WR2(sc, SDHCI_HOST_CONTROL2, 0); + WR2(sc, 0x52C, 0xc); + + WR1(sc, 0x31c, 0); + WR1(sc, 0x31d, 0); + WR1(sc, 0x320, 8); + WR1(sc, 0x321, 8); + + timeout = 500; + + do { + reg = RD4(sc, DWC_MSHC_PTR_PHY_R); + if (reg == 0x2) + break; + DELAY(1000); + } while (timeout--); + + if (timeout > 0) + device_printf(sc->dev, "%s: PHY powered on\n", __func__); + else + device_printf(sc->dev, "%s: could not power on PHY\n", + __func__); +} + +static void +sdhci_sdio_config_phy(struct eswin_sdhci_softc *sc) +{ + int enable_data_pullup; + int enable_cmd_pullup; + phandle_t node; + uint32_t delay; + uint32_t reg; + uint32_t drv; + + enable_cmd_pullup = 0; + enable_data_pullup = 0; + + node = ofw_bus_get_node(sc->dev); + + if (OF_hasprop(node, "enable-cmd-pullup")) + enable_cmd_pullup = 1; + if (OF_hasprop(node, "enable-data-pullup")) + enable_cmd_pullup = 1; + + drv = PHYCTRL_DR_33OHM << PHY_PAD_SP_DRIVE_SHIF; + WR4(sc, PHY_CNFG_R, drv | ~PHY_RSTN); + + reg = (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_P_BIT_S) | + (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_N_BIT_S) | + (enable_cmd_pullup << PHY_PULL_BIT_SHIF) | PHY_PAD_RXSEL_1; + WR2(sc, PHY_CMDPAD_CNFG_R, reg); + + reg = (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_P_BIT_S) | + (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_N_BIT_S) | + (enable_data_pullup << PHY_PULL_BIT_SHIF) | PHY_PAD_RXSEL_1; + WR2(sc, PHY_DATAPAD_CNFG_R, reg); + + reg = (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_P_BIT_S) | + (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_N_BIT_S) | PHY_PAD_RXSEL_0; + WR2(sc, PHY_CLKPAD_CNFG_R, reg); + + reg = (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_P_BIT_S) | + (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_N_BIT_S) | + (PHY_PULL_UP << PHY_PULL_BIT_SHIF) | PHY_PAD_RXSEL_1; + WR2(sc, PHY_RSTNPAD_CNFG_R, reg); + WR4(sc, PHY_CNFG_R, drv | PHY_RSTN); + + if ((OF_getencprop(node, "delay_code", &delay, sizeof(delay))) > 0) { + delay &= PHY_CLK_MAX_DELAY_MASK; + WR1(sc, PHY_SDCLKDL_CNFG_R, PHY_UPDATE_DELAY_CODE); + WR1(sc, PHY_SDCLKDL_DC_R, delay); + WR1(sc, PHY_SDCLKDL_CNFG_R, 0); + } +} + +static void +eswin_sdhci_set_uhs_timing(device_t dev, struct sdhci_slot *slot) +{ + struct eswin_sdhci_softc *sc; + + sc = device_get_softc(dev); + + sdhci_sdio_config_phy(sc); +} + +static uint8_t +eswin_sdhci_read_1(device_t dev, struct sdhci_slot *slot, bus_size_t off) +{ + struct eswin_sdhci_softc *sc; + + sc = device_get_softc(dev); + + return (bus_read_1(sc->mem_res, off)); +} + +static void +eswin_sdhci_write_1(device_t dev, struct sdhci_slot *slot, bus_size_t off, + uint8_t val) +{ + struct eswin_sdhci_softc *sc; + + sc = device_get_softc(dev); + + bus_write_1(sc->mem_res, off, val); +} + +static uint16_t +eswin_sdhci_read_2(device_t dev, struct sdhci_slot *slot, bus_size_t off) +{ + struct eswin_sdhci_softc *sc; + + sc = device_get_softc(dev); + + return (bus_read_2(sc->mem_res, off)); +} + +static void +eswin_sdhci_write_2(device_t dev, struct sdhci_slot *slot, bus_size_t off, + uint16_t val) +{ + struct eswin_sdhci_softc *sc; + + sc = device_get_softc(dev); + + bus_write_2(sc->mem_res, off, val); +} + +static uint32_t +eswin_sdhci_read_4(device_t dev, struct sdhci_slot *slot, bus_size_t off) +{ + struct eswin_sdhci_softc *sc; + uint32_t reg; + + sc = device_get_softc(dev); + + reg = bus_read_4(sc->mem_res, off); + if (off == SDHCI_CAPABILITIES && sc->no_18v) + reg &= ~SDHCI_CAN_VDD_180; + + return (reg); +} + +static void +eswin_sdhci_write_4(device_t dev, struct sdhci_slot *slot, bus_size_t off, + uint32_t val) +{ + struct eswin_sdhci_softc *sc; + + sc = device_get_softc(dev); + + bus_write_4(sc->mem_res, off, val); +} + +static void +eswin_sdhci_read_multi_4(device_t dev, struct sdhci_slot *slot, + bus_size_t off, uint32_t *data, bus_size_t count) +{ + struct eswin_sdhci_softc *sc; + + sc = device_get_softc(dev); + + bus_read_multi_4(sc->mem_res, off, data, count); +} + +static void +eswin_sdhci_write_multi_4(device_t dev, struct sdhci_slot *slot, + bus_size_t off, uint32_t *data, bus_size_t count) +{ + struct eswin_sdhci_softc *sc; + + sc = device_get_softc(dev); + + bus_write_multi_4(sc->mem_res, off, data, count); +} + +static void +eswin_sdhci_intr(void *arg) +{ + struct eswin_sdhci_softc *sc; + + sc = (struct eswin_sdhci_softc *)arg; + + sdhci_generic_intr(&sc->slot); +} + +static int +eswin_sdhci_get_ro(device_t bus, device_t dev) +{ + struct eswin_sdhci_softc *sc; + + sc = device_get_softc(bus); + if (sc->wp_disabled) + return (false); + + return (sdhci_generic_get_ro(bus, dev) ^ sc->wp_inverted); +} + +static int +eswin_sdhci_probe(device_t dev) +{ + struct eswin_sdhci_softc *sc; + phandle_t node; + pcell_t cid; + + sc = device_get_softc(dev); + sc->quirks = 0; + sc->max_clk = 0; + + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) + return (ENXIO); + + device_set_desc(dev, "Eswin Generic SDHCI controller"); + + node = ofw_bus_get_node(dev); + + /* Allow dts to patch quirks, slots, and max-frequency. */ + if ((OF_getencprop(node, "quirks", &cid, sizeof(cid))) > 0) + sc->quirks = cid; + if ((OF_getencprop(node, "max-frequency", &cid, sizeof(cid))) > 0) + sc->max_clk = cid; + + return (0); +} + +static int +eswin_sdhci_attach(device_t dev) +{ + struct eswin_sdhci_softc *sc; + struct sdhci_slot *slot; + int error; + int rid; + + sc = device_get_softc(dev); + sc->dev = dev; + + rid = 0; + sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, + RF_ACTIVE); + if (sc->mem_res == NULL) { + device_printf(dev, "Can't allocate memory resource.\n"); + return (ENXIO); + } + + rid = 0; + sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, + RF_ACTIVE); + if (sc->irq_res == NULL) { + device_printf(dev, "Can't allocate IRQ resource.\n"); + return (ENXIO); + } + + sdhci_init_clocks(sc); /* Ignore status code for now. */ + sdhci_phy_poweron(sc); + + slot = &sc->slot; + slot->quirks = sc->quirks; + slot->caps = sc->caps; + slot->max_clk = sc->max_clk; + slot->sdma_boundary = sc->sdma_boundary; + if (sdhci_init_slot(dev, slot, 0) != 0) + return (ENXIO); + + /* Setup interrupt handler. */ + error = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_MISC | INTR_MPSAFE, + NULL, eswin_sdhci_intr, sc, &sc->intrhand); + if (error) { + device_printf(dev, "Cannot setup IRQ\n"); + return (error); + } + + sdhci_start_slot(&sc->slot); + + return (0); +} + +static int +eswin_sdhci_detach(device_t dev) +{ + struct eswin_sdhci_softc *sc; + + sc = device_get_softc(dev); + + bus_generic_detach(dev); + bus_teardown_intr(dev, sc->irq_res, sc->intrhand); + bus_release_resource(dev, SYS_RES_IRQ, rman_get_rid(sc->irq_res), + sc->irq_res); + + sdhci_cleanup_slot(&sc->slot); + bus_release_resource(dev, SYS_RES_MEMORY, rman_get_rid(sc->mem_res), + sc->mem_res); + + return (0); +} + +static device_method_t eswin_sdhci_methods[] = { + /* Device interface. */ + DEVMETHOD(device_probe, eswin_sdhci_probe), + DEVMETHOD(device_attach, eswin_sdhci_attach), + DEVMETHOD(device_detach, eswin_sdhci_detach), + + /* Bus interface. */ + DEVMETHOD(bus_read_ivar, sdhci_generic_read_ivar), + DEVMETHOD(bus_write_ivar, sdhci_generic_write_ivar), + + /* MMC bridge interface. */ + DEVMETHOD(mmcbr_update_ios, sdhci_generic_update_ios), + DEVMETHOD(mmcbr_request, sdhci_generic_request), + DEVMETHOD(mmcbr_get_ro, eswin_sdhci_get_ro), + DEVMETHOD(mmcbr_acquire_host, sdhci_generic_acquire_host), + DEVMETHOD(mmcbr_release_host, sdhci_generic_release_host), + + /* SDHCI interface. */ + DEVMETHOD(sdhci_read_1, eswin_sdhci_read_1), + DEVMETHOD(sdhci_read_2, eswin_sdhci_read_2), + DEVMETHOD(sdhci_read_4, eswin_sdhci_read_4), + DEVMETHOD(sdhci_read_multi_4, eswin_sdhci_read_multi_4), + DEVMETHOD(sdhci_write_1, eswin_sdhci_write_1), + DEVMETHOD(sdhci_write_2, eswin_sdhci_write_2), + DEVMETHOD(sdhci_write_4, eswin_sdhci_write_4), + DEVMETHOD(sdhci_write_multi_4, eswin_sdhci_write_multi_4), + DEVMETHOD(sdhci_set_uhs_timing, eswin_sdhci_set_uhs_timing), + + DEVMETHOD_END +}; + +static driver_t eswin_sdhci_driver = { + "eswin_sdhci", + eswin_sdhci_methods, + sizeof(struct eswin_sdhci_softc), +}; + +DRIVER_MODULE(eswin_sdhci, simplebus, eswin_sdhci_driver, NULL, NULL); +SDHCI_DEPEND(eswin_sdhci); +#ifndef MMCCAM +MMC_DECLARE_BRIDGE(eswin_sdhci); +#endif