1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Subdriver for Scopium astro-camera (DTCS033, 0547:7303) |
4 | * |
5 | * Copyright (C) 2014 Robert Butora (robert.butora.fi@gmail.com) |
6 | */ |
7 | |
8 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
9 | #define MODULE_NAME "dtcs033" |
10 | #include "gspca.h" |
11 | |
12 | MODULE_AUTHOR("Robert Butora <robert.butora.fi@gmail.com>" ); |
13 | MODULE_DESCRIPTION("Scopium DTCS033 astro-cam USB Camera Driver" ); |
14 | MODULE_LICENSE("GPL" ); |
15 | |
16 | struct dtcs033_usb_requests { |
17 | u8 bRequestType; |
18 | u8 bRequest; |
19 | u16 wValue; |
20 | u16 wIndex; |
21 | u16 wLength; |
22 | }; |
23 | |
24 | /* send a usb request */ |
25 | static void reg_rw(struct gspca_dev *gspca_dev, |
26 | u8 bRequestType, u8 bRequest, |
27 | u16 wValue, u16 wIndex, u16 wLength) |
28 | { |
29 | struct usb_device *udev = gspca_dev->dev; |
30 | int ret; |
31 | |
32 | if (gspca_dev->usb_err < 0) |
33 | return; |
34 | |
35 | ret = usb_control_msg(dev: udev, |
36 | usb_rcvctrlpipe(udev, 0), |
37 | request: bRequest, |
38 | requesttype: bRequestType, |
39 | value: wValue, index: wIndex, |
40 | data: gspca_dev->usb_buf, size: wLength, timeout: 500); |
41 | |
42 | if (ret < 0) { |
43 | gspca_dev->usb_err = ret; |
44 | pr_err("usb_control_msg error %d\n" , ret); |
45 | } |
46 | |
47 | return; |
48 | } |
49 | /* send several usb in/out requests */ |
50 | static int reg_reqs(struct gspca_dev *gspca_dev, |
51 | const struct dtcs033_usb_requests *preqs, int n_reqs) |
52 | { |
53 | int i = 0; |
54 | const struct dtcs033_usb_requests *preq; |
55 | |
56 | while ((i < n_reqs) && (gspca_dev->usb_err >= 0)) { |
57 | |
58 | preq = &preqs[i]; |
59 | |
60 | reg_rw(gspca_dev, bRequestType: preq->bRequestType, bRequest: preq->bRequest, |
61 | wValue: preq->wValue, wIndex: preq->wIndex, wLength: preq->wLength); |
62 | |
63 | if (gspca_dev->usb_err < 0) { |
64 | |
65 | gspca_err(gspca_dev, "usb error request no: %d / %d\n" , |
66 | i, n_reqs); |
67 | } else if (preq->bRequestType & USB_DIR_IN) { |
68 | |
69 | gspca_dbg(gspca_dev, D_STREAM, |
70 | "USB IN (%d) returned[%d] %3ph %s" , |
71 | i, |
72 | preq->wLength, |
73 | gspca_dev->usb_buf, |
74 | preq->wLength > 3 ? "...\n" : "\n" ); |
75 | } |
76 | |
77 | i++; |
78 | } |
79 | return gspca_dev->usb_err; |
80 | } |
81 | |
82 | /* -- subdriver interface implementation -- */ |
83 | |
84 | #define DT_COLS (640) |
85 | static const struct v4l2_pix_format dtcs033_mode[] = { |
86 | /* raw Bayer patterned output */ |
87 | {DT_COLS, 480, V4L2_PIX_FMT_GREY, V4L2_FIELD_NONE, |
88 | .bytesperline = DT_COLS, |
89 | .sizeimage = DT_COLS*480, |
90 | .colorspace = V4L2_COLORSPACE_SRGB, |
91 | }, |
92 | /* this mode will demosaic the Bayer pattern */ |
93 | {DT_COLS, 480, V4L2_PIX_FMT_SRGGB8, V4L2_FIELD_NONE, |
94 | .bytesperline = DT_COLS, |
95 | .sizeimage = DT_COLS*480, |
96 | .colorspace = V4L2_COLORSPACE_SRGB, |
97 | } |
98 | }; |
99 | |
100 | /* config called at probe time */ |
101 | static int sd_config(struct gspca_dev *gspca_dev, |
102 | const struct usb_device_id *id) |
103 | { |
104 | gspca_dev->cam.cam_mode = dtcs033_mode; |
105 | gspca_dev->cam.nmodes = ARRAY_SIZE(dtcs033_mode); |
106 | |
107 | gspca_dev->cam.bulk = 1; |
108 | gspca_dev->cam.bulk_nurbs = 1; |
109 | gspca_dev->cam.bulk_size = DT_COLS*512; |
110 | |
111 | return 0; |
112 | } |
113 | |
114 | /* init called at probe and resume time */ |
115 | static int sd_init(struct gspca_dev *gspca_dev) |
116 | { |
117 | return 0; |
118 | } |
119 | |
120 | /* start stop the camera */ |
121 | static int dtcs033_start(struct gspca_dev *gspca_dev); |
122 | static void dtcs033_stopN(struct gspca_dev *gspca_dev); |
123 | |
124 | /* intercept camera image data */ |
125 | static void dtcs033_pkt_scan(struct gspca_dev *gspca_dev, |
126 | u8 *data, /* packet data */ |
127 | int len) /* packet data length */ |
128 | { |
129 | /* drop incomplete frames */ |
130 | if (len != DT_COLS*512) { |
131 | gspca_dev->last_packet_type = DISCARD_PACKET; |
132 | /* gspca.c: discard invalidates the whole frame. */ |
133 | return; |
134 | } |
135 | |
136 | /* forward complete frames */ |
137 | gspca_frame_add(gspca_dev, packet_type: FIRST_PACKET, NULL, len: 0); |
138 | gspca_frame_add(gspca_dev, packet_type: INTER_PACKET, |
139 | data: data + 16*DT_COLS, |
140 | len: len - 32*DT_COLS); /* skip first & last 16 lines */ |
141 | gspca_frame_add(gspca_dev, packet_type: LAST_PACKET, NULL, len: 0); |
142 | |
143 | return; |
144 | } |
145 | |
146 | /* -- controls: exposure and gain -- */ |
147 | |
148 | static void dtcs033_setexposure(struct gspca_dev *gspca_dev, |
149 | s32 expo, s32 gain) |
150 | { |
151 | /* gain [dB] encoding */ |
152 | u16 sGain = (u16)gain; |
153 | u16 gainVal = 224+(sGain-14)*(768-224)/(33-14); |
154 | u16 wIndex = 0x0100|(0x00FF&gainVal); |
155 | u16 wValue = (0xFF00&gainVal)>>8; |
156 | |
157 | /* exposure time [msec] encoding */ |
158 | u16 sXTime = (u16)expo; |
159 | u16 xtimeVal = (524*(150-(sXTime-1)))/150; |
160 | |
161 | const u8 bRequestType = |
162 | USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE; |
163 | const u8 bRequest = 0x18; |
164 | |
165 | reg_rw(gspca_dev, |
166 | bRequestType, bRequest, wValue, wIndex, wLength: 0); |
167 | if (gspca_dev->usb_err < 0) |
168 | gspca_err(gspca_dev, "usb error in setexposure(gain) sequence\n" ); |
169 | |
170 | reg_rw(gspca_dev, |
171 | bRequestType, bRequest, wValue: (xtimeVal<<4), wIndex: 0x6300, wLength: 0); |
172 | if (gspca_dev->usb_err < 0) |
173 | gspca_err(gspca_dev, "usb error in setexposure(time) sequence\n" ); |
174 | } |
175 | |
176 | /* specific webcam descriptor */ |
177 | struct sd { |
178 | struct gspca_dev gspca_dev;/* !! must be the first item */ |
179 | |
180 | /* exposure & gain controls */ |
181 | struct { |
182 | struct v4l2_ctrl *exposure; |
183 | struct v4l2_ctrl *gain; |
184 | }; |
185 | }; |
186 | |
187 | static int sd_s_ctrl(struct v4l2_ctrl *ctrl) |
188 | { |
189 | struct gspca_dev *gspca_dev = |
190 | container_of(ctrl->handler, |
191 | struct gspca_dev, ctrl_handler); |
192 | struct sd *sd = (struct sd *) gspca_dev; |
193 | |
194 | gspca_dev->usb_err = 0; |
195 | |
196 | if (!gspca_dev->streaming) |
197 | return 0; |
198 | |
199 | switch (ctrl->id) { |
200 | case V4L2_CID_EXPOSURE: |
201 | dtcs033_setexposure(gspca_dev, |
202 | expo: ctrl->val, gain: sd->gain->val); |
203 | break; |
204 | case V4L2_CID_GAIN: |
205 | dtcs033_setexposure(gspca_dev, |
206 | expo: sd->exposure->val, gain: ctrl->val); |
207 | break; |
208 | } |
209 | return gspca_dev->usb_err; |
210 | } |
211 | |
212 | static const struct v4l2_ctrl_ops sd_ctrl_ops = { |
213 | .s_ctrl = sd_s_ctrl, |
214 | }; |
215 | |
216 | static int dtcs033_init_controls(struct gspca_dev *gspca_dev) |
217 | { |
218 | struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler; |
219 | struct sd *sd = (struct sd *) gspca_dev; |
220 | |
221 | gspca_dev->vdev.ctrl_handler = hdl; |
222 | v4l2_ctrl_handler_init(hdl, 2); |
223 | /* min max step default */ |
224 | sd->exposure = v4l2_ctrl_new_std(hdl, ops: &sd_ctrl_ops, |
225 | V4L2_CID_EXPOSURE, |
226 | min: 1, max: 150, step: 1, def: 75);/* [msec] */ |
227 | sd->gain = v4l2_ctrl_new_std(hdl, ops: &sd_ctrl_ops, |
228 | V4L2_CID_GAIN, |
229 | min: 14, max: 33, step: 1, def: 24);/* [dB] */ |
230 | if (hdl->error) { |
231 | gspca_err(gspca_dev, "Could not initialize controls: %d\n" , |
232 | hdl->error); |
233 | return hdl->error; |
234 | } |
235 | |
236 | v4l2_ctrl_cluster(ncontrols: 2, controls: &sd->exposure); |
237 | return 0; |
238 | } |
239 | |
240 | /* sub-driver description */ |
241 | static const struct sd_desc sd_desc = { |
242 | .name = MODULE_NAME, |
243 | .config = sd_config, |
244 | .init = sd_init, |
245 | .start = dtcs033_start, |
246 | .stopN = dtcs033_stopN, |
247 | .pkt_scan = dtcs033_pkt_scan, |
248 | .init_controls = dtcs033_init_controls, |
249 | }; |
250 | |
251 | /* -- module initialisation -- */ |
252 | |
253 | static const struct usb_device_id device_table[] = { |
254 | {USB_DEVICE(0x0547, 0x7303)}, |
255 | {} |
256 | }; |
257 | MODULE_DEVICE_TABLE(usb, device_table); |
258 | |
259 | /* device connect */ |
260 | static int sd_probe(struct usb_interface *intf, |
261 | const struct usb_device_id *id) |
262 | { |
263 | return gspca_dev_probe(intf, id, |
264 | sd_desc: &sd_desc, dev_size: sizeof(struct sd), |
265 | THIS_MODULE); |
266 | } |
267 | |
268 | static struct usb_driver sd_driver = { |
269 | .name = MODULE_NAME, |
270 | .id_table = device_table, |
271 | .probe = sd_probe, |
272 | .disconnect = gspca_disconnect, |
273 | #ifdef CONFIG_PM |
274 | .suspend = gspca_suspend, |
275 | .resume = gspca_resume, |
276 | .reset_resume = gspca_resume, |
277 | #endif |
278 | }; |
279 | module_usb_driver(sd_driver); |
280 | |
281 | |
282 | /* --------------------------------------------------------- |
283 | USB requests to start/stop the camera [USB 2.0 spec Ch.9]. |
284 | |
285 | bRequestType : |
286 | 0x40 = USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
287 | 0xC0 = USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, |
288 | */ |
289 | static const struct dtcs033_usb_requests dtcs033_start_reqs[] = { |
290 | /* -- bRequest,wValue,wIndex,wLength */ |
291 | { 0x40, 0x01, 0x0001, 0x000F, 0x0000 }, |
292 | { 0x40, 0x01, 0x0000, 0x000F, 0x0000 }, |
293 | { 0x40, 0x01, 0x0001, 0x000F, 0x0000 }, |
294 | { 0x40, 0x18, 0x0000, 0x7F00, 0x0000 }, |
295 | { 0x40, 0x18, 0x0000, 0x1001, 0x0000 }, |
296 | { 0x40, 0x18, 0x0000, 0x0004, 0x0000 }, |
297 | { 0x40, 0x18, 0x0000, 0x7F01, 0x0000 }, |
298 | { 0x40, 0x18, 0x30E0, 0x0009, 0x0000 }, |
299 | { 0x40, 0x18, 0x0500, 0x012C, 0x0000 }, |
300 | { 0x40, 0x18, 0x0380, 0x0200, 0x0000 }, |
301 | { 0x40, 0x18, 0x0000, 0x035C, 0x0000 }, |
302 | { 0x40, 0x18, 0x05C0, 0x0438, 0x0000 }, |
303 | { 0x40, 0x18, 0x0440, 0x0500, 0x0000 }, |
304 | { 0x40, 0x18, 0x0000, 0x0668, 0x0000 }, |
305 | { 0x40, 0x18, 0x0000, 0x0700, 0x0000 }, |
306 | { 0x40, 0x18, 0x0000, 0x0800, 0x0000 }, |
307 | { 0x40, 0x18, 0x0000, 0x0900, 0x0000 }, |
308 | { 0x40, 0x18, 0x0000, 0x0A00, 0x0000 }, |
309 | { 0x40, 0x18, 0x0000, 0x0B00, 0x0000 }, |
310 | { 0x40, 0x18, 0x30E0, 0x6009, 0x0000 }, |
311 | { 0x40, 0x18, 0x0500, 0x612C, 0x0000 }, |
312 | { 0x40, 0x18, 0x2090, 0x6274, 0x0000 }, |
313 | { 0x40, 0x18, 0x05C0, 0x6338, 0x0000 }, |
314 | { 0x40, 0x18, 0x0000, 0x6400, 0x0000 }, |
315 | { 0x40, 0x18, 0x05C0, 0x6538, 0x0000 }, |
316 | { 0x40, 0x18, 0x0000, 0x6600, 0x0000 }, |
317 | { 0x40, 0x18, 0x0680, 0x6744, 0x0000 }, |
318 | { 0x40, 0x18, 0x0000, 0x6800, 0x0000 }, |
319 | { 0x40, 0x18, 0x0000, 0x6900, 0x0000 }, |
320 | { 0x40, 0x18, 0x0000, 0x6A00, 0x0000 }, |
321 | { 0x40, 0x18, 0x0000, 0x6B00, 0x0000 }, |
322 | { 0x40, 0x18, 0x0000, 0x6C00, 0x0000 }, |
323 | { 0x40, 0x18, 0x0000, 0x6D00, 0x0000 }, |
324 | { 0x40, 0x18, 0x0000, 0x6E00, 0x0000 }, |
325 | { 0x40, 0x18, 0x0000, 0x808C, 0x0000 }, |
326 | { 0x40, 0x18, 0x0010, 0x8101, 0x0000 }, |
327 | { 0x40, 0x18, 0x30E0, 0x8200, 0x0000 }, |
328 | { 0x40, 0x18, 0x0810, 0x832C, 0x0000 }, |
329 | { 0x40, 0x18, 0x0680, 0x842B, 0x0000 }, |
330 | { 0x40, 0x18, 0x0000, 0x8500, 0x0000 }, |
331 | { 0x40, 0x18, 0x0000, 0x8600, 0x0000 }, |
332 | { 0x40, 0x18, 0x0280, 0x8715, 0x0000 }, |
333 | { 0x40, 0x18, 0x0000, 0x880C, 0x0000 }, |
334 | { 0x40, 0x18, 0x0010, 0x8901, 0x0000 }, |
335 | { 0x40, 0x18, 0x30E0, 0x8A00, 0x0000 }, |
336 | { 0x40, 0x18, 0x0810, 0x8B2C, 0x0000 }, |
337 | { 0x40, 0x18, 0x0680, 0x8C2B, 0x0000 }, |
338 | { 0x40, 0x18, 0x0000, 0x8D00, 0x0000 }, |
339 | { 0x40, 0x18, 0x0000, 0x8E00, 0x0000 }, |
340 | { 0x40, 0x18, 0x0280, 0x8F15, 0x0000 }, |
341 | { 0x40, 0x18, 0x0010, 0xD040, 0x0000 }, |
342 | { 0x40, 0x18, 0x0000, 0xD100, 0x0000 }, |
343 | { 0x40, 0x18, 0x00B0, 0xD20A, 0x0000 }, |
344 | { 0x40, 0x18, 0x0000, 0xD300, 0x0000 }, |
345 | { 0x40, 0x18, 0x30E2, 0xD40D, 0x0000 }, |
346 | { 0x40, 0x18, 0x0001, 0xD5C0, 0x0000 }, |
347 | { 0x40, 0x18, 0x00A0, 0xD60A, 0x0000 }, |
348 | { 0x40, 0x18, 0x0000, 0xD700, 0x0000 }, |
349 | { 0x40, 0x18, 0x0000, 0x7F00, 0x0000 }, |
350 | { 0x40, 0x18, 0x0000, 0x1501, 0x0000 }, |
351 | { 0x40, 0x18, 0x0001, 0x01FF, 0x0000 }, |
352 | { 0x40, 0x18, 0x0000, 0x0200, 0x0000 }, |
353 | { 0x40, 0x18, 0x0000, 0x0304, 0x0000 }, |
354 | { 0x40, 0x18, 0x0000, 0x1101, 0x0000 }, |
355 | { 0x40, 0x18, 0x0000, 0x1201, 0x0000 }, |
356 | { 0x40, 0x18, 0x0000, 0x1300, 0x0000 }, |
357 | { 0x40, 0x18, 0x0000, 0x1400, 0x0000 }, |
358 | { 0x40, 0x18, 0x0000, 0x1601, 0x0000 }, |
359 | { 0x40, 0x18, 0x0000, 0x1800, 0x0000 }, |
360 | { 0x40, 0x18, 0x0000, 0x1900, 0x0000 }, |
361 | { 0x40, 0x18, 0x0000, 0x1A00, 0x0000 }, |
362 | { 0x40, 0x18, 0x2000, 0x1B00, 0x0000 }, |
363 | { 0x40, 0x18, 0x0000, 0x1C00, 0x0000 }, |
364 | { 0x40, 0x18, 0x0000, 0x2100, 0x0000 }, |
365 | { 0x40, 0x18, 0x00C0, 0x228E, 0x0000 }, |
366 | { 0x40, 0x18, 0x0000, 0x3001, 0x0000 }, |
367 | { 0x40, 0x18, 0x0010, 0x3101, 0x0000 }, |
368 | { 0x40, 0x18, 0x0008, 0x3301, 0x0000 }, |
369 | { 0x40, 0x18, 0x0000, 0x3400, 0x0000 }, |
370 | { 0x40, 0x18, 0x0012, 0x3549, 0x0000 }, |
371 | { 0x40, 0x18, 0x0000, 0x3620, 0x0000 }, |
372 | { 0x40, 0x18, 0x0001, 0x3700, 0x0000 }, |
373 | { 0x40, 0x18, 0x0000, 0x4000, 0x0000 }, |
374 | { 0x40, 0x18, 0xFFFF, 0x41FF, 0x0000 }, |
375 | { 0x40, 0x18, 0xFFFF, 0x42FF, 0x0000 }, |
376 | { 0x40, 0x18, 0x0000, 0x500F, 0x0000 }, |
377 | { 0x40, 0x18, 0x2272, 0x5108, 0x0000 }, |
378 | { 0x40, 0x18, 0x2272, 0x5208, 0x0000 }, |
379 | { 0x40, 0x18, 0xFFFF, 0x53FF, 0x0000 }, |
380 | { 0x40, 0x18, 0xFFFF, 0x54FF, 0x0000 }, |
381 | { 0x40, 0x18, 0x0000, 0x6000, 0x0000 }, |
382 | { 0x40, 0x18, 0x0000, 0x6102, 0x0000 }, |
383 | { 0x40, 0x18, 0x0010, 0x6214, 0x0000 }, |
384 | { 0x40, 0x18, 0x0C80, 0x6300, 0x0000 }, |
385 | { 0x40, 0x18, 0x0000, 0x6401, 0x0000 }, |
386 | { 0x40, 0x18, 0x0680, 0x6551, 0x0000 }, |
387 | { 0x40, 0x18, 0xFFFF, 0x66FF, 0x0000 }, |
388 | { 0x40, 0x18, 0x0000, 0x6702, 0x0000 }, |
389 | { 0x40, 0x18, 0x0010, 0x6800, 0x0000 }, |
390 | { 0x40, 0x18, 0x0000, 0x6900, 0x0000 }, |
391 | { 0x40, 0x18, 0x0000, 0x6A00, 0x0000 }, |
392 | { 0x40, 0x18, 0x0000, 0x6B00, 0x0000 }, |
393 | { 0x40, 0x18, 0x0000, 0x6C00, 0x0000 }, |
394 | { 0x40, 0x18, 0x0000, 0x6D01, 0x0000 }, |
395 | { 0x40, 0x18, 0x0000, 0x6E00, 0x0000 }, |
396 | { 0x40, 0x18, 0x0000, 0x6F00, 0x0000 }, |
397 | { 0x40, 0x18, 0x0000, 0x7000, 0x0000 }, |
398 | { 0x40, 0x18, 0x0001, 0x7118, 0x0000 }, |
399 | { 0x40, 0x18, 0x0000, 0x2001, 0x0000 }, |
400 | { 0x40, 0x18, 0x0000, 0x1101, 0x0000 }, |
401 | { 0x40, 0x18, 0x0000, 0x1301, 0x0000 }, |
402 | { 0x40, 0x18, 0x0000, 0x1300, 0x0000 }, |
403 | { 0x40, 0x18, 0x0000, 0x1501, 0x0000 }, |
404 | { 0xC0, 0x11, 0x0000, 0x24C0, 0x0003 }, |
405 | { 0x40, 0x18, 0x0000, 0x3000, 0x0000 }, |
406 | { 0x40, 0x18, 0x0000, 0x3620, 0x0000 }, |
407 | { 0x40, 0x18, 0x0000, 0x1501, 0x0000 }, |
408 | { 0x40, 0x18, 0x0010, 0x6300, 0x0000 }, |
409 | { 0x40, 0x18, 0x0002, 0x01F0, 0x0000 }, |
410 | { 0x40, 0x01, 0x0003, 0x000F, 0x0000 } |
411 | }; |
412 | |
413 | static const struct dtcs033_usb_requests dtcs033_stop_reqs[] = { |
414 | /* -- bRequest,wValue,wIndex,wLength */ |
415 | { 0x40, 0x01, 0x0001, 0x000F, 0x0000 }, |
416 | { 0x40, 0x01, 0x0000, 0x000F, 0x0000 }, |
417 | { 0x40, 0x18, 0x0000, 0x0003, 0x0000 } |
418 | }; |
419 | static int dtcs033_start(struct gspca_dev *gspca_dev) |
420 | { |
421 | return reg_reqs(gspca_dev, preqs: dtcs033_start_reqs, |
422 | ARRAY_SIZE(dtcs033_start_reqs)); |
423 | } |
424 | |
425 | static void dtcs033_stopN(struct gspca_dev *gspca_dev) |
426 | { |
427 | reg_reqs(gspca_dev, preqs: dtcs033_stop_reqs, |
428 | ARRAY_SIZE(dtcs033_stop_reqs)); |
429 | return; |
430 | } |
431 | |