Changeset View
Standalone View
sys/dev/sdhci/sdhci_fsl_fdt.c
Show All 37 Lines | |||||
#include <sys/rman.h> | #include <sys/rman.h> | ||||
#include <sys/sysctl.h> | #include <sys/sysctl.h> | ||||
#include <sys/taskqueue.h> | #include <sys/taskqueue.h> | ||||
#include <machine/bus.h> | #include <machine/bus.h> | ||||
#include <machine/resource.h> | #include <machine/resource.h> | ||||
#include <dev/extres/clk/clk.h> | #include <dev/extres/clk/clk.h> | ||||
#include <dev/extres/syscon/syscon.h> | |||||
#include <dev/mmc/bridge.h> | #include <dev/mmc/bridge.h> | ||||
#include <dev/mmc/mmcbrvar.h> | #include <dev/mmc/mmcbrvar.h> | ||||
#include <dev/mmc/mmc_fdt_helpers.h> | #include <dev/mmc/mmc_fdt_helpers.h> | ||||
#include <dev/ofw/ofw_bus.h> | #include <dev/ofw/ofw_bus.h> | ||||
#include <dev/ofw/ofw_bus_subr.h> | #include <dev/ofw/ofw_bus_subr.h> | ||||
#include <dev/sdhci/sdhci.h> | #include <dev/sdhci/sdhci.h> | ||||
#include <dev/sdhci/sdhci_fdt_gpio.h> | #include <dev/sdhci/sdhci_fdt_gpio.h> | ||||
#include "mmcbr_if.h" | #include "mmcbr_if.h" | ||||
#include "sdhci_if.h" | #include "sdhci_if.h" | ||||
#include "syscon_if.h" | |||||
#define RD4 (sc->read) | #define RD4 (sc->read) | ||||
#define WR4 (sc->write) | #define WR4 (sc->write) | ||||
#define SDHCI_FSL_PRES_STATE 0x24 | #define SDHCI_FSL_PRES_STATE 0x24 | ||||
#define SDHCI_FSL_PRES_SDSTB (1 << 3) | #define SDHCI_FSL_PRES_SDSTB (1 << 3) | ||||
#define SDHCI_FSL_PRES_COMPAT_MASK 0x000f0f07 | #define SDHCI_FSL_PRES_COMPAT_MASK 0x000f0f07 | ||||
▲ Show 20 Lines • Show All 73 Lines • ▼ Show 20 Lines | |||||
#define SDHCI_FSL_DLLSTAT0 0x170 | #define SDHCI_FSL_DLLSTAT0 0x170 | ||||
#define SDHCI_FSL_DLLSTAT0_SLV_STS (1 << 27) | #define SDHCI_FSL_DLLSTAT0_SLV_STS (1 << 27) | ||||
#define SDHCI_FSL_ESDHC_CTRL 0x40c | #define SDHCI_FSL_ESDHC_CTRL 0x40c | ||||
#define SDHCI_FSL_ESDHC_CTRL_SNOOP (1 << 6) | #define SDHCI_FSL_ESDHC_CTRL_SNOOP (1 << 6) | ||||
#define SDHCI_FSL_ESDHC_CTRL_FAF (1 << 18) | #define SDHCI_FSL_ESDHC_CTRL_FAF (1 << 18) | ||||
#define SDHCI_FSL_ESDHC_CTRL_CLK_DIV2 (1 << 19) | #define SDHCI_FSL_ESDHC_CTRL_CLK_DIV2 (1 << 19) | ||||
#define SCFG_SDHCIOVSELCR 0x408 | |||||
#define SCFG_SDHCIOVSELCR_TGLEN (1 << 0) | |||||
#define SCFG_SDHCIOVSELCR_VS (1 << 31) | |||||
#define SCFG_SDHCIOVSELCR_VSELVAL_MASK (3 << 1) | |||||
#define SCFG_SDHCIOVSELCR_VSELVAL_1_8 0x0 | |||||
#define SCFG_SDHCIOVSELCR_VSELVAL_3_3 0x2 | |||||
#define SDHCI_FSL_CAN_VDD_MASK \ | #define SDHCI_FSL_CAN_VDD_MASK \ | ||||
(SDHCI_CAN_VDD_180 | SDHCI_CAN_VDD_300 | SDHCI_CAN_VDD_330) | (SDHCI_CAN_VDD_180 | SDHCI_CAN_VDD_300 | SDHCI_CAN_VDD_330) | ||||
/* Some platforms do not detect pulse width correctly. */ | /* Some platforms do not detect pulse width correctly. */ | ||||
#define SDHCI_FSL_UNRELIABLE_PULSE_DET (1 << 0) | #define SDHCI_FSL_UNRELIABLE_PULSE_DET (1 << 0) | ||||
/* On some platforms switching voltage to 1.8V is not supported */ | /* On some platforms switching voltage to 1.8V is not supported */ | ||||
#define SDHCI_FSL_UNSUPP_1_8V (1 << 1) | #define SDHCI_FSL_UNSUPP_1_8V (1 << 1) | ||||
/* Hardware tuning can fail, fallback to SW tuning in that case. */ | /* Hardware tuning can fail, fallback to SW tuning in that case. */ | ||||
#define SDHCI_FSL_TUNING_ERRATUM_TYPE1 (1 << 2) | #define SDHCI_FSL_TUNING_ERRATUM_TYPE1 (1 << 2) | ||||
/* | /* | ||||
* Pointer window might not be set properly on some platforms. | * Pointer window might not be set properly on some platforms. | ||||
* Check window and perform SW tuning. | * Check window and perform SW tuning. | ||||
*/ | */ | ||||
#define SDHCI_FSL_TUNING_ERRATUM_TYPE2 (1 << 3) | #define SDHCI_FSL_TUNING_ERRATUM_TYPE2 (1 << 3) | ||||
/* | /* | ||||
* In HS400 mode only 4, 8, 12 clock dividers can be used. | * In HS400 mode only 4, 8, 12 clock dividers can be used. | ||||
* Use the smallest value, bigger than requested in that case. | * Use the smallest value, bigger than requested in that case. | ||||
*/ | */ | ||||
#define SDHCI_FSL_HS400_LIMITED_CLK_DIV (1 << 4) | #define SDHCI_FSL_HS400_LIMITED_CLK_DIV (1 << 4) | ||||
/* | /* | ||||
* Some SoCs don't have a fixed regulator. Switching voltage | |||||
contact_artur-rojek.eu: s/does not have fixed regulator/don't have a fixed regulator | |||||
* requires special routine including syscon registers. | |||||
*/ | |||||
#define SDHCI_FSL_MISSING_VCCQ_REG (1 << 5) | |||||
/* | |||||
* HS400 tuning is done in HS200 mode, but it has to be done using | * HS400 tuning is done in HS200 mode, but it has to be done using | ||||
* the target frequency. In order to apply the errata above we need to | * the target frequency. In order to apply the errata above we need to | ||||
* know the target mode during tuning procedure. Use this flag for just that. | * know the target mode during tuning procedure. Use this flag for just that. | ||||
*/ | */ | ||||
#define SDHCI_FSL_HS400_FLAG (1 << 0) | #define SDHCI_FSL_HS400_FLAG (1 << 0) | ||||
#define SDHCI_FSL_MAX_RETRIES 20000 /* DELAY(10) * this = 200ms */ | #define SDHCI_FSL_MAX_RETRIES 20000 /* DELAY(10) * this = 200ms */ | ||||
Show All 18 Lines | struct sdhci_fsl_fdt_softc { | ||||
uint32_t (* read)(struct sdhci_fsl_fdt_softc *, bus_size_t); | uint32_t (* read)(struct sdhci_fsl_fdt_softc *, bus_size_t); | ||||
void (* write)(struct sdhci_fsl_fdt_softc *, bus_size_t, uint32_t); | void (* write)(struct sdhci_fsl_fdt_softc *, bus_size_t, uint32_t); | ||||
}; | }; | ||||
struct sdhci_fsl_fdt_soc_data { | struct sdhci_fsl_fdt_soc_data { | ||||
int quirks; | int quirks; | ||||
int baseclk_div; | int baseclk_div; | ||||
uint8_t errata; | uint8_t errata; | ||||
char *syscon_compat; | |||||
Not Done Inline Actionss/char* syscon_compat/char *syscon_compat contact_artur-rojek.eu: s/char* syscon_compat/char *syscon_compat | |||||
}; | }; | ||||
static const struct sdhci_fsl_fdt_soc_data sdhci_fsl_fdt_ls1012a_soc_data = { | static const struct sdhci_fsl_fdt_soc_data sdhci_fsl_fdt_ls1012a_soc_data = { | ||||
.quirks = 0, | .quirks = 0, | ||||
.baseclk_div = 1, | .baseclk_div = 1, | ||||
.errata = SDHCI_FSL_UNSUPP_1_8V, | .errata = SDHCI_FSL_MISSING_VCCQ_REG | SDHCI_FSL_TUNING_ERRATUM_TYPE2, | ||||
.syscon_compat = "fsl,ls1012a-scfg", | |||||
}; | }; | ||||
static const struct sdhci_fsl_fdt_soc_data sdhci_fsl_fdt_ls1028a_soc_data = { | static const struct sdhci_fsl_fdt_soc_data sdhci_fsl_fdt_ls1028a_soc_data = { | ||||
.quirks = SDHCI_QUIRK_DONT_SET_HISPD_BIT | | .quirks = SDHCI_QUIRK_DONT_SET_HISPD_BIT | | ||||
SDHCI_QUIRK_BROKEN_AUTO_STOP | SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK, | SDHCI_QUIRK_BROKEN_AUTO_STOP | SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK, | ||||
.baseclk_div = 2, | .baseclk_div = 2, | ||||
.errata = SDHCI_FSL_UNRELIABLE_PULSE_DET | | .errata = SDHCI_FSL_UNRELIABLE_PULSE_DET | | ||||
SDHCI_FSL_HS400_LIMITED_CLK_DIV, | SDHCI_FSL_HS400_LIMITED_CLK_DIV, | ||||
}; | }; | ||||
static const struct sdhci_fsl_fdt_soc_data sdhci_fsl_fdt_ls1046a_soc_data = { | static const struct sdhci_fsl_fdt_soc_data sdhci_fsl_fdt_ls1046a_soc_data = { | ||||
.quirks = SDHCI_QUIRK_DONT_SET_HISPD_BIT | SDHCI_QUIRK_BROKEN_AUTO_STOP, | .quirks = SDHCI_QUIRK_DONT_SET_HISPD_BIT | SDHCI_QUIRK_BROKEN_AUTO_STOP, | ||||
.baseclk_div = 2, | .baseclk_div = 2, | ||||
.errata = SDHCI_FSL_UNSUPP_1_8V | SDHCI_FSL_TUNING_ERRATUM_TYPE2, | .errata = SDHCI_FSL_MISSING_VCCQ_REG | SDHCI_FSL_TUNING_ERRATUM_TYPE2, | ||||
.syscon_compat = "fsl,ls1046a-scfg", | |||||
}; | }; | ||||
static const struct sdhci_fsl_fdt_soc_data sdhci_fsl_fdt_lx2160a_soc_data = { | static const struct sdhci_fsl_fdt_soc_data sdhci_fsl_fdt_lx2160a_soc_data = { | ||||
.quirks = 0, | .quirks = 0, | ||||
.baseclk_div = 2, | .baseclk_div = 2, | ||||
.errata = SDHCI_FSL_UNRELIABLE_PULSE_DET | | .errata = SDHCI_FSL_UNRELIABLE_PULSE_DET | | ||||
SDHCI_FSL_HS400_LIMITED_CLK_DIV, | SDHCI_FSL_HS400_LIMITED_CLK_DIV, | ||||
}; | }; | ||||
▲ Show 20 Lines • Show All 389 Lines • ▼ Show 20 Lines | if (sc->fdt_helper.vqmmc_supply) | ||||
regulator_enable(sc->fdt_helper.vqmmc_supply); | regulator_enable(sc->fdt_helper.vqmmc_supply); | ||||
break; | break; | ||||
}; | }; | ||||
return (0); | return (0); | ||||
} | } | ||||
static int | static int | ||||
sdhci_fsl_fdt_switch_syscon_voltage(device_t dev, | |||||
struct sdhci_fsl_fdt_softc *sc, enum mmc_vccq vccq) | |||||
{ | |||||
struct syscon *syscon; | |||||
phandle_t syscon_node; | |||||
uint32_t reg; | |||||
if (sc->soc_data->syscon_compat == NULL) { | |||||
device_printf(dev, "Empty syscon compat string.\n"); | |||||
return (ENXIO); | |||||
} | |||||
syscon_node = ofw_bus_find_compatible(OF_finddevice("/"), | |||||
sc->soc_data->syscon_compat); | |||||
if (syscon_get_by_ofw_node(dev, syscon_node, &syscon) != 0) { | |||||
device_printf(dev, "Could not find syscon node.\n"); | |||||
return (ENXIO); | |||||
} | |||||
reg = SYSCON_READ_4(syscon, SCFG_SDHCIOVSELCR); | |||||
reg &= ~SCFG_SDHCIOVSELCR_VSELVAL_MASK; | |||||
reg |= SCFG_SDHCIOVSELCR_TGLEN; | |||||
switch (vccq) { | |||||
case vccq_180: | |||||
reg |= SCFG_SDHCIOVSELCR_VSELVAL_1_8; | |||||
SYSCON_WRITE_4(syscon, SCFG_SDHCIOVSELCR, reg); | |||||
DELAY(5000); | |||||
reg = SYSCON_READ_4(syscon, SCFG_SDHCIOVSELCR); | |||||
reg |= SCFG_SDHCIOVSELCR_VS; | |||||
Not Done Inline ActionsDoes a write to SCFG_SDHCIOVSELCR clear SCFG_SDHCIOVSELCR_TGLEN? Otherwise I see no reason setting it again for each voltage, when it's already set in line 665. contact_artur-rojek.eu: Does a write to `SCFG_SDHCIOVSELCR` clear `SCFG_SDHCIOVSELCR_TGLEN`? Otherwise I see no reason… | |||||
break; | |||||
case vccq_330: | |||||
reg |= SCFG_SDHCIOVSELCR_VSELVAL_3_3; | |||||
SYSCON_WRITE_4(syscon, SCFG_SDHCIOVSELCR, reg); | |||||
DELAY(5000); | |||||
reg = SYSCON_READ_4(syscon, SCFG_SDHCIOVSELCR); | |||||
reg &= ~SCFG_SDHCIOVSELCR_VS; | |||||
break; | |||||
default: | |||||
device_printf(dev, "Unsupported voltage requested.\n"); | |||||
return (ENXIO); | |||||
} | |||||
SYSCON_WRITE_4(syscon, SCFG_SDHCIOVSELCR, reg); | |||||
return (0); | |||||
} | |||||
static int | |||||
sdhci_fsl_fdt_switch_vccq(device_t brdev, device_t reqdev) | sdhci_fsl_fdt_switch_vccq(device_t brdev, device_t reqdev) | ||||
{ | { | ||||
struct sdhci_fsl_fdt_softc *sc; | struct sdhci_fsl_fdt_softc *sc; | ||||
struct sdhci_slot *slot; | struct sdhci_slot *slot; | ||||
regulator_t vqmmc_supply; | regulator_t vqmmc_supply; | ||||
uint32_t val_old, val; | uint32_t val_old, val; | ||||
int uvolt, err; | int uvolt, err = 0; | ||||
sc = device_get_softc(brdev); | sc = device_get_softc(brdev); | ||||
slot = device_get_ivars(reqdev); | slot = device_get_ivars(reqdev); | ||||
val_old = val = RD4(sc, SDHCI_FSL_PROT_CTRL); | val_old = val = RD4(sc, SDHCI_FSL_PROT_CTRL); | ||||
switch (slot->host.ios.vccq) { | switch (slot->host.ios.vccq) { | ||||
case vccq_180: | case vccq_180: | ||||
if (sc->soc_data->errata & SDHCI_FSL_UNSUPP_1_8V) | if (sc->soc_data->errata & SDHCI_FSL_UNSUPP_1_8V) | ||||
return (EOPNOTSUPP); | return (EOPNOTSUPP); | ||||
Not Done Inline ActionsOne more thought - to prevent this code from becoming spaghetti, I'd suggest to delegate the actual voltage switch logic to its own helper. contact_artur-rojek.eu: One more thought - to prevent this code from becoming spaghetti, I'd suggest to delegate the… | |||||
val |= SDHCI_FSL_PROT_CTRL_VOLT_SEL; | val |= SDHCI_FSL_PROT_CTRL_VOLT_SEL; | ||||
uvolt = 1800000; | uvolt = 1800000; | ||||
break; | break; | ||||
case vccq_330: | case vccq_330: | ||||
val &= ~SDHCI_FSL_PROT_CTRL_VOLT_SEL; | val &= ~SDHCI_FSL_PROT_CTRL_VOLT_SEL; | ||||
uvolt = 3300000; | uvolt = 3300000; | ||||
break; | break; | ||||
default: | default: | ||||
return (EOPNOTSUPP); | return (EOPNOTSUPP); | ||||
} | } | ||||
WR4(sc, SDHCI_FSL_PROT_CTRL, val); | WR4(sc, SDHCI_FSL_PROT_CTRL, val); | ||||
if (sc->soc_data->errata & SDHCI_FSL_MISSING_VCCQ_REG) { | |||||
Not Done Inline ActionsNow we're skipping over the switch (slot->host.ios.vccq) block, leaving uvolt uninitialized, but we later use it in regulator_set_voltage(vqmmc_supply, uvolt, uvolt). contact_artur-rojek.eu: Now we're skipping over the `switch (slot->host.ios.vccq)` block, leaving `uvolt` uninitialized… | |||||
err = sdhci_fsl_fdt_switch_syscon_voltage(brdev, sc, | |||||
slot->host.ios.vccq); | |||||
if (err != 0) | |||||
goto vccq_fail; | |||||
} | |||||
vqmmc_supply = sc->fdt_helper.vqmmc_supply; | vqmmc_supply = sc->fdt_helper.vqmmc_supply; | ||||
Not Done Inline ActionsWill we still get non-null value here on boards that have the regulator connected by syscon? If so, I think the logic should terminate here. contact_artur-rojek.eu: Will we still get non-null value here on boards that have the regulator connected by syscon? If… | |||||
/* | /* | ||||
* Even though we expect to find a fixed regulator in this controller | * Even though we expect to find a fixed regulator in this controller | ||||
* family, let's play safe. | * family, let's play safe. | ||||
*/ | */ | ||||
if (vqmmc_supply != NULL) { | if (vqmmc_supply != NULL) { | ||||
err = regulator_set_voltage(vqmmc_supply, uvolt, uvolt); | err = regulator_set_voltage(vqmmc_supply, uvolt, uvolt); | ||||
if (err != 0) { | if (err != 0) | ||||
device_printf(sc->dev, | goto vccq_fail; | ||||
"Cannot set vqmmc to %d<->%d\n", uvolt, uvolt); | |||||
WR4(sc, SDHCI_FSL_PROT_CTRL, val_old); | |||||
return (err); | |||||
} | } | ||||
} | |||||
return (0); | return (0); | ||||
vccq_fail: | |||||
device_printf(sc->dev, "Cannot set vqmmc to %d<->%d\n", uvolt, uvolt); | |||||
WR4(sc, SDHCI_FSL_PROT_CTRL, val_old); | |||||
return (err); | |||||
} | } | ||||
static int | static int | ||||
sdhci_fsl_fdt_get_ro(device_t bus, device_t child) | sdhci_fsl_fdt_get_ro(device_t bus, device_t child) | ||||
{ | { | ||||
struct sdhci_fsl_fdt_softc *sc; | struct sdhci_fsl_fdt_softc *sc; | ||||
sc = device_get_softc(bus); | sc = device_get_softc(bus); | ||||
▲ Show 20 Lines • Show All 818 Lines • Show Last 20 Lines |
s/does not have fixed regulator/don't have a fixed regulator