usb: hid: boot protocol

Set_Protocol and Get_Protocol requests are handled now.
Tested with USB3CV.

Signed-off-by: Marcin Szymczyk <Marcin.Szymczyk@nordicsemi.no>
diff --git a/include/usb/class/usb_hid.h b/include/usb/class/usb_hid.h
index 8e42765..dae10c9 100644
--- a/include/usb/class/usb_hid.h
+++ b/include/usb/class/usb_hid.h
@@ -60,6 +60,7 @@
 typedef int (*hid_cb_t)(struct usb_setup_packet *setup, s32_t *len,
 			u8_t **data);
 typedef void (*hid_int_ready_callback)(void);
+typedef void (*hid_protocol_cb_t)(u8_t protocol);
 typedef void (*hid_idle_cb_t)(u16_t report_id);
 
 struct hid_ops {
@@ -69,6 +70,7 @@
 	hid_cb_t set_report;
 	hid_cb_t set_idle;
 	hid_cb_t set_protocol;
+	hid_protocol_cb_t protocol_change;
 	hid_idle_cb_t on_idle;
 	/*
 	 * int_in_ready is an optional callback that is called when
@@ -162,6 +164,10 @@
 #define COLLECTION_PHYSICAL		0x00
 #define COLLECTION_APPLICATION		0x01
 
+/* Protocols */
+#define HID_PROTOCOL_BOOT		0x00
+#define HID_PROTOCOL_REPORT		0x01
+
 /* Register HID device */
 void usb_hid_register_device(const u8_t *desc, size_t size,
 			     const struct hid_ops *op);
diff --git a/samples/subsys/usb/hid/prj.conf b/samples/subsys/usb/hid/prj.conf
index d2b92ed..638dba4 100644
--- a/samples/subsys/usb/hid/prj.conf
+++ b/samples/subsys/usb/hid/prj.conf
@@ -2,6 +2,7 @@
 CONFIG_USB_DEVICE_STACK=y
 CONFIG_USB_DEVICE_HID=y
 CONFIG_USB_DEVICE_PRODUCT="Zephyr HID sample"
+CONFIG_USB_HID_BOOT_PROTOCOL=y
 
 CONFIG_LOG=y
 CONFIG_USB_DRIVER_LOG_LEVEL_ERR=y
diff --git a/samples/subsys/usb/hid/src/main.c b/samples/subsys/usb/hid/src/main.c
index 9f231a9..1e84e46 100644
--- a/samples/subsys/usb/hid/src/main.c
+++ b/samples/subsys/usb/hid/src/main.c
@@ -99,10 +99,17 @@
 	LOG_DBG("Idle callback: wrote %d bytes with ret %d", wrote, ret);
 }
 
+static void protocol_cb(u8_t protocol)
+{
+	LOG_DBG("New protocol: %s", protocol == HID_PROTOCOL_BOOT ?
+		"boot" : "report");
+}
+
 static const struct hid_ops ops = {
 	.int_in_ready = in_ready_cb,
 	.status_cb = status_cb,
 	.on_idle = idle_cb,
+	.protocol_change = protocol_cb,
 };
 
 void main(void)
diff --git a/subsys/usb/class/hid/Kconfig b/subsys/usb/class/hid/Kconfig
index 68ce374..220c9a5 100644
--- a/subsys/usb/class/hid/Kconfig
+++ b/subsys/usb/class/hid/Kconfig
@@ -45,4 +45,29 @@
 	  Number of HID reports in the instance.
 	  Must be equal or higher than highest report ID (if they are not consecutive).
 
+config USB_HID_BOOT_PROTOCOL
+	bool
+	prompt "Enable USB HID Boot Protocol handling"
+	help
+	  Sets bInterfaceSubClass to 1 and enables Set_Protocol and Get_Protocol
+	  requests handling.
+	  See Chapter 4.2 of Device Class Definition for Human Interface Devices 1.11
+	  for more information.
+
+if USB_HID_BOOT_PROTOCOL
+
+config USB_HID_PROTOCOL_CODE
+	int "HID protocol code"
+	default 0
+	range 0 2
+	help
+	  Sets bIntefaceProtocol in HID instance.
+	  0 = None
+	  1 = Keyboard
+	  2 = Mouse
+	  See Chapter 4.3 of Device Class Definition for Human Interface Devices 1.11
+	  for more information.
+
+endif # USB_HID_BOOT_PROTOCOL
+
 endif # USB_DEVICE_HID
diff --git a/subsys/usb/class/hid/core.c b/subsys/usb/class/hid/core.c
index 0583df6..bf5da76 100644
--- a/subsys/usb/class/hid/core.c
+++ b/subsys/usb/class/hid/core.c
@@ -44,8 +44,13 @@
 		.bAlternateSetting = 0,
 		.bNumEndpoints = 1,
 		.bInterfaceClass = HID_CLASS,
+#ifdef CONFIG_USB_HID_BOOT_PROTOCOL
+		.bInterfaceSubClass = 1,
+		.bInterfaceProtocol = CONFIG_USB_HID_PROTOCOL_CODE,
+#else
 		.bInterfaceSubClass = 0,
 		.bInterfaceProtocol = 0,
+#endif
 		.iInterface = 0,
 	},
 	.if0_hid = {
@@ -95,6 +100,9 @@
 	bool idle_id_report;
 	u8_t idle_rate[CONFIG_USB_HID_REPORTS + 1];
 #endif
+#ifdef CONFIG_USB_HID_BOOT_PROTOCOL
+	u8_t protocol;
+#endif
 } hid_device;
 
 static int hid_on_get_idle(struct usb_setup_packet *setup, s32_t *len,
@@ -133,11 +141,22 @@
 static int hid_on_get_protocol(struct usb_setup_packet *setup, s32_t *len,
 			       u8_t **data)
 {
-	LOG_DBG("Get Protocol callback");
+#ifdef CONFIG_USB_HID_BOOT_PROTOCOL
+	if (setup->wValue) {
+		LOG_ERR("wValue should be 0");
+		return -ENOTSUP;
+	}
 
-	/* TODO: Do something. */
+	u32_t size = sizeof(hid_device.protocol);
 
+	LOG_DBG("Get Protocol callback, protocol: %d", hid_device.protocol);
+
+	*data = &hid_device.protocol;
+	len = &size;
+	return 0;
+#else
 	return -ENOTSUP;
+#endif
 }
 
 static int hid_on_set_idle(struct usb_setup_packet *setup, s32_t *len,
@@ -205,11 +224,28 @@
 static int hid_on_set_protocol(struct usb_setup_packet *setup, s32_t *len,
 			       u8_t **data)
 {
-	LOG_DBG("Set Protocol callback");
+#ifdef CONFIG_USB_HID_BOOT_PROTOCOL
+	u16_t protocol = sys_le16_to_cpu(setup->wValue);
 
-	/* TODO: Do something. */
+	if (protocol > HID_PROTOCOL_REPORT) {
+		LOG_ERR("Unsupported protocol: %u", protocol);
+		return -ENOTSUP;
+	}
 
+	LOG_DBG("Set Protocol callback, protocol: %u", protocol);
+
+	if (hid_device.protocol != protocol) {
+		hid_device.protocol = protocol;
+
+		if (hid_device.ops && hid_device.ops->protocol_change) {
+			hid_device.ops->protocol_change(protocol);
+		}
+	}
+
+	return 0;
+#else
 	return -ENOTSUP;
+#endif
 }
 
 static void usb_set_hid_report_size(u16_t size)
@@ -264,6 +300,9 @@
 		break;
 	case USB_DC_RESET:
 		LOG_DBG("USB device reset detected");
+#ifdef CONFIG_USB_HID_BOOT_PROTOCOL
+		hid_device.protocol = HID_PROTOCOL_REPORT;
+#endif
 #ifdef CONFIG_USB_DEVICE_SOF
 		hid_clear_idle_ctx();
 #endif