| // SPDX-License-Identifier: GPL-2.0 |
| // |
| // loongson_i2s_pci.c -- Loongson I2S controller driver |
| // |
| // Copyright (C) 2023 Loongson Technology Corporation Limited |
| // Author: Yingkun Meng <mengyingkun@loongson.cn> |
| // |
| |
| #include <linux/module.h> |
| #include <linux/delay.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/acpi.h> |
| #include <linux/pci.h> |
| #include <sound/soc.h> |
| #include "loongson_i2s.h" |
| #include "loongson_dma.h" |
| |
| static bool loongson_i2s_wr_reg(struct device *dev, unsigned int reg) |
| { |
| switch (reg) { |
| case LS_I2S_CFG: |
| case LS_I2S_CTRL: |
| case LS_I2S_RX_DATA: |
| case LS_I2S_TX_DATA: |
| case LS_I2S_CFG1: |
| return true; |
| default: |
| return false; |
| }; |
| } |
| |
| static bool loongson_i2s_rd_reg(struct device *dev, unsigned int reg) |
| { |
| switch (reg) { |
| case LS_I2S_VER: |
| case LS_I2S_CFG: |
| case LS_I2S_CTRL: |
| case LS_I2S_RX_DATA: |
| case LS_I2S_TX_DATA: |
| case LS_I2S_CFG1: |
| return true; |
| default: |
| return false; |
| }; |
| } |
| |
| static bool loongson_i2s_volatile_reg(struct device *dev, unsigned int reg) |
| { |
| switch (reg) { |
| case LS_I2S_CFG: |
| case LS_I2S_CTRL: |
| case LS_I2S_RX_DATA: |
| case LS_I2S_TX_DATA: |
| case LS_I2S_CFG1: |
| return true; |
| default: |
| return false; |
| }; |
| } |
| |
| static const struct regmap_config loongson_i2s_regmap_config = { |
| .reg_bits = 32, |
| .reg_stride = 4, |
| .val_bits = 32, |
| .max_register = LS_I2S_CFG1, |
| .writeable_reg = loongson_i2s_wr_reg, |
| .readable_reg = loongson_i2s_rd_reg, |
| .volatile_reg = loongson_i2s_volatile_reg, |
| .cache_type = REGCACHE_FLAT, |
| }; |
| |
| static int loongson_i2s_pci_probe(struct pci_dev *pdev, |
| const struct pci_device_id *pid) |
| { |
| const struct fwnode_handle *fwnode = pdev->dev.fwnode; |
| struct loongson_dma_data *tx_data, *rx_data; |
| struct loongson_i2s *i2s; |
| int ret; |
| |
| if (pcim_enable_device(pdev)) { |
| dev_err(&pdev->dev, "pci_enable_device failed\n"); |
| return -ENODEV; |
| } |
| |
| i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); |
| if (!i2s) |
| return -ENOMEM; |
| |
| i2s->rev_id = pdev->revision; |
| i2s->dev = &pdev->dev; |
| pci_set_drvdata(pdev, i2s); |
| |
| ret = pcim_iomap_regions(pdev, 1 << 0, dev_name(&pdev->dev)); |
| if (ret < 0) { |
| dev_err(&pdev->dev, "iomap_regions failed\n"); |
| return ret; |
| } |
| i2s->reg_base = pcim_iomap_table(pdev)[0]; |
| i2s->regmap = devm_regmap_init_mmio(&pdev->dev, i2s->reg_base, |
| &loongson_i2s_regmap_config); |
| if (IS_ERR(i2s->regmap)) { |
| dev_err(&pdev->dev, "regmap_init_mmio failed\n"); |
| return PTR_ERR(i2s->regmap); |
| } |
| |
| tx_data = &i2s->tx_dma_data; |
| rx_data = &i2s->rx_dma_data; |
| |
| tx_data->dev_addr = pci_resource_start(pdev, 0) + LS_I2S_TX_DATA; |
| tx_data->order_addr = i2s->reg_base + LS_I2S_TX_ORDER; |
| |
| rx_data->dev_addr = pci_resource_start(pdev, 0) + LS_I2S_RX_DATA; |
| rx_data->order_addr = i2s->reg_base + LS_I2S_RX_ORDER; |
| |
| tx_data->irq = fwnode_irq_get_byname(fwnode, "tx"); |
| if (tx_data->irq < 0) { |
| dev_err(&pdev->dev, "dma tx irq invalid\n"); |
| return tx_data->irq; |
| } |
| |
| rx_data->irq = fwnode_irq_get_byname(fwnode, "rx"); |
| if (rx_data->irq < 0) { |
| dev_err(&pdev->dev, "dma rx irq invalid\n"); |
| return rx_data->irq; |
| } |
| |
| device_property_read_u32(&pdev->dev, "clock-frequency", &i2s->clk_rate); |
| if (!i2s->clk_rate) { |
| dev_err(&pdev->dev, "clock-frequency property invalid\n"); |
| return -EINVAL; |
| } |
| |
| dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); |
| |
| if (i2s->rev_id == 1) { |
| regmap_write(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_RESET); |
| udelay(200); |
| } |
| |
| ret = devm_snd_soc_register_component(&pdev->dev, |
| &loongson_i2s_component, |
| &loongson_i2s_dai, 1); |
| if (ret) { |
| dev_err(&pdev->dev, "register DAI failed %d\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static const struct pci_device_id loongson_i2s_ids[] = { |
| { PCI_DEVICE(PCI_VENDOR_ID_LOONGSON, 0x7a27) }, |
| { }, |
| }; |
| MODULE_DEVICE_TABLE(pci, loongson_i2s_ids); |
| |
| static struct pci_driver loongson_i2s_driver = { |
| .name = "loongson-i2s-pci", |
| .id_table = loongson_i2s_ids, |
| .probe = loongson_i2s_pci_probe, |
| .driver = { |
| .owner = THIS_MODULE, |
| .pm = pm_sleep_ptr(&loongson_i2s_pm), |
| }, |
| }; |
| module_pci_driver(loongson_i2s_driver); |
| |
| MODULE_DESCRIPTION("Loongson I2S Master Mode ASoC Driver"); |
| MODULE_AUTHOR("Loongson Technology Corporation Limited"); |
| MODULE_LICENSE("GPL"); |