Bug 214537

Summary: ALC668 on ASUS G551JW: problem on detecting and setting jack to CTIA
Product: Drivers Reporter: msd (msd.mmq)
Component: Sound(ALSA)Assignee: Jaroslav Kysela (perex)
Status: NEW ---    
Severity: normal CC: hui.wang, kailang, tiwai
Priority: P1    
Hardware: All   
OS: Linux   
Kernel Version: 5.4.0-80 Subsystem:
Regression: No Bisected commit-id:
Attachments: alsa info
Unplugged
CTIA plugged
TRS plugged
CTIA plugged and the 0x15 manually set to 0x0d60
setting 0x0c60 to 0x15
0x0c60: als-info-unplugged
0x0c60: als-info-plugged-CTIA
adding a setting of 0xc3 to 0x200
windows10 dump tool
Windows 10 log file
fixed ctia headset-mic
working patch
test new patch (fixed ctia type)

Description msd 2021-09-27 07:16:26 UTC
Created attachment 298977 [details]
alsa info

On my ASUS G551JW laptop with ALC668 codec, the hda driver does not properly detect CTIA jack, causing noisy headset sound with a non-functional headset microphone. If I manually set the CTIA mode by setting the 0x15 coef value to 0x0d60 by using the hda-verb after plugging the headset, everything works perfectly. After some debugging, it appears that the following part of alc_determine_headset_type() in patch_realtek.c file causes this issue:

    case 0x10ec0668:
        alc_process_coef_fw(codec, coef0688);
        msleep(300);
        val = alc_read_coef_idx(codec, 0xbe);
        is_ctia = (val & 0x1c02) == 0x1c02;
        break;

It seems 0xbe coef is read-only and cannot be set to 0x1c02 in the initialization. Its value is always 0x0002 while unplugged, even if I set it to 0x1c02 with hda-verb (I have noticed that this default value is also present on some other bug reports related to ALC688 such as https://bugzilla.kernel.org/show_bug.cgi?id=116421). As a result, "is_ctia" is always "false". However, after plugging the headset and manually setting the value of 0x15 coef to 0x0d60, the value of 0xbe coef becomes 0x1c02. Therefore, it seems that the logic of the above code is not correct.

I should mention that I have set the model to dell-headset-multi. Without this, the jack behaves like a TRS jack.
Comment 1 Takashi Iwai 2021-09-28 13:26:19 UTC
So it seems that the implementation is wrong for your codec.

Could you try to set the module option dump_coef=1 for snd-hda-codec (e.g. boot with snd_hda_codec.dump_coef=1 boot option), get alsa-info.sh outputs (or cat the corresponding /proc/asound/card*/codec* file) in various cases (CTIA plugged, TRS plugged, unplugged) for comparison?
Comment 2 msd 2021-09-28 14:34:23 UTC
Created attachment 298999 [details]
Unplugged
Comment 3 msd 2021-09-28 14:34:53 UTC
Created attachment 299001 [details]
CTIA plugged
Comment 4 msd 2021-09-28 14:35:25 UTC
Created attachment 299003 [details]
TRS plugged
Comment 5 msd 2021-09-28 14:39:02 UTC
I just uploaded the cat files for various cases.
Comment 6 msd 2021-09-28 14:44:07 UTC
Created attachment 299005 [details]
CTIA plugged and the 0x15 manually set to 0x0d60
Comment 7 Takashi Iwai 2021-09-29 08:12:18 UTC
Thanks.  Apparently, reading COEF 0xbe for determining the jack type doesn't seem right; the value seems changed after you set COEF 0x15 to 0x0d60, though.

This needs a help from Realtek people as it's a vendor-specific feature that isn't publicly available...
Comment 8 Takashi Iwai 2021-09-29 08:14:08 UTC
Adding some relevant people to Cc.
Comment 9 Hui Wang 2021-09-29 11:40:35 UTC
@msd,

When the audio jack is empty or sth is unplugged from the audio jack, the alc_determine_headset_type() will not be called, instead the function alc_headset_mode_unplugged() is called, so it doesn't matter what value will be returned from the register 0xbe.

When the ctia type is plugged, the register should return 0x1c02, but it doesn't, maybe it is because the msleep(300) is not enough, could you please test msleep(800) and plug the ctia headset as quickly as possible (within 800ms, please insert the headset into the jack completely).


And also it is possible that you don't need to set 'model=dell-headset-multi' at all, maybe you need to apply a quirk as below, the hardware has capability to detect the headset-mic and its type automatically.
		.type = HDA_FIXUP_PINS,
		.v.pins = (const struct hda_pintbl[]) {
			{ 0x1b, 0x03a11020 }, /* headset mic with jack detect */
			{ }
		},
Comment 10 msd 2021-09-30 08:49:29 UTC
@Takashi,
Thanks for following up.

@Hui,
Thanks, I just increased the msleep() and did what you suggested, but it didn't make any difference.

I also removed 'model=dell-headset-multi' and applied the quirk. By this, dmesg shows pin 0x1b as 'Mic' and it doesn't detect plugging the headset. I also tried to chain it with ALC668_FIXUP_HEADSET_MODE. In this case, dmesg shows the 0x1b pin as 'Headset Mic', but again it doesn't detect plugging the headset. I noticed that the value of 0xbe is always 0x0002 (0x15 is always 0x0d40) with this quirk.

Interestingly, when I don't apply any quirk, the value of 0x15 is always 0x0d60 and 0xbe is 0x1c02. However, the 0x1b pin is not present in the dmesg (no headset mic).
Comment 11 Hui Wang 2021-09-30 09:32:53 UTC
@msd,

If you remove 'model=dell-headset-multi' and set 0x1b to 0x03a11020, but don't chain ALC668_FIXUP_HEADSET_MODE, does the 0xbe has the value of 0x1c02?

Maybe Besides setting 0x1b to 0x03a11020, it also needs to run a hda verb to enable the auto-detecting mode on the Mic like the codec of alc269 family does (refer to ALC255_FIXUP_XIAOMI_HEADSET_MIC):
		.type = HDA_FIXUP_VERBS,
		.v.verbs = (const struct hda_verb[]) {
			{ 0x20, AC_VERB_SET_COEF_INDEX, 0x45 },
			{ 0x20, AC_VERB_SET_PROC_COEF, 0x5089 },
			{ }


For alc668, maybe need to set 0x15 to 0x0d60 to enable auto-detect?

This needs Kailang@realtek to confirm. Let me ping him.
Comment 12 msd 2021-09-30 10:36:33 UTC
(In reply to Hui Wang from comment #11)
> @msd,
> 
> If you remove 'model=dell-headset-multi' and set 0x1b to 0x03a11020, but
> don't chain ALC668_FIXUP_HEADSET_MODE, does the 0xbe has the value of 0x1c02?
I checked it again. The value is 0x0002 (both plugged and unplugged)

> Maybe Besides setting 0x1b to 0x03a11020, it also needs to run a hda verb to
> enable the auto-detecting mode on the Mic like the codec of alc269 family
> does (refer to ALC255_FIXUP_XIAOMI_HEADSET_MIC):
>               .type = HDA_FIXUP_VERBS,
>               .v.verbs = (const struct hda_verb[]) {
>                       { 0x20, AC_VERB_SET_COEF_INDEX, 0x45 },
>                       { 0x20, AC_VERB_SET_PROC_COEF, 0x5089 },
>                       { }
It didn't work. Maybe a different coef and value for ALC668?

> 
> For alc668, maybe need to set 0x15 to 0x0d60 to enable auto-detect?
This didn't work, either. The value stays 0x0d40 (0x0002 for 0xbe).
Comment 13 msd 2021-09-30 10:40:01 UTC
(In reply to msd from comment #10)

> Interestingly, when I don't apply any quirk, the value of 0x15 is always
> 0x0d60 and 0xbe is 0x1c02. However, the 0x1b pin is not present in the dmesg
> (no headset mic).
Please disregard the above comment. This doesn't happen anymore (the 0x15 is 0x0d40). Not sure what caused this behavior in my first test.
Comment 14 Hui Wang 2021-09-30 11:15:03 UTC
OK, let us wait for Kailang's input then.

And you could take a look at below commit before Kailang's input, this commit is for fixed ctia-type headset, so no need to call alc_determine_headset_type():

Author: Kailang Yang <kailang@realtek.com>
Date:   Thu Dec 17 16:52:44 2020 +0800

    ALSA: hda/realtek - Supported Dell fixed type headset
    
    This platform only supported iphone type headset.
    It can't support Dell headset mode.
    
    Signed-off-by: Kailang Yang <kailang@realtek.com>
    Cc: <stable@vger.kernel.org>
    Link: https://lore.kernel.org/r/b97e971978034bc9b772a08ec91265e8@realtek.com
    Signed-off-by: Takashi Iwai <tiwai@suse.de>
Comment 15 msd 2021-10-01 11:17:55 UTC
I made some more debugging and it appears that the device becomes unresponsive as soon as I set the mic pin to autodetect (the quirk in comment #9). I mean it no longer executes set verb commands. For example, if I set_coef_idx on pin 0x20 to a different value than what get_coef_idx gives, the value will not change to the new value. This doesn't happen when the jack detection of 0x1b pin is disabled.
Comment 16 Hui Wang 2021-10-04 01:51:12 UTC
Created attachment 299079 [details]
setting 0x0c60 to 0x15

@msd,

Kailang@realtek suggests setting 0x0c60 to coeff 0x15, this could enable the auto-detect of the mic. Please test the attached patch. thx.
Comment 17 msd 2021-10-04 15:20:39 UTC
Created attachment 299083 [details]
0x0c60: als-info-unplugged

@Hui,

The patch did not solve the problem. The codec still becomes unresponsive (as described in #15). In case it helps, please find the attached alsa-info files with this patch, before plugging and after it.
Comment 18 msd 2021-10-04 15:21:20 UTC
Created attachment 299085 [details]
0x0c60: als-info-plugged-CTIA
Comment 19 Hui Wang 2021-10-05 08:02:00 UTC
Created attachment 299105 [details]
adding a setting of 0xc3 to 0x200

@msd,

Please also set the 0xc3 to 0x0200, please refer to the new attached testing patch.
Comment 20 Hui Wang 2021-10-05 08:26:59 UTC
@msd,

BTW, don't forget to set the power_save to 0, this will disable the codec runtime PM.
Comment 21 Hui Wang 2021-10-05 09:44:00 UTC
@msd,

If "0xc3=0x200" doesn't work, please try set 0xc3 to 0x4200 and test.
Comment 22 msd 2021-10-06 13:15:57 UTC
@Hui,

> BTW, don't forget to set the power_save to 0, this will disable the codec
> runtime PM.
This solves the unresponsive problem.

I tested the patch (with both 0x200 and 0x4200 values for 0xc3), but it didn't work.

After applying this patch and also disabling the power-saving, the pin_cap value of pin 0x1b becomes 0x3724 (it was 0x24, before). As I understand, this means that VREF control is now enabled for all signal levels. However, pin_cap value of pin 0x15 is 0x1001c (VREF control only enabled for 80% signal level). Since autodetection works for pin 0x15, could this be an issue?
Comment 23 msd 2021-10-06 13:27:49 UTC
> After applying this patch and also disabling the power-saving, the pin_cap
> value of pin 0x1b becomes 0x3724 (it was 0x24, before). As I understand,
> this means that VREF control is now enabled for all signal levels. However,
> pin_cap value of pin 0x15 is 0x1001c (VREF control only enabled for 80%
> signal level). Since autodetection works for pin 0x15, could this be an
> issue?
Please ignore this part. This deduction is wrong. I should have checked the pin_widget value, which is 0x24 for pin 0x1b.
Comment 24 Hui Wang 2021-10-06 13:31:15 UTC
Did you test with a patched kernel or just use hda-verb to change those values? If you patched a kernel, could you upload your testing patch?
Comment 25 msd 2021-10-06 13:42:25 UTC
I used your provided patch to build a dkms module for kernel 5.4.0-80 (Ubuntu 20.04). Should I build the entire kernel?
For reading the pin_cap and pin_widget values, I just used hda-verb.
Comment 26 Hui Wang 2021-10-06 13:48:47 UTC
dkms is ok.

"This solves the unresponsive problem.", what does it mean?
Comment 27 msd 2021-10-06 13:54:11 UTC
(In reply to Hui Wang from comment #26)
> dkms is ok.
> 
> "This solves the unresponsive problem.", what does it mean?

As I described in comment #15, the codec somehow doesn't perform set- commands via hda-verb as soon as the pin 0x1b is set to auto-detect mode when the power-saving mode is enabled (the default). Setting the power_save to 0 solves this problem.
Comment 28 Hui Wang 2021-10-06 14:02:16 UTC
OK, got it.


If you installed windows on the machine and the headset-mic works well, you could dump a log via the tool I will upload, then we could compare the coeff setting between windows and linux.
Comment 29 Hui Wang 2021-10-06 14:03:21 UTC
Created attachment 299125 [details]
windows10 dump tool
Comment 30 msd 2021-10-06 18:46:13 UTC
Created attachment 299127 [details]
Windows 10 log file

The headset-mic works fine in Windows. Please find the attachment.
Comment 31 Hui Wang 2021-10-07 03:28:22 UTC
The windows set the pincfg of NID 0x1b to 0x04a11020 (Wid=1B  Codec=411111F0  Drv=04A11020  Loc=00000000), so maybe we could set it as windows, and set power_save to 0, plug a ctia headset.

Then use the hda-verb to set coeff value as windows if the value under linux is different from windows. e.g.:

$sudo hda-verb /dev/snd/hwC0D0 0x20 SET_COEF_INDEX 0x01
$sudo hda-verb /dev/snd/hwC0D0 0x20 SET_PROC_COEF 0xaabe
$sudo hda-verb /dev/snd/hwC0D0 0x20 SET_COEF_INDEX 0x07
$sudo hda-verb /dev/snd/hwC0D0 0x20 SET_PROC_COEF 0x2f81
$sudo hda-verb /dev/snd/hwC0D0 0x20 SET_COEF_INDEX 0x0a
$sudo hda-verb /dev/snd/hwC0D0 0x20 SET_PROC_COEF 0x0078
$sudo hda-verb /dev/snd/hwC0D0 0x20 SET_COEF_INDEX 0x15
$sudo hda-verb /dev/snd/hwC0D0 0x20 SET_COEF_INDEX 0x0d60
...

And don't forget to check the Mic Jack status, Maybe after you set some coeff as windows, the driver could detect the Mic plugging.
Comment 32 msd 2021-10-07 10:02:01 UTC
I tested different combinations of these values along with the pincfg 0x04a11020 and here is what I can say:

1. coeff 0x0a=0x78 changes the "sense" status of 0x1b pin to "plugged" no matter the jack is plugged or unplugged (reported by hdajacksensetest as YES for both cases).
2. Setting 0x01 and 0x07 coefficients makes no difference.
3. With coeff 0x0a=0x78, the internal mic becomes non-functional (headset unplugged). Also, the headset mic does not work.
4. With coeff 0x0a=0x78, the value of coeff 0xbe is 0x1c02 while a CTIA jack is plugged. The value remains the same after unplugging.
5. With coeff 0x0a=0x78, the value of coeff 0xbe is 0x0002 while a TRS jack is plugged. The value becomes 0x1802 after unplugging.


I also noticed that the windows dump tool reports the mic pin is always connected even when the headset is unplugged (similar to No.1 above), but the value of coeff 0xbe becomes 0x0002 after unplugging. Having said that, both internal mic and headset mic work as expected in Windows.
Comment 33 Hui Wang 2021-10-07 15:38:05 UTC
Created attachment 299129 [details]
fixed ctia headset-mic

Please test this patch.
Comment 34 msd 2021-10-08 18:35:43 UTC
Created attachment 299139 [details]
working patch

@Hui,

I tested the patch, but it did not work. I think the reason is that coeff 0x15 never is set to 0xd60 with this patch. Its value (read by hda-verb) is always 0xd40.

However, I had success when I replaced alc269_fixup_headset_mic function in this patch with alc_fixup_headset_mode_no_hp_mic. Along with this change, I also increased the msleep() value to 800 as you suggested in comment #9 (please see the attached patch). After these changes, the CTIA is configured correctly although it still fails to detect CTIA if I plug the headset not fast enough.

A strange thing is that sometimes when I plug a CTIA jack, the value of coeff 0xbe becomes 0x1802 instead of 0x1c02. I was unable to find the reason.

Also, I have a question. Is it possible to re-trigger the headset type detection after the user selects plugged device type (headphone/headset) through "Select Audio Device" pop-up (maybe in alc_update_headset_mode())? With this, the jack will always be configured correctly because the initial detection during plugging is highly dependent on the speed of plugging and also the value of msleep().
Comment 35 Hui Wang 2021-10-09 12:23:53 UTC
About your question, I remember I met a similar issue on a Dell machine before, the headset type detection depends on the 1st time calling determine_headset_type(), if don't plug out the headset from jack, it is useless to call determine_headset_type() again, since this function will always return the type same as the 1st time.


The purpose of my patch is to skip calling determine_headset_type() or alc_update_headset_mode(), so I linked alc269_fixup_headset_mic instead of alc269_fixup_headset_mode/alc_fixup_headset_mode_no_hp_mic. You said the patch didn't work, does it mean the headset-mic is detected when plugging in, but you can't record sound from headset-mic?

If so, maybe it is the issue of coeff 0x15, let me set it to 0xd60 in the new patch and please test the new patch.
Comment 36 Hui Wang 2021-10-09 12:37:46 UTC
Created attachment 299149 [details]
test new patch (fixed ctia type)

please test this new patch when you have time.
Comment 37 msd 2021-10-09 16:31:01 UTC
@Hui,

Thanks! your last patch works well for me (tested both CTIA headset and TRS headphone). However, I assume that it disables the feature of detecting OMTP and CTIA jack types. Therefore, one would encounter an issue plugging an OMTP headset, although this might not be a big deal given that the majority of headsets are produced with the CTIA standard, these days.

Please consider my comments below just as some thoughts to make the ALC668 (and maybe other codecs) better supported by the kernel.


> About your question, I remember I met a similar issue on a Dell machine
> before, the headset type detection depends on the 1st time calling
> determine_headset_type(), if don't plug out the headset from jack, it is
> useless to call determine_headset_type() again, since this function will
> always return the type same as the 1st time.

It seems the last part of your comment might not be true, at least for ALC668. The current code runs determine_headset_type() as soon as plugging is sensed. The logic of this function sets coeff 0x15 to 0xd60 and waits for 300ms before reading the value of coeff 0xbe to determine the jack type. Depending on the quality of the hardware connector and also the plugging speed, 300ms might not be long enough to let the jack become fully plugged, causing a short-circuit between the sleeve and adjacent ring of the jack and, consequently, wrongly detecting the jack type. However, if the value of coeff 0x15 is set to 0xd60 after the jack is completely plugged, the value of coeff 0xbe becomes 0x1c02 and 0x0002 for CTIA and TRS jacks, respectively (unfortunately I don't have an OMTP headset to test). So, determine_headset_type() may return a different type when it is called for the second time after the jack is completely inserted.

In my opinion, this second-time detection can either be scheduled to be done after, say, one second or (maybe a better choice) be performed after user interaction with the "Select Audio Device" pop-up. If other Realtek codecs worked similar to ALC668, this would help fix issues related to jack detection.
Comment 38 Hui Wang 2021-10-10 02:05:31 UTC
> Thanks! your last patch works well for me (tested both CTIA headset and TRS
> headphone). However, I assume that it disables the feature of detecting OMTP
> and CTIA jack types. Therefore, one would encounter an issue plugging an
> OMTP headset, although this might not be a big deal given that the majority
> of headsets are produced with the CTIA standard, these days.
> 
Right. I write this patch because many machines have their audio jack fixed to ctia type, don't support omtp headset at all. This is implemented by circuit design instead of codec capability. And this patch will not call determine_headset_type(), it will avoid to set the audio jack to omtp by a mistake.

If windows supports omtp headset on this machine, then this patch should be dropped.



> It seems the last part of your comment might not be true, at least for
> ALC668. The current code runs determine_headset_type() as soon as plugging
> is sensed. The logic of this function sets coeff 0x15 to 0xd60 and waits for
> 300ms before reading the value of coeff 0xbe to determine the jack type.
> Depending on the quality of the hardware connector and also the plugging
> speed, 300ms might not be long enough to let the jack become fully plugged,
> causing a short-circuit between the sleeve and adjacent ring of the jack
> and, consequently, wrongly detecting the jack type. However, if the value of
> coeff 0x15 is set to 0xd60 after the jack is completely plugged, the value
> of coeff 0xbe becomes 0x1c02 and 0x0002 for CTIA and TRS jacks, respectively
> (unfortunately I don't have an OMTP headset to test). So,
> determine_headset_type() may return a different type when it is called for
> the second time after the jack is completely inserted.

I know what you mean, but at the first time, you set the coeff 0x15 via the function determine_headset_type(), at the 2nd time, you set the coeff 0x15 via hda-verb, and read the value of 0xbe via the hda-verb too, the delay between two hda-verb operations is more than 1-2 seconds I guess, maybe just because the delay is long enough at the 2nd time, you get the correct value from 0xbe.

You could hack the driver to try to call the determine_headset_type() twice or multiple times, maybe you will get different result from the manually running hda-verb.
 
> 
> In my opinion, this second-time detection can either be scheduled to be done
> after, say, one second or (maybe a better choice) be performed after user
> interaction with the "Select Audio Device" pop-up. If other Realtek codecs
> worked similar to ALC668, this would help fix issues related to jack
> detection.
I wrote a similar patch before, after users do a plugin action, the determine_headset_type() will not be called, just schedule a delayed_work, after 2 seconds (the headset is completely plugged in), the determine_headset_type() is called in the delayed_work, this makes the headset type detection more precise than before. If you have time, could try and test it.

https://mailman.alsa-project.org/pipermail/alsa-devel/2021-March/182296.html
Comment 39 Hui Wang 2021-10-10 02:15:27 UTC
Oh, weird, your machine has internal mic, so when you plug a headset to the audio jack, the determine_headset_type() will not be called immediately until you manually click the button on the audio-device-selection dialogue, that is to say the headset is completely plugged in when you click the button, I have no idea why the determine_headset_type() still returns the wrong type.
Comment 40 msd 2021-10-10 15:04:13 UTC
I tested the patch you linked in comment #38 and it worked! I even removed the msleep(300) between writing to 0x15 and reading the value of 0xbe, and it still worked, correctly detecting CTIA jack every time I plugged the jack, regardless of my speed. This clearly shows that the codec does not need a delay to prepare correct value for 0xbe. That delay is supposedly just for letting the jack become fully plugged.


> I know what you mean, but at the first time, you set the coeff 0x15 via the
> function determine_headset_type(), at the 2nd time, you set the coeff 0x15
> via hda-verb, and read the value of 0xbe via the hda-verb too, the delay
> between two hda-verb operations is more than 1-2 seconds I guess, maybe just
> because the delay is long enough at the 2nd time, you get the correct value
> from 0xbe.

As I described above, I removed the msleep() to test this case. Programmatically reading the value of 0xbe immediately after writing to 0x15 works perfectly. So no delay is even required if the jack is physically in its correct position.


> Oh, weird, your machine has internal mic, so when you plug a headset to the
> audio jack, the determine_headset_type() will not be called immediately
> until you manually click the button on the audio-device-selection dialogue,
> that is to say the headset is completely plugged in when you click the
> button, I have no idea why the determine_headset_type() still returns the
> wrong type.

I debugged this and apparently it behaves differently. The determine_headset_type() is immediately called when I plug the headset and the headset mic is selected by default (which is the correct behavior IMHO). Call stack shows that the function is called through mux_enum_put() in hda_generic.c. Interestingly, alc_update_headset_mode() is not called again if I click on "headset" button in the audio-device-selection dialogue.
Comment 41 Hui Wang 2021-10-11 01:49:02 UTC
> I debugged this and apparently it behaves differently. The
> determine_headset_type() is immediately called when I plug the headset and
> the headset mic is selected by default (which is the correct behavior IMHO).
> Call stack shows that the function is called through mux_enum_put() in
> hda_generic.c. Interestingly, alc_update_headset_mode() is not called again
> if I click on "headset" button in the audio-device-selection dialogue.

Oh, that is because we introduced a change in the ubuntu pulseaudio, the upstream pulseaudio will not work like that. With the upstream pulseaudio, if the internal mic connects to the HDA codec, the determine_headset_type() will be called only users select sth from dialogue.

The patch in the #38 is not easy to be accepted by upstream since it will bring impact to many many codecs (different codecs have different msleep value, plus an extra 2s delay, some codecs will reach 3s delay).

So Let us choose a solution from #36 and #34, which one do you prefer? If we meet other issues with #36 or #34 in the future, we could continue to improve it.
Comment 42 msd 2021-10-11 14:19:54 UTC
Right. I prefer the patch in #36 as it works flawlessly for CTIA headsets. Thanks!