Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

media: tuners: add new i2c driver for Sharp qm1d1b0004 ISDB-S tuner

The tuner is used in Earthsoft PT1/PT2 DVB boards,
and the driver was extraced from (the former) va1j5jf8007s.c of PT1.
it might contain PT1 specific configs.

Signed-off-by: Akihiro Tsukada <tskd08@gmail.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>

authored by

Akihiro Tsukada and committed by
Mauro Carvalho Chehab
a959c52d 648db06d

+296
+7
drivers/media/tuners/Kconfig
··· 284 284 default m if !MEDIA_SUBDRV_AUTOSELECT 285 285 help 286 286 Sharp QM1D1C0042 trellis coded 8PSK tuner driver. 287 + 288 + config MEDIA_TUNER_QM1D1B0004 289 + tristate "Sharp QM1D1B0004 tuner" 290 + depends on MEDIA_SUPPORT && I2C 291 + default m if !MEDIA_SUBDRV_AUTOSELECT 292 + help 293 + Sharp QM1D1B0004 ISDB-S tuner driver. 287 294 endmenu
+1
drivers/media/tuners/Makefile
··· 41 41 obj-$(CONFIG_MEDIA_TUNER_R820T) += r820t.o 42 42 obj-$(CONFIG_MEDIA_TUNER_MXL301RF) += mxl301rf.o 43 43 obj-$(CONFIG_MEDIA_TUNER_QM1D1C0042) += qm1d1c0042.o 44 + obj-$(CONFIG_MEDIA_TUNER_QM1D1B0004) += qm1d1b0004.o 44 45 obj-$(CONFIG_MEDIA_TUNER_M88RS6000T) += m88rs6000t.o 45 46 obj-$(CONFIG_MEDIA_TUNER_TDA18250) += tda18250.o 46 47
+264
drivers/media/tuners/qm1d1b0004.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * Sharp QM1D1B0004 satellite tuner 4 + * 5 + * Copyright (C) 2014 Akihiro Tsukada <tskd08@gmail.com> 6 + * 7 + * based on (former) drivers/media/pci/pt1/va1j5jf8007s.c. 8 + */ 9 + 10 + /* 11 + * Note: 12 + * Since the data-sheet of this tuner chip is not available, 13 + * this driver lacks some tuner_ops and config options. 14 + * In addition, the implementation might be dependent on the specific use 15 + * in the FE module: VA1J5JF8007S and/or in the product: Earthsoft PT1/PT2. 16 + */ 17 + 18 + #include <linux/kernel.h> 19 + #include <linux/module.h> 20 + #include <media/dvb_frontend.h> 21 + #include "qm1d1b0004.h" 22 + 23 + /* 24 + * Tuner I/F (copied from the former va1j5jf8007s.c) 25 + * b[0] I2C addr 26 + * b[1] "0":1, BG:2, divider_quotient[7:3]:5 27 + * b[2] divider_quotient[2:0]:3, divider_remainder:5 28 + * b[3] "111":3, LPF[3:2]:2, TM:1, "0":1, REF:1 29 + * b[4] BANDX, PSC:1, LPF[1:0]:2, DIV:1, "0":1 30 + * 31 + * PLL frequency step := 32 + * REF == 0 -> PLL XTL frequency(4MHz) / 8 33 + * REF == 1 -> PLL XTL frequency(4MHz) / 4 34 + * 35 + * PreScaler := 36 + * PSC == 0 -> x32 37 + * PSC == 1 -> x16 38 + * 39 + * divider_quotient := (frequency / PLL frequency step) / PreScaler 40 + * divider_remainder := (frequency / PLL frequency step) % PreScaler 41 + * 42 + * LPF := LPF Frequency / 1000 / 2 - 2 43 + * LPF Frequency @ baudrate=28.86Mbps = 30000 44 + * 45 + * band (1..9) 46 + * band 1 (freq < 986000) -> DIV:1, BANDX:5, PSC:1 47 + * band 2 (freq < 1072000) -> DIV:1, BANDX:6, PSC:1 48 + * band 3 (freq < 1154000) -> DIV:1, BANDX:7, PSC:0 49 + * band 4 (freq < 1291000) -> DIV:0, BANDX:1, PSC:0 50 + * band 5 (freq < 1447000) -> DIV:0, BANDX:2, PSC:0 51 + * band 6 (freq < 1615000) -> DIV:0, BANDX:3, PSC:0 52 + * band 7 (freq < 1791000) -> DIV:0, BANDX:4, PSC:0 53 + * band 8 (freq < 1972000) -> DIV:0, BANDX:5, PSC:0 54 + * band 9 (freq < 2150000) -> DIV:0, BANDX:6, PSC:0 55 + */ 56 + 57 + #define QM1D1B0004_PSC_MASK (1 << 4) 58 + 59 + #define QM1D1B0004_XTL_FREQ 4000 60 + #define QM1D1B0004_LPF_FALLBACK 30000 61 + 62 + static const struct qm1d1b0004_config default_cfg = { 63 + .lpf_freq = QM1D1B0004_CFG_LPF_DFLT, 64 + .half_step = false, 65 + }; 66 + 67 + struct qm1d1b0004_state { 68 + struct qm1d1b0004_config cfg; 69 + struct i2c_client *i2c; 70 + }; 71 + 72 + 73 + struct qm1d1b0004_cb_map { 74 + u32 frequency; 75 + u8 cb; 76 + }; 77 + 78 + static const struct qm1d1b0004_cb_map cb_maps[] = { 79 + { 986000, 0xb2 }, 80 + { 1072000, 0xd2 }, 81 + { 1154000, 0xe2 }, 82 + { 1291000, 0x20 }, 83 + { 1447000, 0x40 }, 84 + { 1615000, 0x60 }, 85 + { 1791000, 0x80 }, 86 + { 1972000, 0xa0 }, 87 + }; 88 + 89 + static u8 lookup_cb(u32 frequency) 90 + { 91 + int i; 92 + const struct qm1d1b0004_cb_map *map; 93 + 94 + for (i = 0; i < ARRAY_SIZE(cb_maps); i++) { 95 + map = &cb_maps[i]; 96 + if (frequency < map->frequency) 97 + return map->cb; 98 + } 99 + return 0xc0; 100 + } 101 + 102 + static int qm1d1b0004_set_params(struct dvb_frontend *fe) 103 + { 104 + struct qm1d1b0004_state *state; 105 + u32 frequency, pll, lpf_freq; 106 + u16 word; 107 + u8 buf[4], cb, lpf; 108 + int ret; 109 + 110 + state = fe->tuner_priv; 111 + frequency = fe->dtv_property_cache.frequency; 112 + 113 + pll = QM1D1B0004_XTL_FREQ / 4; 114 + if (state->cfg.half_step) 115 + pll /= 2; 116 + word = DIV_ROUND_CLOSEST(frequency, pll); 117 + cb = lookup_cb(frequency); 118 + if (cb & QM1D1B0004_PSC_MASK) 119 + word = (word << 1 & ~0x1f) | (word & 0x0f); 120 + 121 + /* step.1: set frequency with BG:2, TM:0(4MHZ), LPF:4MHz */ 122 + buf[0] = 0x40 | word >> 8; 123 + buf[1] = word; 124 + /* inconsisnten with the above I/F doc. maybe the doc is wrong */ 125 + buf[2] = 0xe0 | state->cfg.half_step; 126 + buf[3] = cb; 127 + ret = i2c_master_send(state->i2c, buf, 4); 128 + if (ret < 0) 129 + return ret; 130 + 131 + /* step.2: set TM:1 */ 132 + buf[0] = 0xe4 | state->cfg.half_step; 133 + ret = i2c_master_send(state->i2c, buf, 1); 134 + if (ret < 0) 135 + return ret; 136 + msleep(20); 137 + 138 + /* step.3: set LPF */ 139 + lpf_freq = state->cfg.lpf_freq; 140 + if (lpf_freq == QM1D1B0004_CFG_LPF_DFLT) 141 + lpf_freq = fe->dtv_property_cache.symbol_rate / 1000; 142 + if (lpf_freq == 0) 143 + lpf_freq = QM1D1B0004_LPF_FALLBACK; 144 + lpf = DIV_ROUND_UP(lpf_freq, 2000) - 2; 145 + buf[0] = 0xe4 | ((lpf & 0x0c) << 1) | state->cfg.half_step; 146 + buf[1] = cb | ((lpf & 0x03) << 2); 147 + ret = i2c_master_send(state->i2c, buf, 2); 148 + if (ret < 0) 149 + return ret; 150 + 151 + /* step.4: read PLL lock? */ 152 + buf[0] = 0; 153 + ret = i2c_master_recv(state->i2c, buf, 1); 154 + if (ret < 0) 155 + return ret; 156 + return 0; 157 + } 158 + 159 + 160 + static int qm1d1b0004_set_config(struct dvb_frontend *fe, void *priv_cfg) 161 + { 162 + struct qm1d1b0004_state *state; 163 + 164 + state = fe->tuner_priv; 165 + memcpy(&state->cfg, priv_cfg, sizeof(state->cfg)); 166 + return 0; 167 + } 168 + 169 + 170 + static int qm1d1b0004_init(struct dvb_frontend *fe) 171 + { 172 + struct qm1d1b0004_state *state; 173 + u8 buf[2] = {0xf8, 0x04}; 174 + 175 + state = fe->tuner_priv; 176 + if (state->cfg.half_step) 177 + buf[0] |= 0x01; 178 + 179 + return i2c_master_send(state->i2c, buf, 2); 180 + } 181 + 182 + 183 + static const struct dvb_tuner_ops qm1d1b0004_ops = { 184 + .info = { 185 + .name = "Sharp qm1d1b0004", 186 + 187 + .frequency_min = 950000, 188 + .frequency_max = 2150000, 189 + }, 190 + 191 + .init = qm1d1b0004_init, 192 + 193 + .set_params = qm1d1b0004_set_params, 194 + .set_config = qm1d1b0004_set_config, 195 + }; 196 + 197 + static int 198 + qm1d1b0004_probe(struct i2c_client *client, const struct i2c_device_id *id) 199 + { 200 + struct dvb_frontend *fe; 201 + struct qm1d1b0004_config *cfg; 202 + struct qm1d1b0004_state *state; 203 + int ret; 204 + 205 + cfg = client->dev.platform_data; 206 + fe = cfg->fe; 207 + i2c_set_clientdata(client, fe); 208 + 209 + fe->tuner_priv = kzalloc(sizeof(struct qm1d1b0004_state), GFP_KERNEL); 210 + if (!fe->tuner_priv) { 211 + ret = -ENOMEM; 212 + goto err_mem; 213 + } 214 + 215 + memcpy(&fe->ops.tuner_ops, &qm1d1b0004_ops, sizeof(fe->ops.tuner_ops)); 216 + 217 + state = fe->tuner_priv; 218 + state->i2c = client; 219 + ret = qm1d1b0004_set_config(fe, cfg); 220 + if (ret != 0) 221 + goto err_priv; 222 + 223 + dev_info(&client->dev, "Sharp QM1D1B0004 attached.\n"); 224 + return 0; 225 + 226 + err_priv: 227 + kfree(fe->tuner_priv); 228 + err_mem: 229 + fe->tuner_priv = NULL; 230 + return ret; 231 + } 232 + 233 + static int qm1d1b0004_remove(struct i2c_client *client) 234 + { 235 + struct dvb_frontend *fe; 236 + 237 + fe = i2c_get_clientdata(client); 238 + kfree(fe->tuner_priv); 239 + fe->tuner_priv = NULL; 240 + return 0; 241 + } 242 + 243 + 244 + static const struct i2c_device_id qm1d1b0004_id[] = { 245 + {"qm1d1b0004", 0}, 246 + {} 247 + }; 248 + 249 + MODULE_DEVICE_TABLE(i2c, qm1d1b0004_id); 250 + 251 + static struct i2c_driver qm1d1b0004_driver = { 252 + .driver = { 253 + .name = "qm1d1b0004", 254 + }, 255 + .probe = qm1d1b0004_probe, 256 + .remove = qm1d1b0004_remove, 257 + .id_table = qm1d1b0004_id, 258 + }; 259 + 260 + module_i2c_driver(qm1d1b0004_driver); 261 + 262 + MODULE_DESCRIPTION("Sharp QM1D1B0004"); 263 + MODULE_AUTHOR("Akihiro Tsukada"); 264 + MODULE_LICENSE("GPL");
+24
drivers/media/tuners/qm1d1b0004.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0 */ 2 + /* 3 + * Sharp QM1D1B0004 satellite tuner 4 + * 5 + * Copyright (C) 2014 Akihiro Tsukada <tskd08@gmail.com> 6 + */ 7 + 8 + #ifndef QM1D1B0004_H 9 + #define QM1D1B0004_H 10 + 11 + #include <media/dvb_frontend.h> 12 + 13 + struct qm1d1b0004_config { 14 + struct dvb_frontend *fe; 15 + 16 + u32 lpf_freq; /* LPF frequency[kHz]. Default: symbol rate */ 17 + bool half_step; /* use PLL frequency step of 500Hz istead of 1000Hz */ 18 + }; 19 + 20 + /* special values indicating to use the default in qm1d1b0004_config */ 21 + #define QM1D1B0004_CFG_PLL_DFLT 0 22 + #define QM1D1B0004_CFG_LPF_DFLT 0 23 + 24 + #endif