Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ASoC: tas2764: Apply Apple quirks

Apple's SN012776 driver has some peculiar aspects to its behavior that
are suspected to work around issues in the codec part. Add a module
parameter for enabling individual quirks that should be imitated after
the Apple driver.

Setting some of these by default seems to be required. For example,
setting 0xf fixes an issue with transient overcurrent errors which
can crash the chip until the next system reboot. To be safe, let's
enable all of them by default.

Reviewed-by: Neal Gompa <neal@gompa.dev>
Co-developed-by: Hector Martin <marcan@marcan.st>
Signed-off-by: Hector Martin <marcan@marcan.st>
Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
Co-developed-by: James Calligeros <jcalligeros99@gmail.com>
Signed-off-by: James Calligeros <jcalligeros99@gmail.com>
Link: https://patch.msgid.link/20250406-apple-codec-changes-v5-6-50a00ec850a3@gmail.com
Signed-off-by: Mark Brown <broonie@kernel.org>

authored by

Martin Povišer and committed by
Mark Brown
f33b01e0 f0aff451

+219
+180
sound/soc/codecs/tas2764-quirks.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0-only */ 2 + #ifndef __TAS2764_QUIRKS__ 3 + #define __TAS2764_QUIRKS__ 4 + 5 + #include <linux/regmap.h> 6 + 7 + #include "tas2764.h" 8 + 9 + /* Bitmask of enabled Apple quirks */ 10 + #define ENABLED_APPLE_QUIRKS 0x3f 11 + 12 + /* 13 + * Disable noise gate and flip down reserved bit in NS_CFG0 14 + */ 15 + #define TAS2764_NOISE_GATE_DISABLE BIT(0) 16 + 17 + static const struct reg_sequence tas2764_noise_gate_dis_seq[] = { 18 + REG_SEQ0(TAS2764_REG(0x0, 0x35), 0xb0) 19 + }; 20 + 21 + /* 22 + * CONV_VBAT_PVDD_MODE=1 23 + */ 24 + #define TAS2764_CONV_VBAT_PVDD_MODE BIT(1) 25 + 26 + static const struct reg_sequence tas2764_conv_vbat_pvdd_mode_seq[] = { 27 + REG_SEQ0(TAS2764_REG(0x0, 0x6b), 0x41) 28 + }; 29 + 30 + /* 31 + * Reset of DAC modulator when DSP is OFF 32 + */ 33 + #define TAS2764_DMOD_RST BIT(2) 34 + 35 + static const struct reg_sequence tas2764_dmod_rst_seq[] = { 36 + REG_SEQ0(TAS2764_REG(0x0, 0x76), 0x0) 37 + }; 38 + 39 + /* 40 + * Unknown 0x133/0x137 writes (maybe TDM related) 41 + */ 42 + #define TAS2764_UNK_SEQ0 BIT(3) 43 + 44 + static const struct reg_sequence tas2764_unk_seq0[] = { 45 + REG_SEQ0(TAS2764_REG(0x1, 0x33), 0x80), 46 + REG_SEQ0(TAS2764_REG(0x1, 0x37), 0x3a), 47 + }; 48 + 49 + /* 50 + * Unknown 0x614 - 0x61f writes 51 + */ 52 + #define TAS2764_APPLE_UNK_SEQ1 BIT(4) 53 + 54 + static const struct reg_sequence tas2764_unk_seq1[] = { 55 + REG_SEQ0(TAS2764_REG(0x6, 0x14), 0x0), 56 + REG_SEQ0(TAS2764_REG(0x6, 0x15), 0x13), 57 + REG_SEQ0(TAS2764_REG(0x6, 0x16), 0x52), 58 + REG_SEQ0(TAS2764_REG(0x6, 0x17), 0x0), 59 + REG_SEQ0(TAS2764_REG(0x6, 0x18), 0xe4), 60 + REG_SEQ0(TAS2764_REG(0x6, 0x19), 0xc), 61 + REG_SEQ0(TAS2764_REG(0x6, 0x16), 0xaa), 62 + REG_SEQ0(TAS2764_REG(0x6, 0x1b), 0x0), 63 + REG_SEQ0(TAS2764_REG(0x6, 0x1c), 0x12), 64 + REG_SEQ0(TAS2764_REG(0x6, 0x1d), 0xa0), 65 + REG_SEQ0(TAS2764_REG(0x6, 0x1e), 0xd8), 66 + REG_SEQ0(TAS2764_REG(0x6, 0x1f), 0x0), 67 + }; 68 + 69 + /* 70 + * Unknown writes in the 0xfd page (with secondary paging inside) 71 + */ 72 + #define TAS2764_APPLE_UNK_SEQ2 BIT(5) 73 + 74 + static const struct reg_sequence tas2764_unk_seq2[] = { 75 + REG_SEQ0(TAS2764_REG(0xfd, 0x0d), 0xd), 76 + REG_SEQ0(TAS2764_REG(0xfd, 0x6c), 0x2), 77 + REG_SEQ0(TAS2764_REG(0xfd, 0x6d), 0xf), 78 + REG_SEQ0(TAS2764_REG(0xfd, 0x0d), 0x0), 79 + }; 80 + 81 + /* 82 + * Disable 'Thermal Threshold 1' 83 + */ 84 + #define TAS2764_THERMAL_TH1_DISABLE BIT(6) 85 + 86 + static const struct reg_sequence tas2764_thermal_th1_dis_seq[] = { 87 + REG_SEQ0(TAS2764_REG(0x1, 0x47), 0x2), 88 + }; 89 + 90 + /* 91 + * Imitate Apple's shutdown dance 92 + */ 93 + #define TAS2764_SHUTDOWN_DANCE BIT(7) 94 + 95 + static const struct reg_sequence tas2764_shutdown_dance_init_seq[] = { 96 + /* 97 + * SDZ_MODE=01 (immediate) 98 + * 99 + * We want the shutdown to happen under the influence of 100 + * the magic writes in the 0xfdXX region, so make sure 101 + * the shutdown is immediate and there's no grace period 102 + * followed by the codec part. 103 + */ 104 + REG_SEQ0(TAS2764_REG(0x0, 0x7), 0x60), 105 + }; 106 + 107 + static const struct reg_sequence tas2764_pre_shutdown_seq[] = { 108 + REG_SEQ0(TAS2764_REG(0xfd, 0x0d), 0xd), /* switch hidden page */ 109 + REG_SEQ0(TAS2764_REG(0xfd, 0x64), 0x4), /* do write (unknown semantics) */ 110 + REG_SEQ0(TAS2764_REG(0xfd, 0x0d), 0x0), /* switch hidden page back */ 111 + }; 112 + 113 + static const struct reg_sequence tas2764_post_shutdown_seq[] = { 114 + REG_SEQ0(TAS2764_REG(0xfd, 0x0d), 0xd), 115 + REG_SEQ0(TAS2764_REG(0xfd, 0x64), 0x0), /* revert write from pre sequence */ 116 + REG_SEQ0(TAS2764_REG(0xfd, 0x0d), 0x0), 117 + }; 118 + 119 + static int tas2764_do_quirky_pwr_ctrl_change(struct tas2764_priv *tas2764, 120 + unsigned int target) 121 + { 122 + unsigned int curr; 123 + int ret; 124 + 125 + curr = snd_soc_component_read_field(tas2764->component, 126 + TAS2764_PWR_CTRL, 127 + TAS2764_PWR_CTRL_MASK); 128 + 129 + if (target == curr) 130 + return 0; 131 + 132 + /* Handle power state transition to shutdown */ 133 + if (target == TAS2764_PWR_CTRL_SHUTDOWN && 134 + (curr == TAS2764_PWR_CTRL_MUTE || curr == TAS2764_PWR_CTRL_ACTIVE)) { 135 + ret = regmap_multi_reg_write(tas2764->regmap, tas2764_pre_shutdown_seq, 136 + ARRAY_SIZE(tas2764_pre_shutdown_seq)); 137 + if (!ret) 138 + ret = snd_soc_component_update_bits(tas2764->component, 139 + TAS2764_PWR_CTRL, 140 + TAS2764_PWR_CTRL_MASK, 141 + TAS2764_PWR_CTRL_SHUTDOWN); 142 + if (!ret) 143 + ret = regmap_multi_reg_write(tas2764->regmap, 144 + tas2764_post_shutdown_seq, 145 + ARRAY_SIZE(tas2764_post_shutdown_seq)); 146 + } 147 + 148 + ret = snd_soc_component_update_bits(tas2764->component, TAS2764_PWR_CTRL, 149 + TAS2764_PWR_CTRL_MASK, target); 150 + 151 + return ret; 152 + } 153 + 154 + /* 155 + * Via devicetree (TODO): 156 + * - switch from spread spectrum to class-D switching 157 + * - disable edge control 158 + * - set BOP settings (the BOP config bits *and* BOP_SRC) 159 + */ 160 + 161 + /* 162 + * Other setup TODOs: 163 + * - DVC ramp rate 164 + */ 165 + 166 + static const struct tas2764_quirk_init_sequence { 167 + const struct reg_sequence *seq; 168 + int len; 169 + } tas2764_quirk_init_sequences[] = { 170 + { tas2764_noise_gate_dis_seq, ARRAY_SIZE(tas2764_noise_gate_dis_seq) }, 171 + { tas2764_dmod_rst_seq, ARRAY_SIZE(tas2764_dmod_rst_seq) }, 172 + { tas2764_conv_vbat_pvdd_mode_seq, ARRAY_SIZE(tas2764_conv_vbat_pvdd_mode_seq) }, 173 + { tas2764_unk_seq0, ARRAY_SIZE(tas2764_unk_seq0) }, 174 + { tas2764_unk_seq1, ARRAY_SIZE(tas2764_unk_seq1) }, 175 + { tas2764_unk_seq2, ARRAY_SIZE(tas2764_unk_seq2) }, 176 + { tas2764_thermal_th1_dis_seq, ARRAY_SIZE(tas2764_thermal_th1_dis_seq) }, 177 + { tas2764_shutdown_dance_init_seq, ARRAY_SIZE(tas2764_shutdown_dance_init_seq) }, 178 + }; 179 + 180 + #endif /* __TAS2764_QUIRKS__ */
+39
sound/soc/codecs/tas2764.c
··· 45 45 bool unmuted; 46 46 }; 47 47 48 + #include "tas2764-quirks.h" 49 + 48 50 static const char *tas2764_int_ltch0_msgs[8] = { 49 51 "fault: over temperature", /* INT_LTCH0 & BIT(0) */ 50 52 "fault: over current", ··· 123 121 TAS2764_PWR_CTRL_ACTIVE : TAS2764_PWR_CTRL_MUTE; 124 122 else 125 123 val = TAS2764_PWR_CTRL_SHUTDOWN; 124 + 125 + if (ENABLED_APPLE_QUIRKS & TAS2764_SHUTDOWN_DANCE) 126 + return tas2764_do_quirky_pwr_ctrl_change(tas2764, val); 126 127 127 128 ret = snd_soc_component_update_bits(component, TAS2764_PWR_CTRL, 128 129 TAS2764_PWR_CTRL_MASK, val); ··· 553 548 554 549 static const struct regmap_config tas2764_i2c_regmap; 555 550 551 + static int tas2764_apply_init_quirks(struct tas2764_priv *tas2764) 552 + { 553 + int ret, i; 554 + 555 + for (i = 0; i < ARRAY_SIZE(tas2764_quirk_init_sequences); i++) { 556 + const struct tas2764_quirk_init_sequence *init_seq = 557 + &tas2764_quirk_init_sequences[i]; 558 + 559 + if (!init_seq->seq) 560 + continue; 561 + 562 + if (!(BIT(i) & ENABLED_APPLE_QUIRKS)) 563 + continue; 564 + 565 + ret = regmap_multi_reg_write(tas2764->regmap, init_seq->seq, 566 + init_seq->len); 567 + 568 + if (ret < 0) 569 + return ret; 570 + } 571 + 572 + return 0; 573 + } 574 + 556 575 static int tas2764_codec_probe(struct snd_soc_component *component) 557 576 { 558 577 struct tas2764_priv *tas2764 = snd_soc_component_get_drvdata(component); ··· 646 617 if (ret < 0) 647 618 return ret; 648 619 } 620 + 621 + /* Apply all enabled Apple quirks */ 622 + ret = tas2764_apply_init_quirks(tas2764); 623 + 624 + if (ret < 0) 625 + return ret; 626 + 649 627 break; 650 628 default: 651 629 break; ··· 736 700 case TAS2764_SW_RST: 737 701 case TAS2764_INT_LTCH0 ... TAS2764_INT_LTCH4: 738 702 case TAS2764_INT_CLK_CFG: 703 + return true; 704 + case TAS2764_REG(0xf0, 0x0) ... TAS2764_REG(0xff, 0x0): 705 + /* TI's undocumented registers for the application of quirks */ 739 706 return true; 740 707 default: 741 708 return false;