Index: stable/12/sys/arm/allwinner/a10_ahci.c =================================================================== --- stable/12/sys/arm/allwinner/a10_ahci.c (revision 362349) +++ stable/12/sys/arm/allwinner/a10_ahci.c (revision 362350) @@ -1,418 +1,427 @@ /*- * Copyright (c) 2014-2015 M. Warner Losh * Copyright (c) 2015 Luiz Otavio O Souza * 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include /* * 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 0x0070 #define AHCI_P0PHYCR 0x0078 #define AHCI_P0PHYSR 0x007C #define PLL_FREQ 100000000 struct ahci_a10_softc { struct ahci_controller ahci_ctlr; regulator_t ahci_reg; + clk_t clk_pll; + clk_t clk_gate; }; 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 1000 static void ahci_a10_phy_reset(device_t dev) { uint32_t to, val; struct ahci_controller *ctlr = device_get_softc(dev); /* * Here starts 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); DELAY(100); /* * Set bit 19 in PHYCS1R. Guessing this disables driving the PHY * port for a bit while we reset things. */ ahci_set(ctlr->r_mem, AHCI_PHYCS1R, PHYCS1R_HIGHZ); /* * Frob PHYCS0R... */ ahci_mask_set(ctlr->r_mem, AHCI_PHYCS0R, ~PHYCS0R_UF2_MASK, PHYCS0R_UF2_INIT | PHYCS0R_BIT23 | PHYCS0R_BIT18); /* * Set three fields in PHYCS1R */ ahci_mask_set(ctlr->r_mem, 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(ctlr->r_mem, 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(ctlr->r_mem, AHCI_PHYCS1R, PHYCS1R_HIGHZ); /* * Frob PHYCS0R again... */ ahci_mask_set(ctlr->r_mem, AHCI_PHYCS0R, ~PHYCS0R_UF1_MASK, PHYCS0R_UF1_INIT); /* * Frob PHYCS2R, because 25 means something? */ ahci_mask_set(ctlr->r_mem, 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(ctlr->r_mem, AHCI_PHYCS0R, PHYCS0R_POWER_ENABLE); for (to = PHY_RESET_TIMEOUT; to > 0; to--) { val = ATA_INL(ctlr->r_mem, AHCI_PHYCS0R); if ((val & PHYCS0R_POWER_STATUS_MASK) == PHYCS0R_PS_GOOD) break; DELAY(10); } 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(ctlr->r_mem, AHCI_PHYCS2R, PHYCS2R_CALIBRATE); for (to = PHY_RESET_TIMEOUT; to > 0; to--) { val = ATA_INL(ctlr->r_mem, AHCI_PHYCS2R); if ((val & PHYCS2R_CALIBRATE) == 0) break; DELAY(10); } if (to == 0 && bootverbose) device_printf(dev, "PHY Cal Failed PHYCS2R %#x\n", val); /* * OK, let things settle down a bit. */ DELAY(1000); /* * Go back into normal mode now that we've calibrated the PHY. */ ATA_OUTL(ctlr->r_mem, AHCI_RWCR, 7); } static void ahci_a10_ch_start(struct ahci_channel *ch) { uint32_t reg; /* * Magical values from Allwinner SDK, setup the DMA before start * operations on this channel. */ reg = ATA_INL(ch->r_mem, AHCI_P0DMACR); reg &= ~0xff00; reg |= 0x4400; ATA_OUTL(ch->r_mem, AHCI_P0DMACR, reg); } 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 Integrated AHCI controller"); return (BUS_PROBE_DEFAULT); } static int ahci_a10_attach(device_t dev) { int error; struct ahci_a10_softc *sc; struct ahci_controller *ctlr; - clk_t clk_pll, clk_gate; sc = device_get_softc(dev); ctlr = &sc->ahci_ctlr; - clk_pll = clk_gate = NULL; ctlr->quirks = AHCI_Q_NOPMP; 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); - /* Enable the regulator */ - error = regulator_get_by_ofw_property(dev, 0, "target-supply", - &sc->ahci_reg); - if (error != 0) { - device_printf(dev, "Cannot get regulator\n"); - goto fail; + /* Enable the (optional) regulator */ + if (regulator_get_by_ofw_property(dev, 0, "target-supply", + &sc->ahci_reg) == 0) { + error = regulator_enable(sc->ahci_reg); + if (error != 0) { + device_printf(dev, "Could not enable regulator\n"); + goto fail; + } } - error = regulator_enable(sc->ahci_reg); - if (error != 0) { - device_printf(dev, "Could not enable regulator\n"); - goto fail; - } /* Enable clocks */ - error = clk_get_by_ofw_index(dev, 0, 0, &clk_gate); + error = clk_get_by_ofw_index(dev, 0, 0, &sc->clk_gate); if (error != 0) { device_printf(dev, "Cannot get gate clock\n"); goto fail; } - error = clk_get_by_ofw_index(dev, 0, 1, &clk_pll); + error = clk_get_by_ofw_index(dev, 0, 1, &sc->clk_pll); if (error != 0) { device_printf(dev, "Cannot get PLL clock\n"); goto fail; } - error = clk_set_freq(clk_pll, PLL_FREQ, CLK_SET_ROUND_DOWN); + error = clk_set_freq(sc->clk_pll, PLL_FREQ, CLK_SET_ROUND_DOWN); if (error != 0) { device_printf(dev, "Cannot set PLL frequency\n"); goto fail; } - error = clk_enable(clk_pll); + error = clk_enable(sc->clk_pll); if (error != 0) { device_printf(dev, "Cannot enable PLL\n"); goto fail; } - error = clk_enable(clk_gate); + error = clk_enable(sc->clk_gate); if (error != 0) { device_printf(dev, "Cannot enable clk gate\n"); goto fail; } /* Reset controller */ if ((error = ahci_a10_ctlr_reset(dev)) != 0) goto fail; /* * No MSI registers on this platform. */ ctlr->msi = 0; ctlr->numirqs = 1; /* Channel start callback(). */ ctlr->ch_start = ahci_a10_ch_start; /* * Note: ahci_attach will release ctlr->r_mem on errors automatically */ return (ahci_attach(dev)); fail: - if (sc->ahci_reg != 0) + if (sc->ahci_reg != NULL) regulator_disable(sc->ahci_reg); - if (clk_gate != NULL) - clk_release(clk_gate); - if (clk_pll != NULL) - clk_release(clk_pll); + if (sc->clk_gate != NULL) + clk_release(sc->clk_gate); + if (sc->clk_pll != NULL) + clk_release(sc->clk_pll); bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_rid, ctlr->r_mem); return (error); } static int ahci_a10_detach(device_t dev) { + struct ahci_a10_softc *sc; + struct ahci_controller *ctlr; + sc = device_get_softc(dev); + ctlr = &sc->ahci_ctlr; + + if (sc->ahci_reg != NULL) + regulator_disable(sc->ahci_reg); + if (sc->clk_gate != NULL) + clk_release(sc->clk_gate); + if (sc->clk_pll != NULL) + clk_release(sc->clk_pll); + bus_release_resource(dev, SYS_RES_MEMORY, ctlr->r_rid, ctlr->r_mem); return (ahci_detach(dev)); } 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(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), DEVMETHOD_END }; static driver_t ahci_ata_driver = { "ahci", ahci_ata_methods, sizeof(struct ahci_a10_softc) }; DRIVER_MODULE(a10_ahci, simplebus, ahci_ata_driver, ahci_devclass, 0, 0); Index: stable/12/sys/arm/allwinner/aw_gpio.c =================================================================== --- stable/12/sys/arm/allwinner/aw_gpio.c (revision 362349) +++ stable/12/sys/arm/allwinner/aw_gpio.c (revision 362350) @@ -1,1037 +1,1132 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2013 Ganbold Tsagaankhuu * Copyright (c) 2012 Oleksandr Tymoshenko * Copyright (c) 2012 Luiz Otavio O Souza. * 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. * */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #if defined(__aarch64__) #include "opt_soc.h" #endif #include "gpio_if.h" #define AW_GPIO_DEFAULT_CAPS (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | \ GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN) #define AW_GPIO_NONE 0 #define AW_GPIO_PULLUP 1 #define AW_GPIO_PULLDOWN 2 #define AW_GPIO_INPUT 0 #define AW_GPIO_OUTPUT 1 #define AW_GPIO_DRV_MASK 0x3 #define AW_GPIO_PUD_MASK 0x3 #define AW_PINCTRL 1 #define AW_R_PINCTRL 2 +struct aw_gpio_conf { + struct allwinner_padconf *padconf; + const char *banks; +}; + /* Defined in aw_padconf.c */ #ifdef SOC_ALLWINNER_A10 -extern const struct allwinner_padconf a10_padconf; +extern struct allwinner_padconf a10_padconf; +struct aw_gpio_conf a10_gpio_conf = { + .padconf = &a10_padconf, + .banks = "abcdefghi", +}; #endif /* Defined in a13_padconf.c */ #ifdef SOC_ALLWINNER_A13 -extern const struct allwinner_padconf a13_padconf; +extern struct allwinner_padconf a13_padconf; +struct aw_gpio_conf a13_gpio_conf = { + .padconf = &a13_padconf, + .banks = "bcdefg", +}; #endif /* Defined in a20_padconf.c */ #ifdef SOC_ALLWINNER_A20 -extern const struct allwinner_padconf a20_padconf; +extern struct allwinner_padconf a20_padconf; +struct aw_gpio_conf a20_gpio_conf = { + .padconf = &a20_padconf, + .banks = "abcdefghi", +}; #endif /* Defined in a31_padconf.c */ #ifdef SOC_ALLWINNER_A31 -extern const struct allwinner_padconf a31_padconf; +extern struct allwinner_padconf a31_padconf; +struct aw_gpio_conf a31_gpio_conf = { + .padconf = &a31_padconf, + .banks = "abcdefgh", +}; #endif /* Defined in a31s_padconf.c */ #ifdef SOC_ALLWINNER_A31S -extern const struct allwinner_padconf a31s_padconf; +extern struct allwinner_padconf a31s_padconf; +struct aw_gpio_conf a31s_gpio_conf = { + .padconf = &a31s_padconf, + .banks = "abcdefgh", +}; #endif #if defined(SOC_ALLWINNER_A31) || defined(SOC_ALLWINNER_A31S) -extern const struct allwinner_padconf a31_r_padconf; +extern struct allwinner_padconf a31_r_padconf; +struct aw_gpio_conf a31_r_gpio_conf = { + .padconf = &a31_r_padconf, + .banks = "lm", +}; #endif /* Defined in a33_padconf.c */ #ifdef SOC_ALLWINNER_A33 -extern const struct allwinner_padconf a33_padconf; +extern struct allwinner_padconf a33_padconf; +struct aw_gpio_conf a33_gpio_conf = { + .padconf = &a33_padconf, + .banks = "bcdefgh", +}; #endif /* Defined in h3_padconf.c */ #if defined(SOC_ALLWINNER_H3) || defined(SOC_ALLWINNER_H5) -extern const struct allwinner_padconf h3_padconf; -extern const struct allwinner_padconf h3_r_padconf; +extern struct allwinner_padconf h3_padconf; +extern struct allwinner_padconf h3_r_padconf; +struct aw_gpio_conf h3_gpio_conf = { + .padconf = &h3_padconf, + .banks = "acdefg", +}; +struct aw_gpio_conf h3_r_gpio_conf = { + .padconf = &h3_r_padconf, + .banks = "l", +}; #endif /* Defined in a83t_padconf.c */ #ifdef SOC_ALLWINNER_A83T -extern const struct allwinner_padconf a83t_padconf; -extern const struct allwinner_padconf a83t_r_padconf; +extern struct allwinner_padconf a83t_padconf; +extern struct allwinner_padconf a83t_r_padconf; +struct aw_gpio_conf a83t_gpio_conf = { + .padconf = &a83t_padconf, + .banks = "bcdefgh" +}; +struct aw_gpio_conf a83t_r_gpio_conf = { + .padconf = &a83t_r_padconf, + .banks = "l", +}; #endif /* Defined in a64_padconf.c */ #ifdef SOC_ALLWINNER_A64 -extern const struct allwinner_padconf a64_padconf; -extern const struct allwinner_padconf a64_r_padconf; +extern struct allwinner_padconf a64_padconf; +extern struct allwinner_padconf a64_r_padconf; +struct aw_gpio_conf a64_gpio_conf = { + .padconf = &a64_padconf, + .banks = "bcdefgh", +}; +struct aw_gpio_conf a64_r_gpio_conf = { + .padconf = &a64_r_padconf, + .banks = "l", +}; #endif /* Defined in h6_padconf.c */ #ifdef SOC_ALLWINNER_H6 -extern const struct allwinner_padconf h6_padconf; -extern const struct allwinner_padconf h6_r_padconf; +extern struct allwinner_padconf h6_padconf; +extern struct allwinner_padconf h6_r_padconf; +struct aw_gpio_conf h6_gpio_conf = { + .padconf = &h6_padconf, + .banks = "cdfgh", +}; +struct aw_gpio_conf h6_r_gpio_conf = { + .padconf = &h6_r_padconf, + .banks = "lm", +}; #endif static struct ofw_compat_data compat_data[] = { #ifdef SOC_ALLWINNER_A10 - {"allwinner,sun4i-a10-pinctrl", (uintptr_t)&a10_padconf}, + {"allwinner,sun4i-a10-pinctrl", (uintptr_t)&a10_gpio_conf}, #endif #ifdef SOC_ALLWINNER_A13 - {"allwinner,sun5i-a13-pinctrl", (uintptr_t)&a13_padconf}, + {"allwinner,sun5i-a13-pinctrl", (uintptr_t)&a13_gpio_conf}, #endif #ifdef SOC_ALLWINNER_A20 - {"allwinner,sun7i-a20-pinctrl", (uintptr_t)&a20_padconf}, + {"allwinner,sun7i-a20-pinctrl", (uintptr_t)&a20_gpio_conf}, #endif #ifdef SOC_ALLWINNER_A31 - {"allwinner,sun6i-a31-pinctrl", (uintptr_t)&a31_padconf}, + {"allwinner,sun6i-a31-pinctrl", (uintptr_t)&a31_gpio_conf}, #endif #ifdef SOC_ALLWINNER_A31S - {"allwinner,sun6i-a31s-pinctrl", (uintptr_t)&a31s_padconf}, + {"allwinner,sun6i-a31s-pinctrl", (uintptr_t)&a31s_gpio_conf}, #endif #if defined(SOC_ALLWINNER_A31) || defined(SOC_ALLWINNER_A31S) - {"allwinner,sun6i-a31-r-pinctrl", (uintptr_t)&a31_r_padconf}, + {"allwinner,sun6i-a31-r-pinctrl", (uintptr_t)&a31_r_gpio_conf}, #endif #ifdef SOC_ALLWINNER_A33 - {"allwinner,sun6i-a33-pinctrl", (uintptr_t)&a33_padconf}, + {"allwinner,sun6i-a33-pinctrl", (uintptr_t)&a33_gpio_conf}, #endif #ifdef SOC_ALLWINNER_A83T - {"allwinner,sun8i-a83t-pinctrl", (uintptr_t)&a83t_padconf}, - {"allwinner,sun8i-a83t-r-pinctrl", (uintptr_t)&a83t_r_padconf}, + {"allwinner,sun8i-a83t-pinctrl", (uintptr_t)&a83t_gpio_conf}, + {"allwinner,sun8i-a83t-r-pinctrl", (uintptr_t)&a83t_r_gpio_conf}, #endif #if defined(SOC_ALLWINNER_H3) || defined(SOC_ALLWINNER_H5) - {"allwinner,sun8i-h3-pinctrl", (uintptr_t)&h3_padconf}, - {"allwinner,sun50i-h5-pinctrl", (uintptr_t)&h3_padconf}, - {"allwinner,sun8i-h3-r-pinctrl", (uintptr_t)&h3_r_padconf}, + {"allwinner,sun8i-h3-pinctrl", (uintptr_t)&h3_gpio_conf}, + {"allwinner,sun50i-h5-pinctrl", (uintptr_t)&h3_gpio_conf}, + {"allwinner,sun8i-h3-r-pinctrl", (uintptr_t)&h3_r_gpio_conf}, #endif #ifdef SOC_ALLWINNER_A64 - {"allwinner,sun50i-a64-pinctrl", (uintptr_t)&a64_padconf}, - {"allwinner,sun50i-a64-r-pinctrl", (uintptr_t)&a64_r_padconf}, + {"allwinner,sun50i-a64-pinctrl", (uintptr_t)&a64_gpio_conf}, + {"allwinner,sun50i-a64-r-pinctrl", (uintptr_t)&a64_r_gpio_conf}, #endif #ifdef SOC_ALLWINNER_H6 - {"allwinner,sun50i-h6-pinctrl", (uintptr_t)&h6_padconf}, - {"allwinner,sun50i-h6-r-pinctrl", (uintptr_t)&h6_r_padconf}, + {"allwinner,sun50i-h6-pinctrl", (uintptr_t)&h6_gpio_conf}, + {"allwinner,sun50i-h6-r-pinctrl", (uintptr_t)&h6_r_gpio_conf}, #endif {NULL, 0} }; struct clk_list { TAILQ_ENTRY(clk_list) next; clk_t clk; }; struct aw_gpio_softc { device_t sc_dev; device_t sc_busdev; struct mtx sc_mtx; struct resource * sc_mem_res; struct resource * sc_irq_res; bus_space_tag_t sc_bst; bus_space_handle_t sc_bsh; void * sc_intrhand; - const struct allwinner_padconf * padconf; + struct aw_gpio_conf *conf; TAILQ_HEAD(, clk_list) clk_list; }; #define AW_GPIO_LOCK(_sc) mtx_lock_spin(&(_sc)->sc_mtx) #define AW_GPIO_UNLOCK(_sc) mtx_unlock_spin(&(_sc)->sc_mtx) #define AW_GPIO_LOCK_ASSERT(_sc) mtx_assert(&(_sc)->sc_mtx, MA_OWNED) #define AW_GPIO_GP_CFG(_bank, _idx) 0x00 + ((_bank) * 0x24) + ((_idx) << 2) #define AW_GPIO_GP_DAT(_bank) 0x10 + ((_bank) * 0x24) #define AW_GPIO_GP_DRV(_bank, _idx) 0x14 + ((_bank) * 0x24) + ((_idx) << 2) #define AW_GPIO_GP_PUL(_bank, _idx) 0x1c + ((_bank) * 0x24) + ((_idx) << 2) #define AW_GPIO_GP_INT_CFG0 0x200 #define AW_GPIO_GP_INT_CFG1 0x204 #define AW_GPIO_GP_INT_CFG2 0x208 #define AW_GPIO_GP_INT_CFG3 0x20c #define AW_GPIO_GP_INT_CTL 0x210 #define AW_GPIO_GP_INT_STA 0x214 #define AW_GPIO_GP_INT_DEB 0x218 static char *aw_gpio_parse_function(phandle_t node); static const char **aw_gpio_parse_pins(phandle_t node, int *pins_nb); static uint32_t aw_gpio_parse_bias(phandle_t node); static int aw_gpio_parse_drive_strength(phandle_t node, uint32_t *drive); static int aw_gpio_pin_get(device_t dev, uint32_t pin, unsigned int *value); static int aw_gpio_pin_set(device_t dev, uint32_t pin, unsigned int value); static int aw_gpio_pin_get_locked(struct aw_gpio_softc *sc, uint32_t pin, unsigned int *value); static int aw_gpio_pin_set_locked(struct aw_gpio_softc *sc, uint32_t pin, unsigned int value); #define AW_GPIO_WRITE(_sc, _off, _val) \ bus_space_write_4(_sc->sc_bst, _sc->sc_bsh, _off, _val) #define AW_GPIO_READ(_sc, _off) \ bus_space_read_4(_sc->sc_bst, _sc->sc_bsh, _off) static uint32_t aw_gpio_get_function(struct aw_gpio_softc *sc, uint32_t pin) { uint32_t bank, func, offset; /* Must be called with lock held. */ AW_GPIO_LOCK_ASSERT(sc); - if (pin > sc->padconf->npins) + if (pin > sc->conf->padconf->npins) return (0); - bank = sc->padconf->pins[pin].port; - pin = sc->padconf->pins[pin].pin; + bank = sc->conf->padconf->pins[pin].port; + pin = sc->conf->padconf->pins[pin].pin; offset = ((pin & 0x07) << 2); func = AW_GPIO_READ(sc, AW_GPIO_GP_CFG(bank, pin >> 3)); return ((func >> offset) & 0x7); } static int aw_gpio_set_function(struct aw_gpio_softc *sc, uint32_t pin, uint32_t f) { uint32_t bank, data, offset; /* Check if the function exists in the padconf data */ - if (sc->padconf->pins[pin].functions[f] == NULL) + if (sc->conf->padconf->pins[pin].functions[f] == NULL) return (EINVAL); /* Must be called with lock held. */ AW_GPIO_LOCK_ASSERT(sc); - bank = sc->padconf->pins[pin].port; - pin = sc->padconf->pins[pin].pin; + bank = sc->conf->padconf->pins[pin].port; + pin = sc->conf->padconf->pins[pin].pin; offset = ((pin & 0x07) << 2); data = AW_GPIO_READ(sc, AW_GPIO_GP_CFG(bank, pin >> 3)); data &= ~(7 << offset); data |= (f << offset); AW_GPIO_WRITE(sc, AW_GPIO_GP_CFG(bank, pin >> 3), data); return (0); } static uint32_t aw_gpio_get_pud(struct aw_gpio_softc *sc, uint32_t pin) { uint32_t bank, offset, val; /* Must be called with lock held. */ AW_GPIO_LOCK_ASSERT(sc); - bank = sc->padconf->pins[pin].port; - pin = sc->padconf->pins[pin].pin; + bank = sc->conf->padconf->pins[pin].port; + pin = sc->conf->padconf->pins[pin].pin; offset = ((pin & 0x0f) << 1); val = AW_GPIO_READ(sc, AW_GPIO_GP_PUL(bank, pin >> 4)); return ((val >> offset) & AW_GPIO_PUD_MASK); } static void aw_gpio_set_pud(struct aw_gpio_softc *sc, uint32_t pin, uint32_t state) { uint32_t bank, offset, val; if (aw_gpio_get_pud(sc, pin) == state) return; /* Must be called with lock held. */ AW_GPIO_LOCK_ASSERT(sc); - bank = sc->padconf->pins[pin].port; - pin = sc->padconf->pins[pin].pin; + bank = sc->conf->padconf->pins[pin].port; + pin = sc->conf->padconf->pins[pin].pin; offset = ((pin & 0x0f) << 1); val = AW_GPIO_READ(sc, AW_GPIO_GP_PUL(bank, pin >> 4)); val &= ~(AW_GPIO_PUD_MASK << offset); val |= (state << offset); AW_GPIO_WRITE(sc, AW_GPIO_GP_PUL(bank, pin >> 4), val); } static uint32_t aw_gpio_get_drv(struct aw_gpio_softc *sc, uint32_t pin) { uint32_t bank, offset, val; /* Must be called with lock held. */ AW_GPIO_LOCK_ASSERT(sc); - bank = sc->padconf->pins[pin].port; - pin = sc->padconf->pins[pin].pin; + bank = sc->conf->padconf->pins[pin].port; + pin = sc->conf->padconf->pins[pin].pin; offset = ((pin & 0x0f) << 1); val = AW_GPIO_READ(sc, AW_GPIO_GP_DRV(bank, pin >> 4)); return ((val >> offset) & AW_GPIO_DRV_MASK); } static void aw_gpio_set_drv(struct aw_gpio_softc *sc, uint32_t pin, uint32_t drive) { uint32_t bank, offset, val; if (aw_gpio_get_drv(sc, pin) == drive) return; /* Must be called with lock held. */ AW_GPIO_LOCK_ASSERT(sc); - bank = sc->padconf->pins[pin].port; - pin = sc->padconf->pins[pin].pin; + bank = sc->conf->padconf->pins[pin].port; + pin = sc->conf->padconf->pins[pin].pin; offset = ((pin & 0x0f) << 1); val = AW_GPIO_READ(sc, AW_GPIO_GP_DRV(bank, pin >> 4)); val &= ~(AW_GPIO_DRV_MASK << offset); val |= (drive << offset); AW_GPIO_WRITE(sc, AW_GPIO_GP_DRV(bank, pin >> 4), val); } static int aw_gpio_pin_configure(struct aw_gpio_softc *sc, uint32_t pin, uint32_t flags) { u_int val; int err = 0; /* Must be called with lock held. */ AW_GPIO_LOCK_ASSERT(sc); - if (pin > sc->padconf->npins) + if (pin > sc->conf->padconf->npins) return (EINVAL); /* Manage input/output. */ if (flags & GPIO_PIN_INPUT) { err = aw_gpio_set_function(sc, pin, AW_GPIO_INPUT); } else if ((flags & GPIO_PIN_OUTPUT) && aw_gpio_get_function(sc, pin) != AW_GPIO_OUTPUT) { if (flags & GPIO_PIN_PRESET_LOW) { aw_gpio_pin_set_locked(sc, pin, 0); } else if (flags & GPIO_PIN_PRESET_HIGH) { aw_gpio_pin_set_locked(sc, pin, 1); } else { /* Read the pin and preset output to current state. */ err = aw_gpio_set_function(sc, pin, AW_GPIO_INPUT); if (err == 0) { aw_gpio_pin_get_locked(sc, pin, &val); aw_gpio_pin_set_locked(sc, pin, val); } } if (err == 0) err = aw_gpio_set_function(sc, pin, AW_GPIO_OUTPUT); } if (err) return (err); /* Manage Pull-up/pull-down. */ if (flags & GPIO_PIN_PULLUP) aw_gpio_set_pud(sc, pin, AW_GPIO_PULLUP); else if (flags & GPIO_PIN_PULLDOWN) aw_gpio_set_pud(sc, pin, AW_GPIO_PULLDOWN); else aw_gpio_set_pud(sc, pin, AW_GPIO_NONE); return (0); } static device_t aw_gpio_get_bus(device_t dev) { struct aw_gpio_softc *sc; sc = device_get_softc(dev); return (sc->sc_busdev); } static int aw_gpio_pin_max(device_t dev, int *maxpin) { struct aw_gpio_softc *sc; sc = device_get_softc(dev); - *maxpin = sc->padconf->npins - 1; + *maxpin = sc->conf->padconf->npins - 1; return (0); } static int aw_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) { struct aw_gpio_softc *sc; sc = device_get_softc(dev); - if (pin >= sc->padconf->npins) + if (pin >= sc->conf->padconf->npins) return (EINVAL); *caps = AW_GPIO_DEFAULT_CAPS; return (0); } static int aw_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags) { struct aw_gpio_softc *sc; uint32_t func; uint32_t pud; sc = device_get_softc(dev); - if (pin >= sc->padconf->npins) + if (pin >= sc->conf->padconf->npins) return (EINVAL); AW_GPIO_LOCK(sc); func = aw_gpio_get_function(sc, pin); switch (func) { case AW_GPIO_INPUT: *flags = GPIO_PIN_INPUT; break; case AW_GPIO_OUTPUT: *flags = GPIO_PIN_OUTPUT; break; default: *flags = 0; break; } pud = aw_gpio_get_pud(sc, pin); switch (pud) { case AW_GPIO_PULLDOWN: *flags |= GPIO_PIN_PULLDOWN; break; case AW_GPIO_PULLUP: *flags |= GPIO_PIN_PULLUP; break; default: break; } AW_GPIO_UNLOCK(sc); return (0); } static int aw_gpio_pin_getname(device_t dev, uint32_t pin, char *name) { struct aw_gpio_softc *sc; sc = device_get_softc(dev); - if (pin >= sc->padconf->npins) + if (pin >= sc->conf->padconf->npins) return (EINVAL); snprintf(name, GPIOMAXNAME - 1, "%s", - sc->padconf->pins[pin].name); + sc->conf->padconf->pins[pin].name); name[GPIOMAXNAME - 1] = '\0'; return (0); } static int aw_gpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) { struct aw_gpio_softc *sc; int err; sc = device_get_softc(dev); - if (pin > sc->padconf->npins) + if (pin > sc->conf->padconf->npins) return (EINVAL); AW_GPIO_LOCK(sc); err = aw_gpio_pin_configure(sc, pin, flags); AW_GPIO_UNLOCK(sc); return (err); } static int aw_gpio_pin_set_locked(struct aw_gpio_softc *sc, uint32_t pin, unsigned int value) { uint32_t bank, data; AW_GPIO_LOCK_ASSERT(sc); - if (pin > sc->padconf->npins) + if (pin > sc->conf->padconf->npins) return (EINVAL); - bank = sc->padconf->pins[pin].port; - pin = sc->padconf->pins[pin].pin; + bank = sc->conf->padconf->pins[pin].port; + pin = sc->conf->padconf->pins[pin].pin; data = AW_GPIO_READ(sc, AW_GPIO_GP_DAT(bank)); if (value) data |= (1 << pin); else data &= ~(1 << pin); AW_GPIO_WRITE(sc, AW_GPIO_GP_DAT(bank), data); return (0); } static int aw_gpio_pin_set(device_t dev, uint32_t pin, unsigned int value) { struct aw_gpio_softc *sc; int ret; sc = device_get_softc(dev); AW_GPIO_LOCK(sc); ret = aw_gpio_pin_set_locked(sc, pin, value); AW_GPIO_UNLOCK(sc); return (ret); } static int aw_gpio_pin_get_locked(struct aw_gpio_softc *sc,uint32_t pin, unsigned int *val) { uint32_t bank, reg_data; AW_GPIO_LOCK_ASSERT(sc); - if (pin > sc->padconf->npins) + if (pin > sc->conf->padconf->npins) return (EINVAL); - bank = sc->padconf->pins[pin].port; - pin = sc->padconf->pins[pin].pin; + bank = sc->conf->padconf->pins[pin].port; + pin = sc->conf->padconf->pins[pin].pin; reg_data = AW_GPIO_READ(sc, AW_GPIO_GP_DAT(bank)); *val = (reg_data & (1 << pin)) ? 1 : 0; return (0); } static char * aw_gpio_parse_function(phandle_t node) { char *function; if (OF_getprop_alloc(node, "function", (void **)&function) != -1) return (function); if (OF_getprop_alloc(node, "allwinner,function", (void **)&function) != -1) return (function); return (NULL); } static const char ** aw_gpio_parse_pins(phandle_t node, int *pins_nb) { const char **pinlist; *pins_nb = ofw_bus_string_list_to_array(node, "pins", &pinlist); if (*pins_nb > 0) return (pinlist); *pins_nb = ofw_bus_string_list_to_array(node, "allwinner,pins", &pinlist); if (*pins_nb > 0) return (pinlist); return (NULL); } static uint32_t aw_gpio_parse_bias(phandle_t node) { uint32_t bias; if (OF_getencprop(node, "pull", &bias, sizeof(bias)) != -1) return (bias); if (OF_getencprop(node, "allwinner,pull", &bias, sizeof(bias)) != -1) return (bias); if (OF_hasprop(node, "bias-disable")) return (AW_GPIO_NONE); if (OF_hasprop(node, "bias-pull-up")) return (AW_GPIO_PULLUP); if (OF_hasprop(node, "bias-pull-down")) return (AW_GPIO_PULLDOWN); return (AW_GPIO_NONE); } static int aw_gpio_parse_drive_strength(phandle_t node, uint32_t *drive) { uint32_t drive_str; if (OF_getencprop(node, "drive", drive, sizeof(*drive)) != -1) return (0); if (OF_getencprop(node, "allwinner,drive", drive, sizeof(*drive)) != -1) return (0); if (OF_getencprop(node, "drive-strength", &drive_str, sizeof(drive_str)) != -1) { *drive = (drive_str / 10) - 1; return (0); } return (1); } static int aw_gpio_pin_get(device_t dev, uint32_t pin, unsigned int *val) { struct aw_gpio_softc *sc; int ret; sc = device_get_softc(dev); AW_GPIO_LOCK(sc); ret = aw_gpio_pin_get_locked(sc, pin, val); AW_GPIO_UNLOCK(sc); return (ret); } static int aw_gpio_pin_toggle(device_t dev, uint32_t pin) { struct aw_gpio_softc *sc; uint32_t bank, data; sc = device_get_softc(dev); - if (pin > sc->padconf->npins) + if (pin > sc->conf->padconf->npins) return (EINVAL); - bank = sc->padconf->pins[pin].port; - pin = sc->padconf->pins[pin].pin; + bank = sc->conf->padconf->pins[pin].port; + pin = sc->conf->padconf->pins[pin].pin; AW_GPIO_LOCK(sc); data = AW_GPIO_READ(sc, AW_GPIO_GP_DAT(bank)); if (data & (1 << pin)) data &= ~(1 << pin); else data |= (1 << pin); AW_GPIO_WRITE(sc, AW_GPIO_GP_DAT(bank), data); AW_GPIO_UNLOCK(sc); return (0); } static int aw_gpio_pin_access_32(device_t dev, uint32_t first_pin, uint32_t clear_pins, uint32_t change_pins, uint32_t *orig_pins) { struct aw_gpio_softc *sc; uint32_t bank, data, pin; sc = device_get_softc(dev); - if (first_pin > sc->padconf->npins) + if (first_pin > sc->conf->padconf->npins) return (EINVAL); /* * We require that first_pin refers to the first pin in a bank, because * this API is not about convenience, it's for making a set of pins * change simultaneously (required) with reasonably high performance * (desired); we need to do a read-modify-write on a single register. */ - bank = sc->padconf->pins[first_pin].port; - pin = sc->padconf->pins[first_pin].pin; + bank = sc->conf->padconf->pins[first_pin].port; + pin = sc->conf->padconf->pins[first_pin].pin; if (pin != 0) return (EINVAL); AW_GPIO_LOCK(sc); data = AW_GPIO_READ(sc, AW_GPIO_GP_DAT(bank)); if ((clear_pins | change_pins) != 0) AW_GPIO_WRITE(sc, AW_GPIO_GP_DAT(bank), (data & ~clear_pins) ^ change_pins); AW_GPIO_UNLOCK(sc); if (orig_pins != NULL) *orig_pins = data; return (0); } static int aw_gpio_pin_config_32(device_t dev, uint32_t first_pin, uint32_t num_pins, uint32_t *pin_flags) { struct aw_gpio_softc *sc; uint32_t bank, pin; int err; sc = device_get_softc(dev); - if (first_pin > sc->padconf->npins) + if (first_pin > sc->conf->padconf->npins) return (EINVAL); - bank = sc->padconf->pins[first_pin].port; - if (sc->padconf->pins[first_pin].pin != 0) + bank = sc->conf->padconf->pins[first_pin].port; + if (sc->conf->padconf->pins[first_pin].pin != 0) return (EINVAL); /* * The configuration for a bank of pins is scattered among several * registers; we cannot g'tee to simultaneously change the state of all * the pins in the flags array. So just loop through the array * configuring each pin for now. If there was a strong need, it might * be possible to support some limited simultaneous config, such as * adjacent groups of 8 pins that line up the same as the config regs. */ for (err = 0, pin = first_pin; err == 0 && pin < num_pins; ++pin) { if (pin_flags[pin] & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) err = aw_gpio_pin_configure(sc, pin, pin_flags[pin]); } return (err); } static int aw_find_pinnum_by_name(struct aw_gpio_softc *sc, const char *pinname) { int i; - for (i = 0; i < sc->padconf->npins; i++) - if (!strcmp(pinname, sc->padconf->pins[i].name)) + for (i = 0; i < sc->conf->padconf->npins; i++) + if (!strcmp(pinname, sc->conf->padconf->pins[i].name)) return i; return (-1); } static int aw_find_pin_func(struct aw_gpio_softc *sc, int pin, const char *func) { int i; for (i = 0; i < AW_MAX_FUNC_BY_PIN; i++) - if (sc->padconf->pins[pin].functions[i] && - !strcmp(func, sc->padconf->pins[pin].functions[i])) + if (sc->conf->padconf->pins[pin].functions[i] && + !strcmp(func, sc->conf->padconf->pins[pin].functions[i])) return (i); return (-1); } static int aw_fdt_configure_pins(device_t dev, phandle_t cfgxref) { struct aw_gpio_softc *sc; phandle_t node; const char **pinlist = NULL; char *pin_function = NULL; uint32_t pin_drive, pin_pull; int pins_nb, pin_num, pin_func, i, ret; bool set_drive; sc = device_get_softc(dev); node = OF_node_from_xref(cfgxref); ret = 0; set_drive = false; /* Getting all prop for configuring pins */ pinlist = aw_gpio_parse_pins(node, &pins_nb); if (pinlist == NULL) return (ENOENT); pin_function = aw_gpio_parse_function(node); if (pin_function == NULL) { ret = ENOENT; goto out; } if (aw_gpio_parse_drive_strength(node, &pin_drive) == 0) set_drive = true; pin_pull = aw_gpio_parse_bias(node); /* Configure each pin to the correct function, drive and pull */ for (i = 0; i < pins_nb; i++) { pin_num = aw_find_pinnum_by_name(sc, pinlist[i]); if (pin_num == -1) { ret = ENOENT; goto out; } pin_func = aw_find_pin_func(sc, pin_num, pin_function); if (pin_func == -1) { ret = ENOENT; goto out; } AW_GPIO_LOCK(sc); if (aw_gpio_get_function(sc, pin_num) != pin_func) aw_gpio_set_function(sc, pin_num, pin_func); if (set_drive) aw_gpio_set_drv(sc, pin_num, pin_drive); if (pin_pull != AW_GPIO_NONE) aw_gpio_set_pud(sc, pin_num, pin_pull); AW_GPIO_UNLOCK(sc); } out: OF_prop_free(pinlist); OF_prop_free(pin_function); return (ret); } +static void +aw_gpio_enable_bank_supply(void *arg) +{ + struct aw_gpio_softc *sc = arg; + regulator_t vcc_supply; + char bank_reg_name[16]; + int i, nbanks; + + nbanks = strlen(sc->conf->banks); + for (i = 0; i < nbanks; i++) { + snprintf(bank_reg_name, sizeof(bank_reg_name), "vcc-p%c-supply", + sc->conf->banks[i]); + + if (regulator_get_by_ofw_property(sc->sc_dev, 0, bank_reg_name, &vcc_supply) == 0) { + if (bootverbose) + device_printf(sc->sc_dev, + "Enabling regulator for gpio bank %c\n", + sc->conf->banks[i]); + if (regulator_enable(vcc_supply) != 0) { + device_printf(sc->sc_dev, + "Cannot enable regulator for bank %c\n", + sc->conf->banks[i]); + } + } + } +} + static int aw_gpio_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "Allwinner GPIO/Pinmux controller"); return (BUS_PROBE_DEFAULT); } static int aw_gpio_attach(device_t dev) { int rid, error; phandle_t gpio; struct aw_gpio_softc *sc; struct clk_list *clkp, *clkp_tmp; clk_t clk; hwreset_t rst = NULL; int off, err, clkret; sc = device_get_softc(dev); sc->sc_dev = dev; mtx_init(&sc->sc_mtx, "aw gpio", "gpio", MTX_SPIN); rid = 0; sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (!sc->sc_mem_res) { device_printf(dev, "cannot allocate memory window\n"); goto fail; } sc->sc_bst = rman_get_bustag(sc->sc_mem_res); sc->sc_bsh = rman_get_bushandle(sc->sc_mem_res); rid = 0; sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (!sc->sc_irq_res) { device_printf(dev, "cannot allocate interrupt\n"); goto fail; } /* Find our node. */ gpio = ofw_bus_get_node(sc->sc_dev); if (!OF_hasprop(gpio, "gpio-controller")) /* Node is not a GPIO controller. */ goto fail; /* Use the right pin data for the current SoC */ - sc->padconf = (struct allwinner_padconf *)ofw_bus_search_compatible(dev, + sc->conf = (struct aw_gpio_conf *)ofw_bus_search_compatible(dev, compat_data)->ocd_data; if (hwreset_get_by_ofw_idx(dev, 0, 0, &rst) == 0) { error = hwreset_deassert(rst); if (error != 0) { device_printf(dev, "cannot de-assert reset\n"); goto fail; } } TAILQ_INIT(&sc->clk_list); for (off = 0, clkret = 0; clkret == 0; off++) { clkret = clk_get_by_ofw_index(dev, 0, off, &clk); if (clkret != 0) break; err = clk_enable(clk); if (err != 0) { device_printf(dev, "Could not enable clock %s\n", clk_get_name(clk)); goto fail; } clkp = malloc(sizeof(*clkp), M_DEVBUF, M_WAITOK | M_ZERO); clkp->clk = clk; TAILQ_INSERT_TAIL(&sc->clk_list, clkp, next); } if (clkret != 0 && clkret != ENOENT) { device_printf(dev, "Could not find clock at offset %d (%d)\n", off, clkret); goto fail; } sc->sc_busdev = gpiobus_attach_bus(dev); if (sc->sc_busdev == NULL) goto fail; /* * Register as a pinctrl device */ fdt_pinctrl_register(dev, "pins"); fdt_pinctrl_configure_tree(dev); fdt_pinctrl_register(dev, "allwinner,pins"); fdt_pinctrl_configure_tree(dev); + config_intrhook_oneshot(aw_gpio_enable_bank_supply, sc); + return (0); fail: if (sc->sc_irq_res) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res); if (sc->sc_mem_res) bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res); mtx_destroy(&sc->sc_mtx); /* Disable clock */ TAILQ_FOREACH_SAFE(clkp, &sc->clk_list, next, clkp_tmp) { err = clk_disable(clkp->clk); if (err != 0) device_printf(dev, "Could not disable clock %s\n", clk_get_name(clkp->clk)); err = clk_release(clkp->clk); if (err != 0) device_printf(dev, "Could not release clock %s\n", clk_get_name(clkp->clk)); TAILQ_REMOVE(&sc->clk_list, clkp, next); free(clkp, M_DEVBUF); } /* Assert resets */ if (rst) { hwreset_assert(rst); hwreset_release(rst); } return (ENXIO); } static int aw_gpio_detach(device_t dev) { return (EBUSY); } static phandle_t aw_gpio_get_node(device_t dev, device_t bus) { /* We only have one child, the GPIO bus, which needs our own node. */ return (ofw_bus_get_node(dev)); } static int aw_gpio_map_gpios(device_t bus, phandle_t dev, phandle_t gparent, int gcells, pcell_t *gpios, uint32_t *pin, uint32_t *flags) { struct aw_gpio_softc *sc; int i; sc = device_get_softc(bus); /* The GPIO pins are mapped as: . */ - for (i = 0; i < sc->padconf->npins; i++) - if (sc->padconf->pins[i].port == gpios[0] && - sc->padconf->pins[i].pin == gpios[1]) { + for (i = 0; i < sc->conf->padconf->npins; i++) + if (sc->conf->padconf->pins[i].port == gpios[0] && + sc->conf->padconf->pins[i].pin == gpios[1]) { *pin = i; break; } *flags = gpios[gcells - 1]; return (0); } static device_method_t aw_gpio_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aw_gpio_probe), DEVMETHOD(device_attach, aw_gpio_attach), DEVMETHOD(device_detach, aw_gpio_detach), /* GPIO protocol */ DEVMETHOD(gpio_get_bus, aw_gpio_get_bus), DEVMETHOD(gpio_pin_max, aw_gpio_pin_max), DEVMETHOD(gpio_pin_getname, aw_gpio_pin_getname), DEVMETHOD(gpio_pin_getflags, aw_gpio_pin_getflags), DEVMETHOD(gpio_pin_getcaps, aw_gpio_pin_getcaps), DEVMETHOD(gpio_pin_setflags, aw_gpio_pin_setflags), DEVMETHOD(gpio_pin_get, aw_gpio_pin_get), DEVMETHOD(gpio_pin_set, aw_gpio_pin_set), DEVMETHOD(gpio_pin_toggle, aw_gpio_pin_toggle), DEVMETHOD(gpio_pin_access_32, aw_gpio_pin_access_32), DEVMETHOD(gpio_pin_config_32, aw_gpio_pin_config_32), DEVMETHOD(gpio_map_gpios, aw_gpio_map_gpios), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_node, aw_gpio_get_node), /* fdt_pinctrl interface */ DEVMETHOD(fdt_pinctrl_configure,aw_fdt_configure_pins), DEVMETHOD_END }; static devclass_t aw_gpio_devclass; static driver_t aw_gpio_driver = { "gpio", aw_gpio_methods, sizeof(struct aw_gpio_softc), }; EARLY_DRIVER_MODULE(aw_gpio, simplebus, aw_gpio_driver, aw_gpio_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE); Index: stable/12/sys/arm/allwinner/aw_spi.c =================================================================== --- stable/12/sys/arm/allwinner/aw_spi.c (revision 362349) +++ stable/12/sys/arm/allwinner/aw_spi.c (revision 362350) @@ -1,605 +1,605 @@ /*- * Copyright (c) 2018 Emmanuel Vadot * * 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. * * $FreeBSD$ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "spibus_if.h" #define AW_SPI_GCR 0x04 /* Global Control Register */ #define AW_SPI_GCR_EN (1 << 0) /* ENable */ #define AW_SPI_GCR_MODE_MASTER (1 << 1) /* 1 = Master, 0 = Slave */ #define AW_SPI_GCR_TP_EN (1 << 7) /* 1 = Stop transmit when FIFO is full */ #define AW_SPI_GCR_SRST (1 << 31) /* Soft Reset */ #define AW_SPI_TCR 0x08 /* Transfer Control register */ #define AW_SPI_TCR_XCH (1 << 31) /* Initiate transfer */ #define AW_SPI_TCR_SDDM (1 << 14) /* Sending Delay Data Mode */ #define AW_SPI_TCR_SDM (1 << 13) /* Master Sample Data Mode */ #define AW_SPI_TCR_FBS (1 << 12) /* First Transmit Bit Select (1 == LSB) */ #define AW_SPI_TCR_SDC (1 << 11) /* Master Sample Data Control */ #define AW_SPI_TCR_RPSM (1 << 10) /* Rapid Mode Select */ #define AW_SPI_TCR_DDB (1 << 9) /* Dummy Burst Type */ #define AW_SPI_TCR_SSSEL_MASK 0x30 /* Chip select */ #define AW_SPI_TCR_SSSEL_SHIFT 4 #define AW_SPI_TCR_SS_LEVEL (1 << 7) /* 1 == CS High */ #define AW_SPI_TCR_SS_OWNER (1 << 6) /* 1 == Software controlled */ #define AW_SPI_TCR_SPOL (1 << 2) /* 1 == Active low */ #define AW_SPI_TCR_CPOL (1 << 1) /* 1 == Active low */ #define AW_SPI_TCR_CPHA (1 << 0) /* 1 == Phase 1 */ #define AW_SPI_IER 0x10 /* Interrupt Control Register */ #define AW_SPI_IER_SS (1 << 13) /* Chip select went from valid to invalid */ #define AW_SPI_IER_TC (1 << 12) /* Transfer complete */ #define AW_SPI_IER_TF_UDR (1 << 11) /* TXFIFO underrun */ #define AW_SPI_IER_TF_OVF (1 << 10) /* TXFIFO overrun */ #define AW_SPI_IER_RF_UDR (1 << 9) /* RXFIFO underrun */ #define AW_SPI_IER_RF_OVF (1 << 8) /* RXFIFO overrun */ #define AW_SPI_IER_TF_FULL (1 << 6) /* TXFIFO Full */ #define AW_SPI_IER_TF_EMP (1 << 5) /* TXFIFO Empty */ #define AW_SPI_IER_TF_ERQ (1 << 4) /* TXFIFO Empty Request */ #define AW_SPI_IER_RF_FULL (1 << 2) /* RXFIFO Full */ #define AW_SPI_IER_RF_EMP (1 << 1) /* RXFIFO Empty */ #define AW_SPI_IER_RF_ERQ (1 << 0) /* RXFIFO Empty Request */ #define AW_SPI_ISR 0x14 /* Interrupt Status Register */ #define AW_SPI_FCR 0x18 /* FIFO Control Register */ #define AW_SPI_FCR_TX_RST (1 << 31) /* Reset TX FIFO */ #define AW_SPI_FCR_TX_TRIG_MASK 0xFF0000 /* TX FIFO Trigger level */ #define AW_SPI_FCR_TX_TRIG_SHIFT 16 #define AW_SPI_FCR_RX_RST (1 << 15) /* Reset RX FIFO */ #define AW_SPI_FCR_RX_TRIG_MASK 0xFF /* RX FIFO Trigger level */ #define AW_SPI_FCR_RX_TRIG_SHIFT 0 #define AW_SPI_FSR 0x1C /* FIFO Status Register */ #define AW_SPI_FSR_TB_WR (1 << 31) #define AW_SPI_FSR_TB_CNT_MASK 0x70000000 #define AW_SPI_FSR_TB_CNT_SHIFT 28 #define AW_SPI_FSR_TF_CNT_MASK 0xFF0000 #define AW_SPI_FSR_TF_CNT_SHIFT 16 #define AW_SPI_FSR_RB_WR (1 << 15) #define AW_SPI_FSR_RB_CNT_MASK 0x7000 #define AW_SPI_FSR_RB_CNT_SHIFT 12 #define AW_SPI_FSR_RF_CNT_MASK 0xFF #define AW_SPI_FSR_RF_CNT_SHIFT 0 #define AW_SPI_WCR 0x20 /* Wait Clock Counter Register */ #define AW_SPI_CCR 0x24 /* Clock Rate Control Register */ #define AW_SPI_CCR_DRS (1 << 12) /* Clock divider select */ #define AW_SPI_CCR_CDR1_MASK 0xF00 #define AW_SPI_CCR_CDR1_SHIFT 8 #define AW_SPI_CCR_CDR2_MASK 0xFF #define AW_SPI_CCR_CDR2_SHIFT 0 #define AW_SPI_MBC 0x30 /* Burst Counter Register */ #define AW_SPI_MTC 0x34 /* Transmit Counter Register */ #define AW_SPI_BCC 0x38 /* Burst Control Register */ #define AW_SPI_MDMA_CTL 0x88 /* Normal DMA Control Register */ #define AW_SPI_TXD 0x200 /* TX Data Register */ #define AW_SPI_RDX 0x300 /* RX Data Register */ #define AW_SPI_MAX_CS 4 #define AW_SPI_FIFO_SIZE 64 static struct ofw_compat_data compat_data[] = { { "allwinner,sun8i-h3-spi", 1 }, { NULL, 0 } }; static struct resource_spec aw_spi_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE }, { -1, 0 } }; struct aw_spi_softc { device_t dev; device_t spibus; struct resource *res[2]; struct mtx mtx; clk_t clk_ahb; clk_t clk_mod; uint64_t mod_freq; hwreset_t rst_ahb; void * intrhand; int transfer; uint8_t *rxbuf; uint32_t rxcnt; uint8_t *txbuf; uint32_t txcnt; uint32_t txlen; uint32_t rxlen; }; #define AW_SPI_LOCK(sc) mtx_lock(&(sc)->mtx) #define AW_SPI_UNLOCK(sc) mtx_unlock(&(sc)->mtx) #define AW_SPI_ASSERT_LOCKED(sc) mtx_assert(&(sc)->mtx, MA_OWNED) #define AW_SPI_READ_1(sc, reg) bus_read_1((sc)->res[0], (reg)) #define AW_SPI_WRITE_1(sc, reg, val) bus_write_1((sc)->res[0], (reg), (val)) #define AW_SPI_READ_4(sc, reg) bus_read_4((sc)->res[0], (reg)) #define AW_SPI_WRITE_4(sc, reg, val) bus_write_4((sc)->res[0], (reg), (val)) static int aw_spi_probe(device_t dev); static int aw_spi_attach(device_t dev); static int aw_spi_detach(device_t dev); static void aw_spi_intr(void *arg); static int aw_spi_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data) return (ENXIO); device_set_desc(dev, "Allwinner SPI"); return (BUS_PROBE_DEFAULT); } static int aw_spi_attach(device_t dev) { struct aw_spi_softc *sc; int error; sc = device_get_softc(dev); sc->dev = dev; mtx_init(&sc->mtx, device_get_nameunit(dev), NULL, MTX_DEF); if (bus_alloc_resources(dev, aw_spi_spec, sc->res) != 0) { device_printf(dev, "cannot allocate resources for device\n"); error = ENXIO; goto fail; } if (bus_setup_intr(dev, sc->res[1], INTR_TYPE_MISC | INTR_MPSAFE, NULL, aw_spi_intr, sc, &sc->intrhand)) { bus_release_resources(dev, aw_spi_spec, sc->res); device_printf(dev, "cannot setup interrupt handler\n"); return (ENXIO); } /* De-assert reset */ if (hwreset_get_by_ofw_idx(dev, 0, 0, &sc->rst_ahb) == 0) { error = hwreset_deassert(sc->rst_ahb); if (error != 0) { device_printf(dev, "cannot de-assert reset\n"); goto fail; } } /* Activate the module clock. */ error = clk_get_by_ofw_name(dev, 0, "ahb", &sc->clk_ahb); if (error != 0) { device_printf(dev, "cannot get ahb clock\n"); goto fail; } error = clk_get_by_ofw_name(dev, 0, "mod", &sc->clk_mod); if (error != 0) { device_printf(dev, "cannot get mod clock\n"); goto fail; } error = clk_enable(sc->clk_ahb); if (error != 0) { device_printf(dev, "cannot enable ahb clock\n"); goto fail; } error = clk_enable(sc->clk_mod); if (error != 0) { device_printf(dev, "cannot enable mod clock\n"); goto fail; } sc->spibus = device_add_child(dev, "spibus", -1); - return (0); + return (bus_generic_attach(dev)); fail: aw_spi_detach(dev); return (error); } static int aw_spi_detach(device_t dev) { struct aw_spi_softc *sc; sc = device_get_softc(dev); bus_generic_detach(sc->dev); if (sc->spibus != NULL) device_delete_child(dev, sc->spibus); if (sc->clk_mod != NULL) clk_release(sc->clk_mod); if (sc->clk_ahb) clk_release(sc->clk_ahb); if (sc->rst_ahb) hwreset_assert(sc->rst_ahb); if (sc->intrhand != NULL) bus_teardown_intr(sc->dev, sc->res[1], sc->intrhand); bus_release_resources(dev, aw_spi_spec, sc->res); mtx_destroy(&sc->mtx); return (0); } static phandle_t aw_spi_get_node(device_t bus, device_t dev) { return ofw_bus_get_node(bus); } static void aw_spi_setup_mode(struct aw_spi_softc *sc, uint32_t mode) { uint32_t reg; /* We only support master mode */ reg = AW_SPI_READ_4(sc, AW_SPI_GCR); reg |= AW_SPI_GCR_MODE_MASTER; AW_SPI_WRITE_4(sc, AW_SPI_GCR, reg); /* Setup the modes */ reg = AW_SPI_READ_4(sc, AW_SPI_TCR); if (mode & SPIBUS_MODE_CPHA) reg |= AW_SPI_TCR_CPHA; if (mode & SPIBUS_MODE_CPOL) reg |= AW_SPI_TCR_CPOL; AW_SPI_WRITE_4(sc, AW_SPI_TCR, reg); } static void aw_spi_setup_cs(struct aw_spi_softc *sc, uint32_t cs, bool low) { uint32_t reg; /* Setup CS */ reg = AW_SPI_READ_4(sc, AW_SPI_TCR); reg &= ~(AW_SPI_TCR_SSSEL_MASK); reg |= cs << AW_SPI_TCR_SSSEL_SHIFT; reg |= AW_SPI_TCR_SS_OWNER; if (low) reg &= ~(AW_SPI_TCR_SS_LEVEL); else reg |= AW_SPI_TCR_SS_LEVEL; AW_SPI_WRITE_4(sc, AW_SPI_TCR, reg); } static uint64_t aw_spi_clock_test_cdr1(struct aw_spi_softc *sc, uint64_t clock, uint32_t *ccr) { uint64_t cur, best = 0; int i, max, best_div; max = AW_SPI_CCR_CDR1_MASK >> AW_SPI_CCR_CDR1_SHIFT; for (i = 0; i < max; i++) { cur = sc->mod_freq / (1 << i); if ((clock - cur) < (clock - best)) { best = cur; best_div = i; } } *ccr = (best_div << AW_SPI_CCR_CDR1_SHIFT); return (best); } static uint64_t aw_spi_clock_test_cdr2(struct aw_spi_softc *sc, uint64_t clock, uint32_t *ccr) { uint64_t cur, best = 0; int i, max, best_div; max = ((AW_SPI_CCR_CDR2_MASK) >> AW_SPI_CCR_CDR2_SHIFT); for (i = 0; i < max; i++) { cur = sc->mod_freq / (2 * i + 1); if ((clock - cur) < (clock - best)) { best = cur; best_div = i; } } *ccr = AW_SPI_CCR_DRS | (best_div << AW_SPI_CCR_CDR2_SHIFT); return (best); } static void aw_spi_setup_clock(struct aw_spi_softc *sc, uint64_t clock) { uint64_t best_ccr1, best_ccr2; uint32_t ccr, ccr1, ccr2; best_ccr1 = aw_spi_clock_test_cdr1(sc, clock, &ccr1); best_ccr2 = aw_spi_clock_test_cdr2(sc, clock, &ccr2); if (best_ccr1 == clock) { ccr = ccr1; } else if (best_ccr2 == clock) { ccr = ccr2; } else { if ((clock - best_ccr1) < (clock - best_ccr2)) ccr = ccr1; else ccr = ccr2; } AW_SPI_WRITE_4(sc, AW_SPI_CCR, ccr); } static inline void aw_spi_fill_txfifo(struct aw_spi_softc *sc) { uint32_t reg, txcnt; int i; if (sc->txcnt == sc->txlen) return; reg = AW_SPI_READ_4(sc, AW_SPI_FSR); reg &= AW_SPI_FSR_TF_CNT_MASK; txcnt = reg >> AW_SPI_FSR_TF_CNT_SHIFT; for (i = 0; i < (AW_SPI_FIFO_SIZE - txcnt); i++) { AW_SPI_WRITE_1(sc, AW_SPI_TXD, sc->txbuf[sc->txcnt++]); if (sc->txcnt == sc->txlen) break; } return; } static inline void aw_spi_read_rxfifo(struct aw_spi_softc *sc) { uint32_t reg; uint8_t val; int i; if (sc->rxcnt == sc->rxlen) return; reg = AW_SPI_READ_4(sc, AW_SPI_FSR); reg = (reg & AW_SPI_FSR_RF_CNT_MASK) >> AW_SPI_FSR_RF_CNT_SHIFT; for (i = 0; i < reg; i++) { val = AW_SPI_READ_1(sc, AW_SPI_RDX); if (sc->rxcnt < sc->rxlen) sc->rxbuf[sc->rxcnt++] = val; } } static void aw_spi_intr(void *arg) { struct aw_spi_softc *sc; uint32_t intr; sc = (struct aw_spi_softc *)arg; intr = AW_SPI_READ_4(sc, AW_SPI_ISR); if (intr & AW_SPI_IER_RF_FULL) aw_spi_read_rxfifo(sc); if (intr & AW_SPI_IER_TF_EMP) { aw_spi_fill_txfifo(sc); /* * If we don't have anything else to write * disable TXFifo interrupts */ if (sc->txcnt == sc->txlen) AW_SPI_WRITE_4(sc, AW_SPI_IER, AW_SPI_IER_TC | AW_SPI_IER_RF_FULL); } if (intr & AW_SPI_IER_TC) { /* read the rest of the data from the fifo */ aw_spi_read_rxfifo(sc); /* Disable the interrupts */ AW_SPI_WRITE_4(sc, AW_SPI_IER, 0); sc->transfer = 0; wakeup(sc); } /* Clear Interrupts */ AW_SPI_WRITE_4(sc, AW_SPI_ISR, intr); } static int aw_spi_xfer(struct aw_spi_softc *sc, void *rxbuf, void *txbuf, uint32_t txlen, uint32_t rxlen) { uint32_t reg; int error = 0, timeout; sc->rxbuf = rxbuf; sc->rxcnt = 0; sc->txbuf = txbuf; sc->txcnt = 0; sc->txlen = txlen; sc->rxlen = rxlen; /* Reset the FIFOs */ AW_SPI_WRITE_4(sc, AW_SPI_FCR, AW_SPI_FCR_TX_RST | AW_SPI_FCR_RX_RST); for (timeout = 1000; timeout > 0; timeout--) { reg = AW_SPI_READ_4(sc, AW_SPI_FCR); if (reg == 0) break; } if (timeout == 0) { device_printf(sc->dev, "Cannot reset the FIFOs\n"); return (EIO); } /* Write the counters */ AW_SPI_WRITE_4(sc, AW_SPI_MBC, txlen); AW_SPI_WRITE_4(sc, AW_SPI_MTC, txlen); AW_SPI_WRITE_4(sc, AW_SPI_BCC, txlen); /* First fill */ aw_spi_fill_txfifo(sc); /* Start transmit */ reg = AW_SPI_READ_4(sc, AW_SPI_TCR); reg |= AW_SPI_TCR_XCH; AW_SPI_WRITE_4(sc, AW_SPI_TCR, reg); /* * Enable interrupts for : * Transmit complete * TX Fifo empty * RX Fifo full */ AW_SPI_WRITE_4(sc, AW_SPI_IER, AW_SPI_IER_TC | AW_SPI_IER_TF_EMP | AW_SPI_IER_RF_FULL); sc->transfer = 1; while (error == 0 && sc->transfer != 0) error = msleep(sc, &sc->mtx, 0, "aw_spi", 10 * hz); return (0); } static int aw_spi_transfer(device_t dev, device_t child, struct spi_command *cmd) { struct aw_spi_softc *sc; uint32_t cs, mode, clock, reg; int err = 0; sc = device_get_softc(dev); spibus_get_cs(child, &cs); spibus_get_clock(child, &clock); spibus_get_mode(child, &mode); /* The minimum divider is 2 so set the clock at twice the needed speed */ clk_set_freq(sc->clk_mod, 2 * clock, CLK_SET_ROUND_DOWN); clk_get_freq(sc->clk_mod, &sc->mod_freq); if (cs >= AW_SPI_MAX_CS) { device_printf(dev, "Invalid cs %d\n", cs); return (EINVAL); } mtx_lock(&sc->mtx); /* Enable and reset the module */ reg = AW_SPI_READ_4(sc, AW_SPI_GCR); reg |= AW_SPI_GCR_EN | AW_SPI_GCR_SRST; AW_SPI_WRITE_4(sc, AW_SPI_GCR, reg); /* Setup clock, CS and mode */ aw_spi_setup_clock(sc, clock); aw_spi_setup_mode(sc, mode); if (cs & SPIBUS_CS_HIGH) aw_spi_setup_cs(sc, cs, false); else aw_spi_setup_cs(sc, cs, true); /* xfer */ err = 0; if (cmd->tx_cmd_sz > 0) err = aw_spi_xfer(sc, cmd->rx_cmd, cmd->tx_cmd, cmd->tx_cmd_sz, cmd->rx_cmd_sz); if (cmd->tx_data_sz > 0 && err == 0) err = aw_spi_xfer(sc, cmd->rx_data, cmd->tx_data, cmd->tx_data_sz, cmd->rx_data_sz); if (cs & SPIBUS_CS_HIGH) aw_spi_setup_cs(sc, cs, true); else aw_spi_setup_cs(sc, cs, false); /* Disable the module */ reg = AW_SPI_READ_4(sc, AW_SPI_GCR); reg &= ~AW_SPI_GCR_EN; AW_SPI_WRITE_4(sc, AW_SPI_GCR, reg); mtx_unlock(&sc->mtx); return (err); } static device_method_t aw_spi_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aw_spi_probe), DEVMETHOD(device_attach, aw_spi_attach), DEVMETHOD(device_detach, aw_spi_detach), /* spibus_if */ DEVMETHOD(spibus_transfer, aw_spi_transfer), /* ofw_bus_if */ DEVMETHOD(ofw_bus_get_node, aw_spi_get_node), DEVMETHOD_END }; static driver_t aw_spi_driver = { "aw_spi", aw_spi_methods, sizeof(struct aw_spi_softc), }; static devclass_t aw_spi_devclass; DRIVER_MODULE(aw_spi, simplebus, aw_spi_driver, aw_spi_devclass, 0, 0); DRIVER_MODULE(ofw_spibus, aw_spi, ofw_spibus_driver, ofw_spibus_devclass, 0, 0); MODULE_DEPEND(aw_spi, ofw_spibus, 1, 1, 1); SIMPLEBUS_PNP_INFO(compat_data); Index: stable/12/sys/arm/allwinner/axp209.c =================================================================== --- stable/12/sys/arm/allwinner/axp209.c (revision 362349) +++ stable/12/sys/arm/allwinner/axp209.c (revision 362350) @@ -1,1409 +1,1426 @@ /*- * Copyright (c) 2015-2016 Emmanuel Vadot * Copyright (c) 2016 Jared McNeill * 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. */ #include __FBSDID("$FreeBSD$"); /* * X-Power AXP209/AXP211 PMU for Allwinner SoCs */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gpio_if.h" #include "regdev_if.h" MALLOC_DEFINE(M_AXP2XX_REG, "Axp2XX regulator", "Axp2XX power regulator"); struct axp2xx_regdef { intptr_t id; char *name; uint8_t enable_reg; uint8_t enable_mask; uint8_t voltage_reg; uint8_t voltage_mask; uint8_t voltage_shift; int voltage_min; int voltage_max; int voltage_step; int voltage_nstep; }; static struct axp2xx_regdef axp209_regdefs[] = { { .id = AXP209_REG_ID_DCDC2, .name = "dcdc2", .enable_reg = AXP209_POWERCTL, .enable_mask = AXP209_POWERCTL_DCDC2, .voltage_reg = AXP209_REG_DCDC2_VOLTAGE, .voltage_mask = 0x3f, .voltage_min = 700, .voltage_max = 2275, .voltage_step = 25, .voltage_nstep = 64, }, { .id = AXP209_REG_ID_DCDC3, .name = "dcdc3", .enable_reg = AXP209_POWERCTL, .enable_mask = AXP209_POWERCTL_DCDC3, .voltage_reg = AXP209_REG_DCDC3_VOLTAGE, .voltage_mask = 0x7f, .voltage_min = 700, .voltage_max = 3500, .voltage_step = 25, .voltage_nstep = 128, }, { .id = AXP209_REG_ID_LDO2, .name = "ldo2", .enable_reg = AXP209_POWERCTL, .enable_mask = AXP209_POWERCTL_LDO2, .voltage_reg = AXP209_REG_LDO24_VOLTAGE, .voltage_mask = 0xf0, .voltage_shift = 4, .voltage_min = 1800, .voltage_max = 3300, .voltage_step = 100, .voltage_nstep = 16, }, { .id = AXP209_REG_ID_LDO3, .name = "ldo3", .enable_reg = AXP209_POWERCTL, .enable_mask = AXP209_POWERCTL_LDO3, .voltage_reg = AXP209_REG_LDO3_VOLTAGE, .voltage_mask = 0x7f, .voltage_min = 700, .voltage_max = 2275, .voltage_step = 25, .voltage_nstep = 128, }, }; static struct axp2xx_regdef axp221_regdefs[] = { { .id = AXP221_REG_ID_DLDO1, .name = "dldo1", .enable_reg = AXP221_POWERCTL_2, .enable_mask = AXP221_POWERCTL2_DLDO1, .voltage_reg = AXP221_REG_DLDO1_VOLTAGE, .voltage_mask = 0x1f, .voltage_min = 700, .voltage_max = 3300, .voltage_step = 100, .voltage_nstep = 26, }, { .id = AXP221_REG_ID_DLDO2, .name = "dldo2", .enable_reg = AXP221_POWERCTL_2, .enable_mask = AXP221_POWERCTL2_DLDO2, .voltage_reg = AXP221_REG_DLDO2_VOLTAGE, .voltage_mask = 0x1f, .voltage_min = 700, .voltage_max = 3300, .voltage_step = 100, .voltage_nstep = 26, }, { .id = AXP221_REG_ID_DLDO3, .name = "dldo3", .enable_reg = AXP221_POWERCTL_2, .enable_mask = AXP221_POWERCTL2_DLDO3, .voltage_reg = AXP221_REG_DLDO3_VOLTAGE, .voltage_mask = 0x1f, .voltage_min = 700, .voltage_max = 3300, .voltage_step = 100, .voltage_nstep = 26, }, { .id = AXP221_REG_ID_DLDO4, .name = "dldo4", .enable_reg = AXP221_POWERCTL_2, .enable_mask = AXP221_POWERCTL2_DLDO4, .voltage_reg = AXP221_REG_DLDO4_VOLTAGE, .voltage_mask = 0x1f, .voltage_min = 700, .voltage_max = 3300, .voltage_step = 100, .voltage_nstep = 26, }, { .id = AXP221_REG_ID_ELDO1, .name = "eldo1", .enable_reg = AXP221_POWERCTL_2, .enable_mask = AXP221_POWERCTL2_ELDO1, .voltage_reg = AXP221_REG_ELDO1_VOLTAGE, .voltage_mask = 0x1f, .voltage_min = 700, .voltage_max = 3300, .voltage_step = 100, .voltage_nstep = 26, }, { .id = AXP221_REG_ID_ELDO2, .name = "eldo2", .enable_reg = AXP221_POWERCTL_2, .enable_mask = AXP221_POWERCTL2_ELDO2, .voltage_reg = AXP221_REG_ELDO2_VOLTAGE, .voltage_mask = 0x1f, .voltage_min = 700, .voltage_max = 3300, .voltage_step = 100, .voltage_nstep = 26, }, { .id = AXP221_REG_ID_ELDO3, .name = "eldo3", .enable_reg = AXP221_POWERCTL_2, .enable_mask = AXP221_POWERCTL2_ELDO3, .voltage_reg = AXP221_REG_ELDO3_VOLTAGE, .voltage_mask = 0x1f, .voltage_min = 700, .voltage_max = 3300, .voltage_step = 100, .voltage_nstep = 26, }, { .id = AXP221_REG_ID_DC5LDO, .name = "dc5ldo", .enable_reg = AXP221_POWERCTL_1, .enable_mask = AXP221_POWERCTL1_DC5LDO, .voltage_reg = AXP221_REG_DC5LDO_VOLTAGE, .voltage_mask = 0x3, .voltage_min = 700, .voltage_max = 1400, .voltage_step = 100, .voltage_nstep = 7, }, { .id = AXP221_REG_ID_DCDC1, .name = "dcdc1", .enable_reg = AXP221_POWERCTL_1, .enable_mask = AXP221_POWERCTL1_DCDC1, .voltage_reg = AXP221_REG_DCDC1_VOLTAGE, .voltage_mask = 0x1f, .voltage_min = 1600, .voltage_max = 3400, .voltage_step = 100, .voltage_nstep = 18, }, { .id = AXP221_REG_ID_DCDC2, .name = "dcdc2", .enable_reg = AXP221_POWERCTL_1, .enable_mask = AXP221_POWERCTL1_DCDC2, .voltage_reg = AXP221_REG_DCDC2_VOLTAGE, .voltage_mask = 0x3f, .voltage_min = 600, .voltage_max = 1540, .voltage_step = 20, .voltage_nstep = 47, }, { .id = AXP221_REG_ID_DCDC3, .name = "dcdc3", .enable_reg = AXP221_POWERCTL_1, .enable_mask = AXP221_POWERCTL1_DCDC3, .voltage_reg = AXP221_REG_DCDC3_VOLTAGE, .voltage_mask = 0x3f, .voltage_min = 600, .voltage_max = 1860, .voltage_step = 20, .voltage_nstep = 63, }, { .id = AXP221_REG_ID_DCDC4, .name = "dcdc4", .enable_reg = AXP221_POWERCTL_1, .enable_mask = AXP221_POWERCTL1_DCDC4, .voltage_reg = AXP221_REG_DCDC4_VOLTAGE, .voltage_mask = 0x3f, .voltage_min = 600, .voltage_max = 1540, .voltage_step = 20, .voltage_nstep = 47, }, { .id = AXP221_REG_ID_DCDC5, .name = "dcdc5", .enable_reg = AXP221_POWERCTL_1, .enable_mask = AXP221_POWERCTL1_DCDC5, .voltage_reg = AXP221_REG_DCDC5_VOLTAGE, .voltage_mask = 0x1f, .voltage_min = 1000, .voltage_max = 2550, .voltage_step = 50, .voltage_nstep = 31, }, { .id = AXP221_REG_ID_ALDO1, .name = "aldo1", .enable_reg = AXP221_POWERCTL_1, .enable_mask = AXP221_POWERCTL1_ALDO1, .voltage_reg = AXP221_REG_ALDO1_VOLTAGE, .voltage_mask = 0x1f, .voltage_min = 700, .voltage_max = 3300, .voltage_step = 100, .voltage_nstep = 26, }, { .id = AXP221_REG_ID_ALDO2, .name = "aldo2", .enable_reg = AXP221_POWERCTL_1, .enable_mask = AXP221_POWERCTL1_ALDO2, .voltage_reg = AXP221_REG_ALDO2_VOLTAGE, .voltage_mask = 0x1f, .voltage_min = 700, .voltage_max = 3300, .voltage_step = 100, .voltage_nstep = 26, }, { .id = AXP221_REG_ID_ALDO3, .name = "aldo3", .enable_reg = AXP221_POWERCTL_3, .enable_mask = AXP221_POWERCTL3_ALDO3, .voltage_reg = AXP221_REG_ALDO3_VOLTAGE, .voltage_mask = 0x1f, .voltage_min = 700, .voltage_max = 3300, .voltage_step = 100, .voltage_nstep = 26, }, { .id = AXP221_REG_ID_DC1SW, .name = "dc1sw", .enable_reg = AXP221_POWERCTL_2, .enable_mask = AXP221_POWERCTL2_DC1SW, }, }; struct axp2xx_reg_sc { struct regnode *regnode; device_t base_dev; struct axp2xx_regdef *def; phandle_t xref; struct regnode_std_param *param; }; struct axp2xx_pins { const char *name; uint8_t ctrl_reg; uint8_t status_reg; uint8_t status_mask; uint8_t status_shift; }; /* GPIO3 is different, don't expose it for now */ static const struct axp2xx_pins axp209_pins[] = { { .name = "GPIO0", .ctrl_reg = AXP2XX_GPIO0_CTRL, .status_reg = AXP2XX_GPIO_STATUS, .status_mask = 0x10, .status_shift = 4, }, { .name = "GPIO1", .ctrl_reg = AXP2XX_GPIO1_CTRL, .status_reg = AXP2XX_GPIO_STATUS, .status_mask = 0x20, .status_shift = 5, }, { .name = "GPIO2", .ctrl_reg = AXP209_GPIO2_CTRL, .status_reg = AXP2XX_GPIO_STATUS, .status_mask = 0x40, .status_shift = 6, }, }; static const struct axp2xx_pins axp221_pins[] = { { .name = "GPIO0", .ctrl_reg = AXP2XX_GPIO0_CTRL, .status_reg = AXP2XX_GPIO_STATUS, .status_mask = 0x1, .status_shift = 0x0, }, { .name = "GPIO1", .ctrl_reg = AXP2XX_GPIO0_CTRL, .status_reg = AXP2XX_GPIO_STATUS, .status_mask = 0x2, .status_shift = 0x1, }, }; struct axp2xx_sensors { int id; const char *name; const char *desc; const char *format; uint8_t enable_reg; uint8_t enable_mask; uint8_t value_reg; uint8_t value_size; uint8_t h_value_mask; uint8_t h_value_shift; uint8_t l_value_mask; uint8_t l_value_shift; int value_step; int value_convert; }; static const struct axp2xx_sensors axp209_sensors[] = { { .id = AXP209_ACVOLT, .name = "acvolt", .desc = "AC Voltage (microvolt)", .format = "I", .enable_reg = AXP2XX_ADC_ENABLE1, .enable_mask = AXP209_ADC1_ACVOLT, .value_reg = AXP209_ACIN_VOLTAGE, .value_size = 2, .h_value_mask = 0xff, .h_value_shift = 4, .l_value_mask = 0xf, .l_value_shift = 0, .value_step = AXP209_VOLT_STEP, }, { .id = AXP209_ACCURRENT, .name = "accurrent", .desc = "AC Current (microAmpere)", .format = "I", .enable_reg = AXP2XX_ADC_ENABLE1, .enable_mask = AXP209_ADC1_ACCURRENT, .value_reg = AXP209_ACIN_CURRENT, .value_size = 2, .h_value_mask = 0xff, .h_value_shift = 4, .l_value_mask = 0xf, .l_value_shift = 0, .value_step = AXP209_ACCURRENT_STEP, }, { .id = AXP209_VBUSVOLT, .name = "vbusvolt", .desc = "VBUS Voltage (microVolt)", .format = "I", .enable_reg = AXP2XX_ADC_ENABLE1, .enable_mask = AXP209_ADC1_VBUSVOLT, .value_reg = AXP209_VBUS_VOLTAGE, .value_size = 2, .h_value_mask = 0xff, .h_value_shift = 4, .l_value_mask = 0xf, .l_value_shift = 0, .value_step = AXP209_VOLT_STEP, }, { .id = AXP209_VBUSCURRENT, .name = "vbuscurrent", .desc = "VBUS Current (microAmpere)", .format = "I", .enable_reg = AXP2XX_ADC_ENABLE1, .enable_mask = AXP209_ADC1_VBUSCURRENT, .value_reg = AXP209_VBUS_CURRENT, .value_size = 2, .h_value_mask = 0xff, .h_value_shift = 4, .l_value_mask = 0xf, .l_value_shift = 0, .value_step = AXP209_VBUSCURRENT_STEP, }, { .id = AXP2XX_BATVOLT, .name = "batvolt", .desc = "Battery Voltage (microVolt)", .format = "I", .enable_reg = AXP2XX_ADC_ENABLE1, .enable_mask = AXP2XX_ADC1_BATVOLT, .value_reg = AXP2XX_BAT_VOLTAGE, .value_size = 2, .h_value_mask = 0xff, .h_value_shift = 4, .l_value_mask = 0xf, .l_value_shift = 0, .value_step = AXP2XX_BATVOLT_STEP, }, { .id = AXP2XX_BATCHARGECURRENT, .name = "batchargecurrent", .desc = "Battery Charging Current (microAmpere)", .format = "I", .enable_reg = AXP2XX_ADC_ENABLE1, .enable_mask = AXP2XX_ADC1_BATCURRENT, .value_reg = AXP2XX_BAT_CHARGE_CURRENT, .value_size = 2, .h_value_mask = 0xff, .h_value_shift = 5, .l_value_mask = 0x1f, .l_value_shift = 0, .value_step = AXP2XX_BATCURRENT_STEP, }, { .id = AXP2XX_BATDISCHARGECURRENT, .name = "batdischargecurrent", .desc = "Battery Discharging Current (microAmpere)", .format = "I", .enable_reg = AXP2XX_ADC_ENABLE1, .enable_mask = AXP2XX_ADC1_BATCURRENT, .value_reg = AXP2XX_BAT_DISCHARGE_CURRENT, .value_size = 2, .h_value_mask = 0xff, .h_value_shift = 5, .l_value_mask = 0x1f, .l_value_shift = 0, .value_step = AXP2XX_BATCURRENT_STEP, }, { .id = AXP2XX_TEMP, .name = "temp", .desc = "Internal Temperature", .format = "IK", .enable_reg = AXP209_ADC_ENABLE2, .enable_mask = AXP209_ADC2_TEMP, .value_reg = AXP209_TEMPMON, .value_size = 2, .h_value_mask = 0xff, .h_value_shift = 4, .l_value_mask = 0xf, .l_value_shift = 0, .value_step = 1, .value_convert = -(AXP209_TEMPMON_MIN - AXP209_0C_TO_K), }, }; static const struct axp2xx_sensors axp221_sensors[] = { { .id = AXP2XX_BATVOLT, .name = "batvolt", .desc = "Battery Voltage (microVolt)", .format = "I", .enable_reg = AXP2XX_ADC_ENABLE1, .enable_mask = AXP2XX_ADC1_BATVOLT, .value_reg = AXP2XX_BAT_VOLTAGE, .value_size = 2, .h_value_mask = 0xff, .h_value_shift = 4, .l_value_mask = 0xf, .l_value_shift = 0, .value_step = AXP2XX_BATVOLT_STEP, }, { .id = AXP2XX_BATCHARGECURRENT, .name = "batchargecurrent", .desc = "Battery Charging Current (microAmpere)", .format = "I", .enable_reg = AXP2XX_ADC_ENABLE1, .enable_mask = AXP2XX_ADC1_BATCURRENT, .value_reg = AXP2XX_BAT_CHARGE_CURRENT, .value_size = 2, .h_value_mask = 0xff, .h_value_shift = 5, .l_value_mask = 0x1f, .l_value_shift = 0, .value_step = AXP2XX_BATCURRENT_STEP, }, { .id = AXP2XX_BATDISCHARGECURRENT, .name = "batdischargecurrent", .desc = "Battery Discharging Current (microAmpere)", .format = "I", .enable_reg = AXP2XX_ADC_ENABLE1, .enable_mask = AXP2XX_ADC1_BATCURRENT, .value_reg = AXP2XX_BAT_DISCHARGE_CURRENT, .value_size = 2, .h_value_mask = 0xff, .h_value_shift = 5, .l_value_mask = 0x1f, .l_value_shift = 0, .value_step = AXP2XX_BATCURRENT_STEP, }, { .id = AXP2XX_TEMP, .name = "temp", .desc = "Internal Temperature", .format = "IK", .enable_reg = AXP2XX_ADC_ENABLE1, .enable_mask = AXP221_ADC1_TEMP, .value_reg = AXP221_TEMPMON, .value_size = 2, .h_value_mask = 0xff, .h_value_shift = 4, .l_value_mask = 0xf, .l_value_shift = 0, .value_step = 1, .value_convert = -(AXP221_TEMPMON_MIN - AXP209_0C_TO_K), }, }; enum AXP2XX_TYPE { AXP209 = 1, AXP221, }; struct axp2xx_softc { device_t dev; struct resource * res[1]; void * intrcookie; struct intr_config_hook intr_hook; struct mtx mtx; uint8_t type; /* GPIO */ device_t gpiodev; int npins; const struct axp2xx_pins *pins; /* Sensors */ const struct axp2xx_sensors *sensors; int nsensors; /* Regulators */ struct axp2xx_reg_sc **regs; int nregs; struct axp2xx_regdef *regdefs; }; static struct ofw_compat_data compat_data[] = { { "x-powers,axp209", AXP209 }, { "x-powers,axp221", AXP221 }, { NULL, 0 } }; static struct resource_spec axp_res_spec[] = { { SYS_RES_IRQ, 0, RF_ACTIVE }, { -1, 0, 0 } }; #define AXP_LOCK(sc) mtx_lock(&(sc)->mtx) #define AXP_UNLOCK(sc) mtx_unlock(&(sc)->mtx) static int axp2xx_read(device_t dev, uint8_t reg, uint8_t *data, uint8_t size) { return (iicdev_readfrom(dev, reg, data, size, IIC_INTRWAIT)); } static int axp2xx_write(device_t dev, uint8_t reg, uint8_t data) { return (iicdev_writeto(dev, reg, &data, sizeof(data), IIC_INTRWAIT)); } static int axp2xx_regnode_init(struct regnode *regnode) { return (0); } static int axp2xx_regnode_enable(struct regnode *regnode, bool enable, int *udelay) { struct axp2xx_reg_sc *sc; uint8_t val; sc = regnode_get_softc(regnode); axp2xx_read(sc->base_dev, sc->def->enable_reg, &val, 1); if (enable) val |= sc->def->enable_mask; else val &= ~sc->def->enable_mask; axp2xx_write(sc->base_dev, sc->def->enable_reg, val); *udelay = 0; return (0); } static void axp2xx_regnode_reg_to_voltage(struct axp2xx_reg_sc *sc, uint8_t val, int *uv) { if (val < sc->def->voltage_nstep) *uv = sc->def->voltage_min + val * sc->def->voltage_step; else *uv = sc->def->voltage_min + (sc->def->voltage_nstep * sc->def->voltage_step); *uv *= 1000; } static int axp2xx_regnode_voltage_to_reg(struct axp2xx_reg_sc *sc, int min_uvolt, int max_uvolt, uint8_t *val) { uint8_t nval; int nstep, uvolt; nval = 0; uvolt = sc->def->voltage_min * 1000; for (nstep = 0; nstep < sc->def->voltage_nstep && uvolt < min_uvolt; nstep++) { ++nval; uvolt += (sc->def->voltage_step * 1000); } if (uvolt > max_uvolt) return (EINVAL); *val = nval; return (0); } static int +axp2xx_regnode_status(struct regnode *regnode, int *status) +{ + struct axp2xx_reg_sc *sc; + uint8_t val; + + sc = regnode_get_softc(regnode); + + *status = 0; + axp2xx_read(sc->base_dev, sc->def->enable_reg, &val, 1); + if (val & sc->def->enable_mask) + *status = REGULATOR_STATUS_ENABLED; + + return (0); +} + +static int axp2xx_regnode_set_voltage(struct regnode *regnode, int min_uvolt, int max_uvolt, int *udelay) { struct axp2xx_reg_sc *sc; uint8_t val; sc = regnode_get_softc(regnode); if (!sc->def->voltage_step) return (ENXIO); if (axp2xx_regnode_voltage_to_reg(sc, min_uvolt, max_uvolt, &val) != 0) return (ERANGE); axp2xx_write(sc->base_dev, sc->def->voltage_reg, val); *udelay = 0; return (0); } static int axp2xx_regnode_get_voltage(struct regnode *regnode, int *uvolt) { struct axp2xx_reg_sc *sc; uint8_t val; sc = regnode_get_softc(regnode); if (!sc->def->voltage_step) return (ENXIO); axp2xx_read(sc->base_dev, sc->def->voltage_reg, &val, 1); axp2xx_regnode_reg_to_voltage(sc, val & sc->def->voltage_mask, uvolt); return (0); } static regnode_method_t axp2xx_regnode_methods[] = { /* Regulator interface */ REGNODEMETHOD(regnode_init, axp2xx_regnode_init), REGNODEMETHOD(regnode_enable, axp2xx_regnode_enable), + REGNODEMETHOD(regnode_status, axp2xx_regnode_status), REGNODEMETHOD(regnode_set_voltage, axp2xx_regnode_set_voltage), REGNODEMETHOD(regnode_get_voltage, axp2xx_regnode_get_voltage), REGNODEMETHOD(regnode_check_voltage, regnode_method_check_voltage), REGNODEMETHOD_END }; DEFINE_CLASS_1(axp2xx_regnode, axp2xx_regnode_class, axp2xx_regnode_methods, sizeof(struct axp2xx_reg_sc), regnode_class); static int axp2xx_sysctl(SYSCTL_HANDLER_ARGS) { struct axp2xx_softc *sc; device_t dev = arg1; enum axp2xx_sensor sensor = arg2; uint8_t data[2]; int val, error, i, found; sc = device_get_softc(dev); for (found = 0, i = 0; i < sc->nsensors; i++) { if (sc->sensors[i].id == sensor) { found = 1; break; } } if (found == 0) return (ENOENT); error = axp2xx_read(dev, sc->sensors[i].value_reg, data, 2); if (error != 0) return (error); val = ((data[0] & sc->sensors[i].h_value_mask) << sc->sensors[i].h_value_shift); val |= ((data[1] & sc->sensors[i].l_value_mask) << sc->sensors[i].l_value_shift); val *= sc->sensors[i].value_step; val += sc->sensors[i].value_convert; return sysctl_handle_opaque(oidp, &val, sizeof(val), req); } static void axp2xx_shutdown(void *devp, int howto) { device_t dev; if (!(howto & RB_POWEROFF)) return; dev = (device_t)devp; if (bootverbose) device_printf(dev, "Shutdown AXP2xx\n"); axp2xx_write(dev, AXP2XX_SHUTBAT, AXP2XX_SHUTBAT_SHUTDOWN); } static void axp2xx_intr(void *arg) { struct axp2xx_softc *sc; uint8_t reg; sc = arg; axp2xx_read(sc->dev, AXP2XX_IRQ1_STATUS, ®, 1); if (reg) { if (reg & AXP2XX_IRQ1_AC_OVERVOLT) devctl_notify("PMU", "AC", "overvoltage", NULL); if (reg & AXP2XX_IRQ1_VBUS_OVERVOLT) devctl_notify("PMU", "USB", "overvoltage", NULL); if (reg & AXP2XX_IRQ1_VBUS_LOW) devctl_notify("PMU", "USB", "undervoltage", NULL); if (reg & AXP2XX_IRQ1_AC_CONN) devctl_notify("PMU", "AC", "plugged", NULL); if (reg & AXP2XX_IRQ1_AC_DISCONN) devctl_notify("PMU", "AC", "unplugged", NULL); if (reg & AXP2XX_IRQ1_VBUS_CONN) devctl_notify("PMU", "USB", "plugged", NULL); if (reg & AXP2XX_IRQ1_VBUS_DISCONN) devctl_notify("PMU", "USB", "unplugged", NULL); axp2xx_write(sc->dev, AXP2XX_IRQ1_STATUS, AXP2XX_IRQ_ACK); } axp2xx_read(sc->dev, AXP2XX_IRQ2_STATUS, ®, 1); if (reg) { if (reg & AXP2XX_IRQ2_BATT_CHARGED) devctl_notify("PMU", "Battery", "charged", NULL); if (reg & AXP2XX_IRQ2_BATT_CHARGING) devctl_notify("PMU", "Battery", "charging", NULL); if (reg & AXP2XX_IRQ2_BATT_CONN) devctl_notify("PMU", "Battery", "connected", NULL); if (reg & AXP2XX_IRQ2_BATT_DISCONN) devctl_notify("PMU", "Battery", "disconnected", NULL); if (reg & AXP2XX_IRQ2_BATT_TEMP_LOW) devctl_notify("PMU", "Battery", "low temp", NULL); if (reg & AXP2XX_IRQ2_BATT_TEMP_OVER) devctl_notify("PMU", "Battery", "high temp", NULL); axp2xx_write(sc->dev, AXP2XX_IRQ2_STATUS, AXP2XX_IRQ_ACK); } axp2xx_read(sc->dev, AXP2XX_IRQ3_STATUS, ®, 1); if (reg) { if (reg & AXP2XX_IRQ3_PEK_SHORT) shutdown_nice(RB_POWEROFF); axp2xx_write(sc->dev, AXP2XX_IRQ3_STATUS, AXP2XX_IRQ_ACK); } axp2xx_read(sc->dev, AXP2XX_IRQ4_STATUS, ®, 1); if (reg) { axp2xx_write(sc->dev, AXP2XX_IRQ4_STATUS, AXP2XX_IRQ_ACK); } axp2xx_read(sc->dev, AXP2XX_IRQ5_STATUS, ®, 1); if (reg) { axp2xx_write(sc->dev, AXP2XX_IRQ5_STATUS, AXP2XX_IRQ_ACK); } } static device_t axp2xx_gpio_get_bus(device_t dev) { struct axp2xx_softc *sc; sc = device_get_softc(dev); return (sc->gpiodev); } static int axp2xx_gpio_pin_max(device_t dev, int *maxpin) { struct axp2xx_softc *sc; sc = device_get_softc(dev); *maxpin = sc->npins - 1; return (0); } static int axp2xx_gpio_pin_getname(device_t dev, uint32_t pin, char *name) { struct axp2xx_softc *sc; sc = device_get_softc(dev); if (pin >= sc->npins) return (EINVAL); snprintf(name, GPIOMAXNAME, "%s", axp209_pins[pin].name); return (0); } static int axp2xx_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) { struct axp2xx_softc *sc; sc = device_get_softc(dev); if (pin >= sc->npins) return (EINVAL); *caps = GPIO_PIN_INPUT | GPIO_PIN_OUTPUT; return (0); } static int axp2xx_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags) { struct axp2xx_softc *sc; uint8_t data, func; int error; sc = device_get_softc(dev); if (pin >= sc->npins) return (EINVAL); AXP_LOCK(sc); error = axp2xx_read(dev, sc->pins[pin].ctrl_reg, &data, 1); if (error == 0) { func = data & AXP2XX_GPIO_FUNC_MASK; if (func == AXP2XX_GPIO_FUNC_INPUT) *flags = GPIO_PIN_INPUT; else if (func == AXP2XX_GPIO_FUNC_DRVLO || func == AXP2XX_GPIO_FUNC_DRVHI) *flags = GPIO_PIN_OUTPUT; else *flags = 0; } AXP_UNLOCK(sc); return (error); } static int axp2xx_gpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) { struct axp2xx_softc *sc; uint8_t data; int error; sc = device_get_softc(dev); if (pin >= sc->npins) return (EINVAL); AXP_LOCK(sc); error = axp2xx_read(dev, sc->pins[pin].ctrl_reg, &data, 1); if (error == 0) { data &= ~AXP2XX_GPIO_FUNC_MASK; if ((flags & (GPIO_PIN_INPUT|GPIO_PIN_OUTPUT)) != 0) { if ((flags & GPIO_PIN_OUTPUT) == 0) data |= AXP2XX_GPIO_FUNC_INPUT; } error = axp2xx_write(dev, sc->pins[pin].ctrl_reg, data); } AXP_UNLOCK(sc); return (error); } static int axp2xx_gpio_pin_get(device_t dev, uint32_t pin, unsigned int *val) { struct axp2xx_softc *sc; uint8_t data, func; int error; sc = device_get_softc(dev); if (pin >= sc->npins) return (EINVAL); AXP_LOCK(sc); error = axp2xx_read(dev, sc->pins[pin].ctrl_reg, &data, 1); if (error == 0) { func = data & AXP2XX_GPIO_FUNC_MASK; switch (func) { case AXP2XX_GPIO_FUNC_DRVLO: *val = 0; break; case AXP2XX_GPIO_FUNC_DRVHI: *val = 1; break; case AXP2XX_GPIO_FUNC_INPUT: error = axp2xx_read(dev, sc->pins[pin].status_reg, &data, 1); if (error == 0) { *val = (data & sc->pins[pin].status_mask); *val >>= sc->pins[pin].status_shift; } break; default: error = EIO; break; } } AXP_UNLOCK(sc); return (error); } static int axp2xx_gpio_pin_set(device_t dev, uint32_t pin, unsigned int val) { struct axp2xx_softc *sc; uint8_t data, func; int error; sc = device_get_softc(dev); if (pin >= sc->npins) return (EINVAL); AXP_LOCK(sc); error = axp2xx_read(dev, sc->pins[pin].ctrl_reg, &data, 1); if (error == 0) { func = data & AXP2XX_GPIO_FUNC_MASK; switch (func) { case AXP2XX_GPIO_FUNC_DRVLO: case AXP2XX_GPIO_FUNC_DRVHI: /* GPIO2 can't be set to 1 */ if (pin == 2 && val == 1) { error = EINVAL; break; } data &= ~AXP2XX_GPIO_FUNC_MASK; data |= val; break; default: error = EIO; break; } } if (error == 0) error = axp2xx_write(dev, sc->pins[pin].ctrl_reg, data); AXP_UNLOCK(sc); return (error); } static int axp2xx_gpio_pin_toggle(device_t dev, uint32_t pin) { struct axp2xx_softc *sc; uint8_t data, func; int error; sc = device_get_softc(dev); if (pin >= sc->npins) return (EINVAL); AXP_LOCK(sc); error = axp2xx_read(dev, sc->pins[pin].ctrl_reg, &data, 1); if (error == 0) { func = data & AXP2XX_GPIO_FUNC_MASK; switch (func) { case AXP2XX_GPIO_FUNC_DRVLO: /* Pin 2 can't be set to 1*/ if (pin == 2) { error = EINVAL; break; } data &= ~AXP2XX_GPIO_FUNC_MASK; data |= AXP2XX_GPIO_FUNC_DRVHI; break; case AXP2XX_GPIO_FUNC_DRVHI: data &= ~AXP2XX_GPIO_FUNC_MASK; data |= AXP2XX_GPIO_FUNC_DRVLO; break; default: error = EIO; break; } } if (error == 0) error = axp2xx_write(dev, sc->pins[pin].ctrl_reg, data); AXP_UNLOCK(sc); return (error); } static int axp2xx_gpio_map_gpios(device_t bus, phandle_t dev, phandle_t gparent, int gcells, pcell_t *gpios, uint32_t *pin, uint32_t *flags) { struct axp2xx_softc *sc; sc = device_get_softc(bus); if (gpios[0] >= sc->npins) return (EINVAL); *pin = gpios[0]; *flags = gpios[1]; return (0); } static phandle_t axp2xx_get_node(device_t dev, device_t bus) { return (ofw_bus_get_node(dev)); } static struct axp2xx_reg_sc * axp2xx_reg_attach(device_t dev, phandle_t node, struct axp2xx_regdef *def) { struct axp2xx_reg_sc *reg_sc; struct regnode_init_def initdef; struct regnode *regnode; memset(&initdef, 0, sizeof(initdef)); if (regulator_parse_ofw_stdparam(dev, node, &initdef) != 0) { device_printf(dev, "cannot create regulator\n"); return (NULL); } if (initdef.std_param.min_uvolt == 0) initdef.std_param.min_uvolt = def->voltage_min * 1000; if (initdef.std_param.max_uvolt == 0) initdef.std_param.max_uvolt = def->voltage_max * 1000; initdef.id = def->id; initdef.ofw_node = node; regnode = regnode_create(dev, &axp2xx_regnode_class, &initdef); if (regnode == NULL) { device_printf(dev, "cannot create regulator\n"); return (NULL); } reg_sc = regnode_get_softc(regnode); reg_sc->regnode = regnode; reg_sc->base_dev = dev; reg_sc->def = def; reg_sc->xref = OF_xref_from_node(node); reg_sc->param = regnode_get_stdparam(regnode); regnode_register(regnode); return (reg_sc); } static int axp2xx_regdev_map(device_t dev, phandle_t xref, int ncells, pcell_t *cells, intptr_t *num) { struct axp2xx_softc *sc; int i; sc = device_get_softc(dev); for (i = 0; i < sc->nregs; i++) { if (sc->regs[i] == NULL) continue; if (sc->regs[i]->xref == xref) { *num = sc->regs[i]->def->id; return (0); } } return (ENXIO); } static void axp2xx_start(void *pdev) { device_t dev; struct axp2xx_softc *sc; const char *pwr_name[] = {"Battery", "AC", "USB", "AC and USB"}; int i; uint8_t reg, data; uint8_t pwr_src; dev = pdev; sc = device_get_softc(dev); sc->dev = dev; if (bootverbose) { /* * Read the Power State register. * Shift the AC presence into bit 0. * Shift the Battery presence into bit 1. */ axp2xx_read(dev, AXP2XX_PSR, &data, 1); pwr_src = ((data & AXP2XX_PSR_ACIN) >> AXP2XX_PSR_ACIN_SHIFT) | ((data & AXP2XX_PSR_VBUS) >> (AXP2XX_PSR_VBUS_SHIFT - 1)); device_printf(dev, "Powered by %s\n", pwr_name[pwr_src]); } /* Only enable interrupts that we are interested in */ axp2xx_write(dev, AXP2XX_IRQ1_ENABLE, AXP2XX_IRQ1_AC_OVERVOLT | AXP2XX_IRQ1_AC_DISCONN | AXP2XX_IRQ1_AC_CONN | AXP2XX_IRQ1_VBUS_OVERVOLT | AXP2XX_IRQ1_VBUS_DISCONN | AXP2XX_IRQ1_VBUS_CONN); axp2xx_write(dev, AXP2XX_IRQ2_ENABLE, AXP2XX_IRQ2_BATT_CONN | AXP2XX_IRQ2_BATT_DISCONN | AXP2XX_IRQ2_BATT_CHARGE_ACCT_ON | AXP2XX_IRQ2_BATT_CHARGE_ACCT_OFF | AXP2XX_IRQ2_BATT_CHARGING | AXP2XX_IRQ2_BATT_CHARGED | AXP2XX_IRQ2_BATT_TEMP_OVER | AXP2XX_IRQ2_BATT_TEMP_LOW); axp2xx_write(dev, AXP2XX_IRQ3_ENABLE, AXP2XX_IRQ3_PEK_SHORT | AXP2XX_IRQ3_PEK_LONG); axp2xx_write(dev, AXP2XX_IRQ4_ENABLE, AXP2XX_IRQ4_APS_LOW_2); axp2xx_write(dev, AXP2XX_IRQ5_ENABLE, 0x0); EVENTHANDLER_REGISTER(shutdown_final, axp2xx_shutdown, dev, SHUTDOWN_PRI_LAST); /* Enable ADC sensors */ for (i = 0; i < sc->nsensors; i++) { if (axp2xx_read(dev, sc->sensors[i].enable_reg, ®, 1) == -1) { device_printf(dev, "Cannot enable sensor '%s'\n", sc->sensors[i].name); continue; } reg |= sc->sensors[i].enable_mask; if (axp2xx_write(dev, sc->sensors[i].enable_reg, reg) == -1) { device_printf(dev, "Cannot enable sensor '%s'\n", sc->sensors[i].name); continue; } SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, sc->sensors[i].name, CTLTYPE_INT | CTLFLAG_RD, dev, sc->sensors[i].id, axp2xx_sysctl, sc->sensors[i].format, sc->sensors[i].desc); } if ((bus_setup_intr(dev, sc->res[0], INTR_TYPE_MISC | INTR_MPSAFE, NULL, axp2xx_intr, sc, &sc->intrcookie))) device_printf(dev, "unable to register interrupt handler\n"); config_intrhook_disestablish(&sc->intr_hook); } static int axp2xx_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); switch (ofw_bus_search_compatible(dev, compat_data)->ocd_data) { case AXP209: device_set_desc(dev, "X-Powers AXP209 Power Management Unit"); break; case AXP221: device_set_desc(dev, "X-Powers AXP221 Power Management Unit"); break; default: return (ENXIO); } return (BUS_PROBE_DEFAULT); } static int axp2xx_attach(device_t dev) { struct axp2xx_softc *sc; struct axp2xx_reg_sc *reg; struct axp2xx_regdef *regdefs; phandle_t rnode, child; int i; sc = device_get_softc(dev); mtx_init(&sc->mtx, device_get_nameunit(dev), NULL, MTX_DEF); if (bus_alloc_resources(dev, axp_res_spec, sc->res) != 0) { device_printf(dev, "can't allocate device resources\n"); return (ENXIO); } sc->type = ofw_bus_search_compatible(dev, compat_data)->ocd_data; switch (sc->type) { case AXP209: sc->pins = axp209_pins; sc->npins = nitems(axp209_pins); sc->gpiodev = gpiobus_attach_bus(dev); sc->sensors = axp209_sensors; sc->nsensors = nitems(axp209_sensors); regdefs = axp209_regdefs; sc->nregs = nitems(axp209_regdefs); break; case AXP221: sc->pins = axp221_pins; sc->npins = nitems(axp221_pins); sc->gpiodev = gpiobus_attach_bus(dev); sc->sensors = axp221_sensors; sc->nsensors = nitems(axp221_sensors); regdefs = axp221_regdefs; sc->nregs = nitems(axp221_regdefs); break; } sc->regs = malloc(sizeof(struct axp2xx_reg_sc *) * sc->nregs, M_AXP2XX_REG, M_WAITOK | M_ZERO); sc->intr_hook.ich_func = axp2xx_start; sc->intr_hook.ich_arg = dev; if (config_intrhook_establish(&sc->intr_hook) != 0) return (ENOMEM); /* Attach known regulators that exist in the DT */ rnode = ofw_bus_find_child(ofw_bus_get_node(dev), "regulators"); if (rnode > 0) { for (i = 0; i < sc->nregs; i++) { child = ofw_bus_find_child(rnode, regdefs[i].name); if (child == 0) continue; reg = axp2xx_reg_attach(dev, child, ®defs[i]); if (reg == NULL) { device_printf(dev, "cannot attach regulator %s\n", regdefs[i].name); continue; } sc->regs[i] = reg; if (bootverbose) device_printf(dev, "Regulator %s attached\n", regdefs[i].name); } } return (0); } static device_method_t axp2xx_methods[] = { DEVMETHOD(device_probe, axp2xx_probe), DEVMETHOD(device_attach, axp2xx_attach), /* GPIO interface */ DEVMETHOD(gpio_get_bus, axp2xx_gpio_get_bus), DEVMETHOD(gpio_pin_max, axp2xx_gpio_pin_max), DEVMETHOD(gpio_pin_getname, axp2xx_gpio_pin_getname), DEVMETHOD(gpio_pin_getcaps, axp2xx_gpio_pin_getcaps), DEVMETHOD(gpio_pin_getflags, axp2xx_gpio_pin_getflags), DEVMETHOD(gpio_pin_setflags, axp2xx_gpio_pin_setflags), DEVMETHOD(gpio_pin_get, axp2xx_gpio_pin_get), DEVMETHOD(gpio_pin_set, axp2xx_gpio_pin_set), DEVMETHOD(gpio_pin_toggle, axp2xx_gpio_pin_toggle), DEVMETHOD(gpio_map_gpios, axp2xx_gpio_map_gpios), /* Regdev interface */ DEVMETHOD(regdev_map, axp2xx_regdev_map), /* OFW bus interface */ DEVMETHOD(ofw_bus_get_node, axp2xx_get_node), DEVMETHOD_END }; static driver_t axp2xx_driver = { "axp2xx_pmu", axp2xx_methods, sizeof(struct axp2xx_softc), }; static devclass_t axp2xx_devclass; extern devclass_t ofwgpiobus_devclass, gpioc_devclass; extern driver_t ofw_gpiobus_driver, gpioc_driver; EARLY_DRIVER_MODULE(axp2xx, iicbus, axp2xx_driver, axp2xx_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE); EARLY_DRIVER_MODULE(ofw_gpiobus, axp2xx_pmu, ofw_gpiobus_driver, ofwgpiobus_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE); DRIVER_MODULE(gpioc, axp2xx_pmu, gpioc_driver, gpioc_devclass, 0, 0); MODULE_VERSION(axp2xx, 1); MODULE_DEPEND(axp2xx, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); Index: stable/12/sys/arm/allwinner/axp81x.c =================================================================== --- stable/12/sys/arm/allwinner/axp81x.c (revision 362349) +++ stable/12/sys/arm/allwinner/axp81x.c (revision 362350) @@ -1,1616 +1,1652 @@ /*- * Copyright (c) 2018 Emmanuel Vadot * Copyright (c) 2016 Jared McNeill * 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. * * $FreeBSD$ */ /* * X-Powers AXP803/813/818 PMU for Allwinner SoCs */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gpio_if.h" #include "iicbus_if.h" #include "regdev_if.h" MALLOC_DEFINE(M_AXP8XX_REG, "AXP8xx regulator", "AXP8xx power regulator"); #define AXP_POWERSRC 0x00 #define AXP_POWERSRC_ACIN (1 << 7) #define AXP_POWERSRC_VBUS (1 << 5) #define AXP_POWERSRC_VBAT (1 << 3) #define AXP_POWERSRC_CHARING (1 << 2) /* Charging Direction */ #define AXP_POWERSRC_SHORTED (1 << 1) #define AXP_POWERSRC_STARTUP (1 << 0) #define AXP_POWERMODE 0x01 #define AXP_POWERMODE_BAT_CHARGING (1 << 6) #define AXP_POWERMODE_BAT_PRESENT (1 << 5) #define AXP_POWERMODE_BAT_VALID (1 << 4) #define AXP_ICTYPE 0x03 #define AXP_POWERCTL1 0x10 #define AXP_POWERCTL1_DCDC7 (1 << 6) /* AXP813/818 only */ #define AXP_POWERCTL1_DCDC6 (1 << 5) #define AXP_POWERCTL1_DCDC5 (1 << 4) #define AXP_POWERCTL1_DCDC4 (1 << 3) #define AXP_POWERCTL1_DCDC3 (1 << 2) #define AXP_POWERCTL1_DCDC2 (1 << 1) #define AXP_POWERCTL1_DCDC1 (1 << 0) #define AXP_POWERCTL2 0x12 #define AXP_POWERCTL2_DC1SW (1 << 7) /* AXP803 only */ #define AXP_POWERCTL2_DLDO4 (1 << 6) #define AXP_POWERCTL2_DLDO3 (1 << 5) #define AXP_POWERCTL2_DLDO2 (1 << 4) #define AXP_POWERCTL2_DLDO1 (1 << 3) #define AXP_POWERCTL2_ELDO3 (1 << 2) #define AXP_POWERCTL2_ELDO2 (1 << 1) #define AXP_POWERCTL2_ELDO1 (1 << 0) #define AXP_POWERCTL3 0x13 #define AXP_POWERCTL3_ALDO3 (1 << 7) #define AXP_POWERCTL3_ALDO2 (1 << 6) #define AXP_POWERCTL3_ALDO1 (1 << 5) #define AXP_POWERCTL3_FLDO3 (1 << 4) /* AXP813/818 only */ #define AXP_POWERCTL3_FLDO2 (1 << 3) #define AXP_POWERCTL3_FLDO1 (1 << 2) #define AXP_VOLTCTL_DLDO1 0x15 #define AXP_VOLTCTL_DLDO2 0x16 #define AXP_VOLTCTL_DLDO3 0x17 #define AXP_VOLTCTL_DLDO4 0x18 #define AXP_VOLTCTL_ELDO1 0x19 #define AXP_VOLTCTL_ELDO2 0x1A #define AXP_VOLTCTL_ELDO3 0x1B #define AXP_VOLTCTL_FLDO1 0x1C #define AXP_VOLTCTL_FLDO2 0x1D #define AXP_VOLTCTL_DCDC1 0x20 #define AXP_VOLTCTL_DCDC2 0x21 #define AXP_VOLTCTL_DCDC3 0x22 #define AXP_VOLTCTL_DCDC4 0x23 #define AXP_VOLTCTL_DCDC5 0x24 #define AXP_VOLTCTL_DCDC6 0x25 #define AXP_VOLTCTL_DCDC7 0x26 #define AXP_VOLTCTL_ALDO1 0x28 #define AXP_VOLTCTL_ALDO2 0x29 #define AXP_VOLTCTL_ALDO3 0x2A #define AXP_VOLTCTL_STATUS (1 << 7) #define AXP_VOLTCTL_MASK 0x7f #define AXP_POWERBAT 0x32 #define AXP_POWERBAT_SHUTDOWN (1 << 7) #define AXP_CHARGERCTL1 0x33 #define AXP_CHARGERCTL1_MIN 0 #define AXP_CHARGERCTL1_MAX 13 #define AXP_CHARGERCTL1_CMASK 0xf #define AXP_IRQEN1 0x40 #define AXP_IRQEN1_ACIN_HI (1 << 6) #define AXP_IRQEN1_ACIN_LO (1 << 5) #define AXP_IRQEN1_VBUS_HI (1 << 3) #define AXP_IRQEN1_VBUS_LO (1 << 2) #define AXP_IRQEN2 0x41 #define AXP_IRQEN2_BAT_IN (1 << 7) #define AXP_IRQEN2_BAT_NO (1 << 6) #define AXP_IRQEN2_BATCHGC (1 << 3) #define AXP_IRQEN2_BATCHGD (1 << 2) #define AXP_IRQEN3 0x42 #define AXP_IRQEN4 0x43 #define AXP_IRQEN4_BATLVL_LO1 (1 << 1) #define AXP_IRQEN4_BATLVL_LO0 (1 << 0) #define AXP_IRQEN5 0x44 #define AXP_IRQEN5_POKSIRQ (1 << 4) #define AXP_IRQEN5_POKLIRQ (1 << 3) #define AXP_IRQEN6 0x45 #define AXP_IRQSTAT1 0x48 #define AXP_IRQSTAT1_ACIN_HI (1 << 6) #define AXP_IRQSTAT1_ACIN_LO (1 << 5) #define AXP_IRQSTAT1_VBUS_HI (1 << 3) #define AXP_IRQSTAT1_VBUS_LO (1 << 2) #define AXP_IRQSTAT2 0x49 #define AXP_IRQSTAT2_BAT_IN (1 << 7) #define AXP_IRQSTAT2_BAT_NO (1 << 6) #define AXP_IRQSTAT2_BATCHGC (1 << 3) #define AXP_IRQSTAT2_BATCHGD (1 << 2) #define AXP_IRQSTAT3 0x4a #define AXP_IRQSTAT4 0x4b #define AXP_IRQSTAT4_BATLVL_LO1 (1 << 1) #define AXP_IRQSTAT4_BATLVL_LO0 (1 << 0) #define AXP_IRQSTAT5 0x4c #define AXP_IRQSTAT5_POKSIRQ (1 << 4) #define AXP_IRQEN5_POKLIRQ (1 << 3) #define AXP_IRQSTAT6 0x4d #define AXP_BATSENSE_HI 0x78 #define AXP_BATSENSE_LO 0x79 #define AXP_BATCHG_HI 0x7a #define AXP_BATCHG_LO 0x7b #define AXP_BATDISCHG_HI 0x7c #define AXP_BATDISCHG_LO 0x7d #define AXP_GPIO0_CTRL 0x90 #define AXP_GPIO0LDO_CTRL 0x91 #define AXP_GPIO1_CTRL 0x92 #define AXP_GPIO1LDO_CTRL 0x93 #define AXP_GPIO_FUNC (0x7 << 0) #define AXP_GPIO_FUNC_SHIFT 0 #define AXP_GPIO_FUNC_DRVLO 0 #define AXP_GPIO_FUNC_DRVHI 1 #define AXP_GPIO_FUNC_INPUT 2 #define AXP_GPIO_FUNC_LDO_ON 3 #define AXP_GPIO_FUNC_LDO_OFF 4 #define AXP_GPIO_SIGBIT 0x94 #define AXP_GPIO_PD 0x97 #define AXP_FUEL_GAUGECTL 0xb8 #define AXP_FUEL_GAUGECTL_EN (1 << 7) #define AXP_BAT_CAP 0xb9 #define AXP_BAT_CAP_VALID (1 << 7) #define AXP_BAT_CAP_PERCENT 0x7f #define AXP_BAT_MAX_CAP_HI 0xe0 #define AXP_BAT_MAX_CAP_VALID (1 << 7) #define AXP_BAT_MAX_CAP_LO 0xe1 #define AXP_BAT_COULOMB_HI 0xe2 #define AXP_BAT_COULOMB_VALID (1 << 7) #define AXP_BAT_COULOMB_LO 0xe3 #define AXP_BAT_CAP_WARN 0xe6 #define AXP_BAT_CAP_WARN_LV1 0xf0 /* Bits 4, 5, 6, 7 */ #define AXP_BAP_CAP_WARN_LV1BASE 5 /* 5-20%, 1% per step */ #define AXP_BAT_CAP_WARN_LV2 0xf /* Bits 0, 1, 2, 3 */ /* Sensor conversion macros */ #define AXP_SENSOR_BAT_H(hi) ((hi) << 4) #define AXP_SENSOR_BAT_L(lo) ((lo) & 0xf) #define AXP_SENSOR_COULOMB(hi, lo) (((hi & ~(1 << 7)) << 8) | (lo)) static const struct { const char *name; uint8_t ctrl_reg; } axp8xx_pins[] = { { "GPIO0", AXP_GPIO0_CTRL }, { "GPIO1", AXP_GPIO1_CTRL }, }; enum AXP8XX_TYPE { AXP803 = 1, AXP813, }; static struct ofw_compat_data compat_data[] = { { "x-powers,axp803", AXP803 }, { "x-powers,axp813", AXP813 }, { "x-powers,axp818", AXP813 }, { NULL, 0 } }; static struct resource_spec axp8xx_spec[] = { { SYS_RES_IRQ, 0, RF_ACTIVE }, { -1, 0 } }; struct axp8xx_regdef { intptr_t id; char *name; char *supply_name; uint8_t enable_reg; uint8_t enable_mask; uint8_t enable_value; uint8_t disable_value; uint8_t voltage_reg; int voltage_min; int voltage_max; int voltage_step1; int voltage_nstep1; int voltage_step2; int voltage_nstep2; }; enum axp8xx_reg_id { AXP8XX_REG_ID_DCDC1 = 100, AXP8XX_REG_ID_DCDC2, AXP8XX_REG_ID_DCDC3, AXP8XX_REG_ID_DCDC4, AXP8XX_REG_ID_DCDC5, AXP8XX_REG_ID_DCDC6, AXP813_REG_ID_DCDC7, AXP803_REG_ID_DC1SW, AXP8XX_REG_ID_DLDO1, AXP8XX_REG_ID_DLDO2, AXP8XX_REG_ID_DLDO3, AXP8XX_REG_ID_DLDO4, AXP8XX_REG_ID_ELDO1, AXP8XX_REG_ID_ELDO2, AXP8XX_REG_ID_ELDO3, AXP8XX_REG_ID_ALDO1, AXP8XX_REG_ID_ALDO2, AXP8XX_REG_ID_ALDO3, AXP8XX_REG_ID_FLDO1, AXP8XX_REG_ID_FLDO2, AXP813_REG_ID_FLDO3, AXP8XX_REG_ID_GPIO0_LDO, AXP8XX_REG_ID_GPIO1_LDO, }; static struct axp8xx_regdef axp803_regdefs[] = { { .id = AXP803_REG_ID_DC1SW, .name = "dc1sw", .enable_reg = AXP_POWERCTL2, .enable_mask = (uint8_t) AXP_POWERCTL2_DC1SW, .enable_value = AXP_POWERCTL2_DC1SW, }, }; static struct axp8xx_regdef axp813_regdefs[] = { { .id = AXP813_REG_ID_DCDC7, .name = "dcdc7", .enable_reg = AXP_POWERCTL1, .enable_mask = (uint8_t) AXP_POWERCTL1_DCDC7, .enable_value = AXP_POWERCTL1_DCDC7, .voltage_reg = AXP_VOLTCTL_DCDC7, .voltage_min = 600, .voltage_max = 1520, .voltage_step1 = 10, .voltage_nstep1 = 50, .voltage_step2 = 20, .voltage_nstep2 = 21, }, }; static struct axp8xx_regdef axp8xx_common_regdefs[] = { { .id = AXP8XX_REG_ID_DCDC1, .name = "dcdc1", .enable_reg = AXP_POWERCTL1, .enable_mask = (uint8_t) AXP_POWERCTL1_DCDC1, .enable_value = AXP_POWERCTL1_DCDC1, .voltage_reg = AXP_VOLTCTL_DCDC1, .voltage_min = 1600, .voltage_max = 3400, .voltage_step1 = 100, .voltage_nstep1 = 18, }, { .id = AXP8XX_REG_ID_DCDC2, .name = "dcdc2", .enable_reg = AXP_POWERCTL1, .enable_mask = (uint8_t) AXP_POWERCTL1_DCDC2, .enable_value = AXP_POWERCTL1_DCDC2, .voltage_reg = AXP_VOLTCTL_DCDC2, .voltage_min = 500, .voltage_max = 1300, .voltage_step1 = 10, .voltage_nstep1 = 70, .voltage_step2 = 20, .voltage_nstep2 = 5, }, { .id = AXP8XX_REG_ID_DCDC3, .name = "dcdc3", .enable_reg = AXP_POWERCTL1, .enable_mask = (uint8_t) AXP_POWERCTL1_DCDC3, .enable_value = AXP_POWERCTL1_DCDC3, .voltage_reg = AXP_VOLTCTL_DCDC3, .voltage_min = 500, .voltage_max = 1300, .voltage_step1 = 10, .voltage_nstep1 = 70, .voltage_step2 = 20, .voltage_nstep2 = 5, }, { .id = AXP8XX_REG_ID_DCDC4, .name = "dcdc4", .enable_reg = AXP_POWERCTL1, .enable_mask = (uint8_t) AXP_POWERCTL1_DCDC4, .enable_value = AXP_POWERCTL1_DCDC4, .voltage_reg = AXP_VOLTCTL_DCDC4, .voltage_min = 500, .voltage_max = 1300, .voltage_step1 = 10, .voltage_nstep1 = 70, .voltage_step2 = 20, .voltage_nstep2 = 5, }, { .id = AXP8XX_REG_ID_DCDC5, .name = "dcdc5", .enable_reg = AXP_POWERCTL1, .enable_mask = (uint8_t) AXP_POWERCTL1_DCDC5, .enable_value = AXP_POWERCTL1_DCDC5, .voltage_reg = AXP_VOLTCTL_DCDC5, .voltage_min = 800, .voltage_max = 1840, .voltage_step1 = 10, .voltage_nstep1 = 42, .voltage_step2 = 20, .voltage_nstep2 = 36, }, { .id = AXP8XX_REG_ID_DCDC6, .name = "dcdc6", .enable_reg = AXP_POWERCTL1, .enable_mask = (uint8_t) AXP_POWERCTL1_DCDC6, .enable_value = AXP_POWERCTL1_DCDC6, .voltage_reg = AXP_VOLTCTL_DCDC6, .voltage_min = 600, .voltage_max = 1520, .voltage_step1 = 10, .voltage_nstep1 = 50, .voltage_step2 = 20, .voltage_nstep2 = 21, }, { .id = AXP8XX_REG_ID_DLDO1, .name = "dldo1", .enable_reg = AXP_POWERCTL2, .enable_mask = (uint8_t) AXP_POWERCTL2_DLDO1, .enable_value = AXP_POWERCTL2_DLDO1, .voltage_reg = AXP_VOLTCTL_DLDO1, .voltage_min = 700, .voltage_max = 3300, .voltage_step1 = 100, .voltage_nstep1 = 26, }, { .id = AXP8XX_REG_ID_DLDO2, .name = "dldo2", .enable_reg = AXP_POWERCTL2, .enable_mask = (uint8_t) AXP_POWERCTL2_DLDO2, .enable_value = AXP_POWERCTL2_DLDO2, .voltage_reg = AXP_VOLTCTL_DLDO2, .voltage_min = 700, .voltage_max = 4200, .voltage_step1 = 100, .voltage_nstep1 = 27, .voltage_step2 = 200, .voltage_nstep2 = 4, }, { .id = AXP8XX_REG_ID_DLDO3, .name = "dldo3", .enable_reg = AXP_POWERCTL2, .enable_mask = (uint8_t) AXP_POWERCTL2_DLDO3, .enable_value = AXP_POWERCTL2_DLDO3, .voltage_reg = AXP_VOLTCTL_DLDO3, .voltage_min = 700, .voltage_max = 3300, .voltage_step1 = 100, .voltage_nstep1 = 26, }, { .id = AXP8XX_REG_ID_DLDO4, .name = "dldo4", .enable_reg = AXP_POWERCTL2, .enable_mask = (uint8_t) AXP_POWERCTL2_DLDO4, .enable_value = AXP_POWERCTL2_DLDO4, .voltage_reg = AXP_VOLTCTL_DLDO4, .voltage_min = 700, .voltage_max = 3300, .voltage_step1 = 100, .voltage_nstep1 = 26, }, { .id = AXP8XX_REG_ID_ALDO1, .name = "aldo1", .enable_reg = AXP_POWERCTL3, .enable_mask = (uint8_t) AXP_POWERCTL3_ALDO1, .enable_value = AXP_POWERCTL3_ALDO1, + .voltage_reg = AXP_VOLTCTL_ALDO1, .voltage_min = 700, .voltage_max = 3300, .voltage_step1 = 100, .voltage_nstep1 = 26, }, { .id = AXP8XX_REG_ID_ALDO2, .name = "aldo2", .enable_reg = AXP_POWERCTL3, .enable_mask = (uint8_t) AXP_POWERCTL3_ALDO2, .enable_value = AXP_POWERCTL3_ALDO2, + .voltage_reg = AXP_VOLTCTL_ALDO2, .voltage_min = 700, .voltage_max = 3300, .voltage_step1 = 100, .voltage_nstep1 = 26, }, { .id = AXP8XX_REG_ID_ALDO3, .name = "aldo3", .enable_reg = AXP_POWERCTL3, .enable_mask = (uint8_t) AXP_POWERCTL3_ALDO3, .enable_value = AXP_POWERCTL3_ALDO3, + .voltage_reg = AXP_VOLTCTL_ALDO3, .voltage_min = 700, .voltage_max = 3300, .voltage_step1 = 100, .voltage_nstep1 = 26, }, { .id = AXP8XX_REG_ID_ELDO1, .name = "eldo1", .enable_reg = AXP_POWERCTL2, .enable_mask = (uint8_t) AXP_POWERCTL2_ELDO1, .enable_value = AXP_POWERCTL2_ELDO1, + .voltage_reg = AXP_VOLTCTL_ELDO1, .voltage_min = 700, .voltage_max = 1900, .voltage_step1 = 50, .voltage_nstep1 = 24, }, { .id = AXP8XX_REG_ID_ELDO2, .name = "eldo2", .enable_reg = AXP_POWERCTL2, .enable_mask = (uint8_t) AXP_POWERCTL2_ELDO2, .enable_value = AXP_POWERCTL2_ELDO2, + .voltage_reg = AXP_VOLTCTL_ELDO2, .voltage_min = 700, .voltage_max = 1900, .voltage_step1 = 50, .voltage_nstep1 = 24, }, { .id = AXP8XX_REG_ID_ELDO3, .name = "eldo3", .enable_reg = AXP_POWERCTL2, .enable_mask = (uint8_t) AXP_POWERCTL2_ELDO3, .enable_value = AXP_POWERCTL2_ELDO3, + .voltage_reg = AXP_VOLTCTL_ELDO3, .voltage_min = 700, .voltage_max = 1900, .voltage_step1 = 50, .voltage_nstep1 = 24, }, { .id = AXP8XX_REG_ID_FLDO1, .name = "fldo1", .enable_reg = AXP_POWERCTL3, .enable_mask = (uint8_t) AXP_POWERCTL3_FLDO1, .enable_value = AXP_POWERCTL3_FLDO1, + .voltage_reg = AXP_VOLTCTL_FLDO1, .voltage_min = 700, .voltage_max = 1450, .voltage_step1 = 50, .voltage_nstep1 = 15, }, { .id = AXP8XX_REG_ID_FLDO2, .name = "fldo2", .enable_reg = AXP_POWERCTL3, .enable_mask = (uint8_t) AXP_POWERCTL3_FLDO2, .enable_value = AXP_POWERCTL3_FLDO2, + .voltage_reg = AXP_VOLTCTL_FLDO2, .voltage_min = 700, .voltage_max = 1450, .voltage_step1 = 50, .voltage_nstep1 = 15, }, { .id = AXP8XX_REG_ID_GPIO0_LDO, .name = "ldo-io0", .enable_reg = AXP_GPIO0_CTRL, .enable_mask = (uint8_t) AXP_GPIO_FUNC, .enable_value = AXP_GPIO_FUNC_LDO_ON, .disable_value = AXP_GPIO_FUNC_LDO_OFF, .voltage_reg = AXP_GPIO0LDO_CTRL, .voltage_min = 700, .voltage_max = 3300, .voltage_step1 = 100, .voltage_nstep1 = 26, }, { .id = AXP8XX_REG_ID_GPIO1_LDO, .name = "ldo-io1", .enable_reg = AXP_GPIO1_CTRL, .enable_mask = (uint8_t) AXP_GPIO_FUNC, .enable_value = AXP_GPIO_FUNC_LDO_ON, .disable_value = AXP_GPIO_FUNC_LDO_OFF, .voltage_reg = AXP_GPIO1LDO_CTRL, .voltage_min = 700, .voltage_max = 3300, .voltage_step1 = 100, .voltage_nstep1 = 26, }, }; enum axp8xx_sensor { AXP_SENSOR_ACIN_PRESENT, AXP_SENSOR_VBUS_PRESENT, AXP_SENSOR_BATT_PRESENT, AXP_SENSOR_BATT_CHARGING, AXP_SENSOR_BATT_CHARGE_STATE, AXP_SENSOR_BATT_VOLTAGE, AXP_SENSOR_BATT_CHARGE_CURRENT, AXP_SENSOR_BATT_DISCHARGE_CURRENT, AXP_SENSOR_BATT_CAPACITY_PERCENT, AXP_SENSOR_BATT_MAXIMUM_CAPACITY, AXP_SENSOR_BATT_CURRENT_CAPACITY, }; enum battery_capacity_state { BATT_CAPACITY_NORMAL = 1, /* normal cap in battery */ BATT_CAPACITY_WARNING, /* warning cap in battery */ BATT_CAPACITY_CRITICAL, /* critical cap in battery */ BATT_CAPACITY_HIGH, /* high cap in battery */ BATT_CAPACITY_MAX, /* maximum cap in battery */ BATT_CAPACITY_LOW /* low cap in battery */ }; struct axp8xx_sensors { int id; const char *name; const char *desc; const char *format; }; static const struct axp8xx_sensors axp8xx_common_sensors[] = { { .id = AXP_SENSOR_ACIN_PRESENT, .name = "acin", .format = "I", .desc = "ACIN Present", }, { .id = AXP_SENSOR_VBUS_PRESENT, .name = "vbus", .format = "I", .desc = "VBUS Present", }, { .id = AXP_SENSOR_BATT_PRESENT, .name = "bat", .format = "I", .desc = "Battery Present", }, { .id = AXP_SENSOR_BATT_CHARGING, .name = "batcharging", .format = "I", .desc = "Battery Charging", }, { .id = AXP_SENSOR_BATT_CHARGE_STATE, .name = "batchargestate", .format = "I", .desc = "Battery Charge State", }, { .id = AXP_SENSOR_BATT_VOLTAGE, .name = "batvolt", .format = "I", .desc = "Battery Voltage", }, { .id = AXP_SENSOR_BATT_CHARGE_CURRENT, .name = "batchargecurrent", .format = "I", .desc = "Average Battery Charging Current", }, { .id = AXP_SENSOR_BATT_DISCHARGE_CURRENT, .name = "batdischargecurrent", .format = "I", .desc = "Average Battery Discharging Current", }, { .id = AXP_SENSOR_BATT_CAPACITY_PERCENT, .name = "batcapacitypercent", .format = "I", .desc = "Battery Capacity Percentage", }, { .id = AXP_SENSOR_BATT_MAXIMUM_CAPACITY, .name = "batmaxcapacity", .format = "I", .desc = "Battery Maximum Capacity", }, { .id = AXP_SENSOR_BATT_CURRENT_CAPACITY, .name = "batcurrentcapacity", .format = "I", .desc = "Battery Current Capacity", }, }; struct axp8xx_config { const char *name; int batsense_step; /* uV */ int charge_step; /* uA */ int discharge_step; /* uA */ int maxcap_step; /* uAh */ int coulomb_step; /* uAh */ }; static struct axp8xx_config axp803_config = { .name = "AXP803", .batsense_step = 1100, .charge_step = 1000, .discharge_step = 1000, .maxcap_step = 1456, .coulomb_step = 1456, }; struct axp8xx_softc; struct axp8xx_reg_sc { struct regnode *regnode; device_t base_dev; struct axp8xx_regdef *def; phandle_t xref; struct regnode_std_param *param; }; struct axp8xx_softc { struct resource *res; uint16_t addr; void *ih; device_t gpiodev; struct mtx mtx; int busy; int type; /* Configs */ const struct axp8xx_config *config; /* Sensors */ const struct axp8xx_sensors *sensors; int nsensors; /* Regulators */ struct axp8xx_reg_sc **regs; int nregs; /* Warning, shutdown thresholds */ int warn_thres; int shut_thres; }; #define AXP_LOCK(sc) mtx_lock(&(sc)->mtx) #define AXP_UNLOCK(sc) mtx_unlock(&(sc)->mtx) +static int axp8xx_regnode_set_voltage(struct regnode *regnode, int min_uvolt, + int max_uvolt, int *udelay); static int axp8xx_read(device_t dev, uint8_t reg, uint8_t *data, uint8_t size) { struct axp8xx_softc *sc; struct iic_msg msg[2]; sc = device_get_softc(dev); msg[0].slave = sc->addr; msg[0].flags = IIC_M_WR; msg[0].len = 1; msg[0].buf = ® msg[1].slave = sc->addr; msg[1].flags = IIC_M_RD; msg[1].len = size; msg[1].buf = data; return (iicbus_transfer(dev, msg, 2)); } static int axp8xx_write(device_t dev, uint8_t reg, uint8_t val) { struct axp8xx_softc *sc; struct iic_msg msg[2]; sc = device_get_softc(dev); msg[0].slave = sc->addr; msg[0].flags = IIC_M_WR; msg[0].len = 1; msg[0].buf = ® msg[1].slave = sc->addr; msg[1].flags = IIC_M_WR; msg[1].len = 1; msg[1].buf = &val; return (iicbus_transfer(dev, msg, 2)); } static int +axp8xx_regnode_init(struct regnode *regnode) +{ + struct axp8xx_reg_sc *sc; + struct regnode_std_param *param; + int rv, udelay; + + sc = regnode_get_softc(regnode); + param = regnode_get_stdparam(regnode); + if (param->min_uvolt == 0) + return (0); + + /* + * Set the regulator at the correct voltage + * Do not enable it, this is will be done either by a + * consumer or by regnode_set_constraint if boot_on is true + */ + rv = axp8xx_regnode_set_voltage(regnode, param->min_uvolt, + param->max_uvolt, &udelay); + if (rv != 0) + DELAY(udelay); + + return (rv); +} + +static int axp8xx_regnode_enable(struct regnode *regnode, bool enable, int *udelay) { struct axp8xx_reg_sc *sc; uint8_t val; sc = regnode_get_softc(regnode); if (bootverbose) device_printf(sc->base_dev, "%sable %s (%s)\n", enable ? "En" : "Dis", regnode_get_name(regnode), sc->def->name); axp8xx_read(sc->base_dev, sc->def->enable_reg, &val, 1); val &= ~sc->def->enable_mask; if (enable) val |= sc->def->enable_value; else { if (sc->def->disable_value) val |= sc->def->disable_value; else val &= ~sc->def->enable_value; } axp8xx_write(sc->base_dev, sc->def->enable_reg, val); *udelay = 0; return (0); } static void axp8xx_regnode_reg_to_voltage(struct axp8xx_reg_sc *sc, uint8_t val, int *uv) { if (val < sc->def->voltage_nstep1) *uv = sc->def->voltage_min + val * sc->def->voltage_step1; else *uv = sc->def->voltage_min + (sc->def->voltage_nstep1 * sc->def->voltage_step1) + ((val - sc->def->voltage_nstep1) * sc->def->voltage_step2); *uv *= 1000; } static int axp8xx_regnode_voltage_to_reg(struct axp8xx_reg_sc *sc, int min_uvolt, int max_uvolt, uint8_t *val) { uint8_t nval; int nstep, uvolt; nval = 0; uvolt = sc->def->voltage_min * 1000; for (nstep = 0; nstep < sc->def->voltage_nstep1 && uvolt < min_uvolt; nstep++) { ++nval; uvolt += (sc->def->voltage_step1 * 1000); } for (nstep = 0; nstep < sc->def->voltage_nstep2 && uvolt < min_uvolt; nstep++) { ++nval; uvolt += (sc->def->voltage_step2 * 1000); } if (uvolt > max_uvolt) return (EINVAL); *val = nval; return (0); } static int axp8xx_regnode_set_voltage(struct regnode *regnode, int min_uvolt, int max_uvolt, int *udelay) { struct axp8xx_reg_sc *sc; uint8_t val; sc = regnode_get_softc(regnode); if (bootverbose) device_printf(sc->base_dev, "Setting %s (%s) to %d<->%d\n", regnode_get_name(regnode), sc->def->name, min_uvolt, max_uvolt); if (sc->def->voltage_step1 == 0) return (ENXIO); if (axp8xx_regnode_voltage_to_reg(sc, min_uvolt, max_uvolt, &val) != 0) return (ERANGE); axp8xx_write(sc->base_dev, sc->def->voltage_reg, val); *udelay = 0; return (0); } static int axp8xx_regnode_get_voltage(struct regnode *regnode, int *uvolt) { struct axp8xx_reg_sc *sc; uint8_t val; sc = regnode_get_softc(regnode); if (!sc->def->voltage_step1 || !sc->def->voltage_step2) return (ENXIO); axp8xx_read(sc->base_dev, sc->def->voltage_reg, &val, 1); axp8xx_regnode_reg_to_voltage(sc, val & AXP_VOLTCTL_MASK, uvolt); return (0); } static regnode_method_t axp8xx_regnode_methods[] = { /* Regulator interface */ + REGNODEMETHOD(regnode_init, axp8xx_regnode_init), REGNODEMETHOD(regnode_enable, axp8xx_regnode_enable), REGNODEMETHOD(regnode_set_voltage, axp8xx_regnode_set_voltage), REGNODEMETHOD(regnode_get_voltage, axp8xx_regnode_get_voltage), REGNODEMETHOD(regnode_check_voltage, regnode_method_check_voltage), REGNODEMETHOD_END }; DEFINE_CLASS_1(axp8xx_regnode, axp8xx_regnode_class, axp8xx_regnode_methods, sizeof(struct axp8xx_reg_sc), regnode_class); static void axp8xx_shutdown(void *devp, int howto) { device_t dev; if ((howto & RB_POWEROFF) == 0) return; dev = devp; if (bootverbose) device_printf(dev, "Shutdown Axp8xx\n"); axp8xx_write(dev, AXP_POWERBAT, AXP_POWERBAT_SHUTDOWN); } static int axp8xx_sysctl_chargecurrent(SYSCTL_HANDLER_ARGS) { device_t dev = arg1; uint8_t data; int val, error; error = axp8xx_read(dev, AXP_CHARGERCTL1, &data, 1); if (error != 0) return (error); if (bootverbose) device_printf(dev, "Raw CHARGECTL1 val: 0x%0x\n", data); val = (data & AXP_CHARGERCTL1_CMASK); error = sysctl_handle_int(oidp, &val, 0, req); if (error || !req->newptr) /* error || read request */ return (error); if ((val < AXP_CHARGERCTL1_MIN) || (val > AXP_CHARGERCTL1_MAX)) return (EINVAL); val |= (data & (AXP_CHARGERCTL1_CMASK << 4)); axp8xx_write(dev, AXP_CHARGERCTL1, val); return (0); } static int axp8xx_sysctl(SYSCTL_HANDLER_ARGS) { struct axp8xx_softc *sc; device_t dev = arg1; enum axp8xx_sensor sensor = arg2; const struct axp8xx_config *c; uint8_t data; int val, i, found, batt_val; uint8_t lo, hi; sc = device_get_softc(dev); c = sc->config; for (found = 0, i = 0; i < sc->nsensors; i++) { if (sc->sensors[i].id == sensor) { found = 1; break; } } if (found == 0) return (ENOENT); switch (sensor) { case AXP_SENSOR_ACIN_PRESENT: if (axp8xx_read(dev, AXP_POWERSRC, &data, 1) == 0) val = !!(data & AXP_POWERSRC_ACIN); break; case AXP_SENSOR_VBUS_PRESENT: if (axp8xx_read(dev, AXP_POWERSRC, &data, 1) == 0) val = !!(data & AXP_POWERSRC_VBUS); break; case AXP_SENSOR_BATT_PRESENT: if (axp8xx_read(dev, AXP_POWERMODE, &data, 1) == 0) { if (data & AXP_POWERMODE_BAT_VALID) val = !!(data & AXP_POWERMODE_BAT_PRESENT); } break; case AXP_SENSOR_BATT_CHARGING: if (axp8xx_read(dev, AXP_POWERMODE, &data, 1) == 0) val = !!(data & AXP_POWERMODE_BAT_CHARGING); break; case AXP_SENSOR_BATT_CHARGE_STATE: if (axp8xx_read(dev, AXP_BAT_CAP, &data, 1) == 0 && (data & AXP_BAT_CAP_VALID) != 0) { batt_val = (data & AXP_BAT_CAP_PERCENT); if (batt_val <= sc->shut_thres) val = BATT_CAPACITY_CRITICAL; else if (batt_val <= sc->warn_thres) val = BATT_CAPACITY_WARNING; else val = BATT_CAPACITY_NORMAL; } break; case AXP_SENSOR_BATT_CAPACITY_PERCENT: if (axp8xx_read(dev, AXP_BAT_CAP, &data, 1) == 0 && (data & AXP_BAT_CAP_VALID) != 0) val = (data & AXP_BAT_CAP_PERCENT); break; case AXP_SENSOR_BATT_VOLTAGE: if (axp8xx_read(dev, AXP_BATSENSE_HI, &hi, 1) == 0 && axp8xx_read(dev, AXP_BATSENSE_LO, &lo, 1) == 0) { val = (AXP_SENSOR_BAT_H(hi) | AXP_SENSOR_BAT_L(lo)); val *= c->batsense_step; } break; case AXP_SENSOR_BATT_CHARGE_CURRENT: if (axp8xx_read(dev, AXP_POWERSRC, &data, 1) == 0 && (data & AXP_POWERSRC_CHARING) != 0 && axp8xx_read(dev, AXP_BATCHG_HI, &hi, 1) == 0 && axp8xx_read(dev, AXP_BATCHG_LO, &lo, 1) == 0) { val = (AXP_SENSOR_BAT_H(hi) | AXP_SENSOR_BAT_L(lo)); val *= c->charge_step; } break; case AXP_SENSOR_BATT_DISCHARGE_CURRENT: if (axp8xx_read(dev, AXP_POWERSRC, &data, 1) == 0 && (data & AXP_POWERSRC_CHARING) == 0 && axp8xx_read(dev, AXP_BATDISCHG_HI, &hi, 1) == 0 && axp8xx_read(dev, AXP_BATDISCHG_LO, &lo, 1) == 0) { val = (AXP_SENSOR_BAT_H(hi) | AXP_SENSOR_BAT_L(lo)); val *= c->discharge_step; } break; case AXP_SENSOR_BATT_MAXIMUM_CAPACITY: if (axp8xx_read(dev, AXP_BAT_MAX_CAP_HI, &hi, 1) == 0 && axp8xx_read(dev, AXP_BAT_MAX_CAP_LO, &lo, 1) == 0) { val = AXP_SENSOR_COULOMB(hi, lo); val *= c->maxcap_step; } break; case AXP_SENSOR_BATT_CURRENT_CAPACITY: if (axp8xx_read(dev, AXP_BAT_COULOMB_HI, &hi, 1) == 0 && axp8xx_read(dev, AXP_BAT_COULOMB_LO, &lo, 1) == 0) { val = AXP_SENSOR_COULOMB(hi, lo); val *= c->coulomb_step; } break; } return sysctl_handle_opaque(oidp, &val, sizeof(val), req); } static void axp8xx_intr(void *arg) { device_t dev; uint8_t val; int error; dev = arg; error = axp8xx_read(dev, AXP_IRQSTAT1, &val, 1); if (error != 0) return; if (val) { if (bootverbose) device_printf(dev, "AXP_IRQSTAT1 val: %x\n", val); if (val & AXP_IRQSTAT1_ACIN_HI) devctl_notify("PMU", "AC", "plugged", NULL); if (val & AXP_IRQSTAT1_ACIN_LO) devctl_notify("PMU", "AC", "unplugged", NULL); if (val & AXP_IRQSTAT1_VBUS_HI) devctl_notify("PMU", "USB", "plugged", NULL); if (val & AXP_IRQSTAT1_VBUS_LO) devctl_notify("PMU", "USB", "unplugged", NULL); /* Acknowledge */ axp8xx_write(dev, AXP_IRQSTAT1, val); } error = axp8xx_read(dev, AXP_IRQSTAT2, &val, 1); if (error != 0) return; if (val) { if (bootverbose) device_printf(dev, "AXP_IRQSTAT2 val: %x\n", val); if (val & AXP_IRQSTAT2_BATCHGD) devctl_notify("PMU", "Battery", "charged", NULL); if (val & AXP_IRQSTAT2_BATCHGC) devctl_notify("PMU", "Battery", "charging", NULL); if (val & AXP_IRQSTAT2_BAT_NO) devctl_notify("PMU", "Battery", "absent", NULL); if (val & AXP_IRQSTAT2_BAT_IN) devctl_notify("PMU", "Battery", "plugged", NULL); /* Acknowledge */ axp8xx_write(dev, AXP_IRQSTAT2, val); } error = axp8xx_read(dev, AXP_IRQSTAT3, &val, 1); if (error != 0) return; if (val) { /* Acknowledge */ axp8xx_write(dev, AXP_IRQSTAT3, val); } error = axp8xx_read(dev, AXP_IRQSTAT4, &val, 1); if (error != 0) return; if (val) { if (bootverbose) device_printf(dev, "AXP_IRQSTAT4 val: %x\n", val); if (val & AXP_IRQSTAT4_BATLVL_LO0) devctl_notify("PMU", "Battery", "shutdown threshold", NULL); if (val & AXP_IRQSTAT4_BATLVL_LO1) devctl_notify("PMU", "Battery", "warning threshold", NULL); /* Acknowledge */ axp8xx_write(dev, AXP_IRQSTAT4, val); } error = axp8xx_read(dev, AXP_IRQSTAT5, &val, 1); if (error != 0) return; if (val != 0) { if ((val & AXP_IRQSTAT5_POKSIRQ) != 0) { if (bootverbose) device_printf(dev, "Power button pressed\n"); shutdown_nice(RB_POWEROFF); } /* Acknowledge */ axp8xx_write(dev, AXP_IRQSTAT5, val); } error = axp8xx_read(dev, AXP_IRQSTAT6, &val, 1); if (error != 0) return; if (val) { /* Acknowledge */ axp8xx_write(dev, AXP_IRQSTAT6, val); } } static device_t axp8xx_gpio_get_bus(device_t dev) { struct axp8xx_softc *sc; sc = device_get_softc(dev); return (sc->gpiodev); } static int axp8xx_gpio_pin_max(device_t dev, int *maxpin) { *maxpin = nitems(axp8xx_pins) - 1; return (0); } static int axp8xx_gpio_pin_getname(device_t dev, uint32_t pin, char *name) { if (pin >= nitems(axp8xx_pins)) return (EINVAL); snprintf(name, GPIOMAXNAME, "%s", axp8xx_pins[pin].name); return (0); } static int axp8xx_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) { if (pin >= nitems(axp8xx_pins)) return (EINVAL); *caps = GPIO_PIN_INPUT | GPIO_PIN_OUTPUT; return (0); } static int axp8xx_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags) { struct axp8xx_softc *sc; uint8_t data, func; int error; if (pin >= nitems(axp8xx_pins)) return (EINVAL); sc = device_get_softc(dev); AXP_LOCK(sc); error = axp8xx_read(dev, axp8xx_pins[pin].ctrl_reg, &data, 1); if (error == 0) { func = (data & AXP_GPIO_FUNC) >> AXP_GPIO_FUNC_SHIFT; if (func == AXP_GPIO_FUNC_INPUT) *flags = GPIO_PIN_INPUT; else if (func == AXP_GPIO_FUNC_DRVLO || func == AXP_GPIO_FUNC_DRVHI) *flags = GPIO_PIN_OUTPUT; else *flags = 0; } AXP_UNLOCK(sc); return (error); } static int axp8xx_gpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) { struct axp8xx_softc *sc; uint8_t data; int error; if (pin >= nitems(axp8xx_pins)) return (EINVAL); sc = device_get_softc(dev); AXP_LOCK(sc); error = axp8xx_read(dev, axp8xx_pins[pin].ctrl_reg, &data, 1); if (error == 0) { data &= ~AXP_GPIO_FUNC; if ((flags & (GPIO_PIN_INPUT|GPIO_PIN_OUTPUT)) != 0) { if ((flags & GPIO_PIN_OUTPUT) == 0) data |= AXP_GPIO_FUNC_INPUT; } error = axp8xx_write(dev, axp8xx_pins[pin].ctrl_reg, data); } AXP_UNLOCK(sc); return (error); } static int axp8xx_gpio_pin_get(device_t dev, uint32_t pin, unsigned int *val) { struct axp8xx_softc *sc; uint8_t data, func; int error; if (pin >= nitems(axp8xx_pins)) return (EINVAL); sc = device_get_softc(dev); AXP_LOCK(sc); error = axp8xx_read(dev, axp8xx_pins[pin].ctrl_reg, &data, 1); if (error == 0) { func = (data & AXP_GPIO_FUNC) >> AXP_GPIO_FUNC_SHIFT; switch (func) { case AXP_GPIO_FUNC_DRVLO: *val = 0; break; case AXP_GPIO_FUNC_DRVHI: *val = 1; break; case AXP_GPIO_FUNC_INPUT: error = axp8xx_read(dev, AXP_GPIO_SIGBIT, &data, 1); if (error == 0) *val = (data & (1 << pin)) ? 1 : 0; break; default: error = EIO; break; } } AXP_UNLOCK(sc); return (error); } static int axp8xx_gpio_pin_set(device_t dev, uint32_t pin, unsigned int val) { struct axp8xx_softc *sc; uint8_t data, func; int error; if (pin >= nitems(axp8xx_pins)) return (EINVAL); sc = device_get_softc(dev); AXP_LOCK(sc); error = axp8xx_read(dev, axp8xx_pins[pin].ctrl_reg, &data, 1); if (error == 0) { func = (data & AXP_GPIO_FUNC) >> AXP_GPIO_FUNC_SHIFT; switch (func) { case AXP_GPIO_FUNC_DRVLO: case AXP_GPIO_FUNC_DRVHI: data &= ~AXP_GPIO_FUNC; data |= (val << AXP_GPIO_FUNC_SHIFT); break; default: error = EIO; break; } } if (error == 0) error = axp8xx_write(dev, axp8xx_pins[pin].ctrl_reg, data); AXP_UNLOCK(sc); return (error); } static int axp8xx_gpio_pin_toggle(device_t dev, uint32_t pin) { struct axp8xx_softc *sc; uint8_t data, func; int error; if (pin >= nitems(axp8xx_pins)) return (EINVAL); sc = device_get_softc(dev); AXP_LOCK(sc); error = axp8xx_read(dev, axp8xx_pins[pin].ctrl_reg, &data, 1); if (error == 0) { func = (data & AXP_GPIO_FUNC) >> AXP_GPIO_FUNC_SHIFT; switch (func) { case AXP_GPIO_FUNC_DRVLO: data &= ~AXP_GPIO_FUNC; data |= (AXP_GPIO_FUNC_DRVHI << AXP_GPIO_FUNC_SHIFT); break; case AXP_GPIO_FUNC_DRVHI: data &= ~AXP_GPIO_FUNC; data |= (AXP_GPIO_FUNC_DRVLO << AXP_GPIO_FUNC_SHIFT); break; default: error = EIO; break; } } if (error == 0) error = axp8xx_write(dev, axp8xx_pins[pin].ctrl_reg, data); AXP_UNLOCK(sc); return (error); } static int axp8xx_gpio_map_gpios(device_t bus, phandle_t dev, phandle_t gparent, int gcells, pcell_t *gpios, uint32_t *pin, uint32_t *flags) { if (gpios[0] >= nitems(axp8xx_pins)) return (EINVAL); *pin = gpios[0]; *flags = gpios[1]; return (0); } static phandle_t axp8xx_get_node(device_t dev, device_t bus) { return (ofw_bus_get_node(dev)); } static struct axp8xx_reg_sc * axp8xx_reg_attach(device_t dev, phandle_t node, struct axp8xx_regdef *def) { struct axp8xx_reg_sc *reg_sc; struct regnode_init_def initdef; struct regnode *regnode; memset(&initdef, 0, sizeof(initdef)); if (regulator_parse_ofw_stdparam(dev, node, &initdef) != 0) return (NULL); if (initdef.std_param.min_uvolt == 0) initdef.std_param.min_uvolt = def->voltage_min * 1000; if (initdef.std_param.max_uvolt == 0) initdef.std_param.max_uvolt = def->voltage_max * 1000; initdef.id = def->id; initdef.ofw_node = node; regnode = regnode_create(dev, &axp8xx_regnode_class, &initdef); if (regnode == NULL) { device_printf(dev, "cannot create regulator\n"); return (NULL); } reg_sc = regnode_get_softc(regnode); reg_sc->regnode = regnode; reg_sc->base_dev = dev; reg_sc->def = def; reg_sc->xref = OF_xref_from_node(node); reg_sc->param = regnode_get_stdparam(regnode); regnode_register(regnode); return (reg_sc); } static int axp8xx_regdev_map(device_t dev, phandle_t xref, int ncells, pcell_t *cells, intptr_t *num) { struct axp8xx_softc *sc; int i; sc = device_get_softc(dev); for (i = 0; i < sc->nregs; i++) { if (sc->regs[i] == NULL) continue; if (sc->regs[i]->xref == xref) { *num = sc->regs[i]->def->id; return (0); } } return (ENXIO); } static int axp8xx_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); switch (ofw_bus_search_compatible(dev, compat_data)->ocd_data) { case AXP803: device_set_desc(dev, "X-Powers AXP803 Power Management Unit"); break; case AXP813: device_set_desc(dev, "X-Powers AXP813 Power Management Unit"); break; default: return (ENXIO); } return (BUS_PROBE_DEFAULT); } static int axp8xx_attach(device_t dev) { struct axp8xx_softc *sc; struct axp8xx_reg_sc *reg; uint8_t chip_id, val; phandle_t rnode, child; int error, i; sc = device_get_softc(dev); sc->addr = iicbus_get_addr(dev); mtx_init(&sc->mtx, device_get_nameunit(dev), NULL, MTX_DEF); error = bus_alloc_resources(dev, axp8xx_spec, &sc->res); if (error != 0) { device_printf(dev, "cannot allocate resources for device\n"); return (error); } if (bootverbose) { axp8xx_read(dev, AXP_ICTYPE, &chip_id, 1); device_printf(dev, "chip ID 0x%02x\n", chip_id); } sc->nregs = nitems(axp8xx_common_regdefs); sc->type = ofw_bus_search_compatible(dev, compat_data)->ocd_data; switch (sc->type) { case AXP803: sc->nregs += nitems(axp803_regdefs); break; case AXP813: sc->nregs += nitems(axp813_regdefs); break; } sc->config = &axp803_config; sc->sensors = axp8xx_common_sensors; sc->nsensors = nitems(axp8xx_common_sensors); sc->regs = malloc(sizeof(struct axp8xx_reg_sc *) * sc->nregs, M_AXP8XX_REG, M_WAITOK | M_ZERO); /* Attach known regulators that exist in the DT */ rnode = ofw_bus_find_child(ofw_bus_get_node(dev), "regulators"); if (rnode > 0) { for (i = 0; i < sc->nregs; i++) { char *regname; struct axp8xx_regdef *regdef; if (i <= nitems(axp8xx_common_regdefs)) { regname = axp8xx_common_regdefs[i].name; regdef = &axp8xx_common_regdefs[i]; } else { int off; off = i - nitems(axp8xx_common_regdefs); switch (sc->type) { case AXP803: regname = axp803_regdefs[off].name; regdef = &axp803_regdefs[off]; break; case AXP813: regname = axp813_regdefs[off].name; regdef = &axp813_regdefs[off]; break; } } child = ofw_bus_find_child(rnode, regname); if (child == 0) continue; reg = axp8xx_reg_attach(dev, child, regdef); if (reg == NULL) { device_printf(dev, "cannot attach regulator %s\n", regname); continue; } sc->regs[i] = reg; } } /* Add sensors */ for (i = 0; i < sc->nsensors; i++) { SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, sc->sensors[i].name, CTLTYPE_INT | CTLFLAG_RD, dev, sc->sensors[i].id, axp8xx_sysctl, sc->sensors[i].format, sc->sensors[i].desc); } SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "batchargecurrentstep", CTLTYPE_INT | CTLFLAG_RW, dev, 0, axp8xx_sysctl_chargecurrent, "I", "Battery Charging Current Step, " "0: 200mA, 1: 400mA, 2: 600mA, 3: 800mA, " "4: 1000mA, 5: 1200mA, 6: 1400mA, 7: 1600mA, " "8: 1800mA, 9: 2000mA, 10: 2200mA, 11: 2400mA, " "12: 2600mA, 13: 2800mA"); /* Get thresholds */ if (axp8xx_read(dev, AXP_BAT_CAP_WARN, &val, 1) == 0) { sc->warn_thres = (val & AXP_BAT_CAP_WARN_LV1) >> 4; sc->warn_thres += AXP_BAP_CAP_WARN_LV1BASE; sc->shut_thres = (val & AXP_BAT_CAP_WARN_LV2); if (bootverbose) { device_printf(dev, "Raw reg val: 0x%02x\n", val); device_printf(dev, "Warning threshold: 0x%02x\n", sc->warn_thres); device_printf(dev, "Shutdown threshold: 0x%02x\n", sc->shut_thres); } } /* Enable interrupts */ axp8xx_write(dev, AXP_IRQEN1, AXP_IRQEN1_VBUS_LO | AXP_IRQEN1_VBUS_HI | AXP_IRQEN1_ACIN_LO | AXP_IRQEN1_ACIN_HI); axp8xx_write(dev, AXP_IRQEN2, AXP_IRQEN2_BATCHGD | AXP_IRQEN2_BATCHGC | AXP_IRQEN2_BAT_NO | AXP_IRQEN2_BAT_IN); axp8xx_write(dev, AXP_IRQEN3, 0); axp8xx_write(dev, AXP_IRQEN4, AXP_IRQEN4_BATLVL_LO0 | AXP_IRQEN4_BATLVL_LO1); axp8xx_write(dev, AXP_IRQEN5, AXP_IRQEN5_POKSIRQ | AXP_IRQEN5_POKLIRQ); axp8xx_write(dev, AXP_IRQEN6, 0); /* Install interrupt handler */ error = bus_setup_intr(dev, sc->res, INTR_TYPE_MISC | INTR_MPSAFE, NULL, axp8xx_intr, dev, &sc->ih); if (error != 0) { device_printf(dev, "cannot setup interrupt handler\n"); return (error); } EVENTHANDLER_REGISTER(shutdown_final, axp8xx_shutdown, dev, SHUTDOWN_PRI_LAST); sc->gpiodev = gpiobus_attach_bus(dev); return (0); } static device_method_t axp8xx_methods[] = { /* Device interface */ DEVMETHOD(device_probe, axp8xx_probe), DEVMETHOD(device_attach, axp8xx_attach), /* GPIO interface */ DEVMETHOD(gpio_get_bus, axp8xx_gpio_get_bus), DEVMETHOD(gpio_pin_max, axp8xx_gpio_pin_max), DEVMETHOD(gpio_pin_getname, axp8xx_gpio_pin_getname), DEVMETHOD(gpio_pin_getcaps, axp8xx_gpio_pin_getcaps), DEVMETHOD(gpio_pin_getflags, axp8xx_gpio_pin_getflags), DEVMETHOD(gpio_pin_setflags, axp8xx_gpio_pin_setflags), DEVMETHOD(gpio_pin_get, axp8xx_gpio_pin_get), DEVMETHOD(gpio_pin_set, axp8xx_gpio_pin_set), DEVMETHOD(gpio_pin_toggle, axp8xx_gpio_pin_toggle), DEVMETHOD(gpio_map_gpios, axp8xx_gpio_map_gpios), /* Regdev interface */ DEVMETHOD(regdev_map, axp8xx_regdev_map), /* OFW bus interface */ DEVMETHOD(ofw_bus_get_node, axp8xx_get_node), DEVMETHOD_END }; static driver_t axp8xx_driver = { "axp8xx_pmu", axp8xx_methods, sizeof(struct axp8xx_softc), }; static devclass_t axp8xx_devclass; extern devclass_t ofwgpiobus_devclass, gpioc_devclass; extern driver_t ofw_gpiobus_driver, gpioc_driver; EARLY_DRIVER_MODULE(axp8xx, iicbus, axp8xx_driver, axp8xx_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LAST); EARLY_DRIVER_MODULE(ofw_gpiobus, axp8xx_pmu, ofw_gpiobus_driver, ofwgpiobus_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LAST); DRIVER_MODULE(gpioc, axp8xx_pmu, gpioc_driver, gpioc_devclass, 0, 0); MODULE_VERSION(axp8xx, 1); MODULE_DEPEND(axp8xx, iicbus, 1, 1, 1); SIMPLEBUS_PNP_INFO(compat_data); Index: stable/12/sys/dev/iicbus/twsi/twsi.c =================================================================== --- stable/12/sys/dev/iicbus/twsi/twsi.c (revision 362349) +++ stable/12/sys/dev/iicbus/twsi/twsi.c (revision 362350) @@ -1,757 +1,753 @@ /*- * Copyright (C) 2008 MARVELL INTERNATIONAL LTD. * All rights reserved. * * Developed by Semihalf. * * 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. * 3. Neither the name of MARVELL nor the names of contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY 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 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. */ /* * Driver for the TWSI (aka I2C, aka IIC) bus controller found on Marvell * and Allwinner SoCs. Supports master operation only. * * Calls to DELAY() are needed per Application Note AN-179 "TWSI Software * Guidelines for Discovery(TM), Horizon (TM) and Feroceon(TM) Devices". */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iicbus_if.h" #define TWSI_CONTROL_ACK (1 << 2) #define TWSI_CONTROL_IFLG (1 << 3) #define TWSI_CONTROL_STOP (1 << 4) #define TWSI_CONTROL_START (1 << 5) #define TWSI_CONTROL_TWSIEN (1 << 6) #define TWSI_CONTROL_INTEN (1 << 7) #define TWSI_STATUS_START 0x08 #define TWSI_STATUS_RPTD_START 0x10 #define TWSI_STATUS_ADDR_W_ACK 0x18 #define TWSI_STATUS_ADDR_W_NACK 0x20 #define TWSI_STATUS_DATA_WR_ACK 0x28 #define TWSI_STATUS_DATA_WR_NACK 0x30 #define TWSI_STATUS_ADDR_R_ACK 0x40 #define TWSI_STATUS_ADDR_R_NACK 0x48 #define TWSI_STATUS_DATA_RD_ACK 0x50 #define TWSI_STATUS_DATA_RD_NOACK 0x58 #define TWSI_DEBUG #undef TWSI_DEBUG #ifdef TWSI_DEBUG #define debugf(dev, fmt, args...) device_printf(dev, "%s: " fmt, __func__, ##args) #else #define debugf(dev, fmt, args...) #endif static struct resource_spec res_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE}, { -1, 0 } }; static __inline uint32_t TWSI_READ(struct twsi_softc *sc, bus_size_t off) { uint32_t val; val = bus_read_4(sc->res[0], off); debugf(sc->dev, "read %x from %lx\n", val, off); return (val); } static __inline void TWSI_WRITE(struct twsi_softc *sc, bus_size_t off, uint32_t val) { debugf(sc->dev, "Writing %x to %lx\n", val, off); bus_write_4(sc->res[0], off, val); } static __inline void twsi_control_clear(struct twsi_softc *sc, uint32_t mask) { uint32_t val; val = TWSI_READ(sc, sc->reg_control); debugf(sc->dev, "read val=%x\n", val); val &= ~(TWSI_CONTROL_STOP | TWSI_CONTROL_START); val &= ~mask; debugf(sc->dev, "write val=%x\n", val); TWSI_WRITE(sc, sc->reg_control, val); } static __inline void twsi_control_set(struct twsi_softc *sc, uint32_t mask) { uint32_t val; val = TWSI_READ(sc, sc->reg_control); debugf(sc->dev, "read val=%x\n", val); val &= ~(TWSI_CONTROL_STOP | TWSI_CONTROL_START); val |= mask; debugf(sc->dev, "write val=%x\n", val); TWSI_WRITE(sc, sc->reg_control, val); } static __inline void twsi_clear_iflg(struct twsi_softc *sc) { DELAY(1000); twsi_control_clear(sc, TWSI_CONTROL_IFLG); DELAY(1000); } /* * timeout given in us * returns * 0 on successful mask change * non-zero on timeout */ static int twsi_poll_ctrl(struct twsi_softc *sc, int timeout, uint32_t mask) { timeout /= 10; debugf(sc->dev, "Waiting for ctrl reg to match mask %x\n", mask); while (!(TWSI_READ(sc, sc->reg_control) & mask)) { DELAY(10); if (--timeout < 0) return (timeout); } debugf(sc->dev, "done\n"); return (0); } /* * 'timeout' is given in us. Note also that timeout handling is not exact -- * twsi_locked_start() total wait can be more than 2 x timeout * (twsi_poll_ctrl() is called twice). 'mask' can be either TWSI_STATUS_START * or TWSI_STATUS_RPTD_START */ static int twsi_locked_start(device_t dev, struct twsi_softc *sc, int32_t mask, u_char slave, int timeout) { int read_access, iflg_set = 0; uint32_t status; mtx_assert(&sc->mutex, MA_OWNED); if (mask == TWSI_STATUS_RPTD_START) /* read IFLG to know if it should be cleared later; from NBSD */ iflg_set = TWSI_READ(sc, sc->reg_control) & TWSI_CONTROL_IFLG; debugf(dev, "send start\n"); twsi_control_set(sc, TWSI_CONTROL_START); if (mask == TWSI_STATUS_RPTD_START && iflg_set) { debugf(dev, "IFLG set, clearing (mask=%x)\n", mask); twsi_clear_iflg(sc); } /* * Without this delay we timeout checking IFLG if the timeout is 0. * NBSD driver always waits here too. */ DELAY(1000); if (twsi_poll_ctrl(sc, timeout, TWSI_CONTROL_IFLG)) { debugf(dev, "timeout sending %sSTART condition\n", mask == TWSI_STATUS_START ? "" : "repeated "); return (IIC_ETIMEOUT); } status = TWSI_READ(sc, sc->reg_status); debugf(dev, "status=%x\n", status); if (status != mask) { debugf(dev, "wrong status (%02x) after sending %sSTART condition\n", status, mask == TWSI_STATUS_START ? "" : "repeated "); return (IIC_ESTATUS); } TWSI_WRITE(sc, sc->reg_data, slave); twsi_clear_iflg(sc); DELAY(1000); if (twsi_poll_ctrl(sc, timeout, TWSI_CONTROL_IFLG)) { debugf(dev, "timeout sending slave address (timeout=%d)\n", timeout); return (IIC_ETIMEOUT); } read_access = (slave & 0x1) ? 1 : 0; status = TWSI_READ(sc, sc->reg_status); if (status != (read_access ? TWSI_STATUS_ADDR_R_ACK : TWSI_STATUS_ADDR_W_ACK)) { debugf(dev, "no ACK (status: %02x) after sending slave address\n", status); return (IIC_ENOACK); } return (IIC_NOERR); } #ifdef EXT_RESOURCES #define TWSI_BAUD_RATE_RAW(C,M,N) ((C)/((10*(M+1))<<(N))) #define ABSSUB(a,b) (((a) > (b)) ? (a) - (b) : (b) - (a)) static int twsi_calc_baud_rate(struct twsi_softc *sc, const u_int target, int *param) { uint64_t clk; uint32_t cur, diff, diff0; int m, n, m0, n0; /* Calculate baud rate. */ diff0 = 0xffffffff; if (clk_get_freq(sc->clk_core, &clk) < 0) return (-1); debugf(sc->dev, "Bus clock is at %ju\n", clk); for (n = 0; n < 8; n++) { for (m = 0; m < 16; m++) { cur = TWSI_BAUD_RATE_RAW(clk,m,n); diff = ABSSUB(target, cur); if (diff < diff0) { m0 = m; n0 = n; diff0 = diff; } } } *param = TWSI_BAUD_RATE_PARAM(m0, n0); return (0); } #endif /* EXT_RESOURCES */ /* * Only slave mode supported, disregard [old]addr */ static int twsi_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr) { struct twsi_softc *sc; uint32_t param; #ifdef EXT_RESOURCES u_int busfreq; #endif sc = device_get_softc(dev); #ifdef EXT_RESOURCES busfreq = IICBUS_GET_FREQUENCY(sc->iicbus, speed); if (twsi_calc_baud_rate(sc, busfreq, ¶m) == -1) { #endif switch (speed) { case IIC_SLOW: case IIC_FAST: param = sc->baud_rate[speed].param; debugf(dev, "Using IIC_FAST mode with speed param=%x\n", param); break; case IIC_FASTEST: case IIC_UNKNOWN: default: param = sc->baud_rate[IIC_FAST].param; debugf(dev, "Using IIC_FASTEST/UNKNOWN mode with speed param=%x\n", param); break; } #ifdef EXT_RESOURCES } #endif debugf(dev, "Using clock param=%x\n", param); mtx_lock(&sc->mutex); TWSI_WRITE(sc, sc->reg_soft_reset, 0x0); TWSI_WRITE(sc, sc->reg_baud_rate, param); TWSI_WRITE(sc, sc->reg_control, TWSI_CONTROL_TWSIEN); DELAY(1000); mtx_unlock(&sc->mutex); return (0); } static int twsi_stop(device_t dev) { struct twsi_softc *sc; sc = device_get_softc(dev); debugf(dev, "%s\n", __func__); mtx_lock(&sc->mutex); twsi_control_clear(sc, TWSI_CONTROL_ACK); twsi_control_set(sc, TWSI_CONTROL_STOP); twsi_clear_iflg(sc); DELAY(1000); mtx_unlock(&sc->mutex); return (IIC_NOERR); } /* * timeout is given in us */ static int twsi_repeated_start(device_t dev, u_char slave, int timeout) { struct twsi_softc *sc; int rv; sc = device_get_softc(dev); debugf(dev, "%s: slave=%x\n", __func__, slave); mtx_lock(&sc->mutex); rv = twsi_locked_start(dev, sc, TWSI_STATUS_RPTD_START, slave, timeout); mtx_unlock(&sc->mutex); if (rv) { twsi_stop(dev); return (rv); } else return (IIC_NOERR); } /* * timeout is given in us */ static int twsi_start(device_t dev, u_char slave, int timeout) { struct twsi_softc *sc; int rv; sc = device_get_softc(dev); debugf(dev, "%s: slave=%x\n", __func__, slave); mtx_lock(&sc->mutex); rv = twsi_locked_start(dev, sc, TWSI_STATUS_START, slave, timeout); mtx_unlock(&sc->mutex); if (rv) { twsi_stop(dev); return (rv); } else return (IIC_NOERR); } static int twsi_read(device_t dev, char *buf, int len, int *read, int last, int delay) { struct twsi_softc *sc; uint32_t status; int last_byte, rv; sc = device_get_softc(dev); mtx_lock(&sc->mutex); *read = 0; while (*read < len) { /* * Check if we are reading last byte of the last buffer, * do not send ACK then, per I2C specs */ last_byte = ((*read == len - 1) && last) ? 1 : 0; if (last_byte) twsi_control_clear(sc, TWSI_CONTROL_ACK); else twsi_control_set(sc, TWSI_CONTROL_ACK); twsi_clear_iflg(sc); DELAY(1000); if (twsi_poll_ctrl(sc, delay, TWSI_CONTROL_IFLG)) { debugf(dev, "timeout reading data (delay=%d)\n", delay); rv = IIC_ETIMEOUT; goto out; } status = TWSI_READ(sc, sc->reg_status); if (status != (last_byte ? TWSI_STATUS_DATA_RD_NOACK : TWSI_STATUS_DATA_RD_ACK)) { debugf(dev, "wrong status (%02x) while reading\n", status); rv = IIC_ESTATUS; goto out; } *buf++ = TWSI_READ(sc, sc->reg_data); (*read)++; } rv = IIC_NOERR; out: mtx_unlock(&sc->mutex); return (rv); } static int twsi_write(device_t dev, const char *buf, int len, int *sent, int timeout) { struct twsi_softc *sc; uint32_t status; int rv; sc = device_get_softc(dev); mtx_lock(&sc->mutex); *sent = 0; while (*sent < len) { TWSI_WRITE(sc, sc->reg_data, *buf++); twsi_clear_iflg(sc); DELAY(1000); if (twsi_poll_ctrl(sc, timeout, TWSI_CONTROL_IFLG)) { debugf(dev, "timeout writing data (timeout=%d)\n", timeout); rv = IIC_ETIMEOUT; goto out; } status = TWSI_READ(sc, sc->reg_status); if (status != TWSI_STATUS_DATA_WR_ACK) { debugf(dev, "wrong status (%02x) while writing\n", status); rv = IIC_ESTATUS; goto out; } (*sent)++; } rv = IIC_NOERR; out: mtx_unlock(&sc->mutex); return (rv); } static int twsi_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs) { struct twsi_softc *sc; - int i; sc = device_get_softc(dev); if (sc->have_intr == false) return (iicbus_transfer_gen(dev, msgs, nmsgs)); sc->error = 0; sc->control_val = TWSI_CONTROL_TWSIEN | TWSI_CONTROL_INTEN | TWSI_CONTROL_ACK; TWSI_WRITE(sc, sc->reg_control, sc->control_val); debugf(dev, "transmitting %d messages\n", nmsgs); debugf(sc->dev, "status=%x\n", TWSI_READ(sc, sc->reg_status)); - for (i = 0; i < nmsgs && sc->error == 0; i++) { - sc->transfer = 1; - sc->msg = &msgs[i]; - debugf(dev, "msg[%d] flags: %x\n", i, msgs[i].flags); - debugf(dev, "msg[%d] len: %d\n", i, msgs[i].len); + sc->nmsgs = nmsgs; + sc->msgs = msgs; + sc->msg_idx = 0; + sc->transfer = 1; - /* Send start and re-enable interrupts */ - sc->control_val = TWSI_CONTROL_TWSIEN | - TWSI_CONTROL_INTEN | TWSI_CONTROL_ACK; - if (sc->msg->len == 1) - sc->control_val &= ~TWSI_CONTROL_ACK; - TWSI_WRITE(sc, sc->reg_control, sc->control_val | TWSI_CONTROL_START); - while (sc->error == 0 && sc->transfer != 0) { - pause_sbt("twsi", SBT_1MS * 30, SBT_1MS, 0); - } + /* Send start and re-enable interrupts */ + sc->control_val = TWSI_CONTROL_TWSIEN | + TWSI_CONTROL_INTEN | TWSI_CONTROL_ACK; + if (sc->msgs[0].len == 1) + sc->control_val &= ~TWSI_CONTROL_ACK; + TWSI_WRITE(sc, sc->reg_control, sc->control_val | TWSI_CONTROL_START); + while (sc->error == 0 && sc->transfer != 0) { + pause_sbt("twsi", SBT_1MS * 30, SBT_1MS, 0); + } + debugf(sc->dev, "pause finish\n"); - debugf(dev, "Done with msg[%d]\n", i); - if (sc->error) { - debugf(sc->dev, "Error, aborting (%d)\n", sc->error); - TWSI_WRITE(sc, sc->reg_control, 0); - goto out; - } + if (sc->error) { + debugf(sc->dev, "Error, aborting (%d)\n", sc->error); + TWSI_WRITE(sc, sc->reg_control, 0); } /* Disable module and interrupts */ debugf(sc->dev, "status=%x\n", TWSI_READ(sc, sc->reg_status)); TWSI_WRITE(sc, sc->reg_control, 0); debugf(sc->dev, "status=%x\n", TWSI_READ(sc, sc->reg_status)); -out: return (sc->error); } static void twsi_intr(void *arg) { struct twsi_softc *sc; uint32_t status; int transfer_done = 0; sc = arg; - debugf(sc->dev, "Got interrupt\n"); + debugf(sc->dev, "Got interrupt Current msg=%x\n", sc->msg_idx); - while (TWSI_READ(sc, sc->reg_control) & TWSI_CONTROL_IFLG) { - status = TWSI_READ(sc, sc->reg_status); - debugf(sc->dev, "status=%x\n", status); + status = TWSI_READ(sc, sc->reg_status); + debugf(sc->dev, "initial status=%x\n", status); - switch (status) { - case TWSI_STATUS_START: - case TWSI_STATUS_RPTD_START: - /* Transmit the address */ - debugf(sc->dev, "Send the address\n"); + switch (status) { + case TWSI_STATUS_START: + case TWSI_STATUS_RPTD_START: + /* Transmit the address */ + debugf(sc->dev, "Send the address\n"); - if (sc->msg->flags & IIC_M_RD) - TWSI_WRITE(sc, sc->reg_data, - sc->msg->slave | LSB); - else - TWSI_WRITE(sc, sc->reg_data, - sc->msg->slave & ~LSB); + if (sc->msgs[sc->msg_idx].flags & IIC_M_RD) + TWSI_WRITE(sc, sc->reg_data, + sc->msgs[sc->msg_idx].slave | LSB); + else + TWSI_WRITE(sc, sc->reg_data, + sc->msgs[sc->msg_idx].slave & ~LSB); + TWSI_WRITE(sc, sc->reg_control, sc->control_val); + break; - TWSI_WRITE(sc, sc->reg_control, sc->control_val); - break; + case TWSI_STATUS_ADDR_W_ACK: + debugf(sc->dev, "Ack received after transmitting the address (write)\n"); + /* Directly send the first byte */ + sc->sent_bytes = 0; + debugf(sc->dev, "Sending byte 0 = %x\n", sc->msgs[sc->msg_idx].buf[0]); + TWSI_WRITE(sc, sc->reg_data, sc->msgs[sc->msg_idx].buf[0]); - case TWSI_STATUS_ADDR_W_ACK: - debugf(sc->dev, "Ack received after transmitting the address\n"); - /* Directly send the first byte */ - sc->sent_bytes = 0; - debugf(sc->dev, "Sending byte 0 = %x\n", sc->msg->buf[0]); - TWSI_WRITE(sc, sc->reg_data, sc->msg->buf[0]); + TWSI_WRITE(sc, sc->reg_control, sc->control_val); + break; - TWSI_WRITE(sc, sc->reg_control, sc->control_val); - break; + case TWSI_STATUS_ADDR_R_ACK: + debugf(sc->dev, "Ack received after transmitting the address (read)\n"); + sc->recv_bytes = 0; - case TWSI_STATUS_ADDR_R_ACK: - debugf(sc->dev, "Ack received after transmitting the address\n"); - sc->recv_bytes = 0; + TWSI_WRITE(sc, sc->reg_control, sc->control_val); + break; - TWSI_WRITE(sc, sc->reg_control, sc->control_val); - break; + case TWSI_STATUS_ADDR_W_NACK: + case TWSI_STATUS_ADDR_R_NACK: + debugf(sc->dev, "No ack received after transmitting the address\n"); + sc->transfer = 0; + sc->error = ETIMEDOUT; + sc->control_val = 0; + wakeup(sc); + break; - case TWSI_STATUS_ADDR_W_NACK: - case TWSI_STATUS_ADDR_R_NACK: - debugf(sc->dev, "No ack received after transmitting the address\n"); - sc->transfer = 0; - sc->error = ETIMEDOUT; - sc->control_val = 0; - wakeup(sc); - break; - - case TWSI_STATUS_DATA_WR_ACK: - debugf(sc->dev, "Ack received after transmitting data\n"); - if (sc->sent_bytes++ == (sc->msg->len - 1)) { - debugf(sc->dev, "Done sending all the bytes\n"); - /* Send stop, no interrupts on stop */ - if (!(sc->msg->flags & IIC_M_NOSTOP)) { - debugf(sc->dev, "Done TX data, send stop\n"); - TWSI_WRITE(sc, sc->reg_control, - sc->control_val | TWSI_CONTROL_STOP); - } else { - sc->control_val &= ~TWSI_CONTROL_INTEN; - TWSI_WRITE(sc, sc->reg_control, - sc->control_val); - } - transfer_done = 1; - } else { - debugf(sc->dev, "Sending byte %d = %x\n", - sc->sent_bytes, - sc->msg->buf[sc->sent_bytes]); - TWSI_WRITE(sc, sc->reg_data, - sc->msg->buf[sc->sent_bytes]); + case TWSI_STATUS_DATA_WR_ACK: + debugf(sc->dev, "Ack received after transmitting data\n"); + if (sc->sent_bytes++ == (sc->msgs[sc->msg_idx].len - 1)) { + debugf(sc->dev, "Done sending all the bytes for msg %d\n", sc->msg_idx); + /* Send stop, no interrupts on stop */ + if (!(sc->msgs[sc->msg_idx].flags & IIC_M_NOSTOP)) { + debugf(sc->dev, "Done TX data, send stop\n"); TWSI_WRITE(sc, sc->reg_control, - sc->control_val); + sc->control_val | TWSI_CONTROL_STOP); + } else { + debugf(sc->dev, "Done TX data with NO_STOP\n"); + TWSI_WRITE(sc, sc->reg_control, sc->control_val | TWSI_CONTROL_START); } + sc->msg_idx++; + if (sc->msg_idx == sc->nmsgs) { + debugf(sc->dev, "transfer_done=1\n"); + transfer_done = 1; + } + } else { + debugf(sc->dev, "Sending byte %d = %x\n", + sc->sent_bytes, + sc->msgs[sc->msg_idx].buf[sc->sent_bytes]); + TWSI_WRITE(sc, sc->reg_data, + sc->msgs[sc->msg_idx].buf[sc->sent_bytes]); + TWSI_WRITE(sc, sc->reg_control, + sc->control_val); + } + break; - break; - case TWSI_STATUS_DATA_RD_ACK: - debugf(sc->dev, "Ack received after receiving data\n"); - debugf(sc->dev, "msg_len=%d recv_bytes=%d\n", sc->msg->len, sc->recv_bytes); - sc->msg->buf[sc->recv_bytes++] = TWSI_READ(sc, sc->reg_data); + case TWSI_STATUS_DATA_RD_ACK: + debugf(sc->dev, "Ack received after receiving data\n"); + debugf(sc->dev, "msg_len=%d recv_bytes=%d\n", sc->msgs[sc->msg_idx].len, sc->recv_bytes); + sc->msgs[sc->msg_idx].buf[sc->recv_bytes++] = TWSI_READ(sc, sc->reg_data); - /* If we only have one byte left, disable ACK */ - if (sc->msg->len - sc->recv_bytes == 1) - sc->control_val &= ~TWSI_CONTROL_ACK; - TWSI_WRITE(sc, sc->reg_control, sc->control_val); - break; + /* If we only have one byte left, disable ACK */ + if (sc->msgs[sc->msg_idx].len - sc->recv_bytes == 1) + sc->control_val &= ~TWSI_CONTROL_ACK; + if (sc->msgs[sc->msg_idx].len - sc->recv_bytes) { + sc->msg_idx++; + if (sc->msg_idx == sc->nmsgs - 1) + transfer_done = 1; + } + TWSI_WRITE(sc, sc->reg_control, sc->control_val); + break; - case TWSI_STATUS_DATA_RD_NOACK: - if (sc->msg->len - sc->recv_bytes == 1) { - sc->msg->buf[sc->recv_bytes++] = TWSI_READ(sc, sc->reg_data); - debugf(sc->dev, "Done RX data, send stop (2)\n"); - if (!(sc->msg->flags & IIC_M_NOSTOP)) - TWSI_WRITE(sc, sc->reg_control, - sc->control_val | TWSI_CONTROL_STOP); - } else { - debugf(sc->dev, "No ack when receiving data\n"); - sc->error = ENXIO; - sc->control_val = 0; - } - sc->transfer = 0; - transfer_done = 1; - break; - - default: - debugf(sc->dev, "status=%x hot handled\n", status); - sc->transfer = 0; + case TWSI_STATUS_DATA_RD_NOACK: + if (sc->msgs[sc->msg_idx].len - sc->recv_bytes == 1) { + sc->msgs[sc->msg_idx].buf[sc->recv_bytes++] = TWSI_READ(sc, sc->reg_data); + debugf(sc->dev, "Done RX data, send stop (2)\n"); + if (!(sc->msgs[sc->msg_idx].flags & IIC_M_NOSTOP)) + TWSI_WRITE(sc, sc->reg_control, + sc->control_val | TWSI_CONTROL_STOP); + } else { + debugf(sc->dev, "No ack when receiving data\n"); sc->error = ENXIO; sc->control_val = 0; - wakeup(sc); - break; } + sc->transfer = 0; + transfer_done = 1; + break; - if (sc->need_ack) - TWSI_WRITE(sc, sc->reg_control, - sc->control_val | TWSI_CONTROL_IFLG); + default: + debugf(sc->dev, "status=%x hot handled\n", status); + sc->transfer = 0; + sc->error = ENXIO; + sc->control_val = 0; + wakeup(sc); + break; } - debugf(sc->dev, "Done with interrupts\n"); + debugf(sc->dev, "Done with interrupts\n\n"); if (transfer_done == 1) { sc->transfer = 0; wakeup(sc); } } static void twsi_intr_start(void *pdev) { struct twsi_softc *sc; sc = device_get_softc(pdev); if ((bus_setup_intr(pdev, sc->res[1], INTR_TYPE_MISC | INTR_MPSAFE, NULL, twsi_intr, sc, &sc->intrhand))) device_printf(pdev, "unable to register interrupt handler\n"); sc->have_intr = true; } int twsi_attach(device_t dev) { struct twsi_softc *sc; sc = device_get_softc(dev); sc->dev = dev; mtx_init(&sc->mutex, device_get_nameunit(dev), "twsi", MTX_DEF); if (bus_alloc_resources(dev, res_spec, sc->res)) { device_printf(dev, "could not allocate resources\n"); twsi_detach(dev); return (ENXIO); } /* Attach the iicbus. */ if ((sc->iicbus = device_add_child(dev, "iicbus", -1)) == NULL) { device_printf(dev, "could not allocate iicbus instance\n"); twsi_detach(dev); return (ENXIO); } bus_generic_attach(dev); config_intrhook_oneshot(twsi_intr_start, dev); return (0); } int twsi_detach(device_t dev) { struct twsi_softc *sc; int rv; sc = device_get_softc(dev); if ((rv = bus_generic_detach(dev)) != 0) return (rv); if (sc->iicbus != NULL) if ((rv = device_delete_child(dev, sc->iicbus)) != 0) return (rv); if (sc->intrhand != NULL) bus_teardown_intr(sc->dev, sc->res[1], sc->intrhand); bus_release_resources(dev, res_spec, sc->res); mtx_destroy(&sc->mutex); return (0); } static device_method_t twsi_methods[] = { /* device interface */ DEVMETHOD(device_detach, twsi_detach), /* Bus interface */ DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD(bus_alloc_resource, bus_generic_alloc_resource), DEVMETHOD(bus_release_resource, bus_generic_release_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_adjust_resource, bus_generic_adjust_resource), DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), /* iicbus interface */ DEVMETHOD(iicbus_callback, iicbus_null_callback), DEVMETHOD(iicbus_repeated_start, twsi_repeated_start), DEVMETHOD(iicbus_start, twsi_start), DEVMETHOD(iicbus_stop, twsi_stop), DEVMETHOD(iicbus_write, twsi_write), DEVMETHOD(iicbus_read, twsi_read), DEVMETHOD(iicbus_reset, twsi_reset), DEVMETHOD(iicbus_transfer, twsi_transfer), { 0, 0 } }; DEFINE_CLASS_0(twsi, twsi_driver, twsi_methods, sizeof(struct twsi_softc)); Index: stable/12/sys/dev/iicbus/twsi/twsi.h =================================================================== --- stable/12/sys/dev/iicbus/twsi/twsi.h (revision 362349) +++ stable/12/sys/dev/iicbus/twsi/twsi.h (revision 362350) @@ -1,85 +1,87 @@ /*- * Copyright (C) 2008 MARVELL INTERNATIONAL LTD. * All rights reserved. * * Developed by Semihalf. * * 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. * 3. Neither the name of MARVELL nor the names of contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY 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 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. * * $FreeBSD$ */ #ifndef _TWSI_H_ #define _TWSI_H_ #ifdef EXT_RESOURCES #include #endif struct twsi_baud_rate { uint32_t raw; int param; int m; int n; }; struct twsi_softc { device_t dev; struct resource *res[2]; struct mtx mutex; device_t iicbus; void * intrhand; bool have_intr; #ifdef EXT_RESOURCES clk_t clk_core; clk_t clk_reg; #endif - struct iic_msg *msg; + struct iic_msg *msgs; + uint32_t nmsgs; + uint32_t msg_idx; uint16_t sent_bytes; uint16_t recv_bytes; int transfer; int error; uint32_t control_val; bool need_ack; bus_size_t reg_data; bus_size_t reg_slave_addr; bus_size_t reg_slave_ext_addr; bus_size_t reg_control; bus_size_t reg_status; bus_size_t reg_baud_rate; bus_size_t reg_soft_reset; struct twsi_baud_rate baud_rate[IIC_FASTEST + 1]; }; DECLARE_CLASS(twsi_driver); #define TWSI_BAUD_RATE_PARAM(M,N) ((((M) << 3) | ((N) & 0x7)) & 0x7f) int twsi_attach(device_t); int twsi_detach(device_t); #endif /* _TWSI_H_ */ Index: stable/12/sys/dts/arm64/overlays/sun50i-a64-spi0-spigen.dtso =================================================================== --- stable/12/sys/dts/arm64/overlays/sun50i-a64-spi0-spigen.dtso (nonexistent) +++ stable/12/sys/dts/arm64/overlays/sun50i-a64-spi0-spigen.dtso (revision 362350) @@ -0,0 +1,17 @@ +/dts-v1/; +/plugin/; + +/ { + compatible = "allwinner,sun50i-a64"; +}; + +&{/soc/spi@1c68000} { + status = "okay"; + + spigen0: spigen0 { + compatible = "freebsd,spigen"; + reg = <0>; + spi-max-frequency = <500000>; + status = "okay"; + }; +}; Property changes on: stable/12/sys/dts/arm64/overlays/sun50i-a64-spi0-spigen.dtso ___________________________________________________________________ Added: fbsd:nokeywords ## -0,0 +1 ## +yes \ No newline at end of property Index: stable/12/sys/modules/dtb/allwinner/Makefile =================================================================== --- stable/12/sys/modules/dtb/allwinner/Makefile (revision 362349) +++ stable/12/sys/modules/dtb/allwinner/Makefile (revision 362350) @@ -1,66 +1,67 @@ # $FreeBSD$ # All the dts files for allwinner systems we support. .if ${MACHINE_ARCH} == "armv7" DTS= \ sun4i-a10-cubieboard.dts \ sun4i-a10-olinuxino-lime.dts \ sun6i-a31s-sinovoip-bpi-m2.dts \ sun5i-a13-olinuxino.dts \ sun5i-r8-chip.dts \ sun7i-a20-bananapi.dts \ sun7i-a20-cubieboard2.dts \ sun7i-a20-lamobo-r1.dts \ sun7i-a20-olimex-som-evb.dts \ sun7i-a20-pcduino3.dts \ sun8i-a83t-bananapi-m3.dts \ sun8i-h2-plus-orangepi-r1.dts \ sun8i-h2-plus-orangepi-zero.dts \ sun8i-h3-nanopi-m1.dts \ sun8i-h3-nanopi-m1-plus.dts \ sun8i-h3-nanopi-neo.dts \ sun8i-h3-orangepi-one.dts \ sun8i-h3-orangepi-pc.dts \ sun8i-h3-orangepi-plus2e.dts DTSO= sun8i-a83t-sid.dtso \ sun8i-h3-i2c0.dtso \ sun8i-h3-sid.dtso \ sun8i-h3-ths.dtso LINKS= \ ${DTBDIR}/sun4i-a10-cubieboard.dtb ${DTBDIR}/cubieboard.dtb \ ${DTBDIR}/sun4i-a10-olinuxino-lime.dtb ${DTBDIR}/olinuxino-lime.dtb \ ${DTBDIR}/sun6i-a31s-sinovoip-bpi-m2.dtb ${DTBDIR}/bananapim2.dtb \ ${DTBDIR}/sun7i-a20-bananapi.dtb ${DTBDIR}/bananapi.dtb \ ${DTBDIR}/sun7i-a20-cubieboard2.dtb ${DTBDIR}/cubieboard2.dtb \ ${DTBDIR}/sun7i-a20-olimex-som-evb.dtb ${DTBDIR}/olimex-a20-som-evb.dtb \ ${DTBDIR}/sun7i-a20-pcduino3.dtb ${DTBDIR}/pcduino3.dtb \ ${DTBDIR}/sun8i-a83t-bananapi-m3.dtb ${DTBDIR}/sinovoip-bpi-m3.dtb \ ${DTBDIR}/sun8i-a83t-bananapi-m3.dtb ${DTBDIR}/sun8i-a83t-sinovoip-bpi-m3.dtb .elif ${MACHINE_ARCH} == "aarch64" DTS= \ allwinner/sun50i-a64-nanopi-a64.dts \ allwinner/sun50i-a64-olinuxino.dts \ allwinner/sun50i-a64-pine64-lts.dts \ allwinner/sun50i-a64-pine64-plus.dts \ allwinner/sun50i-a64-pine64.dts \ allwinner/sun50i-a64-pinebook.dts \ allwinner/sun50i-a64-sopine-baseboard.dts \ allwinner/sun50i-h5-orangepi-pc2.dts \ allwinner/sun50i-h5-nanopi-neo2.dts DTSO= sun50i-a64-opp.dtso \ sun50i-a64-pwm.dtso \ sun50i-a64-rpwm.dtso \ sun50i-a64-sid.dtso \ + sun50i-a64-spi0-spigen.dtso \ sun50i-a64-ths.dtso \ sun50i-a64-timer.dtso \ sun50i-h5-opp.dtso \ sun50i-h5-sid.dtso \ sun50i-h5-ths.dtso \ sun50i-h5-nanopi-neo2-opp.dtso .endif .include Index: stable/12 =================================================================== --- stable/12 (revision 362349) +++ stable/12 (revision 362350) Property changes on: stable/12 ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head:r356276,356609-356610,356637,356798-356800,356802