blob: ef8c84d9ca660f89cf2f4a912c44daa08e99ce56 [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 avioDhubChMap_aio64b_MIC1_W0 0x4
#define avioDhubChMap_aio64b_MIC1_W1 0x5
#define avioDhubChMap_aio64b_MIC1_PDM_W0 0x6
#define avioDhubChMap_aio64b_MIC1_PDM_W1 0x9
#define RA_MIC1_MICCTRL 0x0000
#define RA_MIC1_RSD0 0x000C
#define RA_MIC1_RSD1 0x001C
#define RA_MIC1_RSD2 0x0020
#define RA_MIC1_RSD3 0x0024
#define RA_MIC1_MM_MODE 0x0028
#define RA_MIC1_RXPORT 0x002C
#define SET32MIC1_MM_MODE_RCV_MASTER(r32, v) _BFSET_(r32, 0, 0, v)
#define SET32MIC1_RXPORT_ENABLE(r32, v) _BFSET_(r32, 0, 0, v)
#define SET32IOSEL_MIC1BCLK_SEL(r32, v) _BFSET_(r32, 1, 0, v)
#define SET32IOSEL_MIC1BCLK_INV(r32, v) _BFSET_(r32, 2, 2, v)
#define SET32IOSEL_MIC1FSYNC_SEL(r32, v) _BFSET_(r32, 0, 0, v)
#define SET32IOSEL_MIC1FSYNC_INV(r32, v) _BFSET_(r32, 1, 1, v)
#define AS370_MIC1_RATES (SNDRV_PCM_RATE_8000_96000)
#define AS370_MIC1_FORMATS (SNDRV_PCM_FMTBIT_S16_LE \
| SNDRV_PCM_FMTBIT_S32_LE)
#define MAX_MIC1_CH 4
static atomic_t mic1_cnt = ATOMIC_INIT(0);
struct mic1_priv {
struct platform_device *pdev;
const char *dev_name;
u32 irqc;
unsigned int irq[MAX_MIC1_CH];
u32 chid[MAX_MIC1_CH];
void __iomem *base;
void __iomem *bclk;
void __iomem *fsync;
void __iomem *pdmmicsel;
bool irq_requested;
bool is_tdm;
bool is_master;
bool invbclk;
bool invfsync;
bool use_pri_clk;
};
static int mic1_get_ch_offset(u8 ch)
{
switch (ch) {
case avioDhubChMap_aio64b_MIC1_W0:
return RA_MIC1_RSD0;
case avioDhubChMap_aio64b_MIC1_W1:
return RA_MIC1_RSD1;
case avioDhubChMap_aio64b_MIC1_PDM_W0:
return RA_MIC1_RSD2;
case avioDhubChMap_aio64b_MIC1_PDM_W1:
return RA_MIC1_RSD3;
default:
snd_printk("illegal dhubch %d!, return ch0\n", ch);
return RA_MIC1_RSD0;
}
}
static void mic1_set_rx_port_en(struct mic1_priv *mic1, bool en)
{
void __iomem *addr = mic1->base + RA_MIC1_RXPORT;
u32 val;
val = aio_read(addr);
SET32MIC1_RXPORT_ENABLE(val, en);
aio_write(addr, val);
}
static void mic1_ch_flush(struct mic1_priv *mic1, bool en)
{
void __iomem *addr;
u32 i, ch;
for (i = 0; i < mic1->irqc; i++) {
ch = mic1->chid[i];
addr = mic1->base + mic1_get_ch_offset(ch) + RA_MIC1_MICCTRL;
aio_set_aud_ch_flush(addr, en);
}
}
static void mic1_ch_en(struct mic1_priv *mic1, bool en)
{
void __iomem *addr;
u32 i, ch;
for (i = 0; i < mic1->irqc; i++) {
ch = mic1->chid[i];
addr = mic1->base + mic1_get_ch_offset(ch) + RA_MIC1_MICCTRL;
aio_set_aud_ch_en(addr, en);
}
}
static void mic1_ch_mute(struct mic1_priv *mic1, bool en)
{
void __iomem *addr;
u32 i, ch;
for (i = 0; i < mic1->irqc; i++) {
ch = mic1->chid[i];
addr = mic1->base + mic1_get_ch_offset(ch) + RA_MIC1_MICCTRL;
aio_set_aud_ch_mute(addr, en);
}
}
static void mic1_enable(struct mic1_priv *mic1, bool en)
{
if (en && atomic_inc_return(&mic1_cnt) > 1)
return;
else if (!en && atomic_dec_if_positive(&mic1_cnt) != 0)
return;
if (en) {
mic1_set_rx_port_en(mic1, 1);
} else {
mic1_set_rx_port_en(mic1, 0);
}
}
static void mic1_sel_mic(struct mic1_priv *mic1)
{
u32 sel, ch, i;
for (i = 0; i < mic1->irqc; i++) {
ch = mic1->chid[i];
if (ch == avioDhubChMap_aio64b_MIC1_PDM_W0)
sel = 0x4;
else if (ch == avioDhubChMap_aio64b_MIC1_PDM_W1)
sel = 0x8;
else
sel = 0;
if (sel) {
void __iomem *addr = mic1->pdmmicsel;
u32 val = aio_read(addr);
val |= sel;
aio_write(addr, val);
}
}
}
// Keep consistant for multiple mic1 instances case
static void mic1_set_ctl(struct mic1_priv *mic1,
u32 data_fmt, u32 width_w, u32 width_s,
bool istdm, bool invbclk, bool invfs)
{
void __iomem *addr = mic1->base + RA_MIC1_MICCTRL + RA_PRIAUD_CTRL;
u32 val;
val = aio_read(addr);
SET32PRIAUD_CTRL_LEFTJFY(val, 0);
SET32PRIAUD_CTRL_INVCLK(val, invbclk);
SET32PRIAUD_CTRL_INVFS(val, invfs);
SET32PRIAUD_CTRL_TDM(val, width_s);
SET32PRIAUD_CTRL_TCF(val, width_w);
SET32PRIAUD_CTRL_TFM(val, data_fmt);
SET32PRIAUD_CTRL_TDMMODE(val, istdm);
aio_write(addr, val);
}
static void mic1_set_bclk(struct mic1_priv *mic1,
u8 bclk, bool inv)
{
void __iomem *addr = mic1->bclk;
u32 val;
val = aio_read(addr);
SET32IOSEL_MIC1BCLK_SEL(val, bclk);
SET32IOSEL_MIC1BCLK_INV(val, inv);
aio_write(addr, val);
}
static void mic1_set_fsync(struct mic1_priv *mic1,
bool fsync, bool inv)
{
void __iomem *addr = mic1->fsync;
u32 val;
val = aio_read(addr);
SET32IOSEL_MIC1FSYNC_SEL(val, fsync);
SET32IOSEL_MIC1FSYNC_INV(val, inv);
aio_write(addr, val);
}
static void mic1_set_mm_mode(struct mic1_priv *mic1,
bool en)
{
void __iomem *addr = mic1->base + RA_MIC1_MM_MODE;
u32 val;
val = aio_read(addr);
SET32MIC1_MM_MODE_RCV_MASTER(val, en);
aio_write(addr, val);
}
static struct snd_kcontrol_new as370_mic1_ctrls[] = {
//TODO: add dai controls here
};
static int as370_mic1_startup(struct snd_pcm_substream *ss,
struct snd_soc_dai *dai)
{
struct mic1_priv *mic1 = snd_soc_dai_get_drvdata(dai);
snd_printd("%s: start %p %p\n", __func__, ss, dai);
mic1_ch_en(mic1, 1);
mic1_ch_mute(mic1, 1);
return 0;
}
static void as370_mic1_shutdown(struct snd_pcm_substream *ss,
struct snd_soc_dai *dai)
{
struct mic1_priv *mic1 = snd_soc_dai_get_drvdata(dai);
snd_printd("%s: start %p %p\n", __func__, ss, dai);
mic1_ch_mute(mic1, 1);
mic1_ch_en(mic1, 0);
}
static int as370_mic1_hw_params(struct snd_pcm_substream *ss,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct mic1_priv *mic1 = snd_soc_dai_get_drvdata(dai);
u32 fs = params_rate(params), dfm, chid_num;
int ret;
switch (params_width(params)) {
case 16:
dfm = AIO_16DFM;
break;
case 24:
dfm = AIO_24DFM;
break;
case 32:
default:
dfm = AIO_32DFM;
break;
}
aio_set_pll(fs, 4, 0);
mic1_sel_mic(mic1);
mic1_set_ctl(mic1, 2, AIO_32CFM, dfm,
mic1->is_tdm, mic1->invbclk, mic1->invfsync);
if (mic1->is_master) {
mic1_set_bclk(mic1, 1, mic1->invbclk);
mic1_set_fsync(mic1, 0, mic1->invfsync);
mic1_set_mm_mode(mic1, 1);
} else if (mic1->use_pri_clk) {
mic1_set_bclk(mic1, 2, mic1->invbclk);
mic1_set_fsync(mic1, mic1->use_pri_clk, mic1->invfsync);
mic1_set_mm_mode(mic1, 0);
}
mic1_ch_flush(mic1, 0);
chid_num = (params_channels(params) + 1) / 2;
if (chid_num > mic1->irqc) {
snd_printk("max %d ch support by dts\n", 2 * mic1->irqc);
return -EINVAL;
}
ret = berlin_pcm_request_dma_irq(ss,
chid_num,
1,
mic1->irq,
mic1->dev_name,
I2SI_MODE);
if (ret == 0)
mic1->irq_requested = true;
return ret;
}
static int as370_mic1_set_dai_fmt(struct snd_soc_dai *dai,
unsigned int fmt)
{
struct mic1_priv *mic1 = snd_soc_dai_get_drvdata(dai);
struct device *dev = &mic1->pdev->dev;
int ret = 0;
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
case SND_SOC_DAIFMT_RIGHT_J:
case SND_SOC_DAIFMT_LEFT_J:
case SND_SOC_DAIFMT_PDM:
//TODO
break;
default:
dev_err(dev, "%s: Unknown DAI format mask %x\n",
__func__, 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_NF:
case SND_SOC_DAIFMT_IB_IF:
//TODO
break;
default:
dev_err(dev, "%s: Unknown DAI invert mask %x\n",
__func__, fmt);
return -EINVAL;
}
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBM_CFM:
case SND_SOC_DAIFMT_CBS_CFS:
case SND_SOC_DAIFMT_CBM_CFS:
case SND_SOC_DAIFMT_CBS_CFM:
//TODO
break;
default:
dev_err(dev, "%s: Unknown DAI master mask %x\n",
__func__, fmt);
return -EINVAL;
}
return ret;
}
static int as370_mic1_hw_free(struct snd_pcm_substream *ss,
struct snd_soc_dai *dai)
{
struct mic1_priv *mic1 = snd_soc_dai_get_drvdata(dai);
if (mic1->irq_requested) {
berlin_pcm_free_dma_irq(ss, mic1->irqc, 1, mic1->irq);
mic1->irq_requested = false;
}
if (mic1->is_master || mic1->use_pri_clk) {
mic1_set_bclk(mic1, 0, 0);
mic1_set_fsync(mic1, 0, 0);
mic1_set_mm_mode(mic1, 0);
}
mic1_ch_flush(mic1, 1);
return 0;
}
static int as370_mic1_trigger(struct snd_pcm_substream *ss,
int cmd, struct snd_soc_dai *dai)
{
struct mic1_priv *mic1 = snd_soc_dai_get_drvdata(dai);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
mic1_ch_mute(mic1, 0);
mic1_enable(mic1, 1);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
mic1_enable(mic1, 0);
mic1_ch_mute(mic1, 1);
break;
default:
return -EINVAL;
}
return 0;
}
static int as370_mic1_dai_probe(struct snd_soc_dai *dai)
{
snd_soc_add_dai_controls(dai, as370_mic1_ctrls,
ARRAY_SIZE(as370_mic1_ctrls));
return 0;
}
static struct snd_soc_dai_ops as370_dai_mic1_ops = {
.startup = as370_mic1_startup,
.hw_params = as370_mic1_hw_params,
.set_fmt = as370_mic1_set_dai_fmt,
.hw_free = as370_mic1_hw_free,
.trigger = as370_mic1_trigger,
.shutdown = as370_mic1_shutdown,
};
static struct snd_soc_dai_driver as370_mic1_dai = {
.name = "as370-mic1",
.probe = as370_mic1_dai_probe,
.capture = {
.stream_name = "MIC1-Capture",
.channels_min = 2,
.channels_max = 8,
.rates = AS370_MIC1_RATES,
.formats = AS370_MIC1_FORMATS,
},
.ops = &as370_dai_mic1_ops,
};
static const struct snd_soc_component_driver as370_mic1_component = {
.name = "as370-mic1",
};
static int as370_mic1_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct mic1_priv *mic1;
struct resource *base, *bclk, *fsync, *pdmmicsel;
int i, ret;
mic1 = devm_kzalloc(dev, sizeof(struct mic1_priv), GFP_KERNEL);
if (!mic1)
return -ENOMEM;
mic1->irqc = platform_irq_count(pdev);
if (mic1->irqc == 0) {
snd_printk("no mic1 channel defined in dts, do onthing");
return 0;
}
for (i = 0; i < mic1->irqc; i++) {
mic1->irq[i] = platform_get_irq(pdev, i);
if (mic1->irq[i] <= 0) {
snd_printk("fail to get irq %d for %s\n",
mic1->irq[i], pdev->name);
return -EINVAL;
}
mic1->chid[i] = irqd_to_hwirq(irq_get_irq_data(mic1->irq[i]));
if (mic1->chid[i] < 0) {
snd_printk("got invalid dhub chid %d\n", mic1->chid[i]);
return -EINVAL;
}
}
base = platform_get_resource_byname(pdev,
IORESOURCE_MEM,
"mic1_base");
if (base) {
mic1->base = devm_ioremap(dev, base->start,
resource_size(base));
}
bclk = platform_get_resource_byname(pdev,
IORESOURCE_MEM,
"mic1_bclk");
if (bclk) {
mic1->bclk = devm_ioremap(dev, bclk->start,
resource_size(bclk));
}
fsync = platform_get_resource_byname(pdev,
IORESOURCE_MEM,
"mic1_fsync");
if (fsync) {
mic1->fsync = devm_ioremap(dev, fsync->start,
resource_size(fsync));
}
pdmmicsel = platform_get_resource_byname(pdev,
IORESOURCE_MEM,
"pdmmic_sel");
if (pdmmicsel) {
mic1->pdmmicsel = devm_ioremap(dev, pdmmicsel->start,
resource_size(pdmmicsel));
}
mic1->is_tdm = of_property_read_bool(np, "tdm");
mic1->is_master = of_property_read_bool(np, "master");
mic1->invbclk = of_property_read_bool(np, "invertbclk");
mic1->invfsync = of_property_read_bool(np, "invertfsync");
mic1->use_pri_clk = of_property_read_bool(np, "usepriclk");
mic1->dev_name = dev_name(dev);
mic1->pdev = pdev;
dev_set_drvdata(dev, mic1);
ret = devm_snd_soc_register_component(dev,
&as370_mic1_component,
&as370_mic1_dai, 1);
if (ret) {
snd_printk("failed to register DAI: %d\n", ret);
return ret;
}
snd_printd("%s: done irqc %d tdm %d master %d\n", __func__,
mic1->irqc, mic1->is_tdm, mic1->is_master);
return ret;
}
static const struct of_device_id as370_mic1_dt_ids[] = {
{ .compatible = "syna,as370-mic1", },
{}
};
MODULE_DEVICE_TABLE(of, as370_mic1_dt_ids);
static struct platform_driver as370_mic1_driver = {
.probe = as370_mic1_probe,
.driver = {
.name = "syna-as370-mic1",
.of_match_table = as370_mic1_dt_ids,
},
};
module_platform_driver(as370_mic1_driver);
MODULE_DESCRIPTION("Synaptics AS370 MIC1 ALSA driver");
MODULE_ALIAS("platform:as370-mic1");
MODULE_LICENSE("GPL v2");