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