1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* DVB USB compliant Linux driver for the |
3 | * - TwinhanDTV Alpha/MagicBoxII USB2.0 DVB-T receiver |
4 | * - DigitalNow TinyUSB2 DVB-t receiver |
5 | * |
6 | * Copyright (C) 2004-5 Patrick Boettcher (patrick.boettcher@posteo.de) |
7 | * |
8 | * Thanks to Twinhan who kindly provided hardware and information. |
9 | * |
10 | * see Documentation/driver-api/media/drivers/dvb-usb.rst for more information |
11 | */ |
12 | #include "vp7045.h" |
13 | |
14 | /* debug */ |
15 | static int dvb_usb_vp7045_debug; |
16 | module_param_named(debug,dvb_usb_vp7045_debug, int, 0644); |
17 | MODULE_PARM_DESC(debug, "set debugging level (1=info,xfer=2,rc=4 (or-able))." DVB_USB_DEBUG_STATUS); |
18 | |
19 | DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); |
20 | |
21 | #define deb_info(args...) dprintk(dvb_usb_vp7045_debug,0x01,args) |
22 | #define deb_xfer(args...) dprintk(dvb_usb_vp7045_debug,0x02,args) |
23 | #define deb_rc(args...) dprintk(dvb_usb_vp7045_debug,0x04,args) |
24 | |
25 | int vp7045_usb_op(struct dvb_usb_device *d, u8 cmd, u8 *out, int outlen, u8 *in, int inlen, int msec) |
26 | { |
27 | int ret = 0; |
28 | u8 *buf = d->priv; |
29 | |
30 | buf[0] = cmd; |
31 | |
32 | if (outlen > 19) |
33 | outlen = 19; |
34 | |
35 | if (inlen > 11) |
36 | inlen = 11; |
37 | |
38 | ret = mutex_lock_interruptible(&d->usb_mutex); |
39 | if (ret) |
40 | return ret; |
41 | |
42 | if (out != NULL && outlen > 0) |
43 | memcpy(&buf[1], out, outlen); |
44 | |
45 | deb_xfer("out buffer: " ); |
46 | debug_dump(buf, outlen+1, deb_xfer); |
47 | |
48 | |
49 | if (usb_control_msg(dev: d->udev, |
50 | usb_sndctrlpipe(d->udev,0), |
51 | TH_COMMAND_OUT, USB_TYPE_VENDOR | USB_DIR_OUT, value: 0, index: 0, |
52 | data: buf, size: 20, timeout: 2000) != 20) { |
53 | err("USB control message 'out' went wrong." ); |
54 | ret = -EIO; |
55 | goto unlock; |
56 | } |
57 | |
58 | msleep(msecs: msec); |
59 | |
60 | if (usb_control_msg(dev: d->udev, |
61 | usb_rcvctrlpipe(d->udev,0), |
62 | TH_COMMAND_IN, USB_TYPE_VENDOR | USB_DIR_IN, value: 0, index: 0, |
63 | data: buf, size: 12, timeout: 2000) != 12) { |
64 | err("USB control message 'in' went wrong." ); |
65 | ret = -EIO; |
66 | goto unlock; |
67 | } |
68 | |
69 | deb_xfer("in buffer: " ); |
70 | debug_dump(buf, 12, deb_xfer); |
71 | |
72 | if (in != NULL && inlen > 0) |
73 | memcpy(in, &buf[1], inlen); |
74 | |
75 | unlock: |
76 | mutex_unlock(lock: &d->usb_mutex); |
77 | |
78 | return ret; |
79 | } |
80 | |
81 | u8 vp7045_read_reg(struct dvb_usb_device *d, u8 reg) |
82 | { |
83 | u8 obuf[2] = { 0 },v; |
84 | obuf[1] = reg; |
85 | |
86 | vp7045_usb_op(d,TUNER_REG_READ,out: obuf,outlen: 2,in: &v,inlen: 1,msec: 30); |
87 | |
88 | return v; |
89 | } |
90 | |
91 | static int vp7045_power_ctrl(struct dvb_usb_device *d, int onoff) |
92 | { |
93 | u8 v = onoff; |
94 | return vp7045_usb_op(d,SET_TUNER_POWER,out: &v,outlen: 1,NULL,inlen: 0,msec: 150); |
95 | } |
96 | |
97 | static int vp7045_rc_query(struct dvb_usb_device *d) |
98 | { |
99 | int ret; |
100 | u8 key; |
101 | |
102 | ret = vp7045_usb_op(d, RC_VAL_READ, NULL, outlen: 0, in: &key, inlen: 1, msec: 20); |
103 | if (ret) |
104 | return ret; |
105 | |
106 | deb_rc("remote query key: %x\n" , key); |
107 | |
108 | if (key != 0x44) { |
109 | /* |
110 | * The 8 bit address isn't available, but since the remote uses |
111 | * address 0 we'll use that. nec repeats are ignored too, even |
112 | * though the remote sends them. |
113 | */ |
114 | rc_keydown(dev: d->rc_dev, protocol: RC_PROTO_NEC, RC_SCANCODE_NEC(0, key), toggle: 0); |
115 | } |
116 | |
117 | return 0; |
118 | } |
119 | |
120 | static int vp7045_read_eeprom(struct dvb_usb_device *d,u8 *buf, int len, int offset) |
121 | { |
122 | int i, ret; |
123 | u8 v, br[2]; |
124 | for (i=0; i < len; i++) { |
125 | v = offset + i; |
126 | ret = vp7045_usb_op(d, GET_EE_VALUE, out: &v, outlen: 1, in: br, inlen: 2, msec: 5); |
127 | if (ret) |
128 | return ret; |
129 | |
130 | buf[i] = br[1]; |
131 | } |
132 | deb_info("VP7045 EEPROM read (offs: %d, len: %d) : " , offset, i); |
133 | debug_dump(buf, i, deb_info); |
134 | return 0; |
135 | } |
136 | |
137 | static int vp7045_read_mac_addr(struct dvb_usb_device *d,u8 mac[6]) |
138 | { |
139 | return vp7045_read_eeprom(d,buf: mac, len: 6, MAC_0_ADDR); |
140 | } |
141 | |
142 | static int vp7045_frontend_attach(struct dvb_usb_adapter *adap) |
143 | { |
144 | u8 buf[255] = { 0 }; |
145 | |
146 | vp7045_usb_op(d: adap->dev,VENDOR_STRING_READ,NULL,outlen: 0,in: buf,inlen: 20,msec: 0); |
147 | buf[10] = '\0'; |
148 | deb_info("firmware says: %s " ,buf); |
149 | |
150 | vp7045_usb_op(d: adap->dev,PRODUCT_STRING_READ,NULL,outlen: 0,in: buf,inlen: 20,msec: 0); |
151 | buf[10] = '\0'; |
152 | deb_info("%s " ,buf); |
153 | |
154 | vp7045_usb_op(d: adap->dev,FW_VERSION_READ,NULL,outlen: 0,in: buf,inlen: 20,msec: 0); |
155 | buf[10] = '\0'; |
156 | deb_info("v%s\n" ,buf); |
157 | |
158 | /* Dump the EEPROM */ |
159 | /* vp7045_read_eeprom(d,buf, 255, FX2_ID_ADDR); */ |
160 | |
161 | adap->fe_adap[0].fe = vp7045_fe_attach(d: adap->dev); |
162 | |
163 | return 0; |
164 | } |
165 | |
166 | static struct dvb_usb_device_properties vp7045_properties; |
167 | |
168 | static int vp7045_usb_probe(struct usb_interface *intf, |
169 | const struct usb_device_id *id) |
170 | { |
171 | return dvb_usb_device_init(intf, &vp7045_properties, |
172 | THIS_MODULE, NULL, adapter_nums: adapter_nr); |
173 | } |
174 | |
175 | enum { |
176 | VISIONPLUS_VP7045_COLD, |
177 | VISIONPLUS_VP7045_WARM, |
178 | VISIONPLUS_TINYUSB2_COLD, |
179 | VISIONPLUS_TINYUSB2_WARM, |
180 | }; |
181 | |
182 | static struct usb_device_id vp7045_usb_table[] = { |
183 | DVB_USB_DEV(VISIONPLUS, VISIONPLUS_VP7045_COLD), |
184 | DVB_USB_DEV(VISIONPLUS, VISIONPLUS_VP7045_WARM), |
185 | DVB_USB_DEV(VISIONPLUS, VISIONPLUS_TINYUSB2_COLD), |
186 | DVB_USB_DEV(VISIONPLUS, VISIONPLUS_TINYUSB2_WARM), |
187 | { } |
188 | }; |
189 | |
190 | MODULE_DEVICE_TABLE(usb, vp7045_usb_table); |
191 | |
192 | static struct dvb_usb_device_properties vp7045_properties = { |
193 | .usb_ctrl = CYPRESS_FX2, |
194 | .firmware = "dvb-usb-vp7045-01.fw" , |
195 | .size_of_priv = 20, |
196 | |
197 | .num_adapters = 1, |
198 | .adapter = { |
199 | { |
200 | .num_frontends = 1, |
201 | .fe = {{ |
202 | .frontend_attach = vp7045_frontend_attach, |
203 | /* parameter for the MPEG2-data transfer */ |
204 | .stream = { |
205 | .type = USB_BULK, |
206 | .count = 7, |
207 | .endpoint = 0x02, |
208 | .u = { |
209 | .bulk = { |
210 | .buffersize = 4096, |
211 | } |
212 | } |
213 | }, |
214 | }}, |
215 | } |
216 | }, |
217 | .power_ctrl = vp7045_power_ctrl, |
218 | .read_mac_address = vp7045_read_mac_addr, |
219 | |
220 | .rc.core = { |
221 | .rc_interval = 400, |
222 | .rc_codes = RC_MAP_TWINHAN_VP1027_DVBS, |
223 | .module_name = KBUILD_MODNAME, |
224 | .rc_query = vp7045_rc_query, |
225 | .allowed_protos = RC_PROTO_BIT_NEC, |
226 | .scancode_mask = 0xff, |
227 | }, |
228 | |
229 | .num_device_descs = 2, |
230 | .devices = { |
231 | { .name = "Twinhan USB2.0 DVB-T receiver (TwinhanDTV Alpha/MagicBox II)" , |
232 | .cold_ids = { &vp7045_usb_table[VISIONPLUS_VP7045_COLD], NULL }, |
233 | .warm_ids = { &vp7045_usb_table[VISIONPLUS_VP7045_WARM], NULL }, |
234 | }, |
235 | { .name = "DigitalNow TinyUSB 2 DVB-t Receiver" , |
236 | .cold_ids = { &vp7045_usb_table[VISIONPLUS_TINYUSB2_COLD], NULL }, |
237 | .warm_ids = { &vp7045_usb_table[VISIONPLUS_TINYUSB2_WARM], NULL }, |
238 | }, |
239 | { NULL }, |
240 | } |
241 | }; |
242 | |
243 | /* usb specific object needed to register this driver with the usb subsystem */ |
244 | static struct usb_driver vp7045_usb_driver = { |
245 | .name = "dvb_usb_vp7045" , |
246 | .probe = vp7045_usb_probe, |
247 | .disconnect = dvb_usb_device_exit, |
248 | .id_table = vp7045_usb_table, |
249 | }; |
250 | |
251 | module_usb_driver(vp7045_usb_driver); |
252 | |
253 | MODULE_AUTHOR("Patrick Boettcher <patrick.boettcher@posteo.de>" ); |
254 | MODULE_DESCRIPTION("Driver for Twinhan MagicBox/Alpha and DNTV tinyUSB2 DVB-T USB2.0" ); |
255 | MODULE_VERSION("1.0" ); |
256 | MODULE_LICENSE("GPL" ); |
257 | |