1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2005-2006 Micronas USA Inc. |
4 | */ |
5 | |
6 | #include <linux/module.h> |
7 | #include <linux/init.h> |
8 | #include <linux/i2c.h> |
9 | #include <linux/videodev2.h> |
10 | #include <media/tuner.h> |
11 | #include <media/v4l2-common.h> |
12 | #include <media/v4l2-ioctl.h> |
13 | #include <media/v4l2-device.h> |
14 | #include <linux/slab.h> |
15 | |
16 | MODULE_DESCRIPTION("sony-btf-mpx driver" ); |
17 | MODULE_LICENSE("GPL v2" ); |
18 | |
19 | static int debug; |
20 | module_param(debug, int, 0644); |
21 | MODULE_PARM_DESC(debug, "debug level 0=off(default) 1=on" ); |
22 | |
23 | /* #define MPX_DEBUG */ |
24 | |
25 | /* |
26 | * Note: |
27 | * |
28 | * AS(IF/MPX) pin: LOW HIGH/OPEN |
29 | * IF/MPX address: 0x42/0x40 0x43/0x44 |
30 | */ |
31 | |
32 | |
33 | static int force_mpx_mode = -1; |
34 | module_param(force_mpx_mode, int, 0644); |
35 | |
36 | struct sony_btf_mpx { |
37 | struct v4l2_subdev sd; |
38 | int mpxmode; |
39 | u32 audmode; |
40 | }; |
41 | |
42 | static inline struct sony_btf_mpx *to_state(struct v4l2_subdev *sd) |
43 | { |
44 | return container_of(sd, struct sony_btf_mpx, sd); |
45 | } |
46 | |
47 | static int mpx_write(struct i2c_client *client, int dev, int addr, int val) |
48 | { |
49 | u8 buffer[5]; |
50 | struct i2c_msg msg; |
51 | |
52 | buffer[0] = dev; |
53 | buffer[1] = addr >> 8; |
54 | buffer[2] = addr & 0xff; |
55 | buffer[3] = val >> 8; |
56 | buffer[4] = val & 0xff; |
57 | msg.addr = client->addr; |
58 | msg.flags = 0; |
59 | msg.len = 5; |
60 | msg.buf = buffer; |
61 | i2c_transfer(adap: client->adapter, msgs: &msg, num: 1); |
62 | return 0; |
63 | } |
64 | |
65 | /* |
66 | * MPX register values for the BTF-PG472Z: |
67 | * |
68 | * FM_ NICAM_ SCART_ |
69 | * MODUS SOURCE ACB PRESCAL PRESCAL PRESCAL SYSTEM VOLUME |
70 | * 10/0030 12/0008 12/0013 12/000E 12/0010 12/0000 10/0020 12/0000 |
71 | * --------------------------------------------------------------- |
72 | * Auto 1003 0020 0100 2603 5000 XXXX 0001 7500 |
73 | * |
74 | * B/G |
75 | * Mono 1003 0020 0100 2603 5000 XXXX 0003 7500 |
76 | * A2 1003 0020 0100 2601 5000 XXXX 0003 7500 |
77 | * NICAM 1003 0120 0100 2603 5000 XXXX 0008 7500 |
78 | * |
79 | * I |
80 | * Mono 1003 0020 0100 2603 7900 XXXX 000A 7500 |
81 | * NICAM 1003 0120 0100 2603 7900 XXXX 000A 7500 |
82 | * |
83 | * D/K |
84 | * Mono 1003 0020 0100 2603 5000 XXXX 0004 7500 |
85 | * A2-1 1003 0020 0100 2601 5000 XXXX 0004 7500 |
86 | * A2-2 1003 0020 0100 2601 5000 XXXX 0005 7500 |
87 | * A2-3 1003 0020 0100 2601 5000 XXXX 0007 7500 |
88 | * NICAM 1003 0120 0100 2603 5000 XXXX 000B 7500 |
89 | * |
90 | * L/L' |
91 | * Mono 0003 0200 0100 7C03 5000 2200 0009 7500 |
92 | * NICAM 0003 0120 0100 7C03 5000 XXXX 0009 7500 |
93 | * |
94 | * M |
95 | * Mono 1003 0200 0100 2B03 5000 2B00 0002 7500 |
96 | * |
97 | * For Asia, replace the 0x26XX in FM_PRESCALE with 0x14XX. |
98 | * |
99 | * Bilingual selection in A2/NICAM: |
100 | * |
101 | * High byte of SOURCE Left chan Right chan |
102 | * 0x01 MAIN SUB |
103 | * 0x03 MAIN MAIN |
104 | * 0x04 SUB SUB |
105 | * |
106 | * Force mono in NICAM by setting the high byte of SOURCE to 0x02 (L/L') or |
107 | * 0x00 (all other bands). Force mono in A2 with FMONO_A2: |
108 | * |
109 | * FMONO_A2 |
110 | * 10/0022 |
111 | * -------- |
112 | * Forced mono ON 07F0 |
113 | * Forced mono OFF 0190 |
114 | */ |
115 | |
116 | static const struct { |
117 | enum { AUD_MONO, AUD_A2, AUD_NICAM, AUD_NICAM_L } audio_mode; |
118 | u16 modus; |
119 | u16 source; |
120 | u16 acb; |
121 | u16 fm_prescale; |
122 | u16 nicam_prescale; |
123 | u16 scart_prescale; |
124 | u16 system; |
125 | u16 volume; |
126 | } mpx_audio_modes[] = { |
127 | /* Auto */ { AUD_MONO, 0x1003, 0x0020, 0x0100, 0x2603, |
128 | 0x5000, 0x0000, 0x0001, 0x7500 }, |
129 | /* B/G Mono */ { AUD_MONO, 0x1003, 0x0020, 0x0100, 0x2603, |
130 | 0x5000, 0x0000, 0x0003, 0x7500 }, |
131 | /* B/G A2 */ { AUD_A2, 0x1003, 0x0020, 0x0100, 0x2601, |
132 | 0x5000, 0x0000, 0x0003, 0x7500 }, |
133 | /* B/G NICAM */ { AUD_NICAM, 0x1003, 0x0120, 0x0100, 0x2603, |
134 | 0x5000, 0x0000, 0x0008, 0x7500 }, |
135 | /* I Mono */ { AUD_MONO, 0x1003, 0x0020, 0x0100, 0x2603, |
136 | 0x7900, 0x0000, 0x000A, 0x7500 }, |
137 | /* I NICAM */ { AUD_NICAM, 0x1003, 0x0120, 0x0100, 0x2603, |
138 | 0x7900, 0x0000, 0x000A, 0x7500 }, |
139 | /* D/K Mono */ { AUD_MONO, 0x1003, 0x0020, 0x0100, 0x2603, |
140 | 0x5000, 0x0000, 0x0004, 0x7500 }, |
141 | /* D/K A2-1 */ { AUD_A2, 0x1003, 0x0020, 0x0100, 0x2601, |
142 | 0x5000, 0x0000, 0x0004, 0x7500 }, |
143 | /* D/K A2-2 */ { AUD_A2, 0x1003, 0x0020, 0x0100, 0x2601, |
144 | 0x5000, 0x0000, 0x0005, 0x7500 }, |
145 | /* D/K A2-3 */ { AUD_A2, 0x1003, 0x0020, 0x0100, 0x2601, |
146 | 0x5000, 0x0000, 0x0007, 0x7500 }, |
147 | /* D/K NICAM */ { AUD_NICAM, 0x1003, 0x0120, 0x0100, 0x2603, |
148 | 0x5000, 0x0000, 0x000B, 0x7500 }, |
149 | /* L/L' Mono */ { AUD_MONO, 0x0003, 0x0200, 0x0100, 0x7C03, |
150 | 0x5000, 0x2200, 0x0009, 0x7500 }, |
151 | /* L/L' NICAM */{ AUD_NICAM_L, 0x0003, 0x0120, 0x0100, 0x7C03, |
152 | 0x5000, 0x0000, 0x0009, 0x7500 }, |
153 | }; |
154 | |
155 | #define MPX_NUM_MODES ARRAY_SIZE(mpx_audio_modes) |
156 | |
157 | static int mpx_setup(struct sony_btf_mpx *t) |
158 | { |
159 | struct i2c_client *client = v4l2_get_subdevdata(sd: &t->sd); |
160 | u16 source = 0; |
161 | u8 buffer[3]; |
162 | struct i2c_msg msg; |
163 | int mode = t->mpxmode; |
164 | |
165 | /* reset MPX */ |
166 | buffer[0] = 0x00; |
167 | buffer[1] = 0x80; |
168 | buffer[2] = 0x00; |
169 | msg.addr = client->addr; |
170 | msg.flags = 0; |
171 | msg.len = 3; |
172 | msg.buf = buffer; |
173 | i2c_transfer(adap: client->adapter, msgs: &msg, num: 1); |
174 | buffer[1] = 0x00; |
175 | i2c_transfer(adap: client->adapter, msgs: &msg, num: 1); |
176 | |
177 | if (t->audmode != V4L2_TUNER_MODE_MONO) |
178 | mode++; |
179 | |
180 | if (mpx_audio_modes[mode].audio_mode != AUD_MONO) { |
181 | switch (t->audmode) { |
182 | case V4L2_TUNER_MODE_MONO: |
183 | switch (mpx_audio_modes[mode].audio_mode) { |
184 | case AUD_A2: |
185 | source = mpx_audio_modes[mode].source; |
186 | break; |
187 | case AUD_NICAM: |
188 | source = 0x0000; |
189 | break; |
190 | case AUD_NICAM_L: |
191 | source = 0x0200; |
192 | break; |
193 | default: |
194 | break; |
195 | } |
196 | break; |
197 | case V4L2_TUNER_MODE_STEREO: |
198 | source = mpx_audio_modes[mode].source; |
199 | break; |
200 | case V4L2_TUNER_MODE_LANG1: |
201 | source = 0x0300; |
202 | break; |
203 | case V4L2_TUNER_MODE_LANG2: |
204 | source = 0x0400; |
205 | break; |
206 | } |
207 | source |= mpx_audio_modes[mode].source & 0x00ff; |
208 | } else |
209 | source = mpx_audio_modes[mode].source; |
210 | |
211 | mpx_write(client, dev: 0x10, addr: 0x0030, val: mpx_audio_modes[mode].modus); |
212 | mpx_write(client, dev: 0x12, addr: 0x0008, val: source); |
213 | mpx_write(client, dev: 0x12, addr: 0x0013, val: mpx_audio_modes[mode].acb); |
214 | mpx_write(client, dev: 0x12, addr: 0x000e, |
215 | val: mpx_audio_modes[mode].fm_prescale); |
216 | mpx_write(client, dev: 0x12, addr: 0x0010, |
217 | val: mpx_audio_modes[mode].nicam_prescale); |
218 | mpx_write(client, dev: 0x12, addr: 0x000d, |
219 | val: mpx_audio_modes[mode].scart_prescale); |
220 | mpx_write(client, dev: 0x10, addr: 0x0020, val: mpx_audio_modes[mode].system); |
221 | mpx_write(client, dev: 0x12, addr: 0x0000, val: mpx_audio_modes[mode].volume); |
222 | if (mpx_audio_modes[mode].audio_mode == AUD_A2) |
223 | mpx_write(client, dev: 0x10, addr: 0x0022, |
224 | val: t->audmode == V4L2_TUNER_MODE_MONO ? 0x07f0 : 0x0190); |
225 | |
226 | #ifdef MPX_DEBUG |
227 | { |
228 | u8 buf1[3], buf2[2]; |
229 | struct i2c_msg msgs[2]; |
230 | |
231 | v4l2_info(client, |
232 | "MPX registers: %04x %04x %04x %04x %04x %04x %04x %04x\n" , |
233 | mpx_audio_modes[mode].modus, |
234 | source, |
235 | mpx_audio_modes[mode].acb, |
236 | mpx_audio_modes[mode].fm_prescale, |
237 | mpx_audio_modes[mode].nicam_prescale, |
238 | mpx_audio_modes[mode].scart_prescale, |
239 | mpx_audio_modes[mode].system, |
240 | mpx_audio_modes[mode].volume); |
241 | buf1[0] = 0x11; |
242 | buf1[1] = 0x00; |
243 | buf1[2] = 0x7e; |
244 | msgs[0].addr = client->addr; |
245 | msgs[0].flags = 0; |
246 | msgs[0].len = 3; |
247 | msgs[0].buf = buf1; |
248 | msgs[1].addr = client->addr; |
249 | msgs[1].flags = I2C_M_RD; |
250 | msgs[1].len = 2; |
251 | msgs[1].buf = buf2; |
252 | i2c_transfer(client->adapter, msgs, 2); |
253 | v4l2_info(client, "MPX system: %02x%02x\n" , |
254 | buf2[0], buf2[1]); |
255 | buf1[0] = 0x11; |
256 | buf1[1] = 0x02; |
257 | buf1[2] = 0x00; |
258 | i2c_transfer(client->adapter, msgs, 2); |
259 | v4l2_info(client, "MPX status: %02x%02x\n" , |
260 | buf2[0], buf2[1]); |
261 | } |
262 | #endif |
263 | return 0; |
264 | } |
265 | |
266 | |
267 | static int sony_btf_mpx_s_std(struct v4l2_subdev *sd, v4l2_std_id std) |
268 | { |
269 | struct sony_btf_mpx *t = to_state(sd); |
270 | int default_mpx_mode = 0; |
271 | |
272 | if (std & V4L2_STD_PAL_BG) |
273 | default_mpx_mode = 1; |
274 | else if (std & V4L2_STD_PAL_I) |
275 | default_mpx_mode = 4; |
276 | else if (std & V4L2_STD_PAL_DK) |
277 | default_mpx_mode = 6; |
278 | else if (std & V4L2_STD_SECAM_L) |
279 | default_mpx_mode = 11; |
280 | |
281 | if (default_mpx_mode != t->mpxmode) { |
282 | t->mpxmode = default_mpx_mode; |
283 | mpx_setup(t); |
284 | } |
285 | return 0; |
286 | } |
287 | |
288 | static int sony_btf_mpx_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt) |
289 | { |
290 | struct sony_btf_mpx *t = to_state(sd); |
291 | |
292 | vt->capability = V4L2_TUNER_CAP_NORM | |
293 | V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LANG1 | |
294 | V4L2_TUNER_CAP_LANG2; |
295 | vt->rxsubchans = V4L2_TUNER_SUB_MONO | |
296 | V4L2_TUNER_SUB_STEREO | V4L2_TUNER_SUB_LANG1 | |
297 | V4L2_TUNER_SUB_LANG2; |
298 | vt->audmode = t->audmode; |
299 | return 0; |
300 | } |
301 | |
302 | static int sony_btf_mpx_s_tuner(struct v4l2_subdev *sd, const struct v4l2_tuner *vt) |
303 | { |
304 | struct sony_btf_mpx *t = to_state(sd); |
305 | |
306 | if (vt->type != V4L2_TUNER_ANALOG_TV) |
307 | return -EINVAL; |
308 | |
309 | if (vt->audmode != t->audmode) { |
310 | t->audmode = vt->audmode; |
311 | mpx_setup(t); |
312 | } |
313 | return 0; |
314 | } |
315 | |
316 | /* --------------------------------------------------------------------------*/ |
317 | |
318 | static const struct v4l2_subdev_tuner_ops sony_btf_mpx_tuner_ops = { |
319 | .s_tuner = sony_btf_mpx_s_tuner, |
320 | .g_tuner = sony_btf_mpx_g_tuner, |
321 | }; |
322 | |
323 | static const struct v4l2_subdev_video_ops sony_btf_mpx_video_ops = { |
324 | .s_std = sony_btf_mpx_s_std, |
325 | }; |
326 | |
327 | static const struct v4l2_subdev_ops sony_btf_mpx_ops = { |
328 | .tuner = &sony_btf_mpx_tuner_ops, |
329 | .video = &sony_btf_mpx_video_ops, |
330 | }; |
331 | |
332 | /* --------------------------------------------------------------------------*/ |
333 | |
334 | static int sony_btf_mpx_probe(struct i2c_client *client) |
335 | { |
336 | struct sony_btf_mpx *t; |
337 | struct v4l2_subdev *sd; |
338 | |
339 | if (!i2c_check_functionality(adap: client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) |
340 | return -ENODEV; |
341 | |
342 | v4l_info(client, "chip found @ 0x%x (%s)\n" , |
343 | client->addr << 1, client->adapter->name); |
344 | |
345 | t = devm_kzalloc(dev: &client->dev, size: sizeof(*t), GFP_KERNEL); |
346 | if (t == NULL) |
347 | return -ENOMEM; |
348 | |
349 | sd = &t->sd; |
350 | v4l2_i2c_subdev_init(sd, client, ops: &sony_btf_mpx_ops); |
351 | |
352 | /* Initialize sony_btf_mpx */ |
353 | t->mpxmode = 0; |
354 | t->audmode = V4L2_TUNER_MODE_STEREO; |
355 | |
356 | return 0; |
357 | } |
358 | |
359 | static void sony_btf_mpx_remove(struct i2c_client *client) |
360 | { |
361 | struct v4l2_subdev *sd = i2c_get_clientdata(client); |
362 | |
363 | v4l2_device_unregister_subdev(sd); |
364 | } |
365 | |
366 | /* ----------------------------------------------------------------------- */ |
367 | |
368 | static const struct i2c_device_id sony_btf_mpx_id[] = { |
369 | { "sony-btf-mpx" , 0 }, |
370 | { } |
371 | }; |
372 | MODULE_DEVICE_TABLE(i2c, sony_btf_mpx_id); |
373 | |
374 | static struct i2c_driver sony_btf_mpx_driver = { |
375 | .driver = { |
376 | .name = "sony-btf-mpx" , |
377 | }, |
378 | .probe = sony_btf_mpx_probe, |
379 | .remove = sony_btf_mpx_remove, |
380 | .id_table = sony_btf_mpx_id, |
381 | }; |
382 | module_i2c_driver(sony_btf_mpx_driver); |
383 | |