/* $OpenBSD: rkusbphy.c,v 1.5 2024/06/23 10:18:11 kettenis Exp $ */ /* * Copyright (c) 2023 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * Rockchip USB2PHY with Innosilicon IP */ #include #include #include #include #include #include #include #include #include #include #include /* * chip stuff */ struct rkusbphy_reg { bus_size_t r_offs; unsigned int r_shift; uint32_t r_mask; uint32_t r_set; }; struct rkusbphy_port_regs { struct rkusbphy_reg phy_enable; }; struct rkusbphy_regs { struct rkusbphy_reg clk_enable; struct rkusbphy_port_regs otg; struct rkusbphy_port_regs host; }; struct rkusbphy_chip { bus_addr_t c_base_addr; const struct rkusbphy_regs *c_regs; }; /* * RK3568 has two USB2PHY nodes that have a GRF each. Each GRF has * the same register layout. */ static const struct rkusbphy_regs rkusbphy_rk3568_regs = { /* shift, mask, set */ .clk_enable = { 0x0008, 4, 0x1, 0x0 }, .otg = { .phy_enable = { 0x0000, 0, 0x1ff, 0x1d2 }, }, .host = { .phy_enable = { 0x0004, 0, 0x1ff, 0x1d2 }, }, }; static const struct rkusbphy_chip rkusbphy_rk3568[] = { { .c_base_addr = 0xfe8a0000, .c_regs = &rkusbphy_rk3568_regs, }, { .c_base_addr = 0xfe8b0000, .c_regs = &rkusbphy_rk3568_regs, }, }; static const struct rkusbphy_regs rkusbphy_rk3588_regs = { /* shift, mask, set */ .clk_enable = { 0x0000, 0, 0x1, 0x0 }, .otg = { .phy_enable = { 0x000c, 11, 0x1, 0x0 }, }, .host = { .phy_enable = { 0x0008, 2, 0x1, 0x0 }, }, }; static const struct rkusbphy_chip rkusbphy_rk3588[] = { { .c_base_addr = 0x0000, .c_regs = &rkusbphy_rk3588_regs, }, { .c_base_addr = 0x4000, .c_regs = &rkusbphy_rk3588_regs, }, { .c_base_addr = 0x8000, .c_regs = &rkusbphy_rk3588_regs, }, { .c_base_addr = 0xc000, .c_regs = &rkusbphy_rk3588_regs, }, }; /* * driver stuff */ struct rkusbphy_softc { struct device sc_dev; const struct rkusbphy_regs *sc_regs; struct regmap *sc_grf; int sc_node; int sc_running; struct phy_device sc_otg_phy; struct phy_device sc_host_phy; }; #define DEVNAME(_sc) ((_sc)->sc_dev.dv_xname) static int rkusbphy_match(struct device *, void *, void *); static void rkusbphy_attach(struct device *, struct device *, void *); static uint32_t rkusbphy_rd(struct rkusbphy_softc *, const struct rkusbphy_reg *); static int rkusbphy_isset(struct rkusbphy_softc *, const struct rkusbphy_reg *); static void rkusbphy_wr(struct rkusbphy_softc *, const struct rkusbphy_reg *, uint32_t); static void rkusbphy_set(struct rkusbphy_softc *, const struct rkusbphy_reg *); static int rkusbphy_otg_phy_enable(void *, uint32_t *); static int rkusbphy_host_phy_enable(void *, uint32_t *); struct rkusbphy_port_config { const char *pc_name; int (*pc_enable)(void *, uint32_t *); }; static void rkusbphy_register(struct rkusbphy_softc *, struct phy_device *, const struct rkusbphy_port_config *); static const struct rkusbphy_port_config rkusbphy_otg_config = { .pc_name = "otg-port", .pc_enable = rkusbphy_otg_phy_enable, }; static const struct rkusbphy_port_config rkusbphy_host_config = { .pc_name = "host-port", .pc_enable = rkusbphy_host_phy_enable, }; const struct cfattach rkusbphy_ca = { sizeof (struct rkusbphy_softc), rkusbphy_match, rkusbphy_attach }; struct cfdriver rkusbphy_cd = { NULL, "rkusbphy", DV_DULL }; struct rkusbphy_id { const char *id_name; const struct rkusbphy_chip *id_chips; size_t id_nchips; }; #define RKUSBPHY_ID(_n, _c) { _n, _c, nitems(_c) } static const struct rkusbphy_id rkusbphy_ids[] = { RKUSBPHY_ID("rockchip,rk3568-usb2phy", rkusbphy_rk3568), RKUSBPHY_ID("rockchip,rk3588-usb2phy", rkusbphy_rk3588), }; static const struct rkusbphy_id * rkusbphy_lookup(struct fdt_attach_args *faa) { size_t i; for (i = 0; i < nitems(rkusbphy_ids); i++) { const struct rkusbphy_id *id = &rkusbphy_ids[i]; if (OF_is_compatible(faa->fa_node, id->id_name)) return (id); } return (NULL); } static int rkusbphy_match(struct device *parent, void *match, void *aux) { struct fdt_attach_args *faa = aux; return (rkusbphy_lookup(faa) != NULL ? 1 : 0); } static void rkusbphy_attach(struct device *parent, struct device *self, void *aux) { struct rkusbphy_softc *sc = (struct rkusbphy_softc *)self; struct fdt_attach_args *faa = aux; const struct rkusbphy_id *id = rkusbphy_lookup(faa); size_t i; uint32_t grfph; if (faa->fa_nreg < 1) { printf(": no registers\n"); return; } for (i = 0; i < id->id_nchips; i++) { const struct rkusbphy_chip *c = &id->id_chips[i]; if (faa->fa_reg[0].addr == c->c_base_addr) { printf(": phy %zu\n", i); sc->sc_regs = c->c_regs; break; } } if (sc->sc_regs == NULL) { printf(": unknown base address 0x%llu\n", faa->fa_reg[0].addr); return; } sc->sc_node = faa->fa_node; grfph = OF_getpropint(sc->sc_node, "rockchip,usbgrf", 0); if (grfph) sc->sc_grf = regmap_byphandle(grfph); else sc->sc_grf = regmap_bynode(OF_parent(faa->fa_node)); if (sc->sc_grf == NULL) { printf("%s: rockchip,usbgrf 0x%x not found\n", DEVNAME(sc), grfph); return; } rkusbphy_register(sc, &sc->sc_otg_phy, &rkusbphy_otg_config); rkusbphy_register(sc, &sc->sc_host_phy, &rkusbphy_host_config); } static uint32_t rkusbphy_rd(struct rkusbphy_softc *sc, const struct rkusbphy_reg *r) { uint32_t v; if (r->r_mask == 0) return (0); v = regmap_read_4(sc->sc_grf, r->r_offs); return ((v >> r->r_shift) & r->r_mask); } static int rkusbphy_isset(struct rkusbphy_softc *sc, const struct rkusbphy_reg *r) { return (rkusbphy_rd(sc, r) == r->r_set); } static void rkusbphy_wr(struct rkusbphy_softc *sc, const struct rkusbphy_reg *r, uint32_t v) { if (r->r_mask == 0) return; regmap_write_4(sc->sc_grf, r->r_offs, (r->r_mask << (r->r_shift + 16)) | (v << r->r_shift)); } static void rkusbphy_set(struct rkusbphy_softc *sc, const struct rkusbphy_reg *r) { rkusbphy_wr(sc, r, r->r_set); } static void rkusbphy_register(struct rkusbphy_softc *sc, struct phy_device *pd, const struct rkusbphy_port_config *pc) { char status[32]; int node; node = OF_getnodebyname(sc->sc_node, pc->pc_name); if (node == 0) return; if (OF_getprop(node, "status", status, sizeof(status)) > 0 && strcmp(status, "disabled") == 0) return; pd->pd_node = node; pd->pd_cookie = sc; pd->pd_enable = pc->pc_enable; phy_register(pd); } static void rkusbphy_phy_supply(struct rkusbphy_softc *sc, int node) { int phandle; if (!sc->sc_running) { clock_enable(sc->sc_node, "phyclk"); if (!rkusbphy_isset(sc, &sc->sc_regs->clk_enable)) { rkusbphy_set(sc, &sc->sc_regs->clk_enable); delay(1200); } sc->sc_running = 1; } phandle = OF_getpropint(node, "phy-supply", 0); if (phandle == 0) return; regulator_enable(phandle); } static int rkusbphy_otg_phy_enable(void *cookie, uint32_t *cells) { struct rkusbphy_softc *sc = cookie; rkusbphy_phy_supply(sc, sc->sc_otg_phy.pd_node); rkusbphy_set(sc, &sc->sc_regs->otg.phy_enable); delay(1500); return (EINVAL); } static int rkusbphy_host_phy_enable(void *cookie, uint32_t *cells) { struct rkusbphy_softc *sc = cookie; rkusbphy_phy_supply(sc, sc->sc_host_phy.pd_node); rkusbphy_set(sc, &sc->sc_regs->host.phy_enable); delay(1500); return (EINVAL); }