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