Changeset View
Changeset View
Standalone View
Standalone View
sys/arm64/rockchip/rk3399_emmcphy.c
Property | Old Value | New Value |
---|---|---|
svn:eol-style | null | native \ No newline at end of property |
svn:keywords | null | FreeBSD=%H \ No newline at end of property |
svn:mime-type | null | text/plain \ No newline at end of property |
/*- | |||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD | |||||
* | |||||
* Copyright (c) 2019 Ganbold Tsagaankhuu <ganbold@FreeBSD.org> | |||||
* All rights reserved. | |||||
* | |||||
* 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. | |||||
*/ | |||||
/* | |||||
* Rockchip RK3399 eMMC PHY | |||||
*/ | |||||
#include <sys/cdefs.h> | |||||
__FBSDID("$FreeBSD$"); | |||||
#include <sys/param.h> | |||||
#include <sys/systm.h> | |||||
#include <sys/bus.h> | |||||
#include <sys/rman.h> | |||||
#include <sys/kernel.h> | |||||
#include <sys/module.h> | |||||
#include <sys/gpio.h> | |||||
#include <machine/bus.h> | |||||
#include <dev/fdt/fdt_common.h> | |||||
#include <dev/ofw/ofw_bus.h> | |||||
#include <dev/ofw/ofw_bus_subr.h> | |||||
#include <dev/extres/clk/clk.h> | |||||
#include <dev/extres/syscon/syscon.h> | |||||
#include <dev/extres/phy/phy.h> | |||||
#include "syscon_if.h" | |||||
#include "phynode_if.h" | |||||
#define GRF_EMMCPHY_BASE 0xf780 | |||||
#define GRF_EMMCPHY_CON0 (GRF_EMMCPHY_BASE + 0x00) | |||||
#define PHYCTRL_FRQSEL (1 << 13) | (1 << 12) | |||||
#define PHYCTRL_FRQSEL_200M 0 | |||||
#define PHYCTRL_FRQSEL_50M 1 | |||||
#define PHYCTRL_FRQSEL_100M 2 | |||||
#define PHYCTRL_FRQSEL_150M 3 | |||||
#define PHYCTRL_OTAPDLYENA (1 << 11) | |||||
#define PHYCTRL_OTAPDLYSEL (1 << 10) | (1 << 9) | (1 << 8) | (1 << 7) | |||||
#define PHYCTRL_ITAPCHGWIN (1 << 6) | |||||
#define PHYCTRL_ITAPDLYSEL (1 << 5) | (1 << 4) | (1 << 3) | (1 << 2) | \ | |||||
(1 << 1) | |||||
#define PHYCTRL_ITAPDLYENA (1 << 0) | |||||
#define GRF_EMMCPHY_CON1 (GRF_EMMCPHY_BASE + 0x04) | |||||
#define PHYCTRL_CLKBUFSEL (1 << 8) | (1 << 7) | (1 << 6) | |||||
#define PHYCTRL_SELDLYTXCLK (1 << 5) | |||||
#define PHYCTRL_SELDLYRXCLK (1 << 4) | |||||
#define PHYCTRL_STRBSEL 0xf | |||||
#define GRF_EMMCPHY_CON2 (GRF_EMMCPHY_BASE + 0x08) | |||||
#define PHYCTRL_REN_STRB (1 << 9) | |||||
#define PHYCTRL_REN_CMD (1 << 8) | |||||
#define PHYCTRL_REN_DAT 0xff | |||||
#define GRF_EMMCPHY_CON3 (GRF_EMMCPHY_BASE + 0x0c) | |||||
#define PHYCTRL_PU_STRB (1 << 9) | |||||
#define PHYCTRL_PU_CMD (1 << 8) | |||||
#define PHYCTRL_PU_DAT 0xff | |||||
#define GRF_EMMCPHY_CON4 (GRF_EMMCPHY_BASE + 0x10) | |||||
#define PHYCTRL_OD_RELEASE_CMD (1 << 9) | |||||
#define PHYCTRL_OD_RELEASE_STRB (1 << 8) | |||||
#define PHYCTRL_OD_RELEASE_DAT 0xff | |||||
#define GRF_EMMCPHY_CON5 (GRF_EMMCPHY_BASE + 0x14) | |||||
#define PHYCTRL_ODEN_STRB (1 << 9) | |||||
#define PHYCTRL_ODEN_CMD (1 << 8) | |||||
#define PHYCTRL_ODEN_DAT 0xff | |||||
#define GRF_EMMCPHY_CON6 (GRF_EMMCPHY_BASE + 0x18) | |||||
#define PHYCTRL_DLL_TRM_ICP (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | |||||
#define PHYCTRL_EN_RTRIM (1 << 8) | |||||
#define PHYCTRL_RETRIM (1 << 7) | |||||
#define PHYCTRL_DR_TY (1 << 6) | (1 << 5) | (1 << 4) | |||||
#define PHYCTRL_RETENB (1 << 3) | |||||
#define PHYCTRL_RETEN (1 << 2) | |||||
#define PHYCTRL_ENDLL (1 << 1) | |||||
#define PHYCTRL_PDB (1 << 0) | |||||
#define GRF_EMMCPHY_STATUS (GRF_EMMCPHY_BASE + 0x20) | |||||
#define PHYCTRL_CALDONE (1 << 6) | |||||
#define PHYCTRL_DLLRDY (1 << 5) | |||||
#define PHYCTRL_RTRIM (1 << 4) | (1 << 3) | (1 << 2) | (1 << 1) | |||||
#define PHYCTRL_EXR_NINST (1 << 0) | |||||
static struct ofw_compat_data compat_data[] = { | |||||
{ "rockchip,rk3399-emmc-phy", 1 }, | |||||
{ NULL, 0 } | |||||
}; | |||||
struct rk_emmcphy_softc { | |||||
struct syscon *syscon; | |||||
struct rk_emmcphy_conf *phy_conf; | |||||
clk_t clk; | |||||
}; | |||||
#define LOWEST_SET_BIT(mask) ((((mask) - 1) & (mask)) ^ (mask)) | |||||
#define SHIFTIN(x, mask) ((x) * LOWEST_SET_BIT(mask)) | |||||
static struct phynode *phynode; | |||||
manu: This doesn't need to be a global variable now. | |||||
/* Phy class and methods. */ | |||||
static int rk_emmcphy_enable(struct phynode *phynode, bool enable); | |||||
static phynode_method_t rk_emmcphynode_methods[] = { | |||||
PHYNODEMETHOD(phynode_enable, rk_emmcphy_enable), | |||||
PHYNODEMETHOD_END | |||||
}; | |||||
DEFINE_CLASS_1(rk_emmcphy_phynode, rk_emmcphy_phynode_class, | |||||
rk_emmcphynode_methods, sizeof(struct rk_emmcphy_softc), phynode_class); | |||||
static int | |||||
rk_emmcphy_enable(struct phynode *phynode, bool enable) | |||||
{ | |||||
struct rk_emmcphy_softc *sc; | |||||
device_t dev; | |||||
uint64_t rate, frqsel; | |||||
uint32_t mask, val; | |||||
int error; | |||||
dev = phynode_get_device(phynode); | |||||
sc = device_get_softc(dev); | |||||
sc->phy_conf = (struct rk_emmcphy_conf *)ofw_bus_search_compatible(dev, | |||||
compat_data)->ocd_data; | |||||
Not Done Inline ActionsYou should validate that the phy id is correct by using phynode_get_id manu: You should validate that the phy id is correct by using phynode_get_id | |||||
/* Get clock */ | |||||
Not Done Inline ActionsThis is not correct, this function is suppose to disable the phy by writing to the registers. manu: This is not correct, this function is suppose to disable the phy by writing to the registers. | |||||
error = clk_get_by_ofw_name(dev, 0, "emmcclk", &sc->clk); | |||||
if (error != 0) { | |||||
device_printf(dev, "cannot get emmcclk clock, continue\n"); | |||||
sc->clk = NULL; | |||||
} else | |||||
device_printf(dev, "got emmcclk clock\n"); | |||||
/* Drive strength */ | |||||
mask = PHYCTRL_DR_TY; | |||||
val = SHIFTIN(0, PHYCTRL_DR_TY); | |||||
SYSCON_WRITE_4(sc->syscon, GRF_EMMCPHY_CON6, (mask << 16) | val); | |||||
/* Enable output tap delay */ | |||||
mask = PHYCTRL_OTAPDLYENA | PHYCTRL_OTAPDLYSEL; | |||||
val = PHYCTRL_OTAPDLYENA | SHIFTIN(4, PHYCTRL_OTAPDLYSEL); | |||||
SYSCON_WRITE_4(sc->syscon, GRF_EMMCPHY_CON0, (mask << 16) | val); | |||||
/* Power down PHY and disable DLL before making changes */ | |||||
mask = PHYCTRL_ENDLL | PHYCTRL_PDB; | |||||
val = 0; | |||||
SYSCON_WRITE_4(sc->syscon, GRF_EMMCPHY_CON6, (mask << 16) | val); | |||||
if (sc->clk) { | |||||
error = clk_get_freq(sc->clk, &rate); | |||||
if (error != 0) { | |||||
device_printf(dev, "cannot get clock frequency\n"); | |||||
return (ENXIO); | |||||
} | |||||
} else | |||||
rate = 0; | |||||
if (rate != 0) { | |||||
if (rate < 75000000) | |||||
frqsel = PHYCTRL_FRQSEL_50M; | |||||
else if (rate < 125000000) | |||||
frqsel = PHYCTRL_FRQSEL_100M; | |||||
else if (rate < 175000000) | |||||
frqsel = PHYCTRL_FRQSEL_150M; | |||||
else | |||||
frqsel = PHYCTRL_FRQSEL_200M; | |||||
} else { | |||||
frqsel = PHYCTRL_FRQSEL_200M; | |||||
} | |||||
DELAY(3); | |||||
/* Power up PHY */ | |||||
mask = PHYCTRL_PDB; | |||||
val = PHYCTRL_PDB; | |||||
SYSCON_WRITE_4(sc->syscon, GRF_EMMCPHY_CON6, (mask << 16) | val); | |||||
/* Wait for calibration */ | |||||
DELAY(10); | |||||
val = SYSCON_READ_4(sc->syscon, GRF_EMMCPHY_STATUS); | |||||
if ((val & PHYCTRL_CALDONE) == 0) { | |||||
device_printf(dev, "PHY calibration did not complete\n"); | |||||
return (ENXIO); | |||||
} | |||||
/* Set DLL frequency */ | |||||
mask = PHYCTRL_FRQSEL; | |||||
val = SHIFTIN(frqsel, PHYCTRL_FRQSEL); | |||||
SYSCON_WRITE_4(sc->syscon, GRF_EMMCPHY_CON0, (mask << 16) | val); | |||||
/* Enable DLL */ | |||||
mask = PHYCTRL_ENDLL; | |||||
val = PHYCTRL_ENDLL; | |||||
SYSCON_WRITE_4(sc->syscon, GRF_EMMCPHY_CON6, (mask << 16) | val); | |||||
Not Done Inline ActionsThis should be the phy_init method, then sdhci can just call phy_init(sc->phy); manu: This should be the phy_init method, then sdhci can just call phy_init(sc->phy); | |||||
if (rate != 0) { | |||||
/* Wait for DLL ready */ | |||||
DELAY(50000); | |||||
val = SYSCON_READ_4(sc->syscon, GRF_EMMCPHY_STATUS); | |||||
if ((val & PHYCTRL_DLLRDY) == 0) { | |||||
device_printf(dev, "DLL loop failed to lock\n"); | |||||
return (ENXIO); | |||||
} | |||||
} | |||||
return (0); | |||||
} | |||||
static int | |||||
rk_emmcphy_probe(device_t dev) | |||||
{ | |||||
if (!ofw_bus_status_okay(dev)) | |||||
return (ENXIO); | |||||
if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) | |||||
Not Done Inline ActionsCan you do a smaller delay value and use a loop for the timeout ? manu: Can you do a smaller delay value and use a loop for the timeout ?
Is there any info in the user… | |||||
Not Done Inline ActionsWhy not waiting the correct amount of time based on the frequency ? manu: Why not waiting the correct amount of time based on the frequency ? | |||||
return (ENXIO); | |||||
device_set_desc(dev, "Rockchip RK3399 eMMC PHY"); | |||||
return (BUS_PROBE_DEFAULT); | |||||
} | |||||
static int | |||||
rk_emmcphy_attach(device_t dev) | |||||
{ | |||||
struct phynode_init_def phy_init; | |||||
struct rk_emmcphy_softc *sc; | |||||
phandle_t node; | |||||
phandle_t xnode; | |||||
pcell_t handle; | |||||
sc = device_get_softc(dev); | |||||
node = ofw_bus_get_node(dev); | |||||
if (OF_getencprop(node, "clocks", (void *)&handle, | |||||
sizeof(handle)) <= 0) { | |||||
device_printf(dev, "cannot get clocks handle\n"); | |||||
return (ENXIO); | |||||
} | |||||
xnode = OF_node_from_xref(handle); | |||||
if (OF_hasprop(xnode, "arasan,soc-ctl-syscon") && | |||||
syscon_get_by_ofw_property(dev, xnode, | |||||
"arasan,soc-ctl-syscon", &sc->syscon) != 0) { | |||||
device_printf(dev, "cannot get grf driver handle\n"); | |||||
return (ENXIO); | |||||
} | |||||
if (sc->syscon == NULL) { | |||||
device_printf(dev, "failed to get syscon\n"); | |||||
return (ENXIO); | |||||
} | |||||
/* Create and register phy */ | |||||
bzero(&phy_init, sizeof(phy_init)); | |||||
phy_init.id = 0; | |||||
phy_init.ofw_node = ofw_bus_get_node(dev); | |||||
phynode = phynode_create(dev, &phynode_class, &phy_init); | |||||
if (phynode == NULL) { | |||||
device_printf(dev, "failed to create eMMC PHY\n"); | |||||
return (ENXIO); | |||||
} | |||||
if (phynode_register(phynode) == NULL) { | |||||
device_printf(dev, "failed to create eMMC PHY\n"); | |||||
return (ENXIO); | |||||
} | |||||
return (0); | |||||
} | |||||
static device_method_t rk_emmcphy_methods[] = { | |||||
/* Device interface */ | |||||
DEVMETHOD(device_probe, rk_emmcphy_probe), | |||||
DEVMETHOD(device_attach, rk_emmcphy_attach), | |||||
DEVMETHOD_END | |||||
}; | |||||
static driver_t rk_emmcphy_driver = { | |||||
"rk_emmcphy", | |||||
rk_emmcphy_methods, | |||||
sizeof(struct rk_emmcphy_softc) | |||||
}; | |||||
static devclass_t rk_emmcphy_devclass; | |||||
EARLY_DRIVER_MODULE(rk_emmcphy, simplebus, rk_emmcphy_driver, | |||||
rk_emmcphy_devclass, 0, 0, BUS_PASS_SUPPORTDEV + BUS_PASS_ORDER_MIDDLE); | |||||
MODULE_VERSION(rk_emmcphy, 1); |
This doesn't need to be a global variable now.