// SPDX-License-Identifier: GPL-2.0 /* * Sharp QM1D1B0004 satellite tuner * * Copyright (C) 2014 Akihiro Tsukada * * based on (former) drivers/media/pci/pt1/va1j5jf8007s.c. */ /* * Note: * Since the data-sheet of this tuner chip is not available, * this driver lacks some tuner_ops and config options. * In addition, the implementation might be dependent on the specific use * in the FE module: VA1J5JF8007S and/or in the product: Earthsoft PT1/PT2. */ #include #include #include #include "qm1d1b0004.h" /* * Tuner I/F (copied from the former va1j5jf8007s.c) * b[0] I2C addr * b[1] "0":1, BG:2, divider_quotient[7:3]:5 * b[2] divider_quotient[2:0]:3, divider_remainder:5 * b[3] "111":3, LPF[3:2]:2, TM:1, "0":1, REF:1 * b[4] BANDX, PSC:1, LPF[1:0]:2, DIV:1, "0":1 * * PLL frequency step := * REF == 0 -> PLL XTL frequency(4MHz) / 8 * REF == 1 -> PLL XTL frequency(4MHz) / 4 * * PreScaler := * PSC == 0 -> x32 * PSC == 1 -> x16 * * divider_quotient := (frequency / PLL frequency step) / PreScaler * divider_remainder := (frequency / PLL frequency step) % PreScaler * * LPF := LPF Frequency / 1000 / 2 - 2 * LPF Frequency @ baudrate=28.86Mbps = 30000 * * band (1..9) * band 1 (freq < 986000) -> DIV:1, BANDX:5, PSC:1 * band 2 (freq < 1072000) -> DIV:1, BANDX:6, PSC:1 * band 3 (freq < 1154000) -> DIV:1, BANDX:7, PSC:0 * band 4 (freq < 1291000) -> DIV:0, BANDX:1, PSC:0 * band 5 (freq < 1447000) -> DIV:0, BANDX:2, PSC:0 * band 6 (freq < 1615000) -> DIV:0, BANDX:3, PSC:0 * band 7 (freq < 1791000) -> DIV:0, BANDX:4, PSC:0 * band 8 (freq < 1972000) -> DIV:0, BANDX:5, PSC:0 * band 9 (freq < 2150000) -> DIV:0, BANDX:6, PSC:0 */ #define QM1D1B0004_PSC_MASK (1 << 4) #define QM1D1B0004_XTL_FREQ 4000 #define QM1D1B0004_LPF_FALLBACK 30000 #if 0 /* Currently unused */ static const struct qm1d1b0004_config default_cfg = { .lpf_freq = QM1D1B0004_CFG_LPF_DFLT, .half_step = false, }; #endif struct qm1d1b0004_state { struct qm1d1b0004_config cfg; struct i2c_client *i2c; }; struct qm1d1b0004_cb_map { u32 frequency; u8 cb; }; static const struct qm1d1b0004_cb_map cb_maps[] = { { 986000, 0xb2 }, { 1072000, 0xd2 }, { 1154000, 0xe2 }, { 1291000, 0x20 }, { 1447000, 0x40 }, { 1615000, 0x60 }, { 1791000, 0x80 }, { 1972000, 0xa0 }, }; static u8 lookup_cb(u32 frequency) { int i; const struct qm1d1b0004_cb_map *map; for (i = 0; i < ARRAY_SIZE(cb_maps); i++) { map = &cb_maps[i]; if (frequency < map->frequency) return map->cb; } return 0xc0; } static int qm1d1b0004_set_params(struct dvb_frontend *fe) { struct qm1d1b0004_state *state; u32 frequency, pll, lpf_freq; u16 word; u8 buf[4], cb, lpf; int ret; state = fe->tuner_priv; frequency = fe->dtv_property_cache.frequency; pll = QM1D1B0004_XTL_FREQ / 4; if (state->cfg.half_step) pll /= 2; word = DIV_ROUND_CLOSEST(frequency, pll); cb = lookup_cb(frequency); if (cb & QM1D1B0004_PSC_MASK) word = (word << 1 & ~0x1f) | (word & 0x0f); /* step.1: set frequency with BG:2, TM:0(4MHZ), LPF:4MHz */ buf[0] = 0x40 | word >> 8; buf[1] = word; /* inconsisnten with the above I/F doc. maybe the doc is wrong */ buf[2] = 0xe0 | state->cfg.half_step; buf[3] = cb; ret = i2c_master_send(state->i2c, buf, 4); if (ret < 0) return ret; /* step.2: set TM:1 */ buf[0] = 0xe4 | state->cfg.half_step; ret = i2c_master_send(state->i2c, buf, 1); if (ret < 0) return ret; msleep(20); /* step.3: set LPF */ lpf_freq = state->cfg.lpf_freq; if (lpf_freq == QM1D1B0004_CFG_LPF_DFLT) lpf_freq = fe->dtv_property_cache.symbol_rate / 1000; if (lpf_freq == 0) lpf_freq = QM1D1B0004_LPF_FALLBACK; lpf = DIV_ROUND_UP(lpf_freq, 2000) - 2; buf[0] = 0xe4 | ((lpf & 0x0c) << 1) | state->cfg.half_step; buf[1] = cb | ((lpf & 0x03) << 2); ret = i2c_master_send(state->i2c, buf, 2); if (ret < 0) return ret; /* step.4: read PLL lock? */ buf[0] = 0; ret = i2c_master_recv(state->i2c, buf, 1); if (ret < 0) return ret; return 0; } static int qm1d1b0004_set_config(struct dvb_frontend *fe, void *priv_cfg) { struct qm1d1b0004_state *state; state = fe->tuner_priv; memcpy(&state->cfg, priv_cfg, sizeof(state->cfg)); return 0; } static int qm1d1b0004_init(struct dvb_frontend *fe) { struct qm1d1b0004_state *state; u8 buf[2] = {0xf8, 0x04}; state = fe->tuner_priv; if (state->cfg.half_step) buf[0] |= 0x01; return i2c_master_send(state->i2c, buf, 2); } static const struct dvb_tuner_ops qm1d1b0004_ops = { .info = { .name = "Sharp qm1d1b0004", .frequency_min_hz = 950 * MHz, .frequency_max_hz = 2150 * MHz, }, .init = qm1d1b0004_init, .set_params = qm1d1b0004_set_params, .set_config = qm1d1b0004_set_config, }; static int qm1d1b0004_probe(struct i2c_client *client) { struct dvb_frontend *fe; struct qm1d1b0004_config *cfg; struct qm1d1b0004_state *state; int ret; cfg = client->dev.platform_data; fe = cfg->fe; i2c_set_clientdata(client, fe); fe->tuner_priv = kzalloc(sizeof(struct qm1d1b0004_state), GFP_KERNEL); if (!fe->tuner_priv) { ret = -ENOMEM; goto err_mem; } memcpy(&fe->ops.tuner_ops, &qm1d1b0004_ops, sizeof(fe->ops.tuner_ops)); state = fe->tuner_priv; state->i2c = client; ret = qm1d1b0004_set_config(fe, cfg); if (ret != 0) goto err_priv; dev_info(&client->dev, "Sharp QM1D1B0004 attached.\n"); return 0; err_priv: kfree(fe->tuner_priv); err_mem: fe->tuner_priv = NULL; return ret; } static void qm1d1b0004_remove(struct i2c_client *client) { struct dvb_frontend *fe; fe = i2c_get_clientdata(client); kfree(fe->tuner_priv); fe->tuner_priv = NULL; } static const struct i2c_device_id qm1d1b0004_id[] = { {"qm1d1b0004", 0}, {} }; MODULE_DEVICE_TABLE(i2c, qm1d1b0004_id); static struct i2c_driver qm1d1b0004_driver = { .driver = { .name = "qm1d1b0004", }, .probe = qm1d1b0004_probe, .remove = qm1d1b0004_remove, .id_table = qm1d1b0004_id, }; module_i2c_driver(qm1d1b0004_driver); MODULE_DESCRIPTION("Sharp QM1D1B0004"); MODULE_AUTHOR("Akihiro Tsukada"); MODULE_LICENSE("GPL");