Edit

IABSD.fr/src/sys/dev/fdt/tipd.c

Branch :

  • Show log

    Commit

  • Author : kettenis
    Date : 2023-07-23 11:42:44
    Hash : 23d6429a
    Message : Prevent spurious connection events after resume by caching the current plug state and comparing it with the current plug state when we receive in interrupt. Only call the connect/disconnect callbacks registered for the port if the state really changed. This prevents an spurious attach/detach/attach sequence when resuming with a USB device connected. ok patrick@

  • sys/dev/fdt/tipd.c
  • /*	$OpenBSD: tipd.c,v 1.3 2023/07/23 11:42:44 kettenis Exp $	*/
    /*
     * Copyright (c) 2022 Mark Kettenis <kettenis@openbsd.org>
     *
     * Permission to use, copy, modify, and distribute this software for any
     * purpose with or without fee is hereby granted, provided that the above
     * copyright notice and this permission notice appear in all copies.
     *
     * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     */
    
    #include <sys/param.h>
    #include <sys/systm.h>
    #include <sys/device.h>
    #include <sys/malloc.h>
    
    #include <machine/intr.h>
    #include <machine/fdt.h>
    
    #include <dev/ofw/openfirm.h>
    #include <dev/ofw/ofw_misc.h>
    #include <dev/ofw/fdt.h>
    
    #include <dev/i2c/i2cvar.h>
    
    #define TPS_CMD1		0x08
    #define TPS_DATA1		0x09
    #define TPS_INT_EVENT_1		0x14
    #define TPS_INT_EVENT_2		0x15
    #define TPS_INT_MASK_1		0x16
    #define TPS_INT_MASK_2		0x17
    #define TPS_INT_CLEAR_1		0x18
    #define TPS_INT_CLEAR_2		0x19
    #define TPS_STATUS		0x1a
    #define  TPS_STATUS_PLUG_PRESENT	(1 << 0)
    #define TPS_SYSTEM_POWER_STATE	0x20
    #define  TPS_SYSTEM_POWER_STATE_S0	0
    #define  TPS_SYSTEM_POWER_STATE_S5	5
    #define TPS_POWER_STATUS	0x3f
    
    #define TPS_CMD(s)	((s[3] << 24) | (s[2] << 16) | (s[1] << 8) | s[0])
    
    /*
     * Interrupt bits on the CD321x controllers used by Apple differ from
     * those used by the standard TPS6598x controllers.
     */
    #define CD_INT_PLUG_EVENT		(1 << 1)
    
    struct tipd_softc {
    	struct device		sc_dev;
    	i2c_tag_t		sc_tag;
    	i2c_addr_t		sc_addr;
    
    	void			*sc_ih;
    
    	struct device_ports	sc_ports;
    	uint32_t		sc_status;
    };
    
    int	tipd_match(struct device *, void *, void *);
    void	tipd_attach(struct device *, struct device *, void *);
    int	tipd_activate(struct device *, int);
    
    const struct cfattach tipd_ca = {
    	sizeof(struct tipd_softc), tipd_match, tipd_attach, NULL,
    	tipd_activate
    };
    
    struct cfdriver tipd_cd = {
    	NULL, "tipd", DV_DULL
    };
    
    int	tipd_intr(void *);
    
    int	tipd_read_4(struct tipd_softc *, uint8_t, uint32_t *);
    int	tipd_read_8(struct tipd_softc *, uint8_t, uint64_t *);
    int	tipd_write_4(struct tipd_softc *, uint8_t, uint32_t);
    int	tipd_write_8(struct tipd_softc *, uint8_t, uint64_t);
    int	tipd_exec(struct tipd_softc *, const char *,
    	    const void *, size_t, void *, size_t);
    
    int
    tipd_match(struct device *parent, void *match, void *aux)
    {
    	struct i2c_attach_args *ia = aux;
    
    	return iic_is_compatible(ia, "apple,cd321x");
    }
    
    void
    tipd_attach(struct device *parent, struct device *self, void *aux)
    {
    	struct tipd_softc *sc = (struct tipd_softc *)self;
    	struct i2c_attach_args *ia = aux;
    	int node = *(int *)ia->ia_cookie;
    
    	sc->sc_tag = ia->ia_tag;
    	sc->sc_addr = ia->ia_addr;
    
    	sc->sc_ih = fdt_intr_establish(node, IPL_BIO, tipd_intr,
    	    sc, sc->sc_dev.dv_xname);
    	if (sc->sc_ih == NULL) {
    		printf(": can't establish interrupt\n");
    		return;
    	}
    
    	printf("\n");
    
    	tipd_read_4(sc, TPS_STATUS, &sc->sc_status);
    	tipd_write_8(sc, TPS_INT_MASK_1, CD_INT_PLUG_EVENT);
    
    	node = OF_getnodebyname(node, "connector");
    	if (node) {
    		sc->sc_ports.dp_node = node;
    		device_ports_register(&sc->sc_ports, -1);
    	}
    }
    
    int
    tipd_activate(struct device *self, int act)
    {
    	struct tipd_softc *sc = (struct tipd_softc *)self;
    	uint8_t state;
    	int error;
    
    	switch (act) {
    	case DVACT_QUIESCE:
    		tipd_write_8(sc, TPS_INT_MASK_1, 0);
    		break;
    	case DVACT_SUSPEND:
    		state = TPS_SYSTEM_POWER_STATE_S5;
    		error = tipd_exec(sc, "SSPS", &state, sizeof(state), NULL, 0);
    		if (error)
    			printf("%s: powerdown failed\n", sc->sc_dev.dv_xname);
    		break;
    	case DVACT_RESUME:
    		state = TPS_SYSTEM_POWER_STATE_S0;
    		error = tipd_exec(sc, "SSPS", &state, sizeof(state), NULL, 0);
    		if (error)
    			printf("%s: powerup failed\n", sc->sc_dev.dv_xname);
    		break;
    	case DVACT_WAKEUP:
    		tipd_read_4(sc, TPS_STATUS, &sc->sc_status);
    		tipd_write_8(sc, TPS_INT_MASK_1, CD_INT_PLUG_EVENT);
    		break;
    	}
    
    	return 0;
    }
    
    void
    tipd_connect(struct tipd_softc *sc)
    {
    	struct endpoint *ep, *rep;
    	struct usb_controller_port *port;
    
    	ep = endpoint_byreg(&sc->sc_ports, 0, -1);
    	if (ep == NULL)
    		return;
    	rep = endpoint_remote(ep);
    	if (rep == NULL || rep->ep_type != EP_USB_CONTROLLER_PORT)
    		return;
    	port = endpoint_get_cookie(rep);
    	if (port && port->up_connect)
    		port->up_connect(port->up_cookie);
    }
    
    void
    tipd_disconnect(struct tipd_softc *sc)
    {
    	struct endpoint *ep, *rep;
    	struct usb_controller_port *port;
    
    	ep = endpoint_byreg(&sc->sc_ports, 0, -1);
    	if (ep == NULL)
    		return;
    	rep = endpoint_remote(ep);
    	if (rep == NULL || rep->ep_type != EP_USB_CONTROLLER_PORT)
    		return;
    	port = endpoint_get_cookie(rep);
    	if (port && port->up_disconnect)
    		port->up_disconnect(port->up_cookie);
    }
    
    int
    tipd_intr(void *arg)
    {
    	struct tipd_softc *sc = arg;
    	uint64_t event;
    	uint32_t status;
    	int error;
    
    	error = tipd_read_8(sc, TPS_INT_EVENT_1, &event);
    	if (error)
    		return 0;
    
    	if (event == 0)
    		return 0;
    
    	if (event & CD_INT_PLUG_EVENT) {
    		error = tipd_read_4(sc, TPS_STATUS, &status);
    		if (error)
    			goto fail;
    
    		/*
    		 * We may get a spurious plug event upon resume.  Make
    		 * sure we only signal a new connection when the plug
    		 * present state really changed.
    		 */
    		if ((status ^ sc->sc_status) & TPS_STATUS_PLUG_PRESENT) {
    			if (status & TPS_STATUS_PLUG_PRESENT)
    				tipd_connect(sc);
    			else
    				tipd_disconnect(sc);
    			sc->sc_status = status;
    		}
    	}
    
    fail:
    	tipd_write_8(sc, TPS_INT_CLEAR_1, event);
    	return 1;
    }
    
    int
    tipd_read_4(struct tipd_softc *sc, uint8_t reg, uint32_t *val)
    {
    	uint8_t buf[5];
    	int error;
    
    	iic_acquire_bus(sc->sc_tag, 0);
    	error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP,
    	    sc->sc_addr, &reg, sizeof(reg), buf, sizeof(buf), 0);
    	iic_release_bus(sc->sc_tag, 0);
    
    	if (error == 0)
    		*val = lemtoh32(&buf[1]);
    
    	return error;
    }
    
    int
    tipd_read_8(struct tipd_softc *sc, uint8_t reg, uint64_t *val)
    {
    	uint8_t buf[9];
    	int error;
    
    	iic_acquire_bus(sc->sc_tag, 0);
    	error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP,
    	    sc->sc_addr, &reg, sizeof(reg), buf, sizeof(buf), 0);
    	iic_release_bus(sc->sc_tag, 0);
    
    	if (error == 0)
    		*val = lemtoh64(&buf[1]);
    
    	return error;
    }
    
    int
    tipd_write_4(struct tipd_softc *sc, uint8_t reg, uint32_t val)
    {
    	uint8_t buf[5];
    	int error;
    
    	buf[0] = 4;
    	htolem32(&buf[1], val);
    
    	iic_acquire_bus(sc->sc_tag, 0);
    	error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP,
    	    sc->sc_addr, &reg, sizeof(reg), buf, sizeof(buf), 0);
    	iic_release_bus(sc->sc_tag, 0);
    
    	return error;
    }
    
    int
    tipd_write_8(struct tipd_softc *sc, uint8_t reg, uint64_t val)
    {
    	uint8_t buf[9];
    	int error;
    
    	buf[0] = 8;
    	htolem64(&buf[1], val);
    
    	iic_acquire_bus(sc->sc_tag, 0);
    	error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP,
    	    sc->sc_addr, &reg, sizeof(reg), buf, sizeof(buf), 0);
    	iic_release_bus(sc->sc_tag, 0);
    
    	return error;
    }
    
    int
    tipd_exec(struct tipd_softc *sc, const char *cmd, const void *wbuf,
        size_t wlen, void *rbuf, size_t rlen)
    {
    	char buf[65];
    	uint32_t val;
    	int timo, error;
    	uint8_t reg = TPS_DATA1;
    
    	if (wlen >= sizeof(buf) - 1)
    		return EINVAL;
    
    	error = tipd_read_4(sc, TPS_CMD1, &val);
    	if (error)
    		return error;
    	if (val == TPS_CMD("!CMD"))
    		return EBUSY;
    
    	if (wlen > 0) {
    		buf[0] = wlen;
    		memcpy(&buf[1], wbuf, wlen);
    		iic_acquire_bus(sc->sc_tag, 0);
    		error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP,
    		    sc->sc_addr, &reg, sizeof(reg), buf, sizeof(buf), 0);
    		iic_release_bus(sc->sc_tag, 0);
    		if (error)
    			return error;
    	}
    
    	error = tipd_write_4(sc, TPS_CMD1, TPS_CMD(cmd));
    	if (error)
    		return error;
    
    	for (timo = 1000; timo > 0; timo--) {
    		error = tipd_read_4(sc, TPS_CMD1, &val);
    		if (error)
    			return error;
    		if (val == TPS_CMD("!CMD"))
    			return EBUSY;
    		if (val == 0)
    			break;
    		delay(10);
    	}
    
    	if (timo == 0)
    		return ETIMEDOUT;
    
    	if (rlen > 0) {
    		memset(buf, 0, sizeof(buf));
    		iic_acquire_bus(sc->sc_tag, 0);
    		error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP,
    		    sc->sc_addr, &reg, sizeof(reg), buf, sizeof(buf), 0);
    		iic_release_bus(sc->sc_tag, 0);
    		if (error)
    			return error;
    		if (buf[0] < rlen)
    			return EIO;
    		memcpy(rbuf, &buf[1], rlen);
    	}
    
    	return 0;
    }