EFR32 Blue Gecko 12 Software Documentation  efr32bg12-doc-5.1.2
hidkbd.c
Go to the documentation of this file.
1 /***************************************************************************/
15 #include "em_device.h"
16 #include "em_common.h"
17 #include "em_usb.h"
18 #include "hidkbd.h"
19 
20 /**************************************************************************/
59 #define DEFAULT_IDLE_RATE 500
60 
66 SL_ALIGN(4)
67 const char HIDKBD_ReportDescriptor[ 69 ] SL_ATTRIBUTE_ALIGN(4)=
68 {
69  0x05, 0x01, // USAGE_PAGE (Generic Desktop)
70  0x09, 0x06, // USAGE (Keyboard)
71  0xa1, 0x01, // COLLECTION (Application)
72  0x05, 0x07, // USAGE_PAGE (Keyboard)
73  0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)
74  0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)
75  0x15, 0x00, // LOGICAL_MINIMUM (0)
76  0x25, 0x01, // LOGICAL_MAXIMUM (1)
77  0x75, 0x01, // REPORT_SIZE (1)
78  0x95, 0x08, // REPORT_COUNT (8)
79  0x81, 0x02, // INPUT (Data,Var,Abs)
80  0x15, 0x00, // LOGICAL_MINIMUM (0)
81  0x25, 0x01, // LOGICAL_MAXIMUM (1)
82  0x75, 0x01, // REPORT_SIZE (1)
83  0x95, 0x08, // REPORT_COUNT (8)
84  0x81, 0x01, // INPUT (Cnst,Ary,Abs)
85  0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
86  0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)
87  0x15, 0x00, // LOGICAL_MINIMUM (0)
88  0x25, 0x65, // LOGICAL_MAXIMUM (101)
89  0x75, 0x08, // REPORT_SIZE (8)
90  0x95, 0x06, // REPORT_COUNT (6)
91  0x81, 0x00, // INPUT (Data,Ary,Abs)
92  0x05, 0x08, // USAGE_PAGE (LEDs)
93  0x19, 0x01, // USAGE_MINIMUM (Num Lock)
94  0x29, 0x03, // USAGE_MAXIMUM (Scroll Lock)
95  0x15, 0x00, // LOGICAL_MINIMUM (0)
96  0x25, 0x01, // LOGICAL_MAXIMUM (1)
97  0x75, 0x01, // REPORT_SIZE (1)
98  0x95, 0x03, // REPORT_COUNT (3)
99  0x91, 0x02, // OUTPUT (Data,Var,Abs)
100  0x75, 0x01, // REPORT_SIZE (1)
101  0x95, 0x05, // REPORT_COUNT (5)
102  0x91, 0x01, // OUTPUT (Cnst,Ary,Abs)
103  0xc0 // END_COLLECTION
104 };
105 
108 static uint32_t tmpBuffer;
109 static uint8_t idleRate;
110 static void *hidDescriptor = NULL;
111 static HIDKBD_SetReportFunc_t setReportFunc = NULL;
112 
113 /* The last keyboard report sent to host. */
114 SL_ALIGN(4)
115 static HIDKBD_KeyReport_t lastSentReport SL_ATTRIBUTE_ALIGN(4);
116 
117 /* The last keyboard report reported to the driver. */
118 SL_ALIGN(4)
119 static HIDKBD_KeyReport_t lastKnownReport SL_ATTRIBUTE_ALIGN(4);
120 
121 static bool QueueEmpty( void );
122 static bool QueueFull( void );
123 static bool QueueGet( HIDKBD_KeyReport_t *element );
124 static void QueueInit( void );
125 static bool QueuePut( HIDKBD_KeyReport_t *element );
126 
127 /**************************************************************************/
134 static void IdleTimeout( void )
135 {
136  /* If there is a keyboard event in the queue, we send it to the host. */
137  /* If not we just resend the last one sent. */
138  if ( !QueueEmpty() )
139  {
140  /* A new keyboard event. */
141  QueueGet( &lastSentReport );
142  }
143 
144  USBD_Write( HIDKBD_INTR_IN_EP_ADDR, &lastSentReport,
145  sizeof( HIDKBD_KeyReport_t ), NULL );
146 
147  /* Schedule next idle event at current idle rate, idleRate unit is 4 ms. */
148  USBTIMER_Start( HIDKBD_IDLE_TIMER, idleRate * 4, IdleTimeout );
149 }
150 
151 /**************************************************************************/
162 static int OutputReportReceived( USB_Status_TypeDef status,
163  uint32_t xferred,
164  uint32_t remaining )
165 {
166  (void) remaining;
167 
168  if ( ( status == USB_STATUS_OK )
169  && ( xferred == 1 )
170  && ( setReportFunc != NULL ) )
171  {
172  setReportFunc( (uint8_t)tmpBuffer );
173  }
174 
175  return USB_STATUS_OK;
176 }
177 
180 /***************************************************************************/
188 {
189  hidDescriptor = init->hidDescriptor;
190  setReportFunc = init->setReportFunc;
191  memset( &lastSentReport, 0, sizeof( HIDKBD_KeyReport_t ) );
192  memset( &lastKnownReport, 0, sizeof( HIDKBD_KeyReport_t ) );
193 }
194 
195 /***************************************************************************/
203 {
204  lastKnownReport = *report;
205 
206  if ( idleRate != 0 ) /* Put keyboard events into a queue. */
207  {
208  /* Put the kbd event in the queue, it will be retrieved and reported */
209  /* to host in the idleRate timeout function. */
210  QueuePut( report );
211  }
212  else /* idleRate == 0, send report immediately. */
213  {
214  lastSentReport = *report;
215  USBD_Write( HIDKBD_INTR_IN_EP_ADDR, &lastSentReport,
216  sizeof( HIDKBD_KeyReport_t ), NULL );
217  }
218 }
219 
220 /**************************************************************************/
232 int HIDKBD_SetupCmd( const USB_Setup_TypeDef *setup )
233 {
234  STATIC_UBUF( hidDesc, USB_HID_DESCSIZE );
235 
236  int retVal = USB_STATUS_REQ_UNHANDLED;
237 
238  if ( ( setup->Type == USB_SETUP_TYPE_STANDARD ) &&
239  ( setup->Direction == USB_SETUP_DIR_IN ) &&
240  ( setup->Recipient == USB_SETUP_RECIPIENT_INTERFACE ) )
241  {
242  /* A HID device must extend the standard GET_DESCRIPTOR command */
243  /* with support for HID descriptors. */
244  switch (setup->bRequest)
245  {
246  case GET_DESCRIPTOR:
247  /********************/
248  if ( ( setup->wValue >> 8 ) == USB_HID_REPORT_DESCRIPTOR )
249  {
250  USBD_Write( 0, (void*)HIDKBD_ReportDescriptor,
251  SL_MIN(sizeof(HIDKBD_ReportDescriptor), setup->wLength),
252  NULL );
253  retVal = USB_STATUS_OK;
254  }
255  else if ( ( setup->wValue >> 8 ) == USB_HID_DESCRIPTOR )
256  {
257  /* The HID descriptor might be misaligned ! */
258  memcpy( hidDesc, hidDescriptor, USB_HID_DESCSIZE );
259  USBD_Write( 0, hidDesc, SL_MIN(USB_HID_DESCSIZE, setup->wLength),
260  NULL );
261  retVal = USB_STATUS_OK;
262  }
263  break;
264  }
265  }
266 
267  else if ( ( setup->Type == USB_SETUP_TYPE_CLASS ) &&
268  ( setup->Recipient == USB_SETUP_RECIPIENT_INTERFACE ) &&
269  ( setup->wIndex == HIDKBD_INTERFACE_NO ) )
270  {
271  /* Implement the necessary HID class specific commands. */
272  switch ( setup->bRequest )
273  {
274  case USB_HID_SET_REPORT:
275  /********************/
276  if ( ( ( setup->wValue >> 8 ) == 2 ) && /* Output report */
277  ( ( setup->wValue & 0xFF ) == 0 ) && /* Report ID */
278  ( setup->wLength == 1 ) && /* Report length */
279  ( setup->Direction != USB_SETUP_DIR_IN ) )
280  {
281  USBD_Read( 0, (void*)&tmpBuffer, 1, OutputReportReceived );
282  retVal = USB_STATUS_OK;
283  }
284  break;
285 
286  case USB_HID_GET_REPORT:
287  /********************/
288  if ( ( ( setup->wValue >> 8 ) == 1 ) && /* Input report */
289  ( ( setup->wValue & 0xFF ) == 0 ) && /* Report ID */
290  ( setup->wLength == 8 ) && /* Report length */
291  ( setup->Direction == USB_SETUP_DIR_IN ) )
292  {
293  USBD_Write( HIDKBD_INTR_IN_EP_ADDR, &lastKnownReport,
294  sizeof( HIDKBD_KeyReport_t ), NULL );
295  retVal = USB_STATUS_OK;
296  }
297  break;
298 
299  case USB_HID_SET_IDLE:
300  /********************/
301  if ( ( ( setup->wValue & 0xFF) == 0 ) && /* Report ID */
302  ( setup->wLength == 0 ) &&
303  ( setup->Direction != USB_SETUP_DIR_IN ) )
304  {
305  idleRate = setup->wValue >> 8;
306  if ( ( idleRate != 0 ) && ( idleRate < ( HIDKBD_POLL_RATE / 4 ) ) )
307  {
308  idleRate = HIDKBD_POLL_RATE / 4;
309  }
310  USBTIMER_Stop( HIDKBD_IDLE_TIMER );
311  if ( idleRate != 0 )
312  {
313  IdleTimeout();
314  }
315  retVal = USB_STATUS_OK;
316  }
317  break;
318 
319  case USB_HID_GET_IDLE:
320  /********************/
321  if ( ( setup->wValue == 0 ) && /* Report ID */
322  ( setup->wLength == 1 ) &&
323  ( setup->Direction == USB_SETUP_DIR_IN ) )
324  {
325  *(uint8_t*)&tmpBuffer = idleRate;
326  USBD_Write( 0, (void*)&tmpBuffer, 1, NULL );
327  retVal = USB_STATUS_OK;
328  }
329  break;
330  }
331  }
332 
333  return retVal;
334 }
335 
336 /**************************************************************************/
344 void HIDKBD_StateChangeEvent( USBD_State_TypeDef oldState,
345  USBD_State_TypeDef newState )
346 {
347  if ( newState == USBD_STATE_CONFIGURED )
348  {
349  /* We have been configured, start HID functionality ! */
350  if ( oldState != USBD_STATE_SUSPENDED ) /* Resume ? */
351  {
352  idleRate = DEFAULT_IDLE_RATE / 4; /* Unit is 4 millisecond. */
353  QueueInit();
354  }
355  if ( idleRate )
356  {
357  USBTIMER_Start( HIDKBD_IDLE_TIMER, idleRate * 4, IdleTimeout );
358  }
359  }
360 
361  else if ( ( oldState == USBD_STATE_CONFIGURED ) &&
362  ( newState != USBD_STATE_SUSPENDED ) )
363  {
364  /* We have been de-configured, stop HID functionality */
365  USBTIMER_Stop( HIDKBD_IDLE_TIMER );
366  }
367 
368  else if ( newState == USBD_STATE_SUSPENDED )
369  {
370  /* We have been suspended, stop HID functionality */
371  /* Reduce current consumption to below 2.5 mA. */
372  USBTIMER_Stop( HIDKBD_IDLE_TIMER );
373  }
374 }
375 
378 /* Minimal circular buffer implementation. */
379 
380 #define QUEUE_SIZE 16 /* Must be 2^n !! */
381 
382 typedef struct ringBuffer_t
383 {
384  unsigned int putIdx;
385  unsigned int getIdx;
386  HIDKBD_KeyReport_t buf[ QUEUE_SIZE ];
387 } ringBuffer_t;
388 
389 ringBuffer_t queue;
390 
391 static bool QueueEmpty( void )
392 {
393  return ( queue.putIdx - queue.getIdx ) == 0;
394 }
395 
396 static bool QueueFull( void )
397 {
398  return ( queue.putIdx - queue.getIdx ) >= QUEUE_SIZE;
399 }
400 
401 static bool QueueGet( HIDKBD_KeyReport_t *element )
402 {
403  if ( !QueueEmpty() )
404  {
405  *element = queue.buf[ queue.getIdx++ & (QUEUE_SIZE - 1) ];
406  queue.getIdx = ( queue.getIdx + 1 ) % QUEUE_SIZE;
407  return true;
408  }
409  return false;
410 }
411 
412 static void QueueInit( void )
413 {
414  queue.getIdx = 0;
415  queue.putIdx = 0;
416 }
417 
418 static bool QueuePut( HIDKBD_KeyReport_t *element )
419 {
420  if ( !QueueFull() )
421  {
422  queue.buf[ queue.putIdx++ & (QUEUE_SIZE - 1) ] = *element;
423  queue.putIdx = ( queue.putIdx + 1 ) % QUEUE_SIZE;
424  return true;
425  }
426  return false;
427 }
428 
HIDKBD_SetReportFunc_t setReportFunc
Definition: hidkbd.h:68
#define DEFAULT_IDLE_RATE
Definition: hidkbd.c:59
CMSIS Cortex-M Peripheral Access Layer for Silicon Laboratories microcontroller devices.
#define SL_ALIGN(X)
Macro for aligning a variable. Use this macro before the variable definition. X denotes the stora...
Definition: em_common.h:168
void * hidDescriptor
Definition: hidkbd.h:67
void HIDKBD_StateChangeEvent(USBD_State_TypeDef oldState, USBD_State_TypeDef newState)
Handle USB state change events, this function must be called each time the USB device state is change...
Definition: hidkbd.c:344
General purpose utilities.
const char HIDKBD_ReportDescriptor[69] SL_ATTRIBUTE_ALIGN(4)
int HIDKBD_SetupCmd(const USB_Setup_TypeDef *setup)
Handle USB setup commands. Implements HID class specific commands. This function must be called each ...
Definition: hidkbd.c:232
void HIDKBD_Init(HIDKBD_Init_t *init)
Initialize HID Keyboard driver.
Definition: hidkbd.c:187
void(* HIDKBD_SetReportFunc_t)(uint8_t report)
Callback function pointer for HID output reports. This function will be called by the driver each tim...
Definition: hidkbd.h:58
void HIDKBD_KeyboardEvent(HIDKBD_KeyReport_t *report)
Report a keyboard press/release event.
Definition: hidkbd.c:202
#define SL_MIN(a, b)
Macro for getting minimum value. No sideeffects, a and b are evaluated once only. ...
Definition: em_common.h:137
USB Human Interface Devices (HID) class keyboard driver.