Index: sys/modules/Makefile =================================================================== --- sys/modules/Makefile +++ sys/modules/Makefile @@ -42,6 +42,7 @@ ${_an} \ ${_aout} \ ${_apm} \ + ${_apuled} \ ${_arcmsr} \ ${_allwinner} \ ${_armv8crypto} \ @@ -599,6 +600,7 @@ _agp= agp _an= an _aout= aout +_apuled= apuled _bios= bios .if ${MK_SOURCELESS_UCODE} != "no" _bxe= bxe Index: sys/modules/apuled/Makefile =================================================================== --- sys/modules/apuled/Makefile +++ sys/modules/apuled/Makefile @@ -0,0 +1,5 @@ +SRCS=apuled.c device_if.h bus_if.h isa_if.h pci_if.h + +KMOD= apuled + +.include Index: sys/modules/apuled/apuled.c =================================================================== --- sys/modules/apuled/apuled.c +++ sys/modules/apuled/apuled.c @@ -0,0 +1,690 @@ +/*- + * Copyright (c) 2014-2017 Larry Baird + * All rights reserved. + * + * Feedback provided by Ermal Luci. + * + * Used information from daduke's linux driver (https://daduke.org/linux/apu2) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if __FreeBSD_version < 1100000 +# define kern_getenv(a) getenv(a) +#endif // __FreeBSD_version < 1100000 + +static struct mtx gpio_lock; +MTX_SYSINIT(gpio_lock, &gpio_lock, "gpio lock", MTX_SPIN); + +/* + * Basic idea is to create two MMIO memory resources. One for LEDs and + * one for button on front of APU. Then create a device for each LED and + * the button. On an apu3 also create a device for switch for switching SIMs. + */ + +/* See dev/amdsbwd/amdsbwd.c for magic numbers for southbridges */ + +/* SB7xx RRG 2.3.3.1.1. */ +#define AMDSB_PMIO_INDEX 0xcd6 +#define AMDSB_PMIO_DATA (PMIO_INDEX + 1) +#define AMDSB_PMIO_WIDTH 2 + +#define AMDSB_SMBUS_DEVID 0x43851002 +#define AMDFCH_SMBUS_DEVID 0x780b1022 + +/* SB8xx RRG 2.3.7. */ +#define AMDSB8_MMIO_BASE_ADDR_FIND 0x24 + +/* Here are some magic numbers from APU1 BIOS. */ +#define SB_GPIO_OFFSET 0x100 +#define GPIO_187 187 // APU1 MODESW +#define GPIO_188 188 // APU1 Unknown ?? +#define GPIO_189 189 // APU1 LED1# +#define GPIO_190 190 // APU1 LED2# +#define GPIO_191 191 // APU1 LED3# + +#define SB_GPIO_ON 0x08 +#define SB_GPIO_OFF 0xC8 + +/* Here are some magic numbers for APU2. */ +#define AMDFCH41_MMIO_ADDR 0xfed80000u +#define FCH_GPIO_OFFSET 0x1500 +#define FCH_GPIO_BASE (AMDFCH41_MMIO_ADDR + FCH_GPIO_OFFSET) +#define FCH_GPIO_SIZE 0x300 +#define FCH_GPIO_BIT_WRITE 22 +#define FCH_GPIO_BIT_READ 16 +#define FCH_GPIO_BIT_DIR 23 +#define GPIO_68 68 // APU2/3 LED1# +#define GPIO_69 69 // APU2/3 LED2# +#define GPIO_70 70 // APU2/3 LED3# +#define GPIO_89 89 // APU2/3 MODESW +#define GPIO_90 90 // APU3 SIM switcher + +struct apu_cdev { + struct resource *res; + bus_size_t offset; + struct cdev *cdev; + uint32_t devid; +}; + +struct apu_rid { + int rid; + int rid_type; + struct resource *res; +}; + +struct apu_softc { + int sc_model; + uint32_t sc_devid; + struct apu_rid sc_rid[2]; +# define IDX_RID_LED 0 +# define IDX_RID_MODESW 1 + struct apu_cdev sc_led[3]; + struct apu_cdev sc_sw[2]; +# define IDX_SW_MODE 0 +# define IDX_SW_SIM 1 +}; + +/* + * Mode switch methods. + */ +static int sw_open(struct cdev *dev, int flags, int fmt, struct thread *td); +static int sw_close(struct cdev *dev, int flags, int fmt, struct thread *td); +static int sw_read(struct cdev *dev, struct uio *uio, int ioflag); +static int sw_write(struct cdev *dev, struct uio *uio, int ioflag); + +static void +apu_led_callback(void *ptr, int onoff); + +static struct cdevsw modesw_cdev = { + .d_version = D_VERSION, + .d_open = sw_open, + .d_read = sw_read, + .d_close = sw_close, + .d_name = "modesw", +}; + +static struct cdevsw simsw_cdev = { + .d_version = D_VERSION, + .d_open = sw_open, + .d_read = sw_read, + .d_write = sw_write, + .d_close = sw_close, + .d_name = "simsw", +}; + +static int +hw_is_apu( void ) +{ + int apu = 0; + char *maker; + char *product; + + maker = kern_getenv("smbios.system.maker"); + if (maker != NULL) { + if (strcasecmp("PC Engines", maker) == 0) { + product = kern_getenv("smbios.system.product"); + if (product != NULL) { + if (strcasecmp("APU", product) == 0) + apu = 1; + else if (strcasecmp("apu2", product) == 0) + apu = 2; + else if (strcasecmp("apu3", product) == 0) + apu = 3; + else if (strcasecmp("apu4", product) == 0) + apu = 4; + + freeenv(product); + } + } + + freeenv(maker); + } + + return (apu); +} + +static void +sb_gpio_write( struct resource *res, bus_size_t offset, int active ) +{ + u_int8_t value; + + value = bus_read_1(res, offset); + + if (active) + value = SB_GPIO_ON; + else + value = SB_GPIO_OFF; + + bus_write_1(res, offset, value); +} + +static char +sb_gpio_read( struct resource *res, bus_size_t offset ) +{ + uint8_t value; + char ch; + + /* Is mode switch pressed? */ + value = bus_read_1(res, offset); + + if (value == 0x28 ) + ch = '1'; + else + ch = '0'; + + return (ch); +} + +/* + * gpio methods. + */ +static void +fch_gpio_dir_set( struct resource *res, bus_size_t offset, int out ) +{ + u_int32_t value; + u_int32_t dir_bit = 1 << FCH_GPIO_BIT_DIR; + + value = bus_read_4(res, offset); + + if (out) + value |= dir_bit; + else + value &= ~dir_bit; + + bus_write_4(res, offset, value); +} + +static char +fch_gpio_read( struct resource *res, bus_size_t offset ) +{ + uint32_t value; + char ch; + u_int32_t read_bit = 1 << FCH_GPIO_BIT_READ; + + /* Is mode switch pressed? */ + value = bus_read_4(res, offset); + + if (!(value & read_bit)) + ch = '1'; + else + ch = '0'; + + return (ch); +} + +static void +fch_gpio_write( struct resource *res, bus_size_t offset, int active ) +{ + u_int32_t value; + u_int32_t write_bit = 1 << FCH_GPIO_BIT_WRITE; + + value = bus_read_4(res, offset); + + if (active) + value &= ~write_bit; + else + value |= write_bit; + + bus_write_4(res, offset, value); +} + + +/* Check to see if this is an APU board we support? */ +static void +apuled_identify(driver_t *driver, device_t parent) +{ + device_t child; + device_t smb; + uint32_t devid; + + if (resource_disabled("apuled", 0)) + return; + + if (device_find_child(parent, "apuled", -1) != NULL) + return; + + /* Do was have expected south bridge chipset? */ + smb = pci_find_bsf(0, 20, 0); + if (smb == NULL) + return; + + devid = pci_get_devid(smb); + + switch (hw_is_apu()) { + case 1: + if (devid != AMDSB_SMBUS_DEVID) + return; + break; + case 2: + case 3: + case 4: + if (devid != AMDFCH_SMBUS_DEVID) + return; + break; + default: + return; + } + + /* Everything looks good, enable probe */ + child = BUS_ADD_CHILD(parent, ISA_ORDER_SPECULATIVE, "apuled", -1); + if (child == NULL) + device_printf(parent, "apuled: bus add child failed\n"); +} + +static int +apu_probe_sb(device_t dev, struct apu_softc *sc) +{ + struct resource *res; + int rc; + uint32_t gpio_mmio_base; + int rid; + int i; + + /* Find the ACPImmioAddr base address */ + rc = bus_set_resource(dev, SYS_RES_IOPORT, 0, AMDSB_PMIO_INDEX, + AMDSB_PMIO_WIDTH); + if (rc != 0) { + device_printf(dev, "bus_set_resource for MMIO failed\n"); + return (ENXIO); + } + + rid = 0; + res = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0ul, ~0ul, + AMDSB_PMIO_WIDTH, RF_ACTIVE | RF_SHAREABLE); + + if (res == NULL) { + device_printf(dev, "bus_alloc_resource for MMIO failed.\n"); + return (ENXIO); + } + + /* Find base address of memory mapped WDT registers. */ + /* This will probable be 0xfed80000 */ + for (gpio_mmio_base = 0, i = 0; i < 4; i++) { + gpio_mmio_base <<= 8; + bus_write_1(res, 0, AMDSB8_MMIO_BASE_ADDR_FIND + 3 - i); + gpio_mmio_base |= bus_read_1(res, 1); + } + gpio_mmio_base &= ~0x07u; + + if (bootverbose) + device_printf(dev, "MMIO base adddress 0x%x\n", gpio_mmio_base); + + bus_release_resource(dev, SYS_RES_IOPORT, rid, res); + bus_delete_resource(dev, SYS_RES_IOPORT, rid); + + /* Set memory resource for LEDs. */ + rc = bus_set_resource(dev, SYS_RES_MEMORY, 0, + gpio_mmio_base + SB_GPIO_OFFSET + GPIO_189, + (GPIO_191 - GPIO_189) + 1); + if (rc != 0) { + device_printf(dev, "bus_set_resource for LEDs failed\n"); + return (ENXIO); + } + + /* Set memory resource for switches. */ + rc = bus_set_resource(dev, SYS_RES_MEMORY, 1, + gpio_mmio_base + SB_GPIO_OFFSET + GPIO_187, 1); + if (rc != 0) { + device_printf(dev, "bus_set_resource for switches failed\n"); + return (ENXIO); + } + + return (0); +} + +static int +apu_probe_fch(device_t dev, struct apu_softc *sc) +{ + int rc; + u_long count; + + /* Set memory resource for LEDs. */ + rc = bus_set_resource(dev, SYS_RES_MEMORY, 0, + FCH_GPIO_BASE + (GPIO_68 * sizeof(uint32_t)), + ((GPIO_70 - GPIO_68) + 1) * sizeof(uint32_t) ); + if (rc != 0) { + device_printf(dev, "bus_set_resource for LEDs failed\n"); + return (ENXIO); + } + + /* Set memory resource for switches. */ + if (sc->sc_model == 3) + count = sizeof(uint32_t) * 2; + else + count = sizeof(uint32_t); + rc = bus_set_resource(dev, SYS_RES_MEMORY, 1, + FCH_GPIO_BASE + (GPIO_89 * sizeof(uint32_t)), count ); + if (rc != 0) { + device_printf(dev, "bus_set_resource for switches failed\n"); + return (ENXIO); + } + + return (0); +} + +/* + * APU LED device methods. + */ +static int +apuled_probe(device_t dev) +{ + int error; + char buf[100]; + struct apu_softc *sc = device_get_softc(dev); + device_t smb; + + /* Make sure we do not claim some ISA PNP device. */ + if (isa_get_logicalid(dev) != 0) + return (ENXIO); + + sc->sc_model = hw_is_apu(); + if (sc->sc_model == 0) + return (ENXIO); + + smb = pci_find_bsf(0, 20, 0); + if (smb == NULL) + return (ENXIO); + + sc->sc_devid = pci_get_devid(smb); + + snprintf(buf, sizeof(buf), "APU%d", sc->sc_model); + device_set_desc_copy(dev, buf ); + + switch( sc->sc_devid ) { + case AMDSB_SMBUS_DEVID: + error = apu_probe_sb(dev, sc); + if (error) + return error; + break; + case AMDFCH_SMBUS_DEVID: + error = apu_probe_fch(dev, sc); + if (error) + return error; + break; + default: /* Should never reach here. */ + device_printf(dev, "Unexpected APU south bridge\n" ); + return (ENXIO); + break; + } + + return (0); +} + +static int +apuled_attach(device_t dev) +{ + struct apu_softc *sc = device_get_softc(dev); + int i; + int j; + + for (i = 0; i < sizeof(sc->sc_rid)/sizeof(sc->sc_rid[0]); i++) { + sc->sc_rid[i].res = NULL; + sc->sc_rid[i].rid_type = SYS_RES_MEMORY; + sc->sc_rid[i].rid = i; + } + + for (i = 0; i < sizeof(sc->sc_rid)/sizeof(sc->sc_rid[0]); i++) { + sc->sc_rid[i].res = bus_alloc_resource_any( dev, + sc->sc_rid[i].rid_type, &sc->sc_rid[i].rid, + RF_ACTIVE | RF_SHAREABLE); + if (sc->sc_rid[i].res == NULL) { + device_printf( dev, "Unable to allocate memory region %d\n", i ); + for (j = 0; j < i; j++) { + bus_release_resource(dev, sc->sc_rid[j].rid_type, + sc->sc_rid[j].rid, sc->sc_rid[j].res); + sc->sc_rid[j].res = NULL; + bus_delete_resource(dev, sc->sc_rid[i].rid_type, + sc->sc_rid[i].rid ); + } + return (ENXIO); + } + } + + if (sc->sc_devid == AMDFCH_SMBUS_DEVID) + fch_gpio_dir_set( sc->sc_rid[IDX_RID_MODESW].res, 0, FALSE ); + + for (i = 0; i < sizeof(sc->sc_sw)/sizeof(sc->sc_sw[0]); i++) + sc->sc_sw[i].cdev = NULL; + + sc->sc_sw[IDX_SW_MODE].cdev = make_dev(&modesw_cdev, 0, UID_ROOT, + GID_WHEEL, 0440, "modesw"); + if (sc->sc_sw[IDX_SW_MODE].cdev == NULL) { + device_printf( dev, "Unable to make modesw\n" ); + } else { + sc->sc_sw[IDX_SW_MODE].cdev->si_drv1 = &sc->sc_sw[IDX_SW_MODE]; + sc->sc_sw[IDX_SW_MODE].res = sc->sc_rid[IDX_RID_MODESW].res; + sc->sc_sw[IDX_SW_MODE].offset = 0; + sc->sc_sw[IDX_SW_MODE].devid = sc->sc_devid; + } + + if (sc->sc_model == 3) { + sc->sc_sw[IDX_SW_SIM].cdev = make_dev(&simsw_cdev, 0, UID_ROOT, + GID_WHEEL, 0440, "simsw"); + if (sc->sc_sw[IDX_SW_SIM].cdev == NULL) { + device_printf( dev, "Unable to make simsw\n" ); + } else { + sc->sc_sw[IDX_SW_SIM].cdev->si_drv1 = &sc->sc_sw[IDX_SW_SIM]; + sc->sc_sw[IDX_SW_SIM].res = sc->sc_rid[IDX_RID_MODESW].res; + sc->sc_sw[IDX_SW_SIM].offset = sizeof(uint32_t); + sc->sc_sw[IDX_SW_SIM].devid = sc->sc_devid; + } + } + + for (i = 0; i < sizeof(sc->sc_led)/sizeof(sc->sc_led[0]); i++) { + char name[30]; + + snprintf( name, sizeof(name), "led%d", i + 1 ); + + sc->sc_led[i].res = sc->sc_rid[IDX_RID_LED].res; + sc->sc_led[i].devid = sc->sc_devid; + + switch (sc->sc_devid) { + case AMDSB_SMBUS_DEVID: + sc->sc_led[i].offset = i; + break; + case AMDFCH_SMBUS_DEVID: + sc->sc_led[i].offset = i * sizeof(uint32_t); + fch_gpio_dir_set(sc->sc_led[i].res, + sc->sc_led[i].offset, TRUE); + break; + default: + break; + } + + /* Make sure power LED stays on by default */ + sc->sc_led[i].cdev = led_create_state(apu_led_callback, + &sc->sc_led[i], name, i == 0); + + if (sc->sc_led[i].cdev == NULL) + device_printf(dev, "%s creation failed\n", name); + } + + return (0); +} + +static int +apuled_detach(device_t dev) +{ + struct apu_softc *sc = device_get_softc(dev); + int i; + + for (i = 0; i < sizeof(sc->sc_led)/sizeof(sc->sc_led[0]); i++) + if (sc->sc_led[i].cdev != NULL) { + /* Restore LEDs to stating state */ + if (i == 0) + apu_led_callback(&sc->sc_led[i], TRUE); + else + apu_led_callback(&sc->sc_led[i], FALSE); + + led_destroy(sc->sc_led[i].cdev); + } + + for (i = 0; i < sizeof(sc->sc_sw)/sizeof(sc->sc_sw[0]); i++) + if (sc->sc_sw[i].cdev != NULL) + destroy_dev(sc->sc_sw[i].cdev); + + for (i = 0; i < sizeof(sc->sc_rid)/sizeof(sc->sc_rid[0]); i++) { + if (sc->sc_rid[i].res != NULL) { + bus_release_resource(dev, sc->sc_rid[i].rid_type, + sc->sc_rid[i].rid, sc->sc_rid[i].res); + bus_delete_resource(dev, sc->sc_rid[i].rid_type, + sc->sc_rid[i].rid ); + } + } + + return (0); +} + +static int +sw_open(struct cdev *dev __unused, int flags __unused, int fmt __unused, + struct thread *td) +{ + int error; + + error = priv_check(td, PRIV_IO); + if (error != 0) + return (error); + error = securelevel_gt(td->td_ucred, 0); + + return (error); +} + +static int +sw_read(struct cdev *dev, struct uio *uio, int ioflag) { + struct apu_cdev *sw = (struct apu_cdev *)dev->si_drv1; + char ch = '0'; + int error; + + mtx_lock_spin(&gpio_lock); + + switch (sw->devid) { + case AMDSB_SMBUS_DEVID: + ch = sb_gpio_read( sw->res, sw->offset ); + break; + case AMDFCH_SMBUS_DEVID: + fch_gpio_dir_set( sw->res, sw->offset, FALSE ); + ch = fch_gpio_read( sw->res, sw->offset ); + break; + default: + break; + } + + mtx_unlock_spin(&gpio_lock); + + error = uiomove(&ch, sizeof(ch), uio); + return (error); +} + +static int +sw_write(struct cdev *dev, struct uio *uio, int ioflag) { + struct apu_cdev *sw = (struct apu_cdev *)dev->si_drv1; + char ch; + int error; + + error = uiomove(&ch, sizeof(ch), uio); + if (error) + return (error); + + mtx_lock_spin(&gpio_lock); + + switch (sw->devid) { + case AMDSB_SMBUS_DEVID: + break; + case AMDFCH_SMBUS_DEVID: + fch_gpio_dir_set( sw->res, sw->offset, TRUE ); + fch_gpio_write(sw->res, sw->offset, ch); + break; + default: + break; + } + + mtx_unlock_spin(&gpio_lock); + + return (0); +} + +static int +sw_close(struct cdev *dev __unused, int flags __unused, int fmt __unused, + struct thread *td __unused) +{ + return (0); +} + +static void +apu_led_callback(void *ptr, int onoff) +{ + struct apu_cdev *led = (struct apu_cdev *)ptr; + + mtx_lock_spin(&gpio_lock); + + switch (led->devid) { + case AMDSB_SMBUS_DEVID: + sb_gpio_write( led->res, led->offset, onoff ); + break; + case AMDFCH_SMBUS_DEVID: + fch_gpio_write( led->res, led->offset, onoff ); + break; + default: + break; + } + + mtx_unlock_spin(&gpio_lock); +} + +static device_method_t apuled_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, apuled_probe), + DEVMETHOD(device_attach, apuled_attach), + DEVMETHOD(device_detach, apuled_detach), + DEVMETHOD(device_identify, apuled_identify), + + DEVMETHOD_END +}; + +static driver_t apuled_driver = { + "apuled", + apuled_methods, + sizeof(struct apu_softc), +}; + +static devclass_t apuled_devclass; +DRIVER_MODULE(apuled, isa, apuled_driver, apuled_devclass, NULL, NULL);