├── .github
└── ISSUE_TEMPLATE
│ └── audio-issue.md
├── .gitignore
├── LICENSE
├── README.md
├── blobs
├── avs-topology_2024.02.tar.gz
└── mdn
│ ├── fw
│ ├── sof-rmb.ldc
│ └── sof-rmb.ri
│ └── tplg
│ └── sof-rmb-rt5682s-rt1019.tplg
├── conf
├── avs
│ └── snd-avs.conf
├── common
│ └── 51-increase-headroom.conf
└── sof
│ ├── hifi2-sof.conf
│ └── mtl-sof.conf
├── functions.py
└── setup-audio
/.github/ISSUE_TEMPLATE/audio-issue.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Audio issue
3 | about: Issues related to audio not working.
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **Boardname**
14 | If you don't know where to find this, run `cat /sys/class/dmi/id/product_name`
15 |
16 | **Logs**
17 | Generate logs with `wget https://raw.githubusercontent.com/chrultrabook/linux-tools/main/debugging.sh; bash debugging.sh` and attach the output here.
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__/
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2023, WeirdTreeThing
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 |
11 | 2. Redistributions in binary form must reproduce the above copyright notice,
12 | this list of conditions and the following disclaimer in the documentation
13 | and/or other materials provided with the distribution.
14 |
15 | 3. Neither the name of the copyright holder nor the names of its
16 | contributors may be used to endorse or promote products derived from
17 | this software without specific prior written permission.
18 |
19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
Python script to enable audio support on Chrome devices
2 |
3 | Note: A full install of a supported Linux distro is required! Live USB sessions will not work.
4 |
5 | # Instructions
6 | 1. git clone https://github.com/WeirdTreeThing/chromebook-linux-audio
7 | 2. cd chromebook-linux-audio
8 | 3. ./setup-audio
9 |
10 | # Requirements
11 | 1. `python 3.10`
12 | 2. `git`
13 |
14 | # Supported Devices
15 | See the [Chrultrabook docs](https://docs.chrultrabook.com/docs/firmware/supported-devices.html) for more info.
16 |
17 | # Officially Supported Distros
18 | 1. Alpine Linux edge
19 | 2. Arch Linux
20 | 3. Debian Testing
21 | 4. Fedora 41
22 | 5. OpenSUSE Tumbleweed
23 | 6. Ubuntu 24.10
24 | 7. Void Linux
25 |
26 | # Other Distros
27 | Other distros will likely work but will require you to manually install packages. The script will print a list of any packages you may need to install. It is required to have a relatively modern distro (no old LTS releases) as those will be missing important fixes.
28 |
29 | # Donations
30 | If you would like to support the work I do, consider donating [here](https://paypal.me/weirdtreething).
31 |
--------------------------------------------------------------------------------
/blobs/avs-topology_2024.02.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WeirdTreeThing/chromebook-linux-audio/e02d690a8b46891795c3f8e3dbd192d41a8cee96/blobs/avs-topology_2024.02.tar.gz
--------------------------------------------------------------------------------
/blobs/mdn/fw/sof-rmb.ldc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WeirdTreeThing/chromebook-linux-audio/e02d690a8b46891795c3f8e3dbd192d41a8cee96/blobs/mdn/fw/sof-rmb.ldc
--------------------------------------------------------------------------------
/blobs/mdn/fw/sof-rmb.ri:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WeirdTreeThing/chromebook-linux-audio/e02d690a8b46891795c3f8e3dbd192d41a8cee96/blobs/mdn/fw/sof-rmb.ri
--------------------------------------------------------------------------------
/blobs/mdn/tplg/sof-rmb-rt5682s-rt1019.tplg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WeirdTreeThing/chromebook-linux-audio/e02d690a8b46891795c3f8e3dbd192d41a8cee96/blobs/mdn/tplg/sof-rmb-rt5682s-rt1019.tplg
--------------------------------------------------------------------------------
/conf/avs/snd-avs.conf:
--------------------------------------------------------------------------------
1 | options snd-intel-dspcfg dsp_driver=4
2 | options snd-soc-avs ignore_fw_version=1
3 |
--------------------------------------------------------------------------------
/conf/common/51-increase-headroom.conf:
--------------------------------------------------------------------------------
1 | monitor.alsa.rules = [
2 | {
3 | matches = [
4 | {
5 | node.name = "~alsa_output.*"
6 | }
7 | ]
8 | actions = {
9 | update-props = {
10 | api.alsa.headroom = 4096
11 | }
12 | }
13 | }
14 | ]
15 |
--------------------------------------------------------------------------------
/conf/sof/hifi2-sof.conf:
--------------------------------------------------------------------------------
1 | options snd_sof sof_debug=1
2 | options snd_intel_dspcfg dsp_driver=3
3 |
--------------------------------------------------------------------------------
/conf/sof/mtl-sof.conf:
--------------------------------------------------------------------------------
1 | # TODO: submit patch to linux to remove the need for this
2 | options snd-intel-dspcfg dsp_driver=3
3 |
--------------------------------------------------------------------------------
/functions.py:
--------------------------------------------------------------------------------
1 | import contextlib
2 | import subprocess
3 | import sys
4 | from pathlib import Path
5 | from threading import Thread
6 | from time import sleep
7 |
8 |
9 | #######################################################################################
10 | # PATHLIB FUNCTIONS #
11 | #######################################################################################
12 | # unlink all files in a directory and remove the directory
13 | def rmdir(rm_dir: str, keep_dir: bool = True) -> None:
14 | def unlink_files(path_to_rm: Path) -> None:
15 | try:
16 | for file in path_to_rm.iterdir():
17 | if file.is_file():
18 | file.unlink()
19 | else:
20 | unlink_files(path_to_rm)
21 | except FileNotFoundError:
22 | print(f"Couldn't remove non existent directory: {path_to_rm}, ignoring")
23 |
24 | # convert string to Path object
25 | rm_dir_as_path = Path(rm_dir)
26 | try:
27 | unlink_files(rm_dir_as_path)
28 | except RecursionError: # python doesn't work for folders with a lot of subfolders
29 | print(f"Failed to remove {rm_dir} with python, using bash")
30 | bash(f"rm -rf {rm_dir_as_path.absolute().as_posix()}/*")
31 | # Remove emtpy directory
32 | if not keep_dir:
33 | try:
34 | rm_dir_as_path.rmdir()
35 | except FileNotFoundError: # Directory doesn't exist, because bash was used
36 | return
37 |
38 |
39 | # remove a single file
40 | def rmfile(file: str, force: bool = False) -> None:
41 | if force: # for symbolic links
42 | Path(file).unlink(missing_ok=True)
43 | file_as_path = Path(file)
44 | with contextlib.suppress(FileNotFoundError):
45 | file_as_path.unlink()
46 |
47 |
48 | # make directory
49 | def mkdir(mk_dir: str, create_parents: bool = False) -> None:
50 | mk_dir_as_path = Path(mk_dir)
51 | if not mk_dir_as_path.exists():
52 | mk_dir_as_path.mkdir(parents=create_parents)
53 |
54 |
55 | def path_exists(path_str: str) -> bool:
56 | return Path(path_str).exists()
57 |
58 |
59 | # recursively copy files from a dir into another dir
60 | def cpdir(src_as_str: str, dst_as_string: str) -> None: # dst_dir must be a full path, including the new dir name
61 | src_as_path = Path(src_as_str)
62 | dst_as_path = Path(dst_as_string)
63 | if src_as_path.exists():
64 | if not dst_as_path.exists():
65 | mkdir(dst_as_string)
66 | bash(f"cp -rp {src_as_path.absolute().as_posix()}/* {dst_as_path.absolute().as_posix()}")
67 | else:
68 | raise FileNotFoundError(f"No such directory: {src_as_path.absolute().as_posix()}")
69 |
70 |
71 | def cpfile(src_as_str: str, dst_as_str: str) -> None: # "/etc/resolv.conf", "/var/some_config/resolv.conf"
72 | src_as_path = Path(src_as_str)
73 | dst_as_path = Path(dst_as_str)
74 | if src_as_path.exists():
75 | dst_as_path.write_bytes(src_as_path.read_bytes())
76 | else:
77 | raise FileNotFoundError(f"No such file: {src_as_path.absolute().as_posix()}")
78 |
79 |
80 | #######################################################################################
81 | # BASH FUNCTIONS #
82 | #######################################################################################
83 |
84 | # return the output of a command
85 | def bash(command: str) -> str:
86 | try:
87 | output = subprocess.check_output(command, shell=True, text=True).strip()
88 | return output
89 | except:
90 | print(f"failed to run command: {command}")
91 |
92 |
93 | #######################################################################################
94 | # PRINT FUNCTIONS #
95 | #######################################################################################
96 |
97 |
98 | def print_warning(message: str) -> None:
99 | print("\033[93m" + message + "\033[0m", flush=True)
100 |
101 |
102 | def print_error(message: str) -> None:
103 | print("\033[91m" + message + "\033[0m", flush=True)
104 |
105 |
106 | def print_status(message: str) -> None:
107 | print("\033[94m" + message + "\033[0m", flush=True)
108 |
109 |
110 | def print_question(message: str) -> None:
111 | print("\033[92m" + message + "\033[0m", flush=True)
112 |
113 |
114 | def print_header(message: str) -> None:
115 | print("\033[95m" + message + "\033[0m", flush=True)
116 |
117 | #######################################################################################
118 | # PACKAGE MANAGER FUNCTIONS #
119 | #######################################################################################
120 | def install_package(arch_package: str = "", deb_package: str = "", rpm_package: str = "", suse_package: str = "",
121 | void_package: str = "", alpine_package: str = ""):
122 | with open("/etc/os-release", "r") as file:
123 | distro = file.read()
124 | if distro.lower().__contains__("arch"):
125 | bash(f"pacman -S --noconfirm --needed {arch_package}")
126 | elif distro.lower().__contains__("void"):
127 | bash(f"xbps-install -y {void_package}")
128 | elif distro.lower().__contains__("ubuntu") or distro.lower().__contains__("debian"):
129 | bash(f"apt-get install -y {deb_package}")
130 | elif distro.lower().__contains__("suse"):
131 | bash(f"zypper --non-interactive install {suse_package}")
132 | elif distro.lower().__contains__("fedora"):
133 | bash(f"dnf install -y {rpm_package}")
134 | elif distro.lower().__contains__("alpine"):
135 | bash(f"apk add --no-interactive {alpine_package}")
136 | else:
137 | print_error(f"Unknown package manager! Please install {arch_package} using your package manager.")
138 |
139 | #######################################################################################
140 | # PLATFORM-SPECIFIC CONFIG FUNCTIONS #
141 | #######################################################################################
142 | def platform_config(platform, args):
143 | match platform:
144 | case "bdw" | "byt" | "bsw":
145 | hifi2_sof_config()
146 | check_sof_fw()
147 | case "skl" | "kbl" | "apl":
148 | avs_config(args)
149 | case "glk" | "cml" | "tgl" | "jsl":
150 | check_sof_fw()
151 | case "adl":
152 | adl_sof_config()
153 | check_sof_fw()
154 | case "mtl":
155 | mtl_sof_config()
156 | check_sof_fw()
157 | case "st":
158 | st_warning()
159 | case "mdn":
160 | mdn_config()
161 |
162 | def get_platform():
163 | # first check if we are on a chromeb{ook,ox,ase,let} (either sys_vendor or product_family includes "google" (case-insensitive for old devices where it was GOOGLE))
164 | # product_family *usually* will tell us the platform
165 | # some platforms (jsl, byt) dont have a product_family so instead check the id of pci device 00:00.0 (chipset/pch)
166 | # for some reason, cyan also doesnt have this set even though every other bsw board does
167 | # samus and buddy are BDW but use intel SST
168 | print_header("Detecting platform")
169 | platform = ""
170 | sv = ""
171 | pf = ""
172 | pn = ""
173 |
174 | with open("/sys/class/dmi/id/sys_vendor") as sys_vendor:
175 | sv = sys_vendor.read().strip().lower()
176 | with open("/sys/class/dmi/id/product_family") as product_family:
177 | pf = product_family.read().strip().lower()
178 | with open("/sys/class/dmi/id/product_name") as product_name:
179 | pn = product_name.read().strip().lower()
180 |
181 | # some people are morons
182 | if pn == "crosvm":
183 | print_error("This script can not and will not do anything in the crostini vm!")
184 | exit(1)
185 |
186 | if not "google" in sv and not "google" in pf:
187 | print_error("This script is not supported on non-Chrome devices!")
188 | exit(1)
189 |
190 | if not len(pf) == 0:
191 | match pf:
192 | case "intel_strago":
193 | print_status("Detected Intel Braswell")
194 | platform = "bsw"
195 | case "google_glados":
196 | print_status("Detected Intel Skylake")
197 | platform = "skl"
198 | case "google_coral" | "google_reef":
199 | print_status("Detected Intel Apollolake")
200 | platform = "apl"
201 | case "google_atlas" | "google_poppy" | "google_nami" | "google_nautilus" | "google_nocturne" | "google_rammus" | "google_soraka" | "google_eve" | "google_fizz" | "google_kalista" | "google_endeavour":
202 | print_status("Detected Intel Kabylake")
203 | platform = "kbl"
204 | case "google_octopus":
205 | print_status("Detected Intel Geminilake")
206 | platform = "glk"
207 | case "google_hatch" | "google_puff":
208 | print_status("Detected Intel Cometlake")
209 | platform = "cml"
210 | case "google_volteer":
211 | print_status("Detected Intel Tigerlake")
212 | platform = "tgl"
213 | case "google_brya" | "google_brask":
214 | print_status("Detected Intel Alderlake")
215 | platform = "adl"
216 | case "google_nissa":
217 | print_status("Detected Intel Alderlake-N")
218 | platform = "adl"
219 | case "google_rex":
220 | print_status("Detected Intel Meteorlake")
221 | platform = "mtl"
222 | case "google_kahlee":
223 | print_status("Detected AMD StoneyRidge")
224 | platform = "st"
225 | case "google_zork":
226 | print_status("Detected AMD Picasso/Dali")
227 | platform = "pco"
228 | case "google_guybrush":
229 | print_status("Detected AMD Cezanne")
230 | platform = "czn"
231 | case "google_skyrim":
232 | print_status("Detected AMD Mendocino")
233 | platform = "mdn"
234 | case _:
235 | print_error(f"Unknown platform/baseboard: {pf}")
236 | exit(1)
237 | return platform
238 | else:
239 | # Cyan special case
240 | if pn == "cyan":
241 | print_status("Detected Intel Braswell")
242 | return "bsw"
243 | # BDW special cases (every other BDW uses HDA audio)
244 | if pn == "samus" or pn == "buddy":
245 | print_status("Detected Intel Broadwell")
246 | return "bdw"
247 | id = ""
248 | with open("/sys/bus/pci/devices/0000:00:00.0/device") as devid:
249 | id = devid.read().strip()
250 | # BYT special case - check if pci dev id is 0x0f00
251 | if id == "0x0f00":
252 | print_status("Detected Intel Baytrail")
253 | return "byt"
254 | # JSL special case - check if pci dev id is 0x4e22
255 | if id == "0x4e22":
256 | print_status("Detected Intel Jasperlake")
257 | return "jsl"
258 |
259 | def mdn_config():
260 | print_header("Installing MDN SOF firmware")
261 | mkdir("/lib/firmware/amd/sof/community", create_parents=True)
262 | mkdir("/lib/firmware/amd/sof-tplg", create_parents=True)
263 | cpdir("blobs/mdn/fw", "/lib/firmware/amd/sof/community")
264 | cpdir("blobs/mdn/tplg", "/lib/firmware/amd/sof-tplg")
265 |
266 | def st_warning():
267 | print_warning("WARNING: Audio on AMD StoneyRidge Chromebooks requires a patched kernel.")
268 | print_warning("You can get a prebuilt kernel for Debian/Ubuntu/Fedora from https://chrultrabook.sakamoto.pl/stoneyridge-kernel/")
269 |
270 |
271 | def avs_config(args):
272 | # Only show the warning to devices with max98357a
273 | override_avs = False
274 | if path_exists("/sys/bus/acpi/devices/MX98357A:00"):
275 | if args.force_avs_install:
276 | print_error(
277 | "WARNING: Your device has max98357a and can cause permanent damage to your speakers if you set the volume too loud!")
278 | while input('Type "I understand the risk of permanently damaging my speakers" in all caps to continue: ')\
279 | != "I UNDERSTAND THE RISK OF PERMANENTLY DAMAGING MY SPEAKERS":
280 | print_error("Try again")
281 | override_avs = True
282 | else:
283 | print_error(
284 | "WARNING: Your device has max98357a and can cause permanent damage to your speakers if you "
285 | "set the volume too loud! As a safety precaution devices with max98357a have speakers "
286 | "disabled until a fix is in place. Headphones and HDMI audio are safe from this.")
287 | print_question("If you want to disable this check, restart the script with --force-avs-install")
288 |
289 | while input('Type "I Understand my speakers will not work since my device has max98357a!" in all caps to continue: ')\
290 | != "I UNDERSTAND MY SPEAKERS WILL NOT WORK SINCE MY DEVICE HAS MAX98357A!":
291 | print_error("Try again")
292 | override_avs = False
293 |
294 | # avs tplg is from https://github.com/thesofproject/avs-topology-xml, but isn't packaged in distros yet
295 | print_header("Installing topology")
296 | mkdir("/tmp/avs_tplg")
297 | avs_tplg_ver = "2024.02"
298 | bash(f"tar xf ./blobs/avs-topology_{avs_tplg_ver}.tar.gz -C /tmp/avs_tplg")
299 | mkdir("/lib/firmware/intel/avs", create_parents=True)
300 | cpdir("/tmp/avs_tplg/avs-topology/lib/firmware/intel/avs", "/lib/firmware/intel/avs")
301 |
302 | print_header("Enabling AVS driver")
303 | cpfile("conf/avs/snd-avs.conf", "/etc/modprobe.d/snd-avs.conf")
304 |
305 | # Delete topology for max98357a to prevent it from working until there is a volume limiter.
306 | if not override_avs:
307 | rmfile("/lib/firmware/intel/avs/max98357a-tplg.bin")
308 |
309 | def check_sof_fw():
310 | if not path_exists("/lib/firmware/intel/sof"):
311 | print_error("SOF firmware is missing, audio will not work!")
312 | print_error("Please install the SOF firmware package (usually sof-firmware) with your package manager")
313 |
314 | def adl_sof_config():
315 | # Special tplg cases
316 | # RPL devices load tplg with a different file name than ADL, despite being the exact same file as their ADL counterparts
317 | # sof-bin currently doesn't include these symlinks, so we create them ourselves
318 | tplgs = ["cs35l41", "max98357a-rt5682-4ch", "max98357a-rt5682", "max98360a-cs42l42", "max98360a-nau8825", "max98360a-rt5682-2way", "max98360a-rt5682-4ch", "max98360a-rt5682", "max98373-nau8825", "max98390-rt5682", "max98390-ssp2-rt5682-ssp0", "nau8825", "rt1019-nau8825", "rt1019-rt5682", "rt5682", "rt711", "sdw-max98373-rt5682"]
319 | for tplg in tplgs:
320 | tplg_path="/lib/firmware/intel/sof-tplg"
321 | if path_exists(f"{tplg_path}/sof-adl-{tplg}.tplg"):
322 | bash(f"ln -sf {tplg_path}/sof-adl-{tplg}.tplg {tplg_path}/sof-rpl-{tplg}.tplg")
323 | if path_exists(f"{tplg_path}/sof-adl-{tplg}.tplg.xz"):
324 | bash(f"ln -sf {tplg_path}/sof-adl-{tplg}.tplg.xz {tplg_path}/sof-rpl-{tplg}.tplg.xz")
325 | if path_exists(f"{tplg_path}/sof-adl-{tplg}.tplg.zst"):
326 | bash(f"ln -sf {tplg_path}/sof-adl-{tplg}.tplg.zst {tplg_path}/sof-rpl-{tplg}.tplg.zst")
327 | # sof-adl-max98360a-cs42l42.tplg is symlinked to sof-adl-max98360a-rt5682.tplg in ChromeOS
328 | tplg_file1="/lib/firmware/intel/sof-tplg/sof-adl-max98360a-rt5682.tplg"
329 | tplg_file2="/lib/firmware/intel/sof-tplg/sof-adl-max98360a-cs42l42.tplg"
330 | if path_exists(f"{tplg_file1}"):
331 | bash(f"ln -sf {tplg_file1} {tplg_file2}")
332 | if path_exists(f"{tplg_file1}.xz"):
333 | bash(f"ln -sf {tplg_file1}.xz {tplg_file2}.xz")
334 | if path_exists(f"{tplg_file1}.zst"):
335 | bash(f"ln -sf {tplg_file1}.xz {tplg_file2}.zst")
336 |
337 | def mtl_sof_config():
338 | print_header("Enabling SOF driver")
339 | cpfile("conf/sof/mtl-sof.conf", "/etc/modprobe.d/mtl-sof.conf")
340 |
341 | def hifi2_sof_config():
342 | print_header("Forcing SOF driver in debug mode")
343 | cpfile("conf/sof/hifi2-sof.conf", "/etc/modprobe.d/hifi2-sof.conf")
344 |
345 | #######################################################################################
346 | # GENERAL FUNCTIONS #
347 | #######################################################################################
348 | def check_arch():
349 | # dmi doesnt exist on arm chromebooks
350 | if not path_exists("/sys/devices/virtual/dmi/id/"):
351 | print_error("ARM Chromebooks are not supported by this script. See your distro's documentation for audio support status.")
352 | exit(1)
353 |
354 | def check_kernel_config(platform):
355 | active_kernel = bash("uname -r")
356 | print_header(f"Checking kernel config for {active_kernel}")
357 |
358 | config = ""
359 | if path_exists(f"/boot/config-{active_kernel}"):
360 | with open(f"/boot/config-{active_kernel}") as file:
361 | config = file.read()
362 | elif path_exists("/proc/config.gz"):
363 | bash(f"zcat /proc/config.gz > /tmp/config-{active_kernel}")
364 | with open(f"/tmp/config-{active_kernel}") as file:
365 | config = file.read()
366 | elif path_exists("/boot/config"):
367 | with open("/boot/config") as file:
368 | config = file.read()
369 | else:
370 | # throw hands up in the air crying
371 | print_error("Unable to read kernel config!")
372 | return
373 |
374 | # List of kernel config strings for audio hardware
375 | module_configs = []
376 |
377 | match platform: # may not want to check for machine drivers here but whatever it works good enough for now
378 | case "bdw": # Maybe I should use catpt for bdw instead of sof
379 | module_configs += ["SND_SOC_INTEL_BDW_RT5650_MACH", "SND_SOC_INTEL_BDW_RT5677_MACH", "SND_SOC_SOF_BROADWELL"]
380 | case "byt":
381 | module_configs += ["SND_SOC_INTEL_BYTCR_RT5640_MACH", "SND_SOC_SOF_BAYTRAIL"]
382 | case "bsw":
383 | module_configs += ["SND_SOC_INTEL_CHT_BSW_RT5645_MACH", "SND_SOC_INTEL_CHT_BSW_MAX98090_TI_MACH", "SND_SOC_SOF_BAYTRAIL"]
384 | case "skl" | "kbl" | "apl":
385 | module_configs += ["SND_SOC_INTEL_AVS", "SND_SOC_INTEL_AVS_MACH_DA7219", "SND_SOC_INTEL_AVS_MACH_DMIC", "SND_SOC_INTEL_AVS_MACH_HDAUDIO", "SND_SOC_INTEL_AVS_MACH_MAX98927", "SND_SOC_INTEL_AVS_MACH_MAX98357A", "SND_SOC_INTEL_AVS_MACH_MAX98373", "SND_SOC_INTEL_AVS_MACH_NAU8825", "SND_SOC_INTEL_AVS_MACH_RT5514", "SND_SOC_INTEL_AVS_MACH_RT5663", "SND_SOC_INTEL_AVS_MACH_SSM4567"]
386 | case "glk":
387 | module_configs += ["SND_SOC_SOF_GEMINILAKE", "SND_SOC_INTEL_SOF_CS42L42_MACH", "SND_SOC_INTEL_SOF_RT5682_MACH", "SND_SOC_INTEL_SOF_DA7219_MACH"]
388 | case "cml":
389 | module_configs += ["SND_SOC_SOF_COMETLAKE", "SND_SOC_INTEL_SOF_RT5682_MACH", "SND_SOC_INTEL_SOF_DA7219_MACH"]
390 | case "tgl":
391 | module_configs += ["SND_SOC_SOF_TIGERLAKE", "SND_SOC_INTEL_SOF_RT5682_MACH"]
392 | case "jsl":
393 | module_configs += ["SND_SOC_SOF_ICELAKE", "SND_SOC_INTEL_SOF_RT5682_MACH", "SND_SOC_INTEL_SOF_DA7219_MACH", "SND_SOC_INTEL_SOF_CS42L42_MACH"]
394 | case "adl":
395 | module_configs += ["SND_SOC_SOF_ALDERLAKE", "SND_SOC_INTEL_SOF_CS42L42_MACH", "SND_SOC_INTEL_SOF_DA7219_MACH", "SND_SOC_INTEL_SOF_RT5682_MACH", "SND_SOC_INTEL_SOF_NAU8825_MACH", "SND_SOC_INTEL_SOF_SSP_AMP_MACH"]
396 | case "mtl":
397 | module_configs += [""] # TODO: fill this out
398 | case "st":
399 | module_configs += ["SND_SOC_AMD_ACP", "SND_SOC_AMD_CZ_DA7219MX98357_MACH"]
400 | case "pco":
401 | module_configs += ["SND_SOC_AMD_ACP3x", "SND_SOC_AMD_RV_RT5682_MACH"]
402 | case "czn":
403 | module_configs += [""] # TODO: fill this out
404 | case "mdn":
405 | module_configs += ["SND_SOC_SOF_AMD_REMBRANDT", "SND_AMD_ASOC_REMBRANDT"]
406 |
407 | for codec in get_codecs():
408 | match codec:
409 | case "max98357a" | "max98360a":
410 | module_configs.append("SND_SOC_MAX98357A")
411 | case "max98373":
412 | module_configs.append("SND_SOC_MAX98373")
413 | case "max98927":
414 | module_configs.append("SND_SOC_MAX98927")
415 | case "max98390":
416 | module_configs.append("SND_SOC_MAX98390")
417 | case "rt1011":
418 | module_configs.append("SND_SOC_RT1011")
419 | case "rt1015":
420 | module_configs.append("SND_SOC_RT1015")
421 | case "rt1015p" | "rt1019p":
422 | module_configs.append("SND_SOC_RT1015P")
423 | case "rt1019":
424 | module_configs.append("SND_SOC_RT1019")
425 | case "rt5682":
426 | module_configs.append("SND_SOC_RT5682")
427 | case "rt5682s":
428 | module_configs.append("SND_SOC_RT5682S")
429 | case "rt5663":
430 | module_configs.append("SND_SOC_RT5663")
431 | case "cs42l42":
432 | module_configs.append("SND_SOC_CS42L42")
433 | case "da7219":
434 | module_configs.append("SND_SOC_DA7219")
435 | case "nau8825":
436 | module_configs.append("SND_SOC_NAU8825")
437 | case "max98090":
438 | module_configs.append("SND_SOC_MAX98090")
439 | case "rt5650":
440 | module_configs.append("SND_SOC_RT5645")
441 | case "rt5677":
442 | module_configs.append("SND_SOC_RT5677")
443 | case "rt5514":
444 | module_configs.append("SND_SOC_RT5514")
445 | case "CrosEC audio codec":
446 | module_configs.append("SND_SOC_CROS_EC_CODEC")
447 | failed = 0
448 | for module in module_configs:
449 | if not f"{module}=" in config:
450 | failed = 1
451 | print_error(f"Warning: Kernel is missing module '{module}', audio may not work.")
452 | if not failed:
453 | print_status("Kernel config check passed")
454 |
455 | def get_codecs():
456 | # Get a list of codecs/amps via sysfs
457 | print_header("Detecting codecs")
458 | codec_table = {
459 | # Speaker amps
460 | "MX98357A": "max98357a",
461 | "MX98360A": "max98360a",
462 | "MX98373": "max98373",
463 | "MX98927": "max98927",
464 | "MX98390": "max98390",
465 | "10EC1011": "rt1011",
466 | "10EC1015": "rt1015",
467 | "RTL1015": "rt1015p",
468 | "10EC1019": "rt1019",
469 | "RTL1019": "rt1019p",
470 | "103C8C08": "cs35l53",
471 | # Headphone codecs
472 | "10EC5682": "rt5682",
473 | "RTL5682": "rt5682s",
474 | "10EC5663": "rt5663",
475 | "10134242": "cs42l42",
476 | "DLGS7219": "da7219",
477 | "10158825": "nau8825",
478 | # Speaker/Headphone combo codecs
479 | "193C9890": "max98090",
480 | "10EC5650": "rt5650",
481 | "RT5677CE": "rt5677",
482 | # Mic codecs
483 | "10EC5514": "rt5514",
484 | "GOOG0013": "CrosEC audio codec"
485 | }
486 |
487 | codecs = []
488 |
489 | for codec in codec_table:
490 | if path_exists(f"/sys/bus/acpi/devices/{codec}:00"):
491 | print_status(f"Found {codec_table[codec]}")
492 | codecs.append(codec_table[codec])
493 |
494 | return codecs
495 |
496 | def install_ucm(branch):
497 | print_header("Installing UCM configuration")
498 | try:
499 | bash("rm -rf /tmp/alsa-ucm-conf-cros")
500 | bash(f"git clone https://github.com/WeirdTreeThing/alsa-ucm-conf-cros -b {branch} /tmp/alsa-ucm-conf-cros")
501 | except:
502 | print_error("Error: Failed to clone UCM repo")
503 | exit(1)
504 |
505 | cpdir("/tmp/alsa-ucm-conf-cros/ucm2", "/usr/share/alsa/ucm2/")
506 | cpdir("/tmp/alsa-ucm-conf-cros/overrides", "/usr/share/alsa/ucm2/conf.d")
507 |
--------------------------------------------------------------------------------
/setup-audio:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import argparse
4 | import os
5 | import sys
6 | from functions import *
7 |
8 | # parse arguments from the cli. Only for testing/advanced use.
9 | def process_args():
10 | parser = argparse.ArgumentParser()
11 | parser.add_argument("-b", dest="board_name", type=str, nargs=1, default=[""],
12 | help="Override board name.")
13 | parser.add_argument("--enable-debug", action='store_const', const="Enabling", dest="debug",
14 | help="Enable audio debugging.")
15 | parser.add_argument("--disable-debug", action='store_const', const="Disabling", dest="debug",
16 | help="Disable audio debugging.")
17 | parser.add_argument("--force-avs-install", action="store_true", dest="force_avs_install", default=False,
18 | help="DANGEROUS: Force enable AVS install. MIGHT CAUSE PERMANENT DAMAGE TO SPEAKERS!")
19 | parser.add_argument("--branch", dest="branch_name", type=str, nargs=1, default=["standalone"],
20 | help="Use a different branch when cloning ucm. FOR DEVS AND TESTERS ONLY!")
21 | return parser.parse_args()
22 |
23 | if __name__ == "__main__":
24 | check_arch()
25 | args = process_args()
26 |
27 | # Restart script as root
28 | if os.geteuid() != 0:
29 | # make the two people that use doas happy
30 | if path_exists("/usr/bin/doas"):
31 | doas_args = ['doas', sys.executable] + sys.argv + [os.environ]
32 | os.execlpe('doas', *doas_args)
33 | # other 99 percent of linux users
34 | sudo_args = ['sudo', sys.executable] + sys.argv + [os.environ]
35 | os.execlpe('sudo', *sudo_args)
36 |
37 | # Some distros (Solus) don't have /etc/modprobe.d/ for some reason
38 | mkdir("/etc/modprobe.d", create_parents=True)
39 |
40 | # Platform specific configuration
41 | platform = get_platform()
42 | platform_config(platform, args)
43 |
44 | # Install downstream UCM configuration
45 | install_ucm(args.branch_name[0])
46 |
47 | # Check currently running kernel for all required modules
48 | check_kernel_config(platform)
49 |
50 | # Install wireplumber config to increase headroom
51 | # fixes instability and crashes on various devices
52 | if path_exists("/usr/bin/wireplumber"):
53 | print_header("Increasing alsa headroom (fixes instability)")
54 | mkdir("/etc/wireplumber/wireplumber.conf.d/", create_parents=True)
55 | cpfile("conf/common/51-increase-headroom.conf", "/etc/wireplumber/wireplumber.conf.d/51-increase-headroom.conf")
56 |
57 | print_status("Audio setup finished! Reboot to complete setup.")
58 | print_status("If you still have any issues post-reboot, report them to https://github.com/WeirdTreeThing/chromebook-linux-audio")
59 | print_status("If this script has been helpful for you and you would like to support the work I do, consider donating to https://paypal.me/weirdtreething")
60 |
--------------------------------------------------------------------------------