diff options
author | Hans de Goede <hdegoede@redhat.com> | 2019-07-21 09:25:25 -0400 |
---|---|---|
committer | Hans de Goede <hdegoede@redhat.com> | 2019-07-21 15:01:59 -0400 |
commit | e4f86e43716443e934d705952902d40de0fa9a05 (patch) | |
tree | d84b74c6e73f5362d0bd151532986ab0bd9bd33c | |
parent | 73415b4ea5b5a257db6b5bd135b305cd945e63de (diff) |
drm: Add Grain Media GM12U320 driver v2
Add a modesetting driver for Grain Media GM12U320 based devices
(primarily Acer C120 projector, but there may be compatible devices).
This is based on the fb driver from Viacheslav Nurmekhamitov:
https://github.com/slavrn/gm12u320
This driver uses drm_simple_display_pipe to deal with all the atomic
stuff, gem_shmem_helper functions for buffer management and
drm_fbdev_generic_setup for fbdev emulation, so that leaves the driver
itself with only the actual code for talking to the gm12u320 chip,
leading to a nice simple and clean driver.
Changes in v2:
-Add drm-misc tree to MAINTAINERS
-Drop mode_config.preferred_depth = 24 / fix fbdev support
Reviewed-by: Noralf Trønnes <noralf@tronnes.org>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20190721132525.10396-1-hdegoede@redhat.com
-rw-r--r-- | MAINTAINERS | 6 | ||||
-rw-r--r-- | drivers/gpu/drm/Kconfig | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/Makefile | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/gm12u320/Kconfig | 9 | ||||
-rw-r--r-- | drivers/gpu/drm/gm12u320/Makefile | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/gm12u320/gm12u320.c | 814 |
6 files changed, 834 insertions, 0 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index 0a76716874bd..53779dca27e6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS | |||
@@ -5015,6 +5015,12 @@ S: Maintained | |||
5015 | F: drivers/gpu/drm/panel/panel-feiyang-fy07024di26a30d.c | 5015 | F: drivers/gpu/drm/panel/panel-feiyang-fy07024di26a30d.c |
5016 | F: Documentation/devicetree/bindings/display/panel/feiyang,fy07024di26a30d.txt | 5016 | F: Documentation/devicetree/bindings/display/panel/feiyang,fy07024di26a30d.txt |
5017 | 5017 | ||
5018 | DRM DRIVER FOR GRAIN MEDIA GM12U320 PROJECTORS | ||
5019 | M: Hans de Goede <hdegoede@redhat.com> | ||
5020 | T: git git://anongit.freedesktop.org/drm/drm-misc | ||
5021 | S: Maintained | ||
5022 | F: drivers/gpu/drm/gm12u320/ | ||
5023 | |||
5018 | DRM DRIVER FOR ILITEK ILI9225 PANELS | 5024 | DRM DRIVER FOR ILITEK ILI9225 PANELS |
5019 | M: David Lechner <david@lechnology.com> | 5025 | M: David Lechner <david@lechnology.com> |
5020 | S: Maintained | 5026 | S: Maintained |
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index b9362b4f6353..0a0bb0ad8f35 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig | |||
@@ -354,6 +354,8 @@ source "drivers/gpu/drm/aspeed/Kconfig" | |||
354 | 354 | ||
355 | source "drivers/gpu/drm/mcde/Kconfig" | 355 | source "drivers/gpu/drm/mcde/Kconfig" |
356 | 356 | ||
357 | source "drivers/gpu/drm/gm12u320/Kconfig" | ||
358 | |||
357 | # Keep legacy drivers last | 359 | # Keep legacy drivers last |
358 | 360 | ||
359 | menuconfig DRM_LEGACY | 361 | menuconfig DRM_LEGACY |
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 9f0d2ee35794..9728900fb3a2 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile | |||
@@ -120,3 +120,4 @@ obj-$(CONFIG_DRM_LIMA) += lima/ | |||
120 | obj-$(CONFIG_DRM_PANFROST) += panfrost/ | 120 | obj-$(CONFIG_DRM_PANFROST) += panfrost/ |
121 | obj-$(CONFIG_DRM_ASPEED_GFX) += aspeed/ | 121 | obj-$(CONFIG_DRM_ASPEED_GFX) += aspeed/ |
122 | obj-$(CONFIG_DRM_MCDE) += mcde/ | 122 | obj-$(CONFIG_DRM_MCDE) += mcde/ |
123 | obj-$(CONFIG_DRM_GM12U320) += gm12u320/ | ||
diff --git a/drivers/gpu/drm/gm12u320/Kconfig b/drivers/gpu/drm/gm12u320/Kconfig new file mode 100644 index 000000000000..0882a61c04d5 --- /dev/null +++ b/drivers/gpu/drm/gm12u320/Kconfig | |||
@@ -0,0 +1,9 @@ | |||
1 | # SPDX-License-Identifier: GPL-2.0+ | ||
2 | config DRM_GM12U320 | ||
3 | tristate "GM12U320 driver for USB projectors" | ||
4 | depends on DRM && USB | ||
5 | select DRM_KMS_HELPER | ||
6 | select DRM_GEM_SHMEM_HELPER | ||
7 | help | ||
8 | This is a KMS driver for projectors which use the GM12U320 chipset | ||
9 | for video transfer over USB2/3, such as the Acer C120 mini projector. | ||
diff --git a/drivers/gpu/drm/gm12u320/Makefile b/drivers/gpu/drm/gm12u320/Makefile new file mode 100644 index 000000000000..ea514382f00d --- /dev/null +++ b/drivers/gpu/drm/gm12u320/Makefile | |||
@@ -0,0 +1,2 @@ | |||
1 | # SPDX-License-Identifier: GPL-2.0+ | ||
2 | obj-$(CONFIG_DRM_GM12U320) += gm12u320.o | ||
diff --git a/drivers/gpu/drm/gm12u320/gm12u320.c b/drivers/gpu/drm/gm12u320/gm12u320.c new file mode 100644 index 000000000000..b6f47b8cf240 --- /dev/null +++ b/drivers/gpu/drm/gm12u320/gm12u320.c | |||
@@ -0,0 +1,814 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0+ | ||
2 | /* | ||
3 | * Copyright 2019 Hans de Goede <hdegoede@redhat.com> | ||
4 | */ | ||
5 | |||
6 | #include <linux/dma-buf.h> | ||
7 | #include <linux/module.h> | ||
8 | #include <linux/usb.h> | ||
9 | |||
10 | #include <drm/drm_atomic_helper.h> | ||
11 | #include <drm/drm_atomic_state_helper.h> | ||
12 | #include <drm/drm_connector.h> | ||
13 | #include <drm/drm_damage_helper.h> | ||
14 | #include <drm/drm_drv.h> | ||
15 | #include <drm/drm_fb_helper.h> | ||
16 | #include <drm/drm_file.h> | ||
17 | #include <drm/drm_format_helper.h> | ||
18 | #include <drm/drm_fourcc.h> | ||
19 | #include <drm/drm_gem_shmem_helper.h> | ||
20 | #include <drm/drm_gem_framebuffer_helper.h> | ||
21 | #include <drm/drm_ioctl.h> | ||
22 | #include <drm/drm_modeset_helper_vtables.h> | ||
23 | #include <drm/drm_probe_helper.h> | ||
24 | #include <drm/drm_simple_kms_helper.h> | ||
25 | #include <drm/drm_vblank.h> | ||
26 | |||
27 | static bool eco_mode; | ||
28 | module_param(eco_mode, bool, 0644); | ||
29 | MODULE_PARM_DESC(eco_mode, "Turn on Eco mode (less bright, more silent)"); | ||
30 | |||
31 | #define DRIVER_NAME "gm12u320" | ||
32 | #define DRIVER_DESC "Grain Media GM12U320 USB projector display" | ||
33 | #define DRIVER_DATE "2019" | ||
34 | #define DRIVER_MAJOR 1 | ||
35 | #define DRIVER_MINOR 0 | ||
36 | #define DRIVER_PATCHLEVEL 1 | ||
37 | |||
38 | /* | ||
39 | * The DLP has an actual width of 854 pixels, but that is not a multiple | ||
40 | * of 8, breaking things left and right, so we export a width of 848. | ||
41 | */ | ||
42 | #define GM12U320_USER_WIDTH 848 | ||
43 | #define GM12U320_REAL_WIDTH 854 | ||
44 | #define GM12U320_HEIGHT 480 | ||
45 | |||
46 | #define GM12U320_BLOCK_COUNT 20 | ||
47 | |||
48 | #define MISC_RCV_EPT 1 | ||
49 | #define DATA_RCV_EPT 2 | ||
50 | #define DATA_SND_EPT 3 | ||
51 | #define MISC_SND_EPT 4 | ||
52 | |||
53 | #define DATA_BLOCK_HEADER_SIZE 84 | ||
54 | #define DATA_BLOCK_CONTENT_SIZE 64512 | ||
55 | #define DATA_BLOCK_FOOTER_SIZE 20 | ||
56 | #define DATA_BLOCK_SIZE (DATA_BLOCK_HEADER_SIZE + \ | ||
57 | DATA_BLOCK_CONTENT_SIZE + \ | ||
58 | DATA_BLOCK_FOOTER_SIZE) | ||
59 | #define DATA_LAST_BLOCK_CONTENT_SIZE 4032 | ||
60 | #define DATA_LAST_BLOCK_SIZE (DATA_BLOCK_HEADER_SIZE + \ | ||
61 | DATA_LAST_BLOCK_CONTENT_SIZE + \ | ||
62 | DATA_BLOCK_FOOTER_SIZE) | ||
63 | |||
64 | #define CMD_SIZE 31 | ||
65 | #define READ_STATUS_SIZE 13 | ||
66 | #define MISC_VALUE_SIZE 4 | ||
67 | |||
68 | #define CMD_TIMEOUT msecs_to_jiffies(200) | ||
69 | #define DATA_TIMEOUT msecs_to_jiffies(1000) | ||
70 | #define IDLE_TIMEOUT msecs_to_jiffies(2000) | ||
71 | #define FIRST_FRAME_TIMEOUT msecs_to_jiffies(2000) | ||
72 | |||
73 | #define MISC_REQ_GET_SET_ECO_A 0xff | ||
74 | #define MISC_REQ_GET_SET_ECO_B 0x35 | ||
75 | /* Windows driver does once every second, with arg d = 1, other args 0 */ | ||
76 | #define MISC_REQ_UNKNOWN1_A 0xff | ||
77 | #define MISC_REQ_UNKNOWN1_B 0x38 | ||
78 | /* Windows driver does this on init, with arg a, b = 0, c = 0xa0, d = 4 */ | ||
79 | #define MISC_REQ_UNKNOWN2_A 0xa5 | ||
80 | #define MISC_REQ_UNKNOWN2_B 0x00 | ||
81 | |||
82 | struct gm12u320_device { | ||
83 | struct drm_device dev; | ||
84 | struct drm_simple_display_pipe pipe; | ||
85 | struct drm_connector conn; | ||
86 | struct usb_device *udev; | ||
87 | unsigned char *cmd_buf; | ||
88 | unsigned char *data_buf[GM12U320_BLOCK_COUNT]; | ||
89 | bool pipe_enabled; | ||
90 | struct { | ||
91 | bool run; | ||
92 | struct workqueue_struct *workq; | ||
93 | struct work_struct work; | ||
94 | wait_queue_head_t waitq; | ||
95 | struct mutex lock; | ||
96 | struct drm_framebuffer *fb; | ||
97 | struct drm_rect rect; | ||
98 | } fb_update; | ||
99 | }; | ||
100 | |||
101 | static const char cmd_data[CMD_SIZE] = { | ||
102 | 0x55, 0x53, 0x42, 0x43, 0x00, 0x00, 0x00, 0x00, | ||
103 | 0x68, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x10, 0xff, | ||
104 | 0x00, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x80, 0x00, | ||
105 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 | ||
106 | }; | ||
107 | |||
108 | static const char cmd_draw[CMD_SIZE] = { | ||
109 | 0x55, 0x53, 0x42, 0x43, 0x00, 0x00, 0x00, 0x00, | ||
110 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xfe, | ||
111 | 0x00, 0x00, 0x00, 0xc0, 0xd1, 0x05, 0x00, 0x40, | ||
112 | 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 | ||
113 | }; | ||
114 | |||
115 | static const char cmd_misc[CMD_SIZE] = { | ||
116 | 0x55, 0x53, 0x42, 0x43, 0x00, 0x00, 0x00, 0x00, | ||
117 | 0x04, 0x00, 0x00, 0x00, 0x80, 0x01, 0x10, 0xfd, | ||
118 | 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, | ||
119 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 | ||
120 | }; | ||
121 | |||
122 | static const char data_block_header[DATA_BLOCK_HEADER_SIZE] = { | ||
123 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
124 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
125 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
126 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
127 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
128 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
129 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
130 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
131 | 0xfb, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
132 | 0x00, 0x04, 0x15, 0x00, 0x00, 0xfc, 0x00, 0x00, | ||
133 | 0x01, 0x00, 0x00, 0xdb | ||
134 | }; | ||
135 | |||
136 | static const char data_last_block_header[DATA_BLOCK_HEADER_SIZE] = { | ||
137 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
138 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
139 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
140 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
141 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
142 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
143 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
144 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
145 | 0xfb, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
146 | 0x2a, 0x00, 0x20, 0x00, 0xc0, 0x0f, 0x00, 0x00, | ||
147 | 0x01, 0x00, 0x00, 0xd7 | ||
148 | }; | ||
149 | |||
150 | static const char data_block_footer[DATA_BLOCK_FOOTER_SIZE] = { | ||
151 | 0xfb, 0x14, 0x02, 0x20, 0x00, 0x00, 0x00, 0x00, | ||
152 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
153 | 0x80, 0x00, 0x00, 0x4f | ||
154 | }; | ||
155 | |||
156 | static int gm12u320_usb_alloc(struct gm12u320_device *gm12u320) | ||
157 | { | ||
158 | int i, block_size; | ||
159 | const char *hdr; | ||
160 | |||
161 | gm12u320->cmd_buf = kmalloc(CMD_SIZE, GFP_KERNEL); | ||
162 | if (!gm12u320->cmd_buf) | ||
163 | return -ENOMEM; | ||
164 | |||
165 | for (i = 0; i < GM12U320_BLOCK_COUNT; i++) { | ||
166 | if (i == GM12U320_BLOCK_COUNT - 1) { | ||
167 | block_size = DATA_LAST_BLOCK_SIZE; | ||
168 | hdr = data_last_block_header; | ||
169 | } else { | ||
170 | block_size = DATA_BLOCK_SIZE; | ||
171 | hdr = data_block_header; | ||
172 | } | ||
173 | |||
174 | gm12u320->data_buf[i] = kzalloc(block_size, GFP_KERNEL); | ||
175 | if (!gm12u320->data_buf[i]) | ||
176 | return -ENOMEM; | ||
177 | |||
178 | memcpy(gm12u320->data_buf[i], hdr, DATA_BLOCK_HEADER_SIZE); | ||
179 | memcpy(gm12u320->data_buf[i] + | ||
180 | (block_size - DATA_BLOCK_FOOTER_SIZE), | ||
181 | data_block_footer, DATA_BLOCK_FOOTER_SIZE); | ||
182 | } | ||
183 | |||
184 | gm12u320->fb_update.workq = create_singlethread_workqueue(DRIVER_NAME); | ||
185 | if (!gm12u320->fb_update.workq) | ||
186 | return -ENOMEM; | ||
187 | |||
188 | return 0; | ||
189 | } | ||
190 | |||
191 | static void gm12u320_usb_free(struct gm12u320_device *gm12u320) | ||
192 | { | ||
193 | int i; | ||
194 | |||
195 | if (gm12u320->fb_update.workq) | ||
196 | destroy_workqueue(gm12u320->fb_update.workq); | ||
197 | |||
198 | for (i = 0; i < GM12U320_BLOCK_COUNT; i++) | ||
199 | kfree(gm12u320->data_buf[i]); | ||
200 | |||
201 | kfree(gm12u320->cmd_buf); | ||
202 | } | ||
203 | |||
204 | static int gm12u320_misc_request(struct gm12u320_device *gm12u320, | ||
205 | u8 req_a, u8 req_b, | ||
206 | u8 arg_a, u8 arg_b, u8 arg_c, u8 arg_d) | ||
207 | { | ||
208 | int ret, len; | ||
209 | |||
210 | memcpy(gm12u320->cmd_buf, &cmd_misc, CMD_SIZE); | ||
211 | gm12u320->cmd_buf[20] = req_a; | ||
212 | gm12u320->cmd_buf[21] = req_b; | ||
213 | gm12u320->cmd_buf[22] = arg_a; | ||
214 | gm12u320->cmd_buf[23] = arg_b; | ||
215 | gm12u320->cmd_buf[24] = arg_c; | ||
216 | gm12u320->cmd_buf[25] = arg_d; | ||
217 | |||
218 | /* Send request */ | ||
219 | ret = usb_bulk_msg(gm12u320->udev, | ||
220 | usb_sndbulkpipe(gm12u320->udev, MISC_SND_EPT), | ||
221 | gm12u320->cmd_buf, CMD_SIZE, &len, CMD_TIMEOUT); | ||
222 | if (ret || len != CMD_SIZE) { | ||
223 | dev_err(&gm12u320->udev->dev, "Misc. req. error %d\n", ret); | ||
224 | return -EIO; | ||
225 | } | ||
226 | |||
227 | /* Read value */ | ||
228 | ret = usb_bulk_msg(gm12u320->udev, | ||
229 | usb_rcvbulkpipe(gm12u320->udev, MISC_RCV_EPT), | ||
230 | gm12u320->cmd_buf, MISC_VALUE_SIZE, &len, | ||
231 | DATA_TIMEOUT); | ||
232 | if (ret || len != MISC_VALUE_SIZE) { | ||
233 | dev_err(&gm12u320->udev->dev, "Misc. value error %d\n", ret); | ||
234 | return -EIO; | ||
235 | } | ||
236 | /* cmd_buf[0] now contains the read value, which we don't use */ | ||
237 | |||
238 | /* Read status */ | ||
239 | ret = usb_bulk_msg(gm12u320->udev, | ||
240 | usb_rcvbulkpipe(gm12u320->udev, MISC_RCV_EPT), | ||
241 | gm12u320->cmd_buf, READ_STATUS_SIZE, &len, | ||
242 | CMD_TIMEOUT); | ||
243 | if (ret || len != READ_STATUS_SIZE) { | ||
244 | dev_err(&gm12u320->udev->dev, "Misc. status error %d\n", ret); | ||
245 | return -EIO; | ||
246 | } | ||
247 | |||
248 | return 0; | ||
249 | } | ||
250 | |||
251 | static void gm12u320_32bpp_to_24bpp_packed(u8 *dst, u8 *src, int len) | ||
252 | { | ||
253 | while (len--) { | ||
254 | *dst++ = *src++; | ||
255 | *dst++ = *src++; | ||
256 | *dst++ = *src++; | ||
257 | src++; | ||
258 | } | ||
259 | } | ||
260 | |||
261 | static void gm12u320_copy_fb_to_blocks(struct gm12u320_device *gm12u320) | ||
262 | { | ||
263 | int block, dst_offset, len, remain, ret, x1, x2, y1, y2; | ||
264 | struct drm_framebuffer *fb; | ||
265 | void *vaddr; | ||
266 | u8 *src; | ||
267 | |||
268 | mutex_lock(&gm12u320->fb_update.lock); | ||
269 | |||
270 | if (!gm12u320->fb_update.fb) | ||
271 | goto unlock; | ||
272 | |||
273 | fb = gm12u320->fb_update.fb; | ||
274 | x1 = gm12u320->fb_update.rect.x1; | ||
275 | x2 = gm12u320->fb_update.rect.x2; | ||
276 | y1 = gm12u320->fb_update.rect.y1; | ||
277 | y2 = gm12u320->fb_update.rect.y2; | ||
278 | |||
279 | vaddr = drm_gem_shmem_vmap(fb->obj[0]); | ||
280 | if (IS_ERR(vaddr)) { | ||
281 | DRM_ERROR("failed to vmap fb: %ld\n", PTR_ERR(vaddr)); | ||
282 | goto put_fb; | ||
283 | } | ||
284 | |||
285 | if (fb->obj[0]->import_attach) { | ||
286 | ret = dma_buf_begin_cpu_access( | ||
287 | fb->obj[0]->import_attach->dmabuf, DMA_FROM_DEVICE); | ||
288 | if (ret) { | ||
289 | DRM_ERROR("dma_buf_begin_cpu_access err: %d\n", ret); | ||
290 | goto vunmap; | ||
291 | } | ||
292 | } | ||
293 | |||
294 | src = vaddr + y1 * fb->pitches[0] + x1 * 4; | ||
295 | |||
296 | x1 += (GM12U320_REAL_WIDTH - GM12U320_USER_WIDTH) / 2; | ||
297 | x2 += (GM12U320_REAL_WIDTH - GM12U320_USER_WIDTH) / 2; | ||
298 | |||
299 | for (; y1 < y2; y1++) { | ||
300 | remain = 0; | ||
301 | len = (x2 - x1) * 3; | ||
302 | dst_offset = (y1 * GM12U320_REAL_WIDTH + x1) * 3; | ||
303 | block = dst_offset / DATA_BLOCK_CONTENT_SIZE; | ||
304 | dst_offset %= DATA_BLOCK_CONTENT_SIZE; | ||
305 | |||
306 | if ((dst_offset + len) > DATA_BLOCK_CONTENT_SIZE) { | ||
307 | remain = dst_offset + len - DATA_BLOCK_CONTENT_SIZE; | ||
308 | len = DATA_BLOCK_CONTENT_SIZE - dst_offset; | ||
309 | } | ||
310 | |||
311 | dst_offset += DATA_BLOCK_HEADER_SIZE; | ||
312 | len /= 3; | ||
313 | |||
314 | gm12u320_32bpp_to_24bpp_packed( | ||
315 | gm12u320->data_buf[block] + dst_offset, | ||
316 | src, len); | ||
317 | |||
318 | if (remain) { | ||
319 | block++; | ||
320 | dst_offset = DATA_BLOCK_HEADER_SIZE; | ||
321 | gm12u320_32bpp_to_24bpp_packed( | ||
322 | gm12u320->data_buf[block] + dst_offset, | ||
323 | src + len * 4, remain / 3); | ||
324 | } | ||
325 | src += fb->pitches[0]; | ||
326 | } | ||
327 | |||
328 | if (fb->obj[0]->import_attach) { | ||
329 | ret = dma_buf_end_cpu_access(fb->obj[0]->import_attach->dmabuf, | ||
330 | DMA_FROM_DEVICE); | ||
331 | if (ret) | ||
332 | DRM_ERROR("dma_buf_end_cpu_access err: %d\n", ret); | ||
333 | } | ||
334 | vunmap: | ||
335 | drm_gem_shmem_vunmap(fb->obj[0], vaddr); | ||
336 | put_fb: | ||
337 | drm_framebuffer_put(fb); | ||
338 | gm12u320->fb_update.fb = NULL; | ||
339 | unlock: | ||
340 | mutex_unlock(&gm12u320->fb_update.lock); | ||
341 | } | ||
342 | |||
343 | static int gm12u320_fb_update_ready(struct gm12u320_device *gm12u320) | ||
344 | { | ||
345 | int ret; | ||
346 | |||
347 | mutex_lock(&gm12u320->fb_update.lock); | ||
348 | ret = !gm12u320->fb_update.run || gm12u320->fb_update.fb != NULL; | ||
349 | mutex_unlock(&gm12u320->fb_update.lock); | ||
350 | |||
351 | return ret; | ||
352 | } | ||
353 | |||
354 | static void gm12u320_fb_update_work(struct work_struct *work) | ||
355 | { | ||
356 | struct gm12u320_device *gm12u320 = | ||
357 | container_of(work, struct gm12u320_device, fb_update.work); | ||
358 | int draw_status_timeout = FIRST_FRAME_TIMEOUT; | ||
359 | int block, block_size, len; | ||
360 | int frame = 0; | ||
361 | int ret = 0; | ||
362 | |||
363 | while (gm12u320->fb_update.run) { | ||
364 | gm12u320_copy_fb_to_blocks(gm12u320); | ||
365 | |||
366 | for (block = 0; block < GM12U320_BLOCK_COUNT; block++) { | ||
367 | if (block == GM12U320_BLOCK_COUNT - 1) | ||
368 | block_size = DATA_LAST_BLOCK_SIZE; | ||
369 | else | ||
370 | block_size = DATA_BLOCK_SIZE; | ||
371 | |||
372 | /* Send data command to device */ | ||
373 | memcpy(gm12u320->cmd_buf, cmd_data, CMD_SIZE); | ||
374 | gm12u320->cmd_buf[8] = block_size & 0xff; | ||
375 | gm12u320->cmd_buf[9] = block_size >> 8; | ||
376 | gm12u320->cmd_buf[20] = 0xfc - block * 4; | ||
377 | gm12u320->cmd_buf[21] = block | (frame << 7); | ||
378 | |||
379 | ret = usb_bulk_msg(gm12u320->udev, | ||
380 | usb_sndbulkpipe(gm12u320->udev, DATA_SND_EPT), | ||
381 | gm12u320->cmd_buf, CMD_SIZE, &len, | ||
382 | CMD_TIMEOUT); | ||
383 | if (ret || len != CMD_SIZE) | ||
384 | goto err; | ||
385 | |||
386 | /* Send data block to device */ | ||
387 | ret = usb_bulk_msg(gm12u320->udev, | ||
388 | usb_sndbulkpipe(gm12u320->udev, DATA_SND_EPT), | ||
389 | gm12u320->data_buf[block], block_size, | ||
390 | &len, DATA_TIMEOUT); | ||
391 | if (ret || len != block_size) | ||
392 | goto err; | ||
393 | |||
394 | /* Read status */ | ||
395 | ret = usb_bulk_msg(gm12u320->udev, | ||
396 | usb_rcvbulkpipe(gm12u320->udev, DATA_RCV_EPT), | ||
397 | gm12u320->cmd_buf, READ_STATUS_SIZE, &len, | ||
398 | CMD_TIMEOUT); | ||
399 | if (ret || len != READ_STATUS_SIZE) | ||
400 | goto err; | ||
401 | } | ||
402 | |||
403 | /* Send draw command to device */ | ||
404 | memcpy(gm12u320->cmd_buf, cmd_draw, CMD_SIZE); | ||
405 | ret = usb_bulk_msg(gm12u320->udev, | ||
406 | usb_sndbulkpipe(gm12u320->udev, DATA_SND_EPT), | ||
407 | gm12u320->cmd_buf, CMD_SIZE, &len, CMD_TIMEOUT); | ||
408 | if (ret || len != CMD_SIZE) | ||
409 | goto err; | ||
410 | |||
411 | /* Read status */ | ||
412 | ret = usb_bulk_msg(gm12u320->udev, | ||
413 | usb_rcvbulkpipe(gm12u320->udev, DATA_RCV_EPT), | ||
414 | gm12u320->cmd_buf, READ_STATUS_SIZE, &len, | ||
415 | draw_status_timeout); | ||
416 | if (ret || len != READ_STATUS_SIZE) | ||
417 | goto err; | ||
418 | |||
419 | draw_status_timeout = CMD_TIMEOUT; | ||
420 | frame = !frame; | ||
421 | |||
422 | /* | ||
423 | * We must draw a frame every 2s otherwise the projector | ||
424 | * switches back to showing its logo. | ||
425 | */ | ||
426 | wait_event_timeout(gm12u320->fb_update.waitq, | ||
427 | gm12u320_fb_update_ready(gm12u320), | ||
428 | IDLE_TIMEOUT); | ||
429 | } | ||
430 | return; | ||
431 | err: | ||
432 | /* Do not log errors caused by module unload or device unplug */ | ||
433 | if (ret != -ECONNRESET && ret != -ESHUTDOWN) | ||
434 | dev_err(&gm12u320->udev->dev, "Frame update error: %d\n", ret); | ||
435 | } | ||
436 | |||
437 | static void gm12u320_fb_mark_dirty(struct drm_framebuffer *fb, | ||
438 | struct drm_rect *dirty) | ||
439 | { | ||
440 | struct gm12u320_device *gm12u320 = fb->dev->dev_private; | ||
441 | struct drm_framebuffer *old_fb = NULL; | ||
442 | bool wakeup = false; | ||
443 | |||
444 | mutex_lock(&gm12u320->fb_update.lock); | ||
445 | |||
446 | if (gm12u320->fb_update.fb != fb) { | ||
447 | old_fb = gm12u320->fb_update.fb; | ||
448 | drm_framebuffer_get(fb); | ||
449 | gm12u320->fb_update.fb = fb; | ||
450 | gm12u320->fb_update.rect = *dirty; | ||
451 | wakeup = true; | ||
452 | } else { | ||
453 | struct drm_rect *rect = &gm12u320->fb_update.rect; | ||
454 | |||
455 | rect->x1 = min(rect->x1, dirty->x1); | ||
456 | rect->y1 = min(rect->y1, dirty->y1); | ||
457 | rect->x2 = max(rect->x2, dirty->x2); | ||
458 | rect->y2 = max(rect->y2, dirty->y2); | ||
459 | } | ||
460 | |||
461 | mutex_unlock(&gm12u320->fb_update.lock); | ||
462 | |||
463 | if (wakeup) | ||
464 | wake_up(&gm12u320->fb_update.waitq); | ||
465 | |||
466 | if (old_fb) | ||
467 | drm_framebuffer_put(old_fb); | ||
468 | } | ||
469 | |||
470 | static void gm12u320_start_fb_update(struct gm12u320_device *gm12u320) | ||
471 | { | ||
472 | mutex_lock(&gm12u320->fb_update.lock); | ||
473 | gm12u320->fb_update.run = true; | ||
474 | mutex_unlock(&gm12u320->fb_update.lock); | ||
475 | |||
476 | queue_work(gm12u320->fb_update.workq, &gm12u320->fb_update.work); | ||
477 | } | ||
478 | |||
479 | static void gm12u320_stop_fb_update(struct gm12u320_device *gm12u320) | ||
480 | { | ||
481 | mutex_lock(&gm12u320->fb_update.lock); | ||
482 | gm12u320->fb_update.run = false; | ||
483 | mutex_unlock(&gm12u320->fb_update.lock); | ||
484 | |||
485 | wake_up(&gm12u320->fb_update.waitq); | ||
486 | cancel_work_sync(&gm12u320->fb_update.work); | ||
487 | |||
488 | mutex_lock(&gm12u320->fb_update.lock); | ||
489 | if (gm12u320->fb_update.fb) { | ||
490 | drm_framebuffer_put(gm12u320->fb_update.fb); | ||
491 | gm12u320->fb_update.fb = NULL; | ||
492 | } | ||
493 | mutex_unlock(&gm12u320->fb_update.lock); | ||
494 | } | ||
495 | |||
496 | static int gm12u320_set_ecomode(struct gm12u320_device *gm12u320) | ||
497 | { | ||
498 | return gm12u320_misc_request(gm12u320, MISC_REQ_GET_SET_ECO_A, | ||
499 | MISC_REQ_GET_SET_ECO_B, 0x01 /* set */, | ||
500 | eco_mode ? 0x01 : 0x00, 0x00, 0x01); | ||
501 | } | ||
502 | |||
503 | /* ------------------------------------------------------------------ */ | ||
504 | /* gm12u320 connector */ | ||
505 | |||
506 | /* | ||
507 | *Â We use fake EDID info so that userspace know that it is dealing with | ||
508 | * an Acer projector, rather then listing this as an "unknown" monitor. | ||
509 | * Note this assumes this driver is only ever used with the Acer C120, if we | ||
510 | * add support for other devices the vendor and model should be parameterized. | ||
511 | */ | ||
512 | static struct edid gm12u320_edid = { | ||
513 | .header = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 }, | ||
514 | .mfg_id = { 0x04, 0x72 }, /* "ACR" */ | ||
515 | .prod_code = { 0x20, 0xc1 }, /* C120h */ | ||
516 | .serial = 0xaa55aa55, | ||
517 | .mfg_week = 1, | ||
518 | .mfg_year = 16, | ||
519 | .version = 1, /* EDID 1.3 */ | ||
520 | .revision = 3, /* EDID 1.3 */ | ||
521 | .input = 0x08, /* Analog input */ | ||
522 | .features = 0x0a, /* Pref timing in DTD 1 */ | ||
523 | .standard_timings = { { 1, 1 }, { 1, 1 }, { 1, 1 }, { 1, 1 }, | ||
524 | { 1, 1 }, { 1, 1 }, { 1, 1 }, { 1, 1 } }, | ||
525 | .detailed_timings = { { | ||
526 | .pixel_clock = 3383, | ||
527 | /* hactive = 848, hblank = 256 */ | ||
528 | .data.pixel_data.hactive_lo = 0x50, | ||
529 | .data.pixel_data.hblank_lo = 0x00, | ||
530 | .data.pixel_data.hactive_hblank_hi = 0x31, | ||
531 | /* vactive = 480, vblank = 28 */ | ||
532 | .data.pixel_data.vactive_lo = 0xe0, | ||
533 | .data.pixel_data.vblank_lo = 0x1c, | ||
534 | .data.pixel_data.vactive_vblank_hi = 0x10, | ||
535 | /* hsync offset 40 pw 128, vsync offset 1 pw 4 */ | ||
536 | .data.pixel_data.hsync_offset_lo = 0x28, | ||
537 | .data.pixel_data.hsync_pulse_width_lo = 0x80, | ||
538 | .data.pixel_data.vsync_offset_pulse_width_lo = 0x14, | ||
539 | .data.pixel_data.hsync_vsync_offset_pulse_width_hi = 0x00, | ||
540 | /* Digital separate syncs, hsync+, vsync+ */ | ||
541 | .data.pixel_data.misc = 0x1e, | ||
542 | }, { | ||
543 | .pixel_clock = 0, | ||
544 | .data.other_data.type = 0xfd, /* Monitor ranges */ | ||
545 | .data.other_data.data.range.min_vfreq = 59, | ||
546 | .data.other_data.data.range.max_vfreq = 61, | ||
547 | .data.other_data.data.range.min_hfreq_khz = 29, | ||
548 | .data.other_data.data.range.max_hfreq_khz = 32, | ||
549 | .data.other_data.data.range.pixel_clock_mhz = 4, /* 40 MHz */ | ||
550 | .data.other_data.data.range.flags = 0, | ||
551 | .data.other_data.data.range.formula.cvt = { | ||
552 | 0xa0, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 }, | ||
553 | }, { | ||
554 | .pixel_clock = 0, | ||
555 | .data.other_data.type = 0xfc, /* Model string */ | ||
556 | .data.other_data.data.str.str = { | ||
557 | 'P', 'r', 'o', 'j', 'e', 'c', 't', 'o', 'r', '\n', | ||
558 | ' ', ' ', ' ' }, | ||
559 | }, { | ||
560 | .pixel_clock = 0, | ||
561 | .data.other_data.type = 0xfe, /* Unspecified text / padding */ | ||
562 | .data.other_data.data.str.str = { | ||
563 | '\n', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', | ||
564 | ' ', ' ', ' ' }, | ||
565 | } }, | ||
566 | .checksum = 0x13, | ||
567 | }; | ||
568 | |||
569 | static int gm12u320_conn_get_modes(struct drm_connector *connector) | ||
570 | { | ||
571 | drm_connector_update_edid_property(connector, &gm12u320_edid); | ||
572 | return drm_add_edid_modes(connector, &gm12u320_edid); | ||
573 | } | ||
574 | |||
575 | static const struct drm_connector_helper_funcs gm12u320_conn_helper_funcs = { | ||
576 | .get_modes = gm12u320_conn_get_modes, | ||
577 | }; | ||
578 | |||
579 | static const struct drm_connector_funcs gm12u320_conn_funcs = { | ||
580 | .fill_modes = drm_helper_probe_single_connector_modes, | ||
581 | .destroy = drm_connector_cleanup, | ||
582 | .reset = drm_atomic_helper_connector_reset, | ||
583 | .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, | ||
584 | .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, | ||
585 | }; | ||
586 | |||
587 | static int gm12u320_conn_init(struct gm12u320_device *gm12u320) | ||
588 | { | ||
589 | drm_connector_helper_add(&gm12u320->conn, &gm12u320_conn_helper_funcs); | ||
590 | return drm_connector_init(&gm12u320->dev, &gm12u320->conn, | ||
591 | &gm12u320_conn_funcs, DRM_MODE_CONNECTOR_VGA); | ||
592 | } | ||
593 | |||
594 | /* ------------------------------------------------------------------ */ | ||
595 | /* gm12u320 (simple) display pipe */ | ||
596 | |||
597 | static void gm12u320_pipe_enable(struct drm_simple_display_pipe *pipe, | ||
598 | struct drm_crtc_state *crtc_state, | ||
599 | struct drm_plane_state *plane_state) | ||
600 | { | ||
601 | struct gm12u320_device *gm12u320 = pipe->crtc.dev->dev_private; | ||
602 | struct drm_rect rect = { 0, 0, GM12U320_USER_WIDTH, GM12U320_HEIGHT }; | ||
603 | |||
604 | gm12u320_fb_mark_dirty(plane_state->fb, &rect); | ||
605 | gm12u320_start_fb_update(gm12u320); | ||
606 | gm12u320->pipe_enabled = true; | ||
607 | } | ||
608 | |||
609 | static void gm12u320_pipe_disable(struct drm_simple_display_pipe *pipe) | ||
610 | { | ||
611 | struct gm12u320_device *gm12u320 = pipe->crtc.dev->dev_private; | ||
612 | |||
613 | gm12u320_stop_fb_update(gm12u320); | ||
614 | gm12u320->pipe_enabled = false; | ||
615 | } | ||
616 | |||
617 | static void gm12u320_pipe_update(struct drm_simple_display_pipe *pipe, | ||
618 | struct drm_plane_state *old_state) | ||
619 | { | ||
620 | struct drm_plane_state *state = pipe->plane.state; | ||
621 | struct drm_crtc *crtc = &pipe->crtc; | ||
622 | struct drm_rect rect; | ||
623 | |||
624 | if (drm_atomic_helper_damage_merged(old_state, state, &rect)) | ||
625 | gm12u320_fb_mark_dirty(pipe->plane.state->fb, &rect); | ||
626 | |||
627 | if (crtc->state->event) { | ||
628 | spin_lock_irq(&crtc->dev->event_lock); | ||
629 | drm_crtc_send_vblank_event(crtc, crtc->state->event); | ||
630 | crtc->state->event = NULL; | ||
631 | spin_unlock_irq(&crtc->dev->event_lock); | ||
632 | } | ||
633 | } | ||
634 | |||
635 | static const struct drm_simple_display_pipe_funcs gm12u320_pipe_funcs = { | ||
636 | .enable = gm12u320_pipe_enable, | ||
637 | .disable = gm12u320_pipe_disable, | ||
638 | .update = gm12u320_pipe_update, | ||
639 | }; | ||
640 | |||
641 | static const uint32_t gm12u320_pipe_formats[] = { | ||
642 | DRM_FORMAT_XRGB8888, | ||
643 | }; | ||
644 | |||
645 | static const uint64_t gm12u320_pipe_modifiers[] = { | ||
646 | DRM_FORMAT_MOD_LINEAR, | ||
647 | DRM_FORMAT_MOD_INVALID | ||
648 | }; | ||
649 | |||
650 | static void gm12u320_driver_release(struct drm_device *dev) | ||
651 | { | ||
652 | struct gm12u320_device *gm12u320 = dev->dev_private; | ||
653 | |||
654 | gm12u320_usb_free(gm12u320); | ||
655 | drm_mode_config_cleanup(dev); | ||
656 | drm_dev_fini(dev); | ||
657 | kfree(gm12u320); | ||
658 | } | ||
659 | |||
660 | DEFINE_DRM_GEM_SHMEM_FOPS(gm12u320_fops); | ||
661 | |||
662 | static struct drm_driver gm12u320_drm_driver = { | ||
663 | .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, | ||
664 | |||
665 | .name = DRIVER_NAME, | ||
666 | .desc = DRIVER_DESC, | ||
667 | .date = DRIVER_DATE, | ||
668 | .major = DRIVER_MAJOR, | ||
669 | .minor = DRIVER_MINOR, | ||
670 | |||
671 | .release = gm12u320_driver_release, | ||
672 | .fops = &gm12u320_fops, | ||
673 | DRM_GEM_SHMEM_DRIVER_OPS, | ||
674 | }; | ||
675 | |||
676 | static const struct drm_mode_config_funcs gm12u320_mode_config_funcs = { | ||
677 | .fb_create = drm_gem_fb_create_with_dirty, | ||
678 | .atomic_check = drm_atomic_helper_check, | ||
679 | .atomic_commit = drm_atomic_helper_commit, | ||
680 | }; | ||
681 | |||
682 | static int gm12u320_usb_probe(struct usb_interface *interface, | ||
683 | const struct usb_device_id *id) | ||
684 | { | ||
685 | struct gm12u320_device *gm12u320; | ||
686 | struct drm_device *dev; | ||
687 | int ret; | ||
688 | |||
689 | /* | ||
690 | * The gm12u320 presents itself to the system as 2 usb mass-storage | ||
691 | * interfaces, we only care about / need the first one. | ||
692 | */ | ||
693 | if (interface->cur_altsetting->desc.bInterfaceNumber != 0) | ||
694 | return -ENODEV; | ||
695 | |||
696 | gm12u320 = kzalloc(sizeof(*gm12u320), GFP_KERNEL); | ||
697 | if (gm12u320 == NULL) | ||
698 | return -ENOMEM; | ||
699 | |||
700 | gm12u320->udev = interface_to_usbdev(interface); | ||
701 | INIT_WORK(&gm12u320->fb_update.work, gm12u320_fb_update_work); | ||
702 | mutex_init(&gm12u320->fb_update.lock); | ||
703 | init_waitqueue_head(&gm12u320->fb_update.waitq); | ||
704 | |||
705 | dev = &gm12u320->dev; | ||
706 | ret = drm_dev_init(dev, &gm12u320_drm_driver, &interface->dev); | ||
707 | if (ret) { | ||
708 | kfree(gm12u320); | ||
709 | return ret; | ||
710 | } | ||
711 | dev->dev_private = gm12u320; | ||
712 | |||
713 | drm_mode_config_init(dev); | ||
714 | dev->mode_config.min_width = GM12U320_USER_WIDTH; | ||
715 | dev->mode_config.max_width = GM12U320_USER_WIDTH; | ||
716 | dev->mode_config.min_height = GM12U320_HEIGHT; | ||
717 | dev->mode_config.max_height = GM12U320_HEIGHT; | ||
718 | dev->mode_config.funcs = &gm12u320_mode_config_funcs; | ||
719 | |||
720 | ret = gm12u320_usb_alloc(gm12u320); | ||
721 | if (ret) | ||
722 | goto err_put; | ||
723 | |||
724 | ret = gm12u320_set_ecomode(gm12u320); | ||
725 | if (ret) | ||
726 | goto err_put; | ||
727 | |||
728 | ret = gm12u320_conn_init(gm12u320); | ||
729 | if (ret) | ||
730 | goto err_put; | ||
731 | |||
732 | ret = drm_simple_display_pipe_init(&gm12u320->dev, | ||
733 | &gm12u320->pipe, | ||
734 | &gm12u320_pipe_funcs, | ||
735 | gm12u320_pipe_formats, | ||
736 | ARRAY_SIZE(gm12u320_pipe_formats), | ||
737 | gm12u320_pipe_modifiers, | ||
738 | &gm12u320->conn); | ||
739 | if (ret) | ||
740 | goto err_put; | ||
741 | |||
742 | drm_mode_config_reset(dev); | ||
743 | |||
744 | usb_set_intfdata(interface, dev); | ||
745 | ret = drm_dev_register(dev, 0); | ||
746 | if (ret) | ||
747 | goto err_put; | ||
748 | |||
749 | drm_fbdev_generic_setup(dev, dev->mode_config.preferred_depth); | ||
750 | |||
751 | return 0; | ||
752 | |||
753 | err_put: | ||
754 | drm_dev_put(dev); | ||
755 | return ret; | ||
756 | } | ||
757 | |||
758 | static void gm12u320_usb_disconnect(struct usb_interface *interface) | ||
759 | { | ||
760 | struct drm_device *dev = usb_get_intfdata(interface); | ||
761 | struct gm12u320_device *gm12u320 = dev->dev_private; | ||
762 | |||
763 | gm12u320_stop_fb_update(gm12u320); | ||
764 | drm_dev_unplug(dev); | ||
765 | drm_dev_put(dev); | ||
766 | } | ||
767 | |||
768 | #ifdef CONFIG_PM | ||
769 | static int gm12u320_suspend(struct usb_interface *interface, | ||
770 | pm_message_t message) | ||
771 | { | ||
772 | struct drm_device *dev = usb_get_intfdata(interface); | ||
773 | struct gm12u320_device *gm12u320 = dev->dev_private; | ||
774 | |||
775 | if (gm12u320->pipe_enabled) | ||
776 | gm12u320_stop_fb_update(gm12u320); | ||
777 | |||
778 | return 0; | ||
779 | } | ||
780 | |||
781 | static int gm12u320_resume(struct usb_interface *interface) | ||
782 | { | ||
783 | struct drm_device *dev = usb_get_intfdata(interface); | ||
784 | struct gm12u320_device *gm12u320 = dev->dev_private; | ||
785 | |||
786 | gm12u320_set_ecomode(gm12u320); | ||
787 | if (gm12u320->pipe_enabled) | ||
788 | gm12u320_start_fb_update(gm12u320); | ||
789 | |||
790 | return 0; | ||
791 | } | ||
792 | #endif | ||
793 | |||
794 | static const struct usb_device_id id_table[] = { | ||
795 | { USB_DEVICE(0x1de1, 0xc102) }, | ||
796 | {}, | ||
797 | }; | ||
798 | MODULE_DEVICE_TABLE(usb, id_table); | ||
799 | |||
800 | static struct usb_driver gm12u320_usb_driver = { | ||
801 | .name = "gm12u320", | ||
802 | .probe = gm12u320_usb_probe, | ||
803 | .disconnect = gm12u320_usb_disconnect, | ||
804 | .id_table = id_table, | ||
805 | #ifdef CONFIG_PM | ||
806 | .suspend = gm12u320_suspend, | ||
807 | .resume = gm12u320_resume, | ||
808 | .reset_resume = gm12u320_resume, | ||
809 | #endif | ||
810 | }; | ||
811 | |||
812 | module_usb_driver(gm12u320_usb_driver); | ||
813 | MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); | ||
814 | MODULE_LICENSE("GPL"); | ||