Index: stable/12/sys/dev/chromebook_platform/chromebook_platform.c =================================================================== --- stable/12/sys/dev/chromebook_platform/chromebook_platform.c (revision 355993) +++ stable/12/sys/dev/chromebook_platform/chromebook_platform.c (revision 355994) @@ -1,102 +1,102 @@ /*- * Copyright (c) 2016 The FreeBSD Project. * 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 COPYRIGHT HOLDERS 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 * COPYRIGHT HOLDERS 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include /* * Driver that attaches I2C devices. */ static struct { uint32_t pci_id; const char *name; uint8_t addr; } slaves[] = { { 0x9c628086, "isl", 0x88 }, { 0x9c618086, "cyapa", 0xce }, }; static void chromebook_i2c_identify(driver_t *driver, device_t bus) { device_t controller; device_t child; int i; /* * A stopgap approach to preserve the status quo. * A more intelligent approach is required to correctly * identify a machine model and hardware available on it. * For instance, DMI could be used. * See http://lxr.free-electrons.com/source/drivers/platform/chrome/chromeos_laptop.c */ controller = device_get_parent(bus); - if (strcmp(device_get_name(controller), "ig4iic_pci") != 0) + if (strcmp(device_get_name(controller), "ig4iic") != 0) return; for (i = 0; i < nitems(slaves); i++) { if (device_find_child(bus, slaves[i].name, -1) != NULL) continue; if (slaves[i].pci_id != pci_get_devid(controller)) continue; child = BUS_ADD_CHILD(bus, 0, slaves[i].name, -1); if (child != NULL) iicbus_set_addr(child, slaves[i].addr); } } static device_method_t chromebook_i2c_methods[] = { DEVMETHOD(device_identify, chromebook_i2c_identify), { 0, 0 } }; static driver_t chromebook_i2c_driver = { "chromebook_i2c", chromebook_i2c_methods, 0 /* no softc */ }; static devclass_t chromebook_i2c_devclass; DRIVER_MODULE(chromebook_i2c, iicbus, chromebook_i2c_driver, chromebook_i2c_devclass, 0, 0); MODULE_VERSION(chromebook_i2c, 1); Index: stable/12/sys/dev/cyapa/cyapa.c =================================================================== --- stable/12/sys/dev/cyapa/cyapa.c (revision 355993) +++ stable/12/sys/dev/cyapa/cyapa.c (revision 355994) @@ -1,1710 +1,1734 @@ /* * Copyright (c) 2014 The DragonFly Project. All rights reserved. * * This code is derived from software contributed to The DragonFly Project * by Matthew Dillon and was subsequently ported, * modified and enhanced for FreeBSD by Michael Gmelin . * * 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. * 3. Neither the name of The DragonFly Project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific, prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 * COPYRIGHT HOLDERS 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 __FBSDID("$FreeBSD$"); /* * CYAPA - Cypress APA trackpad with I2C Interface driver * * Based on DragonFlyBSD's cyapa driver, which referenced the linux * cyapa.c driver to figure out the bootstrapping and commands. * * Unable to locate any datasheet for the device. * * * Trackpad layout: * * 2/3 1/3 * +--------------------+------------+ * | | Middle | * | | Button | * | Left | | * | Button +------------+ * | | Right | * | | Button | * +--------------------+............| * | Thumb/Button Area | 15% * +---------------------------------+ * * * FEATURES * * IMPS/2 emulation - Emulates the IntelliMouse protocol. * * Jitter supression - Implements 2-pixel hysteresis with memory. * * Jump detecion - Detect jumps caused by touchpad. * * Two finger scrolling - Use two fingers for Z axis scrolling. * * Button down/2nd finger - While one finger clicks and holds down the * touchpad, the second one can be used to move * the mouse cursor. Useful for drawing or * selecting text. * * Thumb/Button Area - The lower 15%* of the trackpad will not affect * the mouse cursor position. This allows for high * precision clicking, by controlling the cursor * with the index finger and pushing/holding the * pad down with the thumb. * * can be changed using sysctl * * Track-pad button - Push physical button. Left 2/3rds of the pad * will issue a LEFT button event, upper right * corner will issue a MIDDLE button event, * lower right corner will issue a RIGHT button * event. Optional tap support can be enabled * and configured using sysctl. * * WARNINGS * * These trackpads get confused when three or more fingers are down on the * same horizontal axis and will start to glitch the finger detection. * Removing your hand for a few seconds will allow the trackpad to * recalibrate. Generally speaking, when using three or more fingers * please try to place at least one finger off-axis (a little above or * below) the other two. */ #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 "iicbus_if.h" #include "bus_if.h" #include "device_if.h" #define CYAPA_BUFSIZE 128 /* power of 2 */ #define CYAPA_BUFMASK (CYAPA_BUFSIZE - 1) #define ZSCALE 15 #define TIME_TO_IDLE (hz * 10) #define TIME_TO_RESET (hz * 3) static MALLOC_DEFINE(M_CYAPA, "cyapa", "CYAPA device data"); struct cyapa_fifo { int rindex; int windex; char buf[CYAPA_BUFSIZE]; }; struct cyapa_softc { device_t dev; int count; /* >0 if device opened */ struct cdev *devnode; struct selinfo selinfo; struct mtx mutex; + struct intr_config_hook intr_hook; int cap_resx; int cap_resy; int cap_phyx; int cap_phyy; uint8_t cap_buttons; int detaching; /* driver is detaching */ int poll_thread_running; /* poll thread is running */ /* PS/2 mouse emulation */ int track_x; /* current tracking */ int track_y; int track_z; int track_z_ticks; uint16_t track_but; char track_id; /* first finger id */ int track_nfingers; int delta_x; /* accumulation -> report */ int delta_y; int delta_z; int fuzz_x; int fuzz_y; int fuzz_z; int touch_x; /* touch down coordinates */ int touch_y; int touch_z; int finger1_ticks; int finger2_ticks; int finger3_ticks; uint16_t reported_but; struct cyapa_fifo rfifo; /* device->host */ struct cyapa_fifo wfifo; /* host->device */ uint8_t ps2_cmd; /* active p2_cmd waiting for data */ uint8_t ps2_acked; int active_tick; int data_signal; int blocked; int isselect; int reporting_mode; /* 0=disabled 1=enabled */ int scaling_mode; /* 0=1:1 1=2:1 */ int remote_mode; /* 0 for streaming mode */ int zenabled; /* z-axis enabled (mode 1 or 2) */ mousehw_t hw; /* hardware information */ mousemode_t mode; /* mode */ int poll_ticks; }; struct cyapa_cdevpriv { struct cyapa_softc *sc; }; #define CYPOLL_SHUTDOWN 0x0001 static void cyapa_poll_thread(void *arg); static int cyapa_raw_input(struct cyapa_softc *sc, struct cyapa_regs *regs, int freq); static void cyapa_set_power_mode(struct cyapa_softc *sc, int mode); static int fifo_empty(struct cyapa_softc *sc, struct cyapa_fifo *fifo); static size_t fifo_ready(struct cyapa_softc *sc, struct cyapa_fifo *fifo); static char *fifo_read(struct cyapa_softc *sc, struct cyapa_fifo *fifo, size_t n); static char *fifo_write(struct cyapa_softc *sc, struct cyapa_fifo *fifo, size_t n); static uint8_t fifo_read_char(struct cyapa_softc *sc, struct cyapa_fifo *fifo); static void fifo_write_char(struct cyapa_softc *sc, struct cyapa_fifo *fifo, uint8_t c); static size_t fifo_space(struct cyapa_softc *sc, struct cyapa_fifo *fifo); static void fifo_reset(struct cyapa_softc *sc, struct cyapa_fifo *fifo); static int cyapa_fuzz(int delta, int *fuzz); static int cyapa_idle_freq = 1; SYSCTL_INT(_debug, OID_AUTO, cyapa_idle_freq, CTLFLAG_RW, &cyapa_idle_freq, 0, "Scan frequency in idle mode"); static int cyapa_slow_freq = 20; SYSCTL_INT(_debug, OID_AUTO, cyapa_slow_freq, CTLFLAG_RW, &cyapa_slow_freq, 0, "Scan frequency in slow mode "); static int cyapa_norm_freq = 100; SYSCTL_INT(_debug, OID_AUTO, cyapa_norm_freq, CTLFLAG_RW, &cyapa_norm_freq, 0, "Normal scan frequency"); static int cyapa_minpressure = 12; SYSCTL_INT(_debug, OID_AUTO, cyapa_minpressure, CTLFLAG_RW, &cyapa_minpressure, 0, "Minimum pressure to detect finger"); static int cyapa_enable_tapclick = 0; SYSCTL_INT(_debug, OID_AUTO, cyapa_enable_tapclick, CTLFLAG_RW, &cyapa_enable_tapclick, 0, "Enable tap to click"); static int cyapa_tapclick_min_ticks = 1; SYSCTL_INT(_debug, OID_AUTO, cyapa_tapclick_min_ticks, CTLFLAG_RW, &cyapa_tapclick_min_ticks, 0, "Minimum tap duration for click"); static int cyapa_tapclick_max_ticks = 8; SYSCTL_INT(_debug, OID_AUTO, cyapa_tapclick_max_ticks, CTLFLAG_RW, &cyapa_tapclick_max_ticks, 0, "Maximum tap duration for click"); static int cyapa_move_min_ticks = 4; SYSCTL_INT(_debug, OID_AUTO, cyapa_move_min_ticks, CTLFLAG_RW, &cyapa_move_min_ticks, 0, "Minimum ticks before cursor position is changed"); static int cyapa_scroll_wait_ticks = 0; SYSCTL_INT(_debug, OID_AUTO, cyapa_scroll_wait_ticks, CTLFLAG_RW, &cyapa_scroll_wait_ticks, 0, "Wait N ticks before starting to scroll"); static int cyapa_scroll_stick_ticks = 15; SYSCTL_INT(_debug, OID_AUTO, cyapa_scroll_stick_ticks, CTLFLAG_RW, &cyapa_scroll_stick_ticks, 0, "Prevent cursor move on single finger for N ticks after scroll"); static int cyapa_thumbarea_percent = 15; SYSCTL_INT(_debug, OID_AUTO, cyapa_thumbarea_percent, CTLFLAG_RW, &cyapa_thumbarea_percent, 0, "Size of bottom thumb area in percent"); static int cyapa_debug = 0; SYSCTL_INT(_debug, OID_AUTO, cyapa_debug, CTLFLAG_RW, &cyapa_debug, 0, "Enable debugging"); static int cyapa_reset = 0; SYSCTL_INT(_debug, OID_AUTO, cyapa_reset, CTLFLAG_RW, &cyapa_reset, 0, "Reset track pad"); static int cyapa_read_bytes(device_t dev, uint8_t reg, uint8_t *val, int cnt) { uint16_t addr = iicbus_get_addr(dev); struct iic_msg msgs[] = { { addr, IIC_M_WR | IIC_M_NOSTOP, 1, ® }, { addr, IIC_M_RD, cnt, val }, }; return (iicbus_transfer(dev, msgs, nitems(msgs))); } static int cyapa_write_bytes(device_t dev, uint8_t reg, const uint8_t *val, int cnt) { uint16_t addr = iicbus_get_addr(dev); struct iic_msg msgs[] = { { addr, IIC_M_WR | IIC_M_NOSTOP, 1, ® }, { addr, IIC_M_WR | IIC_M_NOSTART, cnt, __DECONST(uint8_t *, val) }, }; return (iicbus_transfer(dev, msgs, nitems(msgs))); } static void cyapa_lock(struct cyapa_softc *sc) { mtx_lock(&sc->mutex); } static void cyapa_unlock(struct cyapa_softc *sc) { mtx_unlock(&sc->mutex); } #define CYAPA_LOCK_ASSERT(sc) mtx_assert(&(sc)->mutex, MA_OWNED); /* * Notify if possible receive data ready. Must be called * with sc->mutex held (cyapa_lock(sc)). */ static void cyapa_notify(struct cyapa_softc *sc) { CYAPA_LOCK_ASSERT(sc); if (sc->data_signal || !fifo_empty(sc, &sc->rfifo)) { KNOTE_LOCKED(&sc->selinfo.si_note, 0); if (sc->blocked || sc->isselect) { if (sc->blocked) { sc->blocked = 0; wakeup(&sc->blocked); } if (sc->isselect) { sc->isselect = 0; selwakeup(&sc->selinfo); } } } } /* * Initialize the device */ static int init_device(device_t dev, struct cyapa_cap *cap, int probe) { static char bl_exit[] = { 0x00, 0xff, 0xa5, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 }; static char bl_deactivate[] = { 0x00, 0xff, 0x3b, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 }; struct cyapa_boot_regs boot; int error; int retries; /* Get status */ error = cyapa_read_bytes(dev, CMD_BOOT_STATUS, (void *)&boot, sizeof(boot)); if (error) goto done; /* * Bootstrap the device if necessary. It can take up to 2 seconds * for the device to fully initialize. */ retries = 20; while ((boot.stat & CYAPA_STAT_RUNNING) == 0 && retries > 0) { if (boot.boot & CYAPA_BOOT_BUSY) { /* Busy, wait loop. */ } else if (boot.error & CYAPA_ERROR_BOOTLOADER) { /* Magic */ error = cyapa_write_bytes(dev, CMD_BOOT_STATUS, bl_deactivate, sizeof(bl_deactivate)); if (error) goto done; } else { /* Magic */ error = cyapa_write_bytes(dev, CMD_BOOT_STATUS, bl_exit, sizeof(bl_exit)); if (error) goto done; } pause("cyapab1", (hz * 2) / 10); --retries; error = cyapa_read_bytes(dev, CMD_BOOT_STATUS, (void *)&boot, sizeof(boot)); if (error) goto done; } if (retries == 0) { device_printf(dev, "Unable to bring device out of bootstrap\n"); error = ENXIO; goto done; } /* Check identity */ if (cap) { error = cyapa_read_bytes(dev, CMD_QUERY_CAPABILITIES, (void *)cap, sizeof(*cap)); if (strncmp(cap->prod_ida, "CYTRA", 5) != 0) { device_printf(dev, "Product ID \"%5.5s\" mismatch\n", cap->prod_ida); error = ENXIO; } } error = cyapa_read_bytes(dev, CMD_BOOT_STATUS, (void *)&boot, sizeof(boot)); if (probe == 0) /* official init */ device_printf(dev, "cyapa init status %02x\n", boot.stat); else if (probe == 2) device_printf(dev, "cyapa reset status %02x\n", boot.stat); done: if (error) device_printf(dev, "Unable to initialize\n"); return (error); } +/* + * Start the polling thread + */ +static void +cyapa_start(void *xdev) +{ + struct cyapa_softc *sc; + device_t dev = xdev; + + sc = device_get_softc(dev); + + config_intrhook_disestablish(&sc->intr_hook); + + /* Setup input event tracking */ + cyapa_set_power_mode(sc, CMD_POWER_MODE_IDLE); + + /* Start the polling thread */ + kthread_add(cyapa_poll_thread, sc, NULL, NULL, + 0, 0, "cyapa-poll"); +} + static int cyapa_probe(device_t); static int cyapa_attach(device_t); static int cyapa_detach(device_t); static void cyapa_cdevpriv_dtor(void*); static devclass_t cyapa_devclass; static device_method_t cyapa_methods[] = { /* device interface */ DEVMETHOD(device_probe, cyapa_probe), DEVMETHOD(device_attach, cyapa_attach), DEVMETHOD(device_detach, cyapa_detach), DEVMETHOD_END }; static driver_t cyapa_driver = { "cyapa", cyapa_methods, sizeof(struct cyapa_softc), }; static d_open_t cyapaopen; static d_ioctl_t cyapaioctl; static d_read_t cyaparead; static d_write_t cyapawrite; static d_kqfilter_t cyapakqfilter; static d_poll_t cyapapoll; static struct cdevsw cyapa_cdevsw = { .d_version = D_VERSION, .d_open = cyapaopen, .d_ioctl = cyapaioctl, .d_read = cyaparead, .d_write = cyapawrite, .d_kqfilter = cyapakqfilter, .d_poll = cyapapoll, }; static int cyapa_probe(device_t dev) { struct cyapa_cap cap; int addr; int error; addr = iicbus_get_addr(dev); /* * 0x67 - cypress trackpad on the acer c720 * (other devices might use other ids). */ if (addr != 0xce) return (ENXIO); error = init_device(dev, &cap, 1); if (error != 0) return (ENXIO); device_set_desc(dev, "Cypress APA I2C Trackpad"); return (BUS_PROBE_VENDOR); } static int cyapa_attach(device_t dev) { struct cyapa_softc *sc; struct cyapa_cap cap; int unit; int addr; sc = device_get_softc(dev); sc->reporting_mode = 1; unit = device_get_unit(dev); addr = iicbus_get_addr(dev); if (init_device(dev, &cap, 0)) return (ENXIO); mtx_init(&sc->mutex, "cyapa", NULL, MTX_DEF); sc->dev = dev; knlist_init_mtx(&sc->selinfo.si_note, &sc->mutex); sc->cap_resx = ((cap.max_abs_xy_high << 4) & 0x0F00) | cap.max_abs_x_low; sc->cap_resy = ((cap.max_abs_xy_high << 8) & 0x0F00) | cap.max_abs_y_low; sc->cap_phyx = ((cap.phy_siz_xy_high << 4) & 0x0F00) | cap.phy_siz_x_low; sc->cap_phyy = ((cap.phy_siz_xy_high << 8) & 0x0F00) | cap.phy_siz_y_low; sc->cap_buttons = cap.buttons; device_printf(dev, "%5.5s-%6.6s-%2.2s buttons=%c%c%c res=%dx%d\n", cap.prod_ida, cap.prod_idb, cap.prod_idc, ((cap.buttons & CYAPA_FNGR_LEFT) ? 'L' : '-'), ((cap.buttons & CYAPA_FNGR_MIDDLE) ? 'M' : '-'), ((cap.buttons & CYAPA_FNGR_RIGHT) ? 'R' : '-'), sc->cap_resx, sc->cap_resy); sc->hw.buttons = 5; sc->hw.iftype = MOUSE_IF_PS2; sc->hw.type = MOUSE_MOUSE; sc->hw.model = MOUSE_MODEL_INTELLI; sc->hw.hwid = addr; sc->mode.protocol = MOUSE_PROTO_PS2; sc->mode.rate = 100; sc->mode.resolution = 4; sc->mode.accelfactor = 1; sc->mode.level = 0; sc->mode.packetsize = MOUSE_PS2_PACKETSIZE; - /* Setup input event tracking */ - cyapa_set_power_mode(sc, CMD_POWER_MODE_IDLE); + sc->intr_hook.ich_func = cyapa_start; + sc->intr_hook.ich_arg = sc->dev; - /* Start the polling thread */ - kthread_add(cyapa_poll_thread, sc, NULL, NULL, - 0, 0, "cyapa-poll"); + /* Postpone start of the polling thread until sleep is available */ + if (config_intrhook_establish(&sc->intr_hook) != 0) { + mtx_destroy(&sc->mutex); + return (ENOMEM); + } sc->devnode = make_dev(&cyapa_cdevsw, unit, UID_ROOT, GID_WHEEL, 0600, "cyapa%d", unit); sc->devnode->si_drv1 = sc; return (0); } static int cyapa_detach(device_t dev) { struct cyapa_softc *sc; sc = device_get_softc(dev); /* Cleanup poller thread */ cyapa_lock(sc); while (sc->poll_thread_running) { sc->detaching = 1; mtx_sleep(&sc->detaching, &sc->mutex, PCATCH, "cyapadet", hz); } cyapa_unlock(sc); destroy_dev(sc->devnode); knlist_clear(&sc->selinfo.si_note, 0); seldrain(&sc->selinfo); knlist_destroy(&sc->selinfo.si_note); mtx_destroy(&sc->mutex); return (0); } /* * USER DEVICE I/O FUNCTIONS */ static int cyapaopen(struct cdev *dev, int oflags, int devtype, struct thread *td) { struct cyapa_cdevpriv *priv; int error; priv = malloc(sizeof(*priv), M_CYAPA, M_WAITOK | M_ZERO); priv->sc = dev->si_drv1; error = devfs_set_cdevpriv(priv, cyapa_cdevpriv_dtor); if (error == 0) { cyapa_lock(priv->sc); priv->sc->count++; cyapa_unlock(priv->sc); } else free(priv, M_CYAPA); return (error); } static void cyapa_cdevpriv_dtor(void *data) { struct cyapa_cdevpriv *priv; priv = data; KASSERT(priv != NULL, ("cyapa cdevpriv should not be NULL!")); cyapa_lock(priv->sc); priv->sc->count--; cyapa_unlock(priv->sc); free(priv, M_CYAPA); } static int cyaparead(struct cdev *dev, struct uio *uio, int ioflag) { struct cyapa_softc *sc; int error; int didread; size_t n; char* ptr; sc = dev->si_drv1; /* If buffer is empty, load a new event if it is ready */ cyapa_lock(sc); again: if (fifo_empty(sc, &sc->rfifo) && (sc->data_signal || sc->delta_x || sc->delta_y || sc->track_but != sc->reported_but)) { uint8_t c0; uint16_t but; int delta_x; int delta_y; int delta_z; /* Accumulate delta_x, delta_y */ sc->data_signal = 0; delta_x = sc->delta_x; delta_y = sc->delta_y; delta_z = sc->delta_z; if (delta_x > 255) { delta_x = 255; sc->data_signal = 1; } if (delta_x < -256) { delta_x = -256; sc->data_signal = 1; } if (delta_y > 255) { delta_y = 255; sc->data_signal = 1; } if (delta_y < -256) { delta_y = -256; sc->data_signal = 1; } if (delta_z > 255) { delta_z = 255; sc->data_signal = 1; } if (delta_z < -256) { delta_z = -256; sc->data_signal = 1; } but = sc->track_but; /* Adjust baseline for next calculation */ sc->delta_x -= delta_x; sc->delta_y -= delta_y; sc->delta_z -= delta_z; sc->reported_but = but; /* * Fuzz reduces movement jitter by introducing some * hysteresis. It operates without cumulative error so * if you swish around quickly and return your finger to * where it started, so to will the mouse. */ delta_x = cyapa_fuzz(delta_x, &sc->fuzz_x); delta_y = cyapa_fuzz(delta_y, &sc->fuzz_y); delta_z = cyapa_fuzz(delta_z, &sc->fuzz_z); /* * Generate report */ c0 = 0; if (delta_x < 0) c0 |= 0x10; if (delta_y < 0) c0 |= 0x20; c0 |= 0x08; if (but & CYAPA_FNGR_LEFT) c0 |= 0x01; if (but & CYAPA_FNGR_MIDDLE) c0 |= 0x04; if (but & CYAPA_FNGR_RIGHT) c0 |= 0x02; fifo_write_char(sc, &sc->rfifo, c0); fifo_write_char(sc, &sc->rfifo, (uint8_t)delta_x); fifo_write_char(sc, &sc->rfifo, (uint8_t)delta_y); switch(sc->zenabled) { case 1: /* Z axis all 8 bits */ fifo_write_char(sc, &sc->rfifo, (uint8_t)delta_z); break; case 2: /* * Z axis low 4 bits + 4th button and 5th button * (high 2 bits must be left 0). Auto-scale * delta_z to fit to avoid a wrong-direction * overflow (don't try to retain the remainder). */ while (delta_z > 7 || delta_z < -8) delta_z >>= 1; c0 = (uint8_t)delta_z & 0x0F; fifo_write_char(sc, &sc->rfifo, c0); break; default: /* basic PS/2 */ break; } cyapa_notify(sc); } /* Blocking / Non-blocking */ error = 0; didread = (uio->uio_resid == 0); while ((ioflag & IO_NDELAY) == 0 && fifo_empty(sc, &sc->rfifo)) { if (sc->data_signal) goto again; sc->blocked = 1; error = mtx_sleep(&sc->blocked, &sc->mutex, PCATCH, "cyablk", 0); if (error) break; } /* Return any buffered data */ while (error == 0 && uio->uio_resid && (n = fifo_ready(sc, &sc->rfifo)) > 0) { if (n > uio->uio_resid) n = uio->uio_resid; ptr = fifo_read(sc, &sc->rfifo, 0); cyapa_unlock(sc); error = uiomove(ptr, n, uio); cyapa_lock(sc); if (error) break; fifo_read(sc, &sc->rfifo, n); didread = 1; } cyapa_unlock(sc); if (error == 0 && didread == 0) { error = EWOULDBLOCK; } return (didread ? 0 : error); } static int cyapawrite(struct cdev *dev, struct uio *uio, int ioflag) { struct cyapa_softc *sc; int error; int cmd_completed; size_t n; uint8_t c0; char* ptr; sc = dev->si_drv1; again: /* * Copy data from userland. This will also cross-over the end * of the fifo and keep filling. */ cyapa_lock(sc); while ((n = fifo_space(sc, &sc->wfifo)) > 0 && uio->uio_resid) { if (n > uio->uio_resid) n = uio->uio_resid; ptr = fifo_write(sc, &sc->wfifo, 0); cyapa_unlock(sc); error = uiomove(ptr, n, uio); cyapa_lock(sc); if (error) break; fifo_write(sc, &sc->wfifo, n); } /* Handle commands */ cmd_completed = (fifo_ready(sc, &sc->wfifo) != 0); while (fifo_ready(sc, &sc->wfifo) && cmd_completed && error == 0) { if (sc->ps2_cmd == 0) sc->ps2_cmd = fifo_read_char(sc, &sc->wfifo); switch(sc->ps2_cmd) { case 0xE6: /* SET SCALING 1:1 */ sc->scaling_mode = 0; fifo_write_char(sc, &sc->rfifo, 0xFA); break; case 0xE7: /* SET SCALING 2:1 */ sc->scaling_mode = 1; fifo_write_char(sc, &sc->rfifo, 0xFA); break; case 0xE8: /* SET RESOLUTION +1 byte */ if (sc->ps2_acked == 0) { sc->ps2_acked = 1; fifo_write_char(sc, &sc->rfifo, 0xFA); } if (fifo_ready(sc, &sc->wfifo) == 0) { cmd_completed = 0; break; } sc->mode.resolution = fifo_read_char(sc, &sc->wfifo); fifo_write_char(sc, &sc->rfifo, 0xFA); break; case 0xE9: /* * STATUS REQUEST * * byte1: * bit 7 0 * bit 6 Mode (1=remote mode, 0=stream mode) * bit 5 Enable (data reporting enabled) * bit 4 Scaling (0=1:1 1=2:1) * bit 3 0 * bit 2 LEFT BUTTON (1 if pressed) * bit 1 MIDDLE BUTTON (1 if pressed) * bit 0 RIGHT BUTTON (1 if pressed) * * byte2: resolution counts/mm * byte3: sample rate */ c0 = 0; if (sc->remote_mode) c0 |= 0x40; if (sc->reporting_mode) c0 |= 0x20; if (sc->scaling_mode) c0 |= 0x10; if (sc->track_but & CYAPA_FNGR_LEFT) c0 |= 0x04; if (sc->track_but & CYAPA_FNGR_MIDDLE) c0 |= 0x02; if (sc->track_but & CYAPA_FNGR_RIGHT) c0 |= 0x01; fifo_write_char(sc, &sc->rfifo, 0xFA); fifo_write_char(sc, &sc->rfifo, c0); fifo_write_char(sc, &sc->rfifo, 0x00); fifo_write_char(sc, &sc->rfifo, 100); break; case 0xEA: /* Set stream mode and reset movement counters */ sc->remote_mode = 0; fifo_write_char(sc, &sc->rfifo, 0xFA); sc->delta_x = 0; sc->delta_y = 0; sc->delta_z = 0; break; case 0xEB: /* * Read Data (if in remote mode). If not in remote * mode force an event. */ fifo_write_char(sc, &sc->rfifo, 0xFA); sc->data_signal = 1; break; case 0xEC: /* Reset Wrap Mode (ignored) */ fifo_write_char(sc, &sc->rfifo, 0xFA); break; case 0xEE: /* Set Wrap Mode (ignored) */ fifo_write_char(sc, &sc->rfifo, 0xFA); break; case 0xF0: /* Set Remote Mode */ sc->remote_mode = 1; fifo_write_char(sc, &sc->rfifo, 0xFA); sc->delta_x = 0; sc->delta_y = 0; sc->delta_z = 0; break; case 0xF2: /* * Get Device ID * * If we send 0x00 - normal PS/2 mouse, no Z-axis * * If we send 0x03 - Intellimouse, data packet has * an additional Z movement byte (8 bits signed). * (also reset movement counters) * * If we send 0x04 - Now includes z-axis and the * 4th and 5th mouse buttons. */ fifo_write_char(sc, &sc->rfifo, 0xFA); switch(sc->zenabled) { case 1: fifo_write_char(sc, &sc->rfifo, 0x03); break; case 2: fifo_write_char(sc, &sc->rfifo, 0x04); break; default: fifo_write_char(sc, &sc->rfifo, 0x00); break; } sc->delta_x = 0; sc->delta_y = 0; sc->delta_z = 0; break; case 0xF3: /* * Set Sample Rate * * byte1: the sample rate */ if (sc->ps2_acked == 0) { sc->ps2_acked = 1; fifo_write_char(sc, &sc->rfifo, 0xFA); } if (fifo_ready(sc, &sc->wfifo) == 0) { cmd_completed = 0; break; } sc->mode.rate = fifo_read_char(sc, &sc->wfifo); fifo_write_char(sc, &sc->rfifo, 0xFA); /* * zenabling sequence: 200,100,80 (device id 0x03) * 200,200,80 (device id 0x04) * * We support id 0x03 (no 4th or 5th button). * We support id 0x04 (w/ 4th and 5th button). */ if (sc->zenabled == 0 && sc->mode.rate == 200) sc->zenabled = -1; else if (sc->zenabled == -1 && sc->mode.rate == 100) sc->zenabled = -2; else if (sc->zenabled == -1 && sc->mode.rate == 200) sc->zenabled = -3; else if (sc->zenabled == -2 && sc->mode.rate == 80) sc->zenabled = 1; /* z-axis mode */ else if (sc->zenabled == -3 && sc->mode.rate == 80) sc->zenabled = 2; /* z-axis+but4/5 */ if (sc->mode.level) sc->zenabled = 1; break; case 0xF4: /* Enable data reporting. Only effects stream mode. */ fifo_write_char(sc, &sc->rfifo, 0xFA); sc->reporting_mode = 1; break; case 0xF5: /* * Disable data reporting. Only effects stream mode * and is ignored right now. */ fifo_write_char(sc, &sc->rfifo, 0xFA); sc->reporting_mode = 1; break; case 0xF6: /* * SET DEFAULTS * * (reset sampling rate, resolution, scaling and * enter stream mode) */ fifo_write_char(sc, &sc->rfifo, 0xFA); sc->mode.rate = 100; sc->mode.resolution = 4; sc->scaling_mode = 0; sc->reporting_mode = 1; sc->remote_mode = 0; sc->delta_x = 0; sc->delta_y = 0; sc->delta_z = 0; /* signal */ break; case 0xFE: /* * RESEND * * Force a resend by guaranteeing that reported_but * differs from track_but. */ fifo_write_char(sc, &sc->rfifo, 0xFA); sc->data_signal = 1; break; case 0xFF: /* * RESET */ fifo_reset(sc, &sc->rfifo); /* should we do this? */ fifo_reset(sc, &sc->wfifo); /* should we do this? */ fifo_write_char(sc, &sc->rfifo, 0xFA); sc->delta_x = 0; sc->delta_y = 0; sc->delta_z = 0; sc->zenabled = 0; sc->mode.level = 0; break; default: printf("unknown command %02x\n", sc->ps2_cmd); break; } if (cmd_completed) { sc->ps2_cmd = 0; sc->ps2_acked = 0; } cyapa_notify(sc); } cyapa_unlock(sc); if (error == 0 && (cmd_completed || uio->uio_resid)) goto again; return (error); } static void cyapafiltdetach(struct knote *); static int cyapafilt(struct knote *, long); static struct filterops cyapa_filtops = { .f_isfd = 1, .f_detach = cyapafiltdetach, .f_event = cyapafilt }; static int cyapakqfilter(struct cdev *dev, struct knote *kn) { struct cyapa_softc *sc; struct knlist *knlist; sc = dev->si_drv1; switch(kn->kn_filter) { case EVFILT_READ: kn->kn_fop = &cyapa_filtops; kn->kn_hook = (void *)sc; break; default: return (EOPNOTSUPP); } knlist = &sc->selinfo.si_note; knlist_add(knlist, kn, 0); return (0); } static int cyapapoll(struct cdev *dev, int events, struct thread *td) { struct cyapa_softc *sc; int revents; sc = dev->si_drv1; revents = 0; cyapa_lock(sc); if (events & (POLLIN | POLLRDNORM)) { if (sc->data_signal || !fifo_empty(sc, &sc->rfifo)) revents = events & (POLLIN | POLLRDNORM); else { sc->isselect = 1; selrecord(td, &sc->selinfo); } } cyapa_unlock(sc); return (revents); } static void cyapafiltdetach(struct knote *kn) { struct cyapa_softc *sc; struct knlist *knlist; sc = (struct cyapa_softc *)kn->kn_hook; knlist = &sc->selinfo.si_note; knlist_remove(knlist, kn, 0); } static int cyapafilt(struct knote *kn, long hint) { struct cyapa_softc *sc; int ready; sc = (struct cyapa_softc *)kn->kn_hook; cyapa_lock(sc); ready = fifo_ready(sc, &sc->rfifo) || sc->data_signal; cyapa_unlock(sc); return (ready); } static int cyapaioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) { struct cyapa_softc *sc; int error; sc = dev->si_drv1; error = 0; cyapa_lock(sc); switch (cmd) { case MOUSE_GETHWINFO: *(mousehw_t *)data = sc->hw; if (sc->mode.level == 0) ((mousehw_t *)data)->model = MOUSE_MODEL_GENERIC; break; case MOUSE_GETMODE: *(mousemode_t *)data = sc->mode; ((mousemode_t *)data)->resolution = MOUSE_RES_LOW - sc->mode.resolution; switch (sc->mode.level) { case 0: ((mousemode_t *)data)->protocol = MOUSE_PROTO_PS2; ((mousemode_t *)data)->packetsize = MOUSE_PS2_PACKETSIZE; break; case 2: ((mousemode_t *)data)->protocol = MOUSE_PROTO_PS2; ((mousemode_t *)data)->packetsize = MOUSE_PS2_PACKETSIZE + 1; break; } break; case MOUSE_GETLEVEL: *(int *)data = sc->mode.level; break; case MOUSE_SETLEVEL: if ((*(int *)data < 0) && (*(int *)data > 2)) { error = EINVAL; break; } sc->mode.level = *(int *)data ? 2 : 0; sc->zenabled = sc->mode.level ? 1 : 0; break; default: error = ENOTTY; break; } cyapa_unlock(sc); return (error); } /* * MAJOR SUPPORT FUNCTIONS */ static void cyapa_poll_thread(void *arg) { struct cyapa_softc *sc; struct cyapa_regs regs; device_t bus; /* iicbus */ int error; int freq; int isidle; int pstate; int npstate; int last_reset; sc = arg; freq = cyapa_norm_freq; isidle = 0; pstate = CMD_POWER_MODE_IDLE; last_reset = ticks; bus = device_get_parent(sc->dev); cyapa_lock(sc); sc->poll_thread_running = 1; while (!sc->detaching) { cyapa_unlock(sc); error = iicbus_request_bus(bus, sc->dev, IIC_WAIT); if (error == 0) { error = cyapa_read_bytes(sc->dev, CMD_DEV_STATUS, (void *)®s, sizeof(regs)); if (error == 0) { isidle = cyapa_raw_input(sc, ®s, freq); } /* * For some reason the device can crap-out. If it * drops back into bootstrap mode try to reinitialize * it. */ if (cyapa_reset || ((regs.stat & CYAPA_STAT_RUNNING) == 0 && (unsigned)(ticks - last_reset) > TIME_TO_RESET)) { cyapa_reset = 0; last_reset = ticks; init_device(sc->dev, NULL, 2); } iicbus_release_bus(bus, sc->dev); } pause("cyapw", hz / freq); ++sc->poll_ticks; if (sc->count == 0) { freq = cyapa_idle_freq; npstate = CMD_POWER_MODE_IDLE; } else if (isidle) { freq = cyapa_slow_freq; npstate = CMD_POWER_MODE_IDLE; } else { freq = cyapa_norm_freq; npstate = CMD_POWER_MODE_FULL; } if (pstate != npstate) { pstate = npstate; cyapa_set_power_mode(sc, pstate); if (cyapa_debug) { switch(pstate) { case CMD_POWER_MODE_OFF: printf("cyapa: power off\n"); break; case CMD_POWER_MODE_IDLE: printf("cyapa: power idle\n"); break; case CMD_POWER_MODE_FULL: printf("cyapa: power full\n"); break; } } } cyapa_lock(sc); } sc->poll_thread_running = 0; cyapa_unlock(sc); kthread_exit(); } static int cyapa_raw_input(struct cyapa_softc *sc, struct cyapa_regs *regs, int freq) { int nfingers; int afingers; /* actual fingers after culling */ int i; int j; int k; int isidle; int thumbarea_begin; int seen_thumb; int x; int y; int z; int newfinger; int lessfingers; int click_x; int click_y; uint16_t but; /* high bits used for simulated but4/but5 */ thumbarea_begin = sc->cap_resy - ((sc->cap_resy * cyapa_thumbarea_percent) / 100); click_x = click_y = 0; /* * If the device is not running the rest of the status * means something else, set fingers to 0. */ if ((regs->stat & CYAPA_STAT_RUNNING) == 0) { regs->fngr = 0; } /* Process fingers/movement */ nfingers = CYAPA_FNGR_NUMFINGERS(regs->fngr); afingers = nfingers; if (cyapa_debug) { printf("stat %02x buttons %c%c%c nfngrs=%d ", regs->stat, ((regs->fngr & CYAPA_FNGR_LEFT) ? 'L' : '-'), ((regs->fngr & CYAPA_FNGR_MIDDLE) ? 'M' : '-'), ((regs->fngr & CYAPA_FNGR_RIGHT) ? 'R' : '-'), nfingers); } seen_thumb = 0; for (i = 0; i < afingers; ) { if (cyapa_debug) { printf(" [x=%04d y=%04d p=%d i=%d]", CYAPA_TOUCH_X(regs, i), CYAPA_TOUCH_Y(regs, i), CYAPA_TOUCH_P(regs, i), regs->touch[i].id); } if ((CYAPA_TOUCH_Y(regs, i) > thumbarea_begin && seen_thumb) || CYAPA_TOUCH_P(regs, i) < cyapa_minpressure) { --afingers; if (i < afingers) { regs->touch[i] = regs->touch[i+1]; continue; } } else { if (CYAPA_TOUCH_Y(regs, i) > thumbarea_begin) seen_thumb = 1; } ++i; } nfingers = afingers; /* Tracking for local solutions */ cyapa_lock(sc); /* * Track timing for finger-downs. Used to detect false-3-finger * button-down. */ switch(afingers) { case 0: break; case 1: if (sc->track_nfingers == 0) sc->finger1_ticks = sc->poll_ticks; break; case 2: if (sc->track_nfingers <= 0) sc->finger1_ticks = sc->poll_ticks; if (sc->track_nfingers <= 1) sc->finger2_ticks = sc->poll_ticks; break; case 3: default: if (sc->track_nfingers <= 0) sc->finger1_ticks = sc->poll_ticks; if (sc->track_nfingers <= 1) sc->finger2_ticks = sc->poll_ticks; if (sc->track_nfingers <= 2) sc->finger3_ticks = sc->poll_ticks; break; } newfinger = sc->track_nfingers < afingers; lessfingers = sc->track_nfingers > afingers; sc->track_nfingers = afingers; /* * Lookup and track finger indexes in the touch[] array. */ if (afingers == 0) { click_x = sc->track_x; click_y = sc->track_y; sc->track_x = -1; sc->track_y = -1; sc->track_z = -1; sc->fuzz_x = 0; sc->fuzz_y = 0; sc->fuzz_z = 0; sc->touch_x = -1; sc->touch_y = -1; sc->touch_z = -1; sc->track_id = -1; sc->track_but = 0; i = 0; j = 0; k = 0; } else { /* * The id assigned on touch can move around in the array, * find it. If that finger is lifted up, assign some other * finger for mouse tracking and reset track_x and track_y * to avoid a mouse jump. * * If >= 2 fingers are down be sure not to assign i and * j to the same index. */ for (i = 0; i < nfingers; ++i) { if (sc->track_id == regs->touch[i].id) break; } if (i == nfingers) { i = 0; sc->track_x = -1; sc->track_y = -1; sc->track_z = -1; while (CYAPA_TOUCH_Y(regs, i) >= thumbarea_begin && i < nfingers) ++i; if (i == nfingers) { i = 0; } sc->track_id = regs->touch[i].id; } else if ((sc->track_but || CYAPA_TOUCH_Y(regs, i) >= thumbarea_begin) && newfinger && afingers == 2) { j = regs->touch[0].id == sc->track_id ? 1 : 0; if (CYAPA_TOUCH_Y(regs, j) < thumbarea_begin) { i = j; sc->track_x = -1; sc->track_y = -1; sc->track_z = -1; sc->track_id = regs->touch[i].id; } } } /* Two finger scrolling - reset after timeout */ if (sc->track_z != -1 && afingers != 2 && (sc->poll_ticks - sc->track_z_ticks) > cyapa_scroll_stick_ticks) { sc->track_z = -1; sc->track_z_ticks = 0; } /* Initiate two finger scrolling */ if (!(regs->fngr & CYAPA_FNGR_LEFT) && ((afingers && sc->track_z != -1) || (afingers == 2 && CYAPA_TOUCH_Y(regs, 0) < thumbarea_begin && CYAPA_TOUCH_Y(regs, 1) < thumbarea_begin))) { if (afingers == 2 && (sc->poll_ticks - sc->finger2_ticks) > cyapa_scroll_wait_ticks) { z = (CYAPA_TOUCH_Y(regs, 0) + CYAPA_TOUCH_Y(regs, 1)) >> 1; sc->delta_z += z / ZSCALE - sc->track_z; if (sc->track_z == -1) { sc->delta_z = 0; } if (sc->touch_z == -1) sc->touch_z = z; /* not used atm */ sc->track_z = z / ZSCALE; sc->track_z_ticks = sc->poll_ticks; } } else if (afingers) { /* Normal pad position reporting */ x = CYAPA_TOUCH_X(regs, i); y = CYAPA_TOUCH_Y(regs, i); click_x = x; click_y = y; if (sc->track_x != -1 && sc->track_y < thumbarea_begin && (afingers > 1 || (sc->poll_ticks - sc->finger1_ticks) >= cyapa_move_min_ticks || freq < cyapa_norm_freq)) { sc->delta_x += x - sc->track_x; sc->delta_y -= y - sc->track_y; if (sc->delta_x > sc->cap_resx) sc->delta_x = sc->cap_resx; if (sc->delta_x < -sc->cap_resx) sc->delta_x = -sc->cap_resx; if (sc->delta_y > sc->cap_resy) sc->delta_y = sc->cap_resy; if (sc->delta_y < -sc->cap_resy) sc->delta_y = -sc->cap_resy; if (abs(sc->delta_y) > sc->cap_resy / 2 || abs(sc->delta_x) > sc->cap_resx / 2) { if (cyapa_debug) printf("Detected jump by %i %i\n", sc->delta_x, sc->delta_y); sc->delta_x = sc->delta_y = 0; } } if (sc->touch_x == -1) { sc->touch_x = x; sc->touch_y = y; } sc->track_x = x; sc->track_y = y; } /* Select finger (L = 2/3x, M = 1/3u, R = 1/3d) */ int is_tapclick = (cyapa_enable_tapclick && lessfingers && afingers == 0 && sc->poll_ticks - sc->finger1_ticks >= cyapa_tapclick_min_ticks && sc->poll_ticks - sc->finger1_ticks < cyapa_tapclick_max_ticks); if (regs->fngr & CYAPA_FNGR_LEFT || is_tapclick) { if (sc->track_but) { but = sc->track_but; } else if (afingers == 1) { if (click_x < sc->cap_resx * 2 / 3) but = CYAPA_FNGR_LEFT; else if (click_y < sc->cap_resy / 2) but = CYAPA_FNGR_MIDDLE; else but = CYAPA_FNGR_RIGHT; } else if (is_tapclick) { if (click_x < sc->cap_resx * 2 / 3 || cyapa_enable_tapclick < 2) but = CYAPA_FNGR_LEFT; else if (click_y < sc->cap_resy / 2 && cyapa_enable_tapclick > 2) but = CYAPA_FNGR_MIDDLE; else but = CYAPA_FNGR_RIGHT; } else { but = CYAPA_FNGR_LEFT; } } else { but = 0; } /* * Detect state change from last reported state and * determine if we have gone idle. */ sc->track_but = but; if (sc->delta_x || sc->delta_y || sc->delta_z || sc->track_but != sc->reported_but) { sc->active_tick = ticks; if (sc->remote_mode == 0 && sc->reporting_mode) sc->data_signal = 1; isidle = 0; } else if ((unsigned)(ticks - sc->active_tick) >= TIME_TO_IDLE) { sc->active_tick = ticks - TIME_TO_IDLE; /* prevent overflow */ isidle = 1; } else { isidle = 0; } cyapa_notify(sc); cyapa_unlock(sc); if (cyapa_debug) printf("%i >> %i << %i\n", isidle, sc->track_id, sc->delta_y); return (isidle); } static void cyapa_set_power_mode(struct cyapa_softc *sc, int mode) { uint8_t data; device_t bus; int error; bus = device_get_parent(sc->dev); error = iicbus_request_bus(bus, sc->dev, IIC_WAIT); if (error == 0) { error = cyapa_read_bytes(sc->dev, CMD_POWER_MODE, &data, 1); data = (data & ~0xFC) | mode; if (error == 0) { error = cyapa_write_bytes(sc->dev, CMD_POWER_MODE, &data, 1); } iicbus_release_bus(bus, sc->dev); } } /* * FIFO FUNCTIONS */ /* * Returns non-zero if the fifo is empty */ static int fifo_empty(struct cyapa_softc *sc, struct cyapa_fifo *fifo) { CYAPA_LOCK_ASSERT(sc); return (fifo->rindex == fifo->windex); } /* * Returns the number of characters available for reading from * the fifo without wrapping the fifo buffer. */ static size_t fifo_ready(struct cyapa_softc *sc, struct cyapa_fifo *fifo) { size_t n; CYAPA_LOCK_ASSERT(sc); n = CYAPA_BUFSIZE - (fifo->rindex & CYAPA_BUFMASK); if (n > (size_t)(fifo->windex - fifo->rindex)) n = (size_t)(fifo->windex - fifo->rindex); return (n); } /* * Returns a read pointer into the fifo and then bumps * rindex. The FIFO must have at least 'n' characters in * it. The value (n) can cause the index to wrap but users * of the buffer should never supply a value for (n) that wraps * the buffer. */ static char * fifo_read(struct cyapa_softc *sc, struct cyapa_fifo *fifo, size_t n) { char *ptr; CYAPA_LOCK_ASSERT(sc); if (n > (CYAPA_BUFSIZE - (fifo->rindex & CYAPA_BUFMASK))) { printf("fifo_read: overflow\n"); return (fifo->buf); } ptr = fifo->buf + (fifo->rindex & CYAPA_BUFMASK); fifo->rindex += n; return (ptr); } static uint8_t fifo_read_char(struct cyapa_softc *sc, struct cyapa_fifo *fifo) { uint8_t c; CYAPA_LOCK_ASSERT(sc); if (fifo->rindex == fifo->windex) { printf("fifo_read_char: overflow\n"); c = 0; } else { c = fifo->buf[fifo->rindex & CYAPA_BUFMASK]; ++fifo->rindex; } return (c); } /* * Write a character to the FIFO. The character will be discarded * if the FIFO is full. */ static void fifo_write_char(struct cyapa_softc *sc, struct cyapa_fifo *fifo, uint8_t c) { CYAPA_LOCK_ASSERT(sc); if (fifo->windex - fifo->rindex < CYAPA_BUFSIZE) { fifo->buf[fifo->windex & CYAPA_BUFMASK] = c; ++fifo->windex; } } /* * Return the amount of space available for writing without wrapping * the fifo. */ static size_t fifo_space(struct cyapa_softc *sc, struct cyapa_fifo *fifo) { size_t n; CYAPA_LOCK_ASSERT(sc); n = CYAPA_BUFSIZE - (fifo->windex & CYAPA_BUFMASK); if (n > (size_t)(CYAPA_BUFSIZE - (fifo->windex - fifo->rindex))) n = (size_t)(CYAPA_BUFSIZE - (fifo->windex - fifo->rindex)); return (n); } static char * fifo_write(struct cyapa_softc *sc, struct cyapa_fifo *fifo, size_t n) { char *ptr; CYAPA_LOCK_ASSERT(sc); ptr = fifo->buf + (fifo->windex & CYAPA_BUFMASK); fifo->windex += n; return (ptr); } static void fifo_reset(struct cyapa_softc *sc, struct cyapa_fifo *fifo) { CYAPA_LOCK_ASSERT(sc); fifo->rindex = 0; fifo->windex = 0; } /* * Fuzz handling */ static int cyapa_fuzz(int delta, int *fuzzp) { int fuzz; fuzz = *fuzzp; if (fuzz >= 0 && delta < 0) { ++delta; --fuzz; } else if (fuzz <= 0 && delta > 0) { --delta; ++fuzz; } *fuzzp = fuzz; return (delta); } DRIVER_MODULE(cyapa, iicbus, cyapa_driver, cyapa_devclass, NULL, NULL); MODULE_DEPEND(cyapa, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); MODULE_VERSION(cyapa, 1); Index: stable/12/sys/dev/ichiic/ig4_acpi.c =================================================================== --- stable/12/sys/dev/ichiic/ig4_acpi.c (revision 355993) +++ stable/12/sys/dev/ichiic/ig4_acpi.c (revision 355994) @@ -1,179 +1,196 @@ /*- * Copyright (c) 2016 Oleksandr Tymoshenko * 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 __FBSDID("$FreeBSD$"); #include "opt_acpi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int ig4iic_acpi_probe(device_t dev); static int ig4iic_acpi_attach(device_t dev); static int ig4iic_acpi_detach(device_t dev); static char *ig4iic_ids[] = { "INT33C2", "INT33C3", "INT3432", "INT3433", "80860F41", "808622C1", "AMDI0510", "AMDI0010", "APMC0D0F", NULL }; static int ig4iic_acpi_probe(device_t dev) { ig4iic_softc_t *sc; - char *hid; sc = device_get_softc(dev); if (acpi_disabled("ig4iic")) return (ENXIO); - hid = ACPI_ID_PROBE(device_get_parent(dev), dev, ig4iic_ids); - if (hid == NULL) + if (ACPI_ID_PROBE(device_get_parent(dev), dev, ig4iic_ids) == NULL) return (ENXIO); - if (strcmp("AMDI0010", hid) == 0) - sc->access_intr_mask = 1; - device_set_desc(dev, "Designware I2C Controller"); return (0); } static int ig4iic_acpi_attach(device_t dev) { ig4iic_softc_t *sc; int error; sc = device_get_softc(dev); sc->dev = dev; /* All the HIDs matched are Atom SOCs. */ sc->version = IG4_ATOM; sc->regs_rid = 0; sc->regs_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->regs_rid, RF_ACTIVE); if (sc->regs_res == NULL) { device_printf(dev, "unable to map registers\n"); ig4iic_acpi_detach(dev); return (ENXIO); } sc->intr_rid = 0; sc->intr_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->intr_rid, RF_SHAREABLE | RF_ACTIVE); if (sc->intr_res == NULL) { device_printf(dev, "unable to map interrupt\n"); ig4iic_acpi_detach(dev); return (ENXIO); } sc->platform_attached = 1; error = ig4iic_attach(sc); if (error) ig4iic_acpi_detach(dev); return (error); } static int ig4iic_acpi_detach(device_t dev) { ig4iic_softc_t *sc = device_get_softc(dev); int error; if (sc->platform_attached) { error = ig4iic_detach(sc); if (error) return (error); sc->platform_attached = 0; } if (sc->intr_res) { bus_release_resource(dev, SYS_RES_IRQ, sc->intr_rid, sc->intr_res); sc->intr_res = NULL; } if (sc->regs_res) { bus_release_resource(dev, SYS_RES_MEMORY, sc->regs_rid, sc->regs_res); sc->regs_res = NULL; } return (0); } +static int +ig4iic_acpi_suspend(device_t dev) +{ + ig4iic_softc_t *sc = device_get_softc(dev); + + return (ig4iic_suspend(sc)); +} + +static int +ig4iic_acpi_resume(device_t dev) +{ + ig4iic_softc_t *sc = device_get_softc(dev); + + return (ig4iic_resume(sc)); +} + static device_method_t ig4iic_acpi_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ig4iic_acpi_probe), DEVMETHOD(device_attach, ig4iic_acpi_attach), DEVMETHOD(device_detach, ig4iic_acpi_detach), + DEVMETHOD(device_suspend, ig4iic_acpi_suspend), + DEVMETHOD(device_resume, ig4iic_acpi_resume), + /* Bus interface */ + DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), + DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), + DEVMETHOD(bus_alloc_resource, bus_generic_alloc_resource), + DEVMETHOD(bus_release_resource, bus_generic_release_resource), + DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), + DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), + DEVMETHOD(bus_adjust_resource, bus_generic_adjust_resource), + /* iicbus interface */ DEVMETHOD(iicbus_transfer, ig4iic_transfer), DEVMETHOD(iicbus_reset, ig4iic_reset), - DEVMETHOD(iicbus_callback, iicbus_null_callback), + DEVMETHOD(iicbus_callback, ig4iic_callback), DEVMETHOD_END }; static driver_t ig4iic_acpi_driver = { - "ig4iic_acpi", + "ig4iic", ig4iic_acpi_methods, sizeof(struct ig4iic_softc), }; -static devclass_t ig4iic_acpi_devclass; -DRIVER_MODULE(ig4iic_acpi, acpi, ig4iic_acpi_driver, ig4iic_acpi_devclass, 0, 0); - -MODULE_DEPEND(ig4iic_acpi, acpi, 1, 1, 1); -MODULE_DEPEND(ig4iic_acpi, pci, 1, 1, 1); -MODULE_DEPEND(ig4iic_acpi, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); -MODULE_VERSION(ig4iic_acpi, 1); +DRIVER_MODULE(ig4iic, acpi, ig4iic_acpi_driver, ig4iic_devclass, 0, 0); +MODULE_DEPEND(ig4iic, acpi, 1, 1, 1); Index: stable/12/sys/dev/ichiic/ig4_iic.c =================================================================== --- stable/12/sys/dev/ichiic/ig4_iic.c (revision 355993) +++ stable/12/sys/dev/ichiic/ig4_iic.c (revision 355994) @@ -1,801 +1,1205 @@ /* * Copyright (c) 2014 The DragonFly Project. All rights reserved. * * This code is derived from software contributed to The DragonFly Project * by Matthew Dillon and was subsequently ported * to FreeBSD by Michael Gmelin * * 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. * 3. Neither the name of The DragonFly Project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific, prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 * COPYRIGHT HOLDERS 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 __FBSDID("$FreeBSD$"); /* * Intel fourth generation mobile cpus integrated I2C device. * * See ig4_reg.h for datasheet reference and notes. * See ig4_var.h for locking semantics. */ +#include "opt_acpi.h" + #include #include #include #include #include +#include #include #include +#include #include #include #include #include #include #include -#include -#include +#ifdef DEV_ACPI +#include +#include +#include +#endif + #include #include #include #include -#define TRANS_NORMAL 1 -#define TRANS_PCALL 2 -#define TRANS_BLOCK 3 +#define DO_POLL(sc) (cold || kdb_active || SCHEDULER_STOPPED() || sc->poll) -static void ig4iic_start(void *xdev); -static void ig4iic_intr(void *cookie); +/* + * tLOW, tHIGH periods of the SCL clock and maximal falling time of both + * lines are taken from I2C specifications. + */ +#define IG4_SPEED_STD_THIGH 4000 /* nsec */ +#define IG4_SPEED_STD_TLOW 4700 /* nsec */ +#define IG4_SPEED_STD_TF_MAX 300 /* nsec */ +#define IG4_SPEED_FAST_THIGH 600 /* nsec */ +#define IG4_SPEED_FAST_TLOW 1300 /* nsec */ +#define IG4_SPEED_FAST_TF_MAX 300 /* nsec */ + +/* + * Ig4 hardware parameters except Haswell are taken from intel_lpss driver + */ +static const struct ig4_hw ig4iic_hw[] = { + [IG4_HASWELL] = { + .ic_clock_rate = 100, /* MHz */ + .sda_hold_time = 90, /* nsec */ + .txfifo_depth = 32, + .rxfifo_depth = 32, + }, + [IG4_ATOM] = { + .ic_clock_rate = 100, + .sda_fall_time = 280, + .scl_fall_time = 240, + .sda_hold_time = 60, + .txfifo_depth = 32, + .rxfifo_depth = 32, + }, + [IG4_SKYLAKE] = { + .ic_clock_rate = 120, + .sda_hold_time = 230, + }, + [IG4_APL] = { + .ic_clock_rate = 133, + .sda_fall_time = 171, + .scl_fall_time = 208, + .sda_hold_time = 207, + }, + [IG4_CANNONLAKE] = { + .ic_clock_rate = 216, + .sda_hold_time = 230, + }, +}; + +static int ig4iic_set_config(ig4iic_softc_t *sc, bool reset); +static driver_filter_t ig4iic_intr; static void ig4iic_dump(ig4iic_softc_t *sc); static int ig4_dump; SYSCTL_INT(_debug, OID_AUTO, ig4_dump, CTLFLAG_RW, &ig4_dump, 0, "Dump controller registers"); /* + * Clock registers initialization control + * 0 - Try read clock registers from ACPI and fallback to p.1. + * 1 - Calculate values based on controller type (IC clock rate). + * 2 - Use values inherited from DragonflyBSD driver (old behavior). + * 3 - Keep clock registers intact. + */ +static int ig4_timings; +SYSCTL_INT(_debug, OID_AUTO, ig4_timings, CTLFLAG_RDTUN, &ig4_timings, 0, + "Controller timings 0=ACPI, 1=predefined, 2=legacy, 3=do not change"); + +/* * Low-level inline support functions */ static __inline void reg_write(ig4iic_softc_t *sc, uint32_t reg, uint32_t value) { bus_write_4(sc->regs_res, reg, value); bus_barrier(sc->regs_res, reg, 4, BUS_SPACE_BARRIER_WRITE); } static __inline uint32_t reg_read(ig4iic_softc_t *sc, uint32_t reg) { uint32_t value; bus_barrier(sc->regs_res, reg, 4, BUS_SPACE_BARRIER_READ); value = bus_read_4(sc->regs_res, reg); return (value); } +static void +ig4iic_set_intr_mask(ig4iic_softc_t *sc, uint32_t val) +{ + if (sc->intr_mask != val) { + reg_write(sc, IG4_REG_INTR_MASK, val); + sc->intr_mask = val; + } +} + +static int +intrstat2iic(ig4iic_softc_t *sc, uint32_t val) +{ + uint32_t src; + + if (val & IG4_INTR_RX_UNDER) + reg_read(sc, IG4_REG_CLR_RX_UNDER); + if (val & IG4_INTR_RX_OVER) + reg_read(sc, IG4_REG_CLR_RX_OVER); + if (val & IG4_INTR_TX_OVER) + reg_read(sc, IG4_REG_CLR_TX_OVER); + + if (val & IG4_INTR_TX_ABRT) { + src = reg_read(sc, IG4_REG_TX_ABRT_SOURCE); + reg_read(sc, IG4_REG_CLR_TX_ABORT); + /* User-requested abort. Not really a error */ + if (src & IG4_ABRTSRC_TRANSFER) + return (IIC_ESTATUS); + /* Master has lost arbitration */ + if (src & IG4_ABRTSRC_ARBLOST) + return (IIC_EBUSBSY); + /* Did not receive an acknowledge from the remote slave */ + if (src & (IG4_ABRTSRC_TXNOACK_ADDR7 | + IG4_ABRTSRC_TXNOACK_ADDR10_1 | + IG4_ABRTSRC_TXNOACK_ADDR10_2 | + IG4_ABRTSRC_TXNOACK_DATA | + IG4_ABRTSRC_GENCALL_NOACK)) + return (IIC_ENOACK); + /* Programming errors */ + if (src & (IG4_ABRTSRC_GENCALL_READ | + IG4_ABRTSRC_NORESTART_START | + IG4_ABRTSRC_NORESTART_10)) + return (IIC_ENOTSUPP); + /* Other errors */ + if (src & IG4_ABRTSRC_ACKED_START) + return (IIC_EBUSERR); + } + /* + * TX_OVER, RX_OVER and RX_UNDER are caused by wrong RX/TX FIFO depth + * detection or driver's read/write pipelining errors. + */ + if (val & (IG4_INTR_TX_OVER | IG4_INTR_RX_OVER)) + return (IIC_EOVERFLOW); + if (val & IG4_INTR_RX_UNDER) + return (IIC_EUNDERFLOW); + + return (IIC_NOERR); +} + /* * Enable or disable the controller and wait for the controller to acknowledge * the state change. */ static int set_controller(ig4iic_softc_t *sc, uint32_t ctl) { int retry; int error; uint32_t v; /* * When the controller is enabled, interrupt on STOP detect * or receive character ready and clear pending interrupts. */ - if (ctl & IG4_I2C_ENABLE) { - reg_write(sc, IG4_REG_INTR_MASK, IG4_INTR_STOP_DET | - IG4_INTR_RX_FULL); + ig4iic_set_intr_mask(sc, 0); + if (ctl & IG4_I2C_ENABLE) reg_read(sc, IG4_REG_CLR_INTR); - } else - reg_write(sc, IG4_REG_INTR_MASK, 0); reg_write(sc, IG4_REG_I2C_EN, ctl); error = IIC_ETIMEOUT; for (retry = 100; retry > 0; --retry) { v = reg_read(sc, IG4_REG_ENABLE_STATUS); if (((v ^ ctl) & IG4_I2C_ENABLE) == 0) { error = 0; break; } - if (cold) - DELAY(1000); - else - mtx_sleep(sc, &sc->io_lock, 0, "i2cslv", 1); + pause("i2cslv", 1); } return (error); } /* - * Wait up to 25ms for the requested status using a 25uS polling loop. + * Wait up to 25ms for the requested interrupt using a 25uS polling loop. */ static int -wait_status(ig4iic_softc_t *sc, uint32_t status) +wait_intr(ig4iic_softc_t *sc, uint32_t intr) { uint32_t v; int error; int txlvl = -1; u_int count_us = 0; u_int limit_us = 25000; /* 25ms */ - error = IIC_ETIMEOUT; - for (;;) { /* * Check requested status */ - v = reg_read(sc, IG4_REG_I2C_STA); - if (v & status) { - error = 0; + v = reg_read(sc, IG4_REG_RAW_INTR_STAT); + error = intrstat2iic(sc, v & IG4_INTR_ERR_MASK); + if (error || (v & intr)) break; - } /* - * When waiting for receive data break-out if the interrupt - * loaded data into the FIFO. - */ - if (status & IG4_STATUS_RX_NOTEMPTY) { - if (sc->rpos != sc->rnext) { - error = 0; - break; - } - } - - /* * When waiting for the transmit FIFO to become empty, * reset the timeout if we see a change in the transmit * FIFO level as progress is being made. */ - if (status & IG4_STATUS_TX_EMPTY) { + if (intr & (IG4_INTR_TX_EMPTY | IG4_INTR_STOP_DET)) { v = reg_read(sc, IG4_REG_TXFLR) & IG4_FIFOLVL_MASK; if (txlvl != v) { txlvl = v; count_us = 0; } } /* * Stop if we've run out of time. */ - if (count_us >= limit_us) + if (count_us >= limit_us) { + error = IIC_ETIMEOUT; break; + } /* - * When waiting for receive data let the interrupt do its - * work, otherwise poll with the lock held. + * When polling is not requested let the interrupt do its work. */ - if (status & IG4_STATUS_RX_NOTEMPTY) { - mtx_sleep(sc, &sc->io_lock, 0, "i2cwait", + if (!DO_POLL(sc)) { + mtx_lock_spin(&sc->io_lock); + ig4iic_set_intr_mask(sc, intr | IG4_INTR_ERR_MASK); + msleep_spin(sc, &sc->io_lock, "i2cwait", (hz + 99) / 100); /* sleep up to 10ms */ + ig4iic_set_intr_mask(sc, 0); + mtx_unlock_spin(&sc->io_lock); count_us += 10000; } else { DELAY(25); count_us += 25; } } return (error); } /* - * Read I2C data. The data might have already been read by - * the interrupt code, otherwise it is sitting in the data - * register. - */ -static uint8_t -data_read(ig4iic_softc_t *sc) -{ - uint8_t c; - - if (sc->rpos == sc->rnext) { - c = (uint8_t)reg_read(sc, IG4_REG_DATA_CMD); - } else { - c = sc->rbuf[sc->rpos & IG4_RBUFMASK]; - ++sc->rpos; - } - return (c); -} - -/* * Set the slave address. The controller must be disabled when * changing the address. * * This operation does not issue anything to the I2C bus but sets * the target address for when the controller later issues a START. */ static void set_slave_addr(ig4iic_softc_t *sc, uint8_t slave) { uint32_t tar; uint32_t ctl; int use_10bit; use_10bit = 0; if (sc->slave_valid && sc->last_slave == slave && sc->use_10bit == use_10bit) { return; } sc->use_10bit = use_10bit; /* * Wait for TXFIFO to drain before disabling the controller. - * - * If a write message has not been completed it's really a - * programming error, but for now in that case issue an extra - * byte + STOP. - * - * If a read message has not been completed it's also a programming - * error, for now just ignore it. */ - wait_status(sc, IG4_STATUS_TX_NOTFULL); - if (sc->write_started) { - reg_write(sc, IG4_REG_DATA_CMD, IG4_DATA_STOP); - sc->write_started = 0; - } - if (sc->read_started) - sc->read_started = 0; - wait_status(sc, IG4_STATUS_TX_EMPTY); + wait_intr(sc, IG4_INTR_TX_EMPTY); set_controller(sc, 0); ctl = reg_read(sc, IG4_REG_CTL); ctl &= ~IG4_CTL_10BIT; ctl |= IG4_CTL_RESTARTEN; tar = slave; if (sc->use_10bit) { tar |= IG4_TAR_10BIT; ctl |= IG4_CTL_10BIT; } reg_write(sc, IG4_REG_CTL, ctl); reg_write(sc, IG4_REG_TAR_ADD, tar); set_controller(sc, IG4_I2C_ENABLE); sc->slave_valid = 1; sc->last_slave = slave; } /* * IICBUS API FUNCTIONS */ static int -ig4iic_xfer_start(ig4iic_softc_t *sc, uint16_t slave) +ig4iic_xfer_start(ig4iic_softc_t *sc, uint16_t slave, bool repeated_start) { set_slave_addr(sc, slave >> 1); + + if (!repeated_start) { + /* + * Clear any previous TX/RX FIFOs overflow/underflow bits + * and I2C bus STOP condition. + */ + reg_read(sc, IG4_REG_CLR_INTR); + } + return (0); } +static bool +ig4iic_xfer_is_started(ig4iic_softc_t *sc) +{ + /* + * It requires that no IG4_REG_CLR_INTR or IG4_REG_CLR_START/STOP_DET + * register reads is issued after START condition. + */ + return ((reg_read(sc, IG4_REG_RAW_INTR_STAT) & + (IG4_INTR_START_DET | IG4_INTR_STOP_DET)) == IG4_INTR_START_DET); +} + static int +ig4iic_xfer_abort(ig4iic_softc_t *sc) +{ + int error; + + /* Request send of STOP condition and flush of TX FIFO */ + set_controller(sc, IG4_I2C_ABORT | IG4_I2C_ENABLE); + /* + * Wait for the TX_ABRT interrupt with ABRTSRC_TRANSFER + * bit set in TX_ABRT_SOURCE register. + */ + error = wait_intr(sc, IG4_INTR_STOP_DET); + set_controller(sc, IG4_I2C_ENABLE); + + return (error == IIC_ESTATUS ? 0 : error); +} + +/* + * Amount of unread data before next burst to get better I2C bus utilization. + * 2 bytes is enough in FAST mode. 8 bytes is better in FAST+ and HIGH modes. + * Intel-recommended value is 16 for DMA transfers with 64-byte depth FIFOs. + */ +#define IG4_FIFO_LOWAT 2 + +static int ig4iic_read(ig4iic_softc_t *sc, uint8_t *buf, uint16_t len, bool repeated_start, bool stop) { uint32_t cmd; - uint16_t i; + int requested = 0; + int received = 0; + int burst, target, lowat = 0; int error; if (len == 0) return (0); - cmd = IG4_DATA_COMMAND_RD; - cmd |= repeated_start ? IG4_DATA_RESTART : 0; - cmd |= stop && len == 1 ? IG4_DATA_STOP : 0; - - /* Issue request for the first byte (could be last as well). */ - reg_write(sc, IG4_REG_DATA_CMD, cmd); - - for (i = 0; i < len; i++) { - /* - * Maintain a pipeline by queueing the allowance for the next - * read before waiting for the current read. - */ - cmd = IG4_DATA_COMMAND_RD; - if (i < len - 1) { + while (received < len) { + burst = sc->cfg.txfifo_depth - + (reg_read(sc, IG4_REG_TXFLR) & IG4_FIFOLVL_MASK); + if (burst <= 0) { + error = wait_intr(sc, IG4_INTR_TX_EMPTY); + if (error) + break; + burst = sc->cfg.txfifo_depth; + } + /* Ensure we have enough free space in RXFIFO */ + burst = MIN(burst, sc->cfg.rxfifo_depth - lowat); + target = MIN(requested + burst, (int)len); + while (requested < target) { cmd = IG4_DATA_COMMAND_RD; - cmd |= stop && i == len - 2 ? IG4_DATA_STOP : 0; + if (repeated_start && requested == 0) + cmd |= IG4_DATA_RESTART; + if (stop && requested == len - 1) + cmd |= IG4_DATA_STOP; reg_write(sc, IG4_REG_DATA_CMD, cmd); + requested++; } - error = wait_status(sc, IG4_STATUS_RX_NOTEMPTY); - if (error) - break; - buf[i] = data_read(sc); + /* Leave some data queued to maintain the hardware pipeline */ + lowat = 0; + if (requested != len && requested - received > IG4_FIFO_LOWAT) + lowat = IG4_FIFO_LOWAT; + /* After TXFLR fills up, clear it by reading available data */ + while (received < requested - lowat) { + burst = MIN((int)len - received, + reg_read(sc, IG4_REG_RXFLR) & IG4_FIFOLVL_MASK); + if (burst > 0) { + while (burst--) + buf[received++] = 0xFF & + reg_read(sc, IG4_REG_DATA_CMD); + } else { + error = wait_intr(sc, IG4_INTR_RX_FULL); + if (error) + goto out; + } + } } - - (void)reg_read(sc, IG4_REG_TX_ABRT_SOURCE); +out: return (error); } static int ig4iic_write(ig4iic_softc_t *sc, uint8_t *buf, uint16_t len, bool repeated_start, bool stop) { uint32_t cmd; - uint16_t i; + int sent = 0; + int burst, target; int error; + bool lowat_set = false; if (len == 0) return (0); - cmd = repeated_start ? IG4_DATA_RESTART : 0; - for (i = 0; i < len; i++) { - error = wait_status(sc, IG4_STATUS_TX_NOTFULL); - if (error) - break; - cmd |= buf[i]; - cmd |= stop && i == len - 1 ? IG4_DATA_STOP : 0; - reg_write(sc, IG4_REG_DATA_CMD, cmd); - cmd = 0; + while (sent < len) { + burst = sc->cfg.txfifo_depth - + (reg_read(sc, IG4_REG_TXFLR) & IG4_FIFOLVL_MASK); + target = MIN(sent + burst, (int)len); + /* Leave some data queued to maintain the hardware pipeline */ + if (!lowat_set && target != len) { + lowat_set = true; + reg_write(sc, IG4_REG_TX_TL, IG4_FIFO_LOWAT); + } + while(sent < target) { + cmd = buf[sent]; + if (repeated_start && sent == 0) + cmd |= IG4_DATA_RESTART; + if (stop && sent == len - 1) + cmd |= IG4_DATA_STOP; + reg_write(sc, IG4_REG_DATA_CMD, cmd); + sent++; + } + if (sent < len) { + error = wait_intr(sc, IG4_INTR_TX_EMPTY); + if (error) + break; + } } + if (lowat_set) + reg_write(sc, IG4_REG_TX_TL, 0); - (void)reg_read(sc, IG4_REG_TX_ABRT_SOURCE); return (error); } int ig4iic_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs) { ig4iic_softc_t *sc = device_get_softc(dev); const char *reason = NULL; uint32_t i; int error; int unit; bool rpstart; bool stop; + bool allocated; /* * The hardware interface imposes limits on allowed I2C messages. * It is not possible to explicitly send a start or stop. * They are automatically sent (or not sent, depending on the * configuration) when a data byte is transferred. * For this reason it's impossible to send a message with no data * at all (like an SMBus quick message). * The start condition is automatically generated after the stop * condition, so it's impossible to not have a start after a stop. * The repeated start condition is automatically sent if a change * of the transfer direction happens, so it's impossible to have * a change of direction without a (repeated) start. * The repeated start can be forced even without the change of * direction. * Changing the target slave address requires resetting the hardware * state, so it's impossible to do that without the stop followed * by the start. */ for (i = 0; i < nmsgs; i++) { #if 0 if (i == 0 && (msgs[i].flags & IIC_M_NOSTART) != 0) { reason = "first message without start"; break; } if (i == nmsgs - 1 && (msgs[i].flags & IIC_M_NOSTOP) != 0) { reason = "last message without stop"; break; } #endif if (msgs[i].len == 0) { reason = "message with no data"; break; } if (i > 0) { if ((msgs[i].flags & IIC_M_NOSTART) != 0 && (msgs[i - 1].flags & IIC_M_NOSTOP) == 0) { reason = "stop not followed by start"; break; } if ((msgs[i - 1].flags & IIC_M_NOSTOP) != 0 && msgs[i].slave != msgs[i - 1].slave) { reason = "change of slave without stop"; break; } if ((msgs[i].flags & IIC_M_NOSTART) != 0 && (msgs[i].flags & IIC_M_RD) != (msgs[i - 1].flags & IIC_M_RD)) { reason = "change of direction without repeated" " start"; break; } } } if (reason != NULL) { if (bootverbose) device_printf(dev, "%s\n", reason); return (IIC_ENOTSUPP); } - sx_xlock(&sc->call_lock); - mtx_lock(&sc->io_lock); + /* Check if device is already allocated with iicbus_request_bus() */ + allocated = sx_xlocked(&sc->call_lock) != 0; + if (!allocated) + sx_xlock(&sc->call_lock); /* Debugging - dump registers. */ if (ig4_dump) { unit = device_get_unit(dev); if (ig4_dump & (1 << unit)) { ig4_dump &= ~(1 << unit); ig4iic_dump(sc); } } /* * Clear any previous abort condition that may have been holding * the txfifo in reset. */ reg_read(sc, IG4_REG_CLR_TX_ABORT); - /* - * Clean out any previously received data. - */ - if (sc->rpos != sc->rnext && bootverbose) { - device_printf(sc->dev, "discarding %d bytes of spurious data\n", - sc->rnext - sc->rpos); - } - sc->rpos = 0; - sc->rnext = 0; - rpstart = false; error = 0; for (i = 0; i < nmsgs; i++) { if ((msgs[i].flags & IIC_M_NOSTART) == 0) { - error = ig4iic_xfer_start(sc, msgs[i].slave); + error = ig4iic_xfer_start(sc, msgs[i].slave, rpstart); } else { if (!sc->slave_valid || (msgs[i].slave >> 1) != sc->last_slave) { device_printf(dev, "start condition suppressed" "but slave address is not set up"); error = EINVAL; break; } rpstart = false; } if (error != 0) break; stop = (msgs[i].flags & IIC_M_NOSTOP) == 0; if (msgs[i].flags & IIC_M_RD) error = ig4iic_read(sc, msgs[i].buf, msgs[i].len, rpstart, stop); else error = ig4iic_write(sc, msgs[i].buf, msgs[i].len, rpstart, stop); - if (error != 0) + + /* Wait for error or stop condition occurred on the I2C bus */ + if (stop && error == 0) { + error = wait_intr(sc, IG4_INTR_STOP_DET); + if (error == 0) + reg_read(sc, IG4_REG_CLR_INTR); + } + + if (error != 0) { + /* + * Send STOP condition if it's not done yet and flush + * both FIFOs. Do a controller soft reset if transfer + * abort is failed. + */ + if (ig4iic_xfer_is_started(sc) && + ig4iic_xfer_abort(sc) != 0) { + device_printf(sc->dev, "Failed to abort " + "transfer. Do the controller reset.\n"); + ig4iic_set_config(sc, true); + } else { + while (reg_read(sc, IG4_REG_I2C_STA) & + IG4_STATUS_RX_NOTEMPTY) + reg_read(sc, IG4_REG_DATA_CMD); + reg_read(sc, IG4_REG_TX_ABRT_SOURCE); + reg_read(sc, IG4_REG_CLR_INTR); + } break; + } rpstart = !stop; } - mtx_unlock(&sc->io_lock); - sx_unlock(&sc->call_lock); + if (!allocated) + sx_unlock(&sc->call_lock); return (error); } int ig4iic_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr) { ig4iic_softc_t *sc = device_get_softc(dev); + bool allocated; - sx_xlock(&sc->call_lock); - mtx_lock(&sc->io_lock); + allocated = sx_xlocked(&sc->call_lock) != 0; + if (!allocated) + sx_xlock(&sc->call_lock); /* TODO handle speed configuration? */ if (oldaddr != NULL) *oldaddr = sc->last_slave << 1; set_slave_addr(sc, addr >> 1); if (addr == IIC_UNKNOWN) sc->slave_valid = false; - mtx_unlock(&sc->io_lock); - sx_unlock(&sc->call_lock); + if (!allocated) + sx_unlock(&sc->call_lock); return (0); } +int +ig4iic_callback(device_t dev, int index, caddr_t data) +{ + ig4iic_softc_t *sc = device_get_softc(dev); + int error = 0; + int how; + + switch (index) { + case IIC_REQUEST_BUS: + /* force polling if ig4iic is requested with IIC_DONTWAIT */ + how = *(int *)data; + if ((how & IIC_WAIT) == 0) { + if (sx_try_xlock(&sc->call_lock) == 0) + error = IIC_EBUSBSY; + else + sc->poll = true; + } else + sx_xlock(&sc->call_lock); + break; + + case IIC_RELEASE_BUS: + sc->poll = false; + sx_unlock(&sc->call_lock); + break; + + default: + error = errno2iic(EINVAL); + } + + return (error); +} + /* - * Called from ig4iic_pci_attach/detach() + * Clock register values can be calculated with following rough equations: + * SCL_HCNT = ceil(IC clock rate * tHIGH) + * SCL_LCNT = ceil(IC clock rate * tLOW) + * SDA_HOLD = ceil(IC clock rate * SDA hold time) + * Precise equations take signal's falling, rising and spike suppression + * times in to account. They can be found in Synopsys or Intel documentation. + * + * Here we snarf formulas and defaults from Linux driver to be able to use + * timing values provided by Intel LPSS driver "as is". */ -int -ig4iic_attach(ig4iic_softc_t *sc) +static int +ig4iic_clk_params(const struct ig4_hw *hw, int speed, + uint16_t *scl_hcnt, uint16_t *scl_lcnt, uint16_t *sda_hold) { - int error; + uint32_t thigh, tlow, tf_max; /* nsec */ + uint32_t sda_fall_time; /* nsec */ + uint32_t scl_fall_time; /* nsec */ + + switch (speed) { + case IG4_CTL_SPEED_STD: + thigh = IG4_SPEED_STD_THIGH; + tlow = IG4_SPEED_STD_TLOW; + tf_max = IG4_SPEED_STD_TF_MAX; + break; + + case IG4_CTL_SPEED_FAST: + thigh = IG4_SPEED_FAST_THIGH; + tlow = IG4_SPEED_FAST_TLOW; + tf_max = IG4_SPEED_FAST_TF_MAX; + break; + + default: + return (EINVAL); + } + + /* Use slowest falling time defaults to be on the safe side */ + sda_fall_time = hw->sda_fall_time == 0 ? tf_max : hw->sda_fall_time; + *scl_hcnt = (uint16_t) + ((hw->ic_clock_rate * (thigh + sda_fall_time) + 500) / 1000 - 3); + + scl_fall_time = hw->scl_fall_time == 0 ? tf_max : hw->scl_fall_time; + *scl_lcnt = (uint16_t) + ((hw->ic_clock_rate * (tlow + scl_fall_time) + 500) / 1000 - 1); + + /* + * There is no "known good" default value for tHD;DAT so keep SDA_HOLD + * intact if sda_hold_time value is not provided. + */ + if (hw->sda_hold_time != 0) + *sda_hold = (uint16_t) + ((hw->ic_clock_rate * hw->sda_hold_time + 500) / 1000); + + return (0); +} + +#ifdef DEV_ACPI +static ACPI_STATUS +ig4iic_acpi_params(ACPI_HANDLE handle, char *method, + uint16_t *scl_hcnt, uint16_t *scl_lcnt, uint16_t *sda_hold) +{ + ACPI_BUFFER buf; + ACPI_OBJECT *obj, *elems; + ACPI_STATUS status; + + buf.Pointer = NULL; + buf.Length = ACPI_ALLOCATE_BUFFER; + + status = AcpiEvaluateObject(handle, method, NULL, &buf); + if (ACPI_FAILURE(status)) + return (status); + + status = AE_TYPE; + obj = (ACPI_OBJECT *)buf.Pointer; + if (obj->Type == ACPI_TYPE_PACKAGE && obj->Package.Count == 3) { + elems = obj->Package.Elements; + *scl_hcnt = elems[0].Integer.Value & IG4_SCL_CLOCK_MASK; + *scl_lcnt = elems[1].Integer.Value & IG4_SCL_CLOCK_MASK; + *sda_hold = elems[2].Integer.Value & IG4_SDA_TX_HOLD_MASK; + status = AE_OK; + } + + AcpiOsFree(obj); + + return (status); +} +#endif /* DEV_ACPI */ + +static void +ig4iic_get_config(ig4iic_softc_t *sc) +{ + const struct ig4_hw *hw; uint32_t v; +#ifdef DEV_ACPI + ACPI_HANDLE handle; +#endif + /* Fetch default hardware config from controller */ + sc->cfg.version = reg_read(sc, IG4_REG_COMP_VER); + sc->cfg.bus_speed = reg_read(sc, IG4_REG_CTL) & IG4_CTL_SPEED_MASK; + sc->cfg.ss_scl_hcnt = + reg_read(sc, IG4_REG_SS_SCL_HCNT) & IG4_SCL_CLOCK_MASK; + sc->cfg.ss_scl_lcnt = + reg_read(sc, IG4_REG_SS_SCL_LCNT) & IG4_SCL_CLOCK_MASK; + sc->cfg.fs_scl_hcnt = + reg_read(sc, IG4_REG_FS_SCL_HCNT) & IG4_SCL_CLOCK_MASK; + sc->cfg.fs_scl_lcnt = + reg_read(sc, IG4_REG_FS_SCL_LCNT) & IG4_SCL_CLOCK_MASK; + sc->cfg.ss_sda_hold = sc->cfg.fs_sda_hold = + reg_read(sc, IG4_REG_SDA_HOLD) & IG4_SDA_TX_HOLD_MASK; - mtx_init(&sc->io_lock, "IG4 I/O lock", NULL, MTX_DEF); - sx_init(&sc->call_lock, "IG4 call lock"); + if (sc->cfg.bus_speed != IG4_CTL_SPEED_STD) + sc->cfg.bus_speed = IG4_CTL_SPEED_FAST; + /* REG_COMP_PARAM1 is not documented in latest Intel specs */ + if (sc->version == IG4_HASWELL || sc->version == IG4_ATOM) { + v = reg_read(sc, IG4_REG_COMP_PARAM1); + if (IG4_PARAM1_TXFIFO_DEPTH(v) != 0) + sc->cfg.txfifo_depth = IG4_PARAM1_TXFIFO_DEPTH(v); + if (IG4_PARAM1_RXFIFO_DEPTH(v) != 0) + sc->cfg.rxfifo_depth = IG4_PARAM1_RXFIFO_DEPTH(v); + } else { + /* + * Hardware does not allow FIFO Threshold Levels value to be + * set larger than the depth of the buffer. If an attempt is + * made to do that, the actual value set will be the maximum + * depth of the buffer. + */ + v = reg_read(sc, IG4_REG_TX_TL); + reg_write(sc, IG4_REG_TX_TL, v | IG4_FIFO_MASK); + sc->cfg.txfifo_depth = + (reg_read(sc, IG4_REG_TX_TL) & IG4_FIFO_MASK) + 1; + reg_write(sc, IG4_REG_TX_TL, v); + v = reg_read(sc, IG4_REG_RX_TL); + reg_write(sc, IG4_REG_RX_TL, v | IG4_FIFO_MASK); + sc->cfg.rxfifo_depth = + (reg_read(sc, IG4_REG_RX_TL) & IG4_FIFO_MASK) + 1; + reg_write(sc, IG4_REG_RX_TL, v); + } + + /* Override hardware config with IC_clock-based counter values */ + if (ig4_timings < 2 && sc->version < nitems(ig4iic_hw)) { + hw = &ig4iic_hw[sc->version]; + sc->cfg.bus_speed = IG4_CTL_SPEED_FAST; + ig4iic_clk_params(hw, IG4_CTL_SPEED_STD, &sc->cfg.ss_scl_hcnt, + &sc->cfg.ss_scl_lcnt, &sc->cfg.ss_sda_hold); + ig4iic_clk_params(hw, IG4_CTL_SPEED_FAST, &sc->cfg.fs_scl_hcnt, + &sc->cfg.fs_scl_lcnt, &sc->cfg.fs_sda_hold); + if (hw->txfifo_depth != 0) + sc->cfg.txfifo_depth = hw->txfifo_depth; + if (hw->rxfifo_depth != 0) + sc->cfg.rxfifo_depth = hw->rxfifo_depth; + } else if (ig4_timings == 2) { + /* + * Timings of original ig4 driver: + * Program based on a 25000 Hz clock. This is a bit of a + * hack (obviously). The defaults are 400 and 470 for standard + * and 60 and 130 for fast. The defaults for standard fail + * utterly (presumably cause an abort) because the clock time + * is ~18.8ms by default. This brings it down to ~4ms. + */ + sc->cfg.bus_speed = IG4_CTL_SPEED_STD; + sc->cfg.ss_scl_hcnt = sc->cfg.fs_scl_hcnt = 100; + sc->cfg.ss_scl_lcnt = sc->cfg.fs_scl_lcnt = 125; + if (sc->version == IG4_SKYLAKE) + sc->cfg.ss_sda_hold = sc->cfg.fs_sda_hold = 28; + } + +#ifdef DEV_ACPI + /* Evaluate SSCN and FMCN ACPI methods to fetch timings */ + if (ig4_timings == 0 && (handle = acpi_get_handle(sc->dev)) != NULL) { + ig4iic_acpi_params(handle, "SSCN", &sc->cfg.ss_scl_hcnt, + &sc->cfg.ss_scl_lcnt, &sc->cfg.ss_sda_hold); + ig4iic_acpi_params(handle, "FMCN", &sc->cfg.fs_scl_hcnt, + &sc->cfg.fs_scl_lcnt, &sc->cfg.fs_sda_hold); + } +#endif + + if (bootverbose) { + device_printf(sc->dev, "Controller parameters:\n"); + printf(" Speed: %s\n", + sc->cfg.bus_speed == IG4_CTL_SPEED_STD ? "Std" : "Fast"); + printf(" Regs: HCNT :LCNT :SDAHLD\n"); + printf(" Std: 0x%04hx:0x%04hx:0x%04hx\n", + sc->cfg.ss_scl_hcnt, sc->cfg.ss_scl_lcnt, + sc->cfg.ss_sda_hold); + printf(" Fast: 0x%04hx:0x%04hx:0x%04hx\n", + sc->cfg.fs_scl_hcnt, sc->cfg.fs_scl_lcnt, + sc->cfg.fs_sda_hold); + printf(" FIFO: RX:0x%04x: TX:0x%04x\n", + sc->cfg.rxfifo_depth, sc->cfg.txfifo_depth); + } +} + +static int +ig4iic_set_config(ig4iic_softc_t *sc, bool reset) +{ + uint32_t v; + v = reg_read(sc, IG4_REG_DEVIDLE_CTRL); - if (sc->version == IG4_SKYLAKE && (v & IG4_RESTORE_REQUIRED) ) { + if (IG4_HAS_ADDREGS(sc->version) && (v & IG4_RESTORE_REQUIRED)) { reg_write(sc, IG4_REG_DEVIDLE_CTRL, IG4_DEVICE_IDLE | IG4_RESTORE_REQUIRED); reg_write(sc, IG4_REG_DEVIDLE_CTRL, 0); + pause("i2crst", 1); + reset = true; + } + if ((sc->version == IG4_HASWELL || sc->version == IG4_ATOM) && reset) { + reg_write(sc, IG4_REG_RESETS_HSW, IG4_RESETS_ASSERT_HSW); + reg_write(sc, IG4_REG_RESETS_HSW, IG4_RESETS_DEASSERT_HSW); + } else if (IG4_HAS_ADDREGS(sc->version) && reset) { reg_write(sc, IG4_REG_RESETS_SKL, IG4_RESETS_ASSERT_SKL); reg_write(sc, IG4_REG_RESETS_SKL, IG4_RESETS_DEASSERT_SKL); - DELAY(1000); } if (sc->version == IG4_ATOM) v = reg_read(sc, IG4_REG_COMP_TYPE); if (sc->version == IG4_HASWELL || sc->version == IG4_ATOM) { v = reg_read(sc, IG4_REG_COMP_PARAM1); v = reg_read(sc, IG4_REG_GENERAL); /* * The content of IG4_REG_GENERAL is different for each * controller version. */ if (sc->version == IG4_HASWELL && (v & IG4_GENERAL_SWMODE) == 0) { v |= IG4_GENERAL_SWMODE; reg_write(sc, IG4_REG_GENERAL, v); v = reg_read(sc, IG4_REG_GENERAL); } } if (sc->version == IG4_HASWELL) { v = reg_read(sc, IG4_REG_SW_LTR_VALUE); v = reg_read(sc, IG4_REG_AUTO_LTR_VALUE); - } else if (sc->version == IG4_SKYLAKE) { + } else if (IG4_HAS_ADDREGS(sc->version)) { v = reg_read(sc, IG4_REG_ACTIVE_LTR_VALUE); v = reg_read(sc, IG4_REG_IDLE_LTR_VALUE); } if (sc->version == IG4_HASWELL || sc->version == IG4_ATOM) { v = reg_read(sc, IG4_REG_COMP_VER); - if (v < IG4_COMP_MIN_VER) { - error = ENXIO; - goto done; - } + if (v < IG4_COMP_MIN_VER) + return(ENXIO); } - v = reg_read(sc, IG4_REG_SS_SCL_HCNT); - v = reg_read(sc, IG4_REG_SS_SCL_LCNT); - v = reg_read(sc, IG4_REG_FS_SCL_HCNT); - v = reg_read(sc, IG4_REG_FS_SCL_LCNT); - v = reg_read(sc, IG4_REG_SDA_HOLD); - v = reg_read(sc, IG4_REG_SS_SCL_HCNT); - reg_write(sc, IG4_REG_FS_SCL_HCNT, v); - v = reg_read(sc, IG4_REG_SS_SCL_LCNT); - reg_write(sc, IG4_REG_FS_SCL_LCNT, v); + if (set_controller(sc, 0)) { + device_printf(sc->dev, "controller error during attach-1\n"); + return (ENXIO); + } - /* - * Program based on a 25000 Hz clock. This is a bit of a - * hack (obviously). The defaults are 400 and 470 for standard - * and 60 and 130 for fast. The defaults for standard fail - * utterly (presumably cause an abort) because the clock time - * is ~18.8ms by default. This brings it down to ~4ms (for now). - */ - reg_write(sc, IG4_REG_SS_SCL_HCNT, 100); - reg_write(sc, IG4_REG_SS_SCL_LCNT, 125); - reg_write(sc, IG4_REG_FS_SCL_HCNT, 100); - reg_write(sc, IG4_REG_FS_SCL_LCNT, 125); - if (sc->version == IG4_SKYLAKE) - reg_write(sc, IG4_REG_SDA_HOLD, 28); + reg_read(sc, IG4_REG_CLR_INTR); + reg_write(sc, IG4_REG_INTR_MASK, 0); + sc->intr_mask = 0; + reg_write(sc, IG4_REG_SS_SCL_HCNT, sc->cfg.ss_scl_hcnt); + reg_write(sc, IG4_REG_SS_SCL_LCNT, sc->cfg.ss_scl_lcnt); + reg_write(sc, IG4_REG_FS_SCL_HCNT, sc->cfg.fs_scl_hcnt); + reg_write(sc, IG4_REG_FS_SCL_LCNT, sc->cfg.fs_scl_lcnt); + reg_write(sc, IG4_REG_SDA_HOLD, + (sc->cfg.bus_speed & IG4_CTL_SPEED_MASK) == IG4_CTL_SPEED_STD ? + sc->cfg.ss_sda_hold : sc->cfg.fs_sda_hold); + /* * Use a threshold of 1 so we get interrupted on each character, * allowing us to use mtx_sleep() in our poll code. Not perfect * but this is better than using DELAY() for receiving data. * * See ig4_var.h for details on interrupt handler synchronization. */ - reg_write(sc, IG4_REG_RX_TL, 1); + reg_write(sc, IG4_REG_RX_TL, 0); + reg_write(sc, IG4_REG_TX_TL, 0); reg_write(sc, IG4_REG_CTL, IG4_CTL_MASTER | IG4_CTL_SLAVE_DISABLE | IG4_CTL_RESTARTEN | - IG4_CTL_SPEED_STD); + (sc->cfg.bus_speed & IG4_CTL_SPEED_MASK)); + /* Force setting of the target address on the next transfer */ + sc->slave_valid = 0; + + return (0); +} + +/* + * Called from ig4iic_pci_attach/detach() + */ +int +ig4iic_attach(ig4iic_softc_t *sc) +{ + int error; + + mtx_init(&sc->io_lock, "IG4 I/O lock", NULL, MTX_SPIN); + sx_init(&sc->call_lock, "IG4 call lock"); + + ig4iic_get_config(sc); + + error = ig4iic_set_config(sc, IG4_HAS_ADDREGS(sc->version)); + if (error) + goto done; + sc->iicbus = device_add_child(sc->dev, "iicbus", -1); if (sc->iicbus == NULL) { device_printf(sc->dev, "iicbus driver not found\n"); error = ENXIO; goto done; } -#if 0 - /* - * Don't do this, it blows up the PCI config - */ - if (sc->version == IG4_HASWELL || sc->version == IG4_ATOM) { - reg_write(sc, IG4_REG_RESETS_HSW, IG4_RESETS_ASSERT_HSW); - reg_write(sc, IG4_REG_RESETS_HSW, IG4_RESETS_DEASSERT_HSW); - } else if (sc->version = IG4_SKYLAKE) { - reg_write(sc, IG4_REG_RESETS_SKL, IG4_RESETS_ASSERT_SKL); - reg_write(sc, IG4_REG_RESETS_SKL, IG4_RESETS_DEASSERT_SKL); - } -#endif - - mtx_lock(&sc->io_lock); - if (set_controller(sc, 0)) - device_printf(sc->dev, "controller error during attach-1\n"); - if (set_controller(sc, IG4_I2C_ENABLE)) + if (set_controller(sc, IG4_I2C_ENABLE)) { device_printf(sc->dev, "controller error during attach-2\n"); - mtx_unlock(&sc->io_lock); + error = ENXIO; + goto done; + } + if (set_controller(sc, 0)) { + device_printf(sc->dev, "controller error during attach-3\n"); + error = ENXIO; + goto done; + } error = bus_setup_intr(sc->dev, sc->intr_res, INTR_TYPE_MISC | INTR_MPSAFE, - NULL, ig4iic_intr, sc, &sc->intr_handle); + ig4iic_intr, NULL, sc, &sc->intr_handle); if (error) { device_printf(sc->dev, "Unable to setup irq: error %d\n", error); } - sc->enum_hook.ich_func = ig4iic_start; - sc->enum_hook.ich_arg = sc->dev; - - /* - * We have to wait until interrupts are enabled. I2C read and write - * only works if the interrupts are available. - */ - if (config_intrhook_establish(&sc->enum_hook) != 0) - error = ENOMEM; - else - error = 0; - -done: - return (error); -} - -void -ig4iic_start(void *xdev) -{ - int error; - ig4iic_softc_t *sc; - device_t dev = (device_t)xdev; - - sc = device_get_softc(dev); - - config_intrhook_disestablish(&sc->enum_hook); - error = bus_generic_attach(sc->dev); if (error) { device_printf(sc->dev, "failed to attach child: error %d\n", error); } + +done: + return (error); } int ig4iic_detach(ig4iic_softc_t *sc) { int error; if (device_is_attached(sc->dev)) { error = bus_generic_detach(sc->dev); if (error) return (error); } if (sc->iicbus) device_delete_child(sc->dev, sc->iicbus); if (sc->intr_handle) bus_teardown_intr(sc->dev, sc->intr_res, sc->intr_handle); sx_xlock(&sc->call_lock); - mtx_lock(&sc->io_lock); sc->iicbus = NULL; sc->intr_handle = NULL; reg_write(sc, IG4_REG_INTR_MASK, 0); set_controller(sc, 0); - mtx_unlock(&sc->io_lock); sx_xunlock(&sc->call_lock); mtx_destroy(&sc->io_lock); sx_destroy(&sc->call_lock); return (0); } +int +ig4iic_suspend(ig4iic_softc_t *sc) +{ + int error; + + /* suspend all children */ + error = bus_generic_suspend(sc->dev); + + sx_xlock(&sc->call_lock); + set_controller(sc, 0); + if (IG4_HAS_ADDREGS(sc->version)) { + /* + * Place the device in the idle state, just to be safe + */ + reg_write(sc, IG4_REG_DEVIDLE_CTRL, IG4_DEVICE_IDLE); + /* + * Controller can become dysfunctional if I2C lines are pulled + * down when suspend procedure turns off power to I2C device. + * Place device in the reset state to avoid this. + */ + reg_write(sc, IG4_REG_RESETS_SKL, IG4_RESETS_ASSERT_SKL); + } + sx_xunlock(&sc->call_lock); + + return (error); +} + +int ig4iic_resume(ig4iic_softc_t *sc) +{ + int error; + + sx_xlock(&sc->call_lock); + if (ig4iic_set_config(sc, IG4_HAS_ADDREGS(sc->version))) + device_printf(sc->dev, "controller error during resume\n"); + sx_xunlock(&sc->call_lock); + + error = bus_generic_resume(sc->dev); + + return (error); +} + /* * Interrupt Operation, see ig4_var.h for locking semantics. */ -static void +static int ig4iic_intr(void *cookie) { ig4iic_softc_t *sc = cookie; - uint32_t status; + int retval = FILTER_STRAY; - mtx_lock(&sc->io_lock); -/* reg_write(sc, IG4_REG_INTR_MASK, IG4_INTR_STOP_DET);*/ - reg_read(sc, IG4_REG_CLR_INTR); - status = reg_read(sc, IG4_REG_I2C_STA); - while (status & IG4_STATUS_RX_NOTEMPTY) { - sc->rbuf[sc->rnext & IG4_RBUFMASK] = - (uint8_t)reg_read(sc, IG4_REG_DATA_CMD); - ++sc->rnext; - status = reg_read(sc, IG4_REG_I2C_STA); + mtx_lock_spin(&sc->io_lock); + /* Ignore stray interrupts */ + if (sc->intr_mask != 0 && reg_read(sc, IG4_REG_INTR_STAT) != 0) { + /* Interrupt bits are cleared in wait_intr() loop */ + ig4iic_set_intr_mask(sc, 0); + wakeup(sc); + retval = FILTER_HANDLED; } + mtx_unlock_spin(&sc->io_lock); - /* - * Workaround to trigger pending interrupt if IG4_REG_INTR_STAT - * is changed after clearing it - */ - if (sc->access_intr_mask != 0) { - status = reg_read(sc, IG4_REG_INTR_MASK); - if (status != 0) { - reg_write(sc, IG4_REG_INTR_MASK, 0); - reg_write(sc, IG4_REG_INTR_MASK, status); - } - } - - wakeup(sc); - mtx_unlock(&sc->io_lock); + return (retval); } #define REGDUMP(sc, reg) \ device_printf(sc->dev, " %-23s %08x\n", #reg, reg_read(sc, reg)) static void ig4iic_dump(ig4iic_softc_t *sc) { device_printf(sc->dev, "ig4iic register dump:\n"); REGDUMP(sc, IG4_REG_CTL); REGDUMP(sc, IG4_REG_TAR_ADD); REGDUMP(sc, IG4_REG_SS_SCL_HCNT); REGDUMP(sc, IG4_REG_SS_SCL_LCNT); REGDUMP(sc, IG4_REG_FS_SCL_HCNT); REGDUMP(sc, IG4_REG_FS_SCL_LCNT); REGDUMP(sc, IG4_REG_INTR_STAT); REGDUMP(sc, IG4_REG_INTR_MASK); REGDUMP(sc, IG4_REG_RAW_INTR_STAT); REGDUMP(sc, IG4_REG_RX_TL); REGDUMP(sc, IG4_REG_TX_TL); REGDUMP(sc, IG4_REG_I2C_EN); REGDUMP(sc, IG4_REG_I2C_STA); REGDUMP(sc, IG4_REG_TXFLR); REGDUMP(sc, IG4_REG_RXFLR); REGDUMP(sc, IG4_REG_SDA_HOLD); REGDUMP(sc, IG4_REG_TX_ABRT_SOURCE); REGDUMP(sc, IG4_REG_SLV_DATA_NACK); REGDUMP(sc, IG4_REG_DMA_CTRL); REGDUMP(sc, IG4_REG_DMA_TDLR); REGDUMP(sc, IG4_REG_DMA_RDLR); REGDUMP(sc, IG4_REG_SDA_SETUP); REGDUMP(sc, IG4_REG_ENABLE_STATUS); - if (sc->version == IG4_HASWELL || sc->version == IG4_ATOM) { - REGDUMP(sc, IG4_REG_COMP_PARAM1); - REGDUMP(sc, IG4_REG_COMP_VER); - } + REGDUMP(sc, IG4_REG_COMP_PARAM1); + REGDUMP(sc, IG4_REG_COMP_VER); if (sc->version == IG4_ATOM) { REGDUMP(sc, IG4_REG_COMP_TYPE); REGDUMP(sc, IG4_REG_CLK_PARMS); } if (sc->version == IG4_HASWELL || sc->version == IG4_ATOM) { REGDUMP(sc, IG4_REG_RESETS_HSW); REGDUMP(sc, IG4_REG_GENERAL); } else if (sc->version == IG4_SKYLAKE) { REGDUMP(sc, IG4_REG_RESETS_SKL); } if (sc->version == IG4_HASWELL) { REGDUMP(sc, IG4_REG_SW_LTR_VALUE); REGDUMP(sc, IG4_REG_AUTO_LTR_VALUE); - } else if (sc->version == IG4_SKYLAKE) { + } else if (IG4_HAS_ADDREGS(sc->version)) { REGDUMP(sc, IG4_REG_ACTIVE_LTR_VALUE); REGDUMP(sc, IG4_REG_IDLE_LTR_VALUE); } } #undef REGDUMP -DRIVER_MODULE(iicbus, ig4iic_acpi, iicbus_driver, iicbus_devclass, NULL, NULL); -DRIVER_MODULE(iicbus, ig4iic_pci, iicbus_driver, iicbus_devclass, NULL, NULL); +devclass_t ig4iic_devclass; + +DRIVER_MODULE(iicbus, ig4iic, iicbus_driver, iicbus_devclass, NULL, NULL); +MODULE_DEPEND(ig4iic, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); +MODULE_VERSION(ig4iic, 1); Index: stable/12/sys/dev/ichiic/ig4_pci.c =================================================================== --- stable/12/sys/dev/ichiic/ig4_pci.c (revision 355993) +++ stable/12/sys/dev/ichiic/ig4_pci.c (revision 355994) @@ -1,238 +1,280 @@ /* * Copyright (c) 2014 The DragonFly Project. All rights reserved. * * This code is derived from software contributed to The DragonFly Project * by Matthew Dillon and was subsequently ported * to FreeBSD by Michael Gmelin * * 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. * 3. Neither the name of The DragonFly Project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific, prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 * COPYRIGHT HOLDERS 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 __FBSDID("$FreeBSD$"); /* * Intel fourth generation mobile cpus integrated I2C device. * * See ig4_reg.h for datasheet reference and notes. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int ig4iic_pci_detach(device_t dev); #define PCI_CHIP_LYNXPT_LP_I2C_1 0x9c618086 #define PCI_CHIP_LYNXPT_LP_I2C_2 0x9c628086 #define PCI_CHIP_BRASWELL_I2C_1 0x22c18086 #define PCI_CHIP_BRASWELL_I2C_2 0x22c28086 #define PCI_CHIP_BRASWELL_I2C_3 0x22c38086 #define PCI_CHIP_BRASWELL_I2C_5 0x22c58086 #define PCI_CHIP_BRASWELL_I2C_6 0x22c68086 #define PCI_CHIP_BRASWELL_I2C_7 0x22c78086 #define PCI_CHIP_SKYLAKE_I2C_0 0x9d608086 #define PCI_CHIP_SKYLAKE_I2C_1 0x9d618086 #define PCI_CHIP_SKYLAKE_I2C_2 0x9d628086 #define PCI_CHIP_SKYLAKE_I2C_3 0x9d638086 #define PCI_CHIP_SKYLAKE_I2C_4 0x9d648086 #define PCI_CHIP_SKYLAKE_I2C_5 0x9d658086 #define PCI_CHIP_KABYLAKE_I2C_0 0xa1608086 #define PCI_CHIP_KABYLAKE_I2C_1 0xa1618086 #define PCI_CHIP_APL_I2C_0 0x5aac8086 #define PCI_CHIP_APL_I2C_1 0x5aae8086 #define PCI_CHIP_APL_I2C_2 0x5ab08086 #define PCI_CHIP_APL_I2C_3 0x5ab28086 #define PCI_CHIP_APL_I2C_4 0x5ab48086 #define PCI_CHIP_APL_I2C_5 0x5ab68086 #define PCI_CHIP_APL_I2C_6 0x5ab88086 #define PCI_CHIP_APL_I2C_7 0x5aba8086 +#define PCI_CHIP_CANNONLAKE_LP_I2C_0 0x9dc58086 +#define PCI_CHIP_CANNONLAKE_LP_I2C_1 0x9dc68086 +#define PCI_CHIP_CANNONLAKE_LP_I2C_2 0x9de88086 +#define PCI_CHIP_CANNONLAKE_LP_I2C_3 0x9de98086 +#define PCI_CHIP_CANNONLAKE_LP_I2C_4 0x9dea8086 +#define PCI_CHIP_CANNONLAKE_LP_I2C_5 0x9deb8086 +#define PCI_CHIP_CANNONLAKE_H_I2C_0 0xa3688086 +#define PCI_CHIP_CANNONLAKE_H_I2C_1 0xa3698086 +#define PCI_CHIP_CANNONLAKE_H_I2C_2 0xa36a8086 +#define PCI_CHIP_CANNONLAKE_H_I2C_3 0xa36b8086 struct ig4iic_pci_device { uint32_t devid; const char *desc; enum ig4_vers version; }; static struct ig4iic_pci_device ig4iic_pci_devices[] = { { PCI_CHIP_LYNXPT_LP_I2C_1, "Intel Lynx Point-LP I2C Controller-1", IG4_HASWELL}, { PCI_CHIP_LYNXPT_LP_I2C_2, "Intel Lynx Point-LP I2C Controller-2", IG4_HASWELL}, { PCI_CHIP_BRASWELL_I2C_1, "Intel Braswell Serial I/O I2C Port 1", IG4_ATOM}, { PCI_CHIP_BRASWELL_I2C_2, "Intel Braswell Serial I/O I2C Port 2", IG4_ATOM}, { PCI_CHIP_BRASWELL_I2C_3, "Intel Braswell Serial I/O I2C Port 3", IG4_ATOM}, { PCI_CHIP_BRASWELL_I2C_5, "Intel Braswell Serial I/O I2C Port 5", IG4_ATOM}, { PCI_CHIP_BRASWELL_I2C_6, "Intel Braswell Serial I/O I2C Port 6", IG4_ATOM}, { PCI_CHIP_BRASWELL_I2C_7, "Intel Braswell Serial I/O I2C Port 7", IG4_ATOM}, { PCI_CHIP_SKYLAKE_I2C_0, "Intel Sunrise Point-LP I2C Controller-0", IG4_SKYLAKE}, { PCI_CHIP_SKYLAKE_I2C_1, "Intel Sunrise Point-LP I2C Controller-1", IG4_SKYLAKE}, { PCI_CHIP_SKYLAKE_I2C_2, "Intel Sunrise Point-LP I2C Controller-2", IG4_SKYLAKE}, { PCI_CHIP_SKYLAKE_I2C_3, "Intel Sunrise Point-LP I2C Controller-3", IG4_SKYLAKE}, { PCI_CHIP_SKYLAKE_I2C_4, "Intel Sunrise Point-LP I2C Controller-4", IG4_SKYLAKE}, { PCI_CHIP_SKYLAKE_I2C_5, "Intel Sunrise Point-LP I2C Controller-5", IG4_SKYLAKE}, { PCI_CHIP_KABYLAKE_I2C_0, "Intel Sunrise Point-H I2C Controller-0", IG4_SKYLAKE}, { PCI_CHIP_KABYLAKE_I2C_1, "Intel Sunrise Point-H I2C Controller-1", IG4_SKYLAKE}, { PCI_CHIP_APL_I2C_0, "Intel Apollo Lake I2C Controller-0", IG4_APL}, { PCI_CHIP_APL_I2C_1, "Intel Apollo Lake I2C Controller-1", IG4_APL}, { PCI_CHIP_APL_I2C_2, "Intel Apollo Lake I2C Controller-2", IG4_APL}, { PCI_CHIP_APL_I2C_3, "Intel Apollo Lake I2C Controller-3", IG4_APL}, { PCI_CHIP_APL_I2C_4, "Intel Apollo Lake I2C Controller-4", IG4_APL}, { PCI_CHIP_APL_I2C_5, "Intel Apollo Lake I2C Controller-5", IG4_APL}, { PCI_CHIP_APL_I2C_6, "Intel Apollo Lake I2C Controller-6", IG4_APL}, - { PCI_CHIP_APL_I2C_7, "Intel Apollo Lake I2C Controller-7", IG4_APL} + { PCI_CHIP_APL_I2C_7, "Intel Apollo Lake I2C Controller-7", IG4_APL}, + { PCI_CHIP_CANNONLAKE_LP_I2C_0, "Intel Cannon Lake-LP I2C Controller-0", IG4_CANNONLAKE}, + { PCI_CHIP_CANNONLAKE_LP_I2C_1, "Intel Cannon Lake-LP I2C Controller-1", IG4_CANNONLAKE}, + { PCI_CHIP_CANNONLAKE_LP_I2C_2, "Intel Cannon Lake-LP I2C Controller-2", IG4_CANNONLAKE}, + { PCI_CHIP_CANNONLAKE_LP_I2C_3, "Intel Cannon Lake-LP I2C Controller-3", IG4_CANNONLAKE}, + { PCI_CHIP_CANNONLAKE_LP_I2C_4, "Intel Cannon Lake-LP I2C Controller-4", IG4_CANNONLAKE}, + { PCI_CHIP_CANNONLAKE_LP_I2C_5, "Intel Cannon Lake-LP I2C Controller-5", IG4_CANNONLAKE}, + { PCI_CHIP_CANNONLAKE_H_I2C_0, "Intel Cannon Lake-H I2C Controller-0", IG4_CANNONLAKE}, + { PCI_CHIP_CANNONLAKE_H_I2C_1, "Intel Cannon Lake-H I2C Controller-1", IG4_CANNONLAKE}, + { PCI_CHIP_CANNONLAKE_H_I2C_2, "Intel Cannon Lake-H I2C Controller-2", IG4_CANNONLAKE}, + { PCI_CHIP_CANNONLAKE_H_I2C_3, "Intel Cannon Lake-H I2C Controller-3", IG4_CANNONLAKE}, }; static int ig4iic_pci_probe(device_t dev) { ig4iic_softc_t *sc = device_get_softc(dev); uint32_t devid; int i; devid = pci_get_devid(dev); for (i = 0; i < nitems(ig4iic_pci_devices); i++) { if (ig4iic_pci_devices[i].devid == devid) { device_set_desc(dev, ig4iic_pci_devices[i].desc); sc->version = ig4iic_pci_devices[i].version; return (BUS_PROBE_DEFAULT); } } return (ENXIO); } static int ig4iic_pci_attach(device_t dev) { ig4iic_softc_t *sc = device_get_softc(dev); int error; sc->dev = dev; sc->regs_rid = PCIR_BAR(0); sc->regs_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->regs_rid, RF_ACTIVE); if (sc->regs_res == NULL) { device_printf(dev, "unable to map registers\n"); ig4iic_pci_detach(dev); return (ENXIO); } sc->intr_rid = 0; if (pci_alloc_msi(dev, &sc->intr_rid)) { device_printf(dev, "Using MSI\n"); } sc->intr_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->intr_rid, RF_SHAREABLE | RF_ACTIVE); if (sc->intr_res == NULL) { device_printf(dev, "unable to map interrupt\n"); ig4iic_pci_detach(dev); return (ENXIO); } sc->platform_attached = 1; error = ig4iic_attach(sc); if (error) ig4iic_pci_detach(dev); return (error); } static int ig4iic_pci_detach(device_t dev) { ig4iic_softc_t *sc = device_get_softc(dev); int error; if (sc->platform_attached) { error = ig4iic_detach(sc); if (error) return (error); sc->platform_attached = 0; } if (sc->intr_res) { bus_release_resource(dev, SYS_RES_IRQ, sc->intr_rid, sc->intr_res); sc->intr_res = NULL; } if (sc->intr_rid != 0) pci_release_msi(dev); if (sc->regs_res) { bus_release_resource(dev, SYS_RES_MEMORY, sc->regs_rid, sc->regs_res); sc->regs_res = NULL; } return (0); } +static int +ig4iic_pci_suspend(device_t dev) +{ + ig4iic_softc_t *sc = device_get_softc(dev); + + return (ig4iic_suspend(sc)); +} + +static int +ig4iic_pci_resume(device_t dev) +{ + ig4iic_softc_t *sc = device_get_softc(dev); + + return (ig4iic_resume(sc)); +} + static device_method_t ig4iic_pci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ig4iic_pci_probe), DEVMETHOD(device_attach, ig4iic_pci_attach), DEVMETHOD(device_detach, ig4iic_pci_detach), + DEVMETHOD(device_suspend, ig4iic_pci_suspend), + DEVMETHOD(device_resume, ig4iic_pci_resume), + /* Bus interface */ + DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), + DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), + DEVMETHOD(bus_alloc_resource, bus_generic_alloc_resource), + DEVMETHOD(bus_release_resource, bus_generic_release_resource), + DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), + DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), + DEVMETHOD(bus_adjust_resource, bus_generic_adjust_resource), + + /* iicbus interface */ DEVMETHOD(iicbus_transfer, ig4iic_transfer), DEVMETHOD(iicbus_reset, ig4iic_reset), - DEVMETHOD(iicbus_callback, iicbus_null_callback), + DEVMETHOD(iicbus_callback, ig4iic_callback), DEVMETHOD_END }; static driver_t ig4iic_pci_driver = { - "ig4iic_pci", + "ig4iic", ig4iic_pci_methods, sizeof(struct ig4iic_softc) }; -static devclass_t ig4iic_pci_devclass; - -DRIVER_MODULE_ORDERED(ig4iic_pci, pci, ig4iic_pci_driver, ig4iic_pci_devclass, 0, 0, +DRIVER_MODULE_ORDERED(ig4iic, pci, ig4iic_pci_driver, ig4iic_devclass, 0, 0, SI_ORDER_ANY); -MODULE_DEPEND(ig4iic_pci, pci, 1, 1, 1); -MODULE_DEPEND(ig4iic_pci, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); -MODULE_VERSION(ig4iic_pci, 1); -/* - * Loading this module breaks suspend/resume on laptops - * Do not add MODULE_PNP_INFO until it's impleneted - */ +MODULE_DEPEND(ig4iic, pci, 1, 1, 1); +MODULE_PNP_INFO("W32:vendor/device", pci, ig4iic, ig4iic_pci_devices, + nitems(ig4iic_pci_devices)); Index: stable/12/sys/dev/ichiic/ig4_reg.h =================================================================== --- stable/12/sys/dev/ichiic/ig4_reg.h (revision 355993) +++ stable/12/sys/dev/ichiic/ig4_reg.h (revision 355994) @@ -1,651 +1,659 @@ /* * Copyright (c) 2014 The DragonFly Project. All rights reserved. * * This code is derived from software contributed to The DragonFly Project * by Matthew Dillon and was subsequently ported * to FreeBSD by Michael Gmelin * * 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. * 3. Neither the name of The DragonFly Project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific, prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 * COPYRIGHT HOLDERS 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$ */ /* * Intel fourth generation mobile cpus integrated I2C device. * * Datasheet reference: Section 22. * * http://www.intel.com/content/www/us/en/processors/core/4th-gen-core-family-mobile-i-o-datasheet.html?wapkw=datasheets+4th+generation * * This is a from-scratch driver under the BSD license using the Intel data * sheet and the linux driver for reference. All code is freshly written * without referencing the linux driver code. However, during testing * I am also using the linux driver code as a reference to help resolve any * issues that come. These will be specifically documented in the code. * * This controller is an I2C master only and cannot act as a slave. The IO * voltage should be set by the BIOS. Standard (100Kb/s) and Fast (400Kb/s) * and fast mode plus (1MB/s) is supported. High speed mode (3.4 MB/s) is NOT * supported. */ #ifndef _ICHIIC_IG4_REG_H_ #define _ICHIIC_IG4_REG_H_ /* * 22.2 MMIO registers can be accessed through BAR0 in PCI mode or through * BAR1 when in ACPI mode. * * Register width is 32-bits * * 22.2 Default Values on device reset are 0 except as specified here: * TAR_ADD 0x00000055 * SS_SCL_HCNT 0x00000264 * SS_SCL_LCNT 0x000002C2 * FS_SCL_HCNT 0x0000006E * FS_SCL_LCNT 0x000000CF * INTR_MASK 0x000008FF * I2C_STA 0x00000006 * SDA_HOLD 0x00000001 * SDA_SETUP 0x00000064 * COMP_PARAM1 0x00FFFF6E */ #define IG4_REG_CTL 0x0000 /* RW Control Register */ #define IG4_REG_TAR_ADD 0x0004 /* RW Target Address */ #define IG4_REG_HS_MADDR 0x000C /* RW High Speed Master Mode Code Address*/ #define IG4_REG_DATA_CMD 0x0010 /* RW Data Buffer and Command */ #define IG4_REG_SS_SCL_HCNT 0x0014 /* RW Std Speed clock High Count */ #define IG4_REG_SS_SCL_LCNT 0x0018 /* RW Std Speed clock Low Count */ #define IG4_REG_FS_SCL_HCNT 0x001C /* RW Fast Speed clock High Count */ #define IG4_REG_FS_SCL_LCNT 0x0020 /* RW Fast Speed clock Low Count */ #define IG4_REG_INTR_STAT 0x002C /* RO Interrupt Status */ #define IG4_REG_INTR_MASK 0x0030 /* RW Interrupt Mask */ #define IG4_REG_RAW_INTR_STAT 0x0034 /* RO Raw Interrupt Status */ #define IG4_REG_RX_TL 0x0038 /* RW Receive FIFO Threshold */ #define IG4_REG_TX_TL 0x003C /* RW Transmit FIFO Threshold */ #define IG4_REG_CLR_INTR 0x0040 /* RO Clear Interrupt */ #define IG4_REG_CLR_RX_UNDER 0x0044 /* RO Clear RX_Under Interrupt */ #define IG4_REG_CLR_RX_OVER 0x0048 /* RO Clear RX_Over Interrupt */ #define IG4_REG_CLR_TX_OVER 0x004C /* RO Clear TX_Over Interrupt */ #define IG4_REG_CLR_RD_REQ 0x0050 /* RO Clear RD_Req Interrupt */ #define IG4_REG_CLR_TX_ABORT 0x0054 /* RO Clear TX_Abort Interrupt */ #define IG4_REG_CLR_RX_DONE 0x0058 /* RO Clear RX_Done Interrupt */ #define IG4_REG_CLR_ACTIVITY 0x005C /* RO Clear Activity Interrupt */ #define IG4_REG_CLR_STOP_DET 0x0060 /* RO Clear STOP Detection Int */ #define IG4_REG_CLR_START_DET 0x0064 /* RO Clear START Detection Int */ #define IG4_REG_CLR_GEN_CALL 0x0068 /* RO Clear General Call Interrupt */ #define IG4_REG_I2C_EN 0x006C /* RW I2C Enable */ #define IG4_REG_I2C_STA 0x0070 /* RO I2C Status */ #define IG4_REG_TXFLR 0x0074 /* RO Transmit FIFO Level */ #define IG4_REG_RXFLR 0x0078 /* RO Receive FIFO Level */ #define IG4_REG_SDA_HOLD 0x007C /* RW SDA Hold Time Length */ #define IG4_REG_TX_ABRT_SOURCE 0x0080 /* RO Transmit Abort Source */ #define IG4_REG_SLV_DATA_NACK 0x0084 /* RW General Slave Data NACK */ #define IG4_REG_DMA_CTRL 0x0088 /* RW DMA Control */ #define IG4_REG_DMA_TDLR 0x008C /* RW DMA Transmit Data Level */ #define IG4_REG_DMA_RDLR 0x0090 /* RW DMA Receive Data Level */ #define IG4_REG_SDA_SETUP 0x0094 /* RW SDA Setup */ #define IG4_REG_ACK_GENERAL_CALL 0x0098 /* RW I2C ACK General Call */ #define IG4_REG_ENABLE_STATUS 0x009C /* RO Enable Status */ -/* Available at least on Atom SoCs and Haswell mobile. */ +/* Available at least on Atom SoCs, Haswell mobile and some Skylakes. */ #define IG4_REG_COMP_PARAM1 0x00F4 /* RO Component Parameter */ #define IG4_REG_COMP_VER 0x00F8 /* RO Component Version */ /* Available at least on Atom SoCs */ #define IG4_REG_COMP_TYPE 0x00FC /* RO Probe width/endian? (linux) */ -/* Available on Skylake-U/Y and Kaby Lake-U/Y */ +/* 0x200-0x2FF - Additional registers available on Skylake-U/Y and others */ #define IG4_REG_RESETS_SKL 0x0204 /* RW Reset Register */ #define IG4_REG_ACTIVE_LTR_VALUE 0x0210 /* RW Active LTR Value */ #define IG4_REG_IDLE_LTR_VALUE 0x0214 /* RW Idle LTR Value */ #define IG4_REG_TX_ACK_COUNT 0x0218 /* RO TX ACK Count */ #define IG4_REG_RX_BYTE_COUNT 0x021C /* RO RX ACK Count */ #define IG4_REG_DEVIDLE_CTRL 0x024C /* RW Device Control */ /* Available at least on Atom SoCs */ #define IG4_REG_CLK_PARMS 0x0800 /* RW Clock Parameters */ /* Available at least on Atom SoCs and Haswell mobile */ #define IG4_REG_RESETS_HSW 0x0804 /* RW Reset Register */ #define IG4_REG_GENERAL 0x0808 /* RW General Register */ /* These LTR config registers are at least available on Haswell mobile. */ #define IG4_REG_SW_LTR_VALUE 0x0810 /* RW SW LTR Value */ #define IG4_REG_AUTO_LTR_VALUE 0x0814 /* RW Auto LTR Value */ /* * CTL - Control Register 22.2.1 * Default Value: 0x0000007F. * * RESTARTEN - RW Restart Enable * 10BIT - RW Controller operates in 10-bit mode, else 7-bit * * NOTE: When restart is disabled the controller is incapable of * performing the following functions: * * Sending a START Byte * Performing any high-speed mode op * Performing direction changes in combined format mode * Performing a read operation with a 10-bit address * * Attempting to perform the above operations will result in the * TX_ABORT bit being set in RAW_INTR_STAT. */ #define IG4_CTL_SLAVE_DISABLE 0x0040 /* snarfed from linux */ #define IG4_CTL_RESTARTEN 0x0020 /* Allow Restart when master */ #define IG4_CTL_10BIT 0x0010 /* ctlr accepts 10-bit addresses */ +#define IG4_CTL_SPEED_MASK 0x0006 /* speed at which the I2C operates */ +#define IG4_CTL_MASTER 0x0001 /* snarfed from linux */ + +#define IG4_CTL_SPEED_HIGH 0x0006 /* snarfed from linux */ #define IG4_CTL_SPEED_FAST 0x0004 /* snarfed from linux */ #define IG4_CTL_SPEED_STD 0x0002 /* snarfed from linux */ -#define IG4_CTL_MASTER 0x0001 /* snarfed from linux */ /* * TAR_ADD - Target Address Register 22.2.2 * Default Value: 0x00000055F * * 10BIT - RW controller starts its transfers in 10-bit * address mode, else 7-bit. * * SPECIAL - RW Indicates whether software performs a General Call * or START BYTE command. * * 0 Ignore GC_OR_START and use TAR address. * * 1 Perform special I2C Command based on GC_OR_START. * * GC_OR_START - RW (only if SPECIAL is set) * * 0 General Call Address. After issuing a General Call, * only writes may be performed. Attempting to issue * a read command results in IX_ABRT in RAW_INTR_STAT. * The controller remains in General Call mode until * bit 11 (SPECIAL) is cleared. * * 1 START BYTE. * * * IC_TAR - RW when transmitting a general call, these bits are * ignored. To generate a START BYTE, the address * needs to be written into these bits once. * * This register should only be updated when the IIC is disabled (I2C_ENABLE=0) */ #define IG4_TAR_10BIT 0x1000 /* start xfer in 10-bit mode */ #define IG4_TAR_SPECIAL 0x0800 /* Perform special command */ #define IG4_TAR_GC_OR_START 0x0400 /* General Call or Start */ #define IG4_TAR_ADDR_MASK 0x03FF /* Target address */ /* * TAR_DATA_CMD - Data Buffer and Command Register 22.2.3 * * RESTART - RW This bit controls whether a forced RESTART is * issued before the byte is sent or received. * * 0 If not set a RESTART is only issued if the transfer * direction is changing from the previous command. * * 1 A RESTART is issued before the byte is sent or * received, regardless of whether or not the transfer * direction is changing from the previous command. * * STOP - RW This bit controls whether a STOP is issued after * the byte is sent or received. * * 0 STOP is not issued after this byte, regardless * of whether or not the Tx FIFO is empty. * * 1 STOP is issued after this byte, regardless of * whether or not the Tx FIFO is empty. If the * Tx FIFO is not empty the master immediately tries * to start a new transfer by issuing a START and * arbitrating for the bus. * * i.e. the STOP is issued along with this byte, * within the write stream. * * COMMAND - RW Control whether a read or write is performed. * * 0 WRITE * * 1 READ * * DATA (7:0) - RW Contains the data to be transmitted or received * on the I2C bus. * * NOTE: Writing to this register causes a START + slave + RW to be * issued if the direction has changed or the last data byte was * sent with a STOP. * * NOTE: We control termination? so this register must be written * for each byte we wish to receive. We can then drain the * receive FIFO. */ #define IG4_DATA_RESTART 0x0400 /* Force RESTART */ #define IG4_DATA_STOP 0x0200 /* Force STOP[+START] */ #define IG4_DATA_COMMAND_RD 0x0100 /* bus direction 0=write 1=read */ #define IG4_DATA_MASK 0x00FF /* * SS_SCL_HCNT - Standard Speed Clock High Count Register 22.2.4 * SS_SCL_LCNT - Standard Speed Clock Low Count Register 22.2.5 * FS_SCL_HCNT - Fast Speed Clock High Count Register 22.2.6 * FS_SCL_LCNT - Fast Speed Clock Low Count Register 22.2.7 * * COUNT (15:0) - Set the period count to a value between 6 and * 65525. */ #define IG4_SCL_CLOCK_MASK 0xFFFFU /* count bits in register */ /* * INTR_STAT - (RO) Interrupt Status Register 22.2.8 * INTR_MASK - (RW) Interrupt Mask Register 22.2.9 * RAW_INTR_STAT- (RO) Raw Interrupt Status Register 22.2.10 * * GEN_CALL Set only when a general call (broadcast) address * is received and acknowleged, stays set until * cleared by reading CLR_GEN_CALL. * * START_DET Set when a START or RESTART condition has occurred * on the interface. * * STOP_DET Set when a STOP condition has occurred on the * interface. * * ACTIVITY Set by any activity on the interface. Cleared * by reading CLR_ACTIVITY or CLR_INTR. * * TX_ABRT Indicates the controller as a transmitter is * unable to complete the intended action. When set, * the controller will hold the TX FIFO in a reset * state (flushed) until CLR_TX_ABORT is read to * clear the condition. Once cleared, the TX FIFO * will be available again. * * TX_EMPTY Indicates that the transmitter is at or below * the specified TX_TL threshold. Automatically * cleared by HW when the buffer level goes above * the threshold. * * TX_OVER Indicates that the processor attempted to write * to the TX FIFO while the TX FIFO was full. Cleared * by reading CLR_TX_OVER. * * RX_FULL Indicates that the receive FIFO has reached or * exceeded the specified RX_TL threshold. Cleared * by HW when the cpu drains the FIFO to below the * threshold. * * RX_OVER Indicates that the receive FIFO was unable to * accept new data and data was lost. Cleared by * reading CLR_RX_OVER. * * RX_UNDER Indicates that the cpu attempted to read data * from the receive buffer while the RX FIFO was * empty. Cleared by reading CLR_RX_UNDER. * * NOTES ON RAW_INTR_STAT: * * This register can be used to monitor the GEN_CALL, START_DET, * STOP_DET, ACTIVITY, TX_ABRT, TX_EMPTY, TX_OVER, RX_FULL, RX_OVER, * and RX_UNDER bits. The documentation is a bit unclear but presumably * this is the unlatched version. * * Code should test FIFO conditions using the I2C_STA (status) register, * not the interrupt status registers. */ #define IG4_INTR_GEN_CALL 0x0800 #define IG4_INTR_START_DET 0x0400 #define IG4_INTR_STOP_DET 0x0200 #define IG4_INTR_ACTIVITY 0x0100 #define IG4_INTR_TX_ABRT 0x0040 #define IG4_INTR_TX_EMPTY 0x0010 #define IG4_INTR_TX_OVER 0x0008 #define IG4_INTR_RX_FULL 0x0004 #define IG4_INTR_RX_OVER 0x0002 #define IG4_INTR_RX_UNDER 0x0001 +#define IG4_INTR_ERR_MASK (IG4_INTR_TX_ABRT | IG4_INTR_TX_OVER | \ + IG4_INTR_RX_OVER | IG4_INTR_RX_UNDER) + /* * RX_TL - (RW) Receive FIFO Threshold Register 22.2.11 * TX_TL - (RW) Transmit FIFO Threshold Register 22.2.12 * * Specify the receive and transmit FIFO threshold register. The * FIFOs have 16 elements. The valid range is 0-15. Setting a * value greater than 15 causes the actual value to be the maximum * depth of the FIFO. * * Generally speaking since everything is messaged, we can use a * mid-level setting for both parameters and (e.g.) fully drain the * receive FIFO on the STOP_DET condition to handle loose ends. */ #define IG4_FIFO_MASK 0x00FF #define IG4_FIFO_LIMIT 16 /* * CLR_INTR - (RO) Clear Interrupt Register 22.2.13 * CLR_RX_UNDER - (RO) Clear Interrupt Register (specific) 22.2.14 * CLR_RX_OVER - (RO) Clear Interrupt Register (specific) 22.2.15 * CLR_TX_OVER - (RO) Clear Interrupt Register (specific) 22.2.16 * CLR_TX_ABORT - (RO) Clear Interrupt Register (specific) 22.2.17 * CLR_ACTIVITY - (RO) Clear Interrupt Register (specific) 22.2.18 * CLR_STOP_DET - (RO) Clear Interrupt Register (specific) 22.2.19 * CLR_START_DET- (RO) Clear Interrupt Register (specific) 22.2.20 * CLR_GEN_CALL - (RO) Clear Interrupt Register (specific) 22.2.21 * * CLR_* specific operations clear the appropriate bit in the * RAW_INTR_STAT register. Intel does not really document whether * these operations clear the normal interrupt status register. * * CLR_INTR clears bits in the normal interrupt status register and * presumably also the raw(?) register? Intel is again unclear. * * NOTE: CLR_INTR only clears software-clearable interrupts. Hardware * clearable interrupts are controlled entirely by the hardware. * CLR_INTR also clears the TX_ABRT_SOURCE register. * * NOTE: CLR_TX_ABORT also clears the TX_ABRT_SOURCE register and releases * the TX FIFO from its flushed/reset state, allowing more writes * to the TX FIFO. * * NOTE: CLR_ACTIVITY has no effect if the I2C bus is still active. * Intel documents that the bit is automatically cleared when * there is no further activity on the bus. */ #define IG4_CLR_BIT 0x0001 /* Reflects source */ /* * I2C_EN - (RW) I2C Enable Register 22.2.22 * * ABORT Software can abort an I2C transfer by setting this - * bit. Hardware will clear the bit once the STOP has + * bit. In response, the controller issues the STOP + * condition over the I2C bus, followed by TX FIFO flush. + * Hardware will clear the bit once the STOP has * been detected. This bit can only be set while the * I2C interface is enabled. * * I2C_ENABLE Enable the controller, else disable it. * (Use I2C_ENABLE_STATUS to poll enable status * & wait for changes) */ #define IG4_I2C_ABORT 0x0002 #define IG4_I2C_ENABLE 0x0001 /* * I2C_STA - (RO) I2C Status Register 22.2.23 */ #define IG4_STATUS_ACTIVITY 0x0020 /* Controller is active */ #define IG4_STATUS_RX_FULL 0x0010 /* RX FIFO completely full */ #define IG4_STATUS_RX_NOTEMPTY 0x0008 /* RX FIFO not empty */ #define IG4_STATUS_TX_EMPTY 0x0004 /* TX FIFO completely empty */ #define IG4_STATUS_TX_NOTFULL 0x0002 /* TX FIFO not full */ #define IG4_STATUS_I2C_ACTIVE 0x0001 /* I2C bus is active */ /* * TXFLR - (RO) Transmit FIFO Level Register 22.2.24 * RXFLR - (RO) Receive FIFO Level Register 22.2.25 * * Read the number of entries currently in the Transmit or Receive * FIFOs. Note that for some reason the mask is 9 bits instead of * the 8 bits the fill level controls. */ -#define IG4_FIFOLVL_MASK 0x001F +#define IG4_FIFOLVL_MASK 0x01FF /* * SDA_HOLD - (RW) SDA Hold Time Length Register 22.2.26 * * Set the SDA hold time length register in I2C clocks. */ -#define IG4_SDA_HOLD_MASK 0x00FF +#define IG4_SDA_TX_HOLD_MASK 0x0000FFFF /* * TX_ABRT_SOURCE- (RO) Transmit Abort Source Register 22.2.27 * * Indicates the cause of a transmit abort. This can indicate a * software programming error or a device expected address width * mismatch or other issues. The NORESTART conditions and GENCALL_NOACK * can only occur if a programming error was made in the driver software. * * In particular, it should be possible to detect whether any devices * are on the bus by observing the GENCALL_READ status, and it might * be possible to detect ADDR7 vs ADDR10 mismatches. */ #define IG4_ABRTSRC_TRANSFER 0x00010000 /* Abort initiated by user */ #define IG4_ABRTSRC_ARBLOST 0x00001000 /* Arbitration lost */ #define IG4_ABRTSRC_NORESTART_10 0x00000400 /* RESTART disabled */ #define IG4_ABRTSRC_NORESTART_START 0x00000200 /* RESTART disabled */ #define IG4_ABRTSRC_ACKED_START 0x00000080 /* Improper acked START */ -#define IG4_ABRTSRC_GENCALL_NOACK 0x00000020 /* Improper GENCALL */ -#define IG4_ABRTSRC_GENCALL_READ 0x00000010 /* Nobody acked GENCALL */ +#define IG4_ABRTSRC_GENCALL_READ 0x00000020 /* Improper GENCALL */ +#define IG4_ABRTSRC_GENCALL_NOACK 0x00000010 /* Nobody acked GENCALL */ #define IG4_ABRTSRC_TXNOACK_DATA 0x00000008 /* data phase no ACK */ #define IG4_ABRTSRC_TXNOACK_ADDR10_2 0x00000004 /* addr10/1 phase no ACK */ #define IG4_ABRTSRC_TXNOACK_ADDR10_1 0x00000002 /* addr10/2 phase no ACK */ #define IG4_ABRTSRC_TXNOACK_ADDR7 0x00000001 /* addr7 phase no ACK */ /* * SLV_DATA_NACK - (RW) Generate Slave DATA NACK Register 22.2.28 * * When the controller is a receiver a NACK can be generated on * receipt of data. * * NACK_GENERATE Set to 0 for normal NACK/ACK generation. * Set to 1 to generate a NACK after next data * byte received. * */ #define IG4_NACK_GENERATE 0x0001 /* * DMA_CTRL - (RW) DMA Control Register 22.2.29 * * Enables DMA on the transmit and/or receive DMA channel. */ #define IG4_TX_DMA_ENABLE 0x0002 #define IG4_RX_DMA_ENABLE 0x0001 /* * DMA_TDLR - (RW) DMA Transmit Data Level Register 22.2.30 * DMA_RDLR - (RW) DMA Receive Data Level Register 22.2.31 * * Similar to RX_TL and TX_TL but controls when a DMA burst occurs * to empty or fill the FIFOs. Use the same IG4_FIFO_MASK and * IG4_FIFO_LIMIT defines for RX_RL and TX_TL. */ /* empty */ /* * SDA_SETUP - (RW) SDA Setup Time Length Register 22.2.32 * * Set the SDA setup time length register in I2C clocks. * The register must be programmed with a value >=2. * (Defaults to 0x64). */ #define IG4_SDA_SETUP_MASK 0x00FF /* * ACK_GEN_CALL - (RW) ACK General Call Register 22.2.33 * * Control whether the controller responds with a ACK or NACK when * it receives an I2C General Call address. * * If set to 0 a NACK is generated and a General Call interrupt is * NOT generated. Otherwise an ACK + interrupt is generated. */ #define IG4_ACKGC_ACK 0x0001 /* * ENABLE_STATUS - (RO) Enable Status Registger 22.2.34 * * DATA_LOST - Indicates that a slave receiver operation has * been aborted with at least one data byte received * from a transfer due to the I2C controller being * disabled (IG4_I2C_ENABLE -> 0) * * ENABLED - Intel documentation is lacking but I assume this * is a reflection of the IG4_I2C_ENABLE bit in the * I2C_EN register. * */ #define IG4_ENASTAT_DATA_LOST 0x0004 #define IG4_ENASTAT_ENABLED 0x0001 /* * COMP_PARAM1 - (RO) Component Parameter Register 22.2.35 * Default Value 0x00FFFF6E * * VALID - Intel documentation is unclear but I believe this * must be read as a 1 to indicate that the rest of * the bits in the register are valid. * * HASDMA - Indicates that the chip is DMA-capable. Presumably * in certain virtualization cases the chip might be * set to not be DMA-capable. * * INTR_IO - Indicates that all interrupts are combined to * generate one interrupt. If not set, interrupts * are individual (more virtualization stuff?) * * HCCNT_RO - Indicates that the clock timing registers are * RW. If not set, the registers are RO. * (more virtualization stuff). * * MAXSPEED - Indicates the maximum speed supported. * * DATAW - Indicates the internal bus width in bits. */ -#define IG4_PARAM1_TXFIFO_DEPTH(v) (((v) >> 16) & 0xFF) -#define IG4_PARAM1_RXFIFO_DEPTH(v) (((v) >> 8) & 0xFF) +#define IG4_PARAM1_TXFIFO_DEPTH(v) ((((v) >> 16) & 0xFF) + 1) +#define IG4_PARAM1_RXFIFO_DEPTH(v) ((((v) >> 8) & 0xFF) + 1) #define IG4_PARAM1_CONFIG_VALID 0x00000080 #define IG4_PARAM1_CONFIG_HASDMA 0x00000040 #define IG4_PARAM1_CONFIG_INTR_IO 0x00000020 #define IG4_PARAM1_CONFIG_HCCNT_RO 0x00000010 #define IG4_PARAM1_CONFIG_MAXSPEED_MASK 0x0000000C #define IG4_PARAM1_CONFIG_DATAW_MASK 0x00000003 #define IG4_CONFIG_MAXSPEED_RESERVED00 0x00000000 #define IG4_CONFIG_MAXSPEED_STANDARD 0x00000004 #define IG4_CONFIG_MAXSPEED_FAST 0x00000008 #define IG4_CONFIG_MAXSPEED_HIGH 0x0000000C #define IG4_CONFIG_DATAW_8 0x00000000 #define IG4_CONFIG_DATAW_16 0x00000001 #define IG4_CONFIG_DATAW_32 0x00000002 #define IG4_CONFIG_DATAW_RESERVED11 0x00000003 /* * COMP_VER - (RO) Component Version Register 22.2.36 * * Contains the chip version number. All 32 bits. */ #define IG4_COMP_MIN_VER 0x3131352A /* * COMP_TYPE - (RO) (linux) Endian and bus width probe * * Read32 from this register and test against IG4_COMP_TYPE * to determine the bus width. e.g. 01404457 = endian-reversed, * and 00000140 or 00004457 means internal 16-bit bus (?). * * This register is not in the intel documentation, I pulled it * from the linux driver i2c-designware-core.c. */ #define IG4_COMP_TYPE 0x44570140 /* * RESETS - (RW) Resets Register 22.2.37 * * Used to reset the I2C host controller by SW. There is no timing * requirement, software can assert and de-assert in back-to-back * transactions. * * 00 I2C host controller is NOT in reset. * 01 (reserved) * 10 (reserved) * 11 I2C host controller is in reset. */ #define IG4_RESETS_ASSERT_HSW 0x0003 #define IG4_RESETS_DEASSERT_HSW 0x0000 /* Skylake-U/Y and Kaby Lake-U/Y have the reset bits inverted */ #define IG4_RESETS_DEASSERT_SKL 0x0003 #define IG4_RESETS_ASSERT_SKL 0x0000 /* Newer versions of the I2C controller allow to check whether * the above ASSERT/DEASSERT is necessary by querying the DEVIDLE_CONTROL * register. * * the RESTORE_REQUIRED bit can be cleared by writing 1 * the DEVICE_IDLE status can be set to put the controller in an idle state * */ #define IG4_RESTORE_REQUIRED 0x0008 #define IG4_DEVICE_IDLE 0x0004 /* * GENERAL - (RW) General Reigster 22.2.38 * * IOVOLT 0=1.8V 1=3.3V * * LTR 0=Auto 1=SW * * In Auto mode the BIOS will write to the host controller's * AUTO LTR Value register (offset 0x0814) with the active * state LTR value, and will write to the SW LTR Value register * (offset 0x0810) with the idle state LTR value. * * In SW mode the SW will write to the host controller SW LTR * value (offset 0x0810). It is the SW responsibility to update * the LTR with the appropriate value. */ #define IG4_GENERAL_IOVOLT3_3 0x0008 #define IG4_GENERAL_SWMODE 0x0004 /* * SW_LTR_VALUE - (RW) SW LTR Value Register 22.2.39 * AUTO_LTR_VALUE - (RW) SW LTR Value Register 22.2.40 * * Default value is 0x00000800 which means the best possible * service/response time. * * It isn't quite clear how the snooping works. There are two scale * bits for both sets but two of the four codes are reserved. The * *SNOOP_VALUE() is specified as a 10-bit latency value. If 0, it * indicates that the device cannot tolerate any delay and needs the * best possible service/response time. * * I think this is for snooping (testing) the I2C bus. The lowest * delay (0) probably runs the controller polling at a high, power hungry * rate. But I dunno. */ #define IG4_SWLTR_NSNOOP_REQ 0x80000000 /* (ro) */ #define IG4_SWLTR_NSNOOP_SCALE_MASK 0x1C000000 /* (ro) */ #define IG4_SWLTR_NSNOOP_SCALE_1US 0x08000000 /* (ro) */ #define IG4_SWLTR_NSNOOP_SCALE_32US 0x0C000000 /* (ro) */ #define IG4_SWLTR_NSNOOP_VALUE_DECODE(v) (((v) >> 16) & 0x3F) #define IG4_SWLTR_NSNOOP_VALUE_ENCODE(v) (((v) & 0x3F) << 16) #define IG4_SWLTR_SNOOP_REQ 0x00008000 /* (rw) */ #define IG4_SWLTR_SNOOP_SCALE_MASK 0x00001C00 /* (rw) */ #define IG4_SWLTR_SNOOP_SCALE_1US 0x00000800 /* (rw) */ #define IG4_SWLTR_SNOOP_SCALE_32US 0x00000C00 /* (rw) */ #define IG4_SWLTR_SNOOP_VALUE_DECODE(v) ((v) & 0x3F) #define IG4_SWLTR_SNOOP_VALUE_ENCODE(v) ((v) & 0x3F) #endif /* _ICHIIC_IG4_REG_H_ */ Index: stable/12/sys/dev/ichiic/ig4_var.h =================================================================== --- stable/12/sys/dev/ichiic/ig4_var.h (revision 355993) +++ stable/12/sys/dev/ichiic/ig4_var.h (revision 355994) @@ -1,112 +1,122 @@ /* * Copyright (c) 2014 The DragonFly Project. All rights reserved. * * This code is derived from software contributed to The DragonFly Project * by Matthew Dillon and was subsequently ported * to FreeBSD by Michael Gmelin * * 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. * 3. Neither the name of The DragonFly Project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific, prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #ifndef _ICHIIC_IG4_VAR_H_ #define _ICHIIC_IG4_VAR_H_ #include "bus_if.h" #include "device_if.h" #include "pci_if.h" #include "iicbus_if.h" -#define IG4_RBUFSIZE 128 -#define IG4_RBUFMASK (IG4_RBUFSIZE - 1) +enum ig4_vers { IG4_HASWELL, IG4_ATOM, IG4_SKYLAKE, IG4_APL, IG4_CANNONLAKE }; +/* Controller has additional registers */ +#define IG4_HAS_ADDREGS(vers) ((vers) == IG4_SKYLAKE || \ + (vers) == IG4_APL || (vers) == IG4_CANNONLAKE) -enum ig4_op { IG4_IDLE, IG4_READ, IG4_WRITE }; -enum ig4_vers { IG4_HASWELL, IG4_ATOM, IG4_SKYLAKE, IG4_APL }; +struct ig4_hw { + uint32_t ic_clock_rate; /* MHz */ + uint32_t sda_fall_time; /* nsec */ + uint32_t scl_fall_time; /* nsec */ + uint32_t sda_hold_time; /* nsec */ + int txfifo_depth; + int rxfifo_depth; +}; +struct ig4_cfg { + uint32_t version; + uint32_t bus_speed; + uint16_t ss_scl_hcnt; + uint16_t ss_scl_lcnt; + uint16_t ss_sda_hold; + uint16_t fs_scl_hcnt; + uint16_t fs_scl_lcnt; + uint16_t fs_sda_hold; + int txfifo_depth; + int rxfifo_depth; +}; + struct ig4iic_softc { device_t dev; - struct intr_config_hook enum_hook; device_t iicbus; struct resource *regs_res; int regs_rid; struct resource *intr_res; int intr_rid; void *intr_handle; int intr_type; enum ig4_vers version; - enum ig4_op op; - int cmd; - int rnext; - int rpos; - char rbuf[IG4_RBUFSIZE]; - int error; + struct ig4_cfg cfg; + uint32_t intr_mask; uint8_t last_slave; int platform_attached : 1; int use_10bit : 1; int slave_valid : 1; - int read_started : 1; - int write_started : 1; - int access_intr_mask : 1; + int poll: 1; /* * Locking semantics: * * Functions implementing the icbus interface that interact * with the controller acquire an exclusive lock on call_lock - * to prevent interleaving of calls to the interface and a lock on - * io_lock right afterwards, to synchronize controller I/O activity. + * to prevent interleaving of calls to the interface. * - * The interrupt handler can only read data while no iicbus call - * is in progress or while io_lock is dropped during mtx_sleep in - * wait_status and set_controller. It is safe to drop io_lock in those - * places, because the interrupt handler only accesses those registers: - * - * - IG4_REG_I2C_STA (I2C Status) - * - IG4_REG_DATA_CMD (Data Buffer and Command) - * - IG4_REG_CLR_INTR (Clear Interrupt) - * - * Locking outside of those places is required to make the content - * of rpos/rnext predictable (e.g. whenever data_read is called and in - * ig4iic_transfer). + * io_lock is used as condition variable to synchronize active process + * with the interrupt handler. It should not be used for tasks other + * than waiting for interrupt and passing parameters to and from + * it's handler. */ struct sx call_lock; struct mtx io_lock; }; typedef struct ig4iic_softc ig4iic_softc_t; +extern devclass_t ig4iic_devclass; + /* Attach/Detach called from ig4iic_pci_*() */ int ig4iic_attach(ig4iic_softc_t *sc); int ig4iic_detach(ig4iic_softc_t *sc); +int ig4iic_suspend(ig4iic_softc_t *sc); +int ig4iic_resume(ig4iic_softc_t *sc); /* iicbus methods */ extern iicbus_transfer_t ig4iic_transfer; extern iicbus_reset_t ig4iic_reset; +extern iicbus_callback_t ig4iic_callback; #endif /* _ICHIIC_IG4_VAR_H_ */ Index: stable/12/sys/dev/iicbus/iicbus.c =================================================================== --- stable/12/sys/dev/iicbus/iicbus.c (revision 355993) +++ stable/12/sys/dev/iicbus/iicbus.c (revision 355994) @@ -1,370 +1,372 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 1998, 2001 Nicolas Souchu * 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 __FBSDID("$FreeBSD$"); /* * Autoconfiguration and support routines for the Philips serial I2C bus */ #include #include #include #include #include #include #include #include #include #include #include #include #include "iicbus_if.h" /* See comments below for why auto-scanning is a bad idea. */ #define SCAN_IICBUS 0 static int iicbus_probe(device_t dev) { device_set_desc(dev, "Philips I2C bus"); /* Allow other subclasses to override this driver. */ return (BUS_PROBE_GENERIC); } #if SCAN_IICBUS static int iic_probe_device(device_t dev, u_char addr) { int count; char byte; if ((addr & 1) == 0) { /* is device writable? */ if (!iicbus_start(dev, (u_char)addr, 0)) { iicbus_stop(dev); return (1); } } else { /* is device readable? */ if (!iicbus_block_read(dev, (u_char)addr, &byte, 1, &count)) return (1); } return (0); } #endif /* * We add all the devices which we know about. * The generic attach routine will attach them if they are alive. */ static int iicbus_attach(device_t dev) { #if SCAN_IICBUS unsigned char addr; #endif struct iicbus_softc *sc = IICBUS_SOFTC(dev); int strict; sc->dev = dev; mtx_init(&sc->lock, "iicbus", NULL, MTX_DEF); iicbus_init_frequency(dev, 0); iicbus_reset(dev, IIC_FASTEST, 0, NULL); if (resource_int_value(device_get_name(dev), device_get_unit(dev), "strict", &strict) == 0) sc->strict = strict; else sc->strict = 1; /* device probing is meaningless since the bus is supposed to be * hot-plug. Moreover, some I2C chips do not appreciate random * accesses like stop after start to fast, reads for less than * x bytes... */ #if SCAN_IICBUS printf("Probing for devices on iicbus%d:", device_get_unit(dev)); /* probe any devices */ for (addr = 16; addr < 240; addr++) { if (iic_probe_device(dev, (u_char)addr)) { printf(" <%x>", addr); } } printf("\n"); #endif bus_generic_probe(dev); bus_enumerate_hinted_children(dev); bus_generic_attach(dev); return (0); } static int iicbus_detach(device_t dev) { struct iicbus_softc *sc = IICBUS_SOFTC(dev); iicbus_reset(dev, IIC_FASTEST, 0, NULL); bus_generic_detach(dev); device_delete_children(dev); mtx_destroy(&sc->lock); return (0); } static int iicbus_print_child(device_t dev, device_t child) { struct iicbus_ivar *devi = IICBUS_IVAR(child); int retval = 0; retval += bus_print_child_header(dev, child); if (devi->addr != 0) retval += printf(" at addr %#x", devi->addr); resource_list_print_type(&devi->rl, "irq", SYS_RES_IRQ, "%jd"); retval += bus_print_child_footer(dev, child); return (retval); } static void iicbus_probe_nomatch(device_t bus, device_t child) { struct iicbus_ivar *devi = IICBUS_IVAR(child); device_printf(bus, " at addr %#x\n", devi->addr); } static int iicbus_child_location_str(device_t bus, device_t child, char *buf, size_t buflen) { struct iicbus_ivar *devi = IICBUS_IVAR(child); snprintf(buf, buflen, "addr=%#x", devi->addr); return (0); } static int iicbus_child_pnpinfo_str(device_t bus, device_t child, char *buf, size_t buflen) { *buf = '\0'; return (0); } static int iicbus_read_ivar(device_t bus, device_t child, int which, uintptr_t *result) { struct iicbus_ivar *devi = IICBUS_IVAR(child); switch (which) { default: return (EINVAL); case IICBUS_IVAR_ADDR: *result = devi->addr; break; } return (0); } static int iicbus_write_ivar(device_t bus, device_t child, int which, uintptr_t value) { struct iicbus_ivar *devi = IICBUS_IVAR(child); switch (which) { default: return (EINVAL); case IICBUS_IVAR_ADDR: if (devi->addr != 0) return (EINVAL); devi->addr = value; } return (0); } static device_t iicbus_add_child(device_t dev, u_int order, const char *name, int unit) { device_t child; struct iicbus_ivar *devi; child = device_add_child_ordered(dev, order, name, unit); if (child == NULL) return (child); devi = malloc(sizeof(struct iicbus_ivar), M_DEVBUF, M_NOWAIT | M_ZERO); if (devi == NULL) { device_delete_child(dev, child); return (0); } resource_list_init(&devi->rl); device_set_ivars(child, devi); return (child); } static void iicbus_hinted_child(device_t bus, const char *dname, int dunit) { device_t child; int irq; struct iicbus_ivar *devi; child = BUS_ADD_CHILD(bus, 0, dname, dunit); devi = IICBUS_IVAR(child); resource_int_value(dname, dunit, "addr", &devi->addr); if (resource_int_value(dname, dunit, "irq", &irq) == 0) { if (bus_set_resource(child, SYS_RES_IRQ, 0, irq, 1) != 0) device_printf(bus, "warning: bus_set_resource() failed\n"); } } static struct resource_list * iicbus_get_resource_list(device_t bus __unused, device_t child) { struct iicbus_ivar *devi; devi = IICBUS_IVAR(child); return (&devi->rl); } int iicbus_generic_intr(device_t dev, int event, char *buf) { return (0); } int iicbus_null_callback(device_t dev, int index, caddr_t data) { return (0); } int iicbus_null_repeated_start(device_t dev, u_char addr) { return (IIC_ENOTSUPP); } void iicbus_init_frequency(device_t dev, u_int bus_freq) { struct iicbus_softc *sc = IICBUS_SOFTC(dev); /* * If a bus frequency value was passed in, use it. Otherwise initialize * it first to the standard i2c 100KHz frequency, then override that * from a hint if one exists. */ if (bus_freq > 0) sc->bus_freq = bus_freq; else { sc->bus_freq = 100000; resource_int_value(device_get_name(dev), device_get_unit(dev), "frequency", (int *)&sc->bus_freq); } /* * Set up the sysctl that allows the bus frequency to be changed. * It is flagged as a tunable so that the user can set the value in * loader(8), and that will override any other setting from any source. * The sysctl tunable/value is the one most directly controlled by the * user and thus the one that always takes precedence. */ SYSCTL_ADD_UINT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "frequency", CTLFLAG_RW | CTLFLAG_TUN, &sc->bus_freq, sc->bus_freq, "Bus frequency in Hz"); } static u_int iicbus_get_frequency(device_t dev, u_char speed) { struct iicbus_softc *sc = IICBUS_SOFTC(dev); /* * If the frequency has not been configured for the bus, or the request * is specifically for SLOW speed, use the standard 100KHz rate, else * use the configured bus speed. */ if (sc->bus_freq == 0 || speed == IIC_SLOW) return (100000); return (sc->bus_freq); } static device_method_t iicbus_methods[] = { /* device interface */ DEVMETHOD(device_probe, iicbus_probe), DEVMETHOD(device_attach, iicbus_attach), DEVMETHOD(device_detach, iicbus_detach), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), /* bus interface */ DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_adjust_resource, bus_generic_adjust_resource), DEVMETHOD(bus_alloc_resource, bus_generic_rl_alloc_resource), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), DEVMETHOD(bus_release_resource, bus_generic_rl_release_resource), DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), DEVMETHOD(bus_get_resource_list, iicbus_get_resource_list), DEVMETHOD(bus_add_child, iicbus_add_child), DEVMETHOD(bus_print_child, iicbus_print_child), DEVMETHOD(bus_probe_nomatch, iicbus_probe_nomatch), DEVMETHOD(bus_read_ivar, iicbus_read_ivar), DEVMETHOD(bus_write_ivar, iicbus_write_ivar), DEVMETHOD(bus_child_pnpinfo_str, iicbus_child_pnpinfo_str), DEVMETHOD(bus_child_location_str, iicbus_child_location_str), DEVMETHOD(bus_hinted_child, iicbus_hinted_child), /* iicbus interface */ DEVMETHOD(iicbus_transfer, iicbus_transfer), DEVMETHOD(iicbus_get_frequency, iicbus_get_frequency), DEVMETHOD_END }; driver_t iicbus_driver = { "iicbus", iicbus_methods, sizeof(struct iicbus_softc), }; devclass_t iicbus_devclass; MODULE_VERSION(iicbus, IICBUS_MODVER); DRIVER_MODULE(iicbus, iichb, iicbus_driver, iicbus_devclass, 0, 0); Index: stable/12 =================================================================== --- stable/12 (revision 355993) +++ stable/12 (revision 355994) Property changes on: stable/12 ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head:r354291-354322,354327,355596