blob: 5120ea84e1916e8edf884d38e9fb44484ce18bf5 [file] [log] [blame]
/*
* 01xx.c
*
* 01SP/01TT TZ1090 Metamorph/Minimorph ASoC Audio Support using tansen codec.
*
* Copyright: (C) 2009-2013 Imagination Technologies
*
*/
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/timer.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/gpio.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/tansen.h>
#include <asm/soc-tz1090/hdmi-audio.h>
#include <asm/soc-tz1090/audiocodec.h>
#include <asm/soc-tz1090/defs.h>
#include "../codecs/tansen_gti.h"
#include "tz1090-pcm.h"
#include "tz1090-i2s.h"
struct zero1xx_priv {
void __iomem *adc_ctrl;
void __iomem *hp_ctrl;
void __iomem *gti_ctrl;
};
static int zero1xx_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 *codec_dai = rtd->codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
int ret = 0;
/*
* Note: as the Comet I2S out block expects 24 bit data left aligned
* from the DMA and ALSA provides 24 bit data right aligned we
* configure the I2S out block in 32 bit mode we then get 24 bit data
* out of the I2S right aligned, which conforms to the Sony
* right aligned I2S timings.
*/
/*
* set codec DAI configuration:
* Sony Right Justify Timing
* normal bit clock normal frame clock
* bit clock and frame clock slave
*
*/
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_RIGHT_J |
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0)
return ret;
/*
* set cpu DAI configuration:
* Sony Right Justify Timing
* normal bit clock normal frame clock
* bit clock and frame clock master
*/
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_RIGHT_J |
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
if (ret < 0)
return ret;
return 0;
}
static struct snd_soc_ops zero1xx_ops = {
.hw_params = zero1xx_hw_params,
};
static int __init zero1xx_tansen_init(struct zero1xx_priv *zero1xx)
{
u32 audio_hp_ctrl;
u32 audio_adc_ctrl;
/*
* Brings DAC out of Reset
* All other config handled by DAC driver
*/
u32 hpctrl, adcctrl;
hpctrl = ioread32(zero1xx->hp_ctrl);
adcctrl = ioread32(zero1xx->adc_ctrl);
/* GTI port must be in reset for power up to work */
gti_reset(zero1xx->gti_ctrl, 1);
/* Power up bandgap */
hpctrl &= ~(AUDIO_PWDN_BG_OP | AUDIO_PWDN_BG_IP);
iowrite32(hpctrl, zero1xx->hp_ctrl);
udelay(100);
/* Reset bandgap filter */
hpctrl |= (AUDIO_RST_BG_OP | AUDIO_RST_BG_IP);
iowrite32(hpctrl, zero1xx->hp_ctrl);
udelay(250);
hpctrl &= ~(AUDIO_RST_BG_OP | AUDIO_RST_BG_IP);
iowrite32(hpctrl, zero1xx->hp_ctrl);
udelay(100);
/* Power up PLL */
hpctrl &= ~AUDIO_PWDN_PLL;
iowrite32(hpctrl, zero1xx->hp_ctrl);
udelay(100);
/* Take digital blocks out of reset */
hpctrl &= ~(AUDIO_RSTB_DIG_OP | AUDIO_RSTB_DIG_IP);
iowrite32(hpctrl, zero1xx->hp_ctrl);
udelay(100);
/* Power up outputs only need PWM_C (Left) and PWM_D (right) */
hpctrl |= (/*AUDIO_PSCNT_PWM_F | AUDIO_PSCNT_PWM_E |*/ AUDIO_PSCNT_PWM_D |
AUDIO_PSCNT_PWM_C /*| AUDIO_PSCNT_PWM_B | AUDIO_PSCNT_PWM_A*/);
hpctrl |= (AUDIO_PSCNTHP_R | AUDIO_PSCNTHP_L);
iowrite32(hpctrl, zero1xx->hp_ctrl);
adcctrl |= (AUDIO_PSCNTADC_R | AUDIO_PSCNTADC_L);
iowrite32(adcctrl, zero1xx->adc_ctrl);
udelay(100);
/* Reset analogue filters */
hpctrl |= (AUDIO_RSTB_ANA_OP | AUDIO_RSTB_ANA_IP);
iowrite32(hpctrl, zero1xx->hp_ctrl);
udelay(250);
hpctrl &= ~(AUDIO_RSTB_ANA_OP | AUDIO_RSTB_ANA_IP);
iowrite32(hpctrl, zero1xx->hp_ctrl);
udelay(250);
/* Take the GTI port out of reset */
gti_reset(zero1xx->gti_ctrl, 0);
/* power up */
/* Get initial state of control registers */
audio_hp_ctrl = ioread32(zero1xx->hp_ctrl);
audio_adc_ctrl = ioread32(zero1xx->adc_ctrl);
/* Need to put the GTI interface into reset for the power up to work. */
gti_reset(zero1xx->gti_ctrl, true);
/*
* Step 1.
* CR_AUDIO_HP_CTRL CR_AUDIO_PWDN_BG_OP = 0
* CR_AUDIO_HP_CTRL CR_AUDIO_PWDN_BG_IP = 0
* DELAY x us
*/
audio_hp_ctrl &= ~(CR_AUDIO_PWDN_BG_OP_MASK <<
CR_AUDIO_PWDN_BG_OP_SHIFT);
audio_adc_ctrl &= ~(CR_AUDIO_PWDN_BG_IP_MASK <<
CR_AUDIO_PWDN_BG_IP_SHIFT);
iowrite32(audio_hp_ctrl, zero1xx->hp_ctrl);
udelay(100);
/*
* Step 2.
* CR_AUDIO_HP_CTRL CR_AUDIO_RST_BG_OP = 1
* CR_AUDIO_HP_CTRL CR_AUDIO_RST_BG_IP = 1
* DELAY > 250 us
*/
audio_hp_ctrl |= (CR_AUDIO_RST_BG_OP_MASK << CR_AUDIO_RST_BG_OP_SHIFT);
audio_hp_ctrl |= (CR_AUDIO_RST_BG_IP_MASK << CR_AUDIO_RST_BG_IP_SHIFT);
iowrite32(audio_hp_ctrl, zero1xx->hp_ctrl);
udelay(250);
/*
* Step 3.
* CR_AUDIO_HP_CTRL CR_AUDIO_RST_BG_OP = 0
* CR_AUDIO_HP_CTRL CR_AUDIO_RST_BG_IP = 0
* DELAY x us
*/
audio_hp_ctrl &= ~(CR_AUDIO_RST_BG_OP_MASK << CR_AUDIO_RST_BG_OP_SHIFT);
audio_hp_ctrl &= ~(CR_AUDIO_RST_BG_IP_MASK << CR_AUDIO_RST_BG_IP_SHIFT);
iowrite32(audio_hp_ctrl, zero1xx->hp_ctrl);
udelay(100);
/*
* Step 4.
* CR_AUDIO_HP_CTRL CR_AUDIO_PWDN_PLL = 0
* DELAY > 100 us
*/
audio_hp_ctrl &= ~(CR_AUDIO_PWDN_PLL_MASK << CR_AUDIO_PWDN_PLL_SHIFT);
iowrite32(audio_hp_ctrl, zero1xx->hp_ctrl);
udelay(100);
/*
* Step 5.
* CR_AUDIO_HP_CTRL CR_AUDIO_RSTB_DIG_OP = 0
* CR_AUDIO_HP_CTRL CR_AUDIO_RSTB_DIG_IP = 0
* DELAY x us
*/
audio_hp_ctrl &= ~(CR_AUDIO_RSTB_DIG_OP_MASK <<
CR_AUDIO_RSTB_DIG_OP_SHIFT);
audio_hp_ctrl &= ~(CR_AUDIO_RSTB_DIG_IP_MASK <<
CR_AUDIO_RSTB_DIG_IP_SHIFT);
iowrite32(audio_hp_ctrl, zero1xx->hp_ctrl);
udelay(100);
/*
* Step 6.
* CR_AUDIO_HP_CTRL CR_AUDIO_PSCNT_PWM_F = 1
* CR_AUDIO_HP_CTRL CR_AUDIO_PSCNT_PWM_E = 1
* CR_AUDIO_HP_CTRL CR_AUDIO_PSCNT_PWM_D = 1
* CR_AUDIO_HP_CTRL CR_AUDIO_PSCNT_PWM_C = 1
* CR_AUDIO_HP_CTRL CR_AUDIO_PSCNT_PWM_B = 1
* CR_AUDIO_HP_CTRL CR_AUDIO_PSCNT_PWM_A = 1
* CR_AUDIO_HP_CTRL CR_AUDIO_PSCNTHP_R = 1
* CR_AUDIO_HP_CTRL CR_AUDIO_PSCNTHP_L = 1
*
* CR_AUDIO_ADC_CTRL CR_AUDIO_PSCNTADC_R = 1
* CR_AUDIO_ADC_CTRL CR_AUDIO_PSCNTADC_L = 1
* DELAY x us
*/
audio_hp_ctrl |= (CR_AUDIO_PSCNT_PWM_F_MASK <<
CR_AUDIO_PSCNT_PWM_F_SHIFT);
audio_hp_ctrl |= (CR_AUDIO_PSCNT_PWM_E_MASK <<
CR_AUDIO_PSCNT_PWM_E_SHIFT);
audio_hp_ctrl |= (CR_AUDIO_PSCNT_PWM_D_MASK <<
CR_AUDIO_PSCNT_PWM_D_SHIFT);
audio_hp_ctrl |= (CR_AUDIO_PSCNT_PWM_C_MASK <<
CR_AUDIO_PSCNT_PWM_C_SHIFT);
audio_hp_ctrl |= (CR_AUDIO_PSCNT_PWM_B_MASK <<
CR_AUDIO_PSCNT_PWM_B_SHIFT);
audio_hp_ctrl |= (CR_AUDIO_PSCNT_PWM_A_MASK <<
CR_AUDIO_PSCNT_PWM_A_SHIFT);
audio_hp_ctrl |= (CR_AUDIO_PSCNTHP_R_MASK <<
CR_AUDIO_PSCNTHP_R_SHIFT);
audio_hp_ctrl |= (CR_AUDIO_PSCNTHP_L_MASK <<
CR_AUDIO_PSCNTHP_L_SHIFT);
audio_hp_ctrl |= (CR_AUDIO_I2S_EXT_MASK <<
CR_AUDIO_I2S_EXT_SHIFT);
audio_hp_ctrl &= ~(CR_AUDIO_PGA_MODE_MASK <<
CR_AUDIO_PGA_MODE_SHIFT);
audio_hp_ctrl |= 0x01 << CR_AUDIO_PGA_MODE_SHIFT;
iowrite32(audio_hp_ctrl, zero1xx->hp_ctrl);
audio_adc_ctrl |= (CR_AUDIO_PSCNTADC_R_MASK <<
CR_AUDIO_PSCNTADC_R_SHIFT);
audio_adc_ctrl |= (CR_AUDIO_PSCNTADC_L_MASK <<
CR_AUDIO_PSCNTADC_L_SHIFT);
iowrite32(audio_adc_ctrl, zero1xx->adc_ctrl);
udelay(100);
/*
* Step 7.
* CR_AUDIO_HP_CTRL CR_AUDIO_RSTB_ANA_OP = 1
* CR_AUDIO_HP_CTRL CR_AUDIO_RSTB_ANA_IP = 1
* DELAY >250 us
*/
audio_hp_ctrl |= (CR_AUDIO_RSTB_ANA_OP_MASK <<
CR_AUDIO_RSTB_ANA_OP_SHIFT);
audio_hp_ctrl |= (CR_AUDIO_RSTB_ANA_IP_MASK <<
CR_AUDIO_RSTB_ANA_IP_SHIFT);
iowrite32(audio_hp_ctrl, zero1xx->hp_ctrl);
udelay(250);
/*
* Step 8.
* CR_AUDIO_HP_CTRL CR_AUDIO_RSTB_ANA_OP = 0
* CR_AUDIO_HP_CTRL CR_AUDIO_RSTB_ANA_IP = 0
* DELAY >250 us
*/
audio_hp_ctrl &= ~(CR_AUDIO_RSTB_ANA_OP_MASK <<
CR_AUDIO_RSTB_ANA_OP_SHIFT);
audio_hp_ctrl &= ~(CR_AUDIO_RSTB_ANA_IP_MASK <<
CR_AUDIO_RSTB_ANA_IP_SHIFT);
iowrite32(audio_hp_ctrl, zero1xx->hp_ctrl);
udelay(250);
/* Take the GTI interface out of reset. */
gti_reset(zero1xx->gti_ctrl, false);
return 0;
}
/*
* 01SP/01TT digital audio interface glue - connects codec <--> CPU
*
* The codec used on the 01SP/01TT is the Cosmic circuits DAC internal
* to the SoC.
*
* Note: As we now only support 1 fixed audio configuration, HDMI audio
* just piggybacks on the back of the DAC config (when
* CONFIG_TZ1090_01XX_HDMI_AUDIO is set) and no longer has its own codec driver
*/
static struct snd_soc_dai_link zero1xx_dai = {
.name = "tansen",
.stream_name = "Playback",
.codec_dai_name = "tansen",
.platform_name = "comet-pcm-audio",
.ops = &zero1xx_ops,
};
/*01SP/01TT audio machine driver */
static struct snd_soc_card snd_soc_zero1xx = {
.name = "01SP/01TT-Tansen-Audio",
.owner = THIS_MODULE,
.dai_link = &zero1xx_dai,
.num_links = 1,
};
static int zero1xx_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct snd_soc_card *card = &snd_soc_zero1xx;
struct zero1xx_priv zero1xx;
struct resource *mem;
int ret;
zero1xx_dai.codec_name = NULL;
zero1xx_dai.codec_of_node = of_parse_phandle(np,
"img,audio-codec", 0);
if (!zero1xx_dai.codec_of_node) {
dev_err(&pdev->dev,
"Property 'img,audio-codec' not found\n");
return -EINVAL;
}
zero1xx_dai.cpu_dai_name = NULL;
zero1xx_dai.cpu_of_node = of_parse_phandle(np,
"img,i2s-controller", 0);
if (!zero1xx_dai.cpu_of_node) {
dev_err(&pdev->dev,
"Property 'img,i2s-controller' not found\n");
return -EINVAL;
}
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
zero1xx.adc_ctrl = devm_request_and_ioremap(&pdev->dev, mem);
if (!zero1xx.adc_ctrl) {
dev_err(&pdev->dev,
"Unable to ioremap the adc ctrl registers\n");
return -ENOMEM;
}
mem = platform_get_resource(pdev, IORESOURCE_MEM, 1);
zero1xx.hp_ctrl = devm_request_and_ioremap(&pdev->dev, mem);
if (!zero1xx.hp_ctrl) {
dev_err(&pdev->dev,
"Unable to ioremap the hp ctrl registers\n");
return -ENOMEM;
}
mem = platform_get_resource(pdev, IORESOURCE_MEM, 2);
if (!mem) {
dev_err(&pdev->dev, "failed to get memory resource\n");
return -ENODEV;
}
zero1xx.gti_ctrl = ioremap_nocache(mem->start,
mem->end - mem->start + 1);
if (!zero1xx.gti_ctrl) {
dev_err(&pdev->dev,
"Unable to ioremap the gti ctrl registers\n");
return -ENOMEM;
}
zero1xx_tansen_init(&zero1xx);
card->dev = &pdev->dev;
ret = snd_soc_register_card(card);
if (ret)
dev_err(&pdev->dev,
"snd_soc_register_card() failed: %d\n",
ret);
return ret;
}
static int zero1xx_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
snd_soc_unregister_card(card);
return 0;
}
static const struct of_device_id tz1090_audio_of_match[] = {
{ .compatible = "img,tz1090-audio", },
{}
};
MODULE_DEVICE_TABLE(of, tz1090_audio_of_match);
static struct platform_driver zero1xx_driver = {
.driver = {
.name = "zero1xx-audio",
.owner = THIS_MODULE,
.of_match_table = tz1090_audio_of_match,
},
.probe = zero1xx_probe,
.remove = zero1xx_remove,
};
module_platform_driver(zero1xx_driver);
/* Module information */
MODULE_AUTHOR("Imagination Technologies");
MODULE_DESCRIPTION("ALSA SoC 01SP/01TT");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:zero1xx-audio");