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

[media] horus3a: Sony Horus3A DVB-S/S2 tuner driver

Add DVB-S/S2 frontend driver for Sony Horus3A (CXD2832AER) chip

Signed-off-by: Kozlov Sergey <serjk@netup.ru>
Signed-off-by: Mauro Carvalho Chehab <mchehab@osg.samsung.com>

authored by

Kozlov Sergey and committed by
Mauro Carvalho Chehab
a5d32b35 3139f43f

+496
+9
MAINTAINERS
··· 6601 6601 F: Documentation/devicetree/bindings/media/renesas,vsp1.txt 6602 6602 F: drivers/media/platform/vsp1/ 6603 6603 6604 + MEDIA DRIVERS FOR HORUS3A 6605 + M: Sergey Kozlov <serjk@netup.ru> 6606 + L: linux-media@vger.kernel.org 6607 + W: http://linuxtv.org/ 6608 + W: http://netup.tv/ 6609 + T: git git://linuxtv.org/media_tree.git 6610 + S: Supported 6611 + F: drivers/media/dvb-frontends/horus3a* 6612 + 6604 6613 MEDIA INPUT INFRASTRUCTURE (V4L/DVB) 6605 6614 M: Mauro Carvalho Chehab <mchehab@osg.samsung.com> 6606 6615 P: LinuxTV.org Project
+7
drivers/media/dvb-frontends/Kconfig
··· 816 816 depends on DVB_CORE && I2C 817 817 default m if !MEDIA_SUBDRV_AUTOSELECT 818 818 819 + config DVB_HORUS3A 820 + tristate "Sony Horus3A tuner" 821 + depends on DVB_CORE && I2C 822 + default m if !MEDIA_SUBDRV_AUTOSELECT 823 + help 824 + Say Y when you want to support this frontend. 825 + 819 826 comment "Tools to develop new frontends" 820 827 821 828 config DVB_DUMMY_FE
+1
drivers/media/dvb-frontends/Makefile
··· 118 118 obj-$(CONFIG_DVB_AF9033) += af9033.o 119 119 obj-$(CONFIG_DVB_AS102_FE) += as102_fe.o 120 120 obj-$(CONFIG_DVB_TC90522) += tc90522.o 121 + obj-$(CONFIG_DVB_HORUS3A) += horus3a.o
+421
drivers/media/dvb-frontends/horus3a.c
··· 1 + /* 2 + * horus3a.h 3 + * 4 + * Sony Horus3A DVB-S/S2 tuner driver 5 + * 6 + * Copyright 2012 Sony Corporation 7 + * Copyright (C) 2014 NetUP Inc. 8 + * Copyright (C) 2014 Sergey Kozlov <serjk@netup.ru> 9 + * Copyright (C) 2014 Abylay Ospan <aospan@netup.ru> 10 + * 11 + * This program is free software; you can redistribute it and/or modify 12 + * it under the terms of the GNU General Public License as published by 13 + * the Free Software Foundation; either version 2 of the License, or 14 + * (at your option) any later version. 15 + * 16 + * This program is distributed in the hope that it will be useful, 17 + * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 + * GNU General Public License for more details. 20 + */ 21 + 22 + #include <linux/slab.h> 23 + #include <linux/module.h> 24 + #include <linux/dvb/frontend.h> 25 + #include <linux/types.h> 26 + #include "horus3a.h" 27 + #include "dvb_frontend.h" 28 + 29 + enum horus3a_state { 30 + STATE_UNKNOWN, 31 + STATE_SLEEP, 32 + STATE_ACTIVE 33 + }; 34 + 35 + struct horus3a_priv { 36 + u32 frequency; 37 + u8 i2c_address; 38 + struct i2c_adapter *i2c; 39 + enum horus3a_state state; 40 + void *set_tuner_data; 41 + int (*set_tuner)(void *, int); 42 + }; 43 + 44 + static void horus3a_i2c_debug(struct horus3a_priv *priv, 45 + u8 reg, u8 write, const u8 *data, u32 len) 46 + { 47 + dev_dbg(&priv->i2c->dev, "horus3a: I2C %s reg 0x%02x size %d\n", 48 + (write == 0 ? "read" : "write"), reg, len); 49 + print_hex_dump_bytes("horus3a: I2C data: ", 50 + DUMP_PREFIX_OFFSET, data, len); 51 + } 52 + 53 + static int horus3a_write_regs(struct horus3a_priv *priv, 54 + u8 reg, const u8 *data, u32 len) 55 + { 56 + int ret; 57 + u8 buf[len+1]; 58 + struct i2c_msg msg[1] = { 59 + { 60 + .addr = priv->i2c_address, 61 + .flags = 0, 62 + .len = sizeof(buf), 63 + .buf = buf, 64 + } 65 + }; 66 + 67 + horus3a_i2c_debug(priv, reg, 1, data, len); 68 + buf[0] = reg; 69 + memcpy(&buf[1], data, len); 70 + ret = i2c_transfer(priv->i2c, msg, 1); 71 + if (ret >= 0 && ret != 1) 72 + ret = -EREMOTEIO; 73 + if (ret < 0) { 74 + dev_warn(&priv->i2c->dev, 75 + "%s: i2c wr failed=%d reg=%02x len=%d\n", 76 + KBUILD_MODNAME, ret, reg, len); 77 + return ret; 78 + } 79 + return 0; 80 + } 81 + 82 + static int horus3a_write_reg(struct horus3a_priv *priv, u8 reg, u8 val) 83 + { 84 + return horus3a_write_regs(priv, reg, &val, 1); 85 + } 86 + 87 + static int horus3a_enter_power_save(struct horus3a_priv *priv) 88 + { 89 + u8 data[2]; 90 + 91 + dev_dbg(&priv->i2c->dev, "%s()\n", __func__); 92 + if (priv->state == STATE_SLEEP) 93 + return 0; 94 + /* IQ Generator disable */ 95 + horus3a_write_reg(priv, 0x2a, 0x79); 96 + /* MDIV_EN = 0 */ 97 + horus3a_write_reg(priv, 0x29, 0x70); 98 + /* VCO disable preparation */ 99 + horus3a_write_reg(priv, 0x28, 0x3e); 100 + /* VCO buffer disable */ 101 + horus3a_write_reg(priv, 0x2a, 0x19); 102 + /* VCO calibration disable */ 103 + horus3a_write_reg(priv, 0x1c, 0x00); 104 + /* Power save setting (xtal is not stopped) */ 105 + data[0] = 0xC0; 106 + /* LNA is Disabled */ 107 + data[1] = 0xA7; 108 + /* 0x11 - 0x12 */ 109 + horus3a_write_regs(priv, 0x11, data, sizeof(data)); 110 + priv->state = STATE_SLEEP; 111 + return 0; 112 + } 113 + 114 + static int horus3a_leave_power_save(struct horus3a_priv *priv) 115 + { 116 + u8 data[2]; 117 + 118 + dev_dbg(&priv->i2c->dev, "%s()\n", __func__); 119 + if (priv->state == STATE_ACTIVE) 120 + return 0; 121 + /* Leave power save */ 122 + data[0] = 0x00; 123 + /* LNA is Disabled */ 124 + data[1] = 0xa7; 125 + /* 0x11 - 0x12 */ 126 + horus3a_write_regs(priv, 0x11, data, sizeof(data)); 127 + /* VCO buffer enable */ 128 + horus3a_write_reg(priv, 0x2a, 0x79); 129 + /* VCO calibration enable */ 130 + horus3a_write_reg(priv, 0x1c, 0xc0); 131 + /* MDIV_EN = 1 */ 132 + horus3a_write_reg(priv, 0x29, 0x71); 133 + usleep_range(5000, 7000); 134 + priv->state = STATE_ACTIVE; 135 + return 0; 136 + } 137 + 138 + static int horus3a_init(struct dvb_frontend *fe) 139 + { 140 + struct horus3a_priv *priv = fe->tuner_priv; 141 + 142 + dev_dbg(&priv->i2c->dev, "%s()\n", __func__); 143 + return 0; 144 + } 145 + 146 + static int horus3a_release(struct dvb_frontend *fe) 147 + { 148 + struct horus3a_priv *priv = fe->tuner_priv; 149 + 150 + dev_dbg(&priv->i2c->dev, "%s()\n", __func__); 151 + kfree(fe->tuner_priv); 152 + fe->tuner_priv = NULL; 153 + return 0; 154 + } 155 + 156 + static int horus3a_sleep(struct dvb_frontend *fe) 157 + { 158 + struct horus3a_priv *priv = fe->tuner_priv; 159 + 160 + dev_dbg(&priv->i2c->dev, "%s()\n", __func__); 161 + horus3a_enter_power_save(priv); 162 + return 0; 163 + } 164 + 165 + static int horus3a_set_params(struct dvb_frontend *fe) 166 + { 167 + struct dtv_frontend_properties *p = &fe->dtv_property_cache; 168 + struct horus3a_priv *priv = fe->tuner_priv; 169 + u32 frequency = p->frequency; 170 + u32 symbol_rate = p->symbol_rate/1000; 171 + u8 mixdiv = 0; 172 + u8 mdiv = 0; 173 + u32 ms = 0; 174 + u8 f_ctl = 0; 175 + u8 g_ctl = 0; 176 + u8 fc_lpf = 0; 177 + u8 data[5]; 178 + 179 + dev_dbg(&priv->i2c->dev, "%s(): frequency %dkHz symbol_rate %dksps\n", 180 + __func__, frequency, symbol_rate); 181 + if (priv->set_tuner) 182 + priv->set_tuner(priv->set_tuner_data, 0); 183 + if (priv->state == STATE_SLEEP) 184 + horus3a_leave_power_save(priv); 185 + 186 + /* frequency should be X MHz (X : integer) */ 187 + frequency = DIV_ROUND_CLOSEST(frequency, 1000) * 1000; 188 + if (frequency <= 1155000) { 189 + mixdiv = 4; 190 + mdiv = 1; 191 + } else { 192 + mixdiv = 2; 193 + mdiv = 0; 194 + } 195 + /* Assumed that fREF == 1MHz (1000kHz) */ 196 + ms = DIV_ROUND_CLOSEST((frequency * mixdiv) / 2, 1000); 197 + if (ms > 0x7FFF) { /* 15 bit */ 198 + dev_err(&priv->i2c->dev, "horus3a: invalid frequency %d\n", 199 + frequency); 200 + return -EINVAL; 201 + } 202 + if (frequency < 975000) { 203 + /* F_CTL=11100 G_CTL=001 */ 204 + f_ctl = 0x1C; 205 + g_ctl = 0x01; 206 + } else if (frequency < 1050000) { 207 + /* F_CTL=11000 G_CTL=010 */ 208 + f_ctl = 0x18; 209 + g_ctl = 0x02; 210 + } else if (frequency < 1150000) { 211 + /* F_CTL=10100 G_CTL=010 */ 212 + f_ctl = 0x14; 213 + g_ctl = 0x02; 214 + } else if (frequency < 1250000) { 215 + /* F_CTL=10000 G_CTL=011 */ 216 + f_ctl = 0x10; 217 + g_ctl = 0x03; 218 + } else if (frequency < 1350000) { 219 + /* F_CTL=01100 G_CTL=100 */ 220 + f_ctl = 0x0C; 221 + g_ctl = 0x04; 222 + } else if (frequency < 1450000) { 223 + /* F_CTL=01010 G_CTL=100 */ 224 + f_ctl = 0x0A; 225 + g_ctl = 0x04; 226 + } else if (frequency < 1600000) { 227 + /* F_CTL=00111 G_CTL=101 */ 228 + f_ctl = 0x07; 229 + g_ctl = 0x05; 230 + } else if (frequency < 1800000) { 231 + /* F_CTL=00100 G_CTL=010 */ 232 + f_ctl = 0x04; 233 + g_ctl = 0x02; 234 + } else if (frequency < 2000000) { 235 + /* F_CTL=00010 G_CTL=001 */ 236 + f_ctl = 0x02; 237 + g_ctl = 0x01; 238 + } else { 239 + /* F_CTL=00000 G_CTL=000 */ 240 + f_ctl = 0x00; 241 + g_ctl = 0x00; 242 + } 243 + /* LPF cutoff frequency setting */ 244 + if (p->delivery_system == SYS_DVBS) { 245 + /* 246 + * rolloff = 0.35 247 + * SR <= 4.3 248 + * fc_lpf = 5 249 + * 4.3 < SR <= 10 250 + * fc_lpf = SR * (1 + rolloff) / 2 + SR / 2 = 251 + * SR * 1.175 = SR * (47/40) 252 + * 10 < SR 253 + * fc_lpf = SR * (1 + rolloff) / 2 + 5 = 254 + * SR * 0.675 + 5 = SR * (27/40) + 5 255 + * NOTE: The result should be round up. 256 + */ 257 + if (symbol_rate <= 4300) 258 + fc_lpf = 5; 259 + else if (symbol_rate <= 10000) 260 + fc_lpf = (u8)DIV_ROUND_UP(symbol_rate * 47, 40000); 261 + else 262 + fc_lpf = (u8)DIV_ROUND_UP(symbol_rate * 27, 40000) + 5; 263 + /* 5 <= fc_lpf <= 36 */ 264 + if (fc_lpf > 36) 265 + fc_lpf = 36; 266 + } else if (p->delivery_system == SYS_DVBS2) { 267 + int rolloff; 268 + 269 + switch (p->rolloff) { 270 + case ROLLOFF_35: 271 + rolloff = 35; 272 + break; 273 + case ROLLOFF_25: 274 + rolloff = 25; 275 + break; 276 + case ROLLOFF_20: 277 + rolloff = 20; 278 + break; 279 + case ROLLOFF_AUTO: 280 + dev_err(&priv->i2c->dev, 281 + "horus3a: auto roll-off is not supported\n"); 282 + return -EINVAL; 283 + } 284 + /* 285 + * SR <= 4.5: 286 + * fc_lpf = 5 287 + * 4.5 < SR <= 10: 288 + * fc_lpf = SR * (1 + rolloff) / 2 + SR / 2 289 + * 10 < SR: 290 + * fc_lpf = SR * (1 + rolloff) / 2 + 5 291 + * NOTE: The result should be round up. 292 + */ 293 + if (symbol_rate <= 4500) 294 + fc_lpf = 5; 295 + else if (symbol_rate <= 10000) 296 + fc_lpf = (u8)DIV_ROUND_UP( 297 + symbol_rate * (200 + rolloff), 200000); 298 + else 299 + fc_lpf = (u8)DIV_ROUND_UP( 300 + symbol_rate * (100 + rolloff), 200000) + 5; 301 + /* 5 <= fc_lpf <= 36 is valid */ 302 + if (fc_lpf > 36) 303 + fc_lpf = 36; 304 + } else { 305 + dev_err(&priv->i2c->dev, 306 + "horus3a: invalid delivery system %d\n", 307 + p->delivery_system); 308 + return -EINVAL; 309 + } 310 + /* 0x00 - 0x04 */ 311 + data[0] = (u8)((ms >> 7) & 0xFF); 312 + data[1] = (u8)((ms << 1) & 0xFF); 313 + data[2] = 0x00; 314 + data[3] = 0x00; 315 + data[4] = (u8)(mdiv << 7); 316 + horus3a_write_regs(priv, 0x00, data, sizeof(data)); 317 + /* Write G_CTL, F_CTL */ 318 + horus3a_write_reg(priv, 0x09, (u8)((g_ctl << 5) | f_ctl)); 319 + /* Write LPF cutoff frequency */ 320 + horus3a_write_reg(priv, 0x37, (u8)(0x80 | (fc_lpf << 1))); 321 + /* Start Calibration */ 322 + horus3a_write_reg(priv, 0x05, 0x80); 323 + /* IQ Generator enable */ 324 + horus3a_write_reg(priv, 0x2a, 0x7b); 325 + /* tuner stabilization time */ 326 + msleep(60); 327 + /* Store tuned frequency to the struct */ 328 + priv->frequency = ms * 2 * 1000 / mixdiv; 329 + return 0; 330 + } 331 + 332 + static int horus3a_get_frequency(struct dvb_frontend *fe, u32 *frequency) 333 + { 334 + struct horus3a_priv *priv = fe->tuner_priv; 335 + 336 + *frequency = priv->frequency; 337 + return 0; 338 + } 339 + 340 + static struct dvb_tuner_ops horus3a_tuner_ops = { 341 + .info = { 342 + .name = "Sony Horus3a", 343 + .frequency_min = 950000, 344 + .frequency_max = 2150000, 345 + .frequency_step = 1000, 346 + }, 347 + .init = horus3a_init, 348 + .release = horus3a_release, 349 + .sleep = horus3a_sleep, 350 + .set_params = horus3a_set_params, 351 + .get_frequency = horus3a_get_frequency, 352 + }; 353 + 354 + struct dvb_frontend *horus3a_attach(struct dvb_frontend *fe, 355 + const struct horus3a_config *config, 356 + struct i2c_adapter *i2c) 357 + { 358 + u8 buf[3], val; 359 + struct horus3a_priv *priv = NULL; 360 + 361 + priv = kzalloc(sizeof(struct horus3a_priv), GFP_KERNEL); 362 + if (priv == NULL) 363 + return NULL; 364 + priv->i2c_address = (config->i2c_address >> 1); 365 + priv->i2c = i2c; 366 + priv->set_tuner_data = config->set_tuner_priv; 367 + priv->set_tuner = config->set_tuner_callback; 368 + 369 + if (fe->ops.i2c_gate_ctrl) 370 + fe->ops.i2c_gate_ctrl(fe, 1); 371 + 372 + /* wait 4ms after power on */ 373 + usleep_range(4000, 6000); 374 + /* IQ Generator disable */ 375 + horus3a_write_reg(priv, 0x2a, 0x79); 376 + /* REF_R = Xtal Frequency */ 377 + buf[0] = config->xtal_freq_mhz; 378 + buf[1] = config->xtal_freq_mhz; 379 + buf[2] = 0; 380 + /* 0x6 - 0x8 */ 381 + horus3a_write_regs(priv, 0x6, buf, 3); 382 + /* IQ Out = Single Ended */ 383 + horus3a_write_reg(priv, 0x0a, 0x40); 384 + switch (config->xtal_freq_mhz) { 385 + case 27: 386 + val = 0x1f; 387 + break; 388 + case 24: 389 + val = 0x10; 390 + break; 391 + case 16: 392 + val = 0xc; 393 + break; 394 + default: 395 + val = 0; 396 + dev_warn(&priv->i2c->dev, 397 + "horus3a: invalid xtal frequency %dMHz\n", 398 + config->xtal_freq_mhz); 399 + break; 400 + } 401 + val <<= 2; 402 + horus3a_write_reg(priv, 0x0e, val); 403 + horus3a_enter_power_save(priv); 404 + usleep_range(3000, 5000); 405 + 406 + if (fe->ops.i2c_gate_ctrl) 407 + fe->ops.i2c_gate_ctrl(fe, 0); 408 + 409 + memcpy(&fe->ops.tuner_ops, &horus3a_tuner_ops, 410 + sizeof(struct dvb_tuner_ops)); 411 + fe->tuner_priv = priv; 412 + dev_info(&priv->i2c->dev, 413 + "Sony HORUS3A attached on addr=%x at I2C adapter %p\n", 414 + priv->i2c_address, priv->i2c); 415 + return fe; 416 + } 417 + EXPORT_SYMBOL(horus3a_attach); 418 + 419 + MODULE_DESCRIPTION("Sony HORUS3A sattelite tuner driver"); 420 + MODULE_AUTHOR("Sergey Kozlov <serjk@netup.ru>"); 421 + MODULE_LICENSE("GPL");
+58
drivers/media/dvb-frontends/horus3a.h
··· 1 + /* 2 + * horus3a.h 3 + * 4 + * Sony Horus3A DVB-S/S2 tuner driver 5 + * 6 + * Copyright 2012 Sony Corporation 7 + * Copyright (C) 2014 NetUP Inc. 8 + * Copyright (C) 2014 Sergey Kozlov <serjk@netup.ru> 9 + * Copyright (C) 2014 Abylay Ospan <aospan@netup.ru> 10 + * 11 + * This program is free software; you can redistribute it and/or modify 12 + * it under the terms of the GNU General Public License as published by 13 + * the Free Software Foundation; either version 2 of the License, or 14 + * (at your option) any later version. 15 + * 16 + * This program is distributed in the hope that it will be useful, 17 + * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 + * GNU General Public License for more details. 20 + */ 21 + 22 + #ifndef __DVB_HORUS3A_H__ 23 + #define __DVB_HORUS3A_H__ 24 + 25 + #include <linux/kconfig.h> 26 + #include <linux/dvb/frontend.h> 27 + #include <linux/i2c.h> 28 + 29 + /** 30 + * struct horus3a_config - the configuration of Horus3A tuner driver 31 + * @i2c_address: I2C address of the tuner 32 + * @xtal_freq_mhz: Oscillator frequency, MHz 33 + * @set_tuner_priv: Callback function private context 34 + * @set_tuner_callback: Callback function that notifies the parent driver 35 + * which tuner is active now 36 + */ 37 + struct horus3a_config { 38 + u8 i2c_address; 39 + u8 xtal_freq_mhz; 40 + void *set_tuner_priv; 41 + int (*set_tuner_callback)(void *, int); 42 + }; 43 + 44 + #if IS_REACHABLE(CONFIG_DVB_HORUS3A) 45 + extern struct dvb_frontend *horus3a_attach(struct dvb_frontend *fe, 46 + const struct horus3a_config *config, 47 + struct i2c_adapter *i2c); 48 + #else 49 + static inline struct dvb_frontend *horus3a_attach( 50 + const struct cxd2820r_config *config, 51 + struct i2c_adapter *i2c) 52 + { 53 + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); 54 + return NULL; 55 + } 56 + #endif 57 + 58 + #endif