Index: head/sys/arm/allwinner/a10_fb.c =================================================================== --- head/sys/arm/allwinner/a10_fb.c (revision 355357) +++ head/sys/arm/allwinner/a10_fb.c (revision 355358) @@ -1,663 +1,662 @@ /*- * 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$ */ /* * Allwinner A10/A20 Framebuffer */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fb_if.h" #include "hdmi_if.h" #define FB_DEFAULT_W 800 #define FB_DEFAULT_H 600 #define FB_DEFAULT_REF 60 #define FB_BPP 32 #define FB_ALIGN 0x1000 #define HDMI_ENABLE_DELAY 20000 #define DEBE_FREQ 300000000 #define DOT_CLOCK_TO_HZ(c) ((c) * 1000) /* Display backend */ #define DEBE_REG_START 0x800 #define DEBE_REG_END 0x1000 #define DEBE_REG_WIDTH 4 #define DEBE_MODCTL 0x800 #define MODCTL_ITLMOD_EN (1 << 28) #define MODCTL_OUT_SEL_MASK (0x7 << 20) #define MODCTL_OUT_SEL(sel) ((sel) << 20) #define OUT_SEL_LCD 0 #define MODCTL_LAY0_EN (1 << 8) #define MODCTL_START_CTL (1 << 1) #define MODCTL_EN (1 << 0) #define DEBE_DISSIZE 0x808 #define DIS_HEIGHT(h) (((h) - 1) << 16) #define DIS_WIDTH(w) (((w) - 1) << 0) #define DEBE_LAYSIZE0 0x810 #define LAY_HEIGHT(h) (((h) - 1) << 16) #define LAY_WIDTH(w) (((w) - 1) << 0) #define DEBE_LAYCOOR0 0x820 #define LAY_XCOOR(x) ((x) << 16) #define LAY_YCOOR(y) ((y) << 0) #define DEBE_LAYLINEWIDTH0 0x840 #define DEBE_LAYFB_L32ADD0 0x850 #define LAYFB_L32ADD(pa) ((pa) << 3) #define DEBE_LAYFB_H4ADD 0x860 #define LAY0FB_H4ADD(pa) ((pa) >> 29) #define DEBE_REGBUFFCTL 0x870 #define REGBUFFCTL_LOAD (1 << 0) #define DEBE_ATTCTL1 0x8a0 #define ATTCTL1_FBFMT(fmt) ((fmt) << 8) #define FBFMT_XRGB8888 9 #define ATTCTL1_FBPS(ps) ((ps) << 0) #define FBPS_32BPP_ARGB 0 /* Timing controller */ #define TCON_GCTL 0x000 #define GCTL_TCON_EN (1 << 31) #define GCTL_IO_MAP_SEL_TCON1 (1 << 0) #define TCON_GINT1 0x008 #define GINT1_TCON1_LINENO(n) (((n) + 2) << 0) #define TCON0_DCLK 0x044 #define DCLK_EN 0xf0000000 #define TCON1_CTL 0x090 #define TCON1_EN (1 << 31) #define INTERLACE_EN (1 << 20) #define TCON1_SRC_SEL(src) ((src) << 0) #define TCON1_SRC_CH1 0 #define TCON1_SRC_CH2 1 #define TCON1_SRC_BLUE 2 #define TCON1_START_DELAY(sd) ((sd) << 4) #define TCON1_BASIC0 0x094 #define TCON1_BASIC1 0x098 #define TCON1_BASIC2 0x09c #define TCON1_BASIC3 0x0a0 #define TCON1_BASIC4 0x0a4 #define TCON1_BASIC5 0x0a8 #define BASIC_X(x) (((x) - 1) << 16) #define BASIC_Y(y) (((y) - 1) << 0) #define BASIC3_HT(ht) (((ht) - 1) << 16) #define BASIC3_HBP(hbp) (((hbp) - 1) << 0) #define BASIC4_VT(vt) ((vt) << 16) #define BASIC4_VBP(vbp) (((vbp) - 1) << 0) #define BASIC5_HSPW(hspw) (((hspw) - 1) << 16) #define BASIC5_VSPW(vspw) (((vspw) - 1) << 0) #define TCON1_IO_POL 0x0f0 #define IO_POL_IO2_INV (1 << 26) #define IO_POL_PHSYNC (1 << 25) #define IO_POL_PVSYNC (1 << 24) #define TCON1_IO_TRI 0x0f4 #define IO0_OUTPUT_TRI_EN (1 << 24) #define IO1_OUTPUT_TRI_EN (1 << 25) #define IO_TRI_MASK 0xffffffff #define START_DELAY(vbl) (MIN(32, (vbl)) - 2) #define VBLANK_LEN(vt, vd, i) ((((vt) << (i)) >> 1) - (vd) - 2) #define VTOTAL(vt) ((vt) * 2) #define DIVIDE(x, y) (((x) + ((y) / 2)) / (y)) struct a10fb_softc { device_t dev; device_t fbdev; struct resource *res[2]; /* Framebuffer */ struct fb_info info; size_t fbsize; bus_addr_t paddr; vm_offset_t vaddr; /* HDMI */ eventhandler_tag hdmi_evh; }; static struct resource_spec a10fb_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, /* DEBE */ { SYS_RES_MEMORY, 1, RF_ACTIVE }, /* TCON */ { -1, 0 } }; #define DEBE_READ(sc, reg) bus_read_4((sc)->res[0], (reg)) #define DEBE_WRITE(sc, reg, val) bus_write_4((sc)->res[0], (reg), (val)) #define TCON_READ(sc, reg) bus_read_4((sc)->res[1], (reg)) #define TCON_WRITE(sc, reg, val) bus_write_4((sc)->res[1], (reg), (val)) static int a10fb_allocfb(struct a10fb_softc *sc) { sc->vaddr = kmem_alloc_contig(sc->fbsize, M_NOWAIT | M_ZERO, 0, ~0, FB_ALIGN, 0, VM_MEMATTR_WRITE_COMBINING); if (sc->vaddr == 0) { device_printf(sc->dev, "failed to allocate FB memory\n"); return (ENOMEM); } sc->paddr = pmap_kextract(sc->vaddr); return (0); } static void a10fb_freefb(struct a10fb_softc *sc) { kmem_free(sc->vaddr, sc->fbsize); } static int a10fb_setup_debe(struct a10fb_softc *sc, const struct videomode *mode) { int width, height, interlace, reg; clk_t clk_ahb, clk_dram, clk_debe; hwreset_t rst; uint32_t val; int error; interlace = !!(mode->flags & VID_INTERLACE); width = mode->hdisplay; height = mode->vdisplay << interlace; /* Leave reset */ error = hwreset_get_by_ofw_name(sc->dev, 0, "de_be", &rst); if (error != 0) { device_printf(sc->dev, "cannot find reset 'de_be'\n"); return (error); } error = hwreset_deassert(rst); if (error != 0) { device_printf(sc->dev, "couldn't de-assert reset 'de_be'\n"); return (error); } /* Gating AHB clock for BE */ error = clk_get_by_ofw_name(sc->dev, 0, "ahb_de_be", &clk_ahb); if (error != 0) { device_printf(sc->dev, "cannot find clk 'ahb_de_be'\n"); return (error); } error = clk_enable(clk_ahb); if (error != 0) { device_printf(sc->dev, "cannot enable clk 'ahb_de_be'\n"); return (error); } /* Enable DRAM clock to BE */ error = clk_get_by_ofw_name(sc->dev, 0, "dram_de_be", &clk_dram); if (error != 0) { device_printf(sc->dev, "cannot find clk 'dram_de_be'\n"); return (error); } error = clk_enable(clk_dram); if (error != 0) { device_printf(sc->dev, "cannot enable clk 'dram_de_be'\n"); return (error); } /* Set BE clock to 300MHz and enable */ error = clk_get_by_ofw_name(sc->dev, 0, "de_be", &clk_debe); if (error != 0) { device_printf(sc->dev, "cannot find clk 'de_be'\n"); return (error); } error = clk_set_freq(clk_debe, DEBE_FREQ, CLK_SET_ROUND_DOWN); if (error != 0) { device_printf(sc->dev, "cannot set 'de_be' frequency\n"); return (error); } error = clk_enable(clk_debe); if (error != 0) { device_printf(sc->dev, "cannot enable clk 'de_be'\n"); return (error); } /* Initialize all registers to 0 */ for (reg = DEBE_REG_START; reg < DEBE_REG_END; reg += DEBE_REG_WIDTH) DEBE_WRITE(sc, reg, 0); /* Enable display backend */ DEBE_WRITE(sc, DEBE_MODCTL, MODCTL_EN); /* Set display size */ DEBE_WRITE(sc, DEBE_DISSIZE, DIS_HEIGHT(height) | DIS_WIDTH(width)); /* Set layer 0 size, position, and stride */ DEBE_WRITE(sc, DEBE_LAYSIZE0, LAY_HEIGHT(height) | LAY_WIDTH(width)); DEBE_WRITE(sc, DEBE_LAYCOOR0, LAY_XCOOR(0) | LAY_YCOOR(0)); DEBE_WRITE(sc, DEBE_LAYLINEWIDTH0, width * FB_BPP); /* Point layer 0 to FB memory */ DEBE_WRITE(sc, DEBE_LAYFB_L32ADD0, LAYFB_L32ADD(sc->paddr)); DEBE_WRITE(sc, DEBE_LAYFB_H4ADD, LAY0FB_H4ADD(sc->paddr)); /* Set backend format and pixel sequence */ DEBE_WRITE(sc, DEBE_ATTCTL1, ATTCTL1_FBFMT(FBFMT_XRGB8888) | ATTCTL1_FBPS(FBPS_32BPP_ARGB)); /* Enable layer 0, output to LCD, setup interlace */ val = DEBE_READ(sc, DEBE_MODCTL); val |= MODCTL_LAY0_EN; val &= ~MODCTL_OUT_SEL_MASK; val |= MODCTL_OUT_SEL(OUT_SEL_LCD); if (interlace) val |= MODCTL_ITLMOD_EN; else val &= ~MODCTL_ITLMOD_EN; DEBE_WRITE(sc, DEBE_MODCTL, val); /* Commit settings */ DEBE_WRITE(sc, DEBE_REGBUFFCTL, REGBUFFCTL_LOAD); /* Start DEBE */ val = DEBE_READ(sc, DEBE_MODCTL); val |= MODCTL_START_CTL; DEBE_WRITE(sc, DEBE_MODCTL, val); return (0); } static int a10fb_setup_pll(struct a10fb_softc *sc, uint64_t freq) { clk_t clk_sclk1, clk_sclk2; int error; error = clk_get_by_ofw_name(sc->dev, 0, "lcd_ch1_sclk1", &clk_sclk1); if (error != 0) { device_printf(sc->dev, "cannot find clk 'lcd_ch1_sclk1'\n"); return (error); } error = clk_get_by_ofw_name(sc->dev, 0, "lcd_ch1_sclk2", &clk_sclk2); if (error != 0) { device_printf(sc->dev, "cannot find clk 'lcd_ch1_sclk2'\n"); return (error); } error = clk_set_freq(clk_sclk2, freq, 0); if (error != 0) { device_printf(sc->dev, "cannot set lcd ch1 frequency\n"); return (error); } error = clk_enable(clk_sclk2); if (error != 0) { device_printf(sc->dev, "cannot enable lcd ch1 sclk2\n"); return (error); } error = clk_enable(clk_sclk1); if (error != 0) { device_printf(sc->dev, "cannot enable lcd ch1 sclk1\n"); return (error); } return (0); } static int a10fb_setup_tcon(struct a10fb_softc *sc, const struct videomode *mode) { u_int interlace, hspw, hbp, vspw, vbp, vbl, width, height, start_delay; u_int vtotal, framerate, clk; clk_t clk_ahb; hwreset_t rst; uint32_t val; int error; interlace = !!(mode->flags & VID_INTERLACE); width = mode->hdisplay; height = mode->vdisplay; hspw = mode->hsync_end - mode->hsync_start; hbp = mode->htotal - mode->hsync_start; vspw = mode->vsync_end - mode->vsync_start; vbp = mode->vtotal - mode->vsync_start; vbl = VBLANK_LEN(mode->vtotal, mode->vdisplay, interlace); start_delay = START_DELAY(vbl); /* Leave reset */ error = hwreset_get_by_ofw_name(sc->dev, 0, "lcd", &rst); if (error != 0) { device_printf(sc->dev, "cannot find reset 'lcd'\n"); return (error); } error = hwreset_deassert(rst); if (error != 0) { device_printf(sc->dev, "couldn't de-assert reset 'lcd'\n"); return (error); } /* Gating AHB clock for LCD */ error = clk_get_by_ofw_name(sc->dev, 0, "ahb_lcd", &clk_ahb); if (error != 0) { device_printf(sc->dev, "cannot find clk 'ahb_lcd'\n"); return (error); } error = clk_enable(clk_ahb); if (error != 0) { device_printf(sc->dev, "cannot enable clk 'ahb_lcd'\n"); return (error); } /* Disable TCON and TCON1 */ TCON_WRITE(sc, TCON_GCTL, 0); TCON_WRITE(sc, TCON1_CTL, 0); /* Enable clocks */ TCON_WRITE(sc, TCON0_DCLK, DCLK_EN); /* Disable IO and data output ports */ TCON_WRITE(sc, TCON1_IO_TRI, IO_TRI_MASK); /* Disable TCON and select TCON1 */ TCON_WRITE(sc, TCON_GCTL, GCTL_IO_MAP_SEL_TCON1); /* Source width and height */ TCON_WRITE(sc, TCON1_BASIC0, BASIC_X(width) | BASIC_Y(height)); /* Scaler width and height */ TCON_WRITE(sc, TCON1_BASIC1, BASIC_X(width) | BASIC_Y(height)); /* Output width and height */ TCON_WRITE(sc, TCON1_BASIC2, BASIC_X(width) | BASIC_Y(height)); /* Horizontal total and back porch */ TCON_WRITE(sc, TCON1_BASIC3, BASIC3_HT(mode->htotal) | BASIC3_HBP(hbp)); /* Vertical total and back porch */ vtotal = VTOTAL(mode->vtotal); if (interlace) { framerate = DIVIDE(DIVIDE(DOT_CLOCK_TO_HZ(mode->dot_clock), mode->htotal), mode->vtotal); clk = mode->htotal * (VTOTAL(mode->vtotal) + 1) * framerate; if ((clk / 2) == DOT_CLOCK_TO_HZ(mode->dot_clock)) vtotal += 1; } TCON_WRITE(sc, TCON1_BASIC4, BASIC4_VT(vtotal) | BASIC4_VBP(vbp)); /* Horizontal and vertical sync */ TCON_WRITE(sc, TCON1_BASIC5, BASIC5_HSPW(hspw) | BASIC5_VSPW(vspw)); /* Polarity */ val = IO_POL_IO2_INV; if (mode->flags & VID_PHSYNC) val |= IO_POL_PHSYNC; if (mode->flags & VID_PVSYNC) val |= IO_POL_PVSYNC; TCON_WRITE(sc, TCON1_IO_POL, val); /* Set scan line for TCON1 line trigger */ TCON_WRITE(sc, TCON_GINT1, GINT1_TCON1_LINENO(start_delay)); /* Enable TCON1 */ val = TCON1_EN; if (interlace) val |= INTERLACE_EN; val |= TCON1_START_DELAY(start_delay); val |= TCON1_SRC_SEL(TCON1_SRC_CH1); TCON_WRITE(sc, TCON1_CTL, val); /* Setup PLL */ return (a10fb_setup_pll(sc, DOT_CLOCK_TO_HZ(mode->dot_clock))); } static void a10fb_enable_tcon(struct a10fb_softc *sc, int onoff) { uint32_t val; /* Enable TCON */ val = TCON_READ(sc, TCON_GCTL); if (onoff) val |= GCTL_TCON_EN; else val &= ~GCTL_TCON_EN; TCON_WRITE(sc, TCON_GCTL, val); /* Enable TCON1 IO0/IO1 outputs */ val = TCON_READ(sc, TCON1_IO_TRI); if (onoff) val &= ~(IO0_OUTPUT_TRI_EN | IO1_OUTPUT_TRI_EN); else val |= (IO0_OUTPUT_TRI_EN | IO1_OUTPUT_TRI_EN); TCON_WRITE(sc, TCON1_IO_TRI, val); } static int a10fb_configure(struct a10fb_softc *sc, const struct videomode *mode) { size_t fbsize; int error; fbsize = round_page(mode->hdisplay * mode->vdisplay * (FB_BPP / NBBY)); /* Detach the old FB device */ if (sc->fbdev != NULL) { device_delete_child(sc->dev, sc->fbdev); sc->fbdev = NULL; } /* If the FB size has changed, free the old FB memory */ if (sc->fbsize > 0 && sc->fbsize != fbsize) { a10fb_freefb(sc); sc->vaddr = 0; } /* Allocate the FB if necessary */ sc->fbsize = fbsize; if (sc->vaddr == 0) { error = a10fb_allocfb(sc); if (error != 0) { device_printf(sc->dev, "failed to allocate FB memory\n"); return (ENXIO); } } /* Setup display backend */ error = a10fb_setup_debe(sc, mode); if (error != 0) return (error); /* Setup display timing controller */ error = a10fb_setup_tcon(sc, mode); if (error != 0) return (error); /* Attach framebuffer device */ sc->info.fb_name = device_get_nameunit(sc->dev); sc->info.fb_vbase = (intptr_t)sc->vaddr; sc->info.fb_pbase = sc->paddr; sc->info.fb_size = sc->fbsize; sc->info.fb_bpp = sc->info.fb_depth = FB_BPP; sc->info.fb_stride = mode->hdisplay * (FB_BPP / NBBY); sc->info.fb_width = mode->hdisplay; sc->info.fb_height = mode->vdisplay; sc->fbdev = device_add_child(sc->dev, "fbd", device_get_unit(sc->dev)); if (sc->fbdev == NULL) { device_printf(sc->dev, "failed to add fbd child\n"); return (ENOENT); } error = device_probe_and_attach(sc->fbdev); if (error != 0) { device_printf(sc->dev, "failed to attach fbd device\n"); return (error); } return (0); } static void a10fb_hdmi_event(void *arg, device_t hdmi_dev) { const struct videomode *mode; struct videomode hdmi_mode; struct a10fb_softc *sc; struct edid_info ei; uint8_t *edid; uint32_t edid_len; int error; sc = arg; edid = NULL; edid_len = 0; mode = NULL; error = HDMI_GET_EDID(hdmi_dev, &edid, &edid_len); if (error != 0) { device_printf(sc->dev, "failed to get EDID: %d\n", error); } else { error = edid_parse(edid, &ei); if (error != 0) { device_printf(sc->dev, "failed to parse EDID: %d\n", error); } else { if (bootverbose) edid_print(&ei); mode = ei.edid_preferred_mode; } } /* If the preferred mode could not be determined, use the default */ if (mode == NULL) mode = pick_mode_by_ref(FB_DEFAULT_W, FB_DEFAULT_H, FB_DEFAULT_REF); if (mode == NULL) { device_printf(sc->dev, "failed to find usable video mode\n"); return; } if (bootverbose) device_printf(sc->dev, "using %dx%d\n", mode->hdisplay, mode->vdisplay); /* Disable HDMI */ HDMI_ENABLE(hdmi_dev, 0); /* Disable timing controller */ a10fb_enable_tcon(sc, 0); /* Configure DEBE and TCON */ error = a10fb_configure(sc, mode); if (error != 0) { device_printf(sc->dev, "failed to configure FB: %d\n", error); return; } hdmi_mode = *mode; hdmi_mode.hskew = mode->hsync_end - mode->hsync_start; hdmi_mode.flags |= VID_HSKEW; HDMI_SET_VIDEOMODE(hdmi_dev, &hdmi_mode); /* Enable timing controller */ a10fb_enable_tcon(sc, 1); DELAY(HDMI_ENABLE_DELAY); /* Enable HDMI */ HDMI_ENABLE(hdmi_dev, 1); } static int a10fb_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "allwinner,sun7i-a20-fb")) return (ENXIO); device_set_desc(dev, "Allwinner Framebuffer"); return (BUS_PROBE_DEFAULT); } static int a10fb_attach(device_t dev) { struct a10fb_softc *sc; sc = device_get_softc(dev); sc->dev = dev; if (bus_alloc_resources(dev, a10fb_spec, sc->res)) { device_printf(dev, "cannot allocate resources for device\n"); return (ENXIO); } sc->hdmi_evh = EVENTHANDLER_REGISTER(hdmi_event, a10fb_hdmi_event, sc, 0); return (0); } static struct fb_info * a10fb_fb_getinfo(device_t dev) { struct a10fb_softc *sc; sc = device_get_softc(dev); return (&sc->info); } static device_method_t a10fb_methods[] = { /* Device interface */ DEVMETHOD(device_probe, a10fb_probe), DEVMETHOD(device_attach, a10fb_attach), /* FB interface */ DEVMETHOD(fb_getinfo, a10fb_fb_getinfo), DEVMETHOD_END }; static driver_t a10fb_driver = { "fb", a10fb_methods, sizeof(struct a10fb_softc), }; static devclass_t a10fb_devclass; DRIVER_MODULE(fb, simplebus, a10fb_driver, a10fb_devclass, 0, 0); Index: head/sys/arm/allwinner/a10_hdmi.c =================================================================== --- head/sys/arm/allwinner/a10_hdmi.c (revision 355357) +++ head/sys/arm/allwinner/a10_hdmi.c (revision 355358) @@ -1,726 +1,725 @@ /*- * 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$ */ /* * Allwinner A10/A20 HDMI TX */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hdmi_if.h" #define HDMI_CTRL 0x004 #define CTRL_MODULE_EN (1 << 31) #define HDMI_INT_STATUS 0x008 #define HDMI_HPD 0x00c #define HPD_DET (1 << 0) #define HDMI_VID_CTRL 0x010 #define VID_CTRL_VIDEO_EN (1 << 31) #define VID_CTRL_HDMI_MODE (1 << 30) #define VID_CTRL_INTERLACE (1 << 4) #define VID_CTRL_REPEATER_2X (1 << 0) #define HDMI_VID_TIMING0 0x014 #define VID_ACT_V(v) (((v) - 1) << 16) #define VID_ACT_H(h) (((h) - 1) << 0) #define HDMI_VID_TIMING1 0x018 #define VID_VBP(vbp) (((vbp) - 1) << 16) #define VID_HBP(hbp) (((hbp) - 1) << 0) #define HDMI_VID_TIMING2 0x01c #define VID_VFP(vfp) (((vfp) - 1) << 16) #define VID_HFP(hfp) (((hfp) - 1) << 0) #define HDMI_VID_TIMING3 0x020 #define VID_VSPW(vspw) (((vspw) - 1) << 16) #define VID_HSPW(hspw) (((hspw) - 1) << 0) #define HDMI_VID_TIMING4 0x024 #define TX_CLOCK_NORMAL 0x03e00000 #define VID_VSYNC_ACTSEL (1 << 1) #define VID_HSYNC_ACTSEL (1 << 0) #define HDMI_AUD_CTRL 0x040 #define AUD_CTRL_EN (1 << 31) #define AUD_CTRL_RST (1 << 30) #define HDMI_ADMA_CTRL 0x044 #define HDMI_ADMA_MODE (1 << 31) #define HDMI_ADMA_MODE_DDMA (0 << 31) #define HDMI_ADMA_MODE_NDMA (1 << 31) #define HDMI_AUD_FMT 0x048 #define AUD_FMT_CH(n) ((n) - 1) #define HDMI_PCM_CTRL 0x04c #define HDMI_AUD_CTS 0x050 #define HDMI_AUD_N 0x054 #define HDMI_AUD_CH_STATUS0 0x058 #define CH_STATUS0_FS_FREQ (0xf << 24) #define CH_STATUS0_FS_FREQ_48 (2 << 24) #define HDMI_AUD_CH_STATUS1 0x05c #define CH_STATUS1_WORD_LEN (0x7 << 1) #define CH_STATUS1_WORD_LEN_16 (1 << 1) #define HDMI_AUDIO_RESET_RETRY 1000 #define HDMI_AUDIO_CHANNELS 2 #define HDMI_AUDIO_CHANNELMAP 0x76543210 #define HDMI_AUDIO_N 6144 /* 48 kHz */ #define HDMI_AUDIO_CTS(r, n) ((((r) * 10) * ((n) / 128)) / 480) #define HDMI_PADCTRL0 0x200 #define PADCTRL0_BIASEN (1 << 31) #define PADCTRL0_LDOCEN (1 << 30) #define PADCTRL0_LDODEN (1 << 29) #define PADCTRL0_PWENC (1 << 28) #define PADCTRL0_PWEND (1 << 27) #define PADCTRL0_PWENG (1 << 26) #define PADCTRL0_CKEN (1 << 25) #define PADCTRL0_SEN (1 << 24) #define PADCTRL0_TXEN (1 << 23) #define HDMI_PADCTRL1 0x204 #define PADCTRL1_AMP_OPT (1 << 23) #define PADCTRL1_AMPCK_OPT (1 << 22) #define PADCTRL1_DMP_OPT (1 << 21) #define PADCTRL1_EMP_OPT (1 << 20) #define PADCTRL1_EMPCK_OPT (1 << 19) #define PADCTRL1_PWSCK (1 << 18) #define PADCTRL1_PWSDT (1 << 17) #define PADCTRL1_REG_CSMPS (1 << 16) #define PADCTRL1_REG_DEN (1 << 15) #define PADCTRL1_REG_DENCK (1 << 14) #define PADCTRL1_REG_PLRCK (1 << 13) #define PADCTRL1_REG_EMP (0x7 << 10) #define PADCTRL1_REG_EMP_EN (0x2 << 10) #define PADCTRL1_REG_CD (0x3 << 8) #define PADCTRL1_REG_CKSS (0x3 << 6) #define PADCTRL1_REG_CKSS_1X (0x1 << 6) #define PADCTRL1_REG_CKSS_2X (0x0 << 6) #define PADCTRL1_REG_AMP (0x7 << 3) #define PADCTRL1_REG_AMP_EN (0x6 << 3) #define PADCTRL1_REG_PLR (0x7 << 0) #define HDMI_PLLCTRL0 0x208 #define PLLCTRL0_PLL_EN (1 << 31) #define PLLCTRL0_BWS (1 << 30) #define PLLCTRL0_HV_IS_33 (1 << 29) #define PLLCTRL0_LDO1_EN (1 << 28) #define PLLCTRL0_LDO2_EN (1 << 27) #define PLLCTRL0_SDIV2 (1 << 25) #define PLLCTRL0_VCO_GAIN (0x1 << 22) #define PLLCTRL0_S (0x7 << 17) #define PLLCTRL0_CP_S (0xf << 12) #define PLLCTRL0_CS (0x7 << 8) #define PLLCTRL0_PREDIV(x) ((x) << 4) #define PLLCTRL0_VCO_S (0x8 << 0) #define HDMI_PLLDBG0 0x20c #define PLLDBG0_CKIN_SEL (1 << 21) #define PLLDBG0_CKIN_SEL_PLL3 (0 << 21) #define PLLDBG0_CKIN_SEL_PLL7 (1 << 21) #define HDMI_PKTCTRL0 0x2f0 #define HDMI_PKTCTRL1 0x2f4 #define PKTCTRL_PACKET(n,t) ((t) << ((n) << 2)) #define PKT_NULL 0 #define PKT_GC 1 #define PKT_AVI 2 #define PKT_AI 3 #define PKT_SPD 5 #define PKT_END 15 #define DDC_CTRL 0x500 #define CTRL_DDC_EN (1 << 31) #define CTRL_DDC_ACMD_START (1 << 30) #define CTRL_DDC_FIFO_DIR (1 << 8) #define CTRL_DDC_FIFO_DIR_READ (0 << 8) #define CTRL_DDC_FIFO_DIR_WRITE (1 << 8) #define CTRL_DDC_SWRST (1 << 0) #define DDC_SLAVE_ADDR 0x504 #define SLAVE_ADDR_SEG_SHIFT 24 #define SLAVE_ADDR_EDDC_SHIFT 16 #define SLAVE_ADDR_OFFSET_SHIFT 8 #define SLAVE_ADDR_SHIFT 0 #define DDC_INT_STATUS 0x50c #define INT_STATUS_XFER_DONE (1 << 0) #define DDC_FIFO_CTRL 0x510 #define FIFO_CTRL_CLEAR (1 << 31) #define DDC_BYTE_COUNTER 0x51c #define DDC_COMMAND 0x520 #define COMMAND_EOREAD (4 << 0) #define DDC_CLOCK 0x528 #define DDC_CLOCK_M (1 << 3) #define DDC_CLOCK_N (5 << 0) #define DDC_FIFO 0x518 #define SWRST_DELAY 1000 #define DDC_DELAY 1000 #define DDC_RETRY 1000 #define DDC_BLKLEN 16 #define DDC_ADDR 0x50 #define EDDC_ADDR 0x60 #define EDID_LENGTH 128 #define DDC_CTRL_LINE 0x540 #define DDC_LINE_SCL_ENABLE (1 << 8) #define DDC_LINE_SDA_ENABLE (1 << 9) #define HDMI_ENABLE_DELAY 50000 #define DDC_READ_RETRY 4 #define EXT_TAG 0x00 #define CEA_TAG_ID 0x02 #define CEA_DTD 0x03 #define DTD_BASIC_AUDIO (1 << 6) #define CEA_REV 0x02 #define CEA_DATA_OFF 0x03 #define CEA_DATA_START 4 #define BLOCK_TAG(x) (((x) >> 5) & 0x7) #define BLOCK_TAG_VSDB 3 #define BLOCK_LEN(x) ((x) & 0x1f) #define HDMI_VSDB_MINLEN 5 #define HDMI_OUI "\x03\x0c\x00" #define HDMI_OUI_LEN 3 #define HDMI_DEFAULT_FREQ 297000000 struct a10hdmi_softc { struct resource *res; struct intr_config_hook mode_hook; uint8_t edid[EDID_LENGTH]; int has_hdmi; int has_audio; clk_t clk_ahb; clk_t clk_hdmi; clk_t clk_lcd; }; static struct resource_spec a10hdmi_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { -1, 0 } }; #define HDMI_READ(sc, reg) bus_read_4((sc)->res, (reg)) #define HDMI_WRITE(sc, reg, val) bus_write_4((sc)->res, (reg), (val)) static void a10hdmi_init(struct a10hdmi_softc *sc) { /* Enable the HDMI module */ HDMI_WRITE(sc, HDMI_CTRL, CTRL_MODULE_EN); /* Configure PLL/DRV settings */ HDMI_WRITE(sc, HDMI_PADCTRL0, PADCTRL0_BIASEN | PADCTRL0_LDOCEN | PADCTRL0_LDODEN | PADCTRL0_PWENC | PADCTRL0_PWEND | PADCTRL0_PWENG | PADCTRL0_CKEN | PADCTRL0_TXEN); HDMI_WRITE(sc, HDMI_PADCTRL1, PADCTRL1_AMP_OPT | PADCTRL1_AMPCK_OPT | PADCTRL1_EMP_OPT | PADCTRL1_EMPCK_OPT | PADCTRL1_REG_DEN | PADCTRL1_REG_DENCK | PADCTRL1_REG_EMP_EN | PADCTRL1_REG_AMP_EN); /* Select PLL3 as input clock */ HDMI_WRITE(sc, HDMI_PLLDBG0, PLLDBG0_CKIN_SEL_PLL3); DELAY(HDMI_ENABLE_DELAY); } static void a10hdmi_hpd(void *arg) { struct a10hdmi_softc *sc; device_t dev; uint32_t hpd; dev = arg; sc = device_get_softc(dev); hpd = HDMI_READ(sc, HDMI_HPD); if ((hpd & HPD_DET) == HPD_DET) EVENTHANDLER_INVOKE(hdmi_event, dev, HDMI_EVENT_CONNECTED); config_intrhook_disestablish(&sc->mode_hook); } static int a10hdmi_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "allwinner,sun7i-a20-hdmi")) return (ENXIO); device_set_desc(dev, "Allwinner HDMI TX"); return (BUS_PROBE_DEFAULT); } static int a10hdmi_attach(device_t dev) { struct a10hdmi_softc *sc; int error; sc = device_get_softc(dev); if (bus_alloc_resources(dev, a10hdmi_spec, &sc->res)) { device_printf(dev, "cannot allocate resources for device\n"); return (ENXIO); } /* Setup clocks */ error = clk_get_by_ofw_name(dev, 0, "ahb", &sc->clk_ahb); if (error != 0) { device_printf(dev, "cannot find ahb clock\n"); return (error); } error = clk_get_by_ofw_name(dev, 0, "hdmi", &sc->clk_hdmi); if (error != 0) { device_printf(dev, "cannot find hdmi clock\n"); return (error); } error = clk_get_by_ofw_name(dev, 0, "lcd", &sc->clk_lcd); if (error != 0) { device_printf(dev, "cannot find lcd clock\n"); } /* Enable HDMI clock */ error = clk_enable(sc->clk_hdmi); if (error != 0) { device_printf(dev, "cannot enable hdmi clock\n"); return (error); } /* Gating AHB clock for HDMI */ error = clk_enable(sc->clk_ahb); if (error != 0) { device_printf(dev, "cannot enable ahb gate\n"); return (error); } a10hdmi_init(sc); sc->mode_hook.ich_func = a10hdmi_hpd; sc->mode_hook.ich_arg = dev; error = config_intrhook_establish(&sc->mode_hook); if (error != 0) return (error); return (0); } static int a10hdmi_ddc_xfer(struct a10hdmi_softc *sc, uint16_t addr, uint8_t seg, uint8_t off, int len) { uint32_t val; int retry; /* Set FIFO direction to read */ val = HDMI_READ(sc, DDC_CTRL); val &= ~CTRL_DDC_FIFO_DIR; val |= CTRL_DDC_FIFO_DIR_READ; HDMI_WRITE(sc, DDC_CTRL, val); /* Setup DDC slave address */ val = (addr << SLAVE_ADDR_SHIFT) | (seg << SLAVE_ADDR_SEG_SHIFT) | (EDDC_ADDR << SLAVE_ADDR_EDDC_SHIFT) | (off << SLAVE_ADDR_OFFSET_SHIFT); HDMI_WRITE(sc, DDC_SLAVE_ADDR, val); /* Clear FIFO */ val = HDMI_READ(sc, DDC_FIFO_CTRL); val |= FIFO_CTRL_CLEAR; HDMI_WRITE(sc, DDC_FIFO_CTRL, val); /* Set transfer length */ HDMI_WRITE(sc, DDC_BYTE_COUNTER, len); /* Set command to "Explicit Offset Address Read" */ HDMI_WRITE(sc, DDC_COMMAND, COMMAND_EOREAD); /* Start transfer */ val = HDMI_READ(sc, DDC_CTRL); val |= CTRL_DDC_ACMD_START; HDMI_WRITE(sc, DDC_CTRL, val); /* Wait for command to start */ retry = DDC_RETRY; while (--retry > 0) { val = HDMI_READ(sc, DDC_CTRL); if ((val & CTRL_DDC_ACMD_START) == 0) break; DELAY(DDC_DELAY); } if (retry == 0) return (ETIMEDOUT); /* Ensure that the transfer completed */ val = HDMI_READ(sc, DDC_INT_STATUS); if ((val & INT_STATUS_XFER_DONE) == 0) return (EIO); return (0); } static int a10hdmi_ddc_read(struct a10hdmi_softc *sc, int block, uint8_t *edid) { int resid, off, len, error; uint8_t *pbuf; pbuf = edid; resid = EDID_LENGTH; off = (block & 1) ? EDID_LENGTH : 0; while (resid > 0) { len = min(resid, DDC_BLKLEN); error = a10hdmi_ddc_xfer(sc, DDC_ADDR, block >> 1, off, len); if (error != 0) return (error); bus_read_multi_1(sc->res, DDC_FIFO, pbuf, len); pbuf += len; off += len; resid -= len; } return (0); } static int a10hdmi_detect_hdmi_vsdb(uint8_t *edid) { int off, p, btag, blen; if (edid[EXT_TAG] != CEA_TAG_ID) return (0); off = edid[CEA_DATA_OFF]; /* CEA data block collection starts at byte 4 */ if (off <= CEA_DATA_START) return (0); /* Parse the CEA data blocks */ for (p = CEA_DATA_START; p < off;) { btag = BLOCK_TAG(edid[p]); blen = BLOCK_LEN(edid[p]); /* Make sure the length is sane */ if (p + blen + 1 > off) break; /* Look for a VSDB with the HDMI 24-bit IEEE registration ID */ if (btag == BLOCK_TAG_VSDB && blen >= HDMI_VSDB_MINLEN && memcmp(&edid[p + 1], HDMI_OUI, HDMI_OUI_LEN) == 0) return (1); /* Next data block */ p += (1 + blen); } return (0); } static void a10hdmi_detect_hdmi(struct a10hdmi_softc *sc, int *phdmi, int *paudio) { struct edid_info ei; uint8_t edid[EDID_LENGTH]; int block; *phdmi = *paudio = 0; if (edid_parse(sc->edid, &ei) != 0) return; /* Scan through extension blocks, looking for a CEA-861 block. */ for (block = 1; block <= ei.edid_ext_block_count; block++) { if (a10hdmi_ddc_read(sc, block, edid) != 0) return; if (a10hdmi_detect_hdmi_vsdb(edid) != 0) { *phdmi = 1; *paudio = ((edid[CEA_DTD] & DTD_BASIC_AUDIO) != 0); return; } } } static int a10hdmi_get_edid(device_t dev, uint8_t **edid, uint32_t *edid_len) { struct a10hdmi_softc *sc; int error, retry; sc = device_get_softc(dev); retry = DDC_READ_RETRY; while (--retry > 0) { /* I2C software reset */ HDMI_WRITE(sc, DDC_FIFO_CTRL, 0); HDMI_WRITE(sc, DDC_CTRL, CTRL_DDC_EN | CTRL_DDC_SWRST); DELAY(SWRST_DELAY); if (HDMI_READ(sc, DDC_CTRL) & CTRL_DDC_SWRST) { device_printf(dev, "DDC software reset failed\n"); return (ENXIO); } /* Configure DDC clock */ HDMI_WRITE(sc, DDC_CLOCK, DDC_CLOCK_M | DDC_CLOCK_N); /* Enable SDA/SCL */ HDMI_WRITE(sc, DDC_CTRL_LINE, DDC_LINE_SCL_ENABLE | DDC_LINE_SDA_ENABLE); /* Read EDID block */ error = a10hdmi_ddc_read(sc, 0, sc->edid); if (error == 0) { *edid = sc->edid; *edid_len = sizeof(sc->edid); break; } } if (error == 0) a10hdmi_detect_hdmi(sc, &sc->has_hdmi, &sc->has_audio); else sc->has_hdmi = sc->has_audio = 0; return (error); } static void a10hdmi_set_audiomode(device_t dev, const struct videomode *mode) { struct a10hdmi_softc *sc; uint32_t val; int retry; sc = device_get_softc(dev); /* Disable and reset audio module and wait for reset bit to clear */ HDMI_WRITE(sc, HDMI_AUD_CTRL, AUD_CTRL_RST); for (retry = HDMI_AUDIO_RESET_RETRY; retry > 0; retry--) { val = HDMI_READ(sc, HDMI_AUD_CTRL); if ((val & AUD_CTRL_RST) == 0) break; } if (retry == 0) { device_printf(dev, "timeout waiting for audio module\n"); return; } if (!sc->has_audio) return; /* DMA and FIFO control */ HDMI_WRITE(sc, HDMI_ADMA_CTRL, HDMI_ADMA_MODE_DDMA); /* Audio format control (LPCM, S16LE, stereo) */ HDMI_WRITE(sc, HDMI_AUD_FMT, AUD_FMT_CH(HDMI_AUDIO_CHANNELS)); /* Channel mappings */ HDMI_WRITE(sc, HDMI_PCM_CTRL, HDMI_AUDIO_CHANNELMAP); /* Clocks */ HDMI_WRITE(sc, HDMI_AUD_CTS, HDMI_AUDIO_CTS(mode->dot_clock, HDMI_AUDIO_N)); HDMI_WRITE(sc, HDMI_AUD_N, HDMI_AUDIO_N); /* Set sampling frequency to 48 kHz, word length to 16-bit */ HDMI_WRITE(sc, HDMI_AUD_CH_STATUS0, CH_STATUS0_FS_FREQ_48); HDMI_WRITE(sc, HDMI_AUD_CH_STATUS1, CH_STATUS1_WORD_LEN_16); /* Enable */ HDMI_WRITE(sc, HDMI_AUD_CTRL, AUD_CTRL_EN); } static int a10hdmi_get_tcon_config(struct a10hdmi_softc *sc, int *div, int *dbl) { uint64_t lcd_fin, lcd_fout; clk_t clk_lcd_parent; const char *pname; int error; error = clk_get_parent(sc->clk_lcd, &clk_lcd_parent); if (error != 0) return (error); /* Get the LCD CH1 special clock 2 divider */ error = clk_get_freq(sc->clk_lcd, &lcd_fout); if (error != 0) return (error); error = clk_get_freq(clk_lcd_parent, &lcd_fin); if (error != 0) return (error); *div = lcd_fin / lcd_fout; /* Detect LCD CH1 special clock using a 1X or 2X source */ /* XXX */ pname = clk_get_name(clk_lcd_parent); if (strcmp(pname, "pll3") == 0 || strcmp(pname, "pll7") == 0) *dbl = 0; else *dbl = 1; return (0); } static int a10hdmi_set_videomode(device_t dev, const struct videomode *mode) { struct a10hdmi_softc *sc; int error, clk_div, clk_dbl; int dblscan, hfp, hspw, hbp, vfp, vspw, vbp; uint32_t val; sc = device_get_softc(dev); dblscan = !!(mode->flags & VID_DBLSCAN); hfp = mode->hsync_start - mode->hdisplay; hspw = mode->hsync_end - mode->hsync_start; hbp = mode->htotal - mode->hsync_start; vfp = mode->vsync_start - mode->vdisplay; vspw = mode->vsync_end - mode->vsync_start; vbp = mode->vtotal - mode->vsync_start; error = a10hdmi_get_tcon_config(sc, &clk_div, &clk_dbl); if (error != 0) { device_printf(dev, "couldn't get tcon config: %d\n", error); return (error); } /* Clear interrupt status */ HDMI_WRITE(sc, HDMI_INT_STATUS, HDMI_READ(sc, HDMI_INT_STATUS)); /* Clock setup */ val = HDMI_READ(sc, HDMI_PADCTRL1); val &= ~PADCTRL1_REG_CKSS; val |= (clk_dbl ? PADCTRL1_REG_CKSS_2X : PADCTRL1_REG_CKSS_1X); HDMI_WRITE(sc, HDMI_PADCTRL1, val); HDMI_WRITE(sc, HDMI_PLLCTRL0, PLLCTRL0_PLL_EN | PLLCTRL0_BWS | PLLCTRL0_HV_IS_33 | PLLCTRL0_LDO1_EN | PLLCTRL0_LDO2_EN | PLLCTRL0_SDIV2 | PLLCTRL0_VCO_GAIN | PLLCTRL0_S | PLLCTRL0_CP_S | PLLCTRL0_CS | PLLCTRL0_PREDIV(clk_div) | PLLCTRL0_VCO_S); /* Setup display settings */ if (bootverbose) device_printf(dev, "HDMI: %s, Audio: %s\n", sc->has_hdmi ? "yes" : "no", sc->has_audio ? "yes" : "no"); val = 0; if (sc->has_hdmi) val |= VID_CTRL_HDMI_MODE; if (mode->flags & VID_INTERLACE) val |= VID_CTRL_INTERLACE; if (mode->flags & VID_DBLSCAN) val |= VID_CTRL_REPEATER_2X; HDMI_WRITE(sc, HDMI_VID_CTRL, val); /* Setup display timings */ HDMI_WRITE(sc, HDMI_VID_TIMING0, VID_ACT_V(mode->vdisplay) | VID_ACT_H(mode->hdisplay << dblscan)); HDMI_WRITE(sc, HDMI_VID_TIMING1, VID_VBP(vbp) | VID_HBP(hbp << dblscan)); HDMI_WRITE(sc, HDMI_VID_TIMING2, VID_VFP(vfp) | VID_HFP(hfp << dblscan)); HDMI_WRITE(sc, HDMI_VID_TIMING3, VID_VSPW(vspw) | VID_HSPW(hspw << dblscan)); val = TX_CLOCK_NORMAL; if (mode->flags & VID_PVSYNC) val |= VID_VSYNC_ACTSEL; if (mode->flags & VID_PHSYNC) val |= VID_HSYNC_ACTSEL; HDMI_WRITE(sc, HDMI_VID_TIMING4, val); /* This is an ordered list of infoframe packets that the HDMI * transmitter will send. Transmit packets in the following order: * 1. General control packet * 2. AVI infoframe * 3. Audio infoframe * There are 2 registers with 4 slots each. The list is terminated * with the special PKT_END marker. */ HDMI_WRITE(sc, HDMI_PKTCTRL0, PKTCTRL_PACKET(0, PKT_GC) | PKTCTRL_PACKET(1, PKT_AVI) | PKTCTRL_PACKET(2, PKT_AI) | PKTCTRL_PACKET(3, PKT_END)); HDMI_WRITE(sc, HDMI_PKTCTRL1, 0); /* Setup audio */ a10hdmi_set_audiomode(dev, mode); return (0); } static int a10hdmi_enable(device_t dev, int onoff) { struct a10hdmi_softc *sc; uint32_t val; sc = device_get_softc(dev); /* Enable or disable video output */ val = HDMI_READ(sc, HDMI_VID_CTRL); if (onoff) val |= VID_CTRL_VIDEO_EN; else val &= ~VID_CTRL_VIDEO_EN; HDMI_WRITE(sc, HDMI_VID_CTRL, val); return (0); } static device_method_t a10hdmi_methods[] = { /* Device interface */ DEVMETHOD(device_probe, a10hdmi_probe), DEVMETHOD(device_attach, a10hdmi_attach), /* HDMI interface */ DEVMETHOD(hdmi_get_edid, a10hdmi_get_edid), DEVMETHOD(hdmi_set_videomode, a10hdmi_set_videomode), DEVMETHOD(hdmi_enable, a10hdmi_enable), DEVMETHOD_END }; static driver_t a10hdmi_driver = { "a10hdmi", a10hdmi_methods, sizeof(struct a10hdmi_softc), }; static devclass_t a10hdmi_devclass; DRIVER_MODULE(a10hdmi, simplebus, a10hdmi_driver, a10hdmi_devclass, 0, 0); MODULE_VERSION(a10hdmi, 1); Index: head/sys/arm/allwinner/a10_hdmiaudio.c =================================================================== --- head/sys/arm/allwinner/a10_hdmiaudio.c (revision 355357) +++ head/sys/arm/allwinner/a10_hdmiaudio.c (revision 355358) @@ -1,438 +1,437 @@ /*- * 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$ */ /* * Allwinner A10/A20 HDMI Audio */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include "sunxi_dma_if.h" #include "mixer_if.h" #define DRQTYPE_HDMIAUDIO 24 #define DRQTYPE_SDRAM 1 #define DMA_WIDTH 32 #define DMA_BURST_LEN 8 #define DDMA_BLKSIZE 32 #define DDMA_WAIT_CYC 8 #define DMABUF_MIN 4096 #define DMABUF_DEFAULT 65536 #define DMABUF_MAX 131072 #define HDMI_SAMPLERATE 48000 #define TX_FIFO 0x01c16400 static uint32_t a10hdmiaudio_fmt[] = { SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps a10hdmiaudio_pcaps = { HDMI_SAMPLERATE, HDMI_SAMPLERATE, a10hdmiaudio_fmt, 0 }; struct a10hdmiaudio_info; struct a10hdmiaudio_chinfo { struct snd_dbuf *buffer; struct pcm_channel *channel; struct a10hdmiaudio_info *parent; bus_dmamap_t dmamap; void *dmaaddr; bus_addr_t physaddr; device_t dmac; void *dmachan; int run; uint32_t pos; uint32_t blocksize; }; struct a10hdmiaudio_info { device_t dev; struct mtx *lock; bus_dma_tag_t dmat; unsigned dmasize; struct a10hdmiaudio_chinfo play; }; /* * Mixer interface */ static int a10hdmiaudio_mixer_init(struct snd_mixer *m) { mix_setdevs(m, SOUND_MASK_PCM); return (0); } static int a10hdmiaudio_mixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { return (-1); } static kobj_method_t a10hdmiaudio_mixer_methods[] = { KOBJMETHOD(mixer_init, a10hdmiaudio_mixer_init), KOBJMETHOD(mixer_set, a10hdmiaudio_mixer_set), KOBJMETHOD_END }; MIXER_DECLARE(a10hdmiaudio_mixer); /* * Channel interface */ static void a10hdmiaudio_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct a10hdmiaudio_chinfo *ch = arg; if (error != 0) return; ch->physaddr = segs[0].ds_addr; } static void a10hdmiaudio_transfer(struct a10hdmiaudio_chinfo *ch) { int error; error = SUNXI_DMA_TRANSFER(ch->dmac, ch->dmachan, ch->physaddr + ch->pos, TX_FIFO, ch->blocksize); if (error) { ch->run = 0; device_printf(ch->parent->dev, "DMA transfer failed: %d\n", error); } } static void a10hdmiaudio_dmaconfig(struct a10hdmiaudio_chinfo *ch) { struct sunxi_dma_config conf; memset(&conf, 0, sizeof(conf)); conf.src_width = conf.dst_width = DMA_WIDTH; conf.src_burst_len = conf.dst_burst_len = DMA_BURST_LEN; conf.src_blksize = conf.dst_blksize = DDMA_BLKSIZE; conf.src_wait_cyc = conf.dst_wait_cyc = DDMA_WAIT_CYC; conf.src_drqtype = DRQTYPE_SDRAM; conf.dst_drqtype = DRQTYPE_HDMIAUDIO; conf.dst_noincr = true; SUNXI_DMA_SET_CONFIG(ch->dmac, ch->dmachan, &conf); } static void a10hdmiaudio_dmaintr(void *priv) { struct a10hdmiaudio_chinfo *ch = priv; unsigned bufsize; bufsize = sndbuf_getsize(ch->buffer); ch->pos += ch->blocksize; if (ch->pos >= bufsize) ch->pos -= bufsize; if (ch->run) { chn_intr(ch->channel); a10hdmiaudio_transfer(ch); } } static void a10hdmiaudio_start(struct a10hdmiaudio_chinfo *ch) { ch->pos = 0; /* Configure DMA channel */ a10hdmiaudio_dmaconfig(ch); /* Start DMA transfer */ a10hdmiaudio_transfer(ch); } static void a10hdmiaudio_stop(struct a10hdmiaudio_chinfo *ch) { /* Disable DMA channel */ SUNXI_DMA_HALT(ch->dmac, ch->dmachan); } static void * a10hdmiaudio_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct a10hdmiaudio_info *sc = devinfo; struct a10hdmiaudio_chinfo *ch = &sc->play; int error; ch->parent = sc; ch->channel = c; ch->buffer = b; ch->dmac = devclass_get_device(devclass_find("a10dmac"), 0); if (ch->dmac == NULL) { device_printf(sc->dev, "cannot find DMA controller\n"); return (NULL); } ch->dmachan = SUNXI_DMA_ALLOC(ch->dmac, true, a10hdmiaudio_dmaintr, ch); if (ch->dmachan == NULL) { device_printf(sc->dev, "cannot allocate DMA channel\n"); return (NULL); } error = bus_dmamem_alloc(sc->dmat, &ch->dmaaddr, BUS_DMA_NOWAIT | BUS_DMA_COHERENT, &ch->dmamap); if (error != 0) { device_printf(sc->dev, "cannot allocate channel buffer\n"); return (NULL); } error = bus_dmamap_load(sc->dmat, ch->dmamap, ch->dmaaddr, sc->dmasize, a10hdmiaudio_dmamap_cb, ch, BUS_DMA_NOWAIT); if (error != 0) { device_printf(sc->dev, "cannot load DMA map\n"); return (NULL); } memset(ch->dmaaddr, 0, sc->dmasize); if (sndbuf_setup(ch->buffer, ch->dmaaddr, sc->dmasize) != 0) { device_printf(sc->dev, "cannot setup sndbuf\n"); return (NULL); } return (ch); } static int a10hdmiaudio_chan_free(kobj_t obj, void *data) { struct a10hdmiaudio_chinfo *ch = data; struct a10hdmiaudio_info *sc = ch->parent; SUNXI_DMA_FREE(ch->dmac, ch->dmachan); bus_dmamap_unload(sc->dmat, ch->dmamap); bus_dmamem_free(sc->dmat, ch->dmaaddr, ch->dmamap); return (0); } static int a10hdmiaudio_chan_setformat(kobj_t obj, void *data, uint32_t format) { return (0); } static uint32_t a10hdmiaudio_chan_setspeed(kobj_t obj, void *data, uint32_t speed) { return (HDMI_SAMPLERATE); } static uint32_t a10hdmiaudio_chan_setblocksize(kobj_t obj, void *data, uint32_t blocksize) { struct a10hdmiaudio_chinfo *ch = data; ch->blocksize = blocksize & ~3; return (ch->blocksize); } static int a10hdmiaudio_chan_trigger(kobj_t obj, void *data, int go) { struct a10hdmiaudio_chinfo *ch = data; struct a10hdmiaudio_info *sc = ch->parent; if (!PCMTRIG_COMMON(go)) return (0); snd_mtxlock(sc->lock); switch (go) { case PCMTRIG_START: ch->run = 1; a10hdmiaudio_start(ch); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: ch->run = 0; a10hdmiaudio_stop(ch); break; default: break; } snd_mtxunlock(sc->lock); return (0); } static uint32_t a10hdmiaudio_chan_getptr(kobj_t obj, void *data) { struct a10hdmiaudio_chinfo *ch = data; return (ch->pos); } static struct pcmchan_caps * a10hdmiaudio_chan_getcaps(kobj_t obj, void *data) { return (&a10hdmiaudio_pcaps); } static kobj_method_t a10hdmiaudio_chan_methods[] = { KOBJMETHOD(channel_init, a10hdmiaudio_chan_init), KOBJMETHOD(channel_free, a10hdmiaudio_chan_free), KOBJMETHOD(channel_setformat, a10hdmiaudio_chan_setformat), KOBJMETHOD(channel_setspeed, a10hdmiaudio_chan_setspeed), KOBJMETHOD(channel_setblocksize, a10hdmiaudio_chan_setblocksize), KOBJMETHOD(channel_trigger, a10hdmiaudio_chan_trigger), KOBJMETHOD(channel_getptr, a10hdmiaudio_chan_getptr), KOBJMETHOD(channel_getcaps, a10hdmiaudio_chan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(a10hdmiaudio_chan); /* * Device interface */ static int a10hdmiaudio_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "allwinner,sun7i-a20-hdmiaudio")) return (ENXIO); device_set_desc(dev, "Allwinner HDMI Audio"); return (BUS_PROBE_DEFAULT); } static int a10hdmiaudio_attach(device_t dev) { struct a10hdmiaudio_info *sc; char status[SND_STATUSLEN]; int error; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->dev = dev; sc->lock = snd_mtxcreate(device_get_nameunit(dev), "a10hdmiaudio softc"); sc->dmasize = pcm_getbuffersize(dev, DMABUF_MIN, DMABUF_DEFAULT, DMABUF_MAX); error = bus_dma_tag_create( bus_get_dma_tag(dev), 4, sc->dmasize, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ sc->dmasize, 1, /* maxsize, nsegs */ sc->dmasize, 0, /* maxsegsize, flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->dmat); if (error != 0) { device_printf(dev, "cannot create DMA tag\n"); goto fail; } if (mixer_init(dev, &a10hdmiaudio_mixer_class, sc)) { device_printf(dev, "mixer_init failed\n"); goto fail; } pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE); pcm_setflags(dev, pcm_getflags(dev) | SD_F_SOFTPCMVOL); if (pcm_register(dev, sc, 1, 0)) { device_printf(dev, "pcm_register failed\n"); goto fail; } pcm_addchan(dev, PCMDIR_PLAY, &a10hdmiaudio_chan_class, sc); snprintf(status, SND_STATUSLEN, "at %s", ofw_bus_get_name(dev)); pcm_setstatus(dev, status); return (0); fail: snd_mtxfree(sc->lock); free(sc, M_DEVBUF); return (error); } static device_method_t a10hdmiaudio_pcm_methods[] = { /* Device interface */ DEVMETHOD(device_probe, a10hdmiaudio_probe), DEVMETHOD(device_attach, a10hdmiaudio_attach), DEVMETHOD_END }; static driver_t a10hdmiaudio_pcm_driver = { "pcm", a10hdmiaudio_pcm_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(a10hdmiaudio, simplebus, a10hdmiaudio_pcm_driver, pcm_devclass, 0, 0); MODULE_DEPEND(a10hdmiaudio, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(a10hdmiaudio, 1); Index: head/sys/arm/allwinner/a64/a64_padconf.c =================================================================== --- head/sys/arm/allwinner/a64/a64_padconf.c (revision 355357) +++ head/sys/arm/allwinner/a64/a64_padconf.c (revision 355358) @@ -1,160 +1,159 @@ /*- * 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$ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include "opt_soc.h" #ifdef SOC_ALLWINNER_A64 static const struct allwinner_pins a64_pins[] = { { "PB0", 1, 0, { "gpio_in", "gpio_out", "uart2", NULL, "jtag", NULL, "pb_eint0" }, 6, 0}, { "PB1", 1, 1, { "gpio_in", "gpio_out", "uart2", NULL, "jtag", "sim", "pb_eint1" }, 6, 1}, { "PB2", 1, 2, { "gpio_in", "gpio_out", "uart2", NULL, "jtag", "sim", "pb_eint2" }, 6, 2}, { "PB3", 1, 3, { "gpio_in", "gpio_out", "uart2", "i2s0", "jtag", "sim", "pb_eint3" }, 6, 3}, { "PB4", 1, 4, { "gpio_in", "gpio_out", "aif2", "pcm0", NULL, "sim", "pb_eint4" }, 6, 4}, { "PB5", 1, 5, { "gpio_in", "gpio_out", "aif2", "pcm0", NULL, "sim", "pb_eint5" }, 6, 5}, { "PB6", 1, 6, { "gpio_in", "gpio_out", "aif2", "pcm0", NULL, "sim", "pb_eint6" }, 6, 6}, { "PB7", 1, 7, { "gpio_in", "gpio_out", "aif2", "pcm0", NULL, "sim", "pb_eint7" }, 6, 7}, { "PB8", 1, 8, { "gpio_in", "gpio_out", NULL, NULL, "uart0", NULL, "pb_eint8" }, 6, 8}, { "PB9", 1, 9, { "gpio_in", "gpio_out", NULL, NULL, "uart0", NULL, "pb_eint9" }, 6, 9}, { "PC0", 2, 0, { "gpio_in", "gpio_out", "nand", NULL, "spi0" } }, { "PC1", 2, 1, { "gpio_in", "gpio_out", "nand", "mmc2", "spi0" } }, { "PC2", 2, 2, { "gpio_in", "gpio_out", "nand", NULL, "spi0" } }, { "PC3", 2, 3, { "gpio_in", "gpio_out", "nand", NULL, "spi0" } }, { "PC4", 2, 4, { "gpio_in", "gpio_out", "nand" } }, { "PC5", 2, 5, { "gpio_in", "gpio_out", "nand", "mmc2" } }, { "PC6", 2, 6, { "gpio_in", "gpio_out", "nand", "mmc2" } }, { "PC7", 2, 7, { "gpio_in", "gpio_out", "nand" } }, { "PC8", 2, 8, { "gpio_in", "gpio_out", "nand", "mmc2" } }, { "PC9", 2, 9, { "gpio_in", "gpio_out", "nand", "mmc2" } }, { "PC10", 2, 10, { "gpio_in", "gpio_out", "nand", "mmc2" } }, { "PC11", 2, 11, { "gpio_in", "gpio_out", "nand", "mmc2" } }, { "PC12", 2, 12, { "gpio_in", "gpio_out", "nand", "mmc2" } }, { "PC13", 2, 13, { "gpio_in", "gpio_out", "nand", "mmc2" } }, { "PC14", 2, 14, { "gpio_in", "gpio_out", "nand", "mmc2" } }, { "PC15", 2, 15, { "gpio_in", "gpio_out", "nand", "mmc2" } }, { "PC16", 2, 16, { "gpio_in", "gpio_out", "nand", "mmc2" } }, { "PD0", 3, 0, { "gpio_in", "gpio_out", "lcd", "uart3", "spi1", "ccir" } }, { "PD1", 3, 1, { "gpio_in", "gpio_out", "lcd", "uart3", "spi1", "ccir" } }, { "PD2", 3, 2, { "gpio_in", "gpio_out", "lcd", "uart4", "spi1", "ccir" } }, { "PD3", 3, 3, { "gpio_in", "gpio_out", "lcd", "uart4", "spi1", "ccir" } }, { "PD4", 3, 4, { "gpio_in", "gpio_out", "lcd", "uart4", "spi1", "ccir" } }, { "PD5", 3, 5, { "gpio_in", "gpio_out", "lcd", "uart4", "spi1", "ccir" } }, { "PD6", 3, 6, { "gpio_in", "gpio_out", "lcd", NULL, NULL, "ccir" } }, { "PD7", 3, 7, { "gpio_in", "gpio_out", "lcd", NULL, NULL, "ccir" } }, { "PD8", 3, 8, { "gpio_in", "gpio_out", "lcd", NULL, "emac", "ccir" } }, { "PD9", 3, 9, { "gpio_in", "gpio_out", "lcd", NULL, "emac", "ccir" } }, { "PD10", 3, 10, { "gpio_in", "gpio_out", "lcd", NULL, "emac" } }, { "PD11", 3, 11, { "gpio_in", "gpio_out", "lcd", NULL, "emac" } }, { "PD12", 3, 12, { "gpio_in", "gpio_out", "lcd", "lvds", "emac" } }, { "PD13", 3, 13, { "gpio_in", "gpio_out", "lcd", "lvds", "emac" } }, { "PD14", 3, 14, { "gpio_in", "gpio_out", "lcd", "lvds", "emac" } }, { "PD15", 3, 15, { "gpio_in", "gpio_out", "lcd", "lvds", "emac", "ccir" } }, { "PD16", 3, 16, { "gpio_in", "gpio_out", "lcd", "lvds", "emac", "ccir" } }, { "PD17", 3, 17, { "gpio_in", "gpio_out", "lcd", "lvds", "emac" } }, { "PD18", 3, 18, { "gpio_in", "gpio_out", "lcd", "lvds", "emac" } }, { "PD19", 3, 19, { "gpio_in", "gpio_out", "lcd", "lvds", "emac" } }, { "PD20", 3, 20, { "gpio_in", "gpio_out", "lcd", "lvds", "emac" } }, { "PD21", 3, 21, { "gpio_in", "gpio_out", "lcd", "lvds", "emac" } }, { "PD22", 3, 22, { "gpio_in", "gpio_out", "pwm0", NULL, "emac" } }, { "PD23", 3, 23, { "gpio_in", "gpio_out", NULL, NULL, "emac" } }, { "PD24", 3, 24, { "gpio_in", "gpio_out" } }, { "PE0", 4, 0, { "gpio_in", "gpio_out", "csi", NULL, "ts" } }, { "PE1", 4, 1, { "gpio_in", "gpio_out", "csi", NULL, "ts" } }, { "PE2", 4, 2, { "gpio_in", "gpio_out", "csi", NULL, "ts" } }, { "PE3", 4, 3, { "gpio_in", "gpio_out", "csi", NULL, "ts" } }, { "PE4", 4, 4, { "gpio_in", "gpio_out", "csi", NULL, "ts" } }, { "PE5", 4, 5, { "gpio_in", "gpio_out", "csi", NULL, "ts" } }, { "PE6", 4, 6, { "gpio_in", "gpio_out", "csi", NULL, "ts" } }, { "PE7", 4, 7, { "gpio_in", "gpio_out", "csi", NULL, "ts" } }, { "PE8", 4, 8, { "gpio_in", "gpio_out", "csi", NULL, "ts" } }, { "PE9", 4, 9, { "gpio_in", "gpio_out", "csi", NULL, "ts" } }, { "PE10", 4, 10, { "gpio_in", "gpio_out", "csi", NULL, "ts" } }, { "PE11", 4, 11, { "gpio_in", "gpio_out", "csi", NULL, "ts" } }, { "PE12", 4, 12, { "gpio_in", "gpio_out", "csi" } }, { "PE13", 4, 13, { "gpio_in", "gpio_out", "csi" } }, { "PE14", 4, 14, { "gpio_in", "gpio_out", "pll_lock", "twi2" } }, { "PE15", 4, 15, { "gpio_in", "gpio_out", NULL, "twi2" } }, { "PE16", 4, 16, { "gpio_in", "gpio_out" } }, { "PE17", 4, 17, { "gpio_in", "gpio_out" } }, { "PF0", 5, 0, { "gpio_in", "gpio_out", "mmc0", "jtag" } }, { "PF1", 5, 1, { "gpio_in", "gpio_out", "mmc0", "jtag" } }, { "PF2", 5, 2, { "gpio_in", "gpio_out", "mmc0", "uart0" } }, { "PF3", 5, 3, { "gpio_in", "gpio_out", "mmc0", "jtag" } }, { "PF4", 5, 4, { "gpio_in", "gpio_out", "mmc0", "uart0" } }, { "PF5", 5, 5, { "gpio_in", "gpio_out", "mmc0", "jtag" } }, { "PF6", 5, 6, { "gpio_in", "gpio_out" } }, { "PG0", 6, 0, { "gpio_in", "gpio_out", "mmc1", NULL, NULL, NULL, "pg_eint0" }, 6, 0}, { "PG1", 6, 1, { "gpio_in", "gpio_out", "mmc1", NULL, NULL, NULL, "pg_eint1" }, 6, 1}, { "PG2", 6, 2, { "gpio_in", "gpio_out", "mmc1", NULL, NULL, NULL, "pg_eint2" }, 6, 2}, { "PG3", 6, 3, { "gpio_in", "gpio_out", "mmc1", NULL, NULL, NULL, "pg_eint3" }, 6, 3}, { "PG4", 6, 4, { "gpio_in", "gpio_out", "mmc1", NULL, NULL, NULL, "pg_eint4" }, 6, 4}, { "PG5", 6, 5, { "gpio_in", "gpio_out", "mmc1", NULL, NULL, NULL, "pg_eint5" }, 6, 5}, { "PG6", 6, 6, { "gpio_in", "gpio_out", "uart1", NULL, NULL, NULL, "pg_eint6" }, 6, 6}, { "PG7", 6, 7, { "gpio_in", "gpio_out", "uart1", NULL, NULL, NULL, "pg_eint7" }, 6, 7}, { "PG8", 6, 8, { "gpio_in", "gpio_out", "uart1", NULL, NULL, NULL, "pg_eint8" }, 6, 8}, { "PG9", 6, 9, { "gpio_in", "gpio_out", "uart1", NULL, NULL, NULL, "pg_eint9" }, 6, 9}, { "PG10", 6, 10, { "gpio_in", "gpio_out", "aif3", "pcm1", NULL, NULL, "pg_eint10" }, 6, 10}, { "PG11", 6, 11, { "gpio_in", "gpio_out", "aif3", "pcm1", NULL, NULL, "pg_eint11" }, 6, 11}, { "PG12", 6, 12, { "gpio_in", "gpio_out", "aif3", "pcm1", NULL, NULL, "pg_eint12" }, 6, 12}, { "PG13", 6, 13, { "gpio_in", "gpio_out", "aif3", "pcm1", NULL, NULL, "pg_eint13" }, 6, 13}, { "PH0", 7, 0, { "gpio_in", "gpio_out", "i2c0", NULL, NULL, NULL, "ph_eint0" }, 6, 0}, { "PH1", 7, 1, { "gpio_in", "gpio_out", "i2c0", NULL, NULL, NULL, "ph_eint1" }, 6, 1}, { "PH2", 7, 2, { "gpio_in", "gpio_out", "i2c1", NULL, NULL, NULL, "ph_eint2" }, 6, 2}, { "PH3", 7, 3, { "gpio_in", "gpio_out", "i2c1", NULL, NULL, NULL, "ph_eint3" }, 6, 3}, { "PH4", 7, 4, { "gpio_in", "gpio_out", "uart3", NULL, NULL, NULL, "ph_eint4" }, 6, 4}, { "PH5", 7, 5, { "gpio_in", "gpio_out", "uart3", NULL, NULL, NULL, "ph_eint5" }, 6, 5}, { "PH6", 7, 6, { "gpio_in", "gpio_out", "uart3", NULL, NULL, NULL, "ph_eint6" }, 6, 6}, { "PH7", 7, 7, { "gpio_in", "gpio_out", "uart3", NULL, NULL, NULL, "ph_eint7" }, 6, 7}, { "PH8", 7, 8, { "gpio_in", "gpio_out", "owa", NULL, NULL, NULL, "ph_eint8" }, 6, 8}, { "PH9", 7, 9, { "gpio_in", "gpio_out", NULL, NULL, NULL, NULL, "ph_eint9" }, 6, 9}, { "PH10", 7, 10, { "gpio_in", "gpio_out", "mic", NULL, NULL, NULL, "ph_eint10" }, 6, 10}, { "PH11", 7, 11, { "gpio_in", "gpio_out", "mic", NULL, NULL, NULL, "ph_eint11" }, 6, 11}, }; const struct allwinner_padconf a64_padconf = { .npins = nitems(a64_pins), .pins = a64_pins, }; #endif /* !SOC_ALLWINNER_A64 */ Index: head/sys/arm/allwinner/a64/a64_r_padconf.c =================================================================== --- head/sys/arm/allwinner/a64/a64_r_padconf.c (revision 355357) +++ head/sys/arm/allwinner/a64/a64_r_padconf.c (revision 355358) @@ -1,64 +1,63 @@ /*- * 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$ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include "opt_soc.h" #ifdef SOC_ALLWINNER_A64 static const struct allwinner_pins a64_r_pins[] = { { "PL0", 0, 0, { "gpio_in", "gpio_out", "s_rsb", "s_i2c", NULL, NULL, "pl_eint0" }, 6, 0}, { "PL1", 0, 1, { "gpio_in", "gpio_out", "s_rsb", "s_i2c", NULL, NULL, "pl_eint1" }, 6, 1}, { "PL2", 0, 2, { "gpio_in", "gpio_out", "s_uart", NULL, NULL, NULL, "pl_eint2" }, 6, 2}, { "PL3", 0, 3, { "gpio_in", "gpio_out", "s_uart", NULL, NULL, NULL, "pl_eint3" }, 6, 3}, { "PL4", 0, 4, { "gpio_in", "gpio_out", "s_jtag", NULL, NULL, NULL, "pl_eint4" }, 6, 4}, { "PL5", 0, 5, { "gpio_in", "gpio_out", "s_jtag", NULL, NULL, NULL, "pl_eint5" }, 6, 5}, { "PL6", 0, 6, { "gpio_in", "gpio_out", "s_jtag", NULL, NULL, NULL, "pl_eint6" }, 6, 6}, { "PL7", 0, 7, { "gpio_in", "gpio_out", "s_jtag", NULL, NULL, NULL, "pl_eint7" }, 6, 7}, { "PL8", 0, 8, { "gpio_in", "gpio_out", "s_i2c", NULL, NULL, NULL, "pl_eint8" }, 6, 8}, { "PL9", 0, 9, { "gpio_in", "gpio_out", "s_i2c", NULL, NULL, NULL, "pl_eint9" }, 6, 9}, { "PL10", 0, 10, { "gpio_in", "gpio_out", "s_pwm", NULL, NULL, NULL, "pl_eint10" }, 6, 10}, { "PL11", 0, 11, { "gpio_in", "gpio_out", "s_cir", NULL, NULL, NULL, "pl_eint11" }, 6, 11}, { "PL12", 0, 12, { "gpio_in", "gpio_out", NULL, NULL, NULL, NULL, "pl_eint12" }, 6, 12}, }; const struct allwinner_padconf a64_r_padconf = { .npins = nitems(a64_r_pins), .pins = a64_r_pins, }; #endif /* !SOC_ALLWINNER_A64 */ Index: head/sys/arm/allwinner/a83t/a83t_padconf.c =================================================================== --- head/sys/arm/allwinner/a83t/a83t_padconf.c (revision 355357) +++ head/sys/arm/allwinner/a83t/a83t_padconf.c (revision 355358) @@ -1,162 +1,161 @@ /*- * 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$ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #ifdef SOC_ALLWINNER_A83T static const struct allwinner_pins a83t_pins[] = { { "PB0", 1, 0, { "gpio_in", "gpio_out", "uart2", "jtag", NULL, NULL, "eint" } }, { "PB1", 1, 1, { "gpio_in", "gpio_out", "uart2", "jtag", NULL, NULL, "eint" } }, { "PB2", 1, 2, { "gpio_in", "gpio_out", "uart2", "jtag", NULL, NULL, "eint" } }, { "PB3", 1, 3, { "gpio_in", "gpio_out", "uart2", "jtag", NULL, NULL, "eint" } }, { "PB4", 1, 4, { "gpio_in", "gpio_out", "i2s0", "tdm", NULL, NULL, "eint" } }, { "PB5", 1, 5, { "gpio_in", "gpio_out", "i2s0", "tdm", NULL, NULL, "eint" } }, { "PB6", 1, 6, { "gpio_in", "gpio_out", "i2s0", "tdm", NULL, NULL, "eint" } }, { "PB7", 1, 7, { "gpio_in", "gpio_out", "i2s0", "tdm", NULL, NULL, "eint" } }, { "PB8", 1, 8, { "gpio_in", "gpio_out", "i2s0", "tdm", NULL, NULL, "eint" } }, { "PB9", 1, 9, { "gpio_in", "gpio_out", "uart0", NULL, NULL, NULL, "eint" } }, { "PB10", 1, 10, { "gpio_in", "gpio_out", "uart0", NULL, NULL, NULL, "eint" } }, { "PC0", 2, 0, { "gpio_in", "gpio_out", "nand", "spi0" } }, { "PC1", 2, 1, { "gpio_in", "gpio_out", "nand", "spi0" } }, { "PC2", 2, 2, { "gpio_in", "gpio_out", "nand", "spi0" } }, { "PC3", 2, 3, { "gpio_in", "gpio_out", "nand", "spi0" } }, { "PC4", 2, 4, { "gpio_in", "gpio_out", "nand" } }, { "PC5", 2, 5, { "gpio_in", "gpio_out", "nand", "mmc2" } }, { "PC6", 2, 6, { "gpio_in", "gpio_out", "nand", "mmc2" } }, { "PC7", 2, 7, { "gpio_in", "gpio_out", "nand" } }, { "PC8", 2, 8, { "gpio_in", "gpio_out", "nand", "mmc2" } }, { "PC9", 2, 9, { "gpio_in", "gpio_out", "nand", "mmc2" } }, { "PC10", 2, 10, { "gpio_in", "gpio_out", "nand", "mmc2" } }, { "PC11", 2, 11, { "gpio_in", "gpio_out", "nand", "mmc2" } }, { "PC12", 2, 12, { "gpio_in", "gpio_out", "nand", "mmc2" } }, { "PC13", 2, 13, { "gpio_in", "gpio_out", "nand", "mmc2" } }, { "PC14", 2, 14, { "gpio_in", "gpio_out", "nand", "mmc2" } }, { "PC15", 2, 15, { "gpio_in", "gpio_out", "nand", "mmc2" } }, { "PC16", 2, 16, { "gpio_in", "gpio_out", "nand", "mmc2" } }, { "PC17", 2, 17, { "gpio_in", "gpio_out", "nand" } }, { "PC18", 2, 18, { "gpio_in", "gpio_out", "nand" } }, { "PD2", 3, 2, { "gpio_in", "gpio_out", "lcd", NULL, "gmac" } }, { "PD3", 3, 3, { "gpio_in", "gpio_out", "lcd", NULL, "gmac" } }, { "PD4", 3, 4, { "gpio_in", "gpio_out", "lcd", NULL, "gmac" } }, { "PD5", 3, 5, { "gpio_in", "gpio_out", "lcd", NULL, "gmac" } }, { "PD6", 3, 6, { "gpio_in", "gpio_out", "lcd", NULL, "gmac" } }, { "PD7", 3, 7, { "gpio_in", "gpio_out", "lcd", NULL, "gmac" } }, { "PD10", 3, 10, { "gpio_in", "gpio_out", "lcd", NULL, "gmac" } }, { "PD11", 3, 11, { "gpio_in", "gpio_out", "lcd", NULL, "gmac" } }, { "PD12", 3, 12, { "gpio_in", "gpio_out", "lcd", NULL, "gmac" } }, { "PD13", 3, 13, { "gpio_in", "gpio_out", "lcd", NULL, "gmac" } }, { "PD14", 3, 14, { "gpio_in", "gpio_out", "lcd", NULL, "gmac" } }, { "PD15", 3, 15, { "gpio_in", "gpio_out", "lcd", NULL, "gmac" } }, { "PD18", 3, 18, { "gpio_in", "gpio_out", "lcd", "lvds", "gmac" } }, { "PD19", 3, 19, { "gpio_in", "gpio_out", "lcd", "lvds", "gmac" } }, { "PD20", 3, 20, { "gpio_in", "gpio_out", "lcd", "lvds", "gmac" } }, { "PD21", 3, 21, { "gpio_in", "gpio_out", "lcd", "lvds", "gmac" } }, { "PD22", 3, 22, { "gpio_in", "gpio_out", "lcd", "lvds", "gmac" } }, { "PD23", 3, 23, { "gpio_in", "gpio_out", "lcd", "lvds", "gmac" } }, { "PD24", 3, 24, { "gpio_in", "gpio_out", "lcd", "lvds" } }, { "PD25", 3, 25, { "gpio_in", "gpio_out", "lcd", "lvds" } }, { "PD26", 3, 26, { "gpio_in", "gpio_out", "lcd", "lvds" } }, { "PD27", 3, 27, { "gpio_in", "gpio_out", "lcd", "lvds" } }, { "PD28", 3, 28, { "gpio_in", "gpio_out", "pwm" } }, { "PD29", 3, 29, { "gpio_in", "gpio_out" } }, { "PE0", 4, 0, { "gpio_in", "gpio_out", "csi", NULL, "ccir" } }, { "PE1", 4, 1, { "gpio_in", "gpio_out", "csi", NULL, "ccir" } }, { "PE2", 4, 2, { "gpio_in", "gpio_out", "csi", NULL, "ccir" } }, { "PE3", 4, 3, { "gpio_in", "gpio_out", "csi", NULL, "ccir" } }, { "PE4", 4, 4, { "gpio_in", "gpio_out", "csi" } }, { "PE5", 4, 5, { "gpio_in", "gpio_out", "csi" } }, { "PE6", 4, 6, { "gpio_in", "gpio_out", "csi", NULL, "ccir" } }, { "PE7", 4, 7, { "gpio_in", "gpio_out", "csi", NULL, "ccir" } }, { "PE8", 4, 8, { "gpio_in", "gpio_out", "csi", NULL, "ccir" } }, { "PE9", 4, 9, { "gpio_in", "gpio_out", "csi", NULL, "ccir" } }, { "PE10", 4, 10, { "gpio_in", "gpio_out", "csi", "uart4", "ccir" } }, { "PE11", 4, 11, { "gpio_in", "gpio_out", "csi", "uart4", "ccir" } }, { "PE12", 4, 12, { "gpio_in", "gpio_out", "csi", "uart4", "ccir" } }, { "PE13", 4, 13, { "gpio_in", "gpio_out", "csi", "uart4", "ccir" } }, { "PE14", 4, 14, { "gpio_in", "gpio_out", "csi", "twi2" } }, { "PE15", 4, 15, { "gpio_in", "gpio_out", "csi", "twi2" } }, { "PE16", 4, 16, { "gpio_in", "gpio_out" } }, { "PE17", 4, 17, { "gpio_in", "gpio_out" } }, { "PE18", 4, 18, { "gpio_in", "gpio_out", NULL, "owa" } }, { "PE19", 4, 19, { "gpio_in", "gpio_out" } }, { "PF0", 5, 0, { "gpio_in", "gpio_out", "mmc0", "jtag" } }, { "PF1", 5, 1, { "gpio_in", "gpio_out", "mmc0", "jtag" } }, { "PF2", 5, 2, { "gpio_in", "gpio_out", "mmc0", "uart0" } }, { "PF3", 5, 3, { "gpio_in", "gpio_out", "mmc0", "jtag" } }, { "PF4", 5, 4, { "gpio_in", "gpio_out", "mmc0", "uart0" } }, { "PF5", 5, 5, { "gpio_in", "gpio_out", "mmc0", "jtag" } }, { "PF6", 5, 6, { "gpio_in", "gpio_out" } }, { "PG0", 6, 0, { "gpio_in", "gpio_out", "mmc1", NULL, NULL, NULL, "eint" } }, { "PG1", 6, 1, { "gpio_in", "gpio_out", "mmc1", NULL, NULL, NULL, "eint" } }, { "PG2", 6, 2, { "gpio_in", "gpio_out", "mmc1", NULL, NULL, NULL, "eint" } }, { "PG3", 6, 3, { "gpio_in", "gpio_out", "mmc1", NULL, NULL, NULL, "eint" } }, { "PG4", 6, 4, { "gpio_in", "gpio_out", "mmc1", NULL, NULL, NULL, "eint" } }, { "PG5", 6, 5, { "gpio_in", "gpio_out", "mmc1", NULL, NULL, NULL, "eint" } }, { "PG6", 6, 6, { "gpio_in", "gpio_out", "uart1", "spi1", NULL, NULL, "eint" } }, { "PG7", 6, 7, { "gpio_in", "gpio_out", "uart1", "spi1", NULL, NULL, "eint" } }, { "PG8", 6, 8, { "gpio_in", "gpio_out", "uart1", "spi1", NULL, NULL, "eint" } }, { "PG9", 6, 9, { "gpio_in", "gpio_out", "uart1", "spi1", NULL, NULL, "eint" } }, { "PG10", 6, 10, { "gpio_in", "gpio_out", "i2s1", "uart3", NULL, NULL, "eint" } }, { "PG11", 6, 11, { "gpio_in", "gpio_out", "i2s1", "uart3", NULL, NULL, "eint" } }, { "PG12", 6, 12, { "gpio_in", "gpio_out", "i2s1", "uart3", NULL, NULL, "eint" } }, { "PG13", 6, 13, { "gpio_in", "gpio_out", "i2s1", "uart3", NULL, NULL, "eint" } }, { "PH0", 7, 0, { "gpio_in", "gpio_out", "i2c0", NULL, NULL, NULL, "eint" } }, { "PH1", 7, 1, { "gpio_in", "gpio_out", "i2c0", NULL, NULL, NULL, "eint" } }, { "PH2", 7, 2, { "gpio_in", "gpio_out", "i2c1", NULL, NULL, NULL, "eint" } }, { "PH3", 7, 3, { "gpio_in", "gpio_out", "i2c1", NULL, NULL, NULL, "eint" } }, { "PH4", 7, 4, { "gpio_in", "gpio_out", "i2c2", NULL, NULL, NULL, "eint" } }, { "PH5", 7, 5, { "gpio_in", "gpio_out", "i2c2", NULL, NULL, NULL, "eint" } }, { "PH6", 7, 6, { "gpio_in", "gpio_out", "hdmiddc", NULL, NULL, NULL, "eint" } }, { "PH7", 7, 7, { "gpio_in", "gpio_out", "hdmiddc", NULL, NULL, NULL, "eint" } }, { "PH8", 7, 8, { "gpio_in", "gpio_out", "hdmiddc", NULL, NULL, NULL, "eint" } }, { "PH9", 7, 9, { "gpio_in", "gpio_out", NULL, NULL, NULL, NULL, "eint" } }, { "PH10", 7, 10, { "gpio_in", "gpio_out", NULL, NULL, NULL, NULL, "eint" } }, { "PH11", 7, 11, { "gpio_in", "gpio_out", NULL, NULL, NULL, NULL, "eint" } }, }; const struct allwinner_padconf a83t_padconf = { .npins = nitems(a83t_pins), .pins = a83t_pins, }; #endif /* !SOC_ALLWINNER_A83T */ Index: head/sys/arm/allwinner/a83t/a83t_r_padconf.c =================================================================== --- head/sys/arm/allwinner/a83t/a83t_r_padconf.c (revision 355357) +++ head/sys/arm/allwinner/a83t/a83t_r_padconf.c (revision 355358) @@ -1,62 +1,61 @@ /*- * 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$ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #ifdef SOC_ALLWINNER_A83T static const struct allwinner_pins a83t_r_pins[] = { { "PL0", 0, 0, { "gpio_in", "gpio_out", "s_rsb", "s_i2c", NULL, NULL, "eint" } }, { "PL1", 0, 1, { "gpio_in", "gpio_out", "s_rsb", "s_i2c", NULL, NULL, "eint" } }, { "PL2", 0, 2, { "gpio_in", "gpio_out", "s_uart", NULL, NULL, NULL, "eint" } }, { "PL3", 0, 3, { "gpio_in", "gpio_out", "s_uart", NULL, NULL, NULL, "eint" } }, { "PL4", 0, 4, { "gpio_in", "gpio_out", "s_jtag", NULL, NULL, NULL, "eint" } }, { "PL5", 0, 5, { "gpio_in", "gpio_out", "s_jtag", NULL, NULL, NULL, "eint" } }, { "PL6", 0, 6, { "gpio_in", "gpio_out", "s_jtag", NULL, NULL, NULL, "eint" } }, { "PL7", 0, 7, { "gpio_in", "gpio_out", "s_jtag", NULL, NULL, NULL, "eint" } }, { "PL8", 0, 8, { "gpio_in", "gpio_out", "s_i2c", NULL, NULL, NULL, "eint" } }, { "PL9", 0, 9, { "gpio_in", "gpio_out", "s_i2c", NULL, NULL, NULL, "eint" } }, { "PL10", 0, 10, { "gpio_in", "gpio_out", "s_pwm", NULL, NULL, NULL, "eint" } }, { "PL11", 0, 11, { "gpio_in", "gpio_out", NULL, NULL, NULL, "eint" } }, { "PL12", 0, 12, { "gpio_in", "gpio_out", "s_cir", NULL, NULL, NULL, "eint" } }, }; const struct allwinner_padconf a83t_r_padconf = { .npins = nitems(a83t_r_pins), .pins = a83t_r_pins, }; #endif /* !SOC_ALLWINNER_A83T */ Index: head/sys/arm/allwinner/aw_ccu.c =================================================================== --- head/sys/arm/allwinner/aw_ccu.c (revision 355357) +++ head/sys/arm/allwinner/aw_ccu.c (revision 355358) @@ -1,255 +1,254 @@ /*- * 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$ */ /* * Allwinner oscillator clock */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include "clkdev_if.h" #define CCU_BASE 0x01c20000 #define CCU_SIZE 0x400 struct aw_ccu_softc { struct simplebus_softc sc; bus_space_tag_t bst; bus_space_handle_t bsh; struct mtx mtx; int flags; }; static struct ofw_compat_data compat_data[] = { { "allwinner,sun7i-a20", 1 }, { "allwinner,sun6i-a31", 1 }, { "allwinner,sun6i-a31s", 1 }, { NULL, 0 } }; static int aw_ccu_check_addr(struct aw_ccu_softc *sc, bus_addr_t addr, bus_space_handle_t *pbsh, bus_size_t *poff) { if (addr >= CCU_BASE && addr < (CCU_BASE + CCU_SIZE)) { *poff = addr - CCU_BASE; *pbsh = sc->bsh; return (0); } return (EINVAL); } static int aw_ccu_write_4(device_t dev, bus_addr_t addr, uint32_t val) { struct aw_ccu_softc *sc; bus_space_handle_t bsh; bus_size_t reg; sc = device_get_softc(dev); if (aw_ccu_check_addr(sc, addr, &bsh, ®) != 0) return (EINVAL); mtx_assert(&sc->mtx, MA_OWNED); bus_space_write_4(sc->bst, bsh, reg, val); return (0); } static int aw_ccu_read_4(device_t dev, bus_addr_t addr, uint32_t *val) { struct aw_ccu_softc *sc; bus_space_handle_t bsh; bus_size_t reg; sc = device_get_softc(dev); if (aw_ccu_check_addr(sc, addr, &bsh, ®) != 0) return (EINVAL); mtx_assert(&sc->mtx, MA_OWNED); *val = bus_space_read_4(sc->bst, bsh, reg); return (0); } static int aw_ccu_modify_4(device_t dev, bus_addr_t addr, uint32_t clr, uint32_t set) { struct aw_ccu_softc *sc; bus_space_handle_t bsh; bus_size_t reg; uint32_t val; sc = device_get_softc(dev); if (aw_ccu_check_addr(sc, addr, &bsh, ®) != 0) return (EINVAL); mtx_assert(&sc->mtx, MA_OWNED); val = bus_space_read_4(sc->bst, bsh, reg); val &= ~clr; val |= set; bus_space_write_4(sc->bst, bsh, reg, val); return (0); } static void aw_ccu_device_lock(device_t dev) { struct aw_ccu_softc *sc; sc = device_get_softc(dev); mtx_lock(&sc->mtx); } static void aw_ccu_device_unlock(device_t dev) { struct aw_ccu_softc *sc; sc = device_get_softc(dev); mtx_unlock(&sc->mtx); } static const struct ofw_compat_data * aw_ccu_search_compatible(void) { const struct ofw_compat_data *compat; phandle_t root; root = OF_finddevice("/"); for (compat = compat_data; compat->ocd_str != NULL; compat++) if (ofw_bus_node_is_compatible(root, compat->ocd_str)) break; return (compat); } static int aw_ccu_probe(device_t dev) { const char *name; name = ofw_bus_get_name(dev); if (name == NULL || strcmp(name, "clocks") != 0) return (ENXIO); if (aw_ccu_search_compatible()->ocd_data == 0) return (ENXIO); device_set_desc(dev, "Allwinner Clock Control Unit"); return (BUS_PROBE_SPECIFIC); } static int aw_ccu_attach(device_t dev) { struct aw_ccu_softc *sc; phandle_t node, child; device_t cdev; int error; sc = device_get_softc(dev); node = ofw_bus_get_node(dev); simplebus_init(dev, node); sc->flags = aw_ccu_search_compatible()->ocd_data; /* * Map registers. The DT doesn't have a "reg" property * for the /clocks node and child nodes have conflicting "reg" * properties. */ sc->bst = bus_get_bus_tag(dev); error = bus_space_map(sc->bst, CCU_BASE, CCU_SIZE, 0, &sc->bsh); if (error != 0) { device_printf(dev, "couldn't map CCU: %d\n", error); return (error); } mtx_init(&sc->mtx, device_get_nameunit(dev), NULL, MTX_DEF); /* Attach child devices */ for (child = OF_child(node); child > 0; child = OF_peer(child)) { cdev = simplebus_add_device(dev, child, 0, NULL, -1, NULL); if (cdev != NULL) device_probe_and_attach(cdev); } return (bus_generic_attach(dev)); } static device_method_t aw_ccu_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aw_ccu_probe), DEVMETHOD(device_attach, aw_ccu_attach), /* clkdev interface */ DEVMETHOD(clkdev_write_4, aw_ccu_write_4), DEVMETHOD(clkdev_read_4, aw_ccu_read_4), DEVMETHOD(clkdev_modify_4, aw_ccu_modify_4), DEVMETHOD(clkdev_device_lock, aw_ccu_device_lock), DEVMETHOD(clkdev_device_unlock, aw_ccu_device_unlock), DEVMETHOD_END }; DEFINE_CLASS_1(aw_ccu, aw_ccu_driver, aw_ccu_methods, sizeof(struct aw_ccu_softc), simplebus_driver); static devclass_t aw_ccu_devclass; EARLY_DRIVER_MODULE(aw_ccu, simplebus, aw_ccu_driver, aw_ccu_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); MODULE_VERSION(aw_ccu, 1); Index: head/sys/arm/allwinner/aw_gmacclk.c =================================================================== --- head/sys/arm/allwinner/aw_gmacclk.c (revision 355357) +++ head/sys/arm/allwinner/aw_gmacclk.c (revision 355358) @@ -1,280 +1,279 @@ /*- * 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$ */ /* * Allwinner GMAC clock */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include "clkdev_if.h" #define GMAC_CLK_PIT (0x1 << 2) #define GMAC_CLK_PIT_SHIFT 2 #define GMAC_CLK_PIT_MII 0 #define GMAC_CLK_PIT_RGMII 1 #define GMAC_CLK_SRC (0x3 << 0) #define GMAC_CLK_SRC_SHIFT 0 #define GMAC_CLK_SRC_MII 0 #define GMAC_CLK_SRC_EXT_RGMII 1 #define GMAC_CLK_SRC_RGMII 2 #define EMAC_TXC_DIV_CFG (1 << 15) #define EMAC_TXC_DIV_CFG_SHIFT 15 #define EMAC_TXC_DIV_CFG_125MHZ 0 #define EMAC_TXC_DIV_CFG_25MHZ 1 #define EMAC_PHY_SELECT (1 << 16) #define EMAC_PHY_SELECT_SHIFT 16 #define EMAC_PHY_SELECT_INT 0 #define EMAC_PHY_SELECT_EXT 1 #define EMAC_ETXDC (0x7 << 10) #define EMAC_ETXDC_SHIFT 10 #define EMAC_ERXDC (0x1f << 5) #define EMAC_ERXDC_SHIFT 5 #define CLK_IDX_MII 0 #define CLK_IDX_RGMII 1 #define CLK_IDX_COUNT 2 static struct ofw_compat_data compat_data[] = { { "allwinner,sun7i-a20-gmac-clk", 1 }, { NULL, 0 } }; struct aw_gmacclk_sc { device_t clkdev; bus_addr_t reg; int rx_delay; int tx_delay; }; #define GMACCLK_READ(sc, val) CLKDEV_READ_4((sc)->clkdev, (sc)->reg, (val)) #define GMACCLK_WRITE(sc, val) CLKDEV_WRITE_4((sc)->clkdev, (sc)->reg, (val)) #define DEVICE_LOCK(sc) CLKDEV_DEVICE_LOCK((sc)->clkdev) #define DEVICE_UNLOCK(sc) CLKDEV_DEVICE_UNLOCK((sc)->clkdev) static int aw_gmacclk_init(struct clknode *clk, device_t dev) { struct aw_gmacclk_sc *sc; uint32_t val, index; sc = clknode_get_softc(clk); DEVICE_LOCK(sc); GMACCLK_READ(sc, &val); DEVICE_UNLOCK(sc); switch ((val & GMAC_CLK_SRC) >> GMAC_CLK_SRC_SHIFT) { case GMAC_CLK_SRC_MII: index = CLK_IDX_MII; break; case GMAC_CLK_SRC_RGMII: index = CLK_IDX_RGMII; break; default: return (ENXIO); } clknode_init_parent_idx(clk, index); return (0); } static int aw_gmacclk_set_mux(struct clknode *clk, int index) { struct aw_gmacclk_sc *sc; uint32_t val, clk_src, pit, txc_div; int error; sc = clknode_get_softc(clk); error = 0; switch (index) { case CLK_IDX_MII: clk_src = GMAC_CLK_SRC_MII; pit = GMAC_CLK_PIT_MII; txc_div = EMAC_TXC_DIV_CFG_25MHZ; break; case CLK_IDX_RGMII: clk_src = GMAC_CLK_SRC_RGMII; pit = GMAC_CLK_PIT_RGMII; txc_div = EMAC_TXC_DIV_CFG_125MHZ; break; default: return (ENXIO); } DEVICE_LOCK(sc); GMACCLK_READ(sc, &val); val &= ~(GMAC_CLK_SRC | GMAC_CLK_PIT); val |= (clk_src << GMAC_CLK_SRC_SHIFT); val |= (pit << GMAC_CLK_PIT_SHIFT); GMACCLK_WRITE(sc, val); DEVICE_UNLOCK(sc); return (0); } static clknode_method_t aw_gmacclk_clknode_methods[] = { /* Device interface */ CLKNODEMETHOD(clknode_init, aw_gmacclk_init), CLKNODEMETHOD(clknode_set_mux, aw_gmacclk_set_mux), CLKNODEMETHOD_END }; DEFINE_CLASS_1(aw_gmacclk_clknode, aw_gmacclk_clknode_class, aw_gmacclk_clknode_methods, sizeof(struct aw_gmacclk_sc), clknode_class); static int aw_gmacclk_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 GMAC Clock"); return (BUS_PROBE_DEFAULT); } static int aw_gmacclk_attach(device_t dev) { struct clknode_init_def def; struct aw_gmacclk_sc *sc; struct clkdom *clkdom; struct clknode *clk; clk_t clk_parent; bus_addr_t paddr; bus_size_t psize; phandle_t node; int error, ncells, i; node = ofw_bus_get_node(dev); if (ofw_reg_to_paddr(node, 0, &paddr, &psize, NULL) != 0) { device_printf(dev, "cannot parse 'reg' property\n"); return (ENXIO); } error = ofw_bus_parse_xref_list_get_length(node, "clocks", "#clock-cells", &ncells); if (error != 0 || ncells != CLK_IDX_COUNT) { device_printf(dev, "couldn't find parent clocks\n"); return (ENXIO); } clkdom = clkdom_create(dev); memset(&def, 0, sizeof(def)); error = clk_parse_ofw_clk_name(dev, node, &def.name); if (error != 0) { device_printf(dev, "cannot parse clock name\n"); error = ENXIO; goto fail; } def.id = 1; def.parent_names = malloc(sizeof(char *) * ncells, M_OFWPROP, M_WAITOK); for (i = 0; i < ncells; i++) { error = clk_get_by_ofw_index(dev, 0, i, &clk_parent); if (error != 0) { device_printf(dev, "cannot get clock %d\n", error); goto fail; } def.parent_names[i] = clk_get_name(clk_parent); clk_release(clk_parent); } def.parent_cnt = ncells; clk = clknode_create(clkdom, &aw_gmacclk_clknode_class, &def); if (clk == NULL) { device_printf(dev, "cannot create clknode\n"); error = ENXIO; goto fail; } sc = clknode_get_softc(clk); sc->reg = paddr; sc->clkdev = device_get_parent(dev); sc->tx_delay = sc->rx_delay = -1; OF_getencprop(node, "tx-delay", &sc->tx_delay, sizeof(sc->tx_delay)); OF_getencprop(node, "rx-delay", &sc->rx_delay, sizeof(sc->rx_delay)); clknode_register(clkdom, clk); if (clkdom_finit(clkdom) != 0) { device_printf(dev, "cannot finalize clkdom initialization\n"); error = ENXIO; goto fail; } if (bootverbose) clkdom_dump(clkdom); return (0); fail: return (error); } static device_method_t aw_gmacclk_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aw_gmacclk_probe), DEVMETHOD(device_attach, aw_gmacclk_attach), DEVMETHOD_END }; static driver_t aw_gmacclk_driver = { "aw_gmacclk", aw_gmacclk_methods, 0 }; static devclass_t aw_gmacclk_devclass; EARLY_DRIVER_MODULE(aw_gmacclk, simplebus, aw_gmacclk_driver, aw_gmacclk_devclass, 0, 0, BUS_PASS_RESOURCE + BUS_PASS_ORDER_MIDDLE); Index: head/sys/arm/allwinner/aw_reset.c =================================================================== --- head/sys/arm/allwinner/aw_reset.c (revision 355357) +++ head/sys/arm/allwinner/aw_reset.c (revision 355358) @@ -1,166 +1,165 @@ /*- * 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$ */ /* * Allwinner module software reset registers */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include "hwreset_if.h" #define RESET_OFFSET(index) ((index / 32) * 4) #define RESET_SHIFT(index) (index % 32) static struct ofw_compat_data compat_data[] = { { "allwinner,sun6i-a31-ahb1-reset", 1 }, { "allwinner,sun6i-a31-clock-reset", 1 }, { NULL, 0 } }; struct aw_reset_softc { struct resource *res; struct mtx mtx; }; static struct resource_spec aw_reset_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { -1, 0 } }; #define RESET_READ(sc, reg) bus_read_4((sc)->res, (reg)) #define RESET_WRITE(sc, reg, val) bus_write_4((sc)->res, (reg), (val)) static int aw_reset_assert(device_t dev, intptr_t id, bool reset) { struct aw_reset_softc *sc; uint32_t reg_value; sc = device_get_softc(dev); mtx_lock(&sc->mtx); reg_value = RESET_READ(sc, RESET_OFFSET(id)); if (reset) reg_value &= ~(1 << RESET_SHIFT(id)); else reg_value |= (1 << RESET_SHIFT(id)); RESET_WRITE(sc, RESET_OFFSET(id), reg_value); mtx_unlock(&sc->mtx); return (0); } static int aw_reset_is_asserted(device_t dev, intptr_t id, bool *reset) { struct aw_reset_softc *sc; uint32_t reg_value; sc = device_get_softc(dev); mtx_lock(&sc->mtx); reg_value = RESET_READ(sc, RESET_OFFSET(id)); mtx_unlock(&sc->mtx); *reset = (reg_value & (1 << RESET_SHIFT(id))) != 0 ? false : true; return (0); } static int aw_reset_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 Module Resets"); return (BUS_PROBE_DEFAULT); } static int aw_reset_attach(device_t dev) { struct aw_reset_softc *sc; sc = device_get_softc(dev); if (bus_alloc_resources(dev, aw_reset_spec, &sc->res) != 0) { device_printf(dev, "cannot allocate resources for device\n"); return (ENXIO); } mtx_init(&sc->mtx, device_get_nameunit(dev), NULL, MTX_DEF); hwreset_register_ofw_provider(dev); return (0); } static device_method_t aw_reset_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aw_reset_probe), DEVMETHOD(device_attach, aw_reset_attach), /* Reset interface */ DEVMETHOD(hwreset_assert, aw_reset_assert), DEVMETHOD(hwreset_is_asserted, aw_reset_is_asserted), DEVMETHOD_END }; static driver_t aw_reset_driver = { "aw_reset", aw_reset_methods, sizeof(struct aw_reset_softc), }; static devclass_t aw_reset_devclass; EARLY_DRIVER_MODULE(aw_reset, simplebus, aw_reset_driver, aw_reset_devclass, 0, 0, BUS_PASS_RESOURCE + BUS_PASS_ORDER_MIDDLE); MODULE_VERSION(aw_reset, 1); Index: head/sys/arm/allwinner/aw_rsb.c =================================================================== --- head/sys/arm/allwinner/aw_rsb.c (revision 355357) +++ head/sys/arm/allwinner/aw_rsb.c (revision 355358) @@ -1,502 +1,501 @@ /*- * 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$ */ /* * Allwinner RSB (Reduced Serial Bus) and P2WI (Push-Pull Two Wire Interface) */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iicbus_if.h" #define RSB_CTRL 0x00 #define START_TRANS (1 << 7) #define GLOBAL_INT_ENB (1 << 1) #define SOFT_RESET (1 << 0) #define RSB_CCR 0x04 #define RSB_INTE 0x08 #define RSB_INTS 0x0c #define INT_TRANS_ERR_ID(x) (((x) >> 8) & 0xf) #define INT_LOAD_BSY (1 << 2) #define INT_TRANS_ERR (1 << 1) #define INT_TRANS_OVER (1 << 0) #define INT_MASK (INT_LOAD_BSY|INT_TRANS_ERR|INT_TRANS_OVER) #define RSB_DADDR0 0x10 #define RSB_DADDR1 0x14 #define RSB_DLEN 0x18 #define DLEN_READ (1 << 4) #define RSB_DATA0 0x1c #define RSB_DATA1 0x20 #define RSB_CMD 0x2c #define CMD_SRTA 0xe8 #define CMD_RD8 0x8b #define CMD_RD16 0x9c #define CMD_RD32 0xa6 #define CMD_WR8 0x4e #define CMD_WR16 0x59 #define CMD_WR32 0x63 #define RSB_DAR 0x30 #define DAR_RTA (0xff << 16) #define DAR_RTA_SHIFT 16 #define DAR_DA (0xffff << 0) #define DAR_DA_SHIFT 0 #define RSB_MAXLEN 8 #define RSB_RESET_RETRY 100 #define RSB_I2C_TIMEOUT hz #define RSB_ADDR_PMIC_PRIMARY 0x3a3 #define RSB_ADDR_PMIC_SECONDARY 0x745 #define RSB_ADDR_PERIPH_IC 0xe89 #define A31_P2WI 1 #define A23_RSB 2 static struct ofw_compat_data compat_data[] = { { "allwinner,sun6i-a31-p2wi", A31_P2WI }, { "allwinner,sun8i-a23-rsb", A23_RSB }, { NULL, 0 } }; static struct resource_spec rsb_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { -1, 0 } }; /* * Device address to Run-time address mappings. * * Run-time address (RTA) is an 8-bit value used to address the device during * a read or write transaction. The following are valid RTAs: * 0x17 0x2d 0x3a 0x4e 0x59 0x63 0x74 0x8b 0x9c 0xa6 0xb1 0xc5 0xd2 0xe8 0xff * * Allwinner uses RTA 0x2d for the primary PMIC, 0x3a for the secondary PMIC, * and 0x4e for the peripheral IC (where applicable). */ static const struct { uint16_t addr; uint8_t rta; } rsb_rtamap[] = { { .addr = RSB_ADDR_PMIC_PRIMARY, .rta = 0x2d }, { .addr = RSB_ADDR_PMIC_SECONDARY, .rta = 0x3a }, { .addr = RSB_ADDR_PERIPH_IC, .rta = 0x4e }, { .addr = 0, .rta = 0 } }; struct rsb_softc { struct resource *res; struct mtx mtx; clk_t clk; hwreset_t rst; device_t iicbus; int busy; uint32_t status; uint16_t cur_addr; int type; struct iic_msg *msg; }; #define RSB_LOCK(sc) mtx_lock(&(sc)->mtx) #define RSB_UNLOCK(sc) mtx_unlock(&(sc)->mtx) #define RSB_ASSERT_LOCKED(sc) mtx_assert(&(sc)->mtx, MA_OWNED) #define RSB_READ(sc, reg) bus_read_4((sc)->res, (reg)) #define RSB_WRITE(sc, reg, val) bus_write_4((sc)->res, (reg), (val)) static phandle_t rsb_get_node(device_t bus, device_t dev) { return (ofw_bus_get_node(bus)); } static int rsb_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr) { struct rsb_softc *sc; int retry; sc = device_get_softc(dev); RSB_LOCK(sc); /* Write soft-reset bit and wait for it to self-clear. */ RSB_WRITE(sc, RSB_CTRL, SOFT_RESET); for (retry = RSB_RESET_RETRY; retry > 0; retry--) if ((RSB_READ(sc, RSB_CTRL) & SOFT_RESET) == 0) break; RSB_UNLOCK(sc); if (retry == 0) { device_printf(dev, "soft reset timeout\n"); return (ETIMEDOUT); } return (IIC_ENOADDR); } static uint32_t rsb_encode(const uint8_t *buf, u_int len, u_int off) { uint32_t val; u_int n; val = 0; for (n = off; n < MIN(len, 4 + off); n++) val |= ((uint32_t)buf[n] << ((n - off) * NBBY)); return val; } static void rsb_decode(const uint32_t val, uint8_t *buf, u_int len, u_int off) { u_int n; for (n = off; n < MIN(len, 4 + off); n++) buf[n] = (val >> ((n - off) * NBBY)) & 0xff; } static int rsb_start(device_t dev) { struct rsb_softc *sc; int error, retry; sc = device_get_softc(dev); RSB_ASSERT_LOCKED(sc); /* Start the transfer */ RSB_WRITE(sc, RSB_CTRL, GLOBAL_INT_ENB | START_TRANS); /* Wait for transfer to complete */ error = ETIMEDOUT; for (retry = RSB_I2C_TIMEOUT; retry > 0; retry--) { sc->status |= RSB_READ(sc, RSB_INTS); if ((sc->status & INT_TRANS_OVER) != 0) { error = 0; break; } DELAY((1000 * hz) / RSB_I2C_TIMEOUT); } if (error == 0 && (sc->status & INT_TRANS_OVER) == 0) { device_printf(dev, "transfer error, status 0x%08x\n", sc->status); error = EIO; } return (error); } static int rsb_set_rta(device_t dev, uint16_t addr) { struct rsb_softc *sc; uint8_t rta; int i; sc = device_get_softc(dev); RSB_ASSERT_LOCKED(sc); /* Lookup run-time address for given device address */ for (rta = 0, i = 0; rsb_rtamap[i].rta != 0; i++) if (rsb_rtamap[i].addr == addr) { rta = rsb_rtamap[i].rta; break; } if (rta == 0) { device_printf(dev, "RTA not known for address %#x\n", addr); return (ENXIO); } /* Set run-time address */ RSB_WRITE(sc, RSB_INTS, RSB_READ(sc, RSB_INTS)); RSB_WRITE(sc, RSB_DAR, (addr << DAR_DA_SHIFT) | (rta << DAR_RTA_SHIFT)); RSB_WRITE(sc, RSB_CMD, CMD_SRTA); return (rsb_start(dev)); } static int rsb_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs) { struct rsb_softc *sc; uint32_t daddr[2], data[2], dlen; uint16_t device_addr; uint8_t cmd; int error; sc = device_get_softc(dev); /* * P2WI and RSB are not really I2C or SMBus controllers, so there are * some restrictions imposed by the driver. * * Transfers must contain exactly two messages. The first is always * a write, containing a single data byte offset. Data will either * be read from or written to the corresponding data byte in the * second message. The slave address in both messages must be the * same. */ if (nmsgs != 2 || (msgs[0].flags & IIC_M_RD) == IIC_M_RD || (msgs[0].slave >> 1) != (msgs[1].slave >> 1) || msgs[0].len != 1 || msgs[1].len > RSB_MAXLEN) return (EINVAL); /* The RSB controller can read or write 1, 2, or 4 bytes at a time. */ if (sc->type == A23_RSB) { if ((msgs[1].flags & IIC_M_RD) != 0) { switch (msgs[1].len) { case 1: cmd = CMD_RD8; break; case 2: cmd = CMD_RD16; break; case 4: cmd = CMD_RD32; break; default: return (EINVAL); } } else { switch (msgs[1].len) { case 1: cmd = CMD_WR8; break; case 2: cmd = CMD_WR16; break; case 4: cmd = CMD_WR32; break; default: return (EINVAL); } } } RSB_LOCK(sc); while (sc->busy) mtx_sleep(sc, &sc->mtx, 0, "i2cbuswait", 0); sc->busy = 1; sc->status = 0; /* Select current run-time address if necessary */ if (sc->type == A23_RSB) { device_addr = msgs[0].slave >> 1; if (sc->cur_addr != device_addr) { error = rsb_set_rta(dev, device_addr); if (error != 0) goto done; sc->cur_addr = device_addr; sc->status = 0; } } /* Clear interrupt status */ RSB_WRITE(sc, RSB_INTS, RSB_READ(sc, RSB_INTS)); /* Program data access address registers */ daddr[0] = rsb_encode(msgs[0].buf, msgs[0].len, 0); RSB_WRITE(sc, RSB_DADDR0, daddr[0]); /* Write data */ if ((msgs[1].flags & IIC_M_RD) == 0) { data[0] = rsb_encode(msgs[1].buf, msgs[1].len, 0); RSB_WRITE(sc, RSB_DATA0, data[0]); } /* Set command type for RSB */ if (sc->type == A23_RSB) RSB_WRITE(sc, RSB_CMD, cmd); /* Program data length register and transfer direction */ dlen = msgs[0].len - 1; if ((msgs[1].flags & IIC_M_RD) == IIC_M_RD) dlen |= DLEN_READ; RSB_WRITE(sc, RSB_DLEN, dlen); /* Start transfer */ error = rsb_start(dev); if (error != 0) goto done; /* Read data */ if ((msgs[1].flags & IIC_M_RD) == IIC_M_RD) { data[0] = RSB_READ(sc, RSB_DATA0); rsb_decode(data[0], msgs[1].buf, msgs[1].len, 0); } done: sc->msg = NULL; sc->busy = 0; wakeup(sc); RSB_UNLOCK(sc); return (error); } static int rsb_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); switch (ofw_bus_search_compatible(dev, compat_data)->ocd_data) { case A23_RSB: device_set_desc(dev, "Allwinner RSB"); break; case A31_P2WI: device_set_desc(dev, "Allwinner P2WI"); break; default: return (ENXIO); } return (BUS_PROBE_DEFAULT); } static int rsb_attach(device_t dev) { struct rsb_softc *sc; int error; sc = device_get_softc(dev); mtx_init(&sc->mtx, device_get_nameunit(dev), "rsb", MTX_DEF); sc->type = ofw_bus_search_compatible(dev, compat_data)->ocd_data; if (clk_get_by_ofw_index(dev, 0, 0, &sc->clk) == 0) { error = clk_enable(sc->clk); if (error != 0) { device_printf(dev, "cannot enable clock\n"); goto fail; } } if (hwreset_get_by_ofw_idx(dev, 0, 0, &sc->rst) == 0) { error = hwreset_deassert(sc->rst); if (error != 0) { device_printf(dev, "cannot de-assert reset\n"); goto fail; } } if (bus_alloc_resources(dev, rsb_spec, &sc->res) != 0) { device_printf(dev, "cannot allocate resources for device\n"); error = ENXIO; goto fail; } sc->iicbus = device_add_child(dev, "iicbus", -1); if (sc->iicbus == NULL) { device_printf(dev, "cannot add iicbus child device\n"); error = ENXIO; goto fail; } bus_generic_attach(dev); return (0); fail: bus_release_resources(dev, rsb_spec, &sc->res); if (sc->rst != NULL) hwreset_release(sc->rst); if (sc->clk != NULL) clk_release(sc->clk); mtx_destroy(&sc->mtx); return (error); } static device_method_t rsb_methods[] = { /* Device interface */ DEVMETHOD(device_probe, rsb_probe), DEVMETHOD(device_attach, rsb_attach), /* 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), /* OFW methods */ DEVMETHOD(ofw_bus_get_node, rsb_get_node), /* iicbus interface */ DEVMETHOD(iicbus_callback, iicbus_null_callback), DEVMETHOD(iicbus_reset, rsb_reset), DEVMETHOD(iicbus_transfer, rsb_transfer), DEVMETHOD_END }; static driver_t rsb_driver = { "iichb", rsb_methods, sizeof(struct rsb_softc), }; static devclass_t rsb_devclass; EARLY_DRIVER_MODULE(iicbus, rsb, iicbus_driver, iicbus_devclass, 0, 0, BUS_PASS_SUPPORTDEV + BUS_PASS_ORDER_MIDDLE); EARLY_DRIVER_MODULE(rsb, simplebus, rsb_driver, rsb_devclass, 0, 0, BUS_PASS_SUPPORTDEV + BUS_PASS_ORDER_MIDDLE); MODULE_VERSION(rsb, 1); MODULE_DEPEND(rsb, iicbus, 1, 1, 1); SIMPLEBUS_PNP_INFO(compat_data); Index: head/sys/arm/allwinner/aw_sid.c =================================================================== --- head/sys/arm/allwinner/aw_sid.c (revision 355357) +++ head/sys/arm/allwinner/aw_sid.c (revision 355358) @@ -1,417 +1,416 @@ /*- * 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$ */ /* * Allwinner secure ID controller */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nvmem_if.h" /* * Starting at least from sun8iw6 (A83T) EFUSE starts at 0x200 * There is 3 registers in the low area to read/write protected EFUSE. */ #define SID_PRCTL 0x40 #define SID_PRCTL_OFFSET_MASK 0xff #define SID_PRCTL_OFFSET(n) (((n) & SID_PRCTL_OFFSET_MASK) << 16) #define SID_PRCTL_LOCK (0xac << 8) #define SID_PRCTL_READ (0x01 << 1) #define SID_PRCTL_WRITE (0x01 << 0) #define SID_PRKEY 0x50 #define SID_RDKEY 0x60 #define EFUSE_OFFSET 0x200 #define EFUSE_NAME_SIZE 32 #define EFUSE_DESC_SIZE 64 struct aw_sid_efuse { char name[EFUSE_NAME_SIZE]; char desc[EFUSE_DESC_SIZE]; bus_size_t base; bus_size_t offset; uint32_t size; enum aw_sid_fuse_id id; bool public; }; static struct aw_sid_efuse a10_efuses[] = { { .name = "rootkey", .desc = "Root Key or ChipID", .offset = 0x0, .size = 16, .id = AW_SID_FUSE_ROOTKEY, .public = true, }, }; static struct aw_sid_efuse a64_efuses[] = { { .name = "rootkey", .desc = "Root Key or ChipID", .base = EFUSE_OFFSET, .offset = 0x00, .size = 16, .id = AW_SID_FUSE_ROOTKEY, .public = true, }, { .name = "ths-calib", .desc = "Thermal Sensor Calibration Data", .base = EFUSE_OFFSET, .offset = 0x34, .size = 6, .id = AW_SID_FUSE_THSSENSOR, .public = true, }, }; static struct aw_sid_efuse a83t_efuses[] = { { .name = "rootkey", .desc = "Root Key or ChipID", .base = EFUSE_OFFSET, .offset = 0x00, .size = 16, .id = AW_SID_FUSE_ROOTKEY, .public = true, }, { .name = "ths-calib", .desc = "Thermal Sensor Calibration Data", .base = EFUSE_OFFSET, .offset = 0x34, .size = 8, .id = AW_SID_FUSE_THSSENSOR, .public = true, }, }; static struct aw_sid_efuse h3_efuses[] = { { .name = "rootkey", .desc = "Root Key or ChipID", .base = EFUSE_OFFSET, .offset = 0x00, .size = 16, .id = AW_SID_FUSE_ROOTKEY, .public = true, }, { .name = "ths-calib", .desc = "Thermal Sensor Calibration Data", .base = EFUSE_OFFSET, .offset = 0x34, .size = 2, .id = AW_SID_FUSE_THSSENSOR, .public = false, }, }; static struct aw_sid_efuse h5_efuses[] = { { .name = "rootkey", .desc = "Root Key or ChipID", .base = EFUSE_OFFSET, .offset = 0x00, .size = 16, .id = AW_SID_FUSE_ROOTKEY, .public = true, }, { .name = "ths-calib", .desc = "Thermal Sensor Calibration Data", .base = EFUSE_OFFSET, .offset = 0x34, .size = 4, .id = AW_SID_FUSE_THSSENSOR, .public = true, }, }; struct aw_sid_conf { struct aw_sid_efuse *efuses; size_t nfuses; }; static const struct aw_sid_conf a10_conf = { .efuses = a10_efuses, .nfuses = nitems(a10_efuses), }; static const struct aw_sid_conf a20_conf = { .efuses = a10_efuses, .nfuses = nitems(a10_efuses), }; static const struct aw_sid_conf a64_conf = { .efuses = a64_efuses, .nfuses = nitems(a64_efuses), }; static const struct aw_sid_conf a83t_conf = { .efuses = a83t_efuses, .nfuses = nitems(a83t_efuses), }; static const struct aw_sid_conf h3_conf = { .efuses = h3_efuses, .nfuses = nitems(h3_efuses), }; static const struct aw_sid_conf h5_conf = { .efuses = h5_efuses, .nfuses = nitems(h5_efuses), }; static struct ofw_compat_data compat_data[] = { { "allwinner,sun4i-a10-sid", (uintptr_t)&a10_conf}, { "allwinner,sun7i-a20-sid", (uintptr_t)&a20_conf}, { "allwinner,sun50i-a64-sid", (uintptr_t)&a64_conf}, { "allwinner,sun8i-a83t-sid", (uintptr_t)&a83t_conf}, { "allwinner,sun8i-h3-sid", (uintptr_t)&h3_conf}, { "allwinner,sun50i-h5-sid", (uintptr_t)&h5_conf}, { NULL, 0 } }; struct aw_sid_softc { device_t sid_dev; struct resource *res; struct aw_sid_conf *sid_conf; struct mtx prctl_mtx; }; static struct aw_sid_softc *aw_sid_sc; static struct resource_spec aw_sid_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { -1, 0 } }; #define RD1(sc, reg) bus_read_1((sc)->res, (reg)) #define RD4(sc, reg) bus_read_4((sc)->res, (reg)) #define WR4(sc, reg, val) bus_write_4((sc)->res, (reg), (val)) static int aw_sid_sysctl(SYSCTL_HANDLER_ARGS); static int aw_sid_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 Secure ID Controller"); return (BUS_PROBE_DEFAULT); } static int aw_sid_attach(device_t dev) { struct aw_sid_softc *sc; phandle_t node; int i; node = ofw_bus_get_node(dev); sc = device_get_softc(dev); sc->sid_dev = dev; if (bus_alloc_resources(dev, aw_sid_spec, &sc->res) != 0) { device_printf(dev, "cannot allocate resources for device\n"); return (ENXIO); } mtx_init(&sc->prctl_mtx, device_get_nameunit(dev), NULL, MTX_DEF); sc->sid_conf = (struct aw_sid_conf *)ofw_bus_search_compatible(dev, compat_data)->ocd_data; aw_sid_sc = sc; /* Register ourself so device can resolve who we are */ OF_device_register_xref(OF_xref_from_node(node), dev); for (i = 0; i < sc->sid_conf->nfuses ;i++) {\ SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, sc->sid_conf->efuses[i].name, CTLTYPE_STRING | CTLFLAG_RD, dev, sc->sid_conf->efuses[i].id, aw_sid_sysctl, "A", sc->sid_conf->efuses[i].desc); } return (0); } int aw_sid_get_fuse(enum aw_sid_fuse_id id, uint8_t *out, uint32_t *size) { struct aw_sid_softc *sc; uint32_t val; int i, j; sc = aw_sid_sc; if (sc == NULL) return (ENXIO); for (i = 0; i < sc->sid_conf->nfuses; i++) if (id == sc->sid_conf->efuses[i].id) break; if (i == sc->sid_conf->nfuses) return (ENOENT); if (*size != sc->sid_conf->efuses[i].size) { *size = sc->sid_conf->efuses[i].size; return (ENOMEM); } if (out == NULL) return (ENOMEM); if (sc->sid_conf->efuses[i].public == false) mtx_lock(&sc->prctl_mtx); for (j = 0; j < sc->sid_conf->efuses[i].size; j += 4) { if (sc->sid_conf->efuses[i].public == false) { val = SID_PRCTL_OFFSET(sc->sid_conf->efuses[i].offset + j) | SID_PRCTL_LOCK | SID_PRCTL_READ; WR4(sc, SID_PRCTL, val); /* Read bit will be cleared once read has concluded */ while (RD4(sc, SID_PRCTL) & SID_PRCTL_READ) continue; val = RD4(sc, SID_RDKEY); } else val = RD4(sc, sc->sid_conf->efuses[i].base + sc->sid_conf->efuses[i].offset + j); out[j] = val & 0xFF; if (j + 1 < *size) out[j + 1] = (val & 0xFF00) >> 8; if (j + 2 < *size) out[j + 2] = (val & 0xFF0000) >> 16; if (j + 3 < *size) out[j + 3] = (val & 0xFF000000) >> 24; } if (sc->sid_conf->efuses[i].public == false) mtx_unlock(&sc->prctl_mtx); return (0); } static int aw_sid_read(device_t dev, uint32_t offset, uint32_t size, uint8_t *buffer) { struct aw_sid_softc *sc; enum aw_sid_fuse_id fuse_id = 0; int i; sc = device_get_softc(dev); for (i = 0; i < sc->sid_conf->nfuses; i++) if (offset == (sc->sid_conf->efuses[i].base + sc->sid_conf->efuses[i].offset)) { fuse_id = sc->sid_conf->efuses[i].id; break; } if (fuse_id == 0) return (ENOENT); return (aw_sid_get_fuse(fuse_id, buffer, &size)); } static int aw_sid_sysctl(SYSCTL_HANDLER_ARGS) { struct aw_sid_softc *sc; device_t dev = arg1; enum aw_sid_fuse_id fuse = arg2; uint8_t data[32]; char out[128]; uint32_t size; int ret, i; sc = device_get_softc(dev); /* Get the size of the efuse data */ size = 0; aw_sid_get_fuse(fuse, NULL, &size); /* We now have the real size */ ret = aw_sid_get_fuse(fuse, data, &size); if (ret != 0) { device_printf(dev, "Cannot get fuse id %d: %d\n", fuse, ret); return (ENOENT); } for (i = 0; i < size; i++) snprintf(out + (i * 2), sizeof(out) - (i * 2), "%.2x", data[i]); return sysctl_handle_string(oidp, out, sizeof(out), req); } static device_method_t aw_sid_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aw_sid_probe), DEVMETHOD(device_attach, aw_sid_attach), /* NVMEM interface */ DEVMETHOD(nvmem_read, aw_sid_read), DEVMETHOD_END }; static driver_t aw_sid_driver = { "aw_sid", aw_sid_methods, sizeof(struct aw_sid_softc), }; static devclass_t aw_sid_devclass; EARLY_DRIVER_MODULE(aw_sid, simplebus, aw_sid_driver, aw_sid_devclass, 0, 0, BUS_PASS_SUPPORTDEV + BUS_PASS_ORDER_FIRST); MODULE_VERSION(aw_sid, 1); SIMPLEBUS_PNP_INFO(compat_data); Index: head/sys/arm/allwinner/aw_sid.h =================================================================== --- head/sys/arm/allwinner/aw_sid.h (revision 355357) +++ head/sys/arm/allwinner/aw_sid.h (revision 355358) @@ -1,40 +1,39 @@ /*- * 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$ */ #ifndef __AW_SID_H__ #define __AW_SID_H__ enum aw_sid_fuse_id { AW_SID_FUSE_ROOTKEY = 1, AW_SID_FUSE_THSSENSOR, }; int aw_sid_read_tscalib(uint32_t *, uint32_t *); int aw_sid_get_fuse(enum aw_sid_fuse_id id, uint8_t *out, uint32_t *size); #endif /* !__AW_SID_H__ */ Index: head/sys/arm/allwinner/aw_thermal.c =================================================================== --- head/sys/arm/allwinner/aw_thermal.c (revision 355357) +++ head/sys/arm/allwinner/aw_thermal.c (revision 355358) @@ -1,733 +1,732 @@ /*- * 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$ */ /* * Allwinner thermal sensor controller */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cpufreq_if.h" #include "nvmem_if.h" #define THS_CTRL0 0x00 #define THS_CTRL1 0x04 #define ADC_CALI_EN (1 << 17) #define THS_CTRL2 0x40 #define SENSOR_ACQ1_SHIFT 16 #define SENSOR2_EN (1 << 2) #define SENSOR1_EN (1 << 1) #define SENSOR0_EN (1 << 0) #define THS_INTC 0x44 #define THS_THERMAL_PER_SHIFT 12 #define THS_INTS 0x48 #define THS2_DATA_IRQ_STS (1 << 10) #define THS1_DATA_IRQ_STS (1 << 9) #define THS0_DATA_IRQ_STS (1 << 8) #define SHUT_INT2_STS (1 << 6) #define SHUT_INT1_STS (1 << 5) #define SHUT_INT0_STS (1 << 4) #define ALARM_INT2_STS (1 << 2) #define ALARM_INT1_STS (1 << 1) #define ALARM_INT0_STS (1 << 0) #define THS_ALARM0_CTRL 0x50 #define ALARM_T_HOT_MASK 0xfff #define ALARM_T_HOT_SHIFT 16 #define ALARM_T_HYST_MASK 0xfff #define ALARM_T_HYST_SHIFT 0 #define THS_SHUTDOWN0_CTRL 0x60 #define SHUT_T_HOT_MASK 0xfff #define SHUT_T_HOT_SHIFT 16 #define THS_FILTER 0x70 #define THS_CALIB0 0x74 #define THS_CALIB1 0x78 #define THS_DATA0 0x80 #define THS_DATA1 0x84 #define THS_DATA2 0x88 #define DATA_MASK 0xfff #define A83T_CLK_RATE 24000000 #define A83T_ADC_ACQUIRE_TIME 23 /* 24Mhz/(23 + 1) = 1us */ #define A83T_THERMAL_PER 1 /* 4096 * (1 + 1) / 24Mhz = 341 us */ #define A83T_FILTER 0x5 /* Filter enabled, avg of 4 */ #define A83T_TEMP_BASE 2719000 #define A83T_TEMP_MUL 1000 #define A83T_TEMP_DIV 14186 #define A64_CLK_RATE 4000000 #define A64_ADC_ACQUIRE_TIME 400 /* 4Mhz/(400 + 1) = 100 us */ #define A64_THERMAL_PER 24 /* 4096 * (24 + 1) / 4Mhz = 25.6 ms */ #define A64_FILTER 0x6 /* Filter enabled, avg of 8 */ #define A64_TEMP_BASE 2170000 #define A64_TEMP_MUL 1000 #define A64_TEMP_DIV 8560 #define H3_CLK_RATE 4000000 #define H3_ADC_ACQUIRE_TIME 0x3f #define H3_THERMAL_PER 401 #define H3_FILTER 0x6 /* Filter enabled, avg of 8 */ #define H3_TEMP_BASE 217 #define H3_TEMP_MUL 1000 #define H3_TEMP_DIV 8253 #define H3_TEMP_MINUS 1794000 #define H3_INIT_ALARM 90 /* degC */ #define H3_INIT_SHUT 105 /* degC */ #define H5_CLK_RATE 24000000 #define H5_ADC_ACQUIRE_TIME 479 /* 24Mhz/479 = 20us */ #define H5_THERMAL_PER 58 /* 4096 * (58 + 1) / 24Mhz = 10ms */ #define H5_FILTER 0x6 /* Filter enabled, avg of 8 */ #define H5_TEMP_BASE 233832448 #define H5_TEMP_MUL 124885 #define H5_TEMP_DIV 20 #define H5_TEMP_BASE_CPU 271581184 #define H5_TEMP_MUL_CPU 152253 #define H5_TEMP_BASE_GPU 289406976 #define H5_TEMP_MUL_GPU 166724 #define H5_INIT_CPU_ALARM 80 /* degC */ #define H5_INIT_CPU_SHUT 96 /* degC */ #define H5_INIT_GPU_ALARM 84 /* degC */ #define H5_INIT_GPU_SHUT 100 /* degC */ #define TEMP_C_TO_K 273 #define SENSOR_ENABLE_ALL (SENSOR0_EN|SENSOR1_EN|SENSOR2_EN) #define SHUT_INT_ALL (SHUT_INT0_STS|SHUT_INT1_STS|SHUT_INT2_STS) #define ALARM_INT_ALL (ALARM_INT0_STS) #define MAX_SENSORS 3 #define MAX_CF_LEVELS 64 #define THROTTLE_ENABLE_DEFAULT 1 /* Enable thermal throttling */ static int aw_thermal_throttle_enable = THROTTLE_ENABLE_DEFAULT; TUNABLE_INT("hw.aw_thermal.throttle_enable", &aw_thermal_throttle_enable); struct aw_thermal_sensor { const char *name; const char *desc; int init_alarm; int init_shut; }; struct aw_thermal_config { struct aw_thermal_sensor sensors[MAX_SENSORS]; int nsensors; uint64_t clk_rate; uint32_t adc_acquire_time; int adc_cali_en; uint32_t filter; uint32_t thermal_per; int (*to_temp)(uint32_t, int); uint32_t (*to_reg)(int, int); int temp_base; int temp_mul; int temp_div; int calib0, calib1; uint32_t calib0_mask, calib1_mask; }; static int a83t_to_temp(uint32_t val, int sensor) { return ((A83T_TEMP_BASE - (val * A83T_TEMP_MUL)) / A83T_TEMP_DIV); } static const struct aw_thermal_config a83t_config = { .nsensors = 3, .sensors = { [0] = { .name = "cluster0", .desc = "CPU cluster 0 temperature", }, [1] = { .name = "cluster1", .desc = "CPU cluster 1 temperature", }, [2] = { .name = "gpu", .desc = "GPU temperature", }, }, .clk_rate = A83T_CLK_RATE, .adc_acquire_time = A83T_ADC_ACQUIRE_TIME, .adc_cali_en = 1, .filter = A83T_FILTER, .thermal_per = A83T_THERMAL_PER, .to_temp = a83t_to_temp, .calib0_mask = 0xffffffff, .calib1_mask = 0xffff, }; static int a64_to_temp(uint32_t val, int sensor) { return ((A64_TEMP_BASE - (val * A64_TEMP_MUL)) / A64_TEMP_DIV); } static const struct aw_thermal_config a64_config = { .nsensors = 3, .sensors = { [0] = { .name = "cpu", .desc = "CPU temperature", }, [1] = { .name = "gpu1", .desc = "GPU temperature 1", }, [2] = { .name = "gpu2", .desc = "GPU temperature 2", }, }, .clk_rate = A64_CLK_RATE, .adc_acquire_time = A64_ADC_ACQUIRE_TIME, .adc_cali_en = 1, .filter = A64_FILTER, .thermal_per = A64_THERMAL_PER, .to_temp = a64_to_temp, .calib0_mask = 0xffffffff, .calib1_mask = 0xffff, }; static int h3_to_temp(uint32_t val, int sensor) { return (H3_TEMP_BASE - ((val * H3_TEMP_MUL) / H3_TEMP_DIV)); } static uint32_t h3_to_reg(int val, int sensor) { return ((H3_TEMP_MINUS - (val * H3_TEMP_DIV)) / H3_TEMP_MUL); } static const struct aw_thermal_config h3_config = { .nsensors = 1, .sensors = { [0] = { .name = "cpu", .desc = "CPU temperature", .init_alarm = H3_INIT_ALARM, .init_shut = H3_INIT_SHUT, }, }, .clk_rate = H3_CLK_RATE, .adc_acquire_time = H3_ADC_ACQUIRE_TIME, .adc_cali_en = 1, .filter = H3_FILTER, .thermal_per = H3_THERMAL_PER, .to_temp = h3_to_temp, .to_reg = h3_to_reg, .calib0_mask = 0xffff, }; static int h5_to_temp(uint32_t val, int sensor) { int tmp; /* Temp is lower than 70 degrees */ if (val > 0x500) { tmp = H5_TEMP_BASE - (val * H5_TEMP_MUL); tmp >>= H5_TEMP_DIV; return (tmp); } if (sensor == 0) tmp = H5_TEMP_BASE_CPU - (val * H5_TEMP_MUL_CPU); else if (sensor == 1) tmp = H5_TEMP_BASE_GPU - (val * H5_TEMP_MUL_GPU); else { printf("Unknown sensor %d\n", sensor); return (val); } tmp >>= H5_TEMP_DIV; return (tmp); } static uint32_t h5_to_reg(int val, int sensor) { int tmp; if (val < 70) { tmp = H5_TEMP_BASE - (val << H5_TEMP_DIV); tmp /= H5_TEMP_MUL; } else { if (sensor == 0) { tmp = H5_TEMP_BASE_CPU - (val << H5_TEMP_DIV); tmp /= H5_TEMP_MUL_CPU; } else if (sensor == 1) { tmp = H5_TEMP_BASE_GPU - (val << H5_TEMP_DIV); tmp /= H5_TEMP_MUL_GPU; } else { printf("Unknown sensor %d\n", sensor); return (val); } } return ((uint32_t)tmp); } static const struct aw_thermal_config h5_config = { .nsensors = 2, .sensors = { [0] = { .name = "cpu", .desc = "CPU temperature", .init_alarm = H5_INIT_CPU_ALARM, .init_shut = H5_INIT_CPU_SHUT, }, [1] = { .name = "gpu", .desc = "GPU temperature", .init_alarm = H5_INIT_GPU_ALARM, .init_shut = H5_INIT_GPU_SHUT, }, }, .clk_rate = H5_CLK_RATE, .adc_acquire_time = H5_ADC_ACQUIRE_TIME, .filter = H5_FILTER, .thermal_per = H5_THERMAL_PER, .to_temp = h5_to_temp, .to_reg = h5_to_reg, .calib0_mask = 0xffffffff, }; static struct ofw_compat_data compat_data[] = { { "allwinner,sun8i-a83t-ths", (uintptr_t)&a83t_config }, { "allwinner,sun8i-h3-ths", (uintptr_t)&h3_config }, { "allwinner,sun50i-a64-ths", (uintptr_t)&a64_config }, { "allwinner,sun50i-h5-ths", (uintptr_t)&h5_config }, { NULL, (uintptr_t)NULL } }; #define THS_CONF(d) \ (void *)ofw_bus_search_compatible((d), compat_data)->ocd_data struct aw_thermal_softc { device_t dev; struct resource *res[2]; struct aw_thermal_config *conf; struct task cf_task; int throttle; int min_freq; struct cf_level levels[MAX_CF_LEVELS]; eventhandler_tag cf_pre_tag; clk_t clk_apb; clk_t clk_ths; }; static struct resource_spec aw_thermal_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE }, { -1, 0 } }; #define RD4(sc, reg) bus_read_4((sc)->res[0], (reg)) #define WR4(sc, reg, val) bus_write_4((sc)->res[0], (reg), (val)) static int aw_thermal_init(struct aw_thermal_softc *sc) { phandle_t node; uint32_t calib[2]; int error; node = ofw_bus_get_node(sc->dev); if (nvmem_get_cell_len(node, "ths-calib") > sizeof(calib)) { device_printf(sc->dev, "ths-calib nvmem cell is too large\n"); return (ENXIO); } error = nvmem_read_cell_by_name(node, "ths-calib", (void *)&calib, nvmem_get_cell_len(node, "ths-calib")); /* Read calibration settings from EFUSE */ if (error != 0) { device_printf(sc->dev, "Cannot read THS efuse\n"); return (error); } calib[0] &= sc->conf->calib0_mask; calib[1] &= sc->conf->calib1_mask; /* Write calibration settings to thermal controller */ if (calib[0] != 0) WR4(sc, THS_CALIB0, calib[0]); if (calib[1] != 0) WR4(sc, THS_CALIB1, calib[1]); /* Configure ADC acquire time (CLK_IN/(N+1)) and enable sensors */ WR4(sc, THS_CTRL1, ADC_CALI_EN); WR4(sc, THS_CTRL0, sc->conf->adc_acquire_time); WR4(sc, THS_CTRL2, sc->conf->adc_acquire_time << SENSOR_ACQ1_SHIFT); /* Set thermal period */ WR4(sc, THS_INTC, sc->conf->thermal_per << THS_THERMAL_PER_SHIFT); /* Enable average filter */ WR4(sc, THS_FILTER, sc->conf->filter); /* Enable interrupts */ WR4(sc, THS_INTS, RD4(sc, THS_INTS)); WR4(sc, THS_INTC, RD4(sc, THS_INTC) | SHUT_INT_ALL | ALARM_INT_ALL); /* Enable sensors */ WR4(sc, THS_CTRL2, RD4(sc, THS_CTRL2) | SENSOR_ENABLE_ALL); return (0); } static int aw_thermal_gettemp(struct aw_thermal_softc *sc, int sensor) { uint32_t val; val = RD4(sc, THS_DATA0 + (sensor * 4)); return (sc->conf->to_temp(val, sensor)); } static int aw_thermal_getshut(struct aw_thermal_softc *sc, int sensor) { uint32_t val; val = RD4(sc, THS_SHUTDOWN0_CTRL + (sensor * 4)); val = (val >> SHUT_T_HOT_SHIFT) & SHUT_T_HOT_MASK; return (sc->conf->to_temp(val, sensor)); } static void aw_thermal_setshut(struct aw_thermal_softc *sc, int sensor, int temp) { uint32_t val; val = RD4(sc, THS_SHUTDOWN0_CTRL + (sensor * 4)); val &= ~(SHUT_T_HOT_MASK << SHUT_T_HOT_SHIFT); val |= (sc->conf->to_reg(temp, sensor) << SHUT_T_HOT_SHIFT); WR4(sc, THS_SHUTDOWN0_CTRL + (sensor * 4), val); } static int aw_thermal_gethyst(struct aw_thermal_softc *sc, int sensor) { uint32_t val; val = RD4(sc, THS_ALARM0_CTRL + (sensor * 4)); val = (val >> ALARM_T_HYST_SHIFT) & ALARM_T_HYST_MASK; return (sc->conf->to_temp(val, sensor)); } static int aw_thermal_getalarm(struct aw_thermal_softc *sc, int sensor) { uint32_t val; val = RD4(sc, THS_ALARM0_CTRL + (sensor * 4)); val = (val >> ALARM_T_HOT_SHIFT) & ALARM_T_HOT_MASK; return (sc->conf->to_temp(val, sensor)); } static void aw_thermal_setalarm(struct aw_thermal_softc *sc, int sensor, int temp) { uint32_t val; val = RD4(sc, THS_ALARM0_CTRL + (sensor * 4)); val &= ~(ALARM_T_HOT_MASK << ALARM_T_HOT_SHIFT); val |= (sc->conf->to_reg(temp, sensor) << ALARM_T_HOT_SHIFT); WR4(sc, THS_ALARM0_CTRL + (sensor * 4), val); } static int aw_thermal_sysctl(SYSCTL_HANDLER_ARGS) { struct aw_thermal_softc *sc; int sensor, val; sc = arg1; sensor = arg2; val = aw_thermal_gettemp(sc, sensor) + TEMP_C_TO_K; return sysctl_handle_opaque(oidp, &val, sizeof(val), req); } static void aw_thermal_throttle(struct aw_thermal_softc *sc, int enable) { device_t cf_dev; int count, error; if (enable == sc->throttle) return; if (enable != 0) { /* Set the lowest available frequency */ cf_dev = devclass_get_device(devclass_find("cpufreq"), 0); if (cf_dev == NULL) return; count = MAX_CF_LEVELS; error = CPUFREQ_LEVELS(cf_dev, sc->levels, &count); if (error != 0 || count == 0) return; sc->min_freq = sc->levels[count - 1].total_set.freq; error = CPUFREQ_SET(cf_dev, &sc->levels[count - 1], CPUFREQ_PRIO_USER); if (error != 0) return; } sc->throttle = enable; } static void aw_thermal_cf_task(void *arg, int pending) { struct aw_thermal_softc *sc; sc = arg; aw_thermal_throttle(sc, 1); } static void aw_thermal_cf_pre_change(void *arg, const struct cf_level *level, int *status) { struct aw_thermal_softc *sc; int temp_cur, temp_alarm; sc = arg; if (aw_thermal_throttle_enable == 0 || sc->throttle == 0 || level->total_set.freq == sc->min_freq) return; temp_cur = aw_thermal_gettemp(sc, 0); temp_alarm = aw_thermal_getalarm(sc, 0); if (temp_cur < temp_alarm) aw_thermal_throttle(sc, 0); else *status = ENXIO; } static void aw_thermal_intr(void *arg) { struct aw_thermal_softc *sc; device_t dev; uint32_t ints; dev = arg; sc = device_get_softc(dev); ints = RD4(sc, THS_INTS); WR4(sc, THS_INTS, ints); if ((ints & SHUT_INT_ALL) != 0) { device_printf(dev, "WARNING - current temperature exceeds safe limits\n"); shutdown_nice(RB_POWEROFF); } if ((ints & ALARM_INT_ALL) != 0) taskqueue_enqueue(taskqueue_thread, &sc->cf_task); } static int aw_thermal_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (THS_CONF(dev) == NULL) return (ENXIO); device_set_desc(dev, "Allwinner Thermal Sensor Controller"); return (BUS_PROBE_DEFAULT); } static int aw_thermal_attach(device_t dev) { struct aw_thermal_softc *sc; hwreset_t rst; int i, error; void *ih; sc = device_get_softc(dev); sc->dev = dev; rst = NULL; ih = NULL; sc->conf = THS_CONF(dev); TASK_INIT(&sc->cf_task, 0, aw_thermal_cf_task, sc); if (bus_alloc_resources(dev, aw_thermal_spec, sc->res) != 0) { device_printf(dev, "cannot allocate resources for device\n"); return (ENXIO); } if (clk_get_by_ofw_name(dev, 0, "apb", &sc->clk_apb) == 0) { error = clk_enable(sc->clk_apb); if (error != 0) { device_printf(dev, "cannot enable apb clock\n"); goto fail; } } if (clk_get_by_ofw_name(dev, 0, "ths", &sc->clk_ths) == 0) { error = clk_set_freq(sc->clk_ths, sc->conf->clk_rate, 0); if (error != 0) { device_printf(dev, "cannot set ths clock rate\n"); goto fail; } error = clk_enable(sc->clk_ths); if (error != 0) { device_printf(dev, "cannot enable ths clock\n"); goto fail; } } 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; } } error = bus_setup_intr(dev, sc->res[1], INTR_TYPE_MISC | INTR_MPSAFE, NULL, aw_thermal_intr, dev, &ih); if (error != 0) { device_printf(dev, "cannot setup interrupt handler\n"); goto fail; } for (i = 0; i < sc->conf->nsensors; i++) { if (sc->conf->sensors[i].init_alarm > 0) aw_thermal_setalarm(sc, i, sc->conf->sensors[i].init_alarm); if (sc->conf->sensors[i].init_shut > 0) aw_thermal_setshut(sc, i, sc->conf->sensors[i].init_shut); } if (aw_thermal_init(sc) != 0) goto fail; for (i = 0; i < sc->conf->nsensors; i++) SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, sc->conf->sensors[i].name, CTLTYPE_INT | CTLFLAG_RD, sc, i, aw_thermal_sysctl, "IK0", sc->conf->sensors[i].desc); if (bootverbose) for (i = 0; i < sc->conf->nsensors; i++) { device_printf(dev, "%s: alarm %dC hyst %dC shut %dC\n", sc->conf->sensors[i].name, aw_thermal_getalarm(sc, i), aw_thermal_gethyst(sc, i), aw_thermal_getshut(sc, i)); } sc->cf_pre_tag = EVENTHANDLER_REGISTER(cpufreq_pre_change, aw_thermal_cf_pre_change, sc, EVENTHANDLER_PRI_FIRST); return (0); fail: if (ih != NULL) bus_teardown_intr(dev, sc->res[1], ih); if (rst != NULL) hwreset_release(rst); if (sc->clk_apb != NULL) clk_release(sc->clk_apb); if (sc->clk_ths != NULL) clk_release(sc->clk_ths); bus_release_resources(dev, aw_thermal_spec, sc->res); return (ENXIO); } static device_method_t aw_thermal_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aw_thermal_probe), DEVMETHOD(device_attach, aw_thermal_attach), DEVMETHOD_END }; static driver_t aw_thermal_driver = { "aw_thermal", aw_thermal_methods, sizeof(struct aw_thermal_softc), }; static devclass_t aw_thermal_devclass; DRIVER_MODULE(aw_thermal, simplebus, aw_thermal_driver, aw_thermal_devclass, 0, 0); MODULE_VERSION(aw_thermal, 1); MODULE_DEPEND(aw_thermal, aw_sid, 1, 1, 1); SIMPLEBUS_PNP_INFO(compat_data); Index: head/sys/arm/allwinner/aw_usbphy.c =================================================================== --- head/sys/arm/allwinner/aw_usbphy.c (revision 355357) +++ head/sys/arm/allwinner/aw_usbphy.c (revision 355358) @@ -1,509 +1,508 @@ /*- * 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$ */ /* * Allwinner USB PHY */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "phynode_if.h" enum awusbphy_type { AWUSBPHY_TYPE_A10 = 1, AWUSBPHY_TYPE_A13, AWUSBPHY_TYPE_A20, AWUSBPHY_TYPE_A31, AWUSBPHY_TYPE_H3, AWUSBPHY_TYPE_A64, AWUSBPHY_TYPE_A83T, AWUSBPHY_TYPE_H6, }; struct aw_usbphy_conf { int num_phys; enum awusbphy_type phy_type; bool pmu_unk1; bool phy0_route; }; static const struct aw_usbphy_conf a10_usbphy_conf = { .num_phys = 3, .phy_type = AWUSBPHY_TYPE_A10, .pmu_unk1 = false, .phy0_route = false, }; static const struct aw_usbphy_conf a13_usbphy_conf = { .num_phys = 2, .phy_type = AWUSBPHY_TYPE_A13, .pmu_unk1 = false, .phy0_route = false, }; static const struct aw_usbphy_conf a20_usbphy_conf = { .num_phys = 3, .phy_type = AWUSBPHY_TYPE_A20, .pmu_unk1 = false, .phy0_route = false, }; static const struct aw_usbphy_conf a31_usbphy_conf = { .num_phys = 3, .phy_type = AWUSBPHY_TYPE_A31, .pmu_unk1 = false, .phy0_route = false, }; static const struct aw_usbphy_conf h3_usbphy_conf = { .num_phys = 4, .phy_type = AWUSBPHY_TYPE_H3, .pmu_unk1 = true, .phy0_route = false, }; static const struct aw_usbphy_conf a64_usbphy_conf = { .num_phys = 2, .phy_type = AWUSBPHY_TYPE_A64, .pmu_unk1 = true, .phy0_route = true, }; static const struct aw_usbphy_conf a83t_usbphy_conf = { .num_phys = 3, .phy_type = AWUSBPHY_TYPE_A83T, .pmu_unk1 = false, .phy0_route = false, }; static const struct aw_usbphy_conf h6_usbphy_conf = { .num_phys = 4, .phy_type = AWUSBPHY_TYPE_H6, .pmu_unk1 = false, .phy0_route = true, }; static struct ofw_compat_data compat_data[] = { { "allwinner,sun4i-a10-usb-phy", (uintptr_t)&a10_usbphy_conf }, { "allwinner,sun5i-a13-usb-phy", (uintptr_t)&a13_usbphy_conf }, { "allwinner,sun6i-a31-usb-phy", (uintptr_t)&a31_usbphy_conf }, { "allwinner,sun7i-a20-usb-phy", (uintptr_t)&a20_usbphy_conf }, { "allwinner,sun8i-h3-usb-phy", (uintptr_t)&h3_usbphy_conf }, { "allwinner,sun50i-a64-usb-phy", (uintptr_t)&a64_usbphy_conf }, { "allwinner,sun8i-a83t-usb-phy", (uintptr_t)&a83t_usbphy_conf }, { "allwinner,sun50i-h6-usb-phy", (uintptr_t)&h6_usbphy_conf }, { NULL, 0 } }; struct awusbphy_softc { struct resource * phy_ctrl; struct resource ** pmu; regulator_t * reg; gpio_pin_t id_det_pin; int id_det_valid; gpio_pin_t vbus_det_pin; int vbus_det_valid; struct aw_usbphy_conf *phy_conf; int mode; }; /* Phy class and methods. */ static int awusbphy_phy_enable(struct phynode *phy, bool enable); static int awusbphy_get_mode(struct phynode *phy, int *mode); static int awusbphy_set_mode(struct phynode *phy, int mode); static phynode_usb_method_t awusbphy_phynode_methods[] = { PHYNODEMETHOD(phynode_enable, awusbphy_phy_enable), PHYNODEMETHOD(phynode_usb_get_mode, awusbphy_get_mode), PHYNODEMETHOD(phynode_usb_set_mode, awusbphy_set_mode), PHYNODEMETHOD_END }; DEFINE_CLASS_1(awusbphy_phynode, awusbphy_phynode_class, awusbphy_phynode_methods, sizeof(struct phynode_usb_sc), phynode_usb_class); #define RD4(res, o) bus_read_4(res, (o)) #define WR4(res, o, v) bus_write_4(res, (o), (v)) #define CLR4(res, o, m) WR4(res, o, RD4(res, o) & ~(m)) #define SET4(res, o, m) WR4(res, o, RD4(res, o) | (m)) #define OTG_PHY_CFG 0x20 #define OTG_PHY_ROUTE_OTG (1 << 0) #define PMU_IRQ_ENABLE 0x00 #define PMU_AHB_INCR8 (1 << 10) #define PMU_AHB_INCR4 (1 << 9) #define PMU_AHB_INCRX_ALIGN (1 << 8) #define PMU_ULPI_BYPASS (1 << 0) #define PMU_UNK_H3 0x10 #define PMU_UNK_H3_CLR 0x2 #define PHY_CSR 0x00 #define ID_PULLUP_EN (1 << 17) #define DPDM_PULLUP_EN (1 << 16) #define FORCE_ID (0x3 << 14) #define FORCE_ID_SHIFT 14 #define FORCE_ID_LOW 2 #define FORCE_VBUS_VALID (0x3 << 12) #define FORCE_VBUS_VALID_SHIFT 12 #define FORCE_VBUS_VALID_HIGH 3 #define VBUS_CHANGE_DET (1 << 6) #define ID_CHANGE_DET (1 << 5) #define DPDM_CHANGE_DET (1 << 4) static void awusbphy_configure(device_t dev, int phyno) { struct awusbphy_softc *sc; sc = device_get_softc(dev); if (sc->pmu[phyno] == NULL) return; if (sc->phy_conf->pmu_unk1 == true) CLR4(sc->pmu[phyno], PMU_UNK_H3, PMU_UNK_H3_CLR); SET4(sc->pmu[phyno], PMU_IRQ_ENABLE, PMU_ULPI_BYPASS | PMU_AHB_INCR8 | PMU_AHB_INCR4 | PMU_AHB_INCRX_ALIGN); } static int awusbphy_init(device_t dev) { struct awusbphy_softc *sc; phandle_t node; char pname[20]; int error, off, rid; regulator_t reg; hwreset_t rst; clk_t clk; sc = device_get_softc(dev); node = ofw_bus_get_node(dev); sc->phy_conf = (struct aw_usbphy_conf *)ofw_bus_search_compatible(dev, compat_data)->ocd_data; /* Get phy_ctrl region */ if (ofw_bus_find_string_index(node, "reg-names", "phy_ctrl", &rid) != 0) { device_printf(dev, "Cannot locate phy control resource\n"); return (ENXIO); } sc->phy_ctrl = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->phy_ctrl == NULL) { device_printf(dev, "Cannot allocate resource\n"); return (ENXIO); } /* Enable clocks */ for (off = 0; clk_get_by_ofw_index(dev, 0, off, &clk) == 0; off++) { error = clk_enable(clk); if (error != 0) { device_printf(dev, "couldn't enable clock %s\n", clk_get_name(clk)); return (error); } } /* De-assert resets */ for (off = 0; hwreset_get_by_ofw_idx(dev, 0, off, &rst) == 0; off++) { error = hwreset_deassert(rst); if (error != 0) { device_printf(dev, "couldn't de-assert reset %d\n", off); return (error); } } /* Get GPIOs */ error = gpio_pin_get_by_ofw_property(dev, node, "usb0_id_det-gpios", &sc->id_det_pin); if (error == 0) sc->id_det_valid = 1; error = gpio_pin_get_by_ofw_property(dev, node, "usb0_vbus_det-gpios", &sc->vbus_det_pin); if (error == 0) sc->vbus_det_valid = 1; sc->reg = malloc(sizeof(*(sc->reg)) * sc->phy_conf->num_phys, M_DEVBUF, M_WAITOK | M_ZERO); sc->pmu = malloc(sizeof(*(sc->pmu)) * sc->phy_conf->num_phys, M_DEVBUF, M_WAITOK | M_ZERO); /* Get regulators */ for (off = 0; off < sc->phy_conf->num_phys; off++) { snprintf(pname, sizeof(pname), "usb%d_vbus-supply", off); if (regulator_get_by_ofw_property(dev, 0, pname, ®) == 0) sc->reg[off] = reg; snprintf(pname, sizeof(pname), "pmu%d", off); if (ofw_bus_find_string_index(node, "reg-names", pname, &rid) != 0) continue; sc->pmu[off] = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->pmu[off] == NULL) { device_printf(dev, "Cannot allocate resource\n"); return (ENXIO); } } return (0); } static int awusbphy_vbus_detect(device_t dev, int *val) { struct awusbphy_softc *sc; bool active; int error; sc = device_get_softc(dev); if (sc->vbus_det_valid) { error = gpio_pin_is_active(sc->vbus_det_pin, &active); if (error != 0) { device_printf(dev, "Cannot get status of id pin %d\n", error); return (error); } *val = active; return (0); } *val = 0; return (0); } static int awusbphy_phy_enable(struct phynode *phynode, bool enable) { device_t dev; intptr_t phy; struct awusbphy_softc *sc; regulator_t reg; int error, vbus_det; dev = phynode_get_device(phynode); phy = phynode_get_id(phynode); sc = device_get_softc(dev); if (phy < 0 || phy >= sc->phy_conf->num_phys) return (ERANGE); /* Configure PHY */ awusbphy_configure(dev, phy); /* Regulators are optional. If not found, return success. */ reg = sc->reg[phy]; if (reg == NULL) return (0); if (phy == 0) { /* If an external vbus is detected, do not enable phy 0 */ error = awusbphy_vbus_detect(dev, &vbus_det); if (error) goto out; if (vbus_det == 1) { if (bootverbose) device_printf(dev, "External VBUS detected, not enabling the regulator\n"); return (0); } } if (enable) { /* Depending on the PHY we need to route OTG to OHCI/EHCI */ error = regulator_enable(reg); } else error = regulator_disable(reg); out: if (error != 0) { device_printf(dev, "couldn't %s regulator for phy %jd\n", enable ? "enable" : "disable", (intmax_t)phy); return (error); } return (0); } static int awusbphy_get_mode(struct phynode *phynode, int *mode) { struct awusbphy_softc *sc; device_t dev; dev = phynode_get_device(phynode); sc = device_get_softc(dev); *mode = sc->mode; return (0); } static int awusbphy_set_mode(struct phynode *phynode, int mode) { device_t dev; intptr_t phy; struct awusbphy_softc *sc; uint32_t val; int error, vbus_det; dev = phynode_get_device(phynode); phy = phynode_get_id(phynode); sc = device_get_softc(dev); if (phy != 0) { if (mode != PHY_USB_MODE_HOST) return (EINVAL); return (0); } switch (mode) { case PHY_USB_MODE_HOST: val = bus_read_4(sc->phy_ctrl, PHY_CSR); val &= ~(VBUS_CHANGE_DET | ID_CHANGE_DET | DPDM_CHANGE_DET); val |= (ID_PULLUP_EN | DPDM_PULLUP_EN); val &= ~FORCE_ID; val |= (FORCE_ID_LOW << FORCE_ID_SHIFT); val &= ~FORCE_VBUS_VALID; val |= (FORCE_VBUS_VALID_HIGH << FORCE_VBUS_VALID_SHIFT); bus_write_4(sc->phy_ctrl, PHY_CSR, val); if (sc->phy_conf->phy0_route == true) { error = awusbphy_vbus_detect(dev, &vbus_det); if (error) goto out; if (vbus_det == 0) CLR4(sc->phy_ctrl, OTG_PHY_CFG, OTG_PHY_ROUTE_OTG); else SET4(sc->phy_ctrl, OTG_PHY_CFG, OTG_PHY_ROUTE_OTG); } break; case PHY_USB_MODE_OTG: /* TODO */ break; } sc->mode = mode; out: return (0); } static int awusbphy_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 USB PHY"); return (BUS_PROBE_DEFAULT); } static int awusbphy_attach(device_t dev) { int error; struct phynode *phynode; struct phynode_init_def phy_init; struct awusbphy_softc *sc; int i; sc = device_get_softc(dev); error = awusbphy_init(dev); if (error) { device_printf(dev, "failed to initialize USB PHY, error %d\n", error); return (error); } /* Create and register phys. */ for (i = 0; i < sc->phy_conf->num_phys; i++) { bzero(&phy_init, sizeof(phy_init)); phy_init.id = i; phy_init.ofw_node = ofw_bus_get_node(dev); phynode = phynode_create(dev, &awusbphy_phynode_class, &phy_init); if (phynode == NULL) { device_printf(dev, "failed to create USB PHY\n"); return (ENXIO); } if (phynode_register(phynode) == NULL) { device_printf(dev, "failed to create USB PHY\n"); return (ENXIO); } } return (error); } static device_method_t awusbphy_methods[] = { /* Device interface */ DEVMETHOD(device_probe, awusbphy_probe), DEVMETHOD(device_attach, awusbphy_attach), DEVMETHOD_END }; static driver_t awusbphy_driver = { "awusbphy", awusbphy_methods, sizeof(struct awusbphy_softc) }; static devclass_t awusbphy_devclass; /* aw_usbphy needs to come up after regulators/gpio/etc, but before ehci/ohci */ EARLY_DRIVER_MODULE(awusbphy, simplebus, awusbphy_driver, awusbphy_devclass, 0, 0, BUS_PASS_SUPPORTDEV + BUS_PASS_ORDER_MIDDLE); MODULE_VERSION(awusbphy, 1); Index: head/sys/arm/allwinner/if_awg.c =================================================================== --- head/sys/arm/allwinner/if_awg.c (revision 355357) +++ head/sys/arm/allwinner/if_awg.c (revision 355358) @@ -1,1963 +1,1962 @@ /*- * 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$ */ /* * Allwinner Gigabit Ethernet MAC (EMAC) controller */ #include "opt_device_polling.h" #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "syscon_if.h" #include "miibus_if.h" #include "gpio_if.h" #define RD4(sc, reg) bus_read_4((sc)->res[_RES_EMAC], (reg)) #define WR4(sc, reg, val) bus_write_4((sc)->res[_RES_EMAC], (reg), (val)) #define AWG_LOCK(sc) mtx_lock(&(sc)->mtx) #define AWG_UNLOCK(sc) mtx_unlock(&(sc)->mtx); #define AWG_ASSERT_LOCKED(sc) mtx_assert(&(sc)->mtx, MA_OWNED) #define AWG_ASSERT_UNLOCKED(sc) mtx_assert(&(sc)->mtx, MA_NOTOWNED) #define DESC_ALIGN 4 #define TX_DESC_COUNT 1024 #define TX_DESC_SIZE (sizeof(struct emac_desc) * TX_DESC_COUNT) #define RX_DESC_COUNT 256 #define RX_DESC_SIZE (sizeof(struct emac_desc) * RX_DESC_COUNT) #define DESC_OFF(n) ((n) * sizeof(struct emac_desc)) #define TX_NEXT(n) (((n) + 1) & (TX_DESC_COUNT - 1)) #define TX_SKIP(n, o) (((n) + (o)) & (TX_DESC_COUNT - 1)) #define RX_NEXT(n) (((n) + 1) & (RX_DESC_COUNT - 1)) #define TX_MAX_SEGS 20 #define SOFT_RST_RETRY 1000 #define MII_BUSY_RETRY 1000 #define MDIO_FREQ 2500000 #define BURST_LEN_DEFAULT 8 #define RX_TX_PRI_DEFAULT 0 #define PAUSE_TIME_DEFAULT 0x400 #define TX_INTERVAL_DEFAULT 64 #define RX_BATCH_DEFAULT 64 /* syscon EMAC clock register */ #define EMAC_CLK_REG 0x30 #define EMAC_CLK_EPHY_ADDR (0x1f << 20) /* H3 */ #define EMAC_CLK_EPHY_ADDR_SHIFT 20 #define EMAC_CLK_EPHY_LED_POL (1 << 17) /* H3 */ #define EMAC_CLK_EPHY_SHUTDOWN (1 << 16) /* H3 */ #define EMAC_CLK_EPHY_SELECT (1 << 15) /* H3 */ #define EMAC_CLK_RMII_EN (1 << 13) #define EMAC_CLK_ETXDC (0x7 << 10) #define EMAC_CLK_ETXDC_SHIFT 10 #define EMAC_CLK_ERXDC (0x1f << 5) #define EMAC_CLK_ERXDC_SHIFT 5 #define EMAC_CLK_PIT (0x1 << 2) #define EMAC_CLK_PIT_MII (0 << 2) #define EMAC_CLK_PIT_RGMII (1 << 2) #define EMAC_CLK_SRC (0x3 << 0) #define EMAC_CLK_SRC_MII (0 << 0) #define EMAC_CLK_SRC_EXT_RGMII (1 << 0) #define EMAC_CLK_SRC_RGMII (2 << 0) /* Burst length of RX and TX DMA transfers */ static int awg_burst_len = BURST_LEN_DEFAULT; TUNABLE_INT("hw.awg.burst_len", &awg_burst_len); /* RX / TX DMA priority. If 1, RX DMA has priority over TX DMA. */ static int awg_rx_tx_pri = RX_TX_PRI_DEFAULT; TUNABLE_INT("hw.awg.rx_tx_pri", &awg_rx_tx_pri); /* Pause time field in the transmitted control frame */ static int awg_pause_time = PAUSE_TIME_DEFAULT; TUNABLE_INT("hw.awg.pause_time", &awg_pause_time); /* Request a TX interrupt every descriptors */ static int awg_tx_interval = TX_INTERVAL_DEFAULT; TUNABLE_INT("hw.awg.tx_interval", &awg_tx_interval); /* Maximum number of mbufs to send to if_input */ static int awg_rx_batch = RX_BATCH_DEFAULT; TUNABLE_INT("hw.awg.rx_batch", &awg_rx_batch); enum awg_type { EMAC_A83T = 1, EMAC_H3, EMAC_A64, }; static struct ofw_compat_data compat_data[] = { { "allwinner,sun8i-a83t-emac", EMAC_A83T }, { "allwinner,sun8i-h3-emac", EMAC_H3 }, { "allwinner,sun50i-a64-emac", EMAC_A64 }, { NULL, 0 } }; struct awg_bufmap { bus_dmamap_t map; struct mbuf *mbuf; }; struct awg_txring { bus_dma_tag_t desc_tag; bus_dmamap_t desc_map; struct emac_desc *desc_ring; bus_addr_t desc_ring_paddr; bus_dma_tag_t buf_tag; struct awg_bufmap buf_map[TX_DESC_COUNT]; u_int cur, next, queued; u_int segs; }; struct awg_rxring { bus_dma_tag_t desc_tag; bus_dmamap_t desc_map; struct emac_desc *desc_ring; bus_addr_t desc_ring_paddr; bus_dma_tag_t buf_tag; struct awg_bufmap buf_map[RX_DESC_COUNT]; bus_dmamap_t buf_spare_map; u_int cur; }; enum { _RES_EMAC, _RES_IRQ, _RES_SYSCON, _RES_NITEMS }; struct awg_softc { struct resource *res[_RES_NITEMS]; struct mtx mtx; if_t ifp; device_t dev; device_t miibus; struct callout stat_ch; struct task link_task; void *ih; u_int mdc_div_ratio_m; int link; int if_flags; enum awg_type type; struct syscon *syscon; struct awg_txring tx; struct awg_rxring rx; }; static struct resource_spec awg_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE }, { SYS_RES_MEMORY, 1, RF_ACTIVE | RF_OPTIONAL }, { -1, 0 } }; static void awg_txeof(struct awg_softc *sc); static int awg_parse_delay(device_t dev, uint32_t *tx_delay, uint32_t *rx_delay); static uint32_t syscon_read_emac_clk_reg(device_t dev); static void syscon_write_emac_clk_reg(device_t dev, uint32_t val); static phandle_t awg_get_phy_node(device_t dev); static bool awg_has_internal_phy(device_t dev); static int awg_miibus_readreg(device_t dev, int phy, int reg) { struct awg_softc *sc; int retry, val; sc = device_get_softc(dev); val = 0; WR4(sc, EMAC_MII_CMD, (sc->mdc_div_ratio_m << MDC_DIV_RATIO_M_SHIFT) | (phy << PHY_ADDR_SHIFT) | (reg << PHY_REG_ADDR_SHIFT) | MII_BUSY); for (retry = MII_BUSY_RETRY; retry > 0; retry--) { if ((RD4(sc, EMAC_MII_CMD) & MII_BUSY) == 0) { val = RD4(sc, EMAC_MII_DATA); break; } DELAY(10); } if (retry == 0) device_printf(dev, "phy read timeout, phy=%d reg=%d\n", phy, reg); return (val); } static int awg_miibus_writereg(device_t dev, int phy, int reg, int val) { struct awg_softc *sc; int retry; sc = device_get_softc(dev); WR4(sc, EMAC_MII_DATA, val); WR4(sc, EMAC_MII_CMD, (sc->mdc_div_ratio_m << MDC_DIV_RATIO_M_SHIFT) | (phy << PHY_ADDR_SHIFT) | (reg << PHY_REG_ADDR_SHIFT) | MII_WR | MII_BUSY); for (retry = MII_BUSY_RETRY; retry > 0; retry--) { if ((RD4(sc, EMAC_MII_CMD) & MII_BUSY) == 0) break; DELAY(10); } if (retry == 0) device_printf(dev, "phy write timeout, phy=%d reg=%d\n", phy, reg); return (0); } static void awg_update_link_locked(struct awg_softc *sc) { struct mii_data *mii; uint32_t val; AWG_ASSERT_LOCKED(sc); if ((if_getdrvflags(sc->ifp) & IFF_DRV_RUNNING) == 0) return; mii = device_get_softc(sc->miibus); if ((mii->mii_media_status & (IFM_ACTIVE | IFM_AVALID)) == (IFM_ACTIVE | IFM_AVALID)) { switch (IFM_SUBTYPE(mii->mii_media_active)) { case IFM_1000_T: case IFM_1000_SX: case IFM_100_TX: case IFM_10_T: sc->link = 1; break; default: sc->link = 0; break; } } else sc->link = 0; if (sc->link == 0) return; val = RD4(sc, EMAC_BASIC_CTL_0); val &= ~(BASIC_CTL_SPEED | BASIC_CTL_DUPLEX); if (IFM_SUBTYPE(mii->mii_media_active) == IFM_1000_T || IFM_SUBTYPE(mii->mii_media_active) == IFM_1000_SX) val |= BASIC_CTL_SPEED_1000 << BASIC_CTL_SPEED_SHIFT; else if (IFM_SUBTYPE(mii->mii_media_active) == IFM_100_TX) val |= BASIC_CTL_SPEED_100 << BASIC_CTL_SPEED_SHIFT; else val |= BASIC_CTL_SPEED_10 << BASIC_CTL_SPEED_SHIFT; if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0) val |= BASIC_CTL_DUPLEX; WR4(sc, EMAC_BASIC_CTL_0, val); val = RD4(sc, EMAC_RX_CTL_0); val &= ~RX_FLOW_CTL_EN; if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_RXPAUSE) != 0) val |= RX_FLOW_CTL_EN; WR4(sc, EMAC_RX_CTL_0, val); val = RD4(sc, EMAC_TX_FLOW_CTL); val &= ~(PAUSE_TIME|TX_FLOW_CTL_EN); if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_TXPAUSE) != 0) val |= TX_FLOW_CTL_EN; if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0) val |= awg_pause_time << PAUSE_TIME_SHIFT; WR4(sc, EMAC_TX_FLOW_CTL, val); } static void awg_link_task(void *arg, int pending) { struct awg_softc *sc; sc = arg; AWG_LOCK(sc); awg_update_link_locked(sc); AWG_UNLOCK(sc); } static void awg_miibus_statchg(device_t dev) { struct awg_softc *sc; sc = device_get_softc(dev); taskqueue_enqueue(taskqueue_swi, &sc->link_task); } static void awg_media_status(if_t ifp, struct ifmediareq *ifmr) { struct awg_softc *sc; struct mii_data *mii; sc = if_getsoftc(ifp); mii = device_get_softc(sc->miibus); AWG_LOCK(sc); mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; AWG_UNLOCK(sc); } static int awg_media_change(if_t ifp) { struct awg_softc *sc; struct mii_data *mii; int error; sc = if_getsoftc(ifp); mii = device_get_softc(sc->miibus); AWG_LOCK(sc); error = mii_mediachg(mii); AWG_UNLOCK(sc); return (error); } static int awg_encap(struct awg_softc *sc, struct mbuf **mp) { bus_dmamap_t map; bus_dma_segment_t segs[TX_MAX_SEGS]; int error, nsegs, cur, first, last, i; u_int csum_flags; uint32_t flags, status; struct mbuf *m; cur = first = sc->tx.cur; map = sc->tx.buf_map[first].map; m = *mp; error = bus_dmamap_load_mbuf_sg(sc->tx.buf_tag, map, m, segs, &nsegs, BUS_DMA_NOWAIT); if (error == EFBIG) { m = m_collapse(m, M_NOWAIT, TX_MAX_SEGS); if (m == NULL) { device_printf(sc->dev, "awg_encap: m_collapse failed\n"); m_freem(*mp); *mp = NULL; return (ENOMEM); } *mp = m; error = bus_dmamap_load_mbuf_sg(sc->tx.buf_tag, map, m, segs, &nsegs, BUS_DMA_NOWAIT); if (error != 0) { m_freem(*mp); *mp = NULL; } } if (error != 0) { device_printf(sc->dev, "awg_encap: bus_dmamap_load_mbuf_sg failed\n"); return (error); } if (nsegs == 0) { m_freem(*mp); *mp = NULL; return (EIO); } if (sc->tx.queued + nsegs > TX_DESC_COUNT) { bus_dmamap_unload(sc->tx.buf_tag, map); return (ENOBUFS); } bus_dmamap_sync(sc->tx.buf_tag, map, BUS_DMASYNC_PREWRITE); flags = TX_FIR_DESC; status = 0; if ((m->m_pkthdr.csum_flags & CSUM_IP) != 0) { if ((m->m_pkthdr.csum_flags & (CSUM_TCP|CSUM_UDP)) != 0) csum_flags = TX_CHECKSUM_CTL_FULL; else csum_flags = TX_CHECKSUM_CTL_IP; flags |= (csum_flags << TX_CHECKSUM_CTL_SHIFT); } for (i = 0; i < nsegs; i++) { sc->tx.segs++; if (i == nsegs - 1) { flags |= TX_LAST_DESC; /* * Can only request TX completion * interrupt on last descriptor. */ if (sc->tx.segs >= awg_tx_interval) { sc->tx.segs = 0; flags |= TX_INT_CTL; } } sc->tx.desc_ring[cur].addr = htole32((uint32_t)segs[i].ds_addr); sc->tx.desc_ring[cur].size = htole32(flags | segs[i].ds_len); sc->tx.desc_ring[cur].status = htole32(status); flags &= ~TX_FIR_DESC; /* * Setting of the valid bit in the first descriptor is * deferred until the whole chain is fully set up. */ status = TX_DESC_CTL; ++sc->tx.queued; cur = TX_NEXT(cur); } sc->tx.cur = cur; /* Store mapping and mbuf in the last segment */ last = TX_SKIP(cur, TX_DESC_COUNT - 1); sc->tx.buf_map[first].map = sc->tx.buf_map[last].map; sc->tx.buf_map[last].map = map; sc->tx.buf_map[last].mbuf = m; /* * The whole mbuf chain has been DMA mapped, * fix the first descriptor. */ sc->tx.desc_ring[first].status = htole32(TX_DESC_CTL); return (0); } static void awg_clean_txbuf(struct awg_softc *sc, int index) { struct awg_bufmap *bmap; --sc->tx.queued; bmap = &sc->tx.buf_map[index]; if (bmap->mbuf != NULL) { bus_dmamap_sync(sc->tx.buf_tag, bmap->map, BUS_DMASYNC_POSTWRITE); bus_dmamap_unload(sc->tx.buf_tag, bmap->map); m_freem(bmap->mbuf); bmap->mbuf = NULL; } } static void awg_setup_rxdesc(struct awg_softc *sc, int index, bus_addr_t paddr) { uint32_t status, size; status = RX_DESC_CTL; size = MCLBYTES - 1; sc->rx.desc_ring[index].addr = htole32((uint32_t)paddr); sc->rx.desc_ring[index].size = htole32(size); sc->rx.desc_ring[index].status = htole32(status); } static void awg_reuse_rxdesc(struct awg_softc *sc, int index) { sc->rx.desc_ring[index].status = htole32(RX_DESC_CTL); } static int awg_newbuf_rx(struct awg_softc *sc, int index) { struct mbuf *m; bus_dma_segment_t seg; bus_dmamap_t map; int nsegs; m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (m == NULL) return (ENOBUFS); m->m_pkthdr.len = m->m_len = m->m_ext.ext_size; m_adj(m, ETHER_ALIGN); if (bus_dmamap_load_mbuf_sg(sc->rx.buf_tag, sc->rx.buf_spare_map, m, &seg, &nsegs, BUS_DMA_NOWAIT) != 0) { m_freem(m); return (ENOBUFS); } if (sc->rx.buf_map[index].mbuf != NULL) { bus_dmamap_sync(sc->rx.buf_tag, sc->rx.buf_map[index].map, BUS_DMASYNC_POSTREAD); bus_dmamap_unload(sc->rx.buf_tag, sc->rx.buf_map[index].map); } map = sc->rx.buf_map[index].map; sc->rx.buf_map[index].map = sc->rx.buf_spare_map; sc->rx.buf_spare_map = map; bus_dmamap_sync(sc->rx.buf_tag, sc->rx.buf_map[index].map, BUS_DMASYNC_PREREAD); sc->rx.buf_map[index].mbuf = m; awg_setup_rxdesc(sc, index, seg.ds_addr); return (0); } static void awg_start_locked(struct awg_softc *sc) { struct mbuf *m; uint32_t val; if_t ifp; int cnt, err; AWG_ASSERT_LOCKED(sc); if (!sc->link) return; ifp = sc->ifp; if ((if_getdrvflags(ifp) & (IFF_DRV_RUNNING|IFF_DRV_OACTIVE)) != IFF_DRV_RUNNING) return; for (cnt = 0; ; cnt++) { m = if_dequeue(ifp); if (m == NULL) break; err = awg_encap(sc, &m); if (err != 0) { if (err == ENOBUFS) if_setdrvflagbits(ifp, IFF_DRV_OACTIVE, 0); if (m != NULL) if_sendq_prepend(ifp, m); break; } if_bpfmtap(ifp, m); } if (cnt != 0) { bus_dmamap_sync(sc->tx.desc_tag, sc->tx.desc_map, BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE); /* Start and run TX DMA */ val = RD4(sc, EMAC_TX_CTL_1); WR4(sc, EMAC_TX_CTL_1, val | TX_DMA_START); } } static void awg_start(if_t ifp) { struct awg_softc *sc; sc = if_getsoftc(ifp); AWG_LOCK(sc); awg_start_locked(sc); AWG_UNLOCK(sc); } static void awg_tick(void *softc) { struct awg_softc *sc; struct mii_data *mii; if_t ifp; int link; sc = softc; ifp = sc->ifp; mii = device_get_softc(sc->miibus); AWG_ASSERT_LOCKED(sc); if ((if_getdrvflags(ifp) & IFF_DRV_RUNNING) == 0) return; link = sc->link; mii_tick(mii); if (sc->link && !link) awg_start_locked(sc); callout_reset(&sc->stat_ch, hz, awg_tick, sc); } /* Bit Reversal - http://aggregate.org/MAGIC/#Bit%20Reversal */ static uint32_t bitrev32(uint32_t x) { x = (((x & 0xaaaaaaaa) >> 1) | ((x & 0x55555555) << 1)); x = (((x & 0xcccccccc) >> 2) | ((x & 0x33333333) << 2)); x = (((x & 0xf0f0f0f0) >> 4) | ((x & 0x0f0f0f0f) << 4)); x = (((x & 0xff00ff00) >> 8) | ((x & 0x00ff00ff) << 8)); return (x >> 16) | (x << 16); } static u_int awg_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt) { uint32_t crc, hashreg, hashbit, *hash = arg; crc = ether_crc32_le(LLADDR(sdl), ETHER_ADDR_LEN) & 0x7f; crc = bitrev32(~crc) >> 26; hashreg = (crc >> 5); hashbit = (crc & 0x1f); hash[hashreg] |= (1 << hashbit); return (1); } static void awg_setup_rxfilter(struct awg_softc *sc) { uint32_t val, hash[2], machi, maclo; uint8_t *eaddr; if_t ifp; AWG_ASSERT_LOCKED(sc); ifp = sc->ifp; val = 0; hash[0] = hash[1] = 0; if (if_getflags(ifp) & IFF_PROMISC) val |= DIS_ADDR_FILTER; else if (if_getflags(ifp) & IFF_ALLMULTI) { val |= RX_ALL_MULTICAST; hash[0] = hash[1] = ~0; } else if (if_foreach_llmaddr(ifp, awg_hash_maddr, hash) > 0) val |= HASH_MULTICAST; /* Write our unicast address */ eaddr = IF_LLADDR(ifp); machi = (eaddr[5] << 8) | eaddr[4]; maclo = (eaddr[3] << 24) | (eaddr[2] << 16) | (eaddr[1] << 8) | (eaddr[0] << 0); WR4(sc, EMAC_ADDR_HIGH(0), machi); WR4(sc, EMAC_ADDR_LOW(0), maclo); /* Multicast hash filters */ WR4(sc, EMAC_RX_HASH_0, hash[1]); WR4(sc, EMAC_RX_HASH_1, hash[0]); /* RX frame filter config */ WR4(sc, EMAC_RX_FRM_FLT, val); } static void awg_enable_intr(struct awg_softc *sc) { /* Enable interrupts */ WR4(sc, EMAC_INT_EN, RX_INT_EN | TX_INT_EN | TX_BUF_UA_INT_EN); } static void awg_disable_intr(struct awg_softc *sc) { /* Disable interrupts */ WR4(sc, EMAC_INT_EN, 0); } static void awg_init_locked(struct awg_softc *sc) { struct mii_data *mii; uint32_t val; if_t ifp; mii = device_get_softc(sc->miibus); ifp = sc->ifp; AWG_ASSERT_LOCKED(sc); if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) return; awg_setup_rxfilter(sc); /* Configure DMA burst length and priorities */ val = awg_burst_len << BASIC_CTL_BURST_LEN_SHIFT; if (awg_rx_tx_pri) val |= BASIC_CTL_RX_TX_PRI; WR4(sc, EMAC_BASIC_CTL_1, val); /* Enable interrupts */ #ifdef DEVICE_POLLING if ((if_getcapenable(ifp) & IFCAP_POLLING) == 0) awg_enable_intr(sc); else awg_disable_intr(sc); #else awg_enable_intr(sc); #endif /* Enable transmit DMA */ val = RD4(sc, EMAC_TX_CTL_1); WR4(sc, EMAC_TX_CTL_1, val | TX_DMA_EN | TX_MD | TX_NEXT_FRAME); /* Enable receive DMA */ val = RD4(sc, EMAC_RX_CTL_1); WR4(sc, EMAC_RX_CTL_1, val | RX_DMA_EN | RX_MD); /* Enable transmitter */ val = RD4(sc, EMAC_TX_CTL_0); WR4(sc, EMAC_TX_CTL_0, val | TX_EN); /* Enable receiver */ val = RD4(sc, EMAC_RX_CTL_0); WR4(sc, EMAC_RX_CTL_0, val | RX_EN | CHECK_CRC); if_setdrvflagbits(ifp, IFF_DRV_RUNNING, IFF_DRV_OACTIVE); mii_mediachg(mii); callout_reset(&sc->stat_ch, hz, awg_tick, sc); } static void awg_init(void *softc) { struct awg_softc *sc; sc = softc; AWG_LOCK(sc); awg_init_locked(sc); AWG_UNLOCK(sc); } static void awg_stop(struct awg_softc *sc) { if_t ifp; uint32_t val; int i; AWG_ASSERT_LOCKED(sc); ifp = sc->ifp; callout_stop(&sc->stat_ch); /* Stop transmit DMA and flush data in the TX FIFO */ val = RD4(sc, EMAC_TX_CTL_1); val &= ~TX_DMA_EN; val |= FLUSH_TX_FIFO; WR4(sc, EMAC_TX_CTL_1, val); /* Disable transmitter */ val = RD4(sc, EMAC_TX_CTL_0); WR4(sc, EMAC_TX_CTL_0, val & ~TX_EN); /* Disable receiver */ val = RD4(sc, EMAC_RX_CTL_0); WR4(sc, EMAC_RX_CTL_0, val & ~RX_EN); /* Disable interrupts */ awg_disable_intr(sc); /* Disable transmit DMA */ val = RD4(sc, EMAC_TX_CTL_1); WR4(sc, EMAC_TX_CTL_1, val & ~TX_DMA_EN); /* Disable receive DMA */ val = RD4(sc, EMAC_RX_CTL_1); WR4(sc, EMAC_RX_CTL_1, val & ~RX_DMA_EN); sc->link = 0; /* Finish handling transmitted buffers */ awg_txeof(sc); /* Release any untransmitted buffers. */ for (i = sc->tx.next; sc->tx.queued > 0; i = TX_NEXT(i)) { val = le32toh(sc->tx.desc_ring[i].status); if ((val & TX_DESC_CTL) != 0) break; awg_clean_txbuf(sc, i); } sc->tx.next = i; for (; sc->tx.queued > 0; i = TX_NEXT(i)) { sc->tx.desc_ring[i].status = 0; awg_clean_txbuf(sc, i); } sc->tx.cur = sc->tx.next; bus_dmamap_sync(sc->tx.desc_tag, sc->tx.desc_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); /* Setup RX buffers for reuse */ bus_dmamap_sync(sc->rx.desc_tag, sc->rx.desc_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); for (i = sc->rx.cur; ; i = RX_NEXT(i)) { val = le32toh(sc->rx.desc_ring[i].status); if ((val & RX_DESC_CTL) != 0) break; awg_reuse_rxdesc(sc, i); } sc->rx.cur = i; bus_dmamap_sync(sc->rx.desc_tag, sc->rx.desc_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); if_setdrvflagbits(ifp, 0, IFF_DRV_RUNNING | IFF_DRV_OACTIVE); } static int awg_rxintr(struct awg_softc *sc) { if_t ifp; struct mbuf *m, *mh, *mt; int error, index, len, cnt, npkt; uint32_t status; ifp = sc->ifp; mh = mt = NULL; cnt = 0; npkt = 0; bus_dmamap_sync(sc->rx.desc_tag, sc->rx.desc_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); for (index = sc->rx.cur; ; index = RX_NEXT(index)) { status = le32toh(sc->rx.desc_ring[index].status); if ((status & RX_DESC_CTL) != 0) break; len = (status & RX_FRM_LEN) >> RX_FRM_LEN_SHIFT; if (len == 0) { if ((status & (RX_NO_ENOUGH_BUF_ERR | RX_OVERFLOW_ERR)) != 0) if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); awg_reuse_rxdesc(sc, index); continue; } m = sc->rx.buf_map[index].mbuf; error = awg_newbuf_rx(sc, index); if (error != 0) { if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1); awg_reuse_rxdesc(sc, index); continue; } m->m_pkthdr.rcvif = ifp; m->m_pkthdr.len = len; m->m_len = len; if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1); if ((if_getcapenable(ifp) & IFCAP_RXCSUM) != 0 && (status & RX_FRM_TYPE) != 0) { m->m_pkthdr.csum_flags = CSUM_IP_CHECKED; if ((status & RX_HEADER_ERR) == 0) m->m_pkthdr.csum_flags |= CSUM_IP_VALID; if ((status & RX_PAYLOAD_ERR) == 0) { m->m_pkthdr.csum_flags |= CSUM_DATA_VALID | CSUM_PSEUDO_HDR; m->m_pkthdr.csum_data = 0xffff; } } m->m_nextpkt = NULL; if (mh == NULL) mh = m; else mt->m_nextpkt = m; mt = m; ++cnt; ++npkt; if (cnt == awg_rx_batch) { AWG_UNLOCK(sc); if_input(ifp, mh); AWG_LOCK(sc); mh = mt = NULL; cnt = 0; } } if (index != sc->rx.cur) { bus_dmamap_sync(sc->rx.desc_tag, sc->rx.desc_map, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); } if (mh != NULL) { AWG_UNLOCK(sc); if_input(ifp, mh); AWG_LOCK(sc); } sc->rx.cur = index; return (npkt); } static void awg_txeof(struct awg_softc *sc) { struct emac_desc *desc; uint32_t status, size; if_t ifp; int i, prog; AWG_ASSERT_LOCKED(sc); bus_dmamap_sync(sc->tx.desc_tag, sc->tx.desc_map, BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); ifp = sc->ifp; prog = 0; for (i = sc->tx.next; sc->tx.queued > 0; i = TX_NEXT(i)) { desc = &sc->tx.desc_ring[i]; status = le32toh(desc->status); if ((status & TX_DESC_CTL) != 0) break; size = le32toh(desc->size); if (size & TX_LAST_DESC) { if ((status & (TX_HEADER_ERR | TX_PAYLOAD_ERR)) != 0) if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); else if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); } prog++; awg_clean_txbuf(sc, i); } if (prog > 0) { sc->tx.next = i; if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE); } } static void awg_intr(void *arg) { struct awg_softc *sc; uint32_t val; sc = arg; AWG_LOCK(sc); val = RD4(sc, EMAC_INT_STA); WR4(sc, EMAC_INT_STA, val); if (val & RX_INT) awg_rxintr(sc); if (val & TX_INT) awg_txeof(sc); if (val & (TX_INT | TX_BUF_UA_INT)) { if (!if_sendq_empty(sc->ifp)) awg_start_locked(sc); } AWG_UNLOCK(sc); } #ifdef DEVICE_POLLING static int awg_poll(if_t ifp, enum poll_cmd cmd, int count) { struct awg_softc *sc; uint32_t val; int rx_npkts; sc = if_getsoftc(ifp); rx_npkts = 0; AWG_LOCK(sc); if ((if_getdrvflags(ifp) & IFF_DRV_RUNNING) == 0) { AWG_UNLOCK(sc); return (0); } rx_npkts = awg_rxintr(sc); awg_txeof(sc); if (!if_sendq_empty(ifp)) awg_start_locked(sc); if (cmd == POLL_AND_CHECK_STATUS) { val = RD4(sc, EMAC_INT_STA); if (val != 0) WR4(sc, EMAC_INT_STA, val); } AWG_UNLOCK(sc); return (rx_npkts); } #endif static int awg_ioctl(if_t ifp, u_long cmd, caddr_t data) { struct awg_softc *sc; struct mii_data *mii; struct ifreq *ifr; int flags, mask, error; sc = if_getsoftc(ifp); mii = device_get_softc(sc->miibus); ifr = (struct ifreq *)data; error = 0; switch (cmd) { case SIOCSIFFLAGS: AWG_LOCK(sc); if (if_getflags(ifp) & IFF_UP) { if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) { flags = if_getflags(ifp) ^ sc->if_flags; if ((flags & (IFF_PROMISC|IFF_ALLMULTI)) != 0) awg_setup_rxfilter(sc); } else awg_init_locked(sc); } else { if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) awg_stop(sc); } sc->if_flags = if_getflags(ifp); AWG_UNLOCK(sc); break; case SIOCADDMULTI: case SIOCDELMULTI: if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) { AWG_LOCK(sc); awg_setup_rxfilter(sc); AWG_UNLOCK(sc); } break; case SIOCSIFMEDIA: case SIOCGIFMEDIA: error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, cmd); break; case SIOCSIFCAP: mask = ifr->ifr_reqcap ^ if_getcapenable(ifp); #ifdef DEVICE_POLLING if (mask & IFCAP_POLLING) { if ((ifr->ifr_reqcap & IFCAP_POLLING) != 0) { error = ether_poll_register(awg_poll, ifp); if (error != 0) break; AWG_LOCK(sc); awg_disable_intr(sc); if_setcapenablebit(ifp, IFCAP_POLLING, 0); AWG_UNLOCK(sc); } else { error = ether_poll_deregister(ifp); AWG_LOCK(sc); awg_enable_intr(sc); if_setcapenablebit(ifp, 0, IFCAP_POLLING); AWG_UNLOCK(sc); } } #endif if (mask & IFCAP_VLAN_MTU) if_togglecapenable(ifp, IFCAP_VLAN_MTU); if (mask & IFCAP_RXCSUM) if_togglecapenable(ifp, IFCAP_RXCSUM); if (mask & IFCAP_TXCSUM) if_togglecapenable(ifp, IFCAP_TXCSUM); if ((if_getcapenable(ifp) & IFCAP_TXCSUM) != 0) if_sethwassistbits(ifp, CSUM_IP | CSUM_UDP | CSUM_TCP, 0); else if_sethwassistbits(ifp, 0, CSUM_IP | CSUM_UDP | CSUM_TCP); break; default: error = ether_ioctl(ifp, cmd, data); break; } return (error); } static uint32_t syscon_read_emac_clk_reg(device_t dev) { struct awg_softc *sc; sc = device_get_softc(dev); if (sc->syscon != NULL) return (SYSCON_READ_4(sc->syscon, EMAC_CLK_REG)); else if (sc->res[_RES_SYSCON] != NULL) return (bus_read_4(sc->res[_RES_SYSCON], 0)); return (0); } static void syscon_write_emac_clk_reg(device_t dev, uint32_t val) { struct awg_softc *sc; sc = device_get_softc(dev); if (sc->syscon != NULL) SYSCON_WRITE_4(sc->syscon, EMAC_CLK_REG, val); else if (sc->res[_RES_SYSCON] != NULL) bus_write_4(sc->res[_RES_SYSCON], 0, val); } static phandle_t awg_get_phy_node(device_t dev) { phandle_t node; pcell_t phy_handle; node = ofw_bus_get_node(dev); if (OF_getencprop(node, "phy-handle", (void *)&phy_handle, sizeof(phy_handle)) <= 0) return (0); return (OF_node_from_xref(phy_handle)); } static bool awg_has_internal_phy(device_t dev) { phandle_t node, phy_node; node = ofw_bus_get_node(dev); /* Legacy binding */ if (OF_hasprop(node, "allwinner,use-internal-phy")) return (true); phy_node = awg_get_phy_node(dev); return (phy_node != 0 && ofw_bus_node_is_compatible(OF_parent(phy_node), "allwinner,sun8i-h3-mdio-internal") != 0); } static int awg_parse_delay(device_t dev, uint32_t *tx_delay, uint32_t *rx_delay) { phandle_t node; uint32_t delay; if (tx_delay == NULL || rx_delay == NULL) return (EINVAL); *tx_delay = *rx_delay = 0; node = ofw_bus_get_node(dev); if (OF_getencprop(node, "tx-delay", &delay, sizeof(delay)) >= 0) *tx_delay = delay; else if (OF_getencprop(node, "allwinner,tx-delay-ps", &delay, sizeof(delay)) >= 0) { if ((delay % 100) != 0) { device_printf(dev, "tx-delay-ps is not a multiple of 100\n"); return (EDOM); } *tx_delay = delay / 100; } if (*tx_delay > 7) { device_printf(dev, "tx-delay out of range\n"); return (ERANGE); } if (OF_getencprop(node, "rx-delay", &delay, sizeof(delay)) >= 0) *rx_delay = delay; else if (OF_getencprop(node, "allwinner,rx-delay-ps", &delay, sizeof(delay)) >= 0) { if ((delay % 100) != 0) { device_printf(dev, "rx-delay-ps is not within documented domain\n"); return (EDOM); } *rx_delay = delay / 100; } if (*rx_delay > 31) { device_printf(dev, "rx-delay out of range\n"); return (ERANGE); } return (0); } static int awg_setup_phy(device_t dev) { struct awg_softc *sc; clk_t clk_tx, clk_tx_parent; const char *tx_parent_name; char *phy_type; phandle_t node; uint32_t reg, tx_delay, rx_delay; int error; bool use_syscon; sc = device_get_softc(dev); node = ofw_bus_get_node(dev); use_syscon = false; if (OF_getprop_alloc(node, "phy-mode", (void **)&phy_type) == 0) return (0); if (sc->syscon != NULL || sc->res[_RES_SYSCON] != NULL) use_syscon = true; if (bootverbose) device_printf(dev, "PHY type: %s, conf mode: %s\n", phy_type, use_syscon ? "reg" : "clk"); if (use_syscon) { /* * Abstract away writing to syscon for devices like the pine64. * For the pine64, we get dtb from U-Boot and it still uses the * legacy setup of specifying syscon register in emac node * rather than as its own node and using an xref in emac. * These abstractions can go away once U-Boot dts is up-to-date. */ reg = syscon_read_emac_clk_reg(dev); reg &= ~(EMAC_CLK_PIT | EMAC_CLK_SRC | EMAC_CLK_RMII_EN); if (strncmp(phy_type, "rgmii", 5) == 0) reg |= EMAC_CLK_PIT_RGMII | EMAC_CLK_SRC_RGMII; else if (strcmp(phy_type, "rmii") == 0) reg |= EMAC_CLK_RMII_EN; else reg |= EMAC_CLK_PIT_MII | EMAC_CLK_SRC_MII; /* * Fail attach if we fail to parse either of the delay * parameters. If we don't have the proper delay to write to * syscon, then awg likely won't function properly anyways. * Lack of delay is not an error! */ error = awg_parse_delay(dev, &tx_delay, &rx_delay); if (error != 0) goto fail; /* Default to 0 and we'll increase it if we need to. */ reg &= ~(EMAC_CLK_ETXDC | EMAC_CLK_ERXDC); if (tx_delay > 0) reg |= (tx_delay << EMAC_CLK_ETXDC_SHIFT); if (rx_delay > 0) reg |= (rx_delay << EMAC_CLK_ERXDC_SHIFT); if (sc->type == EMAC_H3) { if (awg_has_internal_phy(dev)) { reg |= EMAC_CLK_EPHY_SELECT; reg &= ~EMAC_CLK_EPHY_SHUTDOWN; if (OF_hasprop(node, "allwinner,leds-active-low")) reg |= EMAC_CLK_EPHY_LED_POL; else reg &= ~EMAC_CLK_EPHY_LED_POL; /* Set internal PHY addr to 1 */ reg &= ~EMAC_CLK_EPHY_ADDR; reg |= (1 << EMAC_CLK_EPHY_ADDR_SHIFT); } else { reg &= ~EMAC_CLK_EPHY_SELECT; } } if (bootverbose) device_printf(dev, "EMAC clock: 0x%08x\n", reg); syscon_write_emac_clk_reg(dev, reg); } else { if (strncmp(phy_type, "rgmii", 5) == 0) tx_parent_name = "emac_int_tx"; else tx_parent_name = "mii_phy_tx"; /* Get the TX clock */ error = clk_get_by_ofw_name(dev, 0, "tx", &clk_tx); if (error != 0) { device_printf(dev, "cannot get tx clock\n"); goto fail; } /* Find the desired parent clock based on phy-mode property */ error = clk_get_by_name(dev, tx_parent_name, &clk_tx_parent); if (error != 0) { device_printf(dev, "cannot get clock '%s'\n", tx_parent_name); goto fail; } /* Set TX clock parent */ error = clk_set_parent_by_clk(clk_tx, clk_tx_parent); if (error != 0) { device_printf(dev, "cannot set tx clock parent\n"); goto fail; } /* Enable TX clock */ error = clk_enable(clk_tx); if (error != 0) { device_printf(dev, "cannot enable tx clock\n"); goto fail; } } error = 0; fail: OF_prop_free(phy_type); return (error); } static int awg_setup_extres(device_t dev) { struct awg_softc *sc; phandle_t node, phy_node; hwreset_t rst_ahb, rst_ephy; clk_t clk_ahb, clk_ephy; regulator_t reg; uint64_t freq; int error, div; sc = device_get_softc(dev); rst_ahb = rst_ephy = NULL; clk_ahb = clk_ephy = NULL; reg = NULL; node = ofw_bus_get_node(dev); phy_node = awg_get_phy_node(dev); if (phy_node == 0 && OF_hasprop(node, "phy-handle")) { error = ENXIO; device_printf(dev, "cannot get phy handle\n"); goto fail; } /* Get AHB clock and reset resources */ error = hwreset_get_by_ofw_name(dev, 0, "stmmaceth", &rst_ahb); if (error != 0) error = hwreset_get_by_ofw_name(dev, 0, "ahb", &rst_ahb); if (error != 0) { device_printf(dev, "cannot get ahb reset\n"); goto fail; } if (hwreset_get_by_ofw_name(dev, 0, "ephy", &rst_ephy) != 0) if (phy_node == 0 || hwreset_get_by_ofw_idx(dev, phy_node, 0, &rst_ephy) != 0) rst_ephy = NULL; error = clk_get_by_ofw_name(dev, 0, "stmmaceth", &clk_ahb); if (error != 0) error = clk_get_by_ofw_name(dev, 0, "ahb", &clk_ahb); if (error != 0) { device_printf(dev, "cannot get ahb clock\n"); goto fail; } if (clk_get_by_ofw_name(dev, 0, "ephy", &clk_ephy) != 0) if (phy_node == 0 || clk_get_by_ofw_index(dev, phy_node, 0, &clk_ephy) != 0) clk_ephy = NULL; if (OF_hasprop(node, "syscon") && syscon_get_by_ofw_property(dev, node, "syscon", &sc->syscon) != 0) { device_printf(dev, "cannot get syscon driver handle\n"); goto fail; } /* Configure PHY for MII or RGMII mode */ if (awg_setup_phy(dev) != 0) goto fail; /* Enable clocks */ error = clk_enable(clk_ahb); if (error != 0) { device_printf(dev, "cannot enable ahb clock\n"); goto fail; } if (clk_ephy != NULL) { error = clk_enable(clk_ephy); if (error != 0) { device_printf(dev, "cannot enable ephy clock\n"); goto fail; } } /* De-assert reset */ error = hwreset_deassert(rst_ahb); if (error != 0) { device_printf(dev, "cannot de-assert ahb reset\n"); goto fail; } if (rst_ephy != NULL) { /* * The ephy reset is left de-asserted by U-Boot. Assert it * here to make sure that we're in a known good state going * into the PHY reset. */ hwreset_assert(rst_ephy); error = hwreset_deassert(rst_ephy); if (error != 0) { device_printf(dev, "cannot de-assert ephy reset\n"); goto fail; } } /* Enable PHY regulator if applicable */ if (regulator_get_by_ofw_property(dev, 0, "phy-supply", ®) == 0) { error = regulator_enable(reg); if (error != 0) { device_printf(dev, "cannot enable PHY regulator\n"); goto fail; } } /* Determine MDC clock divide ratio based on AHB clock */ error = clk_get_freq(clk_ahb, &freq); if (error != 0) { device_printf(dev, "cannot get AHB clock frequency\n"); goto fail; } div = freq / MDIO_FREQ; if (div <= 16) sc->mdc_div_ratio_m = MDC_DIV_RATIO_M_16; else if (div <= 32) sc->mdc_div_ratio_m = MDC_DIV_RATIO_M_32; else if (div <= 64) sc->mdc_div_ratio_m = MDC_DIV_RATIO_M_64; else if (div <= 128) sc->mdc_div_ratio_m = MDC_DIV_RATIO_M_128; else { device_printf(dev, "cannot determine MDC clock divide ratio\n"); error = ENXIO; goto fail; } if (bootverbose) device_printf(dev, "AHB frequency %ju Hz, MDC div: 0x%x\n", (uintmax_t)freq, sc->mdc_div_ratio_m); return (0); fail: if (reg != NULL) regulator_release(reg); if (clk_ephy != NULL) clk_release(clk_ephy); if (clk_ahb != NULL) clk_release(clk_ahb); if (rst_ephy != NULL) hwreset_release(rst_ephy); if (rst_ahb != NULL) hwreset_release(rst_ahb); return (error); } static void awg_get_eaddr(device_t dev, uint8_t *eaddr) { struct awg_softc *sc; uint32_t maclo, machi, rnd; u_char rootkey[16]; uint32_t rootkey_size; sc = device_get_softc(dev); machi = RD4(sc, EMAC_ADDR_HIGH(0)) & 0xffff; maclo = RD4(sc, EMAC_ADDR_LOW(0)); rootkey_size = sizeof(rootkey); if (maclo == 0xffffffff && machi == 0xffff) { /* MAC address in hardware is invalid, create one */ if (aw_sid_get_fuse(AW_SID_FUSE_ROOTKEY, rootkey, &rootkey_size) == 0 && (rootkey[3] | rootkey[12] | rootkey[13] | rootkey[14] | rootkey[15]) != 0) { /* MAC address is derived from the root key in SID */ maclo = (rootkey[13] << 24) | (rootkey[12] << 16) | (rootkey[3] << 8) | 0x02; machi = (rootkey[15] << 8) | rootkey[14]; } else { /* Create one */ rnd = arc4random(); maclo = 0x00f2 | (rnd & 0xffff0000); machi = rnd & 0xffff; } } eaddr[0] = maclo & 0xff; eaddr[1] = (maclo >> 8) & 0xff; eaddr[2] = (maclo >> 16) & 0xff; eaddr[3] = (maclo >> 24) & 0xff; eaddr[4] = machi & 0xff; eaddr[5] = (machi >> 8) & 0xff; } #ifdef AWG_DEBUG static void awg_dump_regs(device_t dev) { static const struct { const char *name; u_int reg; } regs[] = { { "BASIC_CTL_0", EMAC_BASIC_CTL_0 }, { "BASIC_CTL_1", EMAC_BASIC_CTL_1 }, { "INT_STA", EMAC_INT_STA }, { "INT_EN", EMAC_INT_EN }, { "TX_CTL_0", EMAC_TX_CTL_0 }, { "TX_CTL_1", EMAC_TX_CTL_1 }, { "TX_FLOW_CTL", EMAC_TX_FLOW_CTL }, { "TX_DMA_LIST", EMAC_TX_DMA_LIST }, { "RX_CTL_0", EMAC_RX_CTL_0 }, { "RX_CTL_1", EMAC_RX_CTL_1 }, { "RX_DMA_LIST", EMAC_RX_DMA_LIST }, { "RX_FRM_FLT", EMAC_RX_FRM_FLT }, { "RX_HASH_0", EMAC_RX_HASH_0 }, { "RX_HASH_1", EMAC_RX_HASH_1 }, { "MII_CMD", EMAC_MII_CMD }, { "ADDR_HIGH0", EMAC_ADDR_HIGH(0) }, { "ADDR_LOW0", EMAC_ADDR_LOW(0) }, { "TX_DMA_STA", EMAC_TX_DMA_STA }, { "TX_DMA_CUR_DESC", EMAC_TX_DMA_CUR_DESC }, { "TX_DMA_CUR_BUF", EMAC_TX_DMA_CUR_BUF }, { "RX_DMA_STA", EMAC_RX_DMA_STA }, { "RX_DMA_CUR_DESC", EMAC_RX_DMA_CUR_DESC }, { "RX_DMA_CUR_BUF", EMAC_RX_DMA_CUR_BUF }, { "RGMII_STA", EMAC_RGMII_STA }, }; struct awg_softc *sc; unsigned int n; sc = device_get_softc(dev); for (n = 0; n < nitems(regs); n++) device_printf(dev, " %-20s %08x\n", regs[n].name, RD4(sc, regs[n].reg)); } #endif #define GPIO_ACTIVE_LOW 1 static int awg_phy_reset(device_t dev) { pcell_t gpio_prop[4], delay_prop[3]; phandle_t node, gpio_node; device_t gpio; uint32_t pin, flags; uint32_t pin_value; node = ofw_bus_get_node(dev); if (OF_getencprop(node, "allwinner,reset-gpio", gpio_prop, sizeof(gpio_prop)) <= 0) return (0); if (OF_getencprop(node, "allwinner,reset-delays-us", delay_prop, sizeof(delay_prop)) <= 0) return (ENXIO); gpio_node = OF_node_from_xref(gpio_prop[0]); if ((gpio = OF_device_from_xref(gpio_prop[0])) == NULL) return (ENXIO); if (GPIO_MAP_GPIOS(gpio, node, gpio_node, nitems(gpio_prop) - 1, gpio_prop + 1, &pin, &flags) != 0) return (ENXIO); pin_value = GPIO_PIN_LOW; if (OF_hasprop(node, "allwinner,reset-active-low")) pin_value = GPIO_PIN_HIGH; if (flags & GPIO_ACTIVE_LOW) pin_value = !pin_value; GPIO_PIN_SETFLAGS(gpio, pin, GPIO_PIN_OUTPUT); GPIO_PIN_SET(gpio, pin, pin_value); DELAY(delay_prop[0]); GPIO_PIN_SET(gpio, pin, !pin_value); DELAY(delay_prop[1]); GPIO_PIN_SET(gpio, pin, pin_value); DELAY(delay_prop[2]); return (0); } static int awg_reset(device_t dev) { struct awg_softc *sc; int retry; sc = device_get_softc(dev); /* Reset PHY if necessary */ if (awg_phy_reset(dev) != 0) { device_printf(dev, "failed to reset PHY\n"); return (ENXIO); } /* Soft reset all registers and logic */ WR4(sc, EMAC_BASIC_CTL_1, BASIC_CTL_SOFT_RST); /* Wait for soft reset bit to self-clear */ for (retry = SOFT_RST_RETRY; retry > 0; retry--) { if ((RD4(sc, EMAC_BASIC_CTL_1) & BASIC_CTL_SOFT_RST) == 0) break; DELAY(10); } if (retry == 0) { device_printf(dev, "soft reset timed out\n"); #ifdef AWG_DEBUG awg_dump_regs(dev); #endif return (ETIMEDOUT); } return (0); } static void awg_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error) { if (error != 0) return; *(bus_addr_t *)arg = segs[0].ds_addr; } static int awg_setup_dma(device_t dev) { struct awg_softc *sc; int error, i; sc = device_get_softc(dev); /* Setup TX ring */ error = bus_dma_tag_create( bus_get_dma_tag(dev), /* Parent tag */ DESC_ALIGN, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ TX_DESC_SIZE, 1, /* maxsize, nsegs */ TX_DESC_SIZE, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->tx.desc_tag); if (error != 0) { device_printf(dev, "cannot create TX descriptor ring tag\n"); return (error); } error = bus_dmamem_alloc(sc->tx.desc_tag, (void **)&sc->tx.desc_ring, BUS_DMA_COHERENT | BUS_DMA_WAITOK | BUS_DMA_ZERO, &sc->tx.desc_map); if (error != 0) { device_printf(dev, "cannot allocate TX descriptor ring\n"); return (error); } error = bus_dmamap_load(sc->tx.desc_tag, sc->tx.desc_map, sc->tx.desc_ring, TX_DESC_SIZE, awg_dmamap_cb, &sc->tx.desc_ring_paddr, 0); if (error != 0) { device_printf(dev, "cannot load TX descriptor ring\n"); return (error); } for (i = 0; i < TX_DESC_COUNT; i++) sc->tx.desc_ring[i].next = htole32(sc->tx.desc_ring_paddr + DESC_OFF(TX_NEXT(i))); error = bus_dma_tag_create( bus_get_dma_tag(dev), /* Parent tag */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MCLBYTES, TX_MAX_SEGS, /* maxsize, nsegs */ MCLBYTES, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->tx.buf_tag); if (error != 0) { device_printf(dev, "cannot create TX buffer tag\n"); return (error); } sc->tx.queued = 0; for (i = 0; i < TX_DESC_COUNT; i++) { error = bus_dmamap_create(sc->tx.buf_tag, 0, &sc->tx.buf_map[i].map); if (error != 0) { device_printf(dev, "cannot create TX buffer map\n"); return (error); } } /* Setup RX ring */ error = bus_dma_tag_create( bus_get_dma_tag(dev), /* Parent tag */ DESC_ALIGN, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ RX_DESC_SIZE, 1, /* maxsize, nsegs */ RX_DESC_SIZE, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->rx.desc_tag); if (error != 0) { device_printf(dev, "cannot create RX descriptor ring tag\n"); return (error); } error = bus_dmamem_alloc(sc->rx.desc_tag, (void **)&sc->rx.desc_ring, BUS_DMA_COHERENT | BUS_DMA_WAITOK | BUS_DMA_ZERO, &sc->rx.desc_map); if (error != 0) { device_printf(dev, "cannot allocate RX descriptor ring\n"); return (error); } error = bus_dmamap_load(sc->rx.desc_tag, sc->rx.desc_map, sc->rx.desc_ring, RX_DESC_SIZE, awg_dmamap_cb, &sc->rx.desc_ring_paddr, 0); if (error != 0) { device_printf(dev, "cannot load RX descriptor ring\n"); return (error); } error = bus_dma_tag_create( bus_get_dma_tag(dev), /* Parent tag */ 1, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ MCLBYTES, 1, /* maxsize, nsegs */ MCLBYTES, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->rx.buf_tag); if (error != 0) { device_printf(dev, "cannot create RX buffer tag\n"); return (error); } error = bus_dmamap_create(sc->rx.buf_tag, 0, &sc->rx.buf_spare_map); if (error != 0) { device_printf(dev, "cannot create RX buffer spare map\n"); return (error); } for (i = 0; i < RX_DESC_COUNT; i++) { sc->rx.desc_ring[i].next = htole32(sc->rx.desc_ring_paddr + DESC_OFF(RX_NEXT(i))); error = bus_dmamap_create(sc->rx.buf_tag, 0, &sc->rx.buf_map[i].map); if (error != 0) { device_printf(dev, "cannot create RX buffer map\n"); return (error); } sc->rx.buf_map[i].mbuf = NULL; error = awg_newbuf_rx(sc, i); if (error != 0) { device_printf(dev, "cannot create RX buffer\n"); return (error); } } bus_dmamap_sync(sc->rx.desc_tag, sc->rx.desc_map, BUS_DMASYNC_PREWRITE); /* Write transmit and receive descriptor base address registers */ WR4(sc, EMAC_TX_DMA_LIST, sc->tx.desc_ring_paddr); WR4(sc, EMAC_RX_DMA_LIST, sc->rx.desc_ring_paddr); return (0); } static int awg_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 Gigabit Ethernet"); return (BUS_PROBE_DEFAULT); } static int awg_attach(device_t dev) { uint8_t eaddr[ETHER_ADDR_LEN]; struct awg_softc *sc; int error; sc = device_get_softc(dev); sc->dev = dev; sc->type = ofw_bus_search_compatible(dev, compat_data)->ocd_data; if (bus_alloc_resources(dev, awg_spec, sc->res) != 0) { device_printf(dev, "cannot allocate resources for device\n"); return (ENXIO); } mtx_init(&sc->mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK, MTX_DEF); callout_init_mtx(&sc->stat_ch, &sc->mtx, 0); TASK_INIT(&sc->link_task, 0, awg_link_task, sc); /* Setup clocks and regulators */ error = awg_setup_extres(dev); if (error != 0) return (error); /* Read MAC address before resetting the chip */ awg_get_eaddr(dev, eaddr); /* Soft reset EMAC core */ error = awg_reset(dev); if (error != 0) return (error); /* Setup DMA descriptors */ error = awg_setup_dma(dev); if (error != 0) return (error); /* Install interrupt handler */ error = bus_setup_intr(dev, sc->res[_RES_IRQ], INTR_TYPE_NET | INTR_MPSAFE, NULL, awg_intr, sc, &sc->ih); if (error != 0) { device_printf(dev, "cannot setup interrupt handler\n"); return (error); } /* Setup ethernet interface */ sc->ifp = if_alloc(IFT_ETHER); if_setsoftc(sc->ifp, sc); if_initname(sc->ifp, device_get_name(dev), device_get_unit(dev)); if_setflags(sc->ifp, IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST); if_setstartfn(sc->ifp, awg_start); if_setioctlfn(sc->ifp, awg_ioctl); if_setinitfn(sc->ifp, awg_init); if_setsendqlen(sc->ifp, TX_DESC_COUNT - 1); if_setsendqready(sc->ifp); if_sethwassist(sc->ifp, CSUM_IP | CSUM_UDP | CSUM_TCP); if_setcapabilities(sc->ifp, IFCAP_VLAN_MTU | IFCAP_HWCSUM); if_setcapenable(sc->ifp, if_getcapabilities(sc->ifp)); #ifdef DEVICE_POLLING if_setcapabilitiesbit(sc->ifp, IFCAP_POLLING, 0); #endif /* Attach MII driver */ error = mii_attach(dev, &sc->miibus, sc->ifp, awg_media_change, awg_media_status, BMSR_DEFCAPMASK, MII_PHY_ANY, MII_OFFSET_ANY, MIIF_DOPAUSE); if (error != 0) { device_printf(dev, "cannot attach PHY\n"); return (error); } /* Attach ethernet interface */ ether_ifattach(sc->ifp, eaddr); return (0); } static device_method_t awg_methods[] = { /* Device interface */ DEVMETHOD(device_probe, awg_probe), DEVMETHOD(device_attach, awg_attach), /* MII interface */ DEVMETHOD(miibus_readreg, awg_miibus_readreg), DEVMETHOD(miibus_writereg, awg_miibus_writereg), DEVMETHOD(miibus_statchg, awg_miibus_statchg), DEVMETHOD_END }; static driver_t awg_driver = { "awg", awg_methods, sizeof(struct awg_softc), }; static devclass_t awg_devclass; DRIVER_MODULE(awg, simplebus, awg_driver, awg_devclass, 0, 0); DRIVER_MODULE(miibus, awg, miibus_driver, miibus_devclass, 0, 0); MODULE_DEPEND(awg, ether, 1, 1, 1); MODULE_DEPEND(awg, miibus, 1, 1, 1); MODULE_DEPEND(awg, aw_sid, 1, 1, 1); SIMPLEBUS_PNP_INFO(compat_data); Index: head/sys/arm/allwinner/if_awgreg.h =================================================================== --- head/sys/arm/allwinner/if_awgreg.h (revision 355357) +++ head/sys/arm/allwinner/if_awgreg.h (revision 355358) @@ -1,182 +1,181 @@ /*- * 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$ */ /* * Allwinner Gigabit Ethernet */ #ifndef __IF_AWGREG_H__ #define __IF_AWGREG_H__ #define EMAC_BASIC_CTL_0 0x00 #define BASIC_CTL_SPEED (0x3 << 2) #define BASIC_CTL_SPEED_SHIFT 2 #define BASIC_CTL_SPEED_1000 0 #define BASIC_CTL_SPEED_10 2 #define BASIC_CTL_SPEED_100 3 #define BASIC_CTL_LOOPBACK (1 << 1) #define BASIC_CTL_DUPLEX (1 << 0) #define EMAC_BASIC_CTL_1 0x04 #define BASIC_CTL_BURST_LEN (0x3f << 24) #define BASIC_CTL_BURST_LEN_SHIFT 24 #define BASIC_CTL_RX_TX_PRI (1 << 1) #define BASIC_CTL_SOFT_RST (1 << 0) #define EMAC_INT_STA 0x08 #define RX_BUF_UA_INT (1 << 10) #define RX_INT (1 << 8) #define TX_UNDERFLOW_INT (1 << 4) #define TX_BUF_UA_INT (1 << 2) #define TX_DMA_STOPPED_INT (1 << 1) #define TX_INT (1 << 0) #define EMAC_INT_EN 0x0c #define RX_BUF_UA_INT_EN (1 << 10) #define RX_INT_EN (1 << 8) #define TX_UNDERFLOW_INT_EN (1 << 4) #define TX_BUF_UA_INT_EN (1 << 2) #define TX_DMA_STOPPED_INT_EN (1 << 1) #define TX_INT_EN (1 << 0) #define EMAC_TX_CTL_0 0x10 #define TX_EN (1 << 31) #define EMAC_TX_CTL_1 0x14 #define TX_DMA_START (1 << 31) #define TX_DMA_EN (1 << 30) #define TX_NEXT_FRAME (1 << 2) #define TX_MD (1 << 1) #define FLUSH_TX_FIFO (1 << 0) #define EMAC_TX_FLOW_CTL 0x1c #define PAUSE_TIME (0xffff << 4) #define PAUSE_TIME_SHIFT 4 #define TX_FLOW_CTL_EN (1 << 0) #define EMAC_TX_DMA_LIST 0x20 #define EMAC_RX_CTL_0 0x24 #define RX_EN (1 << 31) #define JUMBO_FRM_EN (1 << 29) #define STRIP_FCS (1 << 28) #define CHECK_CRC (1 << 27) #define RX_FLOW_CTL_EN (1 << 16) #define EMAC_RX_CTL_1 0x28 #define RX_DMA_START (1 << 31) #define RX_DMA_EN (1 << 30) #define RX_MD (1 << 1) #define EMAC_RX_DMA_LIST 0x34 #define EMAC_RX_FRM_FLT 0x38 #define DIS_ADDR_FILTER (1 << 31) #define DIS_BROADCAST (1 << 17) #define RX_ALL_MULTICAST (1 << 16) #define CTL_FRM_FILTER (0x3 << 12) #define CTL_FRM_FILTER_SHIFT 12 #define HASH_MULTICAST (1 << 9) #define HASH_UNICAST (1 << 8) #define SA_FILTER_EN (1 << 6) #define SA_INV_FILTER (1 << 5) #define DA_INV_FILTER (1 << 4) #define FLT_MD (1 << 1) #define RX_ALL (1 << 0) #define EMAC_RX_HASH_0 0x40 #define EMAC_RX_HASH_1 0x44 #define EMAC_MII_CMD 0x48 #define MDC_DIV_RATIO_M (0x7 << 20) #define MDC_DIV_RATIO_M_16 0 #define MDC_DIV_RATIO_M_32 1 #define MDC_DIV_RATIO_M_64 2 #define MDC_DIV_RATIO_M_128 3 #define MDC_DIV_RATIO_M_SHIFT 20 #define PHY_ADDR (0x1f << 12) #define PHY_ADDR_SHIFT 12 #define PHY_REG_ADDR (0x1f << 4) #define PHY_REG_ADDR_SHIFT 4 #define MII_WR (1 << 1) #define MII_BUSY (1 << 0) #define EMAC_MII_DATA 0x4c #define EMAC_ADDR_HIGH(n) (0x50 + (n) * 8) #define EMAC_ADDR_LOW(n) (0x54 + (n) * 8) #define EMAC_TX_DMA_STA 0xb0 #define EMAC_TX_DMA_CUR_DESC 0xb4 #define EMAC_TX_DMA_CUR_BUF 0xb8 #define EMAC_RX_DMA_STA 0xc0 #define EMAC_RX_DMA_CUR_DESC 0xc4 #define EMAC_RX_DMA_CUR_BUF 0xc8 #define EMAC_RGMII_STA 0xd0 struct emac_desc { uint32_t status; /* Transmit */ #define TX_DESC_CTL (1 << 31) #define TX_HEADER_ERR (1 << 16) #define TX_LENGTH_ERR (1 << 14) #define TX_PAYLOAD_ERR (1 << 12) #define TX_CRS_ERR (1 << 10) #define TX_COL_ERR_0 (1 << 9) #define TX_COL_ERR_1 (1 << 8) #define TX_COL_CNT (0xf << 3) #define TX_COL_CNT_SHIFT 3 #define TX_DEFER_ERR (1 << 2) #define TX_UNDERFLOW_ERR (1 << 1) #define TX_DEFER (1 << 0) /* Receive */ #define RX_DESC_CTL (1 << 31) #define RX_DAF_FAIL (1 << 30) #define RX_FRM_LEN (0x3fff << 16) #define RX_FRM_LEN_SHIFT 16 #define RX_NO_ENOUGH_BUF_ERR (1 << 14) #define RX_SAF_FAIL (1 << 13) #define RX_OVERFLOW_ERR (1 << 11) #define RX_FIR_DESC (1 << 9) #define RX_LAST_DESC (1 << 8) #define RX_HEADER_ERR (1 << 7) #define RX_COL_ERR (1 << 6) #define RX_FRM_TYPE (1 << 5) #define RX_LENGTH_ERR (1 << 4) #define RX_PHY_ERR (1 << 3) #define RX_CRC_ERR (1 << 1) #define RX_PAYLOAD_ERR (1 << 0) uint32_t size; /* Transmit */ #define TX_INT_CTL (1 << 31) #define TX_LAST_DESC (1 << 30) #define TX_FIR_DESC (1 << 29) #define TX_CHECKSUM_CTL (0x3 << 27) #define TX_CHECKSUM_CTL_IP 1 #define TX_CHECKSUM_CTL_NO_PSE 2 #define TX_CHECKSUM_CTL_FULL 3 #define TX_CHECKSUM_CTL_SHIFT 27 #define TX_CRC_CTL (1 << 26) #define TX_BUF_SIZE (0xfff << 0) #define TX_BUF_SIZE_SHIFT 0 /* Receive */ #define RX_INT_CTL (1 << 31) #define RX_BUF_SIZE (0xfff << 0) #define RX_BUF_SIZE_SHIFT 0 uint32_t addr; uint32_t next; } __packed; #endif /* !__IF_AWGREG_H__ */ Index: head/sys/dev/gpio/gpioregulator.c =================================================================== --- head/sys/dev/gpio/gpioregulator.c (revision 355357) +++ head/sys/dev/gpio/gpioregulator.c (revision 355358) @@ -1,348 +1,347 @@ /*- * 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$ */ /* * GPIO controlled regulators */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include "regdev_if.h" struct gpioregulator_state { int val; uint32_t mask; }; struct gpioregulator_init_def { struct regnode_init_def reg_init_def; struct gpiobus_pin *enable_pin; int enable_pin_valid; int startup_delay_us; int nstates; struct gpioregulator_state *states; int npins; struct gpiobus_pin **pins; }; struct gpioregulator_reg_sc { struct regnode *regnode; device_t base_dev; struct regnode_std_param *param; struct gpioregulator_init_def *def; }; struct gpioregulator_softc { device_t dev; struct gpioregulator_reg_sc *reg_sc; struct gpioregulator_init_def init_def; }; static int gpioregulator_regnode_init(struct regnode *regnode) { struct gpioregulator_reg_sc *sc; int error, n; sc = regnode_get_softc(regnode); if (sc->def->enable_pin_valid == 1) { error = gpio_pin_setflags(sc->def->enable_pin, GPIO_PIN_OUTPUT); if (error != 0) return (error); } for (n = 0; n < sc->def->npins; n++) { error = gpio_pin_setflags(sc->def->pins[n], GPIO_PIN_OUTPUT); if (error != 0) return (error); } return (0); } static int gpioregulator_regnode_enable(struct regnode *regnode, bool enable, int *udelay) { struct gpioregulator_reg_sc *sc; bool active; int error; sc = regnode_get_softc(regnode); if (sc->def->enable_pin_valid == 1) { active = enable; if (!sc->param->enable_active_high) active = !active; error = gpio_pin_set_active(sc->def->enable_pin, active); if (error != 0) return (error); } *udelay = sc->def->startup_delay_us; return (0); } static int gpioregulator_regnode_set_voltage(struct regnode *regnode, int min_uvolt, int max_uvolt, int *udelay) { struct gpioregulator_reg_sc *sc; const struct gpioregulator_state *state; int error, n; sc = regnode_get_softc(regnode); state = NULL; for (n = 0; n < sc->def->nstates; n++) { if (sc->def->states[n].val >= min_uvolt && sc->def->states[n].val <= max_uvolt) { state = &sc->def->states[n]; break; } } if (state == NULL) return (EINVAL); for (n = 0; n < sc->def->npins; n++) { error = gpio_pin_set_active(sc->def->pins[n], (state->mask >> n) & 1); if (error != 0) return (error); } *udelay = sc->def->startup_delay_us; return (0); } static int gpioregulator_regnode_get_voltage(struct regnode *regnode, int *uvolt) { struct gpioregulator_reg_sc *sc; uint32_t mask; int error, n; bool active; sc = regnode_get_softc(regnode); mask = 0; for (n = 0; n < sc->def->npins; n++) { error = gpio_pin_is_active(sc->def->pins[n], &active); if (error != 0) return (error); mask |= (active << n); } for (n = 0; n < sc->def->nstates; n++) { if (sc->def->states[n].mask == mask) { *uvolt = sc->def->states[n].val; return (0); } } return (EIO); } static regnode_method_t gpioregulator_regnode_methods[] = { /* Regulator interface */ REGNODEMETHOD(regnode_init, gpioregulator_regnode_init), REGNODEMETHOD(regnode_enable, gpioregulator_regnode_enable), REGNODEMETHOD(regnode_set_voltage, gpioregulator_regnode_set_voltage), REGNODEMETHOD(regnode_get_voltage, gpioregulator_regnode_get_voltage), REGNODEMETHOD_END }; DEFINE_CLASS_1(gpioregulator_regnode, gpioregulator_regnode_class, gpioregulator_regnode_methods, sizeof(struct gpioregulator_reg_sc), regnode_class); static int gpioregulator_parse_fdt(struct gpioregulator_softc *sc) { uint32_t *pstates, mask; phandle_t node; ssize_t len; int error, n; node = ofw_bus_get_node(sc->dev); pstates = NULL; mask = 0; error = regulator_parse_ofw_stdparam(sc->dev, node, &sc->init_def.reg_init_def); if (error != 0) return (error); /* "states" property (required) */ len = OF_getencprop_alloc_multi(node, "states", sizeof(*pstates), (void **)&pstates); if (len < 2) { device_printf(sc->dev, "invalid 'states' property\n"); error = EINVAL; goto done; } sc->init_def.nstates = len / 2; sc->init_def.states = malloc(sc->init_def.nstates * sizeof(*sc->init_def.states), M_DEVBUF, M_WAITOK); for (n = 0; n < sc->init_def.nstates; n++) { sc->init_def.states[n].val = pstates[n * 2 + 0]; sc->init_def.states[n].mask = pstates[n * 2 + 1]; mask |= sc->init_def.states[n].mask; } /* "startup-delay-us" property (optional) */ len = OF_getencprop(node, "startup-delay-us", &sc->init_def.startup_delay_us, sizeof(sc->init_def.startup_delay_us)); if (len <= 0) sc->init_def.startup_delay_us = 0; /* "enable-gpio" property (optional) */ error = gpio_pin_get_by_ofw_property(sc->dev, node, "enable-gpio", &sc->init_def.enable_pin); if (error == 0) sc->init_def.enable_pin_valid = 1; /* "gpios" property */ sc->init_def.npins = 32 - __builtin_clz(mask); sc->init_def.pins = malloc(sc->init_def.npins * sizeof(sc->init_def.pins), M_DEVBUF, M_WAITOK | M_ZERO); for (n = 0; n < sc->init_def.npins; n++) { error = gpio_pin_get_by_ofw_idx(sc->dev, node, n, &sc->init_def.pins[n]); if (error != 0) { device_printf(sc->dev, "cannot get pin %d\n", n); goto done; } } done: if (error != 0) { for (n = 0; n < sc->init_def.npins; n++) { if (sc->init_def.pins[n] != NULL) gpio_pin_release(sc->init_def.pins[n]); } free(sc->init_def.states, M_DEVBUF); free(sc->init_def.pins, M_DEVBUF); } OF_prop_free(pstates); return (error); } static int gpioregulator_probe(device_t dev) { if (!ofw_bus_is_compatible(dev, "regulator-gpio")) return (ENXIO); device_set_desc(dev, "GPIO controlled regulator"); return (BUS_PROBE_GENERIC); } static int gpioregulator_attach(device_t dev) { struct gpioregulator_softc *sc; struct regnode *regnode; phandle_t node; int error; sc = device_get_softc(dev); sc->dev = dev; node = ofw_bus_get_node(dev); error = gpioregulator_parse_fdt(sc); if (error != 0) { device_printf(dev, "cannot parse parameters\n"); return (ENXIO); } sc->init_def.reg_init_def.id = 1; sc->init_def.reg_init_def.ofw_node = node; regnode = regnode_create(dev, &gpioregulator_regnode_class, &sc->init_def.reg_init_def); if (regnode == NULL) { device_printf(dev, "cannot create regulator\n"); return (ENXIO); } sc->reg_sc = regnode_get_softc(regnode); sc->reg_sc->regnode = regnode; sc->reg_sc->base_dev = dev; sc->reg_sc->param = regnode_get_stdparam(regnode); sc->reg_sc->def = &sc->init_def; regnode_register(regnode); return (0); } static device_method_t gpioregulator_methods[] = { /* Device interface */ DEVMETHOD(device_probe, gpioregulator_probe), DEVMETHOD(device_attach, gpioregulator_attach), /* Regdev interface */ DEVMETHOD(regdev_map, regdev_default_ofw_map), DEVMETHOD_END }; static driver_t gpioregulator_driver = { "gpioregulator", gpioregulator_methods, sizeof(struct gpioregulator_softc), }; static devclass_t gpioregulator_devclass; EARLY_DRIVER_MODULE(gpioregulator, simplebus, gpioregulator_driver, gpioregulator_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LAST); MODULE_VERSION(gpioregulator, 1); Index: head/sys/dev/hdmi/dwc_hdmi.h =================================================================== --- head/sys/dev/hdmi/dwc_hdmi.h (revision 355357) +++ head/sys/dev/hdmi/dwc_hdmi.h (revision 355358) @@ -1,64 +1,63 @@ /*- * 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. * * $FreeBSD$ */ #ifndef __DWC_HDMI_H__ #define __DWC_HDMI_H__ struct dwc_hdmi_softc { device_t sc_dev; struct resource *sc_mem_res; int sc_mem_rid; uint32_t sc_reg_shift; device_t (*sc_get_i2c_dev)(device_t); uint8_t *sc_edid; uint8_t sc_edid_len; struct intr_config_hook sc_mode_hook; struct videomode sc_mode; struct edid_info sc_edid_info; int sc_has_audio; }; static inline uint8_t RD1(struct dwc_hdmi_softc *sc, bus_size_t off) { return (bus_read_1(sc->sc_mem_res, off << sc->sc_reg_shift)); } static inline void WR1(struct dwc_hdmi_softc *sc, bus_size_t off, uint8_t val) { bus_write_1(sc->sc_mem_res, off << sc->sc_reg_shift, val); } int dwc_hdmi_get_edid(device_t, uint8_t **, uint32_t *); int dwc_hdmi_set_videomode(device_t, const struct videomode *); int dwc_hdmi_init(device_t); #endif /* __DWC_HDMI_H__ */ Index: head/sys/dev/iicbus/sy8106a.c =================================================================== --- head/sys/dev/iicbus/sy8106a.c (revision 355357) +++ head/sys/dev/iicbus/sy8106a.c (revision 355358) @@ -1,303 +1,302 @@ /*- * 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$ */ /* * Silergy Corp. SY8106A buck regulator */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include "iicbus_if.h" #include "regdev_if.h" #define VOUT1_SEL 0x01 #define SEL_GO (1 << 7) #define SEL_VOLTAGE_MASK 0x7f #define SEL_VOLTAGE_BASE 680000 /* uV */ #define SEL_VOLTAGE_STEP 10000 /* uV */ #define VOUT_COM 0x02 #define COM_DISABLE (1 << 0) #define SYS_STATUS 0x06 static struct ofw_compat_data compat_data[] = { { "silergy,sy8106a", 1 }, { NULL, 0 } }; struct sy8106a_reg_sc { struct regnode *regnode; device_t base_dev; phandle_t xref; struct regnode_std_param *param; }; struct sy8106a_softc { uint16_t addr; /* Regulator */ struct sy8106a_reg_sc *reg; }; static int sy8106a_read(device_t dev, uint8_t reg, uint8_t *data, uint8_t size) { struct sy8106a_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 sy8106a_write(device_t dev, uint8_t reg, uint8_t val) { struct sy8106a_softc *sc; struct iic_msg msg; uint8_t buffer[2]; sc = device_get_softc(dev); buffer[0] = reg; buffer[1] = val; msg.slave = sc->addr; msg.flags = IIC_M_WR; msg.len = 2; msg.buf = buffer; return (iicbus_transfer(dev, &msg, 1)); } static int sy8106a_regnode_init(struct regnode *regnode) { return (0); } static int sy8106a_regnode_enable(struct regnode *regnode, bool enable, int *udelay) { struct sy8106a_reg_sc *sc; uint8_t val; sc = regnode_get_softc(regnode); sy8106a_read(sc->base_dev, VOUT_COM, &val, 1); if (enable) val &= ~COM_DISABLE; else val |= COM_DISABLE; sy8106a_write(sc->base_dev, VOUT_COM, val); *udelay = sc->param->ramp_delay; return (0); } static int sy8106a_regnode_set_voltage(struct regnode *regnode, int min_uvolt, int max_uvolt, int *udelay) { struct sy8106a_reg_sc *sc; int cur_uvolt; uint8_t val, oval; sc = regnode_get_softc(regnode); /* Get current voltage */ sy8106a_read(sc->base_dev, VOUT1_SEL, &oval, 1); cur_uvolt = (oval & SEL_VOLTAGE_MASK) * SEL_VOLTAGE_STEP + SEL_VOLTAGE_BASE; /* Set new voltage */ val = SEL_GO | ((min_uvolt - SEL_VOLTAGE_BASE) / SEL_VOLTAGE_STEP); sy8106a_write(sc->base_dev, VOUT1_SEL, val); /* Time to delay is based on the number of voltage steps */ *udelay = sc->param->ramp_delay * (abs(cur_uvolt - min_uvolt) / SEL_VOLTAGE_STEP); return (0); } static int sy8106a_regnode_get_voltage(struct regnode *regnode, int *uvolt) { struct sy8106a_reg_sc *sc; uint8_t val; sc = regnode_get_softc(regnode); sy8106a_read(sc->base_dev, VOUT1_SEL, &val, 1); *uvolt = (val & SEL_VOLTAGE_MASK) * SEL_VOLTAGE_STEP + SEL_VOLTAGE_BASE; return (0); } static regnode_method_t sy8106a_regnode_methods[] = { /* Regulator interface */ REGNODEMETHOD(regnode_init, sy8106a_regnode_init), REGNODEMETHOD(regnode_enable, sy8106a_regnode_enable), REGNODEMETHOD(regnode_set_voltage, sy8106a_regnode_set_voltage), REGNODEMETHOD(regnode_get_voltage, sy8106a_regnode_get_voltage), REGNODEMETHOD_END }; DEFINE_CLASS_1(sy8106a_regnode, sy8106a_regnode_class, sy8106a_regnode_methods, sizeof(struct sy8106a_reg_sc), regnode_class); static struct sy8106a_reg_sc * sy8106a_reg_attach(device_t dev, phandle_t node) { struct sy8106a_reg_sc *reg_sc; struct regnode_init_def initdef; struct regnode *regnode; memset(&initdef, 0, sizeof(initdef)); regulator_parse_ofw_stdparam(dev, node, &initdef); initdef.id = 0; initdef.ofw_node = node; regnode = regnode_create(dev, &sy8106a_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->xref = OF_xref_from_node(node); reg_sc->param = regnode_get_stdparam(regnode); regnode_register(regnode); return (reg_sc); } static int sy8106a_regdev_map(device_t dev, phandle_t xref, int ncells, pcell_t *cells, intptr_t *num) { struct sy8106a_softc *sc; sc = device_get_softc(dev); if (sc->reg->xref != xref) return (ENXIO); *num = 0; return (0); } static int sy8106a_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, "Silergy SY8106A regulator"); return (BUS_PROBE_DEFAULT); } static int sy8106a_attach(device_t dev) { struct sy8106a_softc *sc; phandle_t node; sc = device_get_softc(dev); node = ofw_bus_get_node(dev); sc->addr = iicbus_get_addr(dev); sc->reg = sy8106a_reg_attach(dev, node); if (sc->reg == NULL) { device_printf(dev, "cannot attach regulator\n"); return (ENXIO); } return (0); } static device_method_t sy8106a_methods[] = { /* Device interface */ DEVMETHOD(device_probe, sy8106a_probe), DEVMETHOD(device_attach, sy8106a_attach), /* Regdev interface */ DEVMETHOD(regdev_map, sy8106a_regdev_map), DEVMETHOD_END }; static driver_t sy8106a_driver = { "sy8106a", sy8106a_methods, sizeof(struct sy8106a_softc), }; static devclass_t sy8106a_devclass; EARLY_DRIVER_MODULE(sy8106a, iicbus, sy8106a_driver, sy8106a_devclass, 0, 0, BUS_PASS_RESOURCE); MODULE_VERSION(sy8106a, 1); MODULE_DEPEND(sy8106a, iicbus, 1, 1, 1); IICBUS_FDT_PNP_INFO(compat_data); Index: head/sys/dev/uart/uart_dev_snps.c =================================================================== --- head/sys/dev/uart/uart_dev_snps.c (revision 355357) +++ head/sys/dev/uart/uart_dev_snps.c (revision 355358) @@ -1,309 +1,308 @@ /*- * 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$ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #ifdef EXT_RESOURCES #include #include #endif #include "uart_if.h" struct snps_softc { struct ns8250_softc ns8250; #ifdef EXT_RESOURCES clk_t baudclk; clk_t apb_pclk; hwreset_t reset; #endif }; /* * To use early printf on 64 bits Allwinner SoC, add to kernel config * options SOCDEV_PA=0x0 * options SOCDEV_VA=0x40000000 * options EARLY_PRINTF * * To use early printf on 32 bits Allwinner SoC, add to kernel config * options SOCDEV_PA=0x01C00000 * options SOCDEV_VA=0x10000000 * options EARLY_PRINTF * * remove the if 0 */ #if 0 #ifdef EARLY_PRINTF static void uart_snps_early_putc(int c) { volatile uint32_t *stat; volatile uint32_t *tx; #ifdef ALLWINNER_64 stat = (uint32_t *) (SOCDEV_VA + 0x1C2807C); tx = (uint32_t *) (SOCDEV_VA + 0x1C28000); #endif #ifdef ALLWINNER_32 stat = (uint32_t *) (SOCDEV_VA + 0x2807C); tx = (uint32_t *) (SOCDEV_VA + 0x28000); #endif while ((*stat & (1 << 2)) == 0) continue; *tx = c; } early_putc_t *early_putc = uart_snps_early_putc; #endif /* EARLY_PRINTF */ #endif static kobj_method_t snps_methods[] = { KOBJMETHOD(uart_probe, ns8250_bus_probe), KOBJMETHOD(uart_attach, ns8250_bus_attach), KOBJMETHOD(uart_detach, ns8250_bus_detach), KOBJMETHOD(uart_flush, ns8250_bus_flush), KOBJMETHOD(uart_getsig, ns8250_bus_getsig), KOBJMETHOD(uart_ioctl, ns8250_bus_ioctl), KOBJMETHOD(uart_ipend, ns8250_bus_ipend), KOBJMETHOD(uart_param, ns8250_bus_param), KOBJMETHOD(uart_receive, ns8250_bus_receive), KOBJMETHOD(uart_setsig, ns8250_bus_setsig), KOBJMETHOD(uart_transmit, ns8250_bus_transmit), KOBJMETHOD(uart_grab, ns8250_bus_grab), KOBJMETHOD(uart_ungrab, ns8250_bus_ungrab), KOBJMETHOD_END }; struct uart_class uart_snps_class = { "snps", snps_methods, sizeof(struct snps_softc), .uc_ops = &uart_ns8250_ops, .uc_range = 8, .uc_rclk = 0, }; static struct ofw_compat_data compat_data[] = { { "snps,dw-apb-uart", (uintptr_t)&uart_snps_class }, { "marvell,armada-38x-uart", (uintptr_t)&uart_snps_class }, { NULL, (uintptr_t)NULL } }; UART_FDT_CLASS(compat_data); #ifdef EXT_RESOURCES static int snps_get_clocks(device_t dev, clk_t *baudclk, clk_t *apb_pclk) { *baudclk = NULL; *apb_pclk = NULL; /* Baud clock is either named "baudclk", or there is a single * unnamed clock. */ if (clk_get_by_ofw_name(dev, 0, "baudclk", baudclk) != 0 && clk_get_by_ofw_index(dev, 0, 0, baudclk) != 0) return (ENOENT); /* APB peripheral clock is optional */ (void)clk_get_by_ofw_name(dev, 0, "apb_pclk", apb_pclk); return (0); } #endif static int snps_probe(device_t dev) { struct snps_softc *sc; struct uart_class *uart_class; phandle_t node; uint32_t shift, iowidth, clock; uint64_t freq; int error; #ifdef EXT_RESOURCES clk_t baudclk, apb_pclk; hwreset_t reset; #endif if (!ofw_bus_status_okay(dev)) return (ENXIO); uart_class = (struct uart_class *)ofw_bus_search_compatible(dev, compat_data)->ocd_data; if (uart_class == NULL) return (ENXIO); freq = 0; sc = device_get_softc(dev); sc->ns8250.base.sc_class = uart_class; node = ofw_bus_get_node(dev); if (OF_getencprop(node, "reg-shift", &shift, sizeof(shift)) <= 0) shift = 0; if (OF_getencprop(node, "reg-io-width", &iowidth, sizeof(iowidth)) <= 0) iowidth = 1; if (OF_getencprop(node, "clock-frequency", &clock, sizeof(clock)) <= 0) clock = 0; #ifdef EXT_RESOURCES if (hwreset_get_by_ofw_idx(dev, 0, 0, &reset) == 0) { error = hwreset_deassert(reset); if (error != 0) { device_printf(dev, "cannot de-assert reset\n"); return (error); } } if (snps_get_clocks(dev, &baudclk, &apb_pclk) == 0) { error = clk_enable(baudclk); if (error != 0) { device_printf(dev, "cannot enable baud clock\n"); return (error); } if (apb_pclk != NULL) { error = clk_enable(apb_pclk); if (error != 0) { device_printf(dev, "cannot enable peripheral clock\n"); return (error); } } if (clock == 0) { error = clk_get_freq(baudclk, &freq); if (error != 0) { device_printf(dev, "cannot get frequency\n"); return (error); } clock = (uint32_t)freq; } } #endif if (bootverbose && clock == 0) device_printf(dev, "could not determine frequency\n"); error = uart_bus_probe(dev, (int)shift, (int)iowidth, (int)clock, 0, 0, UART_F_BUSY_DETECT); if (error != 0) return (error); #ifdef EXT_RESOURCES /* XXX uart_bus_probe has changed the softc, so refresh it */ sc = device_get_softc(dev); /* Store clock and reset handles for detach */ sc->baudclk = baudclk; sc->apb_pclk = apb_pclk; sc->reset = reset; #endif return (0); } static int snps_detach(device_t dev) { #ifdef EXT_RESOURCES struct snps_softc *sc; clk_t baudclk, apb_pclk; hwreset_t reset; #endif int error; #ifdef EXT_RESOURCES sc = device_get_softc(dev); baudclk = sc->baudclk; apb_pclk = sc->apb_pclk; reset = sc->reset; #endif error = uart_bus_detach(dev); if (error != 0) return (error); #ifdef EXT_RESOURCES if (reset != NULL) { error = hwreset_assert(reset); if (error != 0) { device_printf(dev, "cannot assert reset\n"); return (error); } hwreset_release(reset); } if (apb_pclk != NULL) { error = clk_release(apb_pclk); if (error != 0) { device_printf(dev, "cannot release peripheral clock\n"); return (error); } } if (baudclk != NULL) { error = clk_release(baudclk); if (error != 0) { device_printf(dev, "cannot release baud clock\n"); return (error); } } #endif return (0); } static device_method_t snps_bus_methods[] = { /* Device interface */ DEVMETHOD(device_probe, snps_probe), DEVMETHOD(device_attach, uart_bus_attach), DEVMETHOD(device_detach, snps_detach), DEVMETHOD_END }; static driver_t snps_uart_driver = { uart_driver_name, snps_bus_methods, sizeof(struct snps_softc) }; DRIVER_MODULE(uart_snps, simplebus, snps_uart_driver, uart_devclass, 0, 0); Index: head/sys/mips/ingenic/jz4780_lcd.c =================================================================== --- head/sys/mips/ingenic/jz4780_lcd.c (revision 355357) +++ head/sys/mips/ingenic/jz4780_lcd.c (revision 355358) @@ -1,576 +1,575 @@ /*- * 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$ */ /* * Ingenic JZ4780 LCD Controller */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fb_if.h" #include "hdmi_if.h" #define FB_DEFAULT_W 800 #define FB_DEFAULT_H 600 #define FB_DEFAULT_REF 60 #define FB_BPP 32 #define FB_ALIGN (16 * 4) #define FB_MAX_BW (1920 * 1080 * 60) #define FB_MAX_W 2048 #define FB_MAX_H 2048 #define FB_DIVIDE(x, y) (((x) + ((y) / 2)) / (y)) #define PCFG_MAGIC 0xc7ff2100 #define DOT_CLOCK_TO_HZ(c) ((c) * 1000) #ifndef VM_MEMATTR_WRITE_COMBINING #define VM_MEMATTR_WRITE_COMBINING VM_MEMATTR_UNCACHEABLE #endif struct jzlcd_softc { device_t dev; device_t fbdev; struct resource *res[1]; /* Clocks */ clk_t clk; clk_t clk_pix; /* Framebuffer */ struct fb_info info; size_t fbsize; bus_addr_t paddr; vm_offset_t vaddr; /* HDMI */ eventhandler_tag hdmi_evh; /* Frame descriptor DMA */ bus_dma_tag_t fdesc_tag; bus_dmamap_t fdesc_map; bus_addr_t fdesc_paddr; struct lcd_frame_descriptor *fdesc; }; static struct resource_spec jzlcd_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { -1, 0 } }; #define LCD_READ(sc, reg) bus_read_4((sc)->res[0], (reg)) #define LCD_WRITE(sc, reg, val) bus_write_4((sc)->res[0], (reg), (val)) static int jzlcd_allocfb(struct jzlcd_softc *sc) { sc->vaddr = kmem_alloc_contig(sc->fbsize, M_NOWAIT | M_ZERO, 0, ~0, FB_ALIGN, 0, VM_MEMATTR_WRITE_COMBINING); if (sc->vaddr == 0) { device_printf(sc->dev, "failed to allocate FB memory\n"); return (ENOMEM); } sc->paddr = pmap_kextract(sc->vaddr); return (0); } static void jzlcd_freefb(struct jzlcd_softc *sc) { kmem_free(sc->vaddr, sc->fbsize); } static void jzlcd_start(struct jzlcd_softc *sc) { uint32_t ctrl; /* Clear status registers */ LCD_WRITE(sc, LCDSTATE, 0); LCD_WRITE(sc, LCDOSDS, 0); /* Enable the controller */ ctrl = LCD_READ(sc, LCDCTRL); ctrl |= LCDCTRL_ENA; ctrl &= ~LCDCTRL_DIS; LCD_WRITE(sc, LCDCTRL, ctrl); } static void jzlcd_stop(struct jzlcd_softc *sc) { uint32_t ctrl; ctrl = LCD_READ(sc, LCDCTRL); if ((ctrl & LCDCTRL_ENA) != 0) { /* Disable the controller and wait for it to stop */ ctrl |= LCDCTRL_DIS; LCD_WRITE(sc, LCDCTRL, ctrl); while ((LCD_READ(sc, LCDSTATE) & LCDSTATE_LDD) == 0) DELAY(100); } /* Clear all status except for disable */ LCD_WRITE(sc, LCDSTATE, LCD_READ(sc, LCDSTATE) & ~LCDSTATE_LDD); } static void jzlcd_setup_descriptor(struct jzlcd_softc *sc, const struct videomode *mode, u_int desno) { struct lcd_frame_descriptor *fdesc; int line_sz; /* Frame size is specified in # words */ line_sz = (mode->hdisplay * FB_BPP) >> 3; line_sz = ((line_sz + 3) & ~3) / 4; fdesc = sc->fdesc + desno; if (desno == 0) fdesc->next = sc->fdesc_paddr + sizeof(struct lcd_frame_descriptor); else fdesc->next = sc->fdesc_paddr; fdesc->physaddr = sc->paddr; fdesc->id = desno; fdesc->cmd = LCDCMD_FRM_EN | (line_sz * mode->vdisplay); fdesc->offs = 0; fdesc->pw = 0; fdesc->cnum_pos = LCDPOS_BPP01_18_24 | LCDPOS_PREMULTI01 | (desno == 0 ? LCDPOS_COEF_BLE01_1 : LCDPOS_COEF_SLE01); fdesc->dessize = LCDDESSIZE_ALPHA | ((mode->vdisplay - 1) << LCDDESSIZE_HEIGHT_SHIFT) | ((mode->hdisplay - 1) << LCDDESSIZE_WIDTH_SHIFT); } static int jzlcd_set_videomode(struct jzlcd_softc *sc, const struct videomode *mode) { u_int hbp, hfp, hsw, vbp, vfp, vsw; u_int hds, hde, ht, vds, vde, vt; uint32_t ctrl; int error; hbp = mode->htotal - mode->hsync_end; hfp = mode->hsync_start - mode->hdisplay; hsw = mode->hsync_end - mode->hsync_start; vbp = mode->vtotal - mode->vsync_end; vfp = mode->vsync_start - mode->vdisplay; vsw = mode->vsync_end - mode->vsync_start; hds = hsw + hbp; hde = hds + mode->hdisplay; ht = hde + hfp; vds = vsw + vbp; vde = vds + mode->vdisplay; vt = vde + vfp; /* Setup timings */ LCD_WRITE(sc, LCDVAT, (ht << LCDVAT_HT_SHIFT) | (vt << LCDVAT_VT_SHIFT)); LCD_WRITE(sc, LCDDAH, (hds << LCDDAH_HDS_SHIFT) | (hde << LCDDAH_HDE_SHIFT)); LCD_WRITE(sc, LCDDAV, (vds << LCDDAV_VDS_SHIFT) | (vde << LCDDAV_VDE_SHIFT)); LCD_WRITE(sc, LCDHSYNC, hsw); LCD_WRITE(sc, LCDVSYNC, vsw); /* Set configuration */ LCD_WRITE(sc, LCDCFG, LCDCFG_NEWDES | LCDCFG_RECOVER | LCDCFG_24 | LCDCFG_PSM | LCDCFG_CLSM | LCDCFG_SPLM | LCDCFG_REVM | LCDCFG_PCP); ctrl = LCD_READ(sc, LCDCTRL); ctrl &= ~LCDCTRL_BST; ctrl |= LCDCTRL_BST_64 | LCDCTRL_OFUM; LCD_WRITE(sc, LCDCTRL, ctrl); LCD_WRITE(sc, LCDPCFG, PCFG_MAGIC); LCD_WRITE(sc, LCDRGBC, LCDRGBC_RGBFMT); /* Update registers */ LCD_WRITE(sc, LCDSTATE, 0); /* Setup frame descriptors */ jzlcd_setup_descriptor(sc, mode, 0); jzlcd_setup_descriptor(sc, mode, 1); bus_dmamap_sync(sc->fdesc_tag, sc->fdesc_map, BUS_DMASYNC_PREWRITE); /* Setup DMA channels */ LCD_WRITE(sc, LCDDA0, sc->fdesc_paddr + sizeof(struct lcd_frame_descriptor)); LCD_WRITE(sc, LCDDA1, sc->fdesc_paddr); /* Set display clock */ error = clk_set_freq(sc->clk_pix, DOT_CLOCK_TO_HZ(mode->dot_clock), 0); if (error != 0) { device_printf(sc->dev, "failed to set pixel clock to %u Hz\n", DOT_CLOCK_TO_HZ(mode->dot_clock)); return (error); } return (0); } static int jzlcd_configure(struct jzlcd_softc *sc, const struct videomode *mode) { size_t fbsize; int error; fbsize = round_page(mode->hdisplay * mode->vdisplay * (FB_BPP / NBBY)); /* Detach the old FB device */ if (sc->fbdev != NULL) { device_delete_child(sc->dev, sc->fbdev); sc->fbdev = NULL; } /* If the FB size has changed, free the old FB memory */ if (sc->fbsize > 0 && sc->fbsize != fbsize) { jzlcd_freefb(sc); sc->vaddr = 0; } /* Allocate the FB if necessary */ sc->fbsize = fbsize; if (sc->vaddr == 0) { error = jzlcd_allocfb(sc); if (error != 0) { device_printf(sc->dev, "failed to allocate FB memory\n"); return (ENXIO); } } /* Setup video mode */ error = jzlcd_set_videomode(sc, mode); if (error != 0) return (error); /* Attach framebuffer device */ sc->info.fb_name = device_get_nameunit(sc->dev); sc->info.fb_vbase = (intptr_t)sc->vaddr; sc->info.fb_pbase = sc->paddr; sc->info.fb_size = sc->fbsize; sc->info.fb_bpp = sc->info.fb_depth = FB_BPP; sc->info.fb_stride = mode->hdisplay * (FB_BPP / NBBY); sc->info.fb_width = mode->hdisplay; sc->info.fb_height = mode->vdisplay; #ifdef VM_MEMATTR_WRITE_COMBINING sc->info.fb_flags = FB_FLAG_MEMATTR; sc->info.fb_memattr = VM_MEMATTR_WRITE_COMBINING; #endif sc->fbdev = device_add_child(sc->dev, "fbd", device_get_unit(sc->dev)); if (sc->fbdev == NULL) { device_printf(sc->dev, "failed to add fbd child\n"); return (ENOENT); } error = device_probe_and_attach(sc->fbdev); if (error != 0) { device_printf(sc->dev, "failed to attach fbd device\n"); return (error); } return (0); } static int jzlcd_get_bandwidth(const struct videomode *mode) { int refresh; refresh = FB_DIVIDE(FB_DIVIDE(DOT_CLOCK_TO_HZ(mode->dot_clock), mode->htotal), mode->vtotal); return mode->hdisplay * mode->vdisplay * refresh; } static int jzlcd_mode_supported(const struct videomode *mode) { /* Width and height must be less than 2048 */ if (mode->hdisplay > FB_MAX_W || mode->vdisplay > FB_MAX_H) return (0); /* Bandwidth check */ if (jzlcd_get_bandwidth(mode) > FB_MAX_BW) return (0); /* Interlace modes not yet supported by the driver */ if ((mode->flags & VID_INTERLACE) != 0) return (0); return (1); } static const struct videomode * jzlcd_find_mode(struct edid_info *ei) { const struct videomode *best; int n, bw, best_bw; /* If the preferred mode is OK, just use it */ if (jzlcd_mode_supported(ei->edid_preferred_mode) != 0) return ei->edid_preferred_mode; /* Pick the mode with the highest bandwidth requirements */ best = NULL; best_bw = 0; for (n = 0; n < ei->edid_nmodes; n++) { if (jzlcd_mode_supported(&ei->edid_modes[n]) == 0) continue; bw = jzlcd_get_bandwidth(&ei->edid_modes[n]); if (bw > FB_MAX_BW) continue; if (best == NULL || bw > best_bw) { best = &ei->edid_modes[n]; best_bw = bw; } } return best; } static void jzlcd_hdmi_event(void *arg, device_t hdmi_dev) { const struct videomode *mode; struct videomode hdmi_mode; struct jzlcd_softc *sc; struct edid_info ei; uint8_t *edid; uint32_t edid_len; int error; sc = arg; edid = NULL; edid_len = 0; mode = NULL; error = HDMI_GET_EDID(hdmi_dev, &edid, &edid_len); if (error != 0) { device_printf(sc->dev, "failed to get EDID: %d\n", error); } else { error = edid_parse(edid, &ei); if (error != 0) { device_printf(sc->dev, "failed to parse EDID: %d\n", error); } else { if (bootverbose) edid_print(&ei); mode = jzlcd_find_mode(&ei); } } /* If a suitable mode could not be found, try the default */ if (mode == NULL) mode = pick_mode_by_ref(FB_DEFAULT_W, FB_DEFAULT_H, FB_DEFAULT_REF); if (mode == NULL) { device_printf(sc->dev, "failed to find usable video mode\n"); return; } if (bootverbose) device_printf(sc->dev, "using %dx%d\n", mode->hdisplay, mode->vdisplay); /* Stop the controller */ jzlcd_stop(sc); /* Configure LCD controller */ error = jzlcd_configure(sc, mode); if (error != 0) { device_printf(sc->dev, "failed to configure FB: %d\n", error); return; } /* Enable HDMI TX */ hdmi_mode = *mode; HDMI_SET_VIDEOMODE(hdmi_dev, &hdmi_mode); /* Start the controller! */ jzlcd_start(sc); } static void jzlcd_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error) { if (error != 0) return; *(bus_addr_t *)arg = segs[0].ds_addr; } static int jzlcd_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "ingenic,jz4780-lcd")) return (ENXIO); device_set_desc(dev, "Ingenic JZ4780 LCD Controller"); return (BUS_PROBE_DEFAULT); } static int jzlcd_attach(device_t dev) { struct jzlcd_softc *sc; int error; sc = device_get_softc(dev); sc->dev = dev; if (bus_alloc_resources(dev, jzlcd_spec, sc->res)) { device_printf(dev, "cannot allocate resources for device\n"); goto failed; } if (clk_get_by_ofw_name(dev, 0, "lcd_clk", &sc->clk) != 0 || clk_get_by_ofw_name(dev, 0, "lcd_pixclk", &sc->clk_pix) != 0) { device_printf(dev, "cannot get clocks\n"); goto failed; } if (clk_enable(sc->clk) != 0 || clk_enable(sc->clk_pix) != 0) { device_printf(dev, "cannot enable clocks\n"); goto failed; } error = bus_dma_tag_create( bus_get_dma_tag(dev), sizeof(struct lcd_frame_descriptor), 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, sizeof(struct lcd_frame_descriptor) * 2, 1, sizeof(struct lcd_frame_descriptor) * 2, 0, NULL, NULL, &sc->fdesc_tag); if (error != 0) { device_printf(dev, "cannot create bus dma tag\n"); goto failed; } error = bus_dmamem_alloc(sc->fdesc_tag, (void **)&sc->fdesc, BUS_DMA_NOCACHE | BUS_DMA_WAITOK | BUS_DMA_ZERO, &sc->fdesc_map); if (error != 0) { device_printf(dev, "cannot allocate dma descriptor\n"); goto dmaalloc_failed; } error = bus_dmamap_load(sc->fdesc_tag, sc->fdesc_map, sc->fdesc, sizeof(struct lcd_frame_descriptor) * 2, jzlcd_dmamap_cb, &sc->fdesc_paddr, 0); if (error != 0) { device_printf(dev, "cannot load dma map\n"); goto dmaload_failed; } sc->hdmi_evh = EVENTHANDLER_REGISTER(hdmi_event, jzlcd_hdmi_event, sc, 0); return (0); dmaload_failed: bus_dmamem_free(sc->fdesc_tag, sc->fdesc, sc->fdesc_map); dmaalloc_failed: bus_dma_tag_destroy(sc->fdesc_tag); failed: if (sc->clk_pix != NULL) clk_release(sc->clk); if (sc->clk != NULL) clk_release(sc->clk); if (sc->res != NULL) bus_release_resources(dev, jzlcd_spec, sc->res); return (ENXIO); } static struct fb_info * jzlcd_fb_getinfo(device_t dev) { struct jzlcd_softc *sc; sc = device_get_softc(dev); return (&sc->info); } static device_method_t jzlcd_methods[] = { /* Device interface */ DEVMETHOD(device_probe, jzlcd_probe), DEVMETHOD(device_attach, jzlcd_attach), /* FB interface */ DEVMETHOD(fb_getinfo, jzlcd_fb_getinfo), DEVMETHOD_END }; static driver_t jzlcd_driver = { "fb", jzlcd_methods, sizeof(struct jzlcd_softc), }; static devclass_t jzlcd_devclass; DRIVER_MODULE(fb, simplebus, jzlcd_driver, jzlcd_devclass, 0, 0); Index: head/sys/mips/ingenic/jz4780_lcd.h =================================================================== --- head/sys/mips/ingenic/jz4780_lcd.h (revision 355357) +++ head/sys/mips/ingenic/jz4780_lcd.h (revision 355358) @@ -1,204 +1,203 @@ /*- * 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$ */ /* * Ingenic JZ4780 LCD Controller */ #ifndef __JZ4780_LCD_H__ #define __JZ4780_LCD_H__ #define LCDCFG 0x0000 #define LCDCFG_LCDPIN (1 << 31) #define LCDCFG_TVEPEH (1 << 30) #define LCDCFG_NEWDES (1 << 28) #define LCDCFG_PALBP (1 << 27) #define LCDCFG_TVEN (1 << 26) #define LCDCFG_RECOVER (1 << 25) #define LCDCFG_PSM (1 << 23) #define LCDCFG_CLSM (1 << 22) #define LCDCFG_SPLM (1 << 21) #define LCDCFG_REVM (1 << 20) #define LCDCFG_HSYNM (1 << 19) #define LCDCFG_VSYNM (1 << 18) #define LCDCFG_INVDAT (1 << 17) #define LCDCFG_SYNDIR (1 << 16) #define LCDCFG_PSP (1 << 15) #define LCDCFG_CLSP (1 << 14) #define LCDCFG_SPLP (1 << 13) #define LCDCFG_REVP (1 << 12) #define LCDCFG_HSP (1 << 11) #define LCDCFG_PCP (1 << 10) #define LCDCFG_DEP (1 << 9) #define LCDCFG_VSP (1 << 8) #define LCDCFG_18_16 (1 << 7) #define LCDCFG_24 (1 << 6) #define LCDCFG_MODE (0xf << 0) #define LCDCTRL 0x0030 #define LCDCTRL_PINMD (1 << 31) #define LCDCTRL_BST (0x7 << 28) #define LCDCTRL_BST_4 (0 << 28) #define LCDCTRL_BST_8 (1 << 28) #define LCDCTRL_BST_16 (2 << 28) #define LCDCTRL_BST_32 (3 << 28) #define LCDCTRL_BST_64 (4 << 28) #define LCDCTRL_OUTRGB (1 << 27) #define LCDCTRL_OFUP (1 << 26) #define LCDCTRL_DACTE (1 << 14) #define LCDCTRL_EOFM (1 << 13) #define LCDCTRL_SOFM (1 << 12) #define LCDCTRL_OFUM (1 << 11) #define LCDCTRL_IFUM0 (1 << 10) #define LCDCTRL_IFUM1 (1 << 9) #define LCDCTRL_LDDM (1 << 8) #define LCDCTRL_QDM (1 << 7) #define LCDCTRL_BEDN (1 << 6) #define LCDCTRL_PEDN (1 << 5) #define LCDCTRL_DIS (1 << 4) #define LCDCTRL_ENA (1 << 3) #define LCDCTRL_BPP0 (0x7 << 0) #define LCDCTRL_BPP0_1 (0 << 0) #define LCDCTRL_BPP0_2 (1 << 0) #define LCDCTRL_BPP0_4 (2 << 0) #define LCDCTRL_BPP0_8 (3 << 0) #define LCDCTRL_BPP0_15_16 (4 << 0) #define LCDCTRL_BPP0_18_24 (5 << 0) #define LCDCTRL_BPP0_24_COMP (6 << 0) #define LCDCTRL_BPP0_30 (7 << 0) #define LCDCTR #define LCDSTATE 0x0034 #define LCDSTATE_QD (1 << 7) #define LCDSTATE_EOF (1 << 5) #define LCDSTATE_SOF (1 << 4) #define LCDSTATE_OUT (1 << 3) #define LCDSTATE_IFU0 (1 << 2) #define LCDSTATE_IFU1 (1 << 1) #define LCDSTATE_LDD (1 << 0) #define LCDOSDC 0x0100 #define LCDOSDCTRL 0x0104 #define LCDOSDS 0x0108 #define LCDBGC0 0x010c #define LCDBGC1 0x02c4 #define LCDKEY0 0x0110 #define LCDKEY1 0x0114 #define LCDALPHA 0x0118 #define LCDIPUR 0x011c #define LCDRGBC 0x0090 #define LCDRGBC_RGBDM (1 << 15) #define LCDRGBC_DMM (1 << 14) #define LCDRGBC_422 (1 << 8) #define LCDRGBC_RGBFMT (1 << 7) #define LCDRGBC_ODDRGB (0x7 << 4) #define LCDRGBC_EVENRGB (0x7 << 0) #define LCDVAT 0x000c #define LCDVAT_HT_SHIFT 16 #define LCDVAT_VT_SHIFT 0 #define LCDDAH 0x0010 #define LCDDAH_HDS_SHIFT 16 #define LCDDAH_HDE_SHIFT 0 #define LCDDAV 0x0014 #define LCDDAV_VDS_SHIFT 16 #define LCDDAV_VDE_SHIFT 0 #define LCDXYP0 0x0120 #define LCDXYP1 0x0124 #define LCDSIZE0 0x0128 #define LCDSIZE1 0x012c #define LCDVSYNC 0x0004 #define LCDHSYNC 0x0008 #define LCDPS 0x0018 #define LCDCLS 0x001c #define LCDSPL 0x0020 #define LCDREV 0x0024 #define LCDIID 0x0038 #define LCDDA0 0x0040 #define LCDSA0 0x0044 #define LCDFID0 0x0048 #define LCDCMD0 0x004c #define LCDCMD_SOFINT (1 << 31) #define LCDCMD_EOFINT (1 << 30) #define LCDCMD_CMD (1 << 29) #define LCDCMD_COMPE (1 << 27) #define LCDCMD_FRM_EN (1 << 26) #define LCDCMD_FIELD_SEL (1 << 25) #define LCDCMD_16X16BLOCK (1 << 24) #define LCDCMD_LEN (0xffffff << 0) #define LCDOFFS0 0x0060 #define LCDPW0 0x0064 #define LCDCNUM0 0x0068 #define LCDPOS0 LCDCNUM0 #define LCDPOS_ALPHAMD1 (1 << 31) #define LCDPOS_RGB01 (1 << 30) #define LCDPOS_BPP01 (0x7 << 27) #define LCDPOS_BPP01_15_16 (4 << 27) #define LCDPOS_BPP01_18_24 (5 << 27) #define LCDPOS_BPP01_24_COMP (6 << 27) #define LCDPOS_BPP01_30 (7 << 27) #define LCDPOS_PREMULTI01 (1 << 26) #define LCDPOS_COEF_SLE01 (0x3 << 24) #define LCDPOS_COEF_BLE01_1 (1 << 24) #define LCDPOS_YPOS01 (0xfff << 12) #define LCDPOS_XPOS01 (0xfff << 0) #define LCDDESSIZE0 0x006c #define LCDDESSIZE_ALPHA (0xff << 24) #define LCDDESSIZE_HEIGHT (0xfff << 12) #define LCDDESSIZE_HEIGHT_SHIFT 12 #define LCDDESSIZE_WIDTH (0xfff << 0) #define LCDDESSIZE_WIDTH_SHIFT 0 #define LCDDA1 0x0050 #define LCDSA1 0x0054 #define LCDFID1 0x0058 #define LCDCMD1 0x005c #define LCDOFFS1 0x0070 #define LCDPW1 0x0074 #define LCDCNUM1 0x0078 #define LCDPOS1 LCDCNUM1 #define LCDDESSIZE1 0x007c #define LCDPCFG 0x02c0 #define LCDDUALCTRL 0x02c8 #define LCDENH_CFG 0x0400 #define LCDENH_CSCCFG 0x0404 #define LCDENH_LUMACFG 0x0408 #define LCDENH_CHROCFG0 0x040c #define LCDENH_CHROCFG1 0x0410 #define LCDENH_DITHERCFG 0x0414 #define LCDENH_STATUS 0x0418 #define LCDENH_GAMMA 0x0800 /* base */ #define LCDENH_VEE 0x1000 /* base */ struct lcd_frame_descriptor { uint32_t next; uint32_t physaddr; uint32_t id; uint32_t cmd; uint32_t offs; uint32_t pw; uint32_t cnum_pos; uint32_t dessize; } __packed; #endif /* !__JZ4780_LCD_H__ */ Index: head/sys/mips/ingenic/jz4780_smb.c =================================================================== --- head/sys/mips/ingenic/jz4780_smb.c (revision 355357) +++ head/sys/mips/ingenic/jz4780_smb.c (revision 355358) @@ -1,482 +1,481 @@ /*- * 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$ */ /* * Ingenic JZ4780 SMB Controller */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iicbus_if.h" #define JZSMB_TIMEOUT ((300UL * hz) / 1000) #define JZSMB_SPEED_STANDARD 100000 #define JZSMB_SETUP_TIME_STANDARD 300 #define JZSMB_HOLD_TIME_STANDARD 400 #define JZSMB_PERIOD_MIN_STANDARD 4000 #define JZSMB_PERIOD_MAX_STANDARD 4700 #define JZSMB_SPEED_FAST 400000 #define JZSMB_SETUP_TIME_FAST 450 #define JZSMB_HOLD_TIME_FAST 450 #define JZSMB_PERIOD_MIN_FAST 600 #define JZSMB_PERIOD_MAX_FAST 1300 #define JZSMB_HCNT_BASE 8 #define JZSMB_HCNT_MIN 6 #define JZSMB_LCNT_BASE 1 #define JZSMB_LCNT_MIN 8 static inline int tstohz(const struct timespec *tsp) { struct timeval tv; TIMESPEC_TO_TIMEVAL(&tv, tsp); return (tvtohz(&tv)); } static struct ofw_compat_data compat_data[] = { { "ingenic,jz4780-i2c", 1 }, { NULL, 0 } }; static struct resource_spec jzsmb_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { -1, 0 } }; struct jzsmb_softc { struct resource *res; struct mtx mtx; clk_t clk; device_t iicbus; int busy; uint32_t i2c_freq; uint64_t bus_freq; uint32_t status; struct iic_msg *msg; }; #define SMB_LOCK(sc) mtx_lock(&(sc)->mtx) #define SMB_UNLOCK(sc) mtx_unlock(&(sc)->mtx) #define SMB_ASSERT_LOCKED(sc) mtx_assert(&(sc)->mtx, MA_OWNED) #define SMB_READ(sc, reg) bus_read_2((sc)->res, (reg)) #define SMB_WRITE(sc, reg, val) bus_write_2((sc)->res, (reg), (val)) static phandle_t jzsmb_get_node(device_t bus, device_t dev) { return (ofw_bus_get_node(bus)); } static int jzsmb_enable(struct jzsmb_softc *sc, int enable) { SMB_ASSERT_LOCKED(sc); if (enable) { SMB_WRITE(sc, SMBENB, SMBENB_SMBENB); while ((SMB_READ(sc, SMBENBST) & SMBENBST_SMBEN) == 0) ; } else { SMB_WRITE(sc, SMBENB, 0); while ((SMB_READ(sc, SMBENBST) & SMBENBST_SMBEN) != 0) ; } return (0); } static int jzsmb_reset_locked(device_t dev, u_char addr) { struct jzsmb_softc *sc; uint16_t con; uint32_t period; int hcnt, lcnt, setup_time, hold_time; sc = device_get_softc(dev); SMB_ASSERT_LOCKED(sc); /* Setup master mode operation */ /* Disable SMB */ jzsmb_enable(sc, 0); /* Disable interrupts */ SMB_WRITE(sc, SMBINTM, 0); /* Set supported speed mode and expected SCL frequency */ period = sc->bus_freq / sc->i2c_freq; con = SMBCON_REST | SMBCON_SLVDIS | SMBCON_MD; switch (sc->i2c_freq) { case JZSMB_SPEED_STANDARD: con |= SMBCON_SPD_STANDARD; setup_time = JZSMB_SETUP_TIME_STANDARD; hold_time = JZSMB_HOLD_TIME_STANDARD; hcnt = (period * JZSMB_PERIOD_MIN_STANDARD) / (JZSMB_PERIOD_MAX_STANDARD + JZSMB_PERIOD_MIN_STANDARD); lcnt = period - hcnt; hcnt = MAX(hcnt - JZSMB_HCNT_BASE, JZSMB_HCNT_MIN); lcnt = MAX(lcnt - JZSMB_LCNT_BASE, JZSMB_LCNT_MIN); SMB_WRITE(sc, SMBCON, con); SMB_WRITE(sc, SMBSHCNT, hcnt); SMB_WRITE(sc, SMBSLCNT, lcnt); break; case JZSMB_SPEED_FAST: con |= SMBCON_SPD_FAST; setup_time = JZSMB_SETUP_TIME_FAST; hold_time = JZSMB_HOLD_TIME_FAST; hcnt = (period * JZSMB_PERIOD_MIN_FAST) / (JZSMB_PERIOD_MAX_FAST + JZSMB_PERIOD_MIN_FAST); lcnt = period - hcnt; hcnt = MAX(hcnt - JZSMB_HCNT_BASE, JZSMB_HCNT_MIN); lcnt = MAX(lcnt - JZSMB_LCNT_BASE, JZSMB_LCNT_MIN); SMB_WRITE(sc, SMBCON, con); SMB_WRITE(sc, SMBFHCNT, hcnt); SMB_WRITE(sc, SMBFLCNT, lcnt); break; default: return (EINVAL); } setup_time = ((setup_time * sc->bus_freq / 1000) / 1000000) + 1; setup_time = MIN(1, MAX(255, setup_time)); SMB_WRITE(sc, SMBSDASU, setup_time); hold_time = ((hold_time * sc->bus_freq / 1000) / 1000000) - 1; hold_time = MAX(255, hold_time); if (hold_time >= 0) SMB_WRITE(sc, SMBSDAHD, hold_time | SMBSDAHD_HDENB); else SMB_WRITE(sc, SMBSDAHD, 0); SMB_WRITE(sc, SMBTAR, addr >> 1); if (addr != 0) { /* Enable SMB */ jzsmb_enable(sc, 1); } return (0); } static int jzsmb_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr) { struct jzsmb_softc *sc; int error; sc = device_get_softc(dev); SMB_LOCK(sc); error = jzsmb_reset_locked(dev, addr); SMB_UNLOCK(sc); return (error); } static int jzsmb_transfer_read(device_t dev, struct iic_msg *msg) { struct jzsmb_softc *sc; struct timespec start, diff; uint16_t con, resid; int timeo; sc = device_get_softc(dev); timeo = JZSMB_TIMEOUT * msg->len; SMB_ASSERT_LOCKED(sc); con = SMB_READ(sc, SMBCON); con |= SMBCON_STPHLD; SMB_WRITE(sc, SMBCON, con); getnanouptime(&start); for (resid = msg->len; resid > 0; resid--) { for (int i = 0; i < min(resid, 8); i++) SMB_WRITE(sc, SMBDC, SMBDC_CMD); for (;;) { getnanouptime(&diff); timespecsub(&diff, &start, &diff); if ((SMB_READ(sc, SMBST) & SMBST_RFNE) != 0) { msg->buf[msg->len - resid] = SMB_READ(sc, SMBDC) & SMBDC_DAT; break; } else DELAY(1000); if (tstohz(&diff) >= timeo) { device_printf(dev, "read timeout (status=0x%02x)\n", SMB_READ(sc, SMBST)); return (EIO); } } } con = SMB_READ(sc, SMBCON); con &= ~SMBCON_STPHLD; SMB_WRITE(sc, SMBCON, con); return (0); } static int jzsmb_transfer_write(device_t dev, struct iic_msg *msg, int stop_hold) { struct jzsmb_softc *sc; struct timespec start, diff; uint16_t con, resid; int timeo; sc = device_get_softc(dev); timeo = JZSMB_TIMEOUT * msg->len; SMB_ASSERT_LOCKED(sc); con = SMB_READ(sc, SMBCON); con |= SMBCON_STPHLD; SMB_WRITE(sc, SMBCON, con); getnanouptime(&start); for (resid = msg->len; resid > 0; resid--) { for (;;) { getnanouptime(&diff); timespecsub(&diff, &start, &diff); if ((SMB_READ(sc, SMBST) & SMBST_TFNF) != 0) { SMB_WRITE(sc, SMBDC, msg->buf[msg->len - resid]); break; } else DELAY((1000 * hz) / JZSMB_TIMEOUT); if (tstohz(&diff) >= timeo) { device_printf(dev, "write timeout (status=0x%02x)\n", SMB_READ(sc, SMBST)); return (EIO); } } } if (!stop_hold) { con = SMB_READ(sc, SMBCON); con &= ~SMBCON_STPHLD; SMB_WRITE(sc, SMBCON, con); } return (0); } static int jzsmb_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs) { struct jzsmb_softc *sc; uint32_t n; uint16_t con; int error; sc = device_get_softc(dev); SMB_LOCK(sc); while (sc->busy) mtx_sleep(sc, &sc->mtx, 0, "i2cbuswait", 0); sc->busy = 1; sc->status = 0; for (n = 0; n < nmsgs; n++) { /* Set target address */ if (n == 0 || msgs[n].slave != msgs[n - 1].slave) jzsmb_reset_locked(dev, msgs[n].slave); /* Set read or write */ if ((msgs[n].flags & IIC_M_RD) != 0) error = jzsmb_transfer_read(dev, &msgs[n]); else error = jzsmb_transfer_write(dev, &msgs[n], n < nmsgs - 1); if (error != 0) goto done; } done: /* Send stop if necessary */ con = SMB_READ(sc, SMBCON); con &= ~SMBCON_STPHLD; SMB_WRITE(sc, SMBCON, con); /* Disable SMB */ jzsmb_enable(sc, 0); sc->msg = NULL; sc->busy = 0; wakeup(sc); SMB_UNLOCK(sc); return (error); } static int jzsmb_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, "Ingenic JZ4780 SMB Controller"); return (BUS_PROBE_DEFAULT); } static int jzsmb_attach(device_t dev) { struct jzsmb_softc *sc; phandle_t node; int error; sc = device_get_softc(dev); node = ofw_bus_get_node(dev); mtx_init(&sc->mtx, device_get_nameunit(dev), "jzsmb", MTX_DEF); error = clk_get_by_ofw_index(dev, 0, 0, &sc->clk); if (error != 0) { device_printf(dev, "cannot get clock\n"); goto fail; } error = clk_enable(sc->clk); if (error != 0) { device_printf(dev, "cannot enable clock\n"); goto fail; } error = clk_get_freq(sc->clk, &sc->bus_freq); if (error != 0 || sc->bus_freq == 0) { device_printf(dev, "cannot get bus frequency\n"); return (error); } if (bus_alloc_resources(dev, jzsmb_spec, &sc->res) != 0) { device_printf(dev, "cannot allocate resources for device\n"); error = ENXIO; goto fail; } if (OF_getencprop(node, "clock-frequency", &sc->i2c_freq, sizeof(sc->i2c_freq)) != 0 || sc->i2c_freq == 0) sc->i2c_freq = 100000; /* Default to standard mode */ sc->iicbus = device_add_child(dev, "iicbus", -1); if (sc->iicbus == NULL) { device_printf(dev, "cannot add iicbus child device\n"); error = ENXIO; goto fail; } bus_generic_attach(dev); return (0); fail: bus_release_resources(dev, jzsmb_spec, &sc->res); if (sc->clk != NULL) clk_release(sc->clk); mtx_destroy(&sc->mtx); return (error); } static device_method_t jzsmb_methods[] = { /* Device interface */ DEVMETHOD(device_probe, jzsmb_probe), DEVMETHOD(device_attach, jzsmb_attach), /* 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), /* OFW methods */ DEVMETHOD(ofw_bus_get_node, jzsmb_get_node), /* iicbus interface */ DEVMETHOD(iicbus_callback, iicbus_null_callback), DEVMETHOD(iicbus_reset, jzsmb_reset), DEVMETHOD(iicbus_transfer, jzsmb_transfer), DEVMETHOD_END }; static driver_t jzsmb_driver = { "iichb", jzsmb_methods, sizeof(struct jzsmb_softc), }; static devclass_t jzsmb_devclass; EARLY_DRIVER_MODULE(iicbus, jzsmb, iicbus_driver, iicbus_devclass, 0, 0, BUS_PASS_RESOURCE + BUS_PASS_ORDER_MIDDLE); EARLY_DRIVER_MODULE(jzsmb, simplebus, jzsmb_driver, jzsmb_devclass, 0, 0, BUS_PASS_RESOURCE + BUS_PASS_ORDER_MIDDLE); MODULE_VERSION(jzsmb, 1); Index: head/sys/mips/ingenic/jz4780_smb.h =================================================================== --- head/sys/mips/ingenic/jz4780_smb.h (revision 355357) +++ head/sys/mips/ingenic/jz4780_smb.h (revision 355358) @@ -1,98 +1,97 @@ /*- * 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$ */ /* * Ingenic JZ4780 SMB Controller */ #ifndef __JZ4780_SMB_H__ #define __JZ4780_SMB_H__ #define SMBCON 0x00 #define SMBCON_STPHLD (1 << 7) #define SMBCON_SLVDIS (1 << 6) #define SMBCON_REST (1 << 5) #define SMBCON_MATP (1 << 4) #define SMBCON_SATP (1 << 3) #define SMBCON_SPD (3 << 1) #define SMBCON_SPD_STANDARD (1 << 1) #define SMBCON_SPD_FAST (2 << 1) #define SMBCON_MD (1 << 0) #define SMBTAR 0x04 #define SMBTAR_MATP (1 << 12) #define SMBTAR_SPECIAL (1 << 11) #define SMBTAR_GC_OR_START (1 << 10) #define SMBTAR_SMBTAR (0x3ff << 0) #define SMBSAR 0x08 #define SMBDC 0x10 #define SMBDC_CMD (1 << 8) #define SMBDC_DAT (0xff << 0) #define SMBSHCNT 0x14 #define SMBSLCNT 0x18 #define SMBFHCNT 0x1c #define SMBFLCNT 0x20 #define SMBINTST 0x2c #define SMBINTM 0x30 #define SMBRXTL 0x38 #define SMBTXTL 0x3c #define SMBCINT 0x40 #define SMBCRXUF 0x44 #define SMBCRXOF 0x48 #define SMBCTXOF 0x4c #define SMBCRXREQ 0x50 #define SMBCTXABT 0x54 #define SMBCRXDN 0x58 #define SMBCACT 0x5c #define SMBCSTP 0x60 #define SMBCSTT 0x64 #define SMBCGC 0x68 #define SMBENB 0x6c #define SMBENB_SMBENB (1 << 0) #define SMBST 0x70 #define SMBST_SLVACT (1 << 6) #define SMBST_MSTACT (1 << 5) #define SMBST_RFF (1 << 4) #define SMBST_RFNE (1 << 3) #define SMBST_TFE (1 << 2) #define SMBST_TFNF (1 << 1) #define SMBST_ACT (1 << 0) #define SMBABTSRC 0x80 #define SMBDMACR 0x88 #define SMBDMATDLR 0x8c #define SMBDMARDLR 0x90 #define SMBSDASU 0x94 #define SMBACKGC 0x98 #define SMBENBST 0x9c #define SMBENBST_SLVRDLST (1 << 2) #define SMBENBST_SLVDISB (1 << 1) #define SMBENBST_SMBEN (1 << 0) #define SMBSDAHD 0xd0 #define SMBSDAHD_HDENB (1 << 8) #define SMBSDAHD_SDAHD (0xff << 0) #endif /* !__JZ4780_SMB_H__ */