1334 lines
34 KiB
C
1334 lines
34 KiB
C
/*-
|
|
* Copyright (c) 2018-2019 Marc Priggemeyer <marc.priggemeyer@gmail.com>
|
|
* Copyright (c) 2019-2020 Vladimir Kondratyev <wulf@FreeBSD.org>
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
/*
|
|
* I2C HID transport backend.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
#include "opt_hid.h"
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/callout.h>
|
|
#include <sys/endian.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/module.h>
|
|
#include <sys/rman.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/taskqueue.h>
|
|
|
|
#include <machine/resource.h>
|
|
|
|
#include <contrib/dev/acpica/include/acpi.h>
|
|
#include <contrib/dev/acpica/include/accommon.h>
|
|
#include <dev/acpica/acpivar.h>
|
|
|
|
#include <dev/evdev/input.h>
|
|
|
|
#include <dev/hid/hid.h>
|
|
#include <dev/hid/hidquirk.h>
|
|
|
|
#include <dev/iicbus/iic.h>
|
|
#include <dev/iicbus/iicbus.h>
|
|
#include <dev/iicbus/iiconf.h>
|
|
|
|
#include "hid_if.h"
|
|
|
|
#ifdef IICHID_DEBUG
|
|
static int iichid_debug = 0;
|
|
|
|
static SYSCTL_NODE(_hw, OID_AUTO, iichid, CTLFLAG_RW, 0, "I2C HID");
|
|
SYSCTL_INT(_hw_iichid, OID_AUTO, debug, CTLFLAG_RWTUN,
|
|
&iichid_debug, 1, "Debug level");
|
|
|
|
#define DPRINTFN(sc, n, ...) do { \
|
|
if (iichid_debug >= (n)) \
|
|
device_printf((sc)->dev, __VA_ARGS__); \
|
|
} while (0)
|
|
#define DPRINTF(sc, ...) DPRINTFN(sc, 1, __VA_ARGS__)
|
|
#else
|
|
#define DPRINTFN(...) do {} while (0)
|
|
#define DPRINTF(...) do {} while (0)
|
|
#endif
|
|
|
|
typedef hid_size_t iichid_size_t;
|
|
#define IICHID_SIZE_MAX (UINT16_MAX - 2)
|
|
|
|
/* 7.2 */
|
|
enum {
|
|
I2C_HID_CMD_DESCR = 0x0,
|
|
I2C_HID_CMD_RESET = 0x1,
|
|
I2C_HID_CMD_GET_REPORT = 0x2,
|
|
I2C_HID_CMD_SET_REPORT = 0x3,
|
|
I2C_HID_CMD_GET_IDLE = 0x4,
|
|
I2C_HID_CMD_SET_IDLE = 0x5,
|
|
I2C_HID_CMD_GET_PROTO = 0x6,
|
|
I2C_HID_CMD_SET_PROTO = 0x7,
|
|
I2C_HID_CMD_SET_POWER = 0x8,
|
|
};
|
|
|
|
#define I2C_HID_POWER_ON 0x0
|
|
#define I2C_HID_POWER_OFF 0x1
|
|
|
|
/*
|
|
* Since interrupt resource acquisition is not always possible (in case of GPIO
|
|
* interrupts) iichid now supports a sampling_mode.
|
|
* Set dev.iichid.<unit>.sampling_rate_slow to a value greater then 0
|
|
* to activate sampling. A value of 0 is possible but will not reset the
|
|
* callout and, thereby, disable further report requests. Do not set the
|
|
* sampling_rate_fast value too high as it may result in periodical lags of
|
|
* cursor motion.
|
|
*/
|
|
#define IICHID_SAMPLING_RATE_FAST 60
|
|
#define IICHID_SAMPLING_RATE_SLOW 10
|
|
#define IICHID_SAMPLING_HYSTERESIS 1
|
|
|
|
/* 5.1.1 - HID Descriptor Format */
|
|
struct i2c_hid_desc {
|
|
uint16_t wHIDDescLength;
|
|
uint16_t bcdVersion;
|
|
uint16_t wReportDescLength;
|
|
uint16_t wReportDescRegister;
|
|
uint16_t wInputRegister;
|
|
uint16_t wMaxInputLength;
|
|
uint16_t wOutputRegister;
|
|
uint16_t wMaxOutputLength;
|
|
uint16_t wCommandRegister;
|
|
uint16_t wDataRegister;
|
|
uint16_t wVendorID;
|
|
uint16_t wProductID;
|
|
uint16_t wVersionID;
|
|
uint32_t reserved;
|
|
} __packed;
|
|
|
|
#define IICHID_REG_NONE -1
|
|
#define IICHID_REG_ACPI (UINT16_MAX + 1)
|
|
#define IICHID_REG_ELAN 0x0001
|
|
|
|
static const struct iichid_id {
|
|
char *id;
|
|
int reg;
|
|
} iichid_ids[] = {
|
|
{ "ELAN0000", IICHID_REG_ELAN },
|
|
{ "PNP0C50", IICHID_REG_ACPI },
|
|
{ "ACPI0C50", IICHID_REG_ACPI },
|
|
{ NULL, 0 },
|
|
};
|
|
|
|
enum iichid_powerstate_how {
|
|
IICHID_PS_NULL,
|
|
IICHID_PS_ON,
|
|
IICHID_PS_OFF,
|
|
};
|
|
|
|
/*
|
|
* Locking: no internal locks are used. To serialize access to shared members,
|
|
* external iicbus lock should be taken. That allows to make locking greatly
|
|
* simple at the cost of running front interrupt handlers with locked bus.
|
|
*/
|
|
struct iichid_softc {
|
|
device_t dev;
|
|
|
|
bool probe_done;
|
|
int probe_result;
|
|
|
|
struct hid_device_info hw;
|
|
uint16_t addr; /* Shifted left by 1 */
|
|
struct i2c_hid_desc desc;
|
|
|
|
hid_intr_t *intr_handler;
|
|
void *intr_ctx;
|
|
uint8_t *intr_buf;
|
|
iichid_size_t intr_bufsize;
|
|
|
|
int irq_rid;
|
|
struct resource *irq_res;
|
|
void *irq_cookie;
|
|
|
|
#ifdef IICHID_SAMPLING
|
|
int sampling_rate_slow; /* iicbus lock */
|
|
int sampling_rate_fast;
|
|
int sampling_hysteresis;
|
|
int missing_samples; /* iicbus lock */
|
|
struct timeout_task periodic_task; /* iicbus lock */
|
|
bool callout_setup; /* iicbus lock */
|
|
struct taskqueue *taskqueue;
|
|
struct task event_task;
|
|
#endif
|
|
|
|
struct task suspend_task;
|
|
bool open; /* iicbus lock */
|
|
bool suspend; /* iicbus lock */
|
|
bool power_on; /* iicbus lock */
|
|
};
|
|
|
|
static device_probe_t iichid_probe;
|
|
static device_attach_t iichid_attach;
|
|
static device_detach_t iichid_detach;
|
|
static device_resume_t iichid_resume;
|
|
static device_suspend_t iichid_suspend;
|
|
|
|
static void iichid_suspend_task(void *, int);
|
|
|
|
#ifdef IICHID_SAMPLING
|
|
static int iichid_setup_callout(struct iichid_softc *);
|
|
static int iichid_reset_callout(struct iichid_softc *);
|
|
static void iichid_teardown_callout(struct iichid_softc *);
|
|
#endif
|
|
|
|
static inline int
|
|
acpi_is_iichid(ACPI_HANDLE handle)
|
|
{
|
|
const struct iichid_id *ids;
|
|
UINT32 sta;
|
|
int reg;
|
|
|
|
for (ids = iichid_ids; ids->id != NULL; ids++) {
|
|
if (acpi_MatchHid(handle, ids->id)) {
|
|
reg = ids->reg;
|
|
break;
|
|
}
|
|
}
|
|
if (ids->id == NULL)
|
|
return (IICHID_REG_NONE);
|
|
|
|
/*
|
|
* If no _STA method or if it failed, then assume that
|
|
* the device is present.
|
|
*/
|
|
if (ACPI_FAILURE(acpi_GetInteger(handle, "_STA", &sta)) ||
|
|
ACPI_DEVICE_PRESENT(sta))
|
|
return (reg);
|
|
|
|
return (IICHID_REG_NONE);
|
|
}
|
|
|
|
static ACPI_STATUS
|
|
iichid_get_config_reg(ACPI_HANDLE handle, uint16_t *config_reg)
|
|
{
|
|
ACPI_OBJECT *result;
|
|
ACPI_BUFFER acpi_buf;
|
|
ACPI_STATUS status;
|
|
|
|
/*
|
|
* function (_DSM) to be evaluated to retrieve the address of
|
|
* the configuration register of the HID device.
|
|
*/
|
|
/* 3cdff6f7-4267-4555-ad05-b30a3d8938de */
|
|
static uint8_t dsm_guid[ACPI_UUID_LENGTH] = {
|
|
0xF7, 0xF6, 0xDF, 0x3C, 0x67, 0x42, 0x55, 0x45,
|
|
0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE,
|
|
};
|
|
|
|
status = acpi_EvaluateDSMTyped(handle, dsm_guid, 1, 1, NULL, &acpi_buf,
|
|
ACPI_TYPE_INTEGER);
|
|
if (ACPI_FAILURE(status)) {
|
|
printf("%s: error evaluating _DSM\n", __func__);
|
|
return (status);
|
|
}
|
|
result = (ACPI_OBJECT *) acpi_buf.Pointer;
|
|
*config_reg = result->Integer.Value & 0xFFFF;
|
|
|
|
AcpiOsFree(result);
|
|
return (status);
|
|
}
|
|
|
|
static int
|
|
iichid_cmd_read(struct iichid_softc* sc, void *buf, iichid_size_t maxlen,
|
|
iichid_size_t *actual_len)
|
|
{
|
|
/*
|
|
* 6.1.3 - Retrieval of Input Reports
|
|
* DEVICE returns the length (2 Bytes) and the entire Input Report.
|
|
*/
|
|
uint8_t actbuf[2] = { 0, 0 };
|
|
/* Read actual input report length. */
|
|
struct iic_msg msgs[] = {
|
|
{ sc->addr, IIC_M_RD | IIC_M_NOSTOP, sizeof(actbuf), actbuf },
|
|
};
|
|
uint16_t actlen;
|
|
int error;
|
|
|
|
error = iicbus_transfer(sc->dev, msgs, nitems(msgs));
|
|
if (error != 0)
|
|
return (error);
|
|
|
|
actlen = actbuf[0] | actbuf[1] << 8;
|
|
if (actlen <= 2 || actlen == 0xFFFF || maxlen == 0) {
|
|
/* Read and discard 1 byte to send I2C STOP condition. */
|
|
msgs[0] = (struct iic_msg)
|
|
{ sc->addr, IIC_M_RD | IIC_M_NOSTART, 1, actbuf };
|
|
actlen = 0;
|
|
} else {
|
|
actlen -= 2;
|
|
if (actlen > maxlen) {
|
|
DPRINTF(sc, "input report too big. requested=%d "
|
|
"received=%d\n", maxlen, actlen);
|
|
actlen = maxlen;
|
|
}
|
|
/* Read input report itself. */
|
|
msgs[0] = (struct iic_msg)
|
|
{ sc->addr, IIC_M_RD | IIC_M_NOSTART, actlen, buf };
|
|
}
|
|
|
|
error = iicbus_transfer(sc->dev, msgs, 1);
|
|
if (error == 0 && actual_len != NULL)
|
|
*actual_len = actlen;
|
|
|
|
DPRINTFN(sc, 5,
|
|
"%*D - %*D\n", 2, actbuf, " ", msgs[0].len, msgs[0].buf, " ");
|
|
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
iichid_cmd_write(struct iichid_softc *sc, const void *buf, iichid_size_t len)
|
|
{
|
|
/* 6.2.3 - Sending Output Reports. */
|
|
uint8_t *cmdreg = (uint8_t *)&sc->desc.wOutputRegister;
|
|
uint16_t replen = 2 + len;
|
|
uint8_t cmd[4] = { cmdreg[0], cmdreg[1], replen & 0xFF, replen >> 8 };
|
|
struct iic_msg msgs[] = {
|
|
{sc->addr, IIC_M_WR | IIC_M_NOSTOP, sizeof(cmd), cmd},
|
|
{sc->addr, IIC_M_WR | IIC_M_NOSTART, len, __DECONST(void *, buf)},
|
|
};
|
|
|
|
if (le16toh(sc->desc.wMaxOutputLength) == 0)
|
|
return (IIC_ENOTSUPP);
|
|
if (len < 2)
|
|
return (IIC_ENOTSUPP);
|
|
|
|
DPRINTF(sc, "HID command I2C_HID_CMD_WRITE (len %d): "
|
|
"%*D\n", len, len, buf, " ");
|
|
|
|
return (iicbus_transfer(sc->dev, msgs, nitems(msgs)));
|
|
}
|
|
|
|
static int
|
|
iichid_cmd_get_hid_desc(struct iichid_softc *sc, uint16_t config_reg,
|
|
struct i2c_hid_desc *hid_desc)
|
|
{
|
|
/*
|
|
* 5.2.2 - HID Descriptor Retrieval
|
|
* register is passed from the controller.
|
|
*/
|
|
uint16_t cmd = htole16(config_reg);
|
|
struct iic_msg msgs[] = {
|
|
{ sc->addr, IIC_M_WR | IIC_M_NOSTOP, 2, (uint8_t *)&cmd },
|
|
{ sc->addr, IIC_M_RD, sizeof(*hid_desc), (uint8_t *)hid_desc },
|
|
};
|
|
int error;
|
|
|
|
DPRINTF(sc, "HID command I2C_HID_CMD_DESCR at 0x%x\n", config_reg);
|
|
|
|
error = iicbus_transfer(sc->dev, msgs, nitems(msgs));
|
|
if (error != 0)
|
|
return (error);
|
|
|
|
DPRINTF(sc, "HID descriptor: %*D\n",
|
|
(int)sizeof(struct i2c_hid_desc), hid_desc, " ");
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
iichid_set_power(struct iichid_softc *sc, uint8_t param)
|
|
{
|
|
uint8_t *cmdreg = (uint8_t *)&sc->desc.wCommandRegister;
|
|
uint8_t cmd[] = { cmdreg[0], cmdreg[1], param, I2C_HID_CMD_SET_POWER };
|
|
struct iic_msg msgs[] = {
|
|
{ sc->addr, IIC_M_WR, sizeof(cmd), cmd },
|
|
};
|
|
|
|
DPRINTF(sc, "HID command I2C_HID_CMD_SET_POWER(%d)\n", param);
|
|
|
|
return (iicbus_transfer(sc->dev, msgs, nitems(msgs)));
|
|
}
|
|
|
|
static int
|
|
iichid_reset(struct iichid_softc *sc)
|
|
{
|
|
uint8_t *cmdreg = (uint8_t *)&sc->desc.wCommandRegister;
|
|
uint8_t cmd[] = { cmdreg[0], cmdreg[1], 0, I2C_HID_CMD_RESET };
|
|
struct iic_msg msgs[] = {
|
|
{ sc->addr, IIC_M_WR, sizeof(cmd), cmd },
|
|
};
|
|
|
|
DPRINTF(sc, "HID command I2C_HID_CMD_RESET\n");
|
|
|
|
return (iicbus_transfer(sc->dev, msgs, nitems(msgs)));
|
|
}
|
|
|
|
static int
|
|
iichid_cmd_get_report_desc(struct iichid_softc* sc, void *buf,
|
|
iichid_size_t len)
|
|
{
|
|
uint16_t cmd = sc->desc.wReportDescRegister;
|
|
struct iic_msg msgs[] = {
|
|
{ sc->addr, IIC_M_WR | IIC_M_NOSTOP, 2, (uint8_t *)&cmd },
|
|
{ sc->addr, IIC_M_RD, len, buf },
|
|
};
|
|
int error;
|
|
|
|
DPRINTF(sc, "HID command I2C_HID_REPORT_DESCR at 0x%x with size %d\n",
|
|
le16toh(cmd), len);
|
|
|
|
error = iicbus_transfer(sc->dev, msgs, nitems(msgs));
|
|
if (error != 0)
|
|
return (error);
|
|
|
|
DPRINTF(sc, "HID report descriptor: %*D\n", len, buf, " ");
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
iichid_cmd_get_report(struct iichid_softc* sc, void *buf, iichid_size_t maxlen,
|
|
iichid_size_t *actual_len, uint8_t type, uint8_t id)
|
|
{
|
|
/*
|
|
* 7.2.2.4 - "The protocol is optimized for Report < 15. If a
|
|
* report ID >= 15 is necessary, then the Report ID in the Low Byte
|
|
* must be set to 1111 and a Third Byte is appended to the protocol.
|
|
* This Third Byte contains the entire/actual report ID."
|
|
*/
|
|
uint8_t *dtareg = (uint8_t *)&sc->desc.wDataRegister;
|
|
uint8_t *cmdreg = (uint8_t *)&sc->desc.wCommandRegister;
|
|
uint8_t cmd[] = { /*________|______id>=15_____|______id<15______*/
|
|
cmdreg[0] ,
|
|
cmdreg[1] ,
|
|
(id >= 15 ? 15 | (type << 4): id | (type << 4)),
|
|
I2C_HID_CMD_GET_REPORT ,
|
|
(id >= 15 ? id : dtareg[0] ),
|
|
(id >= 15 ? dtareg[0] : dtareg[1] ),
|
|
(id >= 15 ? dtareg[1] : 0 ),
|
|
};
|
|
int cmdlen = (id >= 15 ? 7 : 6 );
|
|
uint8_t actbuf[2] = { 0, 0 };
|
|
uint16_t actlen;
|
|
int d, error;
|
|
struct iic_msg msgs[] = {
|
|
{ sc->addr, IIC_M_WR | IIC_M_NOSTOP, cmdlen, cmd },
|
|
{ sc->addr, IIC_M_RD | IIC_M_NOSTOP, 2, actbuf },
|
|
{ sc->addr, IIC_M_RD | IIC_M_NOSTART, maxlen, buf },
|
|
};
|
|
|
|
if (maxlen == 0)
|
|
return (EINVAL);
|
|
|
|
DPRINTF(sc, "HID command I2C_HID_CMD_GET_REPORT %d "
|
|
"(type %d, len %d)\n", id, type, maxlen);
|
|
|
|
/*
|
|
* 7.2.2.2 - Response will be a 2-byte length value, the report
|
|
* id (1 byte, if defined in Report Descriptor), and then the report.
|
|
*/
|
|
error = iicbus_transfer(sc->dev, msgs, nitems(msgs));
|
|
if (error != 0)
|
|
return (error);
|
|
|
|
actlen = actbuf[0] | actbuf[1] << 8;
|
|
if (actlen != maxlen + 2)
|
|
DPRINTF(sc, "response size %d != expected length %d\n",
|
|
actlen, maxlen + 2);
|
|
|
|
if (actlen <= 2 || actlen == 0xFFFF)
|
|
return (ENOMSG);
|
|
|
|
d = id != 0 ? *(uint8_t *)buf : 0;
|
|
if (d != id) {
|
|
DPRINTF(sc, "response report id %d != %d\n", d, id);
|
|
return (EBADMSG);
|
|
}
|
|
|
|
actlen -= 2;
|
|
if (actlen > maxlen)
|
|
actlen = maxlen;
|
|
if (actual_len != NULL)
|
|
*actual_len = actlen;
|
|
|
|
DPRINTF(sc, "response: %*D %*D\n", 2, actbuf, " ", actlen, buf, " ");
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
iichid_cmd_set_report(struct iichid_softc* sc, const void *buf,
|
|
iichid_size_t len, uint8_t type, uint8_t id)
|
|
{
|
|
/*
|
|
* 7.2.2.4 - "The protocol is optimized for Report < 15. If a
|
|
* report ID >= 15 is necessary, then the Report ID in the Low Byte
|
|
* must be set to 1111 and a Third Byte is appended to the protocol.
|
|
* This Third Byte contains the entire/actual report ID."
|
|
*/
|
|
uint8_t *dtareg = (uint8_t *)&sc->desc.wDataRegister;
|
|
uint8_t *cmdreg = (uint8_t *)&sc->desc.wCommandRegister;
|
|
uint16_t replen = 2 + len;
|
|
uint8_t cmd[] = { /*________|______id>=15_____|______id<15______*/
|
|
cmdreg[0] ,
|
|
cmdreg[1] ,
|
|
(id >= 15 ? 15 | (type << 4): id | (type << 4)),
|
|
I2C_HID_CMD_SET_REPORT ,
|
|
(id >= 15 ? id : dtareg[0] ),
|
|
(id >= 15 ? dtareg[0] : dtareg[1] ),
|
|
(id >= 15 ? dtareg[1] : replen & 0xff ),
|
|
(id >= 15 ? replen & 0xff : replen >> 8 ),
|
|
(id >= 15 ? replen >> 8 : 0 ),
|
|
};
|
|
int cmdlen = (id >= 15 ? 9 : 8 );
|
|
struct iic_msg msgs[] = {
|
|
{sc->addr, IIC_M_WR | IIC_M_NOSTOP, cmdlen, cmd},
|
|
{sc->addr, IIC_M_WR | IIC_M_NOSTART, len, __DECONST(void *, buf)},
|
|
};
|
|
|
|
DPRINTF(sc, "HID command I2C_HID_CMD_SET_REPORT %d (type %d, len %d): "
|
|
"%*D\n", id, type, len, len, buf, " ");
|
|
|
|
return (iicbus_transfer(sc->dev, msgs, nitems(msgs)));
|
|
}
|
|
|
|
#ifdef IICHID_SAMPLING
|
|
static void
|
|
iichid_event_task(void *context, int pending)
|
|
{
|
|
struct iichid_softc *sc;
|
|
device_t parent;
|
|
iichid_size_t actual;
|
|
bool bus_requested;
|
|
int error;
|
|
|
|
sc = context;
|
|
parent = device_get_parent(sc->dev);
|
|
|
|
bus_requested = false;
|
|
if (iicbus_request_bus(parent, sc->dev, IIC_WAIT) != 0)
|
|
goto rearm;
|
|
bus_requested = true;
|
|
|
|
if (!sc->power_on)
|
|
goto out;
|
|
|
|
error = iichid_cmd_read(sc, sc->intr_buf, sc->intr_bufsize, &actual);
|
|
if (error == 0) {
|
|
if (actual > 0) {
|
|
sc->intr_handler(sc->intr_ctx, sc->intr_buf, actual);
|
|
sc->missing_samples = 0;
|
|
} else
|
|
++sc->missing_samples;
|
|
} else
|
|
DPRINTF(sc, "read error occurred: %d\n", error);
|
|
|
|
rearm:
|
|
if (sc->callout_setup && sc->sampling_rate_slow > 0) {
|
|
if (sc->missing_samples == sc->sampling_hysteresis)
|
|
sc->intr_handler(sc->intr_ctx, sc->intr_buf, 0);
|
|
taskqueue_enqueue_timeout(sc->taskqueue, &sc->periodic_task,
|
|
hz / MAX(sc->missing_samples >= sc->sampling_hysteresis ?
|
|
sc->sampling_rate_slow : sc->sampling_rate_fast, 1));
|
|
}
|
|
out:
|
|
if (bus_requested)
|
|
iicbus_release_bus(parent, sc->dev);
|
|
}
|
|
#endif /* IICHID_SAMPLING */
|
|
|
|
static void
|
|
iichid_intr(void *context)
|
|
{
|
|
struct iichid_softc *sc;
|
|
device_t parent;
|
|
iichid_size_t maxlen, actual;
|
|
int error;
|
|
|
|
sc = context;
|
|
parent = device_get_parent(sc->dev);
|
|
|
|
/*
|
|
* Designware(IG4) driver-specific hack.
|
|
* Requesting of an I2C bus with IIC_DONTWAIT parameter enables polled
|
|
* mode in the driver, making possible iicbus_transfer execution from
|
|
* interrupt handlers and callouts.
|
|
*/
|
|
if (iicbus_request_bus(parent, sc->dev, IIC_DONTWAIT) != 0)
|
|
return;
|
|
|
|
/*
|
|
* Reading of input reports of I2C devices residing in SLEEP state is
|
|
* not allowed and often returns a garbage. If a HOST needs to
|
|
* communicate with the DEVICE it MUST issue a SET POWER command
|
|
* (to ON) before any other command. As some hardware requires reads to
|
|
* acknowledge interrupts we fetch only length header and discard it.
|
|
*/
|
|
maxlen = sc->power_on ? sc->intr_bufsize : 0;
|
|
error = iichid_cmd_read(sc, sc->intr_buf, maxlen, &actual);
|
|
if (error == 0) {
|
|
if (sc->power_on) {
|
|
if (actual != 0)
|
|
sc->intr_handler(sc->intr_ctx, sc->intr_buf,
|
|
actual);
|
|
else
|
|
DPRINTF(sc, "no data received\n");
|
|
}
|
|
} else
|
|
DPRINTF(sc, "read error occurred: %d\n", error);
|
|
|
|
iicbus_release_bus(parent, sc->dev);
|
|
}
|
|
|
|
static int
|
|
iichid_set_power_state(struct iichid_softc *sc,
|
|
enum iichid_powerstate_how how_open,
|
|
enum iichid_powerstate_how how_suspend)
|
|
{
|
|
device_t parent;
|
|
int error;
|
|
int how_request;
|
|
bool power_on;
|
|
|
|
/*
|
|
* Request iicbus early as sc->suspend and sc->power_on
|
|
* are protected by iicbus internal lock.
|
|
*/
|
|
parent = device_get_parent(sc->dev);
|
|
/* Allow to interrupt open()/close() handlers by SIGINT */
|
|
how_request = how_open == IICHID_PS_NULL ? IIC_WAIT : IIC_INTRWAIT;
|
|
error = iicbus_request_bus(parent, sc->dev, how_request);
|
|
if (error != 0)
|
|
return (error);
|
|
|
|
switch (how_open) {
|
|
case IICHID_PS_ON:
|
|
sc->open = true;
|
|
break;
|
|
case IICHID_PS_OFF:
|
|
sc->open = false;
|
|
break;
|
|
case IICHID_PS_NULL:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch (how_suspend) {
|
|
case IICHID_PS_ON:
|
|
sc->suspend = false;
|
|
break;
|
|
case IICHID_PS_OFF:
|
|
sc->suspend = true;
|
|
break;
|
|
case IICHID_PS_NULL:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
power_on = sc->open & !sc->suspend;
|
|
|
|
if (power_on != sc->power_on) {
|
|
error = iichid_set_power(sc,
|
|
power_on ? I2C_HID_POWER_ON : I2C_HID_POWER_OFF);
|
|
|
|
sc->power_on = power_on;
|
|
#ifdef IICHID_SAMPLING
|
|
if (sc->sampling_rate_slow >= 0 && sc->intr_handler != NULL) {
|
|
if (power_on) {
|
|
iichid_setup_callout(sc);
|
|
iichid_reset_callout(sc);
|
|
} else
|
|
iichid_teardown_callout(sc);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
iicbus_release_bus(parent, sc->dev);
|
|
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
iichid_setup_interrupt(struct iichid_softc *sc)
|
|
{
|
|
sc->irq_cookie = 0;
|
|
|
|
int error = bus_setup_intr(sc->dev, sc->irq_res,
|
|
INTR_TYPE_TTY|INTR_MPSAFE, NULL, iichid_intr, sc, &sc->irq_cookie);
|
|
if (error != 0)
|
|
DPRINTF(sc, "Could not setup interrupt handler\n");
|
|
else
|
|
DPRINTF(sc, "successfully setup interrupt\n");
|
|
|
|
return (error);
|
|
}
|
|
|
|
static void
|
|
iichid_teardown_interrupt(struct iichid_softc *sc)
|
|
{
|
|
if (sc->irq_cookie)
|
|
bus_teardown_intr(sc->dev, sc->irq_res, sc->irq_cookie);
|
|
|
|
sc->irq_cookie = 0;
|
|
}
|
|
|
|
#ifdef IICHID_SAMPLING
|
|
static int
|
|
iichid_setup_callout(struct iichid_softc *sc)
|
|
{
|
|
|
|
if (sc->sampling_rate_slow < 0) {
|
|
DPRINTF(sc, "sampling_rate is below 0, can't setup callout\n");
|
|
return (EINVAL);
|
|
}
|
|
|
|
sc->callout_setup = true;
|
|
DPRINTF(sc, "successfully setup callout\n");
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
iichid_reset_callout(struct iichid_softc *sc)
|
|
{
|
|
|
|
if (sc->sampling_rate_slow <= 0) {
|
|
DPRINTF(sc, "sampling_rate is below or equal to 0, "
|
|
"can't reset callout\n");
|
|
return (EINVAL);
|
|
}
|
|
|
|
if (!sc->callout_setup)
|
|
return (EINVAL);
|
|
|
|
/* Start with slow sampling. */
|
|
sc->missing_samples = sc->sampling_hysteresis;
|
|
taskqueue_enqueue(sc->taskqueue, &sc->event_task);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
iichid_teardown_callout(struct iichid_softc *sc)
|
|
{
|
|
|
|
sc->callout_setup = false;
|
|
taskqueue_cancel_timeout(sc->taskqueue, &sc->periodic_task, NULL);
|
|
DPRINTF(sc, "tore callout down\n");
|
|
}
|
|
|
|
static int
|
|
iichid_sysctl_sampling_rate_handler(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
struct iichid_softc *sc;
|
|
device_t parent;
|
|
int error, oldval, value;
|
|
|
|
sc = arg1;
|
|
|
|
value = sc->sampling_rate_slow;
|
|
error = sysctl_handle_int(oidp, &value, 0, req);
|
|
|
|
if (error != 0 || req->newptr == NULL ||
|
|
value == sc->sampling_rate_slow)
|
|
return (error);
|
|
|
|
/* Can't switch to interrupt mode if it is not supported. */
|
|
if (sc->irq_res == NULL && value < 0)
|
|
return (EINVAL);
|
|
|
|
parent = device_get_parent(sc->dev);
|
|
error = iicbus_request_bus(parent, sc->dev, IIC_WAIT);
|
|
if (error != 0)
|
|
return (iic2errno(error));
|
|
|
|
oldval = sc->sampling_rate_slow;
|
|
sc->sampling_rate_slow = value;
|
|
|
|
if (oldval < 0 && value >= 0) {
|
|
iichid_teardown_interrupt(sc);
|
|
if (sc->power_on)
|
|
iichid_setup_callout(sc);
|
|
} else if (oldval >= 0 && value < 0) {
|
|
if (sc->power_on)
|
|
iichid_teardown_callout(sc);
|
|
iichid_setup_interrupt(sc);
|
|
}
|
|
|
|
if (sc->power_on && value > 0)
|
|
iichid_reset_callout(sc);
|
|
|
|
iicbus_release_bus(parent, sc->dev);
|
|
|
|
DPRINTF(sc, "new sampling_rate value: %d\n", value);
|
|
|
|
return (0);
|
|
}
|
|
#endif /* IICHID_SAMPLING */
|
|
|
|
static void
|
|
iichid_intr_setup(device_t dev, device_t child __unused, hid_intr_t intr,
|
|
void *context, struct hid_rdesc_info *rdesc)
|
|
{
|
|
struct iichid_softc *sc;
|
|
|
|
if (intr == NULL)
|
|
return;
|
|
|
|
sc = device_get_softc(dev);
|
|
/*
|
|
* Do not rely on wMaxInputLength, as some devices may set it to
|
|
* a wrong length. Find the longest input report in report descriptor.
|
|
*/
|
|
rdesc->rdsize = rdesc->isize;
|
|
/* Write and get/set_report sizes are limited by I2C-HID protocol. */
|
|
rdesc->grsize = rdesc->srsize = IICHID_SIZE_MAX;
|
|
rdesc->wrsize = IICHID_SIZE_MAX;
|
|
|
|
sc->intr_handler = intr;
|
|
sc->intr_ctx = context;
|
|
sc->intr_buf = malloc(rdesc->rdsize, M_DEVBUF, M_WAITOK | M_ZERO);
|
|
sc->intr_bufsize = rdesc->rdsize;
|
|
#ifdef IICHID_SAMPLING
|
|
taskqueue_start_threads(&sc->taskqueue, 1, PI_TTY,
|
|
"%s taskq", device_get_nameunit(sc->dev));
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
iichid_intr_unsetup(device_t dev, device_t child __unused)
|
|
{
|
|
struct iichid_softc *sc;
|
|
|
|
sc = device_get_softc(dev);
|
|
#ifdef IICHID_SAMPLING
|
|
taskqueue_drain_all(sc->taskqueue);
|
|
#endif
|
|
free(sc->intr_buf, M_DEVBUF);
|
|
}
|
|
|
|
static int
|
|
iichid_intr_start(device_t dev, device_t child __unused)
|
|
{
|
|
struct iichid_softc *sc;
|
|
|
|
sc = device_get_softc(dev);
|
|
DPRINTF(sc, "iichid device open\n");
|
|
iichid_set_power_state(sc, IICHID_PS_ON, IICHID_PS_NULL);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
iichid_intr_stop(device_t dev, device_t child __unused)
|
|
{
|
|
struct iichid_softc *sc;
|
|
|
|
sc = device_get_softc(dev);
|
|
DPRINTF(sc, "iichid device close\n");
|
|
/*
|
|
* 8.2 - The HOST determines that there are no active applications
|
|
* that are currently using the specific HID DEVICE. The HOST
|
|
* is recommended to issue a HIPO command to the DEVICE to force
|
|
* the DEVICE in to a lower power state.
|
|
*/
|
|
iichid_set_power_state(sc, IICHID_PS_OFF, IICHID_PS_NULL);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
iichid_intr_poll(device_t dev, device_t child __unused)
|
|
{
|
|
struct iichid_softc *sc;
|
|
iichid_size_t actual;
|
|
int error;
|
|
|
|
sc = device_get_softc(dev);
|
|
error = iichid_cmd_read(sc, sc->intr_buf, sc->intr_bufsize, &actual);
|
|
if (error == 0 && actual != 0)
|
|
sc->intr_handler(sc->intr_ctx, sc->intr_buf, actual);
|
|
}
|
|
|
|
/*
|
|
* HID interface
|
|
*/
|
|
static int
|
|
iichid_get_rdesc(device_t dev, device_t child __unused, void *buf,
|
|
hid_size_t len)
|
|
{
|
|
struct iichid_softc *sc;
|
|
int error;
|
|
|
|
sc = device_get_softc(dev);
|
|
error = iichid_cmd_get_report_desc(sc, buf, len);
|
|
if (error)
|
|
DPRINTF(sc, "failed to fetch report descriptor: %d\n", error);
|
|
|
|
return (iic2errno(error));
|
|
}
|
|
|
|
static int
|
|
iichid_read(device_t dev, device_t child __unused, void *buf,
|
|
hid_size_t maxlen, hid_size_t *actlen)
|
|
{
|
|
struct iichid_softc *sc;
|
|
device_t parent;
|
|
int error;
|
|
|
|
if (maxlen > IICHID_SIZE_MAX)
|
|
return (EMSGSIZE);
|
|
sc = device_get_softc(dev);
|
|
parent = device_get_parent(sc->dev);
|
|
error = iicbus_request_bus(parent, sc->dev, IIC_WAIT);
|
|
if (error == 0) {
|
|
error = iichid_cmd_read(sc, buf, maxlen, actlen);
|
|
iicbus_release_bus(parent, sc->dev);
|
|
}
|
|
return (iic2errno(error));
|
|
}
|
|
|
|
static int
|
|
iichid_write(device_t dev, device_t child __unused, const void *buf,
|
|
hid_size_t len)
|
|
{
|
|
struct iichid_softc *sc;
|
|
|
|
if (len > IICHID_SIZE_MAX)
|
|
return (EMSGSIZE);
|
|
sc = device_get_softc(dev);
|
|
return (iic2errno(iichid_cmd_write(sc, buf, len)));
|
|
}
|
|
|
|
static int
|
|
iichid_get_report(device_t dev, device_t child __unused, void *buf,
|
|
hid_size_t maxlen, hid_size_t *actlen, uint8_t type, uint8_t id)
|
|
{
|
|
struct iichid_softc *sc;
|
|
|
|
if (maxlen > IICHID_SIZE_MAX)
|
|
return (EMSGSIZE);
|
|
sc = device_get_softc(dev);
|
|
return (iic2errno(
|
|
iichid_cmd_get_report(sc, buf, maxlen, actlen, type, id)));
|
|
}
|
|
|
|
static int
|
|
iichid_set_report(device_t dev, device_t child __unused, const void *buf,
|
|
hid_size_t len, uint8_t type, uint8_t id)
|
|
{
|
|
struct iichid_softc *sc;
|
|
|
|
if (len > IICHID_SIZE_MAX)
|
|
return (EMSGSIZE);
|
|
sc = device_get_softc(dev);
|
|
return (iic2errno(iichid_cmd_set_report(sc, buf, len, type, id)));
|
|
}
|
|
|
|
static int
|
|
iichid_set_idle(device_t dev, device_t child __unused,
|
|
uint16_t duration, uint8_t id)
|
|
{
|
|
return (ENOTSUP);
|
|
}
|
|
|
|
static int
|
|
iichid_set_protocol(device_t dev, device_t child __unused, uint16_t protocol)
|
|
{
|
|
return (ENOTSUP);
|
|
}
|
|
|
|
static int
|
|
iichid_ioctl(device_t dev, device_t child __unused, unsigned long cmd,
|
|
uintptr_t data)
|
|
{
|
|
int error;
|
|
|
|
switch (cmd) {
|
|
case I2CRDWR:
|
|
error = iic2errno(iicbus_transfer(dev,
|
|
((struct iic_rdwr_data *)data)->msgs,
|
|
((struct iic_rdwr_data *)data)->nmsgs));
|
|
break;
|
|
default:
|
|
error = EINVAL;
|
|
}
|
|
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
iichid_fill_device_info(struct i2c_hid_desc *desc, ACPI_HANDLE handle,
|
|
struct hid_device_info *hw)
|
|
{
|
|
ACPI_DEVICE_INFO *device_info;
|
|
|
|
hw->idBus = BUS_I2C;
|
|
hw->idVendor = le16toh(desc->wVendorID);
|
|
hw->idProduct = le16toh(desc->wProductID);
|
|
hw->idVersion = le16toh(desc->wVersionID);
|
|
|
|
/* get ACPI HID. It is a base part of the device name. */
|
|
if (ACPI_FAILURE(AcpiGetObjectInfo(handle, &device_info)))
|
|
return (ENXIO);
|
|
|
|
if (device_info->Valid & ACPI_VALID_HID)
|
|
strlcpy(hw->idPnP, device_info->HardwareId.String,
|
|
HID_PNP_ID_SIZE);
|
|
snprintf(hw->name, sizeof(hw->name), "%s:%02lX %04X:%04X",
|
|
(device_info->Valid & ACPI_VALID_HID) ?
|
|
device_info->HardwareId.String : "Unknown",
|
|
(device_info->Valid & ACPI_VALID_UID) ?
|
|
strtoul(device_info->UniqueId.String, NULL, 10) : 0UL,
|
|
le16toh(desc->wVendorID), le16toh(desc->wProductID));
|
|
|
|
AcpiOsFree(device_info);
|
|
|
|
strlcpy(hw->serial, "", sizeof(hw->serial));
|
|
hw->rdescsize = le16toh(desc->wReportDescLength);
|
|
if (desc->wOutputRegister == 0 || desc->wMaxOutputLength == 0)
|
|
hid_add_dynamic_quirk(hw, HQ_NOWRITE);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
iichid_probe(device_t dev)
|
|
{
|
|
struct iichid_softc *sc;
|
|
ACPI_HANDLE handle;
|
|
char buf[80];
|
|
uint16_t config_reg;
|
|
int error, reg;
|
|
|
|
sc = device_get_softc(dev);
|
|
sc->dev = dev;
|
|
if (sc->probe_done)
|
|
goto done;
|
|
|
|
sc->probe_done = true;
|
|
sc->probe_result = ENXIO;
|
|
|
|
if (acpi_disabled("iichid"))
|
|
return (ENXIO);
|
|
|
|
sc->addr = iicbus_get_addr(dev) << 1;
|
|
if (sc->addr == 0)
|
|
return (ENXIO);
|
|
|
|
handle = acpi_get_handle(dev);
|
|
if (handle == NULL)
|
|
return (ENXIO);
|
|
|
|
reg = acpi_is_iichid(handle);
|
|
if (reg == IICHID_REG_NONE)
|
|
return (ENXIO);
|
|
|
|
if (reg == IICHID_REG_ACPI) {
|
|
if (ACPI_FAILURE(iichid_get_config_reg(handle, &config_reg)))
|
|
return (ENXIO);
|
|
} else
|
|
config_reg = (uint16_t)reg;
|
|
|
|
DPRINTF(sc, " IICbus addr : 0x%02X\n", sc->addr >> 1);
|
|
DPRINTF(sc, " HID descriptor reg: 0x%02X\n", config_reg);
|
|
|
|
error = iichid_cmd_get_hid_desc(sc, config_reg, &sc->desc);
|
|
if (error) {
|
|
DPRINTF(sc, "could not retrieve HID descriptor from the "
|
|
"device: %d\n", error);
|
|
return (ENXIO);
|
|
}
|
|
|
|
if (le16toh(sc->desc.wHIDDescLength) != 30 ||
|
|
le16toh(sc->desc.bcdVersion) != 0x100) {
|
|
DPRINTF(sc, "HID descriptor is broken\n");
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Setup hid_device_info so we can figure out quirks for the device. */
|
|
if (iichid_fill_device_info(&sc->desc, handle, &sc->hw) != 0) {
|
|
DPRINTF(sc, "error evaluating AcpiGetObjectInfo\n");
|
|
return (ENXIO);
|
|
}
|
|
|
|
if (hid_test_quirk(&sc->hw, HQ_HID_IGNORE))
|
|
return (ENXIO);
|
|
|
|
sc->probe_result = BUS_PROBE_DEFAULT;
|
|
done:
|
|
if (sc->probe_result <= BUS_PROBE_SPECIFIC) {
|
|
snprintf(buf, sizeof(buf), "%s I2C HID device", sc->hw.name);
|
|
device_set_desc_copy(dev, buf);
|
|
}
|
|
return (sc->probe_result);
|
|
}
|
|
|
|
static int
|
|
iichid_attach(device_t dev)
|
|
{
|
|
struct iichid_softc *sc;
|
|
device_t child;
|
|
int error;
|
|
|
|
sc = device_get_softc(dev);
|
|
error = iichid_set_power(sc, I2C_HID_POWER_ON);
|
|
if (error) {
|
|
device_printf(dev, "failed to power on: %d\n", error);
|
|
return (ENXIO);
|
|
}
|
|
/*
|
|
* Windows driver sleeps for 1ms between the SET_POWER and RESET
|
|
* commands. So we too as some devices may depend on this.
|
|
*/
|
|
pause("iichid", (hz + 999) / 1000);
|
|
|
|
error = iichid_reset(sc);
|
|
if (error) {
|
|
device_printf(dev, "failed to reset hardware: %d\n", error);
|
|
error = ENXIO;
|
|
goto done;
|
|
}
|
|
|
|
sc->power_on = true;
|
|
|
|
TASK_INIT(&sc->suspend_task, 0, iichid_suspend_task, sc);
|
|
#ifdef IICHID_SAMPLING
|
|
TASK_INIT(&sc->event_task, 0, iichid_event_task, sc);
|
|
/* taskqueue_create can't fail with M_WAITOK mflag passed. */
|
|
sc->taskqueue = taskqueue_create("iichid_tq", M_WAITOK | M_ZERO,
|
|
taskqueue_thread_enqueue, &sc->taskqueue);
|
|
TIMEOUT_TASK_INIT(sc->taskqueue, &sc->periodic_task, 0,
|
|
iichid_event_task, sc);
|
|
|
|
sc->sampling_rate_slow = -1;
|
|
sc->sampling_rate_fast = IICHID_SAMPLING_RATE_FAST;
|
|
sc->sampling_hysteresis = IICHID_SAMPLING_HYSTERESIS;
|
|
#endif
|
|
|
|
sc->irq_rid = 0;
|
|
sc->irq_res = bus_alloc_resource_any(sc->dev, SYS_RES_IRQ,
|
|
&sc->irq_rid, RF_ACTIVE);
|
|
|
|
if (sc->irq_res != NULL) {
|
|
DPRINTF(sc, "allocated irq at %p and rid %d\n",
|
|
sc->irq_res, sc->irq_rid);
|
|
error = iichid_setup_interrupt(sc);
|
|
}
|
|
|
|
if (sc->irq_res == NULL || error != 0) {
|
|
#ifdef IICHID_SAMPLING
|
|
device_printf(sc->dev,
|
|
"Interrupt setup failed. Fallback to sampling\n");
|
|
sc->sampling_rate_slow = IICHID_SAMPLING_RATE_SLOW;
|
|
#else
|
|
device_printf(sc->dev, "Interrupt setup failed\n");
|
|
if (sc->irq_res != NULL)
|
|
bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid,
|
|
sc->irq_res);
|
|
error = ENXIO;
|
|
goto done;
|
|
#endif
|
|
}
|
|
|
|
#ifdef IICHID_SAMPLING
|
|
SYSCTL_ADD_PROC(device_get_sysctl_ctx(sc->dev),
|
|
SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)),
|
|
OID_AUTO, "sampling_rate_slow", CTLTYPE_INT | CTLFLAG_RWTUN,
|
|
sc, 0, iichid_sysctl_sampling_rate_handler, "I",
|
|
"idle sampling rate in num/second");
|
|
SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->dev),
|
|
SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)),
|
|
OID_AUTO, "sampling_rate_fast", CTLFLAG_RWTUN,
|
|
&sc->sampling_rate_fast, 0,
|
|
"active sampling rate in num/second");
|
|
SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->dev),
|
|
SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)),
|
|
OID_AUTO, "sampling_hysteresis", CTLFLAG_RWTUN,
|
|
&sc->sampling_hysteresis, 0,
|
|
"number of missing samples before enabling of slow mode");
|
|
hid_add_dynamic_quirk(&sc->hw, HQ_IICHID_SAMPLING);
|
|
|
|
if (sc->sampling_rate_slow >= 0) {
|
|
pause("iichid", (hz + 999) / 1000);
|
|
(void)iichid_cmd_read(sc, NULL, 0, NULL);
|
|
}
|
|
#endif /* IICHID_SAMPLING */
|
|
|
|
child = device_add_child(dev, "hidbus", -1);
|
|
if (child == NULL) {
|
|
device_printf(sc->dev, "Could not add I2C device\n");
|
|
iichid_detach(dev);
|
|
error = ENOMEM;
|
|
goto done;
|
|
}
|
|
|
|
device_set_ivars(child, &sc->hw);
|
|
error = bus_generic_attach(dev);
|
|
if (error) {
|
|
device_printf(dev, "failed to attach child: error %d\n", error);
|
|
iichid_detach(dev);
|
|
}
|
|
done:
|
|
(void)iichid_set_power(sc, I2C_HID_POWER_OFF);
|
|
sc->power_on = false;
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
iichid_detach(device_t dev)
|
|
{
|
|
struct iichid_softc *sc;
|
|
int error;
|
|
|
|
sc = device_get_softc(dev);
|
|
error = device_delete_children(dev);
|
|
if (error)
|
|
return (error);
|
|
iichid_teardown_interrupt(sc);
|
|
if (sc->irq_res != NULL)
|
|
bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid,
|
|
sc->irq_res);
|
|
#ifdef IICHID_SAMPLING
|
|
if (sc->taskqueue != NULL)
|
|
taskqueue_free(sc->taskqueue);
|
|
sc->taskqueue = NULL;
|
|
#endif
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
iichid_suspend_task(void *context, int pending)
|
|
{
|
|
struct iichid_softc *sc = context;
|
|
|
|
iichid_teardown_interrupt(sc);
|
|
}
|
|
|
|
static int
|
|
iichid_suspend(device_t dev)
|
|
{
|
|
struct iichid_softc *sc;
|
|
int error;
|
|
|
|
sc = device_get_softc(dev);
|
|
(void)bus_generic_suspend(dev);
|
|
/*
|
|
* 8.2 - The HOST is going into a deep power optimized state and wishes
|
|
* to put all the devices into a low power state also. The HOST
|
|
* is recommended to issue a HIPO command to the DEVICE to force
|
|
* the DEVICE in to a lower power state.
|
|
*/
|
|
DPRINTF(sc, "Suspend called, setting device to power_state 1\n");
|
|
error = iichid_set_power_state(sc, IICHID_PS_NULL, IICHID_PS_OFF);
|
|
if (error != 0)
|
|
DPRINTF(sc, "Could not set power_state, error: %d\n", error);
|
|
else
|
|
DPRINTF(sc, "Successfully set power_state\n");
|
|
|
|
#ifdef IICHID_SAMPLING
|
|
if (sc->sampling_rate_slow < 0)
|
|
#endif
|
|
{
|
|
/*
|
|
* bus_teardown_intr can not be executed right here as it wants
|
|
* to run on certain CPU to interacts with LAPIC while suspend
|
|
* thread is bound to CPU0. So run it from taskqueue context.
|
|
*/
|
|
#ifdef IICHID_SAMPLING
|
|
#define suspend_thread sc->taskqueue
|
|
#else
|
|
#define suspend_thread taskqueue_thread
|
|
#endif
|
|
taskqueue_enqueue(suspend_thread, &sc->suspend_task);
|
|
taskqueue_drain(suspend_thread, &sc->suspend_task);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
iichid_resume(device_t dev)
|
|
{
|
|
struct iichid_softc *sc;
|
|
int error;
|
|
|
|
sc = device_get_softc(dev);
|
|
#ifdef IICHID_SAMPLING
|
|
if (sc->sampling_rate_slow < 0)
|
|
#endif
|
|
iichid_setup_interrupt(sc);
|
|
|
|
DPRINTF(sc, "Resume called, setting device to power_state 0\n");
|
|
error = iichid_set_power_state(sc, IICHID_PS_NULL, IICHID_PS_ON);
|
|
if (error != 0)
|
|
DPRINTF(sc, "Could not set power_state, error: %d\n", error);
|
|
else
|
|
DPRINTF(sc, "Successfully set power_state\n");
|
|
(void)bus_generic_resume(dev);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static device_method_t iichid_methods[] = {
|
|
DEVMETHOD(device_probe, iichid_probe),
|
|
DEVMETHOD(device_attach, iichid_attach),
|
|
DEVMETHOD(device_detach, iichid_detach),
|
|
DEVMETHOD(device_suspend, iichid_suspend),
|
|
DEVMETHOD(device_resume, iichid_resume),
|
|
|
|
DEVMETHOD(hid_intr_setup, iichid_intr_setup),
|
|
DEVMETHOD(hid_intr_unsetup, iichid_intr_unsetup),
|
|
DEVMETHOD(hid_intr_start, iichid_intr_start),
|
|
DEVMETHOD(hid_intr_stop, iichid_intr_stop),
|
|
DEVMETHOD(hid_intr_poll, iichid_intr_poll),
|
|
|
|
/* HID interface */
|
|
DEVMETHOD(hid_get_rdesc, iichid_get_rdesc),
|
|
DEVMETHOD(hid_read, iichid_read),
|
|
DEVMETHOD(hid_write, iichid_write),
|
|
DEVMETHOD(hid_get_report, iichid_get_report),
|
|
DEVMETHOD(hid_set_report, iichid_set_report),
|
|
DEVMETHOD(hid_set_idle, iichid_set_idle),
|
|
DEVMETHOD(hid_set_protocol, iichid_set_protocol),
|
|
DEVMETHOD(hid_ioctl, iichid_ioctl),
|
|
|
|
DEVMETHOD_END
|
|
};
|
|
|
|
static driver_t iichid_driver = {
|
|
.name = "iichid",
|
|
.methods = iichid_methods,
|
|
.size = sizeof(struct iichid_softc),
|
|
};
|
|
|
|
DRIVER_MODULE(iichid, iicbus, iichid_driver, NULL, NULL);
|
|
MODULE_DEPEND(iichid, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);
|
|
MODULE_DEPEND(iichid, acpi, 1, 1, 1);
|
|
MODULE_DEPEND(iichid, hid, 1, 1, 1);
|
|
MODULE_DEPEND(iichid, hidbus, 1, 1, 1);
|
|
MODULE_VERSION(iichid, 1);
|
|
IICBUS_ACPI_PNP_INFO(iichid_ids);
|