/* $NetBSD: imx6_ahcisata.c,v 1.10 2019/07/22 11:44:01 hkenken Exp $ */ /* * Copyright (c) 2014 Ryo Shimizu * 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 ``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 __KERNEL_RCSID(0, "$NetBSD: imx6_ahcisata.c,v 1.10 2019/07/22 11:44:01 hkenken Exp $"); #include "locators.h" #include "opt_imx.h" #include #include #include #include #include #include #include #include #include #include #include struct imx_ahci_softc { struct ahci_softc sc_ahcisc; device_t sc_dev; bus_space_tag_t sc_iot; bus_space_handle_t sc_ioh; void *sc_ih; struct clk *sc_clk_sata; struct clk *sc_clk_sata_ref; struct clk *sc_clk_ahb; }; static int imx6_ahcisata_match(device_t, cfdata_t, void *); static void imx6_ahcisata_attach(device_t, device_t, void *); static int imx6_ahcisata_detach(device_t, int); static int imx6_ahcisata_phy_ctrl(struct imx_ahci_softc *, uint32_t, int); static int imx6_ahcisata_phy_addr(struct imx_ahci_softc *, uint32_t); static int imx6_ahcisata_phy_write(struct imx_ahci_softc *, uint32_t, uint16_t); static int imx6_ahcisata_phy_read(struct imx_ahci_softc *, uint32_t); static int imx6_ahcisata_init(struct imx_ahci_softc *); static int imx6_ahcisata_init_clocks(struct imx_ahci_softc *); CFATTACH_DECL_NEW(imx6_ahcisata, sizeof(struct imx_ahci_softc), imx6_ahcisata_match, imx6_ahcisata_attach, imx6_ahcisata_detach, NULL); static int imx6_ahcisata_match(device_t parent, cfdata_t match, void *aux) { struct axi_attach_args * const aa = aux; if (aa->aa_addr != IMX6_SATA_BASE) return 0; /* i.MX6 Solo/SoloLite/DualLite has no SATA interface */ switch (IMX6_CHIPID_MAJOR(imx6_chip_id())) { case CHIPID_MAJOR_IMX6SL: case CHIPID_MAJOR_IMX6DL: case CHIPID_MAJOR_IMX6SOLO: case CHIPID_MAJOR_IMX6UL: return 0; default: break; } return 1; } static void imx6_ahcisata_attach(device_t parent, device_t self, void *aux) { struct imx_ahci_softc *sc; struct ahci_softc *ahci_sc; struct axi_attach_args *aa; aa = aux; sc = device_private(self); sc->sc_dev = self; sc->sc_iot = aa->aa_iot; if (aa->aa_size == AXICF_SIZE_DEFAULT) aa->aa_size = IMX6_SATA_SIZE; aprint_naive("\n"); aprint_normal(": AHCI Controller\n"); if (bus_space_map(aa->aa_iot, aa->aa_addr, aa->aa_size, 0, &sc->sc_ioh)) { aprint_error_dev(self, "cannot map registers\n"); return; } sc->sc_clk_sata = imx6_get_clock("sata"); if (sc->sc_clk_sata == NULL) { aprint_error(": couldn't get clock sata\n"); return; } sc->sc_clk_sata_ref = imx6_get_clock("sata_ref"); if (sc->sc_clk_sata_ref == NULL) { aprint_error(": couldn't get clock sata_ref\n"); return; } sc->sc_clk_ahb = imx6_get_clock("ahb"); if (sc->sc_clk_ahb == NULL) { aprint_error(": couldn't get clock ahb\n"); return; } if (imx6_ahcisata_init_clocks(sc) != 0) { aprint_error_dev(self, "couldn't init clocks\n"); return; } if (imx6_ahcisata_init(sc) != 0) { aprint_error_dev(self, "couldn't init ahci\n"); return; } ahci_sc = &sc->sc_ahcisc; ahci_sc->sc_atac.atac_dev = sc->sc_dev; ahci_sc->sc_ahci_ports = 1; ahci_sc->sc_dmat = aa->aa_dmat; ahci_sc->sc_ahcis = aa->aa_size; ahci_sc->sc_ahcit = sc->sc_iot; ahci_sc->sc_ahcih = sc->sc_ioh; sc->sc_ih = intr_establish(aa->aa_irq, IPL_BIO, IST_LEVEL, ahci_intr, ahci_sc); if (sc->sc_ih == NULL) { aprint_error_dev(self, "unable to establish interrupt\n"); return; } ahci_attach(ahci_sc); } static int imx6_ahcisata_detach(device_t self, int flags) { struct imx_ahci_softc *sc; struct ahci_softc *ahci_sc; int rv; sc = device_private(self); ahci_sc = &sc->sc_ahcisc; rv = ahci_detach(ahci_sc, flags); if (rv) return rv; if (sc->sc_ih) { intr_disestablish(sc->sc_ih); sc->sc_ih = NULL; } if (ahci_sc->sc_ahcis) { bus_space_unmap(ahci_sc->sc_ahcit, ahci_sc->sc_ahcih, ahci_sc->sc_ahcis); ahci_sc->sc_ahcis = 0; ahci_sc->sc_ahcit = 0; ahci_sc->sc_ahcih = 0; } return 0; } static int imx6_ahcisata_phy_ctrl(struct imx_ahci_softc *sc, uint32_t bitmask, int on) { uint32_t v; int timeout; v = bus_space_read_4(sc->sc_iot, sc->sc_ioh, SATA_P0PHYCR); if (on) v |= bitmask; else v &= ~bitmask; bus_space_write_4(sc->sc_iot, sc->sc_ioh, SATA_P0PHYCR, v); for (timeout = 5000; timeout > 0; --timeout) { v = bus_space_read_4(sc->sc_iot, sc->sc_ioh, SATA_P0PHYSR); if (!!(v & SATA_P0PHYSR_CR_ACK) == !!on) break; delay(100); } if (timeout > 0) return 0; return -1; } static int imx6_ahcisata_phy_addr(struct imx_ahci_softc *sc, uint32_t addr) { delay(100); bus_space_write_4(sc->sc_iot, sc->sc_ioh, SATA_P0PHYCR, addr); if (imx6_ahcisata_phy_ctrl(sc, SATA_P0PHYCR_CR_CAP_ADDR, 1) != 0) return -1; if (imx6_ahcisata_phy_ctrl(sc, SATA_P0PHYCR_CR_CAP_ADDR, 0) != 0) return -1; return 0; } static int imx6_ahcisata_phy_write(struct imx_ahci_softc *sc, uint32_t addr, uint16_t data) { if (imx6_ahcisata_phy_addr(sc, addr) != 0) return -1; bus_space_write_4(sc->sc_iot, sc->sc_ioh, SATA_P0PHYCR, data); if (imx6_ahcisata_phy_ctrl(sc, SATA_P0PHYCR_CR_CAP_DATA, 1) != 0) return -1; if (imx6_ahcisata_phy_ctrl(sc, SATA_P0PHYCR_CR_CAP_DATA, 0) != 0) return -1; if ((addr == SATA_PHY_CLOCK_RESET) && data) { /* we can't check ACK after RESET */ bus_space_write_4(sc->sc_iot, sc->sc_ioh, SATA_P0PHYCR, data | SATA_P0PHYCR_CR_WRITE); return 0; } if (imx6_ahcisata_phy_ctrl(sc, SATA_P0PHYCR_CR_WRITE, 1) != 0) return -1; if (imx6_ahcisata_phy_ctrl(sc, SATA_P0PHYCR_CR_WRITE, 0) != 0) return -1; return 0; } static int imx6_ahcisata_phy_read(struct imx_ahci_softc *sc, uint32_t addr) { uint32_t v; if (imx6_ahcisata_phy_addr(sc, addr) != 0) return -1; if (imx6_ahcisata_phy_ctrl(sc, SATA_P0PHYCR_CR_READ, 1) != 0) return -1; v = bus_space_read_4(sc->sc_iot, sc->sc_ioh, SATA_P0PHYSR); if (imx6_ahcisata_phy_ctrl(sc, SATA_P0PHYCR_CR_READ, 0) != 0) return -1; return SATA_P0PHYSR_CR_DATA_OUT(v); } static int imx6_ahcisata_init(struct imx_ahci_softc *sc) { uint32_t v; int timeout, pllstat; v = iomux_read(IOMUX_GPR13); /* clear */ v &= ~(IOMUX_GPR13_SATA_PHY_8 | IOMUX_GPR13_SATA_PHY_7 | IOMUX_GPR13_SATA_PHY_6 | IOMUX_GPR13_SATA_SPEED | IOMUX_GPR13_SATA_PHY_5 | IOMUX_GPR13_SATA_PHY_4 | IOMUX_GPR13_SATA_PHY_3 | IOMUX_GPR13_SATA_PHY_2 | IOMUX_GPR13_SATA_PHY_1 | IOMUX_GPR13_SATA_PHY_0); /* setting */ v |= __SHIFTIN(5, IOMUX_GPR13_SATA_PHY_8); /* Rx 3.0db */ v |= __SHIFTIN(0x12, IOMUX_GPR13_SATA_PHY_7); /* Rx SATA2m */ v |= __SHIFTIN(3, IOMUX_GPR13_SATA_PHY_6); /* Rx DPLL mode */ v |= __SHIFTIN(1, IOMUX_GPR13_SATA_SPEED); /* 3.0GHz */ v |= __SHIFTIN(0, IOMUX_GPR13_SATA_PHY_5); /* SpreadSpectram */ v |= __SHIFTIN(4, IOMUX_GPR13_SATA_PHY_4); /* Tx Attenuation 9/16 */ v |= __SHIFTIN(0, IOMUX_GPR13_SATA_PHY_3); /* Tx Boost 0db */ v |= __SHIFTIN(0x11, IOMUX_GPR13_SATA_PHY_2); /* Tx Level 1.104V */ v |= __SHIFTIN(1, IOMUX_GPR13_SATA_PHY_1); /* PLL clock enable */ iomux_write(IOMUX_GPR13, v); /* phy reset */ if (imx6_ahcisata_phy_write(sc, SATA_PHY_CLOCK_RESET, SATA_PHY_CLOCK_RESET_RST) < 0) { aprint_error_dev(sc->sc_dev, "cannot reset PHY\n"); return -1; } for (timeout = 50; timeout > 0; --timeout) { delay(100); pllstat = imx6_ahcisata_phy_read(sc, SATA_PHY_LANE0_OUT_STAT); if (pllstat < 0) { aprint_error_dev(sc->sc_dev, "cannot read LANE0 status\n"); break; } if (pllstat & SATA_PHY_LANE0_OUT_STAT_RX_PLL_STATE) break; } if (timeout <= 0) return -1; /* Support Staggered Spin-up */ v = bus_space_read_4(sc->sc_iot, sc->sc_ioh, SATA_CAP); bus_space_write_4(sc->sc_iot, sc->sc_ioh, SATA_CAP, v | SATA_CAP_SSS); /* Ports Implmented. must set 1 */ v = bus_space_read_4(sc->sc_iot, sc->sc_ioh, SATA_PI); bus_space_write_4(sc->sc_iot, sc->sc_ioh, SATA_PI, v | SATA_PI_PI); /* set 1ms-timer = AHB clock / 1000 */ bus_space_write_4(sc->sc_iot, sc->sc_ioh, SATA_TIMER1MS, clk_get_rate(sc->sc_clk_ahb) / 1000); return 0; } static int imx6_ahcisata_init_clocks(struct imx_ahci_softc *sc) { int error; error = clk_enable(sc->sc_clk_sata); if (error) { aprint_error_dev(sc->sc_dev, "couldn't enable sata: %d\n", error); return error; } error = clk_enable(sc->sc_clk_sata_ref); if (error) { aprint_error_dev(sc->sc_dev, "couldn't enable sata-ref: %d\n", error); return error; } error = clk_enable(sc->sc_clk_ahb); if (error) { aprint_error_dev(sc->sc_dev, "couldn't enable anb: %d\n", error); return error; } return 0; }