From 9bfd5db1e01af8ab5c74f109c21d4f74655a9ea5 Mon Sep 17 00:00:00 2001 From: Matt Redfearn Date: Wed, 8 Jul 2015 16:06:52 +0100 Subject: [PATCH 01/10] drm: jz4780_hdmi: add hdmi audio config interfaces Original patch: https://lkml.org/lkml/2014/12/14/336 create dw-hdmi-audio device in probe function, and support some interfaces to dw-hdmi-audio driver for setting hdmi audio format. Signed-off-by: Matt Redfearn --- drivers/gpu/drm/jz4780/dwc_hdmi.c | 33 +++++++++++++++++++++++++++++++ drivers/gpu/drm/jz4780/dwc_hdmi.h | 13 ++++++++++++ 2 files changed, 46 insertions(+) diff --git a/drivers/gpu/drm/jz4780/dwc_hdmi.c b/drivers/gpu/drm/jz4780/dwc_hdmi.c index bccd86234081c7..8375f7e8305663 100644 --- a/drivers/gpu/drm/jz4780/dwc_hdmi.c +++ b/drivers/gpu/drm/jz4780/dwc_hdmi.c @@ -75,6 +75,14 @@ static inline u8 hdmi_readb(struct dwc_hdmi *hdmi, int offset) return readb(hdmi->regs + (offset << hdmi->reg_shift)); } +static void hdmi_modb(struct dwc_hdmi *hdmi, u8 data, u8 mask, unsigned reg) +{ + u8 val = hdmi_readb(hdmi, reg) & ~mask; + + val |= data & mask; + hdmi_writeb(hdmi, val, reg); +} + static void hdmi_mask_writeb(struct dwc_hdmi *hdmi, u8 data, unsigned int reg, u8 shift, u8 mask) { @@ -313,6 +321,12 @@ static void hdmi_clk_regenerator_update_pixel_clock(struct dwc_hdmi *hdmi) hdmi_set_clk_regenerator(hdmi); } +static void hdmi_set_sample_rate(struct dwc_hdmi *hdmi, unsigned int sample_rate) +{ + hdmi->sample_rate = sample_rate; + hdmi_set_clk_regenerator(hdmi); +} + /* * this submodule is responsible for the video data synchronization. * for example, for RGB 4:4:4 input, the data map is defined as @@ -1676,6 +1690,8 @@ MODULE_DEVICE_TABLE(of, dwc_hdmi_dt_ids); static int dwc_hdmi_platform_probe(struct platform_device *pdev) { + struct platform_device_info pdevinfo; + struct dwc_hdmi_audio_data audio; const struct of_device_id *of_id = of_match_device(dwc_hdmi_dt_ids, &pdev->dev); struct device_node *np = pdev->dev.of_node; @@ -1791,6 +1807,23 @@ static int dwc_hdmi_platform_probe(struct platform_device *pdev) platform_set_drvdata(pdev, hdmi); + memset(&pdevinfo, 0, sizeof(pdevinfo)); + pdevinfo.parent = &pdev->dev; + pdevinfo.id = PLATFORM_DEVID_NONE; + + audio.irq = irq; + audio.dw = hdmi; + audio.mod = hdmi_modb; + audio.read = hdmi_readb; + audio.write = hdmi_writeb; + audio.set_sample_rate = hdmi_set_sample_rate; + + pdevinfo.name = "dw-hdmi-audio"; + pdevinfo.data = &audio; + pdevinfo.size_data = sizeof(audio); + pdevinfo.dma_mask = DMA_BIT_MASK(32); + hdmi->audio_pdev = platform_device_register_full(&pdevinfo); + return 0; err_iahb: diff --git a/drivers/gpu/drm/jz4780/dwc_hdmi.h b/drivers/gpu/drm/jz4780/dwc_hdmi.h index a626fdc4827a52..b88730cad7204b 100644 --- a/drivers/gpu/drm/jz4780/dwc_hdmi.h +++ b/drivers/gpu/drm/jz4780/dwc_hdmi.h @@ -55,6 +55,18 @@ enum dwc_hdmi_devtype { DWC_HDMI, }; +struct dwc_hdmi; + +struct dwc_hdmi_audio_data { + int irq; + struct dwc_hdmi *dw; + + u8 (*read)(struct dwc_hdmi *hdmi, int offset); + void (*write)(struct dwc_hdmi *hdmi, u8 val, int offset); + void (*mod)(struct dwc_hdmi *hdmi, u8 data, u8 mask, unsigned reg); + void (*set_sample_rate)(struct dwc_hdmi *hdmi, unsigned int rate); +}; + struct dwc_hdmi { struct drm_connector connector; struct dwc_drm_connector *dwc_drm_connector; @@ -63,6 +75,7 @@ struct dwc_hdmi { struct drm_device *drm; + struct platform_device *audio_pdev; enum dwc_hdmi_devtype dev_type; struct device *dev; struct clk *isfr_clk; From 39348c3c2d731839d3412577577bd0e1eb78a62f Mon Sep 17 00:00:00 2001 From: Matt Redfearn Date: Wed, 8 Jul 2015 16:22:35 +0100 Subject: [PATCH 02/10] drm: jz4780_hdmi: add codec driver for dw hdmi audio Original patch: https://lkml.org/lkml/2014/12/14/334 codec driver get some interfaces from dw_hdmi driver, than using those to set hdmi audio formats, corresponding to alsa formats. Signed-off-by: Matt Redfearn --- drivers/gpu/drm/jz4780/Kconfig | 8 + drivers/gpu/drm/jz4780/Makefile | 1 + drivers/gpu/drm/jz4780/dw-hdmi-audio.c | 376 +++++++++++++++++++++++++ drivers/gpu/drm/jz4780/dw-hdmi-audio.h | 82 ++++++ drivers/gpu/drm/jz4780/dwc_hdmi.c | 2 +- 5 files changed, 468 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/jz4780/dw-hdmi-audio.c create mode 100644 drivers/gpu/drm/jz4780/dw-hdmi-audio.h diff --git a/drivers/gpu/drm/jz4780/Kconfig b/drivers/gpu/drm/jz4780/Kconfig index de559dbf291d68..0263436a712370 100644 --- a/drivers/gpu/drm/jz4780/Kconfig +++ b/drivers/gpu/drm/jz4780/Kconfig @@ -11,3 +11,11 @@ config DRM_JZ4780 help Choose this option if you have an Ingenic SoC with LCDC display controller, for example the CI20 board + +config DRM_JZ4780_HDMI_AUDIO + tristate "Support for Ingenic JZ4780 HDMI Audio" + depends on DRM_JZ4780 && SND_JZ4740_SOC_I2S + select CI20_HDMI_AUDIO + help + Choose this option to enable support for audio over HDMI + \ No newline at end of file diff --git a/drivers/gpu/drm/jz4780/Makefile b/drivers/gpu/drm/jz4780/Makefile index 6af98da8052fe1..607afcd7049892 100644 --- a/drivers/gpu/drm/jz4780/Makefile +++ b/drivers/gpu/drm/jz4780/Makefile @@ -9,3 +9,4 @@ jz4780-y := \ jz4780_drv.o obj-$(CONFIG_DRM_JZ4780) += jz4780.o +obj-$(CONFIG_DRM_JZ4780_HDMI_AUDIO) += dw-hdmi-audio.o \ No newline at end of file diff --git a/drivers/gpu/drm/jz4780/dw-hdmi-audio.c b/drivers/gpu/drm/jz4780/dw-hdmi-audio.c new file mode 100644 index 00000000000000..24e76610e40ac4 --- /dev/null +++ b/drivers/gpu/drm/jz4780/dw-hdmi-audio.c @@ -0,0 +1,376 @@ +/* + * dw-hdmi-codec.c + * + * DesignerWare ALSA SoC DAI driver for DW HDMI audio. + * Copyright (c) 2014, CORPORATION. All rights reserved. + * Authors: Yakir Yang + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see .* + * + */ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "dwc_hdmi.h" +#include "dw-hdmi-audio.h" + +struct snd_dwc_hdmi { + struct device *dev; + struct dwc_hdmi_audio_data data; + + struct snd_soc_jack *jack; + struct delayed_work jack_work; + enum hdmi_jack_status jack_status; + enum hdmi_jack_status connect_status; +}; + +/** + * dw_hdmi_jack_detect - Enable hdmi detection via the HDMI IRQ + * + * @codec: HDMI codec + * @jack: jack to report detection events on + * + * Enable HDMI detection via IRQ on the HDMI. + * + * If no jack is supplied detection will be disabled. + */ +int dw_hdmi_jack_detect(struct snd_soc_codec *codec_dai, + struct snd_soc_jack *jack) +{ + struct snd_dwc_hdmi *hdmi = snd_soc_codec_get_drvdata(codec_dai); + + hdmi->jack = jack; + hdmi->jack_status = JACK_NO_LINEOUT; + + snd_soc_jack_report(hdmi->jack, 0, SND_JACK_LINEOUT); + + queue_delayed_work(system_power_efficient_wq, &hdmi->jack_work, + msecs_to_jiffies(50)); + + return 0; +} +EXPORT_SYMBOL_GPL(dw_hdmi_jack_detect); + +static void dw_hdmi_jack_work(struct work_struct *work) +{ + struct snd_dwc_hdmi *hdmi = container_of(work, struct snd_dwc_hdmi, + jack_work.work); + u8 status; + + status = hdmi->data.read(hdmi->data.dw, HDMI_PHY_STAT0) & HDMI_PHY_HPD; + if (status != hdmi->connect_status) { + hdmi->jack_status = status ? SND_JACK_LINEOUT : 0; + snd_soc_jack_report(hdmi->jack, hdmi->jack_status, + SND_JACK_LINEOUT); + dev_info(hdmi->dev, "jack report [%d]\n", hdmi->jack_status); + } + hdmi->connect_status = status; +} + +static irqreturn_t snd_dwc_hdmi_irq(int irq, void *dev_id) +{ + struct snd_dwc_hdmi *hdmi = dev_id; + + queue_delayed_work(system_power_efficient_wq, &hdmi->jack_work, + msecs_to_jiffies(50)); + + return IRQ_HANDLED; +} + +void enable_dw_hdmi_audio(struct snd_dwc_hdmi *hdmi) +{ + hdmi->data.mod(hdmi->data.dw, 0, HDMI_MC_CLKDIS_AUDCLK_DISABLE, + HDMI_MC_CLKDIS); +} + +void disable_dw_hdmi_audio(struct snd_dwc_hdmi *hdmi) +{ + hdmi->data.mod(hdmi->data.dw, 1, + HDMI_MC_CLKDIS_AUDCLK_DISABLE, HDMI_MC_CLKDIS); +} + +void set_dw_hdmi_audio_fmt(struct snd_dwc_hdmi *hdmi, struct hdmi_audio_fmt fmt) +{ + hdmi->data.mod(hdmi->data.dw, fmt.input_type, AUDIO_CONF0_INTERFACE_MSK, + HDMI_AUD_CONF0); + + hdmi->data.mod(hdmi->data.dw, fmt.chan_num, AUDIO_CONF0_I2SINEN_MSK, + HDMI_AUD_CONF0); + + hdmi->data.mod(hdmi->data.dw, fmt.word_length, AUDIO_CONF1_DATWIDTH_MSK, + HDMI_AUD_CONF1); + + hdmi->data.mod(hdmi->data.dw, fmt.dai_fmt, AUDIO_CONF1_DATAMODE_MSK, + HDMI_AUD_CONF1); + + hdmi->data.write(hdmi->data.dw, 0, HDMI_AUD_INPUTCLKFS); + + hdmi->data.set_sample_rate(hdmi->data.dw, fmt.sample_rate); +} + +static int dw_hdmi_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *codec_dai) +{ + struct snd_dwc_hdmi *hdmi = snd_soc_dai_get_drvdata(codec_dai); + + dev_info(codec_dai->dev, "[codec_dai]: startup.\n"); + enable_dw_hdmi_audio(hdmi); + + return 0; +} + +static int dw_hdmi_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *codec_dai) +{ + struct snd_dwc_hdmi *hdmi = snd_soc_dai_get_drvdata(codec_dai); + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct hdmi_audio_fmt hdmi_fmt; + unsigned int fmt, rate, chan, width; + + fmt = rtd->dai_link->dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK; + switch (fmt) { + case SND_SOC_DAIFMT_I2S: + hdmi_fmt.dai_fmt = AUDIO_DAIFMT_IIS; + break; + case SND_SOC_DAIFMT_LEFT_J: + hdmi_fmt.dai_fmt = AUDIO_DAIFMT_LEFT_J; + break; + case SND_SOC_DAIFMT_RIGHT_J: + hdmi_fmt.dai_fmt = AUDIO_DAIFMT_RIGHT_J; + break; + default: + dev_err(codec_dai->dev, "DAI format unsupported"); + return -EINVAL; + } + dev_info(codec_dai->dev, "[codec_dai]: dai_fmt = %d.\n", fmt); + + width = params_width(params); + switch (width) { + case 16: + case 24: + hdmi_fmt.word_length = width; + break; + default: + dev_err(codec_dai->dev, "width[%d] not support!\n", width); + return -EINVAL; + } + dev_info(codec_dai->dev, "[codec_dai]: word_length = %d.\n", width); + + chan = params_channels(params); + switch (chan) { + case 2: + hdmi_fmt.chan_num = AUDIO_CHANNELNUM_2; + break; + case 4: + hdmi_fmt.chan_num = AUDIO_CHANNELNUM_4; + break; + case 6: + hdmi_fmt.chan_num = AUDIO_CHANNELNUM_6; + break; + case 8: + hdmi_fmt.chan_num = AUDIO_CHANNELNUM_8; + break; + default: + dev_err(codec_dai->dev, "channel[%d] not support!\n", chan); + return -EINVAL; + } + dev_info(codec_dai->dev, "[codec_dai]: chan_num = %d.\n", chan); + + rate = params_rate(params); + switch (rate) { + case 32000: + case 44100: + case 48000: + case 88200: + case 96000: + case 176400: + case 192000: + hdmi_fmt.sample_rate = rate; + break; + default: + dev_err(codec_dai->dev, "rate[%d] not support!\n", rate); + return -EINVAL; + } + dev_info(codec_dai->dev, "[codec_dai]: sample_rate = %d.\n", rate); + + hdmi_fmt.input_type = AUDIO_INPUTTYPE_IIS; + + set_dw_hdmi_audio_fmt(hdmi, hdmi_fmt); + + return 0; +} + +static int dw_hdmi_dai_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *codec_dai) +{ + struct snd_dwc_hdmi *hdmi = snd_soc_dai_get_drvdata(codec_dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + disable_dw_hdmi_audio(hdmi); + dev_info(codec_dai->dev, "[codec_dai]: trigger enable.\n"); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + enable_dw_hdmi_audio(hdmi); + dev_info(codec_dai->dev, "[codec_dai]: trigger disable.\n"); + break; + default: + return -EINVAL; + } + + return 0; +} + +static void dw_hdmi_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *codec_dai) +{ + struct snd_dwc_hdmi *hdmi = snd_soc_dai_get_drvdata(codec_dai); + + dev_info(codec_dai->dev, "[codec_dai]: shutdown.\n"); + disable_dw_hdmi_audio(hdmi); +} + +static const struct snd_soc_dapm_widget dw_hdmi_widgets[] = { + SND_SOC_DAPM_OUTPUT("TX"), +}; + +static const struct snd_soc_dapm_route dw_hdmi_routes[] = { + { "TX", NULL, "Playback" }, +}; + +static const struct snd_soc_dai_ops dw_hdmi_dai_ops = { + .startup = dw_hdmi_dai_startup, + .hw_params = dw_hdmi_dai_hw_params, + .trigger = dw_hdmi_dai_trigger, + .shutdown = dw_hdmi_dai_shutdown, +}; + +static struct snd_soc_dai_driver dw_hdmi_audio_dai[] = { +{ + .name = "dw-hdmi-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + }, + .ops = &dw_hdmi_dai_ops, +} +}; + +static struct snd_soc_codec_driver dw_hdmi_audio = { + .dapm_widgets = dw_hdmi_widgets, + .num_dapm_widgets = ARRAY_SIZE(dw_hdmi_widgets), + .dapm_routes = dw_hdmi_routes, + .num_dapm_routes = ARRAY_SIZE(dw_hdmi_routes), +}; + +static int dw_hdmi_audio_probe(struct platform_device *pdev) +{ + struct dwc_hdmi_audio_data *data = pdev->dev.platform_data; + struct snd_dwc_hdmi *hdmi; + int ret; + + hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL); + if (!hdmi) + return -ENOMEM; + + hdmi->data = *data; + hdmi->dev = &pdev->dev; + platform_set_drvdata(pdev, hdmi); + + INIT_DELAYED_WORK(&hdmi->jack_work, dw_hdmi_jack_work); + + ret = devm_request_irq(&pdev->dev, hdmi->data.irq, snd_dwc_hdmi_irq, + IRQF_SHARED, "dw-hdmi-audio", hdmi); + if (ret) { + dev_err(&pdev->dev, "request irq failed (%d)\n", ret); + goto free_hdmi_data; + } + + ret = snd_soc_register_codec(&pdev->dev, &dw_hdmi_audio, + dw_hdmi_audio_dai, + ARRAY_SIZE(dw_hdmi_audio_dai)); + if (ret) { + dev_err(&pdev->dev, "register codec failed (%d)\n", ret); + goto free_irq; + } + + dev_info(&pdev->dev, "hdmi audio init success.\n"); + + return 0; + +free_irq: + devm_free_irq(&pdev->dev, hdmi->data.irq, hdmi); +free_hdmi_data: + devm_kfree(&pdev->dev, hdmi); + + return ret; +} + +static int dw_hdmi_audio_remove(struct platform_device *pdev) +{ + struct snd_dwc_hdmi *hdmi = platform_get_drvdata(pdev); + + snd_soc_unregister_codec(&pdev->dev); + devm_free_irq(&pdev->dev, hdmi->data.irq, hdmi); + devm_kfree(&pdev->dev, hdmi); + + return 0; +} + +static const struct of_device_id dw_hdmi_audio_ids[] = { + { .compatible = "dw-hdmi-audio", }, + { } +}; + +static struct platform_driver dw_hdmi_audio_driver = { + .driver = { + .name = "dw-hdmi-audio", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(dw_hdmi_audio_ids), + }, + .probe = dw_hdmi_audio_probe, + .remove = dw_hdmi_audio_remove, +}; +module_platform_driver(dw_hdmi_audio_driver); + +MODULE_AUTHOR("Yakir Yang "); +MODULE_DESCRIPTION("DW HDMI Audio ASoC Interface"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" "dw-hdmi-audio"); +MODULE_DEVICE_TABLE(of, dw_hdmi_audio_ids); diff --git a/drivers/gpu/drm/jz4780/dw-hdmi-audio.h b/drivers/gpu/drm/jz4780/dw-hdmi-audio.h new file mode 100644 index 00000000000000..38e2e813f67cec --- /dev/null +++ b/drivers/gpu/drm/jz4780/dw-hdmi-audio.h @@ -0,0 +1,82 @@ +/* + * dw-hdmi-audio.h -- DW HDMI ALSA SoC Audio driver + * + * Copyright 2011-2012 DesignerWare Products + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _DW_HDMI_AUDIO_H +#define _DW_HDMI_AUDIO_H + +enum hdmi_audio_reg { + HDMI_PHY_STAT0 = 0x3004, + HDMI_AUD_CONF0 = 0x3100, + HDMI_AUD_CONF1 = 0x3101, + HDMI_AUD_INPUTCLKFS = 0x3206, + HDMI_MC_CLKDIS = 0x4001, +}; + +enum { + HDMI_PHY_HPD = 0x2, + HDMI_MC_CLKDIS_AUDCLK_DISABLE = 0x8, +}; + +enum hdmi_audio_samplerate { + AUDIO_SAMPLERATE_32K = 32000, + AUDIO_SAMPLERATE_44K1 = 44100, + AUDIO_SAMPLERATE_48K = 48000, + AUDIO_SAMPLERATE_88K2 = 88200, + AUDIO_SAMPLERATE_96K = 96000, + AUDIO_SAMPLERATE_176K4 = 176400, + AUDIO_SAMPLERATE_192K = 192000, +}; + +#define AUDIO_CONF1_DATWIDTH_MSK 0x1F +enum hdmi_audio_wordlength { + AUDIO_WORDLENGTH_16BIT = 16, + AUDIO_WORDLENGTH_24BIT = 24, +}; + +#define AUDIO_CONF1_DATAMODE_MSK 0xE0 +enum hdmi_audio_daifmt { + AUDIO_DAIFMT_IIS = 0x00, + AUDIO_DAIFMT_RIGHT_J = 0x20, + AUDIO_DAIFMT_LEFT_J = 0x40, + AUDIO_DAIFMT_BURST_1 = 0x60, + AUDIO_DAIFMT_BURST_2 = 0x80, +}; + +#define AUDIO_CONF0_INTERFACE_MSK 0x20 +enum hdmi_audio_inputtype { + AUDIO_INPUTTYPE_IIS = 0x20, + AUDIO_INPUTTYPE_SPDIF = 0x00, +}; + +#define AUDIO_CONF0_I2SINEN_MSK 0x0F +enum hdmi_audio_channelnum { + AUDIO_CHANNELNUM_2 = 0x01, + AUDIO_CHANNELNUM_4 = 0x03, + AUDIO_CHANNELNUM_6 = 0x07, + AUDIO_CHANNELNUM_8 = 0x0F, +}; + +enum hdmi_jack_status { + JACK_LINEOUT, + JACK_NO_LINEOUT, +}; + +struct hdmi_audio_fmt { + enum hdmi_audio_inputtype input_type; + enum hdmi_audio_channelnum chan_num; + enum hdmi_audio_samplerate sample_rate; + enum hdmi_audio_wordlength word_length; + enum hdmi_audio_daifmt dai_fmt; +}; + +int dw_hdmi_jack_detect(struct snd_soc_codec *codec_dai, + struct snd_soc_jack *jack); + +#endif diff --git a/drivers/gpu/drm/jz4780/dwc_hdmi.c b/drivers/gpu/drm/jz4780/dwc_hdmi.c index 8375f7e8305663..9ee850c51d769f 100644 --- a/drivers/gpu/drm/jz4780/dwc_hdmi.c +++ b/drivers/gpu/drm/jz4780/dwc_hdmi.c @@ -1728,7 +1728,7 @@ static int dwc_hdmi_platform_probe(struct platform_device *pdev) if (irq < 0) return -EINVAL; - ret = devm_request_irq(&pdev->dev, irq, dwc_hdmi_irq, 0, + ret = devm_request_irq(&pdev->dev, irq, dwc_hdmi_irq, IRQF_SHARED, dev_name(&pdev->dev), hdmi); iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); From e8521a0b7bd87a7facb8e5c8bcf601524d2d6f64 Mon Sep 17 00:00:00 2001 From: Matt Redfearn Date: Thu, 9 Jul 2015 10:45:52 +0100 Subject: [PATCH 03/10] drm: jz4780-hdmi: Set Pixel clock / ratio These settings result in CTS / N values that agree with the Synopsis datasheet for the HDMI transmitter Signed-off-by: Matt Redfearn --- drivers/gpu/drm/jz4780/dwc_hdmi.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/jz4780/dwc_hdmi.c b/drivers/gpu/drm/jz4780/dwc_hdmi.c index 9ee850c51d769f..633eae9e99ef5d 100644 --- a/drivers/gpu/drm/jz4780/dwc_hdmi.c +++ b/drivers/gpu/drm/jz4780/dwc_hdmi.c @@ -260,12 +260,11 @@ static unsigned int hdmi_compute_cts(unsigned int freq, unsigned long pixel_clk, static void hdmi_get_pixel_clk(struct dwc_hdmi *hdmi) { - unsigned long rate; - - rate = 65000000; /* FIXME */ + struct hdmi_vmode *vmode = &hdmi->hdmi_data.video_mode; + + hdmi->pixel_clk_rate = vmode->mpixelclock; - if (rate) - hdmi->pixel_clk_rate = rate; + hdmi->ratio = 100; } static void hdmi_set_clk_regenerator(struct dwc_hdmi *hdmi) From 8a72b52b35c1fc81ca9c016601619fc1f9699ee6 Mon Sep 17 00:00:00 2001 From: Matt Redfearn Date: Thu, 9 Jul 2015 15:55:46 +0100 Subject: [PATCH 04/10] sound: soc: JZ4740: Allow External CODEC / master mode Only enable the internal codec when SND_SOC_DAIFMT_CBM_CFM mode is selected. Otherwise, we are the master to an external codec Signed-off-by: Matt Redfearn --- sound/soc/jz4740/jz4740-i2s.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/soc/jz4740/jz4740-i2s.c b/sound/soc/jz4740/jz4740-i2s.c index 85893d83e40923..a5d86e6cda01d1 100644 --- a/sound/soc/jz4740/jz4740-i2s.c +++ b/sound/soc/jz4740/jz4740-i2s.c @@ -210,7 +210,8 @@ static int jz4740_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF); - conf &= ~(JZ_AIC_CONF_BIT_CLK_MASTER | JZ_AIC_CONF_SYNC_CLK_MASTER); + conf &= ~(JZ_AIC_CONF_BIT_CLK_MASTER | JZ_AIC_CONF_SYNC_CLK_MASTER | + JZ_AIC_CONF_INTERNAL_CODEC); switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBS_CFS: @@ -224,6 +225,7 @@ static int jz4740_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) conf |= JZ_AIC_CONF_BIT_CLK_MASTER; break; case SND_SOC_DAIFMT_CBM_CFM: + conf |= JZ_AIC_CONF_INTERNAL_CODEC; break; default: return -EINVAL; From bbae837ebf15b17df398953316224634a1f5cafb Mon Sep 17 00:00:00 2001 From: Matt Redfearn Date: Tue, 14 Jul 2015 09:02:45 +0100 Subject: [PATCH 05/10] drm: jz4780_hdmi: Set up HDMI Infoframe registers The 3.0.8 kernel used Synopsis' HDMI driver which set up these infoframe registers telling the connected device about the audio stream. We now do the same. Signed-off-by: Matt Redfearn --- drivers/gpu/drm/jz4780/dw-hdmi-audio.c | 14 ++++++++++++++ drivers/gpu/drm/jz4780/dw-hdmi-audio.h | 13 ------------- drivers/gpu/drm/jz4780/dwc_hdmi_regs.h | 1 + 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/jz4780/dw-hdmi-audio.c b/drivers/gpu/drm/jz4780/dw-hdmi-audio.c index 24e76610e40ac4..5b7af3211fa72e 100644 --- a/drivers/gpu/drm/jz4780/dw-hdmi-audio.c +++ b/drivers/gpu/drm/jz4780/dw-hdmi-audio.c @@ -38,6 +38,7 @@ #include "dwc_hdmi.h" #include "dw-hdmi-audio.h" +#include "dwc_hdmi_regs.h" struct snd_dwc_hdmi { struct device *dev; @@ -116,6 +117,7 @@ void disable_dw_hdmi_audio(struct snd_dwc_hdmi *hdmi) void set_dw_hdmi_audio_fmt(struct snd_dwc_hdmi *hdmi, struct hdmi_audio_fmt fmt) { + int val; hdmi->data.mod(hdmi->data.dw, fmt.input_type, AUDIO_CONF0_INTERFACE_MSK, HDMI_AUD_CONF0); @@ -131,6 +133,18 @@ void set_dw_hdmi_audio_fmt(struct snd_dwc_hdmi *hdmi, struct hdmi_audio_fmt fmt) hdmi->data.write(hdmi->data.dw, 0, HDMI_AUD_INPUTCLKFS); hdmi->data.set_sample_rate(hdmi->data.dw, fmt.sample_rate); + + /* Set up the HDMI infoframe stuff too */ + + /* Set Channel Count (CC) */ + val = fmt.chan_num << 4; + hdmi->data.write(hdmi->data.dw, val, HDMI_FC_AUDICONF0); + + /* CA = 0 for stereo */ + hdmi->data.write(hdmi->data.dw, 0, HDMI_FC_AUDICONF2); + + /* Channels Valid */ + hdmi->data.write(hdmi->data.dw, 0xee, HDMI_FC_AUDSV); } static int dw_hdmi_dai_startup(struct snd_pcm_substream *substream, diff --git a/drivers/gpu/drm/jz4780/dw-hdmi-audio.h b/drivers/gpu/drm/jz4780/dw-hdmi-audio.h index 38e2e813f67cec..e2bda1b5166094 100644 --- a/drivers/gpu/drm/jz4780/dw-hdmi-audio.h +++ b/drivers/gpu/drm/jz4780/dw-hdmi-audio.h @@ -11,19 +11,6 @@ #ifndef _DW_HDMI_AUDIO_H #define _DW_HDMI_AUDIO_H -enum hdmi_audio_reg { - HDMI_PHY_STAT0 = 0x3004, - HDMI_AUD_CONF0 = 0x3100, - HDMI_AUD_CONF1 = 0x3101, - HDMI_AUD_INPUTCLKFS = 0x3206, - HDMI_MC_CLKDIS = 0x4001, -}; - -enum { - HDMI_PHY_HPD = 0x2, - HDMI_MC_CLKDIS_AUDCLK_DISABLE = 0x8, -}; - enum hdmi_audio_samplerate { AUDIO_SAMPLERATE_32K = 32000, AUDIO_SAMPLERATE_44K1 = 44100, diff --git a/drivers/gpu/drm/jz4780/dwc_hdmi_regs.h b/drivers/gpu/drm/jz4780/dwc_hdmi_regs.h index 30bedb490dbec8..2ee1143c2e919d 100644 --- a/drivers/gpu/drm/jz4780/dwc_hdmi_regs.h +++ b/drivers/gpu/drm/jz4780/dwc_hdmi_regs.h @@ -162,6 +162,7 @@ #define HDMI_FC_SPDDEVICEINF 0x1062 #define HDMI_FC_AUDSCONF 0x1063 #define HDMI_FC_AUDSSTAT 0x1064 +#define HDMI_FC_AUDSV 0x1065 #define HDMI_FC_DATACH0FILL 0x1070 #define HDMI_FC_DATACH1FILL 0x1071 #define HDMI_FC_DATACH2FILL 0x1072 From b2bde4bfe0a6da3fce9d89839cb3a09ef00ab4dd Mon Sep 17 00:00:00 2001 From: Matt Redfearn Date: Tue, 14 Jul 2015 09:04:17 +0100 Subject: [PATCH 06/10] drm: jz4780_hdmi: Reduce driver verbosity The driver from the LKML has lots of dev_info prints - change them to dev_debug to reduce the noise in the kernel log Signed-off-by: Matt Redfearn --- drivers/gpu/drm/jz4780/dw-hdmi-audio.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/jz4780/dw-hdmi-audio.c b/drivers/gpu/drm/jz4780/dw-hdmi-audio.c index 5b7af3211fa72e..36a2641e9e5a53 100644 --- a/drivers/gpu/drm/jz4780/dw-hdmi-audio.c +++ b/drivers/gpu/drm/jz4780/dw-hdmi-audio.c @@ -88,7 +88,7 @@ static void dw_hdmi_jack_work(struct work_struct *work) hdmi->jack_status = status ? SND_JACK_LINEOUT : 0; snd_soc_jack_report(hdmi->jack, hdmi->jack_status, SND_JACK_LINEOUT); - dev_info(hdmi->dev, "jack report [%d]\n", hdmi->jack_status); + dev_dbg(hdmi->dev, "jack report [%d]\n", hdmi->jack_status); } hdmi->connect_status = status; } @@ -152,7 +152,7 @@ static int dw_hdmi_dai_startup(struct snd_pcm_substream *substream, { struct snd_dwc_hdmi *hdmi = snd_soc_dai_get_drvdata(codec_dai); - dev_info(codec_dai->dev, "[codec_dai]: startup.\n"); + dev_dbg(codec_dai->dev, "[codec_dai]: startup.\n"); enable_dw_hdmi_audio(hdmi); return 0; @@ -182,7 +182,7 @@ static int dw_hdmi_dai_hw_params(struct snd_pcm_substream *substream, dev_err(codec_dai->dev, "DAI format unsupported"); return -EINVAL; } - dev_info(codec_dai->dev, "[codec_dai]: dai_fmt = %d.\n", fmt); + dev_dbg(codec_dai->dev, "[codec_dai]: dai_fmt = %d.\n", fmt); width = params_width(params); switch (width) { @@ -194,7 +194,7 @@ static int dw_hdmi_dai_hw_params(struct snd_pcm_substream *substream, dev_err(codec_dai->dev, "width[%d] not support!\n", width); return -EINVAL; } - dev_info(codec_dai->dev, "[codec_dai]: word_length = %d.\n", width); + dev_dbg(codec_dai->dev, "[codec_dai]: word_length = %d.\n", width); chan = params_channels(params); switch (chan) { @@ -214,7 +214,7 @@ static int dw_hdmi_dai_hw_params(struct snd_pcm_substream *substream, dev_err(codec_dai->dev, "channel[%d] not support!\n", chan); return -EINVAL; } - dev_info(codec_dai->dev, "[codec_dai]: chan_num = %d.\n", chan); + dev_dbg(codec_dai->dev, "[codec_dai]: chan_num = %d.\n", chan); rate = params_rate(params); switch (rate) { @@ -231,7 +231,7 @@ static int dw_hdmi_dai_hw_params(struct snd_pcm_substream *substream, dev_err(codec_dai->dev, "rate[%d] not support!\n", rate); return -EINVAL; } - dev_info(codec_dai->dev, "[codec_dai]: sample_rate = %d.\n", rate); + dev_dbg(codec_dai->dev, "[codec_dai]: sample_rate = %d.\n", rate); hdmi_fmt.input_type = AUDIO_INPUTTYPE_IIS; @@ -250,13 +250,13 @@ static int dw_hdmi_dai_trigger(struct snd_pcm_substream *substream, case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: disable_dw_hdmi_audio(hdmi); - dev_info(codec_dai->dev, "[codec_dai]: trigger enable.\n"); + dev_dbg(codec_dai->dev, "[codec_dai]: trigger enable.\n"); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: enable_dw_hdmi_audio(hdmi); - dev_info(codec_dai->dev, "[codec_dai]: trigger disable.\n"); + dev_dbg(codec_dai->dev, "[codec_dai]: trigger disable.\n"); break; default: return -EINVAL; @@ -270,7 +270,7 @@ static void dw_hdmi_dai_shutdown(struct snd_pcm_substream *substream, { struct snd_dwc_hdmi *hdmi = snd_soc_dai_get_drvdata(codec_dai); - dev_info(codec_dai->dev, "[codec_dai]: shutdown.\n"); + dev_dbg(codec_dai->dev, "[codec_dai]: shutdown.\n"); disable_dw_hdmi_audio(hdmi); } From 9911f54661342f67761b1535bf31af85224e6e33 Mon Sep 17 00:00:00 2001 From: Matt Redfearn Date: Fri, 10 Jul 2015 14:31:31 +0100 Subject: [PATCH 07/10] sound: soc: JZ4780 ci20: Working Headphones / HDMI Create 2 dai links to connect the I2S to either the internal codec or HDMI transmitter. Mplayer can now output to either device But they don't show up in the alsa mixer Signed-off-by: Matt Redfearn --- sound/soc/jz4780/ci20.c | 118 ++++++++++++++++++++++++++++++++++------ 1 file changed, 101 insertions(+), 17 deletions(-) diff --git a/sound/soc/jz4780/ci20.c b/sound/soc/jz4780/ci20.c index 451ecc679dbd78..05f1bfae4be8f6 100644 --- a/sound/soc/jz4780/ci20.c +++ b/sound/soc/jz4780/ci20.c @@ -32,6 +32,7 @@ #define GPIO_MIC_SW_EN 174 static struct snd_soc_jack ci20_hp_jack; +static struct snd_soc_jack ci20_hdmi_jack; static struct snd_soc_jack_pin ci20_hp_jack_pins[] = { { @@ -78,14 +79,59 @@ static int ci20_hp_event(struct snd_soc_dapm_widget *widget, static const struct snd_soc_dapm_widget ci20_widgets[] = { SND_SOC_DAPM_MIC("Mic", NULL), SND_SOC_DAPM_HP("Headphone Jack", ci20_hp_event), + SND_SOC_DAPM_LINE("HDMI", NULL), }; static const struct snd_soc_dapm_route ci20_routes[] = { {"Mic", NULL, "AIP2"}, {"Headphone Jack", NULL, "AOHPL"}, {"Headphone Jack", NULL, "AOHPR"}, + {"HDMI", NULL, "TX"}, }; +static int ci20_audio_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + unsigned int dai_fmt = rtd->dai_link->dai_fmt; + int mclk, ret; + + switch (params_rate(params)) { + case 8000: + case 16000: + case 24000: + case 32000: + case 48000: + case 64000: + case 96000: + mclk = 12288000; + break; + case 11025: + case 22050: + case 44100: + case 88200: + mclk = 11289600; + break; + default: + return -EINVAL; + } + + ret = snd_soc_dai_set_fmt(cpu_dai, dai_fmt); + if (ret < 0) { + dev_err(cpu_dai->dev, "failed to set cpu_dai fmt.\n"); + return ret; + } + + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, SND_SOC_CLOCK_OUT); + if (ret < 0) { + dev_err(cpu_dai->dev, "failed to set cpu_dai sysclk.\n"); + return ret; + } + + return 0; +} + static int ci20_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_codec *codec = rtd->codec; @@ -105,24 +151,58 @@ static int ci20_init(struct snd_soc_pcm_runtime *rtd) return 0; } +static int ci20_hdmi_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + + snd_soc_dapm_enable_pin(dapm, "HDMI"); + + /* Enable headphone jack detection */ + snd_soc_jack_new(codec, "HDMI Jack", SND_JACK_LINEOUT, + &ci20_hdmi_jack); + + /* Jack is connected (it just is) */ + snd_soc_jack_report(&ci20_hdmi_jack, SND_JACK_LINEOUT, SND_JACK_LINEOUT); + return 0; +} + +static struct snd_soc_ops ci20_audio_dai_ops = { + .hw_params = ci20_audio_hw_params, +}; + #define CI20_DAIFMT (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF \ | SND_SOC_DAIFMT_CBM_CFM) -static struct snd_soc_dai_link ci20_dai_link = { - .name = "ci20", - .stream_name = "jz4780", - .cpu_dai_name = "jz4780-i2s", - .platform_name = "jz4780-i2s", - .codec_dai_name = "jz4780-hifi", - .codec_name = "jz4780-codec", - .init = ci20_init, - .dai_fmt = CI20_DAIFMT, +static struct snd_soc_dai_link ci20_dai_link[] = { + { + .name = "ci20", + .stream_name = "headphones", + .cpu_dai_name = "jz4780-i2s", + .platform_name = "jz4780-i2s", + .codec_dai_name = "jz4780-hifi", + .codec_name = "jz4780-codec", + .init = ci20_init, + .ops = &ci20_audio_dai_ops, + .dai_fmt = CI20_DAIFMT, + }, + { + .name = "ci20 HDMI", + .stream_name = "hdmi", + .cpu_dai_name = "jz4780-i2s", + .platform_name = "jz4780-i2s", + .codec_dai_name = "dw-hdmi-hifi", + .codec_name = "dw-hdmi-audio", + .init = ci20_hdmi_init, + .ops = &ci20_audio_dai_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS, + } }; static struct snd_soc_card ci20_audio_card = { .name = "ci20", - .dai_link = &ci20_dai_link, - .num_links = 1, + .dai_link = ci20_dai_link, + .num_links = ARRAY_SIZE(ci20_dai_link), .dapm_widgets = ci20_widgets, .num_dapm_widgets = ARRAY_SIZE(ci20_widgets), @@ -153,12 +233,16 @@ static int ingenic_asoc_ci20_probe(struct platform_device *pdev) "Phandle not found for i2s/codecs, using defaults\n"); } else { dev_dbg(&pdev->dev, "Setting dai_link parameters\n"); - ci20_dai_link.cpu_of_node = i2s; - ci20_dai_link.cpu_dai_name = NULL; - ci20_dai_link.platform_of_node = i2s; - ci20_dai_link.platform_name = NULL; - ci20_dai_link.codec_of_node = codec; - ci20_dai_link.codec_name = NULL; + ci20_dai_link[0].cpu_of_node = i2s; + ci20_dai_link[0].cpu_dai_name = NULL; + ci20_dai_link[1].cpu_of_node = i2s; + ci20_dai_link[1].cpu_dai_name = NULL; + ci20_dai_link[0].platform_of_node = i2s; + ci20_dai_link[0].platform_name = NULL; + ci20_dai_link[1].platform_of_node = i2s; + ci20_dai_link[1].platform_name = NULL; + ci20_dai_link[0].codec_of_node = codec; + ci20_dai_link[0].codec_name = NULL; } ret = gpio_request(GPIO_HP_MUTE, "Headphone Mute"); From 6ebd7bbb749deb0a0106e4169ccf23c3d9e550c9 Mon Sep 17 00:00:00 2001 From: Matt Redfearn Date: Tue, 14 Jul 2015 16:26:41 +0100 Subject: [PATCH 08/10] mips: ci20: Add HDMI audio to defconfig Add the HDMI audio driver to the Ci20's defconfig Signed-off-by: Matt Redfearn --- arch/mips/configs/ci20_defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/mips/configs/ci20_defconfig b/arch/mips/configs/ci20_defconfig index 08227ceff6b77a..e7e5b399716810 100644 --- a/arch/mips/configs/ci20_defconfig +++ b/arch/mips/configs/ci20_defconfig @@ -619,6 +619,7 @@ CONFIG_USB_RAREMONO=m CONFIG_USB_MA901=m CONFIG_DRM=y CONFIG_DRM_JZ4780=y +CONFIG_DRM_JZ4780_HDMI_AUDIO=y # CONFIG_LCD_CLASS_DEVICE is not set # CONFIG_BACKLIGHT_GENERIC is not set # CONFIG_VGA_CONSOLE is not set From 6d077c124473db3191fb45bf921affd07afa2c3c Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Fri, 10 Jul 2015 00:20:21 +0200 Subject: [PATCH 09/10] sound: soc: JZ4740: Fix divider written at incorrect offset in register The 4-bit divider value was written at offset 8, while the jz4740 programming manual locates it at offset 0. This fixes a bug in commit 26b0aad80a86d39b8c3a3189fbaf477ef92a64ff Signed-off-by: Paul Cercueil --- sound/soc/jz4740/jz4740-i2s.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/jz4740/jz4740-i2s.c b/sound/soc/jz4740/jz4740-i2s.c index a5d86e6cda01d1..c5fdb9a8bd18c7 100644 --- a/sound/soc/jz4740/jz4740-i2s.c +++ b/sound/soc/jz4740/jz4740-i2s.c @@ -92,7 +92,7 @@ #define JZ_AIC_I2S_STATUS_BUSY BIT(2) #define JZ_AIC_CLK_DIV_MASK 0xf -#define I2SDIV_DV_SHIFT 8 +#define I2SDIV_DV_SHIFT 0 #define I2SDIV_DV_MASK (0xf << I2SDIV_DV_SHIFT) #define I2SDIV_IDV_SHIFT 8 #define I2SDIV_IDV_MASK (0xf << I2SDIV_IDV_SHIFT) From d3fd1e3faea767ded535c54a6208391b8c0335e4 Mon Sep 17 00:00:00 2001 From: Paul Cercueil Date: Fri, 10 Jul 2015 00:27:15 +0200 Subject: [PATCH 10/10] sound: soc: jz4740: Fix broken audio for !mono sound The jz4780 SoC offers the possibility to configure the number of channels used for playback. Right now, the corresponding bits in the configuration register are always zero, which means that the hardware is only configured for monaural sound. With this commit, the hardware is now correctly configured for the number of channels requested by the ALSA core are. Signed-off-by: Paul Cercueil --- sound/soc/jz4740/jz4740-i2s.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sound/soc/jz4740/jz4740-i2s.c b/sound/soc/jz4740/jz4740-i2s.c index c5fdb9a8bd18c7..2e394aa2c02a9f 100644 --- a/sound/soc/jz4740/jz4740-i2s.c +++ b/sound/soc/jz4740/jz4740-i2s.c @@ -65,6 +65,7 @@ #define JZ4780_AIC_CONF_FIFO_TX_THRESHOLD_MASK \ (0x1f << JZ4780_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET) +#define JZ_AIC_CTRL_CHANNELS_MASK (0x7 << 24) #define JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_MASK (0x7 << 19) #define JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_MASK (0x7 << 16) #define JZ_AIC_CTRL_ENABLE_RX_DMA BIT(15) @@ -81,6 +82,7 @@ #define JZ_AIC_CTRL_ENABLE_PLAYBACK BIT(1) #define JZ_AIC_CTRL_ENABLE_CAPTURE BIT(0) +#define JZ_AIC_CTRL_CHANNELS_OFFSET 24 #define JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_OFFSET 19 #define JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_OFFSET 16 @@ -286,6 +288,12 @@ static int jz4740_i2s_hw_params(struct snd_pcm_substream *substream, else ctrl &= ~JZ_AIC_CTRL_MONO_TO_STEREO; + if (i2s->version >= JZ_I2S_JZ4780) { + ctrl &= ~JZ_AIC_CTRL_CHANNELS_MASK; + ctrl |= (params_channels(params) - 1) << + JZ_AIC_CTRL_CHANNELS_OFFSET; + } + div_reg &= ~I2SDIV_DV_MASK; div_reg |= (div - 1) << I2SDIV_DV_SHIFT; } else {