Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

32
sound/soc/ux500/Kconfig Normal file
View file

@ -0,0 +1,32 @@
#
# Ux500 SoC audio configuration
#
menuconfig SND_SOC_UX500
tristate "SoC Audio support for Ux500 platform"
depends on SND_SOC
depends on MFD_DB8500_PRCMU
help
Say Y if you want to enable ASoC-support for
any of the Ux500 platforms (e.g. U8500).
config SND_SOC_UX500_PLAT_MSP_I2S
tristate
depends on SND_SOC_UX500
config SND_SOC_UX500_PLAT_DMA
tristate "Platform - DB8500 (DMA)"
depends on SND_SOC_UX500
select SND_SOC_GENERIC_DMAENGINE_PCM
help
Say Y if you want to enable the Ux500 platform-driver.
config SND_SOC_UX500_MACH_MOP500
tristate "Machine - MOP500 (Ux500 + AB8500)"
depends on AB8500_CORE && AB8500_GPADC && SND_SOC_UX500
select SND_SOC_AB8500_CODEC
select SND_SOC_UX500_PLAT_MSP_I2S
select SND_SOC_UX500_PLAT_DMA
help
Select this to enable the MOP500 machine-driver.
This will enable platform-drivers for: Ux500
This will enable codec-drivers for: AB8500

10
sound/soc/ux500/Makefile Normal file
View file

@ -0,0 +1,10 @@
# Ux500 Platform Support
snd-soc-ux500-plat-msp-i2s-objs := ux500_msp_dai.o ux500_msp_i2s.o
obj-$(CONFIG_SND_SOC_UX500_PLAT_MSP_I2S) += snd-soc-ux500-plat-msp-i2s.o
snd-soc-ux500-plat-dma-objs := ux500_pcm.o
obj-$(CONFIG_SND_SOC_UX500_PLAT_DMA) += snd-soc-ux500-plat-dma.o
snd-soc-ux500-mach-mop500-objs := mop500.o mop500_ab8500.o
obj-$(CONFIG_SND_SOC_UX500_MACH_MOP500) += snd-soc-ux500-mach-mop500.o

170
sound/soc/ux500/mop500.c Normal file
View file

@ -0,0 +1,170 @@
/*
* Copyright (C) ST-Ericsson SA 2012
*
* Author: Ola Lilja (ola.o.lilja@stericsson.com)
* for ST-Ericsson.
*
* License terms:
*
* 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 <asm/mach-types.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/spi/spi.h>
#include <linux/of.h>
#include <sound/soc.h>
#include <sound/initval.h>
#include "ux500_pcm.h"
#include "ux500_msp_dai.h"
#include "mop500_ab8500.h"
/* Define the whole MOP500 soundcard, linking platform to the codec-drivers */
static struct snd_soc_dai_link mop500_dai_links[] = {
{
.name = "ab8500_0",
.stream_name = "ab8500_0",
.cpu_dai_name = "ux500-msp-i2s.1",
.codec_dai_name = "ab8500-codec-dai.0",
.platform_name = "ux500-msp-i2s.1",
.codec_name = "ab8500-codec.0",
.init = mop500_ab8500_machine_init,
.ops = mop500_ab8500_ops,
},
{
.name = "ab8500_1",
.stream_name = "ab8500_1",
.cpu_dai_name = "ux500-msp-i2s.3",
.codec_dai_name = "ab8500-codec-dai.1",
.platform_name = "ux500-msp-i2s.3",
.codec_name = "ab8500-codec.0",
.init = NULL,
.ops = mop500_ab8500_ops,
},
};
static struct snd_soc_card mop500_card = {
.name = "MOP500-card",
.owner = THIS_MODULE,
.probe = NULL,
.dai_link = mop500_dai_links,
.num_links = ARRAY_SIZE(mop500_dai_links),
};
static void mop500_of_node_put(void)
{
int i;
for (i = 0; i < 2; i++) {
if (mop500_dai_links[i].cpu_of_node)
of_node_put((struct device_node *)
mop500_dai_links[i].cpu_of_node);
if (mop500_dai_links[i].codec_of_node)
of_node_put((struct device_node *)
mop500_dai_links[i].codec_of_node);
}
}
static int mop500_of_probe(struct platform_device *pdev,
struct device_node *np)
{
struct device_node *codec_np, *msp_np[2];
int i;
msp_np[0] = of_parse_phandle(np, "stericsson,cpu-dai", 0);
msp_np[1] = of_parse_phandle(np, "stericsson,cpu-dai", 1);
codec_np = of_parse_phandle(np, "stericsson,audio-codec", 0);
if (!(msp_np[0] && msp_np[1] && codec_np)) {
dev_err(&pdev->dev, "Phandle missing or invalid\n");
mop500_of_node_put();
return -EINVAL;
}
for (i = 0; i < 2; i++) {
mop500_dai_links[i].cpu_of_node = msp_np[i];
mop500_dai_links[i].cpu_dai_name = NULL;
mop500_dai_links[i].platform_of_node = msp_np[i];
mop500_dai_links[i].platform_name = NULL;
mop500_dai_links[i].codec_of_node = codec_np;
mop500_dai_links[i].codec_name = NULL;
}
snd_soc_of_parse_card_name(&mop500_card, "stericsson,card-name");
return 0;
}
static int mop500_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
int ret;
dev_dbg(&pdev->dev, "%s: Enter.\n", __func__);
mop500_card.dev = &pdev->dev;
if (np) {
ret = mop500_of_probe(pdev, np);
if (ret)
return ret;
}
dev_dbg(&pdev->dev, "%s: Card %s: Set platform drvdata.\n",
__func__, mop500_card.name);
platform_set_drvdata(pdev, &mop500_card);
snd_soc_card_set_drvdata(&mop500_card, NULL);
dev_dbg(&pdev->dev, "%s: Card %s: num_links = %d\n",
__func__, mop500_card.name, mop500_card.num_links);
dev_dbg(&pdev->dev, "%s: Card %s: DAI-link 0: name = %s\n",
__func__, mop500_card.name, mop500_card.dai_link[0].name);
dev_dbg(&pdev->dev, "%s: Card %s: DAI-link 0: stream_name = %s\n",
__func__, mop500_card.name,
mop500_card.dai_link[0].stream_name);
ret = snd_soc_register_card(&mop500_card);
if (ret)
dev_err(&pdev->dev,
"Error: snd_soc_register_card failed (%d)!\n", ret);
return ret;
}
static int mop500_remove(struct platform_device *pdev)
{
struct snd_soc_card *mop500_card = platform_get_drvdata(pdev);
pr_debug("%s: Enter.\n", __func__);
snd_soc_unregister_card(mop500_card);
mop500_ab8500_remove(mop500_card);
mop500_of_node_put();
return 0;
}
static const struct of_device_id snd_soc_mop500_match[] = {
{ .compatible = "stericsson,snd-soc-mop500", },
{},
};
static struct platform_driver snd_soc_mop500_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "snd-soc-mop500",
.of_match_table = snd_soc_mop500_match,
},
.probe = mop500_probe,
.remove = mop500_remove,
};
module_platform_driver(snd_soc_mop500_driver);

View file

@ -0,0 +1,455 @@
/*
* Copyright (C) ST-Ericsson SA 2012
*
* Author: Ola Lilja <ola.o.lilja@stericsson.com>,
* Kristoffer Karlsson <kristoffer.karlsson@stericsson.com>
* for ST-Ericsson.
*
* License terms:
*
* 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 <linux/module.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/mutex.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include "ux500_pcm.h"
#include "ux500_msp_dai.h"
#include "mop500_ab8500.h"
#include "../codecs/ab8500-codec.h"
#define TX_SLOT_MONO 0x0008
#define TX_SLOT_STEREO 0x000a
#define RX_SLOT_MONO 0x0001
#define RX_SLOT_STEREO 0x0003
#define TX_SLOT_8CH 0x00FF
#define RX_SLOT_8CH 0x00FF
#define DEF_TX_SLOTS TX_SLOT_STEREO
#define DEF_RX_SLOTS RX_SLOT_MONO
#define DRIVERMODE_NORMAL 0
#define DRIVERMODE_CODEC_ONLY 1
/* Slot configuration */
static unsigned int tx_slots = DEF_TX_SLOTS;
static unsigned int rx_slots = DEF_RX_SLOTS;
/* Configuration consistency parameters */
static DEFINE_MUTEX(mop500_ab8500_params_lock);
static unsigned long mop500_ab8500_usage;
static int mop500_ab8500_rate;
static int mop500_ab8500_channels;
/* Clocks */
static const char * const enum_mclk[] = {
"SYSCLK",
"ULPCLK"
};
enum mclk {
MCLK_SYSCLK,
MCLK_ULPCLK,
};
static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_mclk, enum_mclk);
/* Private data for machine-part MOP500<->AB8500 */
struct mop500_ab8500_drvdata {
/* Clocks */
enum mclk mclk_sel;
struct clk *clk_ptr_intclk;
struct clk *clk_ptr_sysclk;
struct clk *clk_ptr_ulpclk;
};
static inline const char *get_mclk_str(enum mclk mclk_sel)
{
switch (mclk_sel) {
case MCLK_SYSCLK:
return "SYSCLK";
case MCLK_ULPCLK:
return "ULPCLK";
default:
return "Unknown";
}
}
static int mop500_ab8500_set_mclk(struct device *dev,
struct mop500_ab8500_drvdata *drvdata)
{
int status;
struct clk *clk_ptr;
if (IS_ERR(drvdata->clk_ptr_intclk)) {
dev_err(dev,
"%s: ERROR: intclk not initialized!\n", __func__);
return -EIO;
}
switch (drvdata->mclk_sel) {
case MCLK_SYSCLK:
clk_ptr = drvdata->clk_ptr_sysclk;
break;
case MCLK_ULPCLK:
clk_ptr = drvdata->clk_ptr_ulpclk;
break;
default:
return -EINVAL;
}
if (IS_ERR(clk_ptr)) {
dev_err(dev, "%s: ERROR: %s not initialized!\n", __func__,
get_mclk_str(drvdata->mclk_sel));
return -EIO;
}
status = clk_set_parent(drvdata->clk_ptr_intclk, clk_ptr);
if (status)
dev_err(dev,
"%s: ERROR: Setting intclk parent to %s failed (ret = %d)!",
__func__, get_mclk_str(drvdata->mclk_sel), status);
else
dev_dbg(dev,
"%s: intclk parent changed to %s.\n",
__func__, get_mclk_str(drvdata->mclk_sel));
return status;
}
/*
* Control-events
*/
static int mclk_input_control_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
struct mop500_ab8500_drvdata *drvdata =
snd_soc_card_get_drvdata(card);
ucontrol->value.enumerated.item[0] = drvdata->mclk_sel;
return 0;
}
static int mclk_input_control_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
struct mop500_ab8500_drvdata *drvdata =
snd_soc_card_get_drvdata(card);
unsigned int val = ucontrol->value.enumerated.item[0];
if (val > (unsigned int)MCLK_ULPCLK)
return -EINVAL;
if (drvdata->mclk_sel == val)
return 0;
drvdata->mclk_sel = val;
return 1;
}
/*
* Controls
*/
static struct snd_kcontrol_new mop500_ab8500_ctrls[] = {
SOC_ENUM_EXT("Master Clock Select",
soc_enum_mclk,
mclk_input_control_get, mclk_input_control_put),
SOC_DAPM_PIN_SWITCH("Headset Left"),
SOC_DAPM_PIN_SWITCH("Headset Right"),
SOC_DAPM_PIN_SWITCH("Earpiece"),
SOC_DAPM_PIN_SWITCH("Speaker Left"),
SOC_DAPM_PIN_SWITCH("Speaker Right"),
SOC_DAPM_PIN_SWITCH("LineOut Left"),
SOC_DAPM_PIN_SWITCH("LineOut Right"),
SOC_DAPM_PIN_SWITCH("Vibra 1"),
SOC_DAPM_PIN_SWITCH("Vibra 2"),
SOC_DAPM_PIN_SWITCH("Mic 1"),
SOC_DAPM_PIN_SWITCH("Mic 2"),
SOC_DAPM_PIN_SWITCH("LineIn Left"),
SOC_DAPM_PIN_SWITCH("LineIn Right"),
SOC_DAPM_PIN_SWITCH("DMic 1"),
SOC_DAPM_PIN_SWITCH("DMic 2"),
SOC_DAPM_PIN_SWITCH("DMic 3"),
SOC_DAPM_PIN_SWITCH("DMic 4"),
SOC_DAPM_PIN_SWITCH("DMic 5"),
SOC_DAPM_PIN_SWITCH("DMic 6"),
};
/* ASoC */
static int mop500_ab8500_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
/* Set audio-clock source */
return mop500_ab8500_set_mclk(rtd->card->dev,
snd_soc_card_get_drvdata(rtd->card));
}
static void mop500_ab8500_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct device *dev = rtd->card->dev;
dev_dbg(dev, "%s: Enter\n", __func__);
/* Reset slots configuration to default(s) */
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
tx_slots = DEF_TX_SLOTS;
else
rx_slots = DEF_RX_SLOTS;
}
static int mop500_ab8500_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;
struct device *dev = rtd->card->dev;
unsigned int fmt;
int channels, ret = 0, driver_mode, slots;
unsigned int sw_codec, sw_cpu;
bool is_playback;
dev_dbg(dev, "%s: Enter\n", __func__);
dev_dbg(dev, "%s: substream->pcm->name = %s\n"
"substream->pcm->id = %s.\n"
"substream->name = %s.\n"
"substream->number = %d.\n",
__func__,
substream->pcm->name,
substream->pcm->id,
substream->name,
substream->number);
/* Ensure configuration consistency between DAIs */
mutex_lock(&mop500_ab8500_params_lock);
if (mop500_ab8500_usage) {
if (mop500_ab8500_rate != params_rate(params) ||
mop500_ab8500_channels != params_channels(params)) {
mutex_unlock(&mop500_ab8500_params_lock);
return -EBUSY;
}
} else {
mop500_ab8500_rate = params_rate(params);
mop500_ab8500_channels = params_channels(params);
}
__set_bit(cpu_dai->id, &mop500_ab8500_usage);
mutex_unlock(&mop500_ab8500_params_lock);
channels = params_channels(params);
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S32_LE:
sw_cpu = 32;
break;
case SNDRV_PCM_FORMAT_S16_LE:
sw_cpu = 16;
break;
default:
return -EINVAL;
}
/* Setup codec depending on driver-mode */
if (channels == 8)
driver_mode = DRIVERMODE_CODEC_ONLY;
else
driver_mode = DRIVERMODE_NORMAL;
dev_dbg(dev, "%s: Driver-mode: %s.\n", __func__,
(driver_mode == DRIVERMODE_NORMAL) ? "NORMAL" : "CODEC_ONLY");
/* Setup format */
if (driver_mode == DRIVERMODE_NORMAL) {
fmt = SND_SOC_DAIFMT_DSP_A |
SND_SOC_DAIFMT_CBM_CFM |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CONT;
} else {
fmt = SND_SOC_DAIFMT_DSP_A |
SND_SOC_DAIFMT_CBM_CFM |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_GATED;
}
ret = snd_soc_dai_set_fmt(codec_dai, fmt);
if (ret < 0) {
dev_err(dev,
"%s: ERROR: snd_soc_dai_set_fmt failed for codec_dai (ret = %d)!\n",
__func__, ret);
return ret;
}
ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
if (ret < 0) {
dev_err(dev,
"%s: ERROR: snd_soc_dai_set_fmt failed for cpu_dai (ret = %d)!\n",
__func__, ret);
return ret;
}
/* Setup TDM-slots */
is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
switch (channels) {
case 1:
slots = 16;
tx_slots = (is_playback) ? TX_SLOT_MONO : 0;
rx_slots = (is_playback) ? 0 : RX_SLOT_MONO;
break;
case 2:
slots = 16;
tx_slots = (is_playback) ? TX_SLOT_STEREO : 0;
rx_slots = (is_playback) ? 0 : RX_SLOT_STEREO;
break;
case 8:
slots = 16;
tx_slots = (is_playback) ? TX_SLOT_8CH : 0;
rx_slots = (is_playback) ? 0 : RX_SLOT_8CH;
break;
default:
return -EINVAL;
}
if (driver_mode == DRIVERMODE_NORMAL)
sw_codec = sw_cpu;
else
sw_codec = 20;
dev_dbg(dev, "%s: CPU-DAI TDM: TX=0x%04X RX=0x%04x\n", __func__,
tx_slots, rx_slots);
ret = snd_soc_dai_set_tdm_slot(cpu_dai, tx_slots, rx_slots, slots,
sw_cpu);
if (ret)
return ret;
dev_dbg(dev, "%s: CODEC-DAI TDM: TX=0x%04X RX=0x%04x\n", __func__,
tx_slots, rx_slots);
ret = snd_soc_dai_set_tdm_slot(codec_dai, tx_slots, rx_slots, slots,
sw_codec);
if (ret)
return ret;
return 0;
}
static int mop500_ab8500_hw_free(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
mutex_lock(&mop500_ab8500_params_lock);
__clear_bit(cpu_dai->id, &mop500_ab8500_usage);
mutex_unlock(&mop500_ab8500_params_lock);
return 0;
}
struct snd_soc_ops mop500_ab8500_ops[] = {
{
.hw_params = mop500_ab8500_hw_params,
.hw_free = mop500_ab8500_hw_free,
.startup = mop500_ab8500_startup,
.shutdown = mop500_ab8500_shutdown,
}
};
int mop500_ab8500_machine_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_codec *codec = rtd->codec;
struct device *dev = rtd->card->dev;
struct mop500_ab8500_drvdata *drvdata;
int ret;
dev_dbg(dev, "%s Enter.\n", __func__);
/* Create driver private-data struct */
drvdata = devm_kzalloc(dev, sizeof(struct mop500_ab8500_drvdata),
GFP_KERNEL);
snd_soc_card_set_drvdata(rtd->card, drvdata);
/* Setup clocks */
drvdata->clk_ptr_sysclk = clk_get(dev, "sysclk");
if (IS_ERR(drvdata->clk_ptr_sysclk))
dev_warn(dev, "%s: WARNING: clk_get failed for 'sysclk'!\n",
__func__);
drvdata->clk_ptr_ulpclk = clk_get(dev, "ulpclk");
if (IS_ERR(drvdata->clk_ptr_ulpclk))
dev_warn(dev, "%s: WARNING: clk_get failed for 'ulpclk'!\n",
__func__);
drvdata->clk_ptr_intclk = clk_get(dev, "intclk");
if (IS_ERR(drvdata->clk_ptr_intclk))
dev_warn(dev, "%s: WARNING: clk_get failed for 'intclk'!\n",
__func__);
/* Set intclk default parent to ulpclk */
drvdata->mclk_sel = MCLK_ULPCLK;
ret = mop500_ab8500_set_mclk(dev, drvdata);
if (ret < 0)
dev_warn(dev, "%s: WARNING: mop500_ab8500_set_mclk!\n",
__func__);
drvdata->mclk_sel = MCLK_ULPCLK;
/* Add controls */
ret = snd_soc_add_card_controls(rtd->card, mop500_ab8500_ctrls,
ARRAY_SIZE(mop500_ab8500_ctrls));
if (ret < 0) {
pr_err("%s: Failed to add machine-controls (%d)!\n",
__func__, ret);
return ret;
}
ret = snd_soc_dapm_disable_pin(&codec->dapm, "Earpiece");
ret |= snd_soc_dapm_disable_pin(&codec->dapm, "Speaker Left");
ret |= snd_soc_dapm_disable_pin(&codec->dapm, "Speaker Right");
ret |= snd_soc_dapm_disable_pin(&codec->dapm, "LineOut Left");
ret |= snd_soc_dapm_disable_pin(&codec->dapm, "LineOut Right");
ret |= snd_soc_dapm_disable_pin(&codec->dapm, "Vibra 1");
ret |= snd_soc_dapm_disable_pin(&codec->dapm, "Vibra 2");
ret |= snd_soc_dapm_disable_pin(&codec->dapm, "Mic 1");
ret |= snd_soc_dapm_disable_pin(&codec->dapm, "Mic 2");
ret |= snd_soc_dapm_disable_pin(&codec->dapm, "LineIn Left");
ret |= snd_soc_dapm_disable_pin(&codec->dapm, "LineIn Right");
ret |= snd_soc_dapm_disable_pin(&codec->dapm, "DMic 1");
ret |= snd_soc_dapm_disable_pin(&codec->dapm, "DMic 2");
ret |= snd_soc_dapm_disable_pin(&codec->dapm, "DMic 3");
ret |= snd_soc_dapm_disable_pin(&codec->dapm, "DMic 4");
ret |= snd_soc_dapm_disable_pin(&codec->dapm, "DMic 5");
ret |= snd_soc_dapm_disable_pin(&codec->dapm, "DMic 6");
return ret;
}
void mop500_ab8500_remove(struct snd_soc_card *card)
{
struct mop500_ab8500_drvdata *drvdata = snd_soc_card_get_drvdata(card);
if (drvdata->clk_ptr_sysclk != NULL)
clk_put(drvdata->clk_ptr_sysclk);
if (drvdata->clk_ptr_ulpclk != NULL)
clk_put(drvdata->clk_ptr_ulpclk);
if (drvdata->clk_ptr_intclk != NULL)
clk_put(drvdata->clk_ptr_intclk);
snd_soc_card_set_drvdata(card, drvdata);
}

View file

@ -0,0 +1,22 @@
/*
* Copyright (C) ST-Ericsson SA 2012
*
* Author: Ola Lilja <ola.o.lilja@stericsson.com>
* for ST-Ericsson.
*
* License terms:
*
* 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 MOP500_AB8500_H
#define MOP500_AB8500_H
extern struct snd_soc_ops mop500_ab8500_ops[];
int mop500_ab8500_machine_init(struct snd_soc_pcm_runtime *runtime);
void mop500_ab8500_remove(struct snd_soc_card *card);
#endif

View file

@ -0,0 +1,867 @@
/*
* Copyright (C) ST-Ericsson SA 2012
*
* Author: Ola Lilja <ola.o.lilja@stericsson.com>,
* Roger Nilsson <roger.xr.nilsson@stericsson.com>
* for ST-Ericsson.
*
* License terms:
*
* 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 <linux/module.h>
#include <linux/slab.h>
#include <linux/bitops.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/of.h>
#include <linux/regulator/consumer.h>
#include <linux/mfd/dbx500-prcmu.h>
#include <linux/platform_data/asoc-ux500-msp.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include <sound/dmaengine_pcm.h>
#include "ux500_msp_i2s.h"
#include "ux500_msp_dai.h"
#include "ux500_pcm.h"
static int setup_pcm_multichan(struct snd_soc_dai *dai,
struct ux500_msp_config *msp_config)
{
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
struct msp_multichannel_config *multi =
&msp_config->multichannel_config;
if (drvdata->slots > 1) {
msp_config->multichannel_configured = 1;
multi->tx_multichannel_enable = true;
multi->rx_multichannel_enable = true;
multi->rx_comparison_enable_mode = MSP_COMPARISON_DISABLED;
multi->tx_channel_0_enable = drvdata->tx_mask;
multi->tx_channel_1_enable = 0;
multi->tx_channel_2_enable = 0;
multi->tx_channel_3_enable = 0;
multi->rx_channel_0_enable = drvdata->rx_mask;
multi->rx_channel_1_enable = 0;
multi->rx_channel_2_enable = 0;
multi->rx_channel_3_enable = 0;
dev_dbg(dai->dev,
"%s: Multichannel enabled. Slots: %d, TX: %u, RX: %u\n",
__func__, drvdata->slots, multi->tx_channel_0_enable,
multi->rx_channel_0_enable);
}
return 0;
}
static int setup_frameper(struct snd_soc_dai *dai, unsigned int rate,
struct msp_protdesc *prot_desc)
{
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
switch (drvdata->slots) {
case 1:
switch (rate) {
case 8000:
prot_desc->frame_period =
FRAME_PER_SINGLE_SLOT_8_KHZ;
break;
case 16000:
prot_desc->frame_period =
FRAME_PER_SINGLE_SLOT_16_KHZ;
break;
case 44100:
prot_desc->frame_period =
FRAME_PER_SINGLE_SLOT_44_1_KHZ;
break;
case 48000:
prot_desc->frame_period =
FRAME_PER_SINGLE_SLOT_48_KHZ;
break;
default:
dev_err(dai->dev,
"%s: Error: Unsupported sample-rate (freq = %d)!\n",
__func__, rate);
return -EINVAL;
}
break;
case 2:
prot_desc->frame_period = FRAME_PER_2_SLOTS;
break;
case 8:
prot_desc->frame_period = FRAME_PER_8_SLOTS;
break;
case 16:
prot_desc->frame_period = FRAME_PER_16_SLOTS;
break;
default:
dev_err(dai->dev,
"%s: Error: Unsupported slot-count (slots = %d)!\n",
__func__, drvdata->slots);
return -EINVAL;
}
prot_desc->clocks_per_frame =
prot_desc->frame_period+1;
dev_dbg(dai->dev, "%s: Clocks per frame: %u\n",
__func__,
prot_desc->clocks_per_frame);
return 0;
}
static int setup_pcm_framing(struct snd_soc_dai *dai, unsigned int rate,
struct msp_protdesc *prot_desc)
{
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
u32 frame_length = MSP_FRAME_LEN_1;
prot_desc->frame_width = 0;
switch (drvdata->slots) {
case 1:
frame_length = MSP_FRAME_LEN_1;
break;
case 2:
frame_length = MSP_FRAME_LEN_2;
break;
case 8:
frame_length = MSP_FRAME_LEN_8;
break;
case 16:
frame_length = MSP_FRAME_LEN_16;
break;
default:
dev_err(dai->dev,
"%s: Error: Unsupported slot-count (slots = %d)!\n",
__func__, drvdata->slots);
return -EINVAL;
}
prot_desc->tx_frame_len_1 = frame_length;
prot_desc->rx_frame_len_1 = frame_length;
prot_desc->tx_frame_len_2 = frame_length;
prot_desc->rx_frame_len_2 = frame_length;
prot_desc->tx_elem_len_1 = MSP_ELEM_LEN_16;
prot_desc->rx_elem_len_1 = MSP_ELEM_LEN_16;
prot_desc->tx_elem_len_2 = MSP_ELEM_LEN_16;
prot_desc->rx_elem_len_2 = MSP_ELEM_LEN_16;
return setup_frameper(dai, rate, prot_desc);
}
static int setup_clocking(struct snd_soc_dai *dai,
unsigned int fmt,
struct ux500_msp_config *msp_config)
{
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
break;
case SND_SOC_DAIFMT_NB_IF:
msp_config->tx_fsync_pol ^= 1 << TFSPOL_SHIFT;
msp_config->rx_fsync_pol ^= 1 << RFSPOL_SHIFT;
break;
default:
dev_err(dai->dev,
"%s: Error: Unsopported inversion (fmt = 0x%x)!\n",
__func__, fmt);
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBM_CFM:
dev_dbg(dai->dev, "%s: Codec is master.\n", __func__);
msp_config->iodelay = 0x20;
msp_config->rx_fsync_sel = 0;
msp_config->tx_fsync_sel = 1 << TFSSEL_SHIFT;
msp_config->tx_clk_sel = 0;
msp_config->rx_clk_sel = 0;
msp_config->srg_clk_sel = 0x2 << SCKSEL_SHIFT;
break;
case SND_SOC_DAIFMT_CBS_CFS:
dev_dbg(dai->dev, "%s: Codec is slave.\n", __func__);
msp_config->tx_clk_sel = TX_CLK_SEL_SRG;
msp_config->tx_fsync_sel = TX_SYNC_SRG_PROG;
msp_config->rx_clk_sel = RX_CLK_SEL_SRG;
msp_config->rx_fsync_sel = RX_SYNC_SRG;
msp_config->srg_clk_sel = 1 << SCKSEL_SHIFT;
break;
default:
dev_err(dai->dev, "%s: Error: Unsopported master (fmt = 0x%x)!\n",
__func__, fmt);
return -EINVAL;
}
return 0;
}
static int setup_pcm_protdesc(struct snd_soc_dai *dai,
unsigned int fmt,
struct msp_protdesc *prot_desc)
{
prot_desc->rx_phase_mode = MSP_SINGLE_PHASE;
prot_desc->tx_phase_mode = MSP_SINGLE_PHASE;
prot_desc->rx_phase2_start_mode = MSP_PHASE2_START_MODE_IMEDIATE;
prot_desc->tx_phase2_start_mode = MSP_PHASE2_START_MODE_IMEDIATE;
prot_desc->rx_byte_order = MSP_BTF_MS_BIT_FIRST;
prot_desc->tx_byte_order = MSP_BTF_MS_BIT_FIRST;
prot_desc->tx_fsync_pol = MSP_FSYNC_POL(MSP_FSYNC_POL_ACT_HI);
prot_desc->rx_fsync_pol = MSP_FSYNC_POL_ACT_HI << RFSPOL_SHIFT;
if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_DSP_A) {
dev_dbg(dai->dev, "%s: DSP_A.\n", __func__);
prot_desc->rx_clk_pol = MSP_RISING_EDGE;
prot_desc->tx_clk_pol = MSP_FALLING_EDGE;
prot_desc->rx_data_delay = MSP_DELAY_1;
prot_desc->tx_data_delay = MSP_DELAY_1;
} else {
dev_dbg(dai->dev, "%s: DSP_B.\n", __func__);
prot_desc->rx_clk_pol = MSP_FALLING_EDGE;
prot_desc->tx_clk_pol = MSP_RISING_EDGE;
prot_desc->rx_data_delay = MSP_DELAY_0;
prot_desc->tx_data_delay = MSP_DELAY_0;
}
prot_desc->rx_half_word_swap = MSP_SWAP_NONE;
prot_desc->tx_half_word_swap = MSP_SWAP_NONE;
prot_desc->compression_mode = MSP_COMPRESS_MODE_LINEAR;
prot_desc->expansion_mode = MSP_EXPAND_MODE_LINEAR;
prot_desc->frame_sync_ignore = MSP_FSYNC_IGNORE;
return 0;
}
static int setup_i2s_protdesc(struct msp_protdesc *prot_desc)
{
prot_desc->rx_phase_mode = MSP_DUAL_PHASE;
prot_desc->tx_phase_mode = MSP_DUAL_PHASE;
prot_desc->rx_phase2_start_mode = MSP_PHASE2_START_MODE_FSYNC;
prot_desc->tx_phase2_start_mode = MSP_PHASE2_START_MODE_FSYNC;
prot_desc->rx_byte_order = MSP_BTF_MS_BIT_FIRST;
prot_desc->tx_byte_order = MSP_BTF_MS_BIT_FIRST;
prot_desc->tx_fsync_pol = MSP_FSYNC_POL(MSP_FSYNC_POL_ACT_LO);
prot_desc->rx_fsync_pol = MSP_FSYNC_POL_ACT_LO << RFSPOL_SHIFT;
prot_desc->rx_frame_len_1 = MSP_FRAME_LEN_1;
prot_desc->rx_frame_len_2 = MSP_FRAME_LEN_1;
prot_desc->tx_frame_len_1 = MSP_FRAME_LEN_1;
prot_desc->tx_frame_len_2 = MSP_FRAME_LEN_1;
prot_desc->rx_elem_len_1 = MSP_ELEM_LEN_16;
prot_desc->rx_elem_len_2 = MSP_ELEM_LEN_16;
prot_desc->tx_elem_len_1 = MSP_ELEM_LEN_16;
prot_desc->tx_elem_len_2 = MSP_ELEM_LEN_16;
prot_desc->rx_clk_pol = MSP_RISING_EDGE;
prot_desc->tx_clk_pol = MSP_FALLING_EDGE;
prot_desc->rx_data_delay = MSP_DELAY_0;
prot_desc->tx_data_delay = MSP_DELAY_0;
prot_desc->tx_half_word_swap = MSP_SWAP_NONE;
prot_desc->rx_half_word_swap = MSP_SWAP_NONE;
prot_desc->compression_mode = MSP_COMPRESS_MODE_LINEAR;
prot_desc->expansion_mode = MSP_EXPAND_MODE_LINEAR;
prot_desc->frame_sync_ignore = MSP_FSYNC_IGNORE;
return 0;
}
static int setup_msp_config(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai,
struct ux500_msp_config *msp_config)
{
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
struct msp_protdesc *prot_desc = &msp_config->protdesc;
struct snd_pcm_runtime *runtime = substream->runtime;
unsigned int fmt = drvdata->fmt;
int ret;
memset(msp_config, 0, sizeof(*msp_config));
msp_config->f_inputclk = drvdata->master_clk;
msp_config->tx_fifo_config = TX_FIFO_ENABLE;
msp_config->rx_fifo_config = RX_FIFO_ENABLE;
msp_config->def_elem_len = 1;
msp_config->direction = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
MSP_DIR_TX : MSP_DIR_RX;
msp_config->data_size = MSP_DATA_BITS_32;
msp_config->frame_freq = runtime->rate;
dev_dbg(dai->dev, "%s: f_inputclk = %u, frame_freq = %u.\n",
__func__, msp_config->f_inputclk, msp_config->frame_freq);
/* To avoid division by zero */
prot_desc->clocks_per_frame = 1;
dev_dbg(dai->dev, "%s: rate: %u, channels: %d.\n", __func__,
runtime->rate, runtime->channels);
switch (fmt &
(SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_MASTER_MASK)) {
case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS:
dev_dbg(dai->dev, "%s: SND_SOC_DAIFMT_I2S.\n", __func__);
msp_config->default_protdesc = 1;
msp_config->protocol = MSP_I2S_PROTOCOL;
break;
case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM:
dev_dbg(dai->dev, "%s: SND_SOC_DAIFMT_I2S.\n", __func__);
msp_config->data_size = MSP_DATA_BITS_16;
msp_config->protocol = MSP_I2S_PROTOCOL;
ret = setup_i2s_protdesc(prot_desc);
if (ret < 0)
return ret;
break;
case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS:
case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBM_CFM:
case SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBS_CFS:
case SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBM_CFM:
dev_dbg(dai->dev, "%s: PCM format.\n", __func__);
msp_config->data_size = MSP_DATA_BITS_16;
msp_config->protocol = MSP_PCM_PROTOCOL;
ret = setup_pcm_protdesc(dai, fmt, prot_desc);
if (ret < 0)
return ret;
ret = setup_pcm_multichan(dai, msp_config);
if (ret < 0)
return ret;
ret = setup_pcm_framing(dai, runtime->rate, prot_desc);
if (ret < 0)
return ret;
break;
default:
dev_err(dai->dev, "%s: Error: Unsopported format (%d)!\n",
__func__, fmt);
return -EINVAL;
}
return setup_clocking(dai, fmt, msp_config);
}
static int ux500_msp_dai_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
int ret = 0;
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
dev_dbg(dai->dev, "%s: MSP %d (%s): Enter.\n", __func__, dai->id,
snd_pcm_stream_str(substream));
/* Enable regulator */
ret = regulator_enable(drvdata->reg_vape);
if (ret != 0) {
dev_err(drvdata->msp->dev,
"%s: Failed to enable regulator!\n", __func__);
return ret;
}
/* Prepare and enable clocks */
dev_dbg(dai->dev, "%s: Enabling MSP-clocks.\n", __func__);
ret = clk_prepare_enable(drvdata->pclk);
if (ret) {
dev_err(drvdata->msp->dev,
"%s: Failed to prepare/enable pclk!\n", __func__);
goto err_pclk;
}
ret = clk_prepare_enable(drvdata->clk);
if (ret) {
dev_err(drvdata->msp->dev,
"%s: Failed to prepare/enable clk!\n", __func__);
goto err_clk;
}
return ret;
err_clk:
clk_disable_unprepare(drvdata->pclk);
err_pclk:
regulator_disable(drvdata->reg_vape);
return ret;
}
static void ux500_msp_dai_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
int ret;
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
dev_dbg(dai->dev, "%s: MSP %d (%s): Enter.\n", __func__, dai->id,
snd_pcm_stream_str(substream));
if (drvdata->vape_opp_constraint == 1) {
prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP,
"ux500_msp_i2s", 50);
drvdata->vape_opp_constraint = 0;
}
if (ux500_msp_i2s_close(drvdata->msp,
is_playback ? MSP_DIR_TX : MSP_DIR_RX)) {
dev_err(dai->dev,
"%s: Error: MSP %d (%s): Unable to close i2s.\n",
__func__, dai->id, snd_pcm_stream_str(substream));
}
/* Disable and unprepare clocks */
clk_disable_unprepare(drvdata->clk);
clk_disable_unprepare(drvdata->pclk);
/* Disable regulator */
ret = regulator_disable(drvdata->reg_vape);
if (ret < 0)
dev_err(dai->dev,
"%s: ERROR: Failed to disable regulator (%d)!\n",
__func__, ret);
}
static int ux500_msp_dai_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
int ret = 0;
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
struct snd_pcm_runtime *runtime = substream->runtime;
struct ux500_msp_config msp_config;
dev_dbg(dai->dev, "%s: MSP %d (%s): Enter (rate = %d).\n", __func__,
dai->id, snd_pcm_stream_str(substream), runtime->rate);
setup_msp_config(substream, dai, &msp_config);
ret = ux500_msp_i2s_open(drvdata->msp, &msp_config);
if (ret < 0) {
dev_err(dai->dev, "%s: Error: msp_setup failed (ret = %d)!\n",
__func__, ret);
return ret;
}
/* Set OPP-level */
if ((drvdata->fmt & SND_SOC_DAIFMT_MASTER_MASK) &&
(drvdata->msp->f_bitclk > 19200000)) {
/* If the bit-clock is higher than 19.2MHz, Vape should be
* run in 100% OPP. Only when bit-clock is used (MSP master) */
prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP,
"ux500-msp-i2s", 100);
drvdata->vape_opp_constraint = 1;
} else {
prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP,
"ux500-msp-i2s", 50);
drvdata->vape_opp_constraint = 0;
}
return ret;
}
static int ux500_msp_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
unsigned int mask, slots_active;
struct snd_pcm_runtime *runtime = substream->runtime;
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
dev_dbg(dai->dev, "%s: MSP %d (%s): Enter.\n",
__func__, dai->id, snd_pcm_stream_str(substream));
switch (drvdata->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
snd_pcm_hw_constraint_minmax(runtime,
SNDRV_PCM_HW_PARAM_CHANNELS,
1, 2);
break;
case SND_SOC_DAIFMT_DSP_B:
case SND_SOC_DAIFMT_DSP_A:
mask = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
drvdata->tx_mask :
drvdata->rx_mask;
slots_active = hweight32(mask);
dev_dbg(dai->dev, "TDM-slots active: %d", slots_active);
snd_pcm_hw_constraint_minmax(runtime,
SNDRV_PCM_HW_PARAM_CHANNELS,
slots_active, slots_active);
break;
default:
dev_err(dai->dev,
"%s: Error: Unsupported protocol (fmt = 0x%x)!\n",
__func__, drvdata->fmt);
return -EINVAL;
}
return 0;
}
static int ux500_msp_dai_set_dai_fmt(struct snd_soc_dai *dai,
unsigned int fmt)
{
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
dev_dbg(dai->dev, "%s: MSP %d: Enter.\n", __func__, dai->id);
switch (fmt & (SND_SOC_DAIFMT_FORMAT_MASK |
SND_SOC_DAIFMT_MASTER_MASK)) {
case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS:
case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM:
case SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBS_CFS:
case SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBM_CFM:
case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS:
case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBM_CFM:
break;
default:
dev_err(dai->dev,
"%s: Error: Unsupported protocol/master (fmt = 0x%x)!\n",
__func__, drvdata->fmt);
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
case SND_SOC_DAIFMT_NB_IF:
case SND_SOC_DAIFMT_IB_IF:
break;
default:
dev_err(dai->dev,
"%s: Error: Unsupported inversion (fmt = 0x%x)!\n",
__func__, drvdata->fmt);
return -EINVAL;
}
drvdata->fmt = fmt;
return 0;
}
static int ux500_msp_dai_set_tdm_slot(struct snd_soc_dai *dai,
unsigned int tx_mask,
unsigned int rx_mask,
int slots, int slot_width)
{
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
unsigned int cap;
switch (slots) {
case 1:
cap = 0x01;
break;
case 2:
cap = 0x03;
break;
case 8:
cap = 0xFF;
break;
case 16:
cap = 0xFFFF;
break;
default:
dev_err(dai->dev, "%s: Error: Unsupported slot-count (%d)!\n",
__func__, slots);
return -EINVAL;
}
drvdata->slots = slots;
if (!(slot_width == 16)) {
dev_err(dai->dev, "%s: Error: Unsupported slot-width (%d)!\n",
__func__, slot_width);
return -EINVAL;
}
drvdata->slot_width = slot_width;
drvdata->tx_mask = tx_mask & cap;
drvdata->rx_mask = rx_mask & cap;
return 0;
}
static int ux500_msp_dai_set_dai_sysclk(struct snd_soc_dai *dai,
int clk_id, unsigned int freq, int dir)
{
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
dev_dbg(dai->dev, "%s: MSP %d: Enter. clk-id: %d, freq: %u.\n",
__func__, dai->id, clk_id, freq);
switch (clk_id) {
case UX500_MSP_MASTER_CLOCK:
drvdata->master_clk = freq;
break;
default:
dev_err(dai->dev, "%s: MSP %d: Invalid clk-id (%d)!\n",
__func__, dai->id, clk_id);
return -EINVAL;
}
return 0;
}
static int ux500_msp_dai_trigger(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *dai)
{
int ret = 0;
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
dev_dbg(dai->dev, "%s: MSP %d (%s): Enter (msp->id = %d, cmd = %d).\n",
__func__, dai->id, snd_pcm_stream_str(substream),
(int)drvdata->msp->id, cmd);
ret = ux500_msp_i2s_trigger(drvdata->msp, cmd, substream->stream);
return ret;
}
static int ux500_msp_dai_of_probe(struct snd_soc_dai *dai)
{
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
struct snd_dmaengine_dai_dma_data *playback_dma_data;
struct snd_dmaengine_dai_dma_data *capture_dma_data;
playback_dma_data = devm_kzalloc(dai->dev,
sizeof(*playback_dma_data),
GFP_KERNEL);
if (!playback_dma_data)
return -ENOMEM;
capture_dma_data = devm_kzalloc(dai->dev,
sizeof(*capture_dma_data),
GFP_KERNEL);
if (!capture_dma_data)
return -ENOMEM;
playback_dma_data->addr = drvdata->msp->playback_dma_data.tx_rx_addr;
capture_dma_data->addr = drvdata->msp->capture_dma_data.tx_rx_addr;
playback_dma_data->maxburst = 4;
capture_dma_data->maxburst = 4;
snd_soc_dai_init_dma_data(dai, playback_dma_data, capture_dma_data);
return 0;
}
static int ux500_msp_dai_probe(struct snd_soc_dai *dai)
{
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
struct msp_i2s_platform_data *pdata = dai->dev->platform_data;
int ret;
if (!pdata) {
ret = ux500_msp_dai_of_probe(dai);
return ret;
}
drvdata->msp->playback_dma_data.data_size = drvdata->slot_width;
drvdata->msp->capture_dma_data.data_size = drvdata->slot_width;
snd_soc_dai_init_dma_data(dai,
&drvdata->msp->playback_dma_data,
&drvdata->msp->capture_dma_data);
return 0;
}
static struct snd_soc_dai_ops ux500_msp_dai_ops[] = {
{
.set_sysclk = ux500_msp_dai_set_dai_sysclk,
.set_fmt = ux500_msp_dai_set_dai_fmt,
.set_tdm_slot = ux500_msp_dai_set_tdm_slot,
.startup = ux500_msp_dai_startup,
.shutdown = ux500_msp_dai_shutdown,
.prepare = ux500_msp_dai_prepare,
.trigger = ux500_msp_dai_trigger,
.hw_params = ux500_msp_dai_hw_params,
}
};
static struct snd_soc_dai_driver ux500_msp_dai_drv = {
.probe = ux500_msp_dai_probe,
.suspend = NULL,
.resume = NULL,
.playback.channels_min = UX500_MSP_MIN_CHANNELS,
.playback.channels_max = UX500_MSP_MAX_CHANNELS,
.playback.rates = UX500_I2S_RATES,
.playback.formats = UX500_I2S_FORMATS,
.capture.channels_min = UX500_MSP_MIN_CHANNELS,
.capture.channels_max = UX500_MSP_MAX_CHANNELS,
.capture.rates = UX500_I2S_RATES,
.capture.formats = UX500_I2S_FORMATS,
.ops = ux500_msp_dai_ops,
};
static const struct snd_soc_component_driver ux500_msp_component = {
.name = "ux500-msp",
};
static int ux500_msp_drv_probe(struct platform_device *pdev)
{
struct ux500_msp_i2s_drvdata *drvdata;
struct msp_i2s_platform_data *pdata = pdev->dev.platform_data;
struct device_node *np = pdev->dev.of_node;
int ret = 0;
if (!pdata && !np) {
dev_err(&pdev->dev, "No platform data or Device Tree found\n");
return -ENODEV;
}
drvdata = devm_kzalloc(&pdev->dev,
sizeof(struct ux500_msp_i2s_drvdata),
GFP_KERNEL);
if (!drvdata)
return -ENOMEM;
drvdata->fmt = 0;
drvdata->slots = 1;
drvdata->tx_mask = 0x01;
drvdata->rx_mask = 0x01;
drvdata->slot_width = 16;
drvdata->master_clk = MSP_INPUT_FREQ_APB;
drvdata->reg_vape = devm_regulator_get(&pdev->dev, "v-ape");
if (IS_ERR(drvdata->reg_vape)) {
ret = (int)PTR_ERR(drvdata->reg_vape);
dev_err(&pdev->dev,
"%s: ERROR: Failed to get Vape supply (%d)!\n",
__func__, ret);
return ret;
}
prcmu_qos_add_requirement(PRCMU_QOS_APE_OPP, (char *)pdev->name, 50);
drvdata->pclk = clk_get(&pdev->dev, "apb_pclk");
if (IS_ERR(drvdata->pclk)) {
ret = (int)PTR_ERR(drvdata->pclk);
dev_err(&pdev->dev, "%s: ERROR: clk_get of pclk failed (%d)!\n",
__func__, ret);
goto err_pclk;
}
drvdata->clk = clk_get(&pdev->dev, NULL);
if (IS_ERR(drvdata->clk)) {
ret = (int)PTR_ERR(drvdata->clk);
dev_err(&pdev->dev, "%s: ERROR: clk_get failed (%d)!\n",
__func__, ret);
goto err_clk;
}
ret = ux500_msp_i2s_init_msp(pdev, &drvdata->msp,
pdev->dev.platform_data);
if (!drvdata->msp) {
dev_err(&pdev->dev,
"%s: ERROR: Failed to init MSP-struct (%d)!",
__func__, ret);
goto err_init_msp;
}
dev_set_drvdata(&pdev->dev, drvdata);
ret = snd_soc_register_component(&pdev->dev, &ux500_msp_component,
&ux500_msp_dai_drv, 1);
if (ret < 0) {
dev_err(&pdev->dev, "Error: %s: Failed to register MSP%d!\n",
__func__, drvdata->msp->id);
goto err_init_msp;
}
ret = ux500_pcm_register_platform(pdev);
if (ret < 0) {
dev_err(&pdev->dev,
"Error: %s: Failed to register PCM platform device!\n",
__func__);
goto err_reg_plat;
}
return 0;
err_reg_plat:
snd_soc_unregister_component(&pdev->dev);
err_init_msp:
clk_put(drvdata->clk);
err_clk:
clk_put(drvdata->pclk);
err_pclk:
devm_regulator_put(drvdata->reg_vape);
return ret;
}
static int ux500_msp_drv_remove(struct platform_device *pdev)
{
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(&pdev->dev);
ux500_pcm_unregister_platform(pdev);
snd_soc_unregister_component(&pdev->dev);
devm_regulator_put(drvdata->reg_vape);
prcmu_qos_remove_requirement(PRCMU_QOS_APE_OPP, "ux500_msp_i2s");
clk_put(drvdata->clk);
clk_put(drvdata->pclk);
ux500_msp_i2s_cleanup_msp(pdev, drvdata->msp);
return 0;
}
static const struct of_device_id ux500_msp_i2s_match[] = {
{ .compatible = "stericsson,ux500-msp-i2s", },
{},
};
static struct platform_driver msp_i2s_driver = {
.driver = {
.name = "ux500-msp-i2s",
.owner = THIS_MODULE,
.of_match_table = ux500_msp_i2s_match,
},
.probe = ux500_msp_drv_probe,
.remove = ux500_msp_drv_remove,
};
module_platform_driver(msp_i2s_driver);
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,71 @@
/*
* Copyright (C) ST-Ericsson SA 2012
*
* Author: Ola Lilja <ola.o.lilja@stericsson.com>,
* Roger Nilsson <roger.xr.nilsson@stericsson.com>
* for ST-Ericsson.
*
* License terms:
*
* 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 UX500_msp_dai_H
#define UX500_msp_dai_H
#include <linux/types.h>
#include <linux/spinlock.h>
#include "ux500_msp_i2s.h"
#define UX500_NBR_OF_DAI 4
#define UX500_I2S_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | \
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
#define UX500_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE)
#define FRAME_PER_SINGLE_SLOT_8_KHZ 31
#define FRAME_PER_SINGLE_SLOT_16_KHZ 124
#define FRAME_PER_SINGLE_SLOT_44_1_KHZ 63
#define FRAME_PER_SINGLE_SLOT_48_KHZ 49
#define FRAME_PER_2_SLOTS 31
#define FRAME_PER_8_SLOTS 138
#define FRAME_PER_16_SLOTS 277
#define UX500_MSP_INTERNAL_CLOCK_FREQ 40000000
#define UX500_MSP1_INTERNAL_CLOCK_FREQ UX500_MSP_INTERNAL_CLOCK_FREQ
#define UX500_MSP_MIN_CHANNELS 1
#define UX500_MSP_MAX_CHANNELS 8
#define PLAYBACK_CONFIGURED 1
#define CAPTURE_CONFIGURED 2
enum ux500_msp_clock_id {
UX500_MSP_MASTER_CLOCK,
};
struct ux500_msp_i2s_drvdata {
struct ux500_msp *msp;
struct regulator *reg_vape;
unsigned int fmt;
unsigned int tx_mask;
unsigned int rx_mask;
int slots;
int slot_width;
/* Clocks */
unsigned int master_clk;
struct clk *clk;
struct clk *pclk;
/* Regulators */
int vape_opp_constraint;
};
int ux500_msp_dai_set_data_delay(struct snd_soc_dai *dai, int delay);
#endif

View file

@ -0,0 +1,736 @@
/*
* Copyright (C) ST-Ericsson SA 2012
*
* Author: Ola Lilja <ola.o.lilja@stericsson.com>,
* Roger Nilsson <roger.xr.nilsson@stericsson.com>,
* Sandeep Kaushik <sandeep.kaushik@st.com>
* for ST-Ericsson.
*
* License terms:
*
* 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 <linux/module.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/platform_data/asoc-ux500-msp.h>
#include <sound/soc.h>
#include "ux500_msp_i2s.h"
/* Protocol desciptors */
static const struct msp_protdesc prot_descs[] = {
{ /* I2S */
MSP_SINGLE_PHASE,
MSP_SINGLE_PHASE,
MSP_PHASE2_START_MODE_IMEDIATE,
MSP_PHASE2_START_MODE_IMEDIATE,
MSP_BTF_MS_BIT_FIRST,
MSP_BTF_MS_BIT_FIRST,
MSP_FRAME_LEN_1,
MSP_FRAME_LEN_1,
MSP_FRAME_LEN_1,
MSP_FRAME_LEN_1,
MSP_ELEM_LEN_32,
MSP_ELEM_LEN_32,
MSP_ELEM_LEN_32,
MSP_ELEM_LEN_32,
MSP_DELAY_1,
MSP_DELAY_1,
MSP_RISING_EDGE,
MSP_FALLING_EDGE,
MSP_FSYNC_POL_ACT_LO,
MSP_FSYNC_POL_ACT_LO,
MSP_SWAP_NONE,
MSP_SWAP_NONE,
MSP_COMPRESS_MODE_LINEAR,
MSP_EXPAND_MODE_LINEAR,
MSP_FSYNC_IGNORE,
31,
15,
32,
}, { /* PCM */
MSP_DUAL_PHASE,
MSP_DUAL_PHASE,
MSP_PHASE2_START_MODE_FSYNC,
MSP_PHASE2_START_MODE_FSYNC,
MSP_BTF_MS_BIT_FIRST,
MSP_BTF_MS_BIT_FIRST,
MSP_FRAME_LEN_1,
MSP_FRAME_LEN_1,
MSP_FRAME_LEN_1,
MSP_FRAME_LEN_1,
MSP_ELEM_LEN_16,
MSP_ELEM_LEN_16,
MSP_ELEM_LEN_16,
MSP_ELEM_LEN_16,
MSP_DELAY_0,
MSP_DELAY_0,
MSP_RISING_EDGE,
MSP_FALLING_EDGE,
MSP_FSYNC_POL_ACT_HI,
MSP_FSYNC_POL_ACT_HI,
MSP_SWAP_NONE,
MSP_SWAP_NONE,
MSP_COMPRESS_MODE_LINEAR,
MSP_EXPAND_MODE_LINEAR,
MSP_FSYNC_IGNORE,
255,
0,
256,
}, { /* Companded PCM */
MSP_SINGLE_PHASE,
MSP_SINGLE_PHASE,
MSP_PHASE2_START_MODE_FSYNC,
MSP_PHASE2_START_MODE_FSYNC,
MSP_BTF_MS_BIT_FIRST,
MSP_BTF_MS_BIT_FIRST,
MSP_FRAME_LEN_1,
MSP_FRAME_LEN_1,
MSP_FRAME_LEN_1,
MSP_FRAME_LEN_1,
MSP_ELEM_LEN_8,
MSP_ELEM_LEN_8,
MSP_ELEM_LEN_8,
MSP_ELEM_LEN_8,
MSP_DELAY_0,
MSP_DELAY_0,
MSP_RISING_EDGE,
MSP_RISING_EDGE,
MSP_FSYNC_POL_ACT_HI,
MSP_FSYNC_POL_ACT_HI,
MSP_SWAP_NONE,
MSP_SWAP_NONE,
MSP_COMPRESS_MODE_LINEAR,
MSP_EXPAND_MODE_LINEAR,
MSP_FSYNC_IGNORE,
255,
0,
256,
},
};
static void set_prot_desc_tx(struct ux500_msp *msp,
struct msp_protdesc *protdesc,
enum msp_data_size data_size)
{
u32 temp_reg = 0;
temp_reg |= MSP_P2_ENABLE_BIT(protdesc->tx_phase_mode);
temp_reg |= MSP_P2_START_MODE_BIT(protdesc->tx_phase2_start_mode);
temp_reg |= MSP_P1_FRAME_LEN_BITS(protdesc->tx_frame_len_1);
temp_reg |= MSP_P2_FRAME_LEN_BITS(protdesc->tx_frame_len_2);
if (msp->def_elem_len) {
temp_reg |= MSP_P1_ELEM_LEN_BITS(protdesc->tx_elem_len_1);
temp_reg |= MSP_P2_ELEM_LEN_BITS(protdesc->tx_elem_len_2);
} else {
temp_reg |= MSP_P1_ELEM_LEN_BITS(data_size);
temp_reg |= MSP_P2_ELEM_LEN_BITS(data_size);
}
temp_reg |= MSP_DATA_DELAY_BITS(protdesc->tx_data_delay);
temp_reg |= MSP_SET_ENDIANNES_BIT(protdesc->tx_byte_order);
temp_reg |= MSP_FSYNC_POL(protdesc->tx_fsync_pol);
temp_reg |= MSP_DATA_WORD_SWAP(protdesc->tx_half_word_swap);
temp_reg |= MSP_SET_COMPANDING_MODE(protdesc->compression_mode);
temp_reg |= MSP_SET_FSYNC_IGNORE(protdesc->frame_sync_ignore);
writel(temp_reg, msp->registers + MSP_TCF);
}
static void set_prot_desc_rx(struct ux500_msp *msp,
struct msp_protdesc *protdesc,
enum msp_data_size data_size)
{
u32 temp_reg = 0;
temp_reg |= MSP_P2_ENABLE_BIT(protdesc->rx_phase_mode);
temp_reg |= MSP_P2_START_MODE_BIT(protdesc->rx_phase2_start_mode);
temp_reg |= MSP_P1_FRAME_LEN_BITS(protdesc->rx_frame_len_1);
temp_reg |= MSP_P2_FRAME_LEN_BITS(protdesc->rx_frame_len_2);
if (msp->def_elem_len) {
temp_reg |= MSP_P1_ELEM_LEN_BITS(protdesc->rx_elem_len_1);
temp_reg |= MSP_P2_ELEM_LEN_BITS(protdesc->rx_elem_len_2);
} else {
temp_reg |= MSP_P1_ELEM_LEN_BITS(data_size);
temp_reg |= MSP_P2_ELEM_LEN_BITS(data_size);
}
temp_reg |= MSP_DATA_DELAY_BITS(protdesc->rx_data_delay);
temp_reg |= MSP_SET_ENDIANNES_BIT(protdesc->rx_byte_order);
temp_reg |= MSP_FSYNC_POL(protdesc->rx_fsync_pol);
temp_reg |= MSP_DATA_WORD_SWAP(protdesc->rx_half_word_swap);
temp_reg |= MSP_SET_COMPANDING_MODE(protdesc->expansion_mode);
temp_reg |= MSP_SET_FSYNC_IGNORE(protdesc->frame_sync_ignore);
writel(temp_reg, msp->registers + MSP_RCF);
}
static int configure_protocol(struct ux500_msp *msp,
struct ux500_msp_config *config)
{
struct msp_protdesc *protdesc;
enum msp_data_size data_size;
u32 temp_reg = 0;
data_size = config->data_size;
msp->def_elem_len = config->def_elem_len;
if (config->default_protdesc == 1) {
if (config->protocol >= MSP_INVALID_PROTOCOL) {
dev_err(msp->dev, "%s: ERROR: Invalid protocol!\n",
__func__);
return -EINVAL;
}
protdesc =
(struct msp_protdesc *)&prot_descs[config->protocol];
} else {
protdesc = (struct msp_protdesc *)&config->protdesc;
}
if (data_size < MSP_DATA_BITS_DEFAULT || data_size > MSP_DATA_BITS_32) {
dev_err(msp->dev,
"%s: ERROR: Invalid data-size requested (data_size = %d)!\n",
__func__, data_size);
return -EINVAL;
}
if (config->direction & MSP_DIR_TX)
set_prot_desc_tx(msp, protdesc, data_size);
if (config->direction & MSP_DIR_RX)
set_prot_desc_rx(msp, protdesc, data_size);
/* The code below should not be separated. */
temp_reg = readl(msp->registers + MSP_GCR) & ~TX_CLK_POL_RISING;
temp_reg |= MSP_TX_CLKPOL_BIT(~protdesc->tx_clk_pol);
writel(temp_reg, msp->registers + MSP_GCR);
temp_reg = readl(msp->registers + MSP_GCR) & ~RX_CLK_POL_RISING;
temp_reg |= MSP_RX_CLKPOL_BIT(protdesc->rx_clk_pol);
writel(temp_reg, msp->registers + MSP_GCR);
return 0;
}
static int setup_bitclk(struct ux500_msp *msp, struct ux500_msp_config *config)
{
u32 reg_val_GCR;
u32 frame_per = 0;
u32 sck_div = 0;
u32 frame_width = 0;
u32 temp_reg = 0;
struct msp_protdesc *protdesc = NULL;
reg_val_GCR = readl(msp->registers + MSP_GCR);
writel(reg_val_GCR & ~SRG_ENABLE, msp->registers + MSP_GCR);
if (config->default_protdesc)
protdesc =
(struct msp_protdesc *)&prot_descs[config->protocol];
else
protdesc = (struct msp_protdesc *)&config->protdesc;
switch (config->protocol) {
case MSP_PCM_PROTOCOL:
case MSP_PCM_COMPAND_PROTOCOL:
frame_width = protdesc->frame_width;
sck_div = config->f_inputclk / (config->frame_freq *
(protdesc->clocks_per_frame));
frame_per = protdesc->frame_period;
break;
case MSP_I2S_PROTOCOL:
frame_width = protdesc->frame_width;
sck_div = config->f_inputclk / (config->frame_freq *
(protdesc->clocks_per_frame));
frame_per = protdesc->frame_period;
break;
default:
dev_err(msp->dev, "%s: ERROR: Unknown protocol (%d)!\n",
__func__,
config->protocol);
return -EINVAL;
}
temp_reg = (sck_div - 1) & SCK_DIV_MASK;
temp_reg |= FRAME_WIDTH_BITS(frame_width);
temp_reg |= FRAME_PERIOD_BITS(frame_per);
writel(temp_reg, msp->registers + MSP_SRG);
msp->f_bitclk = (config->f_inputclk)/(sck_div + 1);
/* Enable bit-clock */
udelay(100);
reg_val_GCR = readl(msp->registers + MSP_GCR);
writel(reg_val_GCR | SRG_ENABLE, msp->registers + MSP_GCR);
udelay(100);
return 0;
}
static int configure_multichannel(struct ux500_msp *msp,
struct ux500_msp_config *config)
{
struct msp_protdesc *protdesc;
struct msp_multichannel_config *mcfg;
u32 reg_val_MCR;
if (config->default_protdesc == 1) {
if (config->protocol >= MSP_INVALID_PROTOCOL) {
dev_err(msp->dev,
"%s: ERROR: Invalid protocol (%d)!\n",
__func__, config->protocol);
return -EINVAL;
}
protdesc = (struct msp_protdesc *)
&prot_descs[config->protocol];
} else {
protdesc = (struct msp_protdesc *)&config->protdesc;
}
mcfg = &config->multichannel_config;
if (mcfg->tx_multichannel_enable) {
if (protdesc->tx_phase_mode == MSP_SINGLE_PHASE) {
reg_val_MCR = readl(msp->registers + MSP_MCR);
writel(reg_val_MCR | (mcfg->tx_multichannel_enable ?
1 << TMCEN_BIT : 0),
msp->registers + MSP_MCR);
writel(mcfg->tx_channel_0_enable,
msp->registers + MSP_TCE0);
writel(mcfg->tx_channel_1_enable,
msp->registers + MSP_TCE1);
writel(mcfg->tx_channel_2_enable,
msp->registers + MSP_TCE2);
writel(mcfg->tx_channel_3_enable,
msp->registers + MSP_TCE3);
} else {
dev_err(msp->dev,
"%s: ERROR: Only single-phase supported (TX-mode: %d)!\n",
__func__, protdesc->tx_phase_mode);
return -EINVAL;
}
}
if (mcfg->rx_multichannel_enable) {
if (protdesc->rx_phase_mode == MSP_SINGLE_PHASE) {
reg_val_MCR = readl(msp->registers + MSP_MCR);
writel(reg_val_MCR | (mcfg->rx_multichannel_enable ?
1 << RMCEN_BIT : 0),
msp->registers + MSP_MCR);
writel(mcfg->rx_channel_0_enable,
msp->registers + MSP_RCE0);
writel(mcfg->rx_channel_1_enable,
msp->registers + MSP_RCE1);
writel(mcfg->rx_channel_2_enable,
msp->registers + MSP_RCE2);
writel(mcfg->rx_channel_3_enable,
msp->registers + MSP_RCE3);
} else {
dev_err(msp->dev,
"%s: ERROR: Only single-phase supported (RX-mode: %d)!\n",
__func__, protdesc->rx_phase_mode);
return -EINVAL;
}
if (mcfg->rx_comparison_enable_mode) {
reg_val_MCR = readl(msp->registers + MSP_MCR);
writel(reg_val_MCR |
(mcfg->rx_comparison_enable_mode << RCMPM_BIT),
msp->registers + MSP_MCR);
writel(mcfg->comparison_mask,
msp->registers + MSP_RCM);
writel(mcfg->comparison_value,
msp->registers + MSP_RCV);
}
}
return 0;
}
static int enable_msp(struct ux500_msp *msp, struct ux500_msp_config *config)
{
int status = 0;
u32 reg_val_DMACR, reg_val_GCR;
/* Configure msp with protocol dependent settings */
configure_protocol(msp, config);
setup_bitclk(msp, config);
if (config->multichannel_configured == 1) {
status = configure_multichannel(msp, config);
if (status)
dev_warn(msp->dev,
"%s: WARN: configure_multichannel failed (%d)!\n",
__func__, status);
}
/* Make sure the correct DMA-directions are configured */
if ((config->direction & MSP_DIR_RX) &&
!msp->capture_dma_data.dma_cfg) {
dev_err(msp->dev, "%s: ERROR: MSP RX-mode is not configured!",
__func__);
return -EINVAL;
}
if ((config->direction == MSP_DIR_TX) &&
!msp->playback_dma_data.dma_cfg) {
dev_err(msp->dev, "%s: ERROR: MSP TX-mode is not configured!",
__func__);
return -EINVAL;
}
reg_val_DMACR = readl(msp->registers + MSP_DMACR);
if (config->direction & MSP_DIR_RX)
reg_val_DMACR |= RX_DMA_ENABLE;
if (config->direction & MSP_DIR_TX)
reg_val_DMACR |= TX_DMA_ENABLE;
writel(reg_val_DMACR, msp->registers + MSP_DMACR);
writel(config->iodelay, msp->registers + MSP_IODLY);
/* Enable frame generation logic */
reg_val_GCR = readl(msp->registers + MSP_GCR);
writel(reg_val_GCR | FRAME_GEN_ENABLE, msp->registers + MSP_GCR);
return status;
}
static void flush_fifo_rx(struct ux500_msp *msp)
{
u32 reg_val_DR, reg_val_GCR, reg_val_FLR;
u32 limit = 32;
reg_val_GCR = readl(msp->registers + MSP_GCR);
writel(reg_val_GCR | RX_ENABLE, msp->registers + MSP_GCR);
reg_val_FLR = readl(msp->registers + MSP_FLR);
while (!(reg_val_FLR & RX_FIFO_EMPTY) && limit--) {
reg_val_DR = readl(msp->registers + MSP_DR);
reg_val_FLR = readl(msp->registers + MSP_FLR);
}
writel(reg_val_GCR, msp->registers + MSP_GCR);
}
static void flush_fifo_tx(struct ux500_msp *msp)
{
u32 reg_val_TSTDR, reg_val_GCR, reg_val_FLR;
u32 limit = 32;
reg_val_GCR = readl(msp->registers + MSP_GCR);
writel(reg_val_GCR | TX_ENABLE, msp->registers + MSP_GCR);
writel(MSP_ITCR_ITEN | MSP_ITCR_TESTFIFO, msp->registers + MSP_ITCR);
reg_val_FLR = readl(msp->registers + MSP_FLR);
while (!(reg_val_FLR & TX_FIFO_EMPTY) && limit--) {
reg_val_TSTDR = readl(msp->registers + MSP_TSTDR);
reg_val_FLR = readl(msp->registers + MSP_FLR);
}
writel(0x0, msp->registers + MSP_ITCR);
writel(reg_val_GCR, msp->registers + MSP_GCR);
}
int ux500_msp_i2s_open(struct ux500_msp *msp,
struct ux500_msp_config *config)
{
u32 old_reg, new_reg, mask;
int res;
unsigned int tx_sel, rx_sel, tx_busy, rx_busy;
if (in_interrupt()) {
dev_err(msp->dev,
"%s: ERROR: Open called in interrupt context!\n",
__func__);
return -1;
}
tx_sel = (config->direction & MSP_DIR_TX) > 0;
rx_sel = (config->direction & MSP_DIR_RX) > 0;
if (!tx_sel && !rx_sel) {
dev_err(msp->dev, "%s: Error: No direction selected!\n",
__func__);
return -EINVAL;
}
tx_busy = (msp->dir_busy & MSP_DIR_TX) > 0;
rx_busy = (msp->dir_busy & MSP_DIR_RX) > 0;
if (tx_busy && tx_sel) {
dev_err(msp->dev, "%s: Error: TX is in use!\n", __func__);
return -EBUSY;
}
if (rx_busy && rx_sel) {
dev_err(msp->dev, "%s: Error: RX is in use!\n", __func__);
return -EBUSY;
}
msp->dir_busy |= (tx_sel ? MSP_DIR_TX : 0) | (rx_sel ? MSP_DIR_RX : 0);
/* First do the global config register */
mask = RX_CLK_SEL_MASK | TX_CLK_SEL_MASK | RX_FSYNC_MASK |
TX_FSYNC_MASK | RX_SYNC_SEL_MASK | TX_SYNC_SEL_MASK |
RX_FIFO_ENABLE_MASK | TX_FIFO_ENABLE_MASK | SRG_CLK_SEL_MASK |
LOOPBACK_MASK | TX_EXTRA_DELAY_MASK;
new_reg = (config->tx_clk_sel | config->rx_clk_sel |
config->rx_fsync_pol | config->tx_fsync_pol |
config->rx_fsync_sel | config->tx_fsync_sel |
config->rx_fifo_config | config->tx_fifo_config |
config->srg_clk_sel | config->loopback_enable |
config->tx_data_enable);
old_reg = readl(msp->registers + MSP_GCR);
old_reg &= ~mask;
new_reg |= old_reg;
writel(new_reg, msp->registers + MSP_GCR);
res = enable_msp(msp, config);
if (res < 0) {
dev_err(msp->dev, "%s: ERROR: enable_msp failed (%d)!\n",
__func__, res);
return -EBUSY;
}
if (config->loopback_enable & 0x80)
msp->loopback_enable = 1;
/* Flush FIFOs */
flush_fifo_tx(msp);
flush_fifo_rx(msp);
msp->msp_state = MSP_STATE_CONFIGURED;
return 0;
}
static void disable_msp_rx(struct ux500_msp *msp)
{
u32 reg_val_GCR, reg_val_DMACR, reg_val_IMSC;
reg_val_GCR = readl(msp->registers + MSP_GCR);
writel(reg_val_GCR & ~RX_ENABLE, msp->registers + MSP_GCR);
reg_val_DMACR = readl(msp->registers + MSP_DMACR);
writel(reg_val_DMACR & ~RX_DMA_ENABLE, msp->registers + MSP_DMACR);
reg_val_IMSC = readl(msp->registers + MSP_IMSC);
writel(reg_val_IMSC &
~(RX_SERVICE_INT | RX_OVERRUN_ERROR_INT),
msp->registers + MSP_IMSC);
msp->dir_busy &= ~MSP_DIR_RX;
}
static void disable_msp_tx(struct ux500_msp *msp)
{
u32 reg_val_GCR, reg_val_DMACR, reg_val_IMSC;
reg_val_GCR = readl(msp->registers + MSP_GCR);
writel(reg_val_GCR & ~TX_ENABLE, msp->registers + MSP_GCR);
reg_val_DMACR = readl(msp->registers + MSP_DMACR);
writel(reg_val_DMACR & ~TX_DMA_ENABLE, msp->registers + MSP_DMACR);
reg_val_IMSC = readl(msp->registers + MSP_IMSC);
writel(reg_val_IMSC &
~(TX_SERVICE_INT | TX_UNDERRUN_ERR_INT),
msp->registers + MSP_IMSC);
msp->dir_busy &= ~MSP_DIR_TX;
}
static int disable_msp(struct ux500_msp *msp, unsigned int dir)
{
u32 reg_val_GCR;
int status = 0;
unsigned int disable_tx, disable_rx;
reg_val_GCR = readl(msp->registers + MSP_GCR);
disable_tx = dir & MSP_DIR_TX;
disable_rx = dir & MSP_DIR_TX;
if (disable_tx && disable_rx) {
reg_val_GCR = readl(msp->registers + MSP_GCR);
writel(reg_val_GCR | LOOPBACK_MASK,
msp->registers + MSP_GCR);
/* Flush TX-FIFO */
flush_fifo_tx(msp);
/* Disable TX-channel */
writel((readl(msp->registers + MSP_GCR) &
(~TX_ENABLE)), msp->registers + MSP_GCR);
/* Flush RX-FIFO */
flush_fifo_rx(msp);
/* Disable Loopback and Receive channel */
writel((readl(msp->registers + MSP_GCR) &
(~(RX_ENABLE | LOOPBACK_MASK))),
msp->registers + MSP_GCR);
disable_msp_tx(msp);
disable_msp_rx(msp);
} else if (disable_tx)
disable_msp_tx(msp);
else if (disable_rx)
disable_msp_rx(msp);
return status;
}
int ux500_msp_i2s_trigger(struct ux500_msp *msp, int cmd, int direction)
{
u32 reg_val_GCR, enable_bit;
if (msp->msp_state == MSP_STATE_IDLE) {
dev_err(msp->dev, "%s: ERROR: MSP is not configured!\n",
__func__);
return -EINVAL;
}
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
if (direction == SNDRV_PCM_STREAM_PLAYBACK)
enable_bit = TX_ENABLE;
else
enable_bit = RX_ENABLE;
reg_val_GCR = readl(msp->registers + MSP_GCR);
writel(reg_val_GCR | enable_bit, msp->registers + MSP_GCR);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (direction == SNDRV_PCM_STREAM_PLAYBACK)
disable_msp_tx(msp);
else
disable_msp_rx(msp);
break;
default:
return -EINVAL;
break;
}
return 0;
}
int ux500_msp_i2s_close(struct ux500_msp *msp, unsigned int dir)
{
int status = 0;
dev_dbg(msp->dev, "%s: Enter (dir = 0x%01x).\n", __func__, dir);
status = disable_msp(msp, dir);
if (msp->dir_busy == 0) {
/* disable sample rate and frame generators */
msp->msp_state = MSP_STATE_IDLE;
writel((readl(msp->registers + MSP_GCR) &
(~(FRAME_GEN_ENABLE | SRG_ENABLE))),
msp->registers + MSP_GCR);
writel(0, msp->registers + MSP_GCR);
writel(0, msp->registers + MSP_TCF);
writel(0, msp->registers + MSP_RCF);
writel(0, msp->registers + MSP_DMACR);
writel(0, msp->registers + MSP_SRG);
writel(0, msp->registers + MSP_MCR);
writel(0, msp->registers + MSP_RCM);
writel(0, msp->registers + MSP_RCV);
writel(0, msp->registers + MSP_TCE0);
writel(0, msp->registers + MSP_TCE1);
writel(0, msp->registers + MSP_TCE2);
writel(0, msp->registers + MSP_TCE3);
writel(0, msp->registers + MSP_RCE0);
writel(0, msp->registers + MSP_RCE1);
writel(0, msp->registers + MSP_RCE2);
writel(0, msp->registers + MSP_RCE3);
}
return status;
}
static int ux500_msp_i2s_of_init_msp(struct platform_device *pdev,
struct ux500_msp *msp,
struct msp_i2s_platform_data **platform_data)
{
struct msp_i2s_platform_data *pdata;
*platform_data = devm_kzalloc(&pdev->dev,
sizeof(struct msp_i2s_platform_data),
GFP_KERNEL);
pdata = *platform_data;
if (!pdata)
return -ENOMEM;
msp->playback_dma_data.dma_cfg = devm_kzalloc(&pdev->dev,
sizeof(struct stedma40_chan_cfg),
GFP_KERNEL);
if (!msp->playback_dma_data.dma_cfg)
return -ENOMEM;
msp->capture_dma_data.dma_cfg = devm_kzalloc(&pdev->dev,
sizeof(struct stedma40_chan_cfg),
GFP_KERNEL);
if (!msp->capture_dma_data.dma_cfg)
return -ENOMEM;
return 0;
}
int ux500_msp_i2s_init_msp(struct platform_device *pdev,
struct ux500_msp **msp_p,
struct msp_i2s_platform_data *platform_data)
{
struct resource *res = NULL;
struct device_node *np = pdev->dev.of_node;
struct ux500_msp *msp;
int ret;
*msp_p = devm_kzalloc(&pdev->dev, sizeof(struct ux500_msp), GFP_KERNEL);
msp = *msp_p;
if (!msp)
return -ENOMEM;
if (!platform_data) {
if (np) {
ret = ux500_msp_i2s_of_init_msp(pdev, msp,
&platform_data);
if (ret)
return ret;
} else
return -EINVAL;
} else {
msp->playback_dma_data.dma_cfg = platform_data->msp_i2s_dma_tx;
msp->capture_dma_data.dma_cfg = platform_data->msp_i2s_dma_rx;
msp->id = platform_data->id;
}
msp->dev = &pdev->dev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
dev_err(&pdev->dev, "%s: ERROR: Unable to get resource!\n",
__func__);
return -ENOMEM;
}
msp->playback_dma_data.tx_rx_addr = res->start + MSP_DR;
msp->capture_dma_data.tx_rx_addr = res->start + MSP_DR;
msp->registers = devm_ioremap(&pdev->dev, res->start,
resource_size(res));
if (msp->registers == NULL) {
dev_err(&pdev->dev, "%s: ERROR: ioremap failed!\n", __func__);
return -ENOMEM;
}
msp->msp_state = MSP_STATE_IDLE;
msp->loopback_enable = 0;
return 0;
}
void ux500_msp_i2s_cleanup_msp(struct platform_device *pdev,
struct ux500_msp *msp)
{
dev_dbg(msp->dev, "%s: Enter (id = %d).\n", __func__, msp->id);
}
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,502 @@
/*
* Copyright (C) ST-Ericsson SA 2012
*
* Author: Ola Lilja <ola.o.lilja@stericsson.com>,
* for ST-Ericsson.
*
* License terms:
*
* 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 UX500_MSP_I2S_H
#define UX500_MSP_I2S_H
#include <linux/platform_device.h>
#include <linux/platform_data/asoc-ux500-msp.h>
#define MSP_INPUT_FREQ_APB 48000000
/*** Stereo mode. Used for APB data accesses as 16 bits accesses (mono),
* 32 bits accesses (stereo).
***/
enum msp_stereo_mode {
MSP_MONO,
MSP_STEREO
};
/* Direction (Transmit/Receive mode) */
enum msp_direction {
MSP_TX = 1,
MSP_RX = 2
};
/* Transmit and receive configuration register */
#define MSP_BIG_ENDIAN 0x00000000
#define MSP_LITTLE_ENDIAN 0x00001000
#define MSP_UNEXPECTED_FS_ABORT 0x00000000
#define MSP_UNEXPECTED_FS_IGNORE 0x00008000
#define MSP_NON_MODE_BIT_MASK 0x00009000
/* Global configuration register */
#define RX_ENABLE 0x00000001
#define RX_FIFO_ENABLE 0x00000002
#define RX_SYNC_SRG 0x00000010
#define RX_CLK_POL_RISING 0x00000020
#define RX_CLK_SEL_SRG 0x00000040
#define TX_ENABLE 0x00000100
#define TX_FIFO_ENABLE 0x00000200
#define TX_SYNC_SRG_PROG 0x00001800
#define TX_SYNC_SRG_AUTO 0x00001000
#define TX_CLK_POL_RISING 0x00002000
#define TX_CLK_SEL_SRG 0x00004000
#define TX_EXTRA_DELAY_ENABLE 0x00008000
#define SRG_ENABLE 0x00010000
#define FRAME_GEN_ENABLE 0x00100000
#define SRG_CLK_SEL_APB 0x00000000
#define RX_FIFO_SYNC_HI 0x00000000
#define TX_FIFO_SYNC_HI 0x00000000
#define SPI_CLK_MODE_NORMAL 0x00000000
#define MSP_FRAME_SIZE_AUTO -1
#define MSP_DR 0x00
#define MSP_GCR 0x04
#define MSP_TCF 0x08
#define MSP_RCF 0x0c
#define MSP_SRG 0x10
#define MSP_FLR 0x14
#define MSP_DMACR 0x18
#define MSP_IMSC 0x20
#define MSP_RIS 0x24
#define MSP_MIS 0x28
#define MSP_ICR 0x2c
#define MSP_MCR 0x30
#define MSP_RCV 0x34
#define MSP_RCM 0x38
#define MSP_TCE0 0x40
#define MSP_TCE1 0x44
#define MSP_TCE2 0x48
#define MSP_TCE3 0x4c
#define MSP_RCE0 0x60
#define MSP_RCE1 0x64
#define MSP_RCE2 0x68
#define MSP_RCE3 0x6c
#define MSP_IODLY 0x70
#define MSP_ITCR 0x80
#define MSP_ITIP 0x84
#define MSP_ITOP 0x88
#define MSP_TSTDR 0x8c
#define MSP_PID0 0xfe0
#define MSP_PID1 0xfe4
#define MSP_PID2 0xfe8
#define MSP_PID3 0xfec
#define MSP_CID0 0xff0
#define MSP_CID1 0xff4
#define MSP_CID2 0xff8
#define MSP_CID3 0xffc
/* Protocol dependant parameters list */
#define RX_ENABLE_MASK BIT(0)
#define RX_FIFO_ENABLE_MASK BIT(1)
#define RX_FSYNC_MASK BIT(2)
#define DIRECT_COMPANDING_MASK BIT(3)
#define RX_SYNC_SEL_MASK BIT(4)
#define RX_CLK_POL_MASK BIT(5)
#define RX_CLK_SEL_MASK BIT(6)
#define LOOPBACK_MASK BIT(7)
#define TX_ENABLE_MASK BIT(8)
#define TX_FIFO_ENABLE_MASK BIT(9)
#define TX_FSYNC_MASK BIT(10)
#define TX_MSP_TDR_TSR BIT(11)
#define TX_SYNC_SEL_MASK (BIT(12) | BIT(11))
#define TX_CLK_POL_MASK BIT(13)
#define TX_CLK_SEL_MASK BIT(14)
#define TX_EXTRA_DELAY_MASK BIT(15)
#define SRG_ENABLE_MASK BIT(16)
#define SRG_CLK_POL_MASK BIT(17)
#define SRG_CLK_SEL_MASK (BIT(19) | BIT(18))
#define FRAME_GEN_EN_MASK BIT(20)
#define SPI_CLK_MODE_MASK (BIT(22) | BIT(21))
#define SPI_BURST_MODE_MASK BIT(23)
#define RXEN_SHIFT 0
#define RFFEN_SHIFT 1
#define RFSPOL_SHIFT 2
#define DCM_SHIFT 3
#define RFSSEL_SHIFT 4
#define RCKPOL_SHIFT 5
#define RCKSEL_SHIFT 6
#define LBM_SHIFT 7
#define TXEN_SHIFT 8
#define TFFEN_SHIFT 9
#define TFSPOL_SHIFT 10
#define TFSSEL_SHIFT 11
#define TCKPOL_SHIFT 13
#define TCKSEL_SHIFT 14
#define TXDDL_SHIFT 15
#define SGEN_SHIFT 16
#define SCKPOL_SHIFT 17
#define SCKSEL_SHIFT 18
#define FGEN_SHIFT 20
#define SPICKM_SHIFT 21
#define TBSWAP_SHIFT 28
#define RCKPOL_MASK BIT(0)
#define TCKPOL_MASK BIT(0)
#define SPICKM_MASK (BIT(1) | BIT(0))
#define MSP_RX_CLKPOL_BIT(n) ((n & RCKPOL_MASK) << RCKPOL_SHIFT)
#define MSP_TX_CLKPOL_BIT(n) ((n & TCKPOL_MASK) << TCKPOL_SHIFT)
#define P1ELEN_SHIFT 0
#define P1FLEN_SHIFT 3
#define DTYP_SHIFT 10
#define ENDN_SHIFT 12
#define DDLY_SHIFT 13
#define FSIG_SHIFT 15
#define P2ELEN_SHIFT 16
#define P2FLEN_SHIFT 19
#define P2SM_SHIFT 26
#define P2EN_SHIFT 27
#define FSYNC_SHIFT 15
#define P1ELEN_MASK 0x00000007
#define P2ELEN_MASK 0x00070000
#define P1FLEN_MASK 0x00000378
#define P2FLEN_MASK 0x03780000
#define DDLY_MASK 0x00003000
#define DTYP_MASK 0x00000600
#define P2SM_MASK 0x04000000
#define P2EN_MASK 0x08000000
#define ENDN_MASK 0x00001000
#define TFSPOL_MASK 0x00000400
#define TBSWAP_MASK 0x30000000
#define COMPANDING_MODE_MASK 0x00000c00
#define FSYNC_MASK 0x00008000
#define MSP_P1_ELEM_LEN_BITS(n) (n & P1ELEN_MASK)
#define MSP_P2_ELEM_LEN_BITS(n) (((n) << P2ELEN_SHIFT) & P2ELEN_MASK)
#define MSP_P1_FRAME_LEN_BITS(n) (((n) << P1FLEN_SHIFT) & P1FLEN_MASK)
#define MSP_P2_FRAME_LEN_BITS(n) (((n) << P2FLEN_SHIFT) & P2FLEN_MASK)
#define MSP_DATA_DELAY_BITS(n) (((n) << DDLY_SHIFT) & DDLY_MASK)
#define MSP_DATA_TYPE_BITS(n) (((n) << DTYP_SHIFT) & DTYP_MASK)
#define MSP_P2_START_MODE_BIT(n) ((n << P2SM_SHIFT) & P2SM_MASK)
#define MSP_P2_ENABLE_BIT(n) ((n << P2EN_SHIFT) & P2EN_MASK)
#define MSP_SET_ENDIANNES_BIT(n) ((n << ENDN_SHIFT) & ENDN_MASK)
#define MSP_FSYNC_POL(n) ((n << TFSPOL_SHIFT) & TFSPOL_MASK)
#define MSP_DATA_WORD_SWAP(n) ((n << TBSWAP_SHIFT) & TBSWAP_MASK)
#define MSP_SET_COMPANDING_MODE(n) ((n << DTYP_SHIFT) & \
COMPANDING_MODE_MASK)
#define MSP_SET_FSYNC_IGNORE(n) ((n << FSYNC_SHIFT) & FSYNC_MASK)
/* Flag register */
#define RX_BUSY BIT(0)
#define RX_FIFO_EMPTY BIT(1)
#define RX_FIFO_FULL BIT(2)
#define TX_BUSY BIT(3)
#define TX_FIFO_EMPTY BIT(4)
#define TX_FIFO_FULL BIT(5)
#define RBUSY_SHIFT 0
#define RFE_SHIFT 1
#define RFU_SHIFT 2
#define TBUSY_SHIFT 3
#define TFE_SHIFT 4
#define TFU_SHIFT 5
/* Multichannel control register */
#define RMCEN_SHIFT 0
#define RMCSF_SHIFT 1
#define RCMPM_SHIFT 3
#define TMCEN_SHIFT 5
#define TNCSF_SHIFT 6
/* Sample rate generator register */
#define SCKDIV_SHIFT 0
#define FRWID_SHIFT 10
#define FRPER_SHIFT 16
#define SCK_DIV_MASK 0x0000003FF
#define FRAME_WIDTH_BITS(n) (((n) << FRWID_SHIFT) & 0x0000FC00)
#define FRAME_PERIOD_BITS(n) (((n) << FRPER_SHIFT) & 0x1FFF0000)
/* DMA controller register */
#define RX_DMA_ENABLE BIT(0)
#define TX_DMA_ENABLE BIT(1)
#define RDMAE_SHIFT 0
#define TDMAE_SHIFT 1
/* Interrupt Register */
#define RX_SERVICE_INT BIT(0)
#define RX_OVERRUN_ERROR_INT BIT(1)
#define RX_FSYNC_ERR_INT BIT(2)
#define RX_FSYNC_INT BIT(3)
#define TX_SERVICE_INT BIT(4)
#define TX_UNDERRUN_ERR_INT BIT(5)
#define TX_FSYNC_ERR_INT BIT(6)
#define TX_FSYNC_INT BIT(7)
#define ALL_INT 0x000000ff
/* MSP test control register */
#define MSP_ITCR_ITEN BIT(0)
#define MSP_ITCR_TESTFIFO BIT(1)
#define RMCEN_BIT 0
#define RMCSF_BIT 1
#define RCMPM_BIT 3
#define TMCEN_BIT 5
#define TNCSF_BIT 6
/* Single or dual phase mode */
enum msp_phase_mode {
MSP_SINGLE_PHASE,
MSP_DUAL_PHASE
};
/* Frame length */
enum msp_frame_length {
MSP_FRAME_LEN_1 = 0,
MSP_FRAME_LEN_2 = 1,
MSP_FRAME_LEN_4 = 3,
MSP_FRAME_LEN_8 = 7,
MSP_FRAME_LEN_12 = 11,
MSP_FRAME_LEN_16 = 15,
MSP_FRAME_LEN_20 = 19,
MSP_FRAME_LEN_32 = 31,
MSP_FRAME_LEN_48 = 47,
MSP_FRAME_LEN_64 = 63
};
/* Element length */
enum msp_elem_length {
MSP_ELEM_LEN_8 = 0,
MSP_ELEM_LEN_10 = 1,
MSP_ELEM_LEN_12 = 2,
MSP_ELEM_LEN_14 = 3,
MSP_ELEM_LEN_16 = 4,
MSP_ELEM_LEN_20 = 5,
MSP_ELEM_LEN_24 = 6,
MSP_ELEM_LEN_32 = 7
};
enum msp_data_xfer_width {
MSP_DATA_TRANSFER_WIDTH_BYTE,
MSP_DATA_TRANSFER_WIDTH_HALFWORD,
MSP_DATA_TRANSFER_WIDTH_WORD
};
enum msp_frame_sync {
MSP_FSYNC_UNIGNORE = 0,
MSP_FSYNC_IGNORE = 1,
};
enum msp_phase2_start_mode {
MSP_PHASE2_START_MODE_IMEDIATE,
MSP_PHASE2_START_MODE_FSYNC
};
enum msp_btf {
MSP_BTF_MS_BIT_FIRST = 0,
MSP_BTF_LS_BIT_FIRST = 1
};
enum msp_fsync_pol {
MSP_FSYNC_POL_ACT_HI = 0,
MSP_FSYNC_POL_ACT_LO = 1
};
/* Data delay (in bit clock cycles) */
enum msp_delay {
MSP_DELAY_0 = 0,
MSP_DELAY_1 = 1,
MSP_DELAY_2 = 2,
MSP_DELAY_3 = 3
};
/* Configurations of clocks (transmit, receive or sample rate generator) */
enum msp_edge {
MSP_FALLING_EDGE = 0,
MSP_RISING_EDGE = 1,
};
enum msp_hws {
MSP_SWAP_NONE = 0,
MSP_SWAP_BYTE_PER_WORD = 1,
MSP_SWAP_BYTE_PER_HALF_WORD = 2,
MSP_SWAP_HALF_WORD_PER_WORD = 3
};
enum msp_compress_mode {
MSP_COMPRESS_MODE_LINEAR = 0,
MSP_COMPRESS_MODE_MU_LAW = 2,
MSP_COMPRESS_MODE_A_LAW = 3
};
enum msp_expand_mode {
MSP_EXPAND_MODE_LINEAR = 0,
MSP_EXPAND_MODE_LINEAR_SIGNED = 1,
MSP_EXPAND_MODE_MU_LAW = 2,
MSP_EXPAND_MODE_A_LAW = 3
};
#define MSP_FRAME_PERIOD_IN_MONO_MODE 256
#define MSP_FRAME_PERIOD_IN_STEREO_MODE 32
#define MSP_FRAME_WIDTH_IN_STEREO_MODE 16
enum msp_protocol {
MSP_I2S_PROTOCOL,
MSP_PCM_PROTOCOL,
MSP_PCM_COMPAND_PROTOCOL,
MSP_INVALID_PROTOCOL
};
/*
* No of registers to backup during
* suspend resume
*/
#define MAX_MSP_BACKUP_REGS 36
enum i2s_direction_t {
MSP_DIR_TX = 0x01,
MSP_DIR_RX = 0x02,
};
enum msp_data_size {
MSP_DATA_BITS_DEFAULT = -1,
MSP_DATA_BITS_8 = 0x00,
MSP_DATA_BITS_10,
MSP_DATA_BITS_12,
MSP_DATA_BITS_14,
MSP_DATA_BITS_16,
MSP_DATA_BITS_20,
MSP_DATA_BITS_24,
MSP_DATA_BITS_32,
};
enum msp_state {
MSP_STATE_IDLE = 0,
MSP_STATE_CONFIGURED = 1,
MSP_STATE_RUNNING = 2,
};
enum msp_rx_comparison_enable_mode {
MSP_COMPARISON_DISABLED = 0,
MSP_COMPARISON_NONEQUAL_ENABLED = 2,
MSP_COMPARISON_EQUAL_ENABLED = 3
};
struct msp_multichannel_config {
bool rx_multichannel_enable;
bool tx_multichannel_enable;
enum msp_rx_comparison_enable_mode rx_comparison_enable_mode;
u8 padding;
u32 comparison_value;
u32 comparison_mask;
u32 rx_channel_0_enable;
u32 rx_channel_1_enable;
u32 rx_channel_2_enable;
u32 rx_channel_3_enable;
u32 tx_channel_0_enable;
u32 tx_channel_1_enable;
u32 tx_channel_2_enable;
u32 tx_channel_3_enable;
};
struct msp_protdesc {
u32 rx_phase_mode;
u32 tx_phase_mode;
u32 rx_phase2_start_mode;
u32 tx_phase2_start_mode;
u32 rx_byte_order;
u32 tx_byte_order;
u32 rx_frame_len_1;
u32 rx_frame_len_2;
u32 tx_frame_len_1;
u32 tx_frame_len_2;
u32 rx_elem_len_1;
u32 rx_elem_len_2;
u32 tx_elem_len_1;
u32 tx_elem_len_2;
u32 rx_data_delay;
u32 tx_data_delay;
u32 rx_clk_pol;
u32 tx_clk_pol;
u32 rx_fsync_pol;
u32 tx_fsync_pol;
u32 rx_half_word_swap;
u32 tx_half_word_swap;
u32 compression_mode;
u32 expansion_mode;
u32 frame_sync_ignore;
u32 frame_period;
u32 frame_width;
u32 clocks_per_frame;
};
struct ux500_msp_config {
unsigned int f_inputclk;
unsigned int rx_clk_sel;
unsigned int tx_clk_sel;
unsigned int srg_clk_sel;
unsigned int rx_fsync_pol;
unsigned int tx_fsync_pol;
unsigned int rx_fsync_sel;
unsigned int tx_fsync_sel;
unsigned int rx_fifo_config;
unsigned int tx_fifo_config;
unsigned int loopback_enable;
unsigned int tx_data_enable;
unsigned int default_protdesc;
struct msp_protdesc protdesc;
int multichannel_configured;
struct msp_multichannel_config multichannel_config;
unsigned int direction;
unsigned int protocol;
unsigned int frame_freq;
enum msp_data_size data_size;
unsigned int def_elem_len;
unsigned int iodelay;
};
struct ux500_msp_dma_params {
unsigned int data_size;
dma_addr_t tx_rx_addr;
struct stedma40_chan_cfg *dma_cfg;
};
struct ux500_msp {
int id;
void __iomem *registers;
struct device *dev;
struct ux500_msp_dma_params playback_dma_data;
struct ux500_msp_dma_params capture_dma_data;
enum msp_state msp_state;
int def_elem_len;
unsigned int dir_busy;
int loopback_enable;
unsigned int f_bitclk;
};
struct msp_i2s_platform_data;
int ux500_msp_i2s_init_msp(struct platform_device *pdev,
struct ux500_msp **msp_p,
struct msp_i2s_platform_data *platform_data);
void ux500_msp_i2s_cleanup_msp(struct platform_device *pdev,
struct ux500_msp *msp);
int ux500_msp_i2s_open(struct ux500_msp *msp, struct ux500_msp_config *config);
int ux500_msp_i2s_close(struct ux500_msp *msp,
unsigned int dir);
int ux500_msp_i2s_trigger(struct ux500_msp *msp, int cmd,
int direction);
#endif

168
sound/soc/ux500/ux500_pcm.c Normal file
View file

@ -0,0 +1,168 @@
/*
* Copyright (C) ST-Ericsson SA 2012
*
* Author: Ola Lilja <ola.o.lilja@stericsson.com>,
* Roger Nilsson <roger.xr.nilsson@stericsson.com>
* for ST-Ericsson.
*
* License terms:
*
* 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 <asm/page.h>
#include <linux/module.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/slab.h>
#include <linux/platform_data/dma-ste-dma40.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/dmaengine_pcm.h>
#include "ux500_msp_i2s.h"
#include "ux500_pcm.h"
#define UX500_PLATFORM_PERIODS_BYTES_MIN 128
#define UX500_PLATFORM_PERIODS_BYTES_MAX (64 * PAGE_SIZE)
#define UX500_PLATFORM_PERIODS_MIN 2
#define UX500_PLATFORM_PERIODS_MAX 48
#define UX500_PLATFORM_BUFFER_BYTES_MAX (2048 * PAGE_SIZE)
static const struct snd_pcm_hardware ux500_pcm_hw = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_RESUME |
SNDRV_PCM_INFO_PAUSE,
.buffer_bytes_max = UX500_PLATFORM_BUFFER_BYTES_MAX,
.period_bytes_min = UX500_PLATFORM_PERIODS_BYTES_MIN,
.period_bytes_max = UX500_PLATFORM_PERIODS_BYTES_MAX,
.periods_min = UX500_PLATFORM_PERIODS_MIN,
.periods_max = UX500_PLATFORM_PERIODS_MAX,
};
static struct dma_chan *ux500_pcm_request_chan(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_substream *substream)
{
struct snd_soc_dai *dai = rtd->cpu_dai;
u16 per_data_width, mem_data_width;
struct stedma40_chan_cfg *dma_cfg;
struct ux500_msp_dma_params *dma_params;
dma_params = snd_soc_dai_get_dma_data(dai, substream);
dma_cfg = dma_params->dma_cfg;
mem_data_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
switch (dma_params->data_size) {
case 32:
per_data_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
break;
case 16:
per_data_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
break;
case 8:
per_data_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
break;
default:
per_data_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
dma_cfg->src_info.data_width = mem_data_width;
dma_cfg->dst_info.data_width = per_data_width;
} else {
dma_cfg->src_info.data_width = per_data_width;
dma_cfg->dst_info.data_width = mem_data_width;
}
return snd_dmaengine_pcm_request_channel(stedma40_filter, dma_cfg);
}
static int ux500_pcm_prepare_slave_config(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct dma_slave_config *slave_config)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct msp_i2s_platform_data *pdata = rtd->cpu_dai->dev->platform_data;
struct snd_dmaengine_dai_dma_data *snd_dma_params;
struct ux500_msp_dma_params *ste_dma_params;
dma_addr_t dma_addr;
int ret;
if (pdata) {
ste_dma_params =
snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
dma_addr = ste_dma_params->tx_rx_addr;
} else {
snd_dma_params =
snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
dma_addr = snd_dma_params->addr;
}
ret = snd_hwparams_to_dma_slave_config(substream, params, slave_config);
if (ret)
return ret;
slave_config->dst_maxburst = 4;
slave_config->src_maxburst = 4;
slave_config->src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
slave_config->dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
slave_config->dst_addr = dma_addr;
else
slave_config->src_addr = dma_addr;
return 0;
}
static const struct snd_dmaengine_pcm_config ux500_dmaengine_pcm_config = {
.pcm_hardware = &ux500_pcm_hw,
.compat_request_channel = ux500_pcm_request_chan,
.prealloc_buffer_size = 128 * 1024,
.prepare_slave_config = ux500_pcm_prepare_slave_config,
};
static const struct snd_dmaengine_pcm_config ux500_dmaengine_of_pcm_config = {
.compat_request_channel = ux500_pcm_request_chan,
.prepare_slave_config = ux500_pcm_prepare_slave_config,
};
int ux500_pcm_register_platform(struct platform_device *pdev)
{
const struct snd_dmaengine_pcm_config *pcm_config;
struct device_node *np = pdev->dev.of_node;
int ret;
if (np)
pcm_config = &ux500_dmaengine_of_pcm_config;
else
pcm_config = &ux500_dmaengine_pcm_config;
ret = snd_dmaengine_pcm_register(&pdev->dev, pcm_config,
SND_DMAENGINE_PCM_FLAG_NO_RESIDUE |
SND_DMAENGINE_PCM_FLAG_COMPAT);
if (ret < 0) {
dev_err(&pdev->dev,
"%s: ERROR: Failed to register platform '%s' (%d)!\n",
__func__, pdev->name, ret);
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(ux500_pcm_register_platform);
int ux500_pcm_unregister_platform(struct platform_device *pdev)
{
snd_dmaengine_pcm_unregister(&pdev->dev);
return 0;
}
EXPORT_SYMBOL_GPL(ux500_pcm_unregister_platform);

View file

@ -0,0 +1,24 @@
/*
* Copyright (C) ST-Ericsson SA 2012
*
* Author: Ola Lilja <ola.o.lilja@stericsson.com>,
* Roger Nilsson <roger.xr.nilsson@stericsson.com>
* for ST-Ericsson.
*
* License terms:
*
* 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 UX500_PCM_H
#define UX500_PCM_H
#include <asm/page.h>
#include <linux/workqueue.h>
int ux500_pcm_register_platform(struct platform_device *pdev);
int ux500_pcm_unregister_platform(struct platform_device *pdev);
#endif