blob: b30e6cd0377c648cfd24a728875099b236f79587 [file] [log] [blame]
/*
* ALSA SoC HMDI codec driver
*
* Author: Ricardo Neri <ricardo.neri@ti.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.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/gpio.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/initval.h>
#include <sound/tlv.h>
#include <plat/omap_hwmod.h>
#include <video/omapdss.h>
#include <video/hdmi_ti_4xxx_ip.h>
#include "../../../drivers/video/omap2/dss/dss_features.h"
#include "../../../drivers/video/omap2/dss/dss.h"
#define HDMI_WP 0x0
#define HDMI_CORE_SYS 0x400
#define HDMI_CORE_AV 0x900
#define HDMI_PLLCTRL 0x200
#define HDMI_PHY 0x300
/* hdmi configuration params */
struct hdmi_params {
int format;
int sample_freq;
int channels_nr;
};
/* codec private data */
struct hdmi_codec_data {
struct hdmi_audio_format audio_fmt;
struct hdmi_audio_dma audio_dma;
struct hdmi_core_audio_config audio_core_cfg;
struct hdmi_core_infoframe_audio aud_if_cfg;
struct hdmi_ip_data ip_data;
struct omap_hwmod *oh;
struct omap_dss_device *dssdev;
struct notifier_block notifier;
struct hdmi_params params;
int active;
} hdmi_data;
static int hdmi_audio_set_configuration(struct hdmi_codec_data *priv)
{
struct hdmi_audio_format *audio_format = &priv->audio_fmt;
struct hdmi_audio_dma *audio_dma = &priv->audio_dma;
struct hdmi_core_audio_config *core_cfg = &priv->audio_core_cfg;
struct hdmi_core_infoframe_audio *aud_if_cfg = &priv->aud_if_cfg;
int err, n, cts, channel_alloc;
enum hdmi_core_audio_sample_freq sample_freq;
u32 pclk = omapdss_hdmi_get_pixel_clock();
switch (priv->params.format) {
case SNDRV_PCM_FORMAT_S16_LE:
core_cfg->i2s_cfg.word_max_length =
HDMI_AUDIO_I2S_MAX_WORD_20BITS;
core_cfg->i2s_cfg.word_length =
HDMI_AUDIO_I2S_CHST_WORD_16_BITS;
core_cfg->i2s_cfg.in_length_bits =
HDMI_AUDIO_I2S_INPUT_LENGTH_16;
core_cfg->i2s_cfg.justification = HDMI_AUDIO_JUSTIFY_LEFT;
audio_format->samples_per_word = HDMI_AUDIO_ONEWORD_TWOSAMPLES;
audio_format->sample_size = HDMI_AUDIO_SAMPLE_16BITS;
audio_format->justification = HDMI_AUDIO_JUSTIFY_LEFT;
audio_dma->transfer_size = 0x10;
break;
case SNDRV_PCM_FORMAT_S24_LE:
core_cfg->i2s_cfg.word_max_length =
HDMI_AUDIO_I2S_MAX_WORD_24BITS;
core_cfg->i2s_cfg.word_length =
HDMI_AUDIO_I2S_CHST_WORD_24_BITS;
core_cfg->i2s_cfg.in_length_bits =
HDMI_AUDIO_I2S_INPUT_LENGTH_24;
audio_format->samples_per_word = HDMI_AUDIO_ONEWORD_ONESAMPLE;
audio_format->sample_size = HDMI_AUDIO_SAMPLE_24BITS;
audio_format->justification = HDMI_AUDIO_JUSTIFY_RIGHT;
core_cfg->i2s_cfg.justification = HDMI_AUDIO_JUSTIFY_RIGHT;
audio_dma->transfer_size = 0x20;
break;
default:
return -EINVAL;
}
switch (priv->params.sample_freq) {
case 32000:
sample_freq = HDMI_AUDIO_FS_32000;
break;
case 44100:
sample_freq = HDMI_AUDIO_FS_44100;
break;
case 48000:
sample_freq = HDMI_AUDIO_FS_48000;
break;
default:
return -EINVAL;
}
err = hdmi_ti_4xxx_config_audio_acr(&priv->ip_data,
priv->params.sample_freq, &n, &cts, pclk);
if (err < 0)
return err;
/* Audio wrapper config */
audio_format->type = HDMI_AUDIO_TYPE_LPCM;
audio_format->sample_order = HDMI_AUDIO_SAMPLE_LEFT_FIRST;
/* Disable start/stop signals of IEC 60958 blocks */
audio_format->en_sig_blk_strt_end = HDMI_AUDIO_BLOCK_SIG_STARTEND_OFF;
audio_dma->block_size = 0xC0;
audio_dma->mode = HDMI_AUDIO_TRANSF_DMA;
audio_dma->fifo_threshold = 0x20; /* in number of samples */
hdmi_ti_4xxx_wp_audio_config_dma(&priv->ip_data, audio_dma);
hdmi_ti_4xxx_wp_audio_config_format(&priv->ip_data, audio_format);
/*
* I2S config
*/
core_cfg->i2s_cfg.en_high_bitrate_aud = false;
/* Only used with high bitrate audio */
core_cfg->i2s_cfg.cbit_order = false;
/* Serial data and word select should change on sck rising edge */
core_cfg->i2s_cfg.sck_edge_mode = HDMI_AUDIO_I2S_SCK_EDGE_RISING;
core_cfg->i2s_cfg.vbit = HDMI_AUDIO_I2S_VBIT_FOR_PCM;
/* Set I2S word select polarity */
core_cfg->i2s_cfg.ws_polarity = HDMI_AUDIO_I2S_WS_POLARITY_LOW_IS_LEFT;
core_cfg->i2s_cfg.direction = HDMI_AUDIO_I2S_MSB_SHIFTED_FIRST;
/* Set serial data to word select shift. See Phillips spec. */
core_cfg->i2s_cfg.shift = HDMI_AUDIO_I2S_FIRST_BIT_SHIFT;
/* Core audio config */
core_cfg->freq_sample = sample_freq;
core_cfg->n = n;
core_cfg->cts = cts;
if (dss_has_feature(FEAT_HDMI_CTS_SWMODE)) {
core_cfg->aud_par_busclk = 0;
core_cfg->cts_mode = HDMI_AUDIO_CTS_MODE_SW;
core_cfg->use_mclk = cpu_is_omap446x();
} else {
core_cfg->aud_par_busclk = (((128 * 31) - 1) << 8);
core_cfg->cts_mode = HDMI_AUDIO_CTS_MODE_HW;
core_cfg->use_mclk = true;
core_cfg->mclk_mode = HDMI_AUDIO_MCLK_128FS;
}
core_cfg->en_spdif = false;
/* Use sample frequency from channel status word */
core_cfg->fs_override = true;
/* Enable ACR packets */
core_cfg->en_acr_pkt = true;
/* Disable direct streaming digital audio */
core_cfg->en_dsd_audio = false;
/* Use parallel audio interface */
core_cfg->en_parallel_aud_input = true;
/* Number of channels */
switch (priv->params.channels_nr) {
case 2:
core_cfg->layout = HDMI_AUDIO_LAYOUT_2CH;
channel_alloc = 0x0;
audio_format->stereo_channels = HDMI_AUDIO_STEREO_ONECHANNEL;
audio_format->active_chnnls_msk = 0x03;
/* Enable one of the four available serial data channels */
core_cfg->i2s_cfg.active_sds = HDMI_AUDIO_I2S_SD0_EN;
break;
case 6:
core_cfg->layout = HDMI_AUDIO_LAYOUT_8CH;
channel_alloc = 0xB;
audio_format->stereo_channels = HDMI_AUDIO_STEREO_FOURCHANNELS;
audio_format->active_chnnls_msk = 0x3f;
/* Enable all of the four available serial data channels */
core_cfg->i2s_cfg.active_sds = HDMI_AUDIO_I2S_SD0_EN |
HDMI_AUDIO_I2S_SD1_EN | HDMI_AUDIO_I2S_SD2_EN |
HDMI_AUDIO_I2S_SD3_EN;
break;
case 8:
core_cfg->layout = HDMI_AUDIO_LAYOUT_8CH;
channel_alloc = 0x13;
audio_format->stereo_channels = HDMI_AUDIO_STEREO_FOURCHANNELS;
audio_format->active_chnnls_msk = 0xff;
/* Enable all of the four available serial data channels */
core_cfg->i2s_cfg.active_sds = HDMI_AUDIO_I2S_SD0_EN |
HDMI_AUDIO_I2S_SD1_EN | HDMI_AUDIO_I2S_SD2_EN |
HDMI_AUDIO_I2S_SD3_EN;
break;
default:
pr_err("Unsupported number of channels\n");
return -EINVAL;
}
hdmi_ti_4xxx_core_audio_config(&priv->ip_data, core_cfg);
hdmi_ti_4xxx_wp_audio_config_format(&priv->ip_data, audio_format);
/*
* Configure packet
* info frame audio see doc CEA861-D page 74
*/
aud_if_cfg->db1_coding_type = HDMI_INFOFRAME_AUDIO_DB1CT_FROM_STREAM;
aud_if_cfg->db1_channel_count = priv->params.channels_nr;
aud_if_cfg->db2_sample_freq = HDMI_INFOFRAME_AUDIO_DB2SF_FROM_STREAM;
aud_if_cfg->db2_sample_size = HDMI_INFOFRAME_AUDIO_DB2SS_FROM_STREAM;
aud_if_cfg->db4_channel_alloc = channel_alloc;
aud_if_cfg->db5_downmix_inh = false;
aud_if_cfg->db5_lsv = 0;
hdmi_ti_4xxx_core_audio_infoframe_config(&priv->ip_data, aud_if_cfg);
return 0;
}
int hdmi_audio_notifier_callback(struct notifier_block *nb,
unsigned long arg, void *ptr)
{
enum omap_dss_display_state state = arg;
if (state == OMAP_DSS_DISPLAY_ACTIVE) {
/* this happens just after hdmi_power_on */
if (hdmi_data.active)
hdmi_ti_4xxx_audio_enable(&hdmi_data.ip_data, 0);
hdmi_audio_set_configuration(&hdmi_data);
if (hdmi_data.active) {
omap_hwmod_set_slave_idlemode(hdmi_data.oh,
HWMOD_IDLEMODE_NO);
hdmi_ti_4xxx_audio_enable(&hdmi_data.ip_data, 1);
}
}
return 0;
}
int hdmi_audio_match(struct omap_dss_device *dssdev, void *arg)
{
return sysfs_streq(dssdev->name , "hdmi");
}
static int hdmi_audio_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_codec *codec = rtd->codec;
struct hdmi_codec_data *priv = snd_soc_codec_get_drvdata(codec);
priv->params.format = params_format(params);
priv->params.sample_freq = params_rate(params);
priv->params.channels_nr = params_channels(params);
return hdmi_audio_set_configuration(priv);
}
static int hdmi_audio_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_codec *codec = rtd->codec;
struct hdmi_codec_data *priv = snd_soc_codec_get_drvdata(codec);
int err = 0;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
/*
* switch to no-idle to avoid DSS_L3_ICLK clock
* to be shutdown during audio activity (as per TRM)
*/
omap_hwmod_set_slave_idlemode(priv->oh,
HWMOD_IDLEMODE_NO);
hdmi_ti_4xxx_audio_enable(&priv->ip_data, 1);
priv->active = 1;
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
priv->active = 0;
hdmi_ti_4xxx_audio_enable(&priv->ip_data, 0);
/*
* switch back to smart-idle & wakeup capable
* after audio activity stops
*/
omap_hwmod_set_slave_idlemode(priv->oh,
HWMOD_IDLEMODE_SMART_WKUP);
break;
default:
err = -EINVAL;
}
return err;
}
static int hdmi_audio_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
if (!omapdss_hdmi_get_mode()) {
pr_err("Current video settings do not support audio.\n");
return -EIO;
}
return 0;
}
static int hdmi_probe(struct snd_soc_codec *codec)
{
struct platform_device *pdev = to_platform_device(codec->dev);
struct resource *hdmi_rsrc;
int ret = 0;
snd_soc_codec_set_drvdata(codec, &hdmi_data);
hdmi_rsrc = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!hdmi_rsrc) {
dev_err(&pdev->dev, "Cannot obtain IORESOURCE_MEM HDMI\n");
ret = -EINVAL;
goto res_err;
}
hdmi_data.oh = omap_hwmod_lookup("dss_hdmi");
if (!hdmi_data.oh) {
dev_err(&pdev->dev, "can't find omap_hwmod for hdmi\n");
ret = -ENODEV;
goto res_err;
}
/* Base address taken from platform */
hdmi_data.ip_data.base_wp = ioremap(hdmi_rsrc->start,
resource_size(hdmi_rsrc));
if (!hdmi_data.ip_data.base_wp) {
dev_err(&pdev->dev, "can't ioremap WP\n");
ret = -ENOMEM;
goto res_err;
}
hdmi_data.ip_data.hdmi_core_sys_offset = HDMI_CORE_SYS;
hdmi_data.ip_data.hdmi_core_av_offset = HDMI_CORE_AV;
hdmi_data.ip_data.hdmi_pll_offset = HDMI_PLLCTRL;
hdmi_data.ip_data.hdmi_phy_offset = HDMI_PHY;
hdmi_data.dssdev = omap_dss_find_device(NULL, hdmi_audio_match);
if (!hdmi_data.dssdev) {
dev_err(&pdev->dev, "can't find HDMI device\n");
ret = -ENODEV;
goto dssdev_err;
}
hdmi_data.notifier.notifier_call = hdmi_audio_notifier_callback;
blocking_notifier_chain_register(&hdmi_data.dssdev->state_notifiers,
&hdmi_data.notifier);
return 0;
dssdev_err:
iounmap(hdmi_data.ip_data.base_wp);
res_err:
return ret;
}
static int hdmi_remove(struct snd_soc_codec *codec)
{
struct hdmi_codec_data *priv = snd_soc_codec_get_drvdata(codec);
blocking_notifier_chain_unregister(&priv->dssdev->state_notifiers,
&priv->notifier);
iounmap(priv->ip_data.base_wp);
kfree(priv);
return 0;
}
static struct snd_soc_codec_driver hdmi_audio_codec_drv = {
.probe = hdmi_probe,
.remove = hdmi_remove,
};
static struct snd_soc_dai_ops hdmi_audio_codec_ops = {
.hw_params = hdmi_audio_hw_params,
.trigger = hdmi_audio_trigger,
.startup = hdmi_audio_startup,
};
static struct snd_soc_dai_driver hdmi_codec_dai_drv = {
.name = "hdmi-audio-codec",
.playback = {
.channels_min = 2,
.channels_max = 8,
.rates = SNDRV_PCM_RATE_32000 |
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE,
},
.ops = &hdmi_audio_codec_ops,
};
static __devinit int hdmi_codec_probe(struct platform_device *pdev)
{
int r;
/* Register ASoC codec DAI */
r = snd_soc_register_codec(&pdev->dev, &hdmi_audio_codec_drv,
&hdmi_codec_dai_drv, 1);
if (r) {
dev_err(&pdev->dev, "can't register ASoC HDMI audio codec\n");
return r;
}
return 0;
}
static int __devexit hdmi_codec_remove(struct platform_device *pdev)
{
snd_soc_unregister_codec(&pdev->dev);
return 0;
}
static struct platform_driver hdmi_codec_driver = {
.probe = hdmi_codec_probe,
.remove = __devexit_p(hdmi_codec_remove),
.driver = {
.name = "omap-hdmi-codec",
.owner = THIS_MODULE,
},
};
static int __init hdmi_codec_init(void)
{
return platform_driver_register(&hdmi_codec_driver);
}
module_init(hdmi_codec_init);
static void __exit hdmi_codec_exit(void)
{
platform_driver_unregister(&hdmi_codec_driver);
}
module_exit(hdmi_codec_exit);
MODULE_AUTHOR("Ricardo Neri <ricardo.neri@ti.com>");
MODULE_DESCRIPTION("ASoC HDMI codec driver");
MODULE_LICENSE("GPL");