blob: 6f5b91ac9dc3dd23b45848f0211c6f4bdf0bcc87 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) 2018 Synaptics Incorporated */
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_irq.h>
#include <sound/soc.h>
#include "berlin_pcm.h"
#include "as370_aio.h"
#include "berlin_aio.h"
#define RA_SEC_SECAUD 0x0000
#define RA_SEC_TSD0_SEC 0x000C
#define RA_SEC_SECPORT 0x0018
#define SET32SEC_SECPORT_ENABLE(r32, v) _BFSET_(r32, 0, 0, v)
#define SET32AIO_SECSRC_SLAVEMODE(r32, v) _BFSET_(r32, 10, 10, v)
#define SET32IOSEL_SECBCLK_SEL(r32, v) _BFSET_(r32, 1, 0, v)
#define SET32avioGbl_CTRL0_I2S3_BCLK_OEN(r32, v) _BFSET_(r32, 1, 1, v)
#define SET32avioGbl_CTRL0_I2S3_LRCLK_OEN(r32, v) _BFSET_(r32, 2, 2, v)
#define AS370_SEC_RATES (SNDRV_PCM_RATE_8000_48000)
#define AS370_SEC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE \
| SNDRV_PCM_FMTBIT_S32_LE)
struct sec_priv {
const char *dev_name;
unsigned int irq;
u32 chid;
bool requested;
bool is_tdm; //tdm mode
bool is_pcm; //pcm mono ch case, for BT
bool is_slave;
bool invbclk;
bool invfs;
bool rightjfy;
bool jfied;
void __iomem *sbase; //SEC register base
void __iomem *sbclk;
void __iomem *ssrc;
void __iomem *soen;
};
static void sec_set_ctl(struct sec_priv *sec, u32 width_w, u32 width_s)
{
u32 offset = RA_SEC_SECAUD + RA_PRIAUD_CTRL;
u32 val;
val = aio_read(sec->sbase + offset);
SET32PRIAUD_CTRL_LEFTJFY(val, sec->rightjfy); //0: LEFTJFY, 1: RIGHTJFY
SET32PRIAUD_CTRL_INVCLK(val, sec->invbclk);
SET32PRIAUD_CTRL_INVFS(val, sec->invfs);
SET32PRIAUD_CTRL_TDM(val, width_s);
SET32PRIAUD_CTRL_TCF(val, width_w);
if (sec->jfied)
SET32PRIAUD_CTRL_TFM(val, 1); //JUSTIFIED
else
SET32PRIAUD_CTRL_TFM(val, 2); //I2S
SET32PRIAUD_CTRL_TDMMODE(val, sec->is_tdm);
aio_write(sec->sbase + offset, val);
if (sec->is_pcm) {
offset = RA_SEC_SECAUD + RA_PRIAUD_CTRL1;
val = aio_read(sec->sbase + offset);
SET32PRIAUD_CTRL_PCM_MONO_CH(val, 1); //set to pcm mono
aio_write(sec->sbase + offset, val);
}
}
static void sec_set_clk_div(struct sec_priv *sec, u32 div)
{
u32 offset = RA_SEC_SECAUD + RA_PRIAUD_CLKDIV;
u32 val;
val = aio_read(sec->sbase + offset);
SET32PRIAUD_CLKDIV_SETTING(val, div);
aio_write(sec->sbase + offset, val);
}
static void sec_blrclk_oen(struct sec_priv *sec, bool en)
{
u32 val;
//enable bclk and lrclk
val = aio_read(sec->soen);
SET32avioGbl_CTRL0_I2S3_BCLK_OEN(val, en);
SET32avioGbl_CTRL0_I2S3_LRCLK_OEN(val, en);
aio_write(sec->soen, val);
}
static void sec_enable_port(struct sec_priv *sec, bool en)
{
u32 offset = RA_SEC_SECPORT;
u32 val;
val = aio_read(sec->sbase + offset);
SET32SEC_SECPORT_ENABLE(val, en);
aio_write(sec->sbase + offset, val);
}
static struct snd_kcontrol_new as370_sec_ctrls[] = {
//TODO: add dai control here
};
/*
* Applies output configuration of |berlin_pcm| to i2s.
* Must be called with instance spinlock held.
* Only one dai instance for playback, so no spin_lock needed
*/
static void sec_set_aio(struct sec_priv *sec,
u32 fs, int width, bool tdm,
bool invbclk, bool invfs)
{
unsigned int div, dfm, cfm;
aio_set_aud_ch_mute(sec->sbase + RA_SEC_TSD0_SEC, 1);
aio_set_aud_ch_en(sec->sbase + RA_SEC_TSD0_SEC, 0);
// update as370_playback_rates if any rates are added or removed.
switch (fs) {
case 8000:
case 11025:
case 12000:
div = AIO_DIV32;
break;
case 16000:
case 22050:
case 24000:
div = AIO_DIV16;
break;
case 32000:
case 44100:
case 48000:
div = AIO_DIV8;
break;
case 64000:
case 88200:
case 96000:
div = AIO_DIV4;
break;
default:
div = AIO_DIV8;
break;
}
switch (width) {
case 16:
dfm = AIO_16DFM;
cfm = AIO_16CFM;
break;
case 24:
dfm = AIO_24DFM;
cfm = AIO_24CFM;
break;
case 32:
dfm = AIO_32DFM;
cfm = AIO_32CFM;
break;
default:
return;
}
if (!sec->is_slave)
sec_set_clk_div(sec, div);
sec_set_ctl(sec, cfm, dfm);
aio_set_aud_ch_en(sec->sbase + RA_SEC_TSD0_SEC, 1);
}
// Set to slavemode, and sel bclk from external, invert CLK
static void sec_set_slave_mode(struct sec_priv *sec,
bool bset, u8 bsel)
{
u32 val;
val = aio_read(sec->ssrc);
SET32AIO_SECSRC_SLAVEMODE(val, bset);
aio_write(sec->ssrc, val);
val = aio_read(sec->sbclk);
SET32IOSEL_SECBCLK_SEL(val, bsel);
aio_write(sec->sbclk, val);
}
static int as370_sec_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct sec_priv *sec = snd_soc_dai_get_drvdata(dai);
// Enable i2s channel without corresponding disable in close.
// This is intentional: Avoid SPDIF 'activation delay' problem.
aio_set_aud_ch_en(sec->sbase + RA_SEC_TSD0_SEC, 1);
aio_set_aud_ch_mute(sec->sbase + RA_SEC_TSD0_SEC, 1);
return 0;
}
static void as370_sec_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct sec_priv *sec = snd_soc_dai_get_drvdata(dai);
aio_set_aud_ch_en(sec->sbase + RA_SEC_TSD0_SEC, 0);
}
static int as370_sec_setfmt(struct snd_soc_dai *dai, unsigned int fmt)
{
struct sec_priv *sec = snd_soc_dai_get_drvdata(dai);
int ret = 0;
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
sec->invbclk = 0;
sec->invfs = 0;
break;
case SND_SOC_DAIFMT_NB_IF:
sec->invbclk = 0;
sec->invfs = 1;
break;
case SND_SOC_DAIFMT_IB_NF:
sec->invbclk = 1;
sec->invfs = 0;
break;
case SND_SOC_DAIFMT_IB_IF:
sec->invbclk = 1;
sec->invfs = 1;
break;
default:
dev_err(dai->dev, "MASK not found in fmt 0x%x\n", fmt);
ret = -EINVAL;
}
return ret;
}
static int as370_sec_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct sec_priv *sec = snd_soc_dai_get_drvdata(dai);
u32 fs = params_rate(params);
int ret;
ret = berlin_pcm_request_dma_irq(substream,
1, 1,
&sec->irq,
sec->dev_name,
I2SO_MODE | PCMMONO_MODE);
if (ret == 0)
sec->requested = true;
else
return ret;
aio_set_pll(fs, 3, 2);
sec_set_aio(sec, fs, params_width(params),
sec->is_tdm, sec->invbclk, sec->invfs);
if (sec->is_slave) {
sec_set_slave_mode(sec, 1, 0);
sec_blrclk_oen(sec, 0);
}
return ret;
}
static int as370_sec_hw_free(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct sec_priv *sec = snd_soc_dai_get_drvdata(dai);
if (sec->is_slave)
sec_set_slave_mode(sec, 0, 1);
if (sec->requested && sec->irq >= 0) {
berlin_pcm_free_dma_irq(substream, 1, 1, &sec->irq);
sec->requested = false;
}
return 0;
}
static int as370_sec_trigger(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *dai)
{
struct sec_priv *sec = snd_soc_dai_get_drvdata(dai);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
sec_enable_port(sec, 1);
aio_set_aud_ch_mute(sec->sbase + RA_SEC_TSD0_SEC, 0);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
aio_set_aud_ch_mute(sec->sbase + RA_SEC_TSD0_SEC, 1);
sec_enable_port(sec, 0);
break;
default:
return -EINVAL;
}
return 0;
}
static int as370_sec_dai_probe(struct snd_soc_dai *dai)
{
snd_soc_add_dai_controls(dai, as370_sec_ctrls,
ARRAY_SIZE(as370_sec_ctrls));
return 0;
}
static struct snd_soc_dai_ops as370_dai_sec_ops = {
.startup = as370_sec_startup,
.set_fmt = as370_sec_setfmt,
.hw_params = as370_sec_hw_params,
.hw_free = as370_sec_hw_free,
.trigger = as370_sec_trigger,
.shutdown = as370_sec_shutdown,
};
static struct snd_soc_dai_driver as370_sec_dai = {
.name = "as370-sec-dai",
.probe = as370_sec_dai_probe,
.playback = {
.stream_name = "SEC-Playback",
.channels_min = 1,
.channels_max = 2,
.rates = AS370_SEC_RATES,
.formats = AS370_SEC_FORMATS,
},
.ops = &as370_dai_sec_ops,
};
static const struct snd_soc_component_driver as370_sec_component = {
.name = "as370-sec",
};
static int as370_sec_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct sec_priv *sec;
struct resource *sbase, *ssrc, *sbclk, *soen;
int irq, ret;
sec = devm_kzalloc(dev, sizeof(struct sec_priv), GFP_KERNEL);
if (!sec)
return -ENOMEM;
irq = platform_get_irq(pdev, 0);
if (irq <= 0) {
snd_printk("fail to get irq for node %s\n", pdev->name);
return -EINVAL;
}
sec->irq = irq;
sec->chid = irqd_to_hwirq(irq_get_irq_data(irq));
sbase = platform_get_resource_byname(pdev,
IORESOURCE_MEM,
"sec_base");
sec->sbase = devm_ioremap_resource(dev, sbase);
ssrc = platform_get_resource_byname(pdev,
IORESOURCE_MEM,
"sec_src");
sec->ssrc = devm_ioremap_resource(dev, ssrc);
sbclk = platform_get_resource_byname(pdev,
IORESOURCE_MEM,
"sec_bclk");
sec->sbclk = devm_ioremap_resource(dev, sbclk);
soen = platform_get_resource_byname(pdev,
IORESOURCE_MEM,
"sec_oen");
sec->soen = devm_ioremap_resource(dev, soen);
sec->is_tdm = of_property_read_bool(np, "tdm");
sec->is_pcm = of_property_read_bool(np, "pcm");
sec->is_slave = of_property_read_bool(np, "slave");
sec->invfs = of_property_read_bool(np, "invfs");
sec->invbclk = of_property_read_bool(np, "invbclk");
sec->rightjfy = of_property_read_bool(np, "r_jfy");
sec->jfied = of_property_read_bool(np, "jfied");
if (sec->is_tdm && sec->is_pcm) {
snd_printk("dts err: can't be both tdm and pcm\n");
return -EINVAL;
}
sec->dev_name = dev_name(dev);
dev_set_drvdata(dev, sec);
ret = devm_snd_soc_register_component(dev,
&as370_sec_component,
&as370_sec_dai, 1);
if (ret) {
snd_printk("failed to register DAI: %d\n", ret);
return ret;
}
snd_printd("%s: done irq %d chid %d tdm %d slave %d\n", __func__,
sec->irq, sec->chid, sec->is_tdm, sec->is_slave);
return ret;
}
static const struct of_device_id as370_sec_dt_ids[] = {
{ .compatible = "syna,as370-sec", },
{}
};
MODULE_DEVICE_TABLE(of, as370_sec_dt_ids);
static struct platform_driver as370_sec_driver = {
.probe = as370_sec_probe,
.driver = {
.name = "syna-as370-sec",
.of_match_table = as370_sec_dt_ids,
},
};
module_platform_driver(as370_sec_driver);
MODULE_DESCRIPTION("Synaptics AS370 playback ALSA dai");
MODULE_ALIAS("platform:as370-sec");
MODULE_LICENSE("GPL v2");