diff options
author | Bruno Prémont <bonbons@linux-vserver.org> | 2012-07-30 15:38:28 -0400 |
---|---|---|
committer | Jiri Kosina <jkosina@suse.cz> | 2012-08-15 04:08:57 -0400 |
commit | fabdbf2fd22fa170b4c5340dbdda5c8cd88fb205 (patch) | |
tree | 25d6b1bbfee02aadca929130cb0d35bf8e8f9c17 | |
parent | e8ff13b0bf88b5e696323a1eec877783d965b3c6 (diff) |
HID: picoLCD: split driver code
In order to make code maintenance easier, split the vairous
functions into individial files (this removes a bunch of #ifdefs).
Signed-off-by: Bruno Prémont <bonbons@linux-vserver.org>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
-rw-r--r-- | drivers/hid/Kconfig | 8 | ||||
-rw-r--r-- | drivers/hid/Makefile | 20 | ||||
-rw-r--r-- | drivers/hid/hid-picolcd.c | 2748 | ||||
-rw-r--r-- | drivers/hid/hid-picolcd.h | 306 | ||||
-rw-r--r-- | drivers/hid/hid-picolcd_backlight.c | 121 | ||||
-rw-r--r-- | drivers/hid/hid-picolcd_cir.c | 61 | ||||
-rw-r--r-- | drivers/hid/hid-picolcd_core.c | 704 | ||||
-rw-r--r-- | drivers/hid/hid-picolcd_debugfs.c | 898 | ||||
-rw-r--r-- | drivers/hid/hid-picolcd_fb.c | 673 | ||||
-rw-r--r-- | drivers/hid/hid-picolcd_lcd.c | 106 | ||||
-rw-r--r-- | drivers/hid/hid-picolcd_leds.c | 172 |
11 files changed, 3069 insertions, 2748 deletions
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index fbf49503508d..a451cdb0841a 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig | |||
@@ -527,6 +527,14 @@ config HID_PICOLCD_LEDS | |||
527 | ---help--- | 527 | ---help--- |
528 | Provide access to PicoLCD's GPO pins via leds class. | 528 | Provide access to PicoLCD's GPO pins via leds class. |
529 | 529 | ||
530 | config HID_PICOLCD_CIR | ||
531 | bool "CIR via RC class" if EXPERT | ||
532 | default !EXPERT | ||
533 | depends on HID_PICOLCD | ||
534 | depends on HID_PICOLCD=RC_CORE || RC_CORE=y | ||
535 | ---help--- | ||
536 | Provide access to PicoLCD's CIR interface via remote control (LIRC). | ||
537 | |||
530 | config HID_PRIMAX | 538 | config HID_PRIMAX |
531 | tristate "Primax non-fully HID-compliant devices" | 539 | tristate "Primax non-fully HID-compliant devices" |
532 | depends on USB_HID | 540 | depends on USB_HID |
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index f975485f88b2..3684750c976e 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile | |||
@@ -69,6 +69,26 @@ obj-$(CONFIG_HID_PRODIKEYS) += hid-prodikeys.o | |||
69 | obj-$(CONFIG_HID_PANTHERLORD) += hid-pl.o | 69 | obj-$(CONFIG_HID_PANTHERLORD) += hid-pl.o |
70 | obj-$(CONFIG_HID_PETALYNX) += hid-petalynx.o | 70 | obj-$(CONFIG_HID_PETALYNX) += hid-petalynx.o |
71 | obj-$(CONFIG_HID_PICOLCD) += hid-picolcd.o | 71 | obj-$(CONFIG_HID_PICOLCD) += hid-picolcd.o |
72 | hid-picolcd-y += hid-picolcd_core.o | ||
73 | ifdef CONFIG_HID_PICOLCD_FB | ||
74 | hid-picolcd-y += hid-picolcd_fb.o | ||
75 | endif | ||
76 | ifdef CONFIG_HID_PICOLCD_BACKLIGHT | ||
77 | hid-picolcd-y += hid-picolcd_backlight.o | ||
78 | endif | ||
79 | ifdef CONFIG_HID_PICOLCD_LCD | ||
80 | hid-picolcd-y += hid-picolcd_lcd.o | ||
81 | endif | ||
82 | ifdef CONFIG_HID_PICOLCD_LEDS | ||
83 | hid-picolcd-y += hid-picolcd_leds.o | ||
84 | endif | ||
85 | ifdef CONFIG_HID_PICOLCD_CIR | ||
86 | hid-picolcd-y += hid-picolcd_cir.o | ||
87 | endif | ||
88 | ifdef CONFIG_DEBUG_FS | ||
89 | hid-picolcd-y += hid-picolcd_debugfs.o | ||
90 | endif | ||
91 | |||
72 | obj-$(CONFIG_HID_PRIMAX) += hid-primax.o | 92 | obj-$(CONFIG_HID_PRIMAX) += hid-primax.o |
73 | obj-$(CONFIG_HID_ROCCAT) += hid-roccat.o hid-roccat-common.o \ | 93 | obj-$(CONFIG_HID_ROCCAT) += hid-roccat.o hid-roccat-common.o \ |
74 | hid-roccat-arvo.o hid-roccat-isku.o hid-roccat-kone.o \ | 94 | hid-roccat-arvo.o hid-roccat-isku.o hid-roccat-kone.o \ |
diff --git a/drivers/hid/hid-picolcd.c b/drivers/hid/hid-picolcd.c deleted file mode 100644 index 27c8ebdfad01..000000000000 --- a/drivers/hid/hid-picolcd.c +++ /dev/null | |||
@@ -1,2748 +0,0 @@ | |||
1 | /*************************************************************************** | ||
2 | * Copyright (C) 2010 by Bruno Prémont <bonbons@linux-vserver.org> * | ||
3 | * * | ||
4 | * Based on Logitech G13 driver (v0.4) * | ||
5 | * Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu> * | ||
6 | * * | ||
7 | * This program is free software: you can redistribute it and/or modify * | ||
8 | * it under the terms of the GNU General Public License as published by * | ||
9 | * the Free Software Foundation, version 2 of the License. * | ||
10 | * * | ||
11 | * This driver is distributed in the hope that it will be useful, but * | ||
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of * | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * | ||
14 | * General Public License for more details. * | ||
15 | * * | ||
16 | * You should have received a copy of the GNU General Public License * | ||
17 | * along with this software. If not see <http://www.gnu.org/licenses/>. * | ||
18 | ***************************************************************************/ | ||
19 | |||
20 | #include <linux/hid.h> | ||
21 | #include <linux/hid-debug.h> | ||
22 | #include <linux/input.h> | ||
23 | #include "hid-ids.h" | ||
24 | #include "usbhid/usbhid.h" | ||
25 | #include <linux/usb.h> | ||
26 | |||
27 | #include <linux/fb.h> | ||
28 | #include <linux/vmalloc.h> | ||
29 | #include <linux/backlight.h> | ||
30 | #include <linux/lcd.h> | ||
31 | |||
32 | #include <linux/leds.h> | ||
33 | |||
34 | #include <linux/seq_file.h> | ||
35 | #include <linux/debugfs.h> | ||
36 | |||
37 | #include <linux/completion.h> | ||
38 | #include <linux/uaccess.h> | ||
39 | #include <linux/module.h> | ||
40 | |||
41 | #define PICOLCD_NAME "PicoLCD (graphic)" | ||
42 | |||
43 | /* Report numbers */ | ||
44 | #define REPORT_ERROR_CODE 0x10 /* LCD: IN[16] */ | ||
45 | #define ERR_SUCCESS 0x00 | ||
46 | #define ERR_PARAMETER_MISSING 0x01 | ||
47 | #define ERR_DATA_MISSING 0x02 | ||
48 | #define ERR_BLOCK_READ_ONLY 0x03 | ||
49 | #define ERR_BLOCK_NOT_ERASABLE 0x04 | ||
50 | #define ERR_BLOCK_TOO_BIG 0x05 | ||
51 | #define ERR_SECTION_OVERFLOW 0x06 | ||
52 | #define ERR_INVALID_CMD_LEN 0x07 | ||
53 | #define ERR_INVALID_DATA_LEN 0x08 | ||
54 | #define REPORT_KEY_STATE 0x11 /* LCD: IN[2] */ | ||
55 | #define REPORT_IR_DATA 0x21 /* LCD: IN[63] */ | ||
56 | #define REPORT_EE_DATA 0x32 /* LCD: IN[63] */ | ||
57 | #define REPORT_MEMORY 0x41 /* LCD: IN[63] */ | ||
58 | #define REPORT_LED_STATE 0x81 /* LCD: OUT[1] */ | ||
59 | #define REPORT_BRIGHTNESS 0x91 /* LCD: OUT[1] */ | ||
60 | #define REPORT_CONTRAST 0x92 /* LCD: OUT[1] */ | ||
61 | #define REPORT_RESET 0x93 /* LCD: OUT[2] */ | ||
62 | #define REPORT_LCD_CMD 0x94 /* LCD: OUT[63] */ | ||
63 | #define REPORT_LCD_DATA 0x95 /* LCD: OUT[63] */ | ||
64 | #define REPORT_LCD_CMD_DATA 0x96 /* LCD: OUT[63] */ | ||
65 | #define REPORT_EE_READ 0xa3 /* LCD: OUT[63] */ | ||
66 | #define REPORT_EE_WRITE 0xa4 /* LCD: OUT[63] */ | ||
67 | #define REPORT_ERASE_MEMORY 0xb2 /* LCD: OUT[2] */ | ||
68 | #define REPORT_READ_MEMORY 0xb3 /* LCD: OUT[3] */ | ||
69 | #define REPORT_WRITE_MEMORY 0xb4 /* LCD: OUT[63] */ | ||
70 | #define REPORT_SPLASH_RESTART 0xc1 /* LCD: OUT[1] */ | ||
71 | #define REPORT_EXIT_KEYBOARD 0xef /* LCD: OUT[2] */ | ||
72 | #define REPORT_VERSION 0xf1 /* LCD: IN[2],OUT[1] Bootloader: IN[2],OUT[1] */ | ||
73 | #define REPORT_BL_ERASE_MEMORY 0xf2 /* Bootloader: IN[36],OUT[4] */ | ||
74 | #define REPORT_BL_READ_MEMORY 0xf3 /* Bootloader: IN[36],OUT[4] */ | ||
75 | #define REPORT_BL_WRITE_MEMORY 0xf4 /* Bootloader: IN[36],OUT[36] */ | ||
76 | #define REPORT_DEVID 0xf5 /* LCD: IN[5], OUT[1] Bootloader: IN[5],OUT[1] */ | ||
77 | #define REPORT_SPLASH_SIZE 0xf6 /* LCD: IN[4], OUT[1] */ | ||
78 | #define REPORT_HOOK_VERSION 0xf7 /* LCD: IN[2], OUT[1] */ | ||
79 | #define REPORT_EXIT_FLASHER 0xff /* Bootloader: OUT[2] */ | ||
80 | |||
81 | #ifdef CONFIG_HID_PICOLCD_FB | ||
82 | /* Framebuffer | ||
83 | * | ||
84 | * The PicoLCD use a Topway LCD module of 256x64 pixel | ||
85 | * This display area is tiled over 4 controllers with 8 tiles | ||
86 | * each. Each tile has 8x64 pixel, each data byte representing | ||
87 | * a 1-bit wide vertical line of the tile. | ||
88 | * | ||
89 | * The display can be updated at a tile granularity. | ||
90 | * | ||
91 | * Chip 1 Chip 2 Chip 3 Chip 4 | ||
92 | * +----------------+----------------+----------------+----------------+ | ||
93 | * | Tile 1 | Tile 1 | Tile 1 | Tile 1 | | ||
94 | * +----------------+----------------+----------------+----------------+ | ||
95 | * | Tile 2 | Tile 2 | Tile 2 | Tile 2 | | ||
96 | * +----------------+----------------+----------------+----------------+ | ||
97 | * ... | ||
98 | * +----------------+----------------+----------------+----------------+ | ||
99 | * | Tile 8 | Tile 8 | Tile 8 | Tile 8 | | ||
100 | * +----------------+----------------+----------------+----------------+ | ||
101 | */ | ||
102 | #define PICOLCDFB_NAME "picolcdfb" | ||
103 | #define PICOLCDFB_WIDTH (256) | ||
104 | #define PICOLCDFB_HEIGHT (64) | ||
105 | #define PICOLCDFB_SIZE (PICOLCDFB_WIDTH * PICOLCDFB_HEIGHT / 8) | ||
106 | |||
107 | #define PICOLCDFB_UPDATE_RATE_LIMIT 10 | ||
108 | #define PICOLCDFB_UPDATE_RATE_DEFAULT 2 | ||
109 | |||
110 | /* Framebuffer visual structures */ | ||
111 | static const struct fb_fix_screeninfo picolcdfb_fix = { | ||
112 | .id = PICOLCDFB_NAME, | ||
113 | .type = FB_TYPE_PACKED_PIXELS, | ||
114 | .visual = FB_VISUAL_MONO01, | ||
115 | .xpanstep = 0, | ||
116 | .ypanstep = 0, | ||
117 | .ywrapstep = 0, | ||
118 | .line_length = PICOLCDFB_WIDTH / 8, | ||
119 | .accel = FB_ACCEL_NONE, | ||
120 | }; | ||
121 | |||
122 | static const struct fb_var_screeninfo picolcdfb_var = { | ||
123 | .xres = PICOLCDFB_WIDTH, | ||
124 | .yres = PICOLCDFB_HEIGHT, | ||
125 | .xres_virtual = PICOLCDFB_WIDTH, | ||
126 | .yres_virtual = PICOLCDFB_HEIGHT, | ||
127 | .width = 103, | ||
128 | .height = 26, | ||
129 | .bits_per_pixel = 1, | ||
130 | .grayscale = 1, | ||
131 | .red = { | ||
132 | .offset = 0, | ||
133 | .length = 1, | ||
134 | .msb_right = 0, | ||
135 | }, | ||
136 | .green = { | ||
137 | .offset = 0, | ||
138 | .length = 1, | ||
139 | .msb_right = 0, | ||
140 | }, | ||
141 | .blue = { | ||
142 | .offset = 0, | ||
143 | .length = 1, | ||
144 | .msb_right = 0, | ||
145 | }, | ||
146 | .transp = { | ||
147 | .offset = 0, | ||
148 | .length = 0, | ||
149 | .msb_right = 0, | ||
150 | }, | ||
151 | }; | ||
152 | #endif /* CONFIG_HID_PICOLCD_FB */ | ||
153 | |||
154 | /* Input device | ||
155 | * | ||
156 | * The PicoLCD has an IR receiver header, a built-in keypad with 5 keys | ||
157 | * and header for 4x4 key matrix. The built-in keys are part of the matrix. | ||
158 | */ | ||
159 | static const unsigned short def_keymap[] = { | ||
160 | KEY_RESERVED, /* none */ | ||
161 | KEY_BACK, /* col 4 + row 1 */ | ||
162 | KEY_HOMEPAGE, /* col 3 + row 1 */ | ||
163 | KEY_RESERVED, /* col 2 + row 1 */ | ||
164 | KEY_RESERVED, /* col 1 + row 1 */ | ||
165 | KEY_SCROLLUP, /* col 4 + row 2 */ | ||
166 | KEY_OK, /* col 3 + row 2 */ | ||
167 | KEY_SCROLLDOWN, /* col 2 + row 2 */ | ||
168 | KEY_RESERVED, /* col 1 + row 2 */ | ||
169 | KEY_RESERVED, /* col 4 + row 3 */ | ||
170 | KEY_RESERVED, /* col 3 + row 3 */ | ||
171 | KEY_RESERVED, /* col 2 + row 3 */ | ||
172 | KEY_RESERVED, /* col 1 + row 3 */ | ||
173 | KEY_RESERVED, /* col 4 + row 4 */ | ||
174 | KEY_RESERVED, /* col 3 + row 4 */ | ||
175 | KEY_RESERVED, /* col 2 + row 4 */ | ||
176 | KEY_RESERVED, /* col 1 + row 4 */ | ||
177 | }; | ||
178 | #define PICOLCD_KEYS ARRAY_SIZE(def_keymap) | ||
179 | |||
180 | /* Description of in-progress IO operation, used for operations | ||
181 | * that trigger response from device */ | ||
182 | struct picolcd_pending { | ||
183 | struct hid_report *out_report; | ||
184 | struct hid_report *in_report; | ||
185 | struct completion ready; | ||
186 | int raw_size; | ||
187 | u8 raw_data[64]; | ||
188 | }; | ||
189 | |||
190 | /* Per device data structure */ | ||
191 | struct picolcd_data { | ||
192 | struct hid_device *hdev; | ||
193 | #ifdef CONFIG_DEBUG_FS | ||
194 | struct dentry *debug_reset; | ||
195 | struct dentry *debug_eeprom; | ||
196 | struct dentry *debug_flash; | ||
197 | struct mutex mutex_flash; | ||
198 | int addr_sz; | ||
199 | #endif | ||
200 | u8 version[2]; | ||
201 | unsigned short opmode_delay; | ||
202 | /* input stuff */ | ||
203 | u8 pressed_keys[2]; | ||
204 | struct input_dev *input_keys; | ||
205 | struct input_dev *input_cir; | ||
206 | unsigned short keycode[PICOLCD_KEYS]; | ||
207 | |||
208 | #ifdef CONFIG_HID_PICOLCD_FB | ||
209 | /* Framebuffer stuff */ | ||
210 | u8 fb_update_rate; | ||
211 | u8 fb_bpp; | ||
212 | u8 fb_force; | ||
213 | u8 *fb_vbitmap; /* local copy of what was sent to PicoLCD */ | ||
214 | u8 *fb_bitmap; /* framebuffer */ | ||
215 | struct fb_info *fb_info; | ||
216 | struct fb_deferred_io fb_defio; | ||
217 | #endif /* CONFIG_HID_PICOLCD_FB */ | ||
218 | #ifdef CONFIG_HID_PICOLCD_LCD | ||
219 | struct lcd_device *lcd; | ||
220 | u8 lcd_contrast; | ||
221 | #endif /* CONFIG_HID_PICOLCD_LCD */ | ||
222 | #ifdef CONFIG_HID_PICOLCD_BACKLIGHT | ||
223 | struct backlight_device *backlight; | ||
224 | u8 lcd_brightness; | ||
225 | u8 lcd_power; | ||
226 | #endif /* CONFIG_HID_PICOLCD_BACKLIGHT */ | ||
227 | #ifdef CONFIG_HID_PICOLCD_LEDS | ||
228 | /* LED stuff */ | ||
229 | u8 led_state; | ||
230 | struct led_classdev *led[8]; | ||
231 | #endif /* CONFIG_HID_PICOLCD_LEDS */ | ||
232 | |||
233 | /* Housekeeping stuff */ | ||
234 | spinlock_t lock; | ||
235 | struct mutex mutex; | ||
236 | struct picolcd_pending *pending; | ||
237 | int status; | ||
238 | #define PICOLCD_BOOTLOADER 1 | ||
239 | #define PICOLCD_FAILED 2 | ||
240 | #define PICOLCD_READY_FB 4 | ||
241 | }; | ||
242 | |||
243 | |||
244 | /* Find a given report */ | ||
245 | #define picolcd_in_report(id, dev) picolcd_report(id, dev, HID_INPUT_REPORT) | ||
246 | #define picolcd_out_report(id, dev) picolcd_report(id, dev, HID_OUTPUT_REPORT) | ||
247 | |||
248 | static struct hid_report *picolcd_report(int id, struct hid_device *hdev, int dir) | ||
249 | { | ||
250 | struct list_head *feature_report_list = &hdev->report_enum[dir].report_list; | ||
251 | struct hid_report *report = NULL; | ||
252 | |||
253 | list_for_each_entry(report, feature_report_list, list) { | ||
254 | if (report->id == id) | ||
255 | return report; | ||
256 | } | ||
257 | hid_warn(hdev, "No report with id 0x%x found\n", id); | ||
258 | return NULL; | ||
259 | } | ||
260 | |||
261 | #ifdef CONFIG_DEBUG_FS | ||
262 | static void picolcd_debug_out_report(struct picolcd_data *data, | ||
263 | struct hid_device *hdev, struct hid_report *report); | ||
264 | #define usbhid_submit_report(a, b, c) \ | ||
265 | do { \ | ||
266 | picolcd_debug_out_report(hid_get_drvdata(a), a, b); \ | ||
267 | usbhid_submit_report(a, b, c); \ | ||
268 | } while (0) | ||
269 | #endif | ||
270 | |||
271 | /* Submit a report and wait for a reply from device - if device fades away | ||
272 | * or does not respond in time, return NULL */ | ||
273 | static struct picolcd_pending *picolcd_send_and_wait(struct hid_device *hdev, | ||
274 | int report_id, const u8 *raw_data, int size) | ||
275 | { | ||
276 | struct picolcd_data *data = hid_get_drvdata(hdev); | ||
277 | struct picolcd_pending *work; | ||
278 | struct hid_report *report = picolcd_out_report(report_id, hdev); | ||
279 | unsigned long flags; | ||
280 | int i, j, k; | ||
281 | |||
282 | if (!report || !data) | ||
283 | return NULL; | ||
284 | if (data->status & PICOLCD_FAILED) | ||
285 | return NULL; | ||
286 | work = kzalloc(sizeof(*work), GFP_KERNEL); | ||
287 | if (!work) | ||
288 | return NULL; | ||
289 | |||
290 | init_completion(&work->ready); | ||
291 | work->out_report = report; | ||
292 | work->in_report = NULL; | ||
293 | work->raw_size = 0; | ||
294 | |||
295 | mutex_lock(&data->mutex); | ||
296 | spin_lock_irqsave(&data->lock, flags); | ||
297 | for (i = k = 0; i < report->maxfield; i++) | ||
298 | for (j = 0; j < report->field[i]->report_count; j++) { | ||
299 | hid_set_field(report->field[i], j, k < size ? raw_data[k] : 0); | ||
300 | k++; | ||
301 | } | ||
302 | data->pending = work; | ||
303 | usbhid_submit_report(data->hdev, report, USB_DIR_OUT); | ||
304 | spin_unlock_irqrestore(&data->lock, flags); | ||
305 | wait_for_completion_interruptible_timeout(&work->ready, HZ*2); | ||
306 | spin_lock_irqsave(&data->lock, flags); | ||
307 | data->pending = NULL; | ||
308 | spin_unlock_irqrestore(&data->lock, flags); | ||
309 | mutex_unlock(&data->mutex); | ||
310 | return work; | ||
311 | } | ||
312 | |||
313 | #ifdef CONFIG_HID_PICOLCD_FB | ||
314 | /* Send a given tile to PicoLCD */ | ||
315 | static int picolcd_fb_send_tile(struct hid_device *hdev, int chip, int tile) | ||
316 | { | ||
317 | struct picolcd_data *data = hid_get_drvdata(hdev); | ||
318 | struct hid_report *report1 = picolcd_out_report(REPORT_LCD_CMD_DATA, hdev); | ||
319 | struct hid_report *report2 = picolcd_out_report(REPORT_LCD_DATA, hdev); | ||
320 | unsigned long flags; | ||
321 | u8 *tdata; | ||
322 | int i; | ||
323 | |||
324 | if (!report1 || report1->maxfield != 1 || !report2 || report2->maxfield != 1) | ||
325 | return -ENODEV; | ||
326 | |||
327 | spin_lock_irqsave(&data->lock, flags); | ||
328 | hid_set_field(report1->field[0], 0, chip << 2); | ||
329 | hid_set_field(report1->field[0], 1, 0x02); | ||
330 | hid_set_field(report1->field[0], 2, 0x00); | ||
331 | hid_set_field(report1->field[0], 3, 0x00); | ||
332 | hid_set_field(report1->field[0], 4, 0xb8 | tile); | ||
333 | hid_set_field(report1->field[0], 5, 0x00); | ||
334 | hid_set_field(report1->field[0], 6, 0x00); | ||
335 | hid_set_field(report1->field[0], 7, 0x40); | ||
336 | hid_set_field(report1->field[0], 8, 0x00); | ||
337 | hid_set_field(report1->field[0], 9, 0x00); | ||
338 | hid_set_field(report1->field[0], 10, 32); | ||
339 | |||
340 | hid_set_field(report2->field[0], 0, (chip << 2) | 0x01); | ||
341 | hid_set_field(report2->field[0], 1, 0x00); | ||
342 | hid_set_field(report2->field[0], 2, 0x00); | ||
343 | hid_set_field(report2->field[0], 3, 32); | ||
344 | |||
345 | tdata = data->fb_vbitmap + (tile * 4 + chip) * 64; | ||
346 | for (i = 0; i < 64; i++) | ||
347 | if (i < 32) | ||
348 | hid_set_field(report1->field[0], 11 + i, tdata[i]); | ||
349 | else | ||
350 | hid_set_field(report2->field[0], 4 + i - 32, tdata[i]); | ||
351 | |||
352 | usbhid_submit_report(data->hdev, report1, USB_DIR_OUT); | ||
353 | usbhid_submit_report(data->hdev, report2, USB_DIR_OUT); | ||
354 | spin_unlock_irqrestore(&data->lock, flags); | ||
355 | return 0; | ||
356 | } | ||
357 | |||
358 | /* Translate a single tile*/ | ||
359 | static int picolcd_fb_update_tile(u8 *vbitmap, const u8 *bitmap, int bpp, | ||
360 | int chip, int tile) | ||
361 | { | ||
362 | int i, b, changed = 0; | ||
363 | u8 tdata[64]; | ||
364 | u8 *vdata = vbitmap + (tile * 4 + chip) * 64; | ||
365 | |||
366 | if (bpp == 1) { | ||
367 | for (b = 7; b >= 0; b--) { | ||
368 | const u8 *bdata = bitmap + tile * 256 + chip * 8 + b * 32; | ||
369 | for (i = 0; i < 64; i++) { | ||
370 | tdata[i] <<= 1; | ||
371 | tdata[i] |= (bdata[i/8] >> (i % 8)) & 0x01; | ||
372 | } | ||
373 | } | ||
374 | } else if (bpp == 8) { | ||
375 | for (b = 7; b >= 0; b--) { | ||
376 | const u8 *bdata = bitmap + (tile * 256 + chip * 8 + b * 32) * 8; | ||
377 | for (i = 0; i < 64; i++) { | ||
378 | tdata[i] <<= 1; | ||
379 | tdata[i] |= (bdata[i] & 0x80) ? 0x01 : 0x00; | ||
380 | } | ||
381 | } | ||
382 | } else { | ||
383 | /* Oops, we should never get here! */ | ||
384 | WARN_ON(1); | ||
385 | return 0; | ||
386 | } | ||
387 | |||
388 | for (i = 0; i < 64; i++) | ||
389 | if (tdata[i] != vdata[i]) { | ||
390 | changed = 1; | ||
391 | vdata[i] = tdata[i]; | ||
392 | } | ||
393 | return changed; | ||
394 | } | ||
395 | |||
396 | /* Reconfigure LCD display */ | ||
397 | static int picolcd_fb_reset(struct picolcd_data *data, int clear) | ||
398 | { | ||
399 | struct hid_report *report = picolcd_out_report(REPORT_LCD_CMD, data->hdev); | ||
400 | int i, j; | ||
401 | unsigned long flags; | ||
402 | static const u8 mapcmd[8] = { 0x00, 0x02, 0x00, 0x64, 0x3f, 0x00, 0x64, 0xc0 }; | ||
403 | |||
404 | if (!report || report->maxfield != 1) | ||
405 | return -ENODEV; | ||
406 | |||
407 | spin_lock_irqsave(&data->lock, flags); | ||
408 | for (i = 0; i < 4; i++) { | ||
409 | for (j = 0; j < report->field[0]->maxusage; j++) | ||
410 | if (j == 0) | ||
411 | hid_set_field(report->field[0], j, i << 2); | ||
412 | else if (j < sizeof(mapcmd)) | ||
413 | hid_set_field(report->field[0], j, mapcmd[j]); | ||
414 | else | ||
415 | hid_set_field(report->field[0], j, 0); | ||
416 | usbhid_submit_report(data->hdev, report, USB_DIR_OUT); | ||
417 | } | ||
418 | |||
419 | data->status |= PICOLCD_READY_FB; | ||
420 | spin_unlock_irqrestore(&data->lock, flags); | ||
421 | |||
422 | if (data->fb_bitmap) { | ||
423 | if (clear) { | ||
424 | memset(data->fb_vbitmap, 0, PICOLCDFB_SIZE); | ||
425 | memset(data->fb_bitmap, 0, PICOLCDFB_SIZE*data->fb_bpp); | ||
426 | } | ||
427 | data->fb_force = 1; | ||
428 | } | ||
429 | |||
430 | /* schedule first output of framebuffer */ | ||
431 | if (data->fb_info) | ||
432 | schedule_delayed_work(&data->fb_info->deferred_work, 0); | ||
433 | |||
434 | return 0; | ||
435 | } | ||
436 | |||
437 | /* Update fb_vbitmap from the screen_base and send changed tiles to device */ | ||
438 | static void picolcd_fb_update(struct picolcd_data *data) | ||
439 | { | ||
440 | int chip, tile, n; | ||
441 | unsigned long flags; | ||
442 | |||
443 | if (!data) | ||
444 | return; | ||
445 | |||
446 | spin_lock_irqsave(&data->lock, flags); | ||
447 | if (!(data->status & PICOLCD_READY_FB)) { | ||
448 | spin_unlock_irqrestore(&data->lock, flags); | ||
449 | picolcd_fb_reset(data, 0); | ||
450 | } else { | ||
451 | spin_unlock_irqrestore(&data->lock, flags); | ||
452 | } | ||
453 | |||
454 | /* | ||
455 | * Translate the framebuffer into the format needed by the PicoLCD. | ||
456 | * See display layout above. | ||
457 | * Do this one tile after the other and push those tiles that changed. | ||
458 | * | ||
459 | * Wait for our IO to complete as otherwise we might flood the queue! | ||
460 | */ | ||
461 | n = 0; | ||
462 | for (chip = 0; chip < 4; chip++) | ||
463 | for (tile = 0; tile < 8; tile++) | ||
464 | if (picolcd_fb_update_tile(data->fb_vbitmap, | ||
465 | data->fb_bitmap, data->fb_bpp, chip, tile) || | ||
466 | data->fb_force) { | ||
467 | n += 2; | ||
468 | if (!data->fb_info->par) | ||
469 | return; /* device lost! */ | ||
470 | if (n >= HID_OUTPUT_FIFO_SIZE / 2) { | ||
471 | usbhid_wait_io(data->hdev); | ||
472 | n = 0; | ||
473 | } | ||
474 | picolcd_fb_send_tile(data->hdev, chip, tile); | ||
475 | } | ||
476 | data->fb_force = false; | ||
477 | if (n) | ||
478 | usbhid_wait_io(data->hdev); | ||
479 | } | ||
480 | |||
481 | /* Stub to call the system default and update the image on the picoLCD */ | ||
482 | static void picolcd_fb_fillrect(struct fb_info *info, | ||
483 | const struct fb_fillrect *rect) | ||
484 | { | ||
485 | if (!info->par) | ||
486 | return; | ||
487 | sys_fillrect(info, rect); | ||
488 | |||
489 | schedule_delayed_work(&info->deferred_work, 0); | ||
490 | } | ||
491 | |||
492 | /* Stub to call the system default and update the image on the picoLCD */ | ||
493 | static void picolcd_fb_copyarea(struct fb_info *info, | ||
494 | const struct fb_copyarea *area) | ||
495 | { | ||
496 | if (!info->par) | ||
497 | return; | ||
498 | sys_copyarea(info, area); | ||
499 | |||
500 | schedule_delayed_work(&info->deferred_work, 0); | ||
501 | } | ||
502 | |||
503 | /* Stub to call the system default and update the image on the picoLCD */ | ||
504 | static void picolcd_fb_imageblit(struct fb_info *info, const struct fb_image *image) | ||
505 | { | ||
506 | if (!info->par) | ||
507 | return; | ||
508 | sys_imageblit(info, image); | ||
509 | |||
510 | schedule_delayed_work(&info->deferred_work, 0); | ||
511 | } | ||
512 | |||
513 | /* | ||
514 | * this is the slow path from userspace. they can seek and write to | ||
515 | * the fb. it's inefficient to do anything less than a full screen draw | ||
516 | */ | ||
517 | static ssize_t picolcd_fb_write(struct fb_info *info, const char __user *buf, | ||
518 | size_t count, loff_t *ppos) | ||
519 | { | ||
520 | ssize_t ret; | ||
521 | if (!info->par) | ||
522 | return -ENODEV; | ||
523 | ret = fb_sys_write(info, buf, count, ppos); | ||
524 | if (ret >= 0) | ||
525 | schedule_delayed_work(&info->deferred_work, 0); | ||
526 | return ret; | ||
527 | } | ||
528 | |||
529 | static int picolcd_fb_blank(int blank, struct fb_info *info) | ||
530 | { | ||
531 | if (!info->par) | ||
532 | return -ENODEV; | ||
533 | /* We let fb notification do this for us via lcd/backlight device */ | ||
534 | return 0; | ||
535 | } | ||
536 | |||
537 | static void picolcd_fb_destroy(struct fb_info *info) | ||
538 | { | ||
539 | struct picolcd_data *data = info->par; | ||
540 | u32 *ref_cnt = info->pseudo_palette; | ||
541 | int may_release; | ||
542 | |||
543 | info->par = NULL; | ||
544 | if (data) | ||
545 | data->fb_info = NULL; | ||
546 | fb_deferred_io_cleanup(info); | ||
547 | |||
548 | ref_cnt--; | ||
549 | mutex_lock(&info->lock); | ||
550 | (*ref_cnt)--; | ||
551 | may_release = !*ref_cnt; | ||
552 | mutex_unlock(&info->lock); | ||
553 | if (may_release) { | ||
554 | vfree((u8 *)info->fix.smem_start); | ||
555 | framebuffer_release(info); | ||
556 | } | ||
557 | } | ||
558 | |||
559 | static int picolcd_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) | ||
560 | { | ||
561 | __u32 bpp = var->bits_per_pixel; | ||
562 | __u32 activate = var->activate; | ||
563 | |||
564 | /* only allow 1/8 bit depth (8-bit is grayscale) */ | ||
565 | *var = picolcdfb_var; | ||
566 | var->activate = activate; | ||
567 | if (bpp >= 8) { | ||
568 | var->bits_per_pixel = 8; | ||
569 | var->red.length = 8; | ||
570 | var->green.length = 8; | ||
571 | var->blue.length = 8; | ||
572 | } else { | ||
573 | var->bits_per_pixel = 1; | ||
574 | var->red.length = 1; | ||
575 | var->green.length = 1; | ||
576 | var->blue.length = 1; | ||
577 | } | ||
578 | return 0; | ||
579 | } | ||
580 | |||
581 | static int picolcd_set_par(struct fb_info *info) | ||
582 | { | ||
583 | struct picolcd_data *data = info->par; | ||
584 | u8 *tmp_fb, *o_fb; | ||
585 | if (!data) | ||
586 | return -ENODEV; | ||
587 | if (info->var.bits_per_pixel == data->fb_bpp) | ||
588 | return 0; | ||
589 | /* switch between 1/8 bit depths */ | ||
590 | if (info->var.bits_per_pixel != 1 && info->var.bits_per_pixel != 8) | ||
591 | return -EINVAL; | ||
592 | |||
593 | o_fb = data->fb_bitmap; | ||
594 | tmp_fb = kmalloc(PICOLCDFB_SIZE*info->var.bits_per_pixel, GFP_KERNEL); | ||
595 | if (!tmp_fb) | ||
596 | return -ENOMEM; | ||
597 | |||
598 | /* translate FB content to new bits-per-pixel */ | ||
599 | if (info->var.bits_per_pixel == 1) { | ||
600 | int i, b; | ||
601 | for (i = 0; i < PICOLCDFB_SIZE; i++) { | ||
602 | u8 p = 0; | ||
603 | for (b = 0; b < 8; b++) { | ||
604 | p <<= 1; | ||
605 | p |= o_fb[i*8+b] ? 0x01 : 0x00; | ||
606 | } | ||
607 | tmp_fb[i] = p; | ||
608 | } | ||
609 | memcpy(o_fb, tmp_fb, PICOLCDFB_SIZE); | ||
610 | info->fix.visual = FB_VISUAL_MONO01; | ||
611 | info->fix.line_length = PICOLCDFB_WIDTH / 8; | ||
612 | } else { | ||
613 | int i; | ||
614 | memcpy(tmp_fb, o_fb, PICOLCDFB_SIZE); | ||
615 | for (i = 0; i < PICOLCDFB_SIZE * 8; i++) | ||
616 | o_fb[i] = tmp_fb[i/8] & (0x01 << (7 - i % 8)) ? 0xff : 0x00; | ||
617 | info->fix.visual = FB_VISUAL_DIRECTCOLOR; | ||
618 | info->fix.line_length = PICOLCDFB_WIDTH; | ||
619 | } | ||
620 | |||
621 | kfree(tmp_fb); | ||
622 | data->fb_bpp = info->var.bits_per_pixel; | ||
623 | return 0; | ||
624 | } | ||
625 | |||
626 | /* Do refcounting on our FB and cleanup per worker if FB is | ||
627 | * closed after unplug of our device | ||
628 | * (fb_release holds info->lock and still touches info after | ||
629 | * we return so we can't release it immediately. | ||
630 | */ | ||
631 | struct picolcd_fb_cleanup_item { | ||
632 | struct fb_info *info; | ||
633 | struct picolcd_fb_cleanup_item *next; | ||
634 | }; | ||
635 | static struct picolcd_fb_cleanup_item *fb_pending; | ||
636 | static DEFINE_SPINLOCK(fb_pending_lock); | ||
637 | |||
638 | static void picolcd_fb_do_cleanup(struct work_struct *data) | ||
639 | { | ||
640 | struct picolcd_fb_cleanup_item *item; | ||
641 | unsigned long flags; | ||
642 | |||
643 | do { | ||
644 | spin_lock_irqsave(&fb_pending_lock, flags); | ||
645 | item = fb_pending; | ||
646 | fb_pending = item ? item->next : NULL; | ||
647 | spin_unlock_irqrestore(&fb_pending_lock, flags); | ||
648 | |||
649 | if (item) { | ||
650 | u8 *fb = (u8 *)item->info->fix.smem_start; | ||
651 | /* make sure we do not race against fb core when | ||
652 | * releasing */ | ||
653 | mutex_lock(&item->info->lock); | ||
654 | mutex_unlock(&item->info->lock); | ||
655 | framebuffer_release(item->info); | ||
656 | vfree(fb); | ||
657 | } | ||
658 | } while (item); | ||
659 | } | ||
660 | |||
661 | static DECLARE_WORK(picolcd_fb_cleanup, picolcd_fb_do_cleanup); | ||
662 | |||
663 | static int picolcd_fb_open(struct fb_info *info, int u) | ||
664 | { | ||
665 | u32 *ref_cnt = info->pseudo_palette; | ||
666 | ref_cnt--; | ||
667 | |||
668 | (*ref_cnt)++; | ||
669 | return 0; | ||
670 | } | ||
671 | |||
672 | static int picolcd_fb_release(struct fb_info *info, int u) | ||
673 | { | ||
674 | u32 *ref_cnt = info->pseudo_palette; | ||
675 | ref_cnt--; | ||
676 | |||
677 | (*ref_cnt)++; | ||
678 | if (!*ref_cnt) { | ||
679 | unsigned long flags; | ||
680 | struct picolcd_fb_cleanup_item *item = (struct picolcd_fb_cleanup_item *)ref_cnt; | ||
681 | item--; | ||
682 | spin_lock_irqsave(&fb_pending_lock, flags); | ||
683 | item->next = fb_pending; | ||
684 | fb_pending = item; | ||
685 | spin_unlock_irqrestore(&fb_pending_lock, flags); | ||
686 | schedule_work(&picolcd_fb_cleanup); | ||
687 | } | ||
688 | return 0; | ||
689 | } | ||
690 | |||
691 | /* Note this can't be const because of struct fb_info definition */ | ||
692 | static struct fb_ops picolcdfb_ops = { | ||
693 | .owner = THIS_MODULE, | ||
694 | .fb_destroy = picolcd_fb_destroy, | ||
695 | .fb_open = picolcd_fb_open, | ||
696 | .fb_release = picolcd_fb_release, | ||
697 | .fb_read = fb_sys_read, | ||
698 | .fb_write = picolcd_fb_write, | ||
699 | .fb_blank = picolcd_fb_blank, | ||
700 | .fb_fillrect = picolcd_fb_fillrect, | ||
701 | .fb_copyarea = picolcd_fb_copyarea, | ||
702 | .fb_imageblit = picolcd_fb_imageblit, | ||
703 | .fb_check_var = picolcd_fb_check_var, | ||
704 | .fb_set_par = picolcd_set_par, | ||
705 | }; | ||
706 | |||
707 | |||
708 | /* Callback from deferred IO workqueue */ | ||
709 | static void picolcd_fb_deferred_io(struct fb_info *info, struct list_head *pagelist) | ||
710 | { | ||
711 | picolcd_fb_update(info->par); | ||
712 | } | ||
713 | |||
714 | static const struct fb_deferred_io picolcd_fb_defio = { | ||
715 | .delay = HZ / PICOLCDFB_UPDATE_RATE_DEFAULT, | ||
716 | .deferred_io = picolcd_fb_deferred_io, | ||
717 | }; | ||
718 | |||
719 | |||
720 | /* | ||
721 | * The "fb_update_rate" sysfs attribute | ||
722 | */ | ||
723 | static ssize_t picolcd_fb_update_rate_show(struct device *dev, | ||
724 | struct device_attribute *attr, char *buf) | ||
725 | { | ||
726 | struct picolcd_data *data = dev_get_drvdata(dev); | ||
727 | unsigned i, fb_update_rate = data->fb_update_rate; | ||
728 | size_t ret = 0; | ||
729 | |||
730 | for (i = 1; i <= PICOLCDFB_UPDATE_RATE_LIMIT; i++) | ||
731 | if (ret >= PAGE_SIZE) | ||
732 | break; | ||
733 | else if (i == fb_update_rate) | ||
734 | ret += snprintf(buf+ret, PAGE_SIZE-ret, "[%u] ", i); | ||
735 | else | ||
736 | ret += snprintf(buf+ret, PAGE_SIZE-ret, "%u ", i); | ||
737 | if (ret > 0) | ||
738 | buf[min(ret, (size_t)PAGE_SIZE)-1] = '\n'; | ||
739 | return ret; | ||
740 | } | ||
741 | |||
742 | static ssize_t picolcd_fb_update_rate_store(struct device *dev, | ||
743 | struct device_attribute *attr, const char *buf, size_t count) | ||
744 | { | ||
745 | struct picolcd_data *data = dev_get_drvdata(dev); | ||
746 | int i; | ||
747 | unsigned u; | ||
748 | |||
749 | if (count < 1 || count > 10) | ||
750 | return -EINVAL; | ||
751 | |||
752 | i = sscanf(buf, "%u", &u); | ||
753 | if (i != 1) | ||
754 | return -EINVAL; | ||
755 | |||
756 | if (u > PICOLCDFB_UPDATE_RATE_LIMIT) | ||
757 | return -ERANGE; | ||
758 | else if (u == 0) | ||
759 | u = PICOLCDFB_UPDATE_RATE_DEFAULT; | ||
760 | |||
761 | data->fb_update_rate = u; | ||
762 | data->fb_defio.delay = HZ / data->fb_update_rate; | ||
763 | return count; | ||
764 | } | ||
765 | |||
766 | static DEVICE_ATTR(fb_update_rate, 0666, picolcd_fb_update_rate_show, | ||
767 | picolcd_fb_update_rate_store); | ||
768 | |||
769 | /* initialize Framebuffer device */ | ||
770 | static int picolcd_init_framebuffer(struct picolcd_data *data) | ||
771 | { | ||
772 | struct device *dev = &data->hdev->dev; | ||
773 | struct fb_info *info = NULL; | ||
774 | int i, error = -ENOMEM; | ||
775 | u8 *fb_vbitmap = NULL; | ||
776 | u8 *fb_bitmap = NULL; | ||
777 | u32 *palette; | ||
778 | |||
779 | fb_bitmap = vmalloc(PICOLCDFB_SIZE*8); | ||
780 | if (fb_bitmap == NULL) { | ||
781 | dev_err(dev, "can't get a free page for framebuffer\n"); | ||
782 | goto err_nomem; | ||
783 | } | ||
784 | |||
785 | fb_vbitmap = kmalloc(PICOLCDFB_SIZE, GFP_KERNEL); | ||
786 | if (fb_vbitmap == NULL) { | ||
787 | dev_err(dev, "can't alloc vbitmap image buffer\n"); | ||
788 | goto err_nomem; | ||
789 | } | ||
790 | |||
791 | data->fb_update_rate = PICOLCDFB_UPDATE_RATE_DEFAULT; | ||
792 | data->fb_defio = picolcd_fb_defio; | ||
793 | /* The extra memory is: | ||
794 | * - struct picolcd_fb_cleanup_item | ||
795 | * - u32 for ref_count | ||
796 | * - 256*u32 for pseudo_palette | ||
797 | */ | ||
798 | info = framebuffer_alloc(257 * sizeof(u32) + sizeof(struct picolcd_fb_cleanup_item), dev); | ||
799 | if (info == NULL) { | ||
800 | dev_err(dev, "failed to allocate a framebuffer\n"); | ||
801 | goto err_nomem; | ||
802 | } | ||
803 | |||
804 | palette = info->par + sizeof(struct picolcd_fb_cleanup_item); | ||
805 | *palette = 1; | ||
806 | palette++; | ||
807 | for (i = 0; i < 256; i++) | ||
808 | palette[i] = i > 0 && i < 16 ? 0xff : 0; | ||
809 | info->pseudo_palette = palette; | ||
810 | info->fbdefio = &data->fb_defio; | ||
811 | info->screen_base = (char __force __iomem *)fb_bitmap; | ||
812 | info->fbops = &picolcdfb_ops; | ||
813 | info->var = picolcdfb_var; | ||
814 | info->fix = picolcdfb_fix; | ||
815 | info->fix.smem_len = PICOLCDFB_SIZE*8; | ||
816 | info->fix.smem_start = (unsigned long)fb_bitmap; | ||
817 | info->par = data; | ||
818 | info->flags = FBINFO_FLAG_DEFAULT; | ||
819 | |||
820 | data->fb_vbitmap = fb_vbitmap; | ||
821 | data->fb_bitmap = fb_bitmap; | ||
822 | data->fb_bpp = picolcdfb_var.bits_per_pixel; | ||
823 | error = picolcd_fb_reset(data, 1); | ||
824 | if (error) { | ||
825 | dev_err(dev, "failed to configure display\n"); | ||
826 | goto err_cleanup; | ||
827 | } | ||
828 | error = device_create_file(dev, &dev_attr_fb_update_rate); | ||
829 | if (error) { | ||
830 | dev_err(dev, "failed to create sysfs attributes\n"); | ||
831 | goto err_cleanup; | ||
832 | } | ||
833 | fb_deferred_io_init(info); | ||
834 | data->fb_info = info; | ||
835 | error = register_framebuffer(info); | ||
836 | if (error) { | ||
837 | dev_err(dev, "failed to register framebuffer\n"); | ||
838 | goto err_sysfs; | ||
839 | } | ||
840 | /* schedule first output of framebuffer */ | ||
841 | data->fb_force = 1; | ||
842 | schedule_delayed_work(&info->deferred_work, 0); | ||
843 | return 0; | ||
844 | |||
845 | err_sysfs: | ||
846 | fb_deferred_io_cleanup(info); | ||
847 | device_remove_file(dev, &dev_attr_fb_update_rate); | ||
848 | err_cleanup: | ||
849 | data->fb_vbitmap = NULL; | ||
850 | data->fb_bitmap = NULL; | ||
851 | data->fb_bpp = 0; | ||
852 | data->fb_info = NULL; | ||
853 | |||
854 | err_nomem: | ||
855 | framebuffer_release(info); | ||
856 | vfree(fb_bitmap); | ||
857 | kfree(fb_vbitmap); | ||
858 | return error; | ||
859 | } | ||
860 | |||
861 | static void picolcd_exit_framebuffer(struct picolcd_data *data) | ||
862 | { | ||
863 | struct fb_info *info = data->fb_info; | ||
864 | u8 *fb_vbitmap = data->fb_vbitmap; | ||
865 | |||
866 | if (!info) | ||
867 | return; | ||
868 | |||
869 | info->par = NULL; | ||
870 | device_remove_file(&data->hdev->dev, &dev_attr_fb_update_rate); | ||
871 | unregister_framebuffer(info); | ||
872 | data->fb_vbitmap = NULL; | ||
873 | data->fb_bitmap = NULL; | ||
874 | data->fb_bpp = 0; | ||
875 | data->fb_info = NULL; | ||
876 | kfree(fb_vbitmap); | ||
877 | } | ||
878 | |||
879 | #define picolcd_fbinfo(d) ((d)->fb_info) | ||
880 | #else | ||
881 | static inline int picolcd_fb_reset(struct picolcd_data *data, int clear) | ||
882 | { | ||
883 | return 0; | ||
884 | } | ||
885 | static inline int picolcd_init_framebuffer(struct picolcd_data *data) | ||
886 | { | ||
887 | return 0; | ||
888 | } | ||
889 | static inline void picolcd_exit_framebuffer(struct picolcd_data *data) | ||
890 | { | ||
891 | } | ||
892 | #define picolcd_fbinfo(d) NULL | ||
893 | #endif /* CONFIG_HID_PICOLCD_FB */ | ||
894 | |||
895 | #ifdef CONFIG_HID_PICOLCD_BACKLIGHT | ||
896 | /* | ||
897 | * backlight class device | ||
898 | */ | ||
899 | static int picolcd_get_brightness(struct backlight_device *bdev) | ||
900 | { | ||
901 | struct picolcd_data *data = bl_get_data(bdev); | ||
902 | return data->lcd_brightness; | ||
903 | } | ||
904 | |||
905 | static int picolcd_set_brightness(struct backlight_device *bdev) | ||
906 | { | ||
907 | struct picolcd_data *data = bl_get_data(bdev); | ||
908 | struct hid_report *report = picolcd_out_report(REPORT_BRIGHTNESS, data->hdev); | ||
909 | unsigned long flags; | ||
910 | |||
911 | if (!report || report->maxfield != 1 || report->field[0]->report_count != 1) | ||
912 | return -ENODEV; | ||
913 | |||
914 | data->lcd_brightness = bdev->props.brightness & 0x0ff; | ||
915 | data->lcd_power = bdev->props.power; | ||
916 | spin_lock_irqsave(&data->lock, flags); | ||
917 | hid_set_field(report->field[0], 0, data->lcd_power == FB_BLANK_UNBLANK ? data->lcd_brightness : 0); | ||
918 | usbhid_submit_report(data->hdev, report, USB_DIR_OUT); | ||
919 | spin_unlock_irqrestore(&data->lock, flags); | ||
920 | return 0; | ||
921 | } | ||
922 | |||
923 | static int picolcd_check_bl_fb(struct backlight_device *bdev, struct fb_info *fb) | ||
924 | { | ||
925 | return fb && fb == picolcd_fbinfo((struct picolcd_data *)bl_get_data(bdev)); | ||
926 | } | ||
927 | |||
928 | static const struct backlight_ops picolcd_blops = { | ||
929 | .update_status = picolcd_set_brightness, | ||
930 | .get_brightness = picolcd_get_brightness, | ||
931 | .check_fb = picolcd_check_bl_fb, | ||
932 | }; | ||
933 | |||
934 | static int picolcd_init_backlight(struct picolcd_data *data, struct hid_report *report) | ||
935 | { | ||
936 | struct device *dev = &data->hdev->dev; | ||
937 | struct backlight_device *bdev; | ||
938 | struct backlight_properties props; | ||
939 | if (!report) | ||
940 | return -ENODEV; | ||
941 | if (report->maxfield != 1 || report->field[0]->report_count != 1 || | ||
942 | report->field[0]->report_size != 8) { | ||
943 | dev_err(dev, "unsupported BRIGHTNESS report"); | ||
944 | return -EINVAL; | ||
945 | } | ||
946 | |||
947 | memset(&props, 0, sizeof(props)); | ||
948 | props.type = BACKLIGHT_RAW; | ||
949 | props.max_brightness = 0xff; | ||
950 | bdev = backlight_device_register(dev_name(dev), dev, data, | ||
951 | &picolcd_blops, &props); | ||
952 | if (IS_ERR(bdev)) { | ||
953 | dev_err(dev, "failed to register backlight\n"); | ||
954 | return PTR_ERR(bdev); | ||
955 | } | ||
956 | bdev->props.brightness = 0xff; | ||
957 | data->lcd_brightness = 0xff; | ||
958 | data->backlight = bdev; | ||
959 | picolcd_set_brightness(bdev); | ||
960 | return 0; | ||
961 | } | ||
962 | |||
963 | static void picolcd_exit_backlight(struct picolcd_data *data) | ||
964 | { | ||
965 | struct backlight_device *bdev = data->backlight; | ||
966 | |||
967 | data->backlight = NULL; | ||
968 | if (bdev) | ||
969 | backlight_device_unregister(bdev); | ||
970 | } | ||
971 | |||
972 | static inline int picolcd_resume_backlight(struct picolcd_data *data) | ||
973 | { | ||
974 | if (!data->backlight) | ||
975 | return 0; | ||
976 | return picolcd_set_brightness(data->backlight); | ||
977 | } | ||
978 | |||
979 | #ifdef CONFIG_PM | ||
980 | static void picolcd_suspend_backlight(struct picolcd_data *data) | ||
981 | { | ||
982 | int bl_power = data->lcd_power; | ||
983 | if (!data->backlight) | ||
984 | return; | ||
985 | |||
986 | data->backlight->props.power = FB_BLANK_POWERDOWN; | ||
987 | picolcd_set_brightness(data->backlight); | ||
988 | data->lcd_power = data->backlight->props.power = bl_power; | ||
989 | } | ||
990 | #endif /* CONFIG_PM */ | ||
991 | #else | ||
992 | static inline int picolcd_init_backlight(struct picolcd_data *data, | ||
993 | struct hid_report *report) | ||
994 | { | ||
995 | return 0; | ||
996 | } | ||
997 | static inline void picolcd_exit_backlight(struct picolcd_data *data) | ||
998 | { | ||
999 | } | ||
1000 | static inline int picolcd_resume_backlight(struct picolcd_data *data) | ||
1001 | { | ||
1002 | return 0; | ||
1003 | } | ||
1004 | static inline void picolcd_suspend_backlight(struct picolcd_data *data) | ||
1005 | { | ||
1006 | } | ||
1007 | #endif /* CONFIG_HID_PICOLCD_BACKLIGHT */ | ||
1008 | |||
1009 | #ifdef CONFIG_HID_PICOLCD_LCD | ||
1010 | /* | ||
1011 | * lcd class device | ||
1012 | */ | ||
1013 | static int picolcd_get_contrast(struct lcd_device *ldev) | ||
1014 | { | ||
1015 | struct picolcd_data *data = lcd_get_data(ldev); | ||
1016 | return data->lcd_contrast; | ||
1017 | } | ||
1018 | |||
1019 | static int picolcd_set_contrast(struct lcd_device *ldev, int contrast) | ||
1020 | { | ||
1021 | struct picolcd_data *data = lcd_get_data(ldev); | ||
1022 | struct hid_report *report = picolcd_out_report(REPORT_CONTRAST, data->hdev); | ||
1023 | unsigned long flags; | ||
1024 | |||
1025 | if (!report || report->maxfield != 1 || report->field[0]->report_count != 1) | ||
1026 | return -ENODEV; | ||
1027 | |||
1028 | data->lcd_contrast = contrast & 0x0ff; | ||
1029 | spin_lock_irqsave(&data->lock, flags); | ||
1030 | hid_set_field(report->field[0], 0, data->lcd_contrast); | ||
1031 | usbhid_submit_report(data->hdev, report, USB_DIR_OUT); | ||
1032 | spin_unlock_irqrestore(&data->lock, flags); | ||
1033 | return 0; | ||
1034 | } | ||
1035 | |||
1036 | static int picolcd_check_lcd_fb(struct lcd_device *ldev, struct fb_info *fb) | ||
1037 | { | ||
1038 | return fb && fb == picolcd_fbinfo((struct picolcd_data *)lcd_get_data(ldev)); | ||
1039 | } | ||
1040 | |||
1041 | static struct lcd_ops picolcd_lcdops = { | ||
1042 | .get_contrast = picolcd_get_contrast, | ||
1043 | .set_contrast = picolcd_set_contrast, | ||
1044 | .check_fb = picolcd_check_lcd_fb, | ||
1045 | }; | ||
1046 | |||
1047 | static int picolcd_init_lcd(struct picolcd_data *data, struct hid_report *report) | ||
1048 | { | ||
1049 | struct device *dev = &data->hdev->dev; | ||
1050 | struct lcd_device *ldev; | ||
1051 | |||
1052 | if (!report) | ||
1053 | return -ENODEV; | ||
1054 | if (report->maxfield != 1 || report->field[0]->report_count != 1 || | ||
1055 | report->field[0]->report_size != 8) { | ||
1056 | dev_err(dev, "unsupported CONTRAST report"); | ||
1057 | return -EINVAL; | ||
1058 | } | ||
1059 | |||
1060 | ldev = lcd_device_register(dev_name(dev), dev, data, &picolcd_lcdops); | ||
1061 | if (IS_ERR(ldev)) { | ||
1062 | dev_err(dev, "failed to register LCD\n"); | ||
1063 | return PTR_ERR(ldev); | ||
1064 | } | ||
1065 | ldev->props.max_contrast = 0x0ff; | ||
1066 | data->lcd_contrast = 0xe5; | ||
1067 | data->lcd = ldev; | ||
1068 | picolcd_set_contrast(ldev, 0xe5); | ||
1069 | return 0; | ||
1070 | } | ||
1071 | |||
1072 | static void picolcd_exit_lcd(struct picolcd_data *data) | ||
1073 | { | ||
1074 | struct lcd_device *ldev = data->lcd; | ||
1075 | |||
1076 | data->lcd = NULL; | ||
1077 | if (ldev) | ||
1078 | lcd_device_unregister(ldev); | ||
1079 | } | ||
1080 | |||
1081 | static inline int picolcd_resume_lcd(struct picolcd_data *data) | ||
1082 | { | ||
1083 | if (!data->lcd) | ||
1084 | return 0; | ||
1085 | return picolcd_set_contrast(data->lcd, data->lcd_contrast); | ||
1086 | } | ||
1087 | #else | ||
1088 | static inline int picolcd_init_lcd(struct picolcd_data *data, | ||
1089 | struct hid_report *report) | ||
1090 | { | ||
1091 | return 0; | ||
1092 | } | ||
1093 | static inline void picolcd_exit_lcd(struct picolcd_data *data) | ||
1094 | { | ||
1095 | } | ||
1096 | static inline int picolcd_resume_lcd(struct picolcd_data *data) | ||
1097 | { | ||
1098 | return 0; | ||
1099 | } | ||
1100 | #endif /* CONFIG_HID_PICOLCD_LCD */ | ||
1101 | |||
1102 | #ifdef CONFIG_HID_PICOLCD_LEDS | ||
1103 | /** | ||
1104 | * LED class device | ||
1105 | */ | ||
1106 | static void picolcd_leds_set(struct picolcd_data *data) | ||
1107 | { | ||
1108 | struct hid_report *report; | ||
1109 | unsigned long flags; | ||
1110 | |||
1111 | if (!data->led[0]) | ||
1112 | return; | ||
1113 | report = picolcd_out_report(REPORT_LED_STATE, data->hdev); | ||
1114 | if (!report || report->maxfield != 1 || report->field[0]->report_count != 1) | ||
1115 | return; | ||
1116 | |||
1117 | spin_lock_irqsave(&data->lock, flags); | ||
1118 | hid_set_field(report->field[0], 0, data->led_state); | ||
1119 | usbhid_submit_report(data->hdev, report, USB_DIR_OUT); | ||
1120 | spin_unlock_irqrestore(&data->lock, flags); | ||
1121 | } | ||
1122 | |||
1123 | static void picolcd_led_set_brightness(struct led_classdev *led_cdev, | ||
1124 | enum led_brightness value) | ||
1125 | { | ||
1126 | struct device *dev; | ||
1127 | struct hid_device *hdev; | ||
1128 | struct picolcd_data *data; | ||
1129 | int i, state = 0; | ||
1130 | |||
1131 | dev = led_cdev->dev->parent; | ||
1132 | hdev = container_of(dev, struct hid_device, dev); | ||
1133 | data = hid_get_drvdata(hdev); | ||
1134 | for (i = 0; i < 8; i++) { | ||
1135 | if (led_cdev != data->led[i]) | ||
1136 | continue; | ||
1137 | state = (data->led_state >> i) & 1; | ||
1138 | if (value == LED_OFF && state) { | ||
1139 | data->led_state &= ~(1 << i); | ||
1140 | picolcd_leds_set(data); | ||
1141 | } else if (value != LED_OFF && !state) { | ||
1142 | data->led_state |= 1 << i; | ||
1143 | picolcd_leds_set(data); | ||
1144 | } | ||
1145 | break; | ||
1146 | } | ||
1147 | } | ||
1148 | |||
1149 | static enum led_brightness picolcd_led_get_brightness(struct led_classdev *led_cdev) | ||
1150 | { | ||
1151 | struct device *dev; | ||
1152 | struct hid_device *hdev; | ||
1153 | struct picolcd_data *data; | ||
1154 | int i, value = 0; | ||
1155 | |||
1156 | dev = led_cdev->dev->parent; | ||
1157 | hdev = container_of(dev, struct hid_device, dev); | ||
1158 | data = hid_get_drvdata(hdev); | ||
1159 | for (i = 0; i < 8; i++) | ||
1160 | if (led_cdev == data->led[i]) { | ||
1161 | value = (data->led_state >> i) & 1; | ||
1162 | break; | ||
1163 | } | ||
1164 | return value ? LED_FULL : LED_OFF; | ||
1165 | } | ||
1166 | |||
1167 | static int picolcd_init_leds(struct picolcd_data *data, struct hid_report *report) | ||
1168 | { | ||
1169 | struct device *dev = &data->hdev->dev; | ||
1170 | struct led_classdev *led; | ||
1171 | size_t name_sz = strlen(dev_name(dev)) + 8; | ||
1172 | char *name; | ||
1173 | int i, ret = 0; | ||
1174 | |||
1175 | if (!report) | ||
1176 | return -ENODEV; | ||
1177 | if (report->maxfield != 1 || report->field[0]->report_count != 1 || | ||
1178 | report->field[0]->report_size != 8) { | ||
1179 | dev_err(dev, "unsupported LED_STATE report"); | ||
1180 | return -EINVAL; | ||
1181 | } | ||
1182 | |||
1183 | for (i = 0; i < 8; i++) { | ||
1184 | led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL); | ||
1185 | if (!led) { | ||
1186 | dev_err(dev, "can't allocate memory for LED %d\n", i); | ||
1187 | ret = -ENOMEM; | ||
1188 | goto err; | ||
1189 | } | ||
1190 | name = (void *)(&led[1]); | ||
1191 | snprintf(name, name_sz, "%s::GPO%d", dev_name(dev), i); | ||
1192 | led->name = name; | ||
1193 | led->brightness = 0; | ||
1194 | led->max_brightness = 1; | ||
1195 | led->brightness_get = picolcd_led_get_brightness; | ||
1196 | led->brightness_set = picolcd_led_set_brightness; | ||
1197 | |||
1198 | data->led[i] = led; | ||
1199 | ret = led_classdev_register(dev, data->led[i]); | ||
1200 | if (ret) { | ||
1201 | data->led[i] = NULL; | ||
1202 | kfree(led); | ||
1203 | dev_err(dev, "can't register LED %d\n", i); | ||
1204 | goto err; | ||
1205 | } | ||
1206 | } | ||
1207 | return 0; | ||
1208 | err: | ||
1209 | for (i = 0; i < 8; i++) | ||
1210 | if (data->led[i]) { | ||
1211 | led = data->led[i]; | ||
1212 | data->led[i] = NULL; | ||
1213 | led_classdev_unregister(led); | ||
1214 | kfree(led); | ||
1215 | } | ||
1216 | return ret; | ||
1217 | } | ||
1218 | |||
1219 | static void picolcd_exit_leds(struct picolcd_data *data) | ||
1220 | { | ||
1221 | struct led_classdev *led; | ||
1222 | int i; | ||
1223 | |||
1224 | for (i = 0; i < 8; i++) { | ||
1225 | led = data->led[i]; | ||
1226 | data->led[i] = NULL; | ||
1227 | if (!led) | ||
1228 | continue; | ||
1229 | led_classdev_unregister(led); | ||
1230 | kfree(led); | ||
1231 | } | ||
1232 | } | ||
1233 | |||
1234 | #else | ||
1235 | static inline int picolcd_init_leds(struct picolcd_data *data, | ||
1236 | struct hid_report *report) | ||
1237 | { | ||
1238 | return 0; | ||
1239 | } | ||
1240 | static inline void picolcd_exit_leds(struct picolcd_data *data) | ||
1241 | { | ||
1242 | } | ||
1243 | static inline int picolcd_leds_set(struct picolcd_data *data) | ||
1244 | { | ||
1245 | return 0; | ||
1246 | } | ||
1247 | #endif /* CONFIG_HID_PICOLCD_LEDS */ | ||
1248 | |||
1249 | /* | ||
1250 | * input class device | ||
1251 | */ | ||
1252 | static int picolcd_raw_keypad(struct picolcd_data *data, | ||
1253 | struct hid_report *report, u8 *raw_data, int size) | ||
1254 | { | ||
1255 | /* | ||
1256 | * Keypad event | ||
1257 | * First and second data bytes list currently pressed keys, | ||
1258 | * 0x00 means no key and at most 2 keys may be pressed at same time | ||
1259 | */ | ||
1260 | int i, j; | ||
1261 | |||
1262 | /* determine newly pressed keys */ | ||
1263 | for (i = 0; i < size; i++) { | ||
1264 | unsigned int key_code; | ||
1265 | if (raw_data[i] == 0) | ||
1266 | continue; | ||
1267 | for (j = 0; j < sizeof(data->pressed_keys); j++) | ||
1268 | if (data->pressed_keys[j] == raw_data[i]) | ||
1269 | goto key_already_down; | ||
1270 | for (j = 0; j < sizeof(data->pressed_keys); j++) | ||
1271 | if (data->pressed_keys[j] == 0) { | ||
1272 | data->pressed_keys[j] = raw_data[i]; | ||
1273 | break; | ||
1274 | } | ||
1275 | input_event(data->input_keys, EV_MSC, MSC_SCAN, raw_data[i]); | ||
1276 | if (raw_data[i] < PICOLCD_KEYS) | ||
1277 | key_code = data->keycode[raw_data[i]]; | ||
1278 | else | ||
1279 | key_code = KEY_UNKNOWN; | ||
1280 | if (key_code != KEY_UNKNOWN) { | ||
1281 | dbg_hid(PICOLCD_NAME " got key press for %u:%d", | ||
1282 | raw_data[i], key_code); | ||
1283 | input_report_key(data->input_keys, key_code, 1); | ||
1284 | } | ||
1285 | input_sync(data->input_keys); | ||
1286 | key_already_down: | ||
1287 | continue; | ||
1288 | } | ||
1289 | |||
1290 | /* determine newly released keys */ | ||
1291 | for (j = 0; j < sizeof(data->pressed_keys); j++) { | ||
1292 | unsigned int key_code; | ||
1293 | if (data->pressed_keys[j] == 0) | ||
1294 | continue; | ||
1295 | for (i = 0; i < size; i++) | ||
1296 | if (data->pressed_keys[j] == raw_data[i]) | ||
1297 | goto key_still_down; | ||
1298 | input_event(data->input_keys, EV_MSC, MSC_SCAN, data->pressed_keys[j]); | ||
1299 | if (data->pressed_keys[j] < PICOLCD_KEYS) | ||
1300 | key_code = data->keycode[data->pressed_keys[j]]; | ||
1301 | else | ||
1302 | key_code = KEY_UNKNOWN; | ||
1303 | if (key_code != KEY_UNKNOWN) { | ||
1304 | dbg_hid(PICOLCD_NAME " got key release for %u:%d", | ||
1305 | data->pressed_keys[j], key_code); | ||
1306 | input_report_key(data->input_keys, key_code, 0); | ||
1307 | } | ||
1308 | input_sync(data->input_keys); | ||
1309 | data->pressed_keys[j] = 0; | ||
1310 | key_still_down: | ||
1311 | continue; | ||
1312 | } | ||
1313 | return 1; | ||
1314 | } | ||
1315 | |||
1316 | static int picolcd_raw_cir(struct picolcd_data *data, | ||
1317 | struct hid_report *report, u8 *raw_data, int size) | ||
1318 | { | ||
1319 | /* Need understanding of CIR data format to implement ... */ | ||
1320 | return 1; | ||
1321 | } | ||
1322 | |||
1323 | static int picolcd_check_version(struct hid_device *hdev) | ||
1324 | { | ||
1325 | struct picolcd_data *data = hid_get_drvdata(hdev); | ||
1326 | struct picolcd_pending *verinfo; | ||
1327 | int ret = 0; | ||
1328 | |||
1329 | if (!data) | ||
1330 | return -ENODEV; | ||
1331 | |||
1332 | verinfo = picolcd_send_and_wait(hdev, REPORT_VERSION, NULL, 0); | ||
1333 | if (!verinfo) { | ||
1334 | hid_err(hdev, "no version response from PicoLCD\n"); | ||
1335 | return -ENODEV; | ||
1336 | } | ||
1337 | |||
1338 | if (verinfo->raw_size == 2) { | ||
1339 | data->version[0] = verinfo->raw_data[1]; | ||
1340 | data->version[1] = verinfo->raw_data[0]; | ||
1341 | if (data->status & PICOLCD_BOOTLOADER) { | ||
1342 | hid_info(hdev, "PicoLCD, bootloader version %d.%d\n", | ||
1343 | verinfo->raw_data[1], verinfo->raw_data[0]); | ||
1344 | } else { | ||
1345 | hid_info(hdev, "PicoLCD, firmware version %d.%d\n", | ||
1346 | verinfo->raw_data[1], verinfo->raw_data[0]); | ||
1347 | } | ||
1348 | } else { | ||
1349 | hid_err(hdev, "confused, got unexpected version response from PicoLCD\n"); | ||
1350 | ret = -EINVAL; | ||
1351 | } | ||
1352 | kfree(verinfo); | ||
1353 | return ret; | ||
1354 | } | ||
1355 | |||
1356 | /* | ||
1357 | * Reset our device and wait for answer to VERSION request | ||
1358 | */ | ||
1359 | static int picolcd_reset(struct hid_device *hdev) | ||
1360 | { | ||
1361 | struct picolcd_data *data = hid_get_drvdata(hdev); | ||
1362 | struct hid_report *report = picolcd_out_report(REPORT_RESET, hdev); | ||
1363 | unsigned long flags; | ||
1364 | int error; | ||
1365 | |||
1366 | if (!data || !report || report->maxfield != 1) | ||
1367 | return -ENODEV; | ||
1368 | |||
1369 | spin_lock_irqsave(&data->lock, flags); | ||
1370 | if (hdev->product == USB_DEVICE_ID_PICOLCD_BOOTLOADER) | ||
1371 | data->status |= PICOLCD_BOOTLOADER; | ||
1372 | |||
1373 | /* perform the reset */ | ||
1374 | hid_set_field(report->field[0], 0, 1); | ||
1375 | usbhid_submit_report(hdev, report, USB_DIR_OUT); | ||
1376 | spin_unlock_irqrestore(&data->lock, flags); | ||
1377 | |||
1378 | error = picolcd_check_version(hdev); | ||
1379 | if (error) | ||
1380 | return error; | ||
1381 | |||
1382 | picolcd_resume_lcd(data); | ||
1383 | picolcd_resume_backlight(data); | ||
1384 | #ifdef CONFIG_HID_PICOLCD_FB | ||
1385 | if (data->fb_info) | ||
1386 | schedule_delayed_work(&data->fb_info->deferred_work, 0); | ||
1387 | #endif /* CONFIG_HID_PICOLCD_FB */ | ||
1388 | |||
1389 | picolcd_leds_set(data); | ||
1390 | return 0; | ||
1391 | } | ||
1392 | |||
1393 | /* | ||
1394 | * The "operation_mode" sysfs attribute | ||
1395 | */ | ||
1396 | static ssize_t picolcd_operation_mode_show(struct device *dev, | ||
1397 | struct device_attribute *attr, char *buf) | ||
1398 | { | ||
1399 | struct picolcd_data *data = dev_get_drvdata(dev); | ||
1400 | |||
1401 | if (data->status & PICOLCD_BOOTLOADER) | ||
1402 | return snprintf(buf, PAGE_SIZE, "[bootloader] lcd\n"); | ||
1403 | else | ||
1404 | return snprintf(buf, PAGE_SIZE, "bootloader [lcd]\n"); | ||
1405 | } | ||
1406 | |||
1407 | static ssize_t picolcd_operation_mode_store(struct device *dev, | ||
1408 | struct device_attribute *attr, const char *buf, size_t count) | ||
1409 | { | ||
1410 | struct picolcd_data *data = dev_get_drvdata(dev); | ||
1411 | struct hid_report *report = NULL; | ||
1412 | size_t cnt = count; | ||
1413 | int timeout = data->opmode_delay; | ||
1414 | unsigned long flags; | ||
1415 | |||
1416 | if (cnt >= 3 && strncmp("lcd", buf, 3) == 0) { | ||
1417 | if (data->status & PICOLCD_BOOTLOADER) | ||
1418 | report = picolcd_out_report(REPORT_EXIT_FLASHER, data->hdev); | ||
1419 | buf += 3; | ||
1420 | cnt -= 3; | ||
1421 | } else if (cnt >= 10 && strncmp("bootloader", buf, 10) == 0) { | ||
1422 | if (!(data->status & PICOLCD_BOOTLOADER)) | ||
1423 | report = picolcd_out_report(REPORT_EXIT_KEYBOARD, data->hdev); | ||
1424 | buf += 10; | ||
1425 | cnt -= 10; | ||
1426 | } | ||
1427 | if (!report) | ||
1428 | return -EINVAL; | ||
1429 | |||
1430 | while (cnt > 0 && (buf[cnt-1] == '\n' || buf[cnt-1] == '\r')) | ||
1431 | cnt--; | ||
1432 | if (cnt != 0) | ||
1433 | return -EINVAL; | ||
1434 | |||
1435 | spin_lock_irqsave(&data->lock, flags); | ||
1436 | hid_set_field(report->field[0], 0, timeout & 0xff); | ||
1437 | hid_set_field(report->field[0], 1, (timeout >> 8) & 0xff); | ||
1438 | usbhid_submit_report(data->hdev, report, USB_DIR_OUT); | ||
1439 | spin_unlock_irqrestore(&data->lock, flags); | ||
1440 | return count; | ||
1441 | } | ||
1442 | |||
1443 | static DEVICE_ATTR(operation_mode, 0644, picolcd_operation_mode_show, | ||
1444 | picolcd_operation_mode_store); | ||
1445 | |||
1446 | /* | ||
1447 | * The "operation_mode_delay" sysfs attribute | ||
1448 | */ | ||
1449 | static ssize_t picolcd_operation_mode_delay_show(struct device *dev, | ||
1450 | struct device_attribute *attr, char *buf) | ||
1451 | { | ||
1452 | struct picolcd_data *data = dev_get_drvdata(dev); | ||
1453 | |||
1454 | return snprintf(buf, PAGE_SIZE, "%hu\n", data->opmode_delay); | ||
1455 | } | ||
1456 | |||
1457 | static ssize_t picolcd_operation_mode_delay_store(struct device *dev, | ||
1458 | struct device_attribute *attr, const char *buf, size_t count) | ||
1459 | { | ||
1460 | struct picolcd_data *data = dev_get_drvdata(dev); | ||
1461 | unsigned u; | ||
1462 | if (sscanf(buf, "%u", &u) != 1) | ||
1463 | return -EINVAL; | ||
1464 | if (u > 30000) | ||
1465 | return -EINVAL; | ||
1466 | else | ||
1467 | data->opmode_delay = u; | ||
1468 | return count; | ||
1469 | } | ||
1470 | |||
1471 | static DEVICE_ATTR(operation_mode_delay, 0644, picolcd_operation_mode_delay_show, | ||
1472 | picolcd_operation_mode_delay_store); | ||
1473 | |||
1474 | |||
1475 | #ifdef CONFIG_DEBUG_FS | ||
1476 | /* | ||
1477 | * The "reset" file | ||
1478 | */ | ||
1479 | static int picolcd_debug_reset_show(struct seq_file *f, void *p) | ||
1480 | { | ||
1481 | if (picolcd_fbinfo((struct picolcd_data *)f->private)) | ||
1482 | seq_printf(f, "all fb\n"); | ||
1483 | else | ||
1484 | seq_printf(f, "all\n"); | ||
1485 | return 0; | ||
1486 | } | ||
1487 | |||
1488 | static int picolcd_debug_reset_open(struct inode *inode, struct file *f) | ||
1489 | { | ||
1490 | return single_open(f, picolcd_debug_reset_show, inode->i_private); | ||
1491 | } | ||
1492 | |||
1493 | static ssize_t picolcd_debug_reset_write(struct file *f, const char __user *user_buf, | ||
1494 | size_t count, loff_t *ppos) | ||
1495 | { | ||
1496 | struct picolcd_data *data = ((struct seq_file *)f->private_data)->private; | ||
1497 | char buf[32]; | ||
1498 | size_t cnt = min(count, sizeof(buf)-1); | ||
1499 | if (copy_from_user(buf, user_buf, cnt)) | ||
1500 | return -EFAULT; | ||
1501 | |||
1502 | while (cnt > 0 && (buf[cnt-1] == ' ' || buf[cnt-1] == '\n')) | ||
1503 | cnt--; | ||
1504 | buf[cnt] = '\0'; | ||
1505 | if (strcmp(buf, "all") == 0) { | ||
1506 | picolcd_reset(data->hdev); | ||
1507 | picolcd_fb_reset(data, 1); | ||
1508 | } else if (strcmp(buf, "fb") == 0) { | ||
1509 | picolcd_fb_reset(data, 1); | ||
1510 | } else { | ||
1511 | return -EINVAL; | ||
1512 | } | ||
1513 | return count; | ||
1514 | } | ||
1515 | |||
1516 | static const struct file_operations picolcd_debug_reset_fops = { | ||
1517 | .owner = THIS_MODULE, | ||
1518 | .open = picolcd_debug_reset_open, | ||
1519 | .read = seq_read, | ||
1520 | .llseek = seq_lseek, | ||
1521 | .write = picolcd_debug_reset_write, | ||
1522 | .release = single_release, | ||
1523 | }; | ||
1524 | |||
1525 | /* | ||
1526 | * The "eeprom" file | ||
1527 | */ | ||
1528 | static ssize_t picolcd_debug_eeprom_read(struct file *f, char __user *u, | ||
1529 | size_t s, loff_t *off) | ||
1530 | { | ||
1531 | struct picolcd_data *data = f->private_data; | ||
1532 | struct picolcd_pending *resp; | ||
1533 | u8 raw_data[3]; | ||
1534 | ssize_t ret = -EIO; | ||
1535 | |||
1536 | if (s == 0) | ||
1537 | return -EINVAL; | ||
1538 | if (*off > 0x0ff) | ||
1539 | return 0; | ||
1540 | |||
1541 | /* prepare buffer with info about what we want to read (addr & len) */ | ||
1542 | raw_data[0] = *off & 0xff; | ||
1543 | raw_data[1] = (*off >> 8) & 0xff; | ||
1544 | raw_data[2] = s < 20 ? s : 20; | ||
1545 | if (*off + raw_data[2] > 0xff) | ||
1546 | raw_data[2] = 0x100 - *off; | ||
1547 | resp = picolcd_send_and_wait(data->hdev, REPORT_EE_READ, raw_data, | ||
1548 | sizeof(raw_data)); | ||
1549 | if (!resp) | ||
1550 | return -EIO; | ||
1551 | |||
1552 | if (resp->in_report && resp->in_report->id == REPORT_EE_DATA) { | ||
1553 | /* successful read :) */ | ||
1554 | ret = resp->raw_data[2]; | ||
1555 | if (ret > s) | ||
1556 | ret = s; | ||
1557 | if (copy_to_user(u, resp->raw_data+3, ret)) | ||
1558 | ret = -EFAULT; | ||
1559 | else | ||
1560 | *off += ret; | ||
1561 | } /* anything else is some kind of IO error */ | ||
1562 | |||
1563 | kfree(resp); | ||
1564 | return ret; | ||
1565 | } | ||
1566 | |||
1567 | static ssize_t picolcd_debug_eeprom_write(struct file *f, const char __user *u, | ||
1568 | size_t s, loff_t *off) | ||
1569 | { | ||
1570 | struct picolcd_data *data = f->private_data; | ||
1571 | struct picolcd_pending *resp; | ||
1572 | ssize_t ret = -EIO; | ||
1573 | u8 raw_data[23]; | ||
1574 | |||
1575 | if (s == 0) | ||
1576 | return -EINVAL; | ||
1577 | if (*off > 0x0ff) | ||
1578 | return -ENOSPC; | ||
1579 | |||
1580 | memset(raw_data, 0, sizeof(raw_data)); | ||
1581 | raw_data[0] = *off & 0xff; | ||
1582 | raw_data[1] = (*off >> 8) & 0xff; | ||
1583 | raw_data[2] = min((size_t)20, s); | ||
1584 | if (*off + raw_data[2] > 0xff) | ||
1585 | raw_data[2] = 0x100 - *off; | ||
1586 | |||
1587 | if (copy_from_user(raw_data+3, u, min((u8)20, raw_data[2]))) | ||
1588 | return -EFAULT; | ||
1589 | resp = picolcd_send_and_wait(data->hdev, REPORT_EE_WRITE, raw_data, | ||
1590 | sizeof(raw_data)); | ||
1591 | |||
1592 | if (!resp) | ||
1593 | return -EIO; | ||
1594 | |||
1595 | if (resp->in_report && resp->in_report->id == REPORT_EE_DATA) { | ||
1596 | /* check if written data matches */ | ||
1597 | if (memcmp(raw_data, resp->raw_data, 3+raw_data[2]) == 0) { | ||
1598 | *off += raw_data[2]; | ||
1599 | ret = raw_data[2]; | ||
1600 | } | ||
1601 | } | ||
1602 | kfree(resp); | ||
1603 | return ret; | ||
1604 | } | ||
1605 | |||
1606 | /* | ||
1607 | * Notes: | ||
1608 | * - read/write happens in chunks of at most 20 bytes, it's up to userspace | ||
1609 | * to loop in order to get more data. | ||
1610 | * - on write errors on otherwise correct write request the bytes | ||
1611 | * that should have been written are in undefined state. | ||
1612 | */ | ||
1613 | static const struct file_operations picolcd_debug_eeprom_fops = { | ||
1614 | .owner = THIS_MODULE, | ||
1615 | .open = simple_open, | ||
1616 | .read = picolcd_debug_eeprom_read, | ||
1617 | .write = picolcd_debug_eeprom_write, | ||
1618 | .llseek = generic_file_llseek, | ||
1619 | }; | ||
1620 | |||
1621 | /* | ||
1622 | * The "flash" file | ||
1623 | */ | ||
1624 | /* record a flash address to buf (bounds check to be done by caller) */ | ||
1625 | static int _picolcd_flash_setaddr(struct picolcd_data *data, u8 *buf, long off) | ||
1626 | { | ||
1627 | buf[0] = off & 0xff; | ||
1628 | buf[1] = (off >> 8) & 0xff; | ||
1629 | if (data->addr_sz == 3) | ||
1630 | buf[2] = (off >> 16) & 0xff; | ||
1631 | return data->addr_sz == 2 ? 2 : 3; | ||
1632 | } | ||
1633 | |||
1634 | /* read a given size of data (bounds check to be done by caller) */ | ||
1635 | static ssize_t _picolcd_flash_read(struct picolcd_data *data, int report_id, | ||
1636 | char __user *u, size_t s, loff_t *off) | ||
1637 | { | ||
1638 | struct picolcd_pending *resp; | ||
1639 | u8 raw_data[4]; | ||
1640 | ssize_t ret = 0; | ||
1641 | int len_off, err = -EIO; | ||
1642 | |||
1643 | while (s > 0) { | ||
1644 | err = -EIO; | ||
1645 | len_off = _picolcd_flash_setaddr(data, raw_data, *off); | ||
1646 | raw_data[len_off] = s > 32 ? 32 : s; | ||
1647 | resp = picolcd_send_and_wait(data->hdev, report_id, raw_data, len_off+1); | ||
1648 | if (!resp || !resp->in_report) | ||
1649 | goto skip; | ||
1650 | if (resp->in_report->id == REPORT_MEMORY || | ||
1651 | resp->in_report->id == REPORT_BL_READ_MEMORY) { | ||
1652 | if (memcmp(raw_data, resp->raw_data, len_off+1) != 0) | ||
1653 | goto skip; | ||
1654 | if (copy_to_user(u+ret, resp->raw_data+len_off+1, raw_data[len_off])) { | ||
1655 | err = -EFAULT; | ||
1656 | goto skip; | ||
1657 | } | ||
1658 | *off += raw_data[len_off]; | ||
1659 | s -= raw_data[len_off]; | ||
1660 | ret += raw_data[len_off]; | ||
1661 | err = 0; | ||
1662 | } | ||
1663 | skip: | ||
1664 | kfree(resp); | ||
1665 | if (err) | ||
1666 | return ret > 0 ? ret : err; | ||
1667 | } | ||
1668 | return ret; | ||
1669 | } | ||
1670 | |||
1671 | static ssize_t picolcd_debug_flash_read(struct file *f, char __user *u, | ||
1672 | size_t s, loff_t *off) | ||
1673 | { | ||
1674 | struct picolcd_data *data = f->private_data; | ||
1675 | |||
1676 | if (s == 0) | ||
1677 | return -EINVAL; | ||
1678 | if (*off > 0x05fff) | ||
1679 | return 0; | ||
1680 | if (*off + s > 0x05fff) | ||
1681 | s = 0x06000 - *off; | ||
1682 | |||
1683 | if (data->status & PICOLCD_BOOTLOADER) | ||
1684 | return _picolcd_flash_read(data, REPORT_BL_READ_MEMORY, u, s, off); | ||
1685 | else | ||
1686 | return _picolcd_flash_read(data, REPORT_READ_MEMORY, u, s, off); | ||
1687 | } | ||
1688 | |||
1689 | /* erase block aligned to 64bytes boundary */ | ||
1690 | static ssize_t _picolcd_flash_erase64(struct picolcd_data *data, int report_id, | ||
1691 | loff_t *off) | ||
1692 | { | ||
1693 | struct picolcd_pending *resp; | ||
1694 | u8 raw_data[3]; | ||
1695 | int len_off; | ||
1696 | ssize_t ret = -EIO; | ||
1697 | |||
1698 | if (*off & 0x3f) | ||
1699 | return -EINVAL; | ||
1700 | |||
1701 | len_off = _picolcd_flash_setaddr(data, raw_data, *off); | ||
1702 | resp = picolcd_send_and_wait(data->hdev, report_id, raw_data, len_off); | ||
1703 | if (!resp || !resp->in_report) | ||
1704 | goto skip; | ||
1705 | if (resp->in_report->id == REPORT_MEMORY || | ||
1706 | resp->in_report->id == REPORT_BL_ERASE_MEMORY) { | ||
1707 | if (memcmp(raw_data, resp->raw_data, len_off) != 0) | ||
1708 | goto skip; | ||
1709 | ret = 0; | ||
1710 | } | ||
1711 | skip: | ||
1712 | kfree(resp); | ||
1713 | return ret; | ||
1714 | } | ||
1715 | |||
1716 | /* write a given size of data (bounds check to be done by caller) */ | ||
1717 | static ssize_t _picolcd_flash_write(struct picolcd_data *data, int report_id, | ||
1718 | const char __user *u, size_t s, loff_t *off) | ||
1719 | { | ||
1720 | struct picolcd_pending *resp; | ||
1721 | u8 raw_data[36]; | ||
1722 | ssize_t ret = 0; | ||
1723 | int len_off, err = -EIO; | ||
1724 | |||
1725 | while (s > 0) { | ||
1726 | err = -EIO; | ||
1727 | len_off = _picolcd_flash_setaddr(data, raw_data, *off); | ||
1728 | raw_data[len_off] = s > 32 ? 32 : s; | ||
1729 | if (copy_from_user(raw_data+len_off+1, u, raw_data[len_off])) { | ||
1730 | err = -EFAULT; | ||
1731 | break; | ||
1732 | } | ||
1733 | resp = picolcd_send_and_wait(data->hdev, report_id, raw_data, | ||
1734 | len_off+1+raw_data[len_off]); | ||
1735 | if (!resp || !resp->in_report) | ||
1736 | goto skip; | ||
1737 | if (resp->in_report->id == REPORT_MEMORY || | ||
1738 | resp->in_report->id == REPORT_BL_WRITE_MEMORY) { | ||
1739 | if (memcmp(raw_data, resp->raw_data, len_off+1+raw_data[len_off]) != 0) | ||
1740 | goto skip; | ||
1741 | *off += raw_data[len_off]; | ||
1742 | s -= raw_data[len_off]; | ||
1743 | ret += raw_data[len_off]; | ||
1744 | err = 0; | ||
1745 | } | ||
1746 | skip: | ||
1747 | kfree(resp); | ||
1748 | if (err) | ||
1749 | break; | ||
1750 | } | ||
1751 | return ret > 0 ? ret : err; | ||
1752 | } | ||
1753 | |||
1754 | static ssize_t picolcd_debug_flash_write(struct file *f, const char __user *u, | ||
1755 | size_t s, loff_t *off) | ||
1756 | { | ||
1757 | struct picolcd_data *data = f->private_data; | ||
1758 | ssize_t err, ret = 0; | ||
1759 | int report_erase, report_write; | ||
1760 | |||
1761 | if (s == 0) | ||
1762 | return -EINVAL; | ||
1763 | if (*off > 0x5fff) | ||
1764 | return -ENOSPC; | ||
1765 | if (s & 0x3f) | ||
1766 | return -EINVAL; | ||
1767 | if (*off & 0x3f) | ||
1768 | return -EINVAL; | ||
1769 | |||
1770 | if (data->status & PICOLCD_BOOTLOADER) { | ||
1771 | report_erase = REPORT_BL_ERASE_MEMORY; | ||
1772 | report_write = REPORT_BL_WRITE_MEMORY; | ||
1773 | } else { | ||
1774 | report_erase = REPORT_ERASE_MEMORY; | ||
1775 | report_write = REPORT_WRITE_MEMORY; | ||
1776 | } | ||
1777 | mutex_lock(&data->mutex_flash); | ||
1778 | while (s > 0) { | ||
1779 | err = _picolcd_flash_erase64(data, report_erase, off); | ||
1780 | if (err) | ||
1781 | break; | ||
1782 | err = _picolcd_flash_write(data, report_write, u, 64, off); | ||
1783 | if (err < 0) | ||
1784 | break; | ||
1785 | ret += err; | ||
1786 | *off += err; | ||
1787 | s -= err; | ||
1788 | if (err != 64) | ||
1789 | break; | ||
1790 | } | ||
1791 | mutex_unlock(&data->mutex_flash); | ||
1792 | return ret > 0 ? ret : err; | ||
1793 | } | ||
1794 | |||
1795 | /* | ||
1796 | * Notes: | ||
1797 | * - concurrent writing is prevented by mutex and all writes must be | ||
1798 | * n*64 bytes and 64-byte aligned, each write being preceded by an | ||
1799 | * ERASE which erases a 64byte block. | ||
1800 | * If less than requested was written or an error is returned for an | ||
1801 | * otherwise correct write request the next 64-byte block which should | ||
1802 | * have been written is in undefined state (mostly: original, erased, | ||
1803 | * (half-)written with write error) | ||
1804 | * - reading can happen without special restriction | ||
1805 | */ | ||
1806 | static const struct file_operations picolcd_debug_flash_fops = { | ||
1807 | .owner = THIS_MODULE, | ||
1808 | .open = simple_open, | ||
1809 | .read = picolcd_debug_flash_read, | ||
1810 | .write = picolcd_debug_flash_write, | ||
1811 | .llseek = generic_file_llseek, | ||
1812 | }; | ||
1813 | |||
1814 | |||
1815 | /* | ||
1816 | * Helper code for HID report level dumping/debugging | ||
1817 | */ | ||
1818 | static const char *error_codes[] = { | ||
1819 | "success", "parameter missing", "data_missing", "block readonly", | ||
1820 | "block not erasable", "block too big", "section overflow", | ||
1821 | "invalid command length", "invalid data length", | ||
1822 | }; | ||
1823 | |||
1824 | static void dump_buff_as_hex(char *dst, size_t dst_sz, const u8 *data, | ||
1825 | const size_t data_len) | ||
1826 | { | ||
1827 | int i, j; | ||
1828 | for (i = j = 0; i < data_len && j + 3 < dst_sz; i++) { | ||
1829 | dst[j++] = hex_asc[(data[i] >> 4) & 0x0f]; | ||
1830 | dst[j++] = hex_asc[data[i] & 0x0f]; | ||
1831 | dst[j++] = ' '; | ||
1832 | } | ||
1833 | if (j < dst_sz) { | ||
1834 | dst[j--] = '\0'; | ||
1835 | dst[j] = '\n'; | ||
1836 | } else | ||
1837 | dst[j] = '\0'; | ||
1838 | } | ||
1839 | |||
1840 | static void picolcd_debug_out_report(struct picolcd_data *data, | ||
1841 | struct hid_device *hdev, struct hid_report *report) | ||
1842 | { | ||
1843 | u8 raw_data[70]; | ||
1844 | int raw_size = (report->size >> 3) + 1; | ||
1845 | char *buff; | ||
1846 | #define BUFF_SZ 256 | ||
1847 | |||
1848 | /* Avoid unnecessary overhead if debugfs is disabled */ | ||
1849 | if (list_empty(&hdev->debug_list)) | ||
1850 | return; | ||
1851 | |||
1852 | buff = kmalloc(BUFF_SZ, GFP_ATOMIC); | ||
1853 | if (!buff) | ||
1854 | return; | ||
1855 | |||
1856 | snprintf(buff, BUFF_SZ, "\nout report %d (size %d) = ", | ||
1857 | report->id, raw_size); | ||
1858 | hid_debug_event(hdev, buff); | ||
1859 | if (raw_size + 5 > sizeof(raw_data)) { | ||
1860 | kfree(buff); | ||
1861 | hid_debug_event(hdev, " TOO BIG\n"); | ||
1862 | return; | ||
1863 | } else { | ||
1864 | raw_data[0] = report->id; | ||
1865 | hid_output_report(report, raw_data); | ||
1866 | dump_buff_as_hex(buff, BUFF_SZ, raw_data, raw_size); | ||
1867 | hid_debug_event(hdev, buff); | ||
1868 | } | ||
1869 | |||
1870 | switch (report->id) { | ||
1871 | case REPORT_LED_STATE: | ||
1872 | /* 1 data byte with GPO state */ | ||
1873 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
1874 | "REPORT_LED_STATE", report->id, raw_size-1); | ||
1875 | hid_debug_event(hdev, buff); | ||
1876 | snprintf(buff, BUFF_SZ, "\tGPO state: 0x%02x\n", raw_data[1]); | ||
1877 | hid_debug_event(hdev, buff); | ||
1878 | break; | ||
1879 | case REPORT_BRIGHTNESS: | ||
1880 | /* 1 data byte with brightness */ | ||
1881 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
1882 | "REPORT_BRIGHTNESS", report->id, raw_size-1); | ||
1883 | hid_debug_event(hdev, buff); | ||
1884 | snprintf(buff, BUFF_SZ, "\tBrightness: 0x%02x\n", raw_data[1]); | ||
1885 | hid_debug_event(hdev, buff); | ||
1886 | break; | ||
1887 | case REPORT_CONTRAST: | ||
1888 | /* 1 data byte with contrast */ | ||
1889 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
1890 | "REPORT_CONTRAST", report->id, raw_size-1); | ||
1891 | hid_debug_event(hdev, buff); | ||
1892 | snprintf(buff, BUFF_SZ, "\tContrast: 0x%02x\n", raw_data[1]); | ||
1893 | hid_debug_event(hdev, buff); | ||
1894 | break; | ||
1895 | case REPORT_RESET: | ||
1896 | /* 2 data bytes with reset duration in ms */ | ||
1897 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
1898 | "REPORT_RESET", report->id, raw_size-1); | ||
1899 | hid_debug_event(hdev, buff); | ||
1900 | snprintf(buff, BUFF_SZ, "\tDuration: 0x%02x%02x (%dms)\n", | ||
1901 | raw_data[2], raw_data[1], raw_data[2] << 8 | raw_data[1]); | ||
1902 | hid_debug_event(hdev, buff); | ||
1903 | break; | ||
1904 | case REPORT_LCD_CMD: | ||
1905 | /* 63 data bytes with LCD commands */ | ||
1906 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
1907 | "REPORT_LCD_CMD", report->id, raw_size-1); | ||
1908 | hid_debug_event(hdev, buff); | ||
1909 | /* TODO: format decoding */ | ||
1910 | break; | ||
1911 | case REPORT_LCD_DATA: | ||
1912 | /* 63 data bytes with LCD data */ | ||
1913 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
1914 | "REPORT_LCD_CMD", report->id, raw_size-1); | ||
1915 | /* TODO: format decoding */ | ||
1916 | hid_debug_event(hdev, buff); | ||
1917 | break; | ||
1918 | case REPORT_LCD_CMD_DATA: | ||
1919 | /* 63 data bytes with LCD commands and data */ | ||
1920 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
1921 | "REPORT_LCD_CMD", report->id, raw_size-1); | ||
1922 | /* TODO: format decoding */ | ||
1923 | hid_debug_event(hdev, buff); | ||
1924 | break; | ||
1925 | case REPORT_EE_READ: | ||
1926 | /* 3 data bytes with read area description */ | ||
1927 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
1928 | "REPORT_EE_READ", report->id, raw_size-1); | ||
1929 | hid_debug_event(hdev, buff); | ||
1930 | snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n", | ||
1931 | raw_data[2], raw_data[1]); | ||
1932 | hid_debug_event(hdev, buff); | ||
1933 | snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]); | ||
1934 | hid_debug_event(hdev, buff); | ||
1935 | break; | ||
1936 | case REPORT_EE_WRITE: | ||
1937 | /* 3+1..20 data bytes with write area description */ | ||
1938 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
1939 | "REPORT_EE_WRITE", report->id, raw_size-1); | ||
1940 | hid_debug_event(hdev, buff); | ||
1941 | snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n", | ||
1942 | raw_data[2], raw_data[1]); | ||
1943 | hid_debug_event(hdev, buff); | ||
1944 | snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]); | ||
1945 | hid_debug_event(hdev, buff); | ||
1946 | if (raw_data[3] == 0) { | ||
1947 | snprintf(buff, BUFF_SZ, "\tNo data\n"); | ||
1948 | } else if (raw_data[3] + 4 <= raw_size) { | ||
1949 | snprintf(buff, BUFF_SZ, "\tData: "); | ||
1950 | hid_debug_event(hdev, buff); | ||
1951 | dump_buff_as_hex(buff, BUFF_SZ, raw_data+4, raw_data[3]); | ||
1952 | } else { | ||
1953 | snprintf(buff, BUFF_SZ, "\tData overflowed\n"); | ||
1954 | } | ||
1955 | hid_debug_event(hdev, buff); | ||
1956 | break; | ||
1957 | case REPORT_ERASE_MEMORY: | ||
1958 | case REPORT_BL_ERASE_MEMORY: | ||
1959 | /* 3 data bytes with pointer inside erase block */ | ||
1960 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
1961 | "REPORT_ERASE_MEMORY", report->id, raw_size-1); | ||
1962 | hid_debug_event(hdev, buff); | ||
1963 | switch (data->addr_sz) { | ||
1964 | case 2: | ||
1965 | snprintf(buff, BUFF_SZ, "\tAddress inside 64 byte block: 0x%02x%02x\n", | ||
1966 | raw_data[2], raw_data[1]); | ||
1967 | break; | ||
1968 | case 3: | ||
1969 | snprintf(buff, BUFF_SZ, "\tAddress inside 64 byte block: 0x%02x%02x%02x\n", | ||
1970 | raw_data[3], raw_data[2], raw_data[1]); | ||
1971 | break; | ||
1972 | default: | ||
1973 | snprintf(buff, BUFF_SZ, "\tNot supported\n"); | ||
1974 | } | ||
1975 | hid_debug_event(hdev, buff); | ||
1976 | break; | ||
1977 | case REPORT_READ_MEMORY: | ||
1978 | case REPORT_BL_READ_MEMORY: | ||
1979 | /* 4 data bytes with read area description */ | ||
1980 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
1981 | "REPORT_READ_MEMORY", report->id, raw_size-1); | ||
1982 | hid_debug_event(hdev, buff); | ||
1983 | switch (data->addr_sz) { | ||
1984 | case 2: | ||
1985 | snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n", | ||
1986 | raw_data[2], raw_data[1]); | ||
1987 | hid_debug_event(hdev, buff); | ||
1988 | snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]); | ||
1989 | break; | ||
1990 | case 3: | ||
1991 | snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x%02x\n", | ||
1992 | raw_data[3], raw_data[2], raw_data[1]); | ||
1993 | hid_debug_event(hdev, buff); | ||
1994 | snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[4]); | ||
1995 | break; | ||
1996 | default: | ||
1997 | snprintf(buff, BUFF_SZ, "\tNot supported\n"); | ||
1998 | } | ||
1999 | hid_debug_event(hdev, buff); | ||
2000 | break; | ||
2001 | case REPORT_WRITE_MEMORY: | ||
2002 | case REPORT_BL_WRITE_MEMORY: | ||
2003 | /* 4+1..32 data bytes with write adrea description */ | ||
2004 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
2005 | "REPORT_WRITE_MEMORY", report->id, raw_size-1); | ||
2006 | hid_debug_event(hdev, buff); | ||
2007 | switch (data->addr_sz) { | ||
2008 | case 2: | ||
2009 | snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n", | ||
2010 | raw_data[2], raw_data[1]); | ||
2011 | hid_debug_event(hdev, buff); | ||
2012 | snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]); | ||
2013 | hid_debug_event(hdev, buff); | ||
2014 | if (raw_data[3] == 0) { | ||
2015 | snprintf(buff, BUFF_SZ, "\tNo data\n"); | ||
2016 | } else if (raw_data[3] + 4 <= raw_size) { | ||
2017 | snprintf(buff, BUFF_SZ, "\tData: "); | ||
2018 | hid_debug_event(hdev, buff); | ||
2019 | dump_buff_as_hex(buff, BUFF_SZ, raw_data+4, raw_data[3]); | ||
2020 | } else { | ||
2021 | snprintf(buff, BUFF_SZ, "\tData overflowed\n"); | ||
2022 | } | ||
2023 | break; | ||
2024 | case 3: | ||
2025 | snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x%02x\n", | ||
2026 | raw_data[3], raw_data[2], raw_data[1]); | ||
2027 | hid_debug_event(hdev, buff); | ||
2028 | snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[4]); | ||
2029 | hid_debug_event(hdev, buff); | ||
2030 | if (raw_data[4] == 0) { | ||
2031 | snprintf(buff, BUFF_SZ, "\tNo data\n"); | ||
2032 | } else if (raw_data[4] + 5 <= raw_size) { | ||
2033 | snprintf(buff, BUFF_SZ, "\tData: "); | ||
2034 | hid_debug_event(hdev, buff); | ||
2035 | dump_buff_as_hex(buff, BUFF_SZ, raw_data+5, raw_data[4]); | ||
2036 | } else { | ||
2037 | snprintf(buff, BUFF_SZ, "\tData overflowed\n"); | ||
2038 | } | ||
2039 | break; | ||
2040 | default: | ||
2041 | snprintf(buff, BUFF_SZ, "\tNot supported\n"); | ||
2042 | } | ||
2043 | hid_debug_event(hdev, buff); | ||
2044 | break; | ||
2045 | case REPORT_SPLASH_RESTART: | ||
2046 | /* TODO */ | ||
2047 | break; | ||
2048 | case REPORT_EXIT_KEYBOARD: | ||
2049 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
2050 | "REPORT_EXIT_KEYBOARD", report->id, raw_size-1); | ||
2051 | hid_debug_event(hdev, buff); | ||
2052 | snprintf(buff, BUFF_SZ, "\tRestart delay: %dms (0x%02x%02x)\n", | ||
2053 | raw_data[1] | (raw_data[2] << 8), | ||
2054 | raw_data[2], raw_data[1]); | ||
2055 | hid_debug_event(hdev, buff); | ||
2056 | break; | ||
2057 | case REPORT_VERSION: | ||
2058 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
2059 | "REPORT_VERSION", report->id, raw_size-1); | ||
2060 | hid_debug_event(hdev, buff); | ||
2061 | break; | ||
2062 | case REPORT_DEVID: | ||
2063 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
2064 | "REPORT_DEVID", report->id, raw_size-1); | ||
2065 | hid_debug_event(hdev, buff); | ||
2066 | break; | ||
2067 | case REPORT_SPLASH_SIZE: | ||
2068 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
2069 | "REPORT_SPLASH_SIZE", report->id, raw_size-1); | ||
2070 | hid_debug_event(hdev, buff); | ||
2071 | break; | ||
2072 | case REPORT_HOOK_VERSION: | ||
2073 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
2074 | "REPORT_HOOK_VERSION", report->id, raw_size-1); | ||
2075 | hid_debug_event(hdev, buff); | ||
2076 | break; | ||
2077 | case REPORT_EXIT_FLASHER: | ||
2078 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
2079 | "REPORT_VERSION", report->id, raw_size-1); | ||
2080 | hid_debug_event(hdev, buff); | ||
2081 | snprintf(buff, BUFF_SZ, "\tRestart delay: %dms (0x%02x%02x)\n", | ||
2082 | raw_data[1] | (raw_data[2] << 8), | ||
2083 | raw_data[2], raw_data[1]); | ||
2084 | hid_debug_event(hdev, buff); | ||
2085 | break; | ||
2086 | default: | ||
2087 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
2088 | "<unknown>", report->id, raw_size-1); | ||
2089 | hid_debug_event(hdev, buff); | ||
2090 | break; | ||
2091 | } | ||
2092 | wake_up_interruptible(&hdev->debug_wait); | ||
2093 | kfree(buff); | ||
2094 | } | ||
2095 | |||
2096 | static void picolcd_debug_raw_event(struct picolcd_data *data, | ||
2097 | struct hid_device *hdev, struct hid_report *report, | ||
2098 | u8 *raw_data, int size) | ||
2099 | { | ||
2100 | char *buff; | ||
2101 | |||
2102 | #define BUFF_SZ 256 | ||
2103 | /* Avoid unnecessary overhead if debugfs is disabled */ | ||
2104 | if (!hdev->debug_events) | ||
2105 | return; | ||
2106 | |||
2107 | buff = kmalloc(BUFF_SZ, GFP_ATOMIC); | ||
2108 | if (!buff) | ||
2109 | return; | ||
2110 | |||
2111 | switch (report->id) { | ||
2112 | case REPORT_ERROR_CODE: | ||
2113 | /* 2 data bytes with affected report and error code */ | ||
2114 | snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", | ||
2115 | "REPORT_ERROR_CODE", report->id, size-1); | ||
2116 | hid_debug_event(hdev, buff); | ||
2117 | if (raw_data[2] < ARRAY_SIZE(error_codes)) | ||
2118 | snprintf(buff, BUFF_SZ, "\tError code 0x%02x (%s) in reply to report 0x%02x\n", | ||
2119 | raw_data[2], error_codes[raw_data[2]], raw_data[1]); | ||
2120 | else | ||
2121 | snprintf(buff, BUFF_SZ, "\tError code 0x%02x in reply to report 0x%02x\n", | ||
2122 | raw_data[2], raw_data[1]); | ||
2123 | hid_debug_event(hdev, buff); | ||
2124 | break; | ||
2125 | case REPORT_KEY_STATE: | ||
2126 | /* 2 data bytes with key state */ | ||
2127 | snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", | ||
2128 | "REPORT_KEY_STATE", report->id, size-1); | ||
2129 | hid_debug_event(hdev, buff); | ||
2130 | if (raw_data[1] == 0) | ||
2131 | snprintf(buff, BUFF_SZ, "\tNo key pressed\n"); | ||
2132 | else if (raw_data[2] == 0) | ||
2133 | snprintf(buff, BUFF_SZ, "\tOne key pressed: 0x%02x (%d)\n", | ||
2134 | raw_data[1], raw_data[1]); | ||
2135 | else | ||
2136 | snprintf(buff, BUFF_SZ, "\tTwo keys pressed: 0x%02x (%d), 0x%02x (%d)\n", | ||
2137 | raw_data[1], raw_data[1], raw_data[2], raw_data[2]); | ||
2138 | hid_debug_event(hdev, buff); | ||
2139 | break; | ||
2140 | case REPORT_IR_DATA: | ||
2141 | /* Up to 20 byes of IR scancode data */ | ||
2142 | snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", | ||
2143 | "REPORT_IR_DATA", report->id, size-1); | ||
2144 | hid_debug_event(hdev, buff); | ||
2145 | if (raw_data[1] == 0) { | ||
2146 | snprintf(buff, BUFF_SZ, "\tUnexpectedly 0 data length\n"); | ||
2147 | hid_debug_event(hdev, buff); | ||
2148 | } else if (raw_data[1] + 1 <= size) { | ||
2149 | snprintf(buff, BUFF_SZ, "\tData length: %d\n\tIR Data: ", | ||
2150 | raw_data[1]-1); | ||
2151 | hid_debug_event(hdev, buff); | ||
2152 | dump_buff_as_hex(buff, BUFF_SZ, raw_data+2, raw_data[1]-1); | ||
2153 | hid_debug_event(hdev, buff); | ||
2154 | } else { | ||
2155 | snprintf(buff, BUFF_SZ, "\tOverflowing data length: %d\n", | ||
2156 | raw_data[1]-1); | ||
2157 | hid_debug_event(hdev, buff); | ||
2158 | } | ||
2159 | break; | ||
2160 | case REPORT_EE_DATA: | ||
2161 | /* Data buffer in response to REPORT_EE_READ or REPORT_EE_WRITE */ | ||
2162 | snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", | ||
2163 | "REPORT_EE_DATA", report->id, size-1); | ||
2164 | hid_debug_event(hdev, buff); | ||
2165 | snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n", | ||
2166 | raw_data[2], raw_data[1]); | ||
2167 | hid_debug_event(hdev, buff); | ||
2168 | snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]); | ||
2169 | hid_debug_event(hdev, buff); | ||
2170 | if (raw_data[3] == 0) { | ||
2171 | snprintf(buff, BUFF_SZ, "\tNo data\n"); | ||
2172 | hid_debug_event(hdev, buff); | ||
2173 | } else if (raw_data[3] + 4 <= size) { | ||
2174 | snprintf(buff, BUFF_SZ, "\tData: "); | ||
2175 | hid_debug_event(hdev, buff); | ||
2176 | dump_buff_as_hex(buff, BUFF_SZ, raw_data+4, raw_data[3]); | ||
2177 | hid_debug_event(hdev, buff); | ||
2178 | } else { | ||
2179 | snprintf(buff, BUFF_SZ, "\tData overflowed\n"); | ||
2180 | hid_debug_event(hdev, buff); | ||
2181 | } | ||
2182 | break; | ||
2183 | case REPORT_MEMORY: | ||
2184 | /* Data buffer in response to REPORT_READ_MEMORY or REPORT_WRTIE_MEMORY */ | ||
2185 | snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", | ||
2186 | "REPORT_MEMORY", report->id, size-1); | ||
2187 | hid_debug_event(hdev, buff); | ||
2188 | switch (data->addr_sz) { | ||
2189 | case 2: | ||
2190 | snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n", | ||
2191 | raw_data[2], raw_data[1]); | ||
2192 | hid_debug_event(hdev, buff); | ||
2193 | snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]); | ||
2194 | hid_debug_event(hdev, buff); | ||
2195 | if (raw_data[3] == 0) { | ||
2196 | snprintf(buff, BUFF_SZ, "\tNo data\n"); | ||
2197 | } else if (raw_data[3] + 4 <= size) { | ||
2198 | snprintf(buff, BUFF_SZ, "\tData: "); | ||
2199 | hid_debug_event(hdev, buff); | ||
2200 | dump_buff_as_hex(buff, BUFF_SZ, raw_data+4, raw_data[3]); | ||
2201 | } else { | ||
2202 | snprintf(buff, BUFF_SZ, "\tData overflowed\n"); | ||
2203 | } | ||
2204 | break; | ||
2205 | case 3: | ||
2206 | snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x%02x\n", | ||
2207 | raw_data[3], raw_data[2], raw_data[1]); | ||
2208 | hid_debug_event(hdev, buff); | ||
2209 | snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[4]); | ||
2210 | hid_debug_event(hdev, buff); | ||
2211 | if (raw_data[4] == 0) { | ||
2212 | snprintf(buff, BUFF_SZ, "\tNo data\n"); | ||
2213 | } else if (raw_data[4] + 5 <= size) { | ||
2214 | snprintf(buff, BUFF_SZ, "\tData: "); | ||
2215 | hid_debug_event(hdev, buff); | ||
2216 | dump_buff_as_hex(buff, BUFF_SZ, raw_data+5, raw_data[4]); | ||
2217 | } else { | ||
2218 | snprintf(buff, BUFF_SZ, "\tData overflowed\n"); | ||
2219 | } | ||
2220 | break; | ||
2221 | default: | ||
2222 | snprintf(buff, BUFF_SZ, "\tNot supported\n"); | ||
2223 | } | ||
2224 | hid_debug_event(hdev, buff); | ||
2225 | break; | ||
2226 | case REPORT_VERSION: | ||
2227 | snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", | ||
2228 | "REPORT_VERSION", report->id, size-1); | ||
2229 | hid_debug_event(hdev, buff); | ||
2230 | snprintf(buff, BUFF_SZ, "\tFirmware version: %d.%d\n", | ||
2231 | raw_data[2], raw_data[1]); | ||
2232 | hid_debug_event(hdev, buff); | ||
2233 | break; | ||
2234 | case REPORT_BL_ERASE_MEMORY: | ||
2235 | snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", | ||
2236 | "REPORT_BL_ERASE_MEMORY", report->id, size-1); | ||
2237 | hid_debug_event(hdev, buff); | ||
2238 | /* TODO */ | ||
2239 | break; | ||
2240 | case REPORT_BL_READ_MEMORY: | ||
2241 | snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", | ||
2242 | "REPORT_BL_READ_MEMORY", report->id, size-1); | ||
2243 | hid_debug_event(hdev, buff); | ||
2244 | /* TODO */ | ||
2245 | break; | ||
2246 | case REPORT_BL_WRITE_MEMORY: | ||
2247 | snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", | ||
2248 | "REPORT_BL_WRITE_MEMORY", report->id, size-1); | ||
2249 | hid_debug_event(hdev, buff); | ||
2250 | /* TODO */ | ||
2251 | break; | ||
2252 | case REPORT_DEVID: | ||
2253 | snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", | ||
2254 | "REPORT_DEVID", report->id, size-1); | ||
2255 | hid_debug_event(hdev, buff); | ||
2256 | snprintf(buff, BUFF_SZ, "\tSerial: 0x%02x%02x%02x%02x\n", | ||
2257 | raw_data[1], raw_data[2], raw_data[3], raw_data[4]); | ||
2258 | hid_debug_event(hdev, buff); | ||
2259 | snprintf(buff, BUFF_SZ, "\tType: 0x%02x\n", | ||
2260 | raw_data[5]); | ||
2261 | hid_debug_event(hdev, buff); | ||
2262 | break; | ||
2263 | case REPORT_SPLASH_SIZE: | ||
2264 | snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", | ||
2265 | "REPORT_SPLASH_SIZE", report->id, size-1); | ||
2266 | hid_debug_event(hdev, buff); | ||
2267 | snprintf(buff, BUFF_SZ, "\tTotal splash space: %d\n", | ||
2268 | (raw_data[2] << 8) | raw_data[1]); | ||
2269 | hid_debug_event(hdev, buff); | ||
2270 | snprintf(buff, BUFF_SZ, "\tUsed splash space: %d\n", | ||
2271 | (raw_data[4] << 8) | raw_data[3]); | ||
2272 | hid_debug_event(hdev, buff); | ||
2273 | break; | ||
2274 | case REPORT_HOOK_VERSION: | ||
2275 | snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", | ||
2276 | "REPORT_HOOK_VERSION", report->id, size-1); | ||
2277 | hid_debug_event(hdev, buff); | ||
2278 | snprintf(buff, BUFF_SZ, "\tFirmware version: %d.%d\n", | ||
2279 | raw_data[1], raw_data[2]); | ||
2280 | hid_debug_event(hdev, buff); | ||
2281 | break; | ||
2282 | default: | ||
2283 | snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", | ||
2284 | "<unknown>", report->id, size-1); | ||
2285 | hid_debug_event(hdev, buff); | ||
2286 | break; | ||
2287 | } | ||
2288 | wake_up_interruptible(&hdev->debug_wait); | ||
2289 | kfree(buff); | ||
2290 | } | ||
2291 | |||
2292 | static void picolcd_init_devfs(struct picolcd_data *data, | ||
2293 | struct hid_report *eeprom_r, struct hid_report *eeprom_w, | ||
2294 | struct hid_report *flash_r, struct hid_report *flash_w, | ||
2295 | struct hid_report *reset) | ||
2296 | { | ||
2297 | struct hid_device *hdev = data->hdev; | ||
2298 | |||
2299 | mutex_init(&data->mutex_flash); | ||
2300 | |||
2301 | /* reset */ | ||
2302 | if (reset) | ||
2303 | data->debug_reset = debugfs_create_file("reset", 0600, | ||
2304 | hdev->debug_dir, data, &picolcd_debug_reset_fops); | ||
2305 | |||
2306 | /* eeprom */ | ||
2307 | if (eeprom_r || eeprom_w) | ||
2308 | data->debug_eeprom = debugfs_create_file("eeprom", | ||
2309 | (eeprom_w ? S_IWUSR : 0) | (eeprom_r ? S_IRUSR : 0), | ||
2310 | hdev->debug_dir, data, &picolcd_debug_eeprom_fops); | ||
2311 | |||
2312 | /* flash */ | ||
2313 | if (flash_r && flash_r->maxfield == 1 && flash_r->field[0]->report_size == 8) | ||
2314 | data->addr_sz = flash_r->field[0]->report_count - 1; | ||
2315 | else | ||
2316 | data->addr_sz = -1; | ||
2317 | if (data->addr_sz == 2 || data->addr_sz == 3) { | ||
2318 | data->debug_flash = debugfs_create_file("flash", | ||
2319 | (flash_w ? S_IWUSR : 0) | (flash_r ? S_IRUSR : 0), | ||
2320 | hdev->debug_dir, data, &picolcd_debug_flash_fops); | ||
2321 | } else if (flash_r || flash_w) | ||
2322 | hid_warn(hdev, "Unexpected FLASH access reports, please submit rdesc for review\n"); | ||
2323 | } | ||
2324 | |||
2325 | static void picolcd_exit_devfs(struct picolcd_data *data) | ||
2326 | { | ||
2327 | struct dentry *dent; | ||
2328 | |||
2329 | dent = data->debug_reset; | ||
2330 | data->debug_reset = NULL; | ||
2331 | if (dent) | ||
2332 | debugfs_remove(dent); | ||
2333 | dent = data->debug_eeprom; | ||
2334 | data->debug_eeprom = NULL; | ||
2335 | if (dent) | ||
2336 | debugfs_remove(dent); | ||
2337 | dent = data->debug_flash; | ||
2338 | data->debug_flash = NULL; | ||
2339 | if (dent) | ||
2340 | debugfs_remove(dent); | ||
2341 | mutex_destroy(&data->mutex_flash); | ||
2342 | } | ||
2343 | #else | ||
2344 | static inline void picolcd_debug_raw_event(struct picolcd_data *data, | ||
2345 | struct hid_device *hdev, struct hid_report *report, | ||
2346 | u8 *raw_data, int size) | ||
2347 | { | ||
2348 | } | ||
2349 | static inline void picolcd_init_devfs(struct picolcd_data *data, | ||
2350 | struct hid_report *eeprom_r, struct hid_report *eeprom_w, | ||
2351 | struct hid_report *flash_r, struct hid_report *flash_w, | ||
2352 | struct hid_report *reset) | ||
2353 | { | ||
2354 | } | ||
2355 | static inline void picolcd_exit_devfs(struct picolcd_data *data) | ||
2356 | { | ||
2357 | } | ||
2358 | #endif /* CONFIG_DEBUG_FS */ | ||
2359 | |||
2360 | /* | ||
2361 | * Handle raw report as sent by device | ||
2362 | */ | ||
2363 | static int picolcd_raw_event(struct hid_device *hdev, | ||
2364 | struct hid_report *report, u8 *raw_data, int size) | ||
2365 | { | ||
2366 | struct picolcd_data *data = hid_get_drvdata(hdev); | ||
2367 | unsigned long flags; | ||
2368 | int ret = 0; | ||
2369 | |||
2370 | if (!data) | ||
2371 | return 1; | ||
2372 | |||
2373 | if (report->id == REPORT_KEY_STATE) { | ||
2374 | if (data->input_keys) | ||
2375 | ret = picolcd_raw_keypad(data, report, raw_data+1, size-1); | ||
2376 | } else if (report->id == REPORT_IR_DATA) { | ||
2377 | if (data->input_cir) | ||
2378 | ret = picolcd_raw_cir(data, report, raw_data+1, size-1); | ||
2379 | } else { | ||
2380 | spin_lock_irqsave(&data->lock, flags); | ||
2381 | /* | ||
2382 | * We let the caller of picolcd_send_and_wait() check if the | ||
2383 | * report we got is one of the expected ones or not. | ||
2384 | */ | ||
2385 | if (data->pending) { | ||
2386 | memcpy(data->pending->raw_data, raw_data+1, size-1); | ||
2387 | data->pending->raw_size = size-1; | ||
2388 | data->pending->in_report = report; | ||
2389 | complete(&data->pending->ready); | ||
2390 | } | ||
2391 | spin_unlock_irqrestore(&data->lock, flags); | ||
2392 | } | ||
2393 | |||
2394 | picolcd_debug_raw_event(data, hdev, report, raw_data, size); | ||
2395 | return 1; | ||
2396 | } | ||
2397 | |||
2398 | #ifdef CONFIG_PM | ||
2399 | static int picolcd_suspend(struct hid_device *hdev, pm_message_t message) | ||
2400 | { | ||
2401 | if (PMSG_IS_AUTO(message)) | ||
2402 | return 0; | ||
2403 | |||
2404 | picolcd_suspend_backlight(hid_get_drvdata(hdev)); | ||
2405 | dbg_hid(PICOLCD_NAME " device ready for suspend\n"); | ||
2406 | return 0; | ||
2407 | } | ||
2408 | |||
2409 | static int picolcd_resume(struct hid_device *hdev) | ||
2410 | { | ||
2411 | int ret; | ||
2412 | ret = picolcd_resume_backlight(hid_get_drvdata(hdev)); | ||
2413 | if (ret) | ||
2414 | dbg_hid(PICOLCD_NAME " restoring backlight failed: %d\n", ret); | ||
2415 | return 0; | ||
2416 | } | ||
2417 | |||
2418 | static int picolcd_reset_resume(struct hid_device *hdev) | ||
2419 | { | ||
2420 | int ret; | ||
2421 | ret = picolcd_reset(hdev); | ||
2422 | if (ret) | ||
2423 | dbg_hid(PICOLCD_NAME " resetting our device failed: %d\n", ret); | ||
2424 | ret = picolcd_fb_reset(hid_get_drvdata(hdev), 0); | ||
2425 | if (ret) | ||
2426 | dbg_hid(PICOLCD_NAME " restoring framebuffer content failed: %d\n", ret); | ||
2427 | ret = picolcd_resume_lcd(hid_get_drvdata(hdev)); | ||
2428 | if (ret) | ||
2429 | dbg_hid(PICOLCD_NAME " restoring lcd failed: %d\n", ret); | ||
2430 | ret = picolcd_resume_backlight(hid_get_drvdata(hdev)); | ||
2431 | if (ret) | ||
2432 | dbg_hid(PICOLCD_NAME " restoring backlight failed: %d\n", ret); | ||
2433 | picolcd_leds_set(hid_get_drvdata(hdev)); | ||
2434 | return 0; | ||
2435 | } | ||
2436 | #endif | ||
2437 | |||
2438 | /* initialize keypad input device */ | ||
2439 | static int picolcd_init_keys(struct picolcd_data *data, | ||
2440 | struct hid_report *report) | ||
2441 | { | ||
2442 | struct hid_device *hdev = data->hdev; | ||
2443 | struct input_dev *idev; | ||
2444 | int error, i; | ||
2445 | |||
2446 | if (!report) | ||
2447 | return -ENODEV; | ||
2448 | if (report->maxfield != 1 || report->field[0]->report_count != 2 || | ||
2449 | report->field[0]->report_size != 8) { | ||
2450 | hid_err(hdev, "unsupported KEY_STATE report\n"); | ||
2451 | return -EINVAL; | ||
2452 | } | ||
2453 | |||
2454 | idev = input_allocate_device(); | ||
2455 | if (idev == NULL) { | ||
2456 | hid_err(hdev, "failed to allocate input device\n"); | ||
2457 | return -ENOMEM; | ||
2458 | } | ||
2459 | input_set_drvdata(idev, hdev); | ||
2460 | memcpy(data->keycode, def_keymap, sizeof(def_keymap)); | ||
2461 | idev->name = hdev->name; | ||
2462 | idev->phys = hdev->phys; | ||
2463 | idev->uniq = hdev->uniq; | ||
2464 | idev->id.bustype = hdev->bus; | ||
2465 | idev->id.vendor = hdev->vendor; | ||
2466 | idev->id.product = hdev->product; | ||
2467 | idev->id.version = hdev->version; | ||
2468 | idev->dev.parent = hdev->dev.parent; | ||
2469 | idev->keycode = &data->keycode; | ||
2470 | idev->keycodemax = PICOLCD_KEYS; | ||
2471 | idev->keycodesize = sizeof(data->keycode[0]); | ||
2472 | input_set_capability(idev, EV_MSC, MSC_SCAN); | ||
2473 | set_bit(EV_REP, idev->evbit); | ||
2474 | for (i = 0; i < PICOLCD_KEYS; i++) | ||
2475 | input_set_capability(idev, EV_KEY, data->keycode[i]); | ||
2476 | error = input_register_device(idev); | ||
2477 | if (error) { | ||
2478 | hid_err(hdev, "error registering the input device\n"); | ||
2479 | input_free_device(idev); | ||
2480 | return error; | ||
2481 | } | ||
2482 | data->input_keys = idev; | ||
2483 | return 0; | ||
2484 | } | ||
2485 | |||
2486 | static void picolcd_exit_keys(struct picolcd_data *data) | ||
2487 | { | ||
2488 | struct input_dev *idev = data->input_keys; | ||
2489 | |||
2490 | data->input_keys = NULL; | ||
2491 | if (idev) | ||
2492 | input_unregister_device(idev); | ||
2493 | } | ||
2494 | |||
2495 | /* initialize CIR input device */ | ||
2496 | static inline int picolcd_init_cir(struct picolcd_data *data, struct hid_report *report) | ||
2497 | { | ||
2498 | /* support not implemented yet */ | ||
2499 | return 0; | ||
2500 | } | ||
2501 | |||
2502 | static inline void picolcd_exit_cir(struct picolcd_data *data) | ||
2503 | { | ||
2504 | } | ||
2505 | |||
2506 | static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data) | ||
2507 | { | ||
2508 | int error; | ||
2509 | |||
2510 | error = picolcd_check_version(hdev); | ||
2511 | if (error) | ||
2512 | return error; | ||
2513 | |||
2514 | if (data->version[0] != 0 && data->version[1] != 3) | ||
2515 | hid_info(hdev, "Device with untested firmware revision, please submit /sys/kernel/debug/hid/%s/rdesc for this device.\n", | ||
2516 | dev_name(&hdev->dev)); | ||
2517 | |||
2518 | /* Setup keypad input device */ | ||
2519 | error = picolcd_init_keys(data, picolcd_in_report(REPORT_KEY_STATE, hdev)); | ||
2520 | if (error) | ||
2521 | goto err; | ||
2522 | |||
2523 | /* Setup CIR input device */ | ||
2524 | error = picolcd_init_cir(data, picolcd_in_report(REPORT_IR_DATA, hdev)); | ||
2525 | if (error) | ||
2526 | goto err; | ||
2527 | |||
2528 | /* Set up the framebuffer device */ | ||
2529 | error = picolcd_init_framebuffer(data); | ||
2530 | if (error) | ||
2531 | goto err; | ||
2532 | |||
2533 | /* Setup lcd class device */ | ||
2534 | error = picolcd_init_lcd(data, picolcd_out_report(REPORT_CONTRAST, hdev)); | ||
2535 | if (error) | ||
2536 | goto err; | ||
2537 | |||
2538 | /* Setup backlight class device */ | ||
2539 | error = picolcd_init_backlight(data, picolcd_out_report(REPORT_BRIGHTNESS, hdev)); | ||
2540 | if (error) | ||
2541 | goto err; | ||
2542 | |||
2543 | /* Setup the LED class devices */ | ||
2544 | error = picolcd_init_leds(data, picolcd_out_report(REPORT_LED_STATE, hdev)); | ||
2545 | if (error) | ||
2546 | goto err; | ||
2547 | |||
2548 | picolcd_init_devfs(data, picolcd_out_report(REPORT_EE_READ, hdev), | ||
2549 | picolcd_out_report(REPORT_EE_WRITE, hdev), | ||
2550 | picolcd_out_report(REPORT_READ_MEMORY, hdev), | ||
2551 | picolcd_out_report(REPORT_WRITE_MEMORY, hdev), | ||
2552 | picolcd_out_report(REPORT_RESET, hdev)); | ||
2553 | return 0; | ||
2554 | err: | ||
2555 | picolcd_exit_leds(data); | ||
2556 | picolcd_exit_backlight(data); | ||
2557 | picolcd_exit_lcd(data); | ||
2558 | picolcd_exit_framebuffer(data); | ||
2559 | picolcd_exit_cir(data); | ||
2560 | picolcd_exit_keys(data); | ||
2561 | return error; | ||
2562 | } | ||
2563 | |||
2564 | static int picolcd_probe_bootloader(struct hid_device *hdev, struct picolcd_data *data) | ||
2565 | { | ||
2566 | int error; | ||
2567 | |||
2568 | error = picolcd_check_version(hdev); | ||
2569 | if (error) | ||
2570 | return error; | ||
2571 | |||
2572 | if (data->version[0] != 1 && data->version[1] != 0) | ||
2573 | hid_info(hdev, "Device with untested bootloader revision, please submit /sys/kernel/debug/hid/%s/rdesc for this device.\n", | ||
2574 | dev_name(&hdev->dev)); | ||
2575 | |||
2576 | picolcd_init_devfs(data, NULL, NULL, | ||
2577 | picolcd_out_report(REPORT_BL_READ_MEMORY, hdev), | ||
2578 | picolcd_out_report(REPORT_BL_WRITE_MEMORY, hdev), NULL); | ||
2579 | return 0; | ||
2580 | } | ||
2581 | |||
2582 | static int picolcd_probe(struct hid_device *hdev, | ||
2583 | const struct hid_device_id *id) | ||
2584 | { | ||
2585 | struct picolcd_data *data; | ||
2586 | int error = -ENOMEM; | ||
2587 | |||
2588 | dbg_hid(PICOLCD_NAME " hardware probe...\n"); | ||
2589 | |||
2590 | /* | ||
2591 | * Let's allocate the picolcd data structure, set some reasonable | ||
2592 | * defaults, and associate it with the device | ||
2593 | */ | ||
2594 | data = kzalloc(sizeof(struct picolcd_data), GFP_KERNEL); | ||
2595 | if (data == NULL) { | ||
2596 | hid_err(hdev, "can't allocate space for Minibox PicoLCD device data\n"); | ||
2597 | error = -ENOMEM; | ||
2598 | goto err_no_cleanup; | ||
2599 | } | ||
2600 | |||
2601 | spin_lock_init(&data->lock); | ||
2602 | mutex_init(&data->mutex); | ||
2603 | data->hdev = hdev; | ||
2604 | data->opmode_delay = 5000; | ||
2605 | if (hdev->product == USB_DEVICE_ID_PICOLCD_BOOTLOADER) | ||
2606 | data->status |= PICOLCD_BOOTLOADER; | ||
2607 | hid_set_drvdata(hdev, data); | ||
2608 | |||
2609 | /* Parse the device reports and start it up */ | ||
2610 | error = hid_parse(hdev); | ||
2611 | if (error) { | ||
2612 | hid_err(hdev, "device report parse failed\n"); | ||
2613 | goto err_cleanup_data; | ||
2614 | } | ||
2615 | |||
2616 | error = hid_hw_start(hdev, 0); | ||
2617 | if (error) { | ||
2618 | hid_err(hdev, "hardware start failed\n"); | ||
2619 | goto err_cleanup_data; | ||
2620 | } | ||
2621 | |||
2622 | error = hid_hw_open(hdev); | ||
2623 | if (error) { | ||
2624 | hid_err(hdev, "failed to open input interrupt pipe for key and IR events\n"); | ||
2625 | goto err_cleanup_hid_hw; | ||
2626 | } | ||
2627 | |||
2628 | error = device_create_file(&hdev->dev, &dev_attr_operation_mode_delay); | ||
2629 | if (error) { | ||
2630 | hid_err(hdev, "failed to create sysfs attributes\n"); | ||
2631 | goto err_cleanup_hid_ll; | ||
2632 | } | ||
2633 | |||
2634 | error = device_create_file(&hdev->dev, &dev_attr_operation_mode); | ||
2635 | if (error) { | ||
2636 | hid_err(hdev, "failed to create sysfs attributes\n"); | ||
2637 | goto err_cleanup_sysfs1; | ||
2638 | } | ||
2639 | |||
2640 | if (data->status & PICOLCD_BOOTLOADER) | ||
2641 | error = picolcd_probe_bootloader(hdev, data); | ||
2642 | else | ||
2643 | error = picolcd_probe_lcd(hdev, data); | ||
2644 | if (error) | ||
2645 | goto err_cleanup_sysfs2; | ||
2646 | |||
2647 | dbg_hid(PICOLCD_NAME " activated and initialized\n"); | ||
2648 | return 0; | ||
2649 | |||
2650 | err_cleanup_sysfs2: | ||
2651 | device_remove_file(&hdev->dev, &dev_attr_operation_mode); | ||
2652 | err_cleanup_sysfs1: | ||
2653 | device_remove_file(&hdev->dev, &dev_attr_operation_mode_delay); | ||
2654 | err_cleanup_hid_ll: | ||
2655 | hid_hw_close(hdev); | ||
2656 | err_cleanup_hid_hw: | ||
2657 | hid_hw_stop(hdev); | ||
2658 | err_cleanup_data: | ||
2659 | kfree(data); | ||
2660 | err_no_cleanup: | ||
2661 | hid_set_drvdata(hdev, NULL); | ||
2662 | |||
2663 | return error; | ||
2664 | } | ||
2665 | |||
2666 | static void picolcd_remove(struct hid_device *hdev) | ||
2667 | { | ||
2668 | struct picolcd_data *data = hid_get_drvdata(hdev); | ||
2669 | unsigned long flags; | ||
2670 | |||
2671 | dbg_hid(PICOLCD_NAME " hardware remove...\n"); | ||
2672 | spin_lock_irqsave(&data->lock, flags); | ||
2673 | data->status |= PICOLCD_FAILED; | ||
2674 | spin_unlock_irqrestore(&data->lock, flags); | ||
2675 | #ifdef CONFIG_HID_PICOLCD_FB | ||
2676 | /* short-circuit FB as early as possible in order to | ||
2677 | * avoid long delays if we host console. | ||
2678 | */ | ||
2679 | if (data->fb_info) | ||
2680 | data->fb_info->par = NULL; | ||
2681 | #endif | ||
2682 | |||
2683 | picolcd_exit_devfs(data); | ||
2684 | device_remove_file(&hdev->dev, &dev_attr_operation_mode); | ||
2685 | device_remove_file(&hdev->dev, &dev_attr_operation_mode_delay); | ||
2686 | hid_hw_close(hdev); | ||
2687 | hid_hw_stop(hdev); | ||
2688 | hid_set_drvdata(hdev, NULL); | ||
2689 | |||
2690 | /* Shortcut potential pending reply that will never arrive */ | ||
2691 | spin_lock_irqsave(&data->lock, flags); | ||
2692 | if (data->pending) | ||
2693 | complete(&data->pending->ready); | ||
2694 | spin_unlock_irqrestore(&data->lock, flags); | ||
2695 | |||
2696 | /* Cleanup LED */ | ||
2697 | picolcd_exit_leds(data); | ||
2698 | /* Clean up the framebuffer */ | ||
2699 | picolcd_exit_backlight(data); | ||
2700 | picolcd_exit_lcd(data); | ||
2701 | picolcd_exit_framebuffer(data); | ||
2702 | /* Cleanup input */ | ||
2703 | picolcd_exit_cir(data); | ||
2704 | picolcd_exit_keys(data); | ||
2705 | |||
2706 | mutex_destroy(&data->mutex); | ||
2707 | /* Finally, clean up the picolcd data itself */ | ||
2708 | kfree(data); | ||
2709 | } | ||
2710 | |||
2711 | static const struct hid_device_id picolcd_devices[] = { | ||
2712 | { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD) }, | ||
2713 | { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD_BOOTLOADER) }, | ||
2714 | { } | ||
2715 | }; | ||
2716 | MODULE_DEVICE_TABLE(hid, picolcd_devices); | ||
2717 | |||
2718 | static struct hid_driver picolcd_driver = { | ||
2719 | .name = "hid-picolcd", | ||
2720 | .id_table = picolcd_devices, | ||
2721 | .probe = picolcd_probe, | ||
2722 | .remove = picolcd_remove, | ||
2723 | .raw_event = picolcd_raw_event, | ||
2724 | #ifdef CONFIG_PM | ||
2725 | .suspend = picolcd_suspend, | ||
2726 | .resume = picolcd_resume, | ||
2727 | .reset_resume = picolcd_reset_resume, | ||
2728 | #endif | ||
2729 | }; | ||
2730 | |||
2731 | static int __init picolcd_init(void) | ||
2732 | { | ||
2733 | return hid_register_driver(&picolcd_driver); | ||
2734 | } | ||
2735 | |||
2736 | static void __exit picolcd_exit(void) | ||
2737 | { | ||
2738 | hid_unregister_driver(&picolcd_driver); | ||
2739 | #ifdef CONFIG_HID_PICOLCD_FB | ||
2740 | flush_work_sync(&picolcd_fb_cleanup); | ||
2741 | WARN_ON(fb_pending); | ||
2742 | #endif | ||
2743 | } | ||
2744 | |||
2745 | module_init(picolcd_init); | ||
2746 | module_exit(picolcd_exit); | ||
2747 | MODULE_DESCRIPTION("Minibox graphics PicoLCD Driver"); | ||
2748 | MODULE_LICENSE("GPL v2"); | ||
diff --git a/drivers/hid/hid-picolcd.h b/drivers/hid/hid-picolcd.h new file mode 100644 index 000000000000..4ddb3188d5c8 --- /dev/null +++ b/drivers/hid/hid-picolcd.h | |||
@@ -0,0 +1,306 @@ | |||
1 | /*************************************************************************** | ||
2 | * Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org> * | ||
3 | * * | ||
4 | * Based on Logitech G13 driver (v0.4) * | ||
5 | * Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu> * | ||
6 | * * | ||
7 | * This program is free software: you can redistribute it and/or modify * | ||
8 | * it under the terms of the GNU General Public License as published by * | ||
9 | * the Free Software Foundation, version 2 of the License. * | ||
10 | * * | ||
11 | * This driver is distributed in the hope that it will be useful, but * | ||
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of * | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * | ||
14 | * General Public License for more details. * | ||
15 | * * | ||
16 | * You should have received a copy of the GNU General Public License * | ||
17 | * along with this software. If not see <http://www.gnu.org/licenses/>. * | ||
18 | ***************************************************************************/ | ||
19 | |||
20 | #define PICOLCD_NAME "PicoLCD (graphic)" | ||
21 | |||
22 | /* Report numbers */ | ||
23 | #define REPORT_ERROR_CODE 0x10 /* LCD: IN[16] */ | ||
24 | #define ERR_SUCCESS 0x00 | ||
25 | #define ERR_PARAMETER_MISSING 0x01 | ||
26 | #define ERR_DATA_MISSING 0x02 | ||
27 | #define ERR_BLOCK_READ_ONLY 0x03 | ||
28 | #define ERR_BLOCK_NOT_ERASABLE 0x04 | ||
29 | #define ERR_BLOCK_TOO_BIG 0x05 | ||
30 | #define ERR_SECTION_OVERFLOW 0x06 | ||
31 | #define ERR_INVALID_CMD_LEN 0x07 | ||
32 | #define ERR_INVALID_DATA_LEN 0x08 | ||
33 | #define REPORT_KEY_STATE 0x11 /* LCD: IN[2] */ | ||
34 | #define REPORT_IR_DATA 0x21 /* LCD: IN[63] */ | ||
35 | #define REPORT_EE_DATA 0x32 /* LCD: IN[63] */ | ||
36 | #define REPORT_MEMORY 0x41 /* LCD: IN[63] */ | ||
37 | #define REPORT_LED_STATE 0x81 /* LCD: OUT[1] */ | ||
38 | #define REPORT_BRIGHTNESS 0x91 /* LCD: OUT[1] */ | ||
39 | #define REPORT_CONTRAST 0x92 /* LCD: OUT[1] */ | ||
40 | #define REPORT_RESET 0x93 /* LCD: OUT[2] */ | ||
41 | #define REPORT_LCD_CMD 0x94 /* LCD: OUT[63] */ | ||
42 | #define REPORT_LCD_DATA 0x95 /* LCD: OUT[63] */ | ||
43 | #define REPORT_LCD_CMD_DATA 0x96 /* LCD: OUT[63] */ | ||
44 | #define REPORT_EE_READ 0xa3 /* LCD: OUT[63] */ | ||
45 | #define REPORT_EE_WRITE 0xa4 /* LCD: OUT[63] */ | ||
46 | #define REPORT_ERASE_MEMORY 0xb2 /* LCD: OUT[2] */ | ||
47 | #define REPORT_READ_MEMORY 0xb3 /* LCD: OUT[3] */ | ||
48 | #define REPORT_WRITE_MEMORY 0xb4 /* LCD: OUT[63] */ | ||
49 | #define REPORT_SPLASH_RESTART 0xc1 /* LCD: OUT[1] */ | ||
50 | #define REPORT_EXIT_KEYBOARD 0xef /* LCD: OUT[2] */ | ||
51 | #define REPORT_VERSION 0xf1 /* LCD: IN[2],OUT[1] Bootloader: IN[2],OUT[1] */ | ||
52 | #define REPORT_BL_ERASE_MEMORY 0xf2 /* Bootloader: IN[36],OUT[4] */ | ||
53 | #define REPORT_BL_READ_MEMORY 0xf3 /* Bootloader: IN[36],OUT[4] */ | ||
54 | #define REPORT_BL_WRITE_MEMORY 0xf4 /* Bootloader: IN[36],OUT[36] */ | ||
55 | #define REPORT_DEVID 0xf5 /* LCD: IN[5], OUT[1] Bootloader: IN[5],OUT[1] */ | ||
56 | #define REPORT_SPLASH_SIZE 0xf6 /* LCD: IN[4], OUT[1] */ | ||
57 | #define REPORT_HOOK_VERSION 0xf7 /* LCD: IN[2], OUT[1] */ | ||
58 | #define REPORT_EXIT_FLASHER 0xff /* Bootloader: OUT[2] */ | ||
59 | |||
60 | /* Description of in-progress IO operation, used for operations | ||
61 | * that trigger response from device */ | ||
62 | struct picolcd_pending { | ||
63 | struct hid_report *out_report; | ||
64 | struct hid_report *in_report; | ||
65 | struct completion ready; | ||
66 | int raw_size; | ||
67 | u8 raw_data[64]; | ||
68 | }; | ||
69 | |||
70 | |||
71 | #define PICOLCD_KEYS 17 | ||
72 | |||
73 | /* Per device data structure */ | ||
74 | struct picolcd_data { | ||
75 | struct hid_device *hdev; | ||
76 | #ifdef CONFIG_DEBUG_FS | ||
77 | struct dentry *debug_reset; | ||
78 | struct dentry *debug_eeprom; | ||
79 | struct dentry *debug_flash; | ||
80 | struct mutex mutex_flash; | ||
81 | int addr_sz; | ||
82 | #endif | ||
83 | u8 version[2]; | ||
84 | unsigned short opmode_delay; | ||
85 | /* input stuff */ | ||
86 | u8 pressed_keys[2]; | ||
87 | struct input_dev *input_keys; | ||
88 | struct input_dev *input_cir; | ||
89 | unsigned short keycode[PICOLCD_KEYS]; | ||
90 | |||
91 | #ifdef CONFIG_HID_PICOLCD_FB | ||
92 | /* Framebuffer stuff */ | ||
93 | u8 fb_update_rate; | ||
94 | u8 fb_bpp; | ||
95 | u8 fb_force; | ||
96 | u8 *fb_vbitmap; /* local copy of what was sent to PicoLCD */ | ||
97 | u8 *fb_bitmap; /* framebuffer */ | ||
98 | struct fb_info *fb_info; | ||
99 | struct fb_deferred_io fb_defio; | ||
100 | #endif /* CONFIG_HID_PICOLCD_FB */ | ||
101 | #ifdef CONFIG_HID_PICOLCD_LCD | ||
102 | struct lcd_device *lcd; | ||
103 | u8 lcd_contrast; | ||
104 | #endif /* CONFIG_HID_PICOLCD_LCD */ | ||
105 | #ifdef CONFIG_HID_PICOLCD_BACKLIGHT | ||
106 | struct backlight_device *backlight; | ||
107 | u8 lcd_brightness; | ||
108 | u8 lcd_power; | ||
109 | #endif /* CONFIG_HID_PICOLCD_BACKLIGHT */ | ||
110 | #ifdef CONFIG_HID_PICOLCD_LEDS | ||
111 | /* LED stuff */ | ||
112 | u8 led_state; | ||
113 | struct led_classdev *led[8]; | ||
114 | #endif /* CONFIG_HID_PICOLCD_LEDS */ | ||
115 | |||
116 | /* Housekeeping stuff */ | ||
117 | spinlock_t lock; | ||
118 | struct mutex mutex; | ||
119 | struct picolcd_pending *pending; | ||
120 | int status; | ||
121 | #define PICOLCD_BOOTLOADER 1 | ||
122 | #define PICOLCD_FAILED 2 | ||
123 | #define PICOLCD_READY_FB 4 | ||
124 | }; | ||
125 | |||
126 | |||
127 | /* Find a given report */ | ||
128 | #define picolcd_in_report(id, dev) picolcd_report(id, dev, HID_INPUT_REPORT) | ||
129 | #define picolcd_out_report(id, dev) picolcd_report(id, dev, HID_OUTPUT_REPORT) | ||
130 | |||
131 | struct hid_report *picolcd_report(int id, struct hid_device *hdev, int dir); | ||
132 | |||
133 | #ifdef CONFIG_DEBUG_FS | ||
134 | void picolcd_debug_out_report(struct picolcd_data *data, | ||
135 | struct hid_device *hdev, struct hid_report *report); | ||
136 | #define usbhid_submit_report(a, b, c) \ | ||
137 | do { \ | ||
138 | picolcd_debug_out_report(hid_get_drvdata(a), a, b); \ | ||
139 | usbhid_submit_report(a, b, c); \ | ||
140 | } while (0) | ||
141 | |||
142 | void picolcd_debug_raw_event(struct picolcd_data *data, | ||
143 | struct hid_device *hdev, struct hid_report *report, | ||
144 | u8 *raw_data, int size); | ||
145 | |||
146 | void picolcd_init_devfs(struct picolcd_data *data, | ||
147 | struct hid_report *eeprom_r, struct hid_report *eeprom_w, | ||
148 | struct hid_report *flash_r, struct hid_report *flash_w, | ||
149 | struct hid_report *reset); | ||
150 | |||
151 | void picolcd_exit_devfs(struct picolcd_data *data); | ||
152 | #else | ||
153 | static inline void picolcd_debug_raw_event(struct picolcd_data *data, | ||
154 | struct hid_device *hdev, struct hid_report *report, | ||
155 | u8 *raw_data, int size) | ||
156 | { | ||
157 | } | ||
158 | static inline void picolcd_init_devfs(struct picolcd_data *data, | ||
159 | struct hid_report *eeprom_r, struct hid_report *eeprom_w, | ||
160 | struct hid_report *flash_r, struct hid_report *flash_w, | ||
161 | struct hid_report *reset) | ||
162 | { | ||
163 | } | ||
164 | static inline void picolcd_exit_devfs(struct picolcd_data *data) | ||
165 | { | ||
166 | } | ||
167 | static inline void picolcd_debug_raw_event(struct picolcd_data *data, | ||
168 | struct hid_device *hdev, struct hid_report *report, | ||
169 | u8 *raw_data, int size) | ||
170 | { | ||
171 | } | ||
172 | #endif /* CONFIG_DEBUG_FS */ | ||
173 | |||
174 | |||
175 | #ifdef CONFIG_HID_PICOLCD_FB | ||
176 | int picolcd_fb_reset(struct picolcd_data *data, int clear); | ||
177 | |||
178 | int picolcd_init_framebuffer(struct picolcd_data *data); | ||
179 | |||
180 | void picolcd_exit_framebuffer(struct picolcd_data *data); | ||
181 | |||
182 | void picolcd_fb_unload(void); | ||
183 | |||
184 | void picolcd_fb_refresh(struct picolcd_data *data); | ||
185 | #define picolcd_fbinfo(d) ((d)->fb_info) | ||
186 | #else | ||
187 | static inline int picolcd_fb_reset(struct picolcd_data *data, int clear) | ||
188 | { | ||
189 | return 0; | ||
190 | } | ||
191 | static inline int picolcd_init_framebuffer(struct picolcd_data *data) | ||
192 | { | ||
193 | return 0; | ||
194 | } | ||
195 | static inline void picolcd_exit_framebuffer(struct picolcd_data *data) | ||
196 | { | ||
197 | } | ||
198 | static inline void picolcd_fb_unload(void) | ||
199 | { | ||
200 | } | ||
201 | static inline void picolcd_fb_refresh(struct picolcd_data *data) | ||
202 | { | ||
203 | } | ||
204 | #define picolcd_fbinfo(d) NULL | ||
205 | #endif /* CONFIG_HID_PICOLCD_FB */ | ||
206 | |||
207 | |||
208 | #ifdef CONFIG_HID_PICOLCD_BACKLIGHT | ||
209 | int picolcd_init_backlight(struct picolcd_data *data, | ||
210 | struct hid_report *report); | ||
211 | |||
212 | void picolcd_exit_backlight(struct picolcd_data *data); | ||
213 | |||
214 | int picolcd_resume_backlight(struct picolcd_data *data); | ||
215 | |||
216 | void picolcd_suspend_backlight(struct picolcd_data *data); | ||
217 | #else | ||
218 | static inline int picolcd_init_backlight(struct picolcd_data *data, | ||
219 | struct hid_report *report) | ||
220 | { | ||
221 | return 0; | ||
222 | } | ||
223 | static inline void picolcd_exit_backlight(struct picolcd_data *data) | ||
224 | { | ||
225 | } | ||
226 | static inline int picolcd_resume_backlight(struct picolcd_data *data) | ||
227 | { | ||
228 | return 0; | ||
229 | } | ||
230 | static inline void picolcd_suspend_backlight(struct picolcd_data *data) | ||
231 | { | ||
232 | } | ||
233 | |||
234 | #endif /* CONFIG_HID_PICOLCD_BACKLIGHT */ | ||
235 | |||
236 | |||
237 | #ifdef CONFIG_HID_PICOLCD_LCD | ||
238 | int picolcd_init_lcd(struct picolcd_data *data, | ||
239 | struct hid_report *report); | ||
240 | |||
241 | void picolcd_exit_lcd(struct picolcd_data *data); | ||
242 | |||
243 | int picolcd_resume_lcd(struct picolcd_data *data); | ||
244 | #else | ||
245 | static inline int picolcd_init_lcd(struct picolcd_data *data, | ||
246 | struct hid_report *report) | ||
247 | { | ||
248 | return 0; | ||
249 | } | ||
250 | static inline void picolcd_exit_lcd(struct picolcd_data *data) | ||
251 | { | ||
252 | } | ||
253 | static inline int picolcd_resume_lcd(struct picolcd_data *data) | ||
254 | { | ||
255 | return 0; | ||
256 | } | ||
257 | #endif /* CONFIG_HID_PICOLCD_LCD */ | ||
258 | |||
259 | |||
260 | #ifdef CONFIG_HID_PICOLCD_LEDS | ||
261 | int picolcd_init_leds(struct picolcd_data *data, | ||
262 | struct hid_report *report); | ||
263 | |||
264 | void picolcd_exit_leds(struct picolcd_data *data); | ||
265 | |||
266 | void picolcd_leds_set(struct picolcd_data *data); | ||
267 | #else | ||
268 | static inline int picolcd_init_leds(struct picolcd_data *data, | ||
269 | struct hid_report *report) | ||
270 | { | ||
271 | return 0; | ||
272 | } | ||
273 | static inline void picolcd_exit_leds(struct picolcd_data *data) | ||
274 | { | ||
275 | } | ||
276 | static inline void picolcd_leds_set(struct picolcd_data *data) | ||
277 | { | ||
278 | } | ||
279 | #endif /* CONFIG_HID_PICOLCD_LEDS */ | ||
280 | |||
281 | |||
282 | #ifdef CONFIG_HID_PICOLCD_CIR | ||
283 | int picolcd_raw_cir(struct picolcd_data *data, | ||
284 | struct hid_report *report, u8 *raw_data, int size); | ||
285 | |||
286 | int picolcd_init_cir(struct picolcd_data *data, struct hid_report *report); | ||
287 | |||
288 | void picolcd_exit_cir(struct picolcd_data *data); | ||
289 | #else | ||
290 | static inline int picolcd_raw_cir(struct picolcd_data *data, | ||
291 | struct hid_report *report, u8 *raw_data, int size) | ||
292 | { | ||
293 | return 1; | ||
294 | } | ||
295 | static inline int picolcd_init_cir(struct picolcd_data *data, struct hid_report *report) | ||
296 | { | ||
297 | return 0; | ||
298 | } | ||
299 | static inline void picolcd_exit_cir(struct picolcd_data *data) | ||
300 | { | ||
301 | } | ||
302 | #endif /* CONFIG_HID_PICOLCD_LIRC */ | ||
303 | |||
304 | int picolcd_reset(struct hid_device *hdev); | ||
305 | struct picolcd_pending *picolcd_send_and_wait(struct hid_device *hdev, | ||
306 | int report_id, const u8 *raw_data, int size); | ||
diff --git a/drivers/hid/hid-picolcd_backlight.c b/drivers/hid/hid-picolcd_backlight.c new file mode 100644 index 000000000000..cd1ccd7205cd --- /dev/null +++ b/drivers/hid/hid-picolcd_backlight.c | |||
@@ -0,0 +1,121 @@ | |||
1 | /*************************************************************************** | ||
2 | * Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org> * | ||
3 | * * | ||
4 | * Based on Logitech G13 driver (v0.4) * | ||
5 | * Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu> * | ||
6 | * * | ||
7 | * This program is free software: you can redistribute it and/or modify * | ||
8 | * it under the terms of the GNU General Public License as published by * | ||
9 | * the Free Software Foundation, version 2 of the License. * | ||
10 | * * | ||
11 | * This driver is distributed in the hope that it will be useful, but * | ||
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of * | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * | ||
14 | * General Public License for more details. * | ||
15 | * * | ||
16 | * You should have received a copy of the GNU General Public License * | ||
17 | * along with this software. If not see <http://www.gnu.org/licenses/>. * | ||
18 | ***************************************************************************/ | ||
19 | |||
20 | #include <linux/hid.h> | ||
21 | #include "usbhid/usbhid.h" | ||
22 | #include <linux/usb.h> | ||
23 | |||
24 | #include <linux/fb.h> | ||
25 | #include <linux/backlight.h> | ||
26 | |||
27 | #include "hid-picolcd.h" | ||
28 | |||
29 | static int picolcd_get_brightness(struct backlight_device *bdev) | ||
30 | { | ||
31 | struct picolcd_data *data = bl_get_data(bdev); | ||
32 | return data->lcd_brightness; | ||
33 | } | ||
34 | |||
35 | static int picolcd_set_brightness(struct backlight_device *bdev) | ||
36 | { | ||
37 | struct picolcd_data *data = bl_get_data(bdev); | ||
38 | struct hid_report *report = picolcd_out_report(REPORT_BRIGHTNESS, data->hdev); | ||
39 | unsigned long flags; | ||
40 | |||
41 | if (!report || report->maxfield != 1 || report->field[0]->report_count != 1) | ||
42 | return -ENODEV; | ||
43 | |||
44 | data->lcd_brightness = bdev->props.brightness & 0x0ff; | ||
45 | data->lcd_power = bdev->props.power; | ||
46 | spin_lock_irqsave(&data->lock, flags); | ||
47 | hid_set_field(report->field[0], 0, data->lcd_power == FB_BLANK_UNBLANK ? data->lcd_brightness : 0); | ||
48 | usbhid_submit_report(data->hdev, report, USB_DIR_OUT); | ||
49 | spin_unlock_irqrestore(&data->lock, flags); | ||
50 | return 0; | ||
51 | } | ||
52 | |||
53 | static int picolcd_check_bl_fb(struct backlight_device *bdev, struct fb_info *fb) | ||
54 | { | ||
55 | return fb && fb == picolcd_fbinfo((struct picolcd_data *)bl_get_data(bdev)); | ||
56 | } | ||
57 | |||
58 | static const struct backlight_ops picolcd_blops = { | ||
59 | .update_status = picolcd_set_brightness, | ||
60 | .get_brightness = picolcd_get_brightness, | ||
61 | .check_fb = picolcd_check_bl_fb, | ||
62 | }; | ||
63 | |||
64 | int picolcd_init_backlight(struct picolcd_data *data, struct hid_report *report) | ||
65 | { | ||
66 | struct device *dev = &data->hdev->dev; | ||
67 | struct backlight_device *bdev; | ||
68 | struct backlight_properties props; | ||
69 | if (!report) | ||
70 | return -ENODEV; | ||
71 | if (report->maxfield != 1 || report->field[0]->report_count != 1 || | ||
72 | report->field[0]->report_size != 8) { | ||
73 | dev_err(dev, "unsupported BRIGHTNESS report"); | ||
74 | return -EINVAL; | ||
75 | } | ||
76 | |||
77 | memset(&props, 0, sizeof(props)); | ||
78 | props.type = BACKLIGHT_RAW; | ||
79 | props.max_brightness = 0xff; | ||
80 | bdev = backlight_device_register(dev_name(dev), dev, data, | ||
81 | &picolcd_blops, &props); | ||
82 | if (IS_ERR(bdev)) { | ||
83 | dev_err(dev, "failed to register backlight\n"); | ||
84 | return PTR_ERR(bdev); | ||
85 | } | ||
86 | bdev->props.brightness = 0xff; | ||
87 | data->lcd_brightness = 0xff; | ||
88 | data->backlight = bdev; | ||
89 | picolcd_set_brightness(bdev); | ||
90 | return 0; | ||
91 | } | ||
92 | |||
93 | void picolcd_exit_backlight(struct picolcd_data *data) | ||
94 | { | ||
95 | struct backlight_device *bdev = data->backlight; | ||
96 | |||
97 | data->backlight = NULL; | ||
98 | if (bdev) | ||
99 | backlight_device_unregister(bdev); | ||
100 | } | ||
101 | |||
102 | int picolcd_resume_backlight(struct picolcd_data *data) | ||
103 | { | ||
104 | if (!data->backlight) | ||
105 | return 0; | ||
106 | return picolcd_set_brightness(data->backlight); | ||
107 | } | ||
108 | |||
109 | #ifdef CONFIG_PM | ||
110 | void picolcd_suspend_backlight(struct picolcd_data *data) | ||
111 | { | ||
112 | int bl_power = data->lcd_power; | ||
113 | if (!data->backlight) | ||
114 | return; | ||
115 | |||
116 | data->backlight->props.power = FB_BLANK_POWERDOWN; | ||
117 | picolcd_set_brightness(data->backlight); | ||
118 | data->lcd_power = data->backlight->props.power = bl_power; | ||
119 | } | ||
120 | #endif /* CONFIG_PM */ | ||
121 | |||
diff --git a/drivers/hid/hid-picolcd_cir.c b/drivers/hid/hid-picolcd_cir.c new file mode 100644 index 000000000000..dc632c3cab36 --- /dev/null +++ b/drivers/hid/hid-picolcd_cir.c | |||
@@ -0,0 +1,61 @@ | |||
1 | /*************************************************************************** | ||
2 | * Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org> * | ||
3 | * * | ||
4 | * Based on Logitech G13 driver (v0.4) * | ||
5 | * Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu> * | ||
6 | * * | ||
7 | * This program is free software: you can redistribute it and/or modify * | ||
8 | * it under the terms of the GNU General Public License as published by * | ||
9 | * the Free Software Foundation, version 2 of the License. * | ||
10 | * * | ||
11 | * This driver is distributed in the hope that it will be useful, but * | ||
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of * | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * | ||
14 | * General Public License for more details. * | ||
15 | * * | ||
16 | * You should have received a copy of the GNU General Public License * | ||
17 | * along with this software. If not see <http://www.gnu.org/licenses/>. * | ||
18 | ***************************************************************************/ | ||
19 | |||
20 | #include <linux/hid.h> | ||
21 | #include <linux/hid-debug.h> | ||
22 | #include <linux/input.h> | ||
23 | #include "hid-ids.h" | ||
24 | #include "usbhid/usbhid.h" | ||
25 | #include <linux/usb.h> | ||
26 | |||
27 | #include <linux/fb.h> | ||
28 | #include <linux/vmalloc.h> | ||
29 | #include <linux/backlight.h> | ||
30 | #include <linux/lcd.h> | ||
31 | |||
32 | #include <linux/leds.h> | ||
33 | |||
34 | #include <linux/seq_file.h> | ||
35 | #include <linux/debugfs.h> | ||
36 | |||
37 | #include <linux/completion.h> | ||
38 | #include <linux/uaccess.h> | ||
39 | #include <linux/module.h> | ||
40 | |||
41 | #include "hid-picolcd.h" | ||
42 | |||
43 | |||
44 | int picolcd_raw_cir(struct picolcd_data *data, | ||
45 | struct hid_report *report, u8 *raw_data, int size) | ||
46 | { | ||
47 | /* Need understanding of CIR data format to implement ... */ | ||
48 | return 1; | ||
49 | } | ||
50 | |||
51 | /* initialize CIR input device */ | ||
52 | int picolcd_init_cir(struct picolcd_data *data, struct hid_report *report) | ||
53 | { | ||
54 | /* support not implemented yet */ | ||
55 | return 0; | ||
56 | } | ||
57 | |||
58 | void picolcd_exit_cir(struct picolcd_data *data) | ||
59 | { | ||
60 | } | ||
61 | |||
diff --git a/drivers/hid/hid-picolcd_core.c b/drivers/hid/hid-picolcd_core.c new file mode 100644 index 000000000000..36e7548a6e19 --- /dev/null +++ b/drivers/hid/hid-picolcd_core.c | |||
@@ -0,0 +1,704 @@ | |||
1 | /*************************************************************************** | ||
2 | * Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org> * | ||
3 | * * | ||
4 | * Based on Logitech G13 driver (v0.4) * | ||
5 | * Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu> * | ||
6 | * * | ||
7 | * This program is free software: you can redistribute it and/or modify * | ||
8 | * it under the terms of the GNU General Public License as published by * | ||
9 | * the Free Software Foundation, version 2 of the License. * | ||
10 | * * | ||
11 | * This driver is distributed in the hope that it will be useful, but * | ||
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of * | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * | ||
14 | * General Public License for more details. * | ||
15 | * * | ||
16 | * You should have received a copy of the GNU General Public License * | ||
17 | * along with this software. If not see <http://www.gnu.org/licenses/>. * | ||
18 | ***************************************************************************/ | ||
19 | |||
20 | #include <linux/hid.h> | ||
21 | #include <linux/hid-debug.h> | ||
22 | #include <linux/input.h> | ||
23 | #include "hid-ids.h" | ||
24 | #include "usbhid/usbhid.h" | ||
25 | #include <linux/usb.h> | ||
26 | |||
27 | #include <linux/fb.h> | ||
28 | #include <linux/vmalloc.h> | ||
29 | |||
30 | #include <linux/completion.h> | ||
31 | #include <linux/uaccess.h> | ||
32 | #include <linux/module.h> | ||
33 | |||
34 | #include "hid-picolcd.h" | ||
35 | |||
36 | |||
37 | /* Input device | ||
38 | * | ||
39 | * The PicoLCD has an IR receiver header, a built-in keypad with 5 keys | ||
40 | * and header for 4x4 key matrix. The built-in keys are part of the matrix. | ||
41 | */ | ||
42 | static const unsigned short def_keymap[PICOLCD_KEYS] = { | ||
43 | KEY_RESERVED, /* none */ | ||
44 | KEY_BACK, /* col 4 + row 1 */ | ||
45 | KEY_HOMEPAGE, /* col 3 + row 1 */ | ||
46 | KEY_RESERVED, /* col 2 + row 1 */ | ||
47 | KEY_RESERVED, /* col 1 + row 1 */ | ||
48 | KEY_SCROLLUP, /* col 4 + row 2 */ | ||
49 | KEY_OK, /* col 3 + row 2 */ | ||
50 | KEY_SCROLLDOWN, /* col 2 + row 2 */ | ||
51 | KEY_RESERVED, /* col 1 + row 2 */ | ||
52 | KEY_RESERVED, /* col 4 + row 3 */ | ||
53 | KEY_RESERVED, /* col 3 + row 3 */ | ||
54 | KEY_RESERVED, /* col 2 + row 3 */ | ||
55 | KEY_RESERVED, /* col 1 + row 3 */ | ||
56 | KEY_RESERVED, /* col 4 + row 4 */ | ||
57 | KEY_RESERVED, /* col 3 + row 4 */ | ||
58 | KEY_RESERVED, /* col 2 + row 4 */ | ||
59 | KEY_RESERVED, /* col 1 + row 4 */ | ||
60 | }; | ||
61 | |||
62 | |||
63 | /* Find a given report */ | ||
64 | struct hid_report *picolcd_report(int id, struct hid_device *hdev, int dir) | ||
65 | { | ||
66 | struct list_head *feature_report_list = &hdev->report_enum[dir].report_list; | ||
67 | struct hid_report *report = NULL; | ||
68 | |||
69 | list_for_each_entry(report, feature_report_list, list) { | ||
70 | if (report->id == id) | ||
71 | return report; | ||
72 | } | ||
73 | hid_warn(hdev, "No report with id 0x%x found\n", id); | ||
74 | return NULL; | ||
75 | } | ||
76 | |||
77 | /* Submit a report and wait for a reply from device - if device fades away | ||
78 | * or does not respond in time, return NULL */ | ||
79 | struct picolcd_pending *picolcd_send_and_wait(struct hid_device *hdev, | ||
80 | int report_id, const u8 *raw_data, int size) | ||
81 | { | ||
82 | struct picolcd_data *data = hid_get_drvdata(hdev); | ||
83 | struct picolcd_pending *work; | ||
84 | struct hid_report *report = picolcd_out_report(report_id, hdev); | ||
85 | unsigned long flags; | ||
86 | int i, j, k; | ||
87 | |||
88 | if (!report || !data) | ||
89 | return NULL; | ||
90 | if (data->status & PICOLCD_FAILED) | ||
91 | return NULL; | ||
92 | work = kzalloc(sizeof(*work), GFP_KERNEL); | ||
93 | if (!work) | ||
94 | return NULL; | ||
95 | |||
96 | init_completion(&work->ready); | ||
97 | work->out_report = report; | ||
98 | work->in_report = NULL; | ||
99 | work->raw_size = 0; | ||
100 | |||
101 | mutex_lock(&data->mutex); | ||
102 | spin_lock_irqsave(&data->lock, flags); | ||
103 | for (i = k = 0; i < report->maxfield; i++) | ||
104 | for (j = 0; j < report->field[i]->report_count; j++) { | ||
105 | hid_set_field(report->field[i], j, k < size ? raw_data[k] : 0); | ||
106 | k++; | ||
107 | } | ||
108 | data->pending = work; | ||
109 | usbhid_submit_report(data->hdev, report, USB_DIR_OUT); | ||
110 | spin_unlock_irqrestore(&data->lock, flags); | ||
111 | wait_for_completion_interruptible_timeout(&work->ready, HZ*2); | ||
112 | spin_lock_irqsave(&data->lock, flags); | ||
113 | data->pending = NULL; | ||
114 | spin_unlock_irqrestore(&data->lock, flags); | ||
115 | mutex_unlock(&data->mutex); | ||
116 | return work; | ||
117 | } | ||
118 | |||
119 | /* | ||
120 | * input class device | ||
121 | */ | ||
122 | static int picolcd_raw_keypad(struct picolcd_data *data, | ||
123 | struct hid_report *report, u8 *raw_data, int size) | ||
124 | { | ||
125 | /* | ||
126 | * Keypad event | ||
127 | * First and second data bytes list currently pressed keys, | ||
128 | * 0x00 means no key and at most 2 keys may be pressed at same time | ||
129 | */ | ||
130 | int i, j; | ||
131 | |||
132 | /* determine newly pressed keys */ | ||
133 | for (i = 0; i < size; i++) { | ||
134 | unsigned int key_code; | ||
135 | if (raw_data[i] == 0) | ||
136 | continue; | ||
137 | for (j = 0; j < sizeof(data->pressed_keys); j++) | ||
138 | if (data->pressed_keys[j] == raw_data[i]) | ||
139 | goto key_already_down; | ||
140 | for (j = 0; j < sizeof(data->pressed_keys); j++) | ||
141 | if (data->pressed_keys[j] == 0) { | ||
142 | data->pressed_keys[j] = raw_data[i]; | ||
143 | break; | ||
144 | } | ||
145 | input_event(data->input_keys, EV_MSC, MSC_SCAN, raw_data[i]); | ||
146 | if (raw_data[i] < PICOLCD_KEYS) | ||
147 | key_code = data->keycode[raw_data[i]]; | ||
148 | else | ||
149 | key_code = KEY_UNKNOWN; | ||
150 | if (key_code != KEY_UNKNOWN) { | ||
151 | dbg_hid(PICOLCD_NAME " got key press for %u:%d", | ||
152 | raw_data[i], key_code); | ||
153 | input_report_key(data->input_keys, key_code, 1); | ||
154 | } | ||
155 | input_sync(data->input_keys); | ||
156 | key_already_down: | ||
157 | continue; | ||
158 | } | ||
159 | |||
160 | /* determine newly released keys */ | ||
161 | for (j = 0; j < sizeof(data->pressed_keys); j++) { | ||
162 | unsigned int key_code; | ||
163 | if (data->pressed_keys[j] == 0) | ||
164 | continue; | ||
165 | for (i = 0; i < size; i++) | ||
166 | if (data->pressed_keys[j] == raw_data[i]) | ||
167 | goto key_still_down; | ||
168 | input_event(data->input_keys, EV_MSC, MSC_SCAN, data->pressed_keys[j]); | ||
169 | if (data->pressed_keys[j] < PICOLCD_KEYS) | ||
170 | key_code = data->keycode[data->pressed_keys[j]]; | ||
171 | else | ||
172 | key_code = KEY_UNKNOWN; | ||
173 | if (key_code != KEY_UNKNOWN) { | ||
174 | dbg_hid(PICOLCD_NAME " got key release for %u:%d", | ||
175 | data->pressed_keys[j], key_code); | ||
176 | input_report_key(data->input_keys, key_code, 0); | ||
177 | } | ||
178 | input_sync(data->input_keys); | ||
179 | data->pressed_keys[j] = 0; | ||
180 | key_still_down: | ||
181 | continue; | ||
182 | } | ||
183 | return 1; | ||
184 | } | ||
185 | |||
186 | static int picolcd_check_version(struct hid_device *hdev) | ||
187 | { | ||
188 | struct picolcd_data *data = hid_get_drvdata(hdev); | ||
189 | struct picolcd_pending *verinfo; | ||
190 | int ret = 0; | ||
191 | |||
192 | if (!data) | ||
193 | return -ENODEV; | ||
194 | |||
195 | verinfo = picolcd_send_and_wait(hdev, REPORT_VERSION, NULL, 0); | ||
196 | if (!verinfo) { | ||
197 | hid_err(hdev, "no version response from PicoLCD\n"); | ||
198 | return -ENODEV; | ||
199 | } | ||
200 | |||
201 | if (verinfo->raw_size == 2) { | ||
202 | data->version[0] = verinfo->raw_data[1]; | ||
203 | data->version[1] = verinfo->raw_data[0]; | ||
204 | if (data->status & PICOLCD_BOOTLOADER) { | ||
205 | hid_info(hdev, "PicoLCD, bootloader version %d.%d\n", | ||
206 | verinfo->raw_data[1], verinfo->raw_data[0]); | ||
207 | } else { | ||
208 | hid_info(hdev, "PicoLCD, firmware version %d.%d\n", | ||
209 | verinfo->raw_data[1], verinfo->raw_data[0]); | ||
210 | } | ||
211 | } else { | ||
212 | hid_err(hdev, "confused, got unexpected version response from PicoLCD\n"); | ||
213 | ret = -EINVAL; | ||
214 | } | ||
215 | kfree(verinfo); | ||
216 | return ret; | ||
217 | } | ||
218 | |||
219 | /* | ||
220 | * Reset our device and wait for answer to VERSION request | ||
221 | */ | ||
222 | int picolcd_reset(struct hid_device *hdev) | ||
223 | { | ||
224 | struct picolcd_data *data = hid_get_drvdata(hdev); | ||
225 | struct hid_report *report = picolcd_out_report(REPORT_RESET, hdev); | ||
226 | unsigned long flags; | ||
227 | int error; | ||
228 | |||
229 | if (!data || !report || report->maxfield != 1) | ||
230 | return -ENODEV; | ||
231 | |||
232 | spin_lock_irqsave(&data->lock, flags); | ||
233 | if (hdev->product == USB_DEVICE_ID_PICOLCD_BOOTLOADER) | ||
234 | data->status |= PICOLCD_BOOTLOADER; | ||
235 | |||
236 | /* perform the reset */ | ||
237 | hid_set_field(report->field[0], 0, 1); | ||
238 | usbhid_submit_report(hdev, report, USB_DIR_OUT); | ||
239 | spin_unlock_irqrestore(&data->lock, flags); | ||
240 | |||
241 | error = picolcd_check_version(hdev); | ||
242 | if (error) | ||
243 | return error; | ||
244 | |||
245 | picolcd_resume_lcd(data); | ||
246 | picolcd_resume_backlight(data); | ||
247 | picolcd_fb_refresh(data); | ||
248 | picolcd_leds_set(data); | ||
249 | return 0; | ||
250 | } | ||
251 | |||
252 | /* | ||
253 | * The "operation_mode" sysfs attribute | ||
254 | */ | ||
255 | static ssize_t picolcd_operation_mode_show(struct device *dev, | ||
256 | struct device_attribute *attr, char *buf) | ||
257 | { | ||
258 | struct picolcd_data *data = dev_get_drvdata(dev); | ||
259 | |||
260 | if (data->status & PICOLCD_BOOTLOADER) | ||
261 | return snprintf(buf, PAGE_SIZE, "[bootloader] lcd\n"); | ||
262 | else | ||
263 | return snprintf(buf, PAGE_SIZE, "bootloader [lcd]\n"); | ||
264 | } | ||
265 | |||
266 | static ssize_t picolcd_operation_mode_store(struct device *dev, | ||
267 | struct device_attribute *attr, const char *buf, size_t count) | ||
268 | { | ||
269 | struct picolcd_data *data = dev_get_drvdata(dev); | ||
270 | struct hid_report *report = NULL; | ||
271 | size_t cnt = count; | ||
272 | int timeout = data->opmode_delay; | ||
273 | unsigned long flags; | ||
274 | |||
275 | if (cnt >= 3 && strncmp("lcd", buf, 3) == 0) { | ||
276 | if (data->status & PICOLCD_BOOTLOADER) | ||
277 | report = picolcd_out_report(REPORT_EXIT_FLASHER, data->hdev); | ||
278 | buf += 3; | ||
279 | cnt -= 3; | ||
280 | } else if (cnt >= 10 && strncmp("bootloader", buf, 10) == 0) { | ||
281 | if (!(data->status & PICOLCD_BOOTLOADER)) | ||
282 | report = picolcd_out_report(REPORT_EXIT_KEYBOARD, data->hdev); | ||
283 | buf += 10; | ||
284 | cnt -= 10; | ||
285 | } | ||
286 | if (!report) | ||
287 | return -EINVAL; | ||
288 | |||
289 | while (cnt > 0 && (buf[cnt-1] == '\n' || buf[cnt-1] == '\r')) | ||
290 | cnt--; | ||
291 | if (cnt != 0) | ||
292 | return -EINVAL; | ||
293 | |||
294 | spin_lock_irqsave(&data->lock, flags); | ||
295 | hid_set_field(report->field[0], 0, timeout & 0xff); | ||
296 | hid_set_field(report->field[0], 1, (timeout >> 8) & 0xff); | ||
297 | usbhid_submit_report(data->hdev, report, USB_DIR_OUT); | ||
298 | spin_unlock_irqrestore(&data->lock, flags); | ||
299 | return count; | ||
300 | } | ||
301 | |||
302 | static DEVICE_ATTR(operation_mode, 0644, picolcd_operation_mode_show, | ||
303 | picolcd_operation_mode_store); | ||
304 | |||
305 | /* | ||
306 | * The "operation_mode_delay" sysfs attribute | ||
307 | */ | ||
308 | static ssize_t picolcd_operation_mode_delay_show(struct device *dev, | ||
309 | struct device_attribute *attr, char *buf) | ||
310 | { | ||
311 | struct picolcd_data *data = dev_get_drvdata(dev); | ||
312 | |||
313 | return snprintf(buf, PAGE_SIZE, "%hu\n", data->opmode_delay); | ||
314 | } | ||
315 | |||
316 | static ssize_t picolcd_operation_mode_delay_store(struct device *dev, | ||
317 | struct device_attribute *attr, const char *buf, size_t count) | ||
318 | { | ||
319 | struct picolcd_data *data = dev_get_drvdata(dev); | ||
320 | unsigned u; | ||
321 | if (sscanf(buf, "%u", &u) != 1) | ||
322 | return -EINVAL; | ||
323 | if (u > 30000) | ||
324 | return -EINVAL; | ||
325 | else | ||
326 | data->opmode_delay = u; | ||
327 | return count; | ||
328 | } | ||
329 | |||
330 | static DEVICE_ATTR(operation_mode_delay, 0644, picolcd_operation_mode_delay_show, | ||
331 | picolcd_operation_mode_delay_store); | ||
332 | |||
333 | /* | ||
334 | * Handle raw report as sent by device | ||
335 | */ | ||
336 | static int picolcd_raw_event(struct hid_device *hdev, | ||
337 | struct hid_report *report, u8 *raw_data, int size) | ||
338 | { | ||
339 | struct picolcd_data *data = hid_get_drvdata(hdev); | ||
340 | unsigned long flags; | ||
341 | int ret = 0; | ||
342 | |||
343 | if (!data) | ||
344 | return 1; | ||
345 | |||
346 | if (report->id == REPORT_KEY_STATE) { | ||
347 | if (data->input_keys) | ||
348 | ret = picolcd_raw_keypad(data, report, raw_data+1, size-1); | ||
349 | } else if (report->id == REPORT_IR_DATA) { | ||
350 | if (data->input_cir) | ||
351 | ret = picolcd_raw_cir(data, report, raw_data+1, size-1); | ||
352 | } else { | ||
353 | spin_lock_irqsave(&data->lock, flags); | ||
354 | /* | ||
355 | * We let the caller of picolcd_send_and_wait() check if the | ||
356 | * report we got is one of the expected ones or not. | ||
357 | */ | ||
358 | if (data->pending) { | ||
359 | memcpy(data->pending->raw_data, raw_data+1, size-1); | ||
360 | data->pending->raw_size = size-1; | ||
361 | data->pending->in_report = report; | ||
362 | complete(&data->pending->ready); | ||
363 | } | ||
364 | spin_unlock_irqrestore(&data->lock, flags); | ||
365 | } | ||
366 | |||
367 | picolcd_debug_raw_event(data, hdev, report, raw_data, size); | ||
368 | return 1; | ||
369 | } | ||
370 | |||
371 | #ifdef CONFIG_PM | ||
372 | static int picolcd_suspend(struct hid_device *hdev, pm_message_t message) | ||
373 | { | ||
374 | if (PMSG_IS_AUTO(message)) | ||
375 | return 0; | ||
376 | |||
377 | picolcd_suspend_backlight(hid_get_drvdata(hdev)); | ||
378 | dbg_hid(PICOLCD_NAME " device ready for suspend\n"); | ||
379 | return 0; | ||
380 | } | ||
381 | |||
382 | static int picolcd_resume(struct hid_device *hdev) | ||
383 | { | ||
384 | int ret; | ||
385 | ret = picolcd_resume_backlight(hid_get_drvdata(hdev)); | ||
386 | if (ret) | ||
387 | dbg_hid(PICOLCD_NAME " restoring backlight failed: %d\n", ret); | ||
388 | return 0; | ||
389 | } | ||
390 | |||
391 | static int picolcd_reset_resume(struct hid_device *hdev) | ||
392 | { | ||
393 | int ret; | ||
394 | ret = picolcd_reset(hdev); | ||
395 | if (ret) | ||
396 | dbg_hid(PICOLCD_NAME " resetting our device failed: %d\n", ret); | ||
397 | ret = picolcd_fb_reset(hid_get_drvdata(hdev), 0); | ||
398 | if (ret) | ||
399 | dbg_hid(PICOLCD_NAME " restoring framebuffer content failed: %d\n", ret); | ||
400 | ret = picolcd_resume_lcd(hid_get_drvdata(hdev)); | ||
401 | if (ret) | ||
402 | dbg_hid(PICOLCD_NAME " restoring lcd failed: %d\n", ret); | ||
403 | ret = picolcd_resume_backlight(hid_get_drvdata(hdev)); | ||
404 | if (ret) | ||
405 | dbg_hid(PICOLCD_NAME " restoring backlight failed: %d\n", ret); | ||
406 | picolcd_leds_set(hid_get_drvdata(hdev)); | ||
407 | return 0; | ||
408 | } | ||
409 | #endif | ||
410 | |||
411 | /* initialize keypad input device */ | ||
412 | static int picolcd_init_keys(struct picolcd_data *data, | ||
413 | struct hid_report *report) | ||
414 | { | ||
415 | struct hid_device *hdev = data->hdev; | ||
416 | struct input_dev *idev; | ||
417 | int error, i; | ||
418 | |||
419 | if (!report) | ||
420 | return -ENODEV; | ||
421 | if (report->maxfield != 1 || report->field[0]->report_count != 2 || | ||
422 | report->field[0]->report_size != 8) { | ||
423 | hid_err(hdev, "unsupported KEY_STATE report\n"); | ||
424 | return -EINVAL; | ||
425 | } | ||
426 | |||
427 | idev = input_allocate_device(); | ||
428 | if (idev == NULL) { | ||
429 | hid_err(hdev, "failed to allocate input device\n"); | ||
430 | return -ENOMEM; | ||
431 | } | ||
432 | input_set_drvdata(idev, hdev); | ||
433 | memcpy(data->keycode, def_keymap, sizeof(def_keymap)); | ||
434 | idev->name = hdev->name; | ||
435 | idev->phys = hdev->phys; | ||
436 | idev->uniq = hdev->uniq; | ||
437 | idev->id.bustype = hdev->bus; | ||
438 | idev->id.vendor = hdev->vendor; | ||
439 | idev->id.product = hdev->product; | ||
440 | idev->id.version = hdev->version; | ||
441 | idev->dev.parent = hdev->dev.parent; | ||
442 | idev->keycode = &data->keycode; | ||
443 | idev->keycodemax = PICOLCD_KEYS; | ||
444 | idev->keycodesize = sizeof(data->keycode[0]); | ||
445 | input_set_capability(idev, EV_MSC, MSC_SCAN); | ||
446 | set_bit(EV_REP, idev->evbit); | ||
447 | for (i = 0; i < PICOLCD_KEYS; i++) | ||
448 | input_set_capability(idev, EV_KEY, data->keycode[i]); | ||
449 | error = input_register_device(idev); | ||
450 | if (error) { | ||
451 | hid_err(hdev, "error registering the input device\n"); | ||
452 | input_free_device(idev); | ||
453 | return error; | ||
454 | } | ||
455 | data->input_keys = idev; | ||
456 | return 0; | ||
457 | } | ||
458 | |||
459 | static void picolcd_exit_keys(struct picolcd_data *data) | ||
460 | { | ||
461 | struct input_dev *idev = data->input_keys; | ||
462 | |||
463 | data->input_keys = NULL; | ||
464 | if (idev) | ||
465 | input_unregister_device(idev); | ||
466 | } | ||
467 | |||
468 | static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data) | ||
469 | { | ||
470 | int error; | ||
471 | |||
472 | error = picolcd_check_version(hdev); | ||
473 | if (error) | ||
474 | return error; | ||
475 | |||
476 | if (data->version[0] != 0 && data->version[1] != 3) | ||
477 | hid_info(hdev, "Device with untested firmware revision, please submit /sys/kernel/debug/hid/%s/rdesc for this device.\n", | ||
478 | dev_name(&hdev->dev)); | ||
479 | |||
480 | /* Setup keypad input device */ | ||
481 | error = picolcd_init_keys(data, picolcd_in_report(REPORT_KEY_STATE, hdev)); | ||
482 | if (error) | ||
483 | goto err; | ||
484 | |||
485 | /* Setup CIR input device */ | ||
486 | error = picolcd_init_cir(data, picolcd_in_report(REPORT_IR_DATA, hdev)); | ||
487 | if (error) | ||
488 | goto err; | ||
489 | |||
490 | /* Set up the framebuffer device */ | ||
491 | error = picolcd_init_framebuffer(data); | ||
492 | if (error) | ||
493 | goto err; | ||
494 | |||
495 | /* Setup lcd class device */ | ||
496 | error = picolcd_init_lcd(data, picolcd_out_report(REPORT_CONTRAST, hdev)); | ||
497 | if (error) | ||
498 | goto err; | ||
499 | |||
500 | /* Setup backlight class device */ | ||
501 | error = picolcd_init_backlight(data, picolcd_out_report(REPORT_BRIGHTNESS, hdev)); | ||
502 | if (error) | ||
503 | goto err; | ||
504 | |||
505 | /* Setup the LED class devices */ | ||
506 | error = picolcd_init_leds(data, picolcd_out_report(REPORT_LED_STATE, hdev)); | ||
507 | if (error) | ||
508 | goto err; | ||
509 | |||
510 | picolcd_init_devfs(data, picolcd_out_report(REPORT_EE_READ, hdev), | ||
511 | picolcd_out_report(REPORT_EE_WRITE, hdev), | ||
512 | picolcd_out_report(REPORT_READ_MEMORY, hdev), | ||
513 | picolcd_out_report(REPORT_WRITE_MEMORY, hdev), | ||
514 | picolcd_out_report(REPORT_RESET, hdev)); | ||
515 | return 0; | ||
516 | err: | ||
517 | picolcd_exit_leds(data); | ||
518 | picolcd_exit_backlight(data); | ||
519 | picolcd_exit_lcd(data); | ||
520 | picolcd_exit_framebuffer(data); | ||
521 | picolcd_exit_cir(data); | ||
522 | picolcd_exit_keys(data); | ||
523 | return error; | ||
524 | } | ||
525 | |||
526 | static int picolcd_probe_bootloader(struct hid_device *hdev, struct picolcd_data *data) | ||
527 | { | ||
528 | int error; | ||
529 | |||
530 | error = picolcd_check_version(hdev); | ||
531 | if (error) | ||
532 | return error; | ||
533 | |||
534 | if (data->version[0] != 1 && data->version[1] != 0) | ||
535 | hid_info(hdev, "Device with untested bootloader revision, please submit /sys/kernel/debug/hid/%s/rdesc for this device.\n", | ||
536 | dev_name(&hdev->dev)); | ||
537 | |||
538 | picolcd_init_devfs(data, NULL, NULL, | ||
539 | picolcd_out_report(REPORT_BL_READ_MEMORY, hdev), | ||
540 | picolcd_out_report(REPORT_BL_WRITE_MEMORY, hdev), NULL); | ||
541 | return 0; | ||
542 | } | ||
543 | |||
544 | static int picolcd_probe(struct hid_device *hdev, | ||
545 | const struct hid_device_id *id) | ||
546 | { | ||
547 | struct picolcd_data *data; | ||
548 | int error = -ENOMEM; | ||
549 | |||
550 | dbg_hid(PICOLCD_NAME " hardware probe...\n"); | ||
551 | |||
552 | /* | ||
553 | * Let's allocate the picolcd data structure, set some reasonable | ||
554 | * defaults, and associate it with the device | ||
555 | */ | ||
556 | data = kzalloc(sizeof(struct picolcd_data), GFP_KERNEL); | ||
557 | if (data == NULL) { | ||
558 | hid_err(hdev, "can't allocate space for Minibox PicoLCD device data\n"); | ||
559 | error = -ENOMEM; | ||
560 | goto err_no_cleanup; | ||
561 | } | ||
562 | |||
563 | spin_lock_init(&data->lock); | ||
564 | mutex_init(&data->mutex); | ||
565 | data->hdev = hdev; | ||
566 | data->opmode_delay = 5000; | ||
567 | if (hdev->product == USB_DEVICE_ID_PICOLCD_BOOTLOADER) | ||
568 | data->status |= PICOLCD_BOOTLOADER; | ||
569 | hid_set_drvdata(hdev, data); | ||
570 | |||
571 | /* Parse the device reports and start it up */ | ||
572 | error = hid_parse(hdev); | ||
573 | if (error) { | ||
574 | hid_err(hdev, "device report parse failed\n"); | ||
575 | goto err_cleanup_data; | ||
576 | } | ||
577 | |||
578 | /* We don't use hidinput but hid_hw_start() fails if nothing is | ||
579 | * claimed. So spoof claimed input. */ | ||
580 | hdev->claimed = HID_CLAIMED_INPUT; | ||
581 | error = hid_hw_start(hdev, 0); | ||
582 | hdev->claimed = 0; | ||
583 | if (error) { | ||
584 | hid_err(hdev, "hardware start failed\n"); | ||
585 | goto err_cleanup_data; | ||
586 | } | ||
587 | |||
588 | error = hid_hw_open(hdev); | ||
589 | if (error) { | ||
590 | hid_err(hdev, "failed to open input interrupt pipe for key and IR events\n"); | ||
591 | goto err_cleanup_hid_hw; | ||
592 | } | ||
593 | |||
594 | error = device_create_file(&hdev->dev, &dev_attr_operation_mode_delay); | ||
595 | if (error) { | ||
596 | hid_err(hdev, "failed to create sysfs attributes\n"); | ||
597 | goto err_cleanup_hid_ll; | ||
598 | } | ||
599 | |||
600 | error = device_create_file(&hdev->dev, &dev_attr_operation_mode); | ||
601 | if (error) { | ||
602 | hid_err(hdev, "failed to create sysfs attributes\n"); | ||
603 | goto err_cleanup_sysfs1; | ||
604 | } | ||
605 | |||
606 | if (data->status & PICOLCD_BOOTLOADER) | ||
607 | error = picolcd_probe_bootloader(hdev, data); | ||
608 | else | ||
609 | error = picolcd_probe_lcd(hdev, data); | ||
610 | if (error) | ||
611 | goto err_cleanup_sysfs2; | ||
612 | |||
613 | dbg_hid(PICOLCD_NAME " activated and initialized\n"); | ||
614 | return 0; | ||
615 | |||
616 | err_cleanup_sysfs2: | ||
617 | device_remove_file(&hdev->dev, &dev_attr_operation_mode); | ||
618 | err_cleanup_sysfs1: | ||
619 | device_remove_file(&hdev->dev, &dev_attr_operation_mode_delay); | ||
620 | err_cleanup_hid_ll: | ||
621 | hid_hw_close(hdev); | ||
622 | err_cleanup_hid_hw: | ||
623 | hid_hw_stop(hdev); | ||
624 | err_cleanup_data: | ||
625 | kfree(data); | ||
626 | err_no_cleanup: | ||
627 | hid_set_drvdata(hdev, NULL); | ||
628 | |||
629 | return error; | ||
630 | } | ||
631 | |||
632 | static void picolcd_remove(struct hid_device *hdev) | ||
633 | { | ||
634 | struct picolcd_data *data = hid_get_drvdata(hdev); | ||
635 | unsigned long flags; | ||
636 | |||
637 | dbg_hid(PICOLCD_NAME " hardware remove...\n"); | ||
638 | spin_lock_irqsave(&data->lock, flags); | ||
639 | data->status |= PICOLCD_FAILED; | ||
640 | spin_unlock_irqrestore(&data->lock, flags); | ||
641 | |||
642 | picolcd_exit_devfs(data); | ||
643 | device_remove_file(&hdev->dev, &dev_attr_operation_mode); | ||
644 | device_remove_file(&hdev->dev, &dev_attr_operation_mode_delay); | ||
645 | hid_hw_close(hdev); | ||
646 | hid_hw_stop(hdev); | ||
647 | hid_set_drvdata(hdev, NULL); | ||
648 | |||
649 | /* Shortcut potential pending reply that will never arrive */ | ||
650 | spin_lock_irqsave(&data->lock, flags); | ||
651 | if (data->pending) | ||
652 | complete(&data->pending->ready); | ||
653 | spin_unlock_irqrestore(&data->lock, flags); | ||
654 | |||
655 | /* Cleanup LED */ | ||
656 | picolcd_exit_leds(data); | ||
657 | /* Clean up the framebuffer */ | ||
658 | picolcd_exit_backlight(data); | ||
659 | picolcd_exit_lcd(data); | ||
660 | picolcd_exit_framebuffer(data); | ||
661 | /* Cleanup input */ | ||
662 | picolcd_exit_cir(data); | ||
663 | picolcd_exit_keys(data); | ||
664 | |||
665 | mutex_destroy(&data->mutex); | ||
666 | /* Finally, clean up the picolcd data itself */ | ||
667 | kfree(data); | ||
668 | } | ||
669 | |||
670 | static const struct hid_device_id picolcd_devices[] = { | ||
671 | { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD) }, | ||
672 | { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD_BOOTLOADER) }, | ||
673 | { } | ||
674 | }; | ||
675 | MODULE_DEVICE_TABLE(hid, picolcd_devices); | ||
676 | |||
677 | static struct hid_driver picolcd_driver = { | ||
678 | .name = "hid-picolcd", | ||
679 | .id_table = picolcd_devices, | ||
680 | .probe = picolcd_probe, | ||
681 | .remove = picolcd_remove, | ||
682 | .raw_event = picolcd_raw_event, | ||
683 | #ifdef CONFIG_PM | ||
684 | .suspend = picolcd_suspend, | ||
685 | .resume = picolcd_resume, | ||
686 | .reset_resume = picolcd_reset_resume, | ||
687 | #endif | ||
688 | }; | ||
689 | |||
690 | static int __init picolcd_init(void) | ||
691 | { | ||
692 | return hid_register_driver(&picolcd_driver); | ||
693 | } | ||
694 | |||
695 | static void __exit picolcd_exit(void) | ||
696 | { | ||
697 | hid_unregister_driver(&picolcd_driver); | ||
698 | picolcd_fb_unload(); | ||
699 | } | ||
700 | |||
701 | module_init(picolcd_init); | ||
702 | module_exit(picolcd_exit); | ||
703 | MODULE_DESCRIPTION("Minibox graphics PicoLCD Driver"); | ||
704 | MODULE_LICENSE("GPL v2"); | ||
diff --git a/drivers/hid/hid-picolcd_debugfs.c b/drivers/hid/hid-picolcd_debugfs.c new file mode 100644 index 000000000000..ff746456823d --- /dev/null +++ b/drivers/hid/hid-picolcd_debugfs.c | |||
@@ -0,0 +1,898 @@ | |||
1 | /*************************************************************************** | ||
2 | * Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org> * | ||
3 | * * | ||
4 | * Based on Logitech G13 driver (v0.4) * | ||
5 | * Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu> * | ||
6 | * * | ||
7 | * This program is free software: you can redistribute it and/or modify * | ||
8 | * it under the terms of the GNU General Public License as published by * | ||
9 | * the Free Software Foundation, version 2 of the License. * | ||
10 | * * | ||
11 | * This driver is distributed in the hope that it will be useful, but * | ||
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of * | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * | ||
14 | * General Public License for more details. * | ||
15 | * * | ||
16 | * You should have received a copy of the GNU General Public License * | ||
17 | * along with this software. If not see <http://www.gnu.org/licenses/>. * | ||
18 | ***************************************************************************/ | ||
19 | |||
20 | #include <linux/hid.h> | ||
21 | #include <linux/hid-debug.h> | ||
22 | #include "usbhid/usbhid.h" | ||
23 | #include <linux/usb.h> | ||
24 | |||
25 | #include <linux/fb.h> | ||
26 | #include <linux/seq_file.h> | ||
27 | #include <linux/debugfs.h> | ||
28 | |||
29 | #include <linux/module.h> | ||
30 | |||
31 | #include "hid-picolcd.h" | ||
32 | |||
33 | |||
34 | static int picolcd_debug_reset_show(struct seq_file *f, void *p) | ||
35 | { | ||
36 | if (picolcd_fbinfo((struct picolcd_data *)f->private)) | ||
37 | seq_printf(f, "all fb\n"); | ||
38 | else | ||
39 | seq_printf(f, "all\n"); | ||
40 | return 0; | ||
41 | } | ||
42 | |||
43 | static int picolcd_debug_reset_open(struct inode *inode, struct file *f) | ||
44 | { | ||
45 | return single_open(f, picolcd_debug_reset_show, inode->i_private); | ||
46 | } | ||
47 | |||
48 | static ssize_t picolcd_debug_reset_write(struct file *f, const char __user *user_buf, | ||
49 | size_t count, loff_t *ppos) | ||
50 | { | ||
51 | struct picolcd_data *data = ((struct seq_file *)f->private_data)->private; | ||
52 | char buf[32]; | ||
53 | size_t cnt = min(count, sizeof(buf)-1); | ||
54 | if (copy_from_user(buf, user_buf, cnt)) | ||
55 | return -EFAULT; | ||
56 | |||
57 | while (cnt > 0 && (buf[cnt-1] == ' ' || buf[cnt-1] == '\n')) | ||
58 | cnt--; | ||
59 | buf[cnt] = '\0'; | ||
60 | if (strcmp(buf, "all") == 0) { | ||
61 | picolcd_reset(data->hdev); | ||
62 | picolcd_fb_reset(data, 1); | ||
63 | } else if (strcmp(buf, "fb") == 0) { | ||
64 | picolcd_fb_reset(data, 1); | ||
65 | } else { | ||
66 | return -EINVAL; | ||
67 | } | ||
68 | return count; | ||
69 | } | ||
70 | |||
71 | static const struct file_operations picolcd_debug_reset_fops = { | ||
72 | .owner = THIS_MODULE, | ||
73 | .open = picolcd_debug_reset_open, | ||
74 | .read = seq_read, | ||
75 | .llseek = seq_lseek, | ||
76 | .write = picolcd_debug_reset_write, | ||
77 | .release = single_release, | ||
78 | }; | ||
79 | |||
80 | /* | ||
81 | * The "eeprom" file | ||
82 | */ | ||
83 | static ssize_t picolcd_debug_eeprom_read(struct file *f, char __user *u, | ||
84 | size_t s, loff_t *off) | ||
85 | { | ||
86 | struct picolcd_data *data = f->private_data; | ||
87 | struct picolcd_pending *resp; | ||
88 | u8 raw_data[3]; | ||
89 | ssize_t ret = -EIO; | ||
90 | |||
91 | if (s == 0) | ||
92 | return -EINVAL; | ||
93 | if (*off > 0x0ff) | ||
94 | return 0; | ||
95 | |||
96 | /* prepare buffer with info about what we want to read (addr & len) */ | ||
97 | raw_data[0] = *off & 0xff; | ||
98 | raw_data[1] = (*off >> 8) & 0xff; | ||
99 | raw_data[2] = s < 20 ? s : 20; | ||
100 | if (*off + raw_data[2] > 0xff) | ||
101 | raw_data[2] = 0x100 - *off; | ||
102 | resp = picolcd_send_and_wait(data->hdev, REPORT_EE_READ, raw_data, | ||
103 | sizeof(raw_data)); | ||
104 | if (!resp) | ||
105 | return -EIO; | ||
106 | |||
107 | if (resp->in_report && resp->in_report->id == REPORT_EE_DATA) { | ||
108 | /* successful read :) */ | ||
109 | ret = resp->raw_data[2]; | ||
110 | if (ret > s) | ||
111 | ret = s; | ||
112 | if (copy_to_user(u, resp->raw_data+3, ret)) | ||
113 | ret = -EFAULT; | ||
114 | else | ||
115 | *off += ret; | ||
116 | } /* anything else is some kind of IO error */ | ||
117 | |||
118 | kfree(resp); | ||
119 | return ret; | ||
120 | } | ||
121 | |||
122 | static ssize_t picolcd_debug_eeprom_write(struct file *f, const char __user *u, | ||
123 | size_t s, loff_t *off) | ||
124 | { | ||
125 | struct picolcd_data *data = f->private_data; | ||
126 | struct picolcd_pending *resp; | ||
127 | ssize_t ret = -EIO; | ||
128 | u8 raw_data[23]; | ||
129 | |||
130 | if (s == 0) | ||
131 | return -EINVAL; | ||
132 | if (*off > 0x0ff) | ||
133 | return -ENOSPC; | ||
134 | |||
135 | memset(raw_data, 0, sizeof(raw_data)); | ||
136 | raw_data[0] = *off & 0xff; | ||
137 | raw_data[1] = (*off >> 8) & 0xff; | ||
138 | raw_data[2] = min((size_t)20, s); | ||
139 | if (*off + raw_data[2] > 0xff) | ||
140 | raw_data[2] = 0x100 - *off; | ||
141 | |||
142 | if (copy_from_user(raw_data+3, u, min((u8)20, raw_data[2]))) | ||
143 | return -EFAULT; | ||
144 | resp = picolcd_send_and_wait(data->hdev, REPORT_EE_WRITE, raw_data, | ||
145 | sizeof(raw_data)); | ||
146 | |||
147 | if (!resp) | ||
148 | return -EIO; | ||
149 | |||
150 | if (resp->in_report && resp->in_report->id == REPORT_EE_DATA) { | ||
151 | /* check if written data matches */ | ||
152 | if (memcmp(raw_data, resp->raw_data, 3+raw_data[2]) == 0) { | ||
153 | *off += raw_data[2]; | ||
154 | ret = raw_data[2]; | ||
155 | } | ||
156 | } | ||
157 | kfree(resp); | ||
158 | return ret; | ||
159 | } | ||
160 | |||
161 | /* | ||
162 | * Notes: | ||
163 | * - read/write happens in chunks of at most 20 bytes, it's up to userspace | ||
164 | * to loop in order to get more data. | ||
165 | * - on write errors on otherwise correct write request the bytes | ||
166 | * that should have been written are in undefined state. | ||
167 | */ | ||
168 | static const struct file_operations picolcd_debug_eeprom_fops = { | ||
169 | .owner = THIS_MODULE, | ||
170 | .open = simple_open, | ||
171 | .read = picolcd_debug_eeprom_read, | ||
172 | .write = picolcd_debug_eeprom_write, | ||
173 | .llseek = generic_file_llseek, | ||
174 | }; | ||
175 | |||
176 | /* | ||
177 | * The "flash" file | ||
178 | */ | ||
179 | /* record a flash address to buf (bounds check to be done by caller) */ | ||
180 | static int _picolcd_flash_setaddr(struct picolcd_data *data, u8 *buf, long off) | ||
181 | { | ||
182 | buf[0] = off & 0xff; | ||
183 | buf[1] = (off >> 8) & 0xff; | ||
184 | if (data->addr_sz == 3) | ||
185 | buf[2] = (off >> 16) & 0xff; | ||
186 | return data->addr_sz == 2 ? 2 : 3; | ||
187 | } | ||
188 | |||
189 | /* read a given size of data (bounds check to be done by caller) */ | ||
190 | static ssize_t _picolcd_flash_read(struct picolcd_data *data, int report_id, | ||
191 | char __user *u, size_t s, loff_t *off) | ||
192 | { | ||
193 | struct picolcd_pending *resp; | ||
194 | u8 raw_data[4]; | ||
195 | ssize_t ret = 0; | ||
196 | int len_off, err = -EIO; | ||
197 | |||
198 | while (s > 0) { | ||
199 | err = -EIO; | ||
200 | len_off = _picolcd_flash_setaddr(data, raw_data, *off); | ||
201 | raw_data[len_off] = s > 32 ? 32 : s; | ||
202 | resp = picolcd_send_and_wait(data->hdev, report_id, raw_data, len_off+1); | ||
203 | if (!resp || !resp->in_report) | ||
204 | goto skip; | ||
205 | if (resp->in_report->id == REPORT_MEMORY || | ||
206 | resp->in_report->id == REPORT_BL_READ_MEMORY) { | ||
207 | if (memcmp(raw_data, resp->raw_data, len_off+1) != 0) | ||
208 | goto skip; | ||
209 | if (copy_to_user(u+ret, resp->raw_data+len_off+1, raw_data[len_off])) { | ||
210 | err = -EFAULT; | ||
211 | goto skip; | ||
212 | } | ||
213 | *off += raw_data[len_off]; | ||
214 | s -= raw_data[len_off]; | ||
215 | ret += raw_data[len_off]; | ||
216 | err = 0; | ||
217 | } | ||
218 | skip: | ||
219 | kfree(resp); | ||
220 | if (err) | ||
221 | return ret > 0 ? ret : err; | ||
222 | } | ||
223 | return ret; | ||
224 | } | ||
225 | |||
226 | static ssize_t picolcd_debug_flash_read(struct file *f, char __user *u, | ||
227 | size_t s, loff_t *off) | ||
228 | { | ||
229 | struct picolcd_data *data = f->private_data; | ||
230 | |||
231 | if (s == 0) | ||
232 | return -EINVAL; | ||
233 | if (*off > 0x05fff) | ||
234 | return 0; | ||
235 | if (*off + s > 0x05fff) | ||
236 | s = 0x06000 - *off; | ||
237 | |||
238 | if (data->status & PICOLCD_BOOTLOADER) | ||
239 | return _picolcd_flash_read(data, REPORT_BL_READ_MEMORY, u, s, off); | ||
240 | else | ||
241 | return _picolcd_flash_read(data, REPORT_READ_MEMORY, u, s, off); | ||
242 | } | ||
243 | |||
244 | /* erase block aligned to 64bytes boundary */ | ||
245 | static ssize_t _picolcd_flash_erase64(struct picolcd_data *data, int report_id, | ||
246 | loff_t *off) | ||
247 | { | ||
248 | struct picolcd_pending *resp; | ||
249 | u8 raw_data[3]; | ||
250 | int len_off; | ||
251 | ssize_t ret = -EIO; | ||
252 | |||
253 | if (*off & 0x3f) | ||
254 | return -EINVAL; | ||
255 | |||
256 | len_off = _picolcd_flash_setaddr(data, raw_data, *off); | ||
257 | resp = picolcd_send_and_wait(data->hdev, report_id, raw_data, len_off); | ||
258 | if (!resp || !resp->in_report) | ||
259 | goto skip; | ||
260 | if (resp->in_report->id == REPORT_MEMORY || | ||
261 | resp->in_report->id == REPORT_BL_ERASE_MEMORY) { | ||
262 | if (memcmp(raw_data, resp->raw_data, len_off) != 0) | ||
263 | goto skip; | ||
264 | ret = 0; | ||
265 | } | ||
266 | skip: | ||
267 | kfree(resp); | ||
268 | return ret; | ||
269 | } | ||
270 | |||
271 | /* write a given size of data (bounds check to be done by caller) */ | ||
272 | static ssize_t _picolcd_flash_write(struct picolcd_data *data, int report_id, | ||
273 | const char __user *u, size_t s, loff_t *off) | ||
274 | { | ||
275 | struct picolcd_pending *resp; | ||
276 | u8 raw_data[36]; | ||
277 | ssize_t ret = 0; | ||
278 | int len_off, err = -EIO; | ||
279 | |||
280 | while (s > 0) { | ||
281 | err = -EIO; | ||
282 | len_off = _picolcd_flash_setaddr(data, raw_data, *off); | ||
283 | raw_data[len_off] = s > 32 ? 32 : s; | ||
284 | if (copy_from_user(raw_data+len_off+1, u, raw_data[len_off])) { | ||
285 | err = -EFAULT; | ||
286 | break; | ||
287 | } | ||
288 | resp = picolcd_send_and_wait(data->hdev, report_id, raw_data, | ||
289 | len_off+1+raw_data[len_off]); | ||
290 | if (!resp || !resp->in_report) | ||
291 | goto skip; | ||
292 | if (resp->in_report->id == REPORT_MEMORY || | ||
293 | resp->in_report->id == REPORT_BL_WRITE_MEMORY) { | ||
294 | if (memcmp(raw_data, resp->raw_data, len_off+1+raw_data[len_off]) != 0) | ||
295 | goto skip; | ||
296 | *off += raw_data[len_off]; | ||
297 | s -= raw_data[len_off]; | ||
298 | ret += raw_data[len_off]; | ||
299 | err = 0; | ||
300 | } | ||
301 | skip: | ||
302 | kfree(resp); | ||
303 | if (err) | ||
304 | break; | ||
305 | } | ||
306 | return ret > 0 ? ret : err; | ||
307 | } | ||
308 | |||
309 | static ssize_t picolcd_debug_flash_write(struct file *f, const char __user *u, | ||
310 | size_t s, loff_t *off) | ||
311 | { | ||
312 | struct picolcd_data *data = f->private_data; | ||
313 | ssize_t err, ret = 0; | ||
314 | int report_erase, report_write; | ||
315 | |||
316 | if (s == 0) | ||
317 | return -EINVAL; | ||
318 | if (*off > 0x5fff) | ||
319 | return -ENOSPC; | ||
320 | if (s & 0x3f) | ||
321 | return -EINVAL; | ||
322 | if (*off & 0x3f) | ||
323 | return -EINVAL; | ||
324 | |||
325 | if (data->status & PICOLCD_BOOTLOADER) { | ||
326 | report_erase = REPORT_BL_ERASE_MEMORY; | ||
327 | report_write = REPORT_BL_WRITE_MEMORY; | ||
328 | } else { | ||
329 | report_erase = REPORT_ERASE_MEMORY; | ||
330 | report_write = REPORT_WRITE_MEMORY; | ||
331 | } | ||
332 | mutex_lock(&data->mutex_flash); | ||
333 | while (s > 0) { | ||
334 | err = _picolcd_flash_erase64(data, report_erase, off); | ||
335 | if (err) | ||
336 | break; | ||
337 | err = _picolcd_flash_write(data, report_write, u, 64, off); | ||
338 | if (err < 0) | ||
339 | break; | ||
340 | ret += err; | ||
341 | *off += err; | ||
342 | s -= err; | ||
343 | if (err != 64) | ||
344 | break; | ||
345 | } | ||
346 | mutex_unlock(&data->mutex_flash); | ||
347 | return ret > 0 ? ret : err; | ||
348 | } | ||
349 | |||
350 | /* | ||
351 | * Notes: | ||
352 | * - concurrent writing is prevented by mutex and all writes must be | ||
353 | * n*64 bytes and 64-byte aligned, each write being preceded by an | ||
354 | * ERASE which erases a 64byte block. | ||
355 | * If less than requested was written or an error is returned for an | ||
356 | * otherwise correct write request the next 64-byte block which should | ||
357 | * have been written is in undefined state (mostly: original, erased, | ||
358 | * (half-)written with write error) | ||
359 | * - reading can happen without special restriction | ||
360 | */ | ||
361 | static const struct file_operations picolcd_debug_flash_fops = { | ||
362 | .owner = THIS_MODULE, | ||
363 | .open = simple_open, | ||
364 | .read = picolcd_debug_flash_read, | ||
365 | .write = picolcd_debug_flash_write, | ||
366 | .llseek = generic_file_llseek, | ||
367 | }; | ||
368 | |||
369 | |||
370 | /* | ||
371 | * Helper code for HID report level dumping/debugging | ||
372 | */ | ||
373 | static const char *error_codes[] = { | ||
374 | "success", "parameter missing", "data_missing", "block readonly", | ||
375 | "block not erasable", "block too big", "section overflow", | ||
376 | "invalid command length", "invalid data length", | ||
377 | }; | ||
378 | |||
379 | static void dump_buff_as_hex(char *dst, size_t dst_sz, const u8 *data, | ||
380 | const size_t data_len) | ||
381 | { | ||
382 | int i, j; | ||
383 | for (i = j = 0; i < data_len && j + 3 < dst_sz; i++) { | ||
384 | dst[j++] = hex_asc[(data[i] >> 4) & 0x0f]; | ||
385 | dst[j++] = hex_asc[data[i] & 0x0f]; | ||
386 | dst[j++] = ' '; | ||
387 | } | ||
388 | if (j < dst_sz) { | ||
389 | dst[j--] = '\0'; | ||
390 | dst[j] = '\n'; | ||
391 | } else | ||
392 | dst[j] = '\0'; | ||
393 | } | ||
394 | |||
395 | void picolcd_debug_out_report(struct picolcd_data *data, | ||
396 | struct hid_device *hdev, struct hid_report *report) | ||
397 | { | ||
398 | u8 raw_data[70]; | ||
399 | int raw_size = (report->size >> 3) + 1; | ||
400 | char *buff; | ||
401 | #define BUFF_SZ 256 | ||
402 | |||
403 | /* Avoid unnecessary overhead if debugfs is disabled */ | ||
404 | if (!hdev->debug_events) | ||
405 | return; | ||
406 | |||
407 | buff = kmalloc(BUFF_SZ, GFP_ATOMIC); | ||
408 | if (!buff) | ||
409 | return; | ||
410 | |||
411 | snprintf(buff, BUFF_SZ, "\nout report %d (size %d) = ", | ||
412 | report->id, raw_size); | ||
413 | hid_debug_event(hdev, buff); | ||
414 | if (raw_size + 5 > sizeof(raw_data)) { | ||
415 | kfree(buff); | ||
416 | hid_debug_event(hdev, " TOO BIG\n"); | ||
417 | return; | ||
418 | } else { | ||
419 | raw_data[0] = report->id; | ||
420 | hid_output_report(report, raw_data); | ||
421 | dump_buff_as_hex(buff, BUFF_SZ, raw_data, raw_size); | ||
422 | hid_debug_event(hdev, buff); | ||
423 | } | ||
424 | |||
425 | switch (report->id) { | ||
426 | case REPORT_LED_STATE: | ||
427 | /* 1 data byte with GPO state */ | ||
428 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
429 | "REPORT_LED_STATE", report->id, raw_size-1); | ||
430 | hid_debug_event(hdev, buff); | ||
431 | snprintf(buff, BUFF_SZ, "\tGPO state: 0x%02x\n", raw_data[1]); | ||
432 | hid_debug_event(hdev, buff); | ||
433 | break; | ||
434 | case REPORT_BRIGHTNESS: | ||
435 | /* 1 data byte with brightness */ | ||
436 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
437 | "REPORT_BRIGHTNESS", report->id, raw_size-1); | ||
438 | hid_debug_event(hdev, buff); | ||
439 | snprintf(buff, BUFF_SZ, "\tBrightness: 0x%02x\n", raw_data[1]); | ||
440 | hid_debug_event(hdev, buff); | ||
441 | break; | ||
442 | case REPORT_CONTRAST: | ||
443 | /* 1 data byte with contrast */ | ||
444 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
445 | "REPORT_CONTRAST", report->id, raw_size-1); | ||
446 | hid_debug_event(hdev, buff); | ||
447 | snprintf(buff, BUFF_SZ, "\tContrast: 0x%02x\n", raw_data[1]); | ||
448 | hid_debug_event(hdev, buff); | ||
449 | break; | ||
450 | case REPORT_RESET: | ||
451 | /* 2 data bytes with reset duration in ms */ | ||
452 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
453 | "REPORT_RESET", report->id, raw_size-1); | ||
454 | hid_debug_event(hdev, buff); | ||
455 | snprintf(buff, BUFF_SZ, "\tDuration: 0x%02x%02x (%dms)\n", | ||
456 | raw_data[2], raw_data[1], raw_data[2] << 8 | raw_data[1]); | ||
457 | hid_debug_event(hdev, buff); | ||
458 | break; | ||
459 | case REPORT_LCD_CMD: | ||
460 | /* 63 data bytes with LCD commands */ | ||
461 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
462 | "REPORT_LCD_CMD", report->id, raw_size-1); | ||
463 | hid_debug_event(hdev, buff); | ||
464 | /* TODO: format decoding */ | ||
465 | break; | ||
466 | case REPORT_LCD_DATA: | ||
467 | /* 63 data bytes with LCD data */ | ||
468 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
469 | "REPORT_LCD_CMD", report->id, raw_size-1); | ||
470 | /* TODO: format decoding */ | ||
471 | hid_debug_event(hdev, buff); | ||
472 | break; | ||
473 | case REPORT_LCD_CMD_DATA: | ||
474 | /* 63 data bytes with LCD commands and data */ | ||
475 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
476 | "REPORT_LCD_CMD", report->id, raw_size-1); | ||
477 | /* TODO: format decoding */ | ||
478 | hid_debug_event(hdev, buff); | ||
479 | break; | ||
480 | case REPORT_EE_READ: | ||
481 | /* 3 data bytes with read area description */ | ||
482 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
483 | "REPORT_EE_READ", report->id, raw_size-1); | ||
484 | hid_debug_event(hdev, buff); | ||
485 | snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n", | ||
486 | raw_data[2], raw_data[1]); | ||
487 | hid_debug_event(hdev, buff); | ||
488 | snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]); | ||
489 | hid_debug_event(hdev, buff); | ||
490 | break; | ||
491 | case REPORT_EE_WRITE: | ||
492 | /* 3+1..20 data bytes with write area description */ | ||
493 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
494 | "REPORT_EE_WRITE", report->id, raw_size-1); | ||
495 | hid_debug_event(hdev, buff); | ||
496 | snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n", | ||
497 | raw_data[2], raw_data[1]); | ||
498 | hid_debug_event(hdev, buff); | ||
499 | snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]); | ||
500 | hid_debug_event(hdev, buff); | ||
501 | if (raw_data[3] == 0) { | ||
502 | snprintf(buff, BUFF_SZ, "\tNo data\n"); | ||
503 | } else if (raw_data[3] + 4 <= raw_size) { | ||
504 | snprintf(buff, BUFF_SZ, "\tData: "); | ||
505 | hid_debug_event(hdev, buff); | ||
506 | dump_buff_as_hex(buff, BUFF_SZ, raw_data+4, raw_data[3]); | ||
507 | } else { | ||
508 | snprintf(buff, BUFF_SZ, "\tData overflowed\n"); | ||
509 | } | ||
510 | hid_debug_event(hdev, buff); | ||
511 | break; | ||
512 | case REPORT_ERASE_MEMORY: | ||
513 | case REPORT_BL_ERASE_MEMORY: | ||
514 | /* 3 data bytes with pointer inside erase block */ | ||
515 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
516 | "REPORT_ERASE_MEMORY", report->id, raw_size-1); | ||
517 | hid_debug_event(hdev, buff); | ||
518 | switch (data->addr_sz) { | ||
519 | case 2: | ||
520 | snprintf(buff, BUFF_SZ, "\tAddress inside 64 byte block: 0x%02x%02x\n", | ||
521 | raw_data[2], raw_data[1]); | ||
522 | break; | ||
523 | case 3: | ||
524 | snprintf(buff, BUFF_SZ, "\tAddress inside 64 byte block: 0x%02x%02x%02x\n", | ||
525 | raw_data[3], raw_data[2], raw_data[1]); | ||
526 | break; | ||
527 | default: | ||
528 | snprintf(buff, BUFF_SZ, "\tNot supported\n"); | ||
529 | } | ||
530 | hid_debug_event(hdev, buff); | ||
531 | break; | ||
532 | case REPORT_READ_MEMORY: | ||
533 | case REPORT_BL_READ_MEMORY: | ||
534 | /* 4 data bytes with read area description */ | ||
535 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
536 | "REPORT_READ_MEMORY", report->id, raw_size-1); | ||
537 | hid_debug_event(hdev, buff); | ||
538 | switch (data->addr_sz) { | ||
539 | case 2: | ||
540 | snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n", | ||
541 | raw_data[2], raw_data[1]); | ||
542 | hid_debug_event(hdev, buff); | ||
543 | snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]); | ||
544 | break; | ||
545 | case 3: | ||
546 | snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x%02x\n", | ||
547 | raw_data[3], raw_data[2], raw_data[1]); | ||
548 | hid_debug_event(hdev, buff); | ||
549 | snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[4]); | ||
550 | break; | ||
551 | default: | ||
552 | snprintf(buff, BUFF_SZ, "\tNot supported\n"); | ||
553 | } | ||
554 | hid_debug_event(hdev, buff); | ||
555 | break; | ||
556 | case REPORT_WRITE_MEMORY: | ||
557 | case REPORT_BL_WRITE_MEMORY: | ||
558 | /* 4+1..32 data bytes with write adrea description */ | ||
559 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
560 | "REPORT_WRITE_MEMORY", report->id, raw_size-1); | ||
561 | hid_debug_event(hdev, buff); | ||
562 | switch (data->addr_sz) { | ||
563 | case 2: | ||
564 | snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n", | ||
565 | raw_data[2], raw_data[1]); | ||
566 | hid_debug_event(hdev, buff); | ||
567 | snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]); | ||
568 | hid_debug_event(hdev, buff); | ||
569 | if (raw_data[3] == 0) { | ||
570 | snprintf(buff, BUFF_SZ, "\tNo data\n"); | ||
571 | } else if (raw_data[3] + 4 <= raw_size) { | ||
572 | snprintf(buff, BUFF_SZ, "\tData: "); | ||
573 | hid_debug_event(hdev, buff); | ||
574 | dump_buff_as_hex(buff, BUFF_SZ, raw_data+4, raw_data[3]); | ||
575 | } else { | ||
576 | snprintf(buff, BUFF_SZ, "\tData overflowed\n"); | ||
577 | } | ||
578 | break; | ||
579 | case 3: | ||
580 | snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x%02x\n", | ||
581 | raw_data[3], raw_data[2], raw_data[1]); | ||
582 | hid_debug_event(hdev, buff); | ||
583 | snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[4]); | ||
584 | hid_debug_event(hdev, buff); | ||
585 | if (raw_data[4] == 0) { | ||
586 | snprintf(buff, BUFF_SZ, "\tNo data\n"); | ||
587 | } else if (raw_data[4] + 5 <= raw_size) { | ||
588 | snprintf(buff, BUFF_SZ, "\tData: "); | ||
589 | hid_debug_event(hdev, buff); | ||
590 | dump_buff_as_hex(buff, BUFF_SZ, raw_data+5, raw_data[4]); | ||
591 | } else { | ||
592 | snprintf(buff, BUFF_SZ, "\tData overflowed\n"); | ||
593 | } | ||
594 | break; | ||
595 | default: | ||
596 | snprintf(buff, BUFF_SZ, "\tNot supported\n"); | ||
597 | } | ||
598 | hid_debug_event(hdev, buff); | ||
599 | break; | ||
600 | case REPORT_SPLASH_RESTART: | ||
601 | /* TODO */ | ||
602 | break; | ||
603 | case REPORT_EXIT_KEYBOARD: | ||
604 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
605 | "REPORT_EXIT_KEYBOARD", report->id, raw_size-1); | ||
606 | hid_debug_event(hdev, buff); | ||
607 | snprintf(buff, BUFF_SZ, "\tRestart delay: %dms (0x%02x%02x)\n", | ||
608 | raw_data[1] | (raw_data[2] << 8), | ||
609 | raw_data[2], raw_data[1]); | ||
610 | hid_debug_event(hdev, buff); | ||
611 | break; | ||
612 | case REPORT_VERSION: | ||
613 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
614 | "REPORT_VERSION", report->id, raw_size-1); | ||
615 | hid_debug_event(hdev, buff); | ||
616 | break; | ||
617 | case REPORT_DEVID: | ||
618 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
619 | "REPORT_DEVID", report->id, raw_size-1); | ||
620 | hid_debug_event(hdev, buff); | ||
621 | break; | ||
622 | case REPORT_SPLASH_SIZE: | ||
623 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
624 | "REPORT_SPLASH_SIZE", report->id, raw_size-1); | ||
625 | hid_debug_event(hdev, buff); | ||
626 | break; | ||
627 | case REPORT_HOOK_VERSION: | ||
628 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
629 | "REPORT_HOOK_VERSION", report->id, raw_size-1); | ||
630 | hid_debug_event(hdev, buff); | ||
631 | break; | ||
632 | case REPORT_EXIT_FLASHER: | ||
633 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
634 | "REPORT_VERSION", report->id, raw_size-1); | ||
635 | hid_debug_event(hdev, buff); | ||
636 | snprintf(buff, BUFF_SZ, "\tRestart delay: %dms (0x%02x%02x)\n", | ||
637 | raw_data[1] | (raw_data[2] << 8), | ||
638 | raw_data[2], raw_data[1]); | ||
639 | hid_debug_event(hdev, buff); | ||
640 | break; | ||
641 | default: | ||
642 | snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n", | ||
643 | "<unknown>", report->id, raw_size-1); | ||
644 | hid_debug_event(hdev, buff); | ||
645 | break; | ||
646 | } | ||
647 | wake_up_interruptible(&hdev->debug_wait); | ||
648 | kfree(buff); | ||
649 | } | ||
650 | |||
651 | void picolcd_debug_raw_event(struct picolcd_data *data, | ||
652 | struct hid_device *hdev, struct hid_report *report, | ||
653 | u8 *raw_data, int size) | ||
654 | { | ||
655 | char *buff; | ||
656 | |||
657 | #define BUFF_SZ 256 | ||
658 | /* Avoid unnecessary overhead if debugfs is disabled */ | ||
659 | if (!hdev->debug_events) | ||
660 | return; | ||
661 | |||
662 | buff = kmalloc(BUFF_SZ, GFP_ATOMIC); | ||
663 | if (!buff) | ||
664 | return; | ||
665 | |||
666 | switch (report->id) { | ||
667 | case REPORT_ERROR_CODE: | ||
668 | /* 2 data bytes with affected report and error code */ | ||
669 | snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", | ||
670 | "REPORT_ERROR_CODE", report->id, size-1); | ||
671 | hid_debug_event(hdev, buff); | ||
672 | if (raw_data[2] < ARRAY_SIZE(error_codes)) | ||
673 | snprintf(buff, BUFF_SZ, "\tError code 0x%02x (%s) in reply to report 0x%02x\n", | ||
674 | raw_data[2], error_codes[raw_data[2]], raw_data[1]); | ||
675 | else | ||
676 | snprintf(buff, BUFF_SZ, "\tError code 0x%02x in reply to report 0x%02x\n", | ||
677 | raw_data[2], raw_data[1]); | ||
678 | hid_debug_event(hdev, buff); | ||
679 | break; | ||
680 | case REPORT_KEY_STATE: | ||
681 | /* 2 data bytes with key state */ | ||
682 | snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", | ||
683 | "REPORT_KEY_STATE", report->id, size-1); | ||
684 | hid_debug_event(hdev, buff); | ||
685 | if (raw_data[1] == 0) | ||
686 | snprintf(buff, BUFF_SZ, "\tNo key pressed\n"); | ||
687 | else if (raw_data[2] == 0) | ||
688 | snprintf(buff, BUFF_SZ, "\tOne key pressed: 0x%02x (%d)\n", | ||
689 | raw_data[1], raw_data[1]); | ||
690 | else | ||
691 | snprintf(buff, BUFF_SZ, "\tTwo keys pressed: 0x%02x (%d), 0x%02x (%d)\n", | ||
692 | raw_data[1], raw_data[1], raw_data[2], raw_data[2]); | ||
693 | hid_debug_event(hdev, buff); | ||
694 | break; | ||
695 | case REPORT_IR_DATA: | ||
696 | /* Up to 20 byes of IR scancode data */ | ||
697 | snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", | ||
698 | "REPORT_IR_DATA", report->id, size-1); | ||
699 | hid_debug_event(hdev, buff); | ||
700 | if (raw_data[1] == 0) { | ||
701 | snprintf(buff, BUFF_SZ, "\tUnexpectedly 0 data length\n"); | ||
702 | hid_debug_event(hdev, buff); | ||
703 | } else if (raw_data[1] + 1 <= size) { | ||
704 | snprintf(buff, BUFF_SZ, "\tData length: %d\n\tIR Data: ", | ||
705 | raw_data[1]-1); | ||
706 | hid_debug_event(hdev, buff); | ||
707 | dump_buff_as_hex(buff, BUFF_SZ, raw_data+2, raw_data[1]-1); | ||
708 | hid_debug_event(hdev, buff); | ||
709 | } else { | ||
710 | snprintf(buff, BUFF_SZ, "\tOverflowing data length: %d\n", | ||
711 | raw_data[1]-1); | ||
712 | hid_debug_event(hdev, buff); | ||
713 | } | ||
714 | break; | ||
715 | case REPORT_EE_DATA: | ||
716 | /* Data buffer in response to REPORT_EE_READ or REPORT_EE_WRITE */ | ||
717 | snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", | ||
718 | "REPORT_EE_DATA", report->id, size-1); | ||
719 | hid_debug_event(hdev, buff); | ||
720 | snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n", | ||
721 | raw_data[2], raw_data[1]); | ||
722 | hid_debug_event(hdev, buff); | ||
723 | snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]); | ||
724 | hid_debug_event(hdev, buff); | ||
725 | if (raw_data[3] == 0) { | ||
726 | snprintf(buff, BUFF_SZ, "\tNo data\n"); | ||
727 | hid_debug_event(hdev, buff); | ||
728 | } else if (raw_data[3] + 4 <= size) { | ||
729 | snprintf(buff, BUFF_SZ, "\tData: "); | ||
730 | hid_debug_event(hdev, buff); | ||
731 | dump_buff_as_hex(buff, BUFF_SZ, raw_data+4, raw_data[3]); | ||
732 | hid_debug_event(hdev, buff); | ||
733 | } else { | ||
734 | snprintf(buff, BUFF_SZ, "\tData overflowed\n"); | ||
735 | hid_debug_event(hdev, buff); | ||
736 | } | ||
737 | break; | ||
738 | case REPORT_MEMORY: | ||
739 | /* Data buffer in response to REPORT_READ_MEMORY or REPORT_WRTIE_MEMORY */ | ||
740 | snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", | ||
741 | "REPORT_MEMORY", report->id, size-1); | ||
742 | hid_debug_event(hdev, buff); | ||
743 | switch (data->addr_sz) { | ||
744 | case 2: | ||
745 | snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n", | ||
746 | raw_data[2], raw_data[1]); | ||
747 | hid_debug_event(hdev, buff); | ||
748 | snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]); | ||
749 | hid_debug_event(hdev, buff); | ||
750 | if (raw_data[3] == 0) { | ||
751 | snprintf(buff, BUFF_SZ, "\tNo data\n"); | ||
752 | } else if (raw_data[3] + 4 <= size) { | ||
753 | snprintf(buff, BUFF_SZ, "\tData: "); | ||
754 | hid_debug_event(hdev, buff); | ||
755 | dump_buff_as_hex(buff, BUFF_SZ, raw_data+4, raw_data[3]); | ||
756 | } else { | ||
757 | snprintf(buff, BUFF_SZ, "\tData overflowed\n"); | ||
758 | } | ||
759 | break; | ||
760 | case 3: | ||
761 | snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x%02x\n", | ||
762 | raw_data[3], raw_data[2], raw_data[1]); | ||
763 | hid_debug_event(hdev, buff); | ||
764 | snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[4]); | ||
765 | hid_debug_event(hdev, buff); | ||
766 | if (raw_data[4] == 0) { | ||
767 | snprintf(buff, BUFF_SZ, "\tNo data\n"); | ||
768 | } else if (raw_data[4] + 5 <= size) { | ||
769 | snprintf(buff, BUFF_SZ, "\tData: "); | ||
770 | hid_debug_event(hdev, buff); | ||
771 | dump_buff_as_hex(buff, BUFF_SZ, raw_data+5, raw_data[4]); | ||
772 | } else { | ||
773 | snprintf(buff, BUFF_SZ, "\tData overflowed\n"); | ||
774 | } | ||
775 | break; | ||
776 | default: | ||
777 | snprintf(buff, BUFF_SZ, "\tNot supported\n"); | ||
778 | } | ||
779 | hid_debug_event(hdev, buff); | ||
780 | break; | ||
781 | case REPORT_VERSION: | ||
782 | snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", | ||
783 | "REPORT_VERSION", report->id, size-1); | ||
784 | hid_debug_event(hdev, buff); | ||
785 | snprintf(buff, BUFF_SZ, "\tFirmware version: %d.%d\n", | ||
786 | raw_data[2], raw_data[1]); | ||
787 | hid_debug_event(hdev, buff); | ||
788 | break; | ||
789 | case REPORT_BL_ERASE_MEMORY: | ||
790 | snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", | ||
791 | "REPORT_BL_ERASE_MEMORY", report->id, size-1); | ||
792 | hid_debug_event(hdev, buff); | ||
793 | /* TODO */ | ||
794 | break; | ||
795 | case REPORT_BL_READ_MEMORY: | ||
796 | snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", | ||
797 | "REPORT_BL_READ_MEMORY", report->id, size-1); | ||
798 | hid_debug_event(hdev, buff); | ||
799 | /* TODO */ | ||
800 | break; | ||
801 | case REPORT_BL_WRITE_MEMORY: | ||
802 | snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", | ||
803 | "REPORT_BL_WRITE_MEMORY", report->id, size-1); | ||
804 | hid_debug_event(hdev, buff); | ||
805 | /* TODO */ | ||
806 | break; | ||
807 | case REPORT_DEVID: | ||
808 | snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", | ||
809 | "REPORT_DEVID", report->id, size-1); | ||
810 | hid_debug_event(hdev, buff); | ||
811 | snprintf(buff, BUFF_SZ, "\tSerial: 0x%02x%02x%02x%02x\n", | ||
812 | raw_data[1], raw_data[2], raw_data[3], raw_data[4]); | ||
813 | hid_debug_event(hdev, buff); | ||
814 | snprintf(buff, BUFF_SZ, "\tType: 0x%02x\n", | ||
815 | raw_data[5]); | ||
816 | hid_debug_event(hdev, buff); | ||
817 | break; | ||
818 | case REPORT_SPLASH_SIZE: | ||
819 | snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", | ||
820 | "REPORT_SPLASH_SIZE", report->id, size-1); | ||
821 | hid_debug_event(hdev, buff); | ||
822 | snprintf(buff, BUFF_SZ, "\tTotal splash space: %d\n", | ||
823 | (raw_data[2] << 8) | raw_data[1]); | ||
824 | hid_debug_event(hdev, buff); | ||
825 | snprintf(buff, BUFF_SZ, "\tUsed splash space: %d\n", | ||
826 | (raw_data[4] << 8) | raw_data[3]); | ||
827 | hid_debug_event(hdev, buff); | ||
828 | break; | ||
829 | case REPORT_HOOK_VERSION: | ||
830 | snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", | ||
831 | "REPORT_HOOK_VERSION", report->id, size-1); | ||
832 | hid_debug_event(hdev, buff); | ||
833 | snprintf(buff, BUFF_SZ, "\tFirmware version: %d.%d\n", | ||
834 | raw_data[1], raw_data[2]); | ||
835 | hid_debug_event(hdev, buff); | ||
836 | break; | ||
837 | default: | ||
838 | snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n", | ||
839 | "<unknown>", report->id, size-1); | ||
840 | hid_debug_event(hdev, buff); | ||
841 | break; | ||
842 | } | ||
843 | wake_up_interruptible(&hdev->debug_wait); | ||
844 | kfree(buff); | ||
845 | } | ||
846 | |||
847 | void picolcd_init_devfs(struct picolcd_data *data, | ||
848 | struct hid_report *eeprom_r, struct hid_report *eeprom_w, | ||
849 | struct hid_report *flash_r, struct hid_report *flash_w, | ||
850 | struct hid_report *reset) | ||
851 | { | ||
852 | struct hid_device *hdev = data->hdev; | ||
853 | |||
854 | mutex_init(&data->mutex_flash); | ||
855 | |||
856 | /* reset */ | ||
857 | if (reset) | ||
858 | data->debug_reset = debugfs_create_file("reset", 0600, | ||
859 | hdev->debug_dir, data, &picolcd_debug_reset_fops); | ||
860 | |||
861 | /* eeprom */ | ||
862 | if (eeprom_r || eeprom_w) | ||
863 | data->debug_eeprom = debugfs_create_file("eeprom", | ||
864 | (eeprom_w ? S_IWUSR : 0) | (eeprom_r ? S_IRUSR : 0), | ||
865 | hdev->debug_dir, data, &picolcd_debug_eeprom_fops); | ||
866 | |||
867 | /* flash */ | ||
868 | if (flash_r && flash_r->maxfield == 1 && flash_r->field[0]->report_size == 8) | ||
869 | data->addr_sz = flash_r->field[0]->report_count - 1; | ||
870 | else | ||
871 | data->addr_sz = -1; | ||
872 | if (data->addr_sz == 2 || data->addr_sz == 3) { | ||
873 | data->debug_flash = debugfs_create_file("flash", | ||
874 | (flash_w ? S_IWUSR : 0) | (flash_r ? S_IRUSR : 0), | ||
875 | hdev->debug_dir, data, &picolcd_debug_flash_fops); | ||
876 | } else if (flash_r || flash_w) | ||
877 | hid_warn(hdev, "Unexpected FLASH access reports, please submit rdesc for review\n"); | ||
878 | } | ||
879 | |||
880 | void picolcd_exit_devfs(struct picolcd_data *data) | ||
881 | { | ||
882 | struct dentry *dent; | ||
883 | |||
884 | dent = data->debug_reset; | ||
885 | data->debug_reset = NULL; | ||
886 | if (dent) | ||
887 | debugfs_remove(dent); | ||
888 | dent = data->debug_eeprom; | ||
889 | data->debug_eeprom = NULL; | ||
890 | if (dent) | ||
891 | debugfs_remove(dent); | ||
892 | dent = data->debug_flash; | ||
893 | data->debug_flash = NULL; | ||
894 | if (dent) | ||
895 | debugfs_remove(dent); | ||
896 | mutex_destroy(&data->mutex_flash); | ||
897 | } | ||
898 | |||
diff --git a/drivers/hid/hid-picolcd_fb.c b/drivers/hid/hid-picolcd_fb.c new file mode 100644 index 000000000000..b2124f566c61 --- /dev/null +++ b/drivers/hid/hid-picolcd_fb.c | |||
@@ -0,0 +1,673 @@ | |||
1 | /*************************************************************************** | ||
2 | * Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org> * | ||
3 | * * | ||
4 | * Based on Logitech G13 driver (v0.4) * | ||
5 | * Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu> * | ||
6 | * * | ||
7 | * This program is free software: you can redistribute it and/or modify * | ||
8 | * it under the terms of the GNU General Public License as published by * | ||
9 | * the Free Software Foundation, version 2 of the License. * | ||
10 | * * | ||
11 | * This driver is distributed in the hope that it will be useful, but * | ||
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of * | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * | ||
14 | * General Public License for more details. * | ||
15 | * * | ||
16 | * You should have received a copy of the GNU General Public License * | ||
17 | * along with this software. If not see <http://www.gnu.org/licenses/>. * | ||
18 | ***************************************************************************/ | ||
19 | |||
20 | #include <linux/hid.h> | ||
21 | #include "usbhid/usbhid.h" | ||
22 | #include <linux/usb.h> | ||
23 | |||
24 | #include <linux/fb.h> | ||
25 | #include <linux/module.h> | ||
26 | |||
27 | #include "hid-picolcd.h" | ||
28 | |||
29 | /* Framebuffer | ||
30 | * | ||
31 | * The PicoLCD use a Topway LCD module of 256x64 pixel | ||
32 | * This display area is tiled over 4 controllers with 8 tiles | ||
33 | * each. Each tile has 8x64 pixel, each data byte representing | ||
34 | * a 1-bit wide vertical line of the tile. | ||
35 | * | ||
36 | * The display can be updated at a tile granularity. | ||
37 | * | ||
38 | * Chip 1 Chip 2 Chip 3 Chip 4 | ||
39 | * +----------------+----------------+----------------+----------------+ | ||
40 | * | Tile 1 | Tile 1 | Tile 1 | Tile 1 | | ||
41 | * +----------------+----------------+----------------+----------------+ | ||
42 | * | Tile 2 | Tile 2 | Tile 2 | Tile 2 | | ||
43 | * +----------------+----------------+----------------+----------------+ | ||
44 | * ... | ||
45 | * +----------------+----------------+----------------+----------------+ | ||
46 | * | Tile 8 | Tile 8 | Tile 8 | Tile 8 | | ||
47 | * +----------------+----------------+----------------+----------------+ | ||
48 | */ | ||
49 | #define PICOLCDFB_NAME "picolcdfb" | ||
50 | #define PICOLCDFB_WIDTH (256) | ||
51 | #define PICOLCDFB_HEIGHT (64) | ||
52 | #define PICOLCDFB_SIZE (PICOLCDFB_WIDTH * PICOLCDFB_HEIGHT / 8) | ||
53 | |||
54 | #define PICOLCDFB_UPDATE_RATE_LIMIT 10 | ||
55 | #define PICOLCDFB_UPDATE_RATE_DEFAULT 2 | ||
56 | |||
57 | /* Framebuffer visual structures */ | ||
58 | static const struct fb_fix_screeninfo picolcdfb_fix = { | ||
59 | .id = PICOLCDFB_NAME, | ||
60 | .type = FB_TYPE_PACKED_PIXELS, | ||
61 | .visual = FB_VISUAL_MONO01, | ||
62 | .xpanstep = 0, | ||
63 | .ypanstep = 0, | ||
64 | .ywrapstep = 0, | ||
65 | .line_length = PICOLCDFB_WIDTH / 8, | ||
66 | .accel = FB_ACCEL_NONE, | ||
67 | }; | ||
68 | |||
69 | static const struct fb_var_screeninfo picolcdfb_var = { | ||
70 | .xres = PICOLCDFB_WIDTH, | ||
71 | .yres = PICOLCDFB_HEIGHT, | ||
72 | .xres_virtual = PICOLCDFB_WIDTH, | ||
73 | .yres_virtual = PICOLCDFB_HEIGHT, | ||
74 | .width = 103, | ||
75 | .height = 26, | ||
76 | .bits_per_pixel = 1, | ||
77 | .grayscale = 1, | ||
78 | .red = { | ||
79 | .offset = 0, | ||
80 | .length = 1, | ||
81 | .msb_right = 0, | ||
82 | }, | ||
83 | .green = { | ||
84 | .offset = 0, | ||
85 | .length = 1, | ||
86 | .msb_right = 0, | ||
87 | }, | ||
88 | .blue = { | ||
89 | .offset = 0, | ||
90 | .length = 1, | ||
91 | .msb_right = 0, | ||
92 | }, | ||
93 | .transp = { | ||
94 | .offset = 0, | ||
95 | .length = 0, | ||
96 | .msb_right = 0, | ||
97 | }, | ||
98 | }; | ||
99 | |||
100 | /* Send a given tile to PicoLCD */ | ||
101 | static int picolcd_fb_send_tile(struct hid_device *hdev, int chip, int tile) | ||
102 | { | ||
103 | struct picolcd_data *data = hid_get_drvdata(hdev); | ||
104 | struct hid_report *report1 = picolcd_out_report(REPORT_LCD_CMD_DATA, hdev); | ||
105 | struct hid_report *report2 = picolcd_out_report(REPORT_LCD_DATA, hdev); | ||
106 | unsigned long flags; | ||
107 | u8 *tdata; | ||
108 | int i; | ||
109 | |||
110 | if (!report1 || report1->maxfield != 1 || !report2 || report2->maxfield != 1) | ||
111 | return -ENODEV; | ||
112 | |||
113 | spin_lock_irqsave(&data->lock, flags); | ||
114 | hid_set_field(report1->field[0], 0, chip << 2); | ||
115 | hid_set_field(report1->field[0], 1, 0x02); | ||
116 | hid_set_field(report1->field[0], 2, 0x00); | ||
117 | hid_set_field(report1->field[0], 3, 0x00); | ||
118 | hid_set_field(report1->field[0], 4, 0xb8 | tile); | ||
119 | hid_set_field(report1->field[0], 5, 0x00); | ||
120 | hid_set_field(report1->field[0], 6, 0x00); | ||
121 | hid_set_field(report1->field[0], 7, 0x40); | ||
122 | hid_set_field(report1->field[0], 8, 0x00); | ||
123 | hid_set_field(report1->field[0], 9, 0x00); | ||
124 | hid_set_field(report1->field[0], 10, 32); | ||
125 | |||
126 | hid_set_field(report2->field[0], 0, (chip << 2) | 0x01); | ||
127 | hid_set_field(report2->field[0], 1, 0x00); | ||
128 | hid_set_field(report2->field[0], 2, 0x00); | ||
129 | hid_set_field(report2->field[0], 3, 32); | ||
130 | |||
131 | tdata = data->fb_vbitmap + (tile * 4 + chip) * 64; | ||
132 | for (i = 0; i < 64; i++) | ||
133 | if (i < 32) | ||
134 | hid_set_field(report1->field[0], 11 + i, tdata[i]); | ||
135 | else | ||
136 | hid_set_field(report2->field[0], 4 + i - 32, tdata[i]); | ||
137 | |||
138 | usbhid_submit_report(data->hdev, report1, USB_DIR_OUT); | ||
139 | usbhid_submit_report(data->hdev, report2, USB_DIR_OUT); | ||
140 | spin_unlock_irqrestore(&data->lock, flags); | ||
141 | return 0; | ||
142 | } | ||
143 | |||
144 | /* Translate a single tile*/ | ||
145 | static int picolcd_fb_update_tile(u8 *vbitmap, const u8 *bitmap, int bpp, | ||
146 | int chip, int tile) | ||
147 | { | ||
148 | int i, b, changed = 0; | ||
149 | u8 tdata[64]; | ||
150 | u8 *vdata = vbitmap + (tile * 4 + chip) * 64; | ||
151 | |||
152 | if (bpp == 1) { | ||
153 | for (b = 7; b >= 0; b--) { | ||
154 | const u8 *bdata = bitmap + tile * 256 + chip * 8 + b * 32; | ||
155 | for (i = 0; i < 64; i++) { | ||
156 | tdata[i] <<= 1; | ||
157 | tdata[i] |= (bdata[i/8] >> (i % 8)) & 0x01; | ||
158 | } | ||
159 | } | ||
160 | } else if (bpp == 8) { | ||
161 | for (b = 7; b >= 0; b--) { | ||
162 | const u8 *bdata = bitmap + (tile * 256 + chip * 8 + b * 32) * 8; | ||
163 | for (i = 0; i < 64; i++) { | ||
164 | tdata[i] <<= 1; | ||
165 | tdata[i] |= (bdata[i] & 0x80) ? 0x01 : 0x00; | ||
166 | } | ||
167 | } | ||
168 | } else { | ||
169 | /* Oops, we should never get here! */ | ||
170 | WARN_ON(1); | ||
171 | return 0; | ||
172 | } | ||
173 | |||
174 | for (i = 0; i < 64; i++) | ||
175 | if (tdata[i] != vdata[i]) { | ||
176 | changed = 1; | ||
177 | vdata[i] = tdata[i]; | ||
178 | } | ||
179 | return changed; | ||
180 | } | ||
181 | |||
182 | void picolcd_fb_refresh(struct picolcd_data *data) | ||
183 | { | ||
184 | if (data->fb_info) | ||
185 | schedule_delayed_work(&data->fb_info->deferred_work, 0); | ||
186 | } | ||
187 | |||
188 | /* Reconfigure LCD display */ | ||
189 | int picolcd_fb_reset(struct picolcd_data *data, int clear) | ||
190 | { | ||
191 | struct hid_report *report = picolcd_out_report(REPORT_LCD_CMD, data->hdev); | ||
192 | int i, j; | ||
193 | unsigned long flags; | ||
194 | static const u8 mapcmd[8] = { 0x00, 0x02, 0x00, 0x64, 0x3f, 0x00, 0x64, 0xc0 }; | ||
195 | |||
196 | if (!report || report->maxfield != 1) | ||
197 | return -ENODEV; | ||
198 | |||
199 | spin_lock_irqsave(&data->lock, flags); | ||
200 | for (i = 0; i < 4; i++) { | ||
201 | for (j = 0; j < report->field[0]->maxusage; j++) | ||
202 | if (j == 0) | ||
203 | hid_set_field(report->field[0], j, i << 2); | ||
204 | else if (j < sizeof(mapcmd)) | ||
205 | hid_set_field(report->field[0], j, mapcmd[j]); | ||
206 | else | ||
207 | hid_set_field(report->field[0], j, 0); | ||
208 | usbhid_submit_report(data->hdev, report, USB_DIR_OUT); | ||
209 | } | ||
210 | |||
211 | data->status |= PICOLCD_READY_FB; | ||
212 | spin_unlock_irqrestore(&data->lock, flags); | ||
213 | |||
214 | if (data->fb_bitmap) { | ||
215 | if (clear) { | ||
216 | memset(data->fb_vbitmap, 0, PICOLCDFB_SIZE); | ||
217 | memset(data->fb_bitmap, 0, PICOLCDFB_SIZE*data->fb_bpp); | ||
218 | } | ||
219 | data->fb_force = 1; | ||
220 | } | ||
221 | |||
222 | /* schedule first output of framebuffer */ | ||
223 | picolcd_fb_refresh(data); | ||
224 | |||
225 | return 0; | ||
226 | } | ||
227 | |||
228 | /* Update fb_vbitmap from the screen_base and send changed tiles to device */ | ||
229 | static void picolcd_fb_update(struct picolcd_data *data) | ||
230 | { | ||
231 | int chip, tile, n; | ||
232 | unsigned long flags; | ||
233 | |||
234 | if (!data) | ||
235 | return; | ||
236 | |||
237 | spin_lock_irqsave(&data->lock, flags); | ||
238 | if (!(data->status & PICOLCD_READY_FB)) { | ||
239 | spin_unlock_irqrestore(&data->lock, flags); | ||
240 | picolcd_fb_reset(data, 0); | ||
241 | } else { | ||
242 | spin_unlock_irqrestore(&data->lock, flags); | ||
243 | } | ||
244 | |||
245 | /* | ||
246 | * Translate the framebuffer into the format needed by the PicoLCD. | ||
247 | * See display layout above. | ||
248 | * Do this one tile after the other and push those tiles that changed. | ||
249 | * | ||
250 | * Wait for our IO to complete as otherwise we might flood the queue! | ||
251 | */ | ||
252 | n = 0; | ||
253 | for (chip = 0; chip < 4; chip++) | ||
254 | for (tile = 0; tile < 8; tile++) | ||
255 | if (picolcd_fb_update_tile(data->fb_vbitmap, | ||
256 | data->fb_bitmap, data->fb_bpp, chip, tile) || | ||
257 | data->fb_force) { | ||
258 | n += 2; | ||
259 | if (!data->fb_info->par) | ||
260 | return; /* device lost! */ | ||
261 | if (n >= HID_OUTPUT_FIFO_SIZE / 2) { | ||
262 | usbhid_wait_io(data->hdev); | ||
263 | n = 0; | ||
264 | } | ||
265 | picolcd_fb_send_tile(data->hdev, chip, tile); | ||
266 | } | ||
267 | data->fb_force = false; | ||
268 | if (n) | ||
269 | usbhid_wait_io(data->hdev); | ||
270 | } | ||
271 | |||
272 | /* Stub to call the system default and update the image on the picoLCD */ | ||
273 | static void picolcd_fb_fillrect(struct fb_info *info, | ||
274 | const struct fb_fillrect *rect) | ||
275 | { | ||
276 | if (!info->par) | ||
277 | return; | ||
278 | sys_fillrect(info, rect); | ||
279 | |||
280 | schedule_delayed_work(&info->deferred_work, 0); | ||
281 | } | ||
282 | |||
283 | /* Stub to call the system default and update the image on the picoLCD */ | ||
284 | static void picolcd_fb_copyarea(struct fb_info *info, | ||
285 | const struct fb_copyarea *area) | ||
286 | { | ||
287 | if (!info->par) | ||
288 | return; | ||
289 | sys_copyarea(info, area); | ||
290 | |||
291 | schedule_delayed_work(&info->deferred_work, 0); | ||
292 | } | ||
293 | |||
294 | /* Stub to call the system default and update the image on the picoLCD */ | ||
295 | static void picolcd_fb_imageblit(struct fb_info *info, const struct fb_image *image) | ||
296 | { | ||
297 | if (!info->par) | ||
298 | return; | ||
299 | sys_imageblit(info, image); | ||
300 | |||
301 | schedule_delayed_work(&info->deferred_work, 0); | ||
302 | } | ||
303 | |||
304 | /* | ||
305 | * this is the slow path from userspace. they can seek and write to | ||
306 | * the fb. it's inefficient to do anything less than a full screen draw | ||
307 | */ | ||
308 | static ssize_t picolcd_fb_write(struct fb_info *info, const char __user *buf, | ||
309 | size_t count, loff_t *ppos) | ||
310 | { | ||
311 | ssize_t ret; | ||
312 | if (!info->par) | ||
313 | return -ENODEV; | ||
314 | ret = fb_sys_write(info, buf, count, ppos); | ||
315 | if (ret >= 0) | ||
316 | schedule_delayed_work(&info->deferred_work, 0); | ||
317 | return ret; | ||
318 | } | ||
319 | |||
320 | static int picolcd_fb_blank(int blank, struct fb_info *info) | ||
321 | { | ||
322 | if (!info->par) | ||
323 | return -ENODEV; | ||
324 | /* We let fb notification do this for us via lcd/backlight device */ | ||
325 | return 0; | ||
326 | } | ||
327 | |||
328 | static void picolcd_fb_destroy(struct fb_info *info) | ||
329 | { | ||
330 | struct picolcd_data *data = info->par; | ||
331 | u32 *ref_cnt = info->pseudo_palette; | ||
332 | int may_release; | ||
333 | |||
334 | info->par = NULL; | ||
335 | if (data) | ||
336 | data->fb_info = NULL; | ||
337 | fb_deferred_io_cleanup(info); | ||
338 | |||
339 | ref_cnt--; | ||
340 | mutex_lock(&info->lock); | ||
341 | (*ref_cnt)--; | ||
342 | may_release = !*ref_cnt; | ||
343 | mutex_unlock(&info->lock); | ||
344 | if (may_release) { | ||
345 | vfree((u8 *)info->fix.smem_start); | ||
346 | framebuffer_release(info); | ||
347 | } | ||
348 | } | ||
349 | |||
350 | static int picolcd_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) | ||
351 | { | ||
352 | __u32 bpp = var->bits_per_pixel; | ||
353 | __u32 activate = var->activate; | ||
354 | |||
355 | /* only allow 1/8 bit depth (8-bit is grayscale) */ | ||
356 | *var = picolcdfb_var; | ||
357 | var->activate = activate; | ||
358 | if (bpp >= 8) { | ||
359 | var->bits_per_pixel = 8; | ||
360 | var->red.length = 8; | ||
361 | var->green.length = 8; | ||
362 | var->blue.length = 8; | ||
363 | } else { | ||
364 | var->bits_per_pixel = 1; | ||
365 | var->red.length = 1; | ||
366 | var->green.length = 1; | ||
367 | var->blue.length = 1; | ||
368 | } | ||
369 | return 0; | ||
370 | } | ||
371 | |||
372 | static int picolcd_set_par(struct fb_info *info) | ||
373 | { | ||
374 | struct picolcd_data *data = info->par; | ||
375 | u8 *tmp_fb, *o_fb; | ||
376 | if (!data) | ||
377 | return -ENODEV; | ||
378 | if (info->var.bits_per_pixel == data->fb_bpp) | ||
379 | return 0; | ||
380 | /* switch between 1/8 bit depths */ | ||
381 | if (info->var.bits_per_pixel != 1 && info->var.bits_per_pixel != 8) | ||
382 | return -EINVAL; | ||
383 | |||
384 | o_fb = data->fb_bitmap; | ||
385 | tmp_fb = kmalloc(PICOLCDFB_SIZE*info->var.bits_per_pixel, GFP_KERNEL); | ||
386 | if (!tmp_fb) | ||
387 | return -ENOMEM; | ||
388 | |||
389 | /* translate FB content to new bits-per-pixel */ | ||
390 | if (info->var.bits_per_pixel == 1) { | ||
391 | int i, b; | ||
392 | for (i = 0; i < PICOLCDFB_SIZE; i++) { | ||
393 | u8 p = 0; | ||
394 | for (b = 0; b < 8; b++) { | ||
395 | p <<= 1; | ||
396 | p |= o_fb[i*8+b] ? 0x01 : 0x00; | ||
397 | } | ||
398 | tmp_fb[i] = p; | ||
399 | } | ||
400 | memcpy(o_fb, tmp_fb, PICOLCDFB_SIZE); | ||
401 | info->fix.visual = FB_VISUAL_MONO01; | ||
402 | info->fix.line_length = PICOLCDFB_WIDTH / 8; | ||
403 | } else { | ||
404 | int i; | ||
405 | memcpy(tmp_fb, o_fb, PICOLCDFB_SIZE); | ||
406 | for (i = 0; i < PICOLCDFB_SIZE * 8; i++) | ||
407 | o_fb[i] = tmp_fb[i/8] & (0x01 << (7 - i % 8)) ? 0xff : 0x00; | ||
408 | info->fix.visual = FB_VISUAL_DIRECTCOLOR; | ||
409 | info->fix.line_length = PICOLCDFB_WIDTH; | ||
410 | } | ||
411 | |||
412 | kfree(tmp_fb); | ||
413 | data->fb_bpp = info->var.bits_per_pixel; | ||
414 | return 0; | ||
415 | } | ||
416 | |||
417 | /* Do refcounting on our FB and cleanup per worker if FB is | ||
418 | * closed after unplug of our device | ||
419 | * (fb_release holds info->lock and still touches info after | ||
420 | * we return so we can't release it immediately. | ||
421 | */ | ||
422 | struct picolcd_fb_cleanup_item { | ||
423 | struct fb_info *info; | ||
424 | struct picolcd_fb_cleanup_item *next; | ||
425 | }; | ||
426 | static struct picolcd_fb_cleanup_item *fb_pending; | ||
427 | static DEFINE_SPINLOCK(fb_pending_lock); | ||
428 | |||
429 | static void picolcd_fb_do_cleanup(struct work_struct *data) | ||
430 | { | ||
431 | struct picolcd_fb_cleanup_item *item; | ||
432 | unsigned long flags; | ||
433 | |||
434 | do { | ||
435 | spin_lock_irqsave(&fb_pending_lock, flags); | ||
436 | item = fb_pending; | ||
437 | fb_pending = item ? item->next : NULL; | ||
438 | spin_unlock_irqrestore(&fb_pending_lock, flags); | ||
439 | |||
440 | if (item) { | ||
441 | u8 *fb = (u8 *)item->info->fix.smem_start; | ||
442 | /* make sure we do not race against fb core when | ||
443 | * releasing */ | ||
444 | mutex_lock(&item->info->lock); | ||
445 | mutex_unlock(&item->info->lock); | ||
446 | framebuffer_release(item->info); | ||
447 | vfree(fb); | ||
448 | } | ||
449 | } while (item); | ||
450 | } | ||
451 | |||
452 | static DECLARE_WORK(picolcd_fb_cleanup, picolcd_fb_do_cleanup); | ||
453 | |||
454 | static int picolcd_fb_open(struct fb_info *info, int u) | ||
455 | { | ||
456 | u32 *ref_cnt = info->pseudo_palette; | ||
457 | ref_cnt--; | ||
458 | |||
459 | (*ref_cnt)++; | ||
460 | return 0; | ||
461 | } | ||
462 | |||
463 | static int picolcd_fb_release(struct fb_info *info, int u) | ||
464 | { | ||
465 | u32 *ref_cnt = info->pseudo_palette; | ||
466 | ref_cnt--; | ||
467 | |||
468 | (*ref_cnt)++; | ||
469 | if (!*ref_cnt) { | ||
470 | unsigned long flags; | ||
471 | struct picolcd_fb_cleanup_item *item = (struct picolcd_fb_cleanup_item *)ref_cnt; | ||
472 | item--; | ||
473 | spin_lock_irqsave(&fb_pending_lock, flags); | ||
474 | item->next = fb_pending; | ||
475 | fb_pending = item; | ||
476 | spin_unlock_irqrestore(&fb_pending_lock, flags); | ||
477 | schedule_work(&picolcd_fb_cleanup); | ||
478 | } | ||
479 | return 0; | ||
480 | } | ||
481 | |||
482 | /* Note this can't be const because of struct fb_info definition */ | ||
483 | static struct fb_ops picolcdfb_ops = { | ||
484 | .owner = THIS_MODULE, | ||
485 | .fb_destroy = picolcd_fb_destroy, | ||
486 | .fb_open = picolcd_fb_open, | ||
487 | .fb_release = picolcd_fb_release, | ||
488 | .fb_read = fb_sys_read, | ||
489 | .fb_write = picolcd_fb_write, | ||
490 | .fb_blank = picolcd_fb_blank, | ||
491 | .fb_fillrect = picolcd_fb_fillrect, | ||
492 | .fb_copyarea = picolcd_fb_copyarea, | ||
493 | .fb_imageblit = picolcd_fb_imageblit, | ||
494 | .fb_check_var = picolcd_fb_check_var, | ||
495 | .fb_set_par = picolcd_set_par, | ||
496 | }; | ||
497 | |||
498 | |||
499 | /* Callback from deferred IO workqueue */ | ||
500 | static void picolcd_fb_deferred_io(struct fb_info *info, struct list_head *pagelist) | ||
501 | { | ||
502 | picolcd_fb_update(info->par); | ||
503 | } | ||
504 | |||
505 | static const struct fb_deferred_io picolcd_fb_defio = { | ||
506 | .delay = HZ / PICOLCDFB_UPDATE_RATE_DEFAULT, | ||
507 | .deferred_io = picolcd_fb_deferred_io, | ||
508 | }; | ||
509 | |||
510 | |||
511 | /* | ||
512 | * The "fb_update_rate" sysfs attribute | ||
513 | */ | ||
514 | static ssize_t picolcd_fb_update_rate_show(struct device *dev, | ||
515 | struct device_attribute *attr, char *buf) | ||
516 | { | ||
517 | struct picolcd_data *data = dev_get_drvdata(dev); | ||
518 | unsigned i, fb_update_rate = data->fb_update_rate; | ||
519 | size_t ret = 0; | ||
520 | |||
521 | for (i = 1; i <= PICOLCDFB_UPDATE_RATE_LIMIT; i++) | ||
522 | if (ret >= PAGE_SIZE) | ||
523 | break; | ||
524 | else if (i == fb_update_rate) | ||
525 | ret += snprintf(buf+ret, PAGE_SIZE-ret, "[%u] ", i); | ||
526 | else | ||
527 | ret += snprintf(buf+ret, PAGE_SIZE-ret, "%u ", i); | ||
528 | if (ret > 0) | ||
529 | buf[min(ret, (size_t)PAGE_SIZE)-1] = '\n'; | ||
530 | return ret; | ||
531 | } | ||
532 | |||
533 | static ssize_t picolcd_fb_update_rate_store(struct device *dev, | ||
534 | struct device_attribute *attr, const char *buf, size_t count) | ||
535 | { | ||
536 | struct picolcd_data *data = dev_get_drvdata(dev); | ||
537 | int i; | ||
538 | unsigned u; | ||
539 | |||
540 | if (count < 1 || count > 10) | ||
541 | return -EINVAL; | ||
542 | |||
543 | i = sscanf(buf, "%u", &u); | ||
544 | if (i != 1) | ||
545 | return -EINVAL; | ||
546 | |||
547 | if (u > PICOLCDFB_UPDATE_RATE_LIMIT) | ||
548 | return -ERANGE; | ||
549 | else if (u == 0) | ||
550 | u = PICOLCDFB_UPDATE_RATE_DEFAULT; | ||
551 | |||
552 | data->fb_update_rate = u; | ||
553 | data->fb_defio.delay = HZ / data->fb_update_rate; | ||
554 | return count; | ||
555 | } | ||
556 | |||
557 | static DEVICE_ATTR(fb_update_rate, 0666, picolcd_fb_update_rate_show, | ||
558 | picolcd_fb_update_rate_store); | ||
559 | |||
560 | /* initialize Framebuffer device */ | ||
561 | int picolcd_init_framebuffer(struct picolcd_data *data) | ||
562 | { | ||
563 | struct device *dev = &data->hdev->dev; | ||
564 | struct fb_info *info = NULL; | ||
565 | int i, error = -ENOMEM; | ||
566 | u8 *fb_vbitmap = NULL; | ||
567 | u8 *fb_bitmap = NULL; | ||
568 | u32 *palette; | ||
569 | |||
570 | fb_bitmap = vmalloc(PICOLCDFB_SIZE*8); | ||
571 | if (fb_bitmap == NULL) { | ||
572 | dev_err(dev, "can't get a free page for framebuffer\n"); | ||
573 | goto err_nomem; | ||
574 | } | ||
575 | |||
576 | fb_vbitmap = kmalloc(PICOLCDFB_SIZE, GFP_KERNEL); | ||
577 | if (fb_vbitmap == NULL) { | ||
578 | dev_err(dev, "can't alloc vbitmap image buffer\n"); | ||
579 | goto err_nomem; | ||
580 | } | ||
581 | |||
582 | data->fb_update_rate = PICOLCDFB_UPDATE_RATE_DEFAULT; | ||
583 | data->fb_defio = picolcd_fb_defio; | ||
584 | /* The extra memory is: | ||
585 | * - struct picolcd_fb_cleanup_item | ||
586 | * - u32 for ref_count | ||
587 | * - 256*u32 for pseudo_palette | ||
588 | */ | ||
589 | info = framebuffer_alloc(257 * sizeof(u32) + sizeof(struct picolcd_fb_cleanup_item), dev); | ||
590 | if (info == NULL) { | ||
591 | dev_err(dev, "failed to allocate a framebuffer\n"); | ||
592 | goto err_nomem; | ||
593 | } | ||
594 | |||
595 | palette = info->par + sizeof(struct picolcd_fb_cleanup_item); | ||
596 | *palette = 1; | ||
597 | palette++; | ||
598 | for (i = 0; i < 256; i++) | ||
599 | palette[i] = i > 0 && i < 16 ? 0xff : 0; | ||
600 | info->pseudo_palette = palette; | ||
601 | info->fbdefio = &data->fb_defio; | ||
602 | info->screen_base = (char __force __iomem *)fb_bitmap; | ||
603 | info->fbops = &picolcdfb_ops; | ||
604 | info->var = picolcdfb_var; | ||
605 | info->fix = picolcdfb_fix; | ||
606 | info->fix.smem_len = PICOLCDFB_SIZE*8; | ||
607 | info->fix.smem_start = (unsigned long)fb_bitmap; | ||
608 | info->par = data; | ||
609 | info->flags = FBINFO_FLAG_DEFAULT; | ||
610 | |||
611 | data->fb_vbitmap = fb_vbitmap; | ||
612 | data->fb_bitmap = fb_bitmap; | ||
613 | data->fb_bpp = picolcdfb_var.bits_per_pixel; | ||
614 | error = picolcd_fb_reset(data, 1); | ||
615 | if (error) { | ||
616 | dev_err(dev, "failed to configure display\n"); | ||
617 | goto err_cleanup; | ||
618 | } | ||
619 | error = device_create_file(dev, &dev_attr_fb_update_rate); | ||
620 | if (error) { | ||
621 | dev_err(dev, "failed to create sysfs attributes\n"); | ||
622 | goto err_cleanup; | ||
623 | } | ||
624 | fb_deferred_io_init(info); | ||
625 | data->fb_info = info; | ||
626 | error = register_framebuffer(info); | ||
627 | if (error) { | ||
628 | dev_err(dev, "failed to register framebuffer\n"); | ||
629 | goto err_sysfs; | ||
630 | } | ||
631 | /* schedule first output of framebuffer */ | ||
632 | data->fb_force = 1; | ||
633 | schedule_delayed_work(&info->deferred_work, 0); | ||
634 | return 0; | ||
635 | |||
636 | err_sysfs: | ||
637 | fb_deferred_io_cleanup(info); | ||
638 | device_remove_file(dev, &dev_attr_fb_update_rate); | ||
639 | err_cleanup: | ||
640 | data->fb_vbitmap = NULL; | ||
641 | data->fb_bitmap = NULL; | ||
642 | data->fb_bpp = 0; | ||
643 | data->fb_info = NULL; | ||
644 | |||
645 | err_nomem: | ||
646 | framebuffer_release(info); | ||
647 | vfree(fb_bitmap); | ||
648 | kfree(fb_vbitmap); | ||
649 | return error; | ||
650 | } | ||
651 | |||
652 | void picolcd_exit_framebuffer(struct picolcd_data *data) | ||
653 | { | ||
654 | struct fb_info *info = data->fb_info; | ||
655 | u8 *fb_vbitmap = data->fb_vbitmap; | ||
656 | |||
657 | if (!info) | ||
658 | return; | ||
659 | |||
660 | device_remove_file(&data->hdev->dev, &dev_attr_fb_update_rate); | ||
661 | unregister_framebuffer(info); | ||
662 | data->fb_vbitmap = NULL; | ||
663 | data->fb_bitmap = NULL; | ||
664 | data->fb_bpp = 0; | ||
665 | data->fb_info = NULL; | ||
666 | kfree(fb_vbitmap); | ||
667 | } | ||
668 | |||
669 | void picolcd_fb_unload() | ||
670 | { | ||
671 | flush_work_sync(&picolcd_fb_cleanup); | ||
672 | WARN_ON(fb_pending); | ||
673 | } | ||
diff --git a/drivers/hid/hid-picolcd_lcd.c b/drivers/hid/hid-picolcd_lcd.c new file mode 100644 index 000000000000..64a067fc837c --- /dev/null +++ b/drivers/hid/hid-picolcd_lcd.c | |||
@@ -0,0 +1,106 @@ | |||
1 | /*************************************************************************** | ||
2 | * Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org> * | ||
3 | * * | ||
4 | * Based on Logitech G13 driver (v0.4) * | ||
5 | * Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu> * | ||
6 | * * | ||
7 | * This program is free software: you can redistribute it and/or modify * | ||
8 | * it under the terms of the GNU General Public License as published by * | ||
9 | * the Free Software Foundation, version 2 of the License. * | ||
10 | * * | ||
11 | * This driver is distributed in the hope that it will be useful, but * | ||
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of * | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * | ||
14 | * General Public License for more details. * | ||
15 | * * | ||
16 | * You should have received a copy of the GNU General Public License * | ||
17 | * along with this software. If not see <http://www.gnu.org/licenses/>. * | ||
18 | ***************************************************************************/ | ||
19 | |||
20 | #include <linux/hid.h> | ||
21 | #include "usbhid/usbhid.h" | ||
22 | #include <linux/usb.h> | ||
23 | |||
24 | #include <linux/fb.h> | ||
25 | #include <linux/lcd.h> | ||
26 | |||
27 | #include "hid-picolcd.h" | ||
28 | |||
29 | /* | ||
30 | * lcd class device | ||
31 | */ | ||
32 | static int picolcd_get_contrast(struct lcd_device *ldev) | ||
33 | { | ||
34 | struct picolcd_data *data = lcd_get_data(ldev); | ||
35 | return data->lcd_contrast; | ||
36 | } | ||
37 | |||
38 | static int picolcd_set_contrast(struct lcd_device *ldev, int contrast) | ||
39 | { | ||
40 | struct picolcd_data *data = lcd_get_data(ldev); | ||
41 | struct hid_report *report = picolcd_out_report(REPORT_CONTRAST, data->hdev); | ||
42 | unsigned long flags; | ||
43 | |||
44 | if (!report || report->maxfield != 1 || report->field[0]->report_count != 1) | ||
45 | return -ENODEV; | ||
46 | |||
47 | data->lcd_contrast = contrast & 0x0ff; | ||
48 | spin_lock_irqsave(&data->lock, flags); | ||
49 | hid_set_field(report->field[0], 0, data->lcd_contrast); | ||
50 | usbhid_submit_report(data->hdev, report, USB_DIR_OUT); | ||
51 | spin_unlock_irqrestore(&data->lock, flags); | ||
52 | return 0; | ||
53 | } | ||
54 | |||
55 | static int picolcd_check_lcd_fb(struct lcd_device *ldev, struct fb_info *fb) | ||
56 | { | ||
57 | return fb && fb == picolcd_fbinfo((struct picolcd_data *)lcd_get_data(ldev)); | ||
58 | } | ||
59 | |||
60 | static struct lcd_ops picolcd_lcdops = { | ||
61 | .get_contrast = picolcd_get_contrast, | ||
62 | .set_contrast = picolcd_set_contrast, | ||
63 | .check_fb = picolcd_check_lcd_fb, | ||
64 | }; | ||
65 | |||
66 | int picolcd_init_lcd(struct picolcd_data *data, struct hid_report *report) | ||
67 | { | ||
68 | struct device *dev = &data->hdev->dev; | ||
69 | struct lcd_device *ldev; | ||
70 | |||
71 | if (!report) | ||
72 | return -ENODEV; | ||
73 | if (report->maxfield != 1 || report->field[0]->report_count != 1 || | ||
74 | report->field[0]->report_size != 8) { | ||
75 | dev_err(dev, "unsupported CONTRAST report"); | ||
76 | return -EINVAL; | ||
77 | } | ||
78 | |||
79 | ldev = lcd_device_register(dev_name(dev), dev, data, &picolcd_lcdops); | ||
80 | if (IS_ERR(ldev)) { | ||
81 | dev_err(dev, "failed to register LCD\n"); | ||
82 | return PTR_ERR(ldev); | ||
83 | } | ||
84 | ldev->props.max_contrast = 0x0ff; | ||
85 | data->lcd_contrast = 0xe5; | ||
86 | data->lcd = ldev; | ||
87 | picolcd_set_contrast(ldev, 0xe5); | ||
88 | return 0; | ||
89 | } | ||
90 | |||
91 | void picolcd_exit_lcd(struct picolcd_data *data) | ||
92 | { | ||
93 | struct lcd_device *ldev = data->lcd; | ||
94 | |||
95 | data->lcd = NULL; | ||
96 | if (ldev) | ||
97 | lcd_device_unregister(ldev); | ||
98 | } | ||
99 | |||
100 | int picolcd_resume_lcd(struct picolcd_data *data) | ||
101 | { | ||
102 | if (!data->lcd) | ||
103 | return 0; | ||
104 | return picolcd_set_contrast(data->lcd, data->lcd_contrast); | ||
105 | } | ||
106 | |||
diff --git a/drivers/hid/hid-picolcd_leds.c b/drivers/hid/hid-picolcd_leds.c new file mode 100644 index 000000000000..9249c068a485 --- /dev/null +++ b/drivers/hid/hid-picolcd_leds.c | |||
@@ -0,0 +1,172 @@ | |||
1 | /*************************************************************************** | ||
2 | * Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org> * | ||
3 | * * | ||
4 | * Based on Logitech G13 driver (v0.4) * | ||
5 | * Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu> * | ||
6 | * * | ||
7 | * This program is free software: you can redistribute it and/or modify * | ||
8 | * it under the terms of the GNU General Public License as published by * | ||
9 | * the Free Software Foundation, version 2 of the License. * | ||
10 | * * | ||
11 | * This driver is distributed in the hope that it will be useful, but * | ||
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of * | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * | ||
14 | * General Public License for more details. * | ||
15 | * * | ||
16 | * You should have received a copy of the GNU General Public License * | ||
17 | * along with this software. If not see <http://www.gnu.org/licenses/>. * | ||
18 | ***************************************************************************/ | ||
19 | |||
20 | #include <linux/hid.h> | ||
21 | #include <linux/hid-debug.h> | ||
22 | #include <linux/input.h> | ||
23 | #include "hid-ids.h" | ||
24 | #include "usbhid/usbhid.h" | ||
25 | #include <linux/usb.h> | ||
26 | |||
27 | #include <linux/fb.h> | ||
28 | #include <linux/vmalloc.h> | ||
29 | #include <linux/backlight.h> | ||
30 | #include <linux/lcd.h> | ||
31 | |||
32 | #include <linux/leds.h> | ||
33 | |||
34 | #include <linux/seq_file.h> | ||
35 | #include <linux/debugfs.h> | ||
36 | |||
37 | #include <linux/completion.h> | ||
38 | #include <linux/uaccess.h> | ||
39 | #include <linux/module.h> | ||
40 | |||
41 | #include "hid-picolcd.h" | ||
42 | |||
43 | |||
44 | void picolcd_leds_set(struct picolcd_data *data) | ||
45 | { | ||
46 | struct hid_report *report; | ||
47 | unsigned long flags; | ||
48 | |||
49 | if (!data->led[0]) | ||
50 | return; | ||
51 | report = picolcd_out_report(REPORT_LED_STATE, data->hdev); | ||
52 | if (!report || report->maxfield != 1 || report->field[0]->report_count != 1) | ||
53 | return; | ||
54 | |||
55 | spin_lock_irqsave(&data->lock, flags); | ||
56 | hid_set_field(report->field[0], 0, data->led_state); | ||
57 | usbhid_submit_report(data->hdev, report, USB_DIR_OUT); | ||
58 | spin_unlock_irqrestore(&data->lock, flags); | ||
59 | } | ||
60 | |||
61 | static void picolcd_led_set_brightness(struct led_classdev *led_cdev, | ||
62 | enum led_brightness value) | ||
63 | { | ||
64 | struct device *dev; | ||
65 | struct hid_device *hdev; | ||
66 | struct picolcd_data *data; | ||
67 | int i, state = 0; | ||
68 | |||
69 | dev = led_cdev->dev->parent; | ||
70 | hdev = container_of(dev, struct hid_device, dev); | ||
71 | data = hid_get_drvdata(hdev); | ||
72 | for (i = 0; i < 8; i++) { | ||
73 | if (led_cdev != data->led[i]) | ||
74 | continue; | ||
75 | state = (data->led_state >> i) & 1; | ||
76 | if (value == LED_OFF && state) { | ||
77 | data->led_state &= ~(1 << i); | ||
78 | picolcd_leds_set(data); | ||
79 | } else if (value != LED_OFF && !state) { | ||
80 | data->led_state |= 1 << i; | ||
81 | picolcd_leds_set(data); | ||
82 | } | ||
83 | break; | ||
84 | } | ||
85 | } | ||
86 | |||
87 | static enum led_brightness picolcd_led_get_brightness(struct led_classdev *led_cdev) | ||
88 | { | ||
89 | struct device *dev; | ||
90 | struct hid_device *hdev; | ||
91 | struct picolcd_data *data; | ||
92 | int i, value = 0; | ||
93 | |||
94 | dev = led_cdev->dev->parent; | ||
95 | hdev = container_of(dev, struct hid_device, dev); | ||
96 | data = hid_get_drvdata(hdev); | ||
97 | for (i = 0; i < 8; i++) | ||
98 | if (led_cdev == data->led[i]) { | ||
99 | value = (data->led_state >> i) & 1; | ||
100 | break; | ||
101 | } | ||
102 | return value ? LED_FULL : LED_OFF; | ||
103 | } | ||
104 | |||
105 | int picolcd_init_leds(struct picolcd_data *data, struct hid_report *report) | ||
106 | { | ||
107 | struct device *dev = &data->hdev->dev; | ||
108 | struct led_classdev *led; | ||
109 | size_t name_sz = strlen(dev_name(dev)) + 8; | ||
110 | char *name; | ||
111 | int i, ret = 0; | ||
112 | |||
113 | if (!report) | ||
114 | return -ENODEV; | ||
115 | if (report->maxfield != 1 || report->field[0]->report_count != 1 || | ||
116 | report->field[0]->report_size != 8) { | ||
117 | dev_err(dev, "unsupported LED_STATE report"); | ||
118 | return -EINVAL; | ||
119 | } | ||
120 | |||
121 | for (i = 0; i < 8; i++) { | ||
122 | led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL); | ||
123 | if (!led) { | ||
124 | dev_err(dev, "can't allocate memory for LED %d\n", i); | ||
125 | ret = -ENOMEM; | ||
126 | goto err; | ||
127 | } | ||
128 | name = (void *)(&led[1]); | ||
129 | snprintf(name, name_sz, "%s::GPO%d", dev_name(dev), i); | ||
130 | led->name = name; | ||
131 | led->brightness = 0; | ||
132 | led->max_brightness = 1; | ||
133 | led->brightness_get = picolcd_led_get_brightness; | ||
134 | led->brightness_set = picolcd_led_set_brightness; | ||
135 | |||
136 | data->led[i] = led; | ||
137 | ret = led_classdev_register(dev, data->led[i]); | ||
138 | if (ret) { | ||
139 | data->led[i] = NULL; | ||
140 | kfree(led); | ||
141 | dev_err(dev, "can't register LED %d\n", i); | ||
142 | goto err; | ||
143 | } | ||
144 | } | ||
145 | return 0; | ||
146 | err: | ||
147 | for (i = 0; i < 8; i++) | ||
148 | if (data->led[i]) { | ||
149 | led = data->led[i]; | ||
150 | data->led[i] = NULL; | ||
151 | led_classdev_unregister(led); | ||
152 | kfree(led); | ||
153 | } | ||
154 | return ret; | ||
155 | } | ||
156 | |||
157 | void picolcd_exit_leds(struct picolcd_data *data) | ||
158 | { | ||
159 | struct led_classdev *led; | ||
160 | int i; | ||
161 | |||
162 | for (i = 0; i < 8; i++) { | ||
163 | led = data->led[i]; | ||
164 | data->led[i] = NULL; | ||
165 | if (!led) | ||
166 | continue; | ||
167 | led_classdev_unregister(led); | ||
168 | kfree(led); | ||
169 | } | ||
170 | } | ||
171 | |||
172 | |||