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