/*- * Copyright (c) 2013 Ermal Luci * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* 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 AMDSB8_SMBUS_REVID 0x40 #define IOMUX_OFFSET 0xD00 #define GPIO_SPACE_OFFSET 0x100 #define GPIO_SPACE_SIZE 0x100 /* SB8xx RRG 2.3.3. */ #define AMDSB8_PM_WDT_EN 0x24 #define GPIO_187 187 // MODESW #define GPIO_189 189 // LED1# #define GPIO_190 190 // LED2# #define GPIO_191 191 // LED3# #define GPIO_OUTPUT 0x08 #define GPIO_INPUT 0x28 struct gpioapu_softc { device_t dev; struct cdev *cdev; struct resource *res_ctrl; struct resource *res_count; int rid_ctrl; int rid_count; }; static int gpioapu_open(struct cdev *dev, int flags, int fmt, struct thread *td); static int gpioapu_close(struct cdev *dev, int flags, int fmt, struct thread *td); static int gpioapu_write(struct cdev *dev, struct uio *uio, int ioflag); static int gpioapu_read(struct cdev *dev, struct uio *uio, int ioflag); static struct cdevsw gpioapu_cdevsw = { .d_version = D_VERSION, .d_open = gpioapu_open, .d_read = gpioapu_read, .d_write = gpioapu_write, .d_close = gpioapu_close, .d_name = "gpioapu", }; static void gpioapu_identify(driver_t *driver, device_t parent); static int gpioapu_probe(device_t dev); static int gpioapu_attach(device_t dev); static int gpioapu_detach(device_t dev); static device_method_t gpioapu_methods[] = { DEVMETHOD(device_identify, gpioapu_identify), DEVMETHOD(device_probe, gpioapu_probe), DEVMETHOD(device_attach, gpioapu_attach), DEVMETHOD(device_detach, gpioapu_detach), {0, 0} }; static devclass_t gpioapu_devclass; static driver_t gpioapu_driver = { "gpioapu", gpioapu_methods, sizeof(struct gpioapu_softc) }; DRIVER_MODULE(gpioapu, isa, gpioapu_driver, gpioapu_devclass, NULL, NULL); static uint8_t pmio_read(struct resource *res, uint8_t reg) { bus_write_1(res, 0, reg); /* Index */ return (bus_read_1(res, 1)); /* Data */ } #if 0 static void pmio_write(struct resource *res, uint8_t reg, uint8_t val) { bus_write_1(res, 0, reg); /* Index */ bus_write_1(res, 1, val); /* Data */ } #endif /* ARGSUSED */ static int gpioapu_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); if (error != 0) return (error); return (error); } static int gpioapu_read(struct cdev *dev, struct uio *uio, int ioflag) { struct gpioapu_softc *sc = dev->si_drv1; uint8_t tmp; char ch; int error; tmp = bus_read_1(sc->res_ctrl, GPIO_187); #ifdef DEBUG device_printf(sc->dev, "returned %x\n", (u_int)tmp); #endif if (tmp & 0x80) ch = '1'; else ch = '0'; error = uiomove(&ch, sizeof(ch), uio); return (error); } static int gpioapu_write(struct cdev *dev, struct uio *uio, int ioflag) { struct gpioapu_softc *sc = dev->si_drv1; char ch[3]; uint8_t old; int error, i, start; error = uiomove(ch, sizeof(ch), uio); if (error) return (error); start = GPIO_189; for (i = 0; i < 3; i++) { old = bus_read_1(sc->res_ctrl, start + i); #ifdef DEBUG device_printf(sc->dev, "returned %x - %c\n", (u_int)old, ch[i]); #endif if (ch[i] == '1') old &= 0x80; else old = 0xc8; bus_write_1(sc->res_ctrl, start + i, old); } return (error); } static int gpioapu_close(struct cdev *dev __unused, int flags __unused, int fmt __unused, struct thread *td) { struct gpioapu_softc *sc = dev->si_drv1; int i, start; start = GPIO_187; for (i = 0; i < 2; i++) { bus_write_1(sc->res_ctrl, start, 0xc8); } return (0); } static void gpioapu_identify(driver_t *driver, device_t parent) { device_t child; device_t smb_dev; if (resource_disabled("gpioapu", 0)) return; if (device_find_child(parent, "gpioapu", -1) != NULL) return; /* * Try to identify SB600/SB7xx by PCI Device ID of SMBus device * that should be present at bus 0, device 20, function 0. */ smb_dev = pci_find_bsf(0, 20, 0); if (smb_dev == NULL) return; if (pci_get_devid(smb_dev) != AMDSB_SMBUS_DEVID) return; child = BUS_ADD_CHILD(parent, ISA_ORDER_SPECULATIVE, "gpioapu", -1); if (child == NULL) device_printf(parent, "add gpioapu child failed\n"); } static int gpioapu_probe(device_t dev) { struct resource *res; uint32_t addr; int rid; int rc, i; char *value; value = getenv("smbios.system.product"); device_printf(dev, "Environment returned %s\n", value); if (value == NULL || strncmp(value, "APU", strlen("APU"))) return (ENXIO); /* Do not claim some ISA PnP device by accident. */ if (isa_get_logicalid(dev) != 0) return (ENXIO); 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 IO 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 IO failed\n"); return (ENXIO); } /* Find base address of memory mapped WDT registers. */ for (addr = 0, i = 0; i < 4; i++) { addr <<= 8; addr |= pmio_read(res, AMDSB8_PM_WDT_EN + 3 - i); } addr &= 0xFFFFF000; device_printf(dev, "Address on reg 0x24 is 0x%x/%u\n", addr, addr); bus_release_resource(dev, SYS_RES_IOPORT, rid, res); bus_delete_resource(dev, SYS_RES_IOPORT, rid); rc = bus_set_resource(dev, SYS_RES_MEMORY, 0, addr + GPIO_SPACE_OFFSET, GPIO_SPACE_SIZE); if (rc != 0) { device_printf(dev, "bus_set_resource for memory failed\n"); return (ENXIO); } return (0); } static int gpioapu_attach(device_t dev) { struct gpioapu_softc *sc; sc = device_get_softc(dev); sc->dev = dev; sc->rid_ctrl = 0; sc->res_ctrl = bus_alloc_resource(dev, SYS_RES_MEMORY, &sc->rid_ctrl, 0ul, ~0ul, GPIO_SPACE_SIZE, RF_ACTIVE | RF_SHAREABLE); if (sc->res_ctrl == NULL) { device_printf(dev, "bus_alloc_resource for memory failed\n"); return (ENXIO); } sc->dev = dev; sc->cdev = make_dev(&gpioapu_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "gpioapu"); sc->cdev->si_drv1 = sc; return (0); } static int gpioapu_detach(device_t dev) { struct gpioapu_softc *sc; sc = device_get_softc(dev); if (sc->res_ctrl != NULL) { bus_release_resource(dev, SYS_RES_MEMORY, sc->rid_ctrl, sc->res_ctrl); bus_delete_resource(dev, SYS_RES_MEMORY, sc->rid_ctrl); } destroy_dev(sc->cdev); return (0); }