| // 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"); |