1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * CIMaX SP2/SP2HF (Atmel T90FJR) CI driver |
4 | * |
5 | * Copyright (C) 2014 Olli Salonen <olli.salonen@iki.fi> |
6 | * |
7 | * Heavily based on CIMax2(R) SP2 driver in conjunction with NetUp Dual |
8 | * DVB-S2 CI card (cimax2) with following copyrights: |
9 | * |
10 | * Copyright (C) 2009 NetUP Inc. |
11 | * Copyright (C) 2009 Igor M. Liplianin <liplianin@netup.ru> |
12 | * Copyright (C) 2009 Abylay Ospan <aospan@netup.ru> |
13 | */ |
14 | |
15 | #include "sp2_priv.h" |
16 | |
17 | static int sp2_read_i2c(struct sp2 *s, u8 reg, u8 *buf, int len) |
18 | { |
19 | int ret; |
20 | struct i2c_client *client = s->client; |
21 | struct i2c_adapter *adap = client->adapter; |
22 | struct i2c_msg msg[] = { |
23 | { |
24 | .addr = client->addr, |
25 | .flags = 0, |
26 | .buf = ®, |
27 | .len = 1 |
28 | }, { |
29 | .addr = client->addr, |
30 | .flags = I2C_M_RD, |
31 | .buf = buf, |
32 | .len = len |
33 | } |
34 | }; |
35 | |
36 | ret = i2c_transfer(adap, msgs: msg, num: 2); |
37 | |
38 | if (ret != 2) { |
39 | dev_err(&client->dev, "i2c read error, reg = 0x%02x, status = %d\n" , |
40 | reg, ret); |
41 | if (ret < 0) |
42 | return ret; |
43 | else |
44 | return -EIO; |
45 | } |
46 | |
47 | dev_dbg(&s->client->dev, "addr=0x%04x, reg = 0x%02x, data = %02x\n" , |
48 | client->addr, reg, buf[0]); |
49 | |
50 | return 0; |
51 | } |
52 | |
53 | static int sp2_write_i2c(struct sp2 *s, u8 reg, u8 *buf, int len) |
54 | { |
55 | int ret; |
56 | u8 buffer[35]; |
57 | struct i2c_client *client = s->client; |
58 | struct i2c_adapter *adap = client->adapter; |
59 | struct i2c_msg msg = { |
60 | .addr = client->addr, |
61 | .flags = 0, |
62 | .buf = &buffer[0], |
63 | .len = len + 1 |
64 | }; |
65 | |
66 | if ((len + 1) > sizeof(buffer)) { |
67 | dev_err(&client->dev, "i2c wr reg=%02x: len=%d is too big!\n" , |
68 | reg, len); |
69 | return -EINVAL; |
70 | } |
71 | |
72 | buffer[0] = reg; |
73 | memcpy(&buffer[1], buf, len); |
74 | |
75 | ret = i2c_transfer(adap, msgs: &msg, num: 1); |
76 | |
77 | if (ret != 1) { |
78 | dev_err(&client->dev, "i2c write error, reg = 0x%02x, status = %d\n" , |
79 | reg, ret); |
80 | if (ret < 0) |
81 | return ret; |
82 | else |
83 | return -EIO; |
84 | } |
85 | |
86 | dev_dbg(&s->client->dev, "addr=0x%04x, reg = 0x%02x, data = %*ph\n" , |
87 | client->addr, reg, len, buf); |
88 | |
89 | return 0; |
90 | } |
91 | |
92 | static int sp2_ci_op_cam(struct dvb_ca_en50221 *en50221, int slot, u8 acs, |
93 | u8 read, int addr, u8 data) |
94 | { |
95 | struct sp2 *s = en50221->data; |
96 | u8 store; |
97 | int mem, ret; |
98 | int (*ci_op_cam)(void*, u8, int, u8, int*) = s->ci_control; |
99 | |
100 | if (slot != 0) |
101 | return -EINVAL; |
102 | |
103 | /* |
104 | * change module access type between IO space and attribute memory |
105 | * when needed |
106 | */ |
107 | if (s->module_access_type != acs) { |
108 | ret = sp2_read_i2c(s, reg: 0x00, buf: &store, len: 1); |
109 | |
110 | if (ret) |
111 | return ret; |
112 | |
113 | store &= ~(SP2_MOD_CTL_ACS1 | SP2_MOD_CTL_ACS0); |
114 | store |= acs; |
115 | |
116 | ret = sp2_write_i2c(s, reg: 0x00, buf: &store, len: 1); |
117 | if (ret) |
118 | return ret; |
119 | } |
120 | |
121 | s->module_access_type = acs; |
122 | |
123 | /* implementation of ci_op_cam is device specific */ |
124 | if (ci_op_cam) { |
125 | ret = ci_op_cam(s->priv, read, addr, data, &mem); |
126 | } else { |
127 | dev_err(&s->client->dev, "callback not defined" ); |
128 | return -EINVAL; |
129 | } |
130 | |
131 | if (ret) |
132 | return ret; |
133 | |
134 | dev_dbg(&s->client->dev, "%s: slot=%d, addr=0x%04x, %s, data=%x" , |
135 | (read) ? "read" : "write" , slot, addr, |
136 | (acs == SP2_CI_ATTR_ACS) ? "attr" : "io" , |
137 | (read) ? mem : data); |
138 | |
139 | if (read) |
140 | return mem; |
141 | else |
142 | return 0; |
143 | |
144 | } |
145 | |
146 | int sp2_ci_read_attribute_mem(struct dvb_ca_en50221 *en50221, |
147 | int slot, int addr) |
148 | { |
149 | return sp2_ci_op_cam(en50221, slot, SP2_CI_ATTR_ACS, |
150 | SP2_CI_RD, addr, data: 0); |
151 | } |
152 | |
153 | int sp2_ci_write_attribute_mem(struct dvb_ca_en50221 *en50221, |
154 | int slot, int addr, u8 data) |
155 | { |
156 | return sp2_ci_op_cam(en50221, slot, SP2_CI_ATTR_ACS, |
157 | SP2_CI_WR, addr, data); |
158 | } |
159 | |
160 | int sp2_ci_read_cam_control(struct dvb_ca_en50221 *en50221, |
161 | int slot, u8 addr) |
162 | { |
163 | return sp2_ci_op_cam(en50221, slot, SP2_CI_IO_ACS, |
164 | SP2_CI_RD, addr, data: 0); |
165 | } |
166 | |
167 | int sp2_ci_write_cam_control(struct dvb_ca_en50221 *en50221, |
168 | int slot, u8 addr, u8 data) |
169 | { |
170 | return sp2_ci_op_cam(en50221, slot, SP2_CI_IO_ACS, |
171 | SP2_CI_WR, addr, data); |
172 | } |
173 | |
174 | int sp2_ci_slot_reset(struct dvb_ca_en50221 *en50221, int slot) |
175 | { |
176 | struct sp2 *s = en50221->data; |
177 | u8 buf; |
178 | int ret; |
179 | |
180 | dev_dbg(&s->client->dev, "slot: %d\n" , slot); |
181 | |
182 | if (slot != 0) |
183 | return -EINVAL; |
184 | |
185 | /* RST on */ |
186 | buf = SP2_MOD_CTL_RST; |
187 | ret = sp2_write_i2c(s, reg: 0x00, buf: &buf, len: 1); |
188 | |
189 | if (ret) |
190 | return ret; |
191 | |
192 | usleep_range(min: 500, max: 600); |
193 | |
194 | /* RST off */ |
195 | buf = 0x00; |
196 | ret = sp2_write_i2c(s, reg: 0x00, buf: &buf, len: 1); |
197 | |
198 | if (ret) |
199 | return ret; |
200 | |
201 | msleep(msecs: 1000); |
202 | |
203 | return 0; |
204 | } |
205 | |
206 | int sp2_ci_slot_shutdown(struct dvb_ca_en50221 *en50221, int slot) |
207 | { |
208 | struct sp2 *s = en50221->data; |
209 | |
210 | dev_dbg(&s->client->dev, "slot:%d\n" , slot); |
211 | |
212 | /* not implemented */ |
213 | return 0; |
214 | } |
215 | |
216 | int sp2_ci_slot_ts_enable(struct dvb_ca_en50221 *en50221, int slot) |
217 | { |
218 | struct sp2 *s = en50221->data; |
219 | u8 buf; |
220 | |
221 | dev_dbg(&s->client->dev, "slot:%d\n" , slot); |
222 | |
223 | if (slot != 0) |
224 | return -EINVAL; |
225 | |
226 | sp2_read_i2c(s, reg: 0x00, buf: &buf, len: 1); |
227 | |
228 | /* disable bypass and enable TS */ |
229 | buf |= (SP2_MOD_CTL_TSOEN | SP2_MOD_CTL_TSIEN); |
230 | return sp2_write_i2c(s, reg: 0, buf: &buf, len: 1); |
231 | } |
232 | |
233 | int sp2_ci_poll_slot_status(struct dvb_ca_en50221 *en50221, |
234 | int slot, int open) |
235 | { |
236 | struct sp2 *s = en50221->data; |
237 | u8 buf[2]; |
238 | int ret; |
239 | |
240 | dev_dbg(&s->client->dev, "slot:%d open:%d\n" , slot, open); |
241 | |
242 | /* |
243 | * CAM module INSERT/REMOVE processing. Slow operation because of i2c |
244 | * transfers. Throttle read to one per sec. |
245 | */ |
246 | if (time_after(jiffies, s->next_status_checked_time)) { |
247 | ret = sp2_read_i2c(s, reg: 0x00, buf, len: 1); |
248 | s->next_status_checked_time = jiffies + msecs_to_jiffies(m: 1000); |
249 | |
250 | if (ret) |
251 | return 0; |
252 | |
253 | if (buf[0] & SP2_MOD_CTL_DET) |
254 | s->status = DVB_CA_EN50221_POLL_CAM_PRESENT | |
255 | DVB_CA_EN50221_POLL_CAM_READY; |
256 | else |
257 | s->status = 0; |
258 | } |
259 | |
260 | return s->status; |
261 | } |
262 | |
263 | static int sp2_init(struct sp2 *s) |
264 | { |
265 | int ret = 0; |
266 | u8 buf; |
267 | u8 cimax_init[34] = { |
268 | 0x00, /* module A control*/ |
269 | 0x00, /* auto select mask high A */ |
270 | 0x00, /* auto select mask low A */ |
271 | 0x00, /* auto select pattern high A */ |
272 | 0x00, /* auto select pattern low A */ |
273 | 0x44, /* memory access time A, 600 ns */ |
274 | 0x00, /* invert input A */ |
275 | 0x00, /* RFU */ |
276 | 0x00, /* RFU */ |
277 | 0x00, /* module B control*/ |
278 | 0x00, /* auto select mask high B */ |
279 | 0x00, /* auto select mask low B */ |
280 | 0x00, /* auto select pattern high B */ |
281 | 0x00, /* auto select pattern low B */ |
282 | 0x44, /* memory access time B, 600 ns */ |
283 | 0x00, /* invert input B */ |
284 | 0x00, /* RFU */ |
285 | 0x00, /* RFU */ |
286 | 0x00, /* auto select mask high Ext */ |
287 | 0x00, /* auto select mask low Ext */ |
288 | 0x00, /* auto select pattern high Ext */ |
289 | 0x00, /* auto select pattern low Ext */ |
290 | 0x00, /* RFU */ |
291 | 0x02, /* destination - module A */ |
292 | 0x01, /* power control reg, VCC power on */ |
293 | 0x00, /* RFU */ |
294 | 0x00, /* int status read only */ |
295 | 0x00, /* Interrupt Mask Register */ |
296 | 0x05, /* EXTINT=active-high, INT=push-pull */ |
297 | 0x00, /* USCG1 */ |
298 | 0x04, /* ack active low */ |
299 | 0x00, /* LOCK = 0 */ |
300 | 0x22, /* unknown */ |
301 | 0x00, /* synchronization? */ |
302 | }; |
303 | |
304 | dev_dbg(&s->client->dev, "\n" ); |
305 | |
306 | s->ca.owner = THIS_MODULE; |
307 | s->ca.read_attribute_mem = sp2_ci_read_attribute_mem; |
308 | s->ca.write_attribute_mem = sp2_ci_write_attribute_mem; |
309 | s->ca.read_cam_control = sp2_ci_read_cam_control; |
310 | s->ca.write_cam_control = sp2_ci_write_cam_control; |
311 | s->ca.slot_reset = sp2_ci_slot_reset; |
312 | s->ca.slot_shutdown = sp2_ci_slot_shutdown; |
313 | s->ca.slot_ts_enable = sp2_ci_slot_ts_enable; |
314 | s->ca.poll_slot_status = sp2_ci_poll_slot_status; |
315 | s->ca.data = s; |
316 | s->module_access_type = 0; |
317 | |
318 | /* initialize all regs */ |
319 | ret = sp2_write_i2c(s, reg: 0x00, buf: &cimax_init[0], len: 34); |
320 | if (ret) |
321 | goto err; |
322 | |
323 | /* lock registers */ |
324 | buf = 1; |
325 | ret = sp2_write_i2c(s, reg: 0x1f, buf: &buf, len: 1); |
326 | if (ret) |
327 | goto err; |
328 | |
329 | /* power on slots */ |
330 | ret = sp2_write_i2c(s, reg: 0x18, buf: &buf, len: 1); |
331 | if (ret) |
332 | goto err; |
333 | |
334 | ret = dvb_ca_en50221_init(dvb_adapter: s->dvb_adap, ca: &s->ca, flags: 0, slot_count: 1); |
335 | if (ret) |
336 | goto err; |
337 | |
338 | return 0; |
339 | |
340 | err: |
341 | dev_dbg(&s->client->dev, "init failed=%d\n" , ret); |
342 | return ret; |
343 | } |
344 | |
345 | static int sp2_exit(struct i2c_client *client) |
346 | { |
347 | struct sp2 *s; |
348 | |
349 | dev_dbg(&client->dev, "\n" ); |
350 | |
351 | if (!client) |
352 | return 0; |
353 | |
354 | s = i2c_get_clientdata(client); |
355 | if (!s) |
356 | return 0; |
357 | |
358 | if (!s->ca.data) |
359 | return 0; |
360 | |
361 | dvb_ca_en50221_release(ca: &s->ca); |
362 | |
363 | return 0; |
364 | } |
365 | |
366 | static int sp2_probe(struct i2c_client *client) |
367 | { |
368 | struct sp2_config *cfg = client->dev.platform_data; |
369 | struct sp2 *s; |
370 | int ret; |
371 | |
372 | dev_dbg(&client->dev, "\n" ); |
373 | |
374 | s = kzalloc(size: sizeof(*s), GFP_KERNEL); |
375 | if (!s) { |
376 | ret = -ENOMEM; |
377 | goto err; |
378 | } |
379 | |
380 | s->client = client; |
381 | s->dvb_adap = cfg->dvb_adap; |
382 | s->priv = cfg->priv; |
383 | s->ci_control = cfg->ci_control; |
384 | |
385 | i2c_set_clientdata(client, data: s); |
386 | |
387 | ret = sp2_init(s); |
388 | if (ret) |
389 | goto err; |
390 | |
391 | dev_info(&s->client->dev, "CIMaX SP2 successfully attached\n" ); |
392 | return 0; |
393 | err: |
394 | dev_dbg(&client->dev, "init failed=%d\n" , ret); |
395 | kfree(objp: s); |
396 | |
397 | return ret; |
398 | } |
399 | |
400 | static void sp2_remove(struct i2c_client *client) |
401 | { |
402 | struct sp2 *s = i2c_get_clientdata(client); |
403 | |
404 | dev_dbg(&client->dev, "\n" ); |
405 | sp2_exit(client); |
406 | kfree(objp: s); |
407 | } |
408 | |
409 | static const struct i2c_device_id sp2_id[] = { |
410 | {"sp2" , 0}, |
411 | {} |
412 | }; |
413 | MODULE_DEVICE_TABLE(i2c, sp2_id); |
414 | |
415 | static struct i2c_driver sp2_driver = { |
416 | .driver = { |
417 | .name = "sp2" , |
418 | }, |
419 | .probe = sp2_probe, |
420 | .remove = sp2_remove, |
421 | .id_table = sp2_id, |
422 | }; |
423 | |
424 | module_i2c_driver(sp2_driver); |
425 | |
426 | MODULE_DESCRIPTION("CIMaX SP2/HF CI driver" ); |
427 | MODULE_AUTHOR("Olli Salonen <olli.salonen@iki.fi>" ); |
428 | MODULE_LICENSE("GPL" ); |
429 | |