// SPDX-License-Identifier: GPL-2.0-only // // AW88399 HDA side codec driver // // Based on cs35l41_hda.c and aw88399.c // #include #include #include #include #include #include #include #include #include "../generic.h" #include "hda_component.h" #include "aw88399_hda.h" #define AW88399_HDA_I2C_BASE_ADDR 0x34 static void aw88399_hda_playback_hook(struct device *dev, int action) { struct aw88399_hda *aw88399 = dev_get_drvdata(dev); struct aw88399 *core = aw88399->core; int ret = 0; dev_dbg(aw88399->dev, "Playback action: %d\n", action); switch (action) { case HDA_GEN_PCM_ACT_OPEN: pm_runtime_get_sync(dev); aw88399->playing = true; break; case HDA_GEN_PCM_ACT_PREPARE: if (core) aw88399_start(core, AW88399_SYNC_START); break; case HDA_GEN_PCM_ACT_CLEANUP: if (aw88399->aw_dev) ret = aw88399_stop(aw88399->aw_dev); if (ret) dev_err(aw88399->dev, "Failed to stop amplifier: %d\n", ret); break; case HDA_GEN_PCM_ACT_CLOSE: if (aw88399->aw_dev) aw88399_stop(aw88399->aw_dev); aw88399->playing = false; pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); break; default: dev_warn(aw88399->dev, "Unsupported action: %d\n", action); break; } } static int aw88399_hda_bind(struct device *dev, struct device *master, void *master_data) { struct aw88399_hda *aw88399 = dev_get_drvdata(dev); struct hda_component_parent *parent = master_data; struct hda_component *comp; comp = hda_component_from_index(parent, aw88399->index); if (!comp) return -EINVAL; if (comp->dev) return -EBUSY; comp->dev = dev; strscpy(comp->name, dev_name(dev), sizeof(comp->name)); comp->playback_hook = aw88399_hda_playback_hook; dev_info(aw88399->dev, "AW88399 Bound - SSID: %s, channel: %d\n", aw88399->acpi_subsystem_id, aw88399->channel); return 0; } static void aw88399_hda_unbind(struct device *dev, struct device *master, void *master_data) { struct aw88399_hda *aw88399 = dev_get_drvdata(dev); struct hda_component_parent *parent = master_data; struct hda_component *comp; comp = hda_component_from_index(parent, aw88399->index); if (comp && (comp->dev == dev)) memset(comp, 0, sizeof(*comp)); dev_dbg(aw88399->dev, "Unbound from HDA codec\n"); } static const struct component_ops aw88399_hda_comp_ops = { .bind = aw88399_hda_bind, .unbind = aw88399_hda_unbind, }; static int aw88399_hda_index_from_i2c(struct aw88399_hda *aw88399) { return to_i2c_client(aw88399->dev)->addr - AW88399_HDA_I2C_BASE_ADDR; } static int aw88399_hda_init(struct aw88399_hda *aw88399) { struct device *dev = aw88399->dev; struct i2c_client *i2c = to_i2c_client(dev); struct aw88399 *core; int ret; core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL); if (!core) return -ENOMEM; mutex_init(&core->lock); core->reset_gpio = aw88399->reset_gpio; core->regmap = aw88399->regmap; core->bsts_unreliable = aw88399->bsts_unreliable; aw88399_hw_reset(core); ret = aw88399_init(core, i2c, aw88399->regmap); if (ret) return ret; /* Set channel BEFORE loading firmware so ACF parser sees correct value */ if (core->aw_pa) aw88399_dev_set_channel(core, aw88399->channel); ret = aw88399_request_firmware_file(core); if (ret) return ret; aw88399->core = core; aw88399->aw_dev = core->aw_pa; return 0; } static int aw88399_swap_channels(struct aw88399_hda *aw88399) { /* * Certain Lenovo Legion laptops have their * I2C wiring reversed: 0x34 is physically the right speaker, * 0x35 is the left. Swap channels to correct L/R assignment. * This is a model-specific hardware wiring issue, not a driver bug. */ aw88399->channel = 1 - aw88399->channel; dev_dbg(aw88399->dev, "Channel swap applied: index %d -> channel %d\n", aw88399->index, aw88399->channel); return 0; } static int aw88399_skip_bsts_check(struct aw88399_hda *aw88399) { /* * BSTS (boost-finished) status bit does not reliably report on * some hardware. On certain Lenovo Legion laptops, both amps * report BSTS=0 (boost not finished) during normal playback * despite clean audio output. Skip BSTS in the startup status * check to avoid false init failures. */ aw88399->bsts_unreliable = true; dev_dbg(aw88399->dev, "BSTS status check disabled\n"); return 0; } static int aw88399_apply_legion_quirks(struct aw88399_hda *aw88399) { aw88399_swap_channels(aw88399); aw88399_skip_bsts_check(aw88399); return 0; } struct aw88399_prop_model { const char *ssid; int (*apply_prop)(struct aw88399_hda *aw88399); }; static const struct aw88399_prop_model aw88399_prop_model_table[] = { { "17AA3906", aw88399_apply_legion_quirks }, { "17AA3907", aw88399_apply_legion_quirks }, { "17AA3927", aw88399_apply_legion_quirks }, { "17AA3928", aw88399_apply_legion_quirks }, { "17AA3936", aw88399_apply_legion_quirks }, { "17AA3937", aw88399_apply_legion_quirks }, { "17AA3938", aw88399_apply_legion_quirks }, { "17AA3939", aw88399_apply_legion_quirks }, { } }; static int aw88399_hda_acpi_probe(struct aw88399_hda *aw88399) { struct acpi_device *adev; const char *sub; const struct aw88399_prop_model *model; aw88399->index = aw88399_hda_index_from_i2c(aw88399); aw88399->channel = aw88399->index; aw88399->acpi_subsystem_id = NULL; adev = acpi_dev_get_first_match_dev("AWDZ8399", NULL, -1); if (!adev) { dev_err(aw88399->dev, "Failed to find an ACPI device for AWDZ8399\n"); return -ENODEV; } struct device *physdev __free(put_device) = get_device(acpi_get_first_physical_node(adev)); acpi_dev_put(adev); if (!physdev) return -ENODEV; sub = acpi_get_subsystem_id(ACPI_HANDLE(physdev)); if (IS_ERR_OR_NULL(sub)) return 0; aw88399->acpi_subsystem_id = devm_kstrdup(aw88399->dev, sub, GFP_KERNEL); kfree(sub); if (!aw88399->acpi_subsystem_id) return -ENOMEM; for (model = aw88399_prop_model_table; model->ssid; model++) { if (!strcasecmp(model->ssid, aw88399->acpi_subsystem_id)) { dev_info(aw88399->dev, "Applying properties for SSID %s\n", aw88399->acpi_subsystem_id); return model->apply_prop(aw88399); } } return 0; } int aw88399_hda_probe(struct device *dev, struct regmap *regmap) { struct aw88399_hda *aw88399; int ret; aw88399 = devm_kzalloc(dev, sizeof(*aw88399), GFP_KERNEL); if (!aw88399) return -ENOMEM; if (IS_ERR(regmap)) return dev_err_probe(dev, PTR_ERR(regmap), "Failed to obtain regmap\n"); aw88399->dev = dev; aw88399->regmap = regmap; dev_set_drvdata(dev, aw88399); aw88399->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(aw88399->reset_gpio)) return dev_err_probe(dev, PTR_ERR(aw88399->reset_gpio), "Failed to get reset GPIO\n"); ret = aw88399_hda_acpi_probe(aw88399); if (ret) return dev_err_probe(dev, ret, "ACPI probe failed\n"); ret = aw88399_hda_init(aw88399); if (ret) return dev_err_probe(dev, ret, "Chip initialization failed\n"); /* Enable runtime PM */ pm_runtime_set_autosuspend_delay(dev, 3000); pm_runtime_use_autosuspend(dev); pm_runtime_mark_last_busy(dev); pm_runtime_set_active(dev); pm_runtime_enable(dev); ret = component_add(dev, &aw88399_hda_comp_ops); if (ret) { pm_runtime_disable(dev); return dev_err_probe(dev, ret, "Failed to register component\n"); } dev_info(dev, "AW88399 HDA side codec registered successfully\n"); return 0; } EXPORT_SYMBOL_NS_GPL(aw88399_hda_probe, "SND_HDA_SCODEC_AW88399"); void aw88399_hda_remove(struct device *dev) { struct aw88399_hda *aw88399 = dev_get_drvdata(dev); pm_runtime_disable(dev); if (aw88399->aw_dev) aw88399_stop(aw88399->aw_dev); component_del(dev, &aw88399_hda_comp_ops); dev_dbg(aw88399->dev, "AW88399 HDA side codec removed\n"); } EXPORT_SYMBOL_NS_GPL(aw88399_hda_remove, "SND_HDA_SCODEC_AW88399"); static int aw88399_hda_runtime_suspend(struct device *dev) { struct aw88399_hda *aw88399 = dev_get_drvdata(dev); dev_dbg(aw88399->dev, "Runtime suspend\n"); if (aw88399->aw_dev && aw88399->playing) aw88399_stop(aw88399->aw_dev); return 0; } static int aw88399_hda_runtime_resume(struct device *dev) { struct aw88399_hda *aw88399 = dev_get_drvdata(dev); dev_dbg(aw88399->dev, "Runtime resume\n"); if (aw88399->core && aw88399->aw_dev && aw88399->playing) aw88399_start(aw88399->core, AW88399_SYNC_START); return 0; } static int aw88399_hda_system_suspend(struct device *dev) { struct aw88399_hda *aw88399 = dev_get_drvdata(dev); int ret; dev_dbg(aw88399->dev, "System suspend\n"); if (aw88399->aw_dev && aw88399->playing) aw88399_stop(aw88399->aw_dev); if (aw88399->core) aw88399->core->fw_needs_reload = true; ret = pm_runtime_force_suspend(dev); if (ret) dev_err(aw88399->dev, "Runtime force suspend failed: %d\n", ret); return ret; } static int aw88399_hda_system_resume(struct device *dev) { struct aw88399_hda *aw88399 = dev_get_drvdata(dev); int ret; dev_dbg(aw88399->dev, "System resume\n"); if (aw88399->aw_dev) aw88399_hw_reset(aw88399->core); ret = pm_runtime_force_resume(dev); if (ret) dev_err(aw88399->dev, "Runtime force resume failed: %d\n", ret); return ret; } const struct dev_pm_ops aw88399_hda_pm_ops = { RUNTIME_PM_OPS(aw88399_hda_runtime_suspend, aw88399_hda_runtime_resume, NULL) SYSTEM_SLEEP_PM_OPS(aw88399_hda_system_suspend, aw88399_hda_system_resume) }; EXPORT_SYMBOL_NS_GPL(aw88399_hda_pm_ops, "SND_HDA_SCODEC_AW88399"); MODULE_DESCRIPTION("AW88399 HDA driver"); MODULE_AUTHOR("Yakov Till "); MODULE_AUTHOR("Marco Giunta "); MODULE_LICENSE("GPL"); MODULE_FIRMWARE("aw88399_acf.bin");