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 */ |
48 | static 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 | }; |
60 | MODULE_DEVICE_TABLE(usb, appledisplay_table); |
61 | |
62 | /* Structure to hold all of our device specific stuff */ |
63 | struct 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 | |
75 | static atomic_t count_displays = ATOMIC_INIT(0); |
76 | |
77 | static 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(dwork: &pdata->work, delay: 0); |
111 | break; |
112 | case ACD_BTN_NONE: |
113 | default: |
114 | pdata->button_pressed = 0; |
115 | break; |
116 | } |
117 | |
118 | exit: |
119 | retval = usb_submit_urb(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 | |
126 | static int appledisplay_bl_update_status(struct backlight_device *bd) |
127 | { |
128 | struct appledisplay *pdata = bl_get_data(bl_dev: 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 | dev: 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 | index: 0, |
142 | data: pdata->msgdata, size: 2, |
143 | ACD_USB_TIMEOUT); |
144 | mutex_unlock(lock: &pdata->sysfslock); |
145 | |
146 | if (retval < 0) |
147 | return retval; |
148 | else |
149 | return 0; |
150 | } |
151 | |
152 | static int appledisplay_bl_get_brightness(struct backlight_device *bd) |
153 | { |
154 | struct appledisplay *pdata = bl_get_data(bl_dev: bd); |
155 | int retval, brightness; |
156 | |
157 | mutex_lock(&pdata->sysfslock); |
158 | retval = usb_control_msg( |
159 | dev: 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 | index: 0, |
165 | data: pdata->msgdata, size: 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(lock: &pdata->sysfslock); |
174 | |
175 | if (retval < 0) |
176 | return retval; |
177 | else |
178 | return brightness; |
179 | } |
180 | |
181 | static const struct backlight_ops appledisplay_bl_data = { |
182 | .get_brightness = appledisplay_bl_get_brightness, |
183 | .update_status = appledisplay_bl_update_status, |
184 | }; |
185 | |
186 | static 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(bd: 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(dwork: &pdata->work, HZ / 8); |
199 | } |
200 | |
201 | static 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(alt: iface->cur_altsetting, int_in: &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(size: 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(iso_packets: 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(dev: pdata->udev, ACD_URB_BUFFER_LEN, |
250 | GFP_KERNEL, dma: &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(urb: pdata->urb, dev: udev, |
259 | usb_rcvintpipe(udev, int_in_endpointAddr), |
260 | transfer_buffer: pdata->urbdata, ACD_URB_BUFFER_LEN, complete_fn: appledisplay_complete, |
261 | context: pdata, interval: 1); |
262 | pdata->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; |
263 | if (usb_submit_urb(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(buf: bl_name, size: sizeof(bl_name), fmt: "appledisplay%d" , |
271 | atomic_inc_return(v: &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(name: bl_name, NULL, devdata: pdata, |
276 | ops: &appledisplay_bl_data, props: &props); |
277 | if (IS_ERR(ptr: pdata->bd)) { |
278 | dev_err(&iface->dev, "Backlight registration failed\n" ); |
279 | retval = PTR_ERR(ptr: pdata->bd); |
280 | goto error; |
281 | } |
282 | |
283 | /* Try to get brightness */ |
284 | brightness = appledisplay_bl_get_brightness(bd: 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(intf: iface, data: pdata); |
298 | |
299 | printk(KERN_INFO "appledisplay: Apple Cinema Display connected\n" ); |
300 | |
301 | return 0; |
302 | |
303 | error: |
304 | if (pdata) { |
305 | if (pdata->urb) { |
306 | usb_kill_urb(urb: pdata->urb); |
307 | cancel_delayed_work_sync(dwork: &pdata->work); |
308 | usb_free_coherent(dev: pdata->udev, ACD_URB_BUFFER_LEN, |
309 | addr: pdata->urbdata, dma: pdata->urb->transfer_dma); |
310 | usb_free_urb(urb: pdata->urb); |
311 | } |
312 | if (!IS_ERR(ptr: pdata->bd)) |
313 | backlight_device_unregister(bd: pdata->bd); |
314 | kfree(objp: pdata->msgdata); |
315 | } |
316 | usb_set_intfdata(intf: iface, NULL); |
317 | kfree(objp: pdata); |
318 | return retval; |
319 | } |
320 | |
321 | static void appledisplay_disconnect(struct usb_interface *iface) |
322 | { |
323 | struct appledisplay *pdata = usb_get_intfdata(intf: iface); |
324 | |
325 | if (pdata) { |
326 | usb_kill_urb(urb: pdata->urb); |
327 | cancel_delayed_work_sync(dwork: &pdata->work); |
328 | backlight_device_unregister(bd: pdata->bd); |
329 | usb_free_coherent(dev: pdata->udev, ACD_URB_BUFFER_LEN, |
330 | addr: pdata->urbdata, dma: pdata->urb->transfer_dma); |
331 | usb_free_urb(urb: pdata->urb); |
332 | kfree(objp: pdata->msgdata); |
333 | kfree(objp: pdata); |
334 | } |
335 | |
336 | printk(KERN_INFO "appledisplay: Apple Cinema Display disconnected\n" ); |
337 | } |
338 | |
339 | static struct usb_driver appledisplay_driver = { |
340 | .name = "appledisplay" , |
341 | .probe = appledisplay_probe, |
342 | .disconnect = appledisplay_disconnect, |
343 | .id_table = appledisplay_table, |
344 | }; |
345 | module_usb_driver(appledisplay_driver); |
346 | |
347 | MODULE_AUTHOR("Michael Hanselmann" ); |
348 | MODULE_DESCRIPTION("Apple Cinema Display driver" ); |
349 | MODULE_LICENSE("GPL" ); |
350 | |