1 // SPDX-License-Identifier: GPL-2.0 2 // 3 // loongson_i2s_pci.c -- Loongson I2S controller driver 4 // 5 // Copyright (C) 2023 Loongson Technology Corporation Limited 6 // Author: Yingkun Meng <mengyingkun@loongson.cn> 7 // 8 9 #include <linux/module.h> 10 #include <linux/delay.h> 11 #include <linux/pm_runtime.h> 12 #include <linux/dma-mapping.h> 13 #include <linux/acpi.h> 14 #include <linux/pci.h> 15 #include <sound/soc.h> 16 #include "loongson_i2s.h" 17 #include "loongson_dma.h" 18 19 static bool loongson_i2s_wr_reg(struct device *dev, unsigned int reg) 20 { 21 switch (reg) { 22 case LS_I2S_CFG: 23 case LS_I2S_CTRL: 24 case LS_I2S_RX_DATA: 25 case LS_I2S_TX_DATA: 26 case LS_I2S_CFG1: 27 return true; 28 default: 29 return false; 30 }; 31 } 32 33 static bool loongson_i2s_rd_reg(struct device *dev, unsigned int reg) 34 { 35 switch (reg) { 36 case LS_I2S_VER: 37 case LS_I2S_CFG: 38 case LS_I2S_CTRL: 39 case LS_I2S_RX_DATA: 40 case LS_I2S_TX_DATA: 41 case LS_I2S_CFG1: 42 return true; 43 default: 44 return false; 45 }; 46 } 47 48 static bool loongson_i2s_volatile_reg(struct device *dev, unsigned int reg) 49 { 50 switch (reg) { 51 case LS_I2S_CFG: 52 case LS_I2S_CTRL: 53 case LS_I2S_RX_DATA: 54 case LS_I2S_TX_DATA: 55 case LS_I2S_CFG1: 56 return true; 57 default: 58 return false; 59 }; 60 } 61 62 static const struct regmap_config loongson_i2s_regmap_config = { 63 .reg_bits = 32, 64 .reg_stride = 4, 65 .val_bits = 32, 66 .max_register = LS_I2S_CFG1, 67 .writeable_reg = loongson_i2s_wr_reg, 68 .readable_reg = loongson_i2s_rd_reg, 69 .volatile_reg = loongson_i2s_volatile_reg, 70 .cache_type = REGCACHE_FLAT, 71 }; 72 73 static int loongson_i2s_pci_probe(struct pci_dev *pdev, 74 const struct pci_device_id *pid) 75 { 76 const struct fwnode_handle *fwnode = pdev->dev.fwnode; 77 struct loongson_dma_data *tx_data, *rx_data; 78 struct loongson_i2s *i2s; 79 int ret; 80 81 if (pcim_enable_device(pdev)) { 82 dev_err(&pdev->dev, "pci_enable_device failed\n"); 83 return -ENODEV; 84 } 85 86 i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); 87 if (!i2s) 88 return -ENOMEM; 89 90 i2s->rev_id = pdev->revision; 91 i2s->dev = &pdev->dev; 92 pci_set_drvdata(pdev, i2s); 93 94 ret = pcim_iomap_regions(pdev, 1 << 0, dev_name(&pdev->dev)); 95 if (ret < 0) { 96 dev_err(&pdev->dev, "iomap_regions failed\n"); 97 return ret; 98 } 99 i2s->reg_base = pcim_iomap_table(pdev)[0]; 100 i2s->regmap = devm_regmap_init_mmio(&pdev->dev, i2s->reg_base, 101 &loongson_i2s_regmap_config); 102 if (IS_ERR(i2s->regmap)) { 103 dev_err(&pdev->dev, "regmap_init_mmio failed\n"); 104 return PTR_ERR(i2s->regmap); 105 } 106 107 tx_data = &i2s->tx_dma_data; 108 rx_data = &i2s->rx_dma_data; 109 110 tx_data->dev_addr = pci_resource_start(pdev, 0) + LS_I2S_TX_DATA; 111 tx_data->order_addr = i2s->reg_base + LS_I2S_TX_ORDER; 112 113 rx_data->dev_addr = pci_resource_start(pdev, 0) + LS_I2S_RX_DATA; 114 rx_data->order_addr = i2s->reg_base + LS_I2S_RX_ORDER; 115 116 tx_data->irq = fwnode_irq_get_byname(fwnode, "tx"); 117 if (tx_data->irq < 0) { 118 dev_err(&pdev->dev, "dma tx irq invalid\n"); 119 return tx_data->irq; 120 } 121 122 rx_data->irq = fwnode_irq_get_byname(fwnode, "rx"); 123 if (rx_data->irq < 0) { 124 dev_err(&pdev->dev, "dma rx irq invalid\n"); 125 return rx_data->irq; 126 } 127 128 device_property_read_u32(&pdev->dev, "clock-frequency", &i2s->clk_rate); 129 if (!i2s->clk_rate) { 130 dev_err(&pdev->dev, "clock-frequency property invalid\n"); 131 return -EINVAL; 132 } 133 134 dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); 135 136 if (i2s->rev_id == 1) { 137 regmap_write(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_RESET); 138 udelay(200); 139 } 140 141 ret = devm_snd_soc_register_component(&pdev->dev, 142 &loongson_i2s_component, 143 &loongson_i2s_dai, 1); 144 if (ret) { 145 dev_err(&pdev->dev, "register DAI failed %d\n", ret); 146 return ret; 147 } 148 149 return 0; 150 } 151 152 static const struct pci_device_id loongson_i2s_ids[] = { 153 { PCI_DEVICE(PCI_VENDOR_ID_LOONGSON, 0x7a27) }, 154 { }, 155 }; 156 MODULE_DEVICE_TABLE(pci, loongson_i2s_ids); 157 158 static struct pci_driver loongson_i2s_driver = { 159 .name = "loongson-i2s-pci", 160 .id_table = loongson_i2s_ids, 161 .probe = loongson_i2s_pci_probe, 162 .driver = { 163 .pm = pm_sleep_ptr(&loongson_i2s_pm), 164 }, 165 }; 166 module_pci_driver(loongson_i2s_driver); 167 168 MODULE_DESCRIPTION("Loongson I2S Master Mode ASoC Driver"); 169 MODULE_AUTHOR("Loongson Technology Corporation Limited"); 170 MODULE_LICENSE("GPL"); 171