/*- * 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 #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);