From patchwork Wed Dec 24 16:42:06 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kenneth Westfield X-Patchwork-Id: 5539971 Return-Path: X-Original-To: patchwork-linux-arm-msm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork2.web.kernel.org (Postfix) with ESMTP id 47844BEEA8 for ; Wed, 24 Dec 2014 16:42:38 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 09EA5201D3 for ; Wed, 24 Dec 2014 16:42:37 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id B454B201F4 for ; Wed, 24 Dec 2014 16:42:35 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752015AbaLXQme (ORCPT ); Wed, 24 Dec 2014 11:42:34 -0500 Received: from smtp.codeaurora.org ([198.145.11.231]:55171 "EHLO smtp.codeaurora.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751504AbaLXQmc (ORCPT ); Wed, 24 Dec 2014 11:42:32 -0500 Received: from smtp.codeaurora.org (localhost [127.0.0.1]) by smtp.codeaurora.org (Postfix) with ESMTP id B133513FAE7; Wed, 24 Dec 2014 16:42:32 +0000 (UTC) Received: by smtp.codeaurora.org (Postfix, from userid 486) id 9A28513FB26; Wed, 24 Dec 2014 16:42:32 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Spam-Level: X-Spam-Status: No, score=-6.9 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, T_RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 Received: from localhost (i-global254.qualcomm.com [199.106.103.254]) (using TLSv1.2 with cipher DHE-RSA-AES128-SHA (128/128 bits)) (No client certificate requested) (Authenticated sender: kwestfie@smtp.codeaurora.org) by smtp.codeaurora.org (Postfix) with ESMTPSA id A14FD13FB26; Wed, 24 Dec 2014 16:42:31 +0000 (UTC) From: Kenneth Westfield To: Mark Brown , Takashi Iwai , Liam Girdwood , David Brown , Bryan Huntsman , Greg KH , Banajit Goswami , Patrick Lai Cc: ALSA Mailing List , MSM Mailing List , Device Tree Mailing List , Kenneth Westfield Subject: [Patch V3 06/10] ASoC: ipq806x: Add LPASS CPU DAI driver Date: Wed, 24 Dec 2014 08:42:06 -0800 Message-Id: <1419439330-2303-7-git-send-email-kwestfie@codeaurora.org> X-Mailer: git-send-email 1.8.2.1 In-Reply-To: <1419439330-2303-1-git-send-email-kwestfie@codeaurora.org> References: <1419439330-2303-1-git-send-email-kwestfie@codeaurora.org> X-Virus-Scanned: ClamAV using ClamSMTP Sender: linux-arm-msm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-arm-msm@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Kenneth Westfield Add the CPU DAI driver for the QCOM LPASS SOC. Signed-off-by: Kenneth Westfield Acked-by: Banajit Goswami --- sound/soc/qcom/lpass-cpu-mi2s.c | 378 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 378 insertions(+) create mode 100644 sound/soc/qcom/lpass-cpu-mi2s.c diff --git a/sound/soc/qcom/lpass-cpu-mi2s.c b/sound/soc/qcom/lpass-cpu-mi2s.c new file mode 100644 index 0000000000000000000000000000000000000000..dc1bd53a78c3d6c097909dd240b6580efae19577 --- /dev/null +++ b/sound/soc/qcom/lpass-cpu-mi2s.c @@ -0,0 +1,378 @@ +/* + * Copyright (c) 2010-2011,2013-2014 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include "lpass-lpaif-reg.h" +#include "lpass-mi2s.h" + +#define DRV_NAME "lpass-cpu-mi2s" + +#define LPASS_OSR_TO_BIT_DIVIDER 4 + +static inline int lpass_lpaif_mi2s_config(struct snd_soc_dai *dai, + unsigned int channels, unsigned int bitwidth) +{ + struct lpass_mi2s_data *drvdata = snd_soc_dai_get_drvdata(dai); + u32 mi2s_control_offset = LPAIF_MI2S_CTL_OFFSET(LPAIF_I2S_PORT_MI2S); + u32 value; + + value = 0; + + value &= ~LPAIF_MI2SCTL_WS; + + switch (bitwidth) { + case 16: + value |= LPAIF_MI2SCTL_BITWIDTH_16; + break; + case 24: + value |= LPAIF_MI2SCTL_BITWIDTH_24; + break; + case 32: + value |= LPAIF_MI2SCTL_BITWIDTH_32; + break; + default: + dev_err(dai->dev, "%s: invalid bitwidth given: %u\n", __func__, + bitwidth); + return -EINVAL; + } + + switch (channels) { + case 1: + value |= LPAIF_MI2SCTL_SPKMODE_SD0; + value |= LPAIF_MI2SCTL_SPKMONO_MONO; + break; + case 2: + value |= LPAIF_MI2SCTL_SPKMODE_SD0; + value |= LPAIF_MI2SCTL_SPKMONO_STEREO; + break; + case 4: + value |= LPAIF_MI2SCTL_SPKMODE_QUAD01; + value |= LPAIF_MI2SCTL_SPKMONO_STEREO; + break; + case 6: + value |= LPAIF_MI2SCTL_SPKMODE_6CH; + value |= LPAIF_MI2SCTL_SPKMONO_STEREO; + break; + case 8: + value |= LPAIF_MI2SCTL_SPKMODE_8CH; + value |= LPAIF_MI2SCTL_SPKMONO_STEREO; + break; + default: + dev_err(dai->dev, "%s: invalid channels given: %u\n", __func__, + channels); + return -EINVAL; + } + + writel(value, drvdata->base + mi2s_control_offset); + + return 0; +} + +static inline void lpass_lpaif_mi2s_playback_start(struct snd_soc_dai *dai) +{ + struct lpass_mi2s_data *drvdata = snd_soc_dai_get_drvdata(dai); + u32 mi2s_control_offset = LPAIF_MI2S_CTL_OFFSET(LPAIF_I2S_PORT_MI2S); + u32 value; + + value = readl(drvdata->base + mi2s_control_offset); + value |= LPAIF_MI2SCTL_SPKEN; + writel(value, drvdata->base + mi2s_control_offset); +} + +static inline void lpass_lpaif_mi2s_playback_stop(struct snd_soc_dai *dai) +{ + struct lpass_mi2s_data *drvdata = snd_soc_dai_get_drvdata(dai); + u32 mi2s_control_offset = LPAIF_MI2S_CTL_OFFSET(LPAIF_I2S_PORT_MI2S); + u32 value; + + value = readl(drvdata->base + mi2s_control_offset); + value &= ~LPAIF_MI2SCTL_SPKEN; + writel(value, drvdata->base + mi2s_control_offset); +} + +static inline void lpass_lpaif_mi2s_playback_stop_clear(struct snd_soc_dai *dai) +{ + struct lpass_mi2s_data *drvdata = snd_soc_dai_get_drvdata(dai); + u32 mi2s_control_offset = LPAIF_MI2S_CTL_OFFSET(LPAIF_I2S_PORT_MI2S); + + writel(0, drvdata->base + mi2s_control_offset); +} + +static int lpass_cpu_mi2s_daiops_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct lpass_mi2s_data *drvdata = snd_soc_dai_get_drvdata(dai); + int ret; + + ret = clk_prepare_enable(drvdata->mi2s_osr_clk); + if (ret) { + dev_err(dai->dev, "%s: error in enabling mi2s osr clk: %d\n", + __func__, ret); + return ret; + } + + ret = clk_prepare_enable(drvdata->mi2s_bit_clk); + if (ret) { + dev_err(dai->dev, "%s: error in enabling mi2s bit clk: %d\n", + __func__, ret); + clk_disable_unprepare(drvdata->mi2s_osr_clk); + return ret; + } + + return 0; +} + +static void lpass_cpu_mi2s_daiops_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct lpass_mi2s_data *drvdata = snd_soc_dai_get_drvdata(dai); + + clk_disable_unprepare(drvdata->mi2s_bit_clk); + clk_disable_unprepare(drvdata->mi2s_osr_clk); +} + +static int lpass_cpu_mi2s_daiops_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct lpass_mi2s_data *drvdata = snd_soc_dai_get_drvdata(dai); + snd_pcm_format_t format = params_format(params); + unsigned int channels = params_channels(params); + unsigned int rate = params_rate(params); + int bitwidth; + int ret; + + bitwidth = snd_pcm_format_width(format); + if (bitwidth < 0) { + dev_err(dai->dev, "%s: Invalid bit width given\n", __func__); + return bitwidth; + } + + ret = lpass_lpaif_mi2s_config(dai, channels, bitwidth); + if (ret) { + dev_err(dai->dev, "%s: Channel setting unsuccessful\n", + __func__); + return -EINVAL; + } + + ret = clk_set_rate(drvdata->mi2s_osr_clk, + (rate * bitwidth * channels * LPASS_OSR_TO_BIT_DIVIDER)); + if (ret) { + dev_err(dai->dev, "%s: error in setting mi2s osr clk: %d\n", + __func__, ret); + return ret; + } + + ret = clk_set_rate(drvdata->mi2s_bit_clk, rate * bitwidth * channels); + if (ret) { + dev_err(dai->dev, "%s: error in setting mi2s bit clk: %d\n", + __func__, ret); + return ret; + } + + return 0; +} + +static int lpass_cpu_mi2s_daiops_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + lpass_lpaif_mi2s_playback_stop_clear(dai); + + return 0; +} + +static int lpass_cpu_mi2s_daiops_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + lpass_lpaif_mi2s_playback_start(dai); + + return 0; +} + +static int lpass_cpu_mi2s_daiops_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct lpass_mi2s_data *drvdata = snd_soc_dai_get_drvdata(dai); + int ret; + + ret = clk_set_rate(drvdata->mi2s_osr_clk, freq); + if (ret) { + dev_err(dai->dev, "%s: error in setting mi2s osr clk: %d\n", + __func__, ret); + return ret; + } + + return 0; +} + +static struct snd_soc_dai_ops lpass_cpu_mi2s_dai_ops = { + .startup = lpass_cpu_mi2s_daiops_startup, + .shutdown = lpass_cpu_mi2s_daiops_shutdown, + .hw_params = lpass_cpu_mi2s_daiops_hw_params, + .hw_free = lpass_cpu_mi2s_daiops_hw_free, + .prepare = lpass_cpu_mi2s_daiops_prepare, + .set_sysclk = lpass_cpu_mi2s_daiops_set_sysclk, +}; + +static int lpass_cpu_mi2s_dai_probe(struct snd_soc_dai *dai) +{ + struct lpass_mi2s_data *drvdata = snd_soc_dai_get_drvdata(dai); + + drvdata->mi2s_osr_clk = devm_clk_get(dai->dev, "mi2s_osr_clk"); + if (IS_ERR(drvdata->mi2s_osr_clk)) { + dev_err(dai->dev, "%s: Error in getting mi2s_osr_clk\n", + __func__); + return PTR_ERR(drvdata->mi2s_osr_clk); + } + + drvdata->mi2s_bit_clk = devm_clk_get(dai->dev, "mi2s_bit_clk"); + if (IS_ERR(drvdata->mi2s_bit_clk)) { + dev_err(dai->dev, "%s: Error in getting mi2s_bit_clk\n", + __func__); + return PTR_ERR(drvdata->mi2s_bit_clk); + } + + /* ensure MI2S port is disabled */ + lpass_lpaif_mi2s_playback_stop_clear(dai); + + return 0; +} + +static struct snd_soc_dai_driver lpass_cpu_mi2s_dai_driver = { + .playback = { + .stream_name = "lpass-cpu-mi2s-playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 8, + }, + .probe = &lpass_cpu_mi2s_dai_probe, + .ops = &lpass_cpu_mi2s_dai_ops, +}; + +static const struct snd_soc_component_driver lpass_cpu_mi2s_comp_driver = { + .name = DRV_NAME, +}; + +static int lpass_cpu_mi2s_platform_probe(struct platform_device *pdev) +{ + struct device_node *of_node = pdev->dev.of_node; + struct lpass_mi2s_data *drvdata; + struct resource *lpass_res; + u32 freq; + int ret; + + drvdata = devm_kzalloc(&pdev->dev, sizeof(struct lpass_mi2s_data), + GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + platform_set_drvdata(pdev, drvdata); + + lpass_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "lpass-lpaif-mem"); + if (!lpass_res) { + dev_err(&pdev->dev, "%s: error getting resource\n", __func__); + return -ENODEV; + } + + drvdata->base = devm_ioremap_resource(&pdev->dev, lpass_res); + if (IS_ERR(drvdata->base)) { + dev_err(&pdev->dev, "%s: error remapping resource\n", + __func__); + return PTR_ERR(drvdata->base); + } + + drvdata->irqnum = platform_get_irq_byname(pdev, "lpass-lpaif-irq"); + if (drvdata->irqnum < 0) { + dev_err(&pdev->dev, "%s: failed get irq res\n", __func__); + return -ENODEV; + } + + drvdata->ahbix_clk = devm_clk_get(&pdev->dev, "ahbix_clk"); + if (IS_ERR(drvdata->ahbix_clk)) { + dev_err(&pdev->dev, "%s: Error getting ahbix_clk\n", __func__); + return PTR_ERR(drvdata->ahbix_clk); + } + + if (of_property_read_bool(of_node, "ahbix-frequency")) + of_property_read_u32(of_node, "ahbix-frequency", &freq); + clk_set_rate(drvdata->ahbix_clk, freq); + + ret = clk_prepare_enable(drvdata->ahbix_clk); + if (ret) { + dev_err(&pdev->dev, "%s: Error enabling ahbix_clk\n", __func__); + return ret; + } + + ret = devm_snd_soc_register_component(&pdev->dev, + &lpass_cpu_mi2s_comp_driver, + &lpass_cpu_mi2s_dai_driver, 1); + if (ret) { + dev_err(&pdev->dev, "%s: error registering soc dai\n", + __func__); + goto err_clk; + } + + ret = lpass_pcm_mi2s_platform_register(&pdev->dev); + if (ret) { + dev_err(&pdev->dev, "%s: error registering pcm device\n", + __func__); + goto err_clk; + } + + return 0; + +err_clk: + clk_disable_unprepare(drvdata->ahbix_clk); + return ret; +} + +static int lpass_cpu_mi2s_platform_remove(struct platform_device *pdev) +{ + struct lpass_mi2s_data *drvdata = platform_get_drvdata(pdev); + + clk_disable_unprepare(drvdata->ahbix_clk); + + return 0; +} + +static const struct of_device_id lpass_cpu_mi2s_dt_match[] = { + {.compatible = "qcom,lpass-cpu-mi2s"}, + {} +}; + +static struct platform_driver lpass_cpu_mi2s_platform_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = lpass_cpu_mi2s_dt_match, + }, + .probe = lpass_cpu_mi2s_platform_probe, + .remove = lpass_cpu_mi2s_platform_remove, +}; +module_platform_driver(lpass_cpu_mi2s_platform_driver); + +MODULE_DESCRIPTION("QCOM LPASS MI2S CPU DRIVER"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, lpass_cpu_mi2s_dt_match);