# HG changeset patch # Parent 918b1954335758a2228c04446da00cb6473554e8 Add AHCI attachment for Allwinner A10/A20 SoCs. The Allwinner SoC has an ahci device on its internal main bus rather than the PCI bus. This SoC is somewhat underdocumented, and its SATA controller is no exception. The methods to support this chip were harvested from the Linux Allwinner SDK, and then constants invented to describe what's going on based on low-level constants contained in the SATA standard and guess work. The AHCI driver isn't (yet) supported on Allwinner due to well known deficiencies in config. diff -r 918b19543357 sys/arm/allwinner/a10_ahci.c --- /dev/null +++ b/sys/arm/allwinner/a10_ahci.c @@ -0,0 +1,375 @@ +/*- + * Copyright (c) 2014 M. Warner Losh + * 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. + * + * The magic-bit-bang sequence used in this code may be based on a linux + * platform driver in the Allwinner SDK from Allwinner Technology Co., Ltd. + * www.allwinnertech.com, by Daniel Wang + * though none of the original code was copied. + */ + +#include "opt_bus.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include "gpio_if.h" + +/* + * Allwinner a1x/a2x/a8x SATA attachment. This is just the AHCI register + * set with a few extra implementation-specific registers that need to + * be accounted for. There's only one PHY in the system, and it needs + * to be trained to bring the link up. In addition, there's some DMA + * specific things that need to be done as well. These things are also + * just about completely undocumented, except in ugly code in the Linux + * SDK Allwinner releases. + */ + +/* BITx -- Unknown bit that needs to be set/cleared at position x */ +/* UFx -- Uknown multi-bit field frobbed during init */ +#define AHCI_BISTAFR 0x00A0 +#define AHCI_BISTCR 0x00A4 +#define AHCI_BISTFCTR 0x00A8 +#define AHCI_BISTSR 0x00AC +#define AHCI_BISTDECR 0x00B0 +#define AHCI_DIAGNR 0x00B4 +#define AHCI_DIAGNR1 0x00B8 +#define AHCI_OOBR 0x00BC +#define AHCI_PHYCS0R 0x00C0 +/* Bits 0..17 are a mystery */ +#define PHYCS0R_BIT18 (1 << 18) +#define PHYCS0R_POWER_ENABLE (1 << 19) +#define PHYCS0R_UF1_MASK (7 << 20) /* Unknown Field 1 */ +#define PHYCS0R_UF1_INIT (3 << 20) +#define PHYCS0R_BIT23 (1 << 23) +#define PHYCS0R_UF2_MASK (7 << 24) /* Uknown Field 2 */ +#define PHYCS0R_UF2_INIT (5 << 24) +/* Bit 27 mystery */ +#define PHYCS0R_POWER_STATUS_MASK (7 << 28) +#define PHYCS0R_PS_GOOD (2 << 28) +/* Bit 31 mystery */ +#define AHCI_PHYCS1R 0x00C4 +/* Bits 0..5 are a mystery */ +#define PHYCS1R_UF1_MASK (3 << 6) +#define PHYCS1R_UF1_INIT (2 << 6) +#define PHYCS1R_UF2_MASK (0x1f << 8) +#define PHYCS1R_UF2_INIT (6 << 8) +/* Bits 13..14 are a mystery */ +#define PHYCS1R_BIT15 (1 << 15) +#define PHYCS1R_UF3_MASK (3 << 16) +#define PHYCS1R_UF3_INIT (2 << 16) +/* Bit 18 mystery */ +#define PHYCS1R_HIGHZ (1 << 19) +/* Bits 20..27 mystery */ +#define PHYCS1R_BIT28 (1 << 28) +/* Bits 29..31 mystery */ +#define AHCI_PHYCS2R 0x00C8 +/* bits 0..4 mystery */ +#define PHYCS2R_UF1_MASK (0x1f << 5) +#define PHYCS2R_UF1_INIT (0x19 << 5) +/* Bits 10..23 mystery */ +#define PHYCS2R_CALIBRATE (1 << 24) +/* Bits 25..31 mystery */ +#define AHCI_TIMER1MS 0x00E0 +#define AHCI_GPARAM1R 0x00E8 +#define AHCI_GPARAM2R 0x00EC +#define AHCI_PPARAMR 0x00F0 +#define AHCI_TESTR 0x00F4 +#define AHCI_VERSIONR 0x00F8 +#define AHCI_IDR 0x00FC +#define AHCI_RWCR 0x00FC + +#define AHCI_P0DMACR 0x0170 +#define AHCI_P0PHYCR 0x0178 +#define AHCI_P0PHYSR 0x017C + +/* Kludge for CUBIEBOARD (and Banana PI too) */ +#define GPIO_AHCI_PWR 40 + +static void inline +ahci_set(struct resource *m, bus_size_t off, uint32_t set) +{ + uint32_t val = ATA_INL(m, off); + + val |= set; + ATA_OUTL(m, off, val); +} + +static void inline +ahci_clr(struct resource *m, bus_size_t off, uint32_t clr) +{ + uint32_t val = ATA_INL(m, off); + + val &= ~clr; + ATA_OUTL(m, off, val); +} + +static void inline +ahci_mask_set(struct resource *m, bus_size_t off, uint32_t mask, uint32_t set) +{ + uint32_t val = ATA_INL(m, off); + + val &= mask; + val |= set; + ATA_OUTL(m, off, val); +} + +/* + * Should this be phy_reset or phy_init + */ +#define PHY_RESET_TIMEOUT 0x100000 +static void +ahci_a10_phy_reset(device_t dev) +{ + uint32_t val; + uint32_t to; + struct ahci_controller *ctlr = device_get_softc(dev); + struct resource *m = ctlr->r_mem; + + /* + * Guess what a good delay here based on the loops in the + * Linux SDK. Nout even sure it is really needed. + */ + DELAY(100); + + /* + * Here starteth the magic -- most of the comments are based + * on guesswork, names of routines and printf error + * messages. The code works, but it will do that even if the + * comments are 100% BS. + */ + + /* + * Lock out other access while we initialize. Or at least that + * seems to be the case based on Linux SDK #defines. Maybe this + * put things into reset? + */ + ATA_OUTL(ctlr->r_mem, AHCI_RWCR, 0); + + /* + * Set bit 19 in PHYCS1R. Guessing this disables driving the PHY + * port for a bit while we reset things. + */ + ahci_set(m, AHCI_PHYCS1R, PHYCS1R_HIGHZ); + + /* + * Frob PHYCS0R... + */ + ahci_mask_set(m, AHCI_PHYCS0R, + ~PHYCS0R_UF2_MASK, + PHYCS0R_UF2_INIT | PHYCS0R_BIT23 | PHYCS0R_BIT18); + + /* + * Set three fields in PHYCS1R + */ + ahci_mask_set(m, AHCI_PHYCS1R, + ~(PHYCS1R_UF1_MASK | PHYCS1R_UF2_MASK | PHYCS1R_UF3_MASK), + PHYCS1R_UF1_INIT | PHYCS1R_UF2_INIT | PHYCS1R_UF3_INIT); + + /* + * Two more mystery bits in PHYCS1R. -- can these be combined above? + */ + ahci_set(m, AHCI_PHYCS1R, PHYCS1R_BIT15 | PHYCS1R_BIT28); + + /* + * Now clear that first mysery bit. Perhaps this starts + * driving the PHY again so we can power it up and start + * talking to the SATA drive, if any below. + */ + ahci_clr(m, AHCI_PHYCS1R, PHYCS1R_HIGHZ); + + /* + * Frob PHYCS0R again... + */ + ahci_mask_set(m, AHCI_PHYCS0R, + ~PHYCS0R_UF1_MASK, PHYCS0R_UF1_INIT); + + /* + * Frob PHYCS2R, because 25 means something? + */ + ahci_mask_set(m, AHCI_PHYCS2R, ~PHYCS2R_UF1_MASK, + PHYCS2R_UF1_INIT); + + DELAY(100); /* WAG */ + + /* + * Turn on the power to the PHY and wait for it to report back + * good? + */ + ahci_set(m, AHCI_PHYCS0R, PHYCS0R_POWER_ENABLE); + for (to = PHY_RESET_TIMEOUT; to > 0; to--) { + val = ATA_INL(m, AHCI_PHYCS0R); + if ((val & PHYCS0R_POWER_STATUS_MASK) == PHYCS0R_PS_GOOD) + break; + } + if (to == 0 && bootverbose) + device_printf(dev, "PHY Power Failed PHYCS0R = %#x\n", val); + + /* + * Calibrate the clocks between the device and the host. This appears + * to be an automated process that clears the bit when it is done. + */ + ahci_set(m, AHCI_PHYCS2R, PHYCS2R_CALIBRATE); + for (to = PHY_RESET_TIMEOUT; to > 0; to--) { + val = ATA_INL(m, AHCI_PHYCS2R); + if ((val & PHYCS2R_CALIBRATE) == 0) + break; + } + if (to == 0 && bootverbose) + device_printf(dev, "PHY Cal Failed PHYCS2R %#x\n", val); + + /* + * OK, let things settle down a bit. + */ + DELAY(100); + + /* + * Go back into normal mode now that we've calibrated the PHY. + */ + ATA_OUTL(ctlr->r_mem, AHCI_RWCR, 7); +} + +static int +ahci_a10_ctlr_reset(device_t dev) +{ + ahci_a10_phy_reset(dev); + + return ahci_ctlr_reset(dev); +} + +static int +ahci_a10_probe(device_t dev) +{ + + if (!ofw_bus_is_compatible(dev, "allwinner,sun4i-a10-ahci")) + return (ENXIO); + + device_set_desc(dev, "Allwinner Sun4i compatible AHCI controller."); + + return (BUS_PROBE_DEFAULT); +} + +static int +ahci_a10_attach(device_t dev) +{ + struct ahci_controller *ctlr = device_get_softc(dev); + int error; + device_t gpio; + + ctlr->quirks = 0; /* Be optimistic -- Linux turns off PMP */ + ctlr->vendorid = 0; + ctlr->deviceid = 0; + ctlr->subvendorid = 0; + ctlr->subdeviceid = 0; + ctlr->r_rid = 0; + if (!(ctlr->r_mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, + &ctlr->r_rid, RF_ACTIVE))) + return ENXIO; + + /* Turn on the PLL for SATA */ + a10_clk_ahci_activate(); + + /* Apply power to the drive, if any */ + gpio = devclass_get_device(devclass_find("gpio"), 0); + if (gpio == NULL) { + device_printf(dev, "GPIO device not yet present, punting (SATA won't work).\n"); + bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_rid, + ctlr->r_mem); + return ENXIO; + } + GPIO_PIN_SETFLAGS(gpio, GPIO_AHCI_PWR, GPIO_PIN_OUTPUT); + GPIO_PIN_SET(gpio, GPIO_AHCI_PWR, GPIO_PIN_HIGH); + + /* Reset controller */ + if ((error = ahci_a10_ctlr_reset(dev)) != 0) { + bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_rid, + ctlr->r_mem); + return (error); + }; + + /* + * No MSI registers on this platform. + */ + ctlr->msi = 0; + + /* + * Note: ahci_attach will release ctlr->r_mem on errors automatically + */ + return ahci_attach(dev); +} + +static int +ahci_a10_detach(device_t dev) +{ + + return ahci_detach(dev); +} + +static int +ahci_a10_suspend(device_t dev) +{ + + return ENXIO; +} + +static int +ahci_a10_resume(device_t dev) +{ + + return ENXIO; +} + +devclass_t ahci_devclass; +static device_method_t ahci_ata_methods[] = { + DEVMETHOD(device_probe, ahci_a10_probe), + DEVMETHOD(device_attach, ahci_a10_attach), + DEVMETHOD(device_detach, ahci_a10_detach), + DEVMETHOD(device_suspend, ahci_a10_suspend), + DEVMETHOD(device_resume, ahci_a10_resume), + DEVMETHOD(bus_print_child, ahci_print_child), + DEVMETHOD(bus_alloc_resource, ahci_alloc_resource), + DEVMETHOD(bus_release_resource, ahci_release_resource), + DEVMETHOD(bus_setup_intr, ahci_setup_intr), + DEVMETHOD(bus_teardown_intr,ahci_teardown_intr), + DEVMETHOD(bus_child_location_str, ahci_child_location_str), + { 0, 0 } +}; +static driver_t ahci_ata_driver = { + "ahci", + ahci_ata_methods, + sizeof(struct ahci_controller) +}; +DRIVER_MODULE(ahci, atapci, ahci_ata_driver, ahci_devclass, 0, 0); diff -r 918b19543357 sys/arm/allwinner/a10_clk.c --- a/sys/arm/allwinner/a10_clk.c +++ b/sys/arm/allwinner/a10_clk.c @@ -175,7 +175,8 @@ a10_clk_usb_deactivate(void) } int -a10_clk_emac_activate(void) { +a10_clk_emac_activate(void) +{ struct a10_ccm_softc *sc = a10_ccm_sc; uint32_t reg_value; @@ -190,3 +191,19 @@ a10_clk_emac_activate(void) { return (0); } +int +a10_clk_ahci_activate(void) +{ + struct a10_ccm_softc *sc = a10_ccm_sc; + uint32_t reg_value; + + if (sc == NULL) + return (ENXIO); + + /* Enable SATA clock on PLL6. */ + reg_value = ccm_read_4(sc, CCM_PLL6_CFG); + reg_value |= CCM_PLL6_CFG_SATA; + ccm_write_4(sc, CCM_PLL6_CFG, reg_value); + + return (0); +} diff -r 918b19543357 sys/arm/allwinner/a10_clk.h --- a/sys/arm/allwinner/a10_clk.h +++ b/sys/arm/allwinner/a10_clk.h @@ -100,6 +100,8 @@ #define CCM_HDMI_CLK 0x0150 #define CCM_MALI400_CLK 0x0154 +#define CCM_PLL6_CFG_SATA (1 << 14) + #define CCM_AHB_GATING_USB0 (1 << 0) #define CCM_AHB_GATING_EHCI0 (1 << 1) #define CCM_AHB_GATING_EHCI1 (1 << 3) @@ -113,5 +115,6 @@ int a10_clk_usb_activate(void); int a10_clk_usb_deactivate(void); int a10_clk_emac_activate(void); +int a10_clk_ahci_activate(void); #endif /* _A10_CLK_H_ */ diff -r 918b19543357 sys/arm/allwinner/a20/files.a20 --- a/sys/arm/allwinner/a20/files.a20 +++ b/sys/arm/allwinner/a20/files.a20 @@ -10,6 +10,7 @@ arm/arm/cpufunc_asm_armv7.S standard arm/arm/gic.c standard arm/allwinner/a20/a20_cpu_cfg.c standard +arm/allwinner/a10_ahci.c optional ahci arm/allwinner/a10_clk.c standard arm/allwinner/a10_sramc.c standard arm/allwinner/a10_gpio.c optional gpio diff -r 918b19543357 sys/arm/allwinner/files.a10 --- a/sys/arm/allwinner/files.a10 +++ b/sys/arm/allwinner/files.a10 @@ -8,6 +8,7 @@ arm/arm/cpufunc_asm_arm10.S standard arm/arm/cpufunc_asm_arm11.S standard arm/arm/cpufunc_asm_armv7.S standard +arm/allwinner/a10_ahci.c optional ahci arm/allwinner/a10_clk.c standard arm/allwinner/a10_common.c standard arm/allwinner/a10_gpio.c optional gpio diff -r 918b19543357 sys/arm/conf/CUBIEBOARD2 --- a/sys/arm/conf/CUBIEBOARD2 +++ b/sys/arm/conf/CUBIEBOARD2 @@ -90,8 +90,8 @@ options ROOTDEVNAME=\"ufs:/dev/da0s2\" #device mmc # mmc/sd bus #device mmcsd # mmc/sd flash cards -# ATA controllers -#device ahci # AHCI-compatible SATA controllers +# SATA/PATA controllers +device ahci # AHCI-compatible SATA controllers #device ata # Legacy ATA/SATA controllers #options ATA_STATIC_ID # Static device numbering @@ -112,7 +112,7 @@ device gpio device scbus # SCSI bus (required for ATA/SCSI) device da # Direct Access (disks) -device pass +device pass # Passthrough device (direct ATA/SCSI access) # USB support options USB_HOST_ALIGN=64 # Align usb buffers to cache line size.