├── .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 | --------------------------------------------------------------------------------