1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | hexium_orion.c - v4l2 driver for the Hexium Orion frame grabber cards |
4 | |
5 | Visit http://www.mihu.de/linux/saa7146/ and follow the link |
6 | to "hexium" for further details about this card. |
7 | |
8 | Copyright (C) 2003 Michael Hunold <michael@mihu.de> |
9 | |
10 | */ |
11 | |
12 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
13 | |
14 | #define DEBUG_VARIABLE debug |
15 | |
16 | #include <media/drv-intf/saa7146_vv.h> |
17 | #include <linux/module.h> |
18 | #include <linux/kernel.h> |
19 | |
20 | static int debug; |
21 | module_param(debug, int, 0); |
22 | MODULE_PARM_DESC(debug, "debug verbosity" ); |
23 | |
24 | /* global variables */ |
25 | static int hexium_num; |
26 | |
27 | #define HEXIUM_HV_PCI6_ORION 1 |
28 | #define HEXIUM_ORION_1SVHS_3BNC 2 |
29 | #define HEXIUM_ORION_4BNC 3 |
30 | |
31 | #define HEXIUM_STD (V4L2_STD_PAL | V4L2_STD_SECAM | V4L2_STD_NTSC) |
32 | #define HEXIUM_INPUTS 9 |
33 | static struct v4l2_input hexium_inputs[HEXIUM_INPUTS] = { |
34 | { 0, "CVBS 1" , V4L2_INPUT_TYPE_CAMERA, 0, 0, HEXIUM_STD, 0, V4L2_IN_CAP_STD }, |
35 | { 1, "CVBS 2" , V4L2_INPUT_TYPE_CAMERA, 0, 0, HEXIUM_STD, 0, V4L2_IN_CAP_STD }, |
36 | { 2, "CVBS 3" , V4L2_INPUT_TYPE_CAMERA, 0, 0, HEXIUM_STD, 0, V4L2_IN_CAP_STD }, |
37 | { 3, "CVBS 4" , V4L2_INPUT_TYPE_CAMERA, 0, 0, HEXIUM_STD, 0, V4L2_IN_CAP_STD }, |
38 | { 4, "CVBS 5" , V4L2_INPUT_TYPE_CAMERA, 0, 0, HEXIUM_STD, 0, V4L2_IN_CAP_STD }, |
39 | { 5, "CVBS 6" , V4L2_INPUT_TYPE_CAMERA, 0, 0, HEXIUM_STD, 0, V4L2_IN_CAP_STD }, |
40 | { 6, "Y/C 1" , V4L2_INPUT_TYPE_CAMERA, 0, 0, HEXIUM_STD, 0, V4L2_IN_CAP_STD }, |
41 | { 7, "Y/C 2" , V4L2_INPUT_TYPE_CAMERA, 0, 0, HEXIUM_STD, 0, V4L2_IN_CAP_STD }, |
42 | { 8, "Y/C 3" , V4L2_INPUT_TYPE_CAMERA, 0, 0, HEXIUM_STD, 0, V4L2_IN_CAP_STD }, |
43 | }; |
44 | |
45 | #define HEXIUM_AUDIOS 0 |
46 | |
47 | struct hexium_data |
48 | { |
49 | s8 adr; |
50 | u8 byte; |
51 | }; |
52 | |
53 | struct hexium |
54 | { |
55 | int type; |
56 | struct video_device video_dev; |
57 | struct i2c_adapter i2c_adapter; |
58 | |
59 | int cur_input; /* current input */ |
60 | }; |
61 | |
62 | /* Philips SAA7110 decoder default registers */ |
63 | static u8 hexium_saa7110[53]={ |
64 | /*00*/ 0x4C,0x3C,0x0D,0xEF,0xBD,0xF0,0x00,0x00, |
65 | /*08*/ 0xF8,0xF8,0x60,0x60,0x40,0x86,0x18,0x90, |
66 | /*10*/ 0x00,0x2C,0x40,0x46,0x42,0x1A,0xFF,0xDA, |
67 | /*18*/ 0xF0,0x8B,0x00,0x00,0x00,0x00,0x00,0x00, |
68 | /*20*/ 0xD9,0x17,0x40,0x41,0x80,0x41,0x80,0x4F, |
69 | /*28*/ 0xFE,0x01,0x0F,0x0F,0x03,0x01,0x81,0x03, |
70 | /*30*/ 0x44,0x75,0x01,0x8C,0x03 |
71 | }; |
72 | |
73 | static struct { |
74 | struct hexium_data data[8]; |
75 | } hexium_input_select[] = { |
76 | { |
77 | { /* cvbs 1 */ |
78 | { 0x06, 0x00 }, |
79 | { 0x20, 0xD9 }, |
80 | { 0x21, 0x17 }, // 0x16, |
81 | { 0x22, 0x40 }, |
82 | { 0x2C, 0x03 }, |
83 | { 0x30, 0x44 }, |
84 | { 0x31, 0x75 }, // ?? |
85 | { 0x21, 0x16 }, // 0x03, |
86 | } |
87 | }, { |
88 | { /* cvbs 2 */ |
89 | { 0x06, 0x00 }, |
90 | { 0x20, 0x78 }, |
91 | { 0x21, 0x07 }, // 0x03, |
92 | { 0x22, 0xD2 }, |
93 | { 0x2C, 0x83 }, |
94 | { 0x30, 0x60 }, |
95 | { 0x31, 0xB5 }, // ? |
96 | { 0x21, 0x03 }, |
97 | } |
98 | }, { |
99 | { /* cvbs 3 */ |
100 | { 0x06, 0x00 }, |
101 | { 0x20, 0xBA }, |
102 | { 0x21, 0x07 }, // 0x05, |
103 | { 0x22, 0x91 }, |
104 | { 0x2C, 0x03 }, |
105 | { 0x30, 0x60 }, |
106 | { 0x31, 0xB5 }, // ?? |
107 | { 0x21, 0x05 }, // 0x03, |
108 | } |
109 | }, { |
110 | { /* cvbs 4 */ |
111 | { 0x06, 0x00 }, |
112 | { 0x20, 0xD8 }, |
113 | { 0x21, 0x17 }, // 0x16, |
114 | { 0x22, 0x40 }, |
115 | { 0x2C, 0x03 }, |
116 | { 0x30, 0x44 }, |
117 | { 0x31, 0x75 }, // ?? |
118 | { 0x21, 0x16 }, // 0x03, |
119 | } |
120 | }, { |
121 | { /* cvbs 5 */ |
122 | { 0x06, 0x00 }, |
123 | { 0x20, 0xB8 }, |
124 | { 0x21, 0x07 }, // 0x05, |
125 | { 0x22, 0x91 }, |
126 | { 0x2C, 0x03 }, |
127 | { 0x30, 0x60 }, |
128 | { 0x31, 0xB5 }, // ?? |
129 | { 0x21, 0x05 }, // 0x03, |
130 | } |
131 | }, { |
132 | { /* cvbs 6 */ |
133 | { 0x06, 0x00 }, |
134 | { 0x20, 0x7C }, |
135 | { 0x21, 0x07 }, // 0x03 |
136 | { 0x22, 0xD2 }, |
137 | { 0x2C, 0x83 }, |
138 | { 0x30, 0x60 }, |
139 | { 0x31, 0xB5 }, // ?? |
140 | { 0x21, 0x03 }, |
141 | } |
142 | }, { |
143 | { /* y/c 1 */ |
144 | { 0x06, 0x80 }, |
145 | { 0x20, 0x59 }, |
146 | { 0x21, 0x17 }, |
147 | { 0x22, 0x42 }, |
148 | { 0x2C, 0xA3 }, |
149 | { 0x30, 0x44 }, |
150 | { 0x31, 0x75 }, |
151 | { 0x21, 0x12 }, |
152 | } |
153 | }, { |
154 | { /* y/c 2 */ |
155 | { 0x06, 0x80 }, |
156 | { 0x20, 0x9A }, |
157 | { 0x21, 0x17 }, |
158 | { 0x22, 0xB1 }, |
159 | { 0x2C, 0x13 }, |
160 | { 0x30, 0x60 }, |
161 | { 0x31, 0xB5 }, |
162 | { 0x21, 0x14 }, |
163 | } |
164 | }, { |
165 | { /* y/c 3 */ |
166 | { 0x06, 0x80 }, |
167 | { 0x20, 0x3C }, |
168 | { 0x21, 0x27 }, |
169 | { 0x22, 0xC1 }, |
170 | { 0x2C, 0x23 }, |
171 | { 0x30, 0x44 }, |
172 | { 0x31, 0x75 }, |
173 | { 0x21, 0x21 }, |
174 | } |
175 | } |
176 | }; |
177 | |
178 | static struct saa7146_standard hexium_standards[] = { |
179 | { |
180 | .name = "PAL" , .id = V4L2_STD_PAL, |
181 | .v_offset = 16, .v_field = 288, |
182 | .h_offset = 1, .h_pixels = 680, |
183 | .v_max_out = 576, .h_max_out = 768, |
184 | }, { |
185 | .name = "NTSC" , .id = V4L2_STD_NTSC, |
186 | .v_offset = 16, .v_field = 240, |
187 | .h_offset = 1, .h_pixels = 640, |
188 | .v_max_out = 480, .h_max_out = 640, |
189 | }, { |
190 | .name = "SECAM" , .id = V4L2_STD_SECAM, |
191 | .v_offset = 16, .v_field = 288, |
192 | .h_offset = 1, .h_pixels = 720, |
193 | .v_max_out = 576, .h_max_out = 768, |
194 | } |
195 | }; |
196 | |
197 | /* this is only called for old HV-PCI6/Orion cards |
198 | without eeprom */ |
199 | static int hexium_probe(struct saa7146_dev *dev) |
200 | { |
201 | struct hexium *hexium = NULL; |
202 | union i2c_smbus_data data; |
203 | int err = 0; |
204 | |
205 | DEB_EE("\n" ); |
206 | |
207 | /* there are no hexium orion cards with revision 0 saa7146s */ |
208 | if (0 == dev->revision) { |
209 | return -EFAULT; |
210 | } |
211 | |
212 | hexium = kzalloc(size: sizeof(*hexium), GFP_KERNEL); |
213 | if (!hexium) |
214 | return -ENOMEM; |
215 | |
216 | /* enable i2c-port pins */ |
217 | saa7146_write(dev, MC1, (MASK_08 | MASK_24 | MASK_10 | MASK_26)); |
218 | |
219 | saa7146_write(dev, DD1_INIT, 0x01000100); |
220 | saa7146_write(dev, DD1_STREAM_B, 0x00000000); |
221 | saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); |
222 | |
223 | strscpy(hexium->i2c_adapter.name, "hexium orion" , |
224 | sizeof(hexium->i2c_adapter.name)); |
225 | saa7146_i2c_adapter_prepare(dev, i2c_adapter: &hexium->i2c_adapter, SAA7146_I2C_BUS_BIT_RATE_480); |
226 | if (i2c_add_adapter(adap: &hexium->i2c_adapter) < 0) { |
227 | DEB_S("cannot register i2c-device. skipping.\n" ); |
228 | kfree(objp: hexium); |
229 | return -EFAULT; |
230 | } |
231 | |
232 | /* set SAA7110 control GPIO 0 */ |
233 | saa7146_setgpio(dev, port: 0, SAA7146_GPIO_OUTHI); |
234 | /* set HWControl GPIO number 2 */ |
235 | saa7146_setgpio(dev, port: 2, SAA7146_GPIO_OUTHI); |
236 | |
237 | mdelay(10); |
238 | |
239 | /* detect newer Hexium Orion cards by subsystem ids */ |
240 | if (0x17c8 == dev->pci->subsystem_vendor && 0x0101 == dev->pci->subsystem_device) { |
241 | pr_info("device is a Hexium Orion w/ 1 SVHS + 3 BNC inputs\n" ); |
242 | /* we store the pointer in our private data field */ |
243 | dev->ext_priv = hexium; |
244 | hexium->type = HEXIUM_ORION_1SVHS_3BNC; |
245 | return 0; |
246 | } |
247 | |
248 | if (0x17c8 == dev->pci->subsystem_vendor && 0x2101 == dev->pci->subsystem_device) { |
249 | pr_info("device is a Hexium Orion w/ 4 BNC inputs\n" ); |
250 | /* we store the pointer in our private data field */ |
251 | dev->ext_priv = hexium; |
252 | hexium->type = HEXIUM_ORION_4BNC; |
253 | return 0; |
254 | } |
255 | |
256 | /* check if this is an old hexium Orion card by looking at |
257 | a saa7110 at address 0x4e */ |
258 | err = i2c_smbus_xfer(adapter: &hexium->i2c_adapter, addr: 0x4e, flags: 0, I2C_SMBUS_READ, |
259 | command: 0x00, I2C_SMBUS_BYTE_DATA, data: &data); |
260 | if (err == 0) { |
261 | pr_info("device is a Hexium HV-PCI6/Orion (old)\n" ); |
262 | /* we store the pointer in our private data field */ |
263 | dev->ext_priv = hexium; |
264 | hexium->type = HEXIUM_HV_PCI6_ORION; |
265 | return 0; |
266 | } |
267 | |
268 | i2c_del_adapter(adap: &hexium->i2c_adapter); |
269 | kfree(objp: hexium); |
270 | return -EFAULT; |
271 | } |
272 | |
273 | /* bring hardware to a sane state. this has to be done, just in case someone |
274 | wants to capture from this device before it has been properly initialized. |
275 | the capture engine would badly fail, because no valid signal arrives on the |
276 | saa7146, thus leading to timeouts and stuff. */ |
277 | static int hexium_init_done(struct saa7146_dev *dev) |
278 | { |
279 | struct hexium *hexium = (struct hexium *) dev->ext_priv; |
280 | union i2c_smbus_data data; |
281 | int i = 0; |
282 | |
283 | DEB_D("hexium_init_done called\n" ); |
284 | |
285 | /* initialize the helper ics to useful values */ |
286 | for (i = 0; i < sizeof(hexium_saa7110); i++) { |
287 | data.byte = hexium_saa7110[i]; |
288 | if (0 != i2c_smbus_xfer(adapter: &hexium->i2c_adapter, addr: 0x4e, flags: 0, I2C_SMBUS_WRITE, command: i, I2C_SMBUS_BYTE_DATA, data: &data)) { |
289 | pr_err("failed for address 0x%02x\n" , i); |
290 | } |
291 | } |
292 | |
293 | return 0; |
294 | } |
295 | |
296 | static int hexium_set_input(struct hexium *hexium, int input) |
297 | { |
298 | union i2c_smbus_data data; |
299 | int i = 0; |
300 | |
301 | DEB_D("\n" ); |
302 | |
303 | for (i = 0; i < 8; i++) { |
304 | int adr = hexium_input_select[input].data[i].adr; |
305 | data.byte = hexium_input_select[input].data[i].byte; |
306 | if (0 != i2c_smbus_xfer(adapter: &hexium->i2c_adapter, addr: 0x4e, flags: 0, I2C_SMBUS_WRITE, command: adr, I2C_SMBUS_BYTE_DATA, data: &data)) { |
307 | return -1; |
308 | } |
309 | pr_debug("%d: 0x%02x => 0x%02x\n" , input, adr, data.byte); |
310 | } |
311 | |
312 | return 0; |
313 | } |
314 | |
315 | static int vidioc_enum_input(struct file *file, void *fh, struct v4l2_input *i) |
316 | { |
317 | DEB_EE("VIDIOC_ENUMINPUT %d\n" , i->index); |
318 | |
319 | if (i->index >= HEXIUM_INPUTS) |
320 | return -EINVAL; |
321 | |
322 | memcpy(i, &hexium_inputs[i->index], sizeof(struct v4l2_input)); |
323 | |
324 | DEB_D("v4l2_ioctl: VIDIOC_ENUMINPUT %d\n" , i->index); |
325 | return 0; |
326 | } |
327 | |
328 | static int vidioc_g_input(struct file *file, void *fh, unsigned int *input) |
329 | { |
330 | struct saa7146_dev *dev = video_drvdata(file); |
331 | struct hexium *hexium = (struct hexium *) dev->ext_priv; |
332 | |
333 | *input = hexium->cur_input; |
334 | |
335 | DEB_D("VIDIOC_G_INPUT: %d\n" , *input); |
336 | return 0; |
337 | } |
338 | |
339 | static int vidioc_s_input(struct file *file, void *fh, unsigned int input) |
340 | { |
341 | struct saa7146_dev *dev = video_drvdata(file); |
342 | struct hexium *hexium = (struct hexium *) dev->ext_priv; |
343 | |
344 | if (input >= HEXIUM_INPUTS) |
345 | return -EINVAL; |
346 | |
347 | hexium->cur_input = input; |
348 | hexium_set_input(hexium, input); |
349 | |
350 | return 0; |
351 | } |
352 | |
353 | static struct saa7146_ext_vv vv_data; |
354 | |
355 | /* this function only gets called when the probing was successful */ |
356 | static int hexium_attach(struct saa7146_dev *dev, struct saa7146_pci_extension_data *info) |
357 | { |
358 | struct hexium *hexium = (struct hexium *) dev->ext_priv; |
359 | int ret; |
360 | |
361 | DEB_EE("\n" ); |
362 | |
363 | ret = saa7146_vv_init(dev, ext_vv: &vv_data); |
364 | if (ret) { |
365 | pr_err("Error in saa7146_vv_init()\n" ); |
366 | return ret; |
367 | } |
368 | |
369 | vv_data.vid_ops.vidioc_enum_input = vidioc_enum_input; |
370 | vv_data.vid_ops.vidioc_g_input = vidioc_g_input; |
371 | vv_data.vid_ops.vidioc_s_input = vidioc_s_input; |
372 | if (0 != saa7146_register_device(vid: &hexium->video_dev, dev, name: "hexium orion" , type: VFL_TYPE_VIDEO)) { |
373 | pr_err("cannot register capture v4l2 device. skipping.\n" ); |
374 | return -1; |
375 | } |
376 | |
377 | pr_err("found 'hexium orion' frame grabber-%d\n" , hexium_num); |
378 | hexium_num++; |
379 | |
380 | /* the rest */ |
381 | hexium->cur_input = 0; |
382 | hexium_init_done(dev); |
383 | hexium_set_input(hexium, input: 0); |
384 | |
385 | return 0; |
386 | } |
387 | |
388 | static int hexium_detach(struct saa7146_dev *dev) |
389 | { |
390 | struct hexium *hexium = (struct hexium *) dev->ext_priv; |
391 | |
392 | DEB_EE("dev:%p\n" , dev); |
393 | |
394 | saa7146_unregister_device(vid: &hexium->video_dev, dev); |
395 | saa7146_vv_release(dev); |
396 | |
397 | hexium_num--; |
398 | |
399 | i2c_del_adapter(adap: &hexium->i2c_adapter); |
400 | kfree(objp: hexium); |
401 | return 0; |
402 | } |
403 | |
404 | static int std_callback(struct saa7146_dev *dev, struct saa7146_standard *std) |
405 | { |
406 | return 0; |
407 | } |
408 | |
409 | static struct saa7146_extension extension; |
410 | |
411 | static struct saa7146_pci_extension_data hexium_hv_pci6 = { |
412 | .ext_priv = "Hexium HV-PCI6 / Orion" , |
413 | .ext = &extension, |
414 | }; |
415 | |
416 | static struct saa7146_pci_extension_data hexium_orion_1svhs_3bnc = { |
417 | .ext_priv = "Hexium HV-PCI6 / Orion (1 SVHS/3 BNC)" , |
418 | .ext = &extension, |
419 | }; |
420 | |
421 | static struct saa7146_pci_extension_data hexium_orion_4bnc = { |
422 | .ext_priv = "Hexium HV-PCI6 / Orion (4 BNC)" , |
423 | .ext = &extension, |
424 | }; |
425 | |
426 | static const struct pci_device_id pci_tbl[] = { |
427 | { |
428 | .vendor = PCI_VENDOR_ID_PHILIPS, |
429 | .device = PCI_DEVICE_ID_PHILIPS_SAA7146, |
430 | .subvendor = 0x0000, |
431 | .subdevice = 0x0000, |
432 | .driver_data = (unsigned long) &hexium_hv_pci6, |
433 | }, |
434 | { |
435 | .vendor = PCI_VENDOR_ID_PHILIPS, |
436 | .device = PCI_DEVICE_ID_PHILIPS_SAA7146, |
437 | .subvendor = 0x17c8, |
438 | .subdevice = 0x0101, |
439 | .driver_data = (unsigned long) &hexium_orion_1svhs_3bnc, |
440 | }, |
441 | { |
442 | .vendor = PCI_VENDOR_ID_PHILIPS, |
443 | .device = PCI_DEVICE_ID_PHILIPS_SAA7146, |
444 | .subvendor = 0x17c8, |
445 | .subdevice = 0x2101, |
446 | .driver_data = (unsigned long) &hexium_orion_4bnc, |
447 | }, |
448 | { |
449 | .vendor = 0, |
450 | } |
451 | }; |
452 | |
453 | MODULE_DEVICE_TABLE(pci, pci_tbl); |
454 | |
455 | static struct saa7146_ext_vv vv_data = { |
456 | .inputs = HEXIUM_INPUTS, |
457 | .capabilities = 0, |
458 | .stds = &hexium_standards[0], |
459 | .num_stds = ARRAY_SIZE(hexium_standards), |
460 | .std_callback = &std_callback, |
461 | }; |
462 | |
463 | static struct saa7146_extension extension = { |
464 | .name = "hexium HV-PCI6 Orion" , |
465 | .flags = 0, // SAA7146_USE_I2C_IRQ, |
466 | |
467 | .pci_tbl = &pci_tbl[0], |
468 | .module = THIS_MODULE, |
469 | |
470 | .probe = hexium_probe, |
471 | .attach = hexium_attach, |
472 | .detach = hexium_detach, |
473 | |
474 | .irq_mask = 0, |
475 | .irq_func = NULL, |
476 | }; |
477 | |
478 | static int __init hexium_init_module(void) |
479 | { |
480 | if (0 != saa7146_register_extension(&extension)) { |
481 | DEB_S("failed to register extension\n" ); |
482 | return -ENODEV; |
483 | } |
484 | |
485 | return 0; |
486 | } |
487 | |
488 | static void __exit hexium_cleanup_module(void) |
489 | { |
490 | saa7146_unregister_extension(&extension); |
491 | } |
492 | |
493 | module_init(hexium_init_module); |
494 | module_exit(hexium_cleanup_module); |
495 | |
496 | MODULE_DESCRIPTION("video4linux-2 driver for Hexium Orion frame grabber cards" ); |
497 | MODULE_AUTHOR("Michael Hunold <michael@mihu.de>" ); |
498 | MODULE_LICENSE("GPL" ); |
499 | |