From nobody Sun Dec 22 06:12:20 2024 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 60FDEC77B7C for ; Fri, 26 May 2023 12:29:22 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S243449AbjEZM3U (ORCPT ); Fri, 26 May 2023 08:29:20 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:59174 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230292AbjEZM3Q (ORCPT ); Fri, 26 May 2023 08:29:16 -0400 Received: from mx0b-001ae601.pphosted.com (mx0a-001ae601.pphosted.com [67.231.149.25]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 92AEA116 for ; Fri, 26 May 2023 05:29:12 -0700 (PDT) Received: from pps.filterd (m0077473.ppops.net [127.0.0.1]) by mx0a-001ae601.pphosted.com (8.17.1.19/8.17.1.19) with ESMTP id 34QC7FH0007721; Fri, 26 May 2023 07:29:01 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cirrus.com; h=from : to : cc : subject : date : message-id : in-reply-to : references : mime-version : content-transfer-encoding : content-type; s=PODMain02222019; bh=uPWmiyWDa6rCdPxjKyZvDj5RXjnFYAxPTgL3bLh3NtU=; b=RKUUd9G+IDojpu9FTp/Gb4HcDZVInjwu4TrMiq6ODQCCImVi/hNZQ7XHOJOzGvaASFTa pH2LsGYd2Ae8qu3YUyz/9zIYIk5PU5n9XmhlPAY9MU81czCZslj6UeaHVIvOAOP3jVci k4WHpZPlpUdpBizaopt0RSOsdgo2TBahO3E1sBrxvKdh5ptoOVMKMdl5Llsu3aVjRmZu B1/uIcZ8BdhJdqkWguGttUv4y0dQp+0lkcYgpGrg9yCcfwWNCGleHUYNaLGuPnZIj+Z7 XOVsLKlSDW95sb9tKh2PSKeFi9czRmnl1sjI+g2ouHUTNleSIWlNxY1niz04sN6rqfcD Ng== Received: from ediex01.ad.cirrus.com ([84.19.233.68]) by mx0a-001ae601.pphosted.com (PPS) with ESMTPS id 3qsde8k66v-6 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Fri, 26 May 2023 07:29:01 -0500 Received: from ediex02.ad.cirrus.com (198.61.84.81) by ediex01.ad.cirrus.com (198.61.84.80) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1118.26; Fri, 26 May 2023 07:28:59 -0500 Received: from ediswmail.ad.cirrus.com (198.61.86.93) by anon-ediex02.ad.cirrus.com (198.61.84.81) with Microsoft SMTP Server id 15.2.1118.26 via Frontend Transport; Fri, 26 May 2023 07:28:59 -0500 Received: from EDIN4L06LR3.ad.cirrus.com (EDIN4L06LR3.ad.cirrus.com [198.61.65.166]) by ediswmail.ad.cirrus.com (Postfix) with ESMTP id 2FC0B11A8; Fri, 26 May 2023 12:28:59 +0000 (UTC) From: Richard Fitzgerald To: , , CC: , , , Simon Trimmer , Richard Fitzgerald Subject: [PATCH v3 12/12] ALSA: hda/cs35l56: Add driver for Cirrus Logic CS35L56 amplifier Date: Fri, 26 May 2023 13:28:52 +0100 Message-ID: <20230526122852.4552-13-rf@opensource.cirrus.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20230526122852.4552-1-rf@opensource.cirrus.com> References: <20230526122852.4552-1-rf@opensource.cirrus.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Proofpoint-ORIG-GUID: v-AmDbrPwaC90u6gsiUXKa8W2g5hTk34 X-Proofpoint-GUID: v-AmDbrPwaC90u6gsiUXKa8W2g5hTk34 X-Proofpoint-Spam-Reason: safe Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" From: Simon Trimmer Add a driver for the Cirrus Logic CS35L56 amplifier. This uses the same component binding API as the CS35L41 driver. This is not a standalone HDA device; it provides control of the CS35L56 for systems that use a combination of an HDA codec and CS35L56 amplifiers with audio routed through the HDA codec. The CS35L56 combines a high-performance mono audio amplifier, Class-H tracking inductive boost converter, Halo Core(TM) DSP and a DC-DC boost converter supporting Class-H tracking. Control interfaces are I2C or SPI through the standard Linux I2C or SPI bus framework. Most chip functionality is controlled by on-board ROM firmware that is always running. Firmware patches can be applied by the driver in the form of a .wmfw file (firmware patch) and/or a .bin file (system tuning). Signed-off-by: Simon Trimmer Signed-off-by: Richard Fitzgerald --- sound/pci/hda/Kconfig | 31 + sound/pci/hda/Makefile | 6 + sound/pci/hda/cs35l56_hda.c | 990 ++++++++++++++++++++++++++++++++ sound/pci/hda/cs35l56_hda.h | 48 ++ sound/pci/hda/cs35l56_hda_i2c.c | 69 +++ sound/pci/hda/cs35l56_hda_spi.c | 68 +++ 6 files changed, 1212 insertions(+) create mode 100644 sound/pci/hda/cs35l56_hda.c create mode 100644 sound/pci/hda/cs35l56_hda.h create mode 100644 sound/pci/hda/cs35l56_hda_i2c.c create mode 100644 sound/pci/hda/cs35l56_hda_spi.c diff --git a/sound/pci/hda/Kconfig b/sound/pci/hda/Kconfig index 886255a03e8b..0f2e941ce646 100644 --- a/sound/pci/hda/Kconfig +++ b/sound/pci/hda/Kconfig @@ -130,6 +130,37 @@ config SND_HDA_SCODEC_CS35L41_SPI comment "Set to Y if you want auto-loading the side codec driver" depends on SND_HDA=3Dy && SND_HDA_SCODEC_CS35L41_SPI=3Dm =20 +config SND_HDA_SCODEC_CS35L56 + tristate + +config SND_HDA_SCODEC_CS35L56_I2C + tristate "Build CS35L56 HD-audio side codec support for I2C Bus" + depends on I2C + depends on ACPI || COMPILE_TEST + depends on SND_SOC + select CS_DSP + select SND_HDA_GENERIC + select SND_SOC_CS35L56_SHARED + select SND_HDA_SCODEC_CS35L56 + select SND_HDA_CS_DSP_CONTROLS + help + Say Y or M here to include CS35L56 amplifier support with + I2C control. + +config SND_HDA_SCODEC_CS35L56_SPI + tristate "Build CS35L56 HD-audio codec support for SPI Bus" + depends on SPI_MASTER + depends on ACPI || COMPILE_TEST + depends on SND_SOC + select CS_DSP + select SND_HDA_GENERIC + select SND_SOC_CS35L56_SHARED + select SND_HDA_SCODEC_CS35L56 + select SND_HDA_CS_DSP_CONTROLS + help + Say Y or M here to include CS35L56 amplifier support with + SPI control. + config SND_HDA_CODEC_REALTEK tristate "Build Realtek HD-audio codec support" select SND_HDA_GENERIC diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile index 00d306104484..c6e6509e7b8e 100644 --- a/sound/pci/hda/Makefile +++ b/sound/pci/hda/Makefile @@ -31,6 +31,9 @@ snd-hda-codec-hdmi-objs :=3D patch_hdmi.o hda_eld.o snd-hda-scodec-cs35l41-objs :=3D cs35l41_hda.o snd-hda-scodec-cs35l41-i2c-objs :=3D cs35l41_hda_i2c.o snd-hda-scodec-cs35l41-spi-objs :=3D cs35l41_hda_spi.o +snd-hda-scodec-cs35l56-objs :=3D cs35l56_hda.o +snd-hda-scodec-cs35l56-i2c-objs :=3D cs35l56_hda_i2c.o +snd-hda-scodec-cs35l56-spi-objs :=3D cs35l56_hda_spi.o snd-hda-cs-dsp-ctls-objs :=3D hda_cs_dsp_ctl.o =20 # common driver @@ -55,6 +58,9 @@ obj-$(CONFIG_SND_HDA_CODEC_HDMI) +=3D snd-hda-codec-hdmi.o obj-$(CONFIG_SND_HDA_SCODEC_CS35L41) +=3D snd-hda-scodec-cs35l41.o obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_I2C) +=3D snd-hda-scodec-cs35l41-i2c.o obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_SPI) +=3D snd-hda-scodec-cs35l41-spi.o +obj-$(CONFIG_SND_HDA_SCODEC_CS35L56) +=3D snd-hda-scodec-cs35l56.o +obj-$(CONFIG_SND_HDA_SCODEC_CS35L56_I2C) +=3D snd-hda-scodec-cs35l56-i2c.o +obj-$(CONFIG_SND_HDA_SCODEC_CS35L56_SPI) +=3D snd-hda-scodec-cs35l56-spi.o obj-$(CONFIG_SND_HDA_CS_DSP_CONTROLS) +=3D snd-hda-cs-dsp-ctls.o =20 # this must be the last entry after codec drivers; diff --git a/sound/pci/hda/cs35l56_hda.c b/sound/pci/hda/cs35l56_hda.c new file mode 100644 index 000000000000..96f8a30e5745 --- /dev/null +++ b/sound/pci/hda/cs35l56_hda.c @@ -0,0 +1,990 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// HDA audio driver for Cirrus Logic CS35L56 smart amp +// +// Copyright (C) 2023 Cirrus Logic, Inc. and +// Cirrus Logic International Semiconductor Ltd. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cs35l56_hda.h" +#include "hda_component.h" +#include "hda_cs_dsp_ctl.h" +#include "hda_generic.h" + + /* + * The cs35l56_hda_dai_config[] reg sequence configures the device as + * ASP1_BCLK_FREQ =3D 3.072 MHz + * ASP1_RX_WIDTH =3D 32 cycles per slot, ASP1_TX_WIDTH =3D 32 cycles per= slot, ASP1_FMT =3D I2S + * ASP1_DOUT_HIZ_CONTROL =3D Hi-Z during unused timeslots + * ASP1_RX_WL =3D 24 bits per sample + * ASP1_TX_WL =3D 24 bits per sample + * ASP1_RXn_EN 1..3 and ASP1_TXn_EN 1..4 disabled + */ +static const struct reg_sequence cs35l56_hda_dai_config[] =3D { + { CS35L56_ASP1_CONTROL1, 0x00000021 }, + { CS35L56_ASP1_CONTROL2, 0x20200200 }, + { CS35L56_ASP1_CONTROL3, 0x00000003 }, + { CS35L56_ASP1_DATA_CONTROL5, 0x00000018 }, + { CS35L56_ASP1_DATA_CONTROL1, 0x00000018 }, + { CS35L56_ASP1_ENABLES1, 0x00000000 }, +}; + +static void cs35l56_hda_play(struct cs35l56_hda *cs35l56) +{ + unsigned int val; + int ret; + + pm_runtime_get_sync(cs35l56->base.dev); + ret =3D cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_PLAY); + if (ret =3D=3D 0) { + /* Wait for firmware to enter PS0 power state */ + ret =3D regmap_read_poll_timeout(cs35l56->base.regmap, + CS35L56_TRANSDUCER_ACTUAL_PS, + val, (val =3D=3D CS35L56_PS0), + CS35L56_PS0_POLL_US, + CS35L56_PS0_TIMEOUT_US); + if (ret) + dev_warn(cs35l56->base.dev, "PS0 wait failed: %d\n", ret); + } + regmap_set_bits(cs35l56->base.regmap, CS35L56_ASP1_ENABLES1, + BIT(CS35L56_ASP_RX1_EN_SHIFT) | BIT(CS35L56_ASP_RX2_EN_SHIFT) | + cs35l56->asp_tx_mask); + cs35l56->playing =3D true; +} + +static void cs35l56_hda_pause(struct cs35l56_hda *cs35l56) +{ + cs35l56->playing =3D false; + cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_PAUSE); + regmap_clear_bits(cs35l56->base.regmap, CS35L56_ASP1_ENABLES1, + BIT(CS35L56_ASP_RX1_EN_SHIFT) | BIT(CS35L56_ASP_RX2_EN_SHIFT) | + BIT(CS35L56_ASP_TX1_EN_SHIFT) | BIT(CS35L56_ASP_TX2_EN_SHIFT) | + BIT(CS35L56_ASP_TX3_EN_SHIFT) | BIT(CS35L56_ASP_TX4_EN_SHIFT)); + + pm_runtime_mark_last_busy(cs35l56->base.dev); + pm_runtime_put_autosuspend(cs35l56->base.dev); +} + +static void cs35l56_hda_playback_hook(struct device *dev, int action) +{ + struct cs35l56_hda *cs35l56 =3D dev_get_drvdata(dev); + + dev_dbg(cs35l56->base.dev, "%s()%d: action: %d\n", __func__, __LINE__, ac= tion); + + switch (action) { + case HDA_GEN_PCM_ACT_PREPARE: + if (cs35l56->playing) + break; + + /* If we're suspended: flag that resume should start playback */ + if (cs35l56->suspended) { + cs35l56->playing =3D true; + break; + } + + cs35l56_hda_play(cs35l56); + break; + case HDA_GEN_PCM_ACT_CLEANUP: + if (!cs35l56->playing) + break; + + cs35l56_hda_pause(cs35l56); + break; + default: + break; + } +} + +static void cs35l56_hda_mute_hook(struct device *dev, bool mute) +{ + struct cs35l56_hda *cs35l56 =3D dev_get_drvdata(dev); + unsigned int val; + + if (mute) + val =3D CS35L56_MAIN_RENDER_USER_MUTE_MASK; + else + val =3D 0; + + regmap_write(cs35l56->base.regmap, CS35L56_MAIN_RENDER_USER_MUTE, val); +} + +static int __maybe_unused cs35l56_hda_runtime_suspend(struct device *dev) +{ + struct cs35l56_hda *cs35l56 =3D dev_get_drvdata(dev); + + if (cs35l56->cs_dsp.booted) + cs_dsp_stop(&cs35l56->cs_dsp); + + return cs35l56_runtime_suspend_common(&cs35l56->base); +} + +static int __maybe_unused cs35l56_hda_runtime_resume(struct device *dev) +{ + struct cs35l56_hda *cs35l56 =3D dev_get_drvdata(dev); + int ret; + + ret =3D cs35l56_runtime_resume_common(&cs35l56->base, false); + if (ret < 0) + return ret; + + if (cs35l56->cs_dsp.booted) { + ret =3D cs_dsp_run(&cs35l56->cs_dsp); + if (ret) { + dev_dbg(cs35l56->base.dev, "%s: cs_dsp_run ret %d\n", __func__, ret); + goto err; + } + } + + return 0; + +err: + cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_ALLOW_AUTO_HIBERNATE); + regmap_write(cs35l56->base.regmap, CS35L56_DSP_VIRTUAL1_MBOX_1, + CS35L56_MBOX_CMD_HIBERNATE_NOW); + + regcache_cache_only(cs35l56->base.regmap, true); + + return ret; +} + +static int cs35l56_hda_mixer_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type =3D SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count =3D 1; + uinfo->value.enumerated.items =3D CS35L56_NUM_INPUT_SRC; + if (uinfo->value.enumerated.item >=3D CS35L56_NUM_INPUT_SRC) + uinfo->value.enumerated.item =3D CS35L56_NUM_INPUT_SRC - 1; + strcpy(uinfo->value.enumerated.name, cs35l56_tx_input_texts[uinfo->value.= enumerated.item]); + + return 0; +} + +static int cs35l56_hda_mixer_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cs35l56_hda *cs35l56 =3D (struct cs35l56_hda *)kcontrol->private_d= ata; + unsigned int reg_val; + int i; + + regmap_read(cs35l56->base.regmap, kcontrol->private_value, ®_val); + reg_val &=3D CS35L56_ASP_TXn_SRC_MASK; + + for (i =3D 0; i < CS35L56_NUM_INPUT_SRC; ++i) { + if (cs35l56_tx_input_values[i] =3D=3D reg_val) { + ucontrol->value.enumerated.item[0] =3D i; + break; + } + } + + return 0; +} + +static int cs35l56_hda_mixer_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cs35l56_hda *cs35l56 =3D (struct cs35l56_hda *)kcontrol->private_d= ata; + unsigned int item =3D ucontrol->value.enumerated.item[0]; + bool changed; + + if (item >=3D CS35L56_NUM_INPUT_SRC) + return -EINVAL; + + regmap_update_bits_check(cs35l56->base.regmap, kcontrol->private_value, + CS35L56_INPUT_MASK, cs35l56_tx_input_values[item], + &changed); + + return changed; +} + +static int cs35l56_hda_posture_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type =3D SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count =3D 1; + uinfo->value.integer.min =3D CS35L56_MAIN_POSTURE_MIN; + uinfo->value.integer.max =3D CS35L56_MAIN_POSTURE_MAX; + return 0; +} + +static int cs35l56_hda_posture_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cs35l56_hda *cs35l56 =3D (struct cs35l56_hda *)kcontrol->private_d= ata; + unsigned int pos; + int ret; + + ret =3D regmap_read(cs35l56->base.regmap, CS35L56_MAIN_POSTURE_NUMBER, &p= os); + if (ret) + return ret; + + ucontrol->value.integer.value[0] =3D pos; + + return ret; +} + +static int cs35l56_hda_posture_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cs35l56_hda *cs35l56 =3D (struct cs35l56_hda *)kcontrol->private_d= ata; + unsigned long pos =3D ucontrol->value.integer.value[0]; + bool changed; + int ret; + + if ((pos < CS35L56_MAIN_POSTURE_MIN) || + (pos > CS35L56_MAIN_POSTURE_MAX)) + return -EINVAL; + + ret =3D regmap_update_bits_check(cs35l56->base.regmap, + CS35L56_MAIN_POSTURE_NUMBER, + CS35L56_MAIN_POSTURE_MASK, + pos, &changed); + if (ret) + return ret; + + return changed; +} + +static const struct { + const char *name; + unsigned int reg; +} cs35l56_hda_mixer_controls[] =3D { + { "ASP1 TX1 Source", CS35L56_ASP1TX1_INPUT }, + { "ASP1 TX2 Source", CS35L56_ASP1TX2_INPUT }, + { "ASP1 TX3 Source", CS35L56_ASP1TX3_INPUT }, + { "ASP1 TX4 Source", CS35L56_ASP1TX4_INPUT }, +}; + +static const DECLARE_TLV_DB_SCALE(cs35l56_hda_vol_tlv, -10000, 25, 0); + +static int cs35l56_hda_vol_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type =3D SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count =3D 1; + uinfo->value.integer.step =3D 1; + uinfo->value.integer.min =3D 0; + uinfo->value.integer.max =3D CS35L56_MAIN_RENDER_USER_VOLUME_MAX - + CS35L56_MAIN_RENDER_USER_VOLUME_MIN; + + return 0; +} + +static int cs35l56_hda_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cs35l56_hda *cs35l56 =3D (struct cs35l56_hda *)kcontrol->private_d= ata; + unsigned int raw_vol; + int vol; + int ret; + + ret =3D regmap_read(cs35l56->base.regmap, CS35L56_MAIN_RENDER_USER_VOLUME= , &raw_vol); + + if (ret) + return ret; + + vol =3D (s16)(raw_vol & 0xFFFF); + vol >>=3D CS35L56_MAIN_RENDER_USER_VOLUME_SHIFT; + + if (vol & BIT(CS35L56_MAIN_RENDER_USER_VOLUME_SIGNBIT)) + vol |=3D ~((int)(BIT(CS35L56_MAIN_RENDER_USER_VOLUME_SIGNBIT) - 1)); + + ucontrol->value.integer.value[0] =3D vol - CS35L56_MAIN_RENDER_USER_VOLUM= E_MIN; + + return 0; +} + +static int cs35l56_hda_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cs35l56_hda *cs35l56 =3D (struct cs35l56_hda *)kcontrol->private_d= ata; + long vol =3D ucontrol->value.integer.value[0]; + unsigned int raw_vol; + bool changed; + int ret; + + if ((vol < 0) || (vol > (CS35L56_MAIN_RENDER_USER_VOLUME_MAX - + CS35L56_MAIN_RENDER_USER_VOLUME_MIN))) + return -EINVAL; + + raw_vol =3D (vol + CS35L56_MAIN_RENDER_USER_VOLUME_MIN) << + CS35L56_MAIN_RENDER_USER_VOLUME_SHIFT; + + ret =3D regmap_update_bits_check(cs35l56->base.regmap, + CS35L56_MAIN_RENDER_USER_VOLUME, + CS35L56_MAIN_RENDER_USER_VOLUME_MASK, + raw_vol, &changed); + if (ret) + return ret; + + return changed; +} + +static void cs35l56_hda_create_controls(struct cs35l56_hda *cs35l56) +{ + struct snd_kcontrol_new ctl_template =3D { + .iface =3D SNDRV_CTL_ELEM_IFACE_MIXER, + .access =3D SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info =3D cs35l56_hda_posture_info, + .get =3D cs35l56_hda_posture_get, + .put =3D cs35l56_hda_posture_put, + }; + char name[64]; + int i; + + snprintf(name, sizeof(name), "%s Posture Number", cs35l56->amp_name); + ctl_template.name =3D name; + cs35l56->posture_ctl =3D snd_ctl_new1(&ctl_template, cs35l56); + if (snd_ctl_add(cs35l56->codec->card, cs35l56->posture_ctl)) + dev_err(cs35l56->base.dev, "Failed to add KControl: %s\n", ctl_template.= name); + + /* Mixer controls */ + ctl_template.info =3D cs35l56_hda_mixer_info; + ctl_template.get =3D cs35l56_hda_mixer_get; + ctl_template.put =3D cs35l56_hda_mixer_put; + + BUILD_BUG_ON(ARRAY_SIZE(cs35l56->mixer_ctl) !=3D ARRAY_SIZE(cs35l56_hda_m= ixer_controls)); + + for (i =3D 0; i < ARRAY_SIZE(cs35l56_hda_mixer_controls); ++i) { + snprintf(name, sizeof(name), "%s %s", cs35l56->amp_name, + cs35l56_hda_mixer_controls[i].name); + ctl_template.private_value =3D cs35l56_hda_mixer_controls[i].reg; + cs35l56->mixer_ctl[i] =3D snd_ctl_new1(&ctl_template, cs35l56); + if (snd_ctl_add(cs35l56->codec->card, cs35l56->mixer_ctl[i])) { + dev_err(cs35l56->base.dev, "Failed to add KControl: %s\n", + ctl_template.name); + } + } + + ctl_template.info =3D cs35l56_hda_vol_info; + ctl_template.get =3D cs35l56_hda_vol_get; + ctl_template.put =3D cs35l56_hda_vol_put; + ctl_template.access =3D (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM= _ACCESS_TLV_READ); + ctl_template.tlv.p =3D cs35l56_hda_vol_tlv; + snprintf(name, sizeof(name), "%s Speaker Playback Volume", cs35l56->amp_n= ame); + ctl_template.name =3D name; + cs35l56->volume_ctl =3D snd_ctl_new1(&ctl_template, cs35l56); + if (snd_ctl_add(cs35l56->codec->card, cs35l56->volume_ctl)) + dev_err(cs35l56->base.dev, "Failed to add KControl: %s\n", ctl_template.= name); +} + +static void cs35l56_hda_remove_controls(struct cs35l56_hda *cs35l56) +{ + int i; + + for (i =3D ARRAY_SIZE(cs35l56->mixer_ctl) - 1; i >=3D 0; i--) + snd_ctl_remove(cs35l56->codec->card, cs35l56->mixer_ctl[i]); + + snd_ctl_remove(cs35l56->codec->card, cs35l56->posture_ctl); + snd_ctl_remove(cs35l56->codec->card, cs35l56->volume_ctl); +} + +static const struct cs_dsp_client_ops cs35l56_hda_client_ops =3D { + .control_remove =3D hda_cs_dsp_control_remove, +}; + +static int cs35l56_hda_request_firmware_file(struct cs35l56_hda *cs35l56, + const struct firmware **firmware, char **filename, + const char *dir, const char *system_name, + const char *amp_name, + const char *filetype) +{ + char *s, c; + int ret =3D 0; + + if (system_name && amp_name) + *filename =3D kasprintf(GFP_KERNEL, "%scs35l56%s-%02x-dsp1-misc-%s-%s.%s= ", dir, + cs35l56->base.secured ? "s" : "", cs35l56->base.rev, + system_name, amp_name, filetype); + else if (system_name) + *filename =3D kasprintf(GFP_KERNEL, "%scs35l56%s-%02x-dsp1-misc-%s.%s", = dir, + cs35l56->base.secured ? "s" : "", cs35l56->base.rev, + system_name, filetype); + else + *filename =3D kasprintf(GFP_KERNEL, "%scs35l56%s-%02x-dsp1-misc.%s", dir, + cs35l56->base.secured ? "s" : "", cs35l56->base.rev, + filetype); + + if (!*filename) + return -ENOMEM; + + /* + * Make sure that filename is lower-case and any non alpha-numeric + * characters except full stop and forward slash are replaced with + * hyphens. + */ + s =3D *filename; + while (*s) { + c =3D *s; + if (isalnum(c)) + *s =3D tolower(c); + else if (c !=3D '.' && c !=3D '/') + *s =3D '-'; + s++; + } + + ret =3D firmware_request_nowarn(firmware, *filename, cs35l56->base.dev); + if (ret) { + dev_dbg(cs35l56->base.dev, "Failed to request '%s'\n", *filename); + kfree(*filename); + *filename =3D NULL; + } else { + dev_dbg(cs35l56->base.dev, "Found '%s'\n", *filename); + } + + return ret; +} + +static const char cirrus_dir[] =3D "cirrus/"; +static int cs35l56_hda_request_firmware_files(struct cs35l56_hda *cs35l56, + const struct firmware **wmfw_firmware, + char **wmfw_filename, + const struct firmware **coeff_firmware, + char **coeff_filename) +{ + const char *system_name =3D cs35l56->system_name; + const char *amp_name =3D cs35l56->amp_name; + int ret; + + if (system_name && amp_name) { + if (!cs35l56_hda_request_firmware_file(cs35l56, wmfw_firmware, wmfw_file= name, + cirrus_dir, system_name, amp_name, "wmfw")) { + cs35l56_hda_request_firmware_file(cs35l56, coeff_firmware, coeff_filena= me, + cirrus_dir, system_name, amp_name, "bin"); + return 0; + } + } + + if (system_name) { + if (!cs35l56_hda_request_firmware_file(cs35l56, wmfw_firmware, wmfw_file= name, + cirrus_dir, system_name, NULL, "wmfw")) { + if (amp_name) + cs35l56_hda_request_firmware_file(cs35l56, + coeff_firmware, coeff_filename, + cirrus_dir, system_name, + amp_name, "bin"); + if (!*coeff_firmware) + cs35l56_hda_request_firmware_file(cs35l56, + coeff_firmware, coeff_filename, + cirrus_dir, system_name, + NULL, "bin"); + return 0; + } + } + + ret =3D cs35l56_hda_request_firmware_file(cs35l56, wmfw_firmware, wmfw_fi= lename, + cirrus_dir, NULL, NULL, "wmfw"); + if (!ret) { + cs35l56_hda_request_firmware_file(cs35l56, coeff_firmware, coeff_filenam= e, + cirrus_dir, NULL, NULL, "bin"); + return 0; + } + + /* When a firmware file is not found must still search for the coeff file= s */ + if (system_name) { + if (amp_name) + cs35l56_hda_request_firmware_file(cs35l56, coeff_firmware, coeff_filena= me, + cirrus_dir, system_name, amp_name, "bin"); + if (!*coeff_firmware) + cs35l56_hda_request_firmware_file(cs35l56, coeff_firmware, coeff_filena= me, + cirrus_dir, system_name, NULL, "bin"); + } + + if (!*coeff_firmware) + cs35l56_hda_request_firmware_file(cs35l56, coeff_firmware, coeff_filenam= e, + cirrus_dir, NULL, NULL, "bin"); + + return 0; +} + +static void cs35l56_hda_add_dsp_controls(struct cs35l56_hda *cs35l56) +{ + struct hda_cs_dsp_ctl_info info; + + info.device_name =3D cs35l56->amp_name; + info.fw_type =3D HDA_CS_DSP_FW_MISC; + info.card =3D cs35l56->codec->card; + + hda_cs_dsp_add_controls(&cs35l56->cs_dsp, &info); +} + +static int cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) +{ + const struct firmware *coeff_firmware =3D NULL; + const struct firmware *wmfw_firmware =3D NULL; + char *coeff_filename =3D NULL; + char *wmfw_filename =3D NULL; + int ret =3D 0; + + mutex_lock(&cs35l56->base.irq_lock); + pm_runtime_get_sync(cs35l56->base.dev); + + /* + * When the device is running in secure mode the firmware files can + * only contain insecure tunings and therefore we do not need to + * shutdown the firmware to apply them and can use the lower cost + * reinit sequence instead. + */ + if (!cs35l56->base.secured) { + ret =3D cs35l56_firmware_shutdown(&cs35l56->base); + if (ret) + goto err; + } + + cs35l56_hda_request_firmware_files(cs35l56, &wmfw_firmware, &wmfw_filenam= e, + &coeff_firmware, &coeff_filename); + + ret =3D cs_dsp_power_up(&cs35l56->cs_dsp, wmfw_firmware, wmfw_filename, + coeff_firmware, coeff_filename, "misc"); + if (ret) { + dev_dbg(cs35l56->base.dev, "%s: cs_dsp_power_up ret %d\n", __func__, ret= ); + goto err; + } + + if (wmfw_filename) + dev_dbg(cs35l56->base.dev, "Loaded WMFW Firmware: %s\n", wmfw_filename); + + if (coeff_filename) + dev_dbg(cs35l56->base.dev, "Loaded Coefficients: %s\n", coeff_filename); + + ret =3D cs_dsp_run(&cs35l56->cs_dsp); + if (ret) { + dev_dbg(cs35l56->base.dev, "%s: cs_dsp_run ret %d\n", __func__, ret); + goto err; + } + + if (cs35l56->base.secured) { + ret =3D cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_REINIT); + if (ret) + goto err; + } else { + /* Reset the device and wait for it to boot */ + cs35l56_system_reset(&cs35l56->base, false); + ret =3D cs35l56_wait_for_firmware_boot(&cs35l56->base); + if (ret) + goto err; + } + + /* Disable auto-hibernate so that runtime_pm has control */ + ret =3D cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_PREVENT_AUTO_H= IBERNATE); + if (ret) + goto err; + + /* Re-read the values from the device after a firmware/cofficient downloa= d */ + cs35l56_reread_firmware_registers(&cs35l56->base); + + regcache_mark_dirty(cs35l56->base.regmap); + regcache_sync(cs35l56->base.regmap); + + regmap_clear_bits(cs35l56->base.regmap, CS35L56_PROTECTION_STATUS, + CS35L56_FIRMWARE_MISSING); + cs35l56->base.fw_patched =3D true; +err: + pm_runtime_put(cs35l56->base.dev); + mutex_unlock(&cs35l56->base.irq_lock); + + return ret; +} + +static int cs35l56_hda_bind(struct device *dev, struct device *master, voi= d *master_data) +{ + struct cs35l56_hda *cs35l56 =3D dev_get_drvdata(dev); + struct hda_component *comps =3D master_data; + int ret; + + if (!comps || cs35l56->index < 0 || cs35l56->index >=3D HDA_MAX_COMPONENT= S) + return -EINVAL; + + comps =3D &comps[cs35l56->index]; + if (comps->dev) + return -EBUSY; + + comps->dev =3D dev; + cs35l56->codec =3D comps->codec; + strscpy(comps->name, dev_name(dev), sizeof(comps->name)); + comps->playback_hook =3D cs35l56_hda_playback_hook; + comps->mute_hook =3D cs35l56_hda_mute_hook; + + ret =3D cs35l56_hda_fw_load(cs35l56); + if (ret) + return ret; + + cs35l56_hda_create_controls(cs35l56); + cs35l56_hda_add_dsp_controls(cs35l56); + +#if IS_ENABLED(CONFIG_SND_DEBUG) + cs35l56->debugfs_root =3D debugfs_create_dir(dev_name(cs35l56->base.dev),= sound_debugfs_root); + cs_dsp_init_debugfs(&cs35l56->cs_dsp, cs35l56->debugfs_root); +#endif + + dev_dbg(cs35l56->base.dev, "Bound\n"); + + return 0; +} + +static void cs35l56_hda_unbind(struct device *dev, struct device *master, = void *master_data) +{ + struct cs35l56_hda *cs35l56 =3D dev_get_drvdata(dev); + struct hda_component *comps =3D master_data; + + cs35l56_hda_remove_controls(cs35l56); + +#if IS_ENABLED(CONFIG_SND_DEBUG) + cs_dsp_cleanup_debugfs(&cs35l56->cs_dsp); + debugfs_remove_recursive(cs35l56->debugfs_root); +#endif + + cs_dsp_remove(&cs35l56->cs_dsp); + + if (comps[cs35l56->index].dev =3D=3D dev) + memset(&comps[cs35l56->index], 0, sizeof(*comps)); + + dev_dbg(cs35l56->base.dev, "Unbound\n"); +} + +static const struct component_ops cs35l56_hda_comp_ops =3D { + .bind =3D cs35l56_hda_bind, + .unbind =3D cs35l56_hda_unbind, +}; + +static int cs35l56_hda_system_suspend(struct device *dev) +{ + struct cs35l56_hda *cs35l56 =3D dev_get_drvdata(dev); + + if (cs35l56->playing) + cs35l56_hda_pause(cs35l56); + + cs35l56->suspended =3D true; + + /* + * The interrupt line is normally shared, but after we start suspending + * we can't check if our device is the source of an interrupt, and can't + * clear it. Prevent this race by temporarily disabling the parent irq + * until we reach _no_irq. + */ + if (cs35l56->base.irq) + disable_irq(cs35l56->base.irq); + + return pm_runtime_force_suspend(dev); +} + +static int cs35l56_hda_system_suspend_late(struct device *dev) +{ + struct cs35l56_hda *cs35l56 =3D dev_get_drvdata(dev); + + /* + * RESET is usually shared by all amps so it must not be asserted until + * all driver instances have done their suspend() stage. + */ + if (cs35l56->base.reset_gpio) { + gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 0); + cs35l56_wait_min_reset_pulse(); + } + + return 0; +} + +static int cs35l56_hda_system_suspend_no_irq(struct device *dev) +{ + struct cs35l56_hda *cs35l56 =3D dev_get_drvdata(dev); + + /* Handlers are now disabled so the parent IRQ can safely be re-enabled. = */ + if (cs35l56->base.irq) + enable_irq(cs35l56->base.irq); + + return 0; +} + +static int cs35l56_hda_system_resume_no_irq(struct device *dev) +{ + struct cs35l56_hda *cs35l56 =3D dev_get_drvdata(dev); + + /* + * WAKE interrupts unmask if the CS35L56 hibernates, which can cause + * spurious interrupts, and the interrupt line is normally shared. + * We can't check if our device is the source of an interrupt, and can't + * clear it, until it has fully resumed. Prevent this race by temporarily + * disabling the parent irq until we complete resume(). + */ + if (cs35l56->base.irq) + disable_irq(cs35l56->base.irq); + + return 0; +} + +static int cs35l56_hda_system_resume_early(struct device *dev) +{ + struct cs35l56_hda *cs35l56 =3D dev_get_drvdata(dev); + + /* Ensure a spec-compliant RESET pulse. */ + if (cs35l56->base.reset_gpio) { + gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 0); + cs35l56_wait_min_reset_pulse(); + + /* Release shared RESET before drivers start resume(). */ + gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 1); + cs35l56_wait_control_port_ready(); + } + + return 0; +} + +static int cs35l56_hda_system_resume(struct device *dev) +{ + struct cs35l56_hda *cs35l56 =3D dev_get_drvdata(dev); + int ret; + + /* Undo pm_runtime_force_suspend() before re-enabling the irq */ + ret =3D pm_runtime_force_resume(dev); + if (cs35l56->base.irq) + enable_irq(cs35l56->base.irq); + + if (ret) + return ret; + + cs35l56->suspended =3D false; + + ret =3D cs35l56_is_fw_reload_needed(&cs35l56->base); + dev_dbg(cs35l56->base.dev, "fw_reload_needed: %d\n", ret); + if (ret > 0) { + ret =3D cs35l56_hda_fw_load(cs35l56); + if (ret) + return ret; + } + + if (cs35l56->playing) + cs35l56_hda_play(cs35l56); + + return 0; +} + +static int cs35l56_hda_read_acpi(struct cs35l56_hda *cs35l56, int id) +{ + u32 values[HDA_MAX_COMPONENTS]; + struct acpi_device *adev; + const char *property, *sub; + size_t nval; + int i, ret; + + /* + * ACPI_COMPANION isn't available when this driver was instantiated by + * the serial-multi-instantiate driver, so lookup the node by HID + */ + if (!ACPI_COMPANION(cs35l56->base.dev)) { + adev =3D acpi_dev_get_first_match_dev("CSC3556", NULL, -1); + if (!adev) { + dev_err(cs35l56->base.dev, "Failed to find an ACPI device for %s\n", + dev_name(cs35l56->base.dev)); + return -ENODEV; + } + ACPI_COMPANION_SET(cs35l56->base.dev, adev); + } + + property =3D "cirrus,dev-index"; + ret =3D device_property_count_u32(cs35l56->base.dev, property); + if (ret <=3D 0) + goto err; + + if (ret > ARRAY_SIZE(values)) { + ret =3D -EINVAL; + goto err; + } + nval =3D ret; + + ret =3D device_property_read_u32_array(cs35l56->base.dev, property, value= s, nval); + if (ret) + goto err; + + cs35l56->index =3D -1; + for (i =3D 0; i < nval; i++) { + if (values[i] =3D=3D id) { + cs35l56->index =3D i; + break; + } + } + if (cs35l56->index =3D=3D -1) { + dev_err(cs35l56->base.dev, "No index found in %s\n", property); + ret =3D -ENODEV; + goto err; + } + + sub =3D acpi_get_subsystem_id(ACPI_HANDLE(cs35l56->base.dev)); + + if (IS_ERR(sub)) { + /* If no ACPI SUB, return 0 and fallback to legacy firmware path, otherw= ise fail */ + if (PTR_ERR(sub) =3D=3D -ENODATA) + return 0; + else + return PTR_ERR(sub); + } + + cs35l56->system_name =3D sub; + + cs35l56->base.reset_gpio =3D devm_gpiod_get_index_optional(cs35l56->base.= dev, + "reset", + cs35l56->index, + GPIOD_OUT_LOW); + if (IS_ERR(cs35l56->base.reset_gpio)) { + ret =3D PTR_ERR(cs35l56->base.reset_gpio); + + /* + * If RESET is shared the first amp to probe will grab the reset + * line and reset all the amps + */ + if (ret !=3D -EBUSY) + return dev_err_probe(cs35l56->base.dev, ret, "Failed to get reset GPIO\= n"); + + dev_info(cs35l56->base.dev, "Reset GPIO busy, assume shared reset\n"); + cs35l56->base.reset_gpio =3D NULL; + } + + return 0; + +err: + dev_err(cs35l56->base.dev, "Failed property %s: %d\n", property, ret); + + return ret; +} + +int cs35l56_hda_common_probe(struct cs35l56_hda *cs35l56, int id) +{ + int ret; + + mutex_init(&cs35l56->base.irq_lock); + dev_set_drvdata(cs35l56->base.dev, cs35l56); + + ret =3D cs35l56_hda_read_acpi(cs35l56, id); + if (ret) { + dev_err_probe(cs35l56->base.dev, ret, "Platform not supported\n"); + goto err; + } + + cs35l56->amp_name =3D devm_kasprintf(cs35l56->base.dev, GFP_KERNEL, "AMP%= d", + cs35l56->index + 1); + if (!cs35l56->amp_name) { + ret =3D -ENOMEM; + goto err; + } + + cs35l56_init_cs_dsp(&cs35l56->base, &cs35l56->cs_dsp); + cs35l56->cs_dsp.client_ops =3D &cs35l56_hda_client_ops; + + if (cs35l56->base.reset_gpio) { + dev_dbg(cs35l56->base.dev, "Hard reset\n"); + + /* + * The GPIOD_OUT_LOW to *_gpiod_get_*() will be ignored if the + * ACPI defines a different default state. So explicitly set low. + */ + gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 0); + cs35l56_wait_min_reset_pulse(); + gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 1); + } + + ret =3D cs35l56_hw_init(&cs35l56->base); + if (ret < 0) + goto err; + + /* Reset the device and wait for it to boot */ + cs35l56_system_reset(&cs35l56->base, false); + ret =3D cs35l56_wait_for_firmware_boot(&cs35l56->base); + if (ret) + goto err; + + regcache_mark_dirty(cs35l56->base.regmap); + regcache_sync(cs35l56->base.regmap); + + /* Disable auto-hibernate so that runtime_pm has control */ + ret =3D cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_PREVENT_AUTO_H= IBERNATE); + if (ret) + goto err; + + ret =3D cs_dsp_halo_init(&cs35l56->cs_dsp); + if (ret) { + dev_err_probe(cs35l56->base.dev, ret, "cs_dsp_halo_init failed\n"); + goto err; + } + + dev_dbg(cs35l56->base.dev, "DSP system name: '%s', amp name: '%s'\n", + cs35l56->system_name, cs35l56->amp_name); + + /* Populate soft registers in the regmap cache */ + cs35l56_reread_firmware_registers(&cs35l56->base); + + regmap_multi_reg_write(cs35l56->base.regmap, cs35l56_hda_dai_config, + ARRAY_SIZE(cs35l56_hda_dai_config)); + + /* + * By default only enable one ASP1TXn, where n=3Damplifier index, + * This prevents multiple amps trying to drive the same slot. + */ + cs35l56->asp_tx_mask =3D BIT(cs35l56->index); + + pm_runtime_set_autosuspend_delay(cs35l56->base.dev, 3000); + pm_runtime_use_autosuspend(cs35l56->base.dev); + pm_runtime_set_active(cs35l56->base.dev); + pm_runtime_mark_last_busy(cs35l56->base.dev); + pm_runtime_enable(cs35l56->base.dev); + + ret =3D component_add(cs35l56->base.dev, &cs35l56_hda_comp_ops); + if (ret) { + dev_err(cs35l56->base.dev, "Register component failed: %d\n", ret); + goto pm_err; + } + + cs35l56->base.init_done =3D true; + + return 0; + +pm_err: + pm_runtime_disable(cs35l56->base.dev); +err: + gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 0); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(cs35l56_hda_common_probe, SND_HDA_SCODEC_CS35L56); + +void cs35l56_hda_remove(struct device *dev) +{ + struct cs35l56_hda *cs35l56 =3D dev_get_drvdata(dev); + + pm_runtime_get_sync(cs35l56->base.dev); + pm_runtime_disable(cs35l56->base.dev); + + component_del(cs35l56->base.dev, &cs35l56_hda_comp_ops); + + kfree(cs35l56->system_name); + pm_runtime_put_noidle(cs35l56->base.dev); + + gpiod_set_value_cansleep(cs35l56->base.reset_gpio, 0); +} +EXPORT_SYMBOL_NS_GPL(cs35l56_hda_remove, SND_HDA_SCODEC_CS35L56); + +const struct dev_pm_ops cs35l56_hda_pm_ops =3D { + SET_RUNTIME_PM_OPS(cs35l56_hda_runtime_suspend, cs35l56_hda_runtime_resum= e, NULL) + SYSTEM_SLEEP_PM_OPS(cs35l56_hda_system_suspend, cs35l56_hda_system_resume) + LATE_SYSTEM_SLEEP_PM_OPS(cs35l56_hda_system_suspend_late, + cs35l56_hda_system_resume_early) + NOIRQ_SYSTEM_SLEEP_PM_OPS(cs35l56_hda_system_suspend_no_irq, + cs35l56_hda_system_resume_no_irq) +}; +EXPORT_SYMBOL_NS_GPL(cs35l56_hda_pm_ops, SND_HDA_SCODEC_CS35L56); + +MODULE_DESCRIPTION("CS35L56 HDA Driver"); +MODULE_IMPORT_NS(SND_HDA_CS_DSP_CONTROLS); +MODULE_IMPORT_NS(SND_SOC_CS35L56_SHARED); +MODULE_AUTHOR("Richard Fitzgerald "); +MODULE_AUTHOR("Simon Trimmer "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(FW_CS_DSP); diff --git a/sound/pci/hda/cs35l56_hda.h b/sound/pci/hda/cs35l56_hda.h new file mode 100644 index 000000000000..6e5bc5397db5 --- /dev/null +++ b/sound/pci/hda/cs35l56_hda.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * HDA audio driver for Cirrus Logic CS35L56 smart amp + * + * Copyright (C) 2023 Cirrus Logic, Inc. and + * Cirrus Logic International Semiconductor Ltd. + */ + +#ifndef __CS35L56_HDA_H__ +#define __CS35L56_HDA_H__ + +#include +#include +#include +#include +#include +#include + +struct dentry; + +struct cs35l56_hda { + struct cs35l56_base base; + struct hda_codec *codec; + + int index; + const char *system_name; + const char *amp_name; + + struct cs_dsp cs_dsp; + bool playing; + bool suspended; + u8 asp_tx_mask; + + struct snd_kcontrol *posture_ctl; + struct snd_kcontrol *volume_ctl; + struct snd_kcontrol *mixer_ctl[4]; + +#if IS_ENABLED(CONFIG_SND_DEBUG) + struct dentry *debugfs_root; +#endif +}; + +extern const struct dev_pm_ops cs35l56_hda_pm_ops; + +int cs35l56_hda_common_probe(struct cs35l56_hda *cs35l56, int id); +void cs35l56_hda_remove(struct device *dev); + +#endif /*__CS35L56_HDA_H__*/ diff --git a/sound/pci/hda/cs35l56_hda_i2c.c b/sound/pci/hda/cs35l56_hda_i2= c.c new file mode 100644 index 000000000000..83e4acdd89ac --- /dev/null +++ b/sound/pci/hda/cs35l56_hda_i2c.c @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// CS35L56 HDA audio driver I2C binding +// +// Copyright (C) 2023 Cirrus Logic, Inc. and +// Cirrus Logic International Semiconductor Ltd. + +#include +#include +#include + +#include "cs35l56_hda.h" + +static int cs35l56_hda_i2c_probe(struct i2c_client *clt) +{ + struct cs35l56_hda *cs35l56; + int ret; + + cs35l56 =3D devm_kzalloc(&clt->dev, sizeof(*cs35l56), GFP_KERNEL); + if (!cs35l56) + return -ENOMEM; + + cs35l56->base.dev =3D &clt->dev; + cs35l56->base.can_hibernate =3D true; + cs35l56->base.regmap =3D devm_regmap_init_i2c(clt, &cs35l56_regmap_i2c); + if (IS_ERR(cs35l56->base.regmap)) { + ret =3D PTR_ERR(cs35l56->base.regmap); + dev_err(cs35l56->base.dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + ret =3D cs35l56_hda_common_probe(cs35l56, clt->addr); + if (ret) + return ret; + ret =3D cs35l56_irq_request(&cs35l56->base, clt->irq); + if (ret < 0) + cs35l56_hda_remove(cs35l56->base.dev); + + return ret; +} + +static void cs35l56_hda_i2c_remove(struct i2c_client *clt) +{ + cs35l56_hda_remove(&clt->dev); +} + +static const struct i2c_device_id cs35l56_hda_i2c_id[] =3D { + { "cs35l56-hda", 0 }, + {} +}; + +static struct i2c_driver cs35l56_hda_i2c_driver =3D { + .driver =3D { + .name =3D "cs35l56-hda", + .pm =3D &cs35l56_hda_pm_ops, + }, + .id_table =3D cs35l56_hda_i2c_id, + .probe =3D cs35l56_hda_i2c_probe, + .remove =3D cs35l56_hda_i2c_remove, +}; +module_i2c_driver(cs35l56_hda_i2c_driver); + +MODULE_DESCRIPTION("HDA CS35L56 I2C driver"); +MODULE_IMPORT_NS(SND_HDA_SCODEC_CS35L56); +MODULE_IMPORT_NS(SND_SOC_CS35L56_SHARED); +MODULE_AUTHOR("Richard Fitzgerald "); +MODULE_AUTHOR("Simon Trimmer "); +MODULE_LICENSE("GPL"); diff --git a/sound/pci/hda/cs35l56_hda_spi.c b/sound/pci/hda/cs35l56_hda_sp= i.c new file mode 100644 index 000000000000..756aec342eab --- /dev/null +++ b/sound/pci/hda/cs35l56_hda_spi.c @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// CS35L56 HDA audio driver SPI binding +// +// Copyright (C) 2023 Cirrus Logic, Inc. and +// Cirrus Logic International Semiconductor Ltd. + +#include +#include +#include + +#include "cs35l56_hda.h" + +static int cs35l56_hda_spi_probe(struct spi_device *spi) +{ + struct cs35l56_hda *cs35l56; + int ret; + + cs35l56 =3D devm_kzalloc(&spi->dev, sizeof(*cs35l56), GFP_KERNEL); + if (!cs35l56) + return -ENOMEM; + + cs35l56->base.dev =3D &spi->dev; + cs35l56->base.regmap =3D devm_regmap_init_spi(spi, &cs35l56_regmap_spi); + if (IS_ERR(cs35l56->base.regmap)) { + ret =3D PTR_ERR(cs35l56->base.regmap); + dev_err(cs35l56->base.dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + ret =3D cs35l56_hda_common_probe(cs35l56, spi->chip_select); + if (ret) + return ret; + ret =3D cs35l56_irq_request(&cs35l56->base, spi->irq); + if (ret < 0) + cs35l56_hda_remove(cs35l56->base.dev); + + return ret; +} + +static void cs35l56_hda_spi_remove(struct spi_device *spi) +{ + cs35l56_hda_remove(&spi->dev); +} + +static const struct spi_device_id cs35l56_hda_spi_id[] =3D { + { "cs35l56-hda", 0 }, + {} +}; + +static struct spi_driver cs35l56_hda_spi_driver =3D { + .driver =3D { + .name =3D "cs35l56-hda", + .pm =3D &cs35l56_hda_pm_ops, + }, + .id_table =3D cs35l56_hda_spi_id, + .probe =3D cs35l56_hda_spi_probe, + .remove =3D cs35l56_hda_spi_remove, +}; +module_spi_driver(cs35l56_hda_spi_driver); + +MODULE_DESCRIPTION("HDA CS35L56 SPI driver"); +MODULE_IMPORT_NS(SND_HDA_SCODEC_CS35L56); +MODULE_IMPORT_NS(SND_SOC_CS35L56_SHARED); +MODULE_AUTHOR("Richard Fitzgerald "); +MODULE_AUTHOR("Simon Trimmer "); +MODULE_LICENSE("GPL"); --=20 2.30.2