|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
| 2 | +/* |
| 3 | + * CM9825 HD-audio codec |
| 4 | + */ |
| 5 | + |
| 6 | +#include <linux/init.h> |
| 7 | +#include <linux/slab.h> |
| 8 | +#include <linux/module.h> |
| 9 | +#include <sound/core.h> |
| 10 | +#include <sound/hda_codec.h> |
| 11 | +#include "hda_local.h" |
| 12 | +#include "hda_auto_parser.h" |
| 13 | +#include "hda_jack.h" |
| 14 | +#include "generic.h" |
| 15 | + |
| 16 | +/* CM9825 Offset Definitions */ |
| 17 | + |
| 18 | +#define CM9825_VERB_SET_HPF_1 0x781 |
| 19 | +#define CM9825_VERB_SET_HPF_2 0x785 |
| 20 | +#define CM9825_VERB_SET_PLL 0x7a0 |
| 21 | +#define CM9825_VERB_SET_NEG 0x7a1 |
| 22 | +#define CM9825_VERB_SET_ADCL 0x7a2 |
| 23 | +#define CM9825_VERB_SET_DACL 0x7a3 |
| 24 | +#define CM9825_VERB_SET_MBIAS 0x7a4 |
| 25 | +#define CM9825_VERB_SET_VNEG 0x7a8 |
| 26 | +#define CM9825_VERB_SET_D2S 0x7a9 |
| 27 | +#define CM9825_VERB_SET_DACTRL 0x7aa |
| 28 | +#define CM9825_VERB_SET_PDNEG 0x7ac |
| 29 | +#define CM9825_VERB_SET_VDO 0x7ad |
| 30 | +#define CM9825_VERB_SET_CDALR 0x7b0 |
| 31 | +#define CM9825_VERB_SET_MTCBA 0x7b1 |
| 32 | +#define CM9825_VERB_SET_OTP 0x7b2 |
| 33 | +#define CM9825_VERB_SET_OCP 0x7b3 |
| 34 | +#define CM9825_VERB_SET_GAD 0x7b4 |
| 35 | +#define CM9825_VERB_SET_TMOD 0x7b5 |
| 36 | +#define CM9825_VERB_SET_SNR 0x7b6 |
| 37 | + |
| 38 | +struct cmi_spec { |
| 39 | + struct hda_gen_spec gen; |
| 40 | + const struct hda_verb *chip_d0_verbs; |
| 41 | + const struct hda_verb *chip_d3_verbs; |
| 42 | + const struct hda_verb *chip_hp_present_verbs; |
| 43 | + const struct hda_verb *chip_hp_remove_verbs; |
| 44 | + struct hda_codec *codec; |
| 45 | + struct delayed_work unsol_hp_work; |
| 46 | + int quirk; |
| 47 | +}; |
| 48 | + |
| 49 | +static const struct hda_verb cm9825_std_d3_verbs[] = { |
| 50 | + /* chip sleep verbs */ |
| 51 | + {0x43, CM9825_VERB_SET_D2S, 0x62}, /* depop */ |
| 52 | + {0x43, CM9825_VERB_SET_PLL, 0x01}, /* PLL set */ |
| 53 | + {0x43, CM9825_VERB_SET_NEG, 0xc2}, /* NEG set */ |
| 54 | + {0x43, CM9825_VERB_SET_ADCL, 0x00}, /* ADC */ |
| 55 | + {0x43, CM9825_VERB_SET_DACL, 0x02}, /* DACL */ |
| 56 | + {0x43, CM9825_VERB_SET_VNEG, 0x50}, /* VOL NEG */ |
| 57 | + {0x43, CM9825_VERB_SET_MBIAS, 0x00}, /* MBIAS */ |
| 58 | + {0x43, CM9825_VERB_SET_PDNEG, 0x04}, /* SEL OSC */ |
| 59 | + {0x43, CM9825_VERB_SET_CDALR, 0xf6}, /* Class D */ |
| 60 | + {0x43, CM9825_VERB_SET_OTP, 0xcd}, /* OTP set */ |
| 61 | + {} |
| 62 | +}; |
| 63 | + |
| 64 | +static const struct hda_verb cm9825_std_d0_verbs[] = { |
| 65 | + /* chip init verbs */ |
| 66 | + {0x34, AC_VERB_SET_EAPD_BTLENABLE, 0x02}, /* EAPD set */ |
| 67 | + {0x43, CM9825_VERB_SET_SNR, 0x30}, /* SNR set */ |
| 68 | + {0x43, CM9825_VERB_SET_PLL, 0x00}, /* PLL set */ |
| 69 | + {0x43, CM9825_VERB_SET_ADCL, 0x00}, /* ADC */ |
| 70 | + {0x43, CM9825_VERB_SET_DACL, 0x02}, /* DACL */ |
| 71 | + {0x43, CM9825_VERB_SET_MBIAS, 0x00}, /* MBIAS */ |
| 72 | + {0x43, CM9825_VERB_SET_VNEG, 0x56}, /* VOL NEG */ |
| 73 | + {0x43, CM9825_VERB_SET_D2S, 0x62}, /* depop */ |
| 74 | + {0x43, CM9825_VERB_SET_DACTRL, 0x00}, /* DACTRL set */ |
| 75 | + {0x43, CM9825_VERB_SET_PDNEG, 0x0c}, /* SEL OSC */ |
| 76 | + {0x43, CM9825_VERB_SET_VDO, 0x80}, /* VDO set */ |
| 77 | + {0x43, CM9825_VERB_SET_CDALR, 0xf4}, /* Class D */ |
| 78 | + {0x43, CM9825_VERB_SET_OTP, 0xcd}, /* OTP set */ |
| 79 | + {0x43, CM9825_VERB_SET_MTCBA, 0x61}, /* SR set */ |
| 80 | + {0x43, CM9825_VERB_SET_OCP, 0x33}, /* OTP set */ |
| 81 | + {0x43, CM9825_VERB_SET_GAD, 0x07}, /* ADC -3db */ |
| 82 | + {0x43, CM9825_VERB_SET_TMOD, 0x26}, /* Class D clk */ |
| 83 | + {0x3C, AC_VERB_SET_AMP_GAIN_MUTE | |
| 84 | + AC_AMP_SET_OUTPUT | AC_AMP_SET_RIGHT, 0x2d}, /* Gain set */ |
| 85 | + {0x3C, AC_VERB_SET_AMP_GAIN_MUTE | |
| 86 | + AC_AMP_SET_OUTPUT | AC_AMP_SET_LEFT, 0x2d}, /* Gain set */ |
| 87 | + {0x43, CM9825_VERB_SET_HPF_1, 0x40}, /* HPF set */ |
| 88 | + {0x43, CM9825_VERB_SET_HPF_2, 0x40}, /* HPF set */ |
| 89 | + {} |
| 90 | +}; |
| 91 | + |
| 92 | +static const struct hda_verb cm9825_hp_present_verbs[] = { |
| 93 | + {0x42, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00}, /* PIN off */ |
| 94 | + {0x43, CM9825_VERB_SET_ADCL, 0x88}, /* ADC */ |
| 95 | + {0x43, CM9825_VERB_SET_DACL, 0xaa}, /* DACL */ |
| 96 | + {0x43, CM9825_VERB_SET_MBIAS, 0x10}, /* MBIAS */ |
| 97 | + {0x43, CM9825_VERB_SET_D2S, 0xf2}, /* depop */ |
| 98 | + {0x43, CM9825_VERB_SET_DACTRL, 0x00}, /* DACTRL set */ |
| 99 | + {0x43, CM9825_VERB_SET_VDO, 0xc4}, /* VDO set */ |
| 100 | + {} |
| 101 | +}; |
| 102 | + |
| 103 | +static const struct hda_verb cm9825_hp_remove_verbs[] = { |
| 104 | + {0x43, CM9825_VERB_SET_ADCL, 0x00}, /* ADC */ |
| 105 | + {0x43, CM9825_VERB_SET_DACL, 0x56}, /* DACL */ |
| 106 | + {0x43, CM9825_VERB_SET_MBIAS, 0x00}, /* MBIAS */ |
| 107 | + {0x43, CM9825_VERB_SET_D2S, 0x62}, /* depop */ |
| 108 | + {0x43, CM9825_VERB_SET_DACTRL, 0xe0}, /* DACTRL set */ |
| 109 | + {0x43, CM9825_VERB_SET_VDO, 0x80}, /* VDO set */ |
| 110 | + {0x42, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40}, /* PIN on */ |
| 111 | + {} |
| 112 | +}; |
| 113 | + |
| 114 | +static void cm9825_unsol_hp_delayed(struct work_struct *work) |
| 115 | +{ |
| 116 | + struct cmi_spec *spec = |
| 117 | + container_of(to_delayed_work(work), struct cmi_spec, unsol_hp_work); |
| 118 | + struct hda_jack_tbl *jack; |
| 119 | + hda_nid_t hp_pin = spec->gen.autocfg.hp_pins[0]; |
| 120 | + bool hp_jack_plugin = false; |
| 121 | + int err = 0; |
| 122 | + |
| 123 | + hp_jack_plugin = snd_hda_jack_detect(spec->codec, hp_pin); |
| 124 | + |
| 125 | + codec_dbg(spec->codec, "hp_jack_plugin %d, hp_pin 0x%X\n", |
| 126 | + (int)hp_jack_plugin, hp_pin); |
| 127 | + |
| 128 | + if (!hp_jack_plugin) { |
| 129 | + err = |
| 130 | + snd_hda_codec_write(spec->codec, 0x42, 0, |
| 131 | + AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40); |
| 132 | + if (err) |
| 133 | + codec_dbg(spec->codec, "codec_write err %d\n", err); |
| 134 | + |
| 135 | + snd_hda_sequence_write(spec->codec, spec->chip_hp_remove_verbs); |
| 136 | + } else { |
| 137 | + snd_hda_sequence_write(spec->codec, |
| 138 | + spec->chip_hp_present_verbs); |
| 139 | + } |
| 140 | + |
| 141 | + jack = snd_hda_jack_tbl_get(spec->codec, hp_pin); |
| 142 | + if (jack) { |
| 143 | + jack->block_report = 0; |
| 144 | + snd_hda_jack_report_sync(spec->codec); |
| 145 | + } |
| 146 | +} |
| 147 | + |
| 148 | +static void hp_callback(struct hda_codec *codec, struct hda_jack_callback *cb) |
| 149 | +{ |
| 150 | + struct cmi_spec *spec = codec->spec; |
| 151 | + struct hda_jack_tbl *tbl; |
| 152 | + |
| 153 | + /* Delay enabling the HP amp, to let the mic-detection |
| 154 | + * state machine run. |
| 155 | + */ |
| 156 | + |
| 157 | + codec_dbg(spec->codec, "cb->nid 0x%X\n", cb->nid); |
| 158 | + |
| 159 | + tbl = snd_hda_jack_tbl_get(codec, cb->nid); |
| 160 | + if (tbl) |
| 161 | + tbl->block_report = 1; |
| 162 | + schedule_delayed_work(&spec->unsol_hp_work, msecs_to_jiffies(200)); |
| 163 | +} |
| 164 | + |
| 165 | +static void cm9825_setup_unsol(struct hda_codec *codec) |
| 166 | +{ |
| 167 | + struct cmi_spec *spec = codec->spec; |
| 168 | + |
| 169 | + hda_nid_t hp_pin = spec->gen.autocfg.hp_pins[0]; |
| 170 | + |
| 171 | + snd_hda_jack_detect_enable_callback(codec, hp_pin, hp_callback); |
| 172 | +} |
| 173 | + |
| 174 | +static int cm9825_init(struct hda_codec *codec) |
| 175 | +{ |
| 176 | + snd_hda_gen_init(codec); |
| 177 | + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_INIT); |
| 178 | + |
| 179 | + return 0; |
| 180 | +} |
| 181 | + |
| 182 | +static void cm9825_remove(struct hda_codec *codec) |
| 183 | +{ |
| 184 | + struct cmi_spec *spec = codec->spec; |
| 185 | + |
| 186 | + cancel_delayed_work_sync(&spec->unsol_hp_work); |
| 187 | + snd_hda_gen_remove(codec); |
| 188 | +} |
| 189 | + |
| 190 | +static int cm9825_suspend(struct hda_codec *codec) |
| 191 | +{ |
| 192 | + struct cmi_spec *spec = codec->spec; |
| 193 | + |
| 194 | + cancel_delayed_work_sync(&spec->unsol_hp_work); |
| 195 | + |
| 196 | + snd_hda_sequence_write(codec, spec->chip_d3_verbs); |
| 197 | + |
| 198 | + return 0; |
| 199 | +} |
| 200 | + |
| 201 | +static int cm9825_resume(struct hda_codec *codec) |
| 202 | +{ |
| 203 | + struct cmi_spec *spec = codec->spec; |
| 204 | + hda_nid_t hp_pin = 0; |
| 205 | + bool hp_jack_plugin = false; |
| 206 | + int err; |
| 207 | + |
| 208 | + err = |
| 209 | + snd_hda_codec_write(spec->codec, 0x42, 0, |
| 210 | + AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00); |
| 211 | + if (err) |
| 212 | + codec_dbg(codec, "codec_write err %d\n", err); |
| 213 | + |
| 214 | + msleep(150); /* for depop noise */ |
| 215 | + |
| 216 | + snd_hda_codec_init(codec); |
| 217 | + |
| 218 | + hp_pin = spec->gen.autocfg.hp_pins[0]; |
| 219 | + hp_jack_plugin = snd_hda_jack_detect(spec->codec, hp_pin); |
| 220 | + |
| 221 | + codec_dbg(spec->codec, "hp_jack_plugin %d, hp_pin 0x%X\n", |
| 222 | + (int)hp_jack_plugin, hp_pin); |
| 223 | + |
| 224 | + if (!hp_jack_plugin) { |
| 225 | + err = |
| 226 | + snd_hda_codec_write(spec->codec, 0x42, 0, |
| 227 | + AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40); |
| 228 | + |
| 229 | + if (err) |
| 230 | + codec_dbg(codec, "codec_write err %d\n", err); |
| 231 | + |
| 232 | + snd_hda_sequence_write(codec, cm9825_hp_remove_verbs); |
| 233 | + } |
| 234 | + |
| 235 | + snd_hda_regmap_sync(codec); |
| 236 | + hda_call_check_power_status(codec, 0x01); |
| 237 | + |
| 238 | + return 0; |
| 239 | +} |
| 240 | + |
| 241 | +static int cm9825_probe(struct hda_codec *codec, const struct hda_device_id *id) |
| 242 | +{ |
| 243 | + struct cmi_spec *spec; |
| 244 | + struct auto_pin_cfg *cfg; |
| 245 | + int err; |
| 246 | + |
| 247 | + spec = kzalloc(sizeof(*spec), GFP_KERNEL); |
| 248 | + if (spec == NULL) |
| 249 | + return -ENOMEM; |
| 250 | + |
| 251 | + INIT_DELAYED_WORK(&spec->unsol_hp_work, cm9825_unsol_hp_delayed); |
| 252 | + codec->spec = spec; |
| 253 | + spec->codec = codec; |
| 254 | + cfg = &spec->gen.autocfg; |
| 255 | + snd_hda_gen_spec_init(&spec->gen); |
| 256 | + spec->chip_d0_verbs = cm9825_std_d0_verbs; |
| 257 | + spec->chip_d3_verbs = cm9825_std_d3_verbs; |
| 258 | + spec->chip_hp_present_verbs = cm9825_hp_present_verbs; |
| 259 | + spec->chip_hp_remove_verbs = cm9825_hp_remove_verbs; |
| 260 | + |
| 261 | + snd_hda_sequence_write(codec, spec->chip_d0_verbs); |
| 262 | + |
| 263 | + err = snd_hda_parse_pin_defcfg(codec, cfg, NULL, 0); |
| 264 | + if (err < 0) |
| 265 | + goto error; |
| 266 | + err = snd_hda_gen_parse_auto_config(codec, cfg); |
| 267 | + if (err < 0) |
| 268 | + goto error; |
| 269 | + |
| 270 | + cm9825_setup_unsol(codec); |
| 271 | + |
| 272 | + return 0; |
| 273 | + |
| 274 | + error: |
| 275 | + cm9825_remove(codec); |
| 276 | + |
| 277 | + codec_info(codec, "Enter err %d\n", err); |
| 278 | + |
| 279 | + return err; |
| 280 | +} |
| 281 | + |
| 282 | +static const struct hda_codec_ops cm9825_codec_ops = { |
| 283 | + .probe = cm9825_probe, |
| 284 | + .remove = cm9825_remove, |
| 285 | + .build_controls = snd_hda_gen_build_controls, |
| 286 | + .build_pcms = snd_hda_gen_build_pcms, |
| 287 | + .init = cm9825_init, |
| 288 | + .unsol_event = snd_hda_jack_unsol_event, |
| 289 | + .suspend = cm9825_suspend, |
| 290 | + .resume = cm9825_resume, |
| 291 | + .check_power_status = snd_hda_gen_check_power_status, |
| 292 | + .stream_pm = snd_hda_gen_stream_pm, |
| 293 | +}; |
| 294 | + |
| 295 | +/* |
| 296 | + * driver entries |
| 297 | + */ |
| 298 | +static const struct hda_device_id snd_hda_id_cm9825[] = { |
| 299 | + HDA_CODEC_ID(0x13f69825, "CM9825"), |
| 300 | + {} /* terminator */ |
| 301 | +}; |
| 302 | +MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_cm9825); |
| 303 | + |
| 304 | +MODULE_LICENSE("GPL"); |
| 305 | +MODULE_DESCRIPTION("CM9825 HD-audio codec"); |
| 306 | + |
| 307 | +static struct hda_codec_driver cm9825_driver = { |
| 308 | + .id = snd_hda_id_cm9825, |
| 309 | + .ops = &cm9825_codec_ops, |
| 310 | +}; |
| 311 | + |
| 312 | +module_hda_codec_driver(cm9825_driver); |
0 commit comments