1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * DVB USB Linux driver for Alcor Micro AU6610 DVB-T USB2.0. |
4 | * |
5 | * Copyright (C) 2006 Antti Palosaari <crope@iki.fi> |
6 | */ |
7 | |
8 | #include "au6610.h" |
9 | #include "zl10353.h" |
10 | #include "qt1010.h" |
11 | |
12 | DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); |
13 | |
14 | static int au6610_usb_msg(struct dvb_usb_device *d, u8 operation, u8 addr, |
15 | u8 *wbuf, u16 wlen, u8 *rbuf, u16 rlen) |
16 | { |
17 | int ret; |
18 | u16 index; |
19 | u8 *usb_buf; |
20 | |
21 | /* |
22 | * allocate enough for all known requests, |
23 | * read returns 5 and write 6 bytes |
24 | */ |
25 | usb_buf = kmalloc(size: 6, GFP_KERNEL); |
26 | if (!usb_buf) |
27 | return -ENOMEM; |
28 | |
29 | switch (wlen) { |
30 | case 1: |
31 | index = wbuf[0] << 8; |
32 | break; |
33 | case 2: |
34 | index = wbuf[0] << 8; |
35 | index += wbuf[1]; |
36 | break; |
37 | default: |
38 | dev_err(&d->udev->dev, "%s: wlen=%d, aborting\n" , |
39 | KBUILD_MODNAME, wlen); |
40 | ret = -EINVAL; |
41 | goto error; |
42 | } |
43 | |
44 | ret = usb_control_msg(dev: d->udev, usb_rcvctrlpipe(d->udev, 0), request: operation, |
45 | USB_TYPE_VENDOR|USB_DIR_IN, value: addr << 1, index, |
46 | data: usb_buf, size: 6, AU6610_USB_TIMEOUT); |
47 | |
48 | dvb_usb_dbg_usb_control_msg(d->udev, operation, |
49 | (USB_TYPE_VENDOR|USB_DIR_IN), addr << 1, index, |
50 | usb_buf, 6); |
51 | |
52 | if (ret < 0) |
53 | goto error; |
54 | |
55 | switch (operation) { |
56 | case AU6610_REQ_I2C_READ: |
57 | case AU6610_REQ_USB_READ: |
58 | /* requested value is always 5th byte in buffer */ |
59 | rbuf[0] = usb_buf[4]; |
60 | } |
61 | error: |
62 | kfree(objp: usb_buf); |
63 | return ret; |
64 | } |
65 | |
66 | static int au6610_i2c_msg(struct dvb_usb_device *d, u8 addr, |
67 | u8 *wbuf, u16 wlen, u8 *rbuf, u16 rlen) |
68 | { |
69 | u8 request; |
70 | u8 wo = (rbuf == NULL || rlen == 0); /* write-only */ |
71 | |
72 | if (wo) { |
73 | request = AU6610_REQ_I2C_WRITE; |
74 | } else { /* rw */ |
75 | request = AU6610_REQ_I2C_READ; |
76 | } |
77 | |
78 | return au6610_usb_msg(d, operation: request, addr, wbuf, wlen, rbuf, rlen); |
79 | } |
80 | |
81 | |
82 | /* I2C */ |
83 | static int au6610_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], |
84 | int num) |
85 | { |
86 | struct dvb_usb_device *d = i2c_get_adapdata(adap); |
87 | int i; |
88 | |
89 | if (num > 2) |
90 | return -EINVAL; |
91 | |
92 | if (mutex_lock_interruptible(&d->i2c_mutex) < 0) |
93 | return -EAGAIN; |
94 | |
95 | for (i = 0; i < num; i++) { |
96 | /* write/read request */ |
97 | if (i+1 < num && (msg[i+1].flags & I2C_M_RD)) { |
98 | if (au6610_i2c_msg(d, addr: msg[i].addr, wbuf: msg[i].buf, |
99 | wlen: msg[i].len, rbuf: msg[i+1].buf, |
100 | rlen: msg[i+1].len) < 0) |
101 | break; |
102 | i++; |
103 | } else if (au6610_i2c_msg(d, addr: msg[i].addr, wbuf: msg[i].buf, |
104 | wlen: msg[i].len, NULL, rlen: 0) < 0) |
105 | break; |
106 | } |
107 | |
108 | mutex_unlock(lock: &d->i2c_mutex); |
109 | return i; |
110 | } |
111 | |
112 | |
113 | static u32 au6610_i2c_func(struct i2c_adapter *adapter) |
114 | { |
115 | return I2C_FUNC_I2C; |
116 | } |
117 | |
118 | static struct i2c_algorithm au6610_i2c_algo = { |
119 | .master_xfer = au6610_i2c_xfer, |
120 | .functionality = au6610_i2c_func, |
121 | }; |
122 | |
123 | /* Callbacks for DVB USB */ |
124 | static struct zl10353_config au6610_zl10353_config = { |
125 | .demod_address = 0x0f, |
126 | .no_tuner = 1, |
127 | .parallel_ts = 1, |
128 | }; |
129 | |
130 | static int au6610_zl10353_frontend_attach(struct dvb_usb_adapter *adap) |
131 | { |
132 | adap->fe[0] = dvb_attach(zl10353_attach, &au6610_zl10353_config, |
133 | &adap_to_d(adap)->i2c_adap); |
134 | if (adap->fe[0] == NULL) |
135 | return -ENODEV; |
136 | |
137 | return 0; |
138 | } |
139 | |
140 | static struct qt1010_config au6610_qt1010_config = { |
141 | .i2c_address = 0x62 |
142 | }; |
143 | |
144 | static int au6610_qt1010_tuner_attach(struct dvb_usb_adapter *adap) |
145 | { |
146 | return dvb_attach(qt1010_attach, adap->fe[0], |
147 | &adap_to_d(adap)->i2c_adap, |
148 | &au6610_qt1010_config) == NULL ? -ENODEV : 0; |
149 | } |
150 | |
151 | static int au6610_init(struct dvb_usb_device *d) |
152 | { |
153 | /* TODO: this functionality belongs likely to the streaming control */ |
154 | /* bInterfaceNumber 0, bAlternateSetting 5 */ |
155 | return usb_set_interface(dev: d->udev, ifnum: 0, alternate: 5); |
156 | } |
157 | |
158 | static struct dvb_usb_device_properties au6610_props = { |
159 | .driver_name = KBUILD_MODNAME, |
160 | .owner = THIS_MODULE, |
161 | .adapter_nr = adapter_nr, |
162 | |
163 | .i2c_algo = &au6610_i2c_algo, |
164 | .frontend_attach = au6610_zl10353_frontend_attach, |
165 | .tuner_attach = au6610_qt1010_tuner_attach, |
166 | .init = au6610_init, |
167 | |
168 | .num_adapters = 1, |
169 | .adapter = { |
170 | { |
171 | .stream = DVB_USB_STREAM_ISOC(0x82, 5, 40, 942, 1), |
172 | }, |
173 | }, |
174 | }; |
175 | |
176 | static const struct usb_device_id au6610_id_table[] = { |
177 | { DVB_USB_DEVICE(USB_VID_ALCOR_MICRO, USB_PID_SIGMATEK_DVB_110, |
178 | &au6610_props, "Sigmatek DVB-110" , NULL) }, |
179 | { } |
180 | }; |
181 | MODULE_DEVICE_TABLE(usb, au6610_id_table); |
182 | |
183 | static struct usb_driver au6610_driver = { |
184 | .name = KBUILD_MODNAME, |
185 | .id_table = au6610_id_table, |
186 | .probe = dvb_usbv2_probe, |
187 | .disconnect = dvb_usbv2_disconnect, |
188 | .suspend = dvb_usbv2_suspend, |
189 | .resume = dvb_usbv2_resume, |
190 | .reset_resume = dvb_usbv2_reset_resume, |
191 | .no_dynamic_id = 1, |
192 | .soft_unbind = 1, |
193 | }; |
194 | |
195 | module_usb_driver(au6610_driver); |
196 | |
197 | MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>" ); |
198 | MODULE_DESCRIPTION("Driver for Alcor Micro AU6610 DVB-T USB2.0" ); |
199 | MODULE_VERSION("0.1" ); |
200 | MODULE_LICENSE("GPL" ); |
201 | |