kernel: laptop touchpads over I2C-HID
A laptop's touchpad is on the chipset's I2C bus, found only through ACPI (PNP0C50 / ACPI0C50), and SchOS had no I2C at all, so the touchpad of every recent laptop did nothing. kernel/acpi/i2c_hid.c drives the DesignWare I2C controller every x86 chipset uses (AMDI0010 on Ryzen) by polling, speaks HID over I2C to the device the _DSM points at, and feeds the reports through the USB HID parser. On AMD the interrupt pin's level is read every tick, so a report is read exactly when one is waiting; elsewhere the input register is polled. The touchpad is driven in mouse mode, which it starts in after a reset: motion, clicks and taps become /dev/input/event1 like any mouse. The HID parser now prefers a descriptor's relative pointer over an absolute one for this, and takes descriptors from transports other than USB. Untested on hardware: QEMU emulates no I2C touchpad. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
parent
4a7165c951
commit
e43f56c619
5 changed files with 733 additions and 15 deletions
635
kernel/acpi/i2c_hid.c
Normal file
635
kernel/acpi/i2c_hid.c
Normal file
|
|
@ -0,0 +1,635 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
//
|
||||
// Laptop touchpads: HID over I2C, on the SoC's DesignWare I2C controller.
|
||||
//
|
||||
// A laptop's touchpad is not on PS/2 any more and not on USB either. It hangs
|
||||
// off an I2C bus inside the chipset, and the only place that says so is ACPI:
|
||||
// a device whose _HID or _CID is PNP0C50, with an I2cSerialBus resource
|
||||
// naming the controller and the touchpad's address, a GpioInt naming the pin
|
||||
// it pulls when it has something to say, and a _DSM giving the one register
|
||||
// number the protocol needs to bootstrap itself. Without this the touchpad of
|
||||
// every recent laptop -- the ASUS TUF A15's included -- does nothing at all.
|
||||
//
|
||||
// Three layers, all here because each is small:
|
||||
//
|
||||
// The controller. Every x86 chipset since Haswell, AMD's included, uses the
|
||||
// same Synopsys DesignWare I2C block (AMDI0010 on Ryzen, INT33C3, INT3433
|
||||
// and the PCI LPSS functions on Intel). Driven by polling: a touchpad report
|
||||
// is about 30 bytes, a millisecond on the bus.
|
||||
//
|
||||
// HID over I2C (Microsoft's specification, version 1.0). Read the HID
|
||||
// descriptor from the register _DSM names; power the device on and reset
|
||||
// it; read the report descriptor; then every input report is a read of the
|
||||
// input register, two bytes of length and the report.
|
||||
//
|
||||
// The interrupt line. It is level-triggered and stays asserted for as long
|
||||
// as a report is waiting, so instead of routing it through the GPIO
|
||||
// controller's interrupt the pin's *level* is read, every tick: low means
|
||||
// read a report. That is exact -- no report is read twice and none is
|
||||
// missed -- and needs nothing from the GPIO controller but its registers.
|
||||
// On AMD those are one 32-bit word per pin. Elsewhere there is no pin to
|
||||
// watch, and the input register is simply read every tick; devices answer
|
||||
// a read with nothing waiting with a zero length.
|
||||
//
|
||||
// The reports themselves go through the USB HID driver's parser and pointer
|
||||
// path (include/dev/hid.h). A Windows Precision Touchpad starts in mouse mode
|
||||
// after a reset -- relative motion, a button for a click and for a tap -- and
|
||||
// that is the report taken: it is what makes it a mouse on /dev/input/event1,
|
||||
// for the console and for X. Multi-finger reports (two-finger scroll) need the
|
||||
// device switched to touchpad mode and a gesture layer; not yet.
|
||||
|
||||
#include "core/types.h"
|
||||
#include "core/defs.h"
|
||||
#include "core/param.h"
|
||||
#include "core/spinlock.h"
|
||||
#include "arch/mmu.h"
|
||||
#include "dev/hid.h"
|
||||
#include "dev/acpi.h"
|
||||
|
||||
#include <uacpi/uacpi.h>
|
||||
#include <uacpi/utilities.h>
|
||||
#include <uacpi/namespace.h>
|
||||
#include <uacpi/resources.h>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DesignWare I2C
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define IC_CON 0x00
|
||||
#define IC_TAR 0x04
|
||||
#define IC_DATA_CMD 0x10
|
||||
#define IC_SS_SCL_HCNT 0x14
|
||||
#define IC_SS_SCL_LCNT 0x18
|
||||
#define IC_FS_SCL_HCNT 0x1C
|
||||
#define IC_FS_SCL_LCNT 0x20
|
||||
#define IC_INTR_MASK 0x30
|
||||
#define IC_RAW_INTR_STAT 0x34
|
||||
#define IC_RX_TL 0x38
|
||||
#define IC_TX_TL 0x3C
|
||||
#define IC_CLR_INTR 0x40
|
||||
#define IC_CLR_TX_ABRT 0x54
|
||||
#define IC_CLR_STOP_DET 0x60
|
||||
#define IC_ENABLE 0x6C
|
||||
#define IC_STATUS 0x70
|
||||
#define IC_TXFLR 0x74
|
||||
#define IC_RXFLR 0x78
|
||||
#define IC_SDA_HOLD 0x7C
|
||||
#define IC_TX_ABRT_SOURCE 0x80
|
||||
#define IC_ENABLE_STATUS 0x9C
|
||||
#define IC_COMP_PARAM_1 0xF4
|
||||
#define IC_COMP_TYPE 0xFC
|
||||
|
||||
#define DW_COMP_TYPE 0x44570140
|
||||
|
||||
#define CON_MASTER (1 << 0)
|
||||
#define CON_SPEED_STD (1 << 1)
|
||||
#define CON_SPEED_FAST (2 << 1)
|
||||
#define CON_RESTART_EN (1 << 5)
|
||||
#define CON_SLAVE_DISABLE (1 << 6)
|
||||
|
||||
#define CMD_READ (1 << 8)
|
||||
#define CMD_STOP (1 << 9)
|
||||
#define CMD_RESTART (1 << 10)
|
||||
|
||||
#define INTR_TX_ABRT (1 << 6)
|
||||
#define INTR_STOP_DET (1 << 9)
|
||||
|
||||
#define NCTL 4
|
||||
|
||||
struct dwi2c {
|
||||
uint64 phys;
|
||||
volatile uint32 *regs;
|
||||
int txdepth, rxdepth;
|
||||
int fast;
|
||||
};
|
||||
|
||||
static struct dwi2c ctls[NCTL];
|
||||
static int nctl;
|
||||
|
||||
static uint32
|
||||
rd(struct dwi2c *c, int reg)
|
||||
{
|
||||
return c->regs[reg / 4];
|
||||
}
|
||||
|
||||
static void
|
||||
wr(struct dwi2c *c, int reg, uint32 v)
|
||||
{
|
||||
c->regs[reg / 4] = v;
|
||||
}
|
||||
|
||||
static int
|
||||
dw_enable(struct dwi2c *c, int on)
|
||||
{
|
||||
int i;
|
||||
|
||||
for(i = 0; i < 1000; i++){
|
||||
wr(c, IC_ENABLE, on);
|
||||
if((rd(c, IC_ENABLE_STATUS) & 1) == (uint32)on)
|
||||
return 0;
|
||||
microdelay(25);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// SCL high and low periods, in controller clocks, from the firmware's own
|
||||
// numbers when it gives them: SSCN and FMCN are packages of (HCNT, LCNT,
|
||||
// SDA hold) for exactly this controller's clock.
|
||||
static int
|
||||
acpi_counts(uacpi_namespace_node *node, const char *name,
|
||||
uint32 *hcnt, uint32 *lcnt, uint32 *hold)
|
||||
{
|
||||
uacpi_object *o = 0;
|
||||
uacpi_object_array a;
|
||||
uacpi_u64 v[3];
|
||||
int i;
|
||||
|
||||
if(uacpi_eval_package(node, name, 0, &o) != UACPI_STATUS_OK)
|
||||
return -1;
|
||||
if(uacpi_object_get_package(o, &a) != UACPI_STATUS_OK || a.count < 2){
|
||||
uacpi_object_unref(o);
|
||||
return -1;
|
||||
}
|
||||
v[2] = 0;
|
||||
for(i = 0; i < 3 && i < (int)a.count; i++)
|
||||
if(uacpi_object_get_integer(a.objects[i], &v[i]) != UACPI_STATUS_OK){
|
||||
uacpi_object_unref(o);
|
||||
return -1;
|
||||
}
|
||||
uacpi_object_unref(o);
|
||||
*hcnt = v[0];
|
||||
*lcnt = v[1];
|
||||
*hold = v[2];
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
dw_init(struct dwi2c *c, uacpi_namespace_node *node, uint32 speed)
|
||||
{
|
||||
uint32 type, param, hcnt, lcnt, hold = 0;
|
||||
|
||||
c->regs = ioremap(c->phys, 0x1000);
|
||||
if(c->regs == 0)
|
||||
return -1;
|
||||
type = rd(c, IC_COMP_TYPE);
|
||||
if(type != DW_COMP_TYPE){
|
||||
cprintf("i2c: controller at 0x%lx is not DesignWare (0x%x)\n",
|
||||
c->phys, type);
|
||||
return -1;
|
||||
}
|
||||
param = rd(c, IC_COMP_PARAM_1);
|
||||
c->txdepth = param ? ((param >> 16) & 0xFF) + 1 : 8;
|
||||
c->rxdepth = param ? ((param >> 8) & 0xFF) + 1 : 8;
|
||||
c->fast = speed >= 400000;
|
||||
|
||||
if(dw_enable(c, 0) < 0)
|
||||
return -1;
|
||||
wr(c, IC_CON, CON_MASTER | CON_SLAVE_DISABLE | CON_RESTART_EN |
|
||||
(c->fast ? CON_SPEED_FAST : CON_SPEED_STD));
|
||||
|
||||
// Without the firmware's numbers, periods long enough for a 150 MHz clock,
|
||||
// the fastest any of these runs at: on a slower clock the bus is merely
|
||||
// slower than it could be, never out of specification.
|
||||
if(c->fast){
|
||||
if(acpi_counts(node, "FMCN", &hcnt, &lcnt, &hold) < 0){
|
||||
hcnt = 135; // 0.6 us high, 1.3 us low, plus rise and fall
|
||||
lcnt = 240;
|
||||
}
|
||||
wr(c, IC_FS_SCL_HCNT, hcnt);
|
||||
wr(c, IC_FS_SCL_LCNT, lcnt);
|
||||
} else {
|
||||
if(acpi_counts(node, "SSCN", &hcnt, &lcnt, &hold) < 0){
|
||||
hcnt = 645; // 4.0 us high, 4.7 us low, plus the same
|
||||
lcnt = 750;
|
||||
}
|
||||
wr(c, IC_SS_SCL_HCNT, hcnt);
|
||||
wr(c, IC_SS_SCL_LCNT, lcnt);
|
||||
}
|
||||
if(hold)
|
||||
wr(c, IC_SDA_HOLD, hold);
|
||||
wr(c, IC_INTR_MASK, 0);
|
||||
wr(c, IC_RX_TL, 0);
|
||||
wr(c, IC_TX_TL, 0);
|
||||
rd(c, IC_CLR_INTR);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Write `wn` bytes to `addr`, then read `rn` bytes back after a repeated
|
||||
// start, all as one transaction. 0, or -1 if the device did not answer.
|
||||
//
|
||||
// The command FIFO holds reads as well as writes, and each read becomes a
|
||||
// byte in the receive FIFO, so reads are queued no faster than they are
|
||||
// drained: a full receive FIFO stalls the bus.
|
||||
static int
|
||||
dw_xfer(struct dwi2c *c, int addr, const uchar *w, int wn, uchar *r, int rn)
|
||||
{
|
||||
int total = wn + rn, sent = 0, got = 0, spin;
|
||||
uint32 raw;
|
||||
|
||||
if(dw_enable(c, 0) < 0)
|
||||
return -1;
|
||||
wr(c, IC_TAR, addr);
|
||||
rd(c, IC_CLR_INTR);
|
||||
if(dw_enable(c, 1) < 0)
|
||||
return -1;
|
||||
|
||||
for(spin = 0; got < rn || sent < total; spin++){
|
||||
raw = rd(c, IC_RAW_INTR_STAT);
|
||||
if(raw & INTR_TX_ABRT)
|
||||
goto abort;
|
||||
while(got < rn && rd(c, IC_RXFLR) > 0)
|
||||
r[got++] = rd(c, IC_DATA_CMD);
|
||||
while(sent < total && (int)rd(c, IC_TXFLR) < c->txdepth){
|
||||
uint32 cmd;
|
||||
if(sent >= wn && (sent - wn) - got >= c->rxdepth)
|
||||
break;
|
||||
if(sent < wn)
|
||||
cmd = w[sent];
|
||||
else
|
||||
cmd = CMD_READ | (sent == wn && wn > 0 ? CMD_RESTART : 0);
|
||||
if(sent == total - 1)
|
||||
cmd |= CMD_STOP;
|
||||
wr(c, IC_DATA_CMD, cmd);
|
||||
sent++;
|
||||
}
|
||||
if(spin > 20000) // 200 ms: no transfer of ours takes that long
|
||||
goto abort;
|
||||
microdelay(10);
|
||||
}
|
||||
for(spin = 0; !(rd(c, IC_RAW_INTR_STAT) & INTR_STOP_DET); spin++){
|
||||
if(rd(c, IC_RAW_INTR_STAT) & INTR_TX_ABRT)
|
||||
goto abort;
|
||||
if(spin > 2000)
|
||||
goto abort;
|
||||
microdelay(10);
|
||||
}
|
||||
rd(c, IC_CLR_STOP_DET);
|
||||
dw_enable(c, 0);
|
||||
return 0;
|
||||
|
||||
abort:
|
||||
rd(c, IC_TX_ABRT_SOURCE);
|
||||
rd(c, IC_CLR_TX_ABRT);
|
||||
rd(c, IC_CLR_INTR);
|
||||
dw_enable(c, 0);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ACPI
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct found {
|
||||
uint64 mem; // the first memory range
|
||||
int i2c, addr;
|
||||
uint32 speed;
|
||||
char bus[64]; // the controller's namespace path
|
||||
int gpio, pin, active_low;
|
||||
char gpioctl[64];
|
||||
};
|
||||
|
||||
static void
|
||||
copy_source(char *dst, const uacpi_resource_source *s)
|
||||
{
|
||||
int n = s->string && s->length ? s->length : 0;
|
||||
if(n > 63)
|
||||
n = 63;
|
||||
if(n)
|
||||
memmove(dst, s->string, n);
|
||||
dst[n] = 0;
|
||||
}
|
||||
|
||||
static uacpi_iteration_decision
|
||||
take_resource(void *user, uacpi_resource *r)
|
||||
{
|
||||
struct found *f = user;
|
||||
|
||||
switch(r->type){
|
||||
case UACPI_RESOURCE_TYPE_FIXED_MEMORY32:
|
||||
if(!f->mem) f->mem = r->fixed_memory32.address;
|
||||
break;
|
||||
case UACPI_RESOURCE_TYPE_MEMORY32:
|
||||
if(!f->mem) f->mem = r->memory32.minimum;
|
||||
break;
|
||||
case UACPI_RESOURCE_TYPE_ADDRESS32:
|
||||
if(!f->mem && r->address32.common.type == UACPI_RANGE_MEMORY)
|
||||
f->mem = r->address32.minimum;
|
||||
break;
|
||||
case UACPI_RESOURCE_TYPE_ADDRESS64:
|
||||
if(!f->mem && r->address64.common.type == UACPI_RANGE_MEMORY)
|
||||
f->mem = r->address64.minimum;
|
||||
break;
|
||||
case UACPI_RESOURCE_TYPE_SERIAL_I2C_CONNECTION:
|
||||
if(!f->i2c){
|
||||
f->i2c = 1;
|
||||
f->addr = r->i2c_connection.slave_address;
|
||||
f->speed = r->i2c_connection.connection_speed;
|
||||
copy_source(f->bus, &r->i2c_connection.common.source);
|
||||
}
|
||||
break;
|
||||
case UACPI_RESOURCE_TYPE_GPIO_CONNECTION:
|
||||
if(!f->gpio && r->gpio_connection.type == UACPI_GPIO_CONNECTION_INTERRUPT &&
|
||||
r->gpio_connection.pin_table_length > 0){
|
||||
f->gpio = 1;
|
||||
f->pin = r->gpio_connection.pin_table[0];
|
||||
f->active_low = r->gpio_connection.intr.polarity != UACPI_POLARITY_ACTIVE_HIGH;
|
||||
copy_source(f->gpioctl, &r->gpio_connection.source);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return UACPI_ITERATION_DECISION_CONTINUE;
|
||||
}
|
||||
|
||||
static int
|
||||
resources(uacpi_namespace_node *node, struct found *f)
|
||||
{
|
||||
memset(f, 0, sizeof(*f));
|
||||
return uacpi_for_each_device_resource(node, "_CRS", take_resource, f) ==
|
||||
UACPI_STATUS_OK ? 0 : -1;
|
||||
}
|
||||
|
||||
// The I2C-HID _DSM, function 1: the HID descriptor's register.
|
||||
static int
|
||||
hid_desc_register(uacpi_namespace_node *node, uint32 *reg)
|
||||
{
|
||||
static const uchar uuid[16] = { // 3CDFF6F7-4267-4555-AD05-B30A3D8938DE
|
||||
0xF7, 0xF6, 0xDF, 0x3C, 0x67, 0x42, 0x55, 0x45,
|
||||
0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE,
|
||||
};
|
||||
uacpi_data_view view = { .const_bytes = uuid, .length = sizeof(uuid) };
|
||||
uacpi_object_array none = { 0, 0 };
|
||||
uacpi_object *args[4], *ret = 0;
|
||||
uacpi_object_array a = { args, 4 };
|
||||
uacpi_u64 v = 0;
|
||||
int rc = -1, i;
|
||||
|
||||
args[0] = uacpi_object_create_buffer(view);
|
||||
args[1] = uacpi_object_create_integer(1); // revision
|
||||
args[2] = uacpi_object_create_integer(1); // function
|
||||
args[3] = uacpi_object_create_package(none);
|
||||
if(args[0] && args[1] && args[2] && args[3] &&
|
||||
uacpi_eval(node, "_DSM", &a, &ret) == UACPI_STATUS_OK && ret &&
|
||||
uacpi_object_get_integer(ret, &v) == UACPI_STATUS_OK){
|
||||
*reg = v;
|
||||
rc = 0;
|
||||
}
|
||||
if(ret)
|
||||
uacpi_object_unref(ret);
|
||||
for(i = 0; i < 4; i++)
|
||||
if(args[i])
|
||||
uacpi_object_unref(args[i]);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static uacpi_namespace_node *
|
||||
node_at(const char *path)
|
||||
{
|
||||
uacpi_namespace_node *n = 0;
|
||||
if(path[0] == 0 ||
|
||||
uacpi_namespace_node_find(0, path, &n) != UACPI_STATUS_OK)
|
||||
return 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
static struct dwi2c *
|
||||
controller(const char *path, uint32 speed)
|
||||
{
|
||||
uacpi_namespace_node *node = node_at(path);
|
||||
struct found f;
|
||||
struct dwi2c *c;
|
||||
int i;
|
||||
|
||||
if(node == 0 || resources(node, &f) < 0 || f.mem == 0){
|
||||
cprintf("i2c: no registers for controller %s\n", path);
|
||||
return 0;
|
||||
}
|
||||
for(i = 0; i < nctl; i++)
|
||||
if(ctls[i].phys == f.mem)
|
||||
return &ctls[i];
|
||||
if(nctl == NCTL)
|
||||
return 0;
|
||||
uacpi_execute(node, "_PS0", 0);
|
||||
c = &ctls[nctl];
|
||||
memset(c, 0, sizeof(*c));
|
||||
c->phys = f.mem;
|
||||
if(dw_init(c, node, speed) < 0)
|
||||
return 0;
|
||||
nctl++;
|
||||
cprintf("i2c: %s at 0x%lx, %s mode, FIFOs %d/%d\n", path, c->phys,
|
||||
c->fast ? "fast" : "standard", c->txdepth, c->rxdepth);
|
||||
return c;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// HID over I2C
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define NI2CHID 4
|
||||
|
||||
struct i2chid {
|
||||
struct dwi2c *c;
|
||||
int addr;
|
||||
uint32 cmdreg, inreg, maxin;
|
||||
volatile uint32 *pin; // the interrupt pin's register, or 0
|
||||
int active_low;
|
||||
void *hid;
|
||||
uchar *buf; // one page, for input reports
|
||||
char name[8];
|
||||
};
|
||||
|
||||
static struct i2chid devs[NI2CHID];
|
||||
static int ndev;
|
||||
|
||||
static int
|
||||
reg_write(struct i2chid *d, const uchar *w, int n)
|
||||
{
|
||||
return dw_xfer(d->c, d->addr, w, n, 0, 0);
|
||||
}
|
||||
|
||||
static int
|
||||
reg_read(struct i2chid *d, uint32 reg, uchar *r, int n)
|
||||
{
|
||||
uchar w[2] = { reg & 0xFF, reg >> 8 };
|
||||
return dw_xfer(d->c, d->addr, w, 2, r, n);
|
||||
}
|
||||
|
||||
// SET_POWER and RESET, the two commands needed: written to the command
|
||||
// register as (register, opcode byte, power state / report byte).
|
||||
static int
|
||||
command(struct i2chid *d, int opcode, int arg)
|
||||
{
|
||||
uchar w[4] = { d->cmdreg & 0xFF, d->cmdreg >> 8, arg, opcode };
|
||||
return reg_write(d, w, 4);
|
||||
}
|
||||
|
||||
#define OP_RESET 0x01
|
||||
#define OP_SET_POWER 0x08
|
||||
|
||||
static void
|
||||
wait_ticks(uint n)
|
||||
{
|
||||
acquire(&tickslock);
|
||||
uint start = ticks;
|
||||
while(ticks - start < n)
|
||||
sleep(&ticks, &tickslock);
|
||||
release(&tickslock);
|
||||
}
|
||||
|
||||
// AMD's GPIO controller: one 32-bit register per pin, its level in bit 16.
|
||||
#define AMD_PIN_STS (1u << 16)
|
||||
|
||||
static int
|
||||
asserted(struct i2chid *d)
|
||||
{
|
||||
int high = (*d->pin & AMD_PIN_STS) != 0;
|
||||
return d->active_low ? !high : high;
|
||||
}
|
||||
|
||||
static volatile uint32 *
|
||||
interrupt_pin(const struct found *f)
|
||||
{
|
||||
static const char *const amd[] = { "AMDI0030", "AMD0030", 0 };
|
||||
uacpi_namespace_node *node = node_at(f->gpioctl);
|
||||
struct found g;
|
||||
|
||||
if(!f->gpio || node == 0 || !uacpi_device_matches_pnp_id(node, amd))
|
||||
return 0;
|
||||
if(resources(node, &g) < 0 || g.mem == 0)
|
||||
return 0;
|
||||
return (volatile uint32 *)((char *)ioremap(g.mem, 0x1000) + f->pin * 4);
|
||||
}
|
||||
|
||||
// One input report: two bytes of length, length included, then the report.
|
||||
// A length of 0 is the device saying it has nothing, or acknowledging a
|
||||
// reset.
|
||||
static int
|
||||
read_input(struct i2chid *d)
|
||||
{
|
||||
int len;
|
||||
|
||||
if(dw_xfer(d->c, d->addr, 0, 0, d->buf, d->maxin) < 0)
|
||||
return -1;
|
||||
len = d->buf[0] | (d->buf[1] << 8);
|
||||
if(len <= 2 || len > (int)d->maxin)
|
||||
return 0;
|
||||
hid_attached_input(d->hid, d->buf + 2, len - 2);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static uacpi_iteration_decision
|
||||
probe(void *user, uacpi_namespace_node *node, uacpi_u32 depth)
|
||||
{
|
||||
struct i2chid *d;
|
||||
struct found f;
|
||||
uchar desc[30], *rdesc;
|
||||
uint32 descreg, rlen, rreg;
|
||||
int i;
|
||||
|
||||
(void)user;
|
||||
(void)depth;
|
||||
if(ndev == NI2CHID)
|
||||
return UACPI_ITERATION_DECISION_BREAK;
|
||||
d = &devs[ndev];
|
||||
memset(d, 0, sizeof(*d));
|
||||
memmove(d->name, uacpi_namespace_node_name(node).text, 4);
|
||||
|
||||
if(resources(node, &f) < 0 || !f.i2c){
|
||||
cprintf("i2c-hid: %s has no I2C connection\n", d->name);
|
||||
return UACPI_ITERATION_DECISION_CONTINUE;
|
||||
}
|
||||
if(hid_desc_register(node, &descreg) < 0){
|
||||
cprintf("i2c-hid: %s: _DSM gave no descriptor register\n", d->name);
|
||||
return UACPI_ITERATION_DECISION_CONTINUE;
|
||||
}
|
||||
if((d->c = controller(f.bus, f.speed)) == 0)
|
||||
return UACPI_ITERATION_DECISION_CONTINUE;
|
||||
d->addr = f.addr;
|
||||
uacpi_execute(node, "_PS0", 0);
|
||||
|
||||
if(reg_read(d, descreg, desc, sizeof(desc)) < 0 ||
|
||||
(desc[0] | desc[1] << 8) != 30){
|
||||
cprintf("i2c-hid: %s at 0x%x does not answer\n", d->name, d->addr);
|
||||
return UACPI_ITERATION_DECISION_CONTINUE;
|
||||
}
|
||||
rlen = desc[4] | desc[5] << 8;
|
||||
rreg = desc[6] | desc[7] << 8;
|
||||
d->inreg = desc[8] | desc[9] << 8;
|
||||
d->maxin = desc[10] | desc[11] << 8;
|
||||
d->cmdreg = desc[16] | desc[17] << 8;
|
||||
if(d->maxin < 3 || d->maxin > PGSIZE || rlen == 0 || rlen > PGSIZE){
|
||||
cprintf("i2c-hid: %s: unusable descriptor\n", d->name);
|
||||
return UACPI_ITERATION_DECISION_CONTINUE;
|
||||
}
|
||||
d->pin = interrupt_pin(&f);
|
||||
d->active_low = f.active_low;
|
||||
|
||||
command(d, OP_SET_POWER, 0); // 0 = on
|
||||
wait_ticks(2);
|
||||
command(d, OP_RESET, 0);
|
||||
wait_ticks(10);
|
||||
d->buf = (uchar *)kalloc();
|
||||
rdesc = (uchar *)kalloc();
|
||||
if(d->buf == 0 || rdesc == 0)
|
||||
return UACPI_ITERATION_DECISION_BREAK;
|
||||
// The reset is acknowledged with an empty report. Read it, and anything
|
||||
// else already waiting, so the first real report is not behind it.
|
||||
for(i = 0; i < 4 && (d->pin == 0 || asserted(d)); i++)
|
||||
if(dw_xfer(d->c, d->addr, 0, 0, d->buf, d->maxin) < 0)
|
||||
break;
|
||||
|
||||
if(reg_read(d, rreg, rdesc, rlen) < 0){
|
||||
cprintf("i2c-hid: %s: report descriptor unreadable\n", d->name);
|
||||
kfree((char *)rdesc);
|
||||
return UACPI_ITERATION_DECISION_CONTINUE;
|
||||
}
|
||||
char what[24] = "i2c-hid ";
|
||||
memmove(what + 8, d->name, 4);
|
||||
d->hid = hid_attach(rdesc, rlen, what);
|
||||
kfree((char *)rdesc);
|
||||
if(d->hid == 0){
|
||||
cprintf("i2c-hid: %s is neither a keyboard nor a pointer\n", d->name);
|
||||
return UACPI_ITERATION_DECISION_CONTINUE;
|
||||
}
|
||||
cprintf("i2c-hid: %s: %04x:%04x at 0x%x, %s\n", d->name,
|
||||
desc[20] | desc[21] << 8, desc[22] | desc[23] << 8, d->addr,
|
||||
d->pin ? "interrupt pin watched" : "polled");
|
||||
ndev++;
|
||||
return UACPI_ITERATION_DECISION_CONTINUE;
|
||||
}
|
||||
|
||||
static void
|
||||
i2c_hid_thread(void)
|
||||
{
|
||||
static const char *const ids[] = { "PNP0C50", "ACPI0C50", 0 };
|
||||
int i, n;
|
||||
|
||||
uacpi_find_devices_at(uacpi_namespace_root(), ids, probe, 0);
|
||||
if(ndev == 0)
|
||||
for(;;)
|
||||
wait_ticks(1000000);
|
||||
|
||||
for(;;){
|
||||
wait_ticks(1);
|
||||
for(i = 0; i < ndev; i++){
|
||||
struct i2chid *d = &devs[i];
|
||||
if(d->pin == 0){
|
||||
read_input(d);
|
||||
continue;
|
||||
}
|
||||
// Everything waiting, up to a bound so that a stuck line cannot keep
|
||||
// the other devices from their turn.
|
||||
for(n = 0; n < 16 && asserted(d); n++)
|
||||
if(read_input(d) < 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
i2c_hid_start(void)
|
||||
{
|
||||
if(acpi_rsdp_phys() == 0)
|
||||
return;
|
||||
if(kthread_create(i2c_hid_thread, "i2chid") < 0)
|
||||
cprintf("i2c-hid: no thread; touchpads will not work\n");
|
||||
}
|
||||
Loading…
Reference in a new issue