xref: /openbmc/linux/drivers/media/radio/tef6862.c (revision aaeb31c0)
11802d0beSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2eea85b0aSRichard Röjfors /*
3eea85b0aSRichard Röjfors  * tef6862.c Philips TEF6862 Car Radio Enhanced Selectivity Tuner
4eea85b0aSRichard Röjfors  * Copyright (c) 2009 Intel Corporation
5eea85b0aSRichard Röjfors  */
6eea85b0aSRichard Röjfors 
7eea85b0aSRichard Röjfors #include <linux/module.h>
8eea85b0aSRichard Röjfors #include <linux/init.h>
9eea85b0aSRichard Röjfors #include <linux/errno.h>
10eea85b0aSRichard Röjfors #include <linux/kernel.h>
11eea85b0aSRichard Röjfors #include <linux/interrupt.h>
12eea85b0aSRichard Röjfors #include <linux/i2c.h>
135a0e3ad6STejun Heo #include <linux/slab.h>
14eea85b0aSRichard Röjfors #include <media/v4l2-ioctl.h>
15eea85b0aSRichard Röjfors #include <media/v4l2-device.h>
16eea85b0aSRichard Röjfors 
17eea85b0aSRichard Röjfors #define DRIVER_NAME "tef6862"
18eea85b0aSRichard Röjfors 
19eea85b0aSRichard Röjfors #define FREQ_MUL 16000
20eea85b0aSRichard Röjfors 
21fa915996SHans Verkuil #define TEF6862_LO_FREQ (875U * FREQ_MUL / 10)
22fa915996SHans Verkuil #define TEF6862_HI_FREQ (108U * FREQ_MUL)
23eea85b0aSRichard Röjfors 
24eea85b0aSRichard Röjfors /* Write mode sub addresses */
25eea85b0aSRichard Röjfors #define WM_SUB_BANDWIDTH	0x0
26eea85b0aSRichard Röjfors #define WM_SUB_PLLM		0x1
27eea85b0aSRichard Röjfors #define WM_SUB_PLLL		0x2
28eea85b0aSRichard Röjfors #define WM_SUB_DAA		0x3
29eea85b0aSRichard Röjfors #define WM_SUB_AGC		0x4
30eea85b0aSRichard Röjfors #define WM_SUB_BAND		0x5
31eea85b0aSRichard Röjfors #define WM_SUB_CONTROL		0x6
32eea85b0aSRichard Röjfors #define WM_SUB_LEVEL		0x7
33eea85b0aSRichard Röjfors #define WM_SUB_IFCF		0x8
34eea85b0aSRichard Röjfors #define WM_SUB_IFCAP		0x9
35eea85b0aSRichard Röjfors #define WM_SUB_ACD		0xA
36eea85b0aSRichard Röjfors #define WM_SUB_TEST		0xF
37eea85b0aSRichard Röjfors 
38eea85b0aSRichard Röjfors /* Different modes of the MSA register */
395f27ca41SMauro Carvalho Chehab #define MSA_MODE_BUFFER		0x0
405f27ca41SMauro Carvalho Chehab #define MSA_MODE_PRESET		0x1
415f27ca41SMauro Carvalho Chehab #define MSA_MODE_SEARCH		0x2
425f27ca41SMauro Carvalho Chehab #define MSA_MODE_AF_UPDATE	0x3
435f27ca41SMauro Carvalho Chehab #define MSA_MODE_JUMP		0x4
445f27ca41SMauro Carvalho Chehab #define MSA_MODE_CHECK		0x5
455f27ca41SMauro Carvalho Chehab #define MSA_MODE_LOAD		0x6
465f27ca41SMauro Carvalho Chehab #define MSA_MODE_END		0x7
475f27ca41SMauro Carvalho Chehab #define MSA_MODE_SHIFT		5
48eea85b0aSRichard Röjfors 
49eea85b0aSRichard Röjfors struct tef6862_state {
50eea85b0aSRichard Röjfors 	struct v4l2_subdev sd;
51eea85b0aSRichard Röjfors 	unsigned long freq;
52eea85b0aSRichard Röjfors };
53eea85b0aSRichard Röjfors 
to_state(struct v4l2_subdev * sd)54eea85b0aSRichard Röjfors static inline struct tef6862_state *to_state(struct v4l2_subdev *sd)
55eea85b0aSRichard Röjfors {
56eea85b0aSRichard Röjfors 	return container_of(sd, struct tef6862_state, sd);
57eea85b0aSRichard Röjfors }
58eea85b0aSRichard Röjfors 
tef6862_sigstr(struct i2c_client * client)59eea85b0aSRichard Röjfors static u16 tef6862_sigstr(struct i2c_client *client)
60eea85b0aSRichard Röjfors {
61eea85b0aSRichard Röjfors 	u8 buf[4];
62eea85b0aSRichard Röjfors 	int err = i2c_master_recv(client, buf, sizeof(buf));
63eea85b0aSRichard Röjfors 	if (err == sizeof(buf))
64eea85b0aSRichard Röjfors 		return buf[3] << 8;
65eea85b0aSRichard Röjfors 	return 0;
66eea85b0aSRichard Röjfors }
67eea85b0aSRichard Röjfors 
tef6862_g_tuner(struct v4l2_subdev * sd,struct v4l2_tuner * v)68eea85b0aSRichard Röjfors static int tef6862_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *v)
69eea85b0aSRichard Röjfors {
70eea85b0aSRichard Röjfors 	if (v->index > 0)
71eea85b0aSRichard Röjfors 		return -EINVAL;
72eea85b0aSRichard Röjfors 
73eea85b0aSRichard Röjfors 	/* only support FM for now */
74c0decac1SMauro Carvalho Chehab 	strscpy(v->name, "FM", sizeof(v->name));
75eea85b0aSRichard Röjfors 	v->type = V4L2_TUNER_RADIO;
76eea85b0aSRichard Röjfors 	v->rangelow = TEF6862_LO_FREQ;
77eea85b0aSRichard Röjfors 	v->rangehigh = TEF6862_HI_FREQ;
78eea85b0aSRichard Röjfors 	v->rxsubchans = V4L2_TUNER_SUB_MONO;
79eea85b0aSRichard Röjfors 	v->capability = V4L2_TUNER_CAP_LOW;
80eea85b0aSRichard Röjfors 	v->audmode = V4L2_TUNER_MODE_STEREO;
81eea85b0aSRichard Röjfors 	v->signal = tef6862_sigstr(v4l2_get_subdevdata(sd));
82eea85b0aSRichard Röjfors 
83eea85b0aSRichard Röjfors 	return 0;
84eea85b0aSRichard Röjfors }
85eea85b0aSRichard Röjfors 
tef6862_s_tuner(struct v4l2_subdev * sd,const struct v4l2_tuner * v)862f73c7c5SHans Verkuil static int tef6862_s_tuner(struct v4l2_subdev *sd, const struct v4l2_tuner *v)
87eea85b0aSRichard Röjfors {
88eea85b0aSRichard Röjfors 	return v->index ? -EINVAL : 0;
89eea85b0aSRichard Röjfors }
90eea85b0aSRichard Röjfors 
tef6862_s_frequency(struct v4l2_subdev * sd,const struct v4l2_frequency * f)91b530a447SHans Verkuil static int tef6862_s_frequency(struct v4l2_subdev *sd, const struct v4l2_frequency *f)
92eea85b0aSRichard Röjfors {
93eea85b0aSRichard Röjfors 	struct tef6862_state *state = to_state(sd);
94eea85b0aSRichard Röjfors 	struct i2c_client *client = v4l2_get_subdevdata(sd);
95fa915996SHans Verkuil 	unsigned freq = f->frequency;
96eea85b0aSRichard Röjfors 	u16 pll;
97eea85b0aSRichard Röjfors 	u8 i2cmsg[3];
98eea85b0aSRichard Röjfors 	int err;
99eea85b0aSRichard Röjfors 
100eea85b0aSRichard Röjfors 	if (f->tuner != 0)
101eea85b0aSRichard Röjfors 		return -EINVAL;
102eea85b0aSRichard Röjfors 
1039ba6a91fSHans Verkuil 	freq = clamp(freq, TEF6862_LO_FREQ, TEF6862_HI_FREQ);
104fa915996SHans Verkuil 	pll = 1964 + ((freq - TEF6862_LO_FREQ) * 20) / FREQ_MUL;
1055f27ca41SMauro Carvalho Chehab 	i2cmsg[0] = (MSA_MODE_PRESET << MSA_MODE_SHIFT) | WM_SUB_PLLM;
106eea85b0aSRichard Röjfors 	i2cmsg[1] = (pll >> 8) & 0xff;
107eea85b0aSRichard Röjfors 	i2cmsg[2] = pll & 0xff;
108eea85b0aSRichard Röjfors 
109eea85b0aSRichard Röjfors 	err = i2c_master_send(client, i2cmsg, sizeof(i2cmsg));
110bd93b3adSAxel Lin 	if (err != sizeof(i2cmsg))
111bd93b3adSAxel Lin 		return err < 0 ? err : -EIO;
112bd93b3adSAxel Lin 
113fa915996SHans Verkuil 	state->freq = freq;
114bd93b3adSAxel Lin 	return 0;
115eea85b0aSRichard Röjfors }
116eea85b0aSRichard Röjfors 
tef6862_g_frequency(struct v4l2_subdev * sd,struct v4l2_frequency * f)117eea85b0aSRichard Röjfors static int tef6862_g_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f)
118eea85b0aSRichard Röjfors {
119eea85b0aSRichard Röjfors 	struct tef6862_state *state = to_state(sd);
120eea85b0aSRichard Röjfors 
121eea85b0aSRichard Röjfors 	if (f->tuner != 0)
122eea85b0aSRichard Röjfors 		return -EINVAL;
123eea85b0aSRichard Röjfors 	f->type = V4L2_TUNER_RADIO;
124eea85b0aSRichard Röjfors 	f->frequency = state->freq;
125eea85b0aSRichard Röjfors 	return 0;
126eea85b0aSRichard Röjfors }
127eea85b0aSRichard Röjfors 
128eea85b0aSRichard Röjfors static const struct v4l2_subdev_tuner_ops tef6862_tuner_ops = {
129eea85b0aSRichard Röjfors 	.g_tuner = tef6862_g_tuner,
130eea85b0aSRichard Röjfors 	.s_tuner = tef6862_s_tuner,
131eea85b0aSRichard Röjfors 	.s_frequency = tef6862_s_frequency,
132eea85b0aSRichard Röjfors 	.g_frequency = tef6862_g_frequency,
133eea85b0aSRichard Röjfors };
134eea85b0aSRichard Röjfors 
135eea85b0aSRichard Röjfors static const struct v4l2_subdev_ops tef6862_ops = {
136eea85b0aSRichard Röjfors 	.tuner = &tef6862_tuner_ops,
137eea85b0aSRichard Röjfors };
138eea85b0aSRichard Röjfors 
139eea85b0aSRichard Röjfors /*
140eea85b0aSRichard Röjfors  * Generic i2c probe
141eea85b0aSRichard Röjfors  * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1'
142eea85b0aSRichard Röjfors  */
143eea85b0aSRichard Röjfors 
tef6862_probe(struct i2c_client * client)144d73a6a43SUwe Kleine-König static int tef6862_probe(struct i2c_client *client)
145eea85b0aSRichard Röjfors {
146eea85b0aSRichard Röjfors 	struct tef6862_state *state;
147eea85b0aSRichard Röjfors 	struct v4l2_subdev *sd;
148eea85b0aSRichard Röjfors 
149eea85b0aSRichard Röjfors 	/* Check if the adapter supports the needed features */
150eea85b0aSRichard Röjfors 	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
151eea85b0aSRichard Röjfors 		return -EIO;
152eea85b0aSRichard Röjfors 
153eea85b0aSRichard Röjfors 	v4l_info(client, "chip found @ 0x%02x (%s)\n",
154eea85b0aSRichard Röjfors 			client->addr << 1, client->adapter->name);
155eea85b0aSRichard Röjfors 
15680845a33SHerton Ronaldo Krzesinski 	state = kzalloc(sizeof(struct tef6862_state), GFP_KERNEL);
157eea85b0aSRichard Röjfors 	if (state == NULL)
158eea85b0aSRichard Röjfors 		return -ENOMEM;
159eea85b0aSRichard Röjfors 	state->freq = TEF6862_LO_FREQ;
160eea85b0aSRichard Röjfors 
161eea85b0aSRichard Röjfors 	sd = &state->sd;
162eea85b0aSRichard Röjfors 	v4l2_i2c_subdev_init(sd, client, &tef6862_ops);
163eea85b0aSRichard Röjfors 
164eea85b0aSRichard Röjfors 	return 0;
165eea85b0aSRichard Röjfors }
166eea85b0aSRichard Röjfors 
tef6862_remove(struct i2c_client * client)167ed5c2f5fSUwe Kleine-König static void tef6862_remove(struct i2c_client *client)
168eea85b0aSRichard Röjfors {
169eea85b0aSRichard Röjfors 	struct v4l2_subdev *sd = i2c_get_clientdata(client);
170eea85b0aSRichard Röjfors 
171eea85b0aSRichard Röjfors 	v4l2_device_unregister_subdev(sd);
172eea85b0aSRichard Röjfors 	kfree(to_state(sd));
173eea85b0aSRichard Röjfors }
174eea85b0aSRichard Röjfors 
175eea85b0aSRichard Röjfors static const struct i2c_device_id tef6862_id[] = {
176eea85b0aSRichard Röjfors 	{DRIVER_NAME, 0},
177eea85b0aSRichard Röjfors 	{},
178eea85b0aSRichard Röjfors };
179eea85b0aSRichard Röjfors 
180eea85b0aSRichard Röjfors MODULE_DEVICE_TABLE(i2c, tef6862_id);
181eea85b0aSRichard Röjfors 
182eea85b0aSRichard Röjfors static struct i2c_driver tef6862_driver = {
183eea85b0aSRichard Röjfors 	.driver = {
184eea85b0aSRichard Röjfors 		.name	= DRIVER_NAME,
185eea85b0aSRichard Röjfors 	},
186*aaeb31c0SUwe Kleine-König 	.probe		= tef6862_probe,
1874c62e976SGreg Kroah-Hartman 	.remove		= tef6862_remove,
188eea85b0aSRichard Röjfors 	.id_table	= tef6862_id,
189eea85b0aSRichard Röjfors };
190eea85b0aSRichard Röjfors 
191c6e8d86fSAxel Lin module_i2c_driver(tef6862_driver);
192eea85b0aSRichard Röjfors 
193eea85b0aSRichard Röjfors MODULE_DESCRIPTION("TEF6862 Car Radio Enhanced Selectivity Tuner");
194eea85b0aSRichard Röjfors MODULE_AUTHOR("Mocean Laboratories");
195eea85b0aSRichard Röjfors MODULE_LICENSE("GPL v2");
196