]>
Commit | Line | Data |
---|---|---|
fb53440b JS |
1 | /* |
2 | * Asus OLED USB driver | |
3 | * | |
4 | * Copyright (C) 2007,2008 Jakub Schmidtke (sjakub@gmail.com) | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2 of the License, or | |
9 | * (at your option) any later version. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License | |
17 | * along with this program; if not, write to the Free Software | |
18 | * Foundation, Inc., | |
19 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
20 | * | |
21 | * | |
22 | * | |
23 | * This module is based on usbled and asus-laptop modules. | |
24 | * | |
25 | * | |
26 | * Asus OLED support is based on asusoled program taken from | |
27 | * https://launchpad.net/asusoled/. | |
28 | * | |
29 | * | |
30 | */ | |
31 | ||
32 | #include <linux/kernel.h> | |
33 | #include <linux/errno.h> | |
34 | #include <linux/init.h> | |
35 | #include <linux/slab.h> | |
36 | #include <linux/module.h> | |
37 | #include <linux/usb.h> | |
38 | #include <linux/platform_device.h> | |
39 | #include <linux/ctype.h> | |
40 | ||
41 | #define ASUS_OLED_VERSION "0.04-dev" | |
42 | #define ASUS_OLED_NAME "asus-oled" | |
43 | #define ASUS_OLED_UNDERSCORE_NAME "asus_oled" | |
44 | ||
45 | #define ASUS_OLED_ERROR "Asus OLED Display Error: " | |
46 | ||
47 | #define ASUS_OLED_STATIC 's' | |
48 | #define ASUS_OLED_ROLL 'r' | |
49 | #define ASUS_OLED_FLASH 'f' | |
50 | ||
51 | #define ASUS_OLED_MAX_WIDTH 1792 | |
52 | #define ASUS_OLED_DISP_HEIGHT 32 | |
53 | #define ASUS_OLED_PACKET_BUF_SIZE 256 | |
54 | ||
55 | MODULE_AUTHOR("Jakub Schmidtke, sjakub@gmail.com"); | |
56 | MODULE_DESCRIPTION("Asus OLED Driver v" ASUS_OLED_VERSION); | |
57 | MODULE_LICENSE("GPL"); | |
58 | ||
b0e5ca81 AH |
59 | static struct class *oled_class; |
60 | static int oled_num; | |
fb53440b | 61 | |
b0e5ca81 | 62 | static uint start_off; |
fb53440b JS |
63 | |
64 | module_param(start_off, uint, 0644); | |
65 | ||
1ff12a4a KG |
66 | MODULE_PARM_DESC(start_off, |
67 | "Set to 1 to switch off OLED display after it is attached"); | |
fb53440b | 68 | |
1ff12a4a | 69 | enum oled_pack_mode{ |
fb53440b JS |
70 | PACK_MODE_G1, |
71 | PACK_MODE_G50, | |
72 | PACK_MODE_LAST | |
1ff12a4a | 73 | }; |
fb53440b JS |
74 | |
75 | struct oled_dev_desc_str { | |
76 | uint16_t idVendor; | |
77 | uint16_t idProduct; | |
1ff12a4a KG |
78 | /* width of display */ |
79 | uint16_t devWidth; | |
80 | /* formula to be used while packing the picture */ | |
81 | enum oled_pack_mode packMode; | |
fb53440b JS |
82 | const char *devDesc; |
83 | }; | |
84 | ||
85 | /* table of devices that work with this driver */ | |
118015c4 | 86 | static struct usb_device_id id_table[] = { |
1ff12a4a KG |
87 | /* Asus G1/G2 (and variants)*/ |
88 | { USB_DEVICE(0x0b05, 0x1726) }, | |
89 | /* Asus G50V (and possibly others - G70? G71?)*/ | |
90 | { USB_DEVICE(0x0b05, 0x175b) }, | |
fb53440b JS |
91 | { }, |
92 | }; | |
93 | ||
94 | /* parameters of specific devices */ | |
118015c4 | 95 | static struct oled_dev_desc_str oled_dev_desc_table[] = { |
fb53440b JS |
96 | { 0x0b05, 0x1726, 128, PACK_MODE_G1, "G1/G2" }, |
97 | { 0x0b05, 0x175b, 256, PACK_MODE_G50, "G50" }, | |
98 | { }, | |
99 | }; | |
100 | ||
118015c4 | 101 | MODULE_DEVICE_TABLE(usb, id_table); |
fb53440b | 102 | |
fb53440b JS |
103 | struct asus_oled_header { |
104 | uint8_t magic1; | |
105 | uint8_t magic2; | |
106 | uint8_t flags; | |
107 | uint8_t value3; | |
108 | uint8_t buffer1; | |
109 | uint8_t buffer2; | |
110 | uint8_t value6; | |
111 | uint8_t value7; | |
112 | uint8_t value8; | |
113 | uint8_t padding2[7]; | |
114 | } __attribute((packed)); | |
115 | ||
116 | struct asus_oled_packet { | |
117 | struct asus_oled_header header; | |
118 | uint8_t bitmap[ASUS_OLED_PACKET_BUF_SIZE]; | |
119 | } __attribute((packed)); | |
120 | ||
121 | struct asus_oled_dev { | |
1ff12a4a | 122 | struct usb_device *udev; |
fb53440b JS |
123 | uint8_t pic_mode; |
124 | uint16_t dev_width; | |
1ff12a4a | 125 | enum oled_pack_mode pack_mode; |
fb53440b JS |
126 | size_t height; |
127 | size_t width; | |
128 | size_t x_shift; | |
129 | size_t y_shift; | |
130 | size_t buf_offs; | |
131 | uint8_t last_val; | |
132 | size_t buf_size; | |
133 | char *buf; | |
134 | uint8_t enabled; | |
135 | struct device *dev; | |
136 | }; | |
137 | ||
1ff12a4a KG |
138 | static void setup_packet_header(struct asus_oled_packet *packet, char flags, |
139 | char value3, char buffer1, char buffer2, char value6, | |
140 | char value7, char value8) | |
141 | { | |
142 | memset(packet, 0, sizeof(struct asus_oled_header)); | |
143 | packet->header.magic1 = 0x55; | |
144 | packet->header.magic2 = 0xaa; | |
145 | packet->header.flags = flags; | |
146 | packet->header.value3 = value3; | |
147 | packet->header.buffer1 = buffer1; | |
148 | packet->header.buffer2 = buffer2; | |
149 | packet->header.value6 = value6; | |
150 | packet->header.value7 = value7; | |
151 | packet->header.value8 = value8; | |
152 | } | |
153 | ||
fb53440b JS |
154 | static void enable_oled(struct asus_oled_dev *odev, uint8_t enabl) |
155 | { | |
156 | int a; | |
157 | int retval; | |
158 | int act_len; | |
1ff12a4a | 159 | struct asus_oled_packet *packet; |
fb53440b JS |
160 | |
161 | packet = kzalloc(sizeof(struct asus_oled_packet), GFP_KERNEL); | |
162 | ||
163 | if (!packet) { | |
164 | dev_err(&odev->udev->dev, "out of memory\n"); | |
165 | return; | |
166 | } | |
167 | ||
1ff12a4a | 168 | setup_packet_header(packet, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00); |
fb53440b | 169 | |
526fd20a AH |
170 | if (enabl) |
171 | packet->bitmap[0] = 0xaf; | |
172 | else | |
173 | packet->bitmap[0] = 0xae; | |
fb53440b | 174 | |
118015c4 | 175 | for (a = 0; a < 1; a++) { |
fb53440b JS |
176 | retval = usb_bulk_msg(odev->udev, |
177 | usb_sndbulkpipe(odev->udev, 2), | |
178 | packet, | |
179 | sizeof(struct asus_oled_header) + 1, | |
180 | &act_len, | |
181 | -1); | |
182 | ||
183 | if (retval) | |
184 | dev_dbg(&odev->udev->dev, "retval = %d\n", retval); | |
185 | } | |
186 | ||
187 | odev->enabled = enabl; | |
188 | ||
189 | kfree(packet); | |
190 | } | |
191 | ||
1ff12a4a KG |
192 | static ssize_t set_enabled(struct device *dev, struct device_attribute *attr, |
193 | const char *buf, size_t count) | |
fb53440b JS |
194 | { |
195 | struct usb_interface *intf = to_usb_interface(dev); | |
196 | struct asus_oled_dev *odev = usb_get_intfdata(intf); | |
1ff12a4a | 197 | int temp = strict_strtoul(buf, 10, NULL); |
fb53440b JS |
198 | |
199 | enable_oled(odev, temp); | |
200 | ||
201 | return count; | |
202 | } | |
203 | ||
1ff12a4a KG |
204 | static ssize_t class_set_enabled(struct device *device, |
205 | struct device_attribute *attr, | |
206 | const char *buf, size_t count) | |
fb53440b | 207 | { |
1ff12a4a KG |
208 | struct asus_oled_dev *odev = |
209 | (struct asus_oled_dev *) dev_get_drvdata(device); | |
fb53440b | 210 | |
1ff12a4a | 211 | int temp = strict_strtoul(buf, 10, NULL); |
fb53440b JS |
212 | |
213 | enable_oled(odev, temp); | |
214 | ||
215 | return count; | |
216 | } | |
217 | ||
1ff12a4a KG |
218 | static ssize_t get_enabled(struct device *dev, struct device_attribute *attr, |
219 | char *buf) | |
fb53440b JS |
220 | { |
221 | struct usb_interface *intf = to_usb_interface(dev); | |
222 | struct asus_oled_dev *odev = usb_get_intfdata(intf); | |
223 | ||
224 | return sprintf(buf, "%d\n", odev->enabled); | |
225 | } | |
226 | ||
1ff12a4a KG |
227 | static ssize_t class_get_enabled(struct device *device, |
228 | struct device_attribute *attr, char *buf) | |
fb53440b | 229 | { |
1ff12a4a KG |
230 | struct asus_oled_dev *odev = |
231 | (struct asus_oled_dev *) dev_get_drvdata(device); | |
fb53440b JS |
232 | |
233 | return sprintf(buf, "%d\n", odev->enabled); | |
234 | } | |
235 | ||
1ff12a4a KG |
236 | static void send_packets(struct usb_device *udev, |
237 | struct asus_oled_packet *packet, | |
238 | char *buf, uint8_t p_type, size_t p_num) | |
fb53440b JS |
239 | { |
240 | size_t i; | |
241 | int act_len; | |
242 | ||
243 | for (i = 0; i < p_num; i++) { | |
244 | int retval; | |
245 | ||
246 | switch (p_type) { | |
1ff12a4a KG |
247 | case ASUS_OLED_ROLL: |
248 | setup_packet_header(packet, 0x40, 0x80, p_num, | |
249 | i + 1, 0x00, 0x01, 0xff); | |
fb53440b | 250 | break; |
1ff12a4a KG |
251 | case ASUS_OLED_STATIC: |
252 | setup_packet_header(packet, 0x10 + i, 0x80, 0x01, | |
253 | 0x01, 0x00, 0x01, 0x00); | |
fb53440b | 254 | break; |
1ff12a4a KG |
255 | case ASUS_OLED_FLASH: |
256 | setup_packet_header(packet, 0x10 + i, 0x80, 0x01, | |
257 | 0x01, 0x00, 0x00, 0xff); | |
fb53440b JS |
258 | break; |
259 | } | |
260 | ||
1ff12a4a KG |
261 | memcpy(packet->bitmap, buf + (ASUS_OLED_PACKET_BUF_SIZE*i), |
262 | ASUS_OLED_PACKET_BUF_SIZE); | |
fb53440b | 263 | |
1ff12a4a KG |
264 | retval = usb_bulk_msg(udev, usb_sndctrlpipe(udev, 2), |
265 | packet, sizeof(struct asus_oled_packet), | |
266 | &act_len, -1); | |
fb53440b JS |
267 | |
268 | if (retval) | |
269 | dev_dbg(&udev->dev, "retval = %d\n", retval); | |
270 | } | |
271 | } | |
272 | ||
1ff12a4a KG |
273 | static void send_packet(struct usb_device *udev, |
274 | struct asus_oled_packet *packet, | |
275 | size_t offset, size_t len, char *buf, uint8_t b1, | |
276 | uint8_t b2, uint8_t b3, uint8_t b4, uint8_t b5, | |
277 | uint8_t b6) { | |
fb53440b JS |
278 | int retval; |
279 | int act_len; | |
280 | ||
1ff12a4a | 281 | setup_packet_header(packet, b1, b2, b3, b4, b5, b6, 0x00); |
fb53440b JS |
282 | memcpy(packet->bitmap, buf + offset, len); |
283 | ||
284 | retval = usb_bulk_msg(udev, | |
1ff12a4a KG |
285 | usb_sndctrlpipe(udev, 2), |
286 | packet, | |
287 | sizeof(struct asus_oled_packet), | |
288 | &act_len, | |
289 | -1); | |
fb53440b JS |
290 | |
291 | if (retval) | |
292 | dev_dbg(&udev->dev, "retval = %d\n", retval); | |
293 | } | |
294 | ||
295 | ||
1ff12a4a KG |
296 | static void send_packets_g50(struct usb_device *udev, |
297 | struct asus_oled_packet *packet, char *buf) | |
fb53440b | 298 | { |
1ff12a4a KG |
299 | send_packet(udev, packet, 0, 0x100, buf, |
300 | 0x10, 0x00, 0x02, 0x01, 0x00, 0x01); | |
301 | send_packet(udev, packet, 0x100, 0x080, buf, | |
302 | 0x10, 0x00, 0x02, 0x02, 0x80, 0x00); | |
303 | ||
304 | send_packet(udev, packet, 0x180, 0x100, buf, | |
305 | 0x11, 0x00, 0x03, 0x01, 0x00, 0x01); | |
306 | send_packet(udev, packet, 0x280, 0x100, buf, | |
307 | 0x11, 0x00, 0x03, 0x02, 0x00, 0x01); | |
308 | send_packet(udev, packet, 0x380, 0x080, buf, | |
309 | 0x11, 0x00, 0x03, 0x03, 0x80, 0x00); | |
fb53440b JS |
310 | } |
311 | ||
312 | ||
313 | static void send_data(struct asus_oled_dev *odev) | |
314 | { | |
315 | size_t packet_num = odev->buf_size / ASUS_OLED_PACKET_BUF_SIZE; | |
1ff12a4a | 316 | struct asus_oled_packet *packet; |
fb53440b JS |
317 | |
318 | packet = kzalloc(sizeof(struct asus_oled_packet), GFP_KERNEL); | |
319 | ||
320 | if (!packet) { | |
321 | dev_err(&odev->udev->dev, "out of memory\n"); | |
322 | return; | |
323 | } | |
324 | ||
118015c4 | 325 | if (odev->pack_mode == PACK_MODE_G1) { |
1ff12a4a KG |
326 | /* When sending roll-mode data the display updated only |
327 | first packet. I have no idea why, but when static picture | |
328 | is sent just before rolling picture everything works fine. */ | |
fb53440b | 329 | if (odev->pic_mode == ASUS_OLED_ROLL) |
1ff12a4a KG |
330 | send_packets(odev->udev, packet, odev->buf, |
331 | ASUS_OLED_STATIC, 2); | |
fb53440b | 332 | |
1ff12a4a | 333 | /* Only ROLL mode can use more than 2 packets.*/ |
fb53440b JS |
334 | if (odev->pic_mode != ASUS_OLED_ROLL && packet_num > 2) |
335 | packet_num = 2; | |
336 | ||
1ff12a4a KG |
337 | send_packets(odev->udev, packet, odev->buf, |
338 | odev->pic_mode, packet_num); | |
339 | } else if (odev->pack_mode == PACK_MODE_G50) { | |
fb53440b JS |
340 | send_packets_g50(odev->udev, packet, odev->buf); |
341 | } | |
342 | ||
343 | kfree(packet); | |
344 | } | |
345 | ||
346 | static int append_values(struct asus_oled_dev *odev, uint8_t val, size_t count) | |
347 | { | |
1ff12a4a KG |
348 | while (count-- > 0 && val) { |
349 | size_t x = odev->buf_offs % odev->width; | |
350 | size_t y = odev->buf_offs / odev->width; | |
351 | size_t i; | |
fb53440b | 352 | |
1ff12a4a KG |
353 | x += odev->x_shift; |
354 | y += odev->y_shift; | |
355 | ||
356 | switch (odev->pack_mode) { | |
357 | case PACK_MODE_G1: | |
358 | /* i = (x/128)*640 + 127 - x + (y/8)*128; | |
359 | This one for 128 is the same, but might be better | |
360 | for different widths? */ | |
361 | i = (x/odev->dev_width)*640 + | |
362 | odev->dev_width - 1 - x + | |
363 | (y/8)*odev->dev_width; | |
364 | break; | |
fb53440b | 365 | |
1ff12a4a KG |
366 | case PACK_MODE_G50: |
367 | i = (odev->dev_width - 1 - x)/8 + y*odev->dev_width/8; | |
368 | break; | |
fb53440b | 369 | |
1ff12a4a KG |
370 | default: |
371 | i = 0; | |
372 | printk(ASUS_OLED_ERROR "Unknown OLED Pack Mode: %d!\n", | |
373 | odev->pack_mode); | |
374 | break; | |
375 | } | |
fb53440b | 376 | |
1ff12a4a KG |
377 | if (i >= odev->buf_size) { |
378 | printk(ASUS_OLED_ERROR "Buffer overflow! Report a bug:" | |
379 | "offs: %d >= %d i: %d (x: %d y: %d)\n", | |
380 | (int) odev->buf_offs, (int) odev->buf_size, | |
381 | (int) i, (int) x, (int) y); | |
382 | return -EIO; | |
383 | } | |
fb53440b | 384 | |
1ff12a4a KG |
385 | switch (odev->pack_mode) { |
386 | case PACK_MODE_G1: | |
387 | odev->buf[i] &= ~(1<<(y%8)); | |
388 | break; | |
fb53440b | 389 | |
1ff12a4a KG |
390 | case PACK_MODE_G50: |
391 | odev->buf[i] &= ~(1<<(x%8)); | |
392 | break; | |
393 | ||
394 | default: | |
395 | /* cannot get here; stops gcc complaining*/ | |
396 | ; | |
fb53440b JS |
397 | } |
398 | ||
399 | odev->last_val = val; | |
400 | odev->buf_offs++; | |
401 | } | |
402 | ||
403 | return 0; | |
404 | } | |
405 | ||
1ff12a4a KG |
406 | static ssize_t odev_set_picture(struct asus_oled_dev *odev, |
407 | const char *buf, size_t count) | |
fb53440b JS |
408 | { |
409 | size_t offs = 0, max_offs; | |
410 | ||
526fd20a AH |
411 | if (count < 1) |
412 | return 0; | |
fb53440b | 413 | |
118015c4 | 414 | if (tolower(buf[0]) == 'b') { |
1ff12a4a | 415 | /* binary mode, set the entire memory*/ |
fb53440b | 416 | |
1ff12a4a | 417 | size_t i; |
fb53440b | 418 | |
1ff12a4a | 419 | odev->buf_size = (odev->dev_width * ASUS_OLED_DISP_HEIGHT) / 8; |
fb53440b | 420 | |
1ff12a4a KG |
421 | kfree(odev->buf); |
422 | odev->buf = kmalloc(odev->buf_size, GFP_KERNEL); | |
fb53440b | 423 | |
1ff12a4a | 424 | memset(odev->buf, 0xff, odev->buf_size); |
fb53440b | 425 | |
1ff12a4a KG |
426 | for (i = 1; i < count && i <= 32 * 32; i++) { |
427 | odev->buf[i-1] = buf[i]; | |
428 | odev->buf_offs = i-1; | |
429 | } | |
fb53440b | 430 | |
1ff12a4a KG |
431 | odev->width = odev->dev_width / 8; |
432 | odev->height = ASUS_OLED_DISP_HEIGHT; | |
433 | odev->x_shift = 0; | |
434 | odev->y_shift = 0; | |
435 | odev->last_val = 0; | |
fb53440b | 436 | |
1ff12a4a | 437 | send_data(odev); |
fb53440b | 438 | |
1ff12a4a | 439 | return count; |
fb53440b JS |
440 | } |
441 | ||
442 | if (buf[0] == '<') { | |
443 | size_t i; | |
444 | size_t w = 0, h = 0; | |
445 | size_t w_mem, h_mem; | |
446 | ||
1ff12a4a | 447 | if (count < 10 || buf[2] != ':') |
fb53440b | 448 | goto error_header; |
1ff12a4a | 449 | |
fb53440b | 450 | |
118015c4 | 451 | switch (tolower(buf[1])) { |
1ff12a4a KG |
452 | case ASUS_OLED_STATIC: |
453 | case ASUS_OLED_ROLL: | |
454 | case ASUS_OLED_FLASH: | |
455 | odev->pic_mode = buf[1]; | |
456 | break; | |
457 | default: | |
458 | printk(ASUS_OLED_ERROR "Wrong picture mode: '%c'.\n", | |
459 | buf[1]); | |
460 | return -EIO; | |
461 | break; | |
fb53440b JS |
462 | } |
463 | ||
464 | for (i = 3; i < count; ++i) { | |
465 | if (buf[i] >= '0' && buf[i] <= '9') { | |
466 | w = 10*w + (buf[i] - '0'); | |
467 | ||
526fd20a AH |
468 | if (w > ASUS_OLED_MAX_WIDTH) |
469 | goto error_width; | |
1ff12a4a | 470 | } else if (tolower(buf[i]) == 'x') { |
526fd20a | 471 | break; |
1ff12a4a | 472 | } else { |
526fd20a | 473 | goto error_width; |
1ff12a4a | 474 | } |
fb53440b JS |
475 | } |
476 | ||
477 | for (++i; i < count; ++i) { | |
478 | if (buf[i] >= '0' && buf[i] <= '9') { | |
479 | h = 10*h + (buf[i] - '0'); | |
480 | ||
526fd20a AH |
481 | if (h > ASUS_OLED_DISP_HEIGHT) |
482 | goto error_height; | |
1ff12a4a | 483 | } else if (tolower(buf[i]) == '>') { |
526fd20a | 484 | break; |
1ff12a4a | 485 | } else { |
526fd20a | 486 | goto error_height; |
1ff12a4a | 487 | } |
fb53440b JS |
488 | } |
489 | ||
526fd20a AH |
490 | if (w < 1 || w > ASUS_OLED_MAX_WIDTH) |
491 | goto error_width; | |
fb53440b | 492 | |
526fd20a AH |
493 | if (h < 1 || h > ASUS_OLED_DISP_HEIGHT) |
494 | goto error_height; | |
fb53440b | 495 | |
526fd20a AH |
496 | if (i >= count || buf[i] != '>') |
497 | goto error_header; | |
fb53440b JS |
498 | |
499 | offs = i+1; | |
500 | ||
501 | if (w % (odev->dev_width) != 0) | |
502 | w_mem = (w/(odev->dev_width) + 1)*(odev->dev_width); | |
503 | else | |
504 | w_mem = w; | |
505 | ||
506 | if (h < ASUS_OLED_DISP_HEIGHT) | |
507 | h_mem = ASUS_OLED_DISP_HEIGHT; | |
508 | else | |
509 | h_mem = h; | |
510 | ||
511 | odev->buf_size = w_mem * h_mem / 8; | |
512 | ||
1ff12a4a | 513 | kfree(odev->buf); |
fb53440b JS |
514 | odev->buf = kmalloc(odev->buf_size, GFP_KERNEL); |
515 | ||
516 | if (odev->buf == NULL) { | |
517 | odev->buf_size = 0; | |
518 | printk(ASUS_OLED_ERROR "Out of memory!\n"); | |
519 | return -ENOMEM; | |
520 | } | |
521 | ||
522 | memset(odev->buf, 0xff, odev->buf_size); | |
523 | ||
524 | odev->buf_offs = 0; | |
525 | odev->width = w; | |
526 | odev->height = h; | |
527 | odev->x_shift = 0; | |
528 | odev->y_shift = 0; | |
529 | odev->last_val = 0; | |
530 | ||
531 | if (odev->pic_mode == ASUS_OLED_FLASH) { | |
532 | if (h < ASUS_OLED_DISP_HEIGHT/2) | |
533 | odev->y_shift = (ASUS_OLED_DISP_HEIGHT/2 - h)/2; | |
1ff12a4a | 534 | } else { |
fb53440b JS |
535 | if (h < ASUS_OLED_DISP_HEIGHT) |
536 | odev->y_shift = (ASUS_OLED_DISP_HEIGHT - h)/2; | |
537 | } | |
538 | ||
539 | if (w < (odev->dev_width)) | |
540 | odev->x_shift = ((odev->dev_width) - w)/2; | |
541 | } | |
542 | ||
543 | max_offs = odev->width * odev->height; | |
544 | ||
545 | while (offs < count && odev->buf_offs < max_offs) { | |
0d99b6eb | 546 | int ret = 0; |
fb53440b JS |
547 | |
548 | if (buf[offs] == '1' || buf[offs] == '#') { | |
94aa5b44 AH |
549 | ret = append_values(odev, 1, 1); |
550 | if (ret < 0) | |
526fd20a | 551 | return ret; |
1ff12a4a | 552 | } else if (buf[offs] == '0' || buf[offs] == ' ') { |
94aa5b44 AH |
553 | ret = append_values(odev, 0, 1); |
554 | if (ret < 0) | |
526fd20a | 555 | return ret; |
1ff12a4a KG |
556 | } else if (buf[offs] == '\n') { |
557 | /* New line detected. Lets assume, that all characters | |
558 | till the end of the line were equal to the last | |
559 | character in this line.*/ | |
fb53440b | 560 | if (odev->buf_offs % odev->width != 0) |
94aa5b44 | 561 | ret = append_values(odev, odev->last_val, |
1ff12a4a KG |
562 | odev->width - |
563 | (odev->buf_offs % | |
564 | odev->width)); | |
565 | if (ret < 0) | |
566 | return ret; | |
fb53440b JS |
567 | } |
568 | ||
569 | offs++; | |
570 | } | |
571 | ||
526fd20a AH |
572 | if (odev->buf_offs >= max_offs) |
573 | send_data(odev); | |
fb53440b JS |
574 | |
575 | return count; | |
576 | ||
577 | error_width: | |
578 | printk(ASUS_OLED_ERROR "Wrong picture width specified.\n"); | |
579 | return -EIO; | |
580 | ||
581 | error_height: | |
582 | printk(ASUS_OLED_ERROR "Wrong picture height specified.\n"); | |
583 | return -EIO; | |
584 | ||
585 | error_header: | |
586 | printk(ASUS_OLED_ERROR "Wrong picture header.\n"); | |
587 | return -EIO; | |
588 | } | |
589 | ||
1ff12a4a KG |
590 | static ssize_t set_picture(struct device *dev, struct device_attribute *attr, |
591 | const char *buf, size_t count) | |
fb53440b JS |
592 | { |
593 | struct usb_interface *intf = to_usb_interface(dev); | |
594 | ||
595 | return odev_set_picture(usb_get_intfdata(intf), buf, count); | |
596 | } | |
597 | ||
1ff12a4a KG |
598 | static ssize_t class_set_picture(struct device *device, |
599 | struct device_attribute *attr, | |
600 | const char *buf, size_t count) | |
fb53440b | 601 | { |
1ff12a4a KG |
602 | return odev_set_picture((struct asus_oled_dev *) |
603 | dev_get_drvdata(device), buf, count); | |
fb53440b JS |
604 | } |
605 | ||
606 | #define ASUS_OLED_DEVICE_ATTR(_file) dev_attr_asus_oled_##_file | |
607 | ||
1ff12a4a KG |
608 | static DEVICE_ATTR(asus_oled_enabled, S_IWUGO | S_IRUGO, |
609 | get_enabled, set_enabled); | |
fb53440b JS |
610 | static DEVICE_ATTR(asus_oled_picture, S_IWUGO , NULL, set_picture); |
611 | ||
1ff12a4a KG |
612 | static DEVICE_ATTR(enabled, S_IWUGO | S_IRUGO, |
613 | class_get_enabled, class_set_enabled); | |
fb53440b JS |
614 | static DEVICE_ATTR(picture, S_IWUGO, NULL, class_set_picture); |
615 | ||
1ff12a4a KG |
616 | static int asus_oled_probe(struct usb_interface *interface, |
617 | const struct usb_device_id *id) | |
fb53440b JS |
618 | { |
619 | struct usb_device *udev = interface_to_usbdev(interface); | |
620 | struct asus_oled_dev *odev = NULL; | |
621 | int retval = -ENOMEM; | |
622 | uint16_t dev_width = 0; | |
1ff12a4a KG |
623 | enum oled_pack_mode pack_mode = PACK_MODE_LAST; |
624 | const struct oled_dev_desc_str *dev_desc = oled_dev_desc_table; | |
ccdb2459 | 625 | const char *desc = NULL; |
fb53440b | 626 | |
ccdb2459 | 627 | if (!id) { |
1ff12a4a | 628 | /* Even possible? Just to make sure...*/ |
fb53440b JS |
629 | dev_err(&interface->dev, "No usb_device_id provided!\n"); |
630 | return -ENODEV; | |
631 | } | |
632 | ||
1ff12a4a | 633 | for (; dev_desc->idVendor; dev_desc++) { |
fb53440b | 634 | if (dev_desc->idVendor == id->idVendor |
1ff12a4a | 635 | && dev_desc->idProduct == id->idProduct) { |
fb53440b JS |
636 | dev_width = dev_desc->devWidth; |
637 | desc = dev_desc->devDesc; | |
638 | pack_mode = dev_desc->packMode; | |
639 | break; | |
640 | } | |
641 | } | |
642 | ||
118015c4 | 643 | if (!desc || dev_width < 1 || pack_mode == PACK_MODE_LAST) { |
1ff12a4a KG |
644 | dev_err(&interface->dev, |
645 | "Missing or incomplete device description!\n"); | |
fb53440b JS |
646 | return -ENODEV; |
647 | } | |
648 | ||
649 | odev = kzalloc(sizeof(struct asus_oled_dev), GFP_KERNEL); | |
650 | ||
651 | if (odev == NULL) { | |
652 | dev_err(&interface->dev, "Out of memory\n"); | |
653 | return -ENOMEM; | |
654 | } | |
655 | ||
656 | odev->udev = usb_get_dev(udev); | |
657 | odev->pic_mode = ASUS_OLED_STATIC; | |
658 | odev->dev_width = dev_width; | |
659 | odev->pack_mode = pack_mode; | |
660 | odev->height = 0; | |
661 | odev->width = 0; | |
662 | odev->x_shift = 0; | |
663 | odev->y_shift = 0; | |
664 | odev->buf_offs = 0; | |
665 | odev->buf_size = 0; | |
666 | odev->last_val = 0; | |
667 | odev->buf = NULL; | |
668 | odev->enabled = 1; | |
ccdb2459 | 669 | odev->dev = NULL; |
fb53440b | 670 | |
118015c4 | 671 | usb_set_intfdata(interface, odev); |
fb53440b | 672 | |
1ff12a4a KG |
673 | retval = device_create_file(&interface->dev, |
674 | &ASUS_OLED_DEVICE_ATTR(enabled)); | |
94aa5b44 | 675 | if (retval) |
fb53440b | 676 | goto err_files; |
fb53440b | 677 | |
1ff12a4a KG |
678 | retval = device_create_file(&interface->dev, |
679 | &ASUS_OLED_DEVICE_ATTR(picture)); | |
94aa5b44 | 680 | if (retval) |
fb53440b | 681 | goto err_files; |
fb53440b | 682 | |
118015c4 | 683 | odev->dev = device_create(oled_class, &interface->dev, MKDEV(0, 0), |
1ff12a4a | 684 | NULL, "oled_%d", ++oled_num); |
fb53440b JS |
685 | |
686 | if (IS_ERR(odev->dev)) { | |
687 | retval = PTR_ERR(odev->dev); | |
688 | goto err_files; | |
689 | } | |
690 | ||
691 | dev_set_drvdata(odev->dev, odev); | |
692 | ||
94aa5b44 AH |
693 | retval = device_create_file(odev->dev, &dev_attr_enabled); |
694 | if (retval) | |
fb53440b | 695 | goto err_class_enabled; |
fb53440b | 696 | |
94aa5b44 AH |
697 | retval = device_create_file(odev->dev, &dev_attr_picture); |
698 | if (retval) | |
fb53440b | 699 | goto err_class_picture; |
fb53440b | 700 | |
1ff12a4a KG |
701 | dev_info(&interface->dev, |
702 | "Attached Asus OLED device: %s [width %u, pack_mode %d]\n", | |
703 | desc, odev->dev_width, odev->pack_mode); | |
fb53440b JS |
704 | |
705 | if (start_off) | |
706 | enable_oled(odev, 0); | |
707 | ||
708 | return 0; | |
709 | ||
710 | err_class_picture: | |
711 | device_remove_file(odev->dev, &dev_attr_picture); | |
712 | ||
713 | err_class_enabled: | |
714 | device_remove_file(odev->dev, &dev_attr_enabled); | |
715 | device_unregister(odev->dev); | |
716 | ||
717 | err_files: | |
718 | device_remove_file(&interface->dev, &ASUS_OLED_DEVICE_ATTR(enabled)); | |
719 | device_remove_file(&interface->dev, &ASUS_OLED_DEVICE_ATTR(picture)); | |
720 | ||
118015c4 | 721 | usb_set_intfdata(interface, NULL); |
fb53440b JS |
722 | usb_put_dev(odev->udev); |
723 | kfree(odev); | |
724 | ||
725 | return retval; | |
726 | } | |
727 | ||
728 | static void asus_oled_disconnect(struct usb_interface *interface) | |
729 | { | |
730 | struct asus_oled_dev *odev; | |
731 | ||
118015c4 AH |
732 | odev = usb_get_intfdata(interface); |
733 | usb_set_intfdata(interface, NULL); | |
fb53440b JS |
734 | |
735 | device_remove_file(odev->dev, &dev_attr_picture); | |
736 | device_remove_file(odev->dev, &dev_attr_enabled); | |
737 | device_unregister(odev->dev); | |
738 | ||
118015c4 AH |
739 | device_remove_file(&interface->dev, &ASUS_OLED_DEVICE_ATTR(picture)); |
740 | device_remove_file(&interface->dev, &ASUS_OLED_DEVICE_ATTR(enabled)); | |
fb53440b JS |
741 | |
742 | usb_put_dev(odev->udev); | |
743 | ||
1ff12a4a | 744 | kfree(odev->buf); |
fb53440b JS |
745 | |
746 | kfree(odev); | |
747 | ||
748 | dev_info(&interface->dev, "Disconnected Asus OLED device\n"); | |
749 | } | |
750 | ||
751 | static struct usb_driver oled_driver = { | |
752 | .name = ASUS_OLED_NAME, | |
753 | .probe = asus_oled_probe, | |
754 | .disconnect = asus_oled_disconnect, | |
755 | .id_table = id_table, | |
756 | }; | |
757 | ||
758 | static ssize_t version_show(struct class *dev, char *buf) | |
759 | { | |
1ff12a4a KG |
760 | return sprintf(buf, ASUS_OLED_UNDERSCORE_NAME " %s\n", |
761 | ASUS_OLED_VERSION); | |
fb53440b JS |
762 | } |
763 | ||
764 | static CLASS_ATTR(version, S_IRUGO, version_show, NULL); | |
765 | ||
766 | static int __init asus_oled_init(void) | |
767 | { | |
768 | int retval = 0; | |
769 | oled_class = class_create(THIS_MODULE, ASUS_OLED_UNDERSCORE_NAME); | |
770 | ||
771 | if (IS_ERR(oled_class)) { | |
772 | err("Error creating " ASUS_OLED_UNDERSCORE_NAME " class"); | |
773 | return PTR_ERR(oled_class); | |
774 | } | |
775 | ||
94aa5b44 AH |
776 | retval = class_create_file(oled_class, &class_attr_version); |
777 | if (retval) { | |
fb53440b JS |
778 | err("Error creating class version file"); |
779 | goto error; | |
780 | } | |
781 | ||
782 | retval = usb_register(&oled_driver); | |
783 | ||
784 | if (retval) { | |
785 | err("usb_register failed. Error number %d", retval); | |
786 | goto error; | |
787 | } | |
788 | ||
789 | return retval; | |
790 | ||
791 | error: | |
792 | class_destroy(oled_class); | |
793 | return retval; | |
794 | } | |
795 | ||
796 | static void __exit asus_oled_exit(void) | |
797 | { | |
798 | class_remove_file(oled_class, &class_attr_version); | |
799 | class_destroy(oled_class); | |
800 | ||
801 | usb_deregister(&oled_driver); | |
802 | } | |
803 | ||
118015c4 AH |
804 | module_init(asus_oled_init); |
805 | module_exit(asus_oled_exit); | |
fb53440b | 806 |