-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
hid: Add support for Intel Touch Host Controller
Based on quo/ithc-linux@67ebf09 Signed-off-by: Dorian Stoll <[email protected]> Patchset: ithc
- Loading branch information
Showing
14 changed files
with
1,562 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
|
Oops, something went wrong.