The second micro driver: i2c_hid.c with everything but reading a keyboard's input reports taken out. No ACPI -- AML cannot run on a dead kernel -- so the live driver hands over what it found at boot (i2c_hid_keyboards): the DesignWare controller, the address, the report size, the AMD GPIO pin if there is one, and the parsed report descriptor. No power commands, no reset, a read-only transfer. usb_hid.c's report-protocol rewrite is split out as hid_boot_report(), which does nothing but rewrite, and kernel/gmmos/hidkeys.c turns boot reports into keys for this and the USB micro drivers to come. GMMOS now probes every micro driver and polls all that answer. Not run: QEMU has no DesignWare I2C, and the laptops at hand have PS/2 keyboards. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
155 lines
3.8 KiB
C
155 lines
3.8 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
//
|
|
// GMMOS's I2C keyboards: kernel/acpi/i2c_hid.c with everything but reading a
|
|
// keyboard's input reports taken out.
|
|
//
|
|
// No ACPI -- the firmware's AML cannot run on a dead kernel, so the devices
|
|
// are the ones the live driver found at boot (i2c_hid_keyboards). No power
|
|
// commands, no reset, no report descriptor: the device is already on and was
|
|
// already described. No interrupt: the pin is read when the device has one,
|
|
// otherwise the input register is read every few milliseconds regardless.
|
|
// And the DesignWare transfer is read-only, since an input report is a plain
|
|
// read with no register write before it.
|
|
|
|
#include "core/types.h"
|
|
#include "core/defs.h"
|
|
#include "dev/hid.h"
|
|
#include "gmmos/gmmos.h"
|
|
|
|
#define IC_TAR 0x04
|
|
#define IC_DATA_CMD 0x10
|
|
#define IC_RAW_INTR_STAT 0x34
|
|
#define IC_CLR_INTR 0x40
|
|
#define IC_CLR_TX_ABRT 0x54
|
|
#define IC_CLR_STOP_DET 0x60
|
|
#define IC_ENABLE 0x6C
|
|
#define IC_TXFLR 0x74
|
|
#define IC_RXFLR 0x78
|
|
#define IC_ENABLE_STATUS 0x9C
|
|
|
|
#define CMD_READ (1 << 8)
|
|
#define CMD_STOP (1 << 9)
|
|
#define INTR_TX_ABRT (1 << 6)
|
|
#define INTR_STOP_DET (1 << 9)
|
|
|
|
#define AMD_PIN_STS (1u << 16)
|
|
|
|
#define NKBD 4
|
|
|
|
static struct gmmos_i2c_kbd kb[NKBD];
|
|
static int nkb, turn;
|
|
static uchar buf[4096];
|
|
static struct gmmos_hidkeys keys;
|
|
|
|
static uint32
|
|
rd(struct gmmos_i2c_kbd *d, int reg)
|
|
{
|
|
return d->regs[reg / 4];
|
|
}
|
|
|
|
static void
|
|
wr(struct gmmos_i2c_kbd *d, int reg, uint32 v)
|
|
{
|
|
d->regs[reg / 4] = v;
|
|
}
|
|
|
|
static int
|
|
enable(struct gmmos_i2c_kbd *d, int on)
|
|
{
|
|
for (int i = 0; i < 1000; i++) {
|
|
wr(d, IC_ENABLE, on);
|
|
if ((rd(d, IC_ENABLE_STATUS) & 1) == (uint32)on)
|
|
return 0;
|
|
microdelay(25);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
// n bytes from the device. Disabling the controller first also abandons
|
|
// whatever transfer the live driver had going when the kernel died.
|
|
static int
|
|
read_bytes(struct gmmos_i2c_kbd *d, uchar *r, int n)
|
|
{
|
|
int sent = 0, got = 0, spin;
|
|
|
|
if (enable(d, 0) < 0)
|
|
return -1;
|
|
wr(d, IC_TAR, d->addr);
|
|
rd(d, IC_CLR_INTR);
|
|
if (enable(d, 1) < 0)
|
|
return -1;
|
|
|
|
for (spin = 0; got < n; spin++) {
|
|
if (rd(d, IC_RAW_INTR_STAT) & INTR_TX_ABRT)
|
|
goto abort;
|
|
while (got < n && rd(d, IC_RXFLR) > 0)
|
|
r[got++] = rd(d, IC_DATA_CMD);
|
|
while (sent < n && (int)rd(d, IC_TXFLR) < d->txdepth &&
|
|
sent - got < d->rxdepth) {
|
|
wr(d, IC_DATA_CMD, CMD_READ | (sent == n - 1 ? CMD_STOP : 0));
|
|
sent++;
|
|
}
|
|
if (spin > 20000)
|
|
goto abort;
|
|
microdelay(10);
|
|
}
|
|
for (spin = 0; !(rd(d, IC_RAW_INTR_STAT) & INTR_STOP_DET); spin++) {
|
|
if (spin > 2000)
|
|
goto abort;
|
|
microdelay(10);
|
|
}
|
|
rd(d, IC_CLR_STOP_DET);
|
|
enable(d, 0);
|
|
return 0;
|
|
|
|
abort:
|
|
rd(d, IC_CLR_TX_ABRT);
|
|
rd(d, IC_CLR_INTR);
|
|
enable(d, 0);
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
probe(void)
|
|
{
|
|
nkb = i2c_hid_keyboards(kb, NKBD);
|
|
for (int i = 0; i < nkb; i++)
|
|
if (kb[i].maxin > sizeof(buf))
|
|
kb[i].maxin = sizeof(buf);
|
|
return nkb > 0;
|
|
}
|
|
|
|
static int
|
|
poll(void)
|
|
{
|
|
int k = gmmos_hid_next(&keys);
|
|
if (k)
|
|
return k;
|
|
|
|
// GMMOS polls every millisecond; a blind read is a whole report's worth of
|
|
// bus time, so a device with no pin is read every fourth time.
|
|
for (int i = 0; i < nkb; i++) {
|
|
struct gmmos_i2c_kbd *d = &kb[i];
|
|
if (d->pin) {
|
|
int high = (*d->pin & AMD_PIN_STS) != 0;
|
|
if (high == d->active_low)
|
|
continue;
|
|
} else if (turn++ % 4) {
|
|
continue;
|
|
}
|
|
if (read_bytes(d, buf, d->maxin) < 0)
|
|
continue;
|
|
int len = buf[0] | (buf[1] << 8);
|
|
uchar boot[8];
|
|
if (len > 2 && len <= (int)d->maxin &&
|
|
hid_boot_report(d->hid, buf + 2, len - 2, boot))
|
|
gmmos_hid_report(&keys, boot);
|
|
}
|
|
return gmmos_hid_next(&keys);
|
|
}
|
|
|
|
const struct gmmos_kbd gmmos_i2c = {
|
|
.name = "I2C",
|
|
.probe = probe,
|
|
.poll = poll,
|
|
};
|