diff options
author | Andrey Smirnov <andreysm@charmander.(none)> | 2013-04-18 12:58:28 -0400 |
---|---|---|
committer | Samuel Ortiz <sameo@linux.intel.com> | 2013-04-19 12:37:39 -0400 |
commit | 730a30ab7fba60c986e0e4a362bb21d2ed196a29 (patch) | |
tree | 530d1ff3b1f23dff3be0b6be163252020b07601a /drivers/mfd/si476x-i2c.c | |
parent | ed4a8fe892ccbeb75a2ab380ef6ef5ef6d9d2636 (diff) |
mfd: si476x: Add the bulk of the core driver
This patch adds main part(out of three) of the I2C driver for the
"core" of MFD device.
Acked-by: Hans Verkuil <hans.verkuil@cisco.com>
Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Diffstat (limited to 'drivers/mfd/si476x-i2c.c')
-rw-r--r-- | drivers/mfd/si476x-i2c.c | 886 |
1 files changed, 886 insertions, 0 deletions
diff --git a/drivers/mfd/si476x-i2c.c b/drivers/mfd/si476x-i2c.c new file mode 100644 index 000000000000..118c6b13d8cb --- /dev/null +++ b/drivers/mfd/si476x-i2c.c | |||
@@ -0,0 +1,886 @@ | |||
1 | /* | ||
2 | * drivers/mfd/si476x-i2c.c -- Core device driver for si476x MFD | ||
3 | * device | ||
4 | * | ||
5 | * Copyright (C) 2012 Innovative Converged Devices(ICD) | ||
6 | * Copyright (C) 2013 Andrey Smirnov | ||
7 | * | ||
8 | * Author: Andrey Smirnov <andrew.smirnov@gmail.com> | ||
9 | * | ||
10 | * This program is free software; you can redistribute it and/or modify | ||
11 | * it under the terms of the GNU General Public License as published by | ||
12 | * the Free Software Foundation; version 2 of the License. | ||
13 | * | ||
14 | * This program is distributed in the hope that it will be useful, but | ||
15 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
17 | * General Public License for more details. | ||
18 | * | ||
19 | */ | ||
20 | #include <linux/module.h> | ||
21 | |||
22 | #include <linux/slab.h> | ||
23 | #include <linux/interrupt.h> | ||
24 | #include <linux/delay.h> | ||
25 | #include <linux/gpio.h> | ||
26 | #include <linux/regulator/consumer.h> | ||
27 | #include <linux/i2c.h> | ||
28 | #include <linux/err.h> | ||
29 | |||
30 | #include <linux/mfd/si476x-core.h> | ||
31 | |||
32 | #define SI476X_MAX_IO_ERRORS 10 | ||
33 | #define SI476X_DRIVER_RDS_FIFO_DEPTH 128 | ||
34 | |||
35 | /** | ||
36 | * si476x_core_config_pinmux() - pin function configuration function | ||
37 | * | ||
38 | * @core: Core device structure | ||
39 | * | ||
40 | * Configure the functions of the pins of the radio chip. | ||
41 | * | ||
42 | * The function returns zero in case of succes or negative error code | ||
43 | * otherwise. | ||
44 | */ | ||
45 | static int si476x_core_config_pinmux(struct si476x_core *core) | ||
46 | { | ||
47 | int err; | ||
48 | dev_dbg(&core->client->dev, "Configuring pinmux\n"); | ||
49 | err = si476x_core_cmd_dig_audio_pin_cfg(core, | ||
50 | core->pinmux.dclk, | ||
51 | core->pinmux.dfs, | ||
52 | core->pinmux.dout, | ||
53 | core->pinmux.xout); | ||
54 | if (err < 0) { | ||
55 | dev_err(&core->client->dev, | ||
56 | "Failed to configure digital audio pins(err = %d)\n", | ||
57 | err); | ||
58 | return err; | ||
59 | } | ||
60 | |||
61 | err = si476x_core_cmd_zif_pin_cfg(core, | ||
62 | core->pinmux.iqclk, | ||
63 | core->pinmux.iqfs, | ||
64 | core->pinmux.iout, | ||
65 | core->pinmux.qout); | ||
66 | if (err < 0) { | ||
67 | dev_err(&core->client->dev, | ||
68 | "Failed to configure ZIF pins(err = %d)\n", | ||
69 | err); | ||
70 | return err; | ||
71 | } | ||
72 | |||
73 | err = si476x_core_cmd_ic_link_gpo_ctl_pin_cfg(core, | ||
74 | core->pinmux.icin, | ||
75 | core->pinmux.icip, | ||
76 | core->pinmux.icon, | ||
77 | core->pinmux.icop); | ||
78 | if (err < 0) { | ||
79 | dev_err(&core->client->dev, | ||
80 | "Failed to configure IC-Link/GPO pins(err = %d)\n", | ||
81 | err); | ||
82 | return err; | ||
83 | } | ||
84 | |||
85 | err = si476x_core_cmd_ana_audio_pin_cfg(core, | ||
86 | core->pinmux.lrout); | ||
87 | if (err < 0) { | ||
88 | dev_err(&core->client->dev, | ||
89 | "Failed to configure analog audio pins(err = %d)\n", | ||
90 | err); | ||
91 | return err; | ||
92 | } | ||
93 | |||
94 | err = si476x_core_cmd_intb_pin_cfg(core, | ||
95 | core->pinmux.intb, | ||
96 | core->pinmux.a1); | ||
97 | if (err < 0) { | ||
98 | dev_err(&core->client->dev, | ||
99 | "Failed to configure interrupt pins(err = %d)\n", | ||
100 | err); | ||
101 | return err; | ||
102 | } | ||
103 | |||
104 | return 0; | ||
105 | } | ||
106 | |||
107 | static inline void si476x_core_schedule_polling_work(struct si476x_core *core) | ||
108 | { | ||
109 | schedule_delayed_work(&core->status_monitor, | ||
110 | usecs_to_jiffies(SI476X_STATUS_POLL_US)); | ||
111 | } | ||
112 | |||
113 | /** | ||
114 | * si476x_core_start() - early chip startup function | ||
115 | * @core: Core device structure | ||
116 | * @soft: When set, this flag forces "soft" startup, where "soft" | ||
117 | * power down is the one done by sending appropriate command instead | ||
118 | * of using reset pin of the tuner | ||
119 | * | ||
120 | * Perform required startup sequence to correctly power | ||
121 | * up the chip and perform initial configuration. It does the | ||
122 | * following sequence of actions: | ||
123 | * 1. Claims and enables the power supplies VD and VIO1 required | ||
124 | * for I2C interface of the chip operation. | ||
125 | * 2. Waits for 100us, pulls the reset line up, enables irq, | ||
126 | * waits for another 100us as it is specified by the | ||
127 | * datasheet. | ||
128 | * 3. Sends 'POWER_UP' command to the device with all provided | ||
129 | * information about power-up parameters. | ||
130 | * 4. Configures, pin multiplexor, disables digital audio and | ||
131 | * configures interrupt sources. | ||
132 | * | ||
133 | * The function returns zero in case of succes or negative error code | ||
134 | * otherwise. | ||
135 | */ | ||
136 | int si476x_core_start(struct si476x_core *core, bool soft) | ||
137 | { | ||
138 | struct i2c_client *client = core->client; | ||
139 | int err; | ||
140 | |||
141 | if (!soft) { | ||
142 | if (gpio_is_valid(core->gpio_reset)) | ||
143 | gpio_set_value_cansleep(core->gpio_reset, 1); | ||
144 | |||
145 | if (client->irq) | ||
146 | enable_irq(client->irq); | ||
147 | |||
148 | udelay(100); | ||
149 | |||
150 | if (!client->irq) { | ||
151 | atomic_set(&core->is_alive, 1); | ||
152 | si476x_core_schedule_polling_work(core); | ||
153 | } | ||
154 | } else { | ||
155 | if (client->irq) | ||
156 | enable_irq(client->irq); | ||
157 | else { | ||
158 | atomic_set(&core->is_alive, 1); | ||
159 | si476x_core_schedule_polling_work(core); | ||
160 | } | ||
161 | } | ||
162 | |||
163 | err = si476x_core_cmd_power_up(core, | ||
164 | &core->power_up_parameters); | ||
165 | |||
166 | if (err < 0) { | ||
167 | dev_err(&core->client->dev, | ||
168 | "Power up failure(err = %d)\n", | ||
169 | err); | ||
170 | goto disable_irq; | ||
171 | } | ||
172 | |||
173 | if (client->irq) | ||
174 | atomic_set(&core->is_alive, 1); | ||
175 | |||
176 | err = si476x_core_config_pinmux(core); | ||
177 | if (err < 0) { | ||
178 | dev_err(&core->client->dev, | ||
179 | "Failed to configure pinmux(err = %d)\n", | ||
180 | err); | ||
181 | goto disable_irq; | ||
182 | } | ||
183 | |||
184 | if (client->irq) { | ||
185 | err = regmap_write(core->regmap, | ||
186 | SI476X_PROP_INT_CTL_ENABLE, | ||
187 | SI476X_RDSIEN | | ||
188 | SI476X_STCIEN | | ||
189 | SI476X_CTSIEN); | ||
190 | if (err < 0) { | ||
191 | dev_err(&core->client->dev, | ||
192 | "Failed to configure interrupt sources" | ||
193 | "(err = %d)\n", err); | ||
194 | goto disable_irq; | ||
195 | } | ||
196 | } | ||
197 | |||
198 | return 0; | ||
199 | |||
200 | disable_irq: | ||
201 | if (err == -ENODEV) | ||
202 | atomic_set(&core->is_alive, 0); | ||
203 | |||
204 | if (client->irq) | ||
205 | disable_irq(client->irq); | ||
206 | else | ||
207 | cancel_delayed_work_sync(&core->status_monitor); | ||
208 | |||
209 | if (gpio_is_valid(core->gpio_reset)) | ||
210 | gpio_set_value_cansleep(core->gpio_reset, 0); | ||
211 | |||
212 | return err; | ||
213 | } | ||
214 | EXPORT_SYMBOL_GPL(si476x_core_start); | ||
215 | |||
216 | /** | ||
217 | * si476x_core_stop() - chip power-down function | ||
218 | * @core: Core device structure | ||
219 | * @soft: When set, function sends a POWER_DOWN command instead of | ||
220 | * bringing reset line low | ||
221 | * | ||
222 | * Power down the chip by performing following actions: | ||
223 | * 1. Disable IRQ or stop the polling worker | ||
224 | * 2. Send the POWER_DOWN command if the power down is soft or bring | ||
225 | * reset line low if not. | ||
226 | * | ||
227 | * The function returns zero in case of succes or negative error code | ||
228 | * otherwise. | ||
229 | */ | ||
230 | int si476x_core_stop(struct si476x_core *core, bool soft) | ||
231 | { | ||
232 | int err = 0; | ||
233 | atomic_set(&core->is_alive, 0); | ||
234 | |||
235 | if (soft) { | ||
236 | /* TODO: This probably shoud be a configurable option, | ||
237 | * so it is possible to have the chips keep their | ||
238 | * oscillators running | ||
239 | */ | ||
240 | struct si476x_power_down_args args = { | ||
241 | .xosc = false, | ||
242 | }; | ||
243 | err = si476x_core_cmd_power_down(core, &args); | ||
244 | } | ||
245 | |||
246 | /* We couldn't disable those before | ||
247 | * 'si476x_core_cmd_power_down' since we expect to get CTS | ||
248 | * interrupt */ | ||
249 | if (core->client->irq) | ||
250 | disable_irq(core->client->irq); | ||
251 | else | ||
252 | cancel_delayed_work_sync(&core->status_monitor); | ||
253 | |||
254 | if (!soft) { | ||
255 | if (gpio_is_valid(core->gpio_reset)) | ||
256 | gpio_set_value_cansleep(core->gpio_reset, 0); | ||
257 | } | ||
258 | return err; | ||
259 | } | ||
260 | EXPORT_SYMBOL_GPL(si476x_core_stop); | ||
261 | |||
262 | /** | ||
263 | * si476x_core_set_power_state() - set the level at which the power is | ||
264 | * supplied for the chip. | ||
265 | * @core: Core device structure | ||
266 | * @next_state: enum si476x_power_state describing power state to | ||
267 | * switch to. | ||
268 | * | ||
269 | * Switch on all the required power supplies | ||
270 | * | ||
271 | * This function returns 0 in case of suvccess and negative error code | ||
272 | * otherwise. | ||
273 | */ | ||
274 | int si476x_core_set_power_state(struct si476x_core *core, | ||
275 | enum si476x_power_state next_state) | ||
276 | { | ||
277 | /* | ||
278 | It is not clear form the datasheet if it is possible to | ||
279 | work with device if not all power domains are operational. | ||
280 | So for now the power-up policy is "power-up all the things!" | ||
281 | */ | ||
282 | int err = 0; | ||
283 | |||
284 | if (core->power_state == SI476X_POWER_INCONSISTENT) { | ||
285 | dev_err(&core->client->dev, | ||
286 | "The device in inconsistent power state\n"); | ||
287 | return -EINVAL; | ||
288 | } | ||
289 | |||
290 | if (next_state != core->power_state) { | ||
291 | switch (next_state) { | ||
292 | case SI476X_POWER_UP_FULL: | ||
293 | err = regulator_bulk_enable(ARRAY_SIZE(core->supplies), | ||
294 | core->supplies); | ||
295 | if (err < 0) { | ||
296 | core->power_state = SI476X_POWER_INCONSISTENT; | ||
297 | break; | ||
298 | } | ||
299 | /* | ||
300 | * Startup timing diagram recommends to have a | ||
301 | * 100 us delay between enabling of the power | ||
302 | * supplies and turning the tuner on. | ||
303 | */ | ||
304 | udelay(100); | ||
305 | |||
306 | err = si476x_core_start(core, false); | ||
307 | if (err < 0) | ||
308 | goto disable_regulators; | ||
309 | |||
310 | core->power_state = next_state; | ||
311 | break; | ||
312 | |||
313 | case SI476X_POWER_DOWN: | ||
314 | core->power_state = next_state; | ||
315 | err = si476x_core_stop(core, false); | ||
316 | if (err < 0) | ||
317 | core->power_state = SI476X_POWER_INCONSISTENT; | ||
318 | disable_regulators: | ||
319 | err = regulator_bulk_disable(ARRAY_SIZE(core->supplies), | ||
320 | core->supplies); | ||
321 | if (err < 0) | ||
322 | core->power_state = SI476X_POWER_INCONSISTENT; | ||
323 | break; | ||
324 | default: | ||
325 | BUG(); | ||
326 | } | ||
327 | } | ||
328 | |||
329 | return err; | ||
330 | } | ||
331 | EXPORT_SYMBOL_GPL(si476x_core_set_power_state); | ||
332 | |||
333 | /** | ||
334 | * si476x_core_report_drainer_stop() - mark the completion of the RDS | ||
335 | * buffer drain porcess by the worker. | ||
336 | * | ||
337 | * @core: Core device structure | ||
338 | */ | ||
339 | static inline void si476x_core_report_drainer_stop(struct si476x_core *core) | ||
340 | { | ||
341 | mutex_lock(&core->rds_drainer_status_lock); | ||
342 | core->rds_drainer_is_working = false; | ||
343 | mutex_unlock(&core->rds_drainer_status_lock); | ||
344 | } | ||
345 | |||
346 | /** | ||
347 | * si476x_core_start_rds_drainer_once() - start RDS drainer worker if | ||
348 | * ther is none working, do nothing otherwise | ||
349 | * | ||
350 | * @core: Datastructure corresponding to the chip. | ||
351 | */ | ||
352 | static inline void si476x_core_start_rds_drainer_once(struct si476x_core *core) | ||
353 | { | ||
354 | mutex_lock(&core->rds_drainer_status_lock); | ||
355 | if (!core->rds_drainer_is_working) { | ||
356 | core->rds_drainer_is_working = true; | ||
357 | schedule_work(&core->rds_fifo_drainer); | ||
358 | } | ||
359 | mutex_unlock(&core->rds_drainer_status_lock); | ||
360 | } | ||
361 | /** | ||
362 | * si476x_drain_rds_fifo() - RDS buffer drainer. | ||
363 | * @work: struct work_struct being ppassed to the function by the | ||
364 | * kernel. | ||
365 | * | ||
366 | * Drain the contents of the RDS FIFO of | ||
367 | */ | ||
368 | static void si476x_core_drain_rds_fifo(struct work_struct *work) | ||
369 | { | ||
370 | int err; | ||
371 | |||
372 | struct si476x_core *core = container_of(work, struct si476x_core, | ||
373 | rds_fifo_drainer); | ||
374 | |||
375 | struct si476x_rds_status_report report; | ||
376 | |||
377 | si476x_core_lock(core); | ||
378 | err = si476x_core_cmd_fm_rds_status(core, true, false, false, &report); | ||
379 | if (!err) { | ||
380 | int i = report.rdsfifoused; | ||
381 | dev_dbg(&core->client->dev, | ||
382 | "%d elements in RDS FIFO. Draining.\n", i); | ||
383 | for (; i > 0; --i) { | ||
384 | err = si476x_core_cmd_fm_rds_status(core, false, false, | ||
385 | (i == 1), &report); | ||
386 | if (err < 0) | ||
387 | goto unlock; | ||
388 | |||
389 | kfifo_in(&core->rds_fifo, report.rds, | ||
390 | sizeof(report.rds)); | ||
391 | dev_dbg(&core->client->dev, "RDS data:\n %*ph\n", | ||
392 | sizeof(report.rds), report.rds); | ||
393 | } | ||
394 | dev_dbg(&core->client->dev, "Drrrrained!\n"); | ||
395 | wake_up_interruptible(&core->rds_read_queue); | ||
396 | } | ||
397 | |||
398 | unlock: | ||
399 | si476x_core_unlock(core); | ||
400 | si476x_core_report_drainer_stop(core); | ||
401 | } | ||
402 | |||
403 | /** | ||
404 | * si476x_core_pronounce_dead() | ||
405 | * | ||
406 | * @core: Core device structure | ||
407 | * | ||
408 | * Mark the device as being dead and wake up all potentially waiting | ||
409 | * threads of execution. | ||
410 | * | ||
411 | */ | ||
412 | static void si476x_core_pronounce_dead(struct si476x_core *core) | ||
413 | { | ||
414 | dev_info(&core->client->dev, "Core device is dead.\n"); | ||
415 | |||
416 | atomic_set(&core->is_alive, 0); | ||
417 | |||
418 | /* Wake up al possible waiting processes */ | ||
419 | wake_up_interruptible(&core->rds_read_queue); | ||
420 | |||
421 | atomic_set(&core->cts, 1); | ||
422 | wake_up(&core->command); | ||
423 | |||
424 | atomic_set(&core->stc, 1); | ||
425 | wake_up(&core->tuning); | ||
426 | } | ||
427 | |||
428 | /** | ||
429 | * si476x_core_i2c_xfer() | ||
430 | * | ||
431 | * @core: Core device structure | ||
432 | * @type: Transfer type | ||
433 | * @buf: Transfer buffer for/with data | ||
434 | * @count: Transfer buffer size | ||
435 | * | ||
436 | * Perfrom and I2C transfer(either read or write) and keep a counter | ||
437 | * of I/O errors. If the error counter rises above the threshold | ||
438 | * pronounce device dead. | ||
439 | * | ||
440 | * The function returns zero on succes or negative error code on | ||
441 | * failure. | ||
442 | */ | ||
443 | int si476x_core_i2c_xfer(struct si476x_core *core, | ||
444 | enum si476x_i2c_type type, | ||
445 | char *buf, int count) | ||
446 | { | ||
447 | static int io_errors_count; | ||
448 | int err; | ||
449 | if (type == SI476X_I2C_SEND) | ||
450 | err = i2c_master_send(core->client, buf, count); | ||
451 | else | ||
452 | err = i2c_master_recv(core->client, buf, count); | ||
453 | |||
454 | if (err < 0) { | ||
455 | if (io_errors_count++ > SI476X_MAX_IO_ERRORS) | ||
456 | si476x_core_pronounce_dead(core); | ||
457 | } else { | ||
458 | io_errors_count = 0; | ||
459 | } | ||
460 | |||
461 | return err; | ||
462 | } | ||
463 | EXPORT_SYMBOL_GPL(si476x_core_i2c_xfer); | ||
464 | |||
465 | /** | ||
466 | * si476x_get_status() | ||
467 | * @core: Core device structure | ||
468 | * | ||
469 | * Get the status byte of the core device by berforming one byte I2C | ||
470 | * read. | ||
471 | * | ||
472 | * The function returns a status value or a negative error code on | ||
473 | * error. | ||
474 | */ | ||
475 | static int si476x_core_get_status(struct si476x_core *core) | ||
476 | { | ||
477 | u8 response; | ||
478 | int err = si476x_core_i2c_xfer(core, SI476X_I2C_RECV, | ||
479 | &response, sizeof(response)); | ||
480 | |||
481 | return (err < 0) ? err : response; | ||
482 | } | ||
483 | |||
484 | /** | ||
485 | * si476x_get_and_signal_status() - IRQ dispatcher | ||
486 | * @core: Core device structure | ||
487 | * | ||
488 | * Dispatch the arrived interrupt request based on the value of the | ||
489 | * status byte reported by the tuner. | ||
490 | * | ||
491 | */ | ||
492 | static void si476x_core_get_and_signal_status(struct si476x_core *core) | ||
493 | { | ||
494 | int status = si476x_core_get_status(core); | ||
495 | if (status < 0) { | ||
496 | dev_err(&core->client->dev, "Failed to get status\n"); | ||
497 | return; | ||
498 | } | ||
499 | |||
500 | if (status & SI476X_CTS) { | ||
501 | /* Unfortunately completions could not be used for | ||
502 | * signalling CTS since this flag cannot be cleared | ||
503 | * in status byte, and therefore once it becomes true | ||
504 | * multiple calls to 'complete' would cause the | ||
505 | * commands following the current one to be completed | ||
506 | * before they actually are */ | ||
507 | dev_dbg(&core->client->dev, "[interrupt] CTSINT\n"); | ||
508 | atomic_set(&core->cts, 1); | ||
509 | wake_up(&core->command); | ||
510 | } | ||
511 | |||
512 | if (status & SI476X_FM_RDS_INT) { | ||
513 | dev_dbg(&core->client->dev, "[interrupt] RDSINT\n"); | ||
514 | si476x_core_start_rds_drainer_once(core); | ||
515 | } | ||
516 | |||
517 | if (status & SI476X_STC_INT) { | ||
518 | dev_dbg(&core->client->dev, "[interrupt] STCINT\n"); | ||
519 | atomic_set(&core->stc, 1); | ||
520 | wake_up(&core->tuning); | ||
521 | } | ||
522 | } | ||
523 | |||
524 | static void si476x_core_poll_loop(struct work_struct *work) | ||
525 | { | ||
526 | struct si476x_core *core = SI476X_WORK_TO_CORE(work); | ||
527 | |||
528 | si476x_core_get_and_signal_status(core); | ||
529 | |||
530 | if (atomic_read(&core->is_alive)) | ||
531 | si476x_core_schedule_polling_work(core); | ||
532 | } | ||
533 | |||
534 | static irqreturn_t si476x_core_interrupt(int irq, void *dev) | ||
535 | { | ||
536 | struct si476x_core *core = dev; | ||
537 | |||
538 | si476x_core_get_and_signal_status(core); | ||
539 | |||
540 | return IRQ_HANDLED; | ||
541 | } | ||
542 | |||
543 | /** | ||
544 | * si476x_firmware_version_to_revision() | ||
545 | * @core: Core device structure | ||
546 | * @major: Firmware major number | ||
547 | * @minor1: Firmware first minor number | ||
548 | * @minor2: Firmware second minor number | ||
549 | * | ||
550 | * Convert a chip's firmware version number into an offset that later | ||
551 | * will be used to as offset in "vtable" of tuner functions | ||
552 | * | ||
553 | * This function returns a positive offset in case of success and a -1 | ||
554 | * in case of failure. | ||
555 | */ | ||
556 | static int si476x_core_fwver_to_revision(struct si476x_core *core, | ||
557 | int func, int major, | ||
558 | int minor1, int minor2) | ||
559 | { | ||
560 | switch (func) { | ||
561 | case SI476X_FUNC_FM_RECEIVER: | ||
562 | switch (major) { | ||
563 | case 5: | ||
564 | return SI476X_REVISION_A10; | ||
565 | case 8: | ||
566 | return SI476X_REVISION_A20; | ||
567 | case 10: | ||
568 | return SI476X_REVISION_A30; | ||
569 | default: | ||
570 | goto unknown_revision; | ||
571 | } | ||
572 | case SI476X_FUNC_AM_RECEIVER: | ||
573 | switch (major) { | ||
574 | case 5: | ||
575 | return SI476X_REVISION_A10; | ||
576 | case 7: | ||
577 | return SI476X_REVISION_A20; | ||
578 | case 9: | ||
579 | return SI476X_REVISION_A30; | ||
580 | default: | ||
581 | goto unknown_revision; | ||
582 | } | ||
583 | case SI476X_FUNC_WB_RECEIVER: | ||
584 | switch (major) { | ||
585 | case 3: | ||
586 | return SI476X_REVISION_A10; | ||
587 | case 5: | ||
588 | return SI476X_REVISION_A20; | ||
589 | case 7: | ||
590 | return SI476X_REVISION_A30; | ||
591 | default: | ||
592 | goto unknown_revision; | ||
593 | } | ||
594 | case SI476X_FUNC_BOOTLOADER: | ||
595 | default: /* FALLTHROUG */ | ||
596 | BUG(); | ||
597 | return -1; | ||
598 | } | ||
599 | |||
600 | unknown_revision: | ||
601 | dev_err(&core->client->dev, | ||
602 | "Unsupported version of the firmware: %d.%d.%d, " | ||
603 | "reverting to A10 comptible functions\n", | ||
604 | major, minor1, minor2); | ||
605 | |||
606 | return SI476X_REVISION_A10; | ||
607 | } | ||
608 | |||
609 | /** | ||
610 | * si476x_get_revision_info() | ||
611 | * @core: Core device structure | ||
612 | * | ||
613 | * Get the firmware version number of the device. It is done in | ||
614 | * following three steps: | ||
615 | * 1. Power-up the device | ||
616 | * 2. Send the 'FUNC_INFO' command | ||
617 | * 3. Powering the device down. | ||
618 | * | ||
619 | * The function return zero on success and a negative error code on | ||
620 | * failure. | ||
621 | */ | ||
622 | static int si476x_core_get_revision_info(struct si476x_core *core) | ||
623 | { | ||
624 | int rval; | ||
625 | struct si476x_func_info info; | ||
626 | |||
627 | si476x_core_lock(core); | ||
628 | rval = si476x_core_set_power_state(core, SI476X_POWER_UP_FULL); | ||
629 | if (rval < 0) | ||
630 | goto exit; | ||
631 | |||
632 | rval = si476x_core_cmd_func_info(core, &info); | ||
633 | if (rval < 0) | ||
634 | goto power_down; | ||
635 | |||
636 | core->revision = si476x_core_fwver_to_revision(core, info.func, | ||
637 | info.firmware.major, | ||
638 | info.firmware.minor[0], | ||
639 | info.firmware.minor[1]); | ||
640 | power_down: | ||
641 | si476x_core_set_power_state(core, SI476X_POWER_DOWN); | ||
642 | exit: | ||
643 | si476x_core_unlock(core); | ||
644 | |||
645 | return rval; | ||
646 | } | ||
647 | |||
648 | bool si476x_core_has_am(struct si476x_core *core) | ||
649 | { | ||
650 | return core->chip_id == SI476X_CHIP_SI4761 || | ||
651 | core->chip_id == SI476X_CHIP_SI4764; | ||
652 | } | ||
653 | EXPORT_SYMBOL_GPL(si476x_core_has_am); | ||
654 | |||
655 | bool si476x_core_has_diversity(struct si476x_core *core) | ||
656 | { | ||
657 | return core->chip_id == SI476X_CHIP_SI4764; | ||
658 | } | ||
659 | EXPORT_SYMBOL_GPL(si476x_core_has_diversity); | ||
660 | |||
661 | bool si476x_core_is_a_secondary_tuner(struct si476x_core *core) | ||
662 | { | ||
663 | return si476x_core_has_diversity(core) && | ||
664 | (core->diversity_mode == SI476X_PHDIV_SECONDARY_ANTENNA || | ||
665 | core->diversity_mode == SI476X_PHDIV_SECONDARY_COMBINING); | ||
666 | } | ||
667 | EXPORT_SYMBOL_GPL(si476x_core_is_a_secondary_tuner); | ||
668 | |||
669 | bool si476x_core_is_a_primary_tuner(struct si476x_core *core) | ||
670 | { | ||
671 | return si476x_core_has_diversity(core) && | ||
672 | (core->diversity_mode == SI476X_PHDIV_PRIMARY_ANTENNA || | ||
673 | core->diversity_mode == SI476X_PHDIV_PRIMARY_COMBINING); | ||
674 | } | ||
675 | EXPORT_SYMBOL_GPL(si476x_core_is_a_primary_tuner); | ||
676 | |||
677 | bool si476x_core_is_in_am_receiver_mode(struct si476x_core *core) | ||
678 | { | ||
679 | return si476x_core_has_am(core) && | ||
680 | (core->power_up_parameters.func == SI476X_FUNC_AM_RECEIVER); | ||
681 | } | ||
682 | EXPORT_SYMBOL_GPL(si476x_core_is_in_am_receiver_mode); | ||
683 | |||
684 | bool si476x_core_is_powered_up(struct si476x_core *core) | ||
685 | { | ||
686 | return core->power_state == SI476X_POWER_UP_FULL; | ||
687 | } | ||
688 | EXPORT_SYMBOL_GPL(si476x_core_is_powered_up); | ||
689 | |||
690 | static int si476x_core_probe(struct i2c_client *client, | ||
691 | const struct i2c_device_id *id) | ||
692 | { | ||
693 | int rval; | ||
694 | struct si476x_core *core; | ||
695 | struct si476x_platform_data *pdata; | ||
696 | struct mfd_cell *cell; | ||
697 | int cell_num; | ||
698 | |||
699 | core = devm_kzalloc(&client->dev, sizeof(*core), GFP_KERNEL); | ||
700 | if (!core) { | ||
701 | dev_err(&client->dev, | ||
702 | "failed to allocate 'struct si476x_core'\n"); | ||
703 | return -ENOMEM; | ||
704 | } | ||
705 | core->client = client; | ||
706 | |||
707 | core->regmap = devm_regmap_init_si476x(core); | ||
708 | if (IS_ERR(core->regmap)) { | ||
709 | rval = PTR_ERR(core->regmap); | ||
710 | dev_err(&client->dev, | ||
711 | "Failed to allocate register map: %d\n", | ||
712 | rval); | ||
713 | return rval; | ||
714 | } | ||
715 | |||
716 | i2c_set_clientdata(client, core); | ||
717 | |||
718 | atomic_set(&core->is_alive, 0); | ||
719 | core->power_state = SI476X_POWER_DOWN; | ||
720 | |||
721 | pdata = client->dev.platform_data; | ||
722 | if (pdata) { | ||
723 | memcpy(&core->power_up_parameters, | ||
724 | &pdata->power_up_parameters, | ||
725 | sizeof(core->power_up_parameters)); | ||
726 | |||
727 | core->gpio_reset = -1; | ||
728 | if (gpio_is_valid(pdata->gpio_reset)) { | ||
729 | rval = gpio_request(pdata->gpio_reset, "si476x reset"); | ||
730 | if (rval) { | ||
731 | dev_err(&client->dev, | ||
732 | "Failed to request gpio: %d\n", rval); | ||
733 | return rval; | ||
734 | } | ||
735 | core->gpio_reset = pdata->gpio_reset; | ||
736 | gpio_direction_output(core->gpio_reset, 0); | ||
737 | } | ||
738 | |||
739 | core->diversity_mode = pdata->diversity_mode; | ||
740 | memcpy(&core->pinmux, &pdata->pinmux, | ||
741 | sizeof(struct si476x_pinmux)); | ||
742 | } else { | ||
743 | dev_err(&client->dev, "No platform data provided\n"); | ||
744 | return -EINVAL; | ||
745 | } | ||
746 | |||
747 | core->supplies[0].supply = "vd"; | ||
748 | core->supplies[1].supply = "va"; | ||
749 | core->supplies[2].supply = "vio1"; | ||
750 | core->supplies[3].supply = "vio2"; | ||
751 | |||
752 | rval = devm_regulator_bulk_get(&client->dev, | ||
753 | ARRAY_SIZE(core->supplies), | ||
754 | core->supplies); | ||
755 | if (rval) { | ||
756 | dev_err(&client->dev, "Failet to gett all of the regulators\n"); | ||
757 | goto free_gpio; | ||
758 | } | ||
759 | |||
760 | mutex_init(&core->cmd_lock); | ||
761 | init_waitqueue_head(&core->command); | ||
762 | init_waitqueue_head(&core->tuning); | ||
763 | |||
764 | rval = kfifo_alloc(&core->rds_fifo, | ||
765 | SI476X_DRIVER_RDS_FIFO_DEPTH * | ||
766 | sizeof(struct v4l2_rds_data), | ||
767 | GFP_KERNEL); | ||
768 | if (rval) { | ||
769 | dev_err(&client->dev, "Could not alloate the FIFO\n"); | ||
770 | goto free_gpio; | ||
771 | } | ||
772 | mutex_init(&core->rds_drainer_status_lock); | ||
773 | init_waitqueue_head(&core->rds_read_queue); | ||
774 | INIT_WORK(&core->rds_fifo_drainer, si476x_core_drain_rds_fifo); | ||
775 | |||
776 | if (client->irq) { | ||
777 | rval = devm_request_threaded_irq(&client->dev, | ||
778 | client->irq, NULL, | ||
779 | si476x_core_interrupt, | ||
780 | IRQF_TRIGGER_FALLING, | ||
781 | client->name, core); | ||
782 | if (rval < 0) { | ||
783 | dev_err(&client->dev, "Could not request IRQ %d\n", | ||
784 | client->irq); | ||
785 | goto free_kfifo; | ||
786 | } | ||
787 | disable_irq(client->irq); | ||
788 | dev_dbg(&client->dev, "IRQ requested.\n"); | ||
789 | |||
790 | core->rds_fifo_depth = 20; | ||
791 | } else { | ||
792 | INIT_DELAYED_WORK(&core->status_monitor, | ||
793 | si476x_core_poll_loop); | ||
794 | dev_info(&client->dev, | ||
795 | "No IRQ number specified, will use polling\n"); | ||
796 | |||
797 | core->rds_fifo_depth = 5; | ||
798 | } | ||
799 | |||
800 | core->chip_id = id->driver_data; | ||
801 | |||
802 | rval = si476x_core_get_revision_info(core); | ||
803 | if (rval < 0) { | ||
804 | rval = -ENODEV; | ||
805 | goto free_kfifo; | ||
806 | } | ||
807 | |||
808 | cell_num = 0; | ||
809 | |||
810 | cell = &core->cells[SI476X_RADIO_CELL]; | ||
811 | cell->name = "si476x-radio"; | ||
812 | cell_num++; | ||
813 | |||
814 | #ifdef CONFIG_SND_SOC_SI476X | ||
815 | if ((core->chip_id == SI476X_CHIP_SI4761 || | ||
816 | core->chip_id == SI476X_CHIP_SI4764) && | ||
817 | core->pinmux.dclk == SI476X_DCLK_DAUDIO && | ||
818 | core->pinmux.dfs == SI476X_DFS_DAUDIO && | ||
819 | core->pinmux.dout == SI476X_DOUT_I2S_OUTPUT && | ||
820 | core->pinmux.xout == SI476X_XOUT_TRISTATE) { | ||
821 | cell = &core->cells[SI476X_CODEC_CELL]; | ||
822 | cell->name = "si476x-codec"; | ||
823 | cell_num++; | ||
824 | } | ||
825 | #endif | ||
826 | rval = mfd_add_devices(&client->dev, | ||
827 | (client->adapter->nr << 8) + client->addr, | ||
828 | core->cells, cell_num, | ||
829 | NULL, 0, NULL); | ||
830 | if (!rval) | ||
831 | return 0; | ||
832 | |||
833 | free_kfifo: | ||
834 | kfifo_free(&core->rds_fifo); | ||
835 | |||
836 | free_gpio: | ||
837 | if (gpio_is_valid(core->gpio_reset)) | ||
838 | gpio_free(core->gpio_reset); | ||
839 | |||
840 | return rval; | ||
841 | } | ||
842 | |||
843 | static int si476x_core_remove(struct i2c_client *client) | ||
844 | { | ||
845 | struct si476x_core *core = i2c_get_clientdata(client); | ||
846 | |||
847 | si476x_core_pronounce_dead(core); | ||
848 | mfd_remove_devices(&client->dev); | ||
849 | |||
850 | if (client->irq) | ||
851 | disable_irq(client->irq); | ||
852 | else | ||
853 | cancel_delayed_work_sync(&core->status_monitor); | ||
854 | |||
855 | kfifo_free(&core->rds_fifo); | ||
856 | |||
857 | if (gpio_is_valid(core->gpio_reset)) | ||
858 | gpio_free(core->gpio_reset); | ||
859 | |||
860 | return 0; | ||
861 | } | ||
862 | |||
863 | |||
864 | static const struct i2c_device_id si476x_id[] = { | ||
865 | { "si4761", SI476X_CHIP_SI4761 }, | ||
866 | { "si4764", SI476X_CHIP_SI4764 }, | ||
867 | { "si4768", SI476X_CHIP_SI4768 }, | ||
868 | { }, | ||
869 | }; | ||
870 | MODULE_DEVICE_TABLE(i2c, si476x_id); | ||
871 | |||
872 | static struct i2c_driver si476x_core_driver = { | ||
873 | .driver = { | ||
874 | .name = "si476x-core", | ||
875 | .owner = THIS_MODULE, | ||
876 | }, | ||
877 | .probe = si476x_core_probe, | ||
878 | .remove = si476x_core_remove, | ||
879 | .id_table = si476x_id, | ||
880 | }; | ||
881 | module_i2c_driver(si476x_core_driver); | ||
882 | |||
883 | |||
884 | MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>"); | ||
885 | MODULE_DESCRIPTION("Si4761/64/68 AM/FM MFD core device driver"); | ||
886 | MODULE_LICENSE("GPL"); | ||