1 | // SPDX-License-Identifier: GPL-2.0-only |
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> |
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 | |
23 | #include <asm/unaligned.h> |
24 | |
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), |
146 | SI476X_ACF_SMATTN = 0x1f, |
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 |
244 | * @core: si476x_device structure for the device we are |
245 | * communicating with |
246 | * @command: command id |
247 | * @args: command arguments we are sending |
248 | * @argn: actual size of @args |
249 | * @resp: buffer to place the expected response from the device |
250 | * @respn: actual size of @resp |
251 | * @usecs: amount of time to wait before reading the response (in |
252 | * usecs) |
253 | * |
254 | * Function returns 0 on success 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(v: &core->cts, i: 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(v: &core->stc, i: 0); |
381 | err = si476x_core_send_command(core, command: 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_core_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 success 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, argn: 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_core_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 success 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_core_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 |
479 | return get_unaligned_be16(p: resp + 2); |
480 | } |
481 | EXPORT_SYMBOL_GPL(si476x_core_cmd_get_property); |
482 | |
483 | /** |
484 | * si476x_core_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_core_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 interface |
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 interface |
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_core_cmd_ic_link_gpo_ctl_pin_cfg - send |
592 | * 'IC_LINK_GPIO_CTL_PIN_CFG' command 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_core_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_core_cmd_intb_pin_cfg_a10 - 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_core_cmd_am_rsq_status - send 'AM_RSQ_STATUS' command to the |
730 | * device |
731 | * @core: - device to send the command to |
732 | * @rsqargs: - pointer to a structure containing a group of sub-args |
733 | * relevant to sending the RSQ status command |
734 | * @report: - all signal quality information returned by the command |
735 | * (if NULL then the output of the command is ignored) |
736 | * |
737 | * Function returns 0 on success and negative error code on failure |
738 | */ |
739 | int si476x_core_cmd_am_rsq_status(struct si476x_core *core, |
740 | struct si476x_rsq_status_args *rsqargs, |
741 | struct si476x_rsq_status_report *report) |
742 | { |
743 | int err; |
744 | u8 resp[CMD_AM_RSQ_STATUS_NRESP]; |
745 | const u8 args[CMD_AM_RSQ_STATUS_NARGS] = { |
746 | rsqargs->rsqack << 3 | rsqargs->attune << 2 | |
747 | rsqargs->cancel << 1 | rsqargs->stcack, |
748 | }; |
749 | |
750 | err = si476x_core_send_command(core, CMD_AM_RSQ_STATUS, |
751 | args, ARRAY_SIZE(args), |
752 | resp, ARRAY_SIZE(resp), |
753 | SI476X_DEFAULT_TIMEOUT); |
754 | /* |
755 | * Besides getting received signal quality information this |
756 | * command can be used to just acknowledge different interrupt |
757 | * flags in those cases it is useless to copy and parse |
758 | * received data so user can pass NULL, and thus avoid |
759 | * unnecessary copying. |
760 | */ |
761 | if (!report) |
762 | return err; |
763 | |
764 | report->snrhint = 0x08 & resp[1]; |
765 | report->snrlint = 0x04 & resp[1]; |
766 | report->rssihint = 0x02 & resp[1]; |
767 | report->rssilint = 0x01 & resp[1]; |
768 | |
769 | report->bltf = 0x80 & resp[2]; |
770 | report->snr_ready = 0x20 & resp[2]; |
771 | report->rssiready = 0x08 & resp[2]; |
772 | report->afcrl = 0x02 & resp[2]; |
773 | report->valid = 0x01 & resp[2]; |
774 | |
775 | report->readfreq = get_unaligned_be16(p: resp + 3); |
776 | report->freqoff = resp[5]; |
777 | report->rssi = resp[6]; |
778 | report->snr = resp[7]; |
779 | report->lassi = resp[9]; |
780 | report->hassi = resp[10]; |
781 | report->mult = resp[11]; |
782 | report->dev = resp[12]; |
783 | |
784 | return err; |
785 | } |
786 | EXPORT_SYMBOL_GPL(si476x_core_cmd_am_rsq_status); |
787 | |
788 | int si476x_core_cmd_fm_acf_status(struct si476x_core *core, |
789 | struct si476x_acf_status_report *report) |
790 | { |
791 | int err; |
792 | u8 resp[CMD_FM_ACF_STATUS_NRESP]; |
793 | const u8 args[CMD_FM_ACF_STATUS_NARGS] = { |
794 | 0x0, |
795 | }; |
796 | |
797 | if (!report) |
798 | return -EINVAL; |
799 | |
800 | err = si476x_core_send_command(core, CMD_FM_ACF_STATUS, |
801 | args, ARRAY_SIZE(args), |
802 | resp, ARRAY_SIZE(resp), |
803 | SI476X_DEFAULT_TIMEOUT); |
804 | if (err < 0) |
805 | return err; |
806 | |
807 | report->blend_int = resp[1] & SI476X_ACF_BLEND_INT; |
808 | report->hblend_int = resp[1] & SI476X_ACF_HIBLEND_INT; |
809 | report->hicut_int = resp[1] & SI476X_ACF_HICUT_INT; |
810 | report->chbw_int = resp[1] & SI476X_ACF_CHBW_INT; |
811 | report->softmute_int = resp[1] & SI476X_ACF_SOFTMUTE_INT; |
812 | report->smute = resp[2] & SI476X_ACF_SMUTE; |
813 | report->smattn = resp[3] & SI476X_ACF_SMATTN; |
814 | report->chbw = resp[4]; |
815 | report->hicut = resp[5]; |
816 | report->hiblend = resp[6]; |
817 | report->pilot = resp[7] & SI476X_ACF_PILOT; |
818 | report->stblend = resp[7] & SI476X_ACF_STBLEND; |
819 | |
820 | return err; |
821 | } |
822 | EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_acf_status); |
823 | |
824 | int si476x_core_cmd_am_acf_status(struct si476x_core *core, |
825 | struct si476x_acf_status_report *report) |
826 | { |
827 | int err; |
828 | u8 resp[CMD_AM_ACF_STATUS_NRESP]; |
829 | const u8 args[CMD_AM_ACF_STATUS_NARGS] = { |
830 | 0x0, |
831 | }; |
832 | |
833 | if (!report) |
834 | return -EINVAL; |
835 | |
836 | err = si476x_core_send_command(core, CMD_AM_ACF_STATUS, |
837 | args, ARRAY_SIZE(args), |
838 | resp, ARRAY_SIZE(resp), |
839 | SI476X_DEFAULT_TIMEOUT); |
840 | if (err < 0) |
841 | return err; |
842 | |
843 | report->blend_int = resp[1] & SI476X_ACF_BLEND_INT; |
844 | report->hblend_int = resp[1] & SI476X_ACF_HIBLEND_INT; |
845 | report->hicut_int = resp[1] & SI476X_ACF_HICUT_INT; |
846 | report->chbw_int = resp[1] & SI476X_ACF_CHBW_INT; |
847 | report->softmute_int = resp[1] & SI476X_ACF_SOFTMUTE_INT; |
848 | report->smute = resp[2] & SI476X_ACF_SMUTE; |
849 | report->smattn = resp[3] & SI476X_ACF_SMATTN; |
850 | report->chbw = resp[4]; |
851 | report->hicut = resp[5]; |
852 | |
853 | return err; |
854 | } |
855 | EXPORT_SYMBOL_GPL(si476x_core_cmd_am_acf_status); |
856 | |
857 | |
858 | /** |
859 | * si476x_core_cmd_fm_seek_start - send 'FM_SEEK_START' command to the |
860 | * device |
861 | * @core: - device to send the command to |
862 | * @seekup: - if set the direction of the search is 'up' |
863 | * @wrap: - if set seek wraps when hitting band limit |
864 | * |
865 | * This function begins search for a valid station. The station is |
866 | * considered valid when 'FM_VALID_SNR_THRESHOLD' and |
867 | * 'FM_VALID_RSSI_THRESHOLD' and 'FM_VALID_MAX_TUNE_ERROR' criteria |
868 | * are met. |
869 | } * |
870 | * Function returns 0 on success and negative error code on failure |
871 | */ |
872 | int si476x_core_cmd_fm_seek_start(struct si476x_core *core, |
873 | bool seekup, bool wrap) |
874 | { |
875 | u8 resp[CMD_FM_SEEK_START_NRESP]; |
876 | const u8 args[CMD_FM_SEEK_START_NARGS] = { |
877 | seekup << 3 | wrap << 2, |
878 | }; |
879 | |
880 | return si476x_cmd_tune_seek_freq(core, CMD_FM_SEEK_START, |
881 | args, argn: sizeof(args), |
882 | resp, respn: sizeof(resp)); |
883 | } |
884 | EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_seek_start); |
885 | |
886 | /** |
887 | * si476x_core_cmd_fm_rds_status - send 'FM_RDS_STATUS' command to the |
888 | * device |
889 | * @core: - device to send the command to |
890 | * @status_only: - if set the data is not removed from RDSFIFO, |
891 | * RDSFIFOUSED is not decremented and data in all the |
892 | * rest RDS data contains the last valid info received |
893 | * @mtfifo: if set the command clears RDS receive FIFO |
894 | * @intack: if set the command clards the RDSINT bit. |
895 | * @report: - all signal quality information returned by the command |
896 | * (if NULL then the output of the command is ignored) |
897 | * |
898 | * Function returns 0 on success and negative error code on failure |
899 | */ |
900 | int si476x_core_cmd_fm_rds_status(struct si476x_core *core, |
901 | bool status_only, |
902 | bool mtfifo, |
903 | bool intack, |
904 | struct si476x_rds_status_report *report) |
905 | { |
906 | int err; |
907 | u8 resp[CMD_FM_RDS_STATUS_NRESP]; |
908 | const u8 args[CMD_FM_RDS_STATUS_NARGS] = { |
909 | status_only << 2 | mtfifo << 1 | intack, |
910 | }; |
911 | |
912 | err = si476x_core_send_command(core, CMD_FM_RDS_STATUS, |
913 | args, ARRAY_SIZE(args), |
914 | resp, ARRAY_SIZE(resp), |
915 | SI476X_DEFAULT_TIMEOUT); |
916 | /* |
917 | * Besides getting RDS status information this command can be |
918 | * used to just acknowledge different interrupt flags in those |
919 | * cases it is useless to copy and parse received data so user |
920 | * can pass NULL, and thus avoid unnecessary copying. |
921 | */ |
922 | if (err < 0 || report == NULL) |
923 | return err; |
924 | |
925 | report->rdstpptyint = 0x10 & resp[1]; |
926 | report->rdspiint = 0x08 & resp[1]; |
927 | report->rdssyncint = 0x02 & resp[1]; |
928 | report->rdsfifoint = 0x01 & resp[1]; |
929 | |
930 | report->tpptyvalid = 0x10 & resp[2]; |
931 | report->pivalid = 0x08 & resp[2]; |
932 | report->rdssync = 0x02 & resp[2]; |
933 | report->rdsfifolost = 0x01 & resp[2]; |
934 | |
935 | report->tp = 0x20 & resp[3]; |
936 | report->pty = 0x1f & resp[3]; |
937 | |
938 | report->pi = get_unaligned_be16(p: resp + 4); |
939 | report->rdsfifoused = resp[6]; |
940 | |
941 | report->ble[V4L2_RDS_BLOCK_A] = 0xc0 & resp[7]; |
942 | report->ble[V4L2_RDS_BLOCK_B] = 0x30 & resp[7]; |
943 | report->ble[V4L2_RDS_BLOCK_C] = 0x0c & resp[7]; |
944 | report->ble[V4L2_RDS_BLOCK_D] = 0x03 & resp[7]; |
945 | |
946 | report->rds[V4L2_RDS_BLOCK_A].block = V4L2_RDS_BLOCK_A; |
947 | report->rds[V4L2_RDS_BLOCK_A].msb = resp[8]; |
948 | report->rds[V4L2_RDS_BLOCK_A].lsb = resp[9]; |
949 | |
950 | report->rds[V4L2_RDS_BLOCK_B].block = V4L2_RDS_BLOCK_B; |
951 | report->rds[V4L2_RDS_BLOCK_B].msb = resp[10]; |
952 | report->rds[V4L2_RDS_BLOCK_B].lsb = resp[11]; |
953 | |
954 | report->rds[V4L2_RDS_BLOCK_C].block = V4L2_RDS_BLOCK_C; |
955 | report->rds[V4L2_RDS_BLOCK_C].msb = resp[12]; |
956 | report->rds[V4L2_RDS_BLOCK_C].lsb = resp[13]; |
957 | |
958 | report->rds[V4L2_RDS_BLOCK_D].block = V4L2_RDS_BLOCK_D; |
959 | report->rds[V4L2_RDS_BLOCK_D].msb = resp[14]; |
960 | report->rds[V4L2_RDS_BLOCK_D].lsb = resp[15]; |
961 | |
962 | return err; |
963 | } |
964 | EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_rds_status); |
965 | |
966 | int si476x_core_cmd_fm_rds_blockcount(struct si476x_core *core, |
967 | bool clear, |
968 | struct si476x_rds_blockcount_report *report) |
969 | { |
970 | int err; |
971 | u8 resp[CMD_FM_RDS_BLOCKCOUNT_NRESP]; |
972 | const u8 args[CMD_FM_RDS_BLOCKCOUNT_NARGS] = { |
973 | clear, |
974 | }; |
975 | |
976 | if (!report) |
977 | return -EINVAL; |
978 | |
979 | err = si476x_core_send_command(core, CMD_FM_RDS_BLOCKCOUNT, |
980 | args, ARRAY_SIZE(args), |
981 | resp, ARRAY_SIZE(resp), |
982 | SI476X_DEFAULT_TIMEOUT); |
983 | |
984 | if (!err) { |
985 | report->expected = get_unaligned_be16(p: resp + 2); |
986 | report->received = get_unaligned_be16(p: resp + 4); |
987 | report->uncorrectable = get_unaligned_be16(p: resp + 6); |
988 | } |
989 | |
990 | return err; |
991 | } |
992 | EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_rds_blockcount); |
993 | |
994 | int si476x_core_cmd_fm_phase_diversity(struct si476x_core *core, |
995 | enum si476x_phase_diversity_mode mode) |
996 | { |
997 | u8 resp[CMD_FM_PHASE_DIVERSITY_NRESP]; |
998 | const u8 args[CMD_FM_PHASE_DIVERSITY_NARGS] = { |
999 | mode & 0x07, |
1000 | }; |
1001 | |
1002 | return si476x_core_send_command(core, CMD_FM_PHASE_DIVERSITY, |
1003 | args, ARRAY_SIZE(args), |
1004 | resp, ARRAY_SIZE(resp), |
1005 | SI476X_DEFAULT_TIMEOUT); |
1006 | } |
1007 | EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_phase_diversity); |
1008 | /** |
1009 | * si476x_core_cmd_fm_phase_div_status() - get the phase diversity |
1010 | * status |
1011 | * |
1012 | * @core: si476x device |
1013 | * |
1014 | * NOTE caller must hold core lock |
1015 | * |
1016 | * Function returns the value of the status bit in case of success and |
1017 | * negative error code in case of failure. |
1018 | */ |
1019 | int si476x_core_cmd_fm_phase_div_status(struct si476x_core *core) |
1020 | { |
1021 | int err; |
1022 | u8 resp[CMD_FM_PHASE_DIV_STATUS_NRESP]; |
1023 | |
1024 | err = si476x_core_send_command(core, CMD_FM_PHASE_DIV_STATUS, |
1025 | NULL, argn: 0, |
1026 | resp, ARRAY_SIZE(resp), |
1027 | SI476X_DEFAULT_TIMEOUT); |
1028 | |
1029 | return (err < 0) ? err : resp[1]; |
1030 | } |
1031 | EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_phase_div_status); |
1032 | |
1033 | |
1034 | /** |
1035 | * si476x_core_cmd_am_seek_start - send 'FM_SEEK_START' command to the |
1036 | * device |
1037 | * @core: - device to send the command to |
1038 | * @seekup: - if set the direction of the search is 'up' |
1039 | * @wrap: - if set seek wraps when hitting band limit |
1040 | * |
1041 | * This function begins search for a valid station. The station is |
1042 | * considered valid when 'FM_VALID_SNR_THRESHOLD' and |
1043 | * 'FM_VALID_RSSI_THRESHOLD' and 'FM_VALID_MAX_TUNE_ERROR' criteria |
1044 | * are met. |
1045 | * |
1046 | * Function returns 0 on success and negative error code on failure |
1047 | */ |
1048 | int si476x_core_cmd_am_seek_start(struct si476x_core *core, |
1049 | bool seekup, bool wrap) |
1050 | { |
1051 | u8 resp[CMD_AM_SEEK_START_NRESP]; |
1052 | const u8 args[CMD_AM_SEEK_START_NARGS] = { |
1053 | seekup << 3 | wrap << 2, |
1054 | }; |
1055 | |
1056 | return si476x_cmd_tune_seek_freq(core, CMD_AM_SEEK_START, |
1057 | args, argn: sizeof(args), |
1058 | resp, respn: sizeof(resp)); |
1059 | } |
1060 | EXPORT_SYMBOL_GPL(si476x_core_cmd_am_seek_start); |
1061 | |
1062 | |
1063 | |
1064 | static int si476x_core_cmd_power_up_a10(struct si476x_core *core, |
1065 | struct si476x_power_up_args *puargs) |
1066 | { |
1067 | u8 resp[CMD_POWER_UP_A10_NRESP]; |
1068 | const bool intsel = (core->pinmux.a1 == SI476X_A1_IRQ); |
1069 | const bool ctsen = (core->client->irq != 0); |
1070 | const u8 args[CMD_POWER_UP_A10_NARGS] = { |
1071 | 0xF7, /* Reserved, always 0xF7 */ |
1072 | 0x3F & puargs->xcload, /* First two bits are reserved to be |
1073 | * zeros */ |
1074 | ctsen << 7 | intsel << 6 | 0x07, /* Last five bits |
1075 | * are reserved to |
1076 | * be written as 0x7 */ |
1077 | puargs->func << 4 | puargs->freq, |
1078 | 0x11, /* Reserved, always 0x11 */ |
1079 | }; |
1080 | |
1081 | return si476x_core_send_command(core, CMD_POWER_UP, |
1082 | args, ARRAY_SIZE(args), |
1083 | resp, ARRAY_SIZE(resp), |
1084 | SI476X_TIMEOUT_POWER_UP); |
1085 | } |
1086 | |
1087 | static int si476x_core_cmd_power_up_a20(struct si476x_core *core, |
1088 | struct si476x_power_up_args *puargs) |
1089 | { |
1090 | u8 resp[CMD_POWER_UP_A20_NRESP]; |
1091 | const bool intsel = (core->pinmux.a1 == SI476X_A1_IRQ); |
1092 | const bool ctsen = (core->client->irq != 0); |
1093 | const u8 args[CMD_POWER_UP_A20_NARGS] = { |
1094 | puargs->ibias6x << 7 | puargs->xstart, |
1095 | 0x3F & puargs->xcload, /* First two bits are reserved to be |
1096 | * zeros */ |
1097 | ctsen << 7 | intsel << 6 | puargs->fastboot << 5 | |
1098 | puargs->xbiashc << 3 | puargs->xbias, |
1099 | puargs->func << 4 | puargs->freq, |
1100 | 0x10 | puargs->xmode, |
1101 | }; |
1102 | |
1103 | return si476x_core_send_command(core, CMD_POWER_UP, |
1104 | args, ARRAY_SIZE(args), |
1105 | resp, ARRAY_SIZE(resp), |
1106 | SI476X_TIMEOUT_POWER_UP); |
1107 | } |
1108 | |
1109 | static int si476x_core_cmd_power_down_a10(struct si476x_core *core, |
1110 | struct si476x_power_down_args *pdargs) |
1111 | { |
1112 | u8 resp[CMD_POWER_DOWN_A10_NRESP]; |
1113 | |
1114 | return si476x_core_send_command(core, CMD_POWER_DOWN, |
1115 | NULL, argn: 0, |
1116 | resp, ARRAY_SIZE(resp), |
1117 | SI476X_DEFAULT_TIMEOUT); |
1118 | } |
1119 | |
1120 | static int si476x_core_cmd_power_down_a20(struct si476x_core *core, |
1121 | struct si476x_power_down_args *pdargs) |
1122 | { |
1123 | u8 resp[CMD_POWER_DOWN_A20_NRESP]; |
1124 | const u8 args[CMD_POWER_DOWN_A20_NARGS] = { |
1125 | pdargs->xosc, |
1126 | }; |
1127 | return si476x_core_send_command(core, CMD_POWER_DOWN, |
1128 | args, ARRAY_SIZE(args), |
1129 | resp, ARRAY_SIZE(resp), |
1130 | SI476X_DEFAULT_TIMEOUT); |
1131 | } |
1132 | |
1133 | static int si476x_core_cmd_am_tune_freq_a10(struct si476x_core *core, |
1134 | struct si476x_tune_freq_args *tuneargs) |
1135 | { |
1136 | |
1137 | const int am_freq = tuneargs->freq; |
1138 | u8 resp[CMD_AM_TUNE_FREQ_NRESP]; |
1139 | const u8 args[CMD_AM_TUNE_FREQ_NARGS] = { |
1140 | (tuneargs->hd << 6), |
1141 | msb(am_freq), |
1142 | lsb(am_freq), |
1143 | }; |
1144 | |
1145 | return si476x_cmd_tune_seek_freq(core, CMD_AM_TUNE_FREQ, args, |
1146 | argn: sizeof(args), |
1147 | resp, respn: sizeof(resp)); |
1148 | } |
1149 | |
1150 | static int si476x_core_cmd_am_tune_freq_a20(struct si476x_core *core, |
1151 | struct si476x_tune_freq_args *tuneargs) |
1152 | { |
1153 | const int am_freq = tuneargs->freq; |
1154 | u8 resp[CMD_AM_TUNE_FREQ_NRESP]; |
1155 | const u8 args[CMD_AM_TUNE_FREQ_NARGS] = { |
1156 | (tuneargs->zifsr << 6) | (tuneargs->injside & 0x03), |
1157 | msb(am_freq), |
1158 | lsb(am_freq), |
1159 | }; |
1160 | |
1161 | return si476x_cmd_tune_seek_freq(core, CMD_AM_TUNE_FREQ, |
1162 | args, argn: sizeof(args), |
1163 | resp, respn: sizeof(resp)); |
1164 | } |
1165 | |
1166 | static int si476x_core_cmd_fm_rsq_status_a10(struct si476x_core *core, |
1167 | struct si476x_rsq_status_args *rsqargs, |
1168 | struct si476x_rsq_status_report *report) |
1169 | { |
1170 | int err; |
1171 | u8 resp[CMD_FM_RSQ_STATUS_A10_NRESP]; |
1172 | const u8 args[CMD_FM_RSQ_STATUS_A10_NARGS] = { |
1173 | rsqargs->rsqack << 3 | rsqargs->attune << 2 | |
1174 | rsqargs->cancel << 1 | rsqargs->stcack, |
1175 | }; |
1176 | |
1177 | err = si476x_core_send_command(core, CMD_FM_RSQ_STATUS, |
1178 | args, ARRAY_SIZE(args), |
1179 | resp, ARRAY_SIZE(resp), |
1180 | SI476X_DEFAULT_TIMEOUT); |
1181 | /* |
1182 | * Besides getting received signal quality information this |
1183 | * command can be used to just acknowledge different interrupt |
1184 | * flags in those cases it is useless to copy and parse |
1185 | * received data so user can pass NULL, and thus avoid |
1186 | * unnecessary copying. |
1187 | */ |
1188 | if (err < 0 || report == NULL) |
1189 | return err; |
1190 | |
1191 | report->multhint = 0x80 & resp[1]; |
1192 | report->multlint = 0x40 & resp[1]; |
1193 | report->snrhint = 0x08 & resp[1]; |
1194 | report->snrlint = 0x04 & resp[1]; |
1195 | report->rssihint = 0x02 & resp[1]; |
1196 | report->rssilint = 0x01 & resp[1]; |
1197 | |
1198 | report->bltf = 0x80 & resp[2]; |
1199 | report->snr_ready = 0x20 & resp[2]; |
1200 | report->rssiready = 0x08 & resp[2]; |
1201 | report->afcrl = 0x02 & resp[2]; |
1202 | report->valid = 0x01 & resp[2]; |
1203 | |
1204 | report->readfreq = get_unaligned_be16(p: resp + 3); |
1205 | report->freqoff = resp[5]; |
1206 | report->rssi = resp[6]; |
1207 | report->snr = resp[7]; |
1208 | report->lassi = resp[9]; |
1209 | report->hassi = resp[10]; |
1210 | report->mult = resp[11]; |
1211 | report->dev = resp[12]; |
1212 | report->readantcap = get_unaligned_be16(p: resp + 13); |
1213 | report->assi = resp[15]; |
1214 | report->usn = resp[16]; |
1215 | |
1216 | return err; |
1217 | } |
1218 | |
1219 | static int si476x_core_cmd_fm_rsq_status_a20(struct si476x_core *core, |
1220 | struct si476x_rsq_status_args *rsqargs, |
1221 | struct si476x_rsq_status_report *report) |
1222 | { |
1223 | int err; |
1224 | u8 resp[CMD_FM_RSQ_STATUS_A10_NRESP]; |
1225 | const u8 args[CMD_FM_RSQ_STATUS_A30_NARGS] = { |
1226 | rsqargs->primary << 4 | rsqargs->rsqack << 3 | |
1227 | rsqargs->attune << 2 | rsqargs->cancel << 1 | |
1228 | rsqargs->stcack, |
1229 | }; |
1230 | |
1231 | err = si476x_core_send_command(core, CMD_FM_RSQ_STATUS, |
1232 | args, ARRAY_SIZE(args), |
1233 | resp, ARRAY_SIZE(resp), |
1234 | SI476X_DEFAULT_TIMEOUT); |
1235 | /* |
1236 | * Besides getting received signal quality information this |
1237 | * command can be used to just acknowledge different interrupt |
1238 | * flags in those cases it is useless to copy and parse |
1239 | * received data so user can pass NULL, and thus avoid |
1240 | * unnecessary copying. |
1241 | */ |
1242 | if (err < 0 || report == NULL) |
1243 | return err; |
1244 | |
1245 | report->multhint = 0x80 & resp[1]; |
1246 | report->multlint = 0x40 & resp[1]; |
1247 | report->snrhint = 0x08 & resp[1]; |
1248 | report->snrlint = 0x04 & resp[1]; |
1249 | report->rssihint = 0x02 & resp[1]; |
1250 | report->rssilint = 0x01 & resp[1]; |
1251 | |
1252 | report->bltf = 0x80 & resp[2]; |
1253 | report->snr_ready = 0x20 & resp[2]; |
1254 | report->rssiready = 0x08 & resp[2]; |
1255 | report->afcrl = 0x02 & resp[2]; |
1256 | report->valid = 0x01 & resp[2]; |
1257 | |
1258 | report->readfreq = get_unaligned_be16(p: resp + 3); |
1259 | report->freqoff = resp[5]; |
1260 | report->rssi = resp[6]; |
1261 | report->snr = resp[7]; |
1262 | report->lassi = resp[9]; |
1263 | report->hassi = resp[10]; |
1264 | report->mult = resp[11]; |
1265 | report->dev = resp[12]; |
1266 | report->readantcap = get_unaligned_be16(p: resp + 13); |
1267 | report->assi = resp[15]; |
1268 | report->usn = resp[16]; |
1269 | |
1270 | return err; |
1271 | } |
1272 | |
1273 | |
1274 | static int si476x_core_cmd_fm_rsq_status_a30(struct si476x_core *core, |
1275 | struct si476x_rsq_status_args *rsqargs, |
1276 | struct si476x_rsq_status_report *report) |
1277 | { |
1278 | int err; |
1279 | u8 resp[CMD_FM_RSQ_STATUS_A30_NRESP]; |
1280 | const u8 args[CMD_FM_RSQ_STATUS_A30_NARGS] = { |
1281 | rsqargs->primary << 4 | rsqargs->rsqack << 3 | |
1282 | rsqargs->attune << 2 | rsqargs->cancel << 1 | |
1283 | rsqargs->stcack, |
1284 | }; |
1285 | |
1286 | err = si476x_core_send_command(core, CMD_FM_RSQ_STATUS, |
1287 | args, ARRAY_SIZE(args), |
1288 | resp, ARRAY_SIZE(resp), |
1289 | SI476X_DEFAULT_TIMEOUT); |
1290 | /* |
1291 | * Besides getting received signal quality information this |
1292 | * command can be used to just acknowledge different interrupt |
1293 | * flags in those cases it is useless to copy and parse |
1294 | * received data so user can pass NULL, and thus avoid |
1295 | * unnecessary copying. |
1296 | */ |
1297 | if (err < 0 || report == NULL) |
1298 | return err; |
1299 | |
1300 | report->multhint = 0x80 & resp[1]; |
1301 | report->multlint = 0x40 & resp[1]; |
1302 | report->snrhint = 0x08 & resp[1]; |
1303 | report->snrlint = 0x04 & resp[1]; |
1304 | report->rssihint = 0x02 & resp[1]; |
1305 | report->rssilint = 0x01 & resp[1]; |
1306 | |
1307 | report->bltf = 0x80 & resp[2]; |
1308 | report->snr_ready = 0x20 & resp[2]; |
1309 | report->rssiready = 0x08 & resp[2]; |
1310 | report->injside = 0x04 & resp[2]; |
1311 | report->afcrl = 0x02 & resp[2]; |
1312 | report->valid = 0x01 & resp[2]; |
1313 | |
1314 | report->readfreq = get_unaligned_be16(p: resp + 3); |
1315 | report->freqoff = resp[5]; |
1316 | report->rssi = resp[6]; |
1317 | report->snr = resp[7]; |
1318 | report->issi = resp[8]; |
1319 | report->lassi = resp[9]; |
1320 | report->hassi = resp[10]; |
1321 | report->mult = resp[11]; |
1322 | report->dev = resp[12]; |
1323 | report->readantcap = get_unaligned_be16(p: resp + 13); |
1324 | report->assi = resp[15]; |
1325 | report->usn = resp[16]; |
1326 | |
1327 | report->pilotdev = resp[17]; |
1328 | report->rdsdev = resp[18]; |
1329 | report->assidev = resp[19]; |
1330 | report->strongdev = resp[20]; |
1331 | report->rdspi = get_unaligned_be16(p: resp + 21); |
1332 | |
1333 | return err; |
1334 | } |
1335 | |
1336 | static int si476x_core_cmd_fm_tune_freq_a10(struct si476x_core *core, |
1337 | struct si476x_tune_freq_args *tuneargs) |
1338 | { |
1339 | u8 resp[CMD_FM_TUNE_FREQ_NRESP]; |
1340 | const u8 args[CMD_FM_TUNE_FREQ_A10_NARGS] = { |
1341 | (tuneargs->hd << 6) | (tuneargs->tunemode << 4) |
1342 | | (tuneargs->smoothmetrics << 2), |
1343 | msb(tuneargs->freq), |
1344 | lsb(tuneargs->freq), |
1345 | msb(tuneargs->antcap), |
1346 | lsb(tuneargs->antcap) |
1347 | }; |
1348 | |
1349 | return si476x_cmd_tune_seek_freq(core, CMD_FM_TUNE_FREQ, |
1350 | args, argn: sizeof(args), |
1351 | resp, respn: sizeof(resp)); |
1352 | } |
1353 | |
1354 | static int si476x_core_cmd_fm_tune_freq_a20(struct si476x_core *core, |
1355 | struct si476x_tune_freq_args *tuneargs) |
1356 | { |
1357 | u8 resp[CMD_FM_TUNE_FREQ_NRESP]; |
1358 | const u8 args[CMD_FM_TUNE_FREQ_A20_NARGS] = { |
1359 | (tuneargs->hd << 6) | (tuneargs->tunemode << 4) |
1360 | | (tuneargs->smoothmetrics << 2) | (tuneargs->injside), |
1361 | msb(tuneargs->freq), |
1362 | lsb(tuneargs->freq), |
1363 | }; |
1364 | |
1365 | return si476x_cmd_tune_seek_freq(core, CMD_FM_TUNE_FREQ, |
1366 | args, argn: sizeof(args), |
1367 | resp, respn: sizeof(resp)); |
1368 | } |
1369 | |
1370 | static int si476x_core_cmd_agc_status_a20(struct si476x_core *core, |
1371 | struct si476x_agc_status_report *report) |
1372 | { |
1373 | int err; |
1374 | u8 resp[CMD_AGC_STATUS_NRESP_A20]; |
1375 | |
1376 | if (!report) |
1377 | return -EINVAL; |
1378 | |
1379 | err = si476x_core_send_command(core, CMD_AGC_STATUS, |
1380 | NULL, argn: 0, |
1381 | resp, ARRAY_SIZE(resp), |
1382 | SI476X_DEFAULT_TIMEOUT); |
1383 | if (err < 0) |
1384 | return err; |
1385 | |
1386 | report->mxhi = resp[1] & SI476X_AGC_MXHI; |
1387 | report->mxlo = resp[1] & SI476X_AGC_MXLO; |
1388 | report->lnahi = resp[1] & SI476X_AGC_LNAHI; |
1389 | report->lnalo = resp[1] & SI476X_AGC_LNALO; |
1390 | report->fmagc1 = resp[2]; |
1391 | report->fmagc2 = resp[3]; |
1392 | report->pgagain = resp[4]; |
1393 | report->fmwblang = resp[5]; |
1394 | |
1395 | return err; |
1396 | } |
1397 | |
1398 | static int si476x_core_cmd_agc_status_a10(struct si476x_core *core, |
1399 | struct si476x_agc_status_report *report) |
1400 | { |
1401 | int err; |
1402 | u8 resp[CMD_AGC_STATUS_NRESP_A10]; |
1403 | |
1404 | if (!report) |
1405 | return -EINVAL; |
1406 | |
1407 | err = si476x_core_send_command(core, CMD_AGC_STATUS, |
1408 | NULL, argn: 0, |
1409 | resp, ARRAY_SIZE(resp), |
1410 | SI476X_DEFAULT_TIMEOUT); |
1411 | if (err < 0) |
1412 | return err; |
1413 | |
1414 | report->mxhi = resp[1] & SI476X_AGC_MXHI; |
1415 | report->mxlo = resp[1] & SI476X_AGC_MXLO; |
1416 | report->lnahi = resp[1] & SI476X_AGC_LNAHI; |
1417 | report->lnalo = resp[1] & SI476X_AGC_LNALO; |
1418 | |
1419 | return err; |
1420 | } |
1421 | |
1422 | typedef int (*tune_freq_func_t) (struct si476x_core *core, |
1423 | struct si476x_tune_freq_args *tuneargs); |
1424 | |
1425 | static struct { |
1426 | int (*power_up)(struct si476x_core *, |
1427 | struct si476x_power_up_args *); |
1428 | int (*power_down)(struct si476x_core *, |
1429 | struct si476x_power_down_args *); |
1430 | |
1431 | tune_freq_func_t fm_tune_freq; |
1432 | tune_freq_func_t am_tune_freq; |
1433 | |
1434 | int (*fm_rsq_status)(struct si476x_core *, |
1435 | struct si476x_rsq_status_args *, |
1436 | struct si476x_rsq_status_report *); |
1437 | |
1438 | int (*agc_status)(struct si476x_core *, |
1439 | struct si476x_agc_status_report *); |
1440 | int (*intb_pin_cfg)(struct si476x_core *core, |
1441 | enum si476x_intb_config intb, |
1442 | enum si476x_a1_config a1); |
1443 | } si476x_cmds_vtable[] = { |
1444 | [SI476X_REVISION_A10] = { |
1445 | .power_up = si476x_core_cmd_power_up_a10, |
1446 | .power_down = si476x_core_cmd_power_down_a10, |
1447 | .fm_tune_freq = si476x_core_cmd_fm_tune_freq_a10, |
1448 | .am_tune_freq = si476x_core_cmd_am_tune_freq_a10, |
1449 | .fm_rsq_status = si476x_core_cmd_fm_rsq_status_a10, |
1450 | .agc_status = si476x_core_cmd_agc_status_a10, |
1451 | .intb_pin_cfg = si476x_core_cmd_intb_pin_cfg_a10, |
1452 | }, |
1453 | [SI476X_REVISION_A20] = { |
1454 | .power_up = si476x_core_cmd_power_up_a20, |
1455 | .power_down = si476x_core_cmd_power_down_a20, |
1456 | .fm_tune_freq = si476x_core_cmd_fm_tune_freq_a20, |
1457 | .am_tune_freq = si476x_core_cmd_am_tune_freq_a20, |
1458 | .fm_rsq_status = si476x_core_cmd_fm_rsq_status_a20, |
1459 | .agc_status = si476x_core_cmd_agc_status_a20, |
1460 | .intb_pin_cfg = si476x_core_cmd_intb_pin_cfg_a20, |
1461 | }, |
1462 | [SI476X_REVISION_A30] = { |
1463 | .power_up = si476x_core_cmd_power_up_a20, |
1464 | .power_down = si476x_core_cmd_power_down_a20, |
1465 | .fm_tune_freq = si476x_core_cmd_fm_tune_freq_a20, |
1466 | .am_tune_freq = si476x_core_cmd_am_tune_freq_a20, |
1467 | .fm_rsq_status = si476x_core_cmd_fm_rsq_status_a30, |
1468 | .agc_status = si476x_core_cmd_agc_status_a20, |
1469 | .intb_pin_cfg = si476x_core_cmd_intb_pin_cfg_a20, |
1470 | }, |
1471 | }; |
1472 | |
1473 | int si476x_core_cmd_power_up(struct si476x_core *core, |
1474 | struct si476x_power_up_args *args) |
1475 | { |
1476 | BUG_ON(core->revision > SI476X_REVISION_A30 || |
1477 | core->revision == -1); |
1478 | return si476x_cmds_vtable[core->revision].power_up(core, args); |
1479 | } |
1480 | EXPORT_SYMBOL_GPL(si476x_core_cmd_power_up); |
1481 | |
1482 | int si476x_core_cmd_power_down(struct si476x_core *core, |
1483 | struct si476x_power_down_args *args) |
1484 | { |
1485 | BUG_ON(core->revision > SI476X_REVISION_A30 || |
1486 | core->revision == -1); |
1487 | return si476x_cmds_vtable[core->revision].power_down(core, args); |
1488 | } |
1489 | EXPORT_SYMBOL_GPL(si476x_core_cmd_power_down); |
1490 | |
1491 | int si476x_core_cmd_fm_tune_freq(struct si476x_core *core, |
1492 | struct si476x_tune_freq_args *args) |
1493 | { |
1494 | BUG_ON(core->revision > SI476X_REVISION_A30 || |
1495 | core->revision == -1); |
1496 | return si476x_cmds_vtable[core->revision].fm_tune_freq(core, args); |
1497 | } |
1498 | EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_tune_freq); |
1499 | |
1500 | int si476x_core_cmd_am_tune_freq(struct si476x_core *core, |
1501 | struct si476x_tune_freq_args *args) |
1502 | { |
1503 | BUG_ON(core->revision > SI476X_REVISION_A30 || |
1504 | core->revision == -1); |
1505 | return si476x_cmds_vtable[core->revision].am_tune_freq(core, args); |
1506 | } |
1507 | EXPORT_SYMBOL_GPL(si476x_core_cmd_am_tune_freq); |
1508 | |
1509 | int si476x_core_cmd_fm_rsq_status(struct si476x_core *core, |
1510 | struct si476x_rsq_status_args *args, |
1511 | struct si476x_rsq_status_report *report) |
1512 | |
1513 | { |
1514 | BUG_ON(core->revision > SI476X_REVISION_A30 || |
1515 | core->revision == -1); |
1516 | return si476x_cmds_vtable[core->revision].fm_rsq_status(core, args, |
1517 | report); |
1518 | } |
1519 | EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_rsq_status); |
1520 | |
1521 | int si476x_core_cmd_agc_status(struct si476x_core *core, |
1522 | struct si476x_agc_status_report *report) |
1523 | |
1524 | { |
1525 | BUG_ON(core->revision > SI476X_REVISION_A30 || |
1526 | core->revision == -1); |
1527 | return si476x_cmds_vtable[core->revision].agc_status(core, report); |
1528 | } |
1529 | EXPORT_SYMBOL_GPL(si476x_core_cmd_agc_status); |
1530 | |
1531 | int si476x_core_cmd_intb_pin_cfg(struct si476x_core *core, |
1532 | enum si476x_intb_config intb, |
1533 | enum si476x_a1_config a1) |
1534 | { |
1535 | BUG_ON(core->revision > SI476X_REVISION_A30 || |
1536 | core->revision == -1); |
1537 | |
1538 | return si476x_cmds_vtable[core->revision].intb_pin_cfg(core, intb, a1); |
1539 | } |
1540 | EXPORT_SYMBOL_GPL(si476x_core_cmd_intb_pin_cfg); |
1541 | |
1542 | MODULE_LICENSE("GPL" ); |
1543 | MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>" ); |
1544 | MODULE_DESCRIPTION("API for command exchange for si476x" ); |
1545 | |