From nobody Tue Dec 16 12:34:02 2025 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 6E525C6FD1F for ; Wed, 22 Mar 2023 13:47:40 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230492AbjCVNri (ORCPT ); Wed, 22 Mar 2023 09:47:38 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:40106 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230430AbjCVNr0 (ORCPT ); Wed, 22 Mar 2023 09:47:26 -0400 Received: from relay7-d.mail.gandi.net (relay7-d.mail.gandi.net [IPv6:2001:4b98:dc4:8::227]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id E841D62B65; Wed, 22 Mar 2023 06:47:20 -0700 (PDT) Received: (Authenticated sender: herve.codina@bootlin.com) by mail.gandi.net (Postfix) with ESMTPA id 2DAE820014; Wed, 22 Mar 2023 13:47:18 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=gm1; t=1679492839; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=7yPUxxveyvajQFmVA13YJg3H/qWQmynFovV6OzXuBdQ=; b=KivwWCP4SQgZO1i/ubq36ctMWEP7nzRrI5dXU8QF2PXCZ7uJb3aiXjWCHInZ4K9iglnxqN 12CFCksjxTKaHwAV9mopWHbLyH464WPXXcGjqWwJn9ATLQMjpZeFNcFlZztmz5LvMv74RG I82vyAhQXg/5sgKQTqn51wd68hmzaROeTUxplb0uA5prxDWEbFYDHMPbXqSeK6VahhXbgi BwMkrDj02C21lc+gXW5rESU46BCGapuYQ/1K5FY5ecNUtwT+KwArAcytw9yMaLoN7Sx8bi xj/ntXHQjloqBRU/fdk5P2Vu+GgEZojDV1Ya1zuXhRGJts5MHFcQMfL1Nvzonw== From: Herve Codina To: Herve Codina , Lee Jones , Rob Herring , Krzysztof Kozlowski , Liam Girdwood , Mark Brown , Jaroslav Kysela , Takashi Iwai Cc: linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, alsa-devel@alsa-project.org, Christophe Leroy , Thomas Petazzoni Subject: [PATCH v3 5/6] ASoC: codecs: Add support for the Lantiq PEF2256 codec Date: Wed, 22 Mar 2023 14:46:53 +0100 Message-Id: <20230322134654.219957-6-herve.codina@bootlin.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20230322134654.219957-1-herve.codina@bootlin.com> References: <20230322134654.219957-1-herve.codina@bootlin.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" The Lantiq PEF2256 is a framer and line interface component designed to fulfill all required interfacing between an analog E1/T1/J1 line and the digital PCM system highway/H.100 bus. The codec support allows to use some of the PCM system highway time-slots as audio channels to transport audio data over the E1/T1/J1 lines. It provides also line carrier detection events reported through the ALSA jack detection feature. Signed-off-by: Herve Codina --- sound/soc/codecs/Kconfig | 14 ++ sound/soc/codecs/Makefile | 2 + sound/soc/codecs/pef2256-codec.c | 390 +++++++++++++++++++++++++++++++ 3 files changed, 406 insertions(+) create mode 100644 sound/soc/codecs/pef2256-codec.c diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 0be061953e9a..4f78da914fc7 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -168,6 +168,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_PCM512x_I2C imply SND_SOC_PCM512x_SPI imply SND_SOC_PEB2466 + imply SND_SOC_PEF2256 imply SND_SOC_RK3328 imply SND_SOC_RK817 imply SND_SOC_RT274 @@ -1252,6 +1253,19 @@ config SND_SOC_PEB2466 To compile this driver as a module, choose M here: the module will be called snd-soc-peb2466. =20 +config SND_SOC_PEF2256 + tristate "Lantiq PEF2256 codec" + depends on MFD_PEF2256 + help + Enable support for the Lantiq PEF2256 (FALC56) codec. + The PEF2256 is a framer and line interface between analog E1/T1/J1 + line and a digital PCM bus. + This codec allows to use some of the time slots available on the + PEF2256 PCM bus to transport some audio data. + + To compile this driver as a module, choose M here: the module + will be called snd-soc-pef2256. + config SND_SOC_RK3328 tristate "Rockchip RK3328 audio CODEC" select REGMAP_MMIO diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 20b388b77f1f..11bd66d46f7b 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -188,6 +188,7 @@ snd-soc-pcm512x-objs :=3D pcm512x.o snd-soc-pcm512x-i2c-objs :=3D pcm512x-i2c.o snd-soc-pcm512x-spi-objs :=3D pcm512x-spi.o snd-soc-peb2466-objs :=3D peb2466.o +snd-soc-pef2256-objs :=3D pef2256-codec.o snd-soc-rk3328-objs :=3D rk3328_codec.o snd-soc-rk817-objs :=3D rk817_codec.o snd-soc-rl6231-objs :=3D rl6231.o @@ -551,6 +552,7 @@ obj-$(CONFIG_SND_SOC_PCM512x) +=3D snd-soc-pcm512x.o obj-$(CONFIG_SND_SOC_PCM512x_I2C) +=3D snd-soc-pcm512x-i2c.o obj-$(CONFIG_SND_SOC_PCM512x_SPI) +=3D snd-soc-pcm512x-spi.o obj-$(CONFIG_SND_SOC_PEB2466) +=3D snd-soc-peb2466.o +obj-$(CONFIG_SND_SOC_PEF2256) +=3D snd-soc-pef2256.o obj-$(CONFIG_SND_SOC_RK3328) +=3D snd-soc-rk3328.o obj-$(CONFIG_SND_SOC_RK817) +=3D snd-soc-rk817.o obj-$(CONFIG_SND_SOC_RL6231) +=3D snd-soc-rl6231.o diff --git a/sound/soc/codecs/pef2256-codec.c b/sound/soc/codecs/pef2256-co= dec.c new file mode 100644 index 000000000000..366df39bd866 --- /dev/null +++ b/sound/soc/codecs/pef2256-codec.c @@ -0,0 +1,390 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// pef2256.c -- Lantiq PEF2256 ALSA SoC driver +// +// Copyright 2023 CS GROUP France +// +// Author: Herve Codina + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PEF2256_NB_CHANNEL 32 +#define PEF2256_JACK_MASK (SND_JACK_LINEIN | SND_JACK_LINEOUT) + +struct pef2256_codec { + struct pef2256 *pef2256; + struct device *dev; + struct snd_soc_jack jack; + struct notifier_block nb; + struct work_struct carrier_work; + int max_chan_playback; + int max_chan_capture; +}; + +static int pef2256_dai_set_tdm_slot(struct snd_soc_dai *dai, unsigned int = tx_mask, + unsigned int rx_mask, int slots, int width) +{ + struct pef2256_codec *pef2256 =3D snd_soc_component_get_drvdata(dai->comp= onent); + + switch (width) { + case 0: + /* Not set -> default 8 */ + case 8: + break; + default: + dev_err(dai->dev, "tdm slot width %d not supported\n", width); + return -EINVAL; + } + + pef2256->max_chan_playback =3D hweight32(tx_mask); + if (pef2256->max_chan_playback > PEF2256_NB_CHANNEL) { + dev_err(dai->dev, "too much tx slots defined (mask =3D 0x%x) support max= %d\n", + tx_mask, PEF2256_NB_CHANNEL); + return -EINVAL; + } + + pef2256->max_chan_capture =3D hweight32(rx_mask); + if (pef2256->max_chan_capture > PEF2256_NB_CHANNEL) { + dev_err(dai->dev, "too much rx slots defined (mask =3D 0x%x) support max= %d\n", + rx_mask, PEF2256_NB_CHANNEL); + return -EINVAL; + } + + return 0; +} + +/* + * The constraints for format/channel is to match with the number of 8bit + * time-slots available. + */ +static int pef2256_dai_hw_rule_channels_by_format(struct snd_soc_dai *dai, + struct snd_pcm_hw_params *params, + unsigned int nb_ts) +{ + struct snd_interval *c =3D hw_param_interval(params, SNDRV_PCM_HW_PARAM_C= HANNELS); + snd_pcm_format_t format =3D params_format(params); + struct snd_interval ch =3D {0}; + + switch (snd_pcm_format_physical_width(format)) { + case 8: + ch.max =3D nb_ts; + break; + case 16: + ch.max =3D nb_ts/2; + break; + case 32: + ch.max =3D nb_ts/4; + break; + case 64: + ch.max =3D nb_ts/8; + break; + default: + dev_err(dai->dev, "format physical width %u not supported\n", + snd_pcm_format_physical_width(format)); + return -EINVAL; + } + + ch.min =3D ch.max ? 1 : 0; + + return snd_interval_refine(c, &ch); +} + +static int pef2256_dai_hw_rule_playback_channels_by_format(struct snd_pcm_= hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_soc_dai *dai =3D rule->private; + struct pef2256_codec *pef2256 =3D snd_soc_component_get_drvdata(dai->comp= onent); + + return pef2256_dai_hw_rule_channels_by_format(dai, params, pef2256->max_c= han_playback); +} + +static int pef2256_dai_hw_rule_capture_channels_by_format(struct snd_pcm_h= w_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_soc_dai *dai =3D rule->private; + struct pef2256_codec *pef2256 =3D snd_soc_component_get_drvdata(dai->comp= onent); + + return pef2256_dai_hw_rule_channels_by_format(dai, params, pef2256->max_c= han_capture); +} + +static int pef2256_dai_hw_rule_format_by_channels(struct snd_soc_dai *dai, + struct snd_pcm_hw_params *params, + unsigned int nb_ts) +{ + struct snd_mask *f_old =3D hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMA= T); + unsigned int channels =3D params_channels(params); + unsigned int slot_width; + struct snd_mask f_new; + unsigned int i; + + if (!channels || channels > nb_ts) { + dev_err(dai->dev, "channels %u not supported\n", nb_ts); + return -EINVAL; + } + + slot_width =3D (nb_ts / channels) * 8; + + snd_mask_none(&f_new); + for (i =3D 0; i <=3D SNDRV_PCM_FORMAT_LAST; i++) { + if (snd_mask_test(f_old, i)) { + if (snd_pcm_format_physical_width(i) <=3D slot_width) + snd_mask_set(&f_new, i); + } + } + + return snd_mask_refine(f_old, &f_new); +} + +static int pef2256_dai_hw_rule_playback_format_by_channels(struct snd_pcm_= hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_soc_dai *dai =3D rule->private; + struct pef2256_codec *pef2256 =3D snd_soc_component_get_drvdata(dai->comp= onent); + + return pef2256_dai_hw_rule_format_by_channels(dai, params, pef2256->max_c= han_playback); +} + +static int pef2256_dai_hw_rule_capture_format_by_channels(struct snd_pcm_h= w_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_soc_dai *dai =3D rule->private; + struct pef2256_codec *pef2256 =3D snd_soc_component_get_drvdata(dai->comp= onent); + + return pef2256_dai_hw_rule_format_by_channels(dai, params, pef2256->max_c= han_capture); +} + +static u64 pef2256_formats(u8 nb_ts) +{ + u64 formats; + unsigned int chan_width; + unsigned int format_width; + int i; + + if (!nb_ts) + return 0; + + formats =3D 0; + chan_width =3D nb_ts * 8; + for (i =3D 0; i <=3D SNDRV_PCM_FORMAT_LAST; i++) { + /* Support physical width multiple of 8bit */ + format_width =3D snd_pcm_format_physical_width(i); + if (format_width =3D=3D 0 || format_width % 8) + continue; + + /* + * And support physical width that can fit N times in the + * channel + */ + if (format_width > chan_width || chan_width % format_width) + continue; + + formats |=3D (1ULL << i); + } + return formats; +} + +static int pef2256_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct pef2256_codec *pef2256 =3D snd_soc_component_get_drvdata(dai->comp= onent); + snd_pcm_hw_rule_func_t hw_rule_channels_by_format; + snd_pcm_hw_rule_func_t hw_rule_format_by_channels; + unsigned int frame_bits; + u64 format; + int ret; + + if (substream->stream =3D=3D SNDRV_PCM_STREAM_CAPTURE) { + format =3D pef2256_formats(pef2256->max_chan_capture); + hw_rule_channels_by_format =3D pef2256_dai_hw_rule_capture_channels_by_f= ormat; + hw_rule_format_by_channels =3D pef2256_dai_hw_rule_capture_format_by_cha= nnels; + frame_bits =3D pef2256->max_chan_capture * 8; + } else { + format =3D pef2256_formats(pef2256->max_chan_playback); + hw_rule_channels_by_format =3D pef2256_dai_hw_rule_playback_channels_by_= format; + hw_rule_format_by_channels =3D pef2256_dai_hw_rule_playback_format_by_ch= annels; + frame_bits =3D pef2256->max_chan_playback * 8; + } + + ret =3D snd_pcm_hw_constraint_mask64(substream->runtime, + SNDRV_PCM_HW_PARAM_FORMAT, format); + if (ret) { + dev_err(dai->dev, "Failed to add format constraint (%d)\n", ret); + return ret; + } + + ret =3D snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_CHA= NNELS, + hw_rule_channels_by_format, dai, + SNDRV_PCM_HW_PARAM_FORMAT, -1); + if (ret) { + dev_err(dai->dev, "Failed to add channels rule (%d)\n", ret); + return ret; + } + + ret =3D snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_FO= RMAT, + hw_rule_format_by_channels, dai, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (ret) { + dev_err(dai->dev, "Failed to add format rule (%d)\n", ret); + return ret; + } + + ret =3D snd_pcm_hw_constraint_single(substream->runtime, + SNDRV_PCM_HW_PARAM_FRAME_BITS, + frame_bits); + if (ret < 0) { + dev_err(dai->dev, "Failed to add frame_bits constraint (%d)\n", ret); + return ret; + } + + return 0; +} + +static u64 pef2256_dai_formats[] =3D { + SND_SOC_POSSIBLE_DAIFMT_DSP_B, +}; + +static const struct snd_soc_dai_ops pef2256_dai_ops =3D { + .startup =3D pef2256_dai_startup, + .set_tdm_slot =3D pef2256_dai_set_tdm_slot, + .auto_selectable_formats =3D pef2256_dai_formats, + .num_auto_selectable_formats =3D ARRAY_SIZE(pef2256_dai_formats), +}; + +static struct snd_soc_dai_driver pef2256_dai_driver =3D { + .name =3D "pef2256", + .playback =3D { + .stream_name =3D "Playback", + .channels_min =3D 1, + .channels_max =3D PEF2256_NB_CHANNEL, + .rates =3D SNDRV_PCM_RATE_8000, + .formats =3D U64_MAX, /* Will be refined on DAI .startup() */ + }, + .capture =3D { + .stream_name =3D "Capture", + .channels_min =3D 1, + .channels_max =3D PEF2256_NB_CHANNEL, + .rates =3D SNDRV_PCM_RATE_8000, + .formats =3D U64_MAX, /* Will be refined on DAI .startup() */ + }, + .ops =3D &pef2256_dai_ops, +}; + +static void pef2256_carrier_work(struct work_struct *work) +{ + struct pef2256_codec *pef2256 =3D container_of(work, struct pef2256_codec= , carrier_work); + int status; + + status =3D pef2256_get_carrier(pef2256->pef2256) ? PEF2256_JACK_MASK : 0; + snd_soc_jack_report(&pef2256->jack, status, PEF2256_JACK_MASK); +} + +static int pef2256_carrier_notifier(struct notifier_block *nb, unsigned lo= ng action, + void *data) +{ + struct pef2256_codec *pef2256 =3D container_of(nb, struct pef2256_codec, = nb); + + switch (action) { + case PEF2256_EVENT_CARRIER: + queue_work(system_power_efficient_wq, &pef2256->carrier_work); + break; + default: + return NOTIFY_DONE; + } + + return NOTIFY_OK; +} + +static int pef2256_component_probe(struct snd_soc_component *component) +{ + struct pef2256_codec *pef2256 =3D snd_soc_component_get_drvdata(component= ); + char *name; + int ret; + + INIT_WORK(&pef2256->carrier_work, pef2256_carrier_work); + + name =3D "carrier"; + if (component->name_prefix) { + name =3D kasprintf(GFP_KERNEL, "%s carrier", component->name_prefix); + if (!name) + return -ENOMEM; + } + + ret =3D snd_soc_card_jack_new(component->card, name, PEF2256_JACK_MASK, &= pef2256->jack); + if (component->name_prefix) + kfree(name); /* A copy is done by snd_soc_card_jack_new */ + if (ret) { + dev_err(component->dev, "Cannot create jack\n"); + return ret; + } + + pef2256->nb.notifier_call =3D pef2256_carrier_notifier; + ret =3D pef2256_register_event_notifier(pef2256->pef2256, &pef2256->nb); + if (ret) { + dev_err(component->dev, "Cannot register event notifier\n"); + return ret; + } + + /* Queue work to set the initial value */ + queue_work(system_power_efficient_wq, &pef2256->carrier_work); + + return 0; +} + +static void pef2256_component_remove(struct snd_soc_component *component) +{ + struct pef2256_codec *pef2256 =3D snd_soc_component_get_drvdata(component= ); + + pef2256_unregister_event_notifier(pef2256->pef2256, &pef2256->nb); + cancel_work_sync(&pef2256->carrier_work); +} + +static const struct snd_soc_component_driver pef2256_component_driver =3D { + .probe =3D pef2256_component_probe, + .remove =3D pef2256_component_remove, + .endianness =3D 1, +}; + +static int pef2256_codec_probe(struct platform_device *pdev) +{ + struct pef2256_codec *pef2256; + + pef2256 =3D devm_kzalloc(&pdev->dev, sizeof(*pef2256), GFP_KERNEL); + if (!pef2256) + return -ENOMEM; + + pef2256->dev =3D &pdev->dev; + pef2256->pef2256 =3D dev_get_drvdata(pef2256->dev->parent); + + platform_set_drvdata(pdev, pef2256); + + return devm_snd_soc_register_component(&pdev->dev, &pef2256_component_dri= ver, + &pef2256_dai_driver, 1); +} + +static const struct of_device_id pef2256_codec_of_match[] =3D { + { .compatible =3D "lantiq,pef2256-codec" }, + {} /* sentinel */ +}; +MODULE_DEVICE_TABLE(of, pef2256_codec_of_match); + +static struct platform_driver pef2256_codec_driver =3D { + .driver =3D { + .name =3D "lantiq-pef2256-codec", + .of_match_table =3D of_match_ptr(pef2256_codec_of_match), + }, + .probe =3D pef2256_codec_probe, +}; +module_platform_driver(pef2256_codec_driver); + +MODULE_AUTHOR("Herve Codina "); +MODULE_DESCRIPTION("PEF2256 ALSA SoC driver"); +MODULE_LICENSE("GPL"); --=20 2.39.2