| From e629b8e80ed3dc7817ba8a92d4d8098bc5fdbe65 Mon Sep 17 00:00:00 2001 |
| From: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> |
| Date: Tue, 8 Nov 2016 01:00:57 +0000 |
| Subject: [PATCH 299/299] drm: bridge: add DesignWare HDMI I2S audio support |
| |
| Current dw-hdmi is supporting sound via AHB bus, but it has |
| I2S audio feature too. This patch adds I2S audio support to dw-hdmi. |
| This HDMI I2S is supported by using ALSA SoC common HDMI encoder |
| driver. |
| |
| Tested-by: Jose Abreu <joabreu@synopsys.com> |
| Acked-by: Russell King <rmk+kernel@armlinux.org.uk> |
| Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> |
| Signed-off-by: Archit Taneja <architt@codeaurora.org> |
| Link: http://patchwork.freedesktop.org/patch/msgid/8737j2bxba.wl%kuninori.morimoto.gx@renesas.com |
| (cherry picked from commit 2761ba6c0925ca9c5b917a95f68135d9dce443fb) |
| Signed-off-by: Simon Horman <horms+renesas@verge.net.au> |
| --- |
| drivers/gpu/drm/bridge/Kconfig | 8 + |
| drivers/gpu/drm/bridge/Makefile | 1 |
| drivers/gpu/drm/bridge/dw-hdmi-audio.h | 7 + |
| drivers/gpu/drm/bridge/dw-hdmi-i2s-audio.c | 141 +++++++++++++++++++++++++++++ |
| drivers/gpu/drm/bridge/dw-hdmi.c | 22 ++++ |
| drivers/gpu/drm/bridge/dw-hdmi.h | 20 ++++ |
| 6 files changed, 197 insertions(+), 2 deletions(-) |
| create mode 100644 drivers/gpu/drm/bridge/dw-hdmi-i2s-audio.c |
| |
| --- a/drivers/gpu/drm/bridge/Kconfig |
| +++ b/drivers/gpu/drm/bridge/Kconfig |
| @@ -39,6 +39,14 @@ config DRM_DW_HDMI_AHB_AUDIO |
| Designware HDMI block. This is used in conjunction with |
| the i.MX6 HDMI driver. |
| |
| +config DRM_DW_HDMI_I2S_AUDIO |
| + tristate "Synopsis Designware I2S Audio interface" |
| + depends on DRM_DW_HDMI |
| + select SND_SOC_HDMI_CODEC |
| + help |
| + Support the I2S Audio interface which is part of the Synopsis |
| + Designware HDMI block. |
| + |
| config DRM_NXP_PTN3460 |
| tristate "NXP PTN3460 DP/LVDS bridge" |
| depends on OF |
| --- a/drivers/gpu/drm/bridge/Makefile |
| +++ b/drivers/gpu/drm/bridge/Makefile |
| @@ -4,6 +4,7 @@ obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += an |
| obj-$(CONFIG_DRM_DUMB_VGA_DAC) += dumb-vga-dac.o |
| obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o |
| obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o |
| +obj-$(CONFIG_DRM_DW_HDMI_I2S_AUDIO) += dw-hdmi-i2s-audio.o |
| obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o |
| obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o |
| obj-$(CONFIG_DRM_SII902X) += sii902x.o |
| --- a/drivers/gpu/drm/bridge/dw-hdmi-audio.h |
| +++ b/drivers/gpu/drm/bridge/dw-hdmi-audio.h |
| @@ -11,4 +11,11 @@ struct dw_hdmi_audio_data { |
| u8 *eld; |
| }; |
| |
| +struct dw_hdmi_i2s_audio_data { |
| + struct dw_hdmi *hdmi; |
| + |
| + void (*write)(struct dw_hdmi *hdmi, u8 val, int offset); |
| + u8 (*read)(struct dw_hdmi *hdmi, int offset); |
| +}; |
| + |
| #endif |
| --- /dev/null |
| +++ b/drivers/gpu/drm/bridge/dw-hdmi-i2s-audio.c |
| @@ -0,0 +1,141 @@ |
| +/* |
| + * dw-hdmi-i2s-audio.c |
| + * |
| + * Copyright (c) 2016 Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> |
| + * |
| + * 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. |
| + */ |
| +#include <drm/bridge/dw_hdmi.h> |
| + |
| +#include <sound/hdmi-codec.h> |
| + |
| +#include "dw-hdmi.h" |
| +#include "dw-hdmi-audio.h" |
| + |
| +#define DRIVER_NAME "dw-hdmi-i2s-audio" |
| + |
| +static inline void hdmi_write(struct dw_hdmi_i2s_audio_data *audio, |
| + u8 val, int offset) |
| +{ |
| + struct dw_hdmi *hdmi = audio->hdmi; |
| + |
| + audio->write(hdmi, val, offset); |
| +} |
| + |
| +static inline u8 hdmi_read(struct dw_hdmi_i2s_audio_data *audio, int offset) |
| +{ |
| + struct dw_hdmi *hdmi = audio->hdmi; |
| + |
| + return audio->read(hdmi, offset); |
| +} |
| + |
| +static int dw_hdmi_i2s_hw_params(struct device *dev, void *data, |
| + struct hdmi_codec_daifmt *fmt, |
| + struct hdmi_codec_params *hparms) |
| +{ |
| + struct dw_hdmi_i2s_audio_data *audio = data; |
| + struct dw_hdmi *hdmi = audio->hdmi; |
| + u8 conf0 = 0; |
| + u8 conf1 = 0; |
| + u8 inputclkfs = 0; |
| + |
| + /* it cares I2S only */ |
| + if ((fmt->fmt != HDMI_I2S) || |
| + (fmt->bit_clk_master | fmt->frame_clk_master)) { |
| + dev_err(dev, "unsupported format/settings\n"); |
| + return -EINVAL; |
| + } |
| + |
| + inputclkfs = HDMI_AUD_INPUTCLKFS_64FS; |
| + conf0 = HDMI_AUD_CONF0_I2S_ALL_ENABLE; |
| + |
| + switch (hparms->sample_width) { |
| + case 16: |
| + conf1 = HDMI_AUD_CONF1_WIDTH_16; |
| + break; |
| + case 24: |
| + case 32: |
| + conf1 = HDMI_AUD_CONF1_WIDTH_24; |
| + break; |
| + } |
| + |
| + dw_hdmi_set_sample_rate(hdmi, hparms->sample_rate); |
| + |
| + hdmi_write(audio, inputclkfs, HDMI_AUD_INPUTCLKFS); |
| + hdmi_write(audio, conf0, HDMI_AUD_CONF0); |
| + hdmi_write(audio, conf1, HDMI_AUD_CONF1); |
| + |
| + dw_hdmi_audio_enable(hdmi); |
| + |
| + return 0; |
| +} |
| + |
| +static void dw_hdmi_i2s_audio_shutdown(struct device *dev, void *data) |
| +{ |
| + struct dw_hdmi_i2s_audio_data *audio = data; |
| + struct dw_hdmi *hdmi = audio->hdmi; |
| + |
| + dw_hdmi_audio_disable(hdmi); |
| + |
| + hdmi_write(audio, HDMI_AUD_CONF0_SW_RESET, HDMI_AUD_CONF0); |
| +} |
| + |
| +static struct hdmi_codec_ops dw_hdmi_i2s_ops = { |
| + .hw_params = dw_hdmi_i2s_hw_params, |
| + .audio_shutdown = dw_hdmi_i2s_audio_shutdown, |
| +}; |
| + |
| +static int snd_dw_hdmi_probe(struct platform_device *pdev) |
| +{ |
| + struct dw_hdmi_i2s_audio_data *audio = pdev->dev.platform_data; |
| + struct platform_device_info pdevinfo; |
| + struct hdmi_codec_pdata pdata; |
| + struct platform_device *platform; |
| + |
| + pdata.ops = &dw_hdmi_i2s_ops; |
| + pdata.i2s = 1; |
| + pdata.max_i2s_channels = 6; |
| + pdata.data = audio; |
| + |
| + memset(&pdevinfo, 0, sizeof(pdevinfo)); |
| + pdevinfo.parent = pdev->dev.parent; |
| + pdevinfo.id = PLATFORM_DEVID_AUTO; |
| + pdevinfo.name = HDMI_CODEC_DRV_NAME; |
| + pdevinfo.data = &pdata; |
| + pdevinfo.size_data = sizeof(pdata); |
| + pdevinfo.dma_mask = DMA_BIT_MASK(32); |
| + |
| + platform = platform_device_register_full(&pdevinfo); |
| + if (IS_ERR(platform)) |
| + return PTR_ERR(platform); |
| + |
| + dev_set_drvdata(&pdev->dev, platform); |
| + |
| + return 0; |
| +} |
| + |
| +static int snd_dw_hdmi_remove(struct platform_device *pdev) |
| +{ |
| + struct platform_device *platform = dev_get_drvdata(&pdev->dev); |
| + |
| + platform_device_unregister(platform); |
| + |
| + return 0; |
| +} |
| + |
| +static struct platform_driver snd_dw_hdmi_driver = { |
| + .probe = snd_dw_hdmi_probe, |
| + .remove = snd_dw_hdmi_remove, |
| + .driver = { |
| + .name = DRIVER_NAME, |
| + .owner = THIS_MODULE, |
| + }, |
| +}; |
| +module_platform_driver(snd_dw_hdmi_driver); |
| + |
| +MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); |
| +MODULE_DESCRIPTION("Synopsis Designware HDMI I2S ALSA SoC interface"); |
| +MODULE_LICENSE("GPL v2"); |
| +MODULE_ALIAS("platform:" DRIVER_NAME); |
| --- a/drivers/gpu/drm/bridge/dw-hdmi.c |
| +++ b/drivers/gpu/drm/bridge/dw-hdmi.c |
| @@ -1871,10 +1871,11 @@ int dw_hdmi_bind(struct device *dev, str |
| struct device_node *np = dev->of_node; |
| struct platform_device_info pdevinfo; |
| struct device_node *ddc_node; |
| - struct dw_hdmi_audio_data audio; |
| struct dw_hdmi *hdmi; |
| int ret; |
| u32 val = 1; |
| + u8 config0; |
| + u8 config1; |
| |
| hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); |
| if (!hdmi) |
| @@ -2011,7 +2012,12 @@ int dw_hdmi_bind(struct device *dev, str |
| pdevinfo.parent = dev; |
| pdevinfo.id = PLATFORM_DEVID_AUTO; |
| |
| - if (hdmi_readb(hdmi, HDMI_CONFIG1_ID) & HDMI_CONFIG1_AHB) { |
| + config0 = hdmi_readb(hdmi, HDMI_CONFIG0_ID); |
| + config1 = hdmi_readb(hdmi, HDMI_CONFIG1_ID); |
| + |
| + if (config1 & HDMI_CONFIG1_AHB) { |
| + struct dw_hdmi_audio_data audio; |
| + |
| audio.phys = iores->start; |
| audio.base = hdmi->regs; |
| audio.irq = irq; |
| @@ -2022,6 +2028,18 @@ int dw_hdmi_bind(struct device *dev, str |
| pdevinfo.data = &audio; |
| pdevinfo.size_data = sizeof(audio); |
| pdevinfo.dma_mask = DMA_BIT_MASK(32); |
| + hdmi->audio = platform_device_register_full(&pdevinfo); |
| + } else if (config0 & HDMI_CONFIG0_I2S) { |
| + struct dw_hdmi_i2s_audio_data audio; |
| + |
| + audio.hdmi = hdmi; |
| + audio.write = hdmi_writeb; |
| + audio.read = hdmi_readb; |
| + |
| + pdevinfo.name = "dw-hdmi-i2s-audio"; |
| + pdevinfo.data = &audio; |
| + pdevinfo.size_data = sizeof(audio); |
| + pdevinfo.dma_mask = DMA_BIT_MASK(32); |
| hdmi->audio = platform_device_register_full(&pdevinfo); |
| } |
| |
| --- a/drivers/gpu/drm/bridge/dw-hdmi.h |
| +++ b/drivers/gpu/drm/bridge/dw-hdmi.h |
| @@ -545,6 +545,9 @@ |
| #define HDMI_I2CM_FS_SCL_LCNT_0_ADDR 0x7E12 |
| |
| enum { |
| +/* CONFIG0_ID field values */ |
| + HDMI_CONFIG0_I2S = 0x10, |
| + |
| /* CONFIG1_ID field values */ |
| HDMI_CONFIG1_AHB = 0x01, |
| |
| @@ -891,6 +894,17 @@ enum { |
| HDMI_PHY_I2CM_CTLINT_ADDR_ARBITRATION_POL = 0x08, |
| HDMI_PHY_I2CM_CTLINT_ADDR_ARBITRATION_MASK = 0x04, |
| |
| +/* AUD_CONF0 field values */ |
| + HDMI_AUD_CONF0_SW_RESET = 0x80, |
| + HDMI_AUD_CONF0_I2S_ALL_ENABLE = 0x2F, |
| + |
| +/* AUD_CONF1 field values */ |
| + HDMI_AUD_CONF1_MODE_I2S = 0x00, |
| + HDMI_AUD_CONF1_MODE_RIGHT_J = 0x02, |
| + HDMI_AUD_CONF1_MODE_LEFT_J = 0x04, |
| + HDMI_AUD_CONF1_WIDTH_16 = 0x10, |
| + HDMI_AUD_CONF1_WIDTH_24 = 0x18, |
| + |
| /* AUD_CTS3 field values */ |
| HDMI_AUD_CTS3_N_SHIFT_OFFSET = 5, |
| HDMI_AUD_CTS3_N_SHIFT_MASK = 0xe0, |
| @@ -905,6 +919,12 @@ enum { |
| HDMI_AUD_CTS3_CTS_MANUAL = 0x10, |
| HDMI_AUD_CTS3_AUDCTS19_16_MASK = 0x0f, |
| |
| +/* HDMI_AUD_INPUTCLKFS field values */ |
| + HDMI_AUD_INPUTCLKFS_128FS = 0, |
| + HDMI_AUD_INPUTCLKFS_256FS = 1, |
| + HDMI_AUD_INPUTCLKFS_512FS = 2, |
| + HDMI_AUD_INPUTCLKFS_64FS = 4, |
| + |
| /* AHB_DMA_CONF0 field values */ |
| HDMI_AHB_DMA_CONF0_SW_FIFO_RST_OFFSET = 7, |
| HDMI_AHB_DMA_CONF0_SW_FIFO_RST_MASK = 0x80, |