/*- * Copyright (C) 2015 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mbox_if.h" #ifdef DEBUG #define DPRINTF(fmt, ...) do { \ printf("%s:%u: ", __func__, __LINE__); \ printf(fmt, ##__VA_ARGS__); \ } while (0) #else #define DPRINTF(fmt, ...) #endif #define FT5406_LOCK(_sc) \ mtx_lock(&(_sc)->sc_mtx) #define FT5406_UNLOCK(_sc) \ mtx_unlock(&(_sc)->sc_mtx) #define FT5406_LOCK_INIT(_sc) \ mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->dev), \ "ft5406", MTX_DEF) #define FT5406_LOCK_DESTROY(_sc) \ mtx_destroy(&_sc->sc_mtx); #define FT5406_LOCK_ASSERT(_sc) \ mtx_assert(&(_sc)->sc_mtx, MA_OWNED) #define EVENT_PEN 1 #define EVENT_ABS_X 2 #define EVENT_ABS_Y 3 #define EVENT_ABS_PRESSURE 4 #define BUFFER_EVENTS 512 struct touchscreen_event { struct timeval ev_time; uint32_t ev_type; uint32_t ev_value; }; #define FT5406_DEVICE_MODE 0 #define FT5406_GESTURE_ID 1 #define FT5406_NUM_POINTS 2 #define FT5406_POINT_XH(n) (0 + 3 + (n)*6) #define FT5406_POINT_XL(n) (1 + 3 + (n)*6) #define FT5406_POINT_YH(n) (2 + 3 + (n)*6) #define FT5406_POINT_YL(n) (3 + 3 + (n)*6) #define FT5406_WINDOW_SIZE 64 #define GET_NUM_POINTS(buf) (buf[FT5406_NUM_POINTS]) #define GET_X(buf, n) (((buf[FT5406_POINT_XH(n)] & 0xf) << 8) | \ (buf[FT5406_POINT_XL(n)])) #define GET_Y(buf, n) (((buf[FT5406_POINT_YH(n)] & 0xf) << 8) | \ (buf[FT5406_POINT_YL(n)])) #define GET_TOUCH_ID(buf, n) ((buf[FT5406_POINT_YH(n)] >> 4) & 0xf) struct ft5406ts_softc { device_t dev; struct mtx sc_mtx; /* mbox buffer (physical address) */ bus_dma_tag_t dma_tag; bus_dmamap_t dma_map; bus_size_t dma_size; void *dma_buf; bus_addr_t dma_phys; volatile uint8_t *touch_buf; /* initial hook for waiting mbox intr */ struct intr_config_hook init_hook; /* touchscreen data */ struct cv sc_cv; int sc_pen_down; struct touchscreen_event sc_events[BUFFER_EVENTS]; int sc_evens_read_pos; int sc_evens_write_pos; struct cdev *sc_cdev; struct selinfo sc_events_poll; }; static d_read_t ft5406ts_read; static d_poll_t ft5406ts_poll; static struct cdevsw tsc_cdevsw = { .d_version = D_VERSION, .d_read = ft5406ts_read, .d_poll = ft5406ts_poll, .d_name = "touchscreen", }; static int ft5406ts_read(struct cdev *dev, struct uio *uio, int ioflag) { struct ft5406ts_softc *sc; size_t tomove; struct touchscreen_event event; int error; sc = dev->si_drv1; error = 0; tomove = uio->uio_resid; if (tomove != sizeof(struct touchscreen_event)) return (EINVAL); FT5406_LOCK(sc); while (sc->sc_evens_write_pos == sc->sc_evens_read_pos) { error = cv_wait_sig(&sc->sc_cv, &sc->sc_mtx); if (error != 0) break; } if (sc->sc_evens_write_pos != sc->sc_evens_read_pos) { memcpy(&event, &sc->sc_events[sc->sc_evens_read_pos], sizeof(event)); sc->sc_evens_read_pos = (sc->sc_evens_read_pos + 1) % BUFFER_EVENTS; } // printf("%s: w:%d, r:%d\n", __func__, sc->sc_evens_write_pos, sc->sc_evens_read_pos); FT5406_UNLOCK(sc); if (error == 0) error = uiomove(&event, tomove, uio); return (error); } static int ft5406ts_poll(struct cdev *dev, int events, struct thread *td) { int revents = 0; struct ft5406ts_softc *sc; sc = dev->si_drv1; FT5406_LOCK(sc); if (events & (POLLIN|POLLRDNORM)) { if (sc->sc_evens_write_pos != sc->sc_evens_read_pos) { revents = events & (POLLIN|POLLRDNORM); } else selrecord(td, &sc->sc_events_poll); } FT5406_UNLOCK(sc); return (revents); } static void ft5406ts_send_event(struct ft5406ts_softc *sc, uint32_t event, uint32_t arg) { int newwpos; FT5406_LOCK(sc); sc->sc_events[sc->sc_evens_write_pos].ev_type = event; sc->sc_events[sc->sc_evens_write_pos].ev_value = arg; getmicrotime(&sc->sc_events[sc->sc_evens_write_pos].ev_time); newwpos = (sc->sc_evens_write_pos + 1) % BUFFER_EVENTS; if (newwpos == sc->sc_evens_read_pos) sc->sc_evens_read_pos = (sc->sc_evens_read_pos + 1) % BUFFER_EVENTS; sc->sc_evens_write_pos = newwpos; // printf("%s: w:%d, r:%d\n", __func__, sc->sc_evens_write_pos, sc->sc_evens_read_pos); FT5406_UNLOCK(sc); selwakeup(&sc->sc_events_poll); cv_broadcast(&sc->sc_cv); } static void ft5406ts_worker(void *data) { struct ft5406ts_softc *sc = (struct ft5406ts_softc *)data; int points; int id, x, y, i, pen_down, updated; int current_x, current_y; uint8_t window[FT5406_WINDOW_SIZE]; uint32_t *window32; current_x = current_y = -1; window32 = (uint32_t*)window; while(1) { pause("touchscreen", 1); for (i = 0; i < FT5406_WINDOW_SIZE/4; i++) window[i] = sc->touch_buf[i]; sc->touch_buf[2] = 99; points = GET_NUM_POINTS(window); if (points == 99) continue; #if 0 if (points) { printf("--\n"); for (i = 0; i < 63; i++) { printf(" %02x", window[i]); if ((i % 16) == 0xf) printf("\n"); } printf("\n"); } #endif pen_down = 0; for (i = 0; i < points; i++) { id = GET_TOUCH_ID(window, 0); if (id != 0) continue; pen_down = 1; x = GET_X(window, 0); y = GET_Y(window, 0); // printf("[%d] %d %d\n", id, x, y); } if (pen_down) { updated = 0; if (x != current_x) { ft5406ts_send_event(sc, EVENT_ABS_X, x); current_x = x; updated = 1; } if (y != current_y) { ft5406ts_send_event(sc, EVENT_ABS_Y, y); current_y = y; updated = 1; } if (updated || !sc->sc_pen_down) { sc->sc_pen_down = 1; ft5406ts_send_event(sc, EVENT_PEN, 1); } } else { if (sc->sc_pen_down) { ft5406ts_send_event(sc, EVENT_PEN, 0); sc->sc_pen_down = 0; current_x = current_y = -1; } } } kproc_exit(0); } static int bcm2835_mbox_call_prop(struct ft5406ts_softc *sc) { struct bcm2835_mbox_hdr *msg = (struct bcm2835_mbox_hdr *)sc->dma_buf; struct bcm2835_mbox_tag_hdr *tag, *last; uint8_t *up; device_t mbox; size_t hdr_size; int idx; int err; /* get mbox device */ mbox = devclass_get_device(devclass_find("mbox"), 0); if (mbox == NULL) { device_printf(sc->dev, "can't find mbox\n"); return (-1); } /* go mailbox property */ bus_dmamap_sync(sc->dma_tag, sc->dma_map, BUS_DMASYNC_PREWRITE | BUS_DMASYNC_PREREAD); MBOX_WRITE(mbox, BCM2835_MBOX_CHAN_PROP, (uint32_t)sc->dma_phys); MBOX_READ(mbox, BCM2835_MBOX_CHAN_PROP, &err); bus_dmamap_sync(sc->dma_tag, sc->dma_map, BUS_DMASYNC_POSTREAD); /* check response code */ if (msg->code != BCM2835_MBOX_CODE_RESP_SUCCESS) { device_printf(sc->dev, "mbox response error %d\n", msg->code); return (-1); } /* tag = first tag */ up = (uint8_t *)msg; hdr_size = sizeof(struct bcm2835_mbox_hdr); tag = (struct bcm2835_mbox_tag_hdr *)(up + hdr_size); /* last = end of buffer specified by header */ last = (struct bcm2835_mbox_tag_hdr *)(up + msg->buf_size); /* loop unitl end tag (=0x0) */ hdr_size = sizeof(struct bcm2835_mbox_tag_hdr); for (idx = 0; tag->tag != 0; idx++) { if ((tag->val_len & BCM2835_MBOX_TAG_VAL_LEN_RESPONSE) == 0) { device_printf(sc->dev, "tag%d response error\n", idx); return (-1); } /* clear response bit */ tag->val_len &= ~BCM2835_MBOX_TAG_VAL_LEN_RESPONSE; /* get next tag */ up = (uint8_t *)tag; tag = (struct bcm2835_mbox_tag_hdr *)(up + hdr_size + tag->val_buf_size); /* check buffer size of header */ if (tag > last) { device_printf(sc->dev, "mbox buffer size error\n"); return (-1); } } return (0); } static void ft5406ts_init(void *arg) { struct ft5406ts_softc *sc = arg; struct bcm2835_mbox_tag_touchbuf *msg; uint32_t touchbuf; int err; struct proc *newp; uint32_t *xxx; /* release this hook (continue boot) */ config_intrhook_disestablish(&sc->init_hook); /* using DMA buffer for VC */ msg = (struct bcm2835_mbox_tag_touchbuf *)sc->dma_buf; if (sizeof(*msg) > sc->dma_size) { device_printf(sc->dev, "DMA size overflow (%zu>%lu)\n", sizeof(*msg), sc->dma_size); return; } /* setup single tag buffer */ memset(msg, 0, sizeof(*msg)); msg->hdr.buf_size = sizeof(*msg); msg->hdr.code = BCM2835_MBOX_CODE_REQ; msg->tag_hdr.tag = BCM2835_MBOX_TAG_GET_TOUCHBUF; msg->tag_hdr.val_buf_size = sizeof(msg->body); msg->tag_hdr.val_len = sizeof(msg->body); msg->end_tag = 0; printf("MSG\n"); xxx = (uint32_t*)msg; for (err = 0; err < sizeof(*msg)/4; err++) { printf(" %08x", xxx[err]); } printf("\n"); /* call mailbox property */ err = bcm2835_mbox_call_prop(sc); if (err) { device_printf(sc->dev, "can't get touchbuf address\n"); return; } printf("MSG\n"); xxx = (uint32_t*)msg; for (err = 0; err < sizeof(*msg)/4; err++) { printf(" %08x", xxx[err]); } printf("\n"); /* result (Hz) */ touchbuf = VCBUS_TO_PHYS(msg->body.resp.address); sc->touch_buf = (uint8_t*)pmap_mapdev(touchbuf, 64); sc->touch_buf[0] = 0; sc->touch_buf[1] = 0; sc->touch_buf[2] = 99; #if 0 printf("touchbuf = %08x(%p)\n", touchbuf, sc->touch_buf); for (int i = 0; i < 64; i++) { printf(" %02x", sc->touch_buf[i]); if ((i % 16) == 0xf) printf("\n"); } #endif if (kproc_create(ft5406ts_worker, (void*)sc, &newp, 0, 0, "bcm2835_audio_worker") != 0) { printf("failed to create bcm2835_audio_worker\n"); } } static void ft5406ts_identify(driver_t *driver, device_t parent) { DPRINTF("driver=%p, parent=%p\n", driver, parent); if (device_find_child(parent, "ft5406ts", -1) != NULL) return; if (BUS_ADD_CHILD(parent, 0, "ft5406ts", -1) == NULL) device_printf(parent, "add child failed\n"); } static int ft5406ts_probe(device_t dev) { if (device_get_unit(dev) != 0) return (ENXIO); device_set_desc(dev, "FT5406 touchscreen (VC memory interface)"); return (0); } static void ft5406ts_cb(void *arg, bus_dma_segment_t *segs, int nseg, int err) { bus_addr_t *addr; if (err) return; addr = (bus_addr_t *)arg; *addr = PHYS_TO_VCBUS(segs[0].ds_addr); } static int ft5406ts_attach(device_t dev) { struct ft5406ts_softc *sc; int err; /* set self dev */ sc = device_get_softc(dev); sc->dev = dev; /* create VC mbox buffer */ sc->dma_size = PAGE_SIZE; err = bus_dma_tag_create( bus_get_dma_tag(sc->dev), PAGE_SIZE, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ sc->dma_size, 1, /* maxsize, nsegments */ sc->dma_size, 0, /* maxsegsize, flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->dma_tag); if (err) { device_printf(dev, "can't create DMA tag\n"); return (ENXIO); } err = bus_dmamem_alloc(sc->dma_tag, (void **)&sc->dma_buf, 0, &sc->dma_map); if (err) { bus_dma_tag_destroy(sc->dma_tag); device_printf(dev, "can't allocate dmamem\n"); return (ENXIO); } err = bus_dmamap_load(sc->dma_tag, sc->dma_map, sc->dma_buf, sc->dma_size, ft5406ts_cb, &sc->dma_phys, 0); if (err) { bus_dmamem_free(sc->dma_tag, sc->dma_buf, sc->dma_map); bus_dma_tag_destroy(sc->dma_tag); device_printf(dev, "can't load DMA map\n"); return (ENXIO); } /* OK, ready to use VC buffer */ /* register callback for using mbox when interrupts are enabled */ sc->init_hook.ich_func = ft5406ts_init; sc->init_hook.ich_arg = sc; if (config_intrhook_establish(&sc->init_hook) != 0) { bus_dmamap_unload(sc->dma_tag, sc->dma_map); bus_dmamem_free(sc->dma_tag, sc->dma_buf, sc->dma_map); bus_dma_tag_destroy(sc->dma_tag); device_printf(dev, "config_intrhook_establish failed\n"); return (ENOMEM); } FT5406_LOCK_INIT(sc); cv_init(&sc->sc_cv, "ft5406"); sc->sc_evens_read_pos = 0; sc->sc_evens_write_pos = 0; sc->sc_pen_down = 0; sc->sc_cdev = make_dev(&tsc_cdevsw, 0, UID_ROOT, GID_WHEEL, 0644, "touchscreen"); sc->sc_cdev->si_drv1 = sc; return (0); } static int ft5406ts_detach(device_t dev) { struct ft5406ts_softc *sc; sc = device_get_softc(dev); if (sc->dma_phys != 0) bus_dmamap_unload(sc->dma_tag, sc->dma_map); if (sc->dma_buf != NULL) bus_dmamem_free(sc->dma_tag, sc->dma_buf, sc->dma_map); if (sc->dma_tag != NULL) bus_dma_tag_destroy(sc->dma_tag); return (cpufreq_unregister(dev)); } static device_method_t ft5406ts_methods[] = { /* Device interface */ DEVMETHOD(device_identify, ft5406ts_identify), DEVMETHOD(device_probe, ft5406ts_probe), DEVMETHOD(device_attach, ft5406ts_attach), DEVMETHOD(device_detach, ft5406ts_detach), DEVMETHOD_END }; static devclass_t ft5406ts_devclass; static driver_t ft5406ts_driver = { "ft5406ts", ft5406ts_methods, sizeof(struct ft5406ts_softc), }; DRIVER_MODULE(ft5406ts, cpu, ft5406ts_driver, ft5406ts_devclass, 0, 0);