aboutsummaryrefslogtreecommitdiffstats
path: root/sound/soc/codecs/twl4030.c
diff options
context:
space:
mode:
authorPeter Ujfalusi <peter.ujfalusi@nokia.com>2008-12-31 03:08:38 -0500
committerMark Brown <broonie@opensource.wolfsonmicro.com>2008-12-31 08:11:59 -0500
commit276c62225a7c98737510483dcaec6af7e7965389 (patch)
tree814347def3eaeecc6fd69b30d72389dedac33f7e /sound/soc/codecs/twl4030.c
parentf9a3fba2ce8622977c5373d2449eb71705613721 (diff)
ASoC: TWL4030: DAPM based capture implementation
This patch adds DAPM implementaion for the capture path on twlx030. TWL has two physical ADC and two digital microphone (stereo) connections. The CPU interface has four microphone channels. For simplicity the microphone channel paths are named as: TX1 (Left/Right) - when using i2s mode, only the TX1 data is valid TX2 (Left/Right) Input routing (simplified version): There is two levels of mux settings for TWL in input path: Analog input mux: ADCL <- {Off, Main mic, Headset mic, AUXL, Carkit mic} ADCR <- {Off, Sub mic, AUXR} Analog/Digital mux: TX1 Analog mode: TX1L <- ADCL TX1R <- ADCR TX1 Digital mode: TX1L <- Digimic0 (Left) TX1R <- Digimic0 (Right) TX2 Analog mode: TX2L <- ADCL TX2R <- ADCR TX2 Digital mode: TX2L <- Digimic1 (Left) TX2R <- Digimic1 (Right) The patch provides the following user controls for the capture path: Mux settings: "TX1 Capture Route": {Analog, Digimic0} "TX2 Capture Route": {Analog, Digimic1} "Analog Left Capture Route": {Off, Main Mic, Headset Mic, AUXL, Carkit Mic} "Analog Right Capture Route": {Off, Sub Mic, AUXR} Volume/Gain controls: "TX1 Digital Capture Volume": Stereo gain control for TX1 path "TX2 Digital Capture Volume": Stereo gain control for TX2 path "Analog Capture Volume": Stereo gain control for the analog path only Important things for the board files: Microphone bias: "Mic Bias 1": Bias for Main mic or for digimic0 (analog or digital path) "Mic Bias 2": Bias for Sub mic or for digimic1 (analog or digital path) "Headset Mic Bias": Bias for Headset mic When the routing configured correctly only the needed components will be powered/enabled. Signed-off-by: Peter Ujfalusi <peter.ujfalusi@nokia.com> Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Diffstat (limited to 'sound/soc/codecs/twl4030.c')
-rw-r--r--sound/soc/codecs/twl4030.c347
1 files changed, 175 insertions, 172 deletions
diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c
index 2c279cd8deb5..31e44e346dc8 100644
--- a/sound/soc/codecs/twl4030.c
+++ b/sound/soc/codecs/twl4030.c
@@ -298,6 +298,55 @@ static const struct soc_enum twl4030_handsfreer_enum =
298static const struct snd_kcontrol_new twl4030_dapm_handsfreer_control = 298static const struct snd_kcontrol_new twl4030_dapm_handsfreer_control =
299SOC_DAPM_ENUM("Route", twl4030_handsfreer_enum); 299SOC_DAPM_ENUM("Route", twl4030_handsfreer_enum);
300 300
301/* Left analog microphone selection */
302static const char *twl4030_analoglmic_texts[] =
303 {"Off", "Main mic", "Headset mic", "Invalid", "AUXL",
304 "Invalid", "Invalid", "Invalid", "Carkit mic"};
305
306static const struct soc_enum twl4030_analoglmic_enum =
307 SOC_ENUM_SINGLE(TWL4030_REG_ANAMICL, 0,
308 ARRAY_SIZE(twl4030_analoglmic_texts),
309 twl4030_analoglmic_texts);
310
311static const struct snd_kcontrol_new twl4030_dapm_analoglmic_control =
312SOC_DAPM_ENUM("Route", twl4030_analoglmic_enum);
313
314/* Right analog microphone selection */
315static const char *twl4030_analogrmic_texts[] =
316 {"Off", "Sub mic", "Invalid", "Invalid", "AUXR"};
317
318static const struct soc_enum twl4030_analogrmic_enum =
319 SOC_ENUM_SINGLE(TWL4030_REG_ANAMICR, 0,
320 ARRAY_SIZE(twl4030_analogrmic_texts),
321 twl4030_analogrmic_texts);
322
323static const struct snd_kcontrol_new twl4030_dapm_analogrmic_control =
324SOC_DAPM_ENUM("Route", twl4030_analogrmic_enum);
325
326/* TX1 L/R Analog/Digital microphone selection */
327static const char *twl4030_micpathtx1_texts[] =
328 {"Analog", "Digimic0"};
329
330static const struct soc_enum twl4030_micpathtx1_enum =
331 SOC_ENUM_SINGLE(TWL4030_REG_ADCMICSEL, 0,
332 ARRAY_SIZE(twl4030_micpathtx1_texts),
333 twl4030_micpathtx1_texts);
334
335static const struct snd_kcontrol_new twl4030_dapm_micpathtx1_control =
336SOC_DAPM_ENUM("Route", twl4030_micpathtx1_enum);
337
338/* TX2 L/R Analog/Digital microphone selection */
339static const char *twl4030_micpathtx2_texts[] =
340 {"Analog", "Digimic1"};
341
342static const struct soc_enum twl4030_micpathtx2_enum =
343 SOC_ENUM_SINGLE(TWL4030_REG_ADCMICSEL, 2,
344 ARRAY_SIZE(twl4030_micpathtx2_texts),
345 twl4030_micpathtx2_texts);
346
347static const struct snd_kcontrol_new twl4030_dapm_micpathtx2_control =
348SOC_DAPM_ENUM("Route", twl4030_micpathtx2_enum);
349
301/* 350/*
302 * This function filters out the non valid mux settings, named as "Invalid" 351 * This function filters out the non valid mux settings, named as "Invalid"
303 * in the enum texts. 352 * in the enum texts.
@@ -320,6 +369,36 @@ static int twl4030_enum_event(struct snd_soc_dapm_widget *w,
320 return ret; 369 return ret;
321} 370}
322 371
372static int micpath_event(struct snd_soc_dapm_widget *w,
373 struct snd_kcontrol *kcontrol, int event)
374{
375 struct soc_enum *e = (struct soc_enum *)w->kcontrols->private_value;
376 unsigned char adcmicsel, micbias_ctl;
377
378 adcmicsel = twl4030_read_reg_cache(w->codec, TWL4030_REG_ADCMICSEL);
379 micbias_ctl = twl4030_read_reg_cache(w->codec, TWL4030_REG_MICBIAS_CTL);
380 /* Prepare the bits for the given TX path:
381 * shift_l == 0: TX1 microphone path
382 * shift_l == 2: TX2 microphone path */
383 if (e->shift_l) {
384 /* TX2 microphone path */
385 if (adcmicsel & TWL4030_TX2IN_SEL)
386 micbias_ctl |= TWL4030_MICBIAS2_CTL; /* digimic */
387 else
388 micbias_ctl &= ~TWL4030_MICBIAS2_CTL;
389 } else {
390 /* TX1 microphone path */
391 if (adcmicsel & TWL4030_TX1IN_SEL)
392 micbias_ctl |= TWL4030_MICBIAS1_CTL; /* digimic */
393 else
394 micbias_ctl &= ~TWL4030_MICBIAS1_CTL;
395 }
396
397 twl4030_write(w->codec, TWL4030_REG_MICBIAS_CTL, micbias_ctl);
398
399 return 0;
400}
401
323static int handsfree_event(struct snd_soc_dapm_widget *w, 402static int handsfree_event(struct snd_soc_dapm_widget *w,
324 struct snd_kcontrol *kcontrol, int event) 403 struct snd_kcontrol *kcontrol, int event)
325{ 404{
@@ -501,162 +580,6 @@ static int snd_soc_put_volsw_r2_twl4030(struct snd_kcontrol *kcontrol,
501 return err; 580 return err;
502} 581}
503 582
504static int twl4030_get_left_input(struct snd_kcontrol *kcontrol,
505 struct snd_ctl_elem_value *ucontrol)
506{
507 struct snd_soc_codec *codec = kcontrol->private_data;
508 u8 reg = twl4030_read_reg_cache(codec, TWL4030_REG_ANAMICL);
509 int result = 0;
510
511 /* one bit must be set a time */
512 reg &= TWL4030_CKMIC_EN | TWL4030_AUXL_EN | TWL4030_HSMIC_EN
513 | TWL4030_MAINMIC_EN;
514 if (reg != 0) {
515 result++;
516 while ((reg & 1) == 0) {
517 result++;
518 reg >>= 1;
519 }
520 }
521
522 ucontrol->value.integer.value[0] = result;
523 return 0;
524}
525
526static int twl4030_put_left_input(struct snd_kcontrol *kcontrol,
527 struct snd_ctl_elem_value *ucontrol)
528{
529 struct snd_soc_codec *codec = kcontrol->private_data;
530 int value = ucontrol->value.integer.value[0];
531 u8 anamicl, micbias, avadc_ctl;
532
533 anamicl = twl4030_read_reg_cache(codec, TWL4030_REG_ANAMICL);
534 anamicl &= ~(TWL4030_CKMIC_EN | TWL4030_AUXL_EN | TWL4030_HSMIC_EN
535 | TWL4030_MAINMIC_EN);
536 micbias = twl4030_read_reg_cache(codec, TWL4030_REG_MICBIAS_CTL);
537 micbias &= ~(TWL4030_HSMICBIAS_EN | TWL4030_MICBIAS1_EN);
538 avadc_ctl = twl4030_read_reg_cache(codec, TWL4030_REG_AVADC_CTL);
539
540 switch (value) {
541 case 1:
542 anamicl |= TWL4030_MAINMIC_EN;
543 micbias |= TWL4030_MICBIAS1_EN;
544 break;
545 case 2:
546 anamicl |= TWL4030_HSMIC_EN;
547 micbias |= TWL4030_HSMICBIAS_EN;
548 break;
549 case 3:
550 anamicl |= TWL4030_AUXL_EN;
551 break;
552 case 4:
553 anamicl |= TWL4030_CKMIC_EN;
554 break;
555 default:
556 break;
557 }
558
559 /* If some input is selected, enable amp and ADC */
560 if (value != 0) {
561 anamicl |= TWL4030_MICAMPL_EN;
562 avadc_ctl |= TWL4030_ADCL_EN;
563 } else {
564 anamicl &= ~TWL4030_MICAMPL_EN;
565 avadc_ctl &= ~TWL4030_ADCL_EN;
566 }
567
568 twl4030_write(codec, TWL4030_REG_ANAMICL, anamicl);
569 twl4030_write(codec, TWL4030_REG_MICBIAS_CTL, micbias);
570 twl4030_write(codec, TWL4030_REG_AVADC_CTL, avadc_ctl);
571
572 return 1;
573}
574
575static int twl4030_get_right_input(struct snd_kcontrol *kcontrol,
576 struct snd_ctl_elem_value *ucontrol)
577{
578 struct snd_soc_codec *codec = kcontrol->private_data;
579 u8 reg = twl4030_read_reg_cache(codec, TWL4030_REG_ANAMICR);
580 int value = 0;
581
582 reg &= TWL4030_SUBMIC_EN|TWL4030_AUXR_EN;
583 switch (reg) {
584 case TWL4030_SUBMIC_EN:
585 value = 1;
586 break;
587 case TWL4030_AUXR_EN:
588 value = 2;
589 break;
590 default:
591 break;
592 }
593
594 ucontrol->value.integer.value[0] = value;
595 return 0;
596}
597
598static int twl4030_put_right_input(struct snd_kcontrol *kcontrol,
599 struct snd_ctl_elem_value *ucontrol)
600{
601 struct snd_soc_codec *codec = kcontrol->private_data;
602 int value = ucontrol->value.integer.value[0];
603 u8 anamicr, micbias, avadc_ctl;
604
605 anamicr = twl4030_read_reg_cache(codec, TWL4030_REG_ANAMICR);
606 anamicr &= ~(TWL4030_SUBMIC_EN|TWL4030_AUXR_EN);
607 micbias = twl4030_read_reg_cache(codec, TWL4030_REG_MICBIAS_CTL);
608 micbias &= ~TWL4030_MICBIAS2_EN;
609 avadc_ctl = twl4030_read_reg_cache(codec, TWL4030_REG_AVADC_CTL);
610
611 switch (value) {
612 case 1:
613 anamicr |= TWL4030_SUBMIC_EN;
614 micbias |= TWL4030_MICBIAS2_EN;
615 break;
616 case 2:
617 anamicr |= TWL4030_AUXR_EN;
618 break;
619 default:
620 break;
621 }
622
623 if (value != 0) {
624 anamicr |= TWL4030_MICAMPR_EN;
625 avadc_ctl |= TWL4030_ADCR_EN;
626 } else {
627 anamicr &= ~TWL4030_MICAMPR_EN;
628 avadc_ctl &= ~TWL4030_ADCR_EN;
629 }
630
631 twl4030_write(codec, TWL4030_REG_ANAMICR, anamicr);
632 twl4030_write(codec, TWL4030_REG_MICBIAS_CTL, micbias);
633 twl4030_write(codec, TWL4030_REG_AVADC_CTL, avadc_ctl);
634
635 return 1;
636}
637
638static const char *twl4030_left_in_sel[] = {
639 "None",
640 "Main Mic",
641 "Headset Mic",
642 "Line In",
643 "Carkit Mic",
644};
645
646static const char *twl4030_right_in_sel[] = {
647 "None",
648 "Sub Mic",
649 "Line In",
650};
651
652static const struct soc_enum twl4030_left_input_mux =
653 SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(twl4030_left_in_sel),
654 twl4030_left_in_sel);
655
656static const struct soc_enum twl4030_right_input_mux =
657 SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(twl4030_right_in_sel),
658 twl4030_right_in_sel);
659
660/* 583/*
661 * FGAIN volume control: 584 * FGAIN volume control:
662 * from -62 to 0 dB in 1 dB steps (mute instead of -63 dB) 585 * from -62 to 0 dB in 1 dB steps (mute instead of -63 dB)
@@ -739,18 +662,15 @@ static const struct snd_kcontrol_new twl4030_snd_controls[] = {
739 TWL4030_REG_EAR_CTL, 4, 3, 0, output_tvl), 662 TWL4030_REG_EAR_CTL, 4, 3, 0, output_tvl),
740 663
741 /* Common capture gain controls */ 664 /* Common capture gain controls */
742 SOC_DOUBLE_R_TLV("Capture Volume", 665 SOC_DOUBLE_R_TLV("TX1 Digital Capture Volume",
743 TWL4030_REG_ATXL1PGA, TWL4030_REG_ATXR1PGA, 666 TWL4030_REG_ATXL1PGA, TWL4030_REG_ATXR1PGA,
744 0, 0x1f, 0, digital_capture_tlv), 667 0, 0x1f, 0, digital_capture_tlv),
668 SOC_DOUBLE_R_TLV("TX2 Digital Capture Volume",
669 TWL4030_REG_AVTXL2PGA, TWL4030_REG_AVTXR2PGA,
670 0, 0x1f, 0, digital_capture_tlv),
745 671
746 SOC_DOUBLE_TLV("Input Boost Volume", TWL4030_REG_ANAMIC_GAIN, 672 SOC_DOUBLE_TLV("Analog Capture Volume", TWL4030_REG_ANAMIC_GAIN,
747 0, 3, 5, 0, input_gain_tlv), 673 0, 3, 5, 0, input_gain_tlv),
748
749 /* Input source controls */
750 SOC_ENUM_EXT("Left Input Source", twl4030_left_input_mux,
751 twl4030_get_left_input, twl4030_put_left_input),
752 SOC_ENUM_EXT("Right Input Source", twl4030_right_input_mux,
753 twl4030_get_right_input, twl4030_put_right_input),
754}; 674};
755 675
756/* add non dapm controls */ 676/* add non dapm controls */
@@ -770,9 +690,19 @@ static int twl4030_add_controls(struct snd_soc_codec *codec)
770} 690}
771 691
772static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { 692static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = {
773 SND_SOC_DAPM_INPUT("INL"), 693 /* Left channel inputs */
774 SND_SOC_DAPM_INPUT("INR"), 694 SND_SOC_DAPM_INPUT("MAINMIC"),
775 695 SND_SOC_DAPM_INPUT("HSMIC"),
696 SND_SOC_DAPM_INPUT("AUXL"),
697 SND_SOC_DAPM_INPUT("CARKITMIC"),
698 /* Right channel inputs */
699 SND_SOC_DAPM_INPUT("SUBMIC"),
700 SND_SOC_DAPM_INPUT("AUXR"),
701 /* Digital microphones (Stereo) */
702 SND_SOC_DAPM_INPUT("DIGIMIC0"),
703 SND_SOC_DAPM_INPUT("DIGIMIC1"),
704
705 /* Outputs */
776 SND_SOC_DAPM_OUTPUT("OUTL"), 706 SND_SOC_DAPM_OUTPUT("OUTL"),
777 SND_SOC_DAPM_OUTPUT("OUTR"), 707 SND_SOC_DAPM_OUTPUT("OUTR"),
778 SND_SOC_DAPM_OUTPUT("EARPIECE"), 708 SND_SOC_DAPM_OUTPUT("EARPIECE"),
@@ -835,8 +765,50 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = {
835 &twl4030_dapm_handsfreer_control, handsfree_event, 765 &twl4030_dapm_handsfreer_control, handsfree_event,
836 SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), 766 SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
837 767
838 SND_SOC_DAPM_ADC("ADCL", "Left Capture", SND_SOC_NOPM, 0, 0), 768 /* Introducing four virtual ADC, since TWL4030 have four channel for
839 SND_SOC_DAPM_ADC("ADCR", "Right Capture", SND_SOC_NOPM, 0, 0), 769 capture */
770 SND_SOC_DAPM_ADC("ADC Virtual Left1", "Left Front Capture",
771 SND_SOC_NOPM, 0, 0),
772 SND_SOC_DAPM_ADC("ADC Virtual Right1", "Right Front Capture",
773 SND_SOC_NOPM, 0, 0),
774 SND_SOC_DAPM_ADC("ADC Virtual Left2", "Left Rear Capture",
775 SND_SOC_NOPM, 0, 0),
776 SND_SOC_DAPM_ADC("ADC Virtual Right2", "Right Rear Capture",
777 SND_SOC_NOPM, 0, 0),
778
779 /* Analog/Digital mic path selection.
780 TX1 Left/Right: either analog Left/Right or Digimic0
781 TX2 Left/Right: either analog Left/Right or Digimic1 */
782 SND_SOC_DAPM_MUX_E("TX1 Capture Route", SND_SOC_NOPM, 0, 0,
783 &twl4030_dapm_micpathtx1_control, micpath_event,
784 SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD|
785 SND_SOC_DAPM_POST_REG),
786 SND_SOC_DAPM_MUX_E("TX2 Capture Route", SND_SOC_NOPM, 0, 0,
787 &twl4030_dapm_micpathtx2_control, micpath_event,
788 SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD|
789 SND_SOC_DAPM_POST_REG),
790
791 /* Analog input muxes with power switch for the physical ADCL/R */
792 SND_SOC_DAPM_MUX_E("Analog Left Capture Route",
793 TWL4030_REG_AVADC_CTL, 3, 0, &twl4030_dapm_analoglmic_control,
794 twl4030_enum_event, SND_SOC_DAPM_PRE_REG),
795 SND_SOC_DAPM_MUX_E("Analog Right Capture Route",
796 TWL4030_REG_AVADC_CTL, 1, 0, &twl4030_dapm_analogrmic_control,
797 twl4030_enum_event, SND_SOC_DAPM_PRE_REG),
798
799 SND_SOC_DAPM_PGA("Analog Left Amplifier",
800 TWL4030_REG_ANAMICL, 4, 0, NULL, 0),
801 SND_SOC_DAPM_PGA("Analog Right Amplifier",
802 TWL4030_REG_ANAMICR, 4, 0, NULL, 0),
803
804 SND_SOC_DAPM_PGA("Digimic0 Enable",
805 TWL4030_REG_ADCMICSEL, 1, 0, NULL, 0),
806 SND_SOC_DAPM_PGA("Digimic1 Enable",
807 TWL4030_REG_ADCMICSEL, 3, 0, NULL, 0),
808
809 SND_SOC_DAPM_MICBIAS("Mic Bias 1", TWL4030_REG_MICBIAS_CTL, 0, 0),
810 SND_SOC_DAPM_MICBIAS("Mic Bias 2", TWL4030_REG_MICBIAS_CTL, 1, 0),
811 SND_SOC_DAPM_MICBIAS("Headset Mic Bias", TWL4030_REG_MICBIAS_CTL, 2, 0),
840}; 812};
841 813
842static const struct snd_soc_dapm_route intercon[] = { 814static const struct snd_soc_dapm_route intercon[] = {
@@ -892,9 +864,39 @@ static const struct snd_soc_dapm_route intercon[] = {
892 {"HFL", NULL, "HandsfreeL Mux"}, 864 {"HFL", NULL, "HandsfreeL Mux"},
893 {"HFR", NULL, "HandsfreeR Mux"}, 865 {"HFR", NULL, "HandsfreeR Mux"},
894 866
895 /* inputs */ 867 /* Capture path */
896 {"ADCL", NULL, "INL"}, 868 {"Analog Left Capture Route", "Main mic", "MAINMIC"},
897 {"ADCR", NULL, "INR"}, 869 {"Analog Left Capture Route", "Headset mic", "HSMIC"},
870 {"Analog Left Capture Route", "AUXL", "AUXL"},
871 {"Analog Left Capture Route", "Carkit mic", "CARKITMIC"},
872
873 {"Analog Right Capture Route", "Sub mic", "SUBMIC"},
874 {"Analog Right Capture Route", "AUXR", "AUXR"},
875
876 {"Analog Left Amplifier", NULL, "Analog Left Capture Route"},
877 {"Analog Right Amplifier", NULL, "Analog Right Capture Route"},
878
879 {"Digimic0 Enable", NULL, "DIGIMIC0"},
880 {"Digimic1 Enable", NULL, "DIGIMIC1"},
881
882 /* TX1 Left capture path */
883 {"TX1 Capture Route", "Analog", "Analog Left Amplifier"},
884 {"TX1 Capture Route", "Digimic0", "Digimic0 Enable"},
885 /* TX1 Right capture path */
886 {"TX1 Capture Route", "Analog", "Analog Right Amplifier"},
887 {"TX1 Capture Route", "Digimic0", "Digimic0 Enable"},
888 /* TX2 Left capture path */
889 {"TX2 Capture Route", "Analog", "Analog Left Amplifier"},
890 {"TX2 Capture Route", "Digimic1", "Digimic1 Enable"},
891 /* TX2 Right capture path */
892 {"TX2 Capture Route", "Analog", "Analog Right Amplifier"},
893 {"TX2 Capture Route", "Digimic1", "Digimic1 Enable"},
894
895 {"ADC Virtual Left1", NULL, "TX1 Capture Route"},
896 {"ADC Virtual Right1", NULL, "TX1 Capture Route"},
897 {"ADC Virtual Left2", NULL, "TX2 Capture Route"},
898 {"ADC Virtual Right2", NULL, "TX2 Capture Route"},
899
898}; 900};
899 901
900static int twl4030_add_widgets(struct snd_soc_codec *codec) 902static int twl4030_add_widgets(struct snd_soc_codec *codec)
@@ -921,6 +923,7 @@ static void twl4030_power_up(struct snd_soc_codec *codec)
921 twl4030_write(codec, TWL4030_REG_ANAMICL, 923 twl4030_write(codec, TWL4030_REG_ANAMICL,
922 anamicl | TWL4030_CNCL_OFFSET_START); 924 anamicl | TWL4030_CNCL_OFFSET_START);
923 925
926
924 /* wait for offset cancellation to complete */ 927 /* wait for offset cancellation to complete */
925 do { 928 do {
926 /* this takes a little while, so don't slam i2c */ 929 /* this takes a little while, so don't slam i2c */