// SPDX-License-Identifier: GPL-2.0-or-later /* * AC97 Controller Driver for Loongson-1 SoC * * Copyright (C) 2025 Keguang Zhang */ #include #include #include #include #include #include #include #include #include #include /* Loongson-1 AC97 Controller Registers */ #define AC97_CSR 0x0 #define AC97_OCC0 0x4 #define AC97_ICC 0x10 #define AC97_CRAC 0x18 #define AC97_INTRAW 0x54 #define AC97_INTM 0x58 #define AC97_INT_CW_CLR 0x68 #define AC97_INT_CR_CLR 0x6c /* Control Status Register Bits (CSR) */ #define CSR_RESUME BIT(1) #define CSR_RST_FORCE BIT(0) /* MIC Channel Configuration Bits */ #define M_DMA_EN BIT(22) #define M_FIFO_THRES GENMASK(21, 20) #define M_FIFO_THRES_FULL FIELD_PREP(M_FIFO_THRES, 3) #define M_FIFO_THRES_HALF FIELD_PREP(M_FIFO_THRES, 1) #define M_FIFO_THRES_QUARTER FIELD_PREP(M_FIFO_THRES, 0) #define M_SW GENMASK(19, 18) #define M_SW_16_BITS FIELD_PREP(M_SW, 2) #define M_SW_8_BITS FIELD_PREP(M_SW, 0) #define M_VSR BIT(17) #define M_CH_EN BIT(16) /* Right Channel Configuration Bits */ #define R_DMA_EN BIT(14) #define R_FIFO_THRES GENMASK(13, 12) #define R_FIFO_THRES_EMPTY FIELD_PREP(R_FIFO_THRES, 3) #define R_FIFO_THRES_HALF FIELD_PREP(R_FIFO_THRES, 1) #define R_FIFO_THRES_QUARTER FIELD_PREP(R_FIFO_THRES, 0) #define R_SW GENMASK(11, 10) #define R_SW_16_BITS FIELD_PREP(R_SW, 2) #define R_SW_8_BITS FIELD_PREP(R_SW, 0) #define R_VSR BIT(9) #define R_CH_EN BIT(8) /* Left Channel Configuration Bits */ #define L_DMA_EN BIT(6) #define L_FIFO_THRES GENMASK(5, 4) #define L_FIFO_THRES_EMPTY FIELD_PREP(L_FIFO_THRES, 3) #define L_FIFO_THRES_HALF FIELD_PREP(L_FIFO_THRES, 1) #define L_FIFO_THRES_QUARTER FIELD_PREP(L_FIFO_THRES, 0) #define L_SW GENMASK(3, 2) #define L_SW_16_BITS FIELD_PREP(L_SW, 2) #define L_SW_8_BITS FIELD_PREP(L_SW, 0) #define L_VSR BIT(1) #define L_CH_EN BIT(0) /* Codec Register Access Command Bits (CRAC) */ #define CODEC_WR BIT(31) #define CODEC_ADR GENMASK(22, 16) #define CODEC_DAT GENMASK(15, 0) /* Interrupt Register (INTRAW) */ #define CW_DONE BIT(1) #define CR_DONE BIT(0) #define LS1X_AC97_DMA_TX_EN BIT(31) #define LS1X_AC97_DMA_STEREO BIT(30) #define LS1X_AC97_DMA_TX_BYTES GENMASK(29, 28) #define LS1X_AC97_DMA_TX_4_BYTES FIELD_PREP(LS1X_AC97_DMA_TX_BYTES, 2) #define LS1X_AC97_DMA_TX_2_BYTES FIELD_PREP(LS1X_AC97_DMA_TX_BYTES, 1) #define LS1X_AC97_DMA_TX_1_BYTE FIELD_PREP(LS1X_AC97_DMA_TX_BYTES, 0) #define LS1X_AC97_DMA_DADDR_MASK GENMASK(27, 0) #define LS1X_AC97_DMA_FIFO_SIZE 128 #define LS1X_AC97_TIMEOUT 3000 struct ls1x_ac97 { void __iomem *reg_base; struct regmap *regmap; dma_addr_t tx_dma_base; dma_addr_t rx_dma_base; struct snd_dmaengine_dai_dma_data capture_dma_data; struct snd_dmaengine_dai_dma_data playback_dma_data; }; static struct ls1x_ac97 *ls1x_ac97; static const struct regmap_config ls1x_ac97_regmap_config = { .reg_bits = 32, .val_bits = 32, .reg_stride = 4, }; static void ls1x_ac97_reset(struct snd_ac97 *ac97) { int val; regmap_write(ls1x_ac97->regmap, AC97_CSR, CSR_RST_FORCE); regmap_read_poll_timeout(ls1x_ac97->regmap, AC97_CSR, val, !(val & CSR_RESUME), 0, LS1X_AC97_TIMEOUT); } static void ls1x_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short val) { int tmp, ret; tmp = FIELD_PREP(CODEC_ADR, reg) | FIELD_PREP(CODEC_DAT, val); regmap_write(ls1x_ac97->regmap, AC97_CRAC, tmp); ret = regmap_read_poll_timeout(ls1x_ac97->regmap, AC97_INTRAW, tmp, (tmp & CW_DONE), 0, LS1X_AC97_TIMEOUT); if (ret) pr_err("timeout on AC97 write! %d\n", ret); regmap_read(ls1x_ac97->regmap, AC97_INT_CW_CLR, &ret); } static unsigned short ls1x_ac97_read(struct snd_ac97 *ac97, unsigned short reg) { int val, ret; val = CODEC_WR | FIELD_PREP(CODEC_ADR, reg); regmap_write(ls1x_ac97->regmap, AC97_CRAC, val); ret = regmap_read_poll_timeout(ls1x_ac97->regmap, AC97_INTRAW, val, (val & CR_DONE), 0, LS1X_AC97_TIMEOUT); if (ret) { pr_err("timeout on AC97 read! %d\n", ret); return ret; } regmap_read(ls1x_ac97->regmap, AC97_INT_CR_CLR, &ret); regmap_read(ls1x_ac97->regmap, AC97_CRAC, &ret); return (ret & CODEC_DAT); } static void ls1x_ac97_init(struct snd_ac97 *ac97) { writel(0, ls1x_ac97->reg_base + AC97_INTRAW); writel(0, ls1x_ac97->reg_base + AC97_INTM); /* Config output channels */ regmap_update_bits(ls1x_ac97->regmap, AC97_OCC0, R_DMA_EN | R_FIFO_THRES | R_CH_EN | L_DMA_EN | L_FIFO_THRES | L_CH_EN, R_DMA_EN | R_FIFO_THRES_EMPTY | R_CH_EN | L_DMA_EN | L_FIFO_THRES_EMPTY | L_CH_EN); /* Config inputs channel */ regmap_update_bits(ls1x_ac97->regmap, AC97_ICC, M_DMA_EN | M_FIFO_THRES | M_CH_EN | R_DMA_EN | R_FIFO_THRES | R_CH_EN | L_DMA_EN | L_FIFO_THRES | L_CH_EN, M_DMA_EN | M_FIFO_THRES_FULL | M_CH_EN | R_DMA_EN | R_FIFO_THRES_EMPTY | R_CH_EN | L_DMA_EN | L_FIFO_THRES_EMPTY | L_CH_EN); if (ac97->ext_id & AC97_EI_VRA) { regmap_update_bits(ls1x_ac97->regmap, AC97_OCC0, R_VSR | L_VSR, R_VSR | L_VSR); regmap_update_bits(ls1x_ac97->regmap, AC97_ICC, M_VSR, M_VSR); } } static struct snd_ac97_bus_ops ls1x_ac97_ops = { .reset = ls1x_ac97_reset, .write = ls1x_ac97_write, .read = ls1x_ac97_read, .init = ls1x_ac97_init, }; static int ls1x_ac97_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *cpu_dai) { struct ls1x_ac97 *ac97 = dev_get_drvdata(cpu_dai->dev); struct snd_dmaengine_dai_dma_data *dma_data = snd_soc_dai_get_dma_data(cpu_dai, substream); switch (params_channels(params)) { case 1: dma_data->addr &= ~LS1X_AC97_DMA_STEREO; break; case 2: dma_data->addr |= LS1X_AC97_DMA_STEREO; break; default: dev_err(cpu_dai->dev, "unsupported channels! %d\n", params_channels(params)); return -EINVAL; } switch (params_format(params)) { case SNDRV_PCM_FORMAT_S8: case SNDRV_PCM_FORMAT_U8: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) regmap_update_bits(ac97->regmap, AC97_OCC0, R_SW | L_SW, R_SW_8_BITS | L_SW_8_BITS); else regmap_update_bits(ac97->regmap, AC97_ICC, M_SW | R_SW | L_SW, M_SW_8_BITS | R_SW_8_BITS | L_SW_8_BITS); break; case SNDRV_PCM_FORMAT_S16_LE: case SNDRV_PCM_FORMAT_U16_LE: case SNDRV_PCM_FORMAT_S16_BE: case SNDRV_PCM_FORMAT_U16_BE: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) regmap_update_bits(ac97->regmap, AC97_OCC0, R_SW | L_SW, R_SW_16_BITS | L_SW_16_BITS); else regmap_update_bits(ac97->regmap, AC97_ICC, M_SW | R_SW | L_SW, M_SW_16_BITS | R_SW_16_BITS | L_SW_16_BITS); break; default: dev_err(cpu_dai->dev, "unsupported format! %d\n", params_format(params)); return -EINVAL; } return 0; } static int ls1x_ac97_dai_probe(struct snd_soc_dai *cpu_dai) { struct ls1x_ac97 *ac97 = dev_get_drvdata(cpu_dai->dev); ac97->capture_dma_data.addr = ac97->rx_dma_base & LS1X_AC97_DMA_DADDR_MASK; ac97->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; ac97->capture_dma_data.fifo_size = LS1X_AC97_DMA_FIFO_SIZE; ac97->playback_dma_data.addr = ac97->tx_dma_base & LS1X_AC97_DMA_DADDR_MASK; ac97->playback_dma_data.addr |= LS1X_AC97_DMA_TX_4_BYTES; ac97->playback_dma_data.addr |= LS1X_AC97_DMA_TX_EN; ac97->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; ac97->playback_dma_data.fifo_size = LS1X_AC97_DMA_FIFO_SIZE; snd_soc_dai_init_dma_data(cpu_dai, &ac97->playback_dma_data, &ac97->capture_dma_data); snd_soc_dai_set_drvdata(cpu_dai, ac97); return 0; } static const struct snd_soc_dai_ops ls1x_ac97_dai_ops = { .probe = ls1x_ac97_dai_probe, .hw_params = ls1x_ac97_hw_params, }; #define LS1X_AC97_FMTS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 |\ SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |\ SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE) static struct snd_soc_dai_driver ls1x_ac97_dai[] = { { .name = "ls1x-ac97", .playback = { .stream_name = "AC97 Playback", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_48000, .formats = LS1X_AC97_FMTS, }, .capture = { .stream_name = "AC97 Capture", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_48000, .formats = LS1X_AC97_FMTS, }, .ops = &ls1x_ac97_dai_ops, }, }; static const struct snd_soc_component_driver ls1x_ac97_component = { .name = KBUILD_MODNAME, .legacy_dai_naming = 1, }; static int ls1x_ac97_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct ls1x_ac97 *ac97; struct resource *res; int ret; ac97 = devm_kzalloc(dev, sizeof(struct ls1x_ac97), GFP_KERNEL); if (!ac97) return -ENOMEM; ls1x_ac97 = ac97; platform_set_drvdata(pdev, ac97); ac97->reg_base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(ac97->reg_base)) return PTR_ERR(ac97->reg_base); ac97->regmap = devm_regmap_init_mmio(dev, ac97->reg_base, &ls1x_ac97_regmap_config); if (IS_ERR(ac97->regmap)) return dev_err_probe(dev, PTR_ERR(ac97->regmap), "devm_regmap_init_mmio failed\n"); res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "audio-tx"); if (!res) return dev_err_probe(dev, -EINVAL, "Missing 'audio-tx' in reg-names property\n"); ac97->tx_dma_base = dma_map_resource(dev, res->start, resource_size(res), DMA_TO_DEVICE, 0); if (dma_mapping_error(dev, ac97->tx_dma_base)) return -ENXIO; res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "audio-rx"); if (!res) return dev_err_probe(dev, -EINVAL, "Missing 'audio-rx' in reg-names property\n"); ac97->rx_dma_base = dma_map_resource(dev, res->start, resource_size(res), DMA_FROM_DEVICE, 0); if (dma_mapping_error(dev, ac97->rx_dma_base)) return -ENXIO; ret = devm_snd_dmaengine_pcm_register(dev, NULL, 0); if (ret) dev_err_probe(dev, ret, "failed to register PCM\n"); ret = devm_snd_soc_register_component(dev, &ls1x_ac97_component, ls1x_ac97_dai, ARRAY_SIZE(ls1x_ac97_dai)); if (ret) dev_err_probe(dev, ret, "failed to register DAI\n"); return snd_soc_set_ac97_ops(&ls1x_ac97_ops); } static void ls1x_ac97_remove(struct platform_device *pdev) { ls1x_ac97 = NULL; snd_soc_set_ac97_ops(NULL); } #ifdef CONFIG_PM_SLEEP static int ls1x_ac97_suspend(struct device *dev) { int val; regmap_clear_bits(ls1x_ac97->regmap, AC97_OCC0, R_DMA_EN | R_CH_EN | L_DMA_EN | L_CH_EN); regmap_clear_bits(ls1x_ac97->regmap, AC97_ICC, M_DMA_EN | M_CH_EN | R_DMA_EN | R_CH_EN | L_DMA_EN | L_CH_EN); regmap_set_bits(ls1x_ac97->regmap, AC97_CSR, CSR_RESUME); return regmap_read_poll_timeout(ls1x_ac97->regmap, AC97_CSR, val, (val & CSR_RESUME), 0, LS1X_AC97_TIMEOUT); } static int ls1x_ac97_resume(struct device *dev) { int val; regmap_set_bits(ls1x_ac97->regmap, AC97_OCC0, R_DMA_EN | R_CH_EN | L_DMA_EN | L_CH_EN); regmap_set_bits(ls1x_ac97->regmap, AC97_ICC, M_DMA_EN | M_CH_EN | R_DMA_EN | R_CH_EN | L_DMA_EN | L_CH_EN); regmap_set_bits(ls1x_ac97->regmap, AC97_CSR, CSR_RESUME); return regmap_read_poll_timeout(ls1x_ac97->regmap, AC97_CSR, val, !(val & CSR_RESUME), 0, LS1X_AC97_TIMEOUT); } #endif static const struct dev_pm_ops ls1x_ac97_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(ls1x_ac97_suspend, ls1x_ac97_resume) }; static const struct of_device_id ls1x_ac97_match[] = { { .compatible = "loongson,ls1b-ac97" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, ls1x_ac97_match); static struct platform_driver ls1x_ac97_driver = { .probe = ls1x_ac97_probe, .remove = ls1x_ac97_remove, .driver = { .name = KBUILD_MODNAME, .of_match_table = ls1x_ac97_match, .pm = &ls1x_ac97_pm_ops, }, }; module_platform_driver(ls1x_ac97_driver); MODULE_AUTHOR("Keguang Zhang "); MODULE_DESCRIPTION("Loongson-1 AC97 Controller Driver"); MODULE_LICENSE("GPL");