From patchwork Thu Mar 12 22:32:41 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kevin Li X-Patchwork-Id: 193294 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-9.8 required=3.0 tests=DKIM_SIGNED,DKIM_VALID, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_PATCH, MAILING_LIST_MULTI, SIGNED_OFF_BY, SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 15D03C10DCE for ; Thu, 12 Mar 2020 22:34:58 +0000 (UTC) Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 94732206CD for ; Thu, 12 Mar 2020 22:34:56 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (1024-bit key) header.d=alsa-project.org header.i=@alsa-project.org header.b="GvfkHee7"; dkim=fail reason="signature verification failed" (1024-bit key) header.d=broadcom.com header.i=@broadcom.com header.b="VcifOnLy" DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 94732206CD Authentication-Results: mail.kernel.org; dmarc=fail (p=quarantine dis=none) header.from=broadcom.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=alsa-devel-bounces@alsa-project.org Received: from alsa1.perex.cz (alsa1.perex.cz [207.180.221.201]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by alsa0.perex.cz (Postfix) with ESMTPS id DFF9E177E; Thu, 12 Mar 2020 23:34:04 +0100 (CET) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa0.perex.cz DFF9E177E DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=alsa-project.org; s=default; t=1584052495; bh=NaCiaUbCdKuprh5ORHct4C3c11Va1RRfpgVlz5ToTlA=; h=From:To:Subject:Date:In-Reply-To:References:Cc:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: From; b=GvfkHee7h3/PR8h1D4tVM1EZCR2QP1lKaQBRbCd+CERQpLxYsxgo60Tlw4caYhU8c OHWvSblGe/Q6cWZmCOxHIGjyqt+utocHkmzFQ7MvwvwcS996Ejh7/Q5eZ3lQu0SsZ5 Ao8lZX42XJP9btJ2IPiEQTrNxdWdF8Yme4UcNXS0= Received: from alsa1.perex.cz (localhost.localdomain [127.0.0.1]) by alsa1.perex.cz (Postfix) with ESMTP id E856FF8021C; Thu, 12 Mar 2020 23:33:12 +0100 (CET) Received: by alsa1.perex.cz (Postfix, from userid 50401) id D4D15F8013E; Thu, 12 Mar 2020 23:33:10 +0100 (CET) Received: from mail-wm1-x344.google.com (mail-wm1-x344.google.com [IPv6:2a00:1450:4864:20::344]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by alsa1.perex.cz (Postfix) with ESMTPS id 71200F8013E for ; Thu, 12 Mar 2020 23:33:05 +0100 (CET) DKIM-Filter: OpenDKIM Filter v2.11.0 alsa1.perex.cz 71200F8013E Authentication-Results: alsa1.perex.cz; dkim=pass (1024-bit key) header.d=broadcom.com header.i=@broadcom.com header.b="VcifOnLy" Received: by mail-wm1-x344.google.com with SMTP id n8so7922978wmc.4 for ; Thu, 12 Mar 2020 15:33:05 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=broadcom.com; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=Uh0qyhxDJZNiOUl/7XtNJ0cPPogwUNK8TyWYeMAzHk8=; b=VcifOnLywxiqWRZMRRd6Thz1I6xISfvToXgD+YAAKfxhGoFInmc+/EFlpCc22lUDp7 jHc/83McLWNxCeRYC7HhJXfm15vJ36B2NBlSTlLVOAhQ6GjflAR3ET9VtYfkzujVEtWI PjGYmi8fcuBq6oXufGq5juMeZqtOFleUpn1vg= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=Uh0qyhxDJZNiOUl/7XtNJ0cPPogwUNK8TyWYeMAzHk8=; b=lxhxuwWeHK4nJn3D9bNkQgyr2qSNYoS0h0H7/nmVu1AIWfVC+O0KbSbhmvX3cVpxPM nviLbnIqe+NelK1bGn9Nm8PH58v/sukxyMqK2uZnrlW7NAV4I0vpeMuZGyiffY2feRVI Q8y/o31/OzovUzPiKXuhxRAGHQ2JqHl+jWq0KJ7512tzakhuNJPuOHmDRoF0n6ko1g9c 2ue/75JaCjOpu9ButUZRBqeUBcAFCwZ0bxxvyFY55661qbKSubJTPlLgtanOds+i2yrP VQo/IyicAvprix+98IQMujwrTHCPJI55wF6JFAGohgqLt+T08G/9k1VZFo9sifhTlNhc UO0A== X-Gm-Message-State: ANhLgQ0QABw5pB6t76KNCogYAIsjwKxpf00qP5fXIrs0K0bQ7Z2rMdEj gLe5K/6c5l/ZfHK14pwuRnjvag== X-Google-Smtp-Source: ADFU+vuwQj1vE2Y/0PRdqVoND8TvDzJtgSi/8WV+5FduCcmrsOo+wrw+w/PP/DI2MslE3u5KG53VGQ== X-Received: by 2002:a1c:5443:: with SMTP id p3mr6584322wmi.149.1584052385037; Thu, 12 Mar 2020 15:33:05 -0700 (PDT) Received: from kevin-Precision-Tower-5810.dhcp.broadcom.net ([192.19.228.250]) by smtp.gmail.com with ESMTPSA id o10sm2964144wrs.65.2020.03.12.15.33.00 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Thu, 12 Mar 2020 15:33:04 -0700 (PDT) From: Kevin Li To: Mark Brown Subject: [PATCH v2 1/2] ASoC: brcm: Add DSL/PON SoC audio driver Date: Thu, 12 Mar 2020 15:32:41 -0700 Message-Id: <20200312223242.2843-2-kevin-ke.li@broadcom.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20200312223242.2843-1-kevin-ke.li@broadcom.com> References: <20200312223242.2843-1-kevin-ke.li@broadcom.com> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org, linux-kernel@vger.kernel.org, Kuninori Morimoto , Scott Branden , Liam Girdwood , Ray Jui , Takashi Iwai , Kevin Li , bcm-kernel-feedback-list@broadcom.com, Stephen Boyd , linux-arm-kernel@lists.infradead.org X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: "Alsa-devel" This patch adds Broadcom DSL/PON SoC audio driver with Whistler I2S block. The SoC supported by this patch are BCM63158B0,BCM63178 and BCM47622/6755 Signed-off-by: Kevin Li --- sound/soc/bcm/Kconfig | 9 + sound/soc/bcm/Makefile | 4 + sound/soc/bcm/bcm63xx-i2s-whistler.c | 317 +++++++++++++++++ sound/soc/bcm/bcm63xx-i2s.h | 90 +++++ sound/soc/bcm/bcm63xx-pcm-whistler.c | 485 +++++++++++++++++++++++++++ 5 files changed, 905 insertions(+) create mode 100644 sound/soc/bcm/bcm63xx-i2s-whistler.c create mode 100644 sound/soc/bcm/bcm63xx-i2s.h create mode 100644 sound/soc/bcm/bcm63xx-pcm-whistler.c diff --git a/sound/soc/bcm/Kconfig b/sound/soc/bcm/Kconfig index 0037e96aa228..4218057b0874 100644 --- a/sound/soc/bcm/Kconfig +++ b/sound/soc/bcm/Kconfig @@ -17,3 +17,12 @@ config SND_SOC_CYGNUS Cygnus chips (bcm958300, bcm958305, bcm911360) If you don't know what to do here, say N. + +config SND_BCM63XX_I2S_WHISTLER + tristate "SoC Audio support for the Broadcom BCM63XX I2S module" + select REGMAP_MMIO + help + Say Y if you want to add support for ASoC audio on Broadcom + DSL/PON chips (bcm63158, bcm63178) + + If you don't know what to do here, say N diff --git a/sound/soc/bcm/Makefile b/sound/soc/bcm/Makefile index b81fa421ec27..7c2d7899603b 100644 --- a/sound/soc/bcm/Makefile +++ b/sound/soc/bcm/Makefile @@ -9,3 +9,7 @@ snd-soc-cygnus-objs := cygnus-pcm.o cygnus-ssp.o obj-$(CONFIG_SND_SOC_CYGNUS) += snd-soc-cygnus.o +# BCM63XX Platform Support +snd-soc-63xx-objs := bcm63xx-i2s-whistler.o bcm63xx-pcm-whistler.o + +obj-$(CONFIG_SND_BCM63XX_I2S_WHISTLER) += snd-soc-63xx.o \ No newline at end of file diff --git a/sound/soc/bcm/bcm63xx-i2s-whistler.c b/sound/soc/bcm/bcm63xx-i2s-whistler.c new file mode 100644 index 000000000000..246a57ac6679 --- /dev/null +++ b/sound/soc/bcm/bcm63xx-i2s-whistler.c @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// linux/sound/bcm/bcm63xx-i2s-whistler.c +// BCM63xx whistler i2s driver +// Copyright (c) 2020 Broadcom Corporation +// Author: Kevin-Ke Li + +#include +#include +#include +#include +#include +#include +#include +#include "bcm63xx-i2s.h" + +#define DRV_NAME "brcm-i2s" + +static bool brcm_i2s_wr_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_TX_CFG ... I2S_TX_DESC_IFF_LEN: + case I2S_TX_CFG_2 ... I2S_RX_DESC_IFF_LEN: + case I2S_RX_CFG_2 ... I2S_REG_MAX: + return true; + default: + return false; + } +} + +static bool brcm_i2s_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_TX_CFG ... I2S_REG_MAX: + return true; + default: + return false; + } +} + +static bool brcm_i2s_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_TX_CFG: + case I2S_TX_IRQ_CTL: + case I2S_TX_DESC_IFF_ADDR: + case I2S_TX_DESC_IFF_LEN: + case I2S_TX_DESC_OFF_ADDR: + case I2S_TX_DESC_OFF_LEN: + case I2S_TX_CFG_2: + case I2S_RX_CFG: + case I2S_RX_IRQ_CTL: + case I2S_RX_DESC_OFF_ADDR: + case I2S_RX_DESC_OFF_LEN: + case I2S_RX_DESC_IFF_LEN: + case I2S_RX_DESC_IFF_ADDR: + case I2S_RX_CFG_2: + return true; + default: + return false; + } +} + +static const struct regmap_config brcm_i2s_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = I2S_REG_MAX, + .writeable_reg = brcm_i2s_wr_reg, + .readable_reg = brcm_i2s_rd_reg, + .volatile_reg = brcm_i2s_volatile_reg, + .cache_type = REGCACHE_FLAT, +}; + +static int bcm63xx_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + int ret = 0; + struct bcm_i2s_priv *i2s_priv = snd_soc_dai_get_drvdata(dai); + + ret = clk_set_rate(i2s_priv->i2s_clk, params_rate(params)); + if (ret < 0) + dev_err(i2s_priv->dev, + "Can't set sample rate, err: %d\n", ret); + + return ret; +} + +static int bcm63xx_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + unsigned int slavemode; + struct bcm_i2s_priv *i2s_priv = snd_soc_dai_get_drvdata(dai); + struct regmap *regmap_i2s = i2s_priv->regmap_i2s; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + regmap_update_bits(regmap_i2s, I2S_TX_CFG, + I2S_TX_OUT_R | I2S_TX_DATA_ALIGNMENT | + I2S_TX_DATA_ENABLE | I2S_TX_CLOCK_ENABLE, + I2S_TX_OUT_R | I2S_TX_DATA_ALIGNMENT | + I2S_TX_DATA_ENABLE | I2S_TX_CLOCK_ENABLE); + regmap_write(regmap_i2s, I2S_TX_IRQ_CTL, 0); + regmap_write(regmap_i2s, I2S_TX_IRQ_IFF_THLD, 0); + regmap_write(regmap_i2s, I2S_TX_IRQ_OFF_THLD, 1); + + /* TX and RX block each have an independent bit to indicate + * if it is generating the clock for the I2S bus. The bus + * clocks need to be generated from either the TX or RX block, + * but not both + */ + regmap_read(regmap_i2s, I2S_RX_CFG_2, &slavemode); + if (slavemode & I2S_RX_SLAVE_MODE_MASK) + regmap_update_bits(regmap_i2s, I2S_TX_CFG_2, + I2S_TX_SLAVE_MODE_MASK, + I2S_TX_MASTER_MODE); + else + regmap_update_bits(regmap_i2s, I2S_TX_CFG_2, + I2S_TX_SLAVE_MODE_MASK, + I2S_TX_SLAVE_MODE); + } else { + regmap_update_bits(regmap_i2s, I2S_RX_CFG, + I2S_RX_IN_R | I2S_RX_DATA_ALIGNMENT | + I2S_RX_CLOCK_ENABLE, + I2S_RX_IN_R | I2S_RX_DATA_ALIGNMENT | + I2S_RX_CLOCK_ENABLE); + regmap_write(regmap_i2s, I2S_RX_IRQ_CTL, 0); + regmap_write(regmap_i2s, I2S_RX_IRQ_IFF_THLD, 0); + regmap_write(regmap_i2s, I2S_RX_IRQ_OFF_THLD, 1); + + regmap_read(regmap_i2s, I2S_TX_CFG_2, &slavemode); + if (slavemode & I2S_TX_SLAVE_MODE_MASK) + regmap_update_bits(regmap_i2s, I2S_RX_CFG_2, + I2S_RX_SLAVE_MODE_MASK, 0); + else + regmap_update_bits(regmap_i2s, I2S_RX_CFG_2, + I2S_RX_SLAVE_MODE_MASK, + I2S_RX_SLAVE_MODE); + } + return 0; +} + +static void bcm63xx_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + unsigned int enabled, slavemode; + struct bcm_i2s_priv *i2s_priv = snd_soc_dai_get_drvdata(dai); + struct regmap *regmap_i2s = i2s_priv->regmap_i2s; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + regmap_update_bits(regmap_i2s, I2S_TX_CFG, + I2S_TX_OUT_R | I2S_TX_DATA_ALIGNMENT | + I2S_TX_DATA_ENABLE | I2S_TX_CLOCK_ENABLE, 0); + regmap_write(regmap_i2s, I2S_TX_IRQ_CTL, 1); + regmap_write(regmap_i2s, I2S_TX_IRQ_IFF_THLD, 4); + regmap_write(regmap_i2s, I2S_TX_IRQ_OFF_THLD, 4); + + regmap_read(regmap_i2s, I2S_TX_CFG_2, &slavemode); + slavemode = slavemode & I2S_TX_SLAVE_MODE_MASK; + if (!slavemode) { + regmap_read(regmap_i2s, I2S_RX_CFG, &enabled); + enabled = enabled & I2S_RX_ENABLE_MASK; + if (enabled) + regmap_update_bits(regmap_i2s, I2S_RX_CFG_2, + I2S_RX_SLAVE_MODE_MASK, + I2S_RX_MASTER_MODE); + } + regmap_update_bits(regmap_i2s, I2S_TX_CFG_2, + I2S_TX_SLAVE_MODE_MASK, + I2S_TX_SLAVE_MODE); + } else { + regmap_update_bits(regmap_i2s, I2S_RX_CFG, + I2S_RX_IN_R | I2S_RX_DATA_ALIGNMENT | + I2S_RX_CLOCK_ENABLE, 0); + regmap_write(regmap_i2s, I2S_RX_IRQ_CTL, 1); + regmap_write(regmap_i2s, I2S_RX_IRQ_IFF_THLD, 4); + regmap_write(regmap_i2s, I2S_RX_IRQ_OFF_THLD, 4); + + regmap_read(regmap_i2s, I2S_RX_CFG_2, &slavemode); + slavemode = slavemode & I2S_RX_SLAVE_MODE_MASK; + if (!slavemode) { + regmap_read(regmap_i2s, I2S_TX_CFG, &enabled); + enabled = enabled & I2S_TX_ENABLE_MASK; + if (enabled) + regmap_update_bits(regmap_i2s, I2S_TX_CFG_2, + I2S_TX_SLAVE_MODE_MASK, + I2S_TX_MASTER_MODE); + } + + regmap_update_bits(regmap_i2s, I2S_RX_CFG_2, + I2S_RX_SLAVE_MODE_MASK, I2S_RX_SLAVE_MODE); + } +} + +static const struct snd_soc_dai_ops bcm63xx_i2s_dai_ops = { + .startup = bcm63xx_i2s_startup, + .shutdown = bcm63xx_i2s_shutdown, + .hw_params = bcm63xx_i2s_hw_params, +}; + +static struct snd_soc_dai_driver bcm63xx_i2s_dai = { + .name = DRV_NAME, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &bcm63xx_i2s_dai_ops, + .symmetric_rates = 1, + .symmetric_channels = 1, +}; + +static const struct snd_soc_component_driver bcm63xx_i2s_component = { + .name = "bcm63xx", +}; + +static int bcm63xx_i2s_dev_probe(struct platform_device *pdev) +{ + int ret = 0; + void __iomem *regs; + struct resource *r_mem, *region; + struct bcm_i2s_priv *i2s_priv; + struct regmap *regmap_i2s; + struct clk *i2s_clk; + + i2s_priv = devm_kzalloc(&pdev->dev, sizeof(*i2s_priv), GFP_KERNEL); + if (!i2s_priv) + return -ENOMEM; + + i2s_clk = devm_clk_get(&pdev->dev, "i2sclk"); + if (IS_ERR(i2s_clk)) { + dev_err(&pdev->dev, "%s: cannot get a brcm clock: %ld\n", + __func__, PTR_ERR(i2s_clk)); + return PTR_ERR(i2s_clk); + } + + r_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r_mem) { + dev_err(&pdev->dev, "Unable to get register resource.\n"); + return -ENODEV; + } + + region = devm_request_mem_region(&pdev->dev, r_mem->start, + resource_size(r_mem), DRV_NAME); + if (!region) { + dev_err(&pdev->dev, "Memory region already claimed\n"); + return -EBUSY; + } + + regs = devm_ioremap_resource(&pdev->dev, r_mem); + if (IS_ERR(regs)) { + ret = PTR_ERR(regs); + return ret; + } + + regmap_i2s = devm_regmap_init_mmio(&pdev->dev, + regs, &brcm_i2s_regmap_config); + if (IS_ERR(regmap_i2s)) + return PTR_ERR(regmap_i2s); + + regmap_update_bits(regmap_i2s, I2S_MISC_CFG, + I2S_PAD_LVL_LOOP_DIS_MASK, + I2S_PAD_LVL_LOOP_DIS_ENABLE); + + ret = devm_snd_soc_register_component(&pdev->dev, + &bcm63xx_i2s_component, + &bcm63xx_i2s_dai, 1); + if (ret) { + dev_err(&pdev->dev, "failed to register the dai\n"); + return ret; + } + + i2s_priv->dev = &pdev->dev; + i2s_priv->i2s_clk = i2s_clk; + i2s_priv->regmap_i2s = regmap_i2s; + dev_set_drvdata(&pdev->dev, i2s_priv); + + ret = bcm63xx_soc_platform_probe(pdev, i2s_priv); + if (ret) + dev_err(&pdev->dev, "failed to register the pcm\n"); + + return ret; +} + +static int bcm63xx_i2s_dev_remove(struct platform_device *pdev) +{ + bcm63xx_soc_platform_remove(pdev); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id snd_soc_bcm_audio_match[] = { + {.compatible = "brcm,bcm63xx-i2s"}, + { } +}; +#endif + +static struct platform_driver bcm63xx_i2s_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(snd_soc_bcm_audio_match), + }, + .probe = bcm63xx_i2s_dev_probe, + .remove = bcm63xx_i2s_dev_remove, +}; + +module_platform_driver(bcm63xx_i2s_driver); + +MODULE_AUTHOR("Kevin,Li "); +MODULE_DESCRIPTION("Broadcom DSL XPON ASOC I2S Interface"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/bcm/bcm63xx-i2s.h b/sound/soc/bcm/bcm63xx-i2s.h new file mode 100644 index 000000000000..4298700fa3c0 --- /dev/null +++ b/sound/soc/bcm/bcm63xx-i2s.h @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// linux/sound/soc/bcm/bcm63xx-i2s.h +// Copyright (c) 2020 Broadcom Corporation +// Author: Kevin-Ke Li + +#ifndef __BCM63XX_I2S_H +#define __BCM63XX_I2S_H + +#define I2S_DESC_FIFO_DEPTH 8 +#define I2S_MISC_CFG (0x003C) +#define I2S_PAD_LVL_LOOP_DIS_MASK (1 << 2) +#define I2S_PAD_LVL_LOOP_DIS_ENABLE I2S_PAD_LVL_LOOP_DIS_MASK + +#define I2S_TX_ENABLE_MASK (1 << 31) +#define I2S_TX_ENABLE I2S_TX_ENABLE_MASK +#define I2S_TX_OUT_R (1 << 19) +#define I2S_TX_DATA_ALIGNMENT (1 << 2) +#define I2S_TX_DATA_ENABLE (1 << 1) +#define I2S_TX_CLOCK_ENABLE (1 << 0) + +#define I2S_TX_DESC_OFF_LEVEL_SHIFT 12 +#define I2S_TX_DESC_OFF_LEVEL_MASK (0x0F << I2S_TX_DESC_OFF_LEVEL_SHIFT) +#define I2S_TX_DESC_IFF_LEVEL_SHIFT 8 +#define I2S_TX_DESC_IFF_LEVEL_MASK (0x0F << I2S_TX_DESC_IFF_LEVEL_SHIFT) +#define I2S_TX_DESC_OFF_INTR_EN_MSK (1 << 1) +#define I2S_TX_DESC_OFF_INTR_EN I2S_TX_DESC_OFF_INTR_EN_MSK + +#define I2S_TX_CFG (0x0000) +#define I2S_TX_IRQ_CTL (0x0004) +#define I2S_TX_IRQ_EN (0x0008) +#define I2S_TX_IRQ_IFF_THLD (0x000c) +#define I2S_TX_IRQ_OFF_THLD (0x0010) +#define I2S_TX_DESC_IFF_ADDR (0x0014) +#define I2S_TX_DESC_IFF_LEN (0x0018) +#define I2S_TX_DESC_OFF_ADDR (0x001C) +#define I2S_TX_DESC_OFF_LEN (0x0020) +#define I2S_TX_CFG_2 (0x0024) +#define I2S_TX_SLAVE_MODE_SHIFT 13 +#define I2S_TX_SLAVE_MODE_MASK (1 << I2S_TX_SLAVE_MODE_SHIFT) +#define I2S_TX_SLAVE_MODE I2S_TX_SLAVE_MODE_MASK +#define I2S_TX_MASTER_MODE 0 +#define I2S_TX_INTR_MASK 0x0F + +#define I2S_RX_ENABLE_MASK (1 << 31) +#define I2S_RX_ENABLE I2S_RX_ENABLE_MASK +#define I2S_RX_IN_R (1 << 19) +#define I2S_RX_DATA_ALIGNMENT (1 << 2) +#define I2S_RX_CLOCK_ENABLE (1 << 0) + +#define I2S_RX_DESC_OFF_LEVEL_SHIFT 12 +#define I2S_RX_DESC_OFF_LEVEL_MASK (0x0F << I2S_RX_DESC_OFF_LEVEL_SHIFT) +#define I2S_RX_DESC_IFF_LEVEL_SHIFT 8 +#define I2S_RX_DESC_IFF_LEVEL_MASK (0x0F << I2S_RX_DESC_IFF_LEVEL_SHIFT) +#define I2S_RX_DESC_OFF_INTR_EN_MSK (1 << 1) +#define I2S_RX_DESC_OFF_INTR_EN I2S_RX_DESC_OFF_INTR_EN_MSK + +#define I2S_RX_CFG (0x0040) /* 20c0 */ +#define I2S_RX_IRQ_CTL (0x0044) +#define I2S_RX_IRQ_EN (0x0048) +#define I2S_RX_IRQ_IFF_THLD (0x004C) +#define I2S_RX_IRQ_OFF_THLD (0x0050) +#define I2S_RX_DESC_IFF_ADDR (0x0054) +#define I2S_RX_DESC_IFF_LEN (0x0058) +#define I2S_RX_DESC_OFF_ADDR (0x005C) +#define I2S_RX_DESC_OFF_LEN (0x0060) +#define I2S_RX_CFG_2 (0x0064) +#define I2S_RX_SLAVE_MODE_SHIFT 13 +#define I2S_RX_SLAVE_MODE_MASK (1 << I2S_RX_SLAVE_MODE_SHIFT) +#define I2S_RX_SLAVE_MODE I2S_RX_SLAVE_MODE_MASK +#define I2S_RX_MASTER_MODE 0 +#define I2S_RX_INTR_MASK 0x0F + +#define I2S_REG_MAX 0x007C + +struct bcm_i2s_priv { + struct device *dev; + struct resource *r_irq; + struct regmap *regmap_i2s; + struct clk *i2s_clk; + struct snd_pcm_substream *play_substream; + struct snd_pcm_substream *capture_substream; + struct i2s_dma_desc *play_dma_desc; + struct i2s_dma_desc *capture_dma_desc; +}; + +extern int bcm63xx_soc_platform_probe(struct platform_device *pdev, + struct bcm_i2s_priv *i2s_priv); +extern int bcm63xx_soc_platform_remove(struct platform_device *pdev); + +#endif diff --git a/sound/soc/bcm/bcm63xx-pcm-whistler.c b/sound/soc/bcm/bcm63xx-pcm-whistler.c new file mode 100644 index 000000000000..55c760f1cf4d --- /dev/null +++ b/sound/soc/bcm/bcm63xx-pcm-whistler.c @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// linux/sound/bcm/bcm63xx-pcm-whistler.c +// BCM63xx whistler pcm interface +// Copyright (c) 2020 Broadcom Corporation +// Author: Kevin-Ke Li + +#include +#include +#include +#include +#include +#include +#include +#include "bcm63xx-i2s.h" + + +struct i2s_dma_desc { + unsigned char *dma_area; + dma_addr_t dma_addr; + unsigned int dma_len; +}; + +struct bcm63xx_runtime_data { + int dma_len; + dma_addr_t dma_addr; + dma_addr_t dma_addr_next; +}; + +static const struct snd_pcm_hardware bcm63xx_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S32_LE, /* support S32 only */ + .period_bytes_max = 8192 - 32, + .periods_min = 1, + .periods_max = PAGE_SIZE/sizeof(struct i2s_dma_desc), + .buffer_bytes_max = 128 * 1024, + .fifo_size = 32, +}; + +static int bcm63xx_pcm_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct i2s_dma_desc *dma_desc; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + + dma_desc = kzalloc(sizeof(*dma_desc), GFP_NOWAIT); + if (!dma_desc) + return -ENOMEM; + + snd_soc_dai_set_dma_data(rtd->cpu_dai, substream, dma_desc); + + return 0; +} + +static int bcm63xx_pcm_hw_free(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct i2s_dma_desc *dma_desc; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + dma_desc = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + kfree(dma_desc); + snd_pcm_set_runtime_buffer(substream, NULL); + + return 0; +} + +static int bcm63xx_pcm_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_soc_pcm_runtime *rtd; + struct bcm_i2s_priv *i2s_priv; + struct regmap *regmap_i2s; + + rtd = substream->private_data; + i2s_priv = dev_get_drvdata(rtd->cpu_dai->dev); + regmap_i2s = i2s_priv->regmap_i2s; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + regmap_update_bits(regmap_i2s, + I2S_TX_IRQ_EN, + I2S_TX_DESC_OFF_INTR_EN, + I2S_TX_DESC_OFF_INTR_EN); + regmap_update_bits(regmap_i2s, + I2S_TX_CFG, + I2S_TX_ENABLE_MASK, + I2S_TX_ENABLE); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + regmap_write(regmap_i2s, + I2S_TX_IRQ_EN, + 0); + regmap_update_bits(regmap_i2s, + I2S_TX_CFG, + I2S_TX_ENABLE_MASK, + 0); + break; + default: + ret = -EINVAL; + } + } else { + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + regmap_update_bits(regmap_i2s, + I2S_RX_IRQ_EN, + I2S_RX_DESC_OFF_INTR_EN_MSK, + I2S_RX_DESC_OFF_INTR_EN); + regmap_update_bits(regmap_i2s, + I2S_RX_CFG, + I2S_RX_ENABLE_MASK, + I2S_RX_ENABLE); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + regmap_update_bits(regmap_i2s, + I2S_RX_IRQ_EN, + I2S_RX_DESC_OFF_INTR_EN_MSK, + 0); + regmap_update_bits(regmap_i2s, + I2S_RX_CFG, + I2S_RX_ENABLE_MASK, + 0); + break; + default: + ret = -EINVAL; + } + } + return ret; +} + +static int bcm63xx_pcm_prepare(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct i2s_dma_desc *dma_desc; + struct regmap *regmap_i2s; + struct bcm_i2s_priv *i2s_priv; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + uint32_t regaddr_desclen, regaddr_descaddr; + + dma_desc = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + dma_desc->dma_len = snd_pcm_lib_period_bytes(substream); + dma_desc->dma_addr = runtime->dma_addr; + dma_desc->dma_area = runtime->dma_area; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + regaddr_desclen = I2S_TX_DESC_IFF_LEN; + regaddr_descaddr = I2S_TX_DESC_IFF_ADDR; + } else { + regaddr_desclen = I2S_RX_DESC_IFF_LEN; + regaddr_descaddr = I2S_RX_DESC_IFF_ADDR; + } + + i2s_priv = dev_get_drvdata(rtd->cpu_dai->dev); + regmap_i2s = i2s_priv->regmap_i2s; + + regmap_write(regmap_i2s, regaddr_desclen, dma_desc->dma_len); + regmap_write(regmap_i2s, regaddr_descaddr, dma_desc->dma_addr); + + return 0; +} + +static snd_pcm_uframes_t +bcm63xx_pcm_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + snd_pcm_uframes_t x; + struct bcm63xx_runtime_data *prtd = substream->runtime->private_data; + + if ((void *)prtd->dma_addr_next == NULL) + prtd->dma_addr_next = substream->runtime->dma_addr; + + x = bytes_to_frames(substream->runtime, + prtd->dma_addr_next - substream->runtime->dma_addr); + + return x == substream->runtime->buffer_size ? 0 : x; +} + +static int bcm63xx_pcm_mmap(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_wc(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); + +} + +static int bcm63xx_pcm_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct bcm63xx_runtime_data *prtd; + + runtime->hw = bcm63xx_pcm_hardware; + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32); + if (ret) + goto out; + + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32); + if (ret) + goto out; + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + goto out; + + ret = -ENOMEM; + prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); + if (!prtd) + goto out; + + runtime->private_data = prtd; + return 0; +out: + return ret; +} + +static int bcm63xx_pcm_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct bcm63xx_runtime_data *prtd = runtime->private_data; + + kfree(prtd); + return 0; +} + +static irqreturn_t i2s_dma_isr(int irq, void *bcm_i2s_priv) +{ + unsigned int availdepth, ifflevel, offlevel, int_status, val_1, val_2; + struct bcm63xx_runtime_data *prtd; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + struct regmap *regmap_i2s; + struct i2s_dma_desc *dma_desc; + struct snd_soc_pcm_runtime *rtd; + struct bcm_i2s_priv *i2s_priv; + + i2s_priv = (struct bcm_i2s_priv *)bcm_i2s_priv; + regmap_i2s = i2s_priv->regmap_i2s; + + /* rx */ + regmap_read(regmap_i2s, I2S_RX_IRQ_CTL, &int_status); + + if (int_status & I2S_RX_DESC_OFF_INTR_EN_MSK) { + substream = i2s_priv->capture_substream; + runtime = substream->runtime; + rtd = substream->private_data; + prtd = runtime->private_data; + dma_desc = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + offlevel = (int_status & I2S_RX_DESC_OFF_LEVEL_MASK) >> + I2S_RX_DESC_OFF_LEVEL_SHIFT; + while (offlevel) { + regmap_read(regmap_i2s, I2S_RX_DESC_OFF_ADDR, &val_1); + regmap_read(regmap_i2s, I2S_RX_DESC_OFF_LEN, &val_2); + offlevel--; + } + prtd->dma_addr_next = val_1 + val_2; + ifflevel = (int_status & I2S_RX_DESC_IFF_LEVEL_MASK) >> + I2S_RX_DESC_IFF_LEVEL_SHIFT; + + availdepth = I2S_DESC_FIFO_DEPTH - ifflevel; + while (availdepth) { + dma_desc->dma_addr += + snd_pcm_lib_period_bytes(substream); + dma_desc->dma_area += + snd_pcm_lib_period_bytes(substream); + if (dma_desc->dma_addr - runtime->dma_addr >= + runtime->dma_bytes) { + dma_desc->dma_addr = runtime->dma_addr; + dma_desc->dma_area = runtime->dma_area; + } + + prtd->dma_addr = dma_desc->dma_addr; + regmap_write(regmap_i2s, I2S_RX_DESC_IFF_LEN, + snd_pcm_lib_period_bytes(substream)); + regmap_write(regmap_i2s, I2S_RX_DESC_IFF_ADDR, + dma_desc->dma_addr); + availdepth--; + } + + snd_pcm_period_elapsed(substream); + + /* Clear interrupt by writing 0 */ + regmap_update_bits(regmap_i2s, I2S_RX_IRQ_CTL, + I2S_RX_INTR_MASK, 0); + } + + /* tx */ + regmap_read(regmap_i2s, I2S_TX_IRQ_CTL, &int_status); + + if (int_status & I2S_TX_DESC_OFF_INTR_EN_MSK) { + substream = i2s_priv->play_substream; + runtime = substream->runtime; + rtd = substream->private_data; + prtd = runtime->private_data; + dma_desc = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + + offlevel = (int_status & I2S_TX_DESC_OFF_LEVEL_MASK) >> + I2S_TX_DESC_OFF_LEVEL_SHIFT; + while (offlevel) { + regmap_read(regmap_i2s, I2S_TX_DESC_OFF_ADDR, &val_1); + regmap_read(regmap_i2s, I2S_TX_DESC_OFF_LEN, &val_2); + prtd->dma_addr_next = val_1 + val_2; + offlevel--; + } + + ifflevel = (int_status & I2S_TX_DESC_IFF_LEVEL_MASK) >> + I2S_TX_DESC_IFF_LEVEL_SHIFT; + availdepth = I2S_DESC_FIFO_DEPTH - ifflevel; + + while (availdepth) { + dma_desc->dma_addr += + snd_pcm_lib_period_bytes(substream); + dma_desc->dma_area += + snd_pcm_lib_period_bytes(substream); + + if (dma_desc->dma_addr - runtime->dma_addr >= + runtime->dma_bytes) { + dma_desc->dma_addr = runtime->dma_addr; + dma_desc->dma_area = runtime->dma_area; + } + + prtd->dma_addr = dma_desc->dma_addr; + regmap_write(regmap_i2s, I2S_TX_DESC_IFF_LEN, + snd_pcm_lib_period_bytes(substream)); + regmap_write(regmap_i2s, I2S_TX_DESC_IFF_ADDR, + dma_desc->dma_addr); + availdepth--; + } + + snd_pcm_period_elapsed(substream); + + /* Clear interrupt by writing 0 */ + regmap_update_bits(regmap_i2s, I2S_TX_IRQ_CTL, + I2S_TX_INTR_MASK, 0); + } + + return IRQ_HANDLED; +} + +static int bcm63xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = bcm63xx_pcm_hardware.buffer_bytes_max; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + + buf->area = dma_alloc_wc(pcm->card->dev, + size, &buf->addr, + GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + buf->bytes = size; + return 0; +} + +static int bcm63xx_soc_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + struct snd_pcm *pcm = rtd->pcm; + struct bcm_i2s_priv *i2s_priv; + int ret; + + i2s_priv = dev_get_drvdata(rtd->cpu_dai->dev); + + of_dma_configure(pcm->card->dev, pcm->card->dev->of_node, 1); + + ret = dma_coerce_mask_and_coherent(pcm->card->dev, DMA_BIT_MASK(32)); + if (ret) + goto out; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + ret = bcm63xx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + + i2s_priv->play_substream = + pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = bcm63xx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + i2s_priv->capture_substream = + pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + } + +out: + return ret; +} + +static void bcm63xx_pcm_free_dma_buffers(struct snd_soc_component *component, + struct snd_pcm *pcm) +{ + int stream; + struct snd_dma_buffer *buf; + struct snd_pcm_substream *substream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + buf = &substream->dma_buffer; + if (!buf->area) + continue; + dma_free_wc(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} + +static const struct snd_soc_component_driver bcm63xx_soc_platform = { + .open = bcm63xx_pcm_open, + .close = bcm63xx_pcm_close, + .hw_params = bcm63xx_pcm_hw_params, + .hw_free = bcm63xx_pcm_hw_free, + .prepare = bcm63xx_pcm_prepare, + .trigger = bcm63xx_pcm_trigger, + .pointer = bcm63xx_pcm_pointer, + .mmap = bcm63xx_pcm_mmap, + .pcm_construct = bcm63xx_soc_pcm_new, + .pcm_destruct = bcm63xx_pcm_free_dma_buffers, +}; + +int bcm63xx_soc_platform_probe(struct platform_device *pdev, + struct bcm_i2s_priv *i2s_priv) +{ + int ret; + + i2s_priv->r_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!i2s_priv->r_irq) { + dev_err(&pdev->dev, "Unable to get register irq resource.\n"); + return -ENODEV; + } + + ret = devm_request_irq(&pdev->dev, i2s_priv->r_irq->start, i2s_dma_isr, + i2s_priv->r_irq->flags, "i2s_dma", (void *)i2s_priv); + if (ret) { + dev_err(&pdev->dev, + "i2s_init: failed to request interrupt.ret=%d\n", ret); + return ret; + } + + return devm_snd_soc_register_component(&pdev->dev, + &bcm63xx_soc_platform, NULL, 0); +} + +int bcm63xx_soc_platform_remove(struct platform_device *pdev) +{ + return 0; +} + +MODULE_AUTHOR("Kevin,Li "); +MODULE_DESCRIPTION("Broadcom DSL XPON ASOC PCM Interface"); +MODULE_LICENSE("GPL v2");