]>
Commit | Line | Data |
---|---|---|
eea85b0a RR |
1 | /* |
2 | * tef6862.c Philips TEF6862 Car Radio Enhanced Selectivity Tuner | |
3 | * Copyright (c) 2009 Intel Corporation | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License version 2 as | |
7 | * published by the Free Software Foundation. | |
8 | * | |
9 | * This program is distributed in the hope that it will be useful, | |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | * GNU General Public License for more details. | |
13 | * | |
14 | * You should have received a copy of the GNU General Public License | |
15 | * along with this program; if not, write to the Free Software | |
16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
17 | */ | |
18 | ||
19 | #include <linux/module.h> | |
20 | #include <linux/init.h> | |
21 | #include <linux/errno.h> | |
22 | #include <linux/kernel.h> | |
23 | #include <linux/interrupt.h> | |
24 | #include <linux/i2c.h> | |
5a0e3ad6 | 25 | #include <linux/slab.h> |
eea85b0a RR |
26 | #include <media/v4l2-ioctl.h> |
27 | #include <media/v4l2-device.h> | |
eea85b0a RR |
28 | |
29 | #define DRIVER_NAME "tef6862" | |
30 | ||
31 | #define FREQ_MUL 16000 | |
32 | ||
fa915996 HV |
33 | #define TEF6862_LO_FREQ (875U * FREQ_MUL / 10) |
34 | #define TEF6862_HI_FREQ (108U * FREQ_MUL) | |
eea85b0a RR |
35 | |
36 | /* Write mode sub addresses */ | |
37 | #define WM_SUB_BANDWIDTH 0x0 | |
38 | #define WM_SUB_PLLM 0x1 | |
39 | #define WM_SUB_PLLL 0x2 | |
40 | #define WM_SUB_DAA 0x3 | |
41 | #define WM_SUB_AGC 0x4 | |
42 | #define WM_SUB_BAND 0x5 | |
43 | #define WM_SUB_CONTROL 0x6 | |
44 | #define WM_SUB_LEVEL 0x7 | |
45 | #define WM_SUB_IFCF 0x8 | |
46 | #define WM_SUB_IFCAP 0x9 | |
47 | #define WM_SUB_ACD 0xA | |
48 | #define WM_SUB_TEST 0xF | |
49 | ||
50 | /* Different modes of the MSA register */ | |
5f27ca41 MCC |
51 | #define MSA_MODE_BUFFER 0x0 |
52 | #define MSA_MODE_PRESET 0x1 | |
53 | #define MSA_MODE_SEARCH 0x2 | |
54 | #define MSA_MODE_AF_UPDATE 0x3 | |
55 | #define MSA_MODE_JUMP 0x4 | |
56 | #define MSA_MODE_CHECK 0x5 | |
57 | #define MSA_MODE_LOAD 0x6 | |
58 | #define MSA_MODE_END 0x7 | |
59 | #define MSA_MODE_SHIFT 5 | |
eea85b0a RR |
60 | |
61 | struct tef6862_state { | |
62 | struct v4l2_subdev sd; | |
63 | unsigned long freq; | |
64 | }; | |
65 | ||
66 | static inline struct tef6862_state *to_state(struct v4l2_subdev *sd) | |
67 | { | |
68 | return container_of(sd, struct tef6862_state, sd); | |
69 | } | |
70 | ||
71 | static u16 tef6862_sigstr(struct i2c_client *client) | |
72 | { | |
73 | u8 buf[4]; | |
74 | int err = i2c_master_recv(client, buf, sizeof(buf)); | |
75 | if (err == sizeof(buf)) | |
76 | return buf[3] << 8; | |
77 | return 0; | |
78 | } | |
79 | ||
80 | static int tef6862_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *v) | |
81 | { | |
82 | if (v->index > 0) | |
83 | return -EINVAL; | |
84 | ||
85 | /* only support FM for now */ | |
86 | strlcpy(v->name, "FM", sizeof(v->name)); | |
87 | v->type = V4L2_TUNER_RADIO; | |
88 | v->rangelow = TEF6862_LO_FREQ; | |
89 | v->rangehigh = TEF6862_HI_FREQ; | |
90 | v->rxsubchans = V4L2_TUNER_SUB_MONO; | |
91 | v->capability = V4L2_TUNER_CAP_LOW; | |
92 | v->audmode = V4L2_TUNER_MODE_STEREO; | |
93 | v->signal = tef6862_sigstr(v4l2_get_subdevdata(sd)); | |
94 | ||
95 | return 0; | |
96 | } | |
97 | ||
2f73c7c5 | 98 | static int tef6862_s_tuner(struct v4l2_subdev *sd, const struct v4l2_tuner *v) |
eea85b0a RR |
99 | { |
100 | return v->index ? -EINVAL : 0; | |
101 | } | |
102 | ||
b530a447 | 103 | static int tef6862_s_frequency(struct v4l2_subdev *sd, const struct v4l2_frequency *f) |
eea85b0a RR |
104 | { |
105 | struct tef6862_state *state = to_state(sd); | |
106 | struct i2c_client *client = v4l2_get_subdevdata(sd); | |
fa915996 | 107 | unsigned freq = f->frequency; |
eea85b0a RR |
108 | u16 pll; |
109 | u8 i2cmsg[3]; | |
110 | int err; | |
111 | ||
112 | if (f->tuner != 0) | |
113 | return -EINVAL; | |
114 | ||
9ba6a91f | 115 | freq = clamp(freq, TEF6862_LO_FREQ, TEF6862_HI_FREQ); |
fa915996 | 116 | pll = 1964 + ((freq - TEF6862_LO_FREQ) * 20) / FREQ_MUL; |
5f27ca41 | 117 | i2cmsg[0] = (MSA_MODE_PRESET << MSA_MODE_SHIFT) | WM_SUB_PLLM; |
eea85b0a RR |
118 | i2cmsg[1] = (pll >> 8) & 0xff; |
119 | i2cmsg[2] = pll & 0xff; | |
120 | ||
121 | err = i2c_master_send(client, i2cmsg, sizeof(i2cmsg)); | |
bd93b3ad AL |
122 | if (err != sizeof(i2cmsg)) |
123 | return err < 0 ? err : -EIO; | |
124 | ||
fa915996 | 125 | state->freq = freq; |
bd93b3ad | 126 | return 0; |
eea85b0a RR |
127 | } |
128 | ||
129 | static int tef6862_g_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f) | |
130 | { | |
131 | struct tef6862_state *state = to_state(sd); | |
132 | ||
133 | if (f->tuner != 0) | |
134 | return -EINVAL; | |
135 | f->type = V4L2_TUNER_RADIO; | |
136 | f->frequency = state->freq; | |
137 | return 0; | |
138 | } | |
139 | ||
eea85b0a RR |
140 | static const struct v4l2_subdev_tuner_ops tef6862_tuner_ops = { |
141 | .g_tuner = tef6862_g_tuner, | |
142 | .s_tuner = tef6862_s_tuner, | |
143 | .s_frequency = tef6862_s_frequency, | |
144 | .g_frequency = tef6862_g_frequency, | |
145 | }; | |
146 | ||
eea85b0a | 147 | static const struct v4l2_subdev_ops tef6862_ops = { |
eea85b0a RR |
148 | .tuner = &tef6862_tuner_ops, |
149 | }; | |
150 | ||
151 | /* | |
152 | * Generic i2c probe | |
153 | * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1' | |
154 | */ | |
155 | ||
4c62e976 GKH |
156 | static int tef6862_probe(struct i2c_client *client, |
157 | const struct i2c_device_id *id) | |
eea85b0a RR |
158 | { |
159 | struct tef6862_state *state; | |
160 | struct v4l2_subdev *sd; | |
161 | ||
162 | /* Check if the adapter supports the needed features */ | |
163 | if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) | |
164 | return -EIO; | |
165 | ||
166 | v4l_info(client, "chip found @ 0x%02x (%s)\n", | |
167 | client->addr << 1, client->adapter->name); | |
168 | ||
80845a33 | 169 | state = kzalloc(sizeof(struct tef6862_state), GFP_KERNEL); |
eea85b0a RR |
170 | if (state == NULL) |
171 | return -ENOMEM; | |
172 | state->freq = TEF6862_LO_FREQ; | |
173 | ||
174 | sd = &state->sd; | |
175 | v4l2_i2c_subdev_init(sd, client, &tef6862_ops); | |
176 | ||
177 | return 0; | |
178 | } | |
179 | ||
4c62e976 | 180 | static int tef6862_remove(struct i2c_client *client) |
eea85b0a RR |
181 | { |
182 | struct v4l2_subdev *sd = i2c_get_clientdata(client); | |
183 | ||
184 | v4l2_device_unregister_subdev(sd); | |
185 | kfree(to_state(sd)); | |
186 | return 0; | |
187 | } | |
188 | ||
189 | static const struct i2c_device_id tef6862_id[] = { | |
190 | {DRIVER_NAME, 0}, | |
191 | {}, | |
192 | }; | |
193 | ||
194 | MODULE_DEVICE_TABLE(i2c, tef6862_id); | |
195 | ||
196 | static struct i2c_driver tef6862_driver = { | |
197 | .driver = { | |
198 | .owner = THIS_MODULE, | |
199 | .name = DRIVER_NAME, | |
200 | }, | |
201 | .probe = tef6862_probe, | |
4c62e976 | 202 | .remove = tef6862_remove, |
eea85b0a RR |
203 | .id_table = tef6862_id, |
204 | }; | |
205 | ||
c6e8d86f | 206 | module_i2c_driver(tef6862_driver); |
eea85b0a RR |
207 | |
208 | MODULE_DESCRIPTION("TEF6862 Car Radio Enhanced Selectivity Tuner"); | |
209 | MODULE_AUTHOR("Mocean Laboratories"); | |
210 | MODULE_LICENSE("GPL v2"); |