1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Intel CE6230 DVB USB driver |
4 | * |
5 | * Copyright (C) 2009 Antti Palosaari <crope@iki.fi> |
6 | */ |
7 | |
8 | #include "ce6230.h" |
9 | |
10 | DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); |
11 | |
12 | static int ce6230_ctrl_msg(struct dvb_usb_device *d, struct usb_req *req) |
13 | { |
14 | int ret; |
15 | unsigned int pipe; |
16 | u8 request; |
17 | u8 requesttype; |
18 | u16 value; |
19 | u16 index; |
20 | u8 *buf; |
21 | |
22 | request = req->cmd; |
23 | value = req->value; |
24 | index = req->index; |
25 | |
26 | switch (req->cmd) { |
27 | case I2C_READ: |
28 | case DEMOD_READ: |
29 | case REG_READ: |
30 | requesttype = (USB_TYPE_VENDOR | USB_DIR_IN); |
31 | break; |
32 | case I2C_WRITE: |
33 | case DEMOD_WRITE: |
34 | case REG_WRITE: |
35 | requesttype = (USB_TYPE_VENDOR | USB_DIR_OUT); |
36 | break; |
37 | default: |
38 | dev_err(&d->udev->dev, "%s: unknown command=%02x\n" , |
39 | KBUILD_MODNAME, req->cmd); |
40 | ret = -EINVAL; |
41 | goto error; |
42 | } |
43 | |
44 | buf = kmalloc(size: req->data_len, GFP_KERNEL); |
45 | if (!buf) { |
46 | ret = -ENOMEM; |
47 | goto error; |
48 | } |
49 | |
50 | if (requesttype == (USB_TYPE_VENDOR | USB_DIR_OUT)) { |
51 | /* write */ |
52 | memcpy(buf, req->data, req->data_len); |
53 | pipe = usb_sndctrlpipe(d->udev, 0); |
54 | } else { |
55 | /* read */ |
56 | pipe = usb_rcvctrlpipe(d->udev, 0); |
57 | } |
58 | |
59 | msleep(msecs: 1); /* avoid I2C errors */ |
60 | |
61 | ret = usb_control_msg(dev: d->udev, pipe, request, requesttype, value, index, |
62 | data: buf, size: req->data_len, CE6230_USB_TIMEOUT); |
63 | |
64 | dvb_usb_dbg_usb_control_msg(d->udev, request, requesttype, value, index, |
65 | buf, req->data_len); |
66 | |
67 | if (ret < 0) |
68 | dev_err(&d->udev->dev, "%s: usb_control_msg() failed=%d\n" , |
69 | KBUILD_MODNAME, ret); |
70 | else |
71 | ret = 0; |
72 | |
73 | /* read request, copy returned data to return buf */ |
74 | if (!ret && requesttype == (USB_TYPE_VENDOR | USB_DIR_IN)) |
75 | memcpy(req->data, buf, req->data_len); |
76 | |
77 | kfree(objp: buf); |
78 | error: |
79 | return ret; |
80 | } |
81 | |
82 | /* I2C */ |
83 | static struct zl10353_config ce6230_zl10353_config; |
84 | |
85 | static int ce6230_i2c_master_xfer(struct i2c_adapter *adap, |
86 | struct i2c_msg msg[], int num) |
87 | { |
88 | struct dvb_usb_device *d = i2c_get_adapdata(adap); |
89 | int ret = 0, i = 0; |
90 | struct usb_req req; |
91 | |
92 | if (num > 2) |
93 | return -EOPNOTSUPP; |
94 | |
95 | memset(&req, 0, sizeof(req)); |
96 | |
97 | if (mutex_lock_interruptible(&d->i2c_mutex) < 0) |
98 | return -EAGAIN; |
99 | |
100 | while (i < num) { |
101 | if (num > i + 1 && (msg[i+1].flags & I2C_M_RD)) { |
102 | if (msg[i].addr == |
103 | ce6230_zl10353_config.demod_address) { |
104 | if (msg[i].len < 1) { |
105 | i = -EOPNOTSUPP; |
106 | break; |
107 | } |
108 | req.cmd = DEMOD_READ; |
109 | req.value = msg[i].addr >> 1; |
110 | req.index = msg[i].buf[0]; |
111 | req.data_len = msg[i+1].len; |
112 | req.data = &msg[i+1].buf[0]; |
113 | ret = ce6230_ctrl_msg(d, req: &req); |
114 | } else { |
115 | dev_err(&d->udev->dev, "%s: I2C read not " \ |
116 | "implemented\n" , |
117 | KBUILD_MODNAME); |
118 | ret = -EOPNOTSUPP; |
119 | } |
120 | i += 2; |
121 | } else { |
122 | if (msg[i].addr == |
123 | ce6230_zl10353_config.demod_address) { |
124 | if (msg[i].len < 1) { |
125 | i = -EOPNOTSUPP; |
126 | break; |
127 | } |
128 | req.cmd = DEMOD_WRITE; |
129 | req.value = msg[i].addr >> 1; |
130 | req.index = msg[i].buf[0]; |
131 | req.data_len = msg[i].len-1; |
132 | req.data = &msg[i].buf[1]; |
133 | ret = ce6230_ctrl_msg(d, req: &req); |
134 | } else { |
135 | req.cmd = I2C_WRITE; |
136 | req.value = 0x2000 + (msg[i].addr >> 1); |
137 | req.index = 0x0000; |
138 | req.data_len = msg[i].len; |
139 | req.data = &msg[i].buf[0]; |
140 | ret = ce6230_ctrl_msg(d, req: &req); |
141 | } |
142 | i += 1; |
143 | } |
144 | if (ret) |
145 | break; |
146 | } |
147 | |
148 | mutex_unlock(lock: &d->i2c_mutex); |
149 | return ret ? ret : i; |
150 | } |
151 | |
152 | static u32 ce6230_i2c_functionality(struct i2c_adapter *adapter) |
153 | { |
154 | return I2C_FUNC_I2C; |
155 | } |
156 | |
157 | static struct i2c_algorithm ce6230_i2c_algorithm = { |
158 | .master_xfer = ce6230_i2c_master_xfer, |
159 | .functionality = ce6230_i2c_functionality, |
160 | }; |
161 | |
162 | /* Callbacks for DVB USB */ |
163 | static struct zl10353_config ce6230_zl10353_config = { |
164 | .demod_address = 0x1e, |
165 | .adc_clock = 450000, |
166 | .if2 = 45700, |
167 | .no_tuner = 1, |
168 | .parallel_ts = 1, |
169 | .clock_ctl_1 = 0x34, |
170 | .pll_0 = 0x0e, |
171 | }; |
172 | |
173 | static int ce6230_zl10353_frontend_attach(struct dvb_usb_adapter *adap) |
174 | { |
175 | struct dvb_usb_device *d = adap_to_d(adap); |
176 | |
177 | dev_dbg(&d->udev->dev, "%s:\n" , __func__); |
178 | |
179 | adap->fe[0] = dvb_attach(zl10353_attach, &ce6230_zl10353_config, |
180 | &d->i2c_adap); |
181 | if (adap->fe[0] == NULL) |
182 | return -ENODEV; |
183 | |
184 | return 0; |
185 | } |
186 | |
187 | static struct mxl5005s_config ce6230_mxl5003s_config = { |
188 | .i2c_address = 0xc6, |
189 | .if_freq = IF_FREQ_4570000HZ, |
190 | .xtal_freq = CRYSTAL_FREQ_16000000HZ, |
191 | .agc_mode = MXL_SINGLE_AGC, |
192 | .tracking_filter = MXL_TF_DEFAULT, |
193 | .rssi_enable = MXL_RSSI_ENABLE, |
194 | .cap_select = MXL_CAP_SEL_ENABLE, |
195 | .div_out = MXL_DIV_OUT_4, |
196 | .clock_out = MXL_CLOCK_OUT_DISABLE, |
197 | .output_load = MXL5005S_IF_OUTPUT_LOAD_200_OHM, |
198 | .top = MXL5005S_TOP_25P2, |
199 | .mod_mode = MXL_DIGITAL_MODE, |
200 | .if_mode = MXL_ZERO_IF, |
201 | .AgcMasterByte = 0x00, |
202 | }; |
203 | |
204 | static int ce6230_mxl5003s_tuner_attach(struct dvb_usb_adapter *adap) |
205 | { |
206 | struct dvb_usb_device *d = adap_to_d(adap); |
207 | int ret; |
208 | |
209 | dev_dbg(&d->udev->dev, "%s:\n" , __func__); |
210 | |
211 | ret = dvb_attach(mxl5005s_attach, adap->fe[0], &d->i2c_adap, |
212 | &ce6230_mxl5003s_config) == NULL ? -ENODEV : 0; |
213 | return ret; |
214 | } |
215 | |
216 | static int ce6230_power_ctrl(struct dvb_usb_device *d, int onoff) |
217 | { |
218 | int ret; |
219 | |
220 | dev_dbg(&d->udev->dev, "%s: onoff=%d\n" , __func__, onoff); |
221 | |
222 | /* InterfaceNumber 1 / AlternateSetting 0 idle |
223 | InterfaceNumber 1 / AlternateSetting 1 streaming */ |
224 | ret = usb_set_interface(dev: d->udev, ifnum: 1, alternate: onoff); |
225 | if (ret) |
226 | dev_err(&d->udev->dev, "%s: usb_set_interface() failed=%d\n" , |
227 | KBUILD_MODNAME, ret); |
228 | |
229 | return ret; |
230 | } |
231 | |
232 | /* DVB USB Driver stuff */ |
233 | static struct dvb_usb_device_properties ce6230_props = { |
234 | .driver_name = KBUILD_MODNAME, |
235 | .owner = THIS_MODULE, |
236 | .adapter_nr = adapter_nr, |
237 | .bInterfaceNumber = 1, |
238 | |
239 | .i2c_algo = &ce6230_i2c_algorithm, |
240 | .power_ctrl = ce6230_power_ctrl, |
241 | .frontend_attach = ce6230_zl10353_frontend_attach, |
242 | .tuner_attach = ce6230_mxl5003s_tuner_attach, |
243 | |
244 | .num_adapters = 1, |
245 | .adapter = { |
246 | { |
247 | .stream = { |
248 | .type = USB_BULK, |
249 | .count = 6, |
250 | .endpoint = 0x82, |
251 | .u = { |
252 | .bulk = { |
253 | .buffersize = (16 * 512), |
254 | } |
255 | } |
256 | }, |
257 | } |
258 | }, |
259 | }; |
260 | |
261 | static const struct usb_device_id ce6230_id_table[] = { |
262 | { DVB_USB_DEVICE(USB_VID_INTEL, USB_PID_INTEL_CE9500, |
263 | &ce6230_props, "Intel CE9500 reference design" , NULL) }, |
264 | { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A310, |
265 | &ce6230_props, "AVerMedia A310 USB 2.0 DVB-T tuner" , NULL) }, |
266 | { } |
267 | }; |
268 | MODULE_DEVICE_TABLE(usb, ce6230_id_table); |
269 | |
270 | static struct usb_driver ce6230_usb_driver = { |
271 | .name = KBUILD_MODNAME, |
272 | .id_table = ce6230_id_table, |
273 | .probe = dvb_usbv2_probe, |
274 | .disconnect = dvb_usbv2_disconnect, |
275 | .suspend = dvb_usbv2_suspend, |
276 | .resume = dvb_usbv2_resume, |
277 | .reset_resume = dvb_usbv2_reset_resume, |
278 | .no_dynamic_id = 1, |
279 | .soft_unbind = 1, |
280 | }; |
281 | |
282 | module_usb_driver(ce6230_usb_driver); |
283 | |
284 | MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>" ); |
285 | MODULE_DESCRIPTION("Intel CE6230 driver" ); |
286 | MODULE_LICENSE("GPL" ); |
287 | |