Commit | Line | Data |
---|---|---|
8e8e69d6 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
ed4a8fe8 AS |
2 | /* |
3 | * drivers/mfd/si476x-cmd.c -- Subroutines implementing command | |
4 | * protocol of si476x series of chips | |
5 | * | |
6 | * Copyright (C) 2012 Innovative Converged Devices(ICD) | |
7 | * Copyright (C) 2013 Andrey Smirnov | |
8 | * | |
9 | * Author: Andrey Smirnov <andrew.smirnov@gmail.com> | |
ed4a8fe8 AS |
10 | */ |
11 | ||
12 | #include <linux/module.h> | |
13 | #include <linux/completion.h> | |
14 | #include <linux/delay.h> | |
15 | #include <linux/atomic.h> | |
16 | #include <linux/i2c.h> | |
17 | #include <linux/device.h> | |
18 | #include <linux/gpio.h> | |
19 | #include <linux/videodev2.h> | |
20 | ||
21 | #include <linux/mfd/si476x-core.h> | |
22 | ||
151978bf GU |
23 | #include <asm/unaligned.h> |
24 | ||
ed4a8fe8 AS |
25 | #define msb(x) ((u8)((u16) x >> 8)) |
26 | #define lsb(x) ((u8)((u16) x & 0x00FF)) | |
27 | ||
28 | ||
29 | ||
30 | #define CMD_POWER_UP 0x01 | |
31 | #define CMD_POWER_UP_A10_NRESP 1 | |
32 | #define CMD_POWER_UP_A10_NARGS 5 | |
33 | ||
34 | #define CMD_POWER_UP_A20_NRESP 1 | |
35 | #define CMD_POWER_UP_A20_NARGS 5 | |
36 | ||
37 | #define POWER_UP_DELAY_MS 110 | |
38 | ||
39 | #define CMD_POWER_DOWN 0x11 | |
40 | #define CMD_POWER_DOWN_A10_NRESP 1 | |
41 | ||
42 | #define CMD_POWER_DOWN_A20_NRESP 1 | |
43 | #define CMD_POWER_DOWN_A20_NARGS 1 | |
44 | ||
45 | #define CMD_FUNC_INFO 0x12 | |
46 | #define CMD_FUNC_INFO_NRESP 7 | |
47 | ||
48 | #define CMD_SET_PROPERTY 0x13 | |
49 | #define CMD_SET_PROPERTY_NARGS 5 | |
50 | #define CMD_SET_PROPERTY_NRESP 1 | |
51 | ||
52 | #define CMD_GET_PROPERTY 0x14 | |
53 | #define CMD_GET_PROPERTY_NARGS 3 | |
54 | #define CMD_GET_PROPERTY_NRESP 4 | |
55 | ||
56 | #define CMD_AGC_STATUS 0x17 | |
57 | #define CMD_AGC_STATUS_NRESP_A10 2 | |
58 | #define CMD_AGC_STATUS_NRESP_A20 6 | |
59 | ||
60 | #define PIN_CFG_BYTE(x) (0x7F & (x)) | |
61 | #define CMD_DIG_AUDIO_PIN_CFG 0x18 | |
62 | #define CMD_DIG_AUDIO_PIN_CFG_NARGS 4 | |
63 | #define CMD_DIG_AUDIO_PIN_CFG_NRESP 5 | |
64 | ||
65 | #define CMD_ZIF_PIN_CFG 0x19 | |
66 | #define CMD_ZIF_PIN_CFG_NARGS 4 | |
67 | #define CMD_ZIF_PIN_CFG_NRESP 5 | |
68 | ||
69 | #define CMD_IC_LINK_GPO_CTL_PIN_CFG 0x1A | |
70 | #define CMD_IC_LINK_GPO_CTL_PIN_CFG_NARGS 4 | |
71 | #define CMD_IC_LINK_GPO_CTL_PIN_CFG_NRESP 5 | |
72 | ||
73 | #define CMD_ANA_AUDIO_PIN_CFG 0x1B | |
74 | #define CMD_ANA_AUDIO_PIN_CFG_NARGS 1 | |
75 | #define CMD_ANA_AUDIO_PIN_CFG_NRESP 2 | |
76 | ||
77 | #define CMD_INTB_PIN_CFG 0x1C | |
78 | #define CMD_INTB_PIN_CFG_NARGS 2 | |
79 | #define CMD_INTB_PIN_CFG_A10_NRESP 6 | |
80 | #define CMD_INTB_PIN_CFG_A20_NRESP 3 | |
81 | ||
82 | #define CMD_FM_TUNE_FREQ 0x30 | |
83 | #define CMD_FM_TUNE_FREQ_A10_NARGS 5 | |
84 | #define CMD_FM_TUNE_FREQ_A20_NARGS 3 | |
85 | #define CMD_FM_TUNE_FREQ_NRESP 1 | |
86 | ||
87 | #define CMD_FM_RSQ_STATUS 0x32 | |
88 | ||
89 | #define CMD_FM_RSQ_STATUS_A10_NARGS 1 | |
90 | #define CMD_FM_RSQ_STATUS_A10_NRESP 17 | |
91 | #define CMD_FM_RSQ_STATUS_A30_NARGS 1 | |
92 | #define CMD_FM_RSQ_STATUS_A30_NRESP 23 | |
93 | ||
94 | ||
95 | #define CMD_FM_SEEK_START 0x31 | |
96 | #define CMD_FM_SEEK_START_NARGS 1 | |
97 | #define CMD_FM_SEEK_START_NRESP 1 | |
98 | ||
99 | #define CMD_FM_RDS_STATUS 0x36 | |
100 | #define CMD_FM_RDS_STATUS_NARGS 1 | |
101 | #define CMD_FM_RDS_STATUS_NRESP 16 | |
102 | ||
103 | #define CMD_FM_RDS_BLOCKCOUNT 0x37 | |
104 | #define CMD_FM_RDS_BLOCKCOUNT_NARGS 1 | |
105 | #define CMD_FM_RDS_BLOCKCOUNT_NRESP 8 | |
106 | ||
107 | #define CMD_FM_PHASE_DIVERSITY 0x38 | |
108 | #define CMD_FM_PHASE_DIVERSITY_NARGS 1 | |
109 | #define CMD_FM_PHASE_DIVERSITY_NRESP 1 | |
110 | ||
111 | #define CMD_FM_PHASE_DIV_STATUS 0x39 | |
112 | #define CMD_FM_PHASE_DIV_STATUS_NRESP 2 | |
113 | ||
114 | #define CMD_AM_TUNE_FREQ 0x40 | |
115 | #define CMD_AM_TUNE_FREQ_NARGS 3 | |
116 | #define CMD_AM_TUNE_FREQ_NRESP 1 | |
117 | ||
118 | #define CMD_AM_RSQ_STATUS 0x42 | |
119 | #define CMD_AM_RSQ_STATUS_NARGS 1 | |
120 | #define CMD_AM_RSQ_STATUS_NRESP 13 | |
121 | ||
122 | #define CMD_AM_SEEK_START 0x41 | |
123 | #define CMD_AM_SEEK_START_NARGS 1 | |
124 | #define CMD_AM_SEEK_START_NRESP 1 | |
125 | ||
126 | ||
127 | #define CMD_AM_ACF_STATUS 0x45 | |
128 | #define CMD_AM_ACF_STATUS_NRESP 6 | |
129 | #define CMD_AM_ACF_STATUS_NARGS 1 | |
130 | ||
131 | #define CMD_FM_ACF_STATUS 0x35 | |
132 | #define CMD_FM_ACF_STATUS_NRESP 8 | |
133 | #define CMD_FM_ACF_STATUS_NARGS 1 | |
134 | ||
135 | #define CMD_MAX_ARGS_COUNT (10) | |
136 | ||
137 | ||
138 | enum si476x_acf_status_report_bits { | |
139 | SI476X_ACF_BLEND_INT = (1 << 4), | |
140 | SI476X_ACF_HIBLEND_INT = (1 << 3), | |
141 | SI476X_ACF_HICUT_INT = (1 << 2), | |
142 | SI476X_ACF_CHBW_INT = (1 << 1), | |
143 | SI476X_ACF_SOFTMUTE_INT = (1 << 0), | |
144 | ||
145 | SI476X_ACF_SMUTE = (1 << 0), | |
b0222afa | 146 | SI476X_ACF_SMATTN = 0x1f, |
ed4a8fe8 AS |
147 | SI476X_ACF_PILOT = (1 << 7), |
148 | SI476X_ACF_STBLEND = ~SI476X_ACF_PILOT, | |
149 | }; | |
150 | ||
151 | enum si476x_agc_status_report_bits { | |
152 | SI476X_AGC_MXHI = (1 << 5), | |
153 | SI476X_AGC_MXLO = (1 << 4), | |
154 | SI476X_AGC_LNAHI = (1 << 3), | |
155 | SI476X_AGC_LNALO = (1 << 2), | |
156 | }; | |
157 | ||
158 | enum si476x_errors { | |
159 | SI476X_ERR_BAD_COMMAND = 0x10, | |
160 | SI476X_ERR_BAD_ARG1 = 0x11, | |
161 | SI476X_ERR_BAD_ARG2 = 0x12, | |
162 | SI476X_ERR_BAD_ARG3 = 0x13, | |
163 | SI476X_ERR_BAD_ARG4 = 0x14, | |
164 | SI476X_ERR_BUSY = 0x18, | |
165 | SI476X_ERR_BAD_INTERNAL_MEMORY = 0x20, | |
166 | SI476X_ERR_BAD_PATCH = 0x30, | |
167 | SI476X_ERR_BAD_BOOT_MODE = 0x31, | |
168 | SI476X_ERR_BAD_PROPERTY = 0x40, | |
169 | }; | |
170 | ||
171 | static int si476x_core_parse_and_nag_about_error(struct si476x_core *core) | |
172 | { | |
173 | int err; | |
174 | char *cause; | |
175 | u8 buffer[2]; | |
176 | ||
177 | if (core->revision != SI476X_REVISION_A10) { | |
178 | err = si476x_core_i2c_xfer(core, SI476X_I2C_RECV, | |
179 | buffer, sizeof(buffer)); | |
180 | if (err == sizeof(buffer)) { | |
181 | switch (buffer[1]) { | |
182 | case SI476X_ERR_BAD_COMMAND: | |
183 | cause = "Bad command"; | |
184 | err = -EINVAL; | |
185 | break; | |
186 | case SI476X_ERR_BAD_ARG1: | |
187 | cause = "Bad argument #1"; | |
188 | err = -EINVAL; | |
189 | break; | |
190 | case SI476X_ERR_BAD_ARG2: | |
191 | cause = "Bad argument #2"; | |
192 | err = -EINVAL; | |
193 | break; | |
194 | case SI476X_ERR_BAD_ARG3: | |
195 | cause = "Bad argument #3"; | |
196 | err = -EINVAL; | |
197 | break; | |
198 | case SI476X_ERR_BAD_ARG4: | |
199 | cause = "Bad argument #4"; | |
200 | err = -EINVAL; | |
201 | break; | |
202 | case SI476X_ERR_BUSY: | |
203 | cause = "Chip is busy"; | |
204 | err = -EBUSY; | |
205 | break; | |
206 | case SI476X_ERR_BAD_INTERNAL_MEMORY: | |
207 | cause = "Bad internal memory"; | |
208 | err = -EIO; | |
209 | break; | |
210 | case SI476X_ERR_BAD_PATCH: | |
211 | cause = "Bad patch"; | |
212 | err = -EINVAL; | |
213 | break; | |
214 | case SI476X_ERR_BAD_BOOT_MODE: | |
215 | cause = "Bad boot mode"; | |
216 | err = -EINVAL; | |
217 | break; | |
218 | case SI476X_ERR_BAD_PROPERTY: | |
219 | cause = "Bad property"; | |
220 | err = -EINVAL; | |
221 | break; | |
222 | default: | |
223 | cause = "Unknown"; | |
224 | err = -EIO; | |
225 | } | |
226 | ||
227 | dev_err(&core->client->dev, | |
228 | "[Chip error status]: %s\n", cause); | |
229 | } else { | |
230 | dev_err(&core->client->dev, | |
231 | "Failed to fetch error code\n"); | |
232 | err = (err >= 0) ? -EIO : err; | |
233 | } | |
234 | } else { | |
235 | err = -EIO; | |
236 | } | |
237 | ||
238 | return err; | |
239 | } | |
240 | ||
241 | /** | |
242 | * si476x_core_send_command() - sends a command to si476x and waits its | |
243 | * response | |
3c719388 | 244 | * @core: si476x_device structure for the device we are |
ed4a8fe8 AS |
245 | * communicating with |
246 | * @command: command id | |
247 | * @args: command arguments we are sending | |
248 | * @argn: actual size of @args | |
3c719388 LJ |
249 | * @resp: buffer to place the expected response from the device |
250 | * @respn: actual size of @resp | |
ed4a8fe8 AS |
251 | * @usecs: amount of time to wait before reading the response (in |
252 | * usecs) | |
253 | * | |
254 | * Function returns 0 on succsess and negative error code on | |
255 | * failure | |
256 | */ | |
257 | static int si476x_core_send_command(struct si476x_core *core, | |
258 | const u8 command, | |
259 | const u8 args[], | |
260 | const int argn, | |
261 | u8 resp[], | |
262 | const int respn, | |
263 | const int usecs) | |
264 | { | |
265 | struct i2c_client *client = core->client; | |
266 | int err; | |
267 | u8 data[CMD_MAX_ARGS_COUNT + 1]; | |
268 | ||
269 | if (argn > CMD_MAX_ARGS_COUNT) { | |
270 | err = -ENOMEM; | |
271 | goto exit; | |
272 | } | |
273 | ||
274 | if (!client->adapter) { | |
275 | err = -ENODEV; | |
276 | goto exit; | |
277 | } | |
278 | ||
279 | /* First send the command and its arguments */ | |
280 | data[0] = command; | |
281 | memcpy(&data[1], args, argn); | |
282 | dev_dbg(&client->dev, "Command:\n %*ph\n", argn + 1, data); | |
283 | ||
284 | err = si476x_core_i2c_xfer(core, SI476X_I2C_SEND, | |
285 | (char *) data, argn + 1); | |
286 | if (err != argn + 1) { | |
287 | dev_err(&core->client->dev, | |
288 | "Error while sending command 0x%02x\n", | |
289 | command); | |
290 | err = (err >= 0) ? -EIO : err; | |
291 | goto exit; | |
292 | } | |
293 | /* Set CTS to zero only after the command is send to avoid | |
294 | * possible racing conditions when working in polling mode */ | |
295 | atomic_set(&core->cts, 0); | |
296 | ||
297 | /* if (unlikely(command == CMD_POWER_DOWN) */ | |
298 | if (!wait_event_timeout(core->command, | |
299 | atomic_read(&core->cts), | |
300 | usecs_to_jiffies(usecs) + 1)) | |
301 | dev_warn(&core->client->dev, | |
302 | "(%s) [CMD 0x%02x] Answer timeout.\n", | |
303 | __func__, command); | |
304 | ||
305 | /* | |
306 | When working in polling mode, for some reason the tuner will | |
307 | report CTS bit as being set in the first status byte read, | |
308 | but all the consequtive ones will return zeros until the | |
309 | tuner is actually completed the POWER_UP command. To | |
310 | workaround that we wait for second CTS to be reported | |
311 | */ | |
312 | if (unlikely(!core->client->irq && command == CMD_POWER_UP)) { | |
313 | if (!wait_event_timeout(core->command, | |
314 | atomic_read(&core->cts), | |
315 | usecs_to_jiffies(usecs) + 1)) | |
316 | dev_warn(&core->client->dev, | |
317 | "(%s) Power up took too much time.\n", | |
318 | __func__); | |
319 | } | |
320 | ||
321 | /* Then get the response */ | |
322 | err = si476x_core_i2c_xfer(core, SI476X_I2C_RECV, resp, respn); | |
323 | if (err != respn) { | |
324 | dev_err(&core->client->dev, | |
325 | "Error while reading response for command 0x%02x\n", | |
326 | command); | |
327 | err = (err >= 0) ? -EIO : err; | |
328 | goto exit; | |
329 | } | |
330 | dev_dbg(&client->dev, "Response:\n %*ph\n", respn, resp); | |
331 | ||
332 | err = 0; | |
333 | ||
334 | if (resp[0] & SI476X_ERR) { | |
335 | dev_err(&core->client->dev, | |
336 | "[CMD 0x%02x] Chip set error flag\n", command); | |
337 | err = si476x_core_parse_and_nag_about_error(core); | |
338 | goto exit; | |
339 | } | |
340 | ||
341 | if (!(resp[0] & SI476X_CTS)) | |
342 | err = -EBUSY; | |
343 | exit: | |
344 | return err; | |
345 | } | |
346 | ||
347 | static int si476x_cmd_clear_stc(struct si476x_core *core) | |
348 | { | |
349 | int err; | |
350 | struct si476x_rsq_status_args args = { | |
351 | .primary = false, | |
352 | .rsqack = false, | |
353 | .attune = false, | |
354 | .cancel = false, | |
355 | .stcack = true, | |
356 | }; | |
357 | ||
358 | switch (core->power_up_parameters.func) { | |
359 | case SI476X_FUNC_FM_RECEIVER: | |
360 | err = si476x_core_cmd_fm_rsq_status(core, &args, NULL); | |
361 | break; | |
362 | case SI476X_FUNC_AM_RECEIVER: | |
363 | err = si476x_core_cmd_am_rsq_status(core, &args, NULL); | |
364 | break; | |
365 | default: | |
366 | err = -EINVAL; | |
367 | } | |
368 | ||
369 | return err; | |
370 | } | |
371 | ||
372 | static int si476x_cmd_tune_seek_freq(struct si476x_core *core, | |
373 | uint8_t cmd, | |
374 | const uint8_t args[], size_t argn, | |
375 | uint8_t *resp, size_t respn) | |
376 | { | |
377 | int err; | |
378 | ||
379 | ||
380 | atomic_set(&core->stc, 0); | |
381 | err = si476x_core_send_command(core, cmd, args, argn, resp, respn, | |
382 | SI476X_TIMEOUT_TUNE); | |
383 | if (!err) { | |
384 | wait_event_killable(core->tuning, | |
385 | atomic_read(&core->stc)); | |
386 | si476x_cmd_clear_stc(core); | |
387 | } | |
388 | ||
389 | return err; | |
390 | } | |
391 | ||
392 | /** | |
393 | * si476x_cmd_func_info() - send 'FUNC_INFO' command to the device | |
394 | * @core: device to send the command to | |
395 | * @info: struct si476x_func_info to fill all the information | |
396 | * returned by the command | |
397 | * | |
398 | * The command requests the firmware and patch version for currently | |
399 | * loaded firmware (dependent on the function of the device FM/AM/WB) | |
400 | * | |
401 | * Function returns 0 on succsess and negative error code on | |
402 | * failure | |
403 | */ | |
404 | int si476x_core_cmd_func_info(struct si476x_core *core, | |
405 | struct si476x_func_info *info) | |
406 | { | |
407 | int err; | |
408 | u8 resp[CMD_FUNC_INFO_NRESP]; | |
409 | ||
410 | err = si476x_core_send_command(core, CMD_FUNC_INFO, | |
411 | NULL, 0, | |
412 | resp, ARRAY_SIZE(resp), | |
413 | SI476X_DEFAULT_TIMEOUT); | |
414 | ||
415 | info->firmware.major = resp[1]; | |
416 | info->firmware.minor[0] = resp[2]; | |
417 | info->firmware.minor[1] = resp[3]; | |
418 | ||
419 | info->patch_id = ((u16) resp[4] << 8) | resp[5]; | |
420 | info->func = resp[6]; | |
421 | ||
422 | return err; | |
423 | } | |
424 | EXPORT_SYMBOL_GPL(si476x_core_cmd_func_info); | |
425 | ||
426 | /** | |
427 | * si476x_cmd_set_property() - send 'SET_PROPERTY' command to the device | |
428 | * @core: device to send the command to | |
429 | * @property: property address | |
430 | * @value: property value | |
431 | * | |
432 | * Function returns 0 on succsess and negative error code on | |
433 | * failure | |
434 | */ | |
435 | int si476x_core_cmd_set_property(struct si476x_core *core, | |
436 | u16 property, u16 value) | |
437 | { | |
438 | u8 resp[CMD_SET_PROPERTY_NRESP]; | |
439 | const u8 args[CMD_SET_PROPERTY_NARGS] = { | |
440 | 0x00, | |
441 | msb(property), | |
442 | lsb(property), | |
443 | msb(value), | |
444 | lsb(value), | |
445 | }; | |
446 | ||
447 | return si476x_core_send_command(core, CMD_SET_PROPERTY, | |
448 | args, ARRAY_SIZE(args), | |
449 | resp, ARRAY_SIZE(resp), | |
450 | SI476X_DEFAULT_TIMEOUT); | |
451 | } | |
452 | EXPORT_SYMBOL_GPL(si476x_core_cmd_set_property); | |
453 | ||
454 | /** | |
455 | * si476x_cmd_get_property() - send 'GET_PROPERTY' command to the device | |
456 | * @core: device to send the command to | |
457 | * @property: property address | |
458 | * | |
459 | * Function return the value of property as u16 on success or a | |
460 | * negative error on failure | |
461 | */ | |
462 | int si476x_core_cmd_get_property(struct si476x_core *core, u16 property) | |
463 | { | |
464 | int err; | |
465 | u8 resp[CMD_GET_PROPERTY_NRESP]; | |
466 | const u8 args[CMD_GET_PROPERTY_NARGS] = { | |
467 | 0x00, | |
468 | msb(property), | |
469 | lsb(property), | |
470 | }; | |
471 | ||
472 | err = si476x_core_send_command(core, CMD_GET_PROPERTY, | |
473 | args, ARRAY_SIZE(args), | |
474 | resp, ARRAY_SIZE(resp), | |
475 | SI476X_DEFAULT_TIMEOUT); | |
476 | if (err < 0) | |
477 | return err; | |
478 | else | |
151978bf | 479 | return get_unaligned_be16(resp + 2); |
ed4a8fe8 AS |
480 | } |
481 | EXPORT_SYMBOL_GPL(si476x_core_cmd_get_property); | |
482 | ||
483 | /** | |
484 | * si476x_cmd_dig_audio_pin_cfg() - send 'DIG_AUDIO_PIN_CFG' command to | |
485 | * the device | |
486 | * @core: device to send the command to | |
487 | * @dclk: DCLK pin function configuration: | |
488 | * #SI476X_DCLK_NOOP - do not modify the behaviour | |
489 | * #SI476X_DCLK_TRISTATE - put the pin in tristate condition, | |
490 | * enable 1MOhm pulldown | |
491 | * #SI476X_DCLK_DAUDIO - set the pin to be a part of digital | |
492 | * audio interface | |
493 | * @dfs: DFS pin function configuration: | |
494 | * #SI476X_DFS_NOOP - do not modify the behaviour | |
495 | * #SI476X_DFS_TRISTATE - put the pin in tristate condition, | |
496 | * enable 1MOhm pulldown | |
497 | * SI476X_DFS_DAUDIO - set the pin to be a part of digital | |
498 | * audio interface | |
499 | * @dout - DOUT pin function configuration: | |
500 | * SI476X_DOUT_NOOP - do not modify the behaviour | |
501 | * SI476X_DOUT_TRISTATE - put the pin in tristate condition, | |
502 | * enable 1MOhm pulldown | |
503 | * SI476X_DOUT_I2S_OUTPUT - set this pin to be digital out on I2S | |
504 | * port 1 | |
505 | * SI476X_DOUT_I2S_INPUT - set this pin to be digital in on I2S | |
506 | * port 1 | |
507 | * @xout - XOUT pin function configuration: | |
508 | * SI476X_XOUT_NOOP - do not modify the behaviour | |
509 | * SI476X_XOUT_TRISTATE - put the pin in tristate condition, | |
510 | * enable 1MOhm pulldown | |
511 | * SI476X_XOUT_I2S_INPUT - set this pin to be digital in on I2S | |
512 | * port 1 | |
513 | * SI476X_XOUT_MODE_SELECT - set this pin to be the input that | |
514 | * selects the mode of the I2S audio | |
515 | * combiner (analog or HD) | |
516 | * [SI4761/63/65/67 Only] | |
517 | * | |
518 | * Function returns 0 on success and negative error code on failure | |
519 | */ | |
520 | int si476x_core_cmd_dig_audio_pin_cfg(struct si476x_core *core, | |
521 | enum si476x_dclk_config dclk, | |
522 | enum si476x_dfs_config dfs, | |
523 | enum si476x_dout_config dout, | |
524 | enum si476x_xout_config xout) | |
525 | { | |
526 | u8 resp[CMD_DIG_AUDIO_PIN_CFG_NRESP]; | |
527 | const u8 args[CMD_DIG_AUDIO_PIN_CFG_NARGS] = { | |
528 | PIN_CFG_BYTE(dclk), | |
529 | PIN_CFG_BYTE(dfs), | |
530 | PIN_CFG_BYTE(dout), | |
531 | PIN_CFG_BYTE(xout), | |
532 | }; | |
533 | ||
534 | return si476x_core_send_command(core, CMD_DIG_AUDIO_PIN_CFG, | |
535 | args, ARRAY_SIZE(args), | |
536 | resp, ARRAY_SIZE(resp), | |
537 | SI476X_DEFAULT_TIMEOUT); | |
538 | } | |
539 | EXPORT_SYMBOL_GPL(si476x_core_cmd_dig_audio_pin_cfg); | |
540 | ||
541 | /** | |
542 | * si476x_cmd_zif_pin_cfg - send 'ZIF_PIN_CFG_COMMAND' | |
543 | * @core - device to send the command to | |
544 | * @iqclk - IQCL pin function configuration: | |
545 | * SI476X_IQCLK_NOOP - do not modify the behaviour | |
546 | * SI476X_IQCLK_TRISTATE - put the pin in tristate condition, | |
547 | * enable 1MOhm pulldown | |
548 | * SI476X_IQCLK_IQ - set pin to be a part of I/Q interace | |
549 | * in master mode | |
550 | * @iqfs - IQFS pin function configuration: | |
551 | * SI476X_IQFS_NOOP - do not modify the behaviour | |
552 | * SI476X_IQFS_TRISTATE - put the pin in tristate condition, | |
553 | * enable 1MOhm pulldown | |
554 | * SI476X_IQFS_IQ - set pin to be a part of I/Q interace | |
555 | * in master mode | |
556 | * @iout - IOUT pin function configuration: | |
557 | * SI476X_IOUT_NOOP - do not modify the behaviour | |
558 | * SI476X_IOUT_TRISTATE - put the pin in tristate condition, | |
559 | * enable 1MOhm pulldown | |
560 | * SI476X_IOUT_OUTPUT - set pin to be I out | |
561 | * @qout - QOUT pin function configuration: | |
562 | * SI476X_QOUT_NOOP - do not modify the behaviour | |
563 | * SI476X_QOUT_TRISTATE - put the pin in tristate condition, | |
564 | * enable 1MOhm pulldown | |
565 | * SI476X_QOUT_OUTPUT - set pin to be Q out | |
566 | * | |
567 | * Function returns 0 on success and negative error code on failure | |
568 | */ | |
569 | int si476x_core_cmd_zif_pin_cfg(struct si476x_core *core, | |
570 | enum si476x_iqclk_config iqclk, | |
571 | enum si476x_iqfs_config iqfs, | |
572 | enum si476x_iout_config iout, | |
573 | enum si476x_qout_config qout) | |
574 | { | |
575 | u8 resp[CMD_ZIF_PIN_CFG_NRESP]; | |
576 | const u8 args[CMD_ZIF_PIN_CFG_NARGS] = { | |
577 | PIN_CFG_BYTE(iqclk), | |
578 | PIN_CFG_BYTE(iqfs), | |
579 | PIN_CFG_BYTE(iout), | |
580 | PIN_CFG_BYTE(qout), | |
581 | }; | |
582 | ||
583 | return si476x_core_send_command(core, CMD_ZIF_PIN_CFG, | |
584 | args, ARRAY_SIZE(args), | |
585 | resp, ARRAY_SIZE(resp), | |
586 | SI476X_DEFAULT_TIMEOUT); | |
587 | } | |
588 | EXPORT_SYMBOL_GPL(si476x_core_cmd_zif_pin_cfg); | |
589 | ||
590 | /** | |
591 | * si476x_cmd_ic_link_gpo_ctl_pin_cfg - send | |
592 | * 'IC_LINK_GPIO_CTL_PIN_CFG' comand to the device | |
593 | * @core - device to send the command to | |
594 | * @icin - ICIN pin function configuration: | |
595 | * SI476X_ICIN_NOOP - do not modify the behaviour | |
596 | * SI476X_ICIN_TRISTATE - put the pin in tristate condition, | |
597 | * enable 1MOhm pulldown | |
598 | * SI476X_ICIN_GPO1_HIGH - set pin to be an output, drive it high | |
599 | * SI476X_ICIN_GPO1_LOW - set pin to be an output, drive it low | |
600 | * SI476X_ICIN_IC_LINK - set the pin to be a part of Inter-Chip link | |
601 | * @icip - ICIP pin function configuration: | |
602 | * SI476X_ICIP_NOOP - do not modify the behaviour | |
603 | * SI476X_ICIP_TRISTATE - put the pin in tristate condition, | |
604 | * enable 1MOhm pulldown | |
605 | * SI476X_ICIP_GPO1_HIGH - set pin to be an output, drive it high | |
606 | * SI476X_ICIP_GPO1_LOW - set pin to be an output, drive it low | |
607 | * SI476X_ICIP_IC_LINK - set the pin to be a part of Inter-Chip link | |
608 | * @icon - ICON pin function configuration: | |
609 | * SI476X_ICON_NOOP - do not modify the behaviour | |
610 | * SI476X_ICON_TRISTATE - put the pin in tristate condition, | |
611 | * enable 1MOhm pulldown | |
612 | * SI476X_ICON_I2S - set the pin to be a part of audio | |
613 | * interface in slave mode (DCLK) | |
614 | * SI476X_ICON_IC_LINK - set the pin to be a part of Inter-Chip link | |
615 | * @icop - ICOP pin function configuration: | |
616 | * SI476X_ICOP_NOOP - do not modify the behaviour | |
617 | * SI476X_ICOP_TRISTATE - put the pin in tristate condition, | |
618 | * enable 1MOhm pulldown | |
619 | * SI476X_ICOP_I2S - set the pin to be a part of audio | |
620 | * interface in slave mode (DOUT) | |
621 | * [Si4761/63/65/67 Only] | |
622 | * SI476X_ICOP_IC_LINK - set the pin to be a part of Inter-Chip link | |
623 | * | |
624 | * Function returns 0 on success and negative error code on failure | |
625 | */ | |
626 | int si476x_core_cmd_ic_link_gpo_ctl_pin_cfg(struct si476x_core *core, | |
627 | enum si476x_icin_config icin, | |
628 | enum si476x_icip_config icip, | |
629 | enum si476x_icon_config icon, | |
630 | enum si476x_icop_config icop) | |
631 | { | |
632 | u8 resp[CMD_IC_LINK_GPO_CTL_PIN_CFG_NRESP]; | |
633 | const u8 args[CMD_IC_LINK_GPO_CTL_PIN_CFG_NARGS] = { | |
634 | PIN_CFG_BYTE(icin), | |
635 | PIN_CFG_BYTE(icip), | |
636 | PIN_CFG_BYTE(icon), | |
637 | PIN_CFG_BYTE(icop), | |
638 | }; | |
639 | ||
640 | return si476x_core_send_command(core, CMD_IC_LINK_GPO_CTL_PIN_CFG, | |
641 | args, ARRAY_SIZE(args), | |
642 | resp, ARRAY_SIZE(resp), | |
643 | SI476X_DEFAULT_TIMEOUT); | |
644 | } | |
645 | EXPORT_SYMBOL_GPL(si476x_core_cmd_ic_link_gpo_ctl_pin_cfg); | |
646 | ||
647 | /** | |
648 | * si476x_cmd_ana_audio_pin_cfg - send 'ANA_AUDIO_PIN_CFG' to the | |
649 | * device | |
650 | * @core - device to send the command to | |
651 | * @lrout - LROUT pin function configuration: | |
652 | * SI476X_LROUT_NOOP - do not modify the behaviour | |
653 | * SI476X_LROUT_TRISTATE - put the pin in tristate condition, | |
654 | * enable 1MOhm pulldown | |
655 | * SI476X_LROUT_AUDIO - set pin to be audio output | |
656 | * SI476X_LROUT_MPX - set pin to be MPX output | |
657 | * | |
658 | * Function returns 0 on success and negative error code on failure | |
659 | */ | |
660 | int si476x_core_cmd_ana_audio_pin_cfg(struct si476x_core *core, | |
661 | enum si476x_lrout_config lrout) | |
662 | { | |
663 | u8 resp[CMD_ANA_AUDIO_PIN_CFG_NRESP]; | |
664 | const u8 args[CMD_ANA_AUDIO_PIN_CFG_NARGS] = { | |
665 | PIN_CFG_BYTE(lrout), | |
666 | }; | |
667 | ||
668 | return si476x_core_send_command(core, CMD_ANA_AUDIO_PIN_CFG, | |
669 | args, ARRAY_SIZE(args), | |
670 | resp, ARRAY_SIZE(resp), | |
671 | SI476X_DEFAULT_TIMEOUT); | |
672 | } | |
673 | EXPORT_SYMBOL_GPL(si476x_core_cmd_ana_audio_pin_cfg); | |
674 | ||
675 | ||
676 | /** | |
677 | * si476x_cmd_intb_pin_cfg - send 'INTB_PIN_CFG' command to the device | |
678 | * @core - device to send the command to | |
679 | * @intb - INTB pin function configuration: | |
680 | * SI476X_INTB_NOOP - do not modify the behaviour | |
681 | * SI476X_INTB_TRISTATE - put the pin in tristate condition, | |
682 | * enable 1MOhm pulldown | |
683 | * SI476X_INTB_DAUDIO - set pin to be a part of digital | |
684 | * audio interface in slave mode | |
685 | * SI476X_INTB_IRQ - set pin to be an interrupt request line | |
686 | * @a1 - A1 pin function configuration: | |
687 | * SI476X_A1_NOOP - do not modify the behaviour | |
688 | * SI476X_A1_TRISTATE - put the pin in tristate condition, | |
689 | * enable 1MOhm pulldown | |
690 | * SI476X_A1_IRQ - set pin to be an interrupt request line | |
691 | * | |
692 | * Function returns 0 on success and negative error code on failure | |
693 | */ | |
694 | static int si476x_core_cmd_intb_pin_cfg_a10(struct si476x_core *core, | |
695 | enum si476x_intb_config intb, | |
696 | enum si476x_a1_config a1) | |
697 | { | |
698 | u8 resp[CMD_INTB_PIN_CFG_A10_NRESP]; | |
699 | const u8 args[CMD_INTB_PIN_CFG_NARGS] = { | |
700 | PIN_CFG_BYTE(intb), | |
701 | PIN_CFG_BYTE(a1), | |
702 | }; | |
703 | ||
704 | return si476x_core_send_command(core, CMD_INTB_PIN_CFG, | |
705 | args, ARRAY_SIZE(args), | |
706 | resp, ARRAY_SIZE(resp), | |
707 | SI476X_DEFAULT_TIMEOUT); | |
708 | } | |
709 | ||
710 | static int si476x_core_cmd_intb_pin_cfg_a20(struct si476x_core *core, | |
711 | enum si476x_intb_config intb, | |
712 | enum si476x_a1_config a1) | |
713 | { | |
714 | u8 resp[CMD_INTB_PIN_CFG_A20_NRESP]; | |
715 | const u8 args[CMD_INTB_PIN_CFG_NARGS] = { | |
716 | PIN_CFG_BYTE(intb), | |
717 | PIN_CFG_BYTE(a1), | |
718 | }; | |
719 | ||
720 | return si476x_core_send_command(core, CMD_INTB_PIN_CFG, | |
721 | args, ARRAY_SIZE(args), | |
722 | resp, ARRAY_SIZE(resp), | |
723 | SI476X_DEFAULT_TIMEOUT); | |
724 | } | |
725 | ||
726 | ||
727 | ||
728 | /** | |
729 | * si476x_cmd_am_rsq_status - send 'AM_RSQ_STATUS' command to the | |
730 | * device | |
731 | * @core - device to send the command to | |
732 | * @rsqack - if set command clears RSQINT, SNRINT, SNRLINT, RSSIHINT, | |
733 | * RSSSILINT, BLENDINT, MULTHINT and MULTLINT | |
734 | * @attune - when set the values in the status report are the values | |
735 | * that were calculated at tune | |
736 | * @cancel - abort ongoing seek/tune opertation | |
737 | * @stcack - clear the STCINT bin in status register | |
738 | * @report - all signal quality information retured by the command | |
739 | * (if NULL then the output of the command is ignored) | |
740 | * | |
741 | * Function returns 0 on success and negative error code on failure | |
742 | */ | |
743 | int si476x_core_cmd_am_rsq_status(struct si476x_core *core, | |
744 | struct si476x_rsq_status_args *rsqargs, | |
745 | struct si476x_rsq_status_report *report) | |
746 | { | |
747 | int err; | |
748 | u8 resp[CMD_AM_RSQ_STATUS_NRESP]; | |
749 | const u8 args[CMD_AM_RSQ_STATUS_NARGS] = { | |
750 | rsqargs->rsqack << 3 | rsqargs->attune << 2 | | |
751 | rsqargs->cancel << 1 | rsqargs->stcack, | |
752 | }; | |
753 | ||
754 | err = si476x_core_send_command(core, CMD_AM_RSQ_STATUS, | |
755 | args, ARRAY_SIZE(args), | |
756 | resp, ARRAY_SIZE(resp), | |
757 | SI476X_DEFAULT_TIMEOUT); | |
758 | /* | |
759 | * Besides getting received signal quality information this | |
760 | * command can be used to just acknowledge different interrupt | |
761 | * flags in those cases it is useless to copy and parse | |
762 | * received data so user can pass NULL, and thus avoid | |
763 | * unnecessary copying. | |
764 | */ | |
765 | if (!report) | |
766 | return err; | |
767 | ||
b0222afa GU |
768 | report->snrhint = 0x08 & resp[1]; |
769 | report->snrlint = 0x04 & resp[1]; | |
770 | report->rssihint = 0x02 & resp[1]; | |
771 | report->rssilint = 0x01 & resp[1]; | |
ed4a8fe8 | 772 | |
b0222afa GU |
773 | report->bltf = 0x80 & resp[2]; |
774 | report->snr_ready = 0x20 & resp[2]; | |
775 | report->rssiready = 0x08 & resp[2]; | |
776 | report->afcrl = 0x02 & resp[2]; | |
777 | report->valid = 0x01 & resp[2]; | |
ed4a8fe8 | 778 | |
151978bf | 779 | report->readfreq = get_unaligned_be16(resp + 3); |
ed4a8fe8 AS |
780 | report->freqoff = resp[5]; |
781 | report->rssi = resp[6]; | |
782 | report->snr = resp[7]; | |
783 | report->lassi = resp[9]; | |
784 | report->hassi = resp[10]; | |
785 | report->mult = resp[11]; | |
786 | report->dev = resp[12]; | |
787 | ||
788 | return err; | |
789 | } | |
790 | EXPORT_SYMBOL_GPL(si476x_core_cmd_am_rsq_status); | |
791 | ||
792 | int si476x_core_cmd_fm_acf_status(struct si476x_core *core, | |
793 | struct si476x_acf_status_report *report) | |
794 | { | |
795 | int err; | |
796 | u8 resp[CMD_FM_ACF_STATUS_NRESP]; | |
797 | const u8 args[CMD_FM_ACF_STATUS_NARGS] = { | |
798 | 0x0, | |
799 | }; | |
800 | ||
801 | if (!report) | |
802 | return -EINVAL; | |
803 | ||
804 | err = si476x_core_send_command(core, CMD_FM_ACF_STATUS, | |
805 | args, ARRAY_SIZE(args), | |
806 | resp, ARRAY_SIZE(resp), | |
807 | SI476X_DEFAULT_TIMEOUT); | |
808 | if (err < 0) | |
809 | return err; | |
810 | ||
811 | report->blend_int = resp[1] & SI476X_ACF_BLEND_INT; | |
812 | report->hblend_int = resp[1] & SI476X_ACF_HIBLEND_INT; | |
813 | report->hicut_int = resp[1] & SI476X_ACF_HICUT_INT; | |
814 | report->chbw_int = resp[1] & SI476X_ACF_CHBW_INT; | |
815 | report->softmute_int = resp[1] & SI476X_ACF_SOFTMUTE_INT; | |
816 | report->smute = resp[2] & SI476X_ACF_SMUTE; | |
817 | report->smattn = resp[3] & SI476X_ACF_SMATTN; | |
818 | report->chbw = resp[4]; | |
819 | report->hicut = resp[5]; | |
820 | report->hiblend = resp[6]; | |
821 | report->pilot = resp[7] & SI476X_ACF_PILOT; | |
822 | report->stblend = resp[7] & SI476X_ACF_STBLEND; | |
823 | ||
824 | return err; | |
825 | } | |
826 | EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_acf_status); | |
827 | ||
828 | int si476x_core_cmd_am_acf_status(struct si476x_core *core, | |
829 | struct si476x_acf_status_report *report) | |
830 | { | |
831 | int err; | |
832 | u8 resp[CMD_AM_ACF_STATUS_NRESP]; | |
833 | const u8 args[CMD_AM_ACF_STATUS_NARGS] = { | |
834 | 0x0, | |
835 | }; | |
836 | ||
837 | if (!report) | |
838 | return -EINVAL; | |
839 | ||
840 | err = si476x_core_send_command(core, CMD_AM_ACF_STATUS, | |
841 | args, ARRAY_SIZE(args), | |
842 | resp, ARRAY_SIZE(resp), | |
843 | SI476X_DEFAULT_TIMEOUT); | |
844 | if (err < 0) | |
845 | return err; | |
846 | ||
847 | report->blend_int = resp[1] & SI476X_ACF_BLEND_INT; | |
848 | report->hblend_int = resp[1] & SI476X_ACF_HIBLEND_INT; | |
849 | report->hicut_int = resp[1] & SI476X_ACF_HICUT_INT; | |
850 | report->chbw_int = resp[1] & SI476X_ACF_CHBW_INT; | |
851 | report->softmute_int = resp[1] & SI476X_ACF_SOFTMUTE_INT; | |
852 | report->smute = resp[2] & SI476X_ACF_SMUTE; | |
853 | report->smattn = resp[3] & SI476X_ACF_SMATTN; | |
854 | report->chbw = resp[4]; | |
855 | report->hicut = resp[5]; | |
856 | ||
857 | return err; | |
858 | } | |
859 | EXPORT_SYMBOL_GPL(si476x_core_cmd_am_acf_status); | |
860 | ||
861 | ||
862 | /** | |
863 | * si476x_cmd_fm_seek_start - send 'FM_SEEK_START' command to the | |
864 | * device | |
865 | * @core - device to send the command to | |
866 | * @seekup - if set the direction of the search is 'up' | |
867 | * @wrap - if set seek wraps when hitting band limit | |
868 | * | |
869 | * This function begins search for a valid station. The station is | |
870 | * considered valid when 'FM_VALID_SNR_THRESHOLD' and | |
871 | * 'FM_VALID_RSSI_THRESHOLD' and 'FM_VALID_MAX_TUNE_ERROR' criteria | |
872 | * are met. | |
873 | } * | |
874 | * Function returns 0 on success and negative error code on failure | |
875 | */ | |
876 | int si476x_core_cmd_fm_seek_start(struct si476x_core *core, | |
877 | bool seekup, bool wrap) | |
878 | { | |
879 | u8 resp[CMD_FM_SEEK_START_NRESP]; | |
880 | const u8 args[CMD_FM_SEEK_START_NARGS] = { | |
881 | seekup << 3 | wrap << 2, | |
882 | }; | |
883 | ||
884 | return si476x_cmd_tune_seek_freq(core, CMD_FM_SEEK_START, | |
885 | args, sizeof(args), | |
886 | resp, sizeof(resp)); | |
887 | } | |
888 | EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_seek_start); | |
889 | ||
890 | /** | |
891 | * si476x_cmd_fm_rds_status - send 'FM_RDS_STATUS' command to the | |
892 | * device | |
893 | * @core - device to send the command to | |
894 | * @status_only - if set the data is not removed from RDSFIFO, | |
895 | * RDSFIFOUSED is not decremented and data in all the | |
896 | * rest RDS data contains the last valid info received | |
897 | * @mtfifo if set the command clears RDS receive FIFO | |
898 | * @intack if set the command clards the RDSINT bit. | |
899 | * | |
900 | * Function returns 0 on success and negative error code on failure | |
901 | */ | |
902 | int si476x_core_cmd_fm_rds_status(struct si476x_core *core, | |
903 | bool status_only, | |
904 | bool mtfifo, | |
905 | bool intack, | |
906 | struct si476x_rds_status_report *report) | |
907 | { | |
908 | int err; | |
909 | u8 resp[CMD_FM_RDS_STATUS_NRESP]; | |
910 | const u8 args[CMD_FM_RDS_STATUS_NARGS] = { | |
911 | status_only << 2 | mtfifo << 1 | intack, | |
912 | }; | |
913 | ||
914 | err = si476x_core_send_command(core, CMD_FM_RDS_STATUS, | |
915 | args, ARRAY_SIZE(args), | |
916 | resp, ARRAY_SIZE(resp), | |
917 | SI476X_DEFAULT_TIMEOUT); | |
918 | /* | |
919 | * Besides getting RDS status information this command can be | |
920 | * used to just acknowledge different interrupt flags in those | |
921 | * cases it is useless to copy and parse received data so user | |
922 | * can pass NULL, and thus avoid unnecessary copying. | |
923 | */ | |
924 | if (err < 0 || report == NULL) | |
925 | return err; | |
926 | ||
b0222afa GU |
927 | report->rdstpptyint = 0x10 & resp[1]; |
928 | report->rdspiint = 0x08 & resp[1]; | |
929 | report->rdssyncint = 0x02 & resp[1]; | |
930 | report->rdsfifoint = 0x01 & resp[1]; | |
ed4a8fe8 | 931 | |
b0222afa GU |
932 | report->tpptyvalid = 0x10 & resp[2]; |
933 | report->pivalid = 0x08 & resp[2]; | |
934 | report->rdssync = 0x02 & resp[2]; | |
935 | report->rdsfifolost = 0x01 & resp[2]; | |
ed4a8fe8 | 936 | |
b0222afa GU |
937 | report->tp = 0x20 & resp[3]; |
938 | report->pty = 0x1f & resp[3]; | |
ed4a8fe8 | 939 | |
151978bf | 940 | report->pi = get_unaligned_be16(resp + 4); |
ed4a8fe8 AS |
941 | report->rdsfifoused = resp[6]; |
942 | ||
b0222afa GU |
943 | report->ble[V4L2_RDS_BLOCK_A] = 0xc0 & resp[7]; |
944 | report->ble[V4L2_RDS_BLOCK_B] = 0x30 & resp[7]; | |
945 | report->ble[V4L2_RDS_BLOCK_C] = 0x0c & resp[7]; | |
946 | report->ble[V4L2_RDS_BLOCK_D] = 0x03 & resp[7]; | |
ed4a8fe8 AS |
947 | |
948 | report->rds[V4L2_RDS_BLOCK_A].block = V4L2_RDS_BLOCK_A; | |
949 | report->rds[V4L2_RDS_BLOCK_A].msb = resp[8]; | |
950 | report->rds[V4L2_RDS_BLOCK_A].lsb = resp[9]; | |
951 | ||
952 | report->rds[V4L2_RDS_BLOCK_B].block = V4L2_RDS_BLOCK_B; | |
953 | report->rds[V4L2_RDS_BLOCK_B].msb = resp[10]; | |
954 | report->rds[V4L2_RDS_BLOCK_B].lsb = resp[11]; | |
955 | ||
956 | report->rds[V4L2_RDS_BLOCK_C].block = V4L2_RDS_BLOCK_C; | |
957 | report->rds[V4L2_RDS_BLOCK_C].msb = resp[12]; | |
958 | report->rds[V4L2_RDS_BLOCK_C].lsb = resp[13]; | |
959 | ||
960 | report->rds[V4L2_RDS_BLOCK_D].block = V4L2_RDS_BLOCK_D; | |
961 | report->rds[V4L2_RDS_BLOCK_D].msb = resp[14]; | |
962 | report->rds[V4L2_RDS_BLOCK_D].lsb = resp[15]; | |
963 | ||
964 | return err; | |
965 | } | |
966 | EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_rds_status); | |
967 | ||
968 | int si476x_core_cmd_fm_rds_blockcount(struct si476x_core *core, | |
969 | bool clear, | |
970 | struct si476x_rds_blockcount_report *report) | |
971 | { | |
972 | int err; | |
973 | u8 resp[CMD_FM_RDS_BLOCKCOUNT_NRESP]; | |
974 | const u8 args[CMD_FM_RDS_BLOCKCOUNT_NARGS] = { | |
975 | clear, | |
976 | }; | |
977 | ||
978 | if (!report) | |
979 | return -EINVAL; | |
980 | ||
981 | err = si476x_core_send_command(core, CMD_FM_RDS_BLOCKCOUNT, | |
982 | args, ARRAY_SIZE(args), | |
983 | resp, ARRAY_SIZE(resp), | |
984 | SI476X_DEFAULT_TIMEOUT); | |
985 | ||
986 | if (!err) { | |
151978bf GU |
987 | report->expected = get_unaligned_be16(resp + 2); |
988 | report->received = get_unaligned_be16(resp + 4); | |
989 | report->uncorrectable = get_unaligned_be16(resp + 6); | |
ed4a8fe8 AS |
990 | } |
991 | ||
992 | return err; | |
993 | } | |
994 | EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_rds_blockcount); | |
995 | ||
996 | int si476x_core_cmd_fm_phase_diversity(struct si476x_core *core, | |
997 | enum si476x_phase_diversity_mode mode) | |
998 | { | |
999 | u8 resp[CMD_FM_PHASE_DIVERSITY_NRESP]; | |
1000 | const u8 args[CMD_FM_PHASE_DIVERSITY_NARGS] = { | |
b0222afa | 1001 | mode & 0x07, |
ed4a8fe8 AS |
1002 | }; |
1003 | ||
1004 | return si476x_core_send_command(core, CMD_FM_PHASE_DIVERSITY, | |
1005 | args, ARRAY_SIZE(args), | |
1006 | resp, ARRAY_SIZE(resp), | |
1007 | SI476X_DEFAULT_TIMEOUT); | |
1008 | } | |
1009 | EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_phase_diversity); | |
1010 | /** | |
1011 | * si476x_core_cmd_fm_phase_div_status() - get the phase diversity | |
1012 | * status | |
1013 | * | |
1014 | * @core: si476x device | |
1015 | * | |
1016 | * NOTE caller must hold core lock | |
1017 | * | |
1018 | * Function returns the value of the status bit in case of success and | |
1019 | * negative error code in case of failre. | |
1020 | */ | |
1021 | int si476x_core_cmd_fm_phase_div_status(struct si476x_core *core) | |
1022 | { | |
1023 | int err; | |
1024 | u8 resp[CMD_FM_PHASE_DIV_STATUS_NRESP]; | |
1025 | ||
1026 | err = si476x_core_send_command(core, CMD_FM_PHASE_DIV_STATUS, | |
1027 | NULL, 0, | |
1028 | resp, ARRAY_SIZE(resp), | |
1029 | SI476X_DEFAULT_TIMEOUT); | |
1030 | ||
1031 | return (err < 0) ? err : resp[1]; | |
1032 | } | |
1033 | EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_phase_div_status); | |
1034 | ||
1035 | ||
1036 | /** | |
1037 | * si476x_cmd_am_seek_start - send 'FM_SEEK_START' command to the | |
1038 | * device | |
1039 | * @core - device to send the command to | |
1040 | * @seekup - if set the direction of the search is 'up' | |
1041 | * @wrap - if set seek wraps when hitting band limit | |
1042 | * | |
1043 | * This function begins search for a valid station. The station is | |
1044 | * considered valid when 'FM_VALID_SNR_THRESHOLD' and | |
1045 | * 'FM_VALID_RSSI_THRESHOLD' and 'FM_VALID_MAX_TUNE_ERROR' criteria | |
1046 | * are met. | |
1047 | * | |
1048 | * Function returns 0 on success and negative error code on failure | |
1049 | */ | |
1050 | int si476x_core_cmd_am_seek_start(struct si476x_core *core, | |
1051 | bool seekup, bool wrap) | |
1052 | { | |
1053 | u8 resp[CMD_AM_SEEK_START_NRESP]; | |
1054 | const u8 args[CMD_AM_SEEK_START_NARGS] = { | |
1055 | seekup << 3 | wrap << 2, | |
1056 | }; | |
1057 | ||
1058 | return si476x_cmd_tune_seek_freq(core, CMD_AM_SEEK_START, | |
1059 | args, sizeof(args), | |
1060 | resp, sizeof(resp)); | |
1061 | } | |
1062 | EXPORT_SYMBOL_GPL(si476x_core_cmd_am_seek_start); | |
1063 | ||
1064 | ||
1065 | ||
1066 | static int si476x_core_cmd_power_up_a10(struct si476x_core *core, | |
1067 | struct si476x_power_up_args *puargs) | |
1068 | { | |
1069 | u8 resp[CMD_POWER_UP_A10_NRESP]; | |
1070 | const bool intsel = (core->pinmux.a1 == SI476X_A1_IRQ); | |
1071 | const bool ctsen = (core->client->irq != 0); | |
1072 | const u8 args[CMD_POWER_UP_A10_NARGS] = { | |
1073 | 0xF7, /* Reserved, always 0xF7 */ | |
1074 | 0x3F & puargs->xcload, /* First two bits are reserved to be | |
1075 | * zeros */ | |
1076 | ctsen << 7 | intsel << 6 | 0x07, /* Last five bits | |
1077 | * are reserved to | |
1078 | * be written as 0x7 */ | |
1079 | puargs->func << 4 | puargs->freq, | |
1080 | 0x11, /* Reserved, always 0x11 */ | |
1081 | }; | |
1082 | ||
1083 | return si476x_core_send_command(core, CMD_POWER_UP, | |
1084 | args, ARRAY_SIZE(args), | |
1085 | resp, ARRAY_SIZE(resp), | |
1086 | SI476X_TIMEOUT_POWER_UP); | |
1087 | } | |
1088 | ||
1089 | static int si476x_core_cmd_power_up_a20(struct si476x_core *core, | |
1090 | struct si476x_power_up_args *puargs) | |
1091 | { | |
1092 | u8 resp[CMD_POWER_UP_A20_NRESP]; | |
1093 | const bool intsel = (core->pinmux.a1 == SI476X_A1_IRQ); | |
1094 | const bool ctsen = (core->client->irq != 0); | |
1095 | const u8 args[CMD_POWER_UP_A20_NARGS] = { | |
1096 | puargs->ibias6x << 7 | puargs->xstart, | |
1097 | 0x3F & puargs->xcload, /* First two bits are reserved to be | |
1098 | * zeros */ | |
1099 | ctsen << 7 | intsel << 6 | puargs->fastboot << 5 | | |
1100 | puargs->xbiashc << 3 | puargs->xbias, | |
1101 | puargs->func << 4 | puargs->freq, | |
1102 | 0x10 | puargs->xmode, | |
1103 | }; | |
1104 | ||
1105 | return si476x_core_send_command(core, CMD_POWER_UP, | |
1106 | args, ARRAY_SIZE(args), | |
1107 | resp, ARRAY_SIZE(resp), | |
1108 | SI476X_TIMEOUT_POWER_UP); | |
1109 | } | |
1110 | ||
1111 | static int si476x_core_cmd_power_down_a10(struct si476x_core *core, | |
1112 | struct si476x_power_down_args *pdargs) | |
1113 | { | |
1114 | u8 resp[CMD_POWER_DOWN_A10_NRESP]; | |
1115 | ||
1116 | return si476x_core_send_command(core, CMD_POWER_DOWN, | |
1117 | NULL, 0, | |
1118 | resp, ARRAY_SIZE(resp), | |
1119 | SI476X_DEFAULT_TIMEOUT); | |
1120 | } | |
1121 | ||
1122 | static int si476x_core_cmd_power_down_a20(struct si476x_core *core, | |
1123 | struct si476x_power_down_args *pdargs) | |
1124 | { | |
1125 | u8 resp[CMD_POWER_DOWN_A20_NRESP]; | |
1126 | const u8 args[CMD_POWER_DOWN_A20_NARGS] = { | |
1127 | pdargs->xosc, | |
1128 | }; | |
1129 | return si476x_core_send_command(core, CMD_POWER_DOWN, | |
1130 | args, ARRAY_SIZE(args), | |
1131 | resp, ARRAY_SIZE(resp), | |
1132 | SI476X_DEFAULT_TIMEOUT); | |
1133 | } | |
1134 | ||
1135 | static int si476x_core_cmd_am_tune_freq_a10(struct si476x_core *core, | |
1136 | struct si476x_tune_freq_args *tuneargs) | |
1137 | { | |
1138 | ||
1139 | const int am_freq = tuneargs->freq; | |
1140 | u8 resp[CMD_AM_TUNE_FREQ_NRESP]; | |
1141 | const u8 args[CMD_AM_TUNE_FREQ_NARGS] = { | |
1142 | (tuneargs->hd << 6), | |
1143 | msb(am_freq), | |
1144 | lsb(am_freq), | |
1145 | }; | |
1146 | ||
1147 | return si476x_cmd_tune_seek_freq(core, CMD_AM_TUNE_FREQ, args, | |
1148 | sizeof(args), | |
1149 | resp, sizeof(resp)); | |
1150 | } | |
1151 | ||
1152 | static int si476x_core_cmd_am_tune_freq_a20(struct si476x_core *core, | |
1153 | struct si476x_tune_freq_args *tuneargs) | |
1154 | { | |
1155 | const int am_freq = tuneargs->freq; | |
1156 | u8 resp[CMD_AM_TUNE_FREQ_NRESP]; | |
1157 | const u8 args[CMD_AM_TUNE_FREQ_NARGS] = { | |
b0222afa | 1158 | (tuneargs->zifsr << 6) | (tuneargs->injside & 0x03), |
ed4a8fe8 AS |
1159 | msb(am_freq), |
1160 | lsb(am_freq), | |
1161 | }; | |
1162 | ||
1163 | return si476x_cmd_tune_seek_freq(core, CMD_AM_TUNE_FREQ, | |
1164 | args, sizeof(args), | |
1165 | resp, sizeof(resp)); | |
1166 | } | |
1167 | ||
1168 | static int si476x_core_cmd_fm_rsq_status_a10(struct si476x_core *core, | |
1169 | struct si476x_rsq_status_args *rsqargs, | |
1170 | struct si476x_rsq_status_report *report) | |
1171 | { | |
1172 | int err; | |
1173 | u8 resp[CMD_FM_RSQ_STATUS_A10_NRESP]; | |
1174 | const u8 args[CMD_FM_RSQ_STATUS_A10_NARGS] = { | |
1175 | rsqargs->rsqack << 3 | rsqargs->attune << 2 | | |
1176 | rsqargs->cancel << 1 | rsqargs->stcack, | |
1177 | }; | |
1178 | ||
1179 | err = si476x_core_send_command(core, CMD_FM_RSQ_STATUS, | |
1180 | args, ARRAY_SIZE(args), | |
1181 | resp, ARRAY_SIZE(resp), | |
1182 | SI476X_DEFAULT_TIMEOUT); | |
1183 | /* | |
1184 | * Besides getting received signal quality information this | |
1185 | * command can be used to just acknowledge different interrupt | |
1186 | * flags in those cases it is useless to copy and parse | |
1187 | * received data so user can pass NULL, and thus avoid | |
1188 | * unnecessary copying. | |
1189 | */ | |
1190 | if (err < 0 || report == NULL) | |
1191 | return err; | |
1192 | ||
b0222afa GU |
1193 | report->multhint = 0x80 & resp[1]; |
1194 | report->multlint = 0x40 & resp[1]; | |
1195 | report->snrhint = 0x08 & resp[1]; | |
1196 | report->snrlint = 0x04 & resp[1]; | |
1197 | report->rssihint = 0x02 & resp[1]; | |
1198 | report->rssilint = 0x01 & resp[1]; | |
ed4a8fe8 | 1199 | |
b0222afa GU |
1200 | report->bltf = 0x80 & resp[2]; |
1201 | report->snr_ready = 0x20 & resp[2]; | |
1202 | report->rssiready = 0x08 & resp[2]; | |
1203 | report->afcrl = 0x02 & resp[2]; | |
1204 | report->valid = 0x01 & resp[2]; | |
ed4a8fe8 | 1205 | |
151978bf | 1206 | report->readfreq = get_unaligned_be16(resp + 3); |
ed4a8fe8 AS |
1207 | report->freqoff = resp[5]; |
1208 | report->rssi = resp[6]; | |
1209 | report->snr = resp[7]; | |
1210 | report->lassi = resp[9]; | |
1211 | report->hassi = resp[10]; | |
1212 | report->mult = resp[11]; | |
1213 | report->dev = resp[12]; | |
151978bf | 1214 | report->readantcap = get_unaligned_be16(resp + 13); |
ed4a8fe8 AS |
1215 | report->assi = resp[15]; |
1216 | report->usn = resp[16]; | |
1217 | ||
1218 | return err; | |
1219 | } | |
1220 | ||
1221 | static int si476x_core_cmd_fm_rsq_status_a20(struct si476x_core *core, | |
d69d4212 LJ |
1222 | struct si476x_rsq_status_args *rsqargs, |
1223 | struct si476x_rsq_status_report *report) | |
ed4a8fe8 AS |
1224 | { |
1225 | int err; | |
1226 | u8 resp[CMD_FM_RSQ_STATUS_A10_NRESP]; | |
1227 | const u8 args[CMD_FM_RSQ_STATUS_A30_NARGS] = { | |
1228 | rsqargs->primary << 4 | rsqargs->rsqack << 3 | | |
1229 | rsqargs->attune << 2 | rsqargs->cancel << 1 | | |
1230 | rsqargs->stcack, | |
1231 | }; | |
1232 | ||
1233 | err = si476x_core_send_command(core, CMD_FM_RSQ_STATUS, | |
1234 | args, ARRAY_SIZE(args), | |
1235 | resp, ARRAY_SIZE(resp), | |
1236 | SI476X_DEFAULT_TIMEOUT); | |
1237 | /* | |
1238 | * Besides getting received signal quality information this | |
1239 | * command can be used to just acknowledge different interrupt | |
1240 | * flags in those cases it is useless to copy and parse | |
1241 | * received data so user can pass NULL, and thus avoid | |
1242 | * unnecessary copying. | |
1243 | */ | |
1244 | if (err < 0 || report == NULL) | |
1245 | return err; | |
1246 | ||
b0222afa GU |
1247 | report->multhint = 0x80 & resp[1]; |
1248 | report->multlint = 0x40 & resp[1]; | |
1249 | report->snrhint = 0x08 & resp[1]; | |
1250 | report->snrlint = 0x04 & resp[1]; | |
1251 | report->rssihint = 0x02 & resp[1]; | |
1252 | report->rssilint = 0x01 & resp[1]; | |
ed4a8fe8 | 1253 | |
b0222afa GU |
1254 | report->bltf = 0x80 & resp[2]; |
1255 | report->snr_ready = 0x20 & resp[2]; | |
1256 | report->rssiready = 0x08 & resp[2]; | |
1257 | report->afcrl = 0x02 & resp[2]; | |
1258 | report->valid = 0x01 & resp[2]; | |
ed4a8fe8 | 1259 | |
151978bf | 1260 | report->readfreq = get_unaligned_be16(resp + 3); |
ed4a8fe8 AS |
1261 | report->freqoff = resp[5]; |
1262 | report->rssi = resp[6]; | |
1263 | report->snr = resp[7]; | |
1264 | report->lassi = resp[9]; | |
1265 | report->hassi = resp[10]; | |
1266 | report->mult = resp[11]; | |
1267 | report->dev = resp[12]; | |
151978bf | 1268 | report->readantcap = get_unaligned_be16(resp + 13); |
ed4a8fe8 AS |
1269 | report->assi = resp[15]; |
1270 | report->usn = resp[16]; | |
1271 | ||
1272 | return err; | |
1273 | } | |
1274 | ||
1275 | ||
1276 | static int si476x_core_cmd_fm_rsq_status_a30(struct si476x_core *core, | |
1277 | struct si476x_rsq_status_args *rsqargs, | |
1278 | struct si476x_rsq_status_report *report) | |
1279 | { | |
1280 | int err; | |
1281 | u8 resp[CMD_FM_RSQ_STATUS_A30_NRESP]; | |
1282 | const u8 args[CMD_FM_RSQ_STATUS_A30_NARGS] = { | |
1283 | rsqargs->primary << 4 | rsqargs->rsqack << 3 | | |
1284 | rsqargs->attune << 2 | rsqargs->cancel << 1 | | |
1285 | rsqargs->stcack, | |
1286 | }; | |
1287 | ||
1288 | err = si476x_core_send_command(core, CMD_FM_RSQ_STATUS, | |
1289 | args, ARRAY_SIZE(args), | |
1290 | resp, ARRAY_SIZE(resp), | |
1291 | SI476X_DEFAULT_TIMEOUT); | |
1292 | /* | |
1293 | * Besides getting received signal quality information this | |
1294 | * command can be used to just acknowledge different interrupt | |
1295 | * flags in those cases it is useless to copy and parse | |
1296 | * received data so user can pass NULL, and thus avoid | |
1297 | * unnecessary copying. | |
1298 | */ | |
1299 | if (err < 0 || report == NULL) | |
1300 | return err; | |
1301 | ||
b0222afa GU |
1302 | report->multhint = 0x80 & resp[1]; |
1303 | report->multlint = 0x40 & resp[1]; | |
1304 | report->snrhint = 0x08 & resp[1]; | |
1305 | report->snrlint = 0x04 & resp[1]; | |
1306 | report->rssihint = 0x02 & resp[1]; | |
1307 | report->rssilint = 0x01 & resp[1]; | |
1308 | ||
1309 | report->bltf = 0x80 & resp[2]; | |
1310 | report->snr_ready = 0x20 & resp[2]; | |
1311 | report->rssiready = 0x08 & resp[2]; | |
1312 | report->injside = 0x04 & resp[2]; | |
1313 | report->afcrl = 0x02 & resp[2]; | |
1314 | report->valid = 0x01 & resp[2]; | |
ed4a8fe8 | 1315 | |
151978bf | 1316 | report->readfreq = get_unaligned_be16(resp + 3); |
ed4a8fe8 AS |
1317 | report->freqoff = resp[5]; |
1318 | report->rssi = resp[6]; | |
1319 | report->snr = resp[7]; | |
1320 | report->issi = resp[8]; | |
1321 | report->lassi = resp[9]; | |
1322 | report->hassi = resp[10]; | |
1323 | report->mult = resp[11]; | |
1324 | report->dev = resp[12]; | |
151978bf | 1325 | report->readantcap = get_unaligned_be16(resp + 13); |
ed4a8fe8 AS |
1326 | report->assi = resp[15]; |
1327 | report->usn = resp[16]; | |
1328 | ||
1329 | report->pilotdev = resp[17]; | |
1330 | report->rdsdev = resp[18]; | |
1331 | report->assidev = resp[19]; | |
1332 | report->strongdev = resp[20]; | |
151978bf | 1333 | report->rdspi = get_unaligned_be16(resp + 21); |
ed4a8fe8 AS |
1334 | |
1335 | return err; | |
1336 | } | |
1337 | ||
1338 | static int si476x_core_cmd_fm_tune_freq_a10(struct si476x_core *core, | |
1339 | struct si476x_tune_freq_args *tuneargs) | |
1340 | { | |
1341 | u8 resp[CMD_FM_TUNE_FREQ_NRESP]; | |
1342 | const u8 args[CMD_FM_TUNE_FREQ_A10_NARGS] = { | |
1343 | (tuneargs->hd << 6) | (tuneargs->tunemode << 4) | |
1344 | | (tuneargs->smoothmetrics << 2), | |
1345 | msb(tuneargs->freq), | |
1346 | lsb(tuneargs->freq), | |
1347 | msb(tuneargs->antcap), | |
1348 | lsb(tuneargs->antcap) | |
1349 | }; | |
1350 | ||
1351 | return si476x_cmd_tune_seek_freq(core, CMD_FM_TUNE_FREQ, | |
1352 | args, sizeof(args), | |
1353 | resp, sizeof(resp)); | |
1354 | } | |
1355 | ||
1356 | static int si476x_core_cmd_fm_tune_freq_a20(struct si476x_core *core, | |
1357 | struct si476x_tune_freq_args *tuneargs) | |
1358 | { | |
1359 | u8 resp[CMD_FM_TUNE_FREQ_NRESP]; | |
1360 | const u8 args[CMD_FM_TUNE_FREQ_A20_NARGS] = { | |
1361 | (tuneargs->hd << 6) | (tuneargs->tunemode << 4) | |
1362 | | (tuneargs->smoothmetrics << 2) | (tuneargs->injside), | |
1363 | msb(tuneargs->freq), | |
1364 | lsb(tuneargs->freq), | |
1365 | }; | |
1366 | ||
1367 | return si476x_cmd_tune_seek_freq(core, CMD_FM_TUNE_FREQ, | |
1368 | args, sizeof(args), | |
1369 | resp, sizeof(resp)); | |
1370 | } | |
1371 | ||
1372 | static int si476x_core_cmd_agc_status_a20(struct si476x_core *core, | |
1373 | struct si476x_agc_status_report *report) | |
1374 | { | |
1375 | int err; | |
1376 | u8 resp[CMD_AGC_STATUS_NRESP_A20]; | |
1377 | ||
1378 | if (!report) | |
1379 | return -EINVAL; | |
1380 | ||
1381 | err = si476x_core_send_command(core, CMD_AGC_STATUS, | |
1382 | NULL, 0, | |
1383 | resp, ARRAY_SIZE(resp), | |
1384 | SI476X_DEFAULT_TIMEOUT); | |
1385 | if (err < 0) | |
1386 | return err; | |
1387 | ||
1388 | report->mxhi = resp[1] & SI476X_AGC_MXHI; | |
1389 | report->mxlo = resp[1] & SI476X_AGC_MXLO; | |
1390 | report->lnahi = resp[1] & SI476X_AGC_LNAHI; | |
1391 | report->lnalo = resp[1] & SI476X_AGC_LNALO; | |
1392 | report->fmagc1 = resp[2]; | |
1393 | report->fmagc2 = resp[3]; | |
1394 | report->pgagain = resp[4]; | |
1395 | report->fmwblang = resp[5]; | |
1396 | ||
1397 | return err; | |
1398 | } | |
1399 | ||
1400 | static int si476x_core_cmd_agc_status_a10(struct si476x_core *core, | |
1401 | struct si476x_agc_status_report *report) | |
1402 | { | |
1403 | int err; | |
1404 | u8 resp[CMD_AGC_STATUS_NRESP_A10]; | |
1405 | ||
1406 | if (!report) | |
1407 | return -EINVAL; | |
1408 | ||
1409 | err = si476x_core_send_command(core, CMD_AGC_STATUS, | |
1410 | NULL, 0, | |
1411 | resp, ARRAY_SIZE(resp), | |
1412 | SI476X_DEFAULT_TIMEOUT); | |
1413 | if (err < 0) | |
1414 | return err; | |
1415 | ||
1416 | report->mxhi = resp[1] & SI476X_AGC_MXHI; | |
1417 | report->mxlo = resp[1] & SI476X_AGC_MXLO; | |
1418 | report->lnahi = resp[1] & SI476X_AGC_LNAHI; | |
1419 | report->lnalo = resp[1] & SI476X_AGC_LNALO; | |
1420 | ||
1421 | return err; | |
1422 | } | |
1423 | ||
1424 | typedef int (*tune_freq_func_t) (struct si476x_core *core, | |
1425 | struct si476x_tune_freq_args *tuneargs); | |
1426 | ||
1427 | static struct { | |
d69d4212 LJ |
1428 | int (*power_up)(struct si476x_core *, |
1429 | struct si476x_power_up_args *); | |
1430 | int (*power_down)(struct si476x_core *, | |
1431 | struct si476x_power_down_args *); | |
ed4a8fe8 AS |
1432 | |
1433 | tune_freq_func_t fm_tune_freq; | |
1434 | tune_freq_func_t am_tune_freq; | |
1435 | ||
1436 | int (*fm_rsq_status)(struct si476x_core *, | |
1437 | struct si476x_rsq_status_args *, | |
1438 | struct si476x_rsq_status_report *); | |
1439 | ||
1440 | int (*agc_status)(struct si476x_core *, | |
1441 | struct si476x_agc_status_report *); | |
1442 | int (*intb_pin_cfg)(struct si476x_core *core, | |
1443 | enum si476x_intb_config intb, | |
1444 | enum si476x_a1_config a1); | |
1445 | } si476x_cmds_vtable[] = { | |
1446 | [SI476X_REVISION_A10] = { | |
1447 | .power_up = si476x_core_cmd_power_up_a10, | |
1448 | .power_down = si476x_core_cmd_power_down_a10, | |
1449 | .fm_tune_freq = si476x_core_cmd_fm_tune_freq_a10, | |
1450 | .am_tune_freq = si476x_core_cmd_am_tune_freq_a10, | |
1451 | .fm_rsq_status = si476x_core_cmd_fm_rsq_status_a10, | |
1452 | .agc_status = si476x_core_cmd_agc_status_a10, | |
1453 | .intb_pin_cfg = si476x_core_cmd_intb_pin_cfg_a10, | |
1454 | }, | |
1455 | [SI476X_REVISION_A20] = { | |
1456 | .power_up = si476x_core_cmd_power_up_a20, | |
1457 | .power_down = si476x_core_cmd_power_down_a20, | |
1458 | .fm_tune_freq = si476x_core_cmd_fm_tune_freq_a20, | |
1459 | .am_tune_freq = si476x_core_cmd_am_tune_freq_a20, | |
1460 | .fm_rsq_status = si476x_core_cmd_fm_rsq_status_a20, | |
1461 | .agc_status = si476x_core_cmd_agc_status_a20, | |
1462 | .intb_pin_cfg = si476x_core_cmd_intb_pin_cfg_a20, | |
1463 | }, | |
1464 | [SI476X_REVISION_A30] = { | |
1465 | .power_up = si476x_core_cmd_power_up_a20, | |
1466 | .power_down = si476x_core_cmd_power_down_a20, | |
1467 | .fm_tune_freq = si476x_core_cmd_fm_tune_freq_a20, | |
1468 | .am_tune_freq = si476x_core_cmd_am_tune_freq_a20, | |
1469 | .fm_rsq_status = si476x_core_cmd_fm_rsq_status_a30, | |
1470 | .agc_status = si476x_core_cmd_agc_status_a20, | |
1471 | .intb_pin_cfg = si476x_core_cmd_intb_pin_cfg_a20, | |
1472 | }, | |
1473 | }; | |
1474 | ||
1475 | int si476x_core_cmd_power_up(struct si476x_core *core, | |
1476 | struct si476x_power_up_args *args) | |
1477 | { | |
1478 | BUG_ON(core->revision > SI476X_REVISION_A30 || | |
1479 | core->revision == -1); | |
1480 | return si476x_cmds_vtable[core->revision].power_up(core, args); | |
1481 | } | |
1482 | EXPORT_SYMBOL_GPL(si476x_core_cmd_power_up); | |
1483 | ||
1484 | int si476x_core_cmd_power_down(struct si476x_core *core, | |
1485 | struct si476x_power_down_args *args) | |
1486 | { | |
1487 | BUG_ON(core->revision > SI476X_REVISION_A30 || | |
1488 | core->revision == -1); | |
1489 | return si476x_cmds_vtable[core->revision].power_down(core, args); | |
1490 | } | |
1491 | EXPORT_SYMBOL_GPL(si476x_core_cmd_power_down); | |
1492 | ||
1493 | int si476x_core_cmd_fm_tune_freq(struct si476x_core *core, | |
1494 | struct si476x_tune_freq_args *args) | |
1495 | { | |
1496 | BUG_ON(core->revision > SI476X_REVISION_A30 || | |
1497 | core->revision == -1); | |
1498 | return si476x_cmds_vtable[core->revision].fm_tune_freq(core, args); | |
1499 | } | |
1500 | EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_tune_freq); | |
1501 | ||
1502 | int si476x_core_cmd_am_tune_freq(struct si476x_core *core, | |
1503 | struct si476x_tune_freq_args *args) | |
1504 | { | |
1505 | BUG_ON(core->revision > SI476X_REVISION_A30 || | |
1506 | core->revision == -1); | |
1507 | return si476x_cmds_vtable[core->revision].am_tune_freq(core, args); | |
1508 | } | |
1509 | EXPORT_SYMBOL_GPL(si476x_core_cmd_am_tune_freq); | |
1510 | ||
1511 | int si476x_core_cmd_fm_rsq_status(struct si476x_core *core, | |
1512 | struct si476x_rsq_status_args *args, | |
1513 | struct si476x_rsq_status_report *report) | |
1514 | ||
1515 | { | |
1516 | BUG_ON(core->revision > SI476X_REVISION_A30 || | |
1517 | core->revision == -1); | |
1518 | return si476x_cmds_vtable[core->revision].fm_rsq_status(core, args, | |
1519 | report); | |
1520 | } | |
1521 | EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_rsq_status); | |
1522 | ||
1523 | int si476x_core_cmd_agc_status(struct si476x_core *core, | |
1524 | struct si476x_agc_status_report *report) | |
1525 | ||
1526 | { | |
1527 | BUG_ON(core->revision > SI476X_REVISION_A30 || | |
1528 | core->revision == -1); | |
1529 | return si476x_cmds_vtable[core->revision].agc_status(core, report); | |
1530 | } | |
1531 | EXPORT_SYMBOL_GPL(si476x_core_cmd_agc_status); | |
1532 | ||
1533 | int si476x_core_cmd_intb_pin_cfg(struct si476x_core *core, | |
1534 | enum si476x_intb_config intb, | |
1535 | enum si476x_a1_config a1) | |
1536 | { | |
1537 | BUG_ON(core->revision > SI476X_REVISION_A30 || | |
1538 | core->revision == -1); | |
1539 | ||
1540 | return si476x_cmds_vtable[core->revision].intb_pin_cfg(core, intb, a1); | |
1541 | } | |
1542 | EXPORT_SYMBOL_GPL(si476x_core_cmd_intb_pin_cfg); | |
1543 | ||
1544 | MODULE_LICENSE("GPL"); | |
1545 | MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>"); | |
1546 | MODULE_DESCRIPTION("API for command exchange for si476x"); |