Attached is a preliminary driver for the Sony jog dial. It's enough that
you can create a /dev/jogdial and watch letters come out.

It needs a lot of improvement:

1. Use interrupts instead of polling.

2. Present mouse-oriented events instead of letters.

3. Fix the probe routine so that it tries to detect the presence of the
device rather than the magic 0x10a0 port location.

4. Eventual ACPIification of the driver.

5. Create a 2nd device to deal with other devices like the lid switch,
capture button, etc.

But I wanted to get this much out there for people to play with.

/*
 * Insert standard FreeBSD Copyright notice here
 *
 * spic -- the Sony Programmable I/O Controller
 *
 * This device exists on most recent Sony laptops. It is the means by which
 * you can watch the Jog Dial and some other functions.
 *
 * At the moment, this driver merely tries to turn the jog dial into a
 * device that moused can park on, with the intent of supplying a Z axis
 * and mouse button out of the jog dial. I suspect that this device will
 * end up having to support at least 2 different minor devices: One to be
 * the jog wheel device for moused to camp out on and the other to perform
 * all of the other miscelaneous functions of this device. But for now,
 * the jog wheel is all you get.
 *
 * What documentation exists is thanks to Andrew Tridge, and his page at
 * http://samba.org/picturebook/
 *
 * $FreeBSD$
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/bus.h>
#include <machine/bus.h>
#include <sys/rman.h>
#include <machine/resource.h>
#include <isa/isavar.h>
#include <sys/poll.h>
#include <machine/pci_cfgreg.h>
#include <machine/clock.h>
#include <sys/tty.h>
#include <sys/conf.h>
#include <sys/fcntl.h>
#include <sys/dkstat.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <sys/uio.h>

#include <dev/spic/spicreg.h>

static int spic_pollrate;

SYSCTL_INT(_machdep, OID_AUTO, spic_pollrate, CTLFLAG_RW, &spic_pollrate, 0, "")
;

devclass_t spic_devclass;

static d_open_t         spicopen;
static d_close_t        spicclose;
static d_read_t         spicread;
static d_ioctl_t        spicioctl;
static d_poll_t         spicpoll;

static struct cdevsw spic_cdevsw = {
        /* open */      spicopen,
        /* close */     spicclose,
        /* read */      spicread,
        /* write */     nowrite,
        /* ioctl */     spicioctl,
        /* poll */      spicpoll,
        /* mmap */      nommap,
        /* strategy */  nostrategy,
        /* name */      "spic",
        /* maj */       CDEV_MAJOR,
        /* dump */      nodump,
        /* psize */     nopsize,
        /* flags */     0,
        /* bmaj */      -1
};

#define SCBUFLEN 128

struct spic_softc {
        u_short sc_port_addr;
        u_char sc_intr;
        struct resource *sc_port_res,*sc_intr_res;
        int     sc_port_rid,sc_intr_rid;
        int sc_opened;
        int sc_sleeping;
        int sc_buttonlast;
        struct callout_handle sc_timeout_ch;
        device_t sc_dev;
        struct selinfo sc_rsel;
        u_char sc_buf[SCBUFLEN];
        int sc_count;
};

static void
write_port1(struct spic_softc *sc, u_char val)
{
        DELAY(10);
        outb(sc->sc_port_addr, val);
}

static void
write_port2(struct spic_softc *sc, u_char val)
{
        DELAY(10);
        outb(sc->sc_port_addr + 4, val);
}

static u_char
read_port1(struct spic_softc *sc)
{
        DELAY(10);
        return inb(sc->sc_port_addr);
}

static u_char
read_port2(struct spic_softc *sc)
{
        DELAY(10);
        return inb(sc->sc_port_addr + 4);
}

static void
busy_wait(struct spic_softc *sc)
{
        int i=0;

        while(read_port2(sc) & 2) {
                DELAY(10);
                if (i++>10000) {
                        printf("spic busy wait abort\n");
                        return;
                }
        }
}

static u_char
spic_call1(struct spic_softc *sc, u_char dev) {
        busy_wait(sc);
        write_port2(sc, dev);
        read_port2(sc);
        return read_port1(sc);
}

static u_char
spic_call2(struct spic_softc *sc, u_char dev, u_char fn)
{
        busy_wait(sc);
        write_port2(sc, dev);
        busy_wait(sc);
        write_port1(sc, fn);
        return read_port1(sc);
}

static int
spic_probe(device_t dev)
{
        struct spic_softc *sc;
        u_char t, spic_irq;

        sc = device_get_softc(dev);

        bzero(sc, sizeof(struct spic_softc));

        if (!(sc->sc_port_res = bus_alloc_resource(dev, SYS_RES_IOPORT,
                &sc->sc_port_rid, 0, ~0, 5, RF_ACTIVE))) {
                device_printf(dev,"Couldn't map I/O\n");
                return ENXIO;
        }
        sc->sc_port_addr = (u_short)rman_get_start(sc->sc_port_res);

        if (!(sc->sc_intr_res = bus_alloc_resource(dev, SYS_RES_IRQ,
                &sc->sc_intr_rid, 0, ~0, 1, RF_ACTIVE))) {
                device_printf(dev,"Couldn't map IRQ\n");
                bus_release_resource(dev, SYS_RES_IOPORT,
                        sc->sc_port_rid, sc->sc_port_res);
                return ENXIO;
        }
        sc->sc_intr = (u_short)rman_get_start(sc->sc_intr_res);

        switch (sc->sc_intr) {
                case 0: spic_irq = 3; break;
                case 5: spic_irq = 0; break;
                case 0xa: spic_irq = 1; break;
                case 0xb: spic_irq = 2; break;
                default: device_printf(dev,"Invalid IRQ\n");
                        bus_release_resource(dev, SYS_RES_IOPORT,
                                sc->sc_port_rid, sc->sc_port_res);
                        bus_release_resource(dev, SYS_RES_IRQ,
                                sc->sc_intr_rid, sc->sc_intr_res);
                        return ENXIO;
        }

        if (sc->sc_port_addr != 0x10A0) {
                bus_release_resource(dev, SYS_RES_IOPORT,
                        sc->sc_port_rid, sc->sc_port_res);
                bus_release_resource(dev, SYS_RES_IRQ,
                        sc->sc_intr_rid, sc->sc_intr_res);
                return ENXIO;
        }
        /*
         * This is an ugly hack. It is necessary until ACPI works correctly.
         *
         * The SPIC consists of 2 registers. They are mapped onto I/O by the
         * PIIX4's General Device 10 function. There is also an interrupt
         * control port at a somewhat magic location, but this first pass is
         * polled.
         *
         * So the first thing we need to do is map the G10 space in.
         *
         */

        pci_cfgregwrite(PIIX4_BUS, PIIX4_SLOT, PIIX4_FUNC, G10A,
                sc->sc_port_addr, 2);
        t = pci_cfgregread(PIIX4_BUS, PIIX4_SLOT, PIIX4_FUNC, G10L, 1);
        t &= 0xf0;
        t |= 4;
        pci_cfgregwrite(PIIX4_BUS, PIIX4_SLOT, PIIX4_FUNC, G10L, t, 1);
        outw(SPIC_IRQ_PORT, (inw(SPIC_IRQ_PORT) & ~(0x3 << SPIC_IRQ_SHIFT)) | 
(spic_irq << SPIC_IRQ_SHIFT));
        t = pci_cfgregread(PIIX4_BUS, PIIX4_SLOT, PIIX4_FUNC, G10L, 1);
        t &= 0x1f;
        t |= 0xc0;
        pci_cfgregwrite(PIIX4_BUS, PIIX4_SLOT, PIIX4_FUNC, G10L, t, 1);

        device_set_desc(dev, "Sony Programmable I/O Controller");

        return 0;
}

static int
spic_attach(device_t dev)
{
        struct spic_softc *sc;

        sc = device_get_softc(dev);

        sc->sc_dev = dev;
        
        spic_pollrate = (hz/50); /* Every 50th of a second */

        spic_call1(sc, 0x82);
        spic_call2(sc, 0x81, 0xff);
        spic_call1(sc, 0x92);

        /* There can be only one */
        make_dev(&spic_cdevsw, 0, 0, 0, 0600, "jogdial");

        return 0;
}

static void
spictimeout(void *arg)
{
        struct spic_softc *sc = arg;
        u_char b, event, param;
        int j;

        if (!sc->sc_opened) {
                device_printf(sc->sc_dev, "timeout called while closed!\n");
                return;
        }

        event = read_port2(sc);
        param = read_port1(sc);

        if ((event != 4) && (!(event & 0x1)))
                switch(event) {
                        case 0x10: /* jog wheel event */
                                b = !!(param & 0x40);
                                if (b != sc->sc_buttonlast) {
                                        sc->sc_buttonlast = b;
                                        sc->sc_buf[sc->sc_count++] =
                                                b?'d':'u';
                                }
                                j = (param & 0xf) | ((param & 0x10)? ~0xf:0);
                                if (j<0)
                                        while(j++!=0) {
                                                sc->sc_buf[sc->sc_count++] =
                                                        'l';
                                        }
                                else if (j>0)
                                        while(j--!=0) {
                                                sc->sc_buf[sc->sc_count++] =
                                                        'r';
                                        }
                                goto got_event;
                        case 0x60: /* Capture button */
                                printf("Capture button event: %x\n",param);
                                goto got_event;
                        case 0x30: /* Lid switch */
                                printf("Lid switch event: %x\n",param);
                                goto got_event;
                        default:
                                printf("Unknown event: %x %x\n", event, param);
                                goto got_event;
                }
        /* No event. Wait some more */
        sc->sc_timeout_ch = timeout(spictimeout, sc, spic_pollrate);
        return;

got_event:
        if (sc->sc_count) {
                if (sc->sc_sleeping) {
                        sc->sc_sleeping = 0;
                        wakeup((caddr_t) sc);
                }
                selwakeup(&sc->sc_rsel);
        }

        spic_call2(sc, 0x81, 0xff); /* Clear event */

        sc->sc_timeout_ch = timeout(spictimeout, sc, spic_pollrate);
}

static int
spicopen(dev_t dev, int flag, int fmt, struct proc *p)
{
        struct spic_softc *sc;

        sc = devclass_get_softc(spic_devclass, 0);

        if (sc->sc_opened)
                return EBUSY;

        sc->sc_opened++;
        sc->sc_count=0;

        /* Start the polling */
        timeout(spictimeout, sc, spic_pollrate);
        return 0;
}

static int
spicclose(dev_t dev, int flag, int fmt, struct proc *p)
{
        struct spic_softc *sc;

        sc = devclass_get_softc(spic_devclass, 0);

        /* Stop polling */
        untimeout(spictimeout, sc, sc->sc_timeout_ch);
        sc->sc_opened = 0;
        return 0;
}

static int
spicread(dev_t dev, struct uio *uio, int flag)
{
        struct spic_softc *sc;
        int l, s, error;
        u_char buf[SCBUFLEN];

        sc = devclass_get_softc(spic_devclass, 0);

        s = spltty();
        while (!(sc->sc_count)) {
                sc->sc_sleeping=1;
                error = tsleep((caddr_t) sc, PZERO | PCATCH, "jogrea", 0);
                sc->sc_sleeping=0;
                if (error) {
                        splx(s);
                        return error;
                }
        }
        splx(s);

        /*
         * This is a very simple device. We send the following characters
         * on events:
         * u = up, d = down -- that's the jog button
         * l = left, r = right -- that's the dial.
         * "left" and "right" are rather caprecious. They actually represent
         * ccw and cw, respectively
         *
         */

        s = spltty();
        l = min(uio->uio_resid, sc->sc_count);
        bcopy(sc->sc_buf, buf, l);
        sc->sc_count -= l;
        bcopy(sc->sc_buf + l, sc->sc_buf, l);
        splx(s);
        return uiomove(buf, l, uio);

}

static int
spicioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct proc *p)
{
        struct spic_softc *sc;

        sc = devclass_get_softc(spic_devclass, 0);

        return EIO;
}

static int
spicpoll(dev_t dev, int events, struct proc *p)
{
        struct spic_softc *sc;
        int revents = 0, s;

        sc = devclass_get_softc(spic_devclass, 0);
        s = spltty();
        if (events & (POLLIN | POLLRDNORM)) {
                if (sc->sc_count)
                        revents |= events & (POLLIN | POLLRDNORM);
                else
                        selrecord(p, &sc->sc_rsel); /* Who shall we wake? */
        }
        splx(s);

        return revents;
}


static device_method_t spic_methods[] = {
        DEVMETHOD(device_probe,         spic_probe),
        DEVMETHOD(device_attach,        spic_attach),

        { 0, 0 }
};

static driver_t spic_driver = {
        "spic",
        spic_methods,
        sizeof(struct spic_softc),
};

DRIVER_MODULE(spic, isa, spic_driver, spic_devclass, 0, 0);


#define CDEV_MAJOR      160

/*
 * Find the PCI device that holds the G10 register needed to map in the SPIC
 */
#define PIIX4_BUS       0
#define PIIX4_SLOT      7
#define PIIX4_FUNC      3

#define G10A    (0x64)
#define G10L    (G10A + 2)

#define BUTTON_UP       1
#define BUTTON_DOWN     2
#define JOG_CW          3
#define JOG_CCW         4

#define SPIC_IRQ_PORT   0x8034
#define SPIC_IRQ_SHIFT  22

Reply via email to