Author: thompsa
Date: Wed May 27 19:27:29 2009
New Revision: 192925
URL: http://svn.freebsd.org/changeset/base/192925

Log:
  Add support for the Apple MacBook Pro keyboard
  - add key mappings for fn keys
  - byte swapping for certain models
  - Fix leds for keyboards which require an ID byte for the HID output 
structures
  
  Submitted by: Hans Petter Selasky

Modified:
  head/sys/dev/usb/input/ukbd.c
  head/sys/dev/usb/usbhid.h

Modified: head/sys/dev/usb/input/ukbd.c
==============================================================================
--- head/sys/dev/usb/input/ukbd.c       Wed May 27 19:21:29 2009        
(r192924)
+++ head/sys/dev/usb/input/ukbd.c       Wed May 27 19:27:29 2009        
(r192925)
@@ -113,13 +113,13 @@ struct ukbd_data {
 #define        MOD_WIN_R       0x80
        uint8_t reserved;
        uint8_t keycode[UKBD_NKEYCODE];
-} __packed;
+       uint8_t exten[8];
+};
 
 enum {
        UKBD_INTR_DT,
-       UKBD_INTR_CS,
        UKBD_CTRL_LED,
-       UKBD_N_TRANSFER = 3,
+       UKBD_N_TRANSFER,
 };
 
 struct ukbd_softc {
@@ -127,6 +127,8 @@ struct ukbd_softc {
        keymap_t sc_keymap;
        accentmap_t sc_accmap;
        fkeytab_t sc_fkeymap[UKBD_NFKEY];
+       struct hid_location sc_loc_apple_eject;
+       struct hid_location sc_loc_apple_fn;
        struct usb2_callout sc_callout;
        struct ukbd_data sc_ndata;
        struct ukbd_data sc_odata;
@@ -144,12 +146,14 @@ struct ukbd_softc {
        uint32_t sc_buffered_char[2];
 #endif
        uint32_t sc_flags;              /* flags */
-#define        UKBD_FLAG_COMPOSE    0x0001
-#define        UKBD_FLAG_POLLING    0x0002
-#define        UKBD_FLAG_SET_LEDS   0x0004
-#define        UKBD_FLAG_INTR_STALL 0x0008
-#define        UKBD_FLAG_ATTACHED   0x0010
-#define        UKBD_FLAG_GONE       0x0020
+#define        UKBD_FLAG_COMPOSE       0x0001
+#define        UKBD_FLAG_POLLING       0x0002
+#define        UKBD_FLAG_SET_LEDS      0x0004
+#define        UKBD_FLAG_ATTACHED      0x0010
+#define        UKBD_FLAG_GONE          0x0020
+#define        UKBD_FLAG_APPLE_EJECT   0x0040
+#define        UKBD_FLAG_APPLE_FN      0x0080
+#define        UKBD_FLAG_APPLE_SWAP    0x0100
 
        int32_t sc_mode;                /* input mode (K_XLATE,K_RAW,K_CODE) */
        int32_t sc_state;               /* shift/lock key state */
@@ -162,6 +166,8 @@ struct ukbd_softc {
        uint8_t sc_leds;                /* store for async led requests */
        uint8_t sc_iface_index;
        uint8_t sc_iface_no;
+       uint8_t sc_kbd_id;
+       uint8_t sc_led_id;
 };
 
 #define        KEY_ERROR         0x01
@@ -451,16 +457,25 @@ ukbd_timeout(void *arg)
        usb2_callout_reset(&sc->sc_callout, hz / 40, &ukbd_timeout, sc);
 }
 
-static void
-ukbd_clear_stall_callback(struct usb2_xfer *xfer)
-{
-       struct ukbd_softc *sc = xfer->priv_sc;
-       struct usb2_xfer *xfer_other = sc->sc_xfer[UKBD_INTR_DT];
+static uint8_t
+ukbd_apple_fn(uint8_t keycode) {
+       switch (keycode) {
+       case 0x28: return 0x49; /* RETURN -> INSERT */
+       case 0x2a: return 0x4c; /* BACKSPACE -> DEL */
+       case 0x50: return 0x4a; /* LEFT ARROW -> HOME */
+       case 0x4f: return 0x4d; /* RIGHT ARROW -> END */
+       case 0x52: return 0x4b; /* UP ARROW -> PGUP */
+       case 0x51: return 0x4e; /* DOWN ARROW -> PGDN */
+       default: return keycode;
+       }
+}
 
-       if (usb2_clear_stall_callback(xfer, xfer_other)) {
-               DPRINTF("stall cleared\n");
-               sc->sc_flags &= ~UKBD_FLAG_INTR_STALL;
-               usb2_transfer_start(xfer_other);
+static uint8_t
+ukbd_apple_swap(uint8_t keycode) {
+       switch (keycode) {
+       case 0x35: return 0x64;
+       case 0x64: return 0x35;
+       default: return keycode;
        }
 }
 
@@ -470,18 +485,59 @@ ukbd_intr_callback(struct usb2_xfer *xfe
        struct ukbd_softc *sc = xfer->priv_sc;
        uint16_t len = xfer->actlen;
        uint8_t i;
+       uint8_t offset;
+       uint8_t id;
+       uint8_t apple_fn;
+       uint8_t apple_eject;
 
        switch (USB_GET_STATE(xfer)) {
        case USB_ST_TRANSFERRED:
                DPRINTF("actlen=%d bytes\n", len);
 
+               if (len == 0) {
+                       DPRINTF("zero length data\n");
+                       goto tr_setup;
+               }
+
+               if (sc->sc_kbd_id != 0) {
+                       /* check and remove HID ID byte */
+                       usb2_copy_out(xfer->frbuffers, 0, &id, 1);
+                       if (id != sc->sc_kbd_id) {
+                               DPRINTF("wrong HID ID\n");
+                               goto tr_setup;
+                       }
+                       offset = 1;
+                       len--;
+               } else {
+                       offset = 0;
+               }
+
                if (len > sizeof(sc->sc_ndata)) {
                        len = sizeof(sc->sc_ndata);
                }
+
                if (len) {
-                       bzero(&sc->sc_ndata, sizeof(sc->sc_ndata));
-                       usb2_copy_out(xfer->frbuffers, 0, &sc->sc_ndata, len);
+                       memset(&sc->sc_ndata, 0, sizeof(sc->sc_ndata));
+                       usb2_copy_out(xfer->frbuffers, offset, 
+                           &sc->sc_ndata, len);
+
+                       if ((sc->sc_flags & UKBD_FLAG_APPLE_EJECT) &&
+                           hid_get_data((uint8_t *)&sc->sc_ndata,
+                               len, &sc->sc_loc_apple_eject))
+                               apple_eject = 1;
+                       else
+                               apple_eject = 0;
+
+                       if ((sc->sc_flags & UKBD_FLAG_APPLE_FN) &&
+                           hid_get_data((uint8_t *)&sc->sc_ndata,
+                               len, &sc->sc_loc_apple_fn))
+                               apple_fn = 1;
+                       else
+                               apple_fn = 0;
 #if USB_DEBUG
+                       DPRINTF("apple_eject=%u apple_fn=%u\n",
+                           apple_eject, apple_fn);
+
                        if (sc->sc_ndata.modifiers) {
                                DPRINTF("mod: 0x%04x\n", 
sc->sc_ndata.modifiers);
                        }
@@ -491,30 +547,42 @@ ukbd_intr_callback(struct usb2_xfer *xfe
                                }
                        }
 #endif                                 /* USB_DEBUG */
+
+                       if (apple_fn) {
+                               for (i = 0; i < UKBD_NKEYCODE; i++) {
+                                       sc->sc_ndata.keycode[i] = 
+                                           
ukbd_apple_fn(sc->sc_ndata.keycode[i]);
+                               }
+                       }
+
+                       if (sc->sc_flags & UKBD_FLAG_APPLE_SWAP) {
+                               for (i = 0; i < UKBD_NKEYCODE; i++) {
+                                       sc->sc_ndata.keycode[i] = 
+                                           
ukbd_apple_swap(sc->sc_ndata.keycode[i]);
+                               }
+                       }
+
                        ukbd_interrupt(sc);
                }
        case USB_ST_SETUP:
-               if (sc->sc_flags & UKBD_FLAG_INTR_STALL) {
-                       usb2_transfer_start(sc->sc_xfer[UKBD_INTR_CS]);
-                       return;
-               }
+tr_setup:
                if (sc->sc_inputs < UKBD_IN_BUF_FULL) {
                        xfer->frlengths[0] = xfer->max_data_length;
                        usb2_start_hardware(xfer);
                } else {
                        DPRINTF("input queue is full!\n");
                }
-               return;
+               break;
 
        default:                        /* Error */
                DPRINTF("error=%s\n", usb2_errstr(xfer->error));
 
                if (xfer->error != USB_ERR_CANCELLED) {
                        /* try to clear stall first */
-                       sc->sc_flags |= UKBD_FLAG_INTR_STALL;
-                       usb2_transfer_start(sc->sc_xfer[UKBD_INTR_CS]);
+                       xfer->flags.stall_pipe = 1;
+                       goto tr_setup;
                }
-               return;
+               break;
        }
 }
 
@@ -522,7 +590,7 @@ static void
 ukbd_set_leds_callback(struct usb2_xfer *xfer)
 {
        struct usb2_device_request req;
-       uint8_t buf[1];
+       uint8_t buf[2];
        struct ukbd_softc *sc = xfer->priv_sc;
 
        switch (USB_GET_STATE(xfer)) {
@@ -536,15 +604,24 @@ ukbd_set_leds_callback(struct usb2_xfer 
                        USETW2(req.wValue, UHID_OUTPUT_REPORT, 0);
                        req.wIndex[0] = sc->sc_iface_no;
                        req.wIndex[1] = 0;
-                       USETW(req.wLength, 1);
+                       req.wLength[1] = 0;
 
-                       buf[0] = sc->sc_leds;
+                       /* check if we need to prefix an ID byte */
+                       if (sc->sc_led_id != 0) {
+                               req.wLength[0] = 2;
+                               buf[0] = sc->sc_led_id;
+                               buf[1] = sc->sc_leds;
+                       } else {
+                               req.wLength[0] = 1;
+                               buf[0] = sc->sc_leds;
+                               buf[1] = 0;
+                       }
 
                        usb2_copy_in(xfer->frbuffers, 0, &req, sizeof(req));
                        usb2_copy_in(xfer->frbuffers + 1, 0, buf, sizeof(buf));
 
                        xfer->frlengths[0] = sizeof(req);
-                       xfer->frlengths[1] = sizeof(buf);
+                       xfer->frlengths[1] = req.wLength[0];
                        xfer->nframes = 2;
                        usb2_start_hardware(xfer);
                }
@@ -567,21 +644,11 @@ static const struct usb2_config ukbd_con
                .callback = &ukbd_intr_callback,
        },
 
-       [UKBD_INTR_CS] = {
-               .type = UE_CONTROL,
-               .endpoint = 0x00,       /* Control pipe */
-               .direction = UE_DIR_ANY,
-               .bufsize = sizeof(struct usb2_device_request),
-               .callback = &ukbd_clear_stall_callback,
-               .timeout = 1000,        /* 1 second */
-               .interval = 50, /* 50ms */
-       },
-
        [UKBD_CTRL_LED] = {
                .type = UE_CONTROL,
                .endpoint = 0x00,       /* Control pipe */
                .direction = UE_DIR_ANY,
-               .bufsize = sizeof(struct usb2_device_request) + 1,
+               .bufsize = sizeof(struct usb2_device_request) + 8,
                .callback = &ukbd_set_leds_callback,
                .timeout = 1000,        /* 1 second */
        },
@@ -620,8 +687,11 @@ ukbd_attach(device_t dev)
        struct usb2_attach_arg *uaa = device_get_ivars(dev);
        int32_t unit = device_get_unit(dev);
        keyboard_t *kbd = &sc->sc_kbd;
+       void *hid_ptr = NULL;
        usb2_error_t err;
+       uint32_t flags;
        uint16_t n;
+       uint16_t hid_len;
 
        mtx_assert(&Giant, MA_OWNED);
 
@@ -669,6 +739,46 @@ ukbd_attach(device_t dev)
         */
        KBD_PROBE_DONE(kbd);
 
+       /* figure out if there is an ID byte in the data */
+       err = usb2_req_get_hid_desc(uaa->device, NULL, &hid_ptr,
+           &hid_len, M_TEMP, uaa->info.bIfaceIndex);
+       if (err == 0) {
+               uint8_t temp_id;
+
+               /* investigate if this is an Apple Keyboard */
+               if (hid_locate(hid_ptr, hid_len,
+                   HID_USAGE2(HUP_CONSUMER, HUG_APPLE_EJECT),
+                   hid_input, 0, &sc->sc_loc_apple_eject, &flags,
+                   &sc->sc_kbd_id)) {
+                       if (flags & HIO_VARIABLE)
+                               sc->sc_flags |= UKBD_FLAG_APPLE_EJECT | 
+                                   UKBD_FLAG_APPLE_SWAP;
+                       if (hid_locate(hid_ptr, hid_len,
+                           HID_USAGE2(0xFFFF, 0x0003),
+                           hid_input, 0, &sc->sc_loc_apple_fn, &flags,
+                           &temp_id)) {
+                               if (flags & HIO_VARIABLE)
+                                       sc->sc_flags |= UKBD_FLAG_APPLE_FN |
+                                           UKBD_FLAG_APPLE_SWAP;
+                               if (temp_id != sc->sc_kbd_id) {
+                                       DPRINTF("HID IDs mismatch\n");
+                               }
+                       }
+               } else {
+                       /* 
+                        * Assume the first HID ID contains the
+                        * keyboard data
+                        */
+                       hid_report_size(hid_ptr, hid_len,
+                           hid_input, &sc->sc_kbd_id);
+               }
+
+               /* investigate if we need an ID-byte for the leds */
+               hid_report_size(hid_ptr, hid_len, hid_output, &sc->sc_led_id);
+
+               free(hid_ptr, M_TEMP);
+       }
+
        /* ignore if SETIDLE fails, hence it is not crucial */
        err = usb2_req_set_idle(sc->sc_udev, &Giant, sc->sc_iface_index, 0, 0);
 

Modified: head/sys/dev/usb/usbhid.h
==============================================================================
--- head/sys/dev/usb/usbhid.h   Wed May 27 19:21:29 2009        (r192924)
+++ head/sys/dev/usb/usbhid.h   Wed May 27 19:27:29 2009        (r192925)
@@ -128,6 +128,7 @@ struct usb2_hid_descriptor {
 #define        HUG_SYSTEM_MENU_LEFT    0x008b
 #define        HUG_SYSTEM_MENU_UP      0x008c
 #define        HUG_SYSTEM_MENU_DOWN    0x008d
+#define        HUG_APPLE_EJECT         0x00b8
 
 /* Usages Digitizers */
 #define        HUD_UNDEFINED           0x0000
_______________________________________________
svn-src-all@freebsd.org mailing list
http://lists.freebsd.org/mailman/listinfo/svn-src-all
To unsubscribe, send any mail to "svn-src-all-unsubscr...@freebsd.org"

Reply via email to