1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3    Conexant cx22700 DVB OFDM demodulator driver
4
5    Copyright (C) 2001-2002 Convergence Integrated Media GmbH
6	Holger Waechtler <holger@convergence.de>
7
8
9*/
10
11#include <linux/kernel.h>
12#include <linux/init.h>
13#include <linux/module.h>
14#include <linux/string.h>
15#include <linux/slab.h>
16#include <media/dvb_frontend.h>
17#include "cx22700.h"
18
19
20struct cx22700_state {
21
22	struct i2c_adapter* i2c;
23
24	const struct cx22700_config* config;
25
26	struct dvb_frontend frontend;
27};
28
29
30static int debug;
31#define dprintk(args...) \
32	do { \
33		if (debug) printk(KERN_DEBUG "cx22700: " args); \
34	} while (0)
35
36static u8 init_tab [] = {
37	0x04, 0x10,
38	0x05, 0x09,
39	0x06, 0x00,
40	0x08, 0x04,
41	0x09, 0x00,
42	0x0a, 0x01,
43	0x15, 0x40,
44	0x16, 0x10,
45	0x17, 0x87,
46	0x18, 0x17,
47	0x1a, 0x10,
48	0x25, 0x04,
49	0x2e, 0x00,
50	0x39, 0x00,
51	0x3a, 0x04,
52	0x45, 0x08,
53	0x46, 0x02,
54	0x47, 0x05,
55};
56
57
58static int cx22700_writereg (struct cx22700_state* state, u8 reg, u8 data)
59{
60	int ret;
61	u8 buf [] = { reg, data };
62	struct i2c_msg msg = { .addr = state->config->demod_address, .flags = 0, .buf = buf, .len = 2 };
63
64	dprintk ("%s\n", __func__);
65
66	ret = i2c_transfer (state->i2c, &msg, 1);
67
68	if (ret != 1)
69		printk("%s: writereg error (reg == 0x%02x, val == 0x%02x, ret == %i)\n",
70			__func__, reg, data, ret);
71
72	return (ret != 1) ? -1 : 0;
73}
74
75static int cx22700_readreg (struct cx22700_state* state, u8 reg)
76{
77	int ret;
78	u8 b0 [] = { reg };
79	u8 b1 [] = { 0 };
80	struct i2c_msg msg [] = { { .addr = state->config->demod_address, .flags = 0, .buf = b0, .len = 1 },
81			   { .addr = state->config->demod_address, .flags = I2C_M_RD, .buf = b1, .len = 1 } };
82
83	dprintk ("%s\n", __func__);
84
85	ret = i2c_transfer (state->i2c, msg, 2);
86
87	if (ret != 2) return -EIO;
88
89	return b1[0];
90}
91
92static int cx22700_set_inversion (struct cx22700_state* state, int inversion)
93{
94	u8 val;
95
96	dprintk ("%s\n", __func__);
97
98	switch (inversion) {
99	case INVERSION_AUTO:
100		return -EOPNOTSUPP;
101	case INVERSION_ON:
102		val = cx22700_readreg (state, 0x09);
103		return cx22700_writereg (state, 0x09, val | 0x01);
104	case INVERSION_OFF:
105		val = cx22700_readreg (state, 0x09);
106		return cx22700_writereg (state, 0x09, val & 0xfe);
107	default:
108		return -EINVAL;
109	}
110}
111
112static int cx22700_set_tps(struct cx22700_state *state,
113			   struct dtv_frontend_properties *p)
114{
115	static const u8 qam_tab [4] = { 0, 1, 0, 2 };
116	static const u8 fec_tab [6] = { 0, 1, 2, 0, 3, 4 };
117	u8 val;
118
119	dprintk ("%s\n", __func__);
120
121	if (p->code_rate_HP < FEC_1_2 || p->code_rate_HP > FEC_7_8)
122		return -EINVAL;
123
124	if (p->code_rate_LP < FEC_1_2 || p->code_rate_LP > FEC_7_8)
125		return -EINVAL;
126
127	if (p->code_rate_HP == FEC_4_5 || p->code_rate_LP == FEC_4_5)
128		return -EINVAL;
129
130	if ((int)p->guard_interval < GUARD_INTERVAL_1_32 ||
131	    p->guard_interval > GUARD_INTERVAL_1_4)
132		return -EINVAL;
133
134	if (p->transmission_mode != TRANSMISSION_MODE_2K &&
135	    p->transmission_mode != TRANSMISSION_MODE_8K)
136		return -EINVAL;
137
138	if (p->modulation != QPSK &&
139	    p->modulation != QAM_16 &&
140	    p->modulation != QAM_64)
141		return -EINVAL;
142
143	if ((int)p->hierarchy < HIERARCHY_NONE ||
144	    p->hierarchy > HIERARCHY_4)
145		return -EINVAL;
146
147	if (p->bandwidth_hz > 8000000 || p->bandwidth_hz < 6000000)
148		return -EINVAL;
149
150	if (p->bandwidth_hz == 7000000)
151		cx22700_writereg (state, 0x09, cx22700_readreg (state, 0x09 | 0x10));
152	else
153		cx22700_writereg (state, 0x09, cx22700_readreg (state, 0x09 & ~0x10));
154
155	val = qam_tab[p->modulation - QPSK];
156	val |= p->hierarchy - HIERARCHY_NONE;
157
158	cx22700_writereg (state, 0x04, val);
159
160	if (p->code_rate_HP - FEC_1_2 >= sizeof(fec_tab) ||
161	    p->code_rate_LP - FEC_1_2 >= sizeof(fec_tab))
162		return -EINVAL;
163	val = fec_tab[p->code_rate_HP - FEC_1_2] << 3;
164	val |= fec_tab[p->code_rate_LP - FEC_1_2];
165
166	cx22700_writereg (state, 0x05, val);
167
168	val = (p->guard_interval - GUARD_INTERVAL_1_32) << 2;
169	val |= p->transmission_mode - TRANSMISSION_MODE_2K;
170
171	cx22700_writereg (state, 0x06, val);
172
173	cx22700_writereg (state, 0x08, 0x04 | 0x02);  /* use user tps parameters */
174	cx22700_writereg (state, 0x08, 0x04);         /* restart acquisition */
175
176	return 0;
177}
178
179static int cx22700_get_tps(struct cx22700_state *state,
180			   struct dtv_frontend_properties *p)
181{
182	static const enum fe_modulation qam_tab[3] = { QPSK, QAM_16, QAM_64 };
183	static const enum fe_code_rate fec_tab[5] = {
184		FEC_1_2, FEC_2_3, FEC_3_4, FEC_5_6, FEC_7_8
185	};
186	u8 val;
187
188	dprintk ("%s\n", __func__);
189
190	if (!(cx22700_readreg(state, 0x07) & 0x20))  /*  tps valid? */
191		return -EAGAIN;
192
193	val = cx22700_readreg (state, 0x01);
194
195	if ((val & 0x7) > 4)
196		p->hierarchy = HIERARCHY_AUTO;
197	else
198		p->hierarchy = HIERARCHY_NONE + (val & 0x7);
199
200	if (((val >> 3) & 0x3) > 2)
201		p->modulation = QAM_AUTO;
202	else
203		p->modulation = qam_tab[(val >> 3) & 0x3];
204
205	val = cx22700_readreg (state, 0x02);
206
207	if (((val >> 3) & 0x07) > 4)
208		p->code_rate_HP = FEC_AUTO;
209	else
210		p->code_rate_HP = fec_tab[(val >> 3) & 0x07];
211
212	if ((val & 0x07) > 4)
213		p->code_rate_LP = FEC_AUTO;
214	else
215		p->code_rate_LP = fec_tab[val & 0x07];
216
217	val = cx22700_readreg (state, 0x03);
218
219	p->guard_interval = GUARD_INTERVAL_1_32 + ((val >> 6) & 0x3);
220	p->transmission_mode = TRANSMISSION_MODE_2K + ((val >> 5) & 0x1);
221
222	return 0;
223}
224
225static int cx22700_init (struct dvb_frontend* fe)
226
227{	struct cx22700_state* state = fe->demodulator_priv;
228	int i;
229
230	dprintk("cx22700_init: init chip\n");
231
232	cx22700_writereg (state, 0x00, 0x02);   /*  soft reset */
233	cx22700_writereg (state, 0x00, 0x00);
234
235	msleep(10);
236
237	for (i=0; i<sizeof(init_tab); i+=2)
238		cx22700_writereg (state, init_tab[i], init_tab[i+1]);
239
240	cx22700_writereg (state, 0x00, 0x01);
241
242	return 0;
243}
244
245static int cx22700_read_status(struct dvb_frontend *fe, enum fe_status *status)
246{
247	struct cx22700_state* state = fe->demodulator_priv;
248
249	u16 rs_ber = (cx22700_readreg (state, 0x0d) << 9)
250		   | (cx22700_readreg (state, 0x0e) << 1);
251	u8 sync = cx22700_readreg (state, 0x07);
252
253	*status = 0;
254
255	if (rs_ber < 0xff00)
256		*status |= FE_HAS_SIGNAL;
257
258	if (sync & 0x20)
259		*status |= FE_HAS_CARRIER;
260
261	if (sync & 0x10)
262		*status |= FE_HAS_VITERBI;
263
264	if (sync & 0x10)
265		*status |= FE_HAS_SYNC;
266
267	if (*status == 0x0f)
268		*status |= FE_HAS_LOCK;
269
270	return 0;
271}
272
273static int cx22700_read_ber(struct dvb_frontend* fe, u32* ber)
274{
275	struct cx22700_state* state = fe->demodulator_priv;
276
277	*ber = cx22700_readreg (state, 0x0c) & 0x7f;
278	cx22700_writereg (state, 0x0c, 0x00);
279
280	return 0;
281}
282
283static int cx22700_read_signal_strength(struct dvb_frontend* fe, u16* signal_strength)
284{
285	struct cx22700_state* state = fe->demodulator_priv;
286
287	u16 rs_ber = (cx22700_readreg (state, 0x0d) << 9)
288		   | (cx22700_readreg (state, 0x0e) << 1);
289	*signal_strength = ~rs_ber;
290
291	return 0;
292}
293
294static int cx22700_read_snr(struct dvb_frontend* fe, u16* snr)
295{
296	struct cx22700_state* state = fe->demodulator_priv;
297
298	u16 rs_ber = (cx22700_readreg (state, 0x0d) << 9)
299		   | (cx22700_readreg (state, 0x0e) << 1);
300	*snr = ~rs_ber;
301
302	return 0;
303}
304
305static int cx22700_read_ucblocks(struct dvb_frontend* fe, u32* ucblocks)
306{
307	struct cx22700_state* state = fe->demodulator_priv;
308
309	*ucblocks = cx22700_readreg (state, 0x0f);
310	cx22700_writereg (state, 0x0f, 0x00);
311
312	return 0;
313}
314
315static int cx22700_set_frontend(struct dvb_frontend *fe)
316{
317	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
318	struct cx22700_state* state = fe->demodulator_priv;
319
320	cx22700_writereg (state, 0x00, 0x02); /* XXX CHECKME: soft reset*/
321	cx22700_writereg (state, 0x00, 0x00);
322
323	if (fe->ops.tuner_ops.set_params) {
324		fe->ops.tuner_ops.set_params(fe);
325		if (fe->ops.i2c_gate_ctrl) fe->ops.i2c_gate_ctrl(fe, 0);
326	}
327
328	cx22700_set_inversion(state, c->inversion);
329	cx22700_set_tps(state, c);
330	cx22700_writereg (state, 0x37, 0x01);  /* PAL loop filter off */
331	cx22700_writereg (state, 0x00, 0x01);  /* restart acquire */
332
333	return 0;
334}
335
336static int cx22700_get_frontend(struct dvb_frontend *fe,
337				struct dtv_frontend_properties *c)
338{
339	struct cx22700_state* state = fe->demodulator_priv;
340	u8 reg09 = cx22700_readreg (state, 0x09);
341
342	c->inversion = reg09 & 0x1 ? INVERSION_ON : INVERSION_OFF;
343	return cx22700_get_tps(state, c);
344}
345
346static int cx22700_i2c_gate_ctrl(struct dvb_frontend* fe, int enable)
347{
348	struct cx22700_state* state = fe->demodulator_priv;
349
350	if (enable) {
351		return cx22700_writereg(state, 0x0a, 0x00);
352	} else {
353		return cx22700_writereg(state, 0x0a, 0x01);
354	}
355}
356
357static int cx22700_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings* fesettings)
358{
359	fesettings->min_delay_ms = 150;
360	fesettings->step_size = 166667;
361	fesettings->max_drift = 166667*2;
362	return 0;
363}
364
365static void cx22700_release(struct dvb_frontend* fe)
366{
367	struct cx22700_state* state = fe->demodulator_priv;
368	kfree(state);
369}
370
371static const struct dvb_frontend_ops cx22700_ops;
372
373struct dvb_frontend* cx22700_attach(const struct cx22700_config* config,
374				    struct i2c_adapter* i2c)
375{
376	struct cx22700_state* state = NULL;
377
378	/* allocate memory for the internal state */
379	state = kzalloc(sizeof(struct cx22700_state), GFP_KERNEL);
380	if (state == NULL) goto error;
381
382	/* setup the state */
383	state->config = config;
384	state->i2c = i2c;
385
386	/* check if the demod is there */
387	if (cx22700_readreg(state, 0x07) < 0) goto error;
388
389	/* create dvb_frontend */
390	memcpy(&state->frontend.ops, &cx22700_ops, sizeof(struct dvb_frontend_ops));
391	state->frontend.demodulator_priv = state;
392	return &state->frontend;
393
394error:
395	kfree(state);
396	return NULL;
397}
398
399static const struct dvb_frontend_ops cx22700_ops = {
400	.delsys = { SYS_DVBT },
401	.info = {
402		.name			= "Conexant CX22700 DVB-T",
403		.frequency_min_hz	= 470 * MHz,
404		.frequency_max_hz	= 860 * MHz,
405		.frequency_stepsize_hz	= 166667,
406		.caps = FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 |
407		      FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO |
408		      FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 |
409		      FE_CAN_RECOVER
410	},
411
412	.release = cx22700_release,
413
414	.init = cx22700_init,
415	.i2c_gate_ctrl = cx22700_i2c_gate_ctrl,
416
417	.set_frontend = cx22700_set_frontend,
418	.get_frontend = cx22700_get_frontend,
419	.get_tune_settings = cx22700_get_tune_settings,
420
421	.read_status = cx22700_read_status,
422	.read_ber = cx22700_read_ber,
423	.read_signal_strength = cx22700_read_signal_strength,
424	.read_snr = cx22700_read_snr,
425	.read_ucblocks = cx22700_read_ucblocks,
426};
427
428module_param(debug, int, 0644);
429MODULE_PARM_DESC(debug, "Turn on/off frontend debugging (default:off).");
430
431MODULE_DESCRIPTION("Conexant CX22700 DVB-T Demodulator driver");
432MODULE_AUTHOR("Holger Waechtler");
433MODULE_LICENSE("GPL");
434
435EXPORT_SYMBOL_GPL(cx22700_attach);
436