Skip to content

Commit

Permalink
hid: Add support for Intel Touch Host Controller
Browse files Browse the repository at this point in the history
Based on quo/ithc-linux@67ebf09

Signed-off-by: Dorian Stoll <[email protected]>
Patchset: ithc
  • Loading branch information
StollD committed Dec 11, 2022
1 parent afc0b5c commit fec48ed
Show file tree
Hide file tree
Showing 14 changed files with 1,562 additions and 0 deletions.
2 changes: 2 additions & 0 deletions drivers/hid/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -1338,4 +1338,6 @@ source "drivers/hid/surface-hid/Kconfig"

source "drivers/hid/ipts/Kconfig"

source "drivers/hid/ithc/Kconfig"

endmenu
1 change: 1 addition & 0 deletions drivers/hid/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,4 @@ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/
obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/

obj-$(CONFIG_HID_IPTS) += ipts/
obj-$(CONFIG_HID_ITHC) += ithc/
6 changes: 6 additions & 0 deletions drivers/hid/ithc/Kbuild
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
obj-m := ithc.o

ithc-objs := ithc-main.o ithc-regs.o ithc-dma.o ithc-api.o ithc-debug.o

ccflags-y := -std=gnu11 -Wno-declaration-after-statement

12 changes: 12 additions & 0 deletions drivers/hid/ithc/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
config HID_ITHC
tristate "Intel Touch Host Controller"
depends on PCI
depends on HID
help
Say Y here if your system has a touchscreen using Intels
Touch Host Controller (ITHC / IPTS) technology.

If unsure say N.

To compile this driver as a module, choose M here: the
module will be called ithc.
123 changes: 123 additions & 0 deletions drivers/hid/ithc/ithc-api.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#include "ithc.h"

// Userspace API:
// All data received on the active DMA RX channel is made available to userspace through character device /dev/ithc (/dev/ithc[01] if both channels are active).
// The chardev is read-only and has no ioctls. Multitouch mode is automatically activated when the device is opened and deactivated when it is closed.
// Reading from the chardev will obtain one or more messages containing data, or block if there is no new data.
// Only complete messages can be read; if the provided user buffer is too small, read() will return -EMSGSIZE instead of performing a partial read.
// Each message has a header (struct ithc_api_header) containing a sequential message number and the size of the data.

static loff_t ithc_api_llseek(struct file *f, loff_t offset, int whence) {
struct ithc_api *a = container_of(f->private_data, struct ithc_api, m);
return generic_file_llseek_size(f, offset, whence, MAX_LFS_FILESIZE, READ_ONCE(a->rx->pos));
}

static ssize_t ithc_api_read_unlocked(struct file *f, char __user *buf, size_t size, loff_t *offset) {
struct ithc_api *a = container_of(f->private_data, struct ithc_api, m);
loff_t pos = READ_ONCE(a->rx->pos);
unsigned n = a->rx->num_received;
unsigned newest = (n + NUM_RX_ALLOC - 1) % NUM_RX_ALLOC;
unsigned oldest = (n + NUM_RX_DEV) % NUM_RX_ALLOC;
unsigned i = newest;

// scan backwards to find which buf we need to copy first
while (1) {
pos -= sizeof(struct ithc_api_header) + a->rx->bufs[i].data_size;
if (pos <= *offset) break;
if (i == oldest) break;
i = i ? i-1 : NUM_RX_ALLOC-1;
n--;
}

// copy as many bufs as possible
size_t nread = 0;
while (nread + sizeof(struct ithc_api_header) + a->rx->bufs[i].data_size <= size) {
struct ithc_dma_data_buffer *b = &a->rx->bufs[i];
if (b->active_idx >= 0) { pci_err(a->ithc->pci, "tried to access active buffer\n"); return -EFAULT; } // should be impossible
struct ithc_api_header hdr = { .hdr_size = sizeof hdr, .msg_num = n, .size = b->data_size };
if (copy_to_user(buf + nread, &hdr, sizeof hdr)) return -EFAULT;
nread += sizeof hdr;

if (copy_to_user(buf + nread, b->addr, b->data_size)) return -EFAULT;
nread += b->data_size;

if (i == newest) break;
if (++i == NUM_RX_ALLOC) i = 0;
n++;
}
if (!nread) return -EMSGSIZE; // user buffer is too small

*offset = pos + nread;
return nread;
}
static ssize_t ithc_api_read(struct file *f, char __user *buf, size_t size, loff_t *offset) {
struct ithc_api *a = container_of(f->private_data, struct ithc_api, m);
if (f->f_pos >= READ_ONCE(a->rx->pos)) {
if (f->f_flags & O_NONBLOCK) return -EWOULDBLOCK;
if (wait_event_interruptible(a->rx->wait, f->f_pos < READ_ONCE(a->rx->pos))) return -ERESTARTSYS;
}
// lock to avoid buffers getting transferred to device while we're reading them
if (mutex_lock_interruptible(&a->rx->mutex)) return -ERESTARTSYS;
int ret = ithc_api_read_unlocked(f, buf, size, offset);
mutex_unlock(&a->rx->mutex);
return ret;
}

static __poll_t ithc_api_poll(struct file *f, struct poll_table_struct *pt) {
struct ithc_api *a = container_of(f->private_data, struct ithc_api, m);
poll_wait(f, &a->rx->wait, pt);
if (f->f_pos < READ_ONCE(a->rx->pos)) return POLLIN;
return 0;
}

static int ithc_api_open(struct inode *n, struct file *f) {
struct ithc_api *a = container_of(f->private_data, struct ithc_api, m);
struct ithc *ithc = a->ithc;
if (atomic_fetch_inc(&a->open_count) == 0) CHECK(ithc_set_multitouch, ithc, true);
return 0;
}

static int ithc_api_release(struct inode *n, struct file *f) {
struct ithc_api *a = container_of(f->private_data, struct ithc_api, m);
struct ithc *ithc = a->ithc;
int c = atomic_dec_return(&a->open_count);
if (c == 0) CHECK(ithc_set_multitouch, ithc, false);
if (c < 0) pci_err(ithc->pci, "open/release mismatch\n");
return 0;
}

static const struct file_operations dev_fops = {
.owner = THIS_MODULE,
.llseek = ithc_api_llseek,
.read = ithc_api_read,
.poll = ithc_api_poll,
.open = ithc_api_open,
.release = ithc_api_release,
};

static void ithc_api_devres_release(struct device *dev, void *res) {
struct ithc_api *a = res;
misc_deregister(&a->m);
}

int ithc_api_init(struct ithc *ithc, struct ithc_dma_rx *rx, const char *name) {
struct ithc_api *a = devres_alloc(ithc_api_devres_release, sizeof *a, GFP_KERNEL);
if (!a) return -ENOMEM;
a->ithc = ithc;
a->rx = rx;
a->m.minor = MISC_DYNAMIC_MINOR;
a->m.name = name;
a->m.fops = &dev_fops;
a->m.mode = 0440;
a->m.parent = &ithc->pci->dev;
int r = CHECK(misc_register, &a->m);
if (r) {
devres_free(a);
return r;
}
pci_info(ithc->pci, "registered device %s\n", name);
devres_add(&ithc->pci->dev, a);
rx->api = a;
return 0;
}

16 changes: 16 additions & 0 deletions drivers/hid/ithc/ithc-api.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
struct ithc_api {
struct ithc *ithc;
struct ithc_dma_rx *rx;
struct miscdevice m;
atomic_t open_count;
};

struct ithc_api_header {
u8 hdr_size;
u8 reserved[3];
u32 msg_num;
u32 size;
};

int ithc_api_init(struct ithc *ithc, struct ithc_dma_rx *rx, const char *name);

95 changes: 95 additions & 0 deletions drivers/hid/ithc/ithc-debug.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#include "ithc.h"

void ithc_log_regs(struct ithc *ithc) {
if (!ithc->prev_regs) return;
u32 *cur = (void*)ithc->regs, *prev = (void*)ithc->prev_regs;
for (int i = 1024; i < sizeof *ithc->regs / 4; i++) {
u32 x = readl(cur + i);
if (x != prev[i]) {
pci_info(ithc->pci, "reg %04x: %08x -> %08x\n", i * 4, prev[i], x);
prev[i] = x;
}
}
}

static ssize_t ithc_debugfs_cmd_write(struct file *f, const char __user *buf, size_t len, loff_t *offset) {
struct ithc *ithc = file_inode(f)->i_private;
char cmd[256];
if (!ithc || !ithc->pci) return -ENODEV;
if (!len) return -EINVAL;
if (len >= sizeof cmd) return -EINVAL;
if (copy_from_user(cmd, buf, len)) return -EFAULT;
cmd[len] = 0;
if (cmd[len-1] == '\n') cmd[len-1] = 0;
pci_info(ithc->pci, "debug command: %s\n", cmd);
u32 n = 0;
const char *s = cmd + 1;
u32 a[32];
while (*s && *s != '\n') {
if (n >= ARRAY_SIZE(a)) return -EINVAL;
if (*s++ != ' ') return -EINVAL;
char *e;
a[n++] = simple_strtoul(s, &e, 0);
if (e == s) return -EINVAL;
s = e;
}
ithc_log_regs(ithc);
switch(cmd[0]) {
case 'x': // reset
ithc_reset(ithc);
break;
case 'w': // write register: offset mask value
if (n != 3 || (a[0] & 3)) return -EINVAL;
pci_info(ithc->pci, "debug write 0x%04x = 0x%08x (mask 0x%08x)\n", a[0], a[2], a[1]);
bitsl(((u32 *)ithc->regs) + a[0] / 4, a[1], a[2]);
break;
case 'r': // read register: offset
if (n != 1 || (a[0] & 3)) return -EINVAL;
pci_info(ithc->pci, "debug read 0x%04x = 0x%08x\n", a[0], readl(((u32 *)ithc->regs) + a[0] / 4));
break;
case 's': // spi command: cmd offset len data...
// read config: s 4 0 64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
// set touch cfg: s 6 12 4 XX
if (n < 3 || a[2] > (n - 3) * 4) return -EINVAL;
pci_info(ithc->pci, "debug spi command %u with %u bytes of data\n", a[0], a[2]);
if (!CHECK(ithc_spi_command, ithc, a[0], a[1], a[2], a + 3))
for (u32 i = 0; i < (a[2] + 3) / 4; i++) pci_info(ithc->pci, "resp %u = 0x%08x\n", i, a[3+i]);
break;
case 'd': // dma command: cmd len data...
// get report descriptor: d 7 8 0 0
// enable multitouch: d 3 2 0x0105
if (n < 2 || a[1] > (n - 2) * 4) return -EINVAL;
pci_info(ithc->pci, "debug dma command %u with %u bytes of data\n", a[0], a[1]);
if (ithc_dma_tx(ithc, a[0], a[1], a + 2)) pci_err(ithc->pci, "dma tx failed\n");
break;
default:
return -EINVAL;
}
ithc_log_regs(ithc);
return len;
}

static const struct file_operations ithc_debugfops_cmd = {
.owner = THIS_MODULE,
.write = ithc_debugfs_cmd_write,
};

static void ithc_debugfs_devres_release(struct device *dev, void *res) {
struct dentry **dbgm = res;
if (*dbgm) debugfs_remove_recursive(*dbgm);
}

int ithc_debug_init(struct ithc *ithc) {
struct dentry **dbgm = devres_alloc(ithc_debugfs_devres_release, sizeof *dbgm, GFP_KERNEL);
if (!dbgm) return -ENOMEM;
devres_add(&ithc->pci->dev, dbgm);
struct dentry *dbg = debugfs_create_dir(DEVNAME, NULL);
if (IS_ERR(dbg)) return PTR_ERR(dbg);
*dbgm = dbg;

struct dentry *cmd = debugfs_create_file("cmd", 0220, dbg, ithc, &ithc_debugfops_cmd);
if (IS_ERR(cmd)) return PTR_ERR(cmd);

return 0;
}

Loading

0 comments on commit fec48ed

Please sign in to comment.