xref: /freebsd-src/sys/dev/iicbus/iichid.c (revision 3ddaf8200bc90b1410755ebac7b5c979ea90a2f6)
1b1f1b07fSVladimir Kondratyev /*-
2b1f1b07fSVladimir Kondratyev  * Copyright (c) 2018-2019 Marc Priggemeyer <marc.priggemeyer@gmail.com>
3b1f1b07fSVladimir Kondratyev  * Copyright (c) 2019-2020 Vladimir Kondratyev <wulf@FreeBSD.org>
4b1f1b07fSVladimir Kondratyev  *
5b1f1b07fSVladimir Kondratyev  * Redistribution and use in source and binary forms, with or without
6b1f1b07fSVladimir Kondratyev  * modification, are permitted provided that the following conditions
7b1f1b07fSVladimir Kondratyev  * are met:
8b1f1b07fSVladimir Kondratyev  * 1. Redistributions of source code must retain the above copyright
9b1f1b07fSVladimir Kondratyev  *    notice, this list of conditions and the following disclaimer.
10b1f1b07fSVladimir Kondratyev  * 2. Redistributions in binary form must reproduce the above copyright
11b1f1b07fSVladimir Kondratyev  *    notice, this list of conditions and the following disclaimer in the
12b1f1b07fSVladimir Kondratyev  *    documentation and/or other materials provided with the distribution.
13b1f1b07fSVladimir Kondratyev  *
14b1f1b07fSVladimir Kondratyev  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15b1f1b07fSVladimir Kondratyev  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16b1f1b07fSVladimir Kondratyev  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17b1f1b07fSVladimir Kondratyev  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18b1f1b07fSVladimir Kondratyev  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19b1f1b07fSVladimir Kondratyev  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20b1f1b07fSVladimir Kondratyev  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21b1f1b07fSVladimir Kondratyev  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22b1f1b07fSVladimir Kondratyev  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23b1f1b07fSVladimir Kondratyev  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24b1f1b07fSVladimir Kondratyev  * SUCH DAMAGE.
25b1f1b07fSVladimir Kondratyev  */
26b1f1b07fSVladimir Kondratyev 
27b1f1b07fSVladimir Kondratyev /*
28b1f1b07fSVladimir Kondratyev  * I2C HID transport backend.
29b1f1b07fSVladimir Kondratyev  */
30b1f1b07fSVladimir Kondratyev 
31b1f1b07fSVladimir Kondratyev #include <sys/cdefs.h>
32b1f1b07fSVladimir Kondratyev #include "opt_hid.h"
33b1f1b07fSVladimir Kondratyev 
34b1f1b07fSVladimir Kondratyev #include <sys/param.h>
35b1f1b07fSVladimir Kondratyev #include <sys/bus.h>
36b1f1b07fSVladimir Kondratyev #include <sys/callout.h>
37b1f1b07fSVladimir Kondratyev #include <sys/endian.h>
38b1f1b07fSVladimir Kondratyev #include <sys/kernel.h>
39b1f1b07fSVladimir Kondratyev #include <sys/lock.h>
40b1f1b07fSVladimir Kondratyev #include <sys/malloc.h>
41b1f1b07fSVladimir Kondratyev #include <sys/module.h>
42b1f1b07fSVladimir Kondratyev #include <sys/rman.h>
43b1f1b07fSVladimir Kondratyev #include <sys/sysctl.h>
44b1f1b07fSVladimir Kondratyev #include <sys/systm.h>
45b1f1b07fSVladimir Kondratyev #include <sys/taskqueue.h>
46b1f1b07fSVladimir Kondratyev 
47b1f1b07fSVladimir Kondratyev #include <machine/resource.h>
48b1f1b07fSVladimir Kondratyev 
49b1f1b07fSVladimir Kondratyev #include <contrib/dev/acpica/include/acpi.h>
50b1f1b07fSVladimir Kondratyev #include <contrib/dev/acpica/include/accommon.h>
51b1f1b07fSVladimir Kondratyev #include <dev/acpica/acpivar.h>
52b1f1b07fSVladimir Kondratyev 
53b1f1b07fSVladimir Kondratyev #include <dev/evdev/input.h>
54b1f1b07fSVladimir Kondratyev 
55b1f1b07fSVladimir Kondratyev #include <dev/hid/hid.h>
56b1f1b07fSVladimir Kondratyev #include <dev/hid/hidquirk.h>
57b1f1b07fSVladimir Kondratyev 
58b1f1b07fSVladimir Kondratyev #include <dev/iicbus/iic.h>
59b1f1b07fSVladimir Kondratyev #include <dev/iicbus/iicbus.h>
60b1f1b07fSVladimir Kondratyev #include <dev/iicbus/iiconf.h>
61b1f1b07fSVladimir Kondratyev 
62b1f1b07fSVladimir Kondratyev #include "hid_if.h"
63b1f1b07fSVladimir Kondratyev 
64b1f1b07fSVladimir Kondratyev #ifdef IICHID_DEBUG
65b1f1b07fSVladimir Kondratyev static int iichid_debug = 0;
66b1f1b07fSVladimir Kondratyev 
67b1f1b07fSVladimir Kondratyev static SYSCTL_NODE(_hw, OID_AUTO, iichid, CTLFLAG_RW, 0, "I2C HID");
68b1f1b07fSVladimir Kondratyev SYSCTL_INT(_hw_iichid, OID_AUTO, debug, CTLFLAG_RWTUN,
69b1f1b07fSVladimir Kondratyev     &iichid_debug, 1, "Debug level");
70b1f1b07fSVladimir Kondratyev 
71b1f1b07fSVladimir Kondratyev #define	DPRINTFN(sc, n, ...) do {			\
72b1f1b07fSVladimir Kondratyev 	if (iichid_debug >= (n))			\
73b1f1b07fSVladimir Kondratyev 		device_printf((sc)->dev, __VA_ARGS__);	\
74b1f1b07fSVladimir Kondratyev } while (0)
75b1f1b07fSVladimir Kondratyev #define	DPRINTF(sc, ...)	DPRINTFN(sc, 1, __VA_ARGS__)
76b1f1b07fSVladimir Kondratyev #else
77b1f1b07fSVladimir Kondratyev #define	DPRINTFN(...)		do {} while (0)
78b1f1b07fSVladimir Kondratyev #define	DPRINTF(...)		do {} while (0)
79b1f1b07fSVladimir Kondratyev #endif
80b1f1b07fSVladimir Kondratyev 
81b1f1b07fSVladimir Kondratyev typedef	hid_size_t	iichid_size_t;
82b1f1b07fSVladimir Kondratyev #define	IICHID_SIZE_MAX	(UINT16_MAX - 2)
83b1f1b07fSVladimir Kondratyev 
84b1f1b07fSVladimir Kondratyev /* 7.2 */
85b1f1b07fSVladimir Kondratyev enum {
86b1f1b07fSVladimir Kondratyev 	I2C_HID_CMD_DESCR	= 0x0,
87b1f1b07fSVladimir Kondratyev 	I2C_HID_CMD_RESET	= 0x1,
88b1f1b07fSVladimir Kondratyev 	I2C_HID_CMD_GET_REPORT	= 0x2,
89b1f1b07fSVladimir Kondratyev 	I2C_HID_CMD_SET_REPORT	= 0x3,
90b1f1b07fSVladimir Kondratyev 	I2C_HID_CMD_GET_IDLE	= 0x4,
91b1f1b07fSVladimir Kondratyev 	I2C_HID_CMD_SET_IDLE	= 0x5,
92b1f1b07fSVladimir Kondratyev 	I2C_HID_CMD_GET_PROTO	= 0x6,
93b1f1b07fSVladimir Kondratyev 	I2C_HID_CMD_SET_PROTO	= 0x7,
94b1f1b07fSVladimir Kondratyev 	I2C_HID_CMD_SET_POWER	= 0x8,
95b1f1b07fSVladimir Kondratyev };
96b1f1b07fSVladimir Kondratyev 
97b1f1b07fSVladimir Kondratyev #define	I2C_HID_POWER_ON		0x0
98b1f1b07fSVladimir Kondratyev #define	I2C_HID_POWER_OFF		0x1
99b1f1b07fSVladimir Kondratyev 
100b1f1b07fSVladimir Kondratyev /*
101b1f1b07fSVladimir Kondratyev  * Since interrupt resource acquisition is not always possible (in case of GPIO
102b1f1b07fSVladimir Kondratyev  * interrupts) iichid now supports a sampling_mode.
103b1f1b07fSVladimir Kondratyev  * Set dev.iichid.<unit>.sampling_rate_slow to a value greater then 0
104b1f1b07fSVladimir Kondratyev  * to activate sampling. A value of 0 is possible but will not reset the
105b1f1b07fSVladimir Kondratyev  * callout and, thereby, disable further report requests. Do not set the
106b1f1b07fSVladimir Kondratyev  * sampling_rate_fast value too high as it may result in periodical lags of
107b1f1b07fSVladimir Kondratyev  * cursor motion.
108b1f1b07fSVladimir Kondratyev  */
10968e457dfSAlexander Motin #define	IICHID_SAMPLING_RATE_FAST	80
110b1f1b07fSVladimir Kondratyev #define	IICHID_SAMPLING_RATE_SLOW	10
11168e457dfSAlexander Motin #define	IICHID_SAMPLING_HYSTERESIS	16	/* ~ 2x fast / slow */
112b1f1b07fSVladimir Kondratyev 
113b1f1b07fSVladimir Kondratyev /* 5.1.1 - HID Descriptor Format */
114b1f1b07fSVladimir Kondratyev struct i2c_hid_desc {
115b1f1b07fSVladimir Kondratyev 	uint16_t wHIDDescLength;
116b1f1b07fSVladimir Kondratyev 	uint16_t bcdVersion;
117b1f1b07fSVladimir Kondratyev 	uint16_t wReportDescLength;
118b1f1b07fSVladimir Kondratyev 	uint16_t wReportDescRegister;
119b1f1b07fSVladimir Kondratyev 	uint16_t wInputRegister;
120b1f1b07fSVladimir Kondratyev 	uint16_t wMaxInputLength;
121b1f1b07fSVladimir Kondratyev 	uint16_t wOutputRegister;
122b1f1b07fSVladimir Kondratyev 	uint16_t wMaxOutputLength;
123b1f1b07fSVladimir Kondratyev 	uint16_t wCommandRegister;
124b1f1b07fSVladimir Kondratyev 	uint16_t wDataRegister;
125b1f1b07fSVladimir Kondratyev 	uint16_t wVendorID;
126b1f1b07fSVladimir Kondratyev 	uint16_t wProductID;
127b1f1b07fSVladimir Kondratyev 	uint16_t wVersionID;
128b1f1b07fSVladimir Kondratyev 	uint32_t reserved;
129b1f1b07fSVladimir Kondratyev } __packed;
130b1f1b07fSVladimir Kondratyev 
13134e051c4SVladimir Kondratyev #define	IICHID_REG_NONE	-1
13234e051c4SVladimir Kondratyev #define	IICHID_REG_ACPI	(UINT16_MAX + 1)
13334e051c4SVladimir Kondratyev #define	IICHID_REG_ELAN	0x0001
13434e051c4SVladimir Kondratyev 
13534e051c4SVladimir Kondratyev static const struct iichid_id {
13634e051c4SVladimir Kondratyev 	char *id;
13734e051c4SVladimir Kondratyev 	int reg;
13834e051c4SVladimir Kondratyev } iichid_ids[] = {
13934e051c4SVladimir Kondratyev 	{ "ELAN0000",	IICHID_REG_ELAN },
14034e051c4SVladimir Kondratyev 	{ "PNP0C50",	IICHID_REG_ACPI },
14134e051c4SVladimir Kondratyev 	{ "ACPI0C50",	IICHID_REG_ACPI },
14234e051c4SVladimir Kondratyev 	{ NULL,		0 },
143b1f1b07fSVladimir Kondratyev };
144b1f1b07fSVladimir Kondratyev 
145b1f1b07fSVladimir Kondratyev enum iichid_powerstate_how {
146b1f1b07fSVladimir Kondratyev 	IICHID_PS_NULL,
147b1f1b07fSVladimir Kondratyev 	IICHID_PS_ON,
148b1f1b07fSVladimir Kondratyev 	IICHID_PS_OFF,
149b1f1b07fSVladimir Kondratyev };
150b1f1b07fSVladimir Kondratyev 
151b1f1b07fSVladimir Kondratyev /*
152b1f1b07fSVladimir Kondratyev  * Locking: no internal locks are used. To serialize access to shared members,
153b1f1b07fSVladimir Kondratyev  * external iicbus lock should be taken.  That allows to make locking greatly
154b1f1b07fSVladimir Kondratyev  * simple at the cost of running front interrupt handlers with locked bus.
155b1f1b07fSVladimir Kondratyev  */
156b1f1b07fSVladimir Kondratyev struct iichid_softc {
157b1f1b07fSVladimir Kondratyev 	device_t		dev;
158b1f1b07fSVladimir Kondratyev 
159b1f1b07fSVladimir Kondratyev 	bool			probe_done;
160b1f1b07fSVladimir Kondratyev 	int			probe_result;
161b1f1b07fSVladimir Kondratyev 
162b1f1b07fSVladimir Kondratyev 	struct hid_device_info	hw;
163b1f1b07fSVladimir Kondratyev 	uint16_t		addr;	/* Shifted left by 1 */
164b1f1b07fSVladimir Kondratyev 	struct i2c_hid_desc	desc;
165b1f1b07fSVladimir Kondratyev 
166b1f1b07fSVladimir Kondratyev 	hid_intr_t		*intr_handler;
167b1f1b07fSVladimir Kondratyev 	void			*intr_ctx;
168b1f1b07fSVladimir Kondratyev 	uint8_t			*intr_buf;
169b1f1b07fSVladimir Kondratyev 	iichid_size_t		intr_bufsize;
170b1f1b07fSVladimir Kondratyev 
171b1f1b07fSVladimir Kondratyev 	int			irq_rid;
172b1f1b07fSVladimir Kondratyev 	struct resource		*irq_res;
173b1f1b07fSVladimir Kondratyev 	void			*irq_cookie;
174b1f1b07fSVladimir Kondratyev 
175b1f1b07fSVladimir Kondratyev #ifdef IICHID_SAMPLING
176b1f1b07fSVladimir Kondratyev 	int			sampling_rate_slow;	/* iicbus lock */
177b1f1b07fSVladimir Kondratyev 	int			sampling_rate_fast;
178b1f1b07fSVladimir Kondratyev 	int			sampling_hysteresis;
179b1f1b07fSVladimir Kondratyev 	int			missing_samples;	/* iicbus lock */
1808c86b981SAlexander Motin 	int			dup_samples;		/* iicbus lock */
1818c86b981SAlexander Motin 	iichid_size_t		dup_size;		/* iicbus lock */
182b1f1b07fSVladimir Kondratyev 	bool			callout_setup;		/* iicbus lock */
1838c86b981SAlexander Motin 	uint8_t			*dup_buf;
184b1f1b07fSVladimir Kondratyev 	struct taskqueue	*taskqueue;
185a8f80c0cSAlexander Motin 	struct timeout_task	sampling_task;		/* iicbus lock */
186b1f1b07fSVladimir Kondratyev #endif
187b1f1b07fSVladimir Kondratyev 
18882626fefSVladimir Kondratyev 	struct task		suspend_task;
189b1f1b07fSVladimir Kondratyev 	bool			open;			/* iicbus lock */
190b1f1b07fSVladimir Kondratyev 	bool			suspend;		/* iicbus lock */
191b1f1b07fSVladimir Kondratyev 	bool			power_on;		/* iicbus lock */
192b1f1b07fSVladimir Kondratyev };
193b1f1b07fSVladimir Kondratyev 
194b1f1b07fSVladimir Kondratyev static device_probe_t	iichid_probe;
195b1f1b07fSVladimir Kondratyev static device_attach_t	iichid_attach;
196b1f1b07fSVladimir Kondratyev static device_detach_t	iichid_detach;
197b1f1b07fSVladimir Kondratyev static device_resume_t	iichid_resume;
198b1f1b07fSVladimir Kondratyev static device_suspend_t	iichid_suspend;
199b1f1b07fSVladimir Kondratyev 
20082626fefSVladimir Kondratyev static void	iichid_suspend_task(void *, int);
20182626fefSVladimir Kondratyev 
202b1f1b07fSVladimir Kondratyev #ifdef IICHID_SAMPLING
203b1f1b07fSVladimir Kondratyev static int	iichid_setup_callout(struct iichid_softc *);
204b1f1b07fSVladimir Kondratyev static int	iichid_reset_callout(struct iichid_softc *);
205b1f1b07fSVladimir Kondratyev static void	iichid_teardown_callout(struct iichid_softc *);
206b1f1b07fSVladimir Kondratyev #endif
207b1f1b07fSVladimir Kondratyev 
20834e051c4SVladimir Kondratyev static inline int
209b1f1b07fSVladimir Kondratyev acpi_is_iichid(ACPI_HANDLE handle)
210b1f1b07fSVladimir Kondratyev {
21134e051c4SVladimir Kondratyev 	const struct iichid_id *ids;
212b1f1b07fSVladimir Kondratyev 	UINT32	sta;
21334e051c4SVladimir Kondratyev 	int reg;
214b1f1b07fSVladimir Kondratyev 
21534e051c4SVladimir Kondratyev 	for (ids = iichid_ids; ids->id != NULL; ids++) {
21634e051c4SVladimir Kondratyev 		if (acpi_MatchHid(handle, ids->id)) {
21734e051c4SVladimir Kondratyev 			reg = ids->reg;
218b1f1b07fSVladimir Kondratyev 			break;
219b1f1b07fSVladimir Kondratyev 		}
22034e051c4SVladimir Kondratyev 	}
22134e051c4SVladimir Kondratyev 	if (ids->id == NULL)
22234e051c4SVladimir Kondratyev 		return (IICHID_REG_NONE);
223b1f1b07fSVladimir Kondratyev 
224b1f1b07fSVladimir Kondratyev 	/*
225b1f1b07fSVladimir Kondratyev 	 * If no _STA method or if it failed, then assume that
226b1f1b07fSVladimir Kondratyev 	 * the device is present.
227b1f1b07fSVladimir Kondratyev 	 */
228b1f1b07fSVladimir Kondratyev 	if (ACPI_FAILURE(acpi_GetInteger(handle, "_STA", &sta)) ||
229b1f1b07fSVladimir Kondratyev 	    ACPI_DEVICE_PRESENT(sta))
23034e051c4SVladimir Kondratyev 		return (reg);
231b1f1b07fSVladimir Kondratyev 
23234e051c4SVladimir Kondratyev 	return (IICHID_REG_NONE);
233b1f1b07fSVladimir Kondratyev }
234b1f1b07fSVladimir Kondratyev 
235b1f1b07fSVladimir Kondratyev static ACPI_STATUS
236b1f1b07fSVladimir Kondratyev iichid_get_config_reg(ACPI_HANDLE handle, uint16_t *config_reg)
237b1f1b07fSVladimir Kondratyev {
238b1f1b07fSVladimir Kondratyev 	ACPI_OBJECT *result;
239b1f1b07fSVladimir Kondratyev 	ACPI_BUFFER acpi_buf;
240b1f1b07fSVladimir Kondratyev 	ACPI_STATUS status;
241b1f1b07fSVladimir Kondratyev 
242b1f1b07fSVladimir Kondratyev 	/*
243b1f1b07fSVladimir Kondratyev 	 * function (_DSM) to be evaluated to retrieve the address of
244b1f1b07fSVladimir Kondratyev 	 * the configuration register of the HID device.
245b1f1b07fSVladimir Kondratyev 	 */
246b1f1b07fSVladimir Kondratyev 	/* 3cdff6f7-4267-4555-ad05-b30a3d8938de */
247b1f1b07fSVladimir Kondratyev 	static uint8_t dsm_guid[ACPI_UUID_LENGTH] = {
248b1f1b07fSVladimir Kondratyev 		0xF7, 0xF6, 0xDF, 0x3C, 0x67, 0x42, 0x55, 0x45,
249b1f1b07fSVladimir Kondratyev 		0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE,
250b1f1b07fSVladimir Kondratyev 	};
251b1f1b07fSVladimir Kondratyev 
252b1f1b07fSVladimir Kondratyev 	status = acpi_EvaluateDSMTyped(handle, dsm_guid, 1, 1, NULL, &acpi_buf,
253b1f1b07fSVladimir Kondratyev 	    ACPI_TYPE_INTEGER);
254b1f1b07fSVladimir Kondratyev 	if (ACPI_FAILURE(status)) {
255b1f1b07fSVladimir Kondratyev 		printf("%s: error evaluating _DSM\n", __func__);
256b1f1b07fSVladimir Kondratyev 		return (status);
257b1f1b07fSVladimir Kondratyev 	}
258b1f1b07fSVladimir Kondratyev 	result = (ACPI_OBJECT *) acpi_buf.Pointer;
259b1f1b07fSVladimir Kondratyev 	*config_reg = result->Integer.Value & 0xFFFF;
260b1f1b07fSVladimir Kondratyev 
261b1f1b07fSVladimir Kondratyev 	AcpiOsFree(result);
262b1f1b07fSVladimir Kondratyev 	return (status);
263b1f1b07fSVladimir Kondratyev }
264b1f1b07fSVladimir Kondratyev 
265b1f1b07fSVladimir Kondratyev static int
266b1f1b07fSVladimir Kondratyev iichid_cmd_read(struct iichid_softc* sc, void *buf, iichid_size_t maxlen,
267b1f1b07fSVladimir Kondratyev     iichid_size_t *actual_len)
268b1f1b07fSVladimir Kondratyev {
269b1f1b07fSVladimir Kondratyev 	/*
270b1f1b07fSVladimir Kondratyev 	 * 6.1.3 - Retrieval of Input Reports
271b1f1b07fSVladimir Kondratyev 	 * DEVICE returns the length (2 Bytes) and the entire Input Report.
272b1f1b07fSVladimir Kondratyev 	 */
273b1f1b07fSVladimir Kondratyev 	uint8_t actbuf[2] = { 0, 0 };
274b1f1b07fSVladimir Kondratyev 	/* Read actual input report length. */
275b1f1b07fSVladimir Kondratyev 	struct iic_msg msgs[] = {
276b1f1b07fSVladimir Kondratyev 	    { sc->addr, IIC_M_RD | IIC_M_NOSTOP, sizeof(actbuf), actbuf },
277b1f1b07fSVladimir Kondratyev 	};
278b1f1b07fSVladimir Kondratyev 	uint16_t actlen;
279b1f1b07fSVladimir Kondratyev 	int error;
280b1f1b07fSVladimir Kondratyev 
281b1f1b07fSVladimir Kondratyev 	error = iicbus_transfer(sc->dev, msgs, nitems(msgs));
282b1f1b07fSVladimir Kondratyev 	if (error != 0)
283b1f1b07fSVladimir Kondratyev 		return (error);
284b1f1b07fSVladimir Kondratyev 
285b1f1b07fSVladimir Kondratyev 	actlen = actbuf[0] | actbuf[1] << 8;
286b1f1b07fSVladimir Kondratyev 	if (actlen <= 2 || actlen == 0xFFFF || maxlen == 0) {
287b1f1b07fSVladimir Kondratyev 		/* Read and discard 1 byte to send I2C STOP condition. */
288b1f1b07fSVladimir Kondratyev 		msgs[0] = (struct iic_msg)
289b1f1b07fSVladimir Kondratyev 		    { sc->addr, IIC_M_RD | IIC_M_NOSTART, 1, actbuf };
290b1f1b07fSVladimir Kondratyev 		actlen = 0;
291b1f1b07fSVladimir Kondratyev 	} else {
292b1f1b07fSVladimir Kondratyev 		actlen -= 2;
293b1f1b07fSVladimir Kondratyev 		if (actlen > maxlen) {
294b1f1b07fSVladimir Kondratyev 			DPRINTF(sc, "input report too big. requested=%d "
295b1f1b07fSVladimir Kondratyev 			    "received=%d\n", maxlen, actlen);
296b1f1b07fSVladimir Kondratyev 			actlen = maxlen;
297b1f1b07fSVladimir Kondratyev 		}
298b1f1b07fSVladimir Kondratyev 		/* Read input report itself. */
299b1f1b07fSVladimir Kondratyev 		msgs[0] = (struct iic_msg)
300b1f1b07fSVladimir Kondratyev 		    { sc->addr, IIC_M_RD | IIC_M_NOSTART, actlen, buf };
301b1f1b07fSVladimir Kondratyev 	}
302b1f1b07fSVladimir Kondratyev 
303b1f1b07fSVladimir Kondratyev 	error = iicbus_transfer(sc->dev, msgs, 1);
304b1f1b07fSVladimir Kondratyev 	if (error == 0 && actual_len != NULL)
305b1f1b07fSVladimir Kondratyev 		*actual_len = actlen;
306b1f1b07fSVladimir Kondratyev 
307b1f1b07fSVladimir Kondratyev 	DPRINTFN(sc, 5,
308b1f1b07fSVladimir Kondratyev 	    "%*D - %*D\n", 2, actbuf, " ", msgs[0].len, msgs[0].buf, " ");
309b1f1b07fSVladimir Kondratyev 
310b1f1b07fSVladimir Kondratyev 	return (error);
311b1f1b07fSVladimir Kondratyev }
312b1f1b07fSVladimir Kondratyev 
313b1f1b07fSVladimir Kondratyev static int
314b1f1b07fSVladimir Kondratyev iichid_cmd_write(struct iichid_softc *sc, const void *buf, iichid_size_t len)
315b1f1b07fSVladimir Kondratyev {
316b1f1b07fSVladimir Kondratyev 	/* 6.2.3 - Sending Output Reports. */
317b1f1b07fSVladimir Kondratyev 	uint8_t *cmdreg = (uint8_t *)&sc->desc.wOutputRegister;
318b1f1b07fSVladimir Kondratyev 	uint16_t replen = 2 + len;
319b1f1b07fSVladimir Kondratyev 	uint8_t cmd[4] = { cmdreg[0], cmdreg[1], replen & 0xFF, replen >> 8 };
320b1f1b07fSVladimir Kondratyev 	struct iic_msg msgs[] = {
321b1f1b07fSVladimir Kondratyev 	    {sc->addr, IIC_M_WR | IIC_M_NOSTOP, sizeof(cmd), cmd},
322b1f1b07fSVladimir Kondratyev 	    {sc->addr, IIC_M_WR | IIC_M_NOSTART, len, __DECONST(void *, buf)},
323b1f1b07fSVladimir Kondratyev 	};
324b1f1b07fSVladimir Kondratyev 
325b1f1b07fSVladimir Kondratyev 	if (le16toh(sc->desc.wMaxOutputLength) == 0)
326b1f1b07fSVladimir Kondratyev 		return (IIC_ENOTSUPP);
327b1f1b07fSVladimir Kondratyev 	if (len < 2)
328b1f1b07fSVladimir Kondratyev 		return (IIC_ENOTSUPP);
329b1f1b07fSVladimir Kondratyev 
330b1f1b07fSVladimir Kondratyev 	DPRINTF(sc, "HID command I2C_HID_CMD_WRITE (len %d): "
331b1f1b07fSVladimir Kondratyev 	    "%*D\n", len, len, buf, " ");
332b1f1b07fSVladimir Kondratyev 
333b1f1b07fSVladimir Kondratyev 	return (iicbus_transfer(sc->dev, msgs, nitems(msgs)));
334b1f1b07fSVladimir Kondratyev }
335b1f1b07fSVladimir Kondratyev 
336b1f1b07fSVladimir Kondratyev static int
337b1f1b07fSVladimir Kondratyev iichid_cmd_get_hid_desc(struct iichid_softc *sc, uint16_t config_reg,
338b1f1b07fSVladimir Kondratyev     struct i2c_hid_desc *hid_desc)
339b1f1b07fSVladimir Kondratyev {
340b1f1b07fSVladimir Kondratyev 	/*
341b1f1b07fSVladimir Kondratyev 	 * 5.2.2 - HID Descriptor Retrieval
342b1f1b07fSVladimir Kondratyev 	 * register is passed from the controller.
343b1f1b07fSVladimir Kondratyev 	 */
344b1f1b07fSVladimir Kondratyev 	uint16_t cmd = htole16(config_reg);
345b1f1b07fSVladimir Kondratyev 	struct iic_msg msgs[] = {
346b1f1b07fSVladimir Kondratyev 	    { sc->addr, IIC_M_WR | IIC_M_NOSTOP, 2, (uint8_t *)&cmd },
347b1f1b07fSVladimir Kondratyev 	    { sc->addr, IIC_M_RD, sizeof(*hid_desc), (uint8_t *)hid_desc },
348b1f1b07fSVladimir Kondratyev 	};
349b1f1b07fSVladimir Kondratyev 	int error;
350b1f1b07fSVladimir Kondratyev 
351b1f1b07fSVladimir Kondratyev 	DPRINTF(sc, "HID command I2C_HID_CMD_DESCR at 0x%x\n", config_reg);
352b1f1b07fSVladimir Kondratyev 
353b1f1b07fSVladimir Kondratyev 	error = iicbus_transfer(sc->dev, msgs, nitems(msgs));
354b1f1b07fSVladimir Kondratyev 	if (error != 0)
355b1f1b07fSVladimir Kondratyev 		return (error);
356b1f1b07fSVladimir Kondratyev 
357b1f1b07fSVladimir Kondratyev 	DPRINTF(sc, "HID descriptor: %*D\n",
358b1f1b07fSVladimir Kondratyev 	    (int)sizeof(struct i2c_hid_desc), hid_desc, " ");
359b1f1b07fSVladimir Kondratyev 
360b1f1b07fSVladimir Kondratyev 	return (0);
361b1f1b07fSVladimir Kondratyev }
362b1f1b07fSVladimir Kondratyev 
363b1f1b07fSVladimir Kondratyev static int
364b1f1b07fSVladimir Kondratyev iichid_set_power(struct iichid_softc *sc, uint8_t param)
365b1f1b07fSVladimir Kondratyev {
366b1f1b07fSVladimir Kondratyev 	uint8_t *cmdreg = (uint8_t *)&sc->desc.wCommandRegister;
367b1f1b07fSVladimir Kondratyev 	uint8_t cmd[] = { cmdreg[0], cmdreg[1], param, I2C_HID_CMD_SET_POWER };
368b1f1b07fSVladimir Kondratyev 	struct iic_msg msgs[] = {
369b1f1b07fSVladimir Kondratyev 	    { sc->addr, IIC_M_WR, sizeof(cmd), cmd },
370b1f1b07fSVladimir Kondratyev 	};
371b1f1b07fSVladimir Kondratyev 
372b1f1b07fSVladimir Kondratyev 	DPRINTF(sc, "HID command I2C_HID_CMD_SET_POWER(%d)\n", param);
373b1f1b07fSVladimir Kondratyev 
374b1f1b07fSVladimir Kondratyev 	return (iicbus_transfer(sc->dev, msgs, nitems(msgs)));
375b1f1b07fSVladimir Kondratyev }
376b1f1b07fSVladimir Kondratyev 
377b1f1b07fSVladimir Kondratyev static int
378b1f1b07fSVladimir Kondratyev iichid_reset(struct iichid_softc *sc)
379b1f1b07fSVladimir Kondratyev {
380b1f1b07fSVladimir Kondratyev 	uint8_t *cmdreg = (uint8_t *)&sc->desc.wCommandRegister;
381b1f1b07fSVladimir Kondratyev 	uint8_t cmd[] = { cmdreg[0], cmdreg[1], 0, I2C_HID_CMD_RESET };
382b1f1b07fSVladimir Kondratyev 	struct iic_msg msgs[] = {
383b1f1b07fSVladimir Kondratyev 	    { sc->addr, IIC_M_WR, sizeof(cmd), cmd },
384b1f1b07fSVladimir Kondratyev 	};
385b1f1b07fSVladimir Kondratyev 
386b1f1b07fSVladimir Kondratyev 	DPRINTF(sc, "HID command I2C_HID_CMD_RESET\n");
387b1f1b07fSVladimir Kondratyev 
388b1f1b07fSVladimir Kondratyev 	return (iicbus_transfer(sc->dev, msgs, nitems(msgs)));
389b1f1b07fSVladimir Kondratyev }
390b1f1b07fSVladimir Kondratyev 
391b1f1b07fSVladimir Kondratyev static int
392b1f1b07fSVladimir Kondratyev iichid_cmd_get_report_desc(struct iichid_softc* sc, void *buf,
393b1f1b07fSVladimir Kondratyev     iichid_size_t len)
394b1f1b07fSVladimir Kondratyev {
395b1f1b07fSVladimir Kondratyev 	uint16_t cmd = sc->desc.wReportDescRegister;
396b1f1b07fSVladimir Kondratyev 	struct iic_msg msgs[] = {
397b1f1b07fSVladimir Kondratyev 	    { sc->addr, IIC_M_WR | IIC_M_NOSTOP, 2, (uint8_t *)&cmd },
398b1f1b07fSVladimir Kondratyev 	    { sc->addr, IIC_M_RD, len, buf },
399b1f1b07fSVladimir Kondratyev 	};
400b1f1b07fSVladimir Kondratyev 	int error;
401b1f1b07fSVladimir Kondratyev 
402b1f1b07fSVladimir Kondratyev 	DPRINTF(sc, "HID command I2C_HID_REPORT_DESCR at 0x%x with size %d\n",
403b1f1b07fSVladimir Kondratyev 	    le16toh(cmd), len);
404b1f1b07fSVladimir Kondratyev 
405b1f1b07fSVladimir Kondratyev 	error = iicbus_transfer(sc->dev, msgs, nitems(msgs));
406b1f1b07fSVladimir Kondratyev 	if (error != 0)
407b1f1b07fSVladimir Kondratyev 		return (error);
408b1f1b07fSVladimir Kondratyev 
409b1f1b07fSVladimir Kondratyev 	DPRINTF(sc, "HID report descriptor: %*D\n", len, buf, " ");
410b1f1b07fSVladimir Kondratyev 
411b1f1b07fSVladimir Kondratyev 	return (0);
412b1f1b07fSVladimir Kondratyev }
413b1f1b07fSVladimir Kondratyev 
414b1f1b07fSVladimir Kondratyev static int
415b1f1b07fSVladimir Kondratyev iichid_cmd_get_report(struct iichid_softc* sc, void *buf, iichid_size_t maxlen,
416b1f1b07fSVladimir Kondratyev     iichid_size_t *actual_len, uint8_t type, uint8_t id)
417b1f1b07fSVladimir Kondratyev {
418b1f1b07fSVladimir Kondratyev 	/*
419b1f1b07fSVladimir Kondratyev 	 * 7.2.2.4 - "The protocol is optimized for Report < 15.  If a
420b1f1b07fSVladimir Kondratyev 	 * report ID >= 15 is necessary, then the Report ID in the Low Byte
421b1f1b07fSVladimir Kondratyev 	 * must be set to 1111 and a Third Byte is appended to the protocol.
422b1f1b07fSVladimir Kondratyev 	 * This Third Byte contains the entire/actual report ID."
423b1f1b07fSVladimir Kondratyev 	 */
424b1f1b07fSVladimir Kondratyev 	uint8_t *dtareg = (uint8_t *)&sc->desc.wDataRegister;
425b1f1b07fSVladimir Kondratyev 	uint8_t *cmdreg = (uint8_t *)&sc->desc.wCommandRegister;
426b1f1b07fSVladimir Kondratyev 	uint8_t cmd[] =	{   /*________|______id>=15_____|______id<15______*/
427b1f1b07fSVladimir Kondratyev 						    cmdreg[0]		   ,
428b1f1b07fSVladimir Kondratyev 						    cmdreg[1]		   ,
429b1f1b07fSVladimir Kondratyev 			    (id >= 15 ? 15 | (type << 4): id | (type << 4)),
430b1f1b07fSVladimir Kondratyev 					      I2C_HID_CMD_GET_REPORT	   ,
431b1f1b07fSVladimir Kondratyev 			    (id >= 15 ?		id	:    dtareg[0]	  ),
432b1f1b07fSVladimir Kondratyev 			    (id >= 15 ?	   dtareg[0]	:    dtareg[1]	  ),
433b1f1b07fSVladimir Kondratyev 			    (id >= 15 ?    dtareg[1]	:	0	  ),
434b1f1b07fSVladimir Kondratyev 			};
435b1f1b07fSVladimir Kondratyev 	int cmdlen    =	    (id >= 15 ?		7	:	6	  );
436b1f1b07fSVladimir Kondratyev 	uint8_t actbuf[2] = { 0, 0 };
437b1f1b07fSVladimir Kondratyev 	uint16_t actlen;
438b1f1b07fSVladimir Kondratyev 	int d, error;
439b1f1b07fSVladimir Kondratyev 	struct iic_msg msgs[] = {
440b1f1b07fSVladimir Kondratyev 	    { sc->addr, IIC_M_WR | IIC_M_NOSTOP, cmdlen, cmd },
441b1f1b07fSVladimir Kondratyev 	    { sc->addr, IIC_M_RD | IIC_M_NOSTOP, 2, actbuf },
442b1f1b07fSVladimir Kondratyev 	    { sc->addr, IIC_M_RD | IIC_M_NOSTART, maxlen, buf },
443b1f1b07fSVladimir Kondratyev 	};
444b1f1b07fSVladimir Kondratyev 
445b1f1b07fSVladimir Kondratyev 	if (maxlen == 0)
446b1f1b07fSVladimir Kondratyev 		return (EINVAL);
447b1f1b07fSVladimir Kondratyev 
448b1f1b07fSVladimir Kondratyev 	DPRINTF(sc, "HID command I2C_HID_CMD_GET_REPORT %d "
449b1f1b07fSVladimir Kondratyev 	    "(type %d, len %d)\n", id, type, maxlen);
450b1f1b07fSVladimir Kondratyev 
451b1f1b07fSVladimir Kondratyev 	/*
452b1f1b07fSVladimir Kondratyev 	 * 7.2.2.2 - Response will be a 2-byte length value, the report
453b1f1b07fSVladimir Kondratyev 	 * id (1 byte, if defined in Report Descriptor), and then the report.
454b1f1b07fSVladimir Kondratyev 	 */
455b1f1b07fSVladimir Kondratyev 	error = iicbus_transfer(sc->dev, msgs, nitems(msgs));
456b1f1b07fSVladimir Kondratyev 	if (error != 0)
457b1f1b07fSVladimir Kondratyev 		return (error);
458b1f1b07fSVladimir Kondratyev 
459b1f1b07fSVladimir Kondratyev 	actlen = actbuf[0] | actbuf[1] << 8;
460b1f1b07fSVladimir Kondratyev 	if (actlen != maxlen + 2)
461b1f1b07fSVladimir Kondratyev 		DPRINTF(sc, "response size %d != expected length %d\n",
462b1f1b07fSVladimir Kondratyev 		    actlen, maxlen + 2);
463b1f1b07fSVladimir Kondratyev 
464b1f1b07fSVladimir Kondratyev 	if (actlen <= 2 || actlen == 0xFFFF)
465b1f1b07fSVladimir Kondratyev 		return (ENOMSG);
466b1f1b07fSVladimir Kondratyev 
467b1f1b07fSVladimir Kondratyev 	d = id != 0 ? *(uint8_t *)buf : 0;
468b1f1b07fSVladimir Kondratyev 	if (d != id) {
469b1f1b07fSVladimir Kondratyev 		DPRINTF(sc, "response report id %d != %d\n", d, id);
470b1f1b07fSVladimir Kondratyev 		return (EBADMSG);
471b1f1b07fSVladimir Kondratyev 	}
472b1f1b07fSVladimir Kondratyev 
473b1f1b07fSVladimir Kondratyev 	actlen -= 2;
474b1f1b07fSVladimir Kondratyev 	if (actlen > maxlen)
475b1f1b07fSVladimir Kondratyev 		actlen = maxlen;
476b1f1b07fSVladimir Kondratyev 	if (actual_len != NULL)
477b1f1b07fSVladimir Kondratyev 		*actual_len = actlen;
478b1f1b07fSVladimir Kondratyev 
479b1f1b07fSVladimir Kondratyev 	DPRINTF(sc, "response: %*D %*D\n", 2, actbuf, " ", actlen, buf, " ");
480b1f1b07fSVladimir Kondratyev 
481b1f1b07fSVladimir Kondratyev 	return (0);
482b1f1b07fSVladimir Kondratyev }
483b1f1b07fSVladimir Kondratyev 
484b1f1b07fSVladimir Kondratyev static int
485b1f1b07fSVladimir Kondratyev iichid_cmd_set_report(struct iichid_softc* sc, const void *buf,
486b1f1b07fSVladimir Kondratyev     iichid_size_t len, uint8_t type, uint8_t id)
487b1f1b07fSVladimir Kondratyev {
488b1f1b07fSVladimir Kondratyev 	/*
489b1f1b07fSVladimir Kondratyev 	 * 7.2.2.4 - "The protocol is optimized for Report < 15.  If a
490b1f1b07fSVladimir Kondratyev 	 * report ID >= 15 is necessary, then the Report ID in the Low Byte
491b1f1b07fSVladimir Kondratyev 	 * must be set to 1111 and a Third Byte is appended to the protocol.
492b1f1b07fSVladimir Kondratyev 	 * This Third Byte contains the entire/actual report ID."
493b1f1b07fSVladimir Kondratyev 	 */
494b1f1b07fSVladimir Kondratyev 	uint8_t *dtareg = (uint8_t *)&sc->desc.wDataRegister;
495b1f1b07fSVladimir Kondratyev 	uint8_t *cmdreg = (uint8_t *)&sc->desc.wCommandRegister;
496b1f1b07fSVladimir Kondratyev 	uint16_t replen = 2 + len;
497b1f1b07fSVladimir Kondratyev 	uint8_t cmd[] =	{   /*________|______id>=15_____|______id<15______*/
498b1f1b07fSVladimir Kondratyev 						    cmdreg[0]		   ,
499b1f1b07fSVladimir Kondratyev 						    cmdreg[1]		   ,
500b1f1b07fSVladimir Kondratyev 			    (id >= 15 ? 15 | (type << 4): id | (type << 4)),
501b1f1b07fSVladimir Kondratyev 					      I2C_HID_CMD_SET_REPORT	   ,
502b1f1b07fSVladimir Kondratyev 			    (id >= 15 ?		id	:    dtareg[0]    ),
503b1f1b07fSVladimir Kondratyev 			    (id >= 15 ?    dtareg[0]	:    dtareg[1]    ),
504b1f1b07fSVladimir Kondratyev 			    (id >= 15 ?    dtareg[1]	:   replen & 0xff ),
505b1f1b07fSVladimir Kondratyev 			    (id >= 15 ?   replen & 0xff	:   replen >> 8   ),
506b1f1b07fSVladimir Kondratyev 			    (id >= 15 ?   replen >> 8	:	0	  ),
507b1f1b07fSVladimir Kondratyev 			};
508b1f1b07fSVladimir Kondratyev 	int cmdlen    =	    (id >= 15 ?		9	:	8	  );
509b1f1b07fSVladimir Kondratyev 	struct iic_msg msgs[] = {
510b1f1b07fSVladimir Kondratyev 	    {sc->addr, IIC_M_WR | IIC_M_NOSTOP, cmdlen, cmd},
511b1f1b07fSVladimir Kondratyev 	    {sc->addr, IIC_M_WR | IIC_M_NOSTART, len, __DECONST(void *, buf)},
512b1f1b07fSVladimir Kondratyev 	};
513b1f1b07fSVladimir Kondratyev 
514b1f1b07fSVladimir Kondratyev 	DPRINTF(sc, "HID command I2C_HID_CMD_SET_REPORT %d (type %d, len %d): "
515b1f1b07fSVladimir Kondratyev 	    "%*D\n", id, type, len, len, buf, " ");
516b1f1b07fSVladimir Kondratyev 
517b1f1b07fSVladimir Kondratyev 	return (iicbus_transfer(sc->dev, msgs, nitems(msgs)));
518b1f1b07fSVladimir Kondratyev }
519b1f1b07fSVladimir Kondratyev 
520b1f1b07fSVladimir Kondratyev #ifdef IICHID_SAMPLING
521b1f1b07fSVladimir Kondratyev static void
522a8f80c0cSAlexander Motin iichid_sampling_task(void *context, int pending)
523b1f1b07fSVladimir Kondratyev {
524b1f1b07fSVladimir Kondratyev 	struct iichid_softc *sc;
525b1f1b07fSVladimir Kondratyev 	device_t parent;
526b1f1b07fSVladimir Kondratyev 	iichid_size_t actual;
527b1f1b07fSVladimir Kondratyev 	bool bus_requested;
5288c86b981SAlexander Motin 	int error, rate;
529b1f1b07fSVladimir Kondratyev 
530b1f1b07fSVladimir Kondratyev 	sc = context;
531b1f1b07fSVladimir Kondratyev 	parent = device_get_parent(sc->dev);
532b1f1b07fSVladimir Kondratyev 
533b1f1b07fSVladimir Kondratyev 	bus_requested = false;
534b1f1b07fSVladimir Kondratyev 	if (iicbus_request_bus(parent, sc->dev, IIC_WAIT) != 0)
535b1f1b07fSVladimir Kondratyev 		goto rearm;
536b1f1b07fSVladimir Kondratyev 	bus_requested = true;
537b1f1b07fSVladimir Kondratyev 
538b1f1b07fSVladimir Kondratyev 	if (!sc->power_on)
539b1f1b07fSVladimir Kondratyev 		goto out;
540b1f1b07fSVladimir Kondratyev 
541b1f1b07fSVladimir Kondratyev 	error = iichid_cmd_read(sc, sc->intr_buf, sc->intr_bufsize, &actual);
542b1f1b07fSVladimir Kondratyev 	if (error == 0) {
543b1f1b07fSVladimir Kondratyev 		if (actual > 0) {
544b1f1b07fSVladimir Kondratyev 			sc->intr_handler(sc->intr_ctx, sc->intr_buf, actual);
545b1f1b07fSVladimir Kondratyev 			sc->missing_samples = 0;
5468c86b981SAlexander Motin 			if (sc->dup_size != actual ||
5478c86b981SAlexander Motin 			    memcmp(sc->dup_buf, sc->intr_buf, actual) != 0) {
5488c86b981SAlexander Motin 				sc->dup_size = actual;
5498c86b981SAlexander Motin 				memcpy(sc->dup_buf, sc->intr_buf, actual);
5508c86b981SAlexander Motin 				sc->dup_samples = 0;
551b1f1b07fSVladimir Kondratyev 			} else
5528c86b981SAlexander Motin 				++sc->dup_samples;
5538c86b981SAlexander Motin 		} else {
5548c86b981SAlexander Motin 			if (++sc->missing_samples == 1)
5558c86b981SAlexander Motin 				sc->intr_handler(sc->intr_ctx, sc->intr_buf, 0);
5568c86b981SAlexander Motin 			sc->dup_samples = 0;
5578c86b981SAlexander Motin 		}
558b1f1b07fSVladimir Kondratyev 	} else
5597fad3ed8SGordon Bergling 		DPRINTF(sc, "read error occurred: %d\n", error);
560b1f1b07fSVladimir Kondratyev 
561b1f1b07fSVladimir Kondratyev rearm:
562b1f1b07fSVladimir Kondratyev 	if (sc->callout_setup && sc->sampling_rate_slow > 0) {
5638c86b981SAlexander Motin 		if (sc->missing_samples >= sc->sampling_hysteresis ||
5648c86b981SAlexander Motin 		    sc->dup_samples >= sc->sampling_hysteresis)
5658c86b981SAlexander Motin 			rate = sc->sampling_rate_slow;
5668c86b981SAlexander Motin 		else
5678c86b981SAlexander Motin 			rate = sc->sampling_rate_fast;
568a8f80c0cSAlexander Motin 		taskqueue_enqueue_timeout_sbt(sc->taskqueue, &sc->sampling_task,
56968e457dfSAlexander Motin 		    SBT_1S / MAX(rate, 1), 0, C_PREL(2));
570b1f1b07fSVladimir Kondratyev 	}
571b1f1b07fSVladimir Kondratyev out:
572b1f1b07fSVladimir Kondratyev 	if (bus_requested)
573b1f1b07fSVladimir Kondratyev 		iicbus_release_bus(parent, sc->dev);
574b1f1b07fSVladimir Kondratyev }
575b1f1b07fSVladimir Kondratyev #endif	/* IICHID_SAMPLING */
576b1f1b07fSVladimir Kondratyev 
577b1f1b07fSVladimir Kondratyev static void
578b1f1b07fSVladimir Kondratyev iichid_intr(void *context)
579b1f1b07fSVladimir Kondratyev {
580b1f1b07fSVladimir Kondratyev 	struct iichid_softc *sc;
581b1f1b07fSVladimir Kondratyev 	device_t parent;
582b1f1b07fSVladimir Kondratyev 	iichid_size_t maxlen, actual;
583b1f1b07fSVladimir Kondratyev 	int error;
584b1f1b07fSVladimir Kondratyev 
585b1f1b07fSVladimir Kondratyev 	sc = context;
586b1f1b07fSVladimir Kondratyev 	parent = device_get_parent(sc->dev);
587b1f1b07fSVladimir Kondratyev 
588b1f1b07fSVladimir Kondratyev 	/*
589b1f1b07fSVladimir Kondratyev 	 * Designware(IG4) driver-specific hack.
590b1f1b07fSVladimir Kondratyev 	 * Requesting of an I2C bus with IIC_DONTWAIT parameter enables polled
591b1f1b07fSVladimir Kondratyev 	 * mode in the driver, making possible iicbus_transfer execution from
592b1f1b07fSVladimir Kondratyev 	 * interrupt handlers and callouts.
593b1f1b07fSVladimir Kondratyev 	 */
594b1f1b07fSVladimir Kondratyev 	if (iicbus_request_bus(parent, sc->dev, IIC_DONTWAIT) != 0)
595b1f1b07fSVladimir Kondratyev 		return;
596b1f1b07fSVladimir Kondratyev 
597b1f1b07fSVladimir Kondratyev 	/*
598b1f1b07fSVladimir Kondratyev 	 * Reading of input reports of I2C devices residing in SLEEP state is
599b1f1b07fSVladimir Kondratyev 	 * not allowed and often returns a garbage.  If a HOST needs to
600b1f1b07fSVladimir Kondratyev 	 * communicate with the DEVICE it MUST issue a SET POWER command
601b1f1b07fSVladimir Kondratyev 	 * (to ON) before any other command. As some hardware requires reads to
6025236888dSJ.R. Oldroyd 	 * acknowledge interrupts we fetch only length header and discard it.
603b1f1b07fSVladimir Kondratyev 	 */
604b1f1b07fSVladimir Kondratyev 	maxlen = sc->power_on ? sc->intr_bufsize : 0;
605b1f1b07fSVladimir Kondratyev 	error = iichid_cmd_read(sc, sc->intr_buf, maxlen, &actual);
606b1f1b07fSVladimir Kondratyev 	if (error == 0) {
607b1f1b07fSVladimir Kondratyev 		if (sc->power_on) {
608b1f1b07fSVladimir Kondratyev 			if (actual != 0)
609b1f1b07fSVladimir Kondratyev 				sc->intr_handler(sc->intr_ctx, sc->intr_buf,
610b1f1b07fSVladimir Kondratyev 				    actual);
611b1f1b07fSVladimir Kondratyev 			else
612b1f1b07fSVladimir Kondratyev 				DPRINTF(sc, "no data received\n");
613b1f1b07fSVladimir Kondratyev 		}
614b1f1b07fSVladimir Kondratyev 	} else
6157fad3ed8SGordon Bergling 		DPRINTF(sc, "read error occurred: %d\n", error);
616b1f1b07fSVladimir Kondratyev 
617b1f1b07fSVladimir Kondratyev 	iicbus_release_bus(parent, sc->dev);
618b1f1b07fSVladimir Kondratyev }
619b1f1b07fSVladimir Kondratyev 
620b1f1b07fSVladimir Kondratyev static int
621b1f1b07fSVladimir Kondratyev iichid_set_power_state(struct iichid_softc *sc,
622b1f1b07fSVladimir Kondratyev      enum iichid_powerstate_how how_open,
623b1f1b07fSVladimir Kondratyev      enum iichid_powerstate_how how_suspend)
624b1f1b07fSVladimir Kondratyev {
625b1f1b07fSVladimir Kondratyev 	device_t parent;
626b1f1b07fSVladimir Kondratyev 	int error;
627b1f1b07fSVladimir Kondratyev 	int how_request;
628b1f1b07fSVladimir Kondratyev 	bool power_on;
629b1f1b07fSVladimir Kondratyev 
630b1f1b07fSVladimir Kondratyev 	/*
631b1f1b07fSVladimir Kondratyev 	 * Request iicbus early as sc->suspend and sc->power_on
632b1f1b07fSVladimir Kondratyev 	 * are protected by iicbus internal lock.
633b1f1b07fSVladimir Kondratyev 	 */
634b1f1b07fSVladimir Kondratyev 	parent = device_get_parent(sc->dev);
635b1f1b07fSVladimir Kondratyev 	/* Allow to interrupt open()/close() handlers by SIGINT */
636b1f1b07fSVladimir Kondratyev 	how_request = how_open == IICHID_PS_NULL ? IIC_WAIT : IIC_INTRWAIT;
637b1f1b07fSVladimir Kondratyev 	error = iicbus_request_bus(parent, sc->dev, how_request);
638b1f1b07fSVladimir Kondratyev 	if (error != 0)
639b1f1b07fSVladimir Kondratyev 		return (error);
640b1f1b07fSVladimir Kondratyev 
641b1f1b07fSVladimir Kondratyev 	switch (how_open) {
642b1f1b07fSVladimir Kondratyev 	case IICHID_PS_ON:
643b1f1b07fSVladimir Kondratyev 		sc->open = true;
644b1f1b07fSVladimir Kondratyev 		break;
645b1f1b07fSVladimir Kondratyev 	case IICHID_PS_OFF:
646b1f1b07fSVladimir Kondratyev 		sc->open = false;
647b1f1b07fSVladimir Kondratyev 		break;
648b1f1b07fSVladimir Kondratyev 	case IICHID_PS_NULL:
649b1f1b07fSVladimir Kondratyev 	default:
650b1f1b07fSVladimir Kondratyev 		break;
651b1f1b07fSVladimir Kondratyev 	}
652b1f1b07fSVladimir Kondratyev 
653b1f1b07fSVladimir Kondratyev 	switch (how_suspend) {
654b1f1b07fSVladimir Kondratyev 	case IICHID_PS_ON:
655b1f1b07fSVladimir Kondratyev 		sc->suspend = false;
656b1f1b07fSVladimir Kondratyev 		break;
657b1f1b07fSVladimir Kondratyev 	case IICHID_PS_OFF:
658b1f1b07fSVladimir Kondratyev 		sc->suspend = true;
659b1f1b07fSVladimir Kondratyev 		break;
660b1f1b07fSVladimir Kondratyev 	case IICHID_PS_NULL:
661b1f1b07fSVladimir Kondratyev 	default:
662b1f1b07fSVladimir Kondratyev 		break;
663b1f1b07fSVladimir Kondratyev 	}
664b1f1b07fSVladimir Kondratyev 
665b1f1b07fSVladimir Kondratyev 	power_on = sc->open & !sc->suspend;
666b1f1b07fSVladimir Kondratyev 
667b1f1b07fSVladimir Kondratyev 	if (power_on != sc->power_on) {
668b1f1b07fSVladimir Kondratyev 		error = iichid_set_power(sc,
669b1f1b07fSVladimir Kondratyev 		    power_on ? I2C_HID_POWER_ON : I2C_HID_POWER_OFF);
670b1f1b07fSVladimir Kondratyev 
671b1f1b07fSVladimir Kondratyev 		sc->power_on = power_on;
672b1f1b07fSVladimir Kondratyev #ifdef IICHID_SAMPLING
673b1f1b07fSVladimir Kondratyev 		if (sc->sampling_rate_slow >= 0 && sc->intr_handler != NULL) {
674b1f1b07fSVladimir Kondratyev 			if (power_on) {
675b1f1b07fSVladimir Kondratyev 				iichid_setup_callout(sc);
676b1f1b07fSVladimir Kondratyev 				iichid_reset_callout(sc);
677b1f1b07fSVladimir Kondratyev 			} else
678b1f1b07fSVladimir Kondratyev 				iichid_teardown_callout(sc);
679b1f1b07fSVladimir Kondratyev 		}
680b1f1b07fSVladimir Kondratyev #endif
681b1f1b07fSVladimir Kondratyev 	}
682b1f1b07fSVladimir Kondratyev 
683b1f1b07fSVladimir Kondratyev 	iicbus_release_bus(parent, sc->dev);
684b1f1b07fSVladimir Kondratyev 
685b1f1b07fSVladimir Kondratyev 	return (error);
686b1f1b07fSVladimir Kondratyev }
687b1f1b07fSVladimir Kondratyev 
688b1f1b07fSVladimir Kondratyev static int
689b1f1b07fSVladimir Kondratyev iichid_setup_interrupt(struct iichid_softc *sc)
690b1f1b07fSVladimir Kondratyev {
691b1f1b07fSVladimir Kondratyev 	sc->irq_cookie = 0;
692b1f1b07fSVladimir Kondratyev 
693b1f1b07fSVladimir Kondratyev 	int error = bus_setup_intr(sc->dev, sc->irq_res,
694b1f1b07fSVladimir Kondratyev 	    INTR_TYPE_TTY|INTR_MPSAFE, NULL, iichid_intr, sc, &sc->irq_cookie);
695b1f1b07fSVladimir Kondratyev 	if (error != 0)
696b1f1b07fSVladimir Kondratyev 		DPRINTF(sc, "Could not setup interrupt handler\n");
697b1f1b07fSVladimir Kondratyev 	else
698b1f1b07fSVladimir Kondratyev 		DPRINTF(sc, "successfully setup interrupt\n");
699b1f1b07fSVladimir Kondratyev 
700b1f1b07fSVladimir Kondratyev 	return (error);
701b1f1b07fSVladimir Kondratyev }
702b1f1b07fSVladimir Kondratyev 
703b1f1b07fSVladimir Kondratyev static void
704b1f1b07fSVladimir Kondratyev iichid_teardown_interrupt(struct iichid_softc *sc)
705b1f1b07fSVladimir Kondratyev {
706b1f1b07fSVladimir Kondratyev 	if (sc->irq_cookie)
707b1f1b07fSVladimir Kondratyev 		bus_teardown_intr(sc->dev, sc->irq_res, sc->irq_cookie);
708b1f1b07fSVladimir Kondratyev 
709b1f1b07fSVladimir Kondratyev 	sc->irq_cookie = 0;
710b1f1b07fSVladimir Kondratyev }
711b1f1b07fSVladimir Kondratyev 
712b1f1b07fSVladimir Kondratyev #ifdef IICHID_SAMPLING
713b1f1b07fSVladimir Kondratyev static int
714b1f1b07fSVladimir Kondratyev iichid_setup_callout(struct iichid_softc *sc)
715b1f1b07fSVladimir Kondratyev {
716b1f1b07fSVladimir Kondratyev 
717b1f1b07fSVladimir Kondratyev 	if (sc->sampling_rate_slow < 0) {
718b1f1b07fSVladimir Kondratyev 		DPRINTF(sc, "sampling_rate is below 0, can't setup callout\n");
719b1f1b07fSVladimir Kondratyev 		return (EINVAL);
720b1f1b07fSVladimir Kondratyev 	}
721b1f1b07fSVladimir Kondratyev 
722b1f1b07fSVladimir Kondratyev 	sc->callout_setup = true;
723b1f1b07fSVladimir Kondratyev 	DPRINTF(sc, "successfully setup callout\n");
724b1f1b07fSVladimir Kondratyev 	return (0);
725b1f1b07fSVladimir Kondratyev }
726b1f1b07fSVladimir Kondratyev 
727b1f1b07fSVladimir Kondratyev static int
728b1f1b07fSVladimir Kondratyev iichid_reset_callout(struct iichid_softc *sc)
729b1f1b07fSVladimir Kondratyev {
730b1f1b07fSVladimir Kondratyev 
731b1f1b07fSVladimir Kondratyev 	if (sc->sampling_rate_slow <= 0) {
732b1f1b07fSVladimir Kondratyev 		DPRINTF(sc, "sampling_rate is below or equal to 0, "
733b1f1b07fSVladimir Kondratyev 		    "can't reset callout\n");
734b1f1b07fSVladimir Kondratyev 		return (EINVAL);
735b1f1b07fSVladimir Kondratyev 	}
736b1f1b07fSVladimir Kondratyev 
737b1f1b07fSVladimir Kondratyev 	if (!sc->callout_setup)
738b1f1b07fSVladimir Kondratyev 		return (EINVAL);
739b1f1b07fSVladimir Kondratyev 
740b1f1b07fSVladimir Kondratyev 	/* Start with slow sampling. */
741b1f1b07fSVladimir Kondratyev 	sc->missing_samples = sc->sampling_hysteresis;
7428c86b981SAlexander Motin 	sc->dup_samples = 0;
7438c86b981SAlexander Motin 	sc->dup_size = 0;
744a8f80c0cSAlexander Motin 	taskqueue_enqueue_timeout(sc->taskqueue, &sc->sampling_task, 0);
745b1f1b07fSVladimir Kondratyev 
746b1f1b07fSVladimir Kondratyev 	return (0);
747b1f1b07fSVladimir Kondratyev }
748b1f1b07fSVladimir Kondratyev 
749b1f1b07fSVladimir Kondratyev static void
750b1f1b07fSVladimir Kondratyev iichid_teardown_callout(struct iichid_softc *sc)
751b1f1b07fSVladimir Kondratyev {
752b1f1b07fSVladimir Kondratyev 
753b1f1b07fSVladimir Kondratyev 	sc->callout_setup = false;
754a8f80c0cSAlexander Motin 	taskqueue_cancel_timeout(sc->taskqueue, &sc->sampling_task, NULL);
755b1f1b07fSVladimir Kondratyev 	DPRINTF(sc, "tore callout down\n");
756b1f1b07fSVladimir Kondratyev }
757b1f1b07fSVladimir Kondratyev 
758b1f1b07fSVladimir Kondratyev static int
759b1f1b07fSVladimir Kondratyev iichid_sysctl_sampling_rate_handler(SYSCTL_HANDLER_ARGS)
760b1f1b07fSVladimir Kondratyev {
761b1f1b07fSVladimir Kondratyev 	struct iichid_softc *sc;
762b1f1b07fSVladimir Kondratyev 	device_t parent;
763b1f1b07fSVladimir Kondratyev 	int error, oldval, value;
764b1f1b07fSVladimir Kondratyev 
765b1f1b07fSVladimir Kondratyev 	sc = arg1;
766b1f1b07fSVladimir Kondratyev 
767b1f1b07fSVladimir Kondratyev 	value = sc->sampling_rate_slow;
768b1f1b07fSVladimir Kondratyev 	error = sysctl_handle_int(oidp, &value, 0, req);
769b1f1b07fSVladimir Kondratyev 
770b1f1b07fSVladimir Kondratyev 	if (error != 0 || req->newptr == NULL ||
771b1f1b07fSVladimir Kondratyev 	    value == sc->sampling_rate_slow)
772b1f1b07fSVladimir Kondratyev 		return (error);
773b1f1b07fSVladimir Kondratyev 
774b1f1b07fSVladimir Kondratyev 	/* Can't switch to interrupt mode if it is not supported. */
775b1f1b07fSVladimir Kondratyev 	if (sc->irq_res == NULL && value < 0)
776b1f1b07fSVladimir Kondratyev 		return (EINVAL);
777b1f1b07fSVladimir Kondratyev 
778b1f1b07fSVladimir Kondratyev 	parent = device_get_parent(sc->dev);
779b1f1b07fSVladimir Kondratyev 	error = iicbus_request_bus(parent, sc->dev, IIC_WAIT);
780b1f1b07fSVladimir Kondratyev 	if (error != 0)
781b1f1b07fSVladimir Kondratyev 		return (iic2errno(error));
782b1f1b07fSVladimir Kondratyev 
783b1f1b07fSVladimir Kondratyev 	oldval = sc->sampling_rate_slow;
784b1f1b07fSVladimir Kondratyev 	sc->sampling_rate_slow = value;
785b1f1b07fSVladimir Kondratyev 
786b1f1b07fSVladimir Kondratyev 	if (oldval < 0 && value >= 0) {
787b1f1b07fSVladimir Kondratyev 		iichid_teardown_interrupt(sc);
788b1f1b07fSVladimir Kondratyev 		if (sc->power_on)
789b1f1b07fSVladimir Kondratyev 			iichid_setup_callout(sc);
790b1f1b07fSVladimir Kondratyev 	} else if (oldval >= 0 && value < 0) {
791b1f1b07fSVladimir Kondratyev 		if (sc->power_on)
792b1f1b07fSVladimir Kondratyev 			iichid_teardown_callout(sc);
793b1f1b07fSVladimir Kondratyev 		iichid_setup_interrupt(sc);
794b1f1b07fSVladimir Kondratyev 	}
795b1f1b07fSVladimir Kondratyev 
796b1f1b07fSVladimir Kondratyev 	if (sc->power_on && value > 0)
797b1f1b07fSVladimir Kondratyev 		iichid_reset_callout(sc);
798b1f1b07fSVladimir Kondratyev 
799b1f1b07fSVladimir Kondratyev 	iicbus_release_bus(parent, sc->dev);
800b1f1b07fSVladimir Kondratyev 
801b1f1b07fSVladimir Kondratyev 	DPRINTF(sc, "new sampling_rate value: %d\n", value);
802b1f1b07fSVladimir Kondratyev 
803b1f1b07fSVladimir Kondratyev 	return (0);
804b1f1b07fSVladimir Kondratyev }
805b1f1b07fSVladimir Kondratyev #endif /* IICHID_SAMPLING */
806b1f1b07fSVladimir Kondratyev 
807b1f1b07fSVladimir Kondratyev static void
8084b171281SVladimir Kondratyev iichid_intr_setup(device_t dev, device_t child __unused, hid_intr_t intr,
8094b171281SVladimir Kondratyev     void *context, struct hid_rdesc_info *rdesc)
810b1f1b07fSVladimir Kondratyev {
811b1f1b07fSVladimir Kondratyev 	struct iichid_softc *sc;
812b1f1b07fSVladimir Kondratyev 
8139aa0e5afSVladimir Kondratyev 	if (intr == NULL)
8149aa0e5afSVladimir Kondratyev 		return;
8159aa0e5afSVladimir Kondratyev 
816b1f1b07fSVladimir Kondratyev 	sc = device_get_softc(dev);
817b1f1b07fSVladimir Kondratyev 	/*
818b1f1b07fSVladimir Kondratyev 	 * Do not rely on wMaxInputLength, as some devices may set it to
819b1f1b07fSVladimir Kondratyev 	 * a wrong length. Find the longest input report in report descriptor.
820b1f1b07fSVladimir Kondratyev 	 */
821b1f1b07fSVladimir Kondratyev 	rdesc->rdsize = rdesc->isize;
822b1f1b07fSVladimir Kondratyev 	/* Write and get/set_report sizes are limited by I2C-HID protocol. */
823b1f1b07fSVladimir Kondratyev 	rdesc->grsize = rdesc->srsize = IICHID_SIZE_MAX;
824b1f1b07fSVladimir Kondratyev 	rdesc->wrsize = IICHID_SIZE_MAX;
825b1f1b07fSVladimir Kondratyev 
826b1f1b07fSVladimir Kondratyev 	sc->intr_handler = intr;
827b1f1b07fSVladimir Kondratyev 	sc->intr_ctx = context;
828b1f1b07fSVladimir Kondratyev 	sc->intr_buf = malloc(rdesc->rdsize, M_DEVBUF, M_WAITOK | M_ZERO);
829b1f1b07fSVladimir Kondratyev 	sc->intr_bufsize = rdesc->rdsize;
830b1f1b07fSVladimir Kondratyev #ifdef IICHID_SAMPLING
8318c86b981SAlexander Motin 	sc->dup_buf = malloc(rdesc->rdsize, M_DEVBUF, M_WAITOK | M_ZERO);
832b1f1b07fSVladimir Kondratyev 	taskqueue_start_threads(&sc->taskqueue, 1, PI_TTY,
833b1f1b07fSVladimir Kondratyev 	    "%s taskq", device_get_nameunit(sc->dev));
834b1f1b07fSVladimir Kondratyev #endif
835b1f1b07fSVladimir Kondratyev }
836b1f1b07fSVladimir Kondratyev 
837b1f1b07fSVladimir Kondratyev static void
8384b171281SVladimir Kondratyev iichid_intr_unsetup(device_t dev, device_t child __unused)
839b1f1b07fSVladimir Kondratyev {
840b1f1b07fSVladimir Kondratyev 	struct iichid_softc *sc;
841b1f1b07fSVladimir Kondratyev 
842b1f1b07fSVladimir Kondratyev 	sc = device_get_softc(dev);
843b1f1b07fSVladimir Kondratyev #ifdef IICHID_SAMPLING
844b1f1b07fSVladimir Kondratyev 	taskqueue_drain_all(sc->taskqueue);
8458c86b981SAlexander Motin 	free(sc->dup_buf, M_DEVBUF);
846b1f1b07fSVladimir Kondratyev #endif
847b1f1b07fSVladimir Kondratyev 	free(sc->intr_buf, M_DEVBUF);
848b1f1b07fSVladimir Kondratyev }
849b1f1b07fSVladimir Kondratyev 
850b1f1b07fSVladimir Kondratyev static int
8514b171281SVladimir Kondratyev iichid_intr_start(device_t dev, device_t child __unused)
852b1f1b07fSVladimir Kondratyev {
853b1f1b07fSVladimir Kondratyev 	struct iichid_softc *sc;
854b1f1b07fSVladimir Kondratyev 
855b1f1b07fSVladimir Kondratyev 	sc = device_get_softc(dev);
856b1f1b07fSVladimir Kondratyev 	DPRINTF(sc, "iichid device open\n");
857b1f1b07fSVladimir Kondratyev 	iichid_set_power_state(sc, IICHID_PS_ON, IICHID_PS_NULL);
858b1f1b07fSVladimir Kondratyev 
859b1f1b07fSVladimir Kondratyev 	return (0);
860b1f1b07fSVladimir Kondratyev }
861b1f1b07fSVladimir Kondratyev 
862b1f1b07fSVladimir Kondratyev static int
8634b171281SVladimir Kondratyev iichid_intr_stop(device_t dev, device_t child __unused)
864b1f1b07fSVladimir Kondratyev {
865b1f1b07fSVladimir Kondratyev 	struct iichid_softc *sc;
866b1f1b07fSVladimir Kondratyev 
867b1f1b07fSVladimir Kondratyev 	sc = device_get_softc(dev);
868b1f1b07fSVladimir Kondratyev 	DPRINTF(sc, "iichid device close\n");
869b1f1b07fSVladimir Kondratyev 	/*
870b1f1b07fSVladimir Kondratyev 	 * 8.2 - The HOST determines that there are no active applications
871b1f1b07fSVladimir Kondratyev 	 * that are currently using the specific HID DEVICE.  The HOST
872b1f1b07fSVladimir Kondratyev 	 * is recommended to issue a HIPO command to the DEVICE to force
873b1f1b07fSVladimir Kondratyev 	 * the DEVICE in to a lower power state.
874b1f1b07fSVladimir Kondratyev 	 */
875b1f1b07fSVladimir Kondratyev 	iichid_set_power_state(sc, IICHID_PS_OFF, IICHID_PS_NULL);
876b1f1b07fSVladimir Kondratyev 
877b1f1b07fSVladimir Kondratyev 	return (0);
878b1f1b07fSVladimir Kondratyev }
879b1f1b07fSVladimir Kondratyev 
880b1f1b07fSVladimir Kondratyev static void
8814b171281SVladimir Kondratyev iichid_intr_poll(device_t dev, device_t child __unused)
882b1f1b07fSVladimir Kondratyev {
883b1f1b07fSVladimir Kondratyev 	struct iichid_softc *sc;
884b1f1b07fSVladimir Kondratyev 	iichid_size_t actual;
885b1f1b07fSVladimir Kondratyev 	int error;
886b1f1b07fSVladimir Kondratyev 
887b1f1b07fSVladimir Kondratyev 	sc = device_get_softc(dev);
888b1f1b07fSVladimir Kondratyev 	error = iichid_cmd_read(sc, sc->intr_buf, sc->intr_bufsize, &actual);
889b1f1b07fSVladimir Kondratyev 	if (error == 0 && actual != 0)
890b1f1b07fSVladimir Kondratyev 		sc->intr_handler(sc->intr_ctx, sc->intr_buf, actual);
891b1f1b07fSVladimir Kondratyev }
892b1f1b07fSVladimir Kondratyev 
893b1f1b07fSVladimir Kondratyev /*
894b1f1b07fSVladimir Kondratyev  * HID interface
895b1f1b07fSVladimir Kondratyev  */
896b1f1b07fSVladimir Kondratyev static int
8974b171281SVladimir Kondratyev iichid_get_rdesc(device_t dev, device_t child __unused, void *buf,
8984b171281SVladimir Kondratyev     hid_size_t len)
899b1f1b07fSVladimir Kondratyev {
900b1f1b07fSVladimir Kondratyev 	struct iichid_softc *sc;
901b1f1b07fSVladimir Kondratyev 	int error;
902b1f1b07fSVladimir Kondratyev 
903b1f1b07fSVladimir Kondratyev 	sc = device_get_softc(dev);
904b1f1b07fSVladimir Kondratyev 	error = iichid_cmd_get_report_desc(sc, buf, len);
905b1f1b07fSVladimir Kondratyev 	if (error)
906b1f1b07fSVladimir Kondratyev 		DPRINTF(sc, "failed to fetch report descriptor: %d\n", error);
907b1f1b07fSVladimir Kondratyev 
908b1f1b07fSVladimir Kondratyev 	return (iic2errno(error));
909b1f1b07fSVladimir Kondratyev }
910b1f1b07fSVladimir Kondratyev 
911b1f1b07fSVladimir Kondratyev static int
9124b171281SVladimir Kondratyev iichid_read(device_t dev, device_t child __unused, void *buf,
9134b171281SVladimir Kondratyev     hid_size_t maxlen, hid_size_t *actlen)
914b1f1b07fSVladimir Kondratyev {
915b1f1b07fSVladimir Kondratyev 	struct iichid_softc *sc;
916b1f1b07fSVladimir Kondratyev 	device_t parent;
917b1f1b07fSVladimir Kondratyev 	int error;
918b1f1b07fSVladimir Kondratyev 
919b1f1b07fSVladimir Kondratyev 	if (maxlen > IICHID_SIZE_MAX)
920b1f1b07fSVladimir Kondratyev 		return (EMSGSIZE);
921b1f1b07fSVladimir Kondratyev 	sc = device_get_softc(dev);
922b1f1b07fSVladimir Kondratyev 	parent = device_get_parent(sc->dev);
923b1f1b07fSVladimir Kondratyev 	error = iicbus_request_bus(parent, sc->dev, IIC_WAIT);
924b1f1b07fSVladimir Kondratyev 	if (error == 0) {
925b1f1b07fSVladimir Kondratyev 		error = iichid_cmd_read(sc, buf, maxlen, actlen);
926b1f1b07fSVladimir Kondratyev 		iicbus_release_bus(parent, sc->dev);
927b1f1b07fSVladimir Kondratyev 	}
928b1f1b07fSVladimir Kondratyev 	return (iic2errno(error));
929b1f1b07fSVladimir Kondratyev }
930b1f1b07fSVladimir Kondratyev 
931b1f1b07fSVladimir Kondratyev static int
9324b171281SVladimir Kondratyev iichid_write(device_t dev, device_t child __unused, const void *buf,
9334b171281SVladimir Kondratyev     hid_size_t len)
934b1f1b07fSVladimir Kondratyev {
935b1f1b07fSVladimir Kondratyev 	struct iichid_softc *sc;
936b1f1b07fSVladimir Kondratyev 
937b1f1b07fSVladimir Kondratyev 	if (len > IICHID_SIZE_MAX)
938b1f1b07fSVladimir Kondratyev 		return (EMSGSIZE);
939b1f1b07fSVladimir Kondratyev 	sc = device_get_softc(dev);
940b1f1b07fSVladimir Kondratyev 	return (iic2errno(iichid_cmd_write(sc, buf, len)));
941b1f1b07fSVladimir Kondratyev }
942b1f1b07fSVladimir Kondratyev 
943b1f1b07fSVladimir Kondratyev static int
9444b171281SVladimir Kondratyev iichid_get_report(device_t dev, device_t child __unused, void *buf,
9454b171281SVladimir Kondratyev     hid_size_t maxlen, hid_size_t *actlen, uint8_t type, uint8_t id)
946b1f1b07fSVladimir Kondratyev {
947b1f1b07fSVladimir Kondratyev 	struct iichid_softc *sc;
948b1f1b07fSVladimir Kondratyev 
949b1f1b07fSVladimir Kondratyev 	if (maxlen > IICHID_SIZE_MAX)
950b1f1b07fSVladimir Kondratyev 		return (EMSGSIZE);
951b1f1b07fSVladimir Kondratyev 	sc = device_get_softc(dev);
952b1f1b07fSVladimir Kondratyev 	return (iic2errno(
953b1f1b07fSVladimir Kondratyev 	    iichid_cmd_get_report(sc, buf, maxlen, actlen, type, id)));
954b1f1b07fSVladimir Kondratyev }
955b1f1b07fSVladimir Kondratyev 
956b1f1b07fSVladimir Kondratyev static int
9574b171281SVladimir Kondratyev iichid_set_report(device_t dev, device_t child __unused, const void *buf,
9584b171281SVladimir Kondratyev     hid_size_t len, uint8_t type, uint8_t id)
959b1f1b07fSVladimir Kondratyev {
960b1f1b07fSVladimir Kondratyev 	struct iichid_softc *sc;
961b1f1b07fSVladimir Kondratyev 
962b1f1b07fSVladimir Kondratyev 	if (len > IICHID_SIZE_MAX)
963b1f1b07fSVladimir Kondratyev 		return (EMSGSIZE);
964b1f1b07fSVladimir Kondratyev 	sc = device_get_softc(dev);
965b1f1b07fSVladimir Kondratyev 	return (iic2errno(iichid_cmd_set_report(sc, buf, len, type, id)));
966b1f1b07fSVladimir Kondratyev }
967b1f1b07fSVladimir Kondratyev 
968b1f1b07fSVladimir Kondratyev static int
9694b171281SVladimir Kondratyev iichid_set_idle(device_t dev, device_t child __unused,
9704b171281SVladimir Kondratyev     uint16_t duration, uint8_t id)
971b1f1b07fSVladimir Kondratyev {
972b1f1b07fSVladimir Kondratyev 	return (ENOTSUP);
973b1f1b07fSVladimir Kondratyev }
974b1f1b07fSVladimir Kondratyev 
975b1f1b07fSVladimir Kondratyev static int
9764b171281SVladimir Kondratyev iichid_set_protocol(device_t dev, device_t child __unused, uint16_t protocol)
977b1f1b07fSVladimir Kondratyev {
978b1f1b07fSVladimir Kondratyev 	return (ENOTSUP);
979b1f1b07fSVladimir Kondratyev }
980b1f1b07fSVladimir Kondratyev 
981b1f1b07fSVladimir Kondratyev static int
9824b171281SVladimir Kondratyev iichid_ioctl(device_t dev, device_t child __unused, unsigned long cmd,
9834b171281SVladimir Kondratyev     uintptr_t data)
9846b1da3d2SVladimir Kondratyev {
9856b1da3d2SVladimir Kondratyev 	int error;
9866b1da3d2SVladimir Kondratyev 
9876b1da3d2SVladimir Kondratyev 	switch (cmd) {
9886b1da3d2SVladimir Kondratyev 	case I2CRDWR:
9896b1da3d2SVladimir Kondratyev 		error = iic2errno(iicbus_transfer(dev,
9906b1da3d2SVladimir Kondratyev 		    ((struct iic_rdwr_data *)data)->msgs,
9916b1da3d2SVladimir Kondratyev 		    ((struct iic_rdwr_data *)data)->nmsgs));
9926b1da3d2SVladimir Kondratyev 		break;
9936b1da3d2SVladimir Kondratyev 	default:
9946b1da3d2SVladimir Kondratyev 		error = EINVAL;
9956b1da3d2SVladimir Kondratyev 	}
9966b1da3d2SVladimir Kondratyev 
9976b1da3d2SVladimir Kondratyev 	return (error);
9986b1da3d2SVladimir Kondratyev }
9996b1da3d2SVladimir Kondratyev 
10006b1da3d2SVladimir Kondratyev static int
1001b1f1b07fSVladimir Kondratyev iichid_fill_device_info(struct i2c_hid_desc *desc, ACPI_HANDLE handle,
1002b1f1b07fSVladimir Kondratyev     struct hid_device_info *hw)
1003b1f1b07fSVladimir Kondratyev {
1004b1f1b07fSVladimir Kondratyev 	ACPI_DEVICE_INFO *device_info;
1005b1f1b07fSVladimir Kondratyev 
1006b1f1b07fSVladimir Kondratyev 	hw->idBus = BUS_I2C;
1007b1f1b07fSVladimir Kondratyev 	hw->idVendor = le16toh(desc->wVendorID);
1008b1f1b07fSVladimir Kondratyev 	hw->idProduct = le16toh(desc->wProductID);
1009b1f1b07fSVladimir Kondratyev 	hw->idVersion = le16toh(desc->wVersionID);
1010b1f1b07fSVladimir Kondratyev 
1011b1f1b07fSVladimir Kondratyev 	/* get ACPI HID. It is a base part of the device name. */
1012b1f1b07fSVladimir Kondratyev 	if (ACPI_FAILURE(AcpiGetObjectInfo(handle, &device_info)))
1013b1f1b07fSVladimir Kondratyev 		return (ENXIO);
1014b1f1b07fSVladimir Kondratyev 
1015b1f1b07fSVladimir Kondratyev 	if (device_info->Valid & ACPI_VALID_HID)
1016b1f1b07fSVladimir Kondratyev 		strlcpy(hw->idPnP, device_info->HardwareId.String,
1017b1f1b07fSVladimir Kondratyev 		    HID_PNP_ID_SIZE);
1018b1f1b07fSVladimir Kondratyev 	snprintf(hw->name, sizeof(hw->name), "%s:%02lX %04X:%04X",
1019b1f1b07fSVladimir Kondratyev 	    (device_info->Valid & ACPI_VALID_HID) ?
1020b1f1b07fSVladimir Kondratyev 	    device_info->HardwareId.String : "Unknown",
1021b1f1b07fSVladimir Kondratyev 	    (device_info->Valid & ACPI_VALID_UID) ?
1022b1f1b07fSVladimir Kondratyev 	    strtoul(device_info->UniqueId.String, NULL, 10) : 0UL,
1023b1f1b07fSVladimir Kondratyev 	    le16toh(desc->wVendorID), le16toh(desc->wProductID));
1024b1f1b07fSVladimir Kondratyev 
1025b1f1b07fSVladimir Kondratyev 	AcpiOsFree(device_info);
1026b1f1b07fSVladimir Kondratyev 
1027b1f1b07fSVladimir Kondratyev 	strlcpy(hw->serial, "", sizeof(hw->serial));
1028b1f1b07fSVladimir Kondratyev 	hw->rdescsize = le16toh(desc->wReportDescLength);
1029b1f1b07fSVladimir Kondratyev 	if (desc->wOutputRegister == 0 || desc->wMaxOutputLength == 0)
1030b1f1b07fSVladimir Kondratyev 		hid_add_dynamic_quirk(hw, HQ_NOWRITE);
1031b1f1b07fSVladimir Kondratyev 
1032b1f1b07fSVladimir Kondratyev 	return (0);
1033b1f1b07fSVladimir Kondratyev }
1034b1f1b07fSVladimir Kondratyev 
1035b1f1b07fSVladimir Kondratyev static int
1036b1f1b07fSVladimir Kondratyev iichid_probe(device_t dev)
1037b1f1b07fSVladimir Kondratyev {
1038b1f1b07fSVladimir Kondratyev 	struct iichid_softc *sc;
1039b1f1b07fSVladimir Kondratyev 	ACPI_HANDLE handle;
1040b1f1b07fSVladimir Kondratyev 	uint16_t config_reg;
104134e051c4SVladimir Kondratyev 	int error, reg;
1042b1f1b07fSVladimir Kondratyev 
1043b1f1b07fSVladimir Kondratyev 	sc = device_get_softc(dev);
1044b1f1b07fSVladimir Kondratyev 	sc->dev = dev;
1045b1f1b07fSVladimir Kondratyev 	if (sc->probe_done)
1046b1f1b07fSVladimir Kondratyev 		goto done;
1047b1f1b07fSVladimir Kondratyev 
1048b1f1b07fSVladimir Kondratyev 	sc->probe_done = true;
1049b1f1b07fSVladimir Kondratyev 	sc->probe_result = ENXIO;
1050b1f1b07fSVladimir Kondratyev 
1051b1f1b07fSVladimir Kondratyev 	if (acpi_disabled("iichid"))
1052b1f1b07fSVladimir Kondratyev 		return (ENXIO);
1053b1f1b07fSVladimir Kondratyev 
1054b1f1b07fSVladimir Kondratyev 	sc->addr = iicbus_get_addr(dev) << 1;
1055b1f1b07fSVladimir Kondratyev 	if (sc->addr == 0)
1056b1f1b07fSVladimir Kondratyev 		return (ENXIO);
1057b1f1b07fSVladimir Kondratyev 
1058b1f1b07fSVladimir Kondratyev 	handle = acpi_get_handle(dev);
1059b1f1b07fSVladimir Kondratyev 	if (handle == NULL)
1060b1f1b07fSVladimir Kondratyev 		return (ENXIO);
1061b1f1b07fSVladimir Kondratyev 
106234e051c4SVladimir Kondratyev 	reg = acpi_is_iichid(handle);
106334e051c4SVladimir Kondratyev 	if (reg == IICHID_REG_NONE)
1064b1f1b07fSVladimir Kondratyev 		return (ENXIO);
1065b1f1b07fSVladimir Kondratyev 
106634e051c4SVladimir Kondratyev 	if (reg == IICHID_REG_ACPI) {
1067b1f1b07fSVladimir Kondratyev 		if (ACPI_FAILURE(iichid_get_config_reg(handle, &config_reg)))
1068b1f1b07fSVladimir Kondratyev 			return (ENXIO);
106934e051c4SVladimir Kondratyev 	} else
107034e051c4SVladimir Kondratyev 		config_reg = (uint16_t)reg;
1071b1f1b07fSVladimir Kondratyev 
1072b1f1b07fSVladimir Kondratyev 	DPRINTF(sc, "  IICbus addr       : 0x%02X\n", sc->addr >> 1);
1073b1f1b07fSVladimir Kondratyev 	DPRINTF(sc, "  HID descriptor reg: 0x%02X\n", config_reg);
1074b1f1b07fSVladimir Kondratyev 
1075b1f1b07fSVladimir Kondratyev 	error = iichid_cmd_get_hid_desc(sc, config_reg, &sc->desc);
1076b1f1b07fSVladimir Kondratyev 	if (error) {
1077b1f1b07fSVladimir Kondratyev 		DPRINTF(sc, "could not retrieve HID descriptor from the "
1078b1f1b07fSVladimir Kondratyev 		    "device: %d\n", error);
1079b1f1b07fSVladimir Kondratyev 		return (ENXIO);
1080b1f1b07fSVladimir Kondratyev 	}
1081b1f1b07fSVladimir Kondratyev 
1082b1f1b07fSVladimir Kondratyev 	if (le16toh(sc->desc.wHIDDescLength) != 30 ||
1083b1f1b07fSVladimir Kondratyev 	    le16toh(sc->desc.bcdVersion) != 0x100) {
1084b1f1b07fSVladimir Kondratyev 		DPRINTF(sc, "HID descriptor is broken\n");
1085b1f1b07fSVladimir Kondratyev 		return (ENXIO);
1086b1f1b07fSVladimir Kondratyev 	}
1087b1f1b07fSVladimir Kondratyev 
1088b1f1b07fSVladimir Kondratyev 	/* Setup hid_device_info so we can figure out quirks for the device. */
1089b1f1b07fSVladimir Kondratyev 	if (iichid_fill_device_info(&sc->desc, handle, &sc->hw) != 0) {
1090b1f1b07fSVladimir Kondratyev 		DPRINTF(sc, "error evaluating AcpiGetObjectInfo\n");
1091b1f1b07fSVladimir Kondratyev 		return (ENXIO);
1092b1f1b07fSVladimir Kondratyev 	}
1093b1f1b07fSVladimir Kondratyev 
1094b1f1b07fSVladimir Kondratyev 	if (hid_test_quirk(&sc->hw, HQ_HID_IGNORE))
1095b1f1b07fSVladimir Kondratyev 		return (ENXIO);
1096b1f1b07fSVladimir Kondratyev 
1097b1f1b07fSVladimir Kondratyev 	sc->probe_result = BUS_PROBE_DEFAULT;
1098b1f1b07fSVladimir Kondratyev done:
109948f5a429SMark Johnston 	if (sc->probe_result <= BUS_PROBE_SPECIFIC)
110048f5a429SMark Johnston 		device_set_descf(dev, "%s I2C HID device", sc->hw.name);
1101b1f1b07fSVladimir Kondratyev 	return (sc->probe_result);
1102b1f1b07fSVladimir Kondratyev }
1103b1f1b07fSVladimir Kondratyev 
1104b1f1b07fSVladimir Kondratyev static int
1105b1f1b07fSVladimir Kondratyev iichid_attach(device_t dev)
1106b1f1b07fSVladimir Kondratyev {
1107b1f1b07fSVladimir Kondratyev 	struct iichid_softc *sc;
1108b1f1b07fSVladimir Kondratyev 	device_t child;
1109b1f1b07fSVladimir Kondratyev 	int error;
1110b1f1b07fSVladimir Kondratyev 
1111b1f1b07fSVladimir Kondratyev 	sc = device_get_softc(dev);
1112b1f1b07fSVladimir Kondratyev 	error = iichid_set_power(sc, I2C_HID_POWER_ON);
1113b1f1b07fSVladimir Kondratyev 	if (error) {
1114b1f1b07fSVladimir Kondratyev 		device_printf(dev, "failed to power on: %d\n", error);
1115b1f1b07fSVladimir Kondratyev 		return (ENXIO);
1116b1f1b07fSVladimir Kondratyev 	}
1117b1f1b07fSVladimir Kondratyev 	/*
1118b1f1b07fSVladimir Kondratyev 	 * Windows driver sleeps for 1ms between the SET_POWER and RESET
1119b1f1b07fSVladimir Kondratyev 	 * commands. So we too as some devices may depend on this.
1120b1f1b07fSVladimir Kondratyev 	 */
1121b1f1b07fSVladimir Kondratyev 	pause("iichid", (hz + 999) / 1000);
1122b1f1b07fSVladimir Kondratyev 
1123b1f1b07fSVladimir Kondratyev 	error = iichid_reset(sc);
1124b1f1b07fSVladimir Kondratyev 	if (error) {
1125b1f1b07fSVladimir Kondratyev 		device_printf(dev, "failed to reset hardware: %d\n", error);
11265236888dSJ.R. Oldroyd 		error = ENXIO;
11275236888dSJ.R. Oldroyd 		goto done;
1128b1f1b07fSVladimir Kondratyev 	}
1129b1f1b07fSVladimir Kondratyev 
11305236888dSJ.R. Oldroyd 	sc->power_on = true;
11315236888dSJ.R. Oldroyd 
113282626fefSVladimir Kondratyev 	TASK_INIT(&sc->suspend_task, 0, iichid_suspend_task, sc);
1133b1f1b07fSVladimir Kondratyev #ifdef IICHID_SAMPLING
1134358453ceSAlexander Motin 	sc->taskqueue = taskqueue_create_fast("iichid_tq", M_WAITOK | M_ZERO,
1135b1f1b07fSVladimir Kondratyev 	    taskqueue_thread_enqueue, &sc->taskqueue);
1136a8f80c0cSAlexander Motin 	TIMEOUT_TASK_INIT(sc->taskqueue, &sc->sampling_task, 0,
1137a8f80c0cSAlexander Motin 	    iichid_sampling_task, sc);
1138b1f1b07fSVladimir Kondratyev 
1139b1f1b07fSVladimir Kondratyev 	sc->sampling_rate_slow = -1;
1140b1f1b07fSVladimir Kondratyev 	sc->sampling_rate_fast = IICHID_SAMPLING_RATE_FAST;
1141b1f1b07fSVladimir Kondratyev 	sc->sampling_hysteresis = IICHID_SAMPLING_HYSTERESIS;
1142b1f1b07fSVladimir Kondratyev #endif
1143b1f1b07fSVladimir Kondratyev 
1144b1f1b07fSVladimir Kondratyev 	sc->irq_rid = 0;
1145b1f1b07fSVladimir Kondratyev 	sc->irq_res = bus_alloc_resource_any(sc->dev, SYS_RES_IRQ,
1146b1f1b07fSVladimir Kondratyev 	    &sc->irq_rid, RF_ACTIVE);
1147b1f1b07fSVladimir Kondratyev 
1148b1f1b07fSVladimir Kondratyev 	if (sc->irq_res != NULL) {
1149b1f1b07fSVladimir Kondratyev 		DPRINTF(sc, "allocated irq at %p and rid %d\n",
1150b1f1b07fSVladimir Kondratyev 		    sc->irq_res, sc->irq_rid);
1151b1f1b07fSVladimir Kondratyev 		error = iichid_setup_interrupt(sc);
1152b1f1b07fSVladimir Kondratyev 	}
1153b1f1b07fSVladimir Kondratyev 
1154b1f1b07fSVladimir Kondratyev 	if (sc->irq_res == NULL || error != 0) {
1155b1f1b07fSVladimir Kondratyev #ifdef IICHID_SAMPLING
1156b1f1b07fSVladimir Kondratyev 		device_printf(sc->dev,
11570ac6cc3fSEd Maste 		    "Using sampling mode\n");
1158b1f1b07fSVladimir Kondratyev 		sc->sampling_rate_slow = IICHID_SAMPLING_RATE_SLOW;
1159b1f1b07fSVladimir Kondratyev #else
1160b1f1b07fSVladimir Kondratyev 		device_printf(sc->dev, "Interrupt setup failed\n");
1161b1f1b07fSVladimir Kondratyev 		if (sc->irq_res != NULL)
1162b1f1b07fSVladimir Kondratyev 			bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid,
1163b1f1b07fSVladimir Kondratyev 			    sc->irq_res);
1164b1f1b07fSVladimir Kondratyev 		error = ENXIO;
1165b1f1b07fSVladimir Kondratyev 		goto done;
1166b1f1b07fSVladimir Kondratyev #endif
1167b1f1b07fSVladimir Kondratyev 	}
1168b1f1b07fSVladimir Kondratyev 
1169b1f1b07fSVladimir Kondratyev #ifdef IICHID_SAMPLING
1170b1f1b07fSVladimir Kondratyev 	SYSCTL_ADD_PROC(device_get_sysctl_ctx(sc->dev),
1171b1f1b07fSVladimir Kondratyev 		SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)),
1172b1f1b07fSVladimir Kondratyev 		OID_AUTO, "sampling_rate_slow", CTLTYPE_INT | CTLFLAG_RWTUN,
1173b1f1b07fSVladimir Kondratyev 		sc, 0, iichid_sysctl_sampling_rate_handler, "I",
1174b1f1b07fSVladimir Kondratyev 		"idle sampling rate in num/second");
1175b1f1b07fSVladimir Kondratyev 	SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->dev),
1176b1f1b07fSVladimir Kondratyev 		SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)),
11778ffcde25SVladimir Kondratyev 		OID_AUTO, "sampling_rate_fast", CTLFLAG_RWTUN,
1178b1f1b07fSVladimir Kondratyev 		&sc->sampling_rate_fast, 0,
1179b1f1b07fSVladimir Kondratyev 		"active sampling rate in num/second");
1180b1f1b07fSVladimir Kondratyev 	SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->dev),
1181b1f1b07fSVladimir Kondratyev 		SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)),
11828ffcde25SVladimir Kondratyev 		OID_AUTO, "sampling_hysteresis", CTLFLAG_RWTUN,
1183b1f1b07fSVladimir Kondratyev 		&sc->sampling_hysteresis, 0,
1184b1f1b07fSVladimir Kondratyev 		"number of missing samples before enabling of slow mode");
1185b1f1b07fSVladimir Kondratyev 	hid_add_dynamic_quirk(&sc->hw, HQ_IICHID_SAMPLING);
1186c508b081SVladimir Kondratyev 
1187c508b081SVladimir Kondratyev 	if (sc->sampling_rate_slow >= 0) {
1188c508b081SVladimir Kondratyev 		pause("iichid", (hz + 999) / 1000);
1189c508b081SVladimir Kondratyev 		(void)iichid_cmd_read(sc, NULL, 0, NULL);
1190c508b081SVladimir Kondratyev 	}
1191b1f1b07fSVladimir Kondratyev #endif /* IICHID_SAMPLING */
1192b1f1b07fSVladimir Kondratyev 
11935b56413dSWarner Losh 	child = device_add_child(dev, "hidbus", DEVICE_UNIT_ANY);
1194b1f1b07fSVladimir Kondratyev 	if (child == NULL) {
1195b1f1b07fSVladimir Kondratyev 		device_printf(sc->dev, "Could not add I2C device\n");
1196b1f1b07fSVladimir Kondratyev 		iichid_detach(dev);
1197b1f1b07fSVladimir Kondratyev 		error = ENOMEM;
1198b1f1b07fSVladimir Kondratyev 		goto done;
1199b1f1b07fSVladimir Kondratyev 	}
1200b1f1b07fSVladimir Kondratyev 
1201b1f1b07fSVladimir Kondratyev 	device_set_ivars(child, &sc->hw);
120218250ec6SJohn Baldwin 	bus_attach_children(dev);
120318250ec6SJohn Baldwin 	error = 0;
1204b1f1b07fSVladimir Kondratyev done:
1205018cb11cSVladimir Kondratyev 	iicbus_request_bus(device_get_parent(dev), dev, IIC_WAIT);
1206018cb11cSVladimir Kondratyev 	if (!sc->open) {
1207b1f1b07fSVladimir Kondratyev 		(void)iichid_set_power(sc, I2C_HID_POWER_OFF);
12085236888dSJ.R. Oldroyd 		sc->power_on = false;
1209018cb11cSVladimir Kondratyev 	}
1210018cb11cSVladimir Kondratyev 	iicbus_release_bus(device_get_parent(dev), dev);
1211b1f1b07fSVladimir Kondratyev 	return (error);
1212b1f1b07fSVladimir Kondratyev }
1213b1f1b07fSVladimir Kondratyev 
1214b1f1b07fSVladimir Kondratyev static int
1215b1f1b07fSVladimir Kondratyev iichid_detach(device_t dev)
1216b1f1b07fSVladimir Kondratyev {
1217b1f1b07fSVladimir Kondratyev 	struct iichid_softc *sc;
1218b1f1b07fSVladimir Kondratyev 	int error;
1219b1f1b07fSVladimir Kondratyev 
1220b1f1b07fSVladimir Kondratyev 	sc = device_get_softc(dev);
1221*3ddaf820SJohn Baldwin 	error = bus_generic_detach(dev);
1222b1f1b07fSVladimir Kondratyev 	if (error)
1223b1f1b07fSVladimir Kondratyev 		return (error);
1224b1f1b07fSVladimir Kondratyev 	iichid_teardown_interrupt(sc);
1225b1f1b07fSVladimir Kondratyev 	if (sc->irq_res != NULL)
1226b1f1b07fSVladimir Kondratyev 		bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid,
1227b1f1b07fSVladimir Kondratyev 		    sc->irq_res);
1228b1f1b07fSVladimir Kondratyev #ifdef IICHID_SAMPLING
1229b1f1b07fSVladimir Kondratyev 	if (sc->taskqueue != NULL)
1230b1f1b07fSVladimir Kondratyev 		taskqueue_free(sc->taskqueue);
1231b1f1b07fSVladimir Kondratyev 	sc->taskqueue = NULL;
1232b1f1b07fSVladimir Kondratyev #endif
1233b1f1b07fSVladimir Kondratyev 	return (0);
1234b1f1b07fSVladimir Kondratyev }
1235b1f1b07fSVladimir Kondratyev 
123682626fefSVladimir Kondratyev static void
123782626fefSVladimir Kondratyev iichid_suspend_task(void *context, int pending)
123882626fefSVladimir Kondratyev {
123982626fefSVladimir Kondratyev 	struct iichid_softc *sc = context;
124082626fefSVladimir Kondratyev 
124182626fefSVladimir Kondratyev 	iichid_teardown_interrupt(sc);
124282626fefSVladimir Kondratyev }
124382626fefSVladimir Kondratyev 
1244b1f1b07fSVladimir Kondratyev static int
1245b1f1b07fSVladimir Kondratyev iichid_suspend(device_t dev)
1246b1f1b07fSVladimir Kondratyev {
1247b1f1b07fSVladimir Kondratyev 	struct iichid_softc *sc;
1248b1f1b07fSVladimir Kondratyev 	int error;
1249b1f1b07fSVladimir Kondratyev 
1250b1f1b07fSVladimir Kondratyev 	sc = device_get_softc(dev);
1251b1f1b07fSVladimir Kondratyev 	(void)bus_generic_suspend(dev);
1252b1f1b07fSVladimir Kondratyev 	/*
1253b1f1b07fSVladimir Kondratyev 	 * 8.2 - The HOST is going into a deep power optimized state and wishes
1254b1f1b07fSVladimir Kondratyev 	 * to put all the devices into a low power state also.  The HOST
1255b1f1b07fSVladimir Kondratyev 	 * is recommended to issue a HIPO command to the DEVICE to force
1256b1f1b07fSVladimir Kondratyev 	 * the DEVICE in to a lower power state.
1257b1f1b07fSVladimir Kondratyev 	 */
12585236888dSJ.R. Oldroyd 	DPRINTF(sc, "Suspend called, setting device to power_state 1\n");
1259b1f1b07fSVladimir Kondratyev 	error = iichid_set_power_state(sc, IICHID_PS_NULL, IICHID_PS_OFF);
1260b1f1b07fSVladimir Kondratyev 	if (error != 0)
1261b1f1b07fSVladimir Kondratyev 		DPRINTF(sc, "Could not set power_state, error: %d\n", error);
1262b1f1b07fSVladimir Kondratyev 	else
1263b1f1b07fSVladimir Kondratyev 		DPRINTF(sc, "Successfully set power_state\n");
1264b1f1b07fSVladimir Kondratyev 
126582626fefSVladimir Kondratyev #ifdef IICHID_SAMPLING
126682626fefSVladimir Kondratyev 	if (sc->sampling_rate_slow < 0)
126782626fefSVladimir Kondratyev #endif
126882626fefSVladimir Kondratyev 	{
126982626fefSVladimir Kondratyev 		/*
127082626fefSVladimir Kondratyev 		 * bus_teardown_intr can not be executed right here as it wants
127182626fefSVladimir Kondratyev 		 * to run on certain CPU to interacts with LAPIC while suspend
127282626fefSVladimir Kondratyev 		 * thread is bound to CPU0. So run it from taskqueue context.
127382626fefSVladimir Kondratyev 		 */
127482626fefSVladimir Kondratyev #ifdef IICHID_SAMPLING
127582626fefSVladimir Kondratyev #define	suspend_thread	sc->taskqueue
127682626fefSVladimir Kondratyev #else
127782626fefSVladimir Kondratyev #define	suspend_thread	taskqueue_thread
127882626fefSVladimir Kondratyev #endif
127982626fefSVladimir Kondratyev 		taskqueue_enqueue(suspend_thread, &sc->suspend_task);
128082626fefSVladimir Kondratyev 		taskqueue_drain(suspend_thread, &sc->suspend_task);
128182626fefSVladimir Kondratyev 	}
128282626fefSVladimir Kondratyev 
1283b1f1b07fSVladimir Kondratyev 	return (0);
1284b1f1b07fSVladimir Kondratyev }
1285b1f1b07fSVladimir Kondratyev 
1286b1f1b07fSVladimir Kondratyev static int
1287b1f1b07fSVladimir Kondratyev iichid_resume(device_t dev)
1288b1f1b07fSVladimir Kondratyev {
1289b1f1b07fSVladimir Kondratyev 	struct iichid_softc *sc;
1290b1f1b07fSVladimir Kondratyev 	int error;
1291b1f1b07fSVladimir Kondratyev 
1292b1f1b07fSVladimir Kondratyev 	sc = device_get_softc(dev);
129382626fefSVladimir Kondratyev #ifdef IICHID_SAMPLING
129482626fefSVladimir Kondratyev 	if (sc->sampling_rate_slow < 0)
129582626fefSVladimir Kondratyev #endif
129682626fefSVladimir Kondratyev 		iichid_setup_interrupt(sc);
129782626fefSVladimir Kondratyev 
1298b1f1b07fSVladimir Kondratyev 	DPRINTF(sc, "Resume called, setting device to power_state 0\n");
1299b1f1b07fSVladimir Kondratyev 	error = iichid_set_power_state(sc, IICHID_PS_NULL, IICHID_PS_ON);
1300b1f1b07fSVladimir Kondratyev 	if (error != 0)
1301b1f1b07fSVladimir Kondratyev 		DPRINTF(sc, "Could not set power_state, error: %d\n", error);
1302b1f1b07fSVladimir Kondratyev 	else
1303b1f1b07fSVladimir Kondratyev 		DPRINTF(sc, "Successfully set power_state\n");
1304b1f1b07fSVladimir Kondratyev 	(void)bus_generic_resume(dev);
1305b1f1b07fSVladimir Kondratyev 
1306b1f1b07fSVladimir Kondratyev 	return (0);
1307b1f1b07fSVladimir Kondratyev }
1308b1f1b07fSVladimir Kondratyev 
1309b1f1b07fSVladimir Kondratyev static device_method_t iichid_methods[] = {
1310b1f1b07fSVladimir Kondratyev 	DEVMETHOD(device_probe,		iichid_probe),
1311b1f1b07fSVladimir Kondratyev 	DEVMETHOD(device_attach,	iichid_attach),
1312b1f1b07fSVladimir Kondratyev 	DEVMETHOD(device_detach,	iichid_detach),
1313b1f1b07fSVladimir Kondratyev 	DEVMETHOD(device_suspend,	iichid_suspend),
1314b1f1b07fSVladimir Kondratyev 	DEVMETHOD(device_resume,	iichid_resume),
1315b1f1b07fSVladimir Kondratyev 
1316b1f1b07fSVladimir Kondratyev 	DEVMETHOD(hid_intr_setup,	iichid_intr_setup),
1317b1f1b07fSVladimir Kondratyev 	DEVMETHOD(hid_intr_unsetup,	iichid_intr_unsetup),
1318b1f1b07fSVladimir Kondratyev 	DEVMETHOD(hid_intr_start,	iichid_intr_start),
1319b1f1b07fSVladimir Kondratyev 	DEVMETHOD(hid_intr_stop,	iichid_intr_stop),
1320b1f1b07fSVladimir Kondratyev 	DEVMETHOD(hid_intr_poll,	iichid_intr_poll),
1321b1f1b07fSVladimir Kondratyev 
1322b1f1b07fSVladimir Kondratyev 	/* HID interface */
1323b1f1b07fSVladimir Kondratyev 	DEVMETHOD(hid_get_rdesc,	iichid_get_rdesc),
1324b1f1b07fSVladimir Kondratyev 	DEVMETHOD(hid_read,		iichid_read),
1325b1f1b07fSVladimir Kondratyev 	DEVMETHOD(hid_write,		iichid_write),
1326b1f1b07fSVladimir Kondratyev 	DEVMETHOD(hid_get_report,	iichid_get_report),
1327b1f1b07fSVladimir Kondratyev 	DEVMETHOD(hid_set_report,	iichid_set_report),
1328b1f1b07fSVladimir Kondratyev 	DEVMETHOD(hid_set_idle,		iichid_set_idle),
1329b1f1b07fSVladimir Kondratyev 	DEVMETHOD(hid_set_protocol,	iichid_set_protocol),
13306b1da3d2SVladimir Kondratyev 	DEVMETHOD(hid_ioctl,		iichid_ioctl),
1331b1f1b07fSVladimir Kondratyev 
1332b1f1b07fSVladimir Kondratyev 	DEVMETHOD_END
1333b1f1b07fSVladimir Kondratyev };
1334b1f1b07fSVladimir Kondratyev 
1335b1f1b07fSVladimir Kondratyev static driver_t iichid_driver = {
1336b1f1b07fSVladimir Kondratyev 	.name = "iichid",
1337b1f1b07fSVladimir Kondratyev 	.methods = iichid_methods,
1338b1f1b07fSVladimir Kondratyev 	.size = sizeof(struct iichid_softc),
1339b1f1b07fSVladimir Kondratyev };
1340b1f1b07fSVladimir Kondratyev 
13413a866152SJohn Baldwin DRIVER_MODULE(iichid, iicbus, iichid_driver, NULL, NULL);
1342b1f1b07fSVladimir Kondratyev MODULE_DEPEND(iichid, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);
1343b1f1b07fSVladimir Kondratyev MODULE_DEPEND(iichid, acpi, 1, 1, 1);
1344b1f1b07fSVladimir Kondratyev MODULE_DEPEND(iichid, hid, 1, 1, 1);
1345b1f1b07fSVladimir Kondratyev MODULE_DEPEND(iichid, hidbus, 1, 1, 1);
1346b1f1b07fSVladimir Kondratyev MODULE_VERSION(iichid, 1);
1347b1f1b07fSVladimir Kondratyev IICBUS_ACPI_PNP_INFO(iichid_ids);
1348