18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * tef6862.c Philips TEF6862 Car Radio Enhanced Selectivity Tuner 48c2ecf20Sopenharmony_ci * Copyright (c) 2009 Intel Corporation 58c2ecf20Sopenharmony_ci */ 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci#include <linux/module.h> 88c2ecf20Sopenharmony_ci#include <linux/init.h> 98c2ecf20Sopenharmony_ci#include <linux/errno.h> 108c2ecf20Sopenharmony_ci#include <linux/kernel.h> 118c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 128c2ecf20Sopenharmony_ci#include <linux/i2c.h> 138c2ecf20Sopenharmony_ci#include <linux/slab.h> 148c2ecf20Sopenharmony_ci#include <media/v4l2-ioctl.h> 158c2ecf20Sopenharmony_ci#include <media/v4l2-device.h> 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci#define DRIVER_NAME "tef6862" 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci#define FREQ_MUL 16000 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#define TEF6862_LO_FREQ (875U * FREQ_MUL / 10) 228c2ecf20Sopenharmony_ci#define TEF6862_HI_FREQ (108U * FREQ_MUL) 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci/* Write mode sub addresses */ 258c2ecf20Sopenharmony_ci#define WM_SUB_BANDWIDTH 0x0 268c2ecf20Sopenharmony_ci#define WM_SUB_PLLM 0x1 278c2ecf20Sopenharmony_ci#define WM_SUB_PLLL 0x2 288c2ecf20Sopenharmony_ci#define WM_SUB_DAA 0x3 298c2ecf20Sopenharmony_ci#define WM_SUB_AGC 0x4 308c2ecf20Sopenharmony_ci#define WM_SUB_BAND 0x5 318c2ecf20Sopenharmony_ci#define WM_SUB_CONTROL 0x6 328c2ecf20Sopenharmony_ci#define WM_SUB_LEVEL 0x7 338c2ecf20Sopenharmony_ci#define WM_SUB_IFCF 0x8 348c2ecf20Sopenharmony_ci#define WM_SUB_IFCAP 0x9 358c2ecf20Sopenharmony_ci#define WM_SUB_ACD 0xA 368c2ecf20Sopenharmony_ci#define WM_SUB_TEST 0xF 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci/* Different modes of the MSA register */ 398c2ecf20Sopenharmony_ci#define MSA_MODE_BUFFER 0x0 408c2ecf20Sopenharmony_ci#define MSA_MODE_PRESET 0x1 418c2ecf20Sopenharmony_ci#define MSA_MODE_SEARCH 0x2 428c2ecf20Sopenharmony_ci#define MSA_MODE_AF_UPDATE 0x3 438c2ecf20Sopenharmony_ci#define MSA_MODE_JUMP 0x4 448c2ecf20Sopenharmony_ci#define MSA_MODE_CHECK 0x5 458c2ecf20Sopenharmony_ci#define MSA_MODE_LOAD 0x6 468c2ecf20Sopenharmony_ci#define MSA_MODE_END 0x7 478c2ecf20Sopenharmony_ci#define MSA_MODE_SHIFT 5 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_cistruct tef6862_state { 508c2ecf20Sopenharmony_ci struct v4l2_subdev sd; 518c2ecf20Sopenharmony_ci unsigned long freq; 528c2ecf20Sopenharmony_ci}; 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_cistatic inline struct tef6862_state *to_state(struct v4l2_subdev *sd) 558c2ecf20Sopenharmony_ci{ 568c2ecf20Sopenharmony_ci return container_of(sd, struct tef6862_state, sd); 578c2ecf20Sopenharmony_ci} 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_cistatic u16 tef6862_sigstr(struct i2c_client *client) 608c2ecf20Sopenharmony_ci{ 618c2ecf20Sopenharmony_ci u8 buf[4]; 628c2ecf20Sopenharmony_ci int err = i2c_master_recv(client, buf, sizeof(buf)); 638c2ecf20Sopenharmony_ci if (err == sizeof(buf)) 648c2ecf20Sopenharmony_ci return buf[3] << 8; 658c2ecf20Sopenharmony_ci return 0; 668c2ecf20Sopenharmony_ci} 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_cistatic int tef6862_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *v) 698c2ecf20Sopenharmony_ci{ 708c2ecf20Sopenharmony_ci if (v->index > 0) 718c2ecf20Sopenharmony_ci return -EINVAL; 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci /* only support FM for now */ 748c2ecf20Sopenharmony_ci strscpy(v->name, "FM", sizeof(v->name)); 758c2ecf20Sopenharmony_ci v->type = V4L2_TUNER_RADIO; 768c2ecf20Sopenharmony_ci v->rangelow = TEF6862_LO_FREQ; 778c2ecf20Sopenharmony_ci v->rangehigh = TEF6862_HI_FREQ; 788c2ecf20Sopenharmony_ci v->rxsubchans = V4L2_TUNER_SUB_MONO; 798c2ecf20Sopenharmony_ci v->capability = V4L2_TUNER_CAP_LOW; 808c2ecf20Sopenharmony_ci v->audmode = V4L2_TUNER_MODE_STEREO; 818c2ecf20Sopenharmony_ci v->signal = tef6862_sigstr(v4l2_get_subdevdata(sd)); 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci return 0; 848c2ecf20Sopenharmony_ci} 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_cistatic int tef6862_s_tuner(struct v4l2_subdev *sd, const struct v4l2_tuner *v) 878c2ecf20Sopenharmony_ci{ 888c2ecf20Sopenharmony_ci return v->index ? -EINVAL : 0; 898c2ecf20Sopenharmony_ci} 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_cistatic int tef6862_s_frequency(struct v4l2_subdev *sd, const struct v4l2_frequency *f) 928c2ecf20Sopenharmony_ci{ 938c2ecf20Sopenharmony_ci struct tef6862_state *state = to_state(sd); 948c2ecf20Sopenharmony_ci struct i2c_client *client = v4l2_get_subdevdata(sd); 958c2ecf20Sopenharmony_ci unsigned freq = f->frequency; 968c2ecf20Sopenharmony_ci u16 pll; 978c2ecf20Sopenharmony_ci u8 i2cmsg[3]; 988c2ecf20Sopenharmony_ci int err; 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci if (f->tuner != 0) 1018c2ecf20Sopenharmony_ci return -EINVAL; 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci freq = clamp(freq, TEF6862_LO_FREQ, TEF6862_HI_FREQ); 1048c2ecf20Sopenharmony_ci pll = 1964 + ((freq - TEF6862_LO_FREQ) * 20) / FREQ_MUL; 1058c2ecf20Sopenharmony_ci i2cmsg[0] = (MSA_MODE_PRESET << MSA_MODE_SHIFT) | WM_SUB_PLLM; 1068c2ecf20Sopenharmony_ci i2cmsg[1] = (pll >> 8) & 0xff; 1078c2ecf20Sopenharmony_ci i2cmsg[2] = pll & 0xff; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci err = i2c_master_send(client, i2cmsg, sizeof(i2cmsg)); 1108c2ecf20Sopenharmony_ci if (err != sizeof(i2cmsg)) 1118c2ecf20Sopenharmony_ci return err < 0 ? err : -EIO; 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci state->freq = freq; 1148c2ecf20Sopenharmony_ci return 0; 1158c2ecf20Sopenharmony_ci} 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_cistatic int tef6862_g_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f) 1188c2ecf20Sopenharmony_ci{ 1198c2ecf20Sopenharmony_ci struct tef6862_state *state = to_state(sd); 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci if (f->tuner != 0) 1228c2ecf20Sopenharmony_ci return -EINVAL; 1238c2ecf20Sopenharmony_ci f->type = V4L2_TUNER_RADIO; 1248c2ecf20Sopenharmony_ci f->frequency = state->freq; 1258c2ecf20Sopenharmony_ci return 0; 1268c2ecf20Sopenharmony_ci} 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_cistatic const struct v4l2_subdev_tuner_ops tef6862_tuner_ops = { 1298c2ecf20Sopenharmony_ci .g_tuner = tef6862_g_tuner, 1308c2ecf20Sopenharmony_ci .s_tuner = tef6862_s_tuner, 1318c2ecf20Sopenharmony_ci .s_frequency = tef6862_s_frequency, 1328c2ecf20Sopenharmony_ci .g_frequency = tef6862_g_frequency, 1338c2ecf20Sopenharmony_ci}; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_cistatic const struct v4l2_subdev_ops tef6862_ops = { 1368c2ecf20Sopenharmony_ci .tuner = &tef6862_tuner_ops, 1378c2ecf20Sopenharmony_ci}; 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci/* 1408c2ecf20Sopenharmony_ci * Generic i2c probe 1418c2ecf20Sopenharmony_ci * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' 1428c2ecf20Sopenharmony_ci */ 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_cistatic int tef6862_probe(struct i2c_client *client, 1458c2ecf20Sopenharmony_ci const struct i2c_device_id *id) 1468c2ecf20Sopenharmony_ci{ 1478c2ecf20Sopenharmony_ci struct tef6862_state *state; 1488c2ecf20Sopenharmony_ci struct v4l2_subdev *sd; 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci /* Check if the adapter supports the needed features */ 1518c2ecf20Sopenharmony_ci if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) 1528c2ecf20Sopenharmony_ci return -EIO; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci v4l_info(client, "chip found @ 0x%02x (%s)\n", 1558c2ecf20Sopenharmony_ci client->addr << 1, client->adapter->name); 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci state = kzalloc(sizeof(struct tef6862_state), GFP_KERNEL); 1588c2ecf20Sopenharmony_ci if (state == NULL) 1598c2ecf20Sopenharmony_ci return -ENOMEM; 1608c2ecf20Sopenharmony_ci state->freq = TEF6862_LO_FREQ; 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci sd = &state->sd; 1638c2ecf20Sopenharmony_ci v4l2_i2c_subdev_init(sd, client, &tef6862_ops); 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci return 0; 1668c2ecf20Sopenharmony_ci} 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_cistatic int tef6862_remove(struct i2c_client *client) 1698c2ecf20Sopenharmony_ci{ 1708c2ecf20Sopenharmony_ci struct v4l2_subdev *sd = i2c_get_clientdata(client); 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci v4l2_device_unregister_subdev(sd); 1738c2ecf20Sopenharmony_ci kfree(to_state(sd)); 1748c2ecf20Sopenharmony_ci return 0; 1758c2ecf20Sopenharmony_ci} 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_cistatic const struct i2c_device_id tef6862_id[] = { 1788c2ecf20Sopenharmony_ci {DRIVER_NAME, 0}, 1798c2ecf20Sopenharmony_ci {}, 1808c2ecf20Sopenharmony_ci}; 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, tef6862_id); 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_cistatic struct i2c_driver tef6862_driver = { 1858c2ecf20Sopenharmony_ci .driver = { 1868c2ecf20Sopenharmony_ci .name = DRIVER_NAME, 1878c2ecf20Sopenharmony_ci }, 1888c2ecf20Sopenharmony_ci .probe = tef6862_probe, 1898c2ecf20Sopenharmony_ci .remove = tef6862_remove, 1908c2ecf20Sopenharmony_ci .id_table = tef6862_id, 1918c2ecf20Sopenharmony_ci}; 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_cimodule_i2c_driver(tef6862_driver); 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("TEF6862 Car Radio Enhanced Selectivity Tuner"); 1968c2ecf20Sopenharmony_ciMODULE_AUTHOR("Mocean Laboratories"); 1978c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 198