0001
0002
0003
0004
0005
0006
0007 #include <linux/i2c.h>
0008 #include <linux/slab.h>
0009 #include <linux/delay.h>
0010 #include <linux/videodev2.h>
0011 #include <media/tuner.h>
0012 #include "tuner-i2c.h"
0013 #include "tea5761.h"
0014
0015 static int debug;
0016 module_param(debug, int, 0644);
0017 MODULE_PARM_DESC(debug, "enable verbose debug messages");
0018
0019 struct tea5761_priv {
0020 struct tuner_i2c_props i2c_props;
0021
0022 u32 frequency;
0023 bool standby;
0024 };
0025
0026
0027
0028
0029
0030
0031
0032
0033
0034
0035 #define TEA5761_INTREG_IFFLAG 0x10
0036 #define TEA5761_INTREG_LEVFLAG 0x8
0037 #define TEA5761_INTREG_FRRFLAG 0x2
0038 #define TEA5761_INTREG_BLFLAG 0x1
0039
0040
0041 #define TEA5761_INTREG_IFMSK 0x10
0042 #define TEA5761_INTREG_LEVMSK 0x8
0043 #define TEA5761_INTREG_FRMSK 0x2
0044 #define TEA5761_INTREG_BLMSK 0x1
0045
0046
0047
0048
0049 #define TEA5761_FRQSET_SEARCH_UP 0x80
0050 #define TEA5761_FRQSET_SEARCH_MODE 0x40
0051
0052
0053
0054
0055
0056
0057
0058
0059
0060
0061 #define TEA5761_TNCTRL_PUPD_0 0x40
0062 #define TEA5761_TNCTRL_BLIM 0X20
0063 #define TEA5761_TNCTRL_SWPM 0x10
0064 #define TEA5761_TNCTRL_IFCTC 0x08
0065 #define TEA5761_TNCTRL_AFM 0x04
0066 #define TEA5761_TNCTRL_SMUTE 0x02
0067 #define TEA5761_TNCTRL_SNC 0x01
0068
0069
0070
0071 #define TEA5761_TNCTRL_MU 0x80
0072 #define TEA5761_TNCTRL_SSL_1 0x40
0073 #define TEA5761_TNCTRL_SSL_0 0x20
0074 #define TEA5761_TNCTRL_HLSI 0x10
0075 #define TEA5761_TNCTRL_MST 0x08
0076 #define TEA5761_TNCTRL_SWP 0x04
0077 #define TEA5761_TNCTRL_DTC 0x02
0078 #define TEA5761_TNCTRL_AHLSI 0x01
0079
0080
0081
0082
0083
0084
0085
0086
0087
0088
0089
0090
0091 #define TEA5761_TUNCHECK_IF_MASK 0x7e
0092 #define TEA5761_TUNCHECK_TUNTO 0x01
0093
0094
0095 #define TEA5761_TUNCHECK_LEV_MASK 0xf0
0096 #define TEA5761_TUNCHECK_LD 0x08
0097 #define TEA5761_TUNCHECK_STEREO 0x04
0098
0099
0100
0101
0102
0103
0104
0105
0106 #define TEA5767_MANID_VERSION_MASK 0xf0
0107 #define TEA5767_MANID_ID_MSB_MASK 0x0f
0108
0109
0110
0111 #define TEA5767_MANID_ID_LSB_MASK 0xfe
0112 #define TEA5767_MANID_IDAV 0x01
0113
0114
0115
0116
0117
0118
0119
0120
0121
0122 #define FREQ_OFFSET 0
0123 static void tea5761_status_dump(unsigned char *buffer)
0124 {
0125 unsigned int div, frq;
0126
0127 div = ((buffer[2] & 0x3f) << 8) | buffer[3];
0128
0129 frq = 1000 * (div * 32768 / 1000 + FREQ_OFFSET + 225) / 4;
0130
0131 printk(KERN_INFO "tea5761: Frequency %d.%03d KHz (divider = 0x%04x)\n",
0132 frq / 1000, frq % 1000, div);
0133 }
0134
0135
0136 static int __set_radio_freq(struct dvb_frontend *fe,
0137 unsigned int freq,
0138 bool mono)
0139 {
0140 struct tea5761_priv *priv = fe->tuner_priv;
0141 unsigned int frq = freq;
0142 unsigned char buffer[7] = {0, 0, 0, 0, 0, 0, 0 };
0143 unsigned div;
0144 int rc;
0145
0146 tuner_dbg("radio freq counter %d\n", frq);
0147
0148 if (priv->standby) {
0149 tuner_dbg("TEA5761 set to standby mode\n");
0150 buffer[5] |= TEA5761_TNCTRL_MU;
0151 } else {
0152 buffer[4] |= TEA5761_TNCTRL_PUPD_0;
0153 }
0154
0155
0156 if (mono) {
0157 tuner_dbg("TEA5761 set to mono\n");
0158 buffer[5] |= TEA5761_TNCTRL_MST;
0159 } else {
0160 tuner_dbg("TEA5761 set to stereo\n");
0161 }
0162
0163 div = (1000 * (frq * 4 / 16 + 700 + 225) ) >> 15;
0164 buffer[1] = (div >> 8) & 0x3f;
0165 buffer[2] = div & 0xff;
0166
0167 if (debug)
0168 tea5761_status_dump(buffer);
0169
0170 if (7 != (rc = tuner_i2c_xfer_send(&priv->i2c_props, buffer, 7)))
0171 tuner_warn("i2c i/o error: rc == %d (should be 5)\n", rc);
0172
0173 priv->frequency = frq * 125 / 2;
0174
0175 return 0;
0176 }
0177
0178 static int set_radio_freq(struct dvb_frontend *fe,
0179 struct analog_parameters *params)
0180 {
0181 struct tea5761_priv *priv = fe->analog_demod_priv;
0182
0183 priv->standby = false;
0184
0185 return __set_radio_freq(fe, params->frequency,
0186 params->audmode == V4L2_TUNER_MODE_MONO);
0187 }
0188
0189 static int set_radio_sleep(struct dvb_frontend *fe)
0190 {
0191 struct tea5761_priv *priv = fe->analog_demod_priv;
0192
0193 priv->standby = true;
0194
0195 return __set_radio_freq(fe, priv->frequency, false);
0196 }
0197
0198 static int tea5761_read_status(struct dvb_frontend *fe, char *buffer)
0199 {
0200 struct tea5761_priv *priv = fe->tuner_priv;
0201 int rc;
0202
0203 memset(buffer, 0, 16);
0204 if (16 != (rc = tuner_i2c_xfer_recv(&priv->i2c_props, buffer, 16))) {
0205 tuner_warn("i2c i/o error: rc == %d (should be 16)\n", rc);
0206 return -EREMOTEIO;
0207 }
0208
0209 return 0;
0210 }
0211
0212 static inline int tea5761_signal(struct dvb_frontend *fe, const char *buffer)
0213 {
0214 struct tea5761_priv *priv = fe->tuner_priv;
0215
0216 int signal = ((buffer[9] & TEA5761_TUNCHECK_LEV_MASK) << (13 - 4));
0217
0218 tuner_dbg("Signal strength: %d\n", signal);
0219
0220 return signal;
0221 }
0222
0223 static inline int tea5761_stereo(struct dvb_frontend *fe, const char *buffer)
0224 {
0225 struct tea5761_priv *priv = fe->tuner_priv;
0226
0227 int stereo = buffer[9] & TEA5761_TUNCHECK_STEREO;
0228
0229 tuner_dbg("Radio ST GET = %02x\n", stereo);
0230
0231 return (stereo ? V4L2_TUNER_SUB_STEREO : 0);
0232 }
0233
0234 static int tea5761_get_status(struct dvb_frontend *fe, u32 *status)
0235 {
0236 unsigned char buffer[16];
0237
0238 *status = 0;
0239
0240 if (0 == tea5761_read_status(fe, buffer)) {
0241 if (tea5761_signal(fe, buffer))
0242 *status = TUNER_STATUS_LOCKED;
0243 if (tea5761_stereo(fe, buffer))
0244 *status |= TUNER_STATUS_STEREO;
0245 }
0246
0247 return 0;
0248 }
0249
0250 static int tea5761_get_rf_strength(struct dvb_frontend *fe, u16 *strength)
0251 {
0252 unsigned char buffer[16];
0253
0254 *strength = 0;
0255
0256 if (0 == tea5761_read_status(fe, buffer))
0257 *strength = tea5761_signal(fe, buffer);
0258
0259 return 0;
0260 }
0261
0262 int tea5761_autodetection(struct i2c_adapter* i2c_adap, u8 i2c_addr)
0263 {
0264 unsigned char buffer[16];
0265 int rc;
0266 struct tuner_i2c_props i2c = { .adap = i2c_adap, .addr = i2c_addr };
0267
0268 if (16 != (rc = tuner_i2c_xfer_recv(&i2c, buffer, 16))) {
0269 printk(KERN_WARNING "it is not a TEA5761. Received %i chars.\n", rc);
0270 return -EINVAL;
0271 }
0272
0273 if ((buffer[13] != 0x2b) || (buffer[14] != 0x57) || (buffer[15] != 0x061)) {
0274 printk(KERN_WARNING "Manufacturer ID= 0x%02x, Chip ID = %02x%02x. It is not a TEA5761\n",
0275 buffer[13], buffer[14], buffer[15]);
0276 return -EINVAL;
0277 }
0278 printk(KERN_WARNING "tea5761: TEA%02x%02x detected. Manufacturer ID= 0x%02x\n",
0279 buffer[14], buffer[15], buffer[13]);
0280
0281 return 0;
0282 }
0283
0284 static void tea5761_release(struct dvb_frontend *fe)
0285 {
0286 kfree(fe->tuner_priv);
0287 fe->tuner_priv = NULL;
0288 }
0289
0290 static int tea5761_get_frequency(struct dvb_frontend *fe, u32 *frequency)
0291 {
0292 struct tea5761_priv *priv = fe->tuner_priv;
0293 *frequency = priv->frequency;
0294 return 0;
0295 }
0296
0297 static const struct dvb_tuner_ops tea5761_tuner_ops = {
0298 .info = {
0299 .name = "tea5761",
0300 },
0301 .set_analog_params = set_radio_freq,
0302 .sleep = set_radio_sleep,
0303 .release = tea5761_release,
0304 .get_frequency = tea5761_get_frequency,
0305 .get_status = tea5761_get_status,
0306 .get_rf_strength = tea5761_get_rf_strength,
0307 };
0308
0309 struct dvb_frontend *tea5761_attach(struct dvb_frontend *fe,
0310 struct i2c_adapter* i2c_adap,
0311 u8 i2c_addr)
0312 {
0313 struct tea5761_priv *priv = NULL;
0314
0315 if (tea5761_autodetection(i2c_adap, i2c_addr) != 0)
0316 return NULL;
0317
0318 priv = kzalloc(sizeof(struct tea5761_priv), GFP_KERNEL);
0319 if (priv == NULL)
0320 return NULL;
0321 fe->tuner_priv = priv;
0322
0323 priv->i2c_props.addr = i2c_addr;
0324 priv->i2c_props.adap = i2c_adap;
0325 priv->i2c_props.name = "tea5761";
0326
0327 memcpy(&fe->ops.tuner_ops, &tea5761_tuner_ops,
0328 sizeof(struct dvb_tuner_ops));
0329
0330 tuner_info("type set to %s\n", "Philips TEA5761HN FM Radio");
0331
0332 return fe;
0333 }
0334
0335
0336 EXPORT_SYMBOL_GPL(tea5761_attach);
0337 EXPORT_SYMBOL_GPL(tea5761_autodetection);
0338
0339 MODULE_DESCRIPTION("Philips TEA5761 FM tuner driver");
0340 MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@kernel.org>");
0341 MODULE_LICENSE("GPL v2");