Received: by 2002:a25:6193:0:0:0:0:0 with SMTP id v141csp878190ybb; Fri, 20 Mar 2020 09:31:43 -0700 (PDT) X-Google-Smtp-Source: ADFU+vt1nHv7ij6CnqlF/phcT3yqfrkDedRHoM1gT1nmbOlrV+6QqzdbfHf6PUcdgV2o+MYmt9da X-Received: by 2002:aca:4243:: with SMTP id p64mr7234632oia.21.1584721903632; Fri, 20 Mar 2020 09:31:43 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1584721903; cv=none; d=google.com; s=arc-20160816; b=H/Fze3Gr7nxXy3I+cqL/rlR0bo/0yWlpyesDBO1x4VUHR5Qneevg1SuQYgySUeEYW0 lTSoHclI5x+cMXCm2owXvfyETBiGmMCMW5BiHrknINTXI6yg8DnjrH0QaUUAb0d/GADB esJ1jUZIG1BdyzuRrfhAVqMCBZBqdTyCQjdbOSiA9dEq1ROmTKCuBFdGNBuFYmlwWKUt tMf7ZSCEpq76tudTB4C7ztBm1G8c3eXvcFd4yjLscL0fPAfWDZz/NiQGgUa7hms2bUD2 cG1Cb5RILhP5MF/LGMSSidcZrWmVd9mB53YyTb4NUfkZVg3elS4hl74GAX1DyOGnB7W4 ivYA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:in-reply-to:content-disposition :mime-version:references:mail-followup-to:message-id:subject:cc:to :from:date; bh=OnPDsnmREHvkaPYS5b/Xvabh3iLbtOGI0TgzMwFDiAc=; b=QuRsI2UTjay9W1oLTxFMXzGTqCDqrV83Doc9hkeT3PMgkVIAuPqhbIGJUU+3kop+O0 cR/VLfEV9COtWs5zdU8NG7tuHSJKZQk9R8pcmwtTevExvIYXZ1PfFx2rvf/08E+MiN+0 fDTCRA5KP1PGHWkFbsS2pk+g/uEnKlgJ9O6wr8iMIpCfADfQ7LUYWAEh2Mo6Rwkglce+ YxZW3k0vVXk6tpRlk/wmkMIO1wwLI+1/RFBX48L/+x8R9PxNKNA37lkhbbwUXi1Ip811 uKe8yN3EPm1yrb+b6ep9DE4Ta23vzQIVWYgDvqWyYv8DO0YHqLQjedjejhXuKWAue6C+ MiOw== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id l15si3025254oil.50.2020.03.20.09.31.30; Fri, 20 Mar 2020 09:31:43 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727464AbgCTQa4 (ORCPT + 99 others); Fri, 20 Mar 2020 12:30:56 -0400 Received: from mout.kundenserver.de ([212.227.126.131]:37595 "EHLO mout.kundenserver.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727016AbgCTQaz (ORCPT ); Fri, 20 Mar 2020 12:30:55 -0400 Received: from mail.cetitecgmbh.com ([87.190.42.90]) by mrelayeu.kundenserver.de (mreue010 [212.227.15.167]) with ESMTPSA (Nemesis) id 1MIxFi-1j0OzX1n2I-00KU1t for ; Fri, 20 Mar 2020 17:30:53 +0100 Received: from pflvmailgateway.corp.cetitec.com (unknown [127.0.0.1]) by mail.cetitecgmbh.com (Postfix) with ESMTP id 4217564FAEC for ; Fri, 20 Mar 2020 16:30:53 +0000 (UTC) X-Virus-Scanned: amavisd-new at cetitec.com Received: from mail.cetitecgmbh.com ([127.0.0.1]) by pflvmailgateway.corp.cetitec.com (pflvmailgateway.corp.cetitec.com [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 3jYkd_vjzyRg for ; Fri, 20 Mar 2020 17:30:52 +0100 (CET) Received: from pfwsexchange.corp.cetitec.com (unknown [10.10.1.99]) by mail.cetitecgmbh.com (Postfix) with ESMTPS id 9D8BA64D99D for ; Fri, 20 Mar 2020 17:30:52 +0100 (CET) Received: from pflmari.corp.cetitec.com (10.8.5.41) by PFWSEXCHANGE.corp.cetitec.com (10.10.1.99) with Microsoft SMTP Server (TLS) id 15.0.1497.2; Fri, 20 Mar 2020 17:30:52 +0100 Received: by pflmari.corp.cetitec.com (Postfix, from userid 1000) id D50EA80502; Fri, 20 Mar 2020 17:12:00 +0100 (CET) Date: Fri, 20 Mar 2020 17:12:00 +0100 From: Alex Riesen To: Kieran Bingham CC: Geert Uytterhoeven , Mauro Carvalho Chehab , Hans Verkuil , "Laurent Pinchart" , Rob Herring , Mark Rutland , Kuninori Morimoto , , , , , , linux-clk Subject: [PATCH v3 05/11] media: adv748x: add support for HDMI audio Message-ID: <82828e89ccf4173de4e5e52dcecacc4d5168315c.1584720678.git.alexander.riesen@cetitec.com> Mail-Followup-To: Alex Riesen , Kieran Bingham , Geert Uytterhoeven , Mauro Carvalho Chehab , Hans Verkuil , Laurent Pinchart , Rob Herring , Mark Rutland , Kuninori Morimoto , devel@driverdev.osuosl.org, linux-media@vger.kernel.org, linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, linux-renesas-soc@vger.kernel.org, linux-clk References: MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Disposition: inline In-Reply-To: X-Originating-IP: [10.8.5.41] X-ClientProxiedBy: PFWSEXCHANGE.corp.cetitec.com (10.10.1.99) To PFWSEXCHANGE.corp.cetitec.com (10.10.1.99) X-EsetResult: clean, is OK X-EsetId: 37303A290D7F536A6D7660 X-Provags-ID: V03:K1:zbdNkjONDyI0bXEOrQFXrAPs8YEW9NGxRN+3dW18NQHVLgq1SnS 641nTidSrFU8AuuZ1pxshE52DDikUNQz0wur8TcicRpyz4L08mdJ0A2qRZ1Aw7vdfmrCJ2N LcymxtbxI4enZ0lsoJpQvUOoOK8ai8QVO4Q7CQZjwudbWS5a8w44MLIhaW6g/O9kjJPTMfW RXRAb4mUDg+0biJAZtO/Q== X-Spam-Flag: NO X-UI-Out-Filterresults: notjunk:1;V03:K0:8ew4NHf+HHg=:+Gp/g/H86TCginWu0j2Lvc Yu1G2MmMBFGvjgveSKCf4d5DBwiQ5ENujWJokR7trTduNbYGb/RDLqNuw5hCp/76gc3aQlDLA Tik14OvoVNlmJKUFT9sisJ9N4ZOclFAcboHhoTVpKTSm0NcsjFgE9i37CKZ5E8zc+Lj/mJmRe 240WJHqh4bP6TsSv/JVrvsIIg+jYnJFW5dPTyjOhmHVmIsIn5ocdM5X+lNTpAFCsdJBfGvSJi h0E52X+uULBfrfnSe/ysHITcMXK2RsBgwGrD4IBz/2X5IZF8lc4bRtw13EOswwsCg0/s69It0 3NFN7005XsIeksHl4MIuS1gHmRuJIaTEptJ/uPtvwp2T7N7kA/0pUmEP9fQl6Onkq7YPWIKQQ LkVhYPdWhs9LQXFa89Ad+x6tLxLGs7y+i5qxG83PTPfkJpOdhZ+DV+T1cwiFXrHhbARwJ7n7d 3Vw/ozLbJwCy06RFpmi2GImhBbniOjNU/0vsa6tvZFrnOUBhDaiXFa/pJNd1GDRnS2F/LJ7od d83bkMA4qpHRhXF59NuQTIVKBNYSDi1wxckxFSqWi0QxnzemZnZFudvDqjumYPs5d/UtjTq4o rGaSb9+XV6kqZ/NKKgJ6dAH7MqCRvFUWye1DfBpfgff+iOqC8M/4ian6de97yfmxAkW/88FHK mxgraHcMr4xvDopeg9jfmLSZY+gEoCHSNUFl8XF+CWrCSjUAm4EcpmecsLTcOOMR/pygqXmDG y5UhqskzyCJyoZEPTJCLCJ8Mk4zEoFG4qWKzBgE5XBrAv4AgZmCBqHZMfjBjp13hhmGKzNt4Q pDEHxtkSlwPUR4e6wm2HWXG69amZ8WMC851ZzJYc+Lv/N3icRuleY4v7Ds2NKkMXu0FD+iT Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org This adds an implemention of SoC DAI driver which provides access to the I2S port of the device. Signed-off-by: Alexander Riesen -- v3: fix clock registration in case of multiple adv748x devices Suggested-by: Geert Uytterhoeven --- drivers/media/i2c/adv748x/Makefile | 3 +- drivers/media/i2c/adv748x/adv748x-core.c | 9 +- drivers/media/i2c/adv748x/adv748x-dai.c | 265 +++++++++++++++++++++++ drivers/media/i2c/adv748x/adv748x.h | 17 +- 4 files changed, 291 insertions(+), 3 deletions(-) create mode 100644 drivers/media/i2c/adv748x/adv748x-dai.c diff --git a/drivers/media/i2c/adv748x/Makefile b/drivers/media/i2c/adv748x/Makefile index 93844f14cb10..6e7a302ef199 100644 --- a/drivers/media/i2c/adv748x/Makefile +++ b/drivers/media/i2c/adv748x/Makefile @@ -3,6 +3,7 @@ adv748x-objs := \ adv748x-afe.o \ adv748x-core.o \ adv748x-csi2.o \ - adv748x-hdmi.o + adv748x-hdmi.o \ + adv748x-dai.o obj-$(CONFIG_VIDEO_ADV748X) += adv748x.o diff --git a/drivers/media/i2c/adv748x/adv748x-core.c b/drivers/media/i2c/adv748x/adv748x-core.c index 8580e6624276..3513ca138e53 100644 --- a/drivers/media/i2c/adv748x/adv748x-core.c +++ b/drivers/media/i2c/adv748x/adv748x-core.c @@ -765,8 +765,14 @@ static int adv748x_probe(struct i2c_client *client) goto err_cleanup_txa; } + ret = adv748x_dai_init(&state->dai); + if (ret) { + adv_err(state, "Failed to probe DAI\n"); + goto err_cleanup_txb; + } return 0; - +err_cleanup_txb: + adv748x_csi2_cleanup(&state->txb); err_cleanup_txa: adv748x_csi2_cleanup(&state->txa); err_cleanup_afe: @@ -787,6 +793,7 @@ static int adv748x_remove(struct i2c_client *client) { struct adv748x_state *state = i2c_get_clientdata(client); + adv748x_dai_cleanup(&state->dai); adv748x_afe_cleanup(&state->afe); adv748x_hdmi_cleanup(&state->hdmi); diff --git a/drivers/media/i2c/adv748x/adv748x-dai.c b/drivers/media/i2c/adv748x/adv748x-dai.c new file mode 100644 index 000000000000..6fce7d000423 --- /dev/null +++ b/drivers/media/i2c/adv748x/adv748x-dai.c @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Analog Devices ADV748X HDMI receiver with AFE + * The implementation of DAI. + */ + +#include "adv748x.h" + +#include +#include +#include + +#define state_of(soc_dai) \ + adv748x_dai_to_state(container_of((soc_dai)->driver, \ + struct adv748x_dai, \ + drv)) + +static const char ADV748X_DAI_NAME[] = "adv748x-i2s"; + +static int set_audio_pads_state(struct adv748x_state *state, int on) +{ + return io_clrset(state, ADV748X_IO_PAD_CONTROLS, + ADV748X_IO_PAD_CONTROLS_TRI_AUD | + ADV748X_IO_PAD_CONTROLS_PDN_AUD, + on ? 0 : 0xff); +} + +static int set_dpll_mclk_fs(struct adv748x_state *state, int fs) +{ + return dpll_clrset(state, ADV748X_DPLL_MCLK_FS, + ADV748X_DPLL_MCLK_FS_N_MASK, (fs / 128) - 1); +} + +static int set_i2s_format(struct adv748x_state *state, uint outmode, + uint bitwidth) +{ + return hdmi_clrset(state, ADV748X_HDMI_I2S, + ADV748X_HDMI_I2SBITWIDTH_MASK | + ADV748X_HDMI_I2SOUTMODE_MASK, + (outmode << ADV748X_HDMI_I2SOUTMODE_SHIFT) | + bitwidth); +} + +static int set_i2s_tdm_mode(struct adv748x_state *state, int is_tdm) +{ + int ret; + + ret = hdmi_clrset(state, ADV748X_HDMI_AUDIO_MUTE_SPEED, + ADV748X_MAN_AUDIO_DL_BYPASS | + ADV748X_AUDIO_DELAY_LINE_BYPASS, + is_tdm ? 0xff : 0); + if (ret < 0) + return ret; + ret = hdmi_clrset(state, ADV748X_HDMI_REG_6D, + ADV748X_I2S_TDM_MODE_ENABLE, + is_tdm ? 0xff : 0); + return ret; +} + +static int set_audio_mute(struct adv748x_state *state, int enable) +{ + return hdmi_clrset(state, ADV748X_HDMI_MUTE_CTRL, + ADV748X_HDMI_MUTE_CTRL_MUTE_AUDIO, + enable ? 0xff : 0); +} + +static int adv748x_dai_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct adv748x_state *state = state_of(dai); + + /* currently supporting only one fixed rate clock */ + if (clk_id != 0 || freq != clk_get_rate(state->dai.mclk)) { + dev_err(dai->dev, "invalid clock (%d) or frequency (%u, dir %d)\n", + clk_id, freq, dir); + return -EINVAL; + } + state->dai.freq = freq; + return 0; +} + +static int adv748x_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct adv748x_state *state = state_of(dai); + int ret = 0; + + if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBM_CFM) { + dev_err(dai->dev, "only I2S master clock mode supported\n"); + ret = -EINVAL; + goto done; + } + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAI_FORMAT_I2S: + state->dai.tdm = 0; + state->dai.fmt = ADV748X_I2SOUTMODE_I2S; + break; + case SND_SOC_DAI_FORMAT_RIGHT_J: + state->dai.tdm = 1; + state->dai.fmt = ADV748X_I2SOUTMODE_RIGHT_J; + break; + case SND_SOC_DAI_FORMAT_LEFT_J: + state->dai.tdm = 1; + state->dai.fmt = ADV748X_I2SOUTMODE_LEFT_J; + break; + default: + dev_err(dai->dev, "only i2s, left_j and right_j supported\n"); + ret = -EINVAL; + goto done; + } + if ((fmt & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF) { + dev_err(dai->dev, "only normal bit clock + frame supported\n"); + ret = -EINVAL; + } +done: + return ret; +} + +static int adv748x_dai_startup(struct snd_pcm_substream *sub, struct snd_soc_dai *dai) +{ + struct adv748x_state *state = state_of(dai); + + if (sub->stream != SNDRV_PCM_STREAM_CAPTURE) + return -EINVAL; + return set_audio_pads_state(state, 1); +} + +static int adv748x_dai_hw_params(struct snd_pcm_substream *sub, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + int ret; + struct adv748x_state *state = state_of(dai); + uint fs = state->dai.freq / params_rate(params); + + dev_dbg(dai->dev, "dai %s substream %s rate=%u (fs=%u), channels=%u sample width=%u(%u)\n", + dai->name, sub->name, + params_rate(params), fs, + params_channels(params), + params_width(params), + params_physical_width(params)); + switch (fs) { + case 128: + case 256: + case 384: + case 512: + case 640: + case 768: + break; + default: + ret = -EINVAL; + dev_err(dai->dev, "invalid clock frequency (%u) or rate (%u)\n", + state->dai.freq, params_rate(params)); + goto done; + } + ret = set_dpll_mclk_fs(state, fs); + if (ret) + goto done; + ret = set_i2s_tdm_mode(state, state->dai.tdm); + if (ret) + goto done; + ret = set_i2s_format(state, state->dai.fmt, params_width(params)); +done: + return ret; +} + +static int adv748x_dai_mute_stream(struct snd_soc_dai *dai, int mute, int dir) +{ + struct adv748x_state *state = state_of(dai); + + return set_audio_mute(state, mute); +} + +static void adv748x_dai_shutdown(struct snd_pcm_substream *sub, struct snd_soc_dai *dai) +{ + struct adv748x_state *state = state_of(dai); + + set_audio_pads_state(state, 0); +} + +static const struct snd_soc_dai_ops adv748x_dai_ops = { + .set_sysclk = adv748x_dai_set_sysclk, + .set_fmt = adv748x_dai_set_fmt, + .startup = adv748x_dai_startup, + .hw_params = adv748x_dai_hw_params, + .mute_stream = adv748x_dai_mute_stream, + .shutdown = adv748x_dai_shutdown, +}; + +static int adv748x_of_xlate_dai_name(struct snd_soc_component *component, + struct of_phandle_args *args, + const char **dai_name) +{ + if (dai_name) + *dai_name = ADV748X_DAI_NAME; + return 0; +} + +static const struct snd_soc_component_driver adv748x_codec = { + .of_xlate_dai_name = adv748x_of_xlate_dai_name, +}; + +int adv748x_dai_init(struct adv748x_dai *dai) +{ + int ret; + struct adv748x_state *state = adv748x_dai_to_state(dai); + + dai->mclk_name = kasprintf(GFP_KERNEL, "%s.%s-i2s-mclk", + state->dev->driver->name, + dev_name(state->dev)); + if (!dai->mclk_name) { + ret = -ENOMEM; + adv_err(state, "No memory for MCLK\n"); + goto fail; + } + dai->mclk = clk_register_fixed_rate(state->dev, + dai->mclk_name, + NULL /* parent_name */, + 0 /* flags */, + 12288000 /* rate */); + if (IS_ERR_OR_NULL(dai->mclk)) { + ret = PTR_ERR(dai->mclk); + adv_err(state, "Failed to register MCLK (%d)\n", ret); + goto fail; + } + ret = of_clk_add_provider(state->dev->of_node, of_clk_src_simple_get, + dai->mclk); + if (ret < 0) { + adv_err(state, "Failed to add MCLK provider (%d)\n", ret); + goto unreg_mclk; + } + dai->drv.name = ADV748X_DAI_NAME; + dai->drv.ops = &adv748x_dai_ops; + dai->drv.capture = (struct snd_soc_pcm_stream){ + .stream_name = "Capture", + .channels_min = 8, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_U24_LE, + }; + + ret = devm_snd_soc_register_component(state->dev, &adv748x_codec, + &dai->drv, 1); + if (ret < 0) { + adv_err(state, "Failed to register the codec (%d)\n", ret); + goto cleanup_mclk; + } + return 0; + +cleanup_mclk: + of_clk_del_provider(state->dev->of_node); +unreg_mclk: + clk_unregister_fixed_rate(dai->mclk); +fail: + return ret; +} + +void adv748x_dai_cleanup(struct adv748x_dai *dai) +{ + struct adv748x_state *state = adv748x_dai_to_state(dai); + + of_clk_del_provider(state->dev->of_node); + clk_unregister_fixed_rate(dai->mclk); + kfree(dai->mclk_name); +} + diff --git a/drivers/media/i2c/adv748x/adv748x.h b/drivers/media/i2c/adv748x/adv748x.h index 1a1ea70086c6..b0735de0ced5 100644 --- a/drivers/media/i2c/adv748x/adv748x.h +++ b/drivers/media/i2c/adv748x/adv748x.h @@ -19,6 +19,7 @@ */ #include +#include #include #include @@ -63,7 +64,8 @@ enum adv748x_ports { ADV748X_PORT_TTL = 9, ADV748X_PORT_TXA = 10, ADV748X_PORT_TXB = 11, - ADV748X_PORT_MAX = 12, + ADV748X_PORT_I2S = 12, + ADV748X_PORT_MAX = 13, }; enum adv748x_csi2_pads { @@ -166,6 +168,13 @@ struct adv748x_afe { container_of(ctrl->handler, struct adv748x_afe, ctrl_hdl) #define adv748x_sd_to_afe(sd) container_of(sd, struct adv748x_afe, sd) +struct adv748x_dai { + struct snd_soc_dai_driver drv; + struct clk *mclk; + char *mclk_name; + unsigned int freq, fmt, tdm; +}; + /** * struct adv748x_state - State of ADV748X * @dev: (OF) device @@ -182,6 +191,7 @@ struct adv748x_afe { * @afe: state of AFE receiver context * @txa: state of TXA transmitter context * @txb: state of TXB transmitter context + * @mclk: MCLK clock of the I2S port */ struct adv748x_state { struct device *dev; @@ -197,10 +207,12 @@ struct adv748x_state { struct adv748x_afe afe; struct adv748x_csi2 txa; struct adv748x_csi2 txb; + struct adv748x_dai dai; }; #define adv748x_hdmi_to_state(h) container_of(h, struct adv748x_state, hdmi) #define adv748x_afe_to_state(a) container_of(a, struct adv748x_state, afe) +#define adv748x_dai_to_state(p) container_of(p, struct adv748x_state, dai) #define adv_err(a, fmt, arg...) dev_err(a->dev, fmt, ##arg) #define adv_info(a, fmt, arg...) dev_info(a->dev, fmt, ##arg) @@ -484,4 +496,7 @@ int adv748x_csi2_set_pixelrate(struct v4l2_subdev *sd, s64 rate); int adv748x_hdmi_init(struct adv748x_hdmi *hdmi); void adv748x_hdmi_cleanup(struct adv748x_hdmi *hdmi); +int adv748x_dai_init(struct adv748x_dai *); +void adv748x_dai_cleanup(struct adv748x_dai *); + #endif /* _ADV748X_H_ */ -- 2.25.1.25.g9ecbe7eb18