Index: head/sys/arm/allwinner/aw_sid.c =================================================================== --- head/sys/arm/allwinner/aw_sid.c (revision 337370) +++ head/sys/arm/allwinner/aw_sid.c (revision 337371) @@ -1,295 +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 -/* efuse registers */ +#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 SID_SRAM 0x200 -/* Offsets into efuse space, for convenience */ -#define SID_THERMAL_CALIB0_OFF (0x34) -#define SID_THERMAL_CALIB1_OFF (0x38) -#define SID_THERMAL_CALIB0 (SID_SRAM + SID_THERMAL_CALIB0_OFF) -#define SID_THERMAL_CALIB1 (SID_SRAM + SID_THERMAL_CALIB1_OFF) +#define EFUSE_OFFSET 0x200 +#define EFUSE_NAME_SIZE 32 +#define EFUSE_DESC_SIZE 64 -#define ROOT_KEY_SIZE 4 +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 { - bus_size_t efuse_size; - bus_size_t rootkey_offset; - bool has_prctl; - bool has_thermal; - bool requires_prctl_read; + struct aw_sid_efuse *efuses; + size_t nfuses; }; static const struct aw_sid_conf a10_conf = { - .efuse_size = 0x10, - .rootkey_offset = 0, + .efuses = a10_efuses, + .nfuses = nitems(a10_efuses), }; static const struct aw_sid_conf a20_conf = { - .efuse_size = 0x10, - .rootkey_offset = 0, + .efuses = a10_efuses, + .nfuses = nitems(a10_efuses), }; static const struct aw_sid_conf a64_conf = { - .efuse_size = 0x100, - .rootkey_offset = SID_SRAM, - .has_prctl = true, - .has_thermal = true, + .efuses = a64_efuses, + .nfuses = nitems(a64_efuses), }; static const struct aw_sid_conf a83t_conf = { - .efuse_size = 0x100, - .rootkey_offset = SID_SRAM, - .has_prctl = true, - .has_thermal = true, + .efuses = a83t_efuses, + .nfuses = nitems(a83t_efuses), }; static const struct aw_sid_conf h3_conf = { - .efuse_size = 0x100, - .rootkey_offset = SID_SRAM, - .has_prctl = true, - .has_thermal = true, - .requires_prctl_read = true, + .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 } }; -enum sid_keys { - AW_SID_ROOT_KEY, -}; - +#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)) -#define PRCTL_RD4(sc, reg, val) aw_sid_prctl_read((sc)->sid_dev, (reg), (val)) - static int aw_sid_sysctl(SYSCTL_HANDLER_ARGS); -static int aw_sid_prctl_read(device_t dev, bus_size_t offset, uint32_t *val); - -/* - * offset here is offset into efuse space, rather than offset into sid register - * space. This form of read is only an option for newer SoC: A83t, H3, A64 - */ static int -aw_sid_prctl_read(device_t dev, bus_size_t offset, uint32_t *val) -{ - struct aw_sid_softc *sc; - uint32_t readval; - - sc = device_get_softc(dev); - if (!sc->sid_conf->has_prctl) - return (1); - - mtx_lock(&sc->prctl_mtx); - readval = SID_PRCTL_OFFSET(offset) | SID_PRCTL_LOCK | SID_PRCTL_READ; - WR4(sc, SID_PRCTL, readval); - /* Read bit will be cleared once read has concluded */ - while (RD4(sc, SID_PRCTL) & SID_PRCTL_READ) - continue; - readval = RD4(sc, SID_RDKEY); - mtx_unlock(&sc->prctl_mtx); - *val = readval; - - return (0); -} - -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; - SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), - SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), - OID_AUTO, "rootkey", - CTLTYPE_STRING | CTLFLAG_RD, - dev, AW_SID_ROOT_KEY, aw_sid_sysctl, "A", "Root Key"); + /* 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_read_tscalib(uint32_t *calib0, uint32_t *calib1) +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); - if (!sc->sid_conf->has_thermal) - return (ENXIO); - if (sc->sid_conf->requires_prctl_read) { - PRCTL_RD4(sc, SID_THERMAL_CALIB0_OFF, calib0); - PRCTL_RD4(sc, SID_THERMAL_CALIB1_OFF, calib1); - } else { - *calib0 = RD4(sc, SID_THERMAL_CALIB0); - *calib1 = RD4(sc, SID_THERMAL_CALIB1); + 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); } -int -aw_sid_get_rootkey(u_char *out) +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; - bus_size_t root_key_off; - u_int tmp; - sc = aw_sid_sc; - if (sc == NULL) - return (ENXIO); - root_key_off = aw_sid_sc->sid_conf->rootkey_offset; - for (i = 0; i < ROOT_KEY_SIZE ; i++) { - if (sc->sid_conf->requires_prctl_read) - PRCTL_RD4(sc, (i * 4), &tmp); - else - tmp = RD4(aw_sid_sc, root_key_off + (i * 4)); - be32enc(&out[i * 4], tmp); - } + sc = device_get_softc(dev); - return (0); + 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) { - enum sid_keys key = arg2; - u_char rootkey[16]; - char out[33]; + 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; - if (key != AW_SID_ROOT_KEY) - return (ENOENT); + sc = device_get_softc(dev); - if (aw_sid_get_rootkey(rootkey) != 0) + /* 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); - snprintf(out, sizeof(out), - "%16D", rootkey, ""); + } + 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_RESOURCE + BUS_PASS_ORDER_FIRST); MODULE_VERSION(aw_sid, 1); Index: head/sys/arm/allwinner/aw_sid.h =================================================================== --- head/sys/arm/allwinner/aw_sid.h (revision 337370) +++ head/sys/arm/allwinner/aw_sid.h (revision 337371) @@ -1,35 +1,40 @@ /*- * 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_rootkey(u_char *out); +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 337370) +++ head/sys/arm/allwinner/aw_thermal.c (revision 337371) @@ -1,617 +1,627 @@ /*- * 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 "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_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_ADC_ACQUIRE_TIME 0x17 #define A83T_FILTER 0x4 #define A83T_INTC 0x1000 #define A83T_TEMP_BASE 2719000 #define A83T_TEMP_MUL 1000 #define A83T_TEMP_DIV 14186 #define A83T_CLK_RATE 24000000 #define A64_ADC_ACQUIRE_TIME 0x190 #define A64_FILTER 0x6 #define A64_INTC 0x18000 #define A64_TEMP_BASE 2170000 #define A64_TEMP_MUL 1000 #define A64_TEMP_DIV 8560 #define A64_CLK_RATE 4000000 #define H3_ADC_ACQUIRE_TIME 0x3f #define H3_FILTER 0x6 #define H3_INTC 0x191000 #define H3_TEMP_BASE 217 #define H3_TEMP_MUL 1000 #define H3_TEMP_DIV 8253 #define H3_TEMP_MINUS 1794000 #define H3_CLK_RATE 4000000 #define H3_INIT_ALARM 90 /* degC */ #define H3_INIT_SHUT 105 /* 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 intc; int (*to_temp)(uint32_t); uint32_t (*to_reg)(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) { 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, .intc = A83T_INTC, .to_temp = a83t_to_temp, .calib0_mask = 0xffffffff, .calib1_mask = 0xffffffff, }; static int a64_to_temp(uint32_t val) { 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, .filter = A64_FILTER, .intc = A64_INTC, .to_temp = a64_to_temp, }; static int h3_to_temp(uint32_t val) { return (H3_TEMP_BASE - ((val * H3_TEMP_MUL) / H3_TEMP_DIV)); } static uint32_t h3_to_reg(int val) { 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, .filter = H3_FILTER, .intc = H3_INTC, .to_temp = h3_to_temp, .to_reg = h3_to_reg, .calib0_mask = 0xfff, }; static struct ofw_compat_data compat_data[] = { { "allwinner,sun8i-a83t-ts", (uintptr_t)&a83t_config }, { "allwinner,sun8i-h3-ts", (uintptr_t)&h3_config }, { "allwinner,sun50i-a64-ts", (uintptr_t)&a64_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; }; 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) { - uint32_t calib0, calib1; + phandle_t node; + uint32_t calib[2]; int error; + node = ofw_bus_get_node(sc->dev); if (sc->conf->calib0_mask != 0 || sc->conf->calib1_mask != 0) { - /* Read calibration settings from SRAM */ - error = aw_sid_read_tscalib(&calib0, &calib1); - if (error != 0) + error = nvmem_read_cell_by_name(node, "ths_calibration", + (void *)calib, sizeof(calib)); + /* Read calibration settings from EFUSE */ + if (error != 0) { + device_printf(sc->dev, "Cannot read THS efuse\n"); return (error); + } - calib0 &= sc->conf->calib0_mask; - calib1 &= sc->conf->calib1_mask; + device_printf(sc->dev, "calib0: %x\n", calib[0]); + device_printf(sc->dev, "calib1: %x\n", calib[1]); + calib[0] &= sc->conf->calib0_mask; + calib[1] &= sc->conf->calib1_mask; + /* Write calibration settings to thermal controller */ - if (calib0 != 0) - WR4(sc, THS_CALIB0, calib0); - if (calib1 != 0) - WR4(sc, THS_CALIB1, calib1); + 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); /* Enable average filter */ WR4(sc, THS_FILTER, sc->conf->filter); /* Enable interrupts */ WR4(sc, THS_INTS, RD4(sc, THS_INTS)); WR4(sc, THS_INTC, sc->conf->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)); } 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)); } 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) << 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)); } 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)); } 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) << 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; clk_t clk_ahb, clk_ths; hwreset_t rst; int i, error; void *ih; sc = device_get_softc(dev); clk_ahb = clk_ths = NULL; 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, "ahb", &clk_ahb) == 0) { error = clk_enable(clk_ahb); if (error != 0) { device_printf(dev, "cannot enable ahb clock\n"); goto fail; } } if (clk_get_by_ofw_name(dev, 0, "ths", &clk_ths) == 0) { error = clk_set_freq(clk_ths, sc->conf->clk_rate, 0); if (error != 0) { device_printf(dev, "cannot set ths clock rate\n"); goto fail; } error = clk_enable(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, "#%d: alarm %dC hyst %dC shut %dC\n", i, 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 (clk_ahb != NULL) clk_release(clk_ahb); if (clk_ths != NULL) clk_release(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); Index: head/sys/arm/allwinner/if_awg.c =================================================================== --- head/sys/arm/allwinner/if_awg.c (revision 337370) +++ head/sys/arm/allwinner/if_awg.c (revision 337371) @@ -1,1963 +1,1966 @@ /*- * 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 void awg_setup_rxfilter(struct awg_softc *sc) { uint32_t val, crc, hashreg, hashbit, hash[2], machi, maclo; int mc_count, mcnt, i; uint8_t *eaddr, *mta; if_t ifp; AWG_ASSERT_LOCKED(sc); ifp = sc->ifp; val = 0; hash[0] = hash[1] = 0; mc_count = if_multiaddr_count(ifp, -1); 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 (mc_count > 0) { val |= HASH_MULTICAST; mta = malloc(sizeof(unsigned char) * ETHER_ADDR_LEN * mc_count, M_DEVBUF, M_NOWAIT); if (mta == NULL) { if_printf(ifp, "failed to allocate temporary multicast list\n"); return; } if_multiaddr_array(ifp, mta, &mcnt, mc_count); for (i = 0; i < mcnt; i++) { crc = ether_crc32_le(mta + (i * ETHER_ADDR_LEN), ETHER_ADDR_LEN) & 0x7f; crc = bitrev32(~crc) >> 26; hashreg = (crc >> 5); hashbit = (crc & 0x1f); hash[hashreg] |= (1 << hashbit); } free(mta, M_DEVBUF); } /* 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) { 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_rootkey(rootkey) == 0 && + 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); Index: head/sys/arm/allwinner/if_emac.c =================================================================== --- head/sys/arm/allwinner/if_emac.c (revision 337370) +++ head/sys/arm/allwinner/if_emac.c (revision 337371) @@ -1,1199 +1,1202 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2013 Ganbold Tsagaankhuu * 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$ */ /* A10/A20 EMAC driver */ #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 #ifdef INET #include #include #include #include #endif #include #include #include #include #include #include #include #include #include #include "miibus_if.h" #include "gpio_if.h" #include "a10_sramc.h" struct emac_softc { struct ifnet *emac_ifp; device_t emac_dev; device_t emac_miibus; bus_space_handle_t emac_handle; bus_space_tag_t emac_tag; struct resource *emac_res; struct resource *emac_irq; void *emac_intrhand; clk_t emac_clk; int emac_if_flags; struct mtx emac_mtx; struct callout emac_tick_ch; int emac_watchdog_timer; int emac_rx_process_limit; int emac_link; uint32_t emac_fifo_mask; }; static int emac_probe(device_t); static int emac_attach(device_t); static int emac_detach(device_t); static int emac_shutdown(device_t); static int emac_suspend(device_t); static int emac_resume(device_t); static int emac_sys_setup(struct emac_softc *); static void emac_reset(struct emac_softc *); static void emac_init_locked(struct emac_softc *); static void emac_start_locked(struct ifnet *); static void emac_init(void *); static void emac_stop_locked(struct emac_softc *); static void emac_intr(void *); static int emac_ioctl(struct ifnet *, u_long, caddr_t); static void emac_rxeof(struct emac_softc *, int); static void emac_txeof(struct emac_softc *, uint32_t); static int emac_miibus_readreg(device_t, int, int); static int emac_miibus_writereg(device_t, int, int, int); static void emac_miibus_statchg(device_t); static int emac_ifmedia_upd(struct ifnet *); static void emac_ifmedia_sts(struct ifnet *, struct ifmediareq *); static int sysctl_int_range(SYSCTL_HANDLER_ARGS, int, int); static int sysctl_hw_emac_proc_limit(SYSCTL_HANDLER_ARGS); #define EMAC_READ_REG(sc, reg) \ bus_space_read_4(sc->emac_tag, sc->emac_handle, reg) #define EMAC_WRITE_REG(sc, reg, val) \ bus_space_write_4(sc->emac_tag, sc->emac_handle, reg, val) static int emac_sys_setup(struct emac_softc *sc) { int error; /* Activate EMAC clock. */ error = clk_get_by_ofw_index(sc->emac_dev, 0, 0, &sc->emac_clk); if (error != 0) { device_printf(sc->emac_dev, "cannot get clock\n"); return (error); } error = clk_enable(sc->emac_clk); if (error != 0) { device_printf(sc->emac_dev, "cannot enable clock\n"); return (error); } /* Map sram. */ a10_map_to_emac(); return (0); } static void emac_get_hwaddr(struct emac_softc *sc, uint8_t *hwaddr) { uint32_t val0, val1, rnd; u_char rootkey[16]; + size_t rootkey_size; /* * Try to get MAC address from running hardware. * If there is something non-zero there just use it. * * Otherwise set the address to a convenient locally assigned address, * using the SID rootkey. * This is was uboot does so we end up with the same mac as if uboot * did set it. * If we can't get the root key, generate a random one, * 'bsd' + random 24 low-order bits. 'b' is 0x62, which has the locally * assigned bit set, and the broadcast/multicast bit clear. */ val0 = EMAC_READ_REG(sc, EMAC_MAC_A0); val1 = EMAC_READ_REG(sc, EMAC_MAC_A1); if ((val0 | val1) != 0 && (val0 | val1) != 0xffffff) { hwaddr[0] = (val1 >> 16) & 0xff; hwaddr[1] = (val1 >> 8) & 0xff; hwaddr[2] = (val1 >> 0) & 0xff; hwaddr[3] = (val0 >> 16) & 0xff; hwaddr[4] = (val0 >> 8) & 0xff; hwaddr[5] = (val0 >> 0) & 0xff; } else { - if (aw_sid_get_rootkey(rootkey) == 0) { + rootkey_size = sizeof(rootkey); + if (aw_sid_get_fuse(AW_SID_FUSE_ROOTKEY, rootkey, + &rootkey_size) == 0) { hwaddr[0] = 0x2; hwaddr[1] = rootkey[3]; hwaddr[2] = rootkey[12]; hwaddr[3] = rootkey[13]; hwaddr[4] = rootkey[14]; hwaddr[5] = rootkey[15]; } else { rnd = arc4random() & 0x00ffffff; hwaddr[0] = 'b'; hwaddr[1] = 's'; hwaddr[2] = 'd'; hwaddr[3] = (rnd >> 16) & 0xff; hwaddr[4] = (rnd >> 8) & 0xff; hwaddr[5] = (rnd >> 0) & 0xff; } } if (bootverbose) printf("MAC address: %s\n", ether_sprintf(hwaddr)); } static void emac_set_rx_mode(struct emac_softc *sc) { struct ifnet *ifp; struct ifmultiaddr *ifma; uint32_t h, hashes[2]; uint32_t rcr = 0; EMAC_ASSERT_LOCKED(sc); ifp = sc->emac_ifp; rcr = EMAC_READ_REG(sc, EMAC_RX_CTL); /* Unicast packet and DA filtering */ rcr |= EMAC_RX_UCAD; rcr |= EMAC_RX_DAF; hashes[0] = 0; hashes[1] = 0; if (ifp->if_flags & IFF_ALLMULTI) { hashes[0] = 0xffffffff; hashes[1] = 0xffffffff; } else { if_maddr_rlock(ifp); CK_STAILQ_FOREACH(ifma, &sc->emac_ifp->if_multiaddrs, ifma_link) { if (ifma->ifma_addr->sa_family != AF_LINK) continue; h = ether_crc32_be(LLADDR((struct sockaddr_dl *) ifma->ifma_addr), ETHER_ADDR_LEN) >> 26; hashes[h >> 5] |= 1 << (h & 0x1f); } if_maddr_runlock(ifp); } rcr |= EMAC_RX_MCO; rcr |= EMAC_RX_MHF; EMAC_WRITE_REG(sc, EMAC_RX_HASH0, hashes[0]); EMAC_WRITE_REG(sc, EMAC_RX_HASH1, hashes[1]); if (ifp->if_flags & IFF_BROADCAST) { rcr |= EMAC_RX_BCO; rcr |= EMAC_RX_MCO; } if (ifp->if_flags & IFF_PROMISC) rcr |= EMAC_RX_PA; else rcr |= EMAC_RX_UCAD; EMAC_WRITE_REG(sc, EMAC_RX_CTL, rcr); } static void emac_reset(struct emac_softc *sc) { EMAC_WRITE_REG(sc, EMAC_CTL, 0); DELAY(200); EMAC_WRITE_REG(sc, EMAC_CTL, 1); DELAY(200); } static void emac_drain_rxfifo(struct emac_softc *sc) { uint32_t data; while (EMAC_READ_REG(sc, EMAC_RX_FBC) > 0) data = EMAC_READ_REG(sc, EMAC_RX_IO_DATA); } static void emac_txeof(struct emac_softc *sc, uint32_t status) { struct ifnet *ifp; EMAC_ASSERT_LOCKED(sc); ifp = sc->emac_ifp; status &= (EMAC_TX_FIFO0 | EMAC_TX_FIFO1); sc->emac_fifo_mask &= ~status; if (status == (EMAC_TX_FIFO0 | EMAC_TX_FIFO1)) if_inc_counter(ifp, IFCOUNTER_OPACKETS, 2); else if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; /* Unarm watchdog timer if no TX */ sc->emac_watchdog_timer = 0; } static void emac_rxeof(struct emac_softc *sc, int count) { struct ifnet *ifp; struct mbuf *m, *m0; uint32_t reg_val, rxcount; int16_t len; uint16_t status; int i; ifp = sc->emac_ifp; for (; count > 0 && (ifp->if_drv_flags & IFF_DRV_RUNNING) != 0; count--) { /* * Race warning: The first packet might arrive with * the interrupts disabled, but the second will fix */ rxcount = EMAC_READ_REG(sc, EMAC_RX_FBC); if (!rxcount) { /* Had one stuck? */ rxcount = EMAC_READ_REG(sc, EMAC_RX_FBC); if (!rxcount) return; } /* Check packet header */ reg_val = EMAC_READ_REG(sc, EMAC_RX_IO_DATA); if (reg_val != EMAC_PACKET_HEADER) { /* Packet header is wrong */ if (bootverbose) if_printf(ifp, "wrong packet header\n"); /* Disable RX */ reg_val = EMAC_READ_REG(sc, EMAC_CTL); reg_val &= ~EMAC_CTL_RX_EN; EMAC_WRITE_REG(sc, EMAC_CTL, reg_val); /* Flush RX FIFO */ reg_val = EMAC_READ_REG(sc, EMAC_RX_CTL); reg_val |= EMAC_RX_FLUSH_FIFO; EMAC_WRITE_REG(sc, EMAC_RX_CTL, reg_val); for (i = 100; i > 0; i--) { DELAY(100); if ((EMAC_READ_REG(sc, EMAC_RX_CTL) & EMAC_RX_FLUSH_FIFO) == 0) break; } if (i == 0) { device_printf(sc->emac_dev, "flush FIFO timeout\n"); /* Reinitialize controller */ emac_init_locked(sc); return; } /* Enable RX */ reg_val = EMAC_READ_REG(sc, EMAC_CTL); reg_val |= EMAC_CTL_RX_EN; EMAC_WRITE_REG(sc, EMAC_CTL, reg_val); return; } /* Get packet size and status */ reg_val = EMAC_READ_REG(sc, EMAC_RX_IO_DATA); len = reg_val & 0xffff; status = (reg_val >> 16) & 0xffff; if (len < 64 || (status & EMAC_PKT_OK) == 0) { if (bootverbose) if_printf(ifp, "bad packet: len = %i status = %i\n", len, status); if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); emac_drain_rxfifo(sc); continue; } #if 0 if (status & (EMAC_CRCERR | EMAC_LENERR)) { good_packet = 0; if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); if (status & EMAC_CRCERR) if_printf(ifp, "crc error\n"); if (status & EMAC_LENERR) if_printf(ifp, "length error\n"); } #endif m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); if (m == NULL) { emac_drain_rxfifo(sc); return; } m->m_len = m->m_pkthdr.len = MCLBYTES; /* Copy entire frame to mbuf first. */ bus_space_read_multi_4(sc->emac_tag, sc->emac_handle, EMAC_RX_IO_DATA, mtod(m, uint32_t *), roundup2(len, 4) / 4); m->m_pkthdr.rcvif = ifp; m->m_len = m->m_pkthdr.len = len - ETHER_CRC_LEN; /* * Emac controller needs strict aligment, so to avoid * copying over an entire frame to align, we allocate * a new mbuf and copy ethernet header + IP header to * the new mbuf. The new mbuf is prepended into the * existing mbuf chain. */ if (m->m_len <= (MHLEN - ETHER_HDR_LEN)) { bcopy(m->m_data, m->m_data + ETHER_HDR_LEN, m->m_len); m->m_data += ETHER_HDR_LEN; } else if (m->m_len <= (MCLBYTES - ETHER_HDR_LEN) && m->m_len > (MHLEN - ETHER_HDR_LEN)) { MGETHDR(m0, M_NOWAIT, MT_DATA); if (m0 != NULL) { len = ETHER_HDR_LEN + m->m_pkthdr.l2hlen; bcopy(m->m_data, m0->m_data, len); m->m_data += len; m->m_len -= len; m0->m_len = len; M_MOVE_PKTHDR(m0, m); m0->m_next = m; m = m0; } else { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); m_freem(m); m = NULL; continue; } } else if (m->m_len > EMAC_MAC_MAXF) { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); m_freem(m); m = NULL; continue; } if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1); EMAC_UNLOCK(sc); (*ifp->if_input)(ifp, m); EMAC_LOCK(sc); } } static void emac_watchdog(struct emac_softc *sc) { struct ifnet *ifp; EMAC_ASSERT_LOCKED(sc); if (sc->emac_watchdog_timer == 0 || --sc->emac_watchdog_timer) return; ifp = sc->emac_ifp; if (sc->emac_link == 0) { if (bootverbose) if_printf(sc->emac_ifp, "watchdog timeout " "(missed link)\n"); } else if_printf(sc->emac_ifp, "watchdog timeout -- resetting\n"); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; emac_init_locked(sc); if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) emac_start_locked(ifp); } static void emac_tick(void *arg) { struct emac_softc *sc; struct mii_data *mii; sc = (struct emac_softc *)arg; mii = device_get_softc(sc->emac_miibus); mii_tick(mii); emac_watchdog(sc); callout_reset(&sc->emac_tick_ch, hz, emac_tick, sc); } static void emac_init(void *xcs) { struct emac_softc *sc; sc = (struct emac_softc *)xcs; EMAC_LOCK(sc); emac_init_locked(sc); EMAC_UNLOCK(sc); } static void emac_init_locked(struct emac_softc *sc) { struct ifnet *ifp; struct mii_data *mii; uint32_t reg_val; uint8_t *eaddr; EMAC_ASSERT_LOCKED(sc); ifp = sc->emac_ifp; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) return; /* Flush RX FIFO */ reg_val = EMAC_READ_REG(sc, EMAC_RX_CTL); reg_val |= EMAC_RX_FLUSH_FIFO; EMAC_WRITE_REG(sc, EMAC_RX_CTL, reg_val); DELAY(1); /* Soft reset MAC */ reg_val = EMAC_READ_REG(sc, EMAC_MAC_CTL0); reg_val &= (~EMAC_MAC_CTL0_SOFT_RST); EMAC_WRITE_REG(sc, EMAC_MAC_CTL0, reg_val); /* Set MII clock */ reg_val = EMAC_READ_REG(sc, EMAC_MAC_MCFG); reg_val &= (~(0xf << 2)); reg_val |= (0xd << 2); EMAC_WRITE_REG(sc, EMAC_MAC_MCFG, reg_val); /* Clear RX counter */ EMAC_WRITE_REG(sc, EMAC_RX_FBC, 0); /* Disable all interrupt and clear interrupt status */ EMAC_WRITE_REG(sc, EMAC_INT_CTL, 0); reg_val = EMAC_READ_REG(sc, EMAC_INT_STA); EMAC_WRITE_REG(sc, EMAC_INT_STA, reg_val); DELAY(1); /* Set up TX */ reg_val = EMAC_READ_REG(sc, EMAC_TX_MODE); reg_val |= EMAC_TX_AB_M; reg_val &= EMAC_TX_TM; EMAC_WRITE_REG(sc, EMAC_TX_MODE, reg_val); /* Set up RX */ reg_val = EMAC_READ_REG(sc, EMAC_RX_CTL); reg_val |= EMAC_RX_SETUP; reg_val &= EMAC_RX_TM; EMAC_WRITE_REG(sc, EMAC_RX_CTL, reg_val); /* Set up MAC CTL0. */ reg_val = EMAC_READ_REG(sc, EMAC_MAC_CTL0); reg_val |= EMAC_MAC_CTL0_SETUP; EMAC_WRITE_REG(sc, EMAC_MAC_CTL0, reg_val); /* Set up MAC CTL1. */ reg_val = EMAC_READ_REG(sc, EMAC_MAC_CTL1); reg_val |= EMAC_MAC_CTL1_SETUP; EMAC_WRITE_REG(sc, EMAC_MAC_CTL1, reg_val); /* Set up IPGT */ EMAC_WRITE_REG(sc, EMAC_MAC_IPGT, EMAC_MAC_IPGT_FD); /* Set up IPGR */ EMAC_WRITE_REG(sc, EMAC_MAC_IPGR, EMAC_MAC_NBTB_IPG2 | (EMAC_MAC_NBTB_IPG1 << 8)); /* Set up Collison window */ EMAC_WRITE_REG(sc, EMAC_MAC_CLRT, EMAC_MAC_RM | (EMAC_MAC_CW << 8)); /* Set up Max Frame Length */ EMAC_WRITE_REG(sc, EMAC_MAC_MAXF, EMAC_MAC_MFL); /* Setup ethernet address */ eaddr = IF_LLADDR(ifp); EMAC_WRITE_REG(sc, EMAC_MAC_A1, eaddr[0] << 16 | eaddr[1] << 8 | eaddr[2]); EMAC_WRITE_REG(sc, EMAC_MAC_A0, eaddr[3] << 16 | eaddr[4] << 8 | eaddr[5]); /* Setup rx filter */ emac_set_rx_mode(sc); /* Enable RX/TX0/RX Hlevel interrupt */ reg_val = EMAC_READ_REG(sc, EMAC_INT_CTL); reg_val |= EMAC_INT_EN; EMAC_WRITE_REG(sc, EMAC_INT_CTL, reg_val); ifp->if_drv_flags |= IFF_DRV_RUNNING; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; sc->emac_link = 0; /* Switch to the current media. */ mii = device_get_softc(sc->emac_miibus); mii_mediachg(mii); callout_reset(&sc->emac_tick_ch, hz, emac_tick, sc); } static void emac_start(struct ifnet *ifp) { struct emac_softc *sc; sc = ifp->if_softc; EMAC_LOCK(sc); emac_start_locked(ifp); EMAC_UNLOCK(sc); } static void emac_start_locked(struct ifnet *ifp) { struct emac_softc *sc; struct mbuf *m, *m0; uint32_t fifo, reg; sc = ifp->if_softc; if (ifp->if_drv_flags & IFF_DRV_OACTIVE) return; if (sc->emac_fifo_mask == (EMAC_TX_FIFO0 | EMAC_TX_FIFO1)) return; if (sc->emac_link == 0) return; IFQ_DRV_DEQUEUE(&ifp->if_snd, m); if (m == NULL) return; /* Select channel */ if (sc->emac_fifo_mask & EMAC_TX_FIFO0) fifo = 1; else fifo = 0; sc->emac_fifo_mask |= (1 << fifo); if (sc->emac_fifo_mask == (EMAC_TX_FIFO0 | EMAC_TX_FIFO1)) ifp->if_drv_flags |= IFF_DRV_OACTIVE; EMAC_WRITE_REG(sc, EMAC_TX_INS, fifo); /* * Emac controller wants 4 byte aligned TX buffers. * We have to copy pretty much all the time. */ if (m->m_next != NULL || (mtod(m, uintptr_t) & 3) != 0) { m0 = m_defrag(m, M_NOWAIT); if (m0 == NULL) { m_freem(m); m = NULL; return; } m = m0; } /* Write data */ bus_space_write_multi_4(sc->emac_tag, sc->emac_handle, EMAC_TX_IO_DATA, mtod(m, uint32_t *), roundup2(m->m_len, 4) / 4); /* Send the data lengh. */ reg = (fifo == 0) ? EMAC_TX_PL0 : EMAC_TX_PL1; EMAC_WRITE_REG(sc, reg, m->m_len); /* Start translate from fifo to phy. */ reg = (fifo == 0) ? EMAC_TX_CTL0 : EMAC_TX_CTL1; EMAC_WRITE_REG(sc, reg, EMAC_READ_REG(sc, reg) | 1); /* Set timeout */ sc->emac_watchdog_timer = 5; /* Data have been sent to hardware, it is okay to free the mbuf now. */ BPF_MTAP(ifp, m); m_freem(m); } static void emac_stop_locked(struct emac_softc *sc) { struct ifnet *ifp; uint32_t reg_val; EMAC_ASSERT_LOCKED(sc); ifp = sc->emac_ifp; ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); sc->emac_link = 0; /* Disable all interrupt and clear interrupt status */ EMAC_WRITE_REG(sc, EMAC_INT_CTL, 0); reg_val = EMAC_READ_REG(sc, EMAC_INT_STA); EMAC_WRITE_REG(sc, EMAC_INT_STA, reg_val); /* Disable RX/TX */ reg_val = EMAC_READ_REG(sc, EMAC_CTL); reg_val &= ~(EMAC_CTL_RST | EMAC_CTL_TX_EN | EMAC_CTL_RX_EN); EMAC_WRITE_REG(sc, EMAC_CTL, reg_val); callout_stop(&sc->emac_tick_ch); } static void emac_intr(void *arg) { struct emac_softc *sc; struct ifnet *ifp; uint32_t reg_val; sc = (struct emac_softc *)arg; EMAC_LOCK(sc); /* Disable all interrupts */ EMAC_WRITE_REG(sc, EMAC_INT_CTL, 0); /* Get EMAC interrupt status */ reg_val = EMAC_READ_REG(sc, EMAC_INT_STA); /* Clear ISR status */ EMAC_WRITE_REG(sc, EMAC_INT_STA, reg_val); /* Received incoming packet */ if (reg_val & EMAC_INT_STA_RX) emac_rxeof(sc, sc->emac_rx_process_limit); /* Transmit Interrupt check */ if (reg_val & EMAC_INT_STA_TX) { emac_txeof(sc, reg_val); ifp = sc->emac_ifp; if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) emac_start_locked(ifp); } /* Re-enable interrupt mask */ reg_val = EMAC_READ_REG(sc, EMAC_INT_CTL); reg_val |= EMAC_INT_EN; EMAC_WRITE_REG(sc, EMAC_INT_CTL, reg_val); EMAC_UNLOCK(sc); } static int emac_ioctl(struct ifnet *ifp, u_long command, caddr_t data) { struct emac_softc *sc; struct mii_data *mii; struct ifreq *ifr; int error = 0; sc = ifp->if_softc; ifr = (struct ifreq *)data; switch (command) { case SIOCSIFFLAGS: EMAC_LOCK(sc); if (ifp->if_flags & IFF_UP) { if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) { if ((ifp->if_flags ^ sc->emac_if_flags) & (IFF_PROMISC | IFF_ALLMULTI)) emac_set_rx_mode(sc); } else emac_init_locked(sc); } else { if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) emac_stop_locked(sc); } sc->emac_if_flags = ifp->if_flags; EMAC_UNLOCK(sc); break; case SIOCADDMULTI: case SIOCDELMULTI: EMAC_LOCK(sc); if (ifp->if_drv_flags & IFF_DRV_RUNNING) { emac_set_rx_mode(sc); } EMAC_UNLOCK(sc); break; case SIOCGIFMEDIA: case SIOCSIFMEDIA: mii = device_get_softc(sc->emac_miibus); error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, command); break; default: error = ether_ioctl(ifp, command, data); break; } return (error); } static int emac_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "allwinner,sun4i-a10-emac")) return (ENXIO); device_set_desc(dev, "A10/A20 EMAC ethernet controller"); return (BUS_PROBE_DEFAULT); } static int emac_detach(device_t dev) { struct emac_softc *sc; sc = device_get_softc(dev); sc->emac_ifp->if_drv_flags &= ~IFF_DRV_RUNNING; if (device_is_attached(dev)) { ether_ifdetach(sc->emac_ifp); EMAC_LOCK(sc); emac_stop_locked(sc); EMAC_UNLOCK(sc); callout_drain(&sc->emac_tick_ch); } if (sc->emac_intrhand != NULL) bus_teardown_intr(sc->emac_dev, sc->emac_irq, sc->emac_intrhand); if (sc->emac_miibus != NULL) { device_delete_child(sc->emac_dev, sc->emac_miibus); bus_generic_detach(sc->emac_dev); } if (sc->emac_clk != NULL) clk_disable(sc->emac_clk); if (sc->emac_res != NULL) bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->emac_res); if (sc->emac_irq != NULL) bus_release_resource(dev, SYS_RES_IRQ, 0, sc->emac_irq); if (sc->emac_ifp != NULL) if_free(sc->emac_ifp); if (mtx_initialized(&sc->emac_mtx)) mtx_destroy(&sc->emac_mtx); return (0); } static int emac_shutdown(device_t dev) { return (emac_suspend(dev)); } static int emac_suspend(device_t dev) { struct emac_softc *sc; struct ifnet *ifp; sc = device_get_softc(dev); EMAC_LOCK(sc); ifp = sc->emac_ifp; if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) emac_stop_locked(sc); EMAC_UNLOCK(sc); return (0); } static int emac_resume(device_t dev) { struct emac_softc *sc; struct ifnet *ifp; sc = device_get_softc(dev); EMAC_LOCK(sc); ifp = sc->emac_ifp; if ((ifp->if_flags & IFF_UP) != 0) { ifp->if_drv_flags &= ~IFF_DRV_RUNNING; emac_init_locked(sc); } EMAC_UNLOCK(sc); return (0); } static int emac_attach(device_t dev) { struct emac_softc *sc; struct ifnet *ifp; int error, rid; uint8_t eaddr[ETHER_ADDR_LEN]; sc = device_get_softc(dev); sc->emac_dev = dev; error = 0; mtx_init(&sc->emac_mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK, MTX_DEF); callout_init_mtx(&sc->emac_tick_ch, &sc->emac_mtx, 0); rid = 0; sc->emac_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->emac_res == NULL) { device_printf(dev, "unable to map memory\n"); error = ENXIO; goto fail; } sc->emac_tag = rman_get_bustag(sc->emac_res); sc->emac_handle = rman_get_bushandle(sc->emac_res); rid = 0; sc->emac_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE); if (sc->emac_irq == NULL) { device_printf(dev, "cannot allocate IRQ resources.\n"); error = ENXIO; goto fail; } /* Create device sysctl node. */ SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "process_limit", CTLTYPE_INT | CTLFLAG_RW, &sc->emac_rx_process_limit, 0, sysctl_hw_emac_proc_limit, "I", "max number of Rx events to process"); sc->emac_rx_process_limit = EMAC_PROC_DEFAULT; error = resource_int_value(device_get_name(dev), device_get_unit(dev), "process_limit", &sc->emac_rx_process_limit); if (error == 0) { if (sc->emac_rx_process_limit < EMAC_PROC_MIN || sc->emac_rx_process_limit > EMAC_PROC_MAX) { device_printf(dev, "process_limit value out of range; " "using default: %d\n", EMAC_PROC_DEFAULT); sc->emac_rx_process_limit = EMAC_PROC_DEFAULT; } } /* Setup EMAC */ error = emac_sys_setup(sc); if (error != 0) goto fail; emac_reset(sc); ifp = sc->emac_ifp = if_alloc(IFT_ETHER); if (ifp == NULL) { device_printf(dev, "unable to allocate ifp\n"); error = ENOSPC; goto fail; } ifp->if_softc = sc; /* Setup MII */ error = mii_attach(dev, &sc->emac_miibus, ifp, emac_ifmedia_upd, emac_ifmedia_sts, BMSR_DEFCAPMASK, MII_PHY_ANY, MII_OFFSET_ANY, 0); if (error != 0) { device_printf(dev, "PHY probe failed\n"); goto fail; } if_initname(ifp, device_get_name(dev), device_get_unit(dev)); ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_start = emac_start; ifp->if_ioctl = emac_ioctl; ifp->if_init = emac_init; IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN); /* Get MAC address */ emac_get_hwaddr(sc, eaddr); ether_ifattach(ifp, eaddr); /* VLAN capability setup. */ ifp->if_capabilities |= IFCAP_VLAN_MTU; ifp->if_capenable = ifp->if_capabilities; /* Tell the upper layer we support VLAN over-sized frames. */ ifp->if_hdrlen = sizeof(struct ether_vlan_header); error = bus_setup_intr(dev, sc->emac_irq, INTR_TYPE_NET | INTR_MPSAFE, NULL, emac_intr, sc, &sc->emac_intrhand); if (error != 0) { device_printf(dev, "could not set up interrupt handler.\n"); ether_ifdetach(ifp); goto fail; } fail: if (error != 0) emac_detach(dev); return (error); } static boolean_t emac_miibus_iowait(struct emac_softc *sc) { uint32_t timeout; for (timeout = 100; timeout != 0; --timeout) { DELAY(100); if ((EMAC_READ_REG(sc, EMAC_MAC_MIND) & 0x1) == 0) return (true); } return (false); } /* * The MII bus interface */ static int emac_miibus_readreg(device_t dev, int phy, int reg) { struct emac_softc *sc; int rval; sc = device_get_softc(dev); /* Issue phy address and reg */ EMAC_WRITE_REG(sc, EMAC_MAC_MADR, (phy << 8) | reg); /* Pull up the phy io line */ EMAC_WRITE_REG(sc, EMAC_MAC_MCMD, 0x1); if (!emac_miibus_iowait(sc)) { device_printf(dev, "timeout waiting for mii read\n"); return (0); } /* Push down the phy io line */ EMAC_WRITE_REG(sc, EMAC_MAC_MCMD, 0x0); /* Read data */ rval = EMAC_READ_REG(sc, EMAC_MAC_MRDD); return (rval); } static int emac_miibus_writereg(device_t dev, int phy, int reg, int data) { struct emac_softc *sc; sc = device_get_softc(dev); /* Issue phy address and reg */ EMAC_WRITE_REG(sc, EMAC_MAC_MADR, (phy << 8) | reg); /* Write data */ EMAC_WRITE_REG(sc, EMAC_MAC_MWTD, data); /* Pull up the phy io line */ EMAC_WRITE_REG(sc, EMAC_MAC_MCMD, 0x1); if (!emac_miibus_iowait(sc)) { device_printf(dev, "timeout waiting for mii write\n"); return (0); } /* Push down the phy io line */ EMAC_WRITE_REG(sc, EMAC_MAC_MCMD, 0x0); return (0); } static void emac_miibus_statchg(device_t dev) { struct emac_softc *sc; struct mii_data *mii; struct ifnet *ifp; uint32_t reg_val; sc = device_get_softc(dev); mii = device_get_softc(sc->emac_miibus); ifp = sc->emac_ifp; if (mii == NULL || ifp == NULL || (ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) return; sc->emac_link = 0; if ((mii->mii_media_status & (IFM_ACTIVE | IFM_AVALID)) == (IFM_ACTIVE | IFM_AVALID)) { switch (IFM_SUBTYPE(mii->mii_media_active)) { case IFM_10_T: case IFM_100_TX: sc->emac_link = 1; break; default: break; } } /* Program MACs with resolved speed/duplex. */ if (sc->emac_link != 0) { reg_val = EMAC_READ_REG(sc, EMAC_MAC_IPGT); if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0) { reg_val &= ~EMAC_MAC_IPGT_HD; reg_val |= EMAC_MAC_IPGT_FD; } else { reg_val &= ~EMAC_MAC_IPGT_FD; reg_val |= EMAC_MAC_IPGT_HD; } EMAC_WRITE_REG(sc, EMAC_MAC_IPGT, reg_val); /* Enable RX/TX */ reg_val = EMAC_READ_REG(sc, EMAC_CTL); reg_val |= EMAC_CTL_RST | EMAC_CTL_TX_EN | EMAC_CTL_RX_EN; EMAC_WRITE_REG(sc, EMAC_CTL, reg_val); } else { /* Disable RX/TX */ reg_val = EMAC_READ_REG(sc, EMAC_CTL); reg_val &= ~(EMAC_CTL_RST | EMAC_CTL_TX_EN | EMAC_CTL_RX_EN); EMAC_WRITE_REG(sc, EMAC_CTL, reg_val); } } static int emac_ifmedia_upd(struct ifnet *ifp) { struct emac_softc *sc; struct mii_data *mii; struct mii_softc *miisc; int error; sc = ifp->if_softc; mii = device_get_softc(sc->emac_miibus); EMAC_LOCK(sc); LIST_FOREACH(miisc, &mii->mii_phys, mii_list) PHY_RESET(miisc); error = mii_mediachg(mii); EMAC_UNLOCK(sc); return (error); } static void emac_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) { struct emac_softc *sc; struct mii_data *mii; sc = ifp->if_softc; mii = device_get_softc(sc->emac_miibus); EMAC_LOCK(sc); mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; EMAC_UNLOCK(sc); } static device_method_t emac_methods[] = { /* Device interface */ DEVMETHOD(device_probe, emac_probe), DEVMETHOD(device_attach, emac_attach), DEVMETHOD(device_detach, emac_detach), DEVMETHOD(device_shutdown, emac_shutdown), DEVMETHOD(device_suspend, emac_suspend), DEVMETHOD(device_resume, emac_resume), /* bus interface, for miibus */ DEVMETHOD(bus_print_child, bus_generic_print_child), DEVMETHOD(bus_driver_added, bus_generic_driver_added), /* MII interface */ DEVMETHOD(miibus_readreg, emac_miibus_readreg), DEVMETHOD(miibus_writereg, emac_miibus_writereg), DEVMETHOD(miibus_statchg, emac_miibus_statchg), DEVMETHOD_END }; static driver_t emac_driver = { "emac", emac_methods, sizeof(struct emac_softc) }; static devclass_t emac_devclass; DRIVER_MODULE(emac, simplebus, emac_driver, emac_devclass, 0, 0); DRIVER_MODULE(miibus, emac, miibus_driver, miibus_devclass, 0, 0); MODULE_DEPEND(emac, miibus, 1, 1, 1); MODULE_DEPEND(emac, ether, 1, 1, 1); static int sysctl_int_range(SYSCTL_HANDLER_ARGS, int low, int high) { int error, value; if (arg1 == NULL) return (EINVAL); value = *(int *)arg1; error = sysctl_handle_int(oidp, &value, 0, req); if (error || req->newptr == NULL) return (error); if (value < low || value > high) return (EINVAL); *(int *)arg1 = value; return (0); } static int sysctl_hw_emac_proc_limit(SYSCTL_HANDLER_ARGS) { return (sysctl_int_range(oidp, arg1, arg2, req, EMAC_PROC_MIN, EMAC_PROC_MAX)); }