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
635 lines
18 KiB
C
635 lines
18 KiB
C
// 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");
|
|
}
|