linux/drivers/usb/misc/appledisplay.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Apple Cinema Display driver
   4 *
   5 * Copyright (C) 2006  Michael Hanselmann (linux-kernel@hansmi.ch)
   6 *
   7 * Thanks to Caskey L. Dickson for his work with acdctl.
   8 */
   9
  10#include <linux/kernel.h>
  11#include <linux/errno.h>
  12#include <linux/init.h>
  13#include <linux/module.h>
  14#include <linux/slab.h>
  15#include <linux/usb.h>
  16#include <linux/backlight.h>
  17#include <linux/timer.h>
  18#include <linux/workqueue.h>
  19#include <linux/atomic.h>
  20
  21#define APPLE_VENDOR_ID         0x05AC
  22
  23#define USB_REQ_GET_REPORT      0x01
  24#define USB_REQ_SET_REPORT      0x09
  25
  26#define ACD_USB_TIMEOUT         250
  27
  28#define ACD_USB_EDID            0x0302
  29#define ACD_USB_BRIGHTNESS      0x0310
  30
  31#define ACD_BTN_NONE            0
  32#define ACD_BTN_BRIGHT_UP       3
  33#define ACD_BTN_BRIGHT_DOWN     4
  34
  35#define ACD_URB_BUFFER_LEN      2
  36#define ACD_MSG_BUFFER_LEN      2
  37
  38#define APPLEDISPLAY_DEVICE(prod)                               \
  39        .match_flags = USB_DEVICE_ID_MATCH_DEVICE |             \
  40                       USB_DEVICE_ID_MATCH_INT_CLASS |          \
  41                       USB_DEVICE_ID_MATCH_INT_PROTOCOL,        \
  42        .idVendor = APPLE_VENDOR_ID,                            \
  43        .idProduct = (prod),                                    \
  44        .bInterfaceClass = USB_CLASS_HID,                       \
  45        .bInterfaceProtocol = 0x00
  46
  47/* table of devices that work with this driver */
  48static const struct usb_device_id appledisplay_table[] = {
  49        { APPLEDISPLAY_DEVICE(0x9218) },
  50        { APPLEDISPLAY_DEVICE(0x9219) },
  51        { APPLEDISPLAY_DEVICE(0x921c) },
  52        { APPLEDISPLAY_DEVICE(0x921d) },
  53        { APPLEDISPLAY_DEVICE(0x9222) },
  54        { APPLEDISPLAY_DEVICE(0x9226) },
  55        { APPLEDISPLAY_DEVICE(0x9236) },
  56
  57        /* Terminating entry */
  58        { }
  59};
  60MODULE_DEVICE_TABLE(usb, appledisplay_table);
  61
  62/* Structure to hold all of our device specific stuff */
  63struct appledisplay {
  64        struct usb_device *udev;        /* usb device */
  65        struct urb *urb;                /* usb request block */
  66        struct backlight_device *bd;    /* backlight device */
  67        u8 *urbdata;                    /* interrupt URB data buffer */
  68        u8 *msgdata;                    /* control message data buffer */
  69
  70        struct delayed_work work;
  71        int button_pressed;
  72        struct mutex sysfslock;         /* concurrent read and write */
  73};
  74
  75static atomic_t count_displays = ATOMIC_INIT(0);
  76
  77static void appledisplay_complete(struct urb *urb)
  78{
  79        struct appledisplay *pdata = urb->context;
  80        struct device *dev = &pdata->udev->dev;
  81        int status = urb->status;
  82        int retval;
  83
  84        switch (status) {
  85        case 0:
  86                /* success */
  87                break;
  88        case -EOVERFLOW:
  89                dev_err(dev,
  90                        "OVERFLOW with data length %d, actual length is %d\n",
  91                        ACD_URB_BUFFER_LEN, pdata->urb->actual_length);
  92                fallthrough;
  93        case -ECONNRESET:
  94        case -ENOENT:
  95        case -ESHUTDOWN:
  96                /* This urb is terminated, clean up */
  97                dev_dbg(dev, "%s - urb shuttingdown with status: %d\n",
  98                        __func__, status);
  99                return;
 100        default:
 101                dev_dbg(dev, "%s - nonzero urb status received: %d\n",
 102                        __func__, status);
 103                goto exit;
 104        }
 105
 106        switch(pdata->urbdata[1]) {
 107        case ACD_BTN_BRIGHT_UP:
 108        case ACD_BTN_BRIGHT_DOWN:
 109                pdata->button_pressed = 1;
 110                schedule_delayed_work(&pdata->work, 0);
 111                break;
 112        case ACD_BTN_NONE:
 113        default:
 114                pdata->button_pressed = 0;
 115                break;
 116        }
 117
 118exit:
 119        retval = usb_submit_urb(pdata->urb, GFP_ATOMIC);
 120        if (retval) {
 121                dev_err(dev, "%s - usb_submit_urb failed with result %d\n",
 122                        __func__, retval);
 123        }
 124}
 125
 126static int appledisplay_bl_update_status(struct backlight_device *bd)
 127{
 128        struct appledisplay *pdata = bl_get_data(bd);
 129        int retval;
 130
 131        mutex_lock(&pdata->sysfslock);
 132        pdata->msgdata[0] = 0x10;
 133        pdata->msgdata[1] = bd->props.brightness;
 134
 135        retval = usb_control_msg(
 136                pdata->udev,
 137                usb_sndctrlpipe(pdata->udev, 0),
 138                USB_REQ_SET_REPORT,
 139                USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
 140                ACD_USB_BRIGHTNESS,
 141                0,
 142                pdata->msgdata, 2,
 143                ACD_USB_TIMEOUT);
 144        mutex_unlock(&pdata->sysfslock);
 145
 146        if (retval < 0)
 147                return retval;
 148        else
 149                return 0;
 150}
 151
 152static int appledisplay_bl_get_brightness(struct backlight_device *bd)
 153{
 154        struct appledisplay *pdata = bl_get_data(bd);
 155        int retval, brightness;
 156
 157        mutex_lock(&pdata->sysfslock);
 158        retval = usb_control_msg(
 159                pdata->udev,
 160                usb_rcvctrlpipe(pdata->udev, 0),
 161                USB_REQ_GET_REPORT,
 162                USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
 163                ACD_USB_BRIGHTNESS,
 164                0,
 165                pdata->msgdata, 2,
 166                ACD_USB_TIMEOUT);
 167        if (retval < 2) {
 168                if (retval >= 0)
 169                        retval = -EMSGSIZE;
 170        } else {
 171                brightness = pdata->msgdata[1];
 172        }
 173        mutex_unlock(&pdata->sysfslock);
 174
 175        if (retval < 0)
 176                return retval;
 177        else
 178                return brightness;
 179}
 180
 181static const struct backlight_ops appledisplay_bl_data = {
 182        .get_brightness = appledisplay_bl_get_brightness,
 183        .update_status  = appledisplay_bl_update_status,
 184};
 185
 186static void appledisplay_work(struct work_struct *work)
 187{
 188        struct appledisplay *pdata =
 189                container_of(work, struct appledisplay, work.work);
 190        int retval;
 191
 192        retval = appledisplay_bl_get_brightness(pdata->bd);
 193        if (retval >= 0)
 194                pdata->bd->props.brightness = retval;
 195
 196        /* Poll again in about 125ms if there's still a button pressed */
 197        if (pdata->button_pressed)
 198                schedule_delayed_work(&pdata->work, HZ / 8);
 199}
 200
 201static int appledisplay_probe(struct usb_interface *iface,
 202        const struct usb_device_id *id)
 203{
 204        struct backlight_properties props;
 205        struct appledisplay *pdata;
 206        struct usb_device *udev = interface_to_usbdev(iface);
 207        struct usb_endpoint_descriptor *endpoint;
 208        int int_in_endpointAddr = 0;
 209        int retval, brightness;
 210        char bl_name[20];
 211
 212        /* set up the endpoint information */
 213        /* use only the first interrupt-in endpoint */
 214        retval = usb_find_int_in_endpoint(iface->cur_altsetting, &endpoint);
 215        if (retval) {
 216                dev_err(&iface->dev, "Could not find int-in endpoint\n");
 217                return retval;
 218        }
 219
 220        int_in_endpointAddr = endpoint->bEndpointAddress;
 221
 222        /* allocate memory for our device state and initialize it */
 223        pdata = kzalloc(sizeof(struct appledisplay), GFP_KERNEL);
 224        if (!pdata) {
 225                retval = -ENOMEM;
 226                goto error;
 227        }
 228
 229        pdata->udev = udev;
 230
 231        INIT_DELAYED_WORK(&pdata->work, appledisplay_work);
 232        mutex_init(&pdata->sysfslock);
 233
 234        /* Allocate buffer for control messages */
 235        pdata->msgdata = kmalloc(ACD_MSG_BUFFER_LEN, GFP_KERNEL);
 236        if (!pdata->msgdata) {
 237                retval = -ENOMEM;
 238                goto error;
 239        }
 240
 241        /* Allocate interrupt URB */
 242        pdata->urb = usb_alloc_urb(0, GFP_KERNEL);
 243        if (!pdata->urb) {
 244                retval = -ENOMEM;
 245                goto error;
 246        }
 247
 248        /* Allocate buffer for interrupt data */
 249        pdata->urbdata = usb_alloc_coherent(pdata->udev, ACD_URB_BUFFER_LEN,
 250                GFP_KERNEL, &pdata->urb->transfer_dma);
 251        if (!pdata->urbdata) {
 252                retval = -ENOMEM;
 253                dev_err(&iface->dev, "Allocating URB buffer failed\n");
 254                goto error;
 255        }
 256
 257        /* Configure interrupt URB */
 258        usb_fill_int_urb(pdata->urb, udev,
 259                usb_rcvintpipe(udev, int_in_endpointAddr),
 260                pdata->urbdata, ACD_URB_BUFFER_LEN, appledisplay_complete,
 261                pdata, 1);
 262        pdata->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
 263        if (usb_submit_urb(pdata->urb, GFP_KERNEL)) {
 264                retval = -EIO;
 265                dev_err(&iface->dev, "Submitting URB failed\n");
 266                goto error;
 267        }
 268
 269        /* Register backlight device */
 270        snprintf(bl_name, sizeof(bl_name), "appledisplay%d",
 271                atomic_inc_return(&count_displays) - 1);
 272        memset(&props, 0, sizeof(struct backlight_properties));
 273        props.type = BACKLIGHT_RAW;
 274        props.max_brightness = 0xff;
 275        pdata->bd = backlight_device_register(bl_name, NULL, pdata,
 276                                              &appledisplay_bl_data, &props);
 277        if (IS_ERR(pdata->bd)) {
 278                dev_err(&iface->dev, "Backlight registration failed\n");
 279                retval = PTR_ERR(pdata->bd);
 280                goto error;
 281        }
 282
 283        /* Try to get brightness */
 284        brightness = appledisplay_bl_get_brightness(pdata->bd);
 285
 286        if (brightness < 0) {
 287                retval = brightness;
 288                dev_err(&iface->dev,
 289                        "Error while getting initial brightness: %d\n", retval);
 290                goto error;
 291        }
 292
 293        /* Set brightness in backlight device */
 294        pdata->bd->props.brightness = brightness;
 295
 296        /* save our data pointer in the interface device */
 297        usb_set_intfdata(iface, pdata);
 298
 299        printk(KERN_INFO "appledisplay: Apple Cinema Display connected\n");
 300
 301        return 0;
 302
 303error:
 304        if (pdata) {
 305                if (pdata->urb) {
 306                        usb_kill_urb(pdata->urb);
 307                        cancel_delayed_work_sync(&pdata->work);
 308                        usb_free_coherent(pdata->udev, ACD_URB_BUFFER_LEN,
 309                                        pdata->urbdata, pdata->urb->transfer_dma);
 310                        usb_free_urb(pdata->urb);
 311                }
 312                if (!IS_ERR(pdata->bd))
 313                        backlight_device_unregister(pdata->bd);
 314                kfree(pdata->msgdata);
 315        }
 316        usb_set_intfdata(iface, NULL);
 317        kfree(pdata);
 318        return retval;
 319}
 320
 321static void appledisplay_disconnect(struct usb_interface *iface)
 322{
 323        struct appledisplay *pdata = usb_get_intfdata(iface);
 324
 325        if (pdata) {
 326                usb_kill_urb(pdata->urb);
 327                cancel_delayed_work_sync(&pdata->work);
 328                backlight_device_unregister(pdata->bd);
 329                usb_free_coherent(pdata->udev, ACD_URB_BUFFER_LEN,
 330                        pdata->urbdata, pdata->urb->transfer_dma);
 331                usb_free_urb(pdata->urb);
 332                kfree(pdata->msgdata);
 333                kfree(pdata);
 334        }
 335
 336        printk(KERN_INFO "appledisplay: Apple Cinema Display disconnected\n");
 337}
 338
 339static struct usb_driver appledisplay_driver = {
 340        .name           = "appledisplay",
 341        .probe          = appledisplay_probe,
 342        .disconnect     = appledisplay_disconnect,
 343        .id_table       = appledisplay_table,
 344};
 345module_usb_driver(appledisplay_driver);
 346
 347MODULE_AUTHOR("Michael Hanselmann");
 348MODULE_DESCRIPTION("Apple Cinema Display driver");
 349MODULE_LICENSE("GPL");
 350